ausmalbilder-malbuch.de
Open in
urlscan Pro
137.74.202.223
Public Scan
Submitted URL: http://ausmalbilder-malbuch.de/ausmalbilder-big-mouth
Effective URL: https://ausmalbilder-malbuch.de/ausmalbilder-big-mouth
Submission: On August 16 via api from US — Scanned from DE
Effective URL: https://ausmalbilder-malbuch.de/ausmalbilder-big-mouth
Submission: On August 16 via api from US — Scanned from DE
Form analysis
0 forms found in the DOMText Content
/** * Error Protection API: WP_Recovery_Mode_Email_Link class * * @package WordPress * @since 5.2.0 */ /** * Core class used to send an email with a link to begin Recovery Mode. * * @since 5.2.0 */ #[AllowDynamicProperties] final class WP_Recovery_Mode_Email_Service { const RATE_LIMIT_OPTION = 'recovery_mode_email_last_sent'; /** * Service to generate recovery mode URLs. * * @since 5.2.0 * @var WP_Recovery_Mode_Link_Service */ private $link_service; /** * WP_Recovery_Mode_Email_Service constructor. * * @since 5.2.0 * * @param WP_Recovery_Mode_Link_Service $link_service */ public function __construct( WP_Recovery_Mode_Link_Service $link_service ) { $this->link_service = $link_service; } /** * Sends the recovery mode email if the rate limit has not been sent. * * @since 5.2.0 * * @param int $rate_limit Number of seconds before another email can be sent. * @param array $error Error details from `error_get_last()`. * @param array $extension { * The extension that caused the error. * * @type string $slug The extension slug. The plugin or theme's directory. * @type string $type The extension type. Either 'plugin' or 'theme'. * } * @return true|WP_Error True if email sent, WP_Error otherwise. */ public function maybe_send_recovery_mode_email( $rate_limit, $error, $extension ) { $last_sent = get_option( self::RATE_LIMIT_OPTION ); if ( ! $last_sent || time() > $last_sent + $rate_limit ) { if ( ! update_option( self::RATE_LIMIT_OPTION, time() ) ) { return new WP_Error( 'storage_error', __( 'Could not update the email last sent time.' ) ); } $sent = $this->send_recovery_mode_email( $rate_limit, $error, $extension ); if ( $sent ) { return true; } return new WP_Error( 'email_failed', sprintf( /* translators: %s: mail() */ __( 'The email could not be sent. Possible reason: your host may have disabled the %s function.' ), 'mail()' ) ); } $err_message = sprintf( /* translators: 1: Last sent as a human time diff, 2: Wait time as a human time diff. */ __( 'A recovery link was already sent %1$s ago. Please wait another %2$s before requesting a new email.' ), human_time_diff( $last_sent ), human_time_diff( $last_sent + $rate_limit ) ); return new WP_Error( 'email_sent_already', $err_message ); } /** * Clears the rate limit, allowing a new recovery mode email to be sent immediately. * * @since 5.2.0 * * @return bool True on success, false on failure. */ public function clear_rate_limit() { return delete_option( self::RATE_LIMIT_OPTION ); } /** * Sends the Recovery Mode email to the site admin email address. * * @since 5.2.0 * * @param int $rate_limit Number of seconds before another email can be sent. * @param array $error Error details from `error_get_last()`. * @param array $extension { * The extension that caused the error. * * @type string $slug The extension slug. The directory of the plugin or theme. * @type string $type The extension type. Either 'plugin' or 'theme'. * } * @return bool Whether the email was sent successfully. */ private function send_recovery_mode_email( $rate_limit, $error, $extension ) { $url = $this->link_service->generate_url(); $blogname = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ); $switched_locale = switch_to_locale( get_locale() ); if ( $extension ) { $cause = $this->get_cause( $extension ); $details = wp_strip_all_tags( wp_get_extension_error_description( $error ) ); if ( $details ) { $header = __( 'Error Details' ); $details = "\n\n" . $header . "\n" . str_pad( '', strlen( $header ), '=' ) . "\n" . $details; } } else { $cause = ''; $details = ''; } /** * Filters the support message sent with the the fatal error protection email. * * @since 5.2.0 * * @param string $message The Message to include in the email. */ $support = apply_filters( 'recovery_email_support_info', __( 'Please contact your host for assistance with investigating this issue further.' ) ); /** * Filters the debug information included in the fatal error protection email. * * @since 5.3.0 * * @param array $message An associative array of debug information. */ $debug = apply_filters( 'recovery_email_debug_info', $this->get_debug( $extension ) ); /* translators: Do not translate LINK, EXPIRES, CAUSE, DETAILS, SITEURL, PAGEURL, SUPPORT. DEBUG: those are placeholders. */ $message = __( 'Howdy! WordPress has a built-in feature that detects when a plugin or theme causes a fatal error on your site, and notifies you with this automated email. ###CAUSE### First, visit your website (###SITEURL###) and check for any visible issues. Next, visit the page where the error was caught (###PAGEURL###) and check for any visible issues. ###SUPPORT### If your site appears broken and you can\'t access your dashboard normally, WordPress now has a special "recovery mode". This lets you safely login to your dashboard and investigate further. ###LINK### To keep your site safe, this link will expire in ###EXPIRES###. Don\'t worry about that, though: a new link will be emailed to you if the error occurs again after it expires. When seeking help with this issue, you may be asked for some of the following information: ###DEBUG### ###DETAILS###' ); $message = str_replace( array( '###LINK###', '###EXPIRES###', '###CAUSE###', '###DETAILS###', '###SITEURL###', '###PAGEURL###', '###SUPPORT###', '###DEBUG###', ), array( $url, human_time_diff( time() + $rate_limit ), $cause ? "\n{$cause}\n" : "\n", $details, home_url( '/' ), home_url( $_SERVER['REQUEST_URI'] ), $support, implode( "\r\n", $debug ), ), $message ); $email = array( 'to' => $this->get_recovery_mode_email_address(), /* translators: %s: Site title. */ 'subject' => __( '[%s] Your Site is Experiencing a Technical Issue' ), 'message' => $message, 'headers' => '', 'attachments' => '', ); /** * Filters the contents of the Recovery Mode email. * * @since 5.2.0 * @since 5.6.0 The `$email` argument includes the `attachments` key. * * @param array $email { * Used to build a call to wp_mail(). * * @type string|array $to Array or comma-separated list of email addresses to send message. * @type string $subject Email subject * @type string $message Message contents * @type string|array $headers Optional. Additional headers. * @type string|array $attachments Optional. Files to attach. * } * @param string $url URL to enter recovery mode. */ $email = apply_filters( 'recovery_mode_email', $email, $url ); $sent = wp_mail( $email['to'], wp_specialchars_decode( sprintf( $email['subject'], $blogname ) ), $email['message'], $email['headers'], $email['attachments'] ); if ( $switched_locale ) { restore_previous_locale(); } return $sent; } /** * Gets the email address to send the recovery mode link to. * * @since 5.2.0 * * @return string Email address to send recovery mode link to. */ private function get_recovery_mode_email_address() { if ( defined( 'RECOVERY_MODE_EMAIL' ) && is_email( RECOVERY_MODE_EMAIL ) ) { return RECOVERY_MODE_EMAIL; } return get_option( 'admin_email' ); } /** * Gets the description indicating the possible cause for the error. * * @since 5.2.0 * * @param array $extension { * The extension that caused the error. * * @type string $slug The extension slug. The directory of the plugin or theme. * @type string $type The extension type. Either 'plugin' or 'theme'. * } * @return string Message about which extension caused the error. */ private function get_cause( $extension ) { if ( 'plugin' === $extension['type'] ) { $plugin = $this->get_plugin( $extension ); if ( false === $plugin ) { $name = $extension['slug']; } else { $name = $plugin['Name']; } /* translators: %s: Plugin name. */ $cause = sprintf( __( 'In this case, WordPress caught an error with one of your plugins, %s.' ), $name ); } else { $theme = wp_get_theme( $extension['slug'] ); $name = $theme->exists() ? $theme->display( 'Name' ) : $extension['slug']; /* translators: %s: Theme name. */ $cause = sprintf( __( 'In this case, WordPress caught an error with your theme, %s.' ), $name ); } return $cause; } /** * Return the details for a single plugin based on the extension data from an error. * * @since 5.3.0 * * @param array $extension { * The extension that caused the error. * * @type string $slug The extension slug. The directory of the plugin or theme. * @type string $type The extension type. Either 'plugin' or 'theme'. * } * @return array|false A plugin array {@see get_plugins()} or `false` if no plugin was found. */ private function get_plugin( $extension ) { if ( ! function_exists( 'get_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $plugins = get_plugins(); // Assume plugin main file name first since it is a common convention. if ( isset( $plugins[ "{$extension['slug']}/{$extension['slug']}.php" ] ) ) { return $plugins[ "{$extension['slug']}/{$extension['slug']}.php" ]; } else { foreach ( $plugins as $file => $plugin_data ) { if ( str_starts_with( $file, "{$extension['slug']}/" ) || $file === $extension['slug'] ) { return $plugin_data; } } } return false; } /** * Return debug information in an easy to manipulate format. * * @since 5.3.0 * * @param array $extension { * The extension that caused the error. * * @type string $slug The extension slug. The directory of the plugin or theme. * @type string $type The extension type. Either 'plugin' or 'theme'. * } * @return array An associative array of debug information. */ private function get_debug( $extension ) { $theme = wp_get_theme(); $wp_version = get_bloginfo( 'version' ); if ( $extension ) { $plugin = $this->get_plugin( $extension ); } else { $plugin = null; } $debug = array( 'wp' => sprintf( /* translators: %s: Current WordPress version number. */ __( 'WordPress version %s' ), $wp_version ), 'theme' => sprintf( /* translators: 1: Current active theme name. 2: Current active theme version. */ __( 'Active theme: %1$s (version %2$s)' ), $theme->get( 'Name' ), $theme->get( 'Version' ) ), ); if ( null !== $plugin ) { $debug['plugin'] = sprintf( /* translators: 1: The failing plugins name. 2: The failing plugins version. */ __( 'Current plugin: %1$s (version %2$s)' ), $plugin['Name'], $plugin['Version'] ); } $debug['php'] = sprintf( /* translators: %s: The currently used PHP version. */ __( 'PHP version %s' ), PHP_VERSION ); return $debug; } } /** * Error Protection API: WP_Recovery_Mode class * * @package WordPress * @since 5.2.0 */ /** * Core class used to implement Recovery Mode. * * @since 5.2.0 */ #[AllowDynamicProperties] class WP_Recovery_Mode { const EXIT_ACTION = 'exit_recovery_mode'; /** * Service to handle cookies. * * @since 5.2.0 * @var WP_Recovery_Mode_Cookie_Service */ private $cookie_service; /** * Service to generate a recovery mode key. * * @since 5.2.0 * @var WP_Recovery_Mode_Key_Service */ private $key_service; /** * Service to generate and validate recovery mode links. * * @since 5.2.0 * @var WP_Recovery_Mode_Link_Service */ private $link_service; /** * Service to handle sending an email with a recovery mode link. * * @since 5.2.0 * @var WP_Recovery_Mode_Email_Service */ private $email_service; /** * Is recovery mode initialized. * * @since 5.2.0 * @var bool */ private $is_initialized = false; /** * Is recovery mode active in this session. * * @since 5.2.0 * @var bool */ private $is_active = false; /** * Get an ID representing the current recovery mode session. * * @since 5.2.0 * @var string */ private $session_id = ''; /** * WP_Recovery_Mode constructor. * * @since 5.2.0 */ public function __construct() { $this->cookie_service = new WP_Recovery_Mode_Cookie_Service(); $this->key_service = new WP_Recovery_Mode_Key_Service(); $this->link_service = new WP_Recovery_Mode_Link_Service( $this->cookie_service, $this->key_service ); $this->email_service = new WP_Recovery_Mode_Email_Service( $this->link_service ); } /** * Initialize recovery mode for the current request. * * @since 5.2.0 */ public function initialize() { $this->is_initialized = true; add_action( 'wp_logout', array( $this, 'exit_recovery_mode' ) ); add_action( 'login_form_' . self::EXIT_ACTION, array( $this, 'handle_exit_recovery_mode' ) ); add_action( 'recovery_mode_clean_expired_keys', array( $this, 'clean_expired_keys' ) ); if ( ! wp_next_scheduled( 'recovery_mode_clean_expired_keys' ) && ! wp_installing() ) { wp_schedule_event( time(), 'daily', 'recovery_mode_clean_expired_keys' ); } if ( defined( 'WP_RECOVERY_MODE_SESSION_ID' ) ) { $this->is_active = true; $this->session_id = WP_RECOVERY_MODE_SESSION_ID; return; } if ( $this->cookie_service->is_cookie_set() ) { $this->handle_cookie(); return; } $this->link_service->handle_begin_link( $this->get_link_ttl() ); } /** * Checks whether recovery mode is active. * * This will not change after recovery mode has been initialized. {@see WP_Recovery_Mode::run()}. * * @since 5.2.0 * * @return bool True if recovery mode is active, false otherwise. */ public function is_active() { return $this->is_active; } /** * Gets the recovery mode session ID. * * @since 5.2.0 * * @return string The session ID if recovery mode is active, empty string otherwise. */ public function get_session_id() { return $this->session_id; } /** * Checks whether recovery mode has been initialized. * * Recovery mode should not be used until this point. Initialization happens immediately before loading plugins. * * @since 5.2.0 * * @return bool */ public function is_initialized() { return $this->is_initialized; } /** * Handles a fatal error occurring. * * The calling API should immediately die() after calling this function. * * @since 5.2.0 * * @param array $error Error details from `error_get_last()`. * @return true|WP_Error True if the error was handled and headers have already been sent. * Or the request will exit to try and catch multiple errors at once. * WP_Error if an error occurred preventing it from being handled. */ public function handle_error( array $error ) { $extension = $this->get_extension_for_error( $error ); if ( ! $extension || $this->is_network_plugin( $extension ) ) { return new WP_Error( 'invalid_source', __( 'Error not caused by a plugin or theme.' ) ); } if ( ! $this->is_active() ) { if ( ! is_protected_endpoint() ) { return new WP_Error( 'non_protected_endpoint', __( 'Error occurred on a non-protected endpoint.' ) ); } if ( ! function_exists( 'wp_generate_password' ) ) { require_once ABSPATH . WPINC . '/pluggable.php'; } return $this->email_service->maybe_send_recovery_mode_email( $this->get_email_rate_limit(), $error, $extension ); } if ( ! $this->store_error( $error ) ) { return new WP_Error( 'storage_error', __( 'Failed to store the error.' ) ); } if ( headers_sent() ) { return true; } $this->redirect_protected(); } /** * Ends the current recovery mode session. * * @since 5.2.0 * * @return bool True on success, false on failure. */ public function exit_recovery_mode() { if ( ! $this->is_active() ) { return false; } $this->email_service->clear_rate_limit(); $this->cookie_service->clear_cookie(); wp_paused_plugins()->delete_all(); wp_paused_themes()->delete_all(); return true; } /** * Handles a request to exit Recovery Mode. * * @since 5.2.0 */ public function handle_exit_recovery_mode() { $redirect_to = wp_get_referer(); // Safety check in case referrer returns false. if ( ! $redirect_to ) { $redirect_to = is_user_logged_in() ? admin_url() : home_url(); } if ( ! $this->is_active() ) { wp_safe_redirect( $redirect_to ); die; } if ( ! isset( $_GET['action'] ) || self::EXIT_ACTION !== $_GET['action'] ) { return; } if ( ! isset( $_GET['_wpnonce'] ) || ! wp_verify_nonce( $_GET['_wpnonce'], self::EXIT_ACTION ) ) { wp_die( __( 'Exit recovery mode link expired.' ), 403 ); } if ( ! $this->exit_recovery_mode() ) { wp_die( __( 'Failed to exit recovery mode. Please try again later.' ) ); } wp_safe_redirect( $redirect_to ); die; } /** * Cleans any recovery mode keys that have expired according to the link TTL. * * Executes on a daily cron schedule. * * @since 5.2.0 */ public function clean_expired_keys() { $this->key_service->clean_expired_keys( $this->get_link_ttl() ); } /** * Handles checking for the recovery mode cookie and validating it. * * @since 5.2.0 */ protected function handle_cookie() { $validated = $this->cookie_service->validate_cookie(); if ( is_wp_error( $validated ) ) { $this->cookie_service->clear_cookie(); $validated->add_data( array( 'status' => 403 ) ); wp_die( $validated ); } $session_id = $this->cookie_service->get_session_id_from_cookie(); if ( is_wp_error( $session_id ) ) { $this->cookie_service->clear_cookie(); $session_id->add_data( array( 'status' => 403 ) ); wp_die( $session_id ); } $this->is_active = true; $this->session_id = $session_id; } /** * Gets the rate limit between sending new recovery mode email links. * * @since 5.2.0 * * @return int Rate limit in seconds. */ protected function get_email_rate_limit() { /** * Filters the rate limit between sending new recovery mode email links. * * @since 5.2.0 * * @param int $rate_limit Time to wait in seconds. Defaults to 1 day. */ return apply_filters( 'recovery_mode_email_rate_limit', DAY_IN_SECONDS ); } /** * Gets the number of seconds the recovery mode link is valid for. * * @since 5.2.0 * * @return int Interval in seconds. */ protected function get_link_ttl() { $rate_limit = $this->get_email_rate_limit(); $valid_for = $rate_limit; /** * Filters the amount of time the recovery mode email link is valid for. * * The ttl must be at least as long as the email rate limit. * * @since 5.2.0 * * @param int $valid_for The number of seconds the link is valid for. */ $valid_for = apply_filters( 'recovery_mode_email_link_ttl', $valid_for ); return max( $valid_for, $rate_limit ); } /** * Gets the extension that the error occurred in. * * @since 5.2.0 * * @global array $wp_theme_directories * * @param array $error Error details from `error_get_last()`. * @return array|false { * Extension details. * * @type string $slug The extension slug. This is the plugin or theme's directory. * @type string $type The extension type. Either 'plugin' or 'theme'. * } */ protected function get_extension_for_error( $error ) { global $wp_theme_directories; if ( ! isset( $error['file'] ) ) { return false; } if ( ! defined( 'WP_PLUGIN_DIR' ) ) { return false; } $error_file = wp_normalize_path( $error['file'] ); $wp_plugin_dir = wp_normalize_path( WP_PLUGIN_DIR ); if ( str_starts_with( $error_file, $wp_plugin_dir ) ) { $path = str_replace( $wp_plugin_dir . '/', '', $error_file ); $parts = explode( '/', $path ); return array( 'type' => 'plugin', 'slug' => $parts[0], ); } if ( empty( $wp_theme_directories ) ) { return false; } foreach ( $wp_theme_directories as $theme_directory ) { $theme_directory = wp_normalize_path( $theme_directory ); if ( str_starts_with( $error_file, $theme_directory ) ) { $path = str_replace( $theme_directory . '/', '', $error_file ); $parts = explode( '/', $path ); return array( 'type' => 'theme', 'slug' => $parts[0], ); } } return false; } /** * Checks whether the given extension a network activated plugin. * * @since 5.2.0 * * @param array $extension Extension data. * @return bool True if network plugin, false otherwise. */ protected function is_network_plugin( $extension ) { if ( 'plugin' !== $extension['type'] ) { return false; } if ( ! is_multisite() ) { return false; } $network_plugins = wp_get_active_network_plugins(); foreach ( $network_plugins as $plugin ) { if ( str_starts_with( $plugin, $extension['slug'] . '/' ) ) { return true; } } return false; } /** * Stores the given error so that the extension causing it is paused. * * @since 5.2.0 * * @param array $error Error details from `error_get_last()`. * @return bool True if the error was stored successfully, false otherwise. */ protected function store_error( $error ) { $extension = $this->get_extension_for_error( $error ); if ( ! $extension ) { return false; } switch ( $extension['type'] ) { case 'plugin': return wp_paused_plugins()->set( $extension['slug'], $error ); case 'theme': return wp_paused_themes()->set( $extension['slug'], $error ); default: return false; } } /** * Redirects the current request to allow recovering multiple errors in one go. * * The redirection will only happen when on a protected endpoint. * * It must be ensured that this method is only called when an error actually occurred and will not occur on the * next request again. Otherwise it will create a redirect loop. * * @since 5.2.0 */ protected function redirect_protected() { // Pluggable is usually loaded after plugins, so we manually include it here for redirection functionality. if ( ! function_exists( 'wp_safe_redirect' ) ) { require_once ABSPATH . WPINC . '/pluggable.php'; } $scheme = is_ssl() ? 'https://' : 'http://'; $url = "{$scheme}{$_SERVER['HTTP_HOST']}{$_SERVER['REQUEST_URI']}"; wp_safe_redirect( $url ); exit; } } /** * Core Metadata API * * Functions for retrieving and manipulating metadata of various WordPress object types. Metadata * for an object is a represented by a simple key-value pair. Objects may contain multiple * metadata entries that share the same key and differ only in their value. * * @package WordPress * @subpackage Meta */ require ABSPATH . WPINC . '/class-wp-metadata-lazyloader.php'; /** * Adds metadata for the specified object. * * @since 2.9.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param string $meta_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. * @param int $object_id ID of the object metadata is for. * @param string $meta_key Metadata key. * @param mixed $meta_value Metadata value. Must be serializable if non-scalar. * @param bool $unique Optional. Whether the specified metadata key should be unique for the object. * If true, and the object already has a value for the specified metadata key, * no change will be made. Default false. * @return int|false The meta ID on success, false on failure. */ function add_metadata( $meta_type, $object_id, $meta_key, $meta_value, $unique = false ) { global $wpdb; if ( ! $meta_type || ! $meta_key || ! is_numeric( $object_id ) ) { return false; } $object_id = absint( $object_id ); if ( ! $object_id ) { return false; } $table = _get_meta_table( $meta_type ); if ( ! $table ) { return false; } $meta_subtype = get_object_subtype( $meta_type, $object_id ); $column = sanitize_key( $meta_type . '_id' ); // expected_slashed ($meta_key) $meta_key = wp_unslash( $meta_key ); $meta_value = wp_unslash( $meta_value ); $meta_value = sanitize_meta( $meta_key, $meta_value, $meta_type, $meta_subtype ); /** * Short-circuits adding metadata of a specific type. * * The dynamic portion of the hook name, `$meta_type`, refers to the meta object type * (post, comment, term, user, or any other type with an associated meta table). * Returning a non-null value will effectively short-circuit the function. * * Possible hook names include: * * - `add_post_metadata` * - `add_comment_metadata` * - `add_term_metadata` * - `add_user_metadata` * * @since 3.1.0 * * @param null|bool $check Whether to allow adding metadata for the given type. * @param int $object_id ID of the object metadata is for. * @param string $meta_key Metadata key. * @param mixed $meta_value Metadata value. Must be serializable if non-scalar. * @param bool $unique Whether the specified meta key should be unique for the object. */ $check = apply_filters( "add_{$meta_type}_metadata", null, $object_id, $meta_key, $meta_value, $unique ); if ( null !== $check ) { return $check; } if ( $unique && $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $table WHERE meta_key = %s AND $column = %d", $meta_key, $object_id ) ) ) { return false; } $_meta_value = $meta_value; $meta_value = maybe_serialize( $meta_value ); /** * Fires immediately before meta of a specific type is added. * * The dynamic portion of the hook name, `$meta_type`, refers to the meta object type * (post, comment, term, user, or any other type with an associated meta table). * * Possible hook names include: * * - `add_post_meta` * - `add_comment_meta` * - `add_term_meta` * - `add_user_meta` * * @since 3.1.0 * * @param int $object_id ID of the object metadata is for. * @param string $meta_key Metadata key. * @param mixed $_meta_value Metadata value. */ do_action( "add_{$meta_type}_meta", $object_id, $meta_key, $_meta_value ); $result = $wpdb->insert( $table, array( $column => $object_id, 'meta_key' => $meta_key, 'meta_value' => $meta_value, ) ); if ( ! $result ) { return false; } $mid = (int) $wpdb->insert_id; wp_cache_delete( $object_id, $meta_type . '_meta' ); /** * Fires immediately after meta of a specific type is added. * * The dynamic portion of the hook name, `$meta_type`, refers to the meta object type * (post, comment, term, user, or any other type with an associated meta table). * * Possible hook names include: * * - `added_post_meta` * - `added_comment_meta` * - `added_term_meta` * - `added_user_meta` * * @since 2.9.0 * * @param int $mid The meta ID after successful update. * @param int $object_id ID of the object metadata is for. * @param string $meta_key Metadata key. * @param mixed $_meta_value Metadata value. */ do_action( "added_{$meta_type}_meta", $mid, $object_id, $meta_key, $_meta_value ); return $mid; } /** * Updates metadata for the specified object. If no value already exists for the specified object * ID and metadata key, the metadata will be added. * * @since 2.9.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param string $meta_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. * @param int $object_id ID of the object metadata is for. * @param string $meta_key Metadata key. * @param mixed $meta_value Metadata value. Must be serializable if non-scalar. * @param mixed $prev_value Optional. Previous value to check before updating. * If specified, only update existing metadata entries with * this value. Otherwise, update all entries. Default empty string. * @return int|bool The new meta field ID if a field with the given key didn't exist * and was therefore added, true on successful update, * false on failure or if the value passed to the function * is the same as the one that is already in the database. */ function update_metadata( $meta_type, $object_id, $meta_key, $meta_value, $prev_value = '' ) { global $wpdb; if ( ! $meta_type || ! $meta_key || ! is_numeric( $object_id ) ) { return false; } $object_id = absint( $object_id ); if ( ! $object_id ) { return false; } $table = _get_meta_table( $meta_type ); if ( ! $table ) { return false; } $meta_subtype = get_object_subtype( $meta_type, $object_id ); $column = sanitize_key( $meta_type . '_id' ); $id_column = ( 'user' === $meta_type ) ? 'umeta_id' : 'meta_id'; // expected_slashed ($meta_key) $raw_meta_key = $meta_key; $meta_key = wp_unslash( $meta_key ); $passed_value = $meta_value; $meta_value = wp_unslash( $meta_value ); $meta_value = sanitize_meta( $meta_key, $meta_value, $meta_type, $meta_subtype ); /** * Short-circuits updating metadata of a specific type. * * The dynamic portion of the hook name, `$meta_type`, refers to the meta object type * (post, comment, term, user, or any other type with an associated meta table). * Returning a non-null value will effectively short-circuit the function. * * Possible hook names include: * * - `update_post_metadata` * - `update_comment_metadata` * - `update_term_metadata` * - `update_user_metadata` * * @since 3.1.0 * * @param null|bool $check Whether to allow updating metadata for the given type. * @param int $object_id ID of the object metadata is for. * @param string $meta_key Metadata key. * @param mixed $meta_value Metadata value. Must be serializable if non-scalar. * @param mixed $prev_value Optional. Previous value to check before updating. * If specified, only update existing metadata entries with * this value. Otherwise, update all entries. */ $check = apply_filters( "update_{$meta_type}_metadata", null, $object_id, $meta_key, $meta_value, $prev_value ); if ( null !== $check ) { return (bool) $check; } // Compare existing value to new value if no prev value given and the key exists only once. if ( empty( $prev_value ) ) { $old_value = get_metadata_raw( $meta_type, $object_id, $meta_key ); if ( is_countable( $old_value ) && count( $old_value ) === 1 ) { if ( $old_value[0] === $meta_value ) { return false; } } } $meta_ids = $wpdb->get_col( $wpdb->prepare( "SELECT $id_column FROM $table WHERE meta_key = %s AND $column = %d", $meta_key, $object_id ) ); if ( empty( $meta_ids ) ) { return add_metadata( $meta_type, $object_id, $raw_meta_key, $passed_value ); } $_meta_value = $meta_value; $meta_value = maybe_serialize( $meta_value ); $data = compact( 'meta_value' ); $where = array( $column => $object_id, 'meta_key' => $meta_key, ); if ( ! empty( $prev_value ) ) { $prev_value = maybe_serialize( $prev_value ); $where['meta_value'] = $prev_value; } foreach ( $meta_ids as $meta_id ) { /** * Fires immediately before updating metadata of a specific type. * * The dynamic portion of the hook name, `$meta_type`, refers to the meta object type * (post, comment, term, user, or any other type with an associated meta table). * * Possible hook names include: * * - `update_post_meta` * - `update_comment_meta` * - `update_term_meta` * - `update_user_meta` * * @since 2.9.0 * * @param int $meta_id ID of the metadata entry to update. * @param int $object_id ID of the object metadata is for. * @param string $meta_key Metadata key. * @param mixed $_meta_value Metadata value. */ do_action( "update_{$meta_type}_meta", $meta_id, $object_id, $meta_key, $_meta_value ); if ( 'post' === $meta_type ) { /** * Fires immediately before updating a post's metadata. * * @since 2.9.0 * * @param int $meta_id ID of metadata entry to update. * @param int $object_id Post ID. * @param string $meta_key Metadata key. * @param mixed $meta_value Metadata value. This will be a PHP-serialized string representation of the value * if the value is an array, an object, or itself a PHP-serialized string. */ do_action( 'update_postmeta', $meta_id, $object_id, $meta_key, $meta_value ); } } $result = $wpdb->update( $table, $data, $where ); if ( ! $result ) { return false; } wp_cache_delete( $object_id, $meta_type . '_meta' ); foreach ( $meta_ids as $meta_id ) { /** * Fires immediately after updating metadata of a specific type. * * The dynamic portion of the hook name, `$meta_type`, refers to the meta object type * (post, comment, term, user, or any other type with an associated meta table). * * Possible hook names include: * * - `updated_post_meta` * - `updated_comment_meta` * - `updated_term_meta` * - `updated_user_meta` * * @since 2.9.0 * * @param int $meta_id ID of updated metadata entry. * @param int $object_id ID of the object metadata is for. * @param string $meta_key Metadata key. * @param mixed $_meta_value Metadata value. */ do_action( "updated_{$meta_type}_meta", $meta_id, $object_id, $meta_key, $_meta_value ); if ( 'post' === $meta_type ) { /** * Fires immediately after updating a post's metadata. * * @since 2.9.0 * * @param int $meta_id ID of updated metadata entry. * @param int $object_id Post ID. * @param string $meta_key Metadata key. * @param mixed $meta_value Metadata value. This will be a PHP-serialized string representation of the value * if the value is an array, an object, or itself a PHP-serialized string. */ do_action( 'updated_postmeta', $meta_id, $object_id, $meta_key, $meta_value ); } } return true; } /** * Deletes metadata for the specified object. * * @since 2.9.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param string $meta_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. * @param int $object_id ID of the object metadata is for. * @param string $meta_key Metadata key. * @param mixed $meta_value Optional. Metadata value. Must be serializable if non-scalar. * If specified, only delete metadata entries with this value. * Otherwise, delete all entries with the specified meta_key. * Pass `null`, `false`, or an empty string to skip this check. * (For backward compatibility, it is not possible to pass an empty string * to delete those entries with an empty string for a value.) * Default empty string. * @param bool $delete_all Optional. If true, delete matching metadata entries for all objects, * ignoring the specified object_id. Otherwise, only delete * matching metadata entries for the specified object_id. Default false. * @return bool True on successful delete, false on failure. */ function delete_metadata( $meta_type, $object_id, $meta_key, $meta_value = '', $delete_all = false ) { global $wpdb; if ( ! $meta_type || ! $meta_key || ! is_numeric( $object_id ) && ! $delete_all ) { return false; } $object_id = absint( $object_id ); if ( ! $object_id && ! $delete_all ) { return false; } $table = _get_meta_table( $meta_type ); if ( ! $table ) { return false; } $type_column = sanitize_key( $meta_type . '_id' ); $id_column = ( 'user' === $meta_type ) ? 'umeta_id' : 'meta_id'; // expected_slashed ($meta_key) $meta_key = wp_unslash( $meta_key ); $meta_value = wp_unslash( $meta_value ); /** * Short-circuits deleting metadata of a specific type. * * The dynamic portion of the hook name, `$meta_type`, refers to the meta object type * (post, comment, term, user, or any other type with an associated meta table). * Returning a non-null value will effectively short-circuit the function. * * Possible hook names include: * * - `delete_post_metadata` * - `delete_comment_metadata` * - `delete_term_metadata` * - `delete_user_metadata` * * @since 3.1.0 * * @param null|bool $delete Whether to allow metadata deletion of the given type. * @param int $object_id ID of the object metadata is for. * @param string $meta_key Metadata key. * @param mixed $meta_value Metadata value. Must be serializable if non-scalar. * @param bool $delete_all Whether to delete the matching metadata entries * for all objects, ignoring the specified $object_id. * Default false. */ $check = apply_filters( "delete_{$meta_type}_metadata", null, $object_id, $meta_key, $meta_value, $delete_all ); if ( null !== $check ) { return (bool) $check; } $_meta_value = $meta_value; $meta_value = maybe_serialize( $meta_value ); $query = $wpdb->prepare( "SELECT $id_column FROM $table WHERE meta_key = %s", $meta_key ); if ( ! $delete_all ) { $query .= $wpdb->prepare( " AND $type_column = %d", $object_id ); } if ( '' !== $meta_value && null !== $meta_value && false !== $meta_value ) { $query .= $wpdb->prepare( ' AND meta_value = %s', $meta_value ); } $meta_ids = $wpdb->get_col( $query ); if ( ! count( $meta_ids ) ) { return false; } if ( $delete_all ) { if ( '' !== $meta_value && null !== $meta_value && false !== $meta_value ) { $object_ids = $wpdb->get_col( $wpdb->prepare( "SELECT $type_column FROM $table WHERE meta_key = %s AND meta_value = %s", $meta_key, $meta_value ) ); } else { $object_ids = $wpdb->get_col( $wpdb->prepare( "SELECT $type_column FROM $table WHERE meta_key = %s", $meta_key ) ); } } /** * Fires immediately before deleting metadata of a specific type. * * The dynamic portion of the hook name, `$meta_type`, refers to the meta object type * (post, comment, term, user, or any other type with an associated meta table). * * Possible hook names include: * * - `delete_post_meta` * - `delete_comment_meta` * - `delete_term_meta` * - `delete_user_meta` * * @since 3.1.0 * * @param string[] $meta_ids An array of metadata entry IDs to delete. * @param int $object_id ID of the object metadata is for. * @param string $meta_key Metadata key. * @param mixed $_meta_value Metadata value. */ do_action( "delete_{$meta_type}_meta", $meta_ids, $object_id, $meta_key, $_meta_value ); // Old-style action. if ( 'post' === $meta_type ) { /** * Fires immediately before deleting metadata for a post. * * @since 2.9.0 * * @param string[] $meta_ids An array of metadata entry IDs to delete. */ do_action( 'delete_postmeta', $meta_ids ); } $query = "DELETE FROM $table WHERE $id_column IN( " . implode( ',', $meta_ids ) . ' )'; $count = $wpdb->query( $query ); if ( ! $count ) { return false; } if ( $delete_all ) { $data = (array) $object_ids; } else { $data = array( $object_id ); } wp_cache_delete_multiple( $data, $meta_type . '_meta' ); /** * Fires immediately after deleting metadata of a specific type. * * The dynamic portion of the hook name, `$meta_type`, refers to the meta object type * (post, comment, term, user, or any other type with an associated meta table). * * Possible hook names include: * * - `deleted_post_meta` * - `deleted_comment_meta` * - `deleted_term_meta` * - `deleted_user_meta` * * @since 2.9.0 * * @param string[] $meta_ids An array of metadata entry IDs to delete. * @param int $object_id ID of the object metadata is for. * @param string $meta_key Metadata key. * @param mixed $_meta_value Metadata value. */ do_action( "deleted_{$meta_type}_meta", $meta_ids, $object_id, $meta_key, $_meta_value ); // Old-style action. if ( 'post' === $meta_type ) { /** * Fires immediately after deleting metadata for a post. * * @since 2.9.0 * * @param string[] $meta_ids An array of metadata entry IDs to delete. */ do_action( 'deleted_postmeta', $meta_ids ); } return true; } /** * Retrieves the value of a metadata field for the specified object type and ID. * * If the meta field exists, a single value is returned if `$single` is true, * or an array of values if it's false. * * If the meta field does not exist, the result depends on get_metadata_default(). * By default, an empty string is returned if `$single` is true, or an empty array * if it's false. * * @since 2.9.0 * * @see get_metadata_raw() * @see get_metadata_default() * * @param string $meta_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. * @param int $object_id ID of the object metadata is for. * @param string $meta_key Optional. Metadata key. If not specified, retrieve all metadata for * the specified object. Default empty string. * @param bool $single Optional. If true, return only the first value of the specified `$meta_key`. * This parameter has no effect if `$meta_key` is not specified. Default false. * @return mixed An array of values if `$single` is false. * The value of the meta field if `$single` is true. * False for an invalid `$object_id` (non-numeric, zero, or negative value), * or if `$meta_type` is not specified. * An empty string if a valid but non-existing object ID is passed. */ function get_metadata( $meta_type, $object_id, $meta_key = '', $single = false ) { $value = get_metadata_raw( $meta_type, $object_id, $meta_key, $single ); if ( ! is_null( $value ) ) { return $value; } return get_metadata_default( $meta_type, $object_id, $meta_key, $single ); } /** * Retrieves raw metadata value for the specified object. * * @since 5.5.0 * * @param string $meta_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. * @param int $object_id ID of the object metadata is for. * @param string $meta_key Optional. Metadata key. If not specified, retrieve all metadata for * the specified object. Default empty string. * @param bool $single Optional. If true, return only the first value of the specified `$meta_key`. * This parameter has no effect if `$meta_key` is not specified. Default false. * @return mixed An array of values if `$single` is false. * The value of the meta field if `$single` is true. * False for an invalid `$object_id` (non-numeric, zero, or negative value), * or if `$meta_type` is not specified. * Null if the value does not exist. */ function get_metadata_raw( $meta_type, $object_id, $meta_key = '', $single = false ) { if ( ! $meta_type || ! is_numeric( $object_id ) ) { return false; } $object_id = absint( $object_id ); if ( ! $object_id ) { return false; } /** * Short-circuits the return value of a meta field. * * The dynamic portion of the hook name, `$meta_type`, refers to the meta object type * (post, comment, term, user, or any other type with an associated meta table). * Returning a non-null value will effectively short-circuit the function. * * Possible filter names include: * * - `get_post_metadata` * - `get_comment_metadata` * - `get_term_metadata` * - `get_user_metadata` * * @since 3.1.0 * @since 5.5.0 Added the `$meta_type` parameter. * * @param mixed $value The value to return, either a single metadata value or an array * of values depending on the value of `$single`. Default null. * @param int $object_id ID of the object metadata is for. * @param string $meta_key Metadata key. * @param bool $single Whether to return only the first value of the specified `$meta_key`. * @param string $meta_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. */ $check = apply_filters( "get_{$meta_type}_metadata", null, $object_id, $meta_key, $single, $meta_type ); if ( null !== $check ) { if ( $single && is_array( $check ) ) { return $check[0]; } else { return $check; } } $meta_cache = wp_cache_get( $object_id, $meta_type . '_meta' ); if ( ! $meta_cache ) { $meta_cache = update_meta_cache( $meta_type, array( $object_id ) ); if ( isset( $meta_cache[ $object_id ] ) ) { $meta_cache = $meta_cache[ $object_id ]; } else { $meta_cache = null; } } if ( ! $meta_key ) { return $meta_cache; } if ( isset( $meta_cache[ $meta_key ] ) ) { if ( $single ) { return maybe_unserialize( $meta_cache[ $meta_key ][0] ); } else { return array_map( 'maybe_unserialize', $meta_cache[ $meta_key ] ); } } return null; } /** * Retrieves default metadata value for the specified meta key and object. * * By default, an empty string is returned if `$single` is true, or an empty array * if it's false. * * @since 5.5.0 * * @param string $meta_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. * @param int $object_id ID of the object metadata is for. * @param string $meta_key Metadata key. * @param bool $single Optional. If true, return only the first value of the specified `$meta_key`. * This parameter has no effect if `$meta_key` is not specified. Default false. * @return mixed An array of default values if `$single` is false. * The default value of the meta field if `$single` is true. */ function get_metadata_default( $meta_type, $object_id, $meta_key, $single = false ) { if ( $single ) { $value = ''; } else { $value = array(); } /** * Filters the default metadata value for a specified meta key and object. * * The dynamic portion of the hook name, `$meta_type`, refers to the meta object type * (post, comment, term, user, or any other type with an associated meta table). * * Possible filter names include: * * - `default_post_metadata` * - `default_comment_metadata` * - `default_term_metadata` * - `default_user_metadata` * * @since 5.5.0 * * @param mixed $value The value to return, either a single metadata value or an array * of values depending on the value of `$single`. * @param int $object_id ID of the object metadata is for. * @param string $meta_key Metadata key. * @param bool $single Whether to return only the first value of the specified `$meta_key`. * @param string $meta_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. */ $value = apply_filters( "default_{$meta_type}_metadata", $value, $object_id, $meta_key, $single, $meta_type ); if ( ! $single && ! wp_is_numeric_array( $value ) ) { $value = array( $value ); } return $value; } /** * Determines if a meta field with the given key exists for the given object ID. * * @since 3.3.0 * * @param string $meta_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. * @param int $object_id ID of the object metadata is for. * @param string $meta_key Metadata key. * @return bool Whether a meta field with the given key exists. */ function metadata_exists( $meta_type, $object_id, $meta_key ) { if ( ! $meta_type || ! is_numeric( $object_id ) ) { return false; } $object_id = absint( $object_id ); if ( ! $object_id ) { return false; } /** This filter is documented in wp-includes/meta.php */ $check = apply_filters( "get_{$meta_type}_metadata", null, $object_id, $meta_key, true, $meta_type ); if ( null !== $check ) { return (bool) $check; } $meta_cache = wp_cache_get( $object_id, $meta_type . '_meta' ); if ( ! $meta_cache ) { $meta_cache = update_meta_cache( $meta_type, array( $object_id ) ); $meta_cache = $meta_cache[ $object_id ]; } if ( isset( $meta_cache[ $meta_key ] ) ) { return true; } return false; } /** * Retrieves metadata by meta ID. * * @since 3.3.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param string $meta_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. * @param int $meta_id ID for a specific meta row. * @return stdClass|false { * Metadata object, or boolean `false` if the metadata doesn't exist. * * @type string $meta_key The meta key. * @type mixed $meta_value The unserialized meta value. * @type string $meta_id Optional. The meta ID when the meta type is any value except 'user'. * @type string $umeta_id Optional. The meta ID when the meta type is 'user'. * @type string $post_id Optional. The object ID when the meta type is 'post'. * @type string $comment_id Optional. The object ID when the meta type is 'comment'. * @type string $term_id Optional. The object ID when the meta type is 'term'. * @type string $user_id Optional. The object ID when the meta type is 'user'. * } */ function get_metadata_by_mid( $meta_type, $meta_id ) { global $wpdb; if ( ! $meta_type || ! is_numeric( $meta_id ) || floor( $meta_id ) != $meta_id ) { return false; } $meta_id = (int) $meta_id; if ( $meta_id <= 0 ) { return false; } $table = _get_meta_table( $meta_type ); if ( ! $table ) { return false; } /** * Short-circuits the return value when fetching a meta field by meta ID. * * The dynamic portion of the hook name, `$meta_type`, refers to the meta object type * (post, comment, term, user, or any other type with an associated meta table). * Returning a non-null value will effectively short-circuit the function. * * Possible hook names include: * * - `get_post_metadata_by_mid` * - `get_comment_metadata_by_mid` * - `get_term_metadata_by_mid` * - `get_user_metadata_by_mid` * * @since 5.0.0 * * @param stdClass|null $value The value to return. * @param int $meta_id Meta ID. */ $check = apply_filters( "get_{$meta_type}_metadata_by_mid", null, $meta_id ); if ( null !== $check ) { return $check; } $id_column = ( 'user' === $meta_type ) ? 'umeta_id' : 'meta_id'; $meta = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table WHERE $id_column = %d", $meta_id ) ); if ( empty( $meta ) ) { return false; } if ( isset( $meta->meta_value ) ) { $meta->meta_value = maybe_unserialize( $meta->meta_value ); } return $meta; } /** * Updates metadata by meta ID. * * @since 3.3.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param string $meta_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. * @param int $meta_id ID for a specific meta row. * @param string $meta_value Metadata value. Must be serializable if non-scalar. * @param string|false $meta_key Optional. You can provide a meta key to update it. Default false. * @return bool True on successful update, false on failure. */ function update_metadata_by_mid( $meta_type, $meta_id, $meta_value, $meta_key = false ) { global $wpdb; // Make sure everything is valid. if ( ! $meta_type || ! is_numeric( $meta_id ) || floor( $meta_id ) != $meta_id ) { return false; } $meta_id = (int) $meta_id; if ( $meta_id <= 0 ) { return false; } $table = _get_meta_table( $meta_type ); if ( ! $table ) { return false; } $column = sanitize_key( $meta_type . '_id' ); $id_column = ( 'user' === $meta_type ) ? 'umeta_id' : 'meta_id'; /** * Short-circuits updating metadata of a specific type by meta ID. * * The dynamic portion of the hook name, `$meta_type`, refers to the meta object type * (post, comment, term, user, or any other type with an associated meta table). * Returning a non-null value will effectively short-circuit the function. * * Possible hook names include: * * - `update_post_metadata_by_mid` * - `update_comment_metadata_by_mid` * - `update_term_metadata_by_mid` * - `update_user_metadata_by_mid` * * @since 5.0.0 * * @param null|bool $check Whether to allow updating metadata for the given type. * @param int $meta_id Meta ID. * @param mixed $meta_value Meta value. Must be serializable if non-scalar. * @param string|false $meta_key Meta key, if provided. */ $check = apply_filters( "update_{$meta_type}_metadata_by_mid", null, $meta_id, $meta_value, $meta_key ); if ( null !== $check ) { return (bool) $check; } // Fetch the meta and go on if it's found. $meta = get_metadata_by_mid( $meta_type, $meta_id ); if ( $meta ) { $original_key = $meta->meta_key; $object_id = $meta->{$column}; /* * If a new meta_key (last parameter) was specified, change the meta key, * otherwise use the original key in the update statement. */ if ( false === $meta_key ) { $meta_key = $original_key; } elseif ( ! is_string( $meta_key ) ) { return false; } $meta_subtype = get_object_subtype( $meta_type, $object_id ); // Sanitize the meta. $_meta_value = $meta_value; $meta_value = sanitize_meta( $meta_key, $meta_value, $meta_type, $meta_subtype ); $meta_value = maybe_serialize( $meta_value ); // Format the data query arguments. $data = array( 'meta_key' => $meta_key, 'meta_value' => $meta_value, ); // Format the where query arguments. $where = array(); $where[ $id_column ] = $meta_id; /** This action is documented in wp-includes/meta.php */ do_action( "update_{$meta_type}_meta", $meta_id, $object_id, $meta_key, $_meta_value ); if ( 'post' === $meta_type ) { /** This action is documented in wp-includes/meta.php */ do_action( 'update_postmeta', $meta_id, $object_id, $meta_key, $meta_value ); } // Run the update query, all fields in $data are %s, $where is a %d. $result = $wpdb->update( $table, $data, $where, '%s', '%d' ); if ( ! $result ) { return false; } // Clear the caches. wp_cache_delete( $object_id, $meta_type . '_meta' ); /** This action is documented in wp-includes/meta.php */ do_action( "updated_{$meta_type}_meta", $meta_id, $object_id, $meta_key, $_meta_value ); if ( 'post' === $meta_type ) { /** This action is documented in wp-includes/meta.php */ do_action( 'updated_postmeta', $meta_id, $object_id, $meta_key, $meta_value ); } return true; } // And if the meta was not found. return false; } /** * Deletes metadata by meta ID. * * @since 3.3.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param string $meta_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. * @param int $meta_id ID for a specific meta row. * @return bool True on successful delete, false on failure. */ function delete_metadata_by_mid( $meta_type, $meta_id ) { global $wpdb; // Make sure everything is valid. if ( ! $meta_type || ! is_numeric( $meta_id ) || floor( $meta_id ) != $meta_id ) { return false; } $meta_id = (int) $meta_id; if ( $meta_id <= 0 ) { return false; } $table = _get_meta_table( $meta_type ); if ( ! $table ) { return false; } // Object and ID columns. $column = sanitize_key( $meta_type . '_id' ); $id_column = ( 'user' === $meta_type ) ? 'umeta_id' : 'meta_id'; /** * Short-circuits deleting metadata of a specific type by meta ID. * * The dynamic portion of the hook name, `$meta_type`, refers to the meta object type * (post, comment, term, user, or any other type with an associated meta table). * Returning a non-null value will effectively short-circuit the function. * * Possible hook names include: * * - `delete_post_metadata_by_mid` * - `delete_comment_metadata_by_mid` * - `delete_term_metadata_by_mid` * - `delete_user_metadata_by_mid` * * @since 5.0.0 * * @param null|bool $delete Whether to allow metadata deletion of the given type. * @param int $meta_id Meta ID. */ $check = apply_filters( "delete_{$meta_type}_metadata_by_mid", null, $meta_id ); if ( null !== $check ) { return (bool) $check; } // Fetch the meta and go on if it's found. $meta = get_metadata_by_mid( $meta_type, $meta_id ); if ( $meta ) { $object_id = (int) $meta->{$column}; /** This action is documented in wp-includes/meta.php */ do_action( "delete_{$meta_type}_meta", (array) $meta_id, $object_id, $meta->meta_key, $meta->meta_value ); // Old-style action. if ( 'post' === $meta_type || 'comment' === $meta_type ) { /** * Fires immediately before deleting post or comment metadata of a specific type. * * The dynamic portion of the hook name, `$meta_type`, refers to the meta * object type (post or comment). * * Possible hook names include: * * - `delete_postmeta` * - `delete_commentmeta` * - `delete_termmeta` * - `delete_usermeta` * * @since 3.4.0 * * @param int $meta_id ID of the metadata entry to delete. */ do_action( "delete_{$meta_type}meta", $meta_id ); } // Run the query, will return true if deleted, false otherwise. $result = (bool) $wpdb->delete( $table, array( $id_column => $meta_id ) ); // Clear the caches. wp_cache_delete( $object_id, $meta_type . '_meta' ); /** This action is documented in wp-includes/meta.php */ do_action( "deleted_{$meta_type}_meta", (array) $meta_id, $object_id, $meta->meta_key, $meta->meta_value ); // Old-style action. if ( 'post' === $meta_type || 'comment' === $meta_type ) { /** * Fires immediately after deleting post or comment metadata of a specific type. * * The dynamic portion of the hook name, `$meta_type`, refers to the meta * object type (post or comment). * * Possible hook names include: * * - `deleted_postmeta` * - `deleted_commentmeta` * - `deleted_termmeta` * - `deleted_usermeta` * * @since 3.4.0 * * @param int $meta_id Deleted metadata entry ID. */ do_action( "deleted_{$meta_type}meta", $meta_id ); } return $result; } // Meta ID was not found. return false; } /** * Updates the metadata cache for the specified objects. * * @since 2.9.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param string $meta_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. * @param string|int[] $object_ids Array or comma delimited list of object IDs to update cache for. * @return array|false Metadata cache for the specified objects, or false on failure. */ function update_meta_cache( $meta_type, $object_ids ) { global $wpdb; if ( ! $meta_type || ! $object_ids ) { return false; } $table = _get_meta_table( $meta_type ); if ( ! $table ) { return false; } $column = sanitize_key( $meta_type . '_id' ); if ( ! is_array( $object_ids ) ) { $object_ids = preg_replace( '|[^0-9,]|', '', $object_ids ); $object_ids = explode( ',', $object_ids ); } $object_ids = array_map( 'intval', $object_ids ); /** * Short-circuits updating the metadata cache of a specific type. * * The dynamic portion of the hook name, `$meta_type`, refers to the meta object type * (post, comment, term, user, or any other type with an associated meta table). * Returning a non-null value will effectively short-circuit the function. * * Possible hook names include: * * - `update_post_metadata_cache` * - `update_comment_metadata_cache` * - `update_term_metadata_cache` * - `update_user_metadata_cache` * * @since 5.0.0 * * @param mixed $check Whether to allow updating the meta cache of the given type. * @param int[] $object_ids Array of object IDs to update the meta cache for. */ $check = apply_filters( "update_{$meta_type}_metadata_cache", null, $object_ids ); if ( null !== $check ) { return (bool) $check; } $cache_key = $meta_type . '_meta'; $non_cached_ids = array(); $cache = array(); $cache_values = wp_cache_get_multiple( $object_ids, $cache_key ); foreach ( $cache_values as $id => $cached_object ) { if ( false === $cached_object ) { $non_cached_ids[] = $id; } else { $cache[ $id ] = $cached_object; } } if ( empty( $non_cached_ids ) ) { return $cache; } // Get meta info. $id_list = implode( ',', $non_cached_ids ); $id_column = ( 'user' === $meta_type ) ? 'umeta_id' : 'meta_id'; $meta_list = $wpdb->get_results( "SELECT $column, meta_key, meta_value FROM $table WHERE $column IN ($id_list) ORDER BY $id_column ASC", ARRAY_A ); if ( ! empty( $meta_list ) ) { foreach ( $meta_list as $metarow ) { $mpid = (int) $metarow[ $column ]; $mkey = $metarow['meta_key']; $mval = $metarow['meta_value']; // Force subkeys to be array type. if ( ! isset( $cache[ $mpid ] ) || ! is_array( $cache[ $mpid ] ) ) { $cache[ $mpid ] = array(); } if ( ! isset( $cache[ $mpid ][ $mkey ] ) || ! is_array( $cache[ $mpid ][ $mkey ] ) ) { $cache[ $mpid ][ $mkey ] = array(); } // Add a value to the current pid/key. $cache[ $mpid ][ $mkey ][] = $mval; } } $data = array(); foreach ( $non_cached_ids as $id ) { if ( ! isset( $cache[ $id ] ) ) { $cache[ $id ] = array(); } $data[ $id ] = $cache[ $id ]; } wp_cache_add_multiple( $data, $cache_key ); return $cache; } /** * Retrieves the queue for lazy-loading metadata. * * @since 4.5.0 * * @return WP_Metadata_Lazyloader Metadata lazyloader queue. */ function wp_metadata_lazyloader() { static $wp_metadata_lazyloader; if ( null === $wp_metadata_lazyloader ) { $wp_metadata_lazyloader = new WP_Metadata_Lazyloader(); } return $wp_metadata_lazyloader; } /** * Given a meta query, generates SQL clauses to be appended to a main query. * * @since 3.2.0 * * @see WP_Meta_Query * * @param array $meta_query A meta query. * @param string $type Type of meta. * @param string $primary_table Primary database table name. * @param string $primary_id_column Primary ID column name. * @param object $context Optional. The main query object. Default null. * @return string[]|false { * Array containing JOIN and WHERE SQL clauses to append to the main query, * or false if no table exists for the requested meta type. * * @type string $join SQL fragment to append to the main JOIN clause. * @type string $where SQL fragment to append to the main WHERE clause. * } */ function get_meta_sql( $meta_query, $type, $primary_table, $primary_id_column, $context = null ) { $meta_query_obj = new WP_Meta_Query( $meta_query ); return $meta_query_obj->get_sql( $type, $primary_table, $primary_id_column, $context ); } /** * Retrieves the name of the metadata table for the specified object type. * * @since 2.9.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param string $type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. * @return string|false Metadata table name, or false if no metadata table exists */ function _get_meta_table( $type ) { global $wpdb; $table_name = $type . 'meta'; if ( empty( $wpdb->$table_name ) ) { return false; } return $wpdb->$table_name; } /** * Determines whether a meta key is considered protected. * * @since 3.1.3 * * @param string $meta_key Metadata key. * @param string $meta_type Optional. Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. Default empty string. * @return bool Whether the meta key is considered protected. */ function is_protected_meta( $meta_key, $meta_type = '' ) { $sanitized_key = preg_replace( "/[^\x20-\x7E\p{L}]/", '', $meta_key ); $protected = strlen( $sanitized_key ) > 0 && ( '_' === $sanitized_key[0] ); /** * Filters whether a meta key is considered protected. * * @since 3.2.0 * * @param bool $protected Whether the key is considered protected. * @param string $meta_key Metadata key. * @param string $meta_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. */ return apply_filters( 'is_protected_meta', $protected, $meta_key, $meta_type ); } /** * Sanitizes meta value. * * @since 3.1.3 * @since 4.9.8 The `$object_subtype` parameter was added. * * @param string $meta_key Metadata key. * @param mixed $meta_value Metadata value to sanitize. * @param string $object_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. * @param string $object_subtype Optional. The subtype of the object type. Default empty string. * @return mixed Sanitized $meta_value. */ function sanitize_meta( $meta_key, $meta_value, $object_type, $object_subtype = '' ) { if ( ! empty( $object_subtype ) && has_filter( "sanitize_{$object_type}_meta_{$meta_key}_for_{$object_subtype}" ) ) { /** * Filters the sanitization of a specific meta key of a specific meta type and subtype. * * The dynamic portions of the hook name, `$object_type`, `$meta_key`, * and `$object_subtype`, refer to the metadata object type (comment, post, term, or user), * the meta key value, and the object subtype respectively. * * @since 4.9.8 * * @param mixed $meta_value Metadata value to sanitize. * @param string $meta_key Metadata key. * @param string $object_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. * @param string $object_subtype Object subtype. */ return apply_filters( "sanitize_{$object_type}_meta_{$meta_key}_for_{$object_subtype}", $meta_value, $meta_key, $object_type, $object_subtype ); } /** * Filters the sanitization of a specific meta key of a specific meta type. * * The dynamic portions of the hook name, `$meta_type`, and `$meta_key`, * refer to the metadata object type (comment, post, term, or user) and the meta * key value, respectively. * * @since 3.3.0 * * @param mixed $meta_value Metadata value to sanitize. * @param string $meta_key Metadata key. * @param string $object_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. */ return apply_filters( "sanitize_{$object_type}_meta_{$meta_key}", $meta_value, $meta_key, $object_type ); } /** * Registers a meta key. * * It is recommended to register meta keys for a specific combination of object type and object subtype. If passing * an object subtype is omitted, the meta key will be registered for the entire object type, however it can be partly * overridden in case a more specific meta key of the same name exists for the same object type and a subtype. * * If an object type does not support any subtypes, such as users or comments, you should commonly call this function * without passing a subtype. * * @since 3.3.0 * @since 4.6.0 {@link https://core.trac.wordpress.org/ticket/35658 Modified * to support an array of data to attach to registered meta keys}. Previous arguments for * `$sanitize_callback` and `$auth_callback` have been folded into this array. * @since 4.9.8 The `$object_subtype` argument was added to the arguments array. * @since 5.3.0 Valid meta types expanded to include "array" and "object". * @since 5.5.0 The `$default` argument was added to the arguments array. * @since 6.4.0 The `$revisions_enabled` argument was added to the arguments array. * * @param string $object_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. * @param string $meta_key Meta key to register. * @param array $args { * Data used to describe the meta key when registered. * * @type string $object_subtype A subtype; e.g. if the object type is "post", the post type. If left empty, * the meta key will be registered on the entire object type. Default empty. * @type string $type The type of data associated with this meta key. * Valid values are 'string', 'boolean', 'integer', 'number', 'array', and 'object'. * @type string $description A description of the data attached to this meta key. * @type bool $single Whether the meta key has one value per object, or an array of values per object. * @type mixed $default The default value returned from get_metadata() if no value has been set yet. * When using a non-single meta key, the default value is for the first entry. * In other words, when calling get_metadata() with `$single` set to `false`, * the default value given here will be wrapped in an array. * @type callable $sanitize_callback A function or method to call when sanitizing `$meta_key` data. * @type callable $auth_callback Optional. A function or method to call when performing edit_post_meta, * add_post_meta, and delete_post_meta capability checks. * @type bool|array $show_in_rest Whether data associated with this meta key can be considered public and * should be accessible via the REST API. A custom post type must also declare * support for custom fields for registered meta to be accessible via REST. * When registering complex meta values this argument may optionally be an * array with 'schema' or 'prepare_callback' keys instead of a boolean. * @type bool $revisions_enabled Whether to enable revisions support for this meta_key. Can only be used when the * object type is 'post'. * } * @param string|array $deprecated Deprecated. Use `$args` instead. * @return bool True if the meta key was successfully registered in the global array, false if not. * Registering a meta key with distinct sanitize and auth callbacks will fire those callbacks, * but will not add to the global registry. */ function register_meta( $object_type, $meta_key, $args, $deprecated = null ) { global $wp_meta_keys; if ( ! is_array( $wp_meta_keys ) ) { $wp_meta_keys = array(); } $defaults = array( 'object_subtype' => '', 'type' => 'string', 'description' => '', 'default' => '', 'single' => false, 'sanitize_callback' => null, 'auth_callback' => null, 'show_in_rest' => false, 'revisions_enabled' => false, ); // There used to be individual args for sanitize and auth callbacks. $has_old_sanitize_cb = false; $has_old_auth_cb = false; if ( is_callable( $args ) ) { $args = array( 'sanitize_callback' => $args, ); $has_old_sanitize_cb = true; } else { $args = (array) $args; } if ( is_callable( $deprecated ) ) { $args['auth_callback'] = $deprecated; $has_old_auth_cb = true; } /** * Filters the registration arguments when registering meta. * * @since 4.6.0 * * @param array $args Array of meta registration arguments. * @param array $defaults Array of default arguments. * @param string $object_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. * @param string $meta_key Meta key. */ $args = apply_filters( 'register_meta_args', $args, $defaults, $object_type, $meta_key ); unset( $defaults['default'] ); $args = wp_parse_args( $args, $defaults ); // Require an item schema when registering array meta. if ( false !== $args['show_in_rest'] && 'array' === $args['type'] ) { if ( ! is_array( $args['show_in_rest'] ) || ! isset( $args['show_in_rest']['schema']['items'] ) ) { _doing_it_wrong( __FUNCTION__, __( 'When registering an "array" meta type to show in the REST API, you must specify the schema for each array item in "show_in_rest.schema.items".' ), '5.3.0' ); return false; } } $object_subtype = ! empty( $args['object_subtype'] ) ? $args['object_subtype'] : ''; if ( $args['revisions_enabled'] ) { if ( 'post' !== $object_type ) { _doing_it_wrong( __FUNCTION__, __( 'Meta keys cannot enable revisions support unless the object type supports revisions.' ), '6.4.0' ); return false; } elseif ( ! empty( $object_subtype ) && ! post_type_supports( $object_subtype, 'revisions' ) ) { _doing_it_wrong( __FUNCTION__, __( 'Meta keys cannot enable revisions support unless the object subtype supports revisions.' ), '6.4.0' ); return false; } } // If `auth_callback` is not provided, fall back to `is_protected_meta()`. if ( empty( $args['auth_callback'] ) ) { if ( is_protected_meta( $meta_key, $object_type ) ) { $args['auth_callback'] = '__return_false'; } else { $args['auth_callback'] = '__return_true'; } } // Back-compat: old sanitize and auth callbacks are applied to all of an object type. if ( is_callable( $args['sanitize_callback'] ) ) { if ( ! empty( $object_subtype ) ) { add_filter( "sanitize_{$object_type}_meta_{$meta_key}_for_{$object_subtype}", $args['sanitize_callback'], 10, 4 ); } else { add_filter( "sanitize_{$object_type}_meta_{$meta_key}", $args['sanitize_callback'], 10, 3 ); } } if ( is_callable( $args['auth_callback'] ) ) { if ( ! empty( $object_subtype ) ) { add_filter( "auth_{$object_type}_meta_{$meta_key}_for_{$object_subtype}", $args['auth_callback'], 10, 6 ); } else { add_filter( "auth_{$object_type}_meta_{$meta_key}", $args['auth_callback'], 10, 6 ); } } if ( array_key_exists( 'default', $args ) ) { $schema = $args; if ( is_array( $args['show_in_rest'] ) && isset( $args['show_in_rest']['schema'] ) ) { $schema = array_merge( $schema, $args['show_in_rest']['schema'] ); } $check = rest_validate_value_from_schema( $args['default'], $schema ); if ( is_wp_error( $check ) ) { _doing_it_wrong( __FUNCTION__, __( 'When registering a default meta value the data must match the type provided.' ), '5.5.0' ); return false; } if ( ! has_filter( "default_{$object_type}_metadata", 'filter_default_metadata' ) ) { add_filter( "default_{$object_type}_metadata", 'filter_default_metadata', 10, 5 ); } } // Global registry only contains meta keys registered with the array of arguments added in 4.6.0. if ( ! $has_old_auth_cb && ! $has_old_sanitize_cb ) { unset( $args['object_subtype'] ); $wp_meta_keys[ $object_type ][ $object_subtype ][ $meta_key ] = $args; return true; } return false; } /** * Filters into default_{$object_type}_metadata and adds in default value. * * @since 5.5.0 * * @param mixed $value Current value passed to filter. * @param int $object_id ID of the object metadata is for. * @param string $meta_key Metadata key. * @param bool $single If true, return only the first value of the specified `$meta_key`. * This parameter has no effect if `$meta_key` is not specified. * @param string $meta_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. * @return mixed An array of default values if `$single` is false. * The default value of the meta field if `$single` is true. */ function filter_default_metadata( $value, $object_id, $meta_key, $single, $meta_type ) { global $wp_meta_keys; if ( wp_installing() ) { return $value; } if ( ! is_array( $wp_meta_keys ) || ! isset( $wp_meta_keys[ $meta_type ] ) ) { return $value; } $defaults = array(); foreach ( $wp_meta_keys[ $meta_type ] as $sub_type => $meta_data ) { foreach ( $meta_data as $_meta_key => $args ) { if ( $_meta_key === $meta_key && array_key_exists( 'default', $args ) ) { $defaults[ $sub_type ] = $args; } } } if ( ! $defaults ) { return $value; } // If this meta type does not have subtypes, then the default is keyed as an empty string. if ( isset( $defaults[''] ) ) { $metadata = $defaults['']; } else { $sub_type = get_object_subtype( $meta_type, $object_id ); if ( ! isset( $defaults[ $sub_type ] ) ) { return $value; } $metadata = $defaults[ $sub_type ]; } if ( $single ) { $value = $metadata['default']; } else { $value = array( $metadata['default'] ); } return $value; } /** * Checks if a meta key is registered. * * @since 4.6.0 * @since 4.9.8 The `$object_subtype` parameter was added. * * @param string $object_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. * @param string $meta_key Metadata key. * @param string $object_subtype Optional. The subtype of the object type. Default empty string. * @return bool True if the meta key is registered to the object type and, if provided, * the object subtype. False if not. */ function registered_meta_key_exists( $object_type, $meta_key, $object_subtype = '' ) { $meta_keys = get_registered_meta_keys( $object_type, $object_subtype ); return isset( $meta_keys[ $meta_key ] ); } /** * Unregisters a meta key from the list of registered keys. * * @since 4.6.0 * @since 4.9.8 The `$object_subtype` parameter was added. * * @param string $object_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. * @param string $meta_key Metadata key. * @param string $object_subtype Optional. The subtype of the object type. Default empty string. * @return bool True if successful. False if the meta key was not registered. */ function unregister_meta_key( $object_type, $meta_key, $object_subtype = '' ) { global $wp_meta_keys; if ( ! registered_meta_key_exists( $object_type, $meta_key, $object_subtype ) ) { return false; } $args = $wp_meta_keys[ $object_type ][ $object_subtype ][ $meta_key ]; if ( isset( $args['sanitize_callback'] ) && is_callable( $args['sanitize_callback'] ) ) { if ( ! empty( $object_subtype ) ) { remove_filter( "sanitize_{$object_type}_meta_{$meta_key}_for_{$object_subtype}", $args['sanitize_callback'] ); } else { remove_filter( "sanitize_{$object_type}_meta_{$meta_key}", $args['sanitize_callback'] ); } } if ( isset( $args['auth_callback'] ) && is_callable( $args['auth_callback'] ) ) { if ( ! empty( $object_subtype ) ) { remove_filter( "auth_{$object_type}_meta_{$meta_key}_for_{$object_subtype}", $args['auth_callback'] ); } else { remove_filter( "auth_{$object_type}_meta_{$meta_key}", $args['auth_callback'] ); } } unset( $wp_meta_keys[ $object_type ][ $object_subtype ][ $meta_key ] ); // Do some clean up. if ( empty( $wp_meta_keys[ $object_type ][ $object_subtype ] ) ) { unset( $wp_meta_keys[ $object_type ][ $object_subtype ] ); } if ( empty( $wp_meta_keys[ $object_type ] ) ) { unset( $wp_meta_keys[ $object_type ] ); } return true; } /** * Retrieves a list of registered metadata args for an object type, keyed by their meta keys. * * @since 4.6.0 * @since 4.9.8 The `$object_subtype` parameter was added. * * @param string $object_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. * @param string $object_subtype Optional. The subtype of the object type. Default empty string. * @return array[] List of registered metadata args, keyed by their meta keys. */ function get_registered_meta_keys( $object_type, $object_subtype = '' ) { global $wp_meta_keys; if ( ! is_array( $wp_meta_keys ) || ! isset( $wp_meta_keys[ $object_type ] ) || ! isset( $wp_meta_keys[ $object_type ][ $object_subtype ] ) ) { return array(); } return $wp_meta_keys[ $object_type ][ $object_subtype ]; } /** * Retrieves registered metadata for a specified object. * * The results include both meta that is registered specifically for the * object's subtype and meta that is registered for the entire object type. * * @since 4.6.0 * * @param string $object_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. * @param int $object_id ID of the object the metadata is for. * @param string $meta_key Optional. Registered metadata key. If not specified, retrieve all registered * metadata for the specified object. * @return mixed A single value or array of values for a key if specified. An array of all registered keys * and values for an object ID if not. False if a given $meta_key is not registered. */ function get_registered_metadata( $object_type, $object_id, $meta_key = '' ) { $object_subtype = get_object_subtype( $object_type, $object_id ); if ( ! empty( $meta_key ) ) { if ( ! empty( $object_subtype ) && ! registered_meta_key_exists( $object_type, $meta_key, $object_subtype ) ) { $object_subtype = ''; } if ( ! registered_meta_key_exists( $object_type, $meta_key, $object_subtype ) ) { return false; } $meta_keys = get_registered_meta_keys( $object_type, $object_subtype ); $meta_key_data = $meta_keys[ $meta_key ]; $data = get_metadata( $object_type, $object_id, $meta_key, $meta_key_data['single'] ); return $data; } $data = get_metadata( $object_type, $object_id ); if ( ! $data ) { return array(); } $meta_keys = get_registered_meta_keys( $object_type ); if ( ! empty( $object_subtype ) ) { $meta_keys = array_merge( $meta_keys, get_registered_meta_keys( $object_type, $object_subtype ) ); } return array_intersect_key( $data, $meta_keys ); } /** * Filters out `register_meta()` args based on an allowed list. * * `register_meta()` args may change over time, so requiring the allowed list * to be explicitly turned off is a warranty seal of sorts. * * @access private * @since 5.5.0 * * @param array $args Arguments from `register_meta()`. * @param array $default_args Default arguments for `register_meta()`. * @return array Filtered arguments. */ function _wp_register_meta_args_allowed_list( $args, $default_args ) { return array_intersect_key( $args, $default_args ); } /** * Returns the object subtype for a given object ID of a specific type. * * @since 4.9.8 * * @param string $object_type Type of object metadata is for. Accepts 'post', 'comment', 'term', 'user', * or any other object type with an associated meta table. * @param int $object_id ID of the object to retrieve its subtype. * @return string The object subtype or an empty string if unspecified subtype. */ function get_object_subtype( $object_type, $object_id ) { $object_id = (int) $object_id; $object_subtype = ''; switch ( $object_type ) { case 'post': $post_type = get_post_type( $object_id ); if ( ! empty( $post_type ) ) { $object_subtype = $post_type; } break; case 'term': $term = get_term( $object_id ); if ( ! $term instanceof WP_Term ) { break; } $object_subtype = $term->taxonomy; break; case 'comment': $comment = get_comment( $object_id ); if ( ! $comment ) { break; } $object_subtype = 'comment'; break; case 'user': $user = get_user_by( 'id', $object_id ); if ( ! $user ) { break; } $object_subtype = 'user'; break; } /** * Filters the object subtype identifier for a non-standard object type. * * The dynamic portion of the hook name, `$object_type`, refers to the meta object type * (post, comment, term, user, or any other type with an associated meta table). * * Possible hook names include: * * - `get_object_subtype_post` * - `get_object_subtype_comment` * - `get_object_subtype_term` * - `get_object_subtype_user` * * @since 4.9.8 * * @param string $object_subtype Empty string to override. * @param int $object_id ID of the object to get the subtype for. */ return apply_filters( "get_object_subtype_{$object_type}", $object_subtype, $object_id ); } /** * Meta API: WP_Meta_Query class * * @package WordPress * @subpackage Meta * @since 4.4.0 */ /** * Core class used to implement meta queries for the Meta API. * * Used for generating SQL clauses that filter a primary query according to metadata keys and values. * * WP_Meta_Query is a helper that allows primary query classes, such as WP_Query and WP_User_Query, * * to filter their results by object metadata, by generating `JOIN` and `WHERE` subclauses to be attached * to the primary SQL query string. * * @since 3.2.0 */ #[AllowDynamicProperties] class WP_Meta_Query { /** * Array of metadata queries. * * See WP_Meta_Query::__construct() for information on meta query arguments. * * @since 3.2.0 * @var array */ public $queries = array(); /** * The relation between the queries. Can be one of 'AND' or 'OR'. * * @since 3.2.0 * @var string */ public $relation; /** * Database table to query for the metadata. * * @since 4.1.0 * @var string */ public $meta_table; /** * Column in meta_table that represents the ID of the object the metadata belongs to. * * @since 4.1.0 * @var string */ public $meta_id_column; /** * Database table that where the metadata's objects are stored (eg $wpdb->users). * * @since 4.1.0 * @var string */ public $primary_table; /** * Column in primary_table that represents the ID of the object. * * @since 4.1.0 * @var string */ public $primary_id_column; /** * A flat list of table aliases used in JOIN clauses. * * @since 4.1.0 * @var array */ protected $table_aliases = array(); /** * A flat list of clauses, keyed by clause 'name'. * * @since 4.2.0 * @var array */ protected $clauses = array(); /** * Whether the query contains any OR relations. * * @since 4.3.0 * @var bool */ protected $has_or_relation = false; /** * Constructor. * * @since 3.2.0 * @since 4.2.0 Introduced support for naming query clauses by associative array keys. * @since 5.1.0 Introduced `$compare_key` clause parameter, which enables LIKE key matches. * @since 5.3.0 Increased the number of operators available to `$compare_key`. Introduced `$type_key`, * which enables the `$key` to be cast to a new data type for comparisons. * * @param array $meta_query { * Array of meta query clauses. When first-order clauses or sub-clauses use strings as * their array keys, they may be referenced in the 'orderby' parameter of the parent query. * * @type string $relation Optional. The MySQL keyword used to join the clauses of the query. * Accepts 'AND' or 'OR'. Default 'AND'. * @type array ...$0 { * Optional. An array of first-order clause parameters, or another fully-formed meta query. * * @type string|string[] $key Meta key or keys to filter by. * @type string $compare_key MySQL operator used for comparing the $key. Accepts: * - '=' * - '!=' * - 'LIKE' * - 'NOT LIKE' * - 'IN' * - 'NOT IN' * - 'REGEXP' * - 'NOT REGEXP' * - 'RLIKE', * - 'EXISTS' (alias of '=') * - 'NOT EXISTS' (alias of '!=') * Default is 'IN' when `$key` is an array, '=' otherwise. * @type string $type_key MySQL data type that the meta_key column will be CAST to for * comparisons. Accepts 'BINARY' for case-sensitive regular expression * comparisons. Default is ''. * @type string|string[] $value Meta value or values to filter by. * @type string $compare MySQL operator used for comparing the $value. Accepts: * - '=', * - '!=' * - '>' * - '>=' * - '<' * - '<=' * - 'LIKE' * - 'NOT LIKE' * - 'IN' * - 'NOT IN' * - 'BETWEEN' * - 'NOT BETWEEN' * - 'REGEXP' * - 'NOT REGEXP' * - 'RLIKE' * - 'EXISTS' * - 'NOT EXISTS' * Default is 'IN' when `$value` is an array, '=' otherwise. * @type string $type MySQL data type that the meta_value column will be CAST to for * comparisons. Accepts: * - 'NUMERIC' * - 'BINARY' * - 'CHAR' * - 'DATE' * - 'DATETIME' * - 'DECIMAL' * - 'SIGNED' * - 'TIME' * - 'UNSIGNED' * Default is 'CHAR'. * } * } */ public function __construct( $meta_query = false ) { if ( ! $meta_query ) { return; } if ( isset( $meta_query['relation'] ) && 'OR' === strtoupper( $meta_query['relation'] ) ) { $this->relation = 'OR'; } else { $this->relation = 'AND'; } $this->queries = $this->sanitize_query( $meta_query ); } /** * Ensures the 'meta_query' argument passed to the class constructor is well-formed. * * Eliminates empty items and ensures that a 'relation' is set. * * @since 4.1.0 * * @param array $queries Array of query clauses. * @return array Sanitized array of query clauses. */ public function sanitize_query( $queries ) { $clean_queries = array(); if ( ! is_array( $queries ) ) { return $clean_queries; } foreach ( $queries as $key => $query ) { if ( 'relation' === $key ) { $relation = $query; } elseif ( ! is_array( $query ) ) { continue; // First-order clause. } elseif ( $this->is_first_order_clause( $query ) ) { if ( isset( $query['value'] ) && array() === $query['value'] ) { unset( $query['value'] ); } $clean_queries[ $key ] = $query; // Otherwise, it's a nested query, so we recurse. } else { $cleaned_query = $this->sanitize_query( $query ); if ( ! empty( $cleaned_query ) ) { $clean_queries[ $key ] = $cleaned_query; } } } if ( empty( $clean_queries ) ) { return $clean_queries; } // Sanitize the 'relation' key provided in the query. if ( isset( $relation ) && 'OR' === strtoupper( $relation ) ) { $clean_queries['relation'] = 'OR'; $this->has_or_relation = true; /* * If there is only a single clause, call the relation 'OR'. * This value will not actually be used to join clauses, but it * simplifies the logic around combining key-only queries. */ } elseif ( 1 === count( $clean_queries ) ) { $clean_queries['relation'] = 'OR'; // Default to AND. } else { $clean_queries['relation'] = 'AND'; } return $clean_queries; } /** * Determines whether a query clause is first-order. * * A first-order meta query clause is one that has either a 'key' or * a 'value' array key. * * @since 4.1.0 * * @param array $query Meta query arguments. * @return bool Whether the query clause is a first-order clause. */ protected function is_first_order_clause( $query ) { return isset( $query['key'] ) || isset( $query['value'] ); } /** * Constructs a meta query based on 'meta_*' query vars * * @since 3.2.0 * * @param array $qv The query variables. */ public function parse_query_vars( $qv ) { $meta_query = array(); /* * For orderby=meta_value to work correctly, simple query needs to be * first (so that its table join is against an unaliased meta table) and * needs to be its own clause (so it doesn't interfere with the logic of * the rest of the meta_query). */ $primary_meta_query = array(); foreach ( array( 'key', 'compare', 'type', 'compare_key', 'type_key' ) as $key ) { if ( ! empty( $qv[ "meta_$key" ] ) ) { $primary_meta_query[ $key ] = $qv[ "meta_$key" ]; } } // WP_Query sets 'meta_value' = '' by default. if ( isset( $qv['meta_value'] ) && '' !== $qv['meta_value'] && ( ! is_array( $qv['meta_value'] ) || $qv['meta_value'] ) ) { $primary_meta_query['value'] = $qv['meta_value']; } $existing_meta_query = isset( $qv['meta_query'] ) && is_array( $qv['meta_query'] ) ? $qv['meta_query'] : array(); if ( ! empty( $primary_meta_query ) && ! empty( $existing_meta_query ) ) { $meta_query = array( 'relation' => 'AND', $primary_meta_query, $existing_meta_query, ); } elseif ( ! empty( $primary_meta_query ) ) { $meta_query = array( $primary_meta_query, ); } elseif ( ! empty( $existing_meta_query ) ) { $meta_query = $existing_meta_query; } $this->__construct( $meta_query ); } /** * Returns the appropriate alias for the given meta type if applicable. * * @since 3.7.0 * * @param string $type MySQL type to cast meta_value. * @return string MySQL type. */ public function get_cast_for_type( $type = '' ) { if ( empty( $type ) ) { return 'CHAR'; } $meta_type = strtoupper( $type ); if ( ! preg_match( '/^(?:BINARY|CHAR|DATE|DATETIME|SIGNED|UNSIGNED|TIME|NUMERIC(?:\(\d+(?:,\s?\d+)?\))?|DECIMAL(?:\(\d+(?:,\s?\d+)?\))?)$/', $meta_type ) ) { return 'CHAR'; } if ( 'NUMERIC' === $meta_type ) { $meta_type = 'SIGNED'; } return $meta_type; } /** * Generates SQL clauses to be appended to a main query. * * @since 3.2.0 * * @param string $type Type of meta. Possible values include but are not limited * to 'post', 'comment', 'blog', 'term', and 'user'. * @param string $primary_table Database table where the object being filtered is stored (eg wp_users). * @param string $primary_id_column ID column for the filtered object in $primary_table. * @param object $context Optional. The main query object that corresponds to the type, for * example a `WP_Query`, `WP_User_Query`, or `WP_Site_Query`. * Default null. * @return string[]|false { * Array containing JOIN and WHERE SQL clauses to append to the main query, * or false if no table exists for the requested meta type. * * @type string $join SQL fragment to append to the main JOIN clause. * @type string $where SQL fragment to append to the main WHERE clause. * } */ public function get_sql( $type, $primary_table, $primary_id_column, $context = null ) { $meta_table = _get_meta_table( $type ); if ( ! $meta_table ) { return false; } $this->table_aliases = array(); $this->meta_table = $meta_table; $this->meta_id_column = sanitize_key( $type . '_id' ); $this->primary_table = $primary_table; $this->primary_id_column = $primary_id_column; $sql = $this->get_sql_clauses(); /* * If any JOINs are LEFT JOINs (as in the case of NOT EXISTS), then all JOINs should * be LEFT. Otherwise posts with no metadata will be excluded from results. */ if ( str_contains( $sql['join'], 'LEFT JOIN' ) ) { $sql['join'] = str_replace( 'INNER JOIN', 'LEFT JOIN', $sql['join'] ); } /** * Filters the meta query's generated SQL. * * @since 3.1.0 * * @param string[] $sql Array containing the query's JOIN and WHERE clauses. * @param array $queries Array of meta queries. * @param string $type Type of meta. Possible values include but are not limited * to 'post', 'comment', 'blog', 'term', and 'user'. * @param string $primary_table Primary table. * @param string $primary_id_column Primary column ID. * @param object $context The main query object that corresponds to the type, for * example a `WP_Query`, `WP_User_Query`, or `WP_Site_Query`. */ return apply_filters_ref_array( 'get_meta_sql', array( $sql, $this->queries, $type, $primary_table, $primary_id_column, $context ) ); } /** * Generates SQL clauses to be appended to a main query. * * Called by the public WP_Meta_Query::get_sql(), this method is abstracted * out to maintain parity with the other Query classes. * * @since 4.1.0 * * @return string[] { * Array containing JOIN and WHERE SQL clauses to append to the main query. * * @type string $join SQL fragment to append to the main JOIN clause. * @type string $where SQL fragment to append to the main WHERE clause. * } */ protected function get_sql_clauses() { /* * $queries are passed by reference to get_sql_for_query() for recursion. * To keep $this->queries unaltered, pass a copy. */ $queries = $this->queries; $sql = $this->get_sql_for_query( $queries ); if ( ! empty( $sql['where'] ) ) { $sql['where'] = ' AND ' . $sql['where']; } return $sql; } /** * Generates SQL clauses for a single query array. * * If nested subqueries are found, this method recurses the tree to * produce the properly nested SQL. * * @since 4.1.0 * * @param array $query Query to parse (passed by reference). * @param int $depth Optional. Number of tree levels deep we currently are. * Used to calculate indentation. Default 0. * @return string[] { * Array containing JOIN and WHERE SQL clauses to append to a single query array. * * @type string $join SQL fragment to append to the main JOIN clause. * @type string $where SQL fragment to append to the main WHERE clause. * } */ protected function get_sql_for_query( &$query, $depth = 0 ) { $sql_chunks = array( 'join' => array(), 'where' => array(), ); $sql = array( 'join' => '', 'where' => '', ); $indent = ''; for ( $i = 0; $i < $depth; $i++ ) { $indent .= ' '; } foreach ( $query as $key => &$clause ) { if ( 'relation' === $key ) { $relation = $query['relation']; } elseif ( is_array( $clause ) ) { // This is a first-order clause. if ( $this->is_first_order_clause( $clause ) ) { $clause_sql = $this->get_sql_for_clause( $clause, $query, $key ); $where_count = count( $clause_sql['where'] ); if ( ! $where_count ) { $sql_chunks['where'][] = ''; } elseif ( 1 === $where_count ) { $sql_chunks['where'][] = $clause_sql['where'][0]; } else { $sql_chunks['where'][] = '( ' . implode( ' AND ', $clause_sql['where'] ) . ' )'; } $sql_chunks['join'] = array_merge( $sql_chunks['join'], $clause_sql['join'] ); // This is a subquery, so we recurse. } else { $clause_sql = $this->get_sql_for_query( $clause, $depth + 1 ); $sql_chunks['where'][] = $clause_sql['where']; $sql_chunks['join'][] = $clause_sql['join']; } } } // Filter to remove empties. $sql_chunks['join'] = array_filter( $sql_chunks['join'] ); $sql_chunks['where'] = array_filter( $sql_chunks['where'] ); if ( empty( $relation ) ) { $relation = 'AND'; } // Filter duplicate JOIN clauses and combine into a single string. if ( ! empty( $sql_chunks['join'] ) ) { $sql['join'] = implode( ' ', array_unique( $sql_chunks['join'] ) ); } // Generate a single WHERE clause with proper brackets and indentation. if ( ! empty( $sql_chunks['where'] ) ) { $sql['where'] = '( ' . "\n " . $indent . implode( ' ' . "\n " . $indent . $relation . ' ' . "\n " . $indent, $sql_chunks['where'] ) . "\n" . $indent . ')'; } return $sql; } /** * Generates SQL JOIN and WHERE clauses for a first-order query clause. * * "First-order" means that it's an array with a 'key' or 'value'. * * @since 4.1.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param array $clause Query clause (passed by reference). * @param array $parent_query Parent query array. * @param string $clause_key Optional. The array key used to name the clause in the original `$meta_query` * parameters. If not provided, a key will be generated automatically. * Default empty string. * @return array { * Array containing JOIN and WHERE SQL clauses to append to a first-order query. * * @type string[] $join Array of SQL fragments to append to the main JOIN clause. * @type string[] $where Array of SQL fragments to append to the main WHERE clause. * } */ public function get_sql_for_clause( &$clause, $parent_query, $clause_key = '' ) { global $wpdb; $sql_chunks = array( 'where' => array(), 'join' => array(), ); if ( isset( $clause['compare'] ) ) { $clause['compare'] = strtoupper( $clause['compare'] ); } else { $clause['compare'] = isset( $clause['value'] ) && is_array( $clause['value'] ) ? 'IN' : '='; } $non_numeric_operators = array( '=', '!=', 'LIKE', 'NOT LIKE', 'IN', 'NOT IN', 'EXISTS', 'NOT EXISTS', 'RLIKE', 'REGEXP', 'NOT REGEXP', ); $numeric_operators = array( '>', '>=', '<', '<=', 'BETWEEN', 'NOT BETWEEN', ); if ( ! in_array( $clause['compare'], $non_numeric_operators, true ) && ! in_array( $clause['compare'], $numeric_operators, true ) ) { $clause['compare'] = '='; } if ( isset( $clause['compare_key'] ) ) { $clause['compare_key'] = strtoupper( $clause['compare_key'] ); } else { $clause['compare_key'] = isset( $clause['key'] ) && is_array( $clause['key'] ) ? 'IN' : '='; } if ( ! in_array( $clause['compare_key'], $non_numeric_operators, true ) ) { $clause['compare_key'] = '='; } $meta_compare = $clause['compare']; $meta_compare_key = $clause['compare_key']; // First build the JOIN clause, if one is required. $join = ''; // We prefer to avoid joins if possible. Look for an existing join compatible with this clause. $alias = $this->find_compatible_table_alias( $clause, $parent_query ); if ( false === $alias ) { $i = count( $this->table_aliases ); $alias = $i ? 'mt' . $i : $this->meta_table; // JOIN clauses for NOT EXISTS have their own syntax. if ( 'NOT EXISTS' === $meta_compare ) { $join .= " LEFT JOIN $this->meta_table"; $join .= $i ? " AS $alias" : ''; if ( 'LIKE' === $meta_compare_key ) { $join .= $wpdb->prepare( " ON ( $this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column AND $alias.meta_key LIKE %s )", '%' . $wpdb->esc_like( $clause['key'] ) . '%' ); } else { $join .= $wpdb->prepare( " ON ( $this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column AND $alias.meta_key = %s )", $clause['key'] ); } // All other JOIN clauses. } else { $join .= " INNER JOIN $this->meta_table"; $join .= $i ? " AS $alias" : ''; $join .= " ON ( $this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column )"; } $this->table_aliases[] = $alias; $sql_chunks['join'][] = $join; } // Save the alias to this clause, for future siblings to find. $clause['alias'] = $alias; // Determine the data type. $_meta_type = isset( $clause['type'] ) ? $clause['type'] : ''; $meta_type = $this->get_cast_for_type( $_meta_type ); $clause['cast'] = $meta_type; // Fallback for clause keys is the table alias. Key must be a string. if ( is_int( $clause_key ) || ! $clause_key ) { $clause_key = $clause['alias']; } // Ensure unique clause keys, so none are overwritten. $iterator = 1; $clause_key_base = $clause_key; while ( isset( $this->clauses[ $clause_key ] ) ) { $clause_key = $clause_key_base . '-' . $iterator; ++$iterator; } // Store the clause in our flat array. $this->clauses[ $clause_key ] =& $clause; // Next, build the WHERE clause. // meta_key. if ( array_key_exists( 'key', $clause ) ) { if ( 'NOT EXISTS' === $meta_compare ) { $sql_chunks['where'][] = $alias . '.' . $this->meta_id_column . ' IS NULL'; } else { /** * In joined clauses negative operators have to be nested into a * NOT EXISTS clause and flipped, to avoid returning records with * matching post IDs but different meta keys. Here we prepare the * nested clause. */ if ( in_array( $meta_compare_key, array( '!=', 'NOT IN', 'NOT LIKE', 'NOT EXISTS', 'NOT REGEXP' ), true ) ) { // Negative clauses may be reused. $i = count( $this->table_aliases ); $subquery_alias = $i ? 'mt' . $i : $this->meta_table; $this->table_aliases[] = $subquery_alias; $meta_compare_string_start = 'NOT EXISTS ('; $meta_compare_string_start .= "SELECT 1 FROM $wpdb->postmeta $subquery_alias "; $meta_compare_string_start .= "WHERE $subquery_alias.post_ID = $alias.post_ID "; $meta_compare_string_end = 'LIMIT 1'; $meta_compare_string_end .= ')'; } switch ( $meta_compare_key ) { case '=': case 'EXISTS': $where = $wpdb->prepare( "$alias.meta_key = %s", trim( $clause['key'] ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared break; case 'LIKE': $meta_compare_value = '%' . $wpdb->esc_like( trim( $clause['key'] ) ) . '%'; $where = $wpdb->prepare( "$alias.meta_key LIKE %s", $meta_compare_value ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared break; case 'IN': $meta_compare_string = "$alias.meta_key IN (" . substr( str_repeat( ',%s', count( $clause['key'] ) ), 1 ) . ')'; $where = $wpdb->prepare( $meta_compare_string, $clause['key'] ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared break; case 'RLIKE': case 'REGEXP': $operator = $meta_compare_key; if ( isset( $clause['type_key'] ) && 'BINARY' === strtoupper( $clause['type_key'] ) ) { $cast = 'BINARY'; $meta_key = "CAST($alias.meta_key AS BINARY)"; } else { $cast = ''; $meta_key = "$alias.meta_key"; } $where = $wpdb->prepare( "$meta_key $operator $cast %s", trim( $clause['key'] ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared break; case '!=': case 'NOT EXISTS': $meta_compare_string = $meta_compare_string_start . "AND $subquery_alias.meta_key = %s " . $meta_compare_string_end; $where = $wpdb->prepare( $meta_compare_string, $clause['key'] ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared break; case 'NOT LIKE': $meta_compare_string = $meta_compare_string_start . "AND $subquery_alias.meta_key LIKE %s " . $meta_compare_string_end; $meta_compare_value = '%' . $wpdb->esc_like( trim( $clause['key'] ) ) . '%'; $where = $wpdb->prepare( $meta_compare_string, $meta_compare_value ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared break; case 'NOT IN': $array_subclause = '(' . substr( str_repeat( ',%s', count( $clause['key'] ) ), 1 ) . ') '; $meta_compare_string = $meta_compare_string_start . "AND $subquery_alias.meta_key IN " . $array_subclause . $meta_compare_string_end; $where = $wpdb->prepare( $meta_compare_string, $clause['key'] ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared break; case 'NOT REGEXP': $operator = $meta_compare_key; if ( isset( $clause['type_key'] ) && 'BINARY' === strtoupper( $clause['type_key'] ) ) { $cast = 'BINARY'; $meta_key = "CAST($subquery_alias.meta_key AS BINARY)"; } else { $cast = ''; $meta_key = "$subquery_alias.meta_key"; } $meta_compare_string = $meta_compare_string_start . "AND $meta_key REGEXP $cast %s " . $meta_compare_string_end; $where = $wpdb->prepare( $meta_compare_string, $clause['key'] ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared break; } $sql_chunks['where'][] = $where; } } // meta_value. if ( array_key_exists( 'value', $clause ) ) { $meta_value = $clause['value']; if ( in_array( $meta_compare, array( 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN' ), true ) ) { if ( ! is_array( $meta_value ) ) { $meta_value = preg_split( '/[,\s]+/', $meta_value ); } } elseif ( is_string( $meta_value ) ) { $meta_value = trim( $meta_value ); } switch ( $meta_compare ) { case 'IN': case 'NOT IN': $meta_compare_string = '(' . substr( str_repeat( ',%s', count( $meta_value ) ), 1 ) . ')'; $where = $wpdb->prepare( $meta_compare_string, $meta_value ); break; case 'BETWEEN': case 'NOT BETWEEN': $where = $wpdb->prepare( '%s AND %s', $meta_value[0], $meta_value[1] ); break; case 'LIKE': case 'NOT LIKE': $meta_value = '%' . $wpdb->esc_like( $meta_value ) . '%'; $where = $wpdb->prepare( '%s', $meta_value ); break; // EXISTS with a value is interpreted as '='. case 'EXISTS': $meta_compare = '='; $where = $wpdb->prepare( '%s', $meta_value ); break; // 'value' is ignored for NOT EXISTS. case 'NOT EXISTS': $where = ''; break; default: $where = $wpdb->prepare( '%s', $meta_value ); break; } if ( $where ) { if ( 'CHAR' === $meta_type ) { $sql_chunks['where'][] = "$alias.meta_value {$meta_compare} {$where}"; } else { $sql_chunks['where'][] = "CAST($alias.meta_value AS {$meta_type}) {$meta_compare} {$where}"; } } } /* * Multiple WHERE clauses (for meta_key and meta_value) should * be joined in parentheses. */ if ( 1 < count( $sql_chunks['where'] ) ) { $sql_chunks['where'] = array( '( ' . implode( ' AND ', $sql_chunks['where'] ) . ' )' ); } return $sql_chunks; } /** * Gets a flattened list of sanitized meta clauses. * * This array should be used for clause lookup, as when the table alias and CAST type must be determined for * a value of 'orderby' corresponding to a meta clause. * * @since 4.2.0 * * @return array Meta clauses. */ public function get_clauses() { return $this->clauses; } /** * Identifies an existing table alias that is compatible with the current * query clause. * * We avoid unnecessary table joins by allowing each clause to look for * an existing table alias that is compatible with the query that it * needs to perform. * * An existing alias is compatible if (a) it is a sibling of `$clause` * (ie, it's under the scope of the same relation), and (b) the combination * of operator and relation between the clauses allows for a shared table join. * In the case of WP_Meta_Query, this only applies to 'IN' clauses that are * connected by the relation 'OR'. * * @since 4.1.0 * * @param array $clause Query clause. * @param array $parent_query Parent query of $clause. * @return string|false Table alias if found, otherwise false. */ protected function find_compatible_table_alias( $clause, $parent_query ) { $alias = false; foreach ( $parent_query as $sibling ) { // If the sibling has no alias yet, there's nothing to check. if ( empty( $sibling['alias'] ) ) { continue; } // We're only interested in siblings that are first-order clauses. if ( ! is_array( $sibling ) || ! $this->is_first_order_clause( $sibling ) ) { continue; } $compatible_compares = array(); // Clauses connected by OR can share joins as long as they have "positive" operators. if ( 'OR' === $parent_query['relation'] ) { $compatible_compares = array( '=', 'IN', 'BETWEEN', 'LIKE', 'REGEXP', 'RLIKE', '>', '>=', '<', '<=' ); // Clauses joined by AND with "negative" operators share a join only if they also share a key. } elseif ( isset( $sibling['key'] ) && isset( $clause['key'] ) && $sibling['key'] === $clause['key'] ) { $compatible_compares = array( '!=', 'NOT IN', 'NOT LIKE' ); } $clause_compare = strtoupper( $clause['compare'] ); $sibling_compare = strtoupper( $sibling['compare'] ); if ( in_array( $clause_compare, $compatible_compares, true ) && in_array( $sibling_compare, $compatible_compares, true ) ) { $alias = preg_replace( '/\W/', '_', $sibling['alias'] ); break; } } /** * Filters the table alias identified as compatible with the current clause. * * @since 4.1.0 * * @param string|false $alias Table alias, or false if none was found. * @param array $clause First-order query clause. * @param array $parent_query Parent of $clause. * @param WP_Meta_Query $query WP_Meta_Query object. */ return apply_filters( 'meta_query_find_compatible_table_alias', $alias, $clause, $parent_query, $this ); } /** * Checks whether the current query has any OR relations. * * In some cases, the presence of an OR relation somewhere in the query will require * the use of a `DISTINCT` or `GROUP BY` keyword in the `SELECT` clause. The current * method can be used in these cases to determine whether such a clause is necessary. * * @since 4.3.0 * * @return bool True if the query contains any `OR` relations, otherwise false. */ public function has_or_relation() { return $this->has_or_relation; } } /** * I18N: WP_Translation_Controller class. * * @package WordPress * @subpackage I18N * @since 6.5.0 */ /** * Class WP_Translation_Controller. * * @since 6.5.0 */ final class WP_Translation_Controller { /** * Current locale. * * @since 6.5.0 * @var string */ protected $current_locale = 'en_US'; /** * Map of loaded translations per locale and text domain. * * [ Locale => [ Textdomain => [ ..., ... ] ] ] * * @since 6.5.0 * @var array> */ protected $loaded_translations = array(); /** * List of loaded translation files. * * [ Filename => [ Locale => [ Textdomain => WP_Translation_File ] ] ] * * @since 6.5.0 * @var array>> */ protected $loaded_files = array(); /** * Container for the main instance of the class. * * @since 6.5.0 * @var WP_Translation_Controller|null */ private static $instance = null; /** * Utility method to retrieve the main instance of the class. * * The instance will be created if it does not exist yet. * * @since 6.5.0 * * @return WP_Translation_Controller */ public static function get_instance(): WP_Translation_Controller { if ( null === self::$instance ) { self::$instance = new self(); } return self::$instance; } /** * Returns the current locale. * * @since 6.5.0 * * @return string Locale. */ public function get_locale(): string { return $this->current_locale; } /** * Sets the current locale. * * @since 6.5.0 * * @param string $locale Locale. */ public function set_locale( string $locale ) { $this->current_locale = $locale; } /** * Loads a translation file for a given text domain. * * @since 6.5.0 * * @param string $translation_file Translation file. * @param string $textdomain Optional. Text domain. Default 'default'. * @param string $locale Optional. Locale. Default current locale. * @return bool True on success, false otherwise. */ public function load_file( string $translation_file, string $textdomain = 'default', string $locale = null ): bool { if ( null === $locale ) { $locale = $this->current_locale; } $translation_file = realpath( $translation_file ); if ( false === $translation_file ) { return false; } if ( isset( $this->loaded_files[ $translation_file ][ $locale ][ $textdomain ] ) && false !== $this->loaded_files[ $translation_file ][ $locale ][ $textdomain ] ) { return null === $this->loaded_files[ $translation_file ][ $locale ][ $textdomain ]->error(); } if ( isset( $this->loaded_files[ $translation_file ][ $locale ] ) && array() !== $this->loaded_files[ $translation_file ][ $locale ] ) { $moe = reset( $this->loaded_files[ $translation_file ][ $locale ] ); } else { $moe = WP_Translation_File::create( $translation_file ); if ( false === $moe || null !== $moe->error() ) { $moe = false; } } $this->loaded_files[ $translation_file ][ $locale ][ $textdomain ] = $moe; if ( ! $moe instanceof WP_Translation_File ) { return false; } if ( ! isset( $this->loaded_translations[ $locale ][ $textdomain ] ) ) { $this->loaded_translations[ $locale ][ $textdomain ] = array(); } $this->loaded_translations[ $locale ][ $textdomain ][] = $moe; return true; } /** * Unloads a translation file for a given text domain. * * @since 6.5.0 * * @param WP_Translation_File|string $file Translation file instance or file name. * @param string $textdomain Optional. Text domain. Default 'default'. * @param string $locale Optional. Locale. Defaults to all locales. * @return bool True on success, false otherwise. */ public function unload_file( $file, string $textdomain = 'default', string $locale = null ): bool { if ( is_string( $file ) ) { $file = realpath( $file ); } if ( null !== $locale ) { if ( isset( $this->loaded_translations[ $locale ][ $textdomain ] ) ) { foreach ( $this->loaded_translations[ $locale ][ $textdomain ] as $i => $moe ) { if ( $file === $moe || $file === $moe->get_file() ) { unset( $this->loaded_translations[ $locale ][ $textdomain ][ $i ] ); unset( $this->loaded_files[ $moe->get_file() ][ $locale ][ $textdomain ] ); return true; } } } return true; } foreach ( $this->loaded_translations as $l => $domains ) { if ( ! isset( $domains[ $textdomain ] ) ) { continue; } foreach ( $domains[ $textdomain ] as $i => $moe ) { if ( $file === $moe || $file === $moe->get_file() ) { unset( $this->loaded_translations[ $l ][ $textdomain ][ $i ] ); unset( $this->loaded_files[ $moe->get_file() ][ $l ][ $textdomain ] ); return true; } } } return false; } /** * Unloads all translation files for a given text domain. * * @since 6.5.0 * * @param string $textdomain Optional. Text domain. Default 'default'. * @param string $locale Optional. Locale. Defaults to all locales. * @return bool True on success, false otherwise. */ public function unload_textdomain( string $textdomain = 'default', string $locale = null ): bool { $unloaded = false; if ( null !== $locale ) { if ( isset( $this->loaded_translations[ $locale ][ $textdomain ] ) ) { $unloaded = true; foreach ( $this->loaded_translations[ $locale ][ $textdomain ] as $moe ) { unset( $this->loaded_files[ $moe->get_file() ][ $locale ][ $textdomain ] ); } } unset( $this->loaded_translations[ $locale ][ $textdomain ] ); return $unloaded; } foreach ( $this->loaded_translations as $l => $domains ) { if ( ! isset( $domains[ $textdomain ] ) ) { continue; } $unloaded = true; foreach ( $domains[ $textdomain ] as $moe ) { unset( $this->loaded_files[ $moe->get_file() ][ $l ][ $textdomain ] ); } unset( $this->loaded_translations[ $l ][ $textdomain ] ); } return $unloaded; } /** * Determines whether translations are loaded for a given text domain. * * @since 6.5.0 * * @param string $textdomain Optional. Text domain. Default 'default'. * @param string $locale Optional. Locale. Default current locale. * @return bool True if there are any loaded translations, false otherwise. */ public function is_textdomain_loaded( string $textdomain = 'default', string $locale = null ): bool { if ( null === $locale ) { $locale = $this->current_locale; } return isset( $this->loaded_translations[ $locale ][ $textdomain ] ) && array() !== $this->loaded_translations[ $locale ][ $textdomain ]; } /** * Translates a singular string. * * @since 6.5.0 * * @param string $text Text to translate. * @param string $context Optional. Context for the string. Default empty string. * @param string $textdomain Optional. Text domain. Default 'default'. * @param string $locale Optional. Locale. Default current locale. * @return string|false Translation on success, false otherwise. */ public function translate( string $text, string $context = '', string $textdomain = 'default', string $locale = null ) { if ( '' !== $context ) { $context .= "\4"; } $translation = $this->locate_translation( "{$context}{$text}", $textdomain, $locale ); if ( false === $translation ) { return false; } return $translation['entries'][0]; } /** * Translates plurals. * * Checks both singular+plural combinations as well as just singulars, * in case the translation file does not store the plural. * * @since 6.5.0 * * @param array{0: string, 1: string} $plurals { * Pair of singular and plural translations. * * @type string $0 Singular translation. * @type string $1 Plural translation. * } * @param int $number Number of items. * @param string $context Optional. Context for the string. Default empty string. * @param string $textdomain Optional. Text domain. Default 'default'. * @param string $locale Optional. Locale. Default current locale. * @return string|false Translation on success, false otherwise. */ public function translate_plural( array $plurals, int $number, string $context = '', string $textdomain = 'default', string $locale = null ) { if ( '' !== $context ) { $context .= "\4"; } $text = implode( "\0", $plurals ); $translation = $this->locate_translation( "{$context}{$text}", $textdomain, $locale ); if ( false === $translation ) { $text = $plurals[0]; $translation = $this->locate_translation( "{$context}{$text}", $textdomain, $locale ); if ( false === $translation ) { return false; } } /** @var WP_Translation_File $source */ $source = $translation['source']; $num = $source->get_plural_form( $number ); // See \Translations::translate_plural(). return $translation['entries'][ $num ] ?? $translation['entries'][0]; } /** * Returns all existing headers for a given text domain. * * @since 6.5.0 * * @param string $textdomain Optional. Text domain. Default 'default'. * @return array Headers. */ public function get_headers( string $textdomain = 'default' ): array { if ( array() === $this->loaded_translations ) { return array(); } $headers = array(); foreach ( $this->get_files( $textdomain ) as $moe ) { foreach ( $moe->headers() as $header => $value ) { $headers[ $this->normalize_header( $header ) ] = $value; } } return $headers; } /** * Normalizes header names to be capitalized. * * @since 6.5.0 * * @param string $header Header name. * @return string Normalized header name. */ protected function normalize_header( string $header ): string { $parts = explode( '-', $header ); $parts = array_map( 'ucfirst', $parts ); return implode( '-', $parts ); } /** * Returns all entries for a given text domain. * * @since 6.5.0 * * @param string $textdomain Optional. Text domain. Default 'default'. * @return array Entries. */ public function get_entries( string $textdomain = 'default' ): array { if ( array() === $this->loaded_translations ) { return array(); } $entries = array(); foreach ( $this->get_files( $textdomain ) as $moe ) { $entries = array_merge( $entries, $moe->entries() ); } return $entries; } /** * Locates translation for a given string and text domain. * * @since 6.5.0 * * @param string $singular Singular translation. * @param string $textdomain Optional. Text domain. Default 'default'. * @param string $locale Optional. Locale. Default current locale. * @return array{source: WP_Translation_File, entries: string[]}|false { * Translations on success, false otherwise. * * @type WP_Translation_File $source Translation file instance. * @type string[] $entries Array of translation entries. * } */ protected function locate_translation( string $singular, string $textdomain = 'default', string $locale = null ) { if ( array() === $this->loaded_translations ) { return false; } // Find the translation in all loaded files for this text domain. foreach ( $this->get_files( $textdomain, $locale ) as $moe ) { $translation = $moe->translate( $singular ); if ( false !== $translation ) { return array( 'entries' => explode( "\0", $translation ), 'source' => $moe, ); } if ( null !== $moe->error() ) { // Unload this file, something is wrong. $this->unload_file( $moe, $textdomain, $locale ); } } // Nothing could be found. return false; } /** * Returns all translation files for a given text domain. * * @since 6.5.0 * * @param string $textdomain Optional. Text domain. Default 'default'. * @param string $locale Optional. Locale. Default current locale. * @return WP_Translation_File[] List of translation files. */ protected function get_files( string $textdomain = 'default', string $locale = null ): array { if ( null === $locale ) { $locale = $this->current_locale; } return $this->loaded_translations[ $locale ][ $textdomain ] ?? array(); } } /** * Sets up the default filters and actions for most * of the WordPress hooks. * * If you need to remove a default hook, this file will * give you the priority to use for removing the hook. * * Not all of the default hooks are found in this file. * For instance, administration-related hooks are located in * wp-admin/includes/admin-filters.php. * * If a hook should only be called from a specific context * (admin area, multisite environment…), please move it * to a more appropriate file instead. * * @package WordPress */ // Strip, trim, kses, special chars for string saves. foreach ( array( 'pre_term_name', 'pre_comment_author_name', 'pre_link_name', 'pre_link_target', 'pre_link_rel', 'pre_user_display_name', 'pre_user_first_name', 'pre_user_last_name', 'pre_user_nickname' ) as $filter ) { add_filter( $filter, 'sanitize_text_field' ); add_filter( $filter, 'wp_filter_kses' ); add_filter( $filter, '_wp_specialchars', 30 ); } // Strip, kses, special chars for string display. foreach ( array( 'term_name', 'comment_author_name', 'link_name', 'link_target', 'link_rel', 'user_display_name', 'user_first_name', 'user_last_name', 'user_nickname' ) as $filter ) { if ( is_admin() ) { // These are expensive. Run only on admin pages for defense in depth. add_filter( $filter, 'sanitize_text_field' ); add_filter( $filter, 'wp_kses_data' ); } add_filter( $filter, '_wp_specialchars', 30 ); } // Kses only for textarea saves. foreach ( array( 'pre_term_description', 'pre_link_description', 'pre_link_notes', 'pre_user_description' ) as $filter ) { add_filter( $filter, 'wp_filter_kses' ); } // Kses only for textarea admin displays. if ( is_admin() ) { foreach ( array( 'term_description', 'link_description', 'link_notes', 'user_description' ) as $filter ) { add_filter( $filter, 'wp_kses_data' ); } add_filter( 'comment_text', 'wp_kses_post' ); } // Email saves. foreach ( array( 'pre_comment_author_email', 'pre_user_email' ) as $filter ) { add_filter( $filter, 'trim' ); add_filter( $filter, 'sanitize_email' ); add_filter( $filter, 'wp_filter_kses' ); } // Email admin display. foreach ( array( 'comment_author_email', 'user_email' ) as $filter ) { add_filter( $filter, 'sanitize_email' ); if ( is_admin() ) { add_filter( $filter, 'wp_kses_data' ); } } // Save URL. foreach ( array( 'pre_comment_author_url', 'pre_user_url', 'pre_link_url', 'pre_link_image', 'pre_link_rss', 'pre_post_guid', ) as $filter ) { add_filter( $filter, 'wp_strip_all_tags' ); add_filter( $filter, 'sanitize_url' ); add_filter( $filter, 'wp_filter_kses' ); } // Display URL. foreach ( array( 'user_url', 'link_url', 'link_image', 'link_rss', 'comment_url', 'post_guid' ) as $filter ) { if ( is_admin() ) { add_filter( $filter, 'wp_strip_all_tags' ); } add_filter( $filter, 'esc_url' ); if ( is_admin() ) { add_filter( $filter, 'wp_kses_data' ); } } // Slugs. add_filter( 'pre_term_slug', 'sanitize_title' ); add_filter( 'wp_insert_post_data', '_wp_customize_changeset_filter_insert_post_data', 10, 2 ); // Keys. foreach ( array( 'pre_post_type', 'pre_post_status', 'pre_post_comment_status', 'pre_post_ping_status' ) as $filter ) { add_filter( $filter, 'sanitize_key' ); } // Mime types. add_filter( 'pre_post_mime_type', 'sanitize_mime_type' ); add_filter( 'post_mime_type', 'sanitize_mime_type' ); // Meta. add_filter( 'register_meta_args', '_wp_register_meta_args_allowed_list', 10, 2 ); // Counts. add_action( 'admin_init', 'wp_schedule_update_user_counts' ); add_action( 'wp_update_user_counts', 'wp_schedule_update_user_counts', 10, 0 ); foreach ( array( 'user_register', 'deleted_user' ) as $action ) { add_action( $action, 'wp_maybe_update_user_counts', 10, 0 ); } // Post meta. add_action( 'added_post_meta', 'wp_cache_set_posts_last_changed' ); add_action( 'updated_post_meta', 'wp_cache_set_posts_last_changed' ); add_action( 'deleted_post_meta', 'wp_cache_set_posts_last_changed' ); // User meta. add_action( 'added_user_meta', 'wp_cache_set_users_last_changed' ); add_action( 'updated_user_meta', 'wp_cache_set_users_last_changed' ); add_action( 'deleted_user_meta', 'wp_cache_set_users_last_changed' ); add_action( 'add_user_role', 'wp_cache_set_users_last_changed' ); add_action( 'set_user_role', 'wp_cache_set_users_last_changed' ); add_action( 'remove_user_role', 'wp_cache_set_users_last_changed' ); // Term meta. add_action( 'added_term_meta', 'wp_cache_set_terms_last_changed' ); add_action( 'updated_term_meta', 'wp_cache_set_terms_last_changed' ); add_action( 'deleted_term_meta', 'wp_cache_set_terms_last_changed' ); add_filter( 'get_term_metadata', 'wp_check_term_meta_support_prefilter' ); add_filter( 'add_term_metadata', 'wp_check_term_meta_support_prefilter' ); add_filter( 'update_term_metadata', 'wp_check_term_meta_support_prefilter' ); add_filter( 'delete_term_metadata', 'wp_check_term_meta_support_prefilter' ); add_filter( 'get_term_metadata_by_mid', 'wp_check_term_meta_support_prefilter' ); add_filter( 'update_term_metadata_by_mid', 'wp_check_term_meta_support_prefilter' ); add_filter( 'delete_term_metadata_by_mid', 'wp_check_term_meta_support_prefilter' ); add_filter( 'update_term_metadata_cache', 'wp_check_term_meta_support_prefilter' ); // Comment meta. add_action( 'added_comment_meta', 'wp_cache_set_comments_last_changed' ); add_action( 'updated_comment_meta', 'wp_cache_set_comments_last_changed' ); add_action( 'deleted_comment_meta', 'wp_cache_set_comments_last_changed' ); // Places to balance tags on input. foreach ( array( 'content_save_pre', 'excerpt_save_pre', 'comment_save_pre', 'pre_comment_content' ) as $filter ) { add_filter( $filter, 'convert_invalid_entities' ); add_filter( $filter, 'balanceTags', 50 ); } // Add proper rel values for links with target. add_action( 'init', 'wp_init_targeted_link_rel_filters' ); // Format strings for display. foreach ( array( 'comment_author', 'term_name', 'link_name', 'link_description', 'link_notes', 'bloginfo', 'wp_title', 'document_title', 'widget_title' ) as $filter ) { add_filter( $filter, 'wptexturize' ); add_filter( $filter, 'convert_chars' ); add_filter( $filter, 'esc_html' ); } // Format WordPress. foreach ( array( 'the_content', 'the_title', 'wp_title', 'document_title' ) as $filter ) { add_filter( $filter, 'capital_P_dangit', 11 ); } add_filter( 'comment_text', 'capital_P_dangit', 31 ); // Format titles. foreach ( array( 'single_post_title', 'single_cat_title', 'single_tag_title', 'single_month_title', 'nav_menu_attr_title', 'nav_menu_description' ) as $filter ) { add_filter( $filter, 'wptexturize' ); add_filter( $filter, 'strip_tags' ); } // Format text area for display. foreach ( array( 'term_description', 'get_the_post_type_description' ) as $filter ) { add_filter( $filter, 'wptexturize' ); add_filter( $filter, 'convert_chars' ); add_filter( $filter, 'wpautop' ); add_filter( $filter, 'shortcode_unautop' ); } // Format for RSS. add_filter( 'term_name_rss', 'convert_chars' ); // Pre save hierarchy. add_filter( 'wp_insert_post_parent', 'wp_check_post_hierarchy_for_loops', 10, 2 ); add_filter( 'wp_update_term_parent', 'wp_check_term_hierarchy_for_loops', 10, 3 ); // Display filters. add_filter( 'the_title', 'wptexturize' ); add_filter( 'the_title', 'convert_chars' ); add_filter( 'the_title', 'trim' ); add_filter( 'the_content', 'do_blocks', 9 ); add_filter( 'the_content', 'wptexturize' ); add_filter( 'the_content', 'convert_smilies', 20 ); add_filter( 'the_content', 'wpautop' ); add_filter( 'the_content', 'shortcode_unautop' ); add_filter( 'the_content', 'prepend_attachment' ); add_filter( 'the_content', 'wp_replace_insecure_home_url' ); add_filter( 'the_content', 'do_shortcode', 11 ); // AFTER wpautop(). add_filter( 'the_content', 'wp_filter_content_tags', 12 ); // Runs after do_shortcode(). add_filter( 'the_excerpt', 'wptexturize' ); add_filter( 'the_excerpt', 'convert_smilies' ); add_filter( 'the_excerpt', 'convert_chars' ); add_filter( 'the_excerpt', 'wpautop' ); add_filter( 'the_excerpt', 'shortcode_unautop' ); add_filter( 'the_excerpt', 'wp_replace_insecure_home_url' ); add_filter( 'the_excerpt', 'wp_filter_content_tags', 12 ); add_filter( 'get_the_excerpt', 'wp_trim_excerpt', 10, 2 ); add_filter( 'the_post_thumbnail_caption', 'wptexturize' ); add_filter( 'the_post_thumbnail_caption', 'convert_smilies' ); add_filter( 'the_post_thumbnail_caption', 'convert_chars' ); add_filter( 'comment_text', 'wptexturize' ); add_filter( 'comment_text', 'convert_chars' ); add_filter( 'comment_text', 'make_clickable', 9 ); add_filter( 'comment_text', 'force_balance_tags', 25 ); add_filter( 'comment_text', 'convert_smilies', 20 ); add_filter( 'comment_text', 'wpautop', 30 ); add_filter( 'comment_excerpt', 'convert_chars' ); add_filter( 'list_cats', 'wptexturize' ); add_filter( 'wp_sprintf', 'wp_sprintf_l', 10, 2 ); add_filter( 'widget_text', 'balanceTags' ); add_filter( 'widget_text_content', 'capital_P_dangit', 11 ); add_filter( 'widget_text_content', 'wptexturize' ); add_filter( 'widget_text_content', 'convert_smilies', 20 ); add_filter( 'widget_text_content', 'wpautop' ); add_filter( 'widget_text_content', 'shortcode_unautop' ); add_filter( 'widget_text_content', 'wp_replace_insecure_home_url' ); add_filter( 'widget_text_content', 'do_shortcode', 11 ); // Runs after wpautop(); note that $post global will be null when shortcodes run. add_filter( 'widget_text_content', 'wp_filter_content_tags', 12 ); // Runs after do_shortcode(). add_filter( 'widget_block_content', 'do_blocks', 9 ); add_filter( 'widget_block_content', 'do_shortcode', 11 ); add_filter( 'widget_block_content', 'wp_filter_content_tags', 12 ); // Runs after do_shortcode(). add_filter( 'block_type_metadata', 'wp_migrate_old_typography_shape' ); add_filter( 'wp_get_custom_css', 'wp_replace_insecure_home_url' ); // RSS filters. add_filter( 'the_title_rss', 'strip_tags' ); add_filter( 'the_title_rss', 'ent2ncr', 8 ); add_filter( 'the_title_rss', 'esc_html' ); add_filter( 'the_content_rss', 'ent2ncr', 8 ); add_filter( 'the_content_feed', 'wp_staticize_emoji' ); add_filter( 'the_content_feed', '_oembed_filter_feed_content' ); add_filter( 'the_excerpt_rss', 'convert_chars' ); add_filter( 'the_excerpt_rss', 'ent2ncr', 8 ); add_filter( 'comment_author_rss', 'ent2ncr', 8 ); add_filter( 'comment_text_rss', 'ent2ncr', 8 ); add_filter( 'comment_text_rss', 'esc_html' ); add_filter( 'comment_text_rss', 'wp_staticize_emoji' ); add_filter( 'bloginfo_rss', 'ent2ncr', 8 ); add_filter( 'the_author', 'ent2ncr', 8 ); add_filter( 'the_guid', 'esc_url' ); // Email filters. add_filter( 'wp_mail', 'wp_staticize_emoji_for_email' ); // Robots filters. add_filter( 'wp_robots', 'wp_robots_noindex' ); add_filter( 'wp_robots', 'wp_robots_noindex_embeds' ); add_filter( 'wp_robots', 'wp_robots_noindex_search' ); add_filter( 'wp_robots', 'wp_robots_max_image_preview_large' ); // Mark site as no longer fresh. foreach ( array( 'publish_post', 'publish_page', 'wp_ajax_save-widget', 'wp_ajax_widgets-order', 'customize_save_after', 'rest_after_save_widget', 'rest_delete_widget', 'rest_save_sidebar', ) as $action ) { add_action( $action, '_delete_option_fresh_site', 0 ); } // Misc filters. add_filter( 'option_ping_sites', 'privacy_ping_filter' ); add_filter( 'option_blog_charset', '_wp_specialchars' ); // IMPORTANT: This must not be wp_specialchars() or esc_html() or it'll cause an infinite loop. add_filter( 'option_blog_charset', '_canonical_charset' ); add_filter( 'option_home', '_config_wp_home' ); add_filter( 'option_siteurl', '_config_wp_siteurl' ); add_filter( 'tiny_mce_before_init', '_mce_set_direction' ); add_filter( 'teeny_mce_before_init', '_mce_set_direction' ); add_filter( 'pre_kses', 'wp_pre_kses_less_than' ); add_filter( 'pre_kses', 'wp_pre_kses_block_attributes', 10, 3 ); add_filter( 'sanitize_title', 'sanitize_title_with_dashes', 10, 3 ); add_action( 'check_comment_flood', 'check_comment_flood_db', 10, 4 ); add_filter( 'comment_flood_filter', 'wp_throttle_comment_flood', 10, 3 ); add_filter( 'pre_comment_content', 'wp_rel_ugc', 15 ); add_filter( 'comment_email', 'antispambot' ); add_filter( 'option_tag_base', '_wp_filter_taxonomy_base' ); add_filter( 'option_category_base', '_wp_filter_taxonomy_base' ); add_filter( 'the_posts', '_close_comments_for_old_posts', 10, 2 ); add_filter( 'comments_open', '_close_comments_for_old_post', 10, 2 ); add_filter( 'pings_open', '_close_comments_for_old_post', 10, 2 ); add_filter( 'editable_slug', 'urldecode' ); add_filter( 'editable_slug', 'esc_textarea' ); add_filter( 'pingback_ping_source_uri', 'pingback_ping_source_uri' ); add_filter( 'xmlrpc_pingback_error', 'xmlrpc_pingback_error' ); add_filter( 'title_save_pre', 'trim' ); add_action( 'transition_comment_status', '_clear_modified_cache_on_transition_comment_status', 10, 2 ); add_filter( 'http_request_host_is_external', 'allowed_http_request_hosts', 10, 2 ); // REST API filters. add_action( 'xmlrpc_rsd_apis', 'rest_output_rsd' ); add_action( 'wp_head', 'rest_output_link_wp_head', 10, 0 ); add_action( 'template_redirect', 'rest_output_link_header', 11, 0 ); add_action( 'auth_cookie_malformed', 'rest_cookie_collect_status' ); add_action( 'auth_cookie_expired', 'rest_cookie_collect_status' ); add_action( 'auth_cookie_bad_username', 'rest_cookie_collect_status' ); add_action( 'auth_cookie_bad_hash', 'rest_cookie_collect_status' ); add_action( 'auth_cookie_valid', 'rest_cookie_collect_status' ); add_action( 'application_password_failed_authentication', 'rest_application_password_collect_status' ); add_action( 'application_password_did_authenticate', 'rest_application_password_collect_status', 10, 2 ); add_filter( 'rest_authentication_errors', 'rest_application_password_check_errors', 90 ); add_filter( 'rest_authentication_errors', 'rest_cookie_check_errors', 100 ); // Actions. add_action( 'wp_head', '_wp_render_title_tag', 1 ); add_action( 'wp_head', 'wp_enqueue_scripts', 1 ); add_action( 'wp_head', 'wp_resource_hints', 2 ); add_action( 'wp_head', 'wp_preload_resources', 1 ); add_action( 'wp_head', 'feed_links', 2 ); add_action( 'wp_head', 'feed_links_extra', 3 ); add_action( 'wp_head', 'rsd_link' ); add_action( 'wp_head', 'locale_stylesheet' ); add_action( 'publish_future_post', 'check_and_publish_future_post', 10, 1 ); add_action( 'wp_head', 'wp_robots', 1 ); add_action( 'wp_head', 'print_emoji_detection_script', 7 ); add_action( 'wp_head', 'wp_print_styles', 8 ); add_action( 'wp_head', 'wp_print_head_scripts', 9 ); add_action( 'wp_head', 'wp_generator' ); add_action( 'wp_head', 'rel_canonical' ); add_action( 'wp_head', 'wp_shortlink_wp_head', 10, 0 ); add_action( 'wp_head', 'wp_custom_css_cb', 101 ); add_action( 'wp_head', 'wp_site_icon', 99 ); add_action( 'wp_footer', 'wp_print_footer_scripts', 20 ); add_action( 'template_redirect', 'wp_shortlink_header', 11, 0 ); add_action( 'wp_print_footer_scripts', '_wp_footer_scripts' ); add_action( 'init', '_register_core_block_patterns_and_categories' ); add_action( 'init', 'check_theme_switched', 99 ); add_action( 'init', array( 'WP_Block_Supports', 'init' ), 22 ); add_action( 'switch_theme', 'wp_clean_theme_json_cache' ); add_action( 'start_previewing_theme', 'wp_clean_theme_json_cache' ); add_action( 'after_switch_theme', '_wp_menus_changed' ); add_action( 'after_switch_theme', '_wp_sidebars_changed' ); add_action( 'wp_enqueue_scripts', 'wp_enqueue_emoji_styles' ); add_action( 'wp_print_styles', 'print_emoji_styles' ); // Retained for backwards-compatibility. Unhooked by wp_enqueue_emoji_styles(). if ( isset( $_GET['replytocom'] ) ) { add_filter( 'wp_robots', 'wp_robots_no_robots' ); } // Login actions. add_action( 'login_head', 'wp_robots', 1 ); add_filter( 'login_head', 'wp_resource_hints', 8 ); add_action( 'login_head', 'wp_print_head_scripts', 9 ); add_action( 'login_head', 'print_admin_styles', 9 ); add_action( 'login_head', 'wp_site_icon', 99 ); add_action( 'login_footer', 'wp_print_footer_scripts', 20 ); add_action( 'login_init', 'send_frame_options_header', 10, 0 ); // Feed generator tags. foreach ( array( 'rss2_head', 'commentsrss2_head', 'rss_head', 'rdf_header', 'atom_head', 'comments_atom_head', 'opml_head', 'app_head' ) as $action ) { add_action( $action, 'the_generator' ); } // Feed Site Icon. add_action( 'atom_head', 'atom_site_icon' ); add_action( 'rss2_head', 'rss2_site_icon' ); // WP Cron. if ( ! defined( 'DOING_CRON' ) ) { add_action( 'init', 'wp_cron' ); } // HTTPS migration. add_action( 'update_option_home', 'wp_update_https_migration_required', 10, 2 ); // 2 Actions 2 Furious. add_action( 'do_feed_rdf', 'do_feed_rdf', 10, 0 ); add_action( 'do_feed_rss', 'do_feed_rss', 10, 0 ); add_action( 'do_feed_rss2', 'do_feed_rss2', 10, 1 ); add_action( 'do_feed_atom', 'do_feed_atom', 10, 1 ); add_action( 'do_pings', 'do_all_pings', 10, 0 ); add_action( 'do_all_pings', 'do_all_pingbacks', 10, 0 ); add_action( 'do_all_pings', 'do_all_enclosures', 10, 0 ); add_action( 'do_all_pings', 'do_all_trackbacks', 10, 0 ); add_action( 'do_all_pings', 'generic_ping', 10, 0 ); add_action( 'do_robots', 'do_robots' ); add_action( 'do_favicon', 'do_favicon' ); add_action( 'set_comment_cookies', 'wp_set_comment_cookies', 10, 3 ); add_action( 'sanitize_comment_cookies', 'sanitize_comment_cookies' ); add_action( 'init', 'smilies_init', 5 ); add_action( 'plugins_loaded', 'wp_maybe_load_widgets', 0 ); add_action( 'plugins_loaded', 'wp_maybe_load_embeds', 0 ); add_action( 'shutdown', 'wp_ob_end_flush_all', 1 ); // Create a revision whenever a post is updated. add_action( 'wp_after_insert_post', 'wp_save_post_revision_on_insert', 9, 3 ); add_action( 'post_updated', 'wp_save_post_revision', 10, 1 ); add_action( 'publish_post', '_publish_post_hook', 5, 1 ); add_action( 'transition_post_status', '_transition_post_status', 5, 3 ); add_action( 'transition_post_status', '_update_term_count_on_transition_post_status', 10, 3 ); add_action( 'comment_form', 'wp_comment_form_unfiltered_html_nonce' ); // Privacy. add_action( 'user_request_action_confirmed', '_wp_privacy_account_request_confirmed' ); add_action( 'user_request_action_confirmed', '_wp_privacy_send_request_confirmation_notification', 12 ); // After request marked as completed. add_filter( 'wp_privacy_personal_data_exporters', 'wp_register_comment_personal_data_exporter' ); add_filter( 'wp_privacy_personal_data_exporters', 'wp_register_media_personal_data_exporter' ); add_filter( 'wp_privacy_personal_data_exporters', 'wp_register_user_personal_data_exporter', 1 ); add_filter( 'wp_privacy_personal_data_erasers', 'wp_register_comment_personal_data_eraser' ); add_action( 'init', 'wp_schedule_delete_old_privacy_export_files' ); add_action( 'wp_privacy_delete_old_export_files', 'wp_privacy_delete_old_export_files' ); // Cron tasks. add_action( 'wp_scheduled_delete', 'wp_scheduled_delete' ); add_action( 'wp_scheduled_auto_draft_delete', 'wp_delete_auto_drafts' ); add_action( 'importer_scheduled_cleanup', 'wp_delete_attachment' ); add_action( 'upgrader_scheduled_cleanup', 'wp_delete_attachment' ); add_action( 'delete_expired_transients', 'delete_expired_transients' ); // Navigation menu actions. add_action( 'delete_post', '_wp_delete_post_menu_item' ); add_action( 'delete_term', '_wp_delete_tax_menu_item', 10, 3 ); add_action( 'transition_post_status', '_wp_auto_add_pages_to_menu', 10, 3 ); add_action( 'delete_post', '_wp_delete_customize_changeset_dependent_auto_drafts' ); // Post Thumbnail specific image filtering. add_action( 'begin_fetch_post_thumbnail_html', '_wp_post_thumbnail_class_filter_add' ); add_action( 'end_fetch_post_thumbnail_html', '_wp_post_thumbnail_class_filter_remove' ); add_action( 'begin_fetch_post_thumbnail_html', '_wp_post_thumbnail_context_filter_add' ); add_action( 'end_fetch_post_thumbnail_html', '_wp_post_thumbnail_context_filter_remove' ); // Redirect old slugs. add_action( 'template_redirect', 'wp_old_slug_redirect' ); add_action( 'post_updated', 'wp_check_for_changed_slugs', 12, 3 ); add_action( 'attachment_updated', 'wp_check_for_changed_slugs', 12, 3 ); // Redirect old dates. add_action( 'post_updated', 'wp_check_for_changed_dates', 12, 3 ); add_action( 'attachment_updated', 'wp_check_for_changed_dates', 12, 3 ); // Nonce check for post previews. add_action( 'init', '_show_post_preview' ); // Output JS to reset window.name for previews. add_action( 'wp_head', 'wp_post_preview_js', 1 ); // Timezone. add_filter( 'pre_option_gmt_offset', 'wp_timezone_override_offset' ); // If the upgrade hasn't run yet, assume link manager is used. add_filter( 'default_option_link_manager_enabled', '__return_true' ); // This option no longer exists; tell plugins we always support auto-embedding. add_filter( 'pre_option_embed_autourls', '__return_true' ); // Default settings for heartbeat. add_filter( 'heartbeat_settings', 'wp_heartbeat_settings' ); // Check if the user is logged out. add_action( 'admin_enqueue_scripts', 'wp_auth_check_load' ); add_filter( 'heartbeat_send', 'wp_auth_check' ); add_filter( 'heartbeat_nopriv_send', 'wp_auth_check' ); // Default authentication filters. add_filter( 'authenticate', 'wp_authenticate_username_password', 20, 3 ); add_filter( 'authenticate', 'wp_authenticate_email_password', 20, 3 ); add_filter( 'authenticate', 'wp_authenticate_application_password', 20, 3 ); add_filter( 'authenticate', 'wp_authenticate_spam_check', 99 ); add_filter( 'determine_current_user', 'wp_validate_auth_cookie' ); add_filter( 'determine_current_user', 'wp_validate_logged_in_cookie', 20 ); add_filter( 'determine_current_user', 'wp_validate_application_password', 20 ); // Split term updates. add_action( 'admin_init', '_wp_check_for_scheduled_split_terms' ); add_action( 'split_shared_term', '_wp_check_split_default_terms', 10, 4 ); add_action( 'split_shared_term', '_wp_check_split_terms_in_menus', 10, 4 ); add_action( 'split_shared_term', '_wp_check_split_nav_menu_terms', 10, 4 ); add_action( 'wp_split_shared_term_batch', '_wp_batch_split_terms' ); // Comment type updates. add_action( 'admin_init', '_wp_check_for_scheduled_update_comment_type' ); add_action( 'wp_update_comment_type_batch', '_wp_batch_update_comment_type' ); // Email notifications. add_action( 'comment_post', 'wp_new_comment_notify_moderator' ); add_action( 'comment_post', 'wp_new_comment_notify_postauthor' ); add_action( 'after_password_reset', 'wp_password_change_notification' ); add_action( 'register_new_user', 'wp_send_new_user_notifications' ); add_action( 'edit_user_created_user', 'wp_send_new_user_notifications', 10, 2 ); // REST API actions. add_action( 'init', 'rest_api_init' ); add_action( 'rest_api_init', 'rest_api_default_filters', 10, 1 ); add_action( 'rest_api_init', 'register_initial_settings', 10 ); add_action( 'rest_api_init', 'create_initial_rest_routes', 99 ); add_action( 'parse_request', 'rest_api_loaded' ); // Sitemaps actions. add_action( 'init', 'wp_sitemaps_get_server' ); /** * Filters formerly mixed into wp-includes. */ // Theme. add_action( 'setup_theme', 'create_initial_theme_features', 0 ); add_action( 'after_setup_theme', '_add_default_theme_supports', 1 ); add_action( 'wp_loaded', '_custom_header_background_just_in_time' ); add_action( 'wp_head', '_custom_logo_header_styles' ); add_action( 'plugins_loaded', '_wp_customize_include' ); add_action( 'transition_post_status', '_wp_customize_publish_changeset', 10, 3 ); add_action( 'admin_enqueue_scripts', '_wp_customize_loader_settings' ); add_action( 'delete_attachment', '_delete_attachment_theme_mod' ); add_action( 'transition_post_status', '_wp_keep_alive_customize_changeset_dependent_auto_drafts', 20, 3 ); // Block Theme Previews. add_action( 'plugins_loaded', 'wp_initialize_theme_preview_hooks', 1 ); // Calendar widget cache. add_action( 'save_post', 'delete_get_calendar_cache' ); add_action( 'delete_post', 'delete_get_calendar_cache' ); add_action( 'update_option_start_of_week', 'delete_get_calendar_cache' ); add_action( 'update_option_gmt_offset', 'delete_get_calendar_cache' ); // Author. add_action( 'transition_post_status', '__clear_multi_author_cache' ); // Post. add_action( 'init', 'create_initial_post_types', 0 ); // Highest priority. add_action( 'admin_menu', '_add_post_type_submenus' ); add_action( 'before_delete_post', '_reset_front_page_settings_for_post' ); add_action( 'wp_trash_post', '_reset_front_page_settings_for_post' ); add_action( 'change_locale', 'create_initial_post_types' ); // Post Formats. add_filter( 'request', '_post_format_request' ); add_filter( 'term_link', '_post_format_link', 10, 3 ); add_filter( 'get_post_format', '_post_format_get_term' ); add_filter( 'get_terms', '_post_format_get_terms', 10, 3 ); add_filter( 'wp_get_object_terms', '_post_format_wp_get_object_terms' ); // KSES. add_action( 'init', 'kses_init' ); add_action( 'set_current_user', 'kses_init' ); // Script Loader. add_action( 'wp_default_scripts', 'wp_default_scripts' ); add_action( 'wp_default_scripts', 'wp_default_packages' ); add_action( 'wp_enqueue_scripts', 'wp_localize_jquery_ui_datepicker', 1000 ); add_action( 'wp_enqueue_scripts', 'wp_common_block_scripts_and_styles' ); add_action( 'wp_enqueue_scripts', 'wp_enqueue_classic_theme_styles' ); add_action( 'admin_enqueue_scripts', 'wp_localize_jquery_ui_datepicker', 1000 ); add_action( 'admin_enqueue_scripts', 'wp_common_block_scripts_and_styles' ); add_action( 'enqueue_block_assets', 'wp_enqueue_registered_block_scripts_and_styles' ); add_action( 'enqueue_block_assets', 'enqueue_block_styles_assets', 30 ); /* * `wp_enqueue_registered_block_scripts_and_styles` is bound to both * `enqueue_block_editor_assets` and `enqueue_block_assets` hooks * since the introduction of the block editor in WordPress 5.0. * * The way this works is that the block assets are loaded before any other assets. * For example, this is the order of styles for the editor: * * - front styles registered for blocks, via `styles` handle (block.json) * - editor styles registered for blocks, via `editorStyles` handle (block.json) * - editor styles enqueued via `enqueue_block_editor_assets` hook * - front styles enqueued via `enqueue_block_assets` hook */ add_action( 'enqueue_block_editor_assets', 'wp_enqueue_registered_block_scripts_and_styles' ); add_action( 'enqueue_block_editor_assets', 'enqueue_editor_block_styles_assets' ); add_action( 'enqueue_block_editor_assets', 'wp_enqueue_editor_block_directory_assets' ); add_action( 'enqueue_block_editor_assets', 'wp_enqueue_editor_format_library_assets' ); add_action( 'enqueue_block_editor_assets', 'wp_enqueue_global_styles_css_custom_properties' ); add_action( 'wp_print_scripts', 'wp_just_in_time_script_localization' ); add_filter( 'print_scripts_array', 'wp_prototype_before_jquery' ); add_action( 'customize_controls_print_styles', 'wp_resource_hints', 1 ); add_action( 'admin_head', 'wp_check_widget_editor_deps' ); add_filter( 'block_editor_settings_all', 'wp_add_editor_classic_theme_styles' ); // Global styles can be enqueued in both the header and the footer. See https://core.trac.wordpress.org/ticket/53494. add_action( 'wp_enqueue_scripts', 'wp_enqueue_global_styles' ); add_action( 'wp_footer', 'wp_enqueue_global_styles', 1 ); // Global styles custom CSS. add_action( 'wp_enqueue_scripts', 'wp_enqueue_global_styles_custom_css' ); // Block supports, and other styles parsed and stored in the Style Engine. add_action( 'wp_enqueue_scripts', 'wp_enqueue_stored_styles' ); add_action( 'wp_footer', 'wp_enqueue_stored_styles', 1 ); add_action( 'wp_default_styles', 'wp_default_styles' ); add_filter( 'style_loader_src', 'wp_style_loader_src', 10, 2 ); add_action( 'wp_head', 'wp_maybe_inline_styles', 1 ); // Run for styles enqueued in . add_action( 'wp_footer', 'wp_maybe_inline_styles', 1 ); // Run for late-loaded styles in the footer. /* * Block specific actions and filters. */ // Footnotes Block. add_action( 'init', '_wp_footnotes_kses_init' ); add_action( 'set_current_user', '_wp_footnotes_kses_init' ); add_filter( 'force_filtered_html_on_import', '_wp_footnotes_force_filtered_html_on_import_filter', 999 ); /* * Disable "Post Attributes" for wp_navigation post type. The attributes are * also conditionally enabled when a site has custom templates. Block Theme * templates can be available for every post type. */ add_filter( 'theme_wp_navigation_templates', '__return_empty_array' ); // Taxonomy. add_action( 'init', 'create_initial_taxonomies', 0 ); // Highest priority. add_action( 'change_locale', 'create_initial_taxonomies' ); // Canonical. add_action( 'template_redirect', 'redirect_canonical' ); add_action( 'template_redirect', 'wp_redirect_admin_locations', 1000 ); // Media. add_action( 'wp_playlist_scripts', 'wp_playlist_scripts' ); add_action( 'customize_controls_enqueue_scripts', 'wp_plupload_default_settings' ); add_action( 'plugins_loaded', '_wp_add_additional_image_sizes', 0 ); add_filter( 'plupload_default_settings', 'wp_show_heic_upload_error' ); // Nav menu. add_filter( 'nav_menu_item_id', '_nav_menu_item_id_use_once', 10, 2 ); add_filter( 'nav_menu_css_class', 'wp_nav_menu_remove_menu_item_has_children_class', 10, 4 ); // Widgets. add_action( 'after_setup_theme', 'wp_setup_widgets_block_editor', 1 ); add_action( 'init', 'wp_widgets_init', 1 ); add_action( 'change_locale', array( 'WP_Widget_Media', 'reset_default_labels' ) ); add_action( 'widgets_init', '_wp_block_theme_register_classic_sidebars', 1 ); // Admin Bar. // Don't remove. Wrong way to disable. add_action( 'template_redirect', '_wp_admin_bar_init', 0 ); add_action( 'admin_init', '_wp_admin_bar_init' ); add_action( 'wp_enqueue_scripts', 'wp_enqueue_admin_bar_bump_styles' ); add_action( 'wp_enqueue_scripts', 'wp_enqueue_admin_bar_header_styles' ); add_action( 'admin_enqueue_scripts', 'wp_enqueue_admin_bar_header_styles' ); add_action( 'before_signup_header', '_wp_admin_bar_init' ); add_action( 'activate_header', '_wp_admin_bar_init' ); add_action( 'wp_body_open', 'wp_admin_bar_render', 0 ); add_action( 'wp_footer', 'wp_admin_bar_render', 1000 ); // Back-compat for themes not using `wp_body_open`. add_action( 'in_admin_header', 'wp_admin_bar_render', 0 ); // Former admin filters that can also be hooked on the front end. add_action( 'media_buttons', 'media_buttons' ); add_filter( 'image_send_to_editor', 'image_add_caption', 20, 8 ); add_filter( 'media_send_to_editor', 'image_media_send_to_editor', 10, 3 ); // Embeds. add_action( 'rest_api_init', 'wp_oembed_register_route' ); add_filter( 'rest_pre_serve_request', '_oembed_rest_pre_serve_request', 10, 4 ); add_action( 'wp_head', 'wp_oembed_add_discovery_links' ); add_action( 'wp_head', 'wp_oembed_add_host_js' ); // Back-compat for sites disabling oEmbed host JS by removing action. add_filter( 'embed_oembed_html', 'wp_maybe_enqueue_oembed_host_js' ); add_action( 'embed_head', 'enqueue_embed_scripts', 1 ); add_action( 'embed_head', 'print_emoji_detection_script' ); add_action( 'embed_head', 'wp_enqueue_embed_styles', 9 ); add_action( 'embed_head', 'print_embed_styles' ); // Retained for backwards-compatibility. Unhooked by wp_enqueue_embed_styles(). add_action( 'embed_head', 'wp_print_head_scripts', 20 ); add_action( 'embed_head', 'wp_print_styles', 20 ); add_action( 'embed_head', 'wp_robots' ); add_action( 'embed_head', 'rel_canonical' ); add_action( 'embed_head', 'locale_stylesheet', 30 ); add_action( 'enqueue_embed_scripts', 'wp_enqueue_emoji_styles' ); add_action( 'embed_content_meta', 'print_embed_comments_button' ); add_action( 'embed_content_meta', 'print_embed_sharing_button' ); add_action( 'embed_footer', 'print_embed_sharing_dialog' ); add_action( 'embed_footer', 'print_embed_scripts' ); add_action( 'embed_footer', 'wp_print_footer_scripts', 20 ); add_filter( 'excerpt_more', 'wp_embed_excerpt_more', 20 ); add_filter( 'the_excerpt_embed', 'wptexturize' ); add_filter( 'the_excerpt_embed', 'convert_chars' ); add_filter( 'the_excerpt_embed', 'wpautop' ); add_filter( 'the_excerpt_embed', 'shortcode_unautop' ); add_filter( 'the_excerpt_embed', 'wp_embed_excerpt_attachment' ); add_filter( 'oembed_dataparse', 'wp_filter_oembed_iframe_title_attribute', 5, 3 ); add_filter( 'oembed_dataparse', 'wp_filter_oembed_result', 10, 3 ); add_filter( 'oembed_response_data', 'get_oembed_response_data_rich', 10, 4 ); add_filter( 'pre_oembed_result', 'wp_filter_pre_oembed_result', 10, 3 ); // Capabilities. add_filter( 'user_has_cap', 'wp_maybe_grant_install_languages_cap', 1 ); add_filter( 'user_has_cap', 'wp_maybe_grant_resume_extensions_caps', 1 ); add_filter( 'user_has_cap', 'wp_maybe_grant_site_health_caps', 1, 4 ); // Block templates post type and rendering. add_filter( 'render_block_context', '_block_template_render_without_post_block_context' ); add_filter( 'pre_wp_unique_post_slug', 'wp_filter_wp_template_unique_post_slug', 10, 5 ); add_action( 'save_post_wp_template_part', 'wp_set_unique_slug_on_create_template_part' ); add_action( 'wp_enqueue_scripts', 'wp_enqueue_block_template_skip_link' ); add_action( 'wp_footer', 'the_block_template_skip_link' ); // Retained for backwards-compatibility. Unhooked by wp_enqueue_block_template_skip_link(). add_action( 'after_setup_theme', 'wp_enable_block_templates', 1 ); add_action( 'wp_loaded', '_add_template_loader_filters' ); // wp_navigation post type. add_filter( 'rest_wp_navigation_item_schema', array( 'WP_Navigation_Fallback', 'update_wp_navigation_post_schema' ) ); // Fluid typography. add_filter( 'render_block', 'wp_render_typography_support', 10, 2 ); // User preferences. add_action( 'init', 'wp_register_persisted_preferences_meta' ); // CPT wp_block custom postmeta field. add_action( 'init', 'wp_create_initial_post_meta' ); // Include revisioned meta when considering whether a post revision has changed. add_filter( 'wp_save_post_revision_post_has_changed', 'wp_check_revisioned_meta_fields_have_changed', 10, 3 ); // Save revisioned post meta immediately after a revision is saved add_action( '_wp_put_post_revision', 'wp_save_revisioned_meta_fields', 10, 2 ); // Include revisioned meta when creating or updating an autosave revision. add_action( 'wp_creating_autosave', 'wp_autosave_post_revisioned_meta_fields' ); // When restoring revisions, also restore revisioned meta. add_action( 'wp_restore_post_revision', 'wp_restore_post_revision_meta', 10, 2 ); // Font management. add_action( 'wp_head', 'wp_print_font_faces', 50 ); add_action( 'deleted_post', '_wp_after_delete_font_family', 10, 2 ); add_action( 'before_delete_post', '_wp_before_delete_font_face', 10, 2 ); add_action( 'init', '_wp_register_default_font_collections' ); // Add ignoredHookedBlocks metadata attribute to the template and template part post types. add_filter( 'rest_pre_insert_wp_template', 'inject_ignored_hooked_blocks_metadata_attributes' ); add_filter( 'rest_pre_insert_wp_template_part', 'inject_ignored_hooked_blocks_metadata_attributes' ); unset( $filter, $action ); /** * Locale API: WP_Locale class * * @package WordPress * @subpackage i18n * @since 4.6.0 */ /** * Core class used to store translated data for a locale. * * @since 2.1.0 * @since 4.6.0 Moved to its own file from wp-includes/locale.php. */ #[AllowDynamicProperties] class WP_Locale { /** * Stores the translated strings for the full weekday names. * * @since 2.1.0 * @since 6.2.0 Initialized to an empty array. * @var string[] */ public $weekday = array(); /** * Stores the translated strings for the one character weekday names. * * There is a hack to make sure that Tuesday and Thursday, as well * as Sunday and Saturday, don't conflict. See init() method for more. * * @see WP_Locale::init() for how to handle the hack. * * @since 2.1.0 * @since 6.2.0 Initialized to an empty array. * @var string[] */ public $weekday_initial = array(); /** * Stores the translated strings for the abbreviated weekday names. * * @since 2.1.0 * @since 6.2.0 Initialized to an empty array. * @var string[] */ public $weekday_abbrev = array(); /** * Stores the translated strings for the full month names. * * @since 2.1.0 * @since 6.2.0 Initialized to an empty array. * @var string[] */ public $month = array(); /** * Stores the translated strings for the month names in genitive case, if the locale specifies. * * @since 4.4.0 * @since 6.2.0 Initialized to an empty array. * @var string[] */ public $month_genitive = array(); /** * Stores the translated strings for the abbreviated month names. * * @since 2.1.0 * @since 6.2.0 Initialized to an empty array. * @var string[] */ public $month_abbrev = array(); /** * Stores the translated strings for 'am' and 'pm'. * * Also the capitalized versions. * * @since 2.1.0 * @since 6.2.0 Initialized to an empty array. * @var string[] */ public $meridiem = array(); /** * The text direction of the locale language. * * Default is left to right 'ltr'. * * @since 2.1.0 * @var string */ public $text_direction = 'ltr'; /** * The thousands separator and decimal point values used for localizing numbers. * * @since 2.3.0 * @since 6.2.0 Initialized to an empty array. * @var array */ public $number_format = array(); /** * The separator string used for localizing list item separator. * * @since 6.0.0 * @var string */ public $list_item_separator; /** * The word count type of the locale language. * * Default is 'words'. * * @since 6.2.0 * @var string */ public $word_count_type; /** * Constructor which calls helper methods to set up object variables. * * @since 2.1.0 */ public function __construct() { $this->init(); $this->register_globals(); } /** * Sets up the translated strings and object properties. * * The method creates the translatable strings for various * calendar elements. Which allows for specifying locale * specific calendar names and text direction. * * @since 2.1.0 * * @global string $text_direction */ public function init() { // The weekdays. $this->weekday[0] = /* translators: Weekday. */ __( 'Sunday' ); $this->weekday[1] = /* translators: Weekday. */ __( 'Monday' ); $this->weekday[2] = /* translators: Weekday. */ __( 'Tuesday' ); $this->weekday[3] = /* translators: Weekday. */ __( 'Wednesday' ); $this->weekday[4] = /* translators: Weekday. */ __( 'Thursday' ); $this->weekday[5] = /* translators: Weekday. */ __( 'Friday' ); $this->weekday[6] = /* translators: Weekday. */ __( 'Saturday' ); // The first letter of each day. $this->weekday_initial[ $this->weekday[0] ] = /* translators: One-letter abbreviation of the weekday. */ _x( 'S', 'Sunday initial' ); $this->weekday_initial[ $this->weekday[1] ] = /* translators: One-letter abbreviation of the weekday. */ _x( 'M', 'Monday initial' ); $this->weekday_initial[ $this->weekday[2] ] = /* translators: One-letter abbreviation of the weekday. */ _x( 'T', 'Tuesday initial' ); $this->weekday_initial[ $this->weekday[3] ] = /* translators: One-letter abbreviation of the weekday. */ _x( 'W', 'Wednesday initial' ); $this->weekday_initial[ $this->weekday[4] ] = /* translators: One-letter abbreviation of the weekday. */ _x( 'T', 'Thursday initial' ); $this->weekday_initial[ $this->weekday[5] ] = /* translators: One-letter abbreviation of the weekday. */ _x( 'F', 'Friday initial' ); $this->weekday_initial[ $this->weekday[6] ] = /* translators: One-letter abbreviation of the weekday. */ _x( 'S', 'Saturday initial' ); // Abbreviations for each day. $this->weekday_abbrev[ $this->weekday[0] ] = /* translators: Three-letter abbreviation of the weekday. */ __( 'Sun' ); $this->weekday_abbrev[ $this->weekday[1] ] = /* translators: Three-letter abbreviation of the weekday. */ __( 'Mon' ); $this->weekday_abbrev[ $this->weekday[2] ] = /* translators: Three-letter abbreviation of the weekday. */ __( 'Tue' ); $this->weekday_abbrev[ $this->weekday[3] ] = /* translators: Three-letter abbreviation of the weekday. */ __( 'Wed' ); $this->weekday_abbrev[ $this->weekday[4] ] = /* translators: Three-letter abbreviation of the weekday. */ __( 'Thu' ); $this->weekday_abbrev[ $this->weekday[5] ] = /* translators: Three-letter abbreviation of the weekday. */ __( 'Fri' ); $this->weekday_abbrev[ $this->weekday[6] ] = /* translators: Three-letter abbreviation of the weekday. */ __( 'Sat' ); // The months. $this->month['01'] = /* translators: Month name. */ __( 'January' ); $this->month['02'] = /* translators: Month name. */ __( 'February' ); $this->month['03'] = /* translators: Month name. */ __( 'March' ); $this->month['04'] = /* translators: Month name. */ __( 'April' ); $this->month['05'] = /* translators: Month name. */ __( 'May' ); $this->month['06'] = /* translators: Month name. */ __( 'June' ); $this->month['07'] = /* translators: Month name. */ __( 'July' ); $this->month['08'] = /* translators: Month name. */ __( 'August' ); $this->month['09'] = /* translators: Month name. */ __( 'September' ); $this->month['10'] = /* translators: Month name. */ __( 'October' ); $this->month['11'] = /* translators: Month name. */ __( 'November' ); $this->month['12'] = /* translators: Month name. */ __( 'December' ); // The months, genitive. $this->month_genitive['01'] = /* translators: Month name, genitive. */ _x( 'January', 'genitive' ); $this->month_genitive['02'] = /* translators: Month name, genitive. */ _x( 'February', 'genitive' ); $this->month_genitive['03'] = /* translators: Month name, genitive. */ _x( 'March', 'genitive' ); $this->month_genitive['04'] = /* translators: Month name, genitive. */ _x( 'April', 'genitive' ); $this->month_genitive['05'] = /* translators: Month name, genitive. */ _x( 'May', 'genitive' ); $this->month_genitive['06'] = /* translators: Month name, genitive. */ _x( 'June', 'genitive' ); $this->month_genitive['07'] = /* translators: Month name, genitive. */ _x( 'July', 'genitive' ); $this->month_genitive['08'] = /* translators: Month name, genitive. */ _x( 'August', 'genitive' ); $this->month_genitive['09'] = /* translators: Month name, genitive. */ _x( 'September', 'genitive' ); $this->month_genitive['10'] = /* translators: Month name, genitive. */ _x( 'October', 'genitive' ); $this->month_genitive['11'] = /* translators: Month name, genitive. */ _x( 'November', 'genitive' ); $this->month_genitive['12'] = /* translators: Month name, genitive. */ _x( 'December', 'genitive' ); // Abbreviations for each month. $this->month_abbrev[ $this->month['01'] ] = /* translators: Three-letter abbreviation of the month. */ _x( 'Jan', 'January abbreviation' ); $this->month_abbrev[ $this->month['02'] ] = /* translators: Three-letter abbreviation of the month. */ _x( 'Feb', 'February abbreviation' ); $this->month_abbrev[ $this->month['03'] ] = /* translators: Three-letter abbreviation of the month. */ _x( 'Mar', 'March abbreviation' ); $this->month_abbrev[ $this->month['04'] ] = /* translators: Three-letter abbreviation of the month. */ _x( 'Apr', 'April abbreviation' ); $this->month_abbrev[ $this->month['05'] ] = /* translators: Three-letter abbreviation of the month. */ _x( 'May', 'May abbreviation' ); $this->month_abbrev[ $this->month['06'] ] = /* translators: Three-letter abbreviation of the month. */ _x( 'Jun', 'June abbreviation' ); $this->month_abbrev[ $this->month['07'] ] = /* translators: Three-letter abbreviation of the month. */ _x( 'Jul', 'July abbreviation' ); $this->month_abbrev[ $this->month['08'] ] = /* translators: Three-letter abbreviation of the month. */ _x( 'Aug', 'August abbreviation' ); $this->month_abbrev[ $this->month['09'] ] = /* translators: Three-letter abbreviation of the month. */ _x( 'Sep', 'September abbreviation' ); $this->month_abbrev[ $this->month['10'] ] = /* translators: Three-letter abbreviation of the month. */ _x( 'Oct', 'October abbreviation' ); $this->month_abbrev[ $this->month['11'] ] = /* translators: Three-letter abbreviation of the month. */ _x( 'Nov', 'November abbreviation' ); $this->month_abbrev[ $this->month['12'] ] = /* translators: Three-letter abbreviation of the month. */ _x( 'Dec', 'December abbreviation' ); // The meridiems. $this->meridiem['am'] = __( 'am' ); $this->meridiem['pm'] = __( 'pm' ); $this->meridiem['AM'] = __( 'AM' ); $this->meridiem['PM'] = __( 'PM' ); /* * Numbers formatting. * See https://www.php.net/number_format */ /* translators: $thousands_sep argument for https://www.php.net/number_format, default is ',' */ $thousands_sep = __( 'number_format_thousands_sep' ); // Replace space with a non-breaking space to avoid wrapping. $thousands_sep = str_replace( ' ', ' ', $thousands_sep ); $this->number_format['thousands_sep'] = ( 'number_format_thousands_sep' === $thousands_sep ) ? ',' : $thousands_sep; /* translators: $dec_point argument for https://www.php.net/number_format, default is '.' */ $decimal_point = __( 'number_format_decimal_point' ); $this->number_format['decimal_point'] = ( 'number_format_decimal_point' === $decimal_point ) ? '.' : $decimal_point; /* translators: Used between list items, there is a space after the comma. */ $this->list_item_separator = __( ', ' ); // Set text direction. if ( isset( $GLOBALS['text_direction'] ) ) { $this->text_direction = $GLOBALS['text_direction']; /* translators: 'rtl' or 'ltr'. This sets the text direction for WordPress. */ } elseif ( 'rtl' === _x( 'ltr', 'text direction' ) ) { $this->text_direction = 'rtl'; } // Set the word count type. $this->word_count_type = $this->get_word_count_type(); } /** * Retrieves the full translated weekday word. * * Week starts on translated Sunday and can be fetched * by using 0 (zero). So the week starts with 0 (zero) * and ends on Saturday with is fetched by using 6 (six). * * @since 2.1.0 * * @param int $weekday_number 0 for Sunday through 6 Saturday. * @return string Full translated weekday. */ public function get_weekday( $weekday_number ) { return $this->weekday[ $weekday_number ]; } /** * Retrieves the translated weekday initial. * * The weekday initial is retrieved by the translated * full weekday word. When translating the weekday initial * pay attention to make sure that the starting letter does * not conflict. * * @since 2.1.0 * * @param string $weekday_name Full translated weekday word. * @return string Translated weekday initial. */ public function get_weekday_initial( $weekday_name ) { return $this->weekday_initial[ $weekday_name ]; } /** * Retrieves the translated weekday abbreviation. * * The weekday abbreviation is retrieved by the translated * full weekday word. * * @since 2.1.0 * * @param string $weekday_name Full translated weekday word. * @return string Translated weekday abbreviation. */ public function get_weekday_abbrev( $weekday_name ) { return $this->weekday_abbrev[ $weekday_name ]; } /** * Retrieves the full translated month by month number. * * The $month_number parameter has to be a string * because it must have the '0' in front of any number * that is less than 10. Starts from '01' and ends at * '12'. * * You can use an integer instead and it will add the * '0' before the numbers less than 10 for you. * * @since 2.1.0 * * @param string|int $month_number '01' through '12'. * @return string Translated full month name. */ public function get_month( $month_number ) { return $this->month[ zeroise( $month_number, 2 ) ]; } /** * Retrieves translated version of month abbreviation string. * * The $month_name parameter is expected to be the translated or * translatable version of the month. * * @since 2.1.0 * * @param string $month_name Translated month to get abbreviated version. * @return string Translated abbreviated month. */ public function get_month_abbrev( $month_name ) { return $this->month_abbrev[ $month_name ]; } /** * Retrieves translated version of meridiem string. * * The $meridiem parameter is expected to not be translated. * * @since 2.1.0 * * @param string $meridiem Either 'am', 'pm', 'AM', or 'PM'. Not translated version. * @return string Translated version */ public function get_meridiem( $meridiem ) { return $this->meridiem[ $meridiem ]; } /** * Global variables are deprecated. * * For backward compatibility only. * * @deprecated For backward compatibility only. * * @global array $weekday * @global array $weekday_initial * @global array $weekday_abbrev * @global array $month * @global array $month_abbrev * * @since 2.1.0 */ public function register_globals() { $GLOBALS['weekday'] = $this->weekday; $GLOBALS['weekday_initial'] = $this->weekday_initial; $GLOBALS['weekday_abbrev'] = $this->weekday_abbrev; $GLOBALS['month'] = $this->month; $GLOBALS['month_abbrev'] = $this->month_abbrev; } /** * Checks if current locale is RTL. * * @since 3.0.0 * @return bool Whether locale is RTL. */ public function is_rtl() { return 'rtl' === $this->text_direction; } /** * Registers date/time format strings for general POT. * * Private, unused method to add some date/time formats translated * on wp-admin/options-general.php to the general POT that would * otherwise be added to the admin POT. * * @since 3.6.0 */ public function _strings_for_pot() { /* translators: Localized date format, see https://www.php.net/manual/datetime.format.php */ __( 'F j, Y' ); /* translators: Localized time format, see https://www.php.net/manual/datetime.format.php */ __( 'g:i a' ); /* translators: Localized date and time format, see https://www.php.net/manual/datetime.format.php */ __( 'F j, Y g:i a' ); } /** * Retrieves the localized list item separator. * * @since 6.0.0 * * @return string Localized list item separator. */ public function get_list_item_separator() { return $this->list_item_separator; } /** * Retrieves the localized word count type. * * @since 6.2.0 * * @return string Localized word count type. Possible values are `characters_excluding_spaces`, * `characters_including_spaces`, or `words`. Defaults to `words`. */ public function get_word_count_type() { /* * translators: If your word count is based on single characters (e.g. East Asian characters), * enter 'characters_excluding_spaces' or 'characters_including_spaces'. Otherwise, enter 'words'. * Do not translate into your own language. */ $word_count_type = is_null( $this->word_count_type ) ? _x( 'words', 'Word count type. Do not translate!' ) : $this->word_count_type; // Check for valid types. if ( 'characters_excluding_spaces' !== $word_count_type && 'characters_including_spaces' !== $word_count_type ) { // Defaults to 'words'. $word_count_type = 'words'; } return $word_count_type; } } /** * A class for displaying various tree-like structures. * * Extend the Walker class to use it, see examples below. Child classes * do not need to implement all of the abstract methods in the class. The child * only needs to implement the methods that are needed. * * @since 2.1.0 * * @package WordPress * @abstract */ #[AllowDynamicProperties] class Walker { /** * What the class handles. * * @since 2.1.0 * @var string */ public $tree_type; /** * DB fields to use. * * @since 2.1.0 * @var string[] */ public $db_fields; /** * Max number of pages walked by the paged walker. * * @since 2.7.0 * @var int */ public $max_pages = 1; /** * Whether the current element has children or not. * * To be used in start_el(). * * @since 4.0.0 * @var bool */ public $has_children; /** * Starts the list before the elements are added. * * The $args parameter holds additional values that may be used with the child * class methods. This method is called at the start of the output list. * * @since 2.1.0 * @abstract * * @param string $output Used to append additional content (passed by reference). * @param int $depth Depth of the item. * @param array $args An array of additional arguments. */ public function start_lvl( &$output, $depth = 0, $args = array() ) {} /** * Ends the list of after the elements are added. * * The $args parameter holds additional values that may be used with the child * class methods. This method finishes the list at the end of output of the elements. * * @since 2.1.0 * @abstract * * @param string $output Used to append additional content (passed by reference). * @param int $depth Depth of the item. * @param array $args An array of additional arguments. */ public function end_lvl( &$output, $depth = 0, $args = array() ) {} /** * Starts the element output. * * The $args parameter holds additional values that may be used with the child * class methods. Also includes the element output. * * @since 2.1.0 * @since 5.9.0 Renamed `$object` (a PHP reserved keyword) to `$data_object` for PHP 8 named parameter support. * @abstract * * @param string $output Used to append additional content (passed by reference). * @param object $data_object The data object. * @param int $depth Depth of the item. * @param array $args An array of additional arguments. * @param int $current_object_id Optional. ID of the current item. Default 0. */ public function start_el( &$output, $data_object, $depth = 0, $args = array(), $current_object_id = 0 ) {} /** * Ends the element output, if needed. * * The $args parameter holds additional values that may be used with the child class methods. * * @since 2.1.0 * @since 5.9.0 Renamed `$object` (a PHP reserved keyword) to `$data_object` for PHP 8 named parameter support. * @abstract * * @param string $output Used to append additional content (passed by reference). * @param object $data_object The data object. * @param int $depth Depth of the item. * @param array $args An array of additional arguments. */ public function end_el( &$output, $data_object, $depth = 0, $args = array() ) {} /** * Traverses elements to create list from elements. * * Display one element if the element doesn't have any children otherwise, * display the element and its children. Will only traverse up to the max * depth and no ignore elements under that depth. It is possible to set the * max depth to include all depths, see walk() method. * * This method should not be called directly, use the walk() method instead. * * @since 2.5.0 * * @param object $element Data object. * @param array $children_elements List of elements to continue traversing (passed by reference). * @param int $max_depth Max depth to traverse. * @param int $depth Depth of current element. * @param array $args An array of arguments. * @param string $output Used to append additional content (passed by reference). */ public function display_element( $element, &$children_elements, $max_depth, $depth, $args, &$output ) { if ( ! $element ) { return; } $id_field = $this->db_fields['id']; $id = $element->$id_field; // Display this element. $this->has_children = ! empty( $children_elements[ $id ] ); if ( isset( $args[0] ) && is_array( $args[0] ) ) { $args[0]['has_children'] = $this->has_children; // Back-compat. } $this->start_el( $output, $element, $depth, ...array_values( $args ) ); // Descend only when the depth is right and there are children for this element. if ( ( 0 == $max_depth || $max_depth > $depth + 1 ) && isset( $children_elements[ $id ] ) ) { foreach ( $children_elements[ $id ] as $child ) { if ( ! isset( $newlevel ) ) { $newlevel = true; // Start the child delimiter. $this->start_lvl( $output, $depth, ...array_values( $args ) ); } $this->display_element( $child, $children_elements, $max_depth, $depth + 1, $args, $output ); } unset( $children_elements[ $id ] ); } if ( isset( $newlevel ) && $newlevel ) { // End the child delimiter. $this->end_lvl( $output, $depth, ...array_values( $args ) ); } // End this element. $this->end_el( $output, $element, $depth, ...array_values( $args ) ); } /** * Displays array of elements hierarchically. * * Does not assume any existing order of elements. * * $max_depth = -1 means flatly display every element. * $max_depth = 0 means display all levels. * $max_depth > 0 specifies the number of display levels. * * @since 2.1.0 * @since 5.3.0 Formalized the existing `...$args` parameter by adding it * to the function signature. * * @param array $elements An array of elements. * @param int $max_depth The maximum hierarchical depth. * @param mixed ...$args Optional additional arguments. * @return string The hierarchical item output. */ public function walk( $elements, $max_depth, ...$args ) { $output = ''; // Invalid parameter or nothing to walk. if ( $max_depth < -1 || empty( $elements ) ) { return $output; } $parent_field = $this->db_fields['parent']; // Flat display. if ( -1 == $max_depth ) { $empty_array = array(); foreach ( $elements as $e ) { $this->display_element( $e, $empty_array, 1, 0, $args, $output ); } return $output; } /* * Need to display in hierarchical order. * Separate elements into two buckets: top level and children elements. * Children_elements is two dimensional array. Example: * Children_elements[10][] contains all sub-elements whose parent is 10. */ $top_level_elements = array(); $children_elements = array(); foreach ( $elements as $e ) { if ( empty( $e->$parent_field ) ) { $top_level_elements[] = $e; } else { $children_elements[ $e->$parent_field ][] = $e; } } /* * When none of the elements is top level. * Assume the first one must be root of the sub elements. */ if ( empty( $top_level_elements ) ) { $first = array_slice( $elements, 0, 1 ); $root = $first[0]; $top_level_elements = array(); $children_elements = array(); foreach ( $elements as $e ) { if ( $root->$parent_field == $e->$parent_field ) { $top_level_elements[] = $e; } else { $children_elements[ $e->$parent_field ][] = $e; } } } foreach ( $top_level_elements as $e ) { $this->display_element( $e, $children_elements, $max_depth, 0, $args, $output ); } /* * If we are displaying all levels, and remaining children_elements is not empty, * then we got orphans, which should be displayed regardless. */ if ( ( 0 == $max_depth ) && count( $children_elements ) > 0 ) { $empty_array = array(); foreach ( $children_elements as $orphans ) { foreach ( $orphans as $op ) { $this->display_element( $op, $empty_array, 1, 0, $args, $output ); } } } return $output; } /** * Produces a page of nested elements. * * Given an array of hierarchical elements, the maximum depth, a specific page number, * and number of elements per page, this function first determines all top level root elements * belonging to that page, then lists them and all of their children in hierarchical order. * * $max_depth = 0 means display all levels. * $max_depth > 0 specifies the number of display levels. * * @since 2.7.0 * @since 5.3.0 Formalized the existing `...$args` parameter by adding it * to the function signature. * * @param array $elements An array of elements. * @param int $max_depth The maximum hierarchical depth. * @param int $page_num The specific page number, beginning with 1. * @param int $per_page Number of elements per page. * @param mixed ...$args Optional additional arguments. * @return string XHTML of the specified page of elements. */ public function paged_walk( $elements, $max_depth, $page_num, $per_page, ...$args ) { if ( empty( $elements ) || $max_depth < -1 ) { return ''; } $output = ''; $parent_field = $this->db_fields['parent']; $count = -1; if ( -1 == $max_depth ) { $total_top = count( $elements ); } if ( $page_num < 1 || $per_page < 0 ) { // No paging. $paging = false; $start = 0; if ( -1 == $max_depth ) { $end = $total_top; } $this->max_pages = 1; } else { $paging = true; $start = ( (int) $page_num - 1 ) * (int) $per_page; $end = $start + $per_page; if ( -1 == $max_depth ) { $this->max_pages = (int) ceil( $total_top / $per_page ); } } // Flat display. if ( -1 == $max_depth ) { if ( ! empty( $args[0]['reverse_top_level'] ) ) { $elements = array_reverse( $elements ); $oldstart = $start; $start = $total_top - $end; $end = $total_top - $oldstart; } $empty_array = array(); foreach ( $elements as $e ) { ++$count; if ( $count < $start ) { continue; } if ( $count >= $end ) { break; } $this->display_element( $e, $empty_array, 1, 0, $args, $output ); } return $output; } /* * Separate elements into two buckets: top level and children elements. * Children_elements is two dimensional array, e.g. * $children_elements[10][] contains all sub-elements whose parent is 10. */ $top_level_elements = array(); $children_elements = array(); foreach ( $elements as $e ) { if ( empty( $e->$parent_field ) ) { $top_level_elements[] = $e; } else { $children_elements[ $e->$parent_field ][] = $e; } } $total_top = count( $top_level_elements ); if ( $paging ) { $this->max_pages = (int) ceil( $total_top / $per_page ); } else { $end = $total_top; } if ( ! empty( $args[0]['reverse_top_level'] ) ) { $top_level_elements = array_reverse( $top_level_elements ); $oldstart = $start; $start = $total_top - $end; $end = $total_top - $oldstart; } if ( ! empty( $args[0]['reverse_children'] ) ) { foreach ( $children_elements as $parent => $children ) { $children_elements[ $parent ] = array_reverse( $children ); } } foreach ( $top_level_elements as $e ) { ++$count; // For the last page, need to unset earlier children in order to keep track of orphans. if ( $end >= $total_top && $count < $start ) { $this->unset_children( $e, $children_elements ); } if ( $count < $start ) { continue; } if ( $count >= $end ) { break; } $this->display_element( $e, $children_elements, $max_depth, 0, $args, $output ); } if ( $end >= $total_top && count( $children_elements ) > 0 ) { $empty_array = array(); foreach ( $children_elements as $orphans ) { foreach ( $orphans as $op ) { $this->display_element( $op, $empty_array, 1, 0, $args, $output ); } } } return $output; } /** * Calculates the total number of root elements. * * @since 2.7.0 * * @param array $elements Elements to list. * @return int Number of root elements. */ public function get_number_of_root_elements( $elements ) { $num = 0; $parent_field = $this->db_fields['parent']; foreach ( $elements as $e ) { if ( empty( $e->$parent_field ) ) { ++$num; } } return $num; } /** * Unsets all the children for a given top level element. * * @since 2.7.0 * * @param object $element The top level element. * @param array $children_elements The children elements. */ public function unset_children( $element, &$children_elements ) { if ( ! $element || ! $children_elements ) { return; } $id_field = $this->db_fields['id']; $id = $element->$id_field; if ( ! empty( $children_elements[ $id ] ) && is_array( $children_elements[ $id ] ) ) { foreach ( (array) $children_elements[ $id ] as $child ) { $this->unset_children( $child, $children_elements ); } } unset( $children_elements[ $id ] ); } } /** * Core User Role & Capabilities API * * @package WordPress * @subpackage Users */ /** * Maps a capability to the primitive capabilities required of the given user to * satisfy the capability being checked. * * This function also accepts an ID of an object to map against if the capability is a meta capability. Meta * capabilities such as `edit_post` and `edit_user` are capabilities used by this function to map to primitive * capabilities that a user or role requires, such as `edit_posts` and `edit_others_posts`. * * Example usage: * * map_meta_cap( 'edit_posts', $user->ID ); * map_meta_cap( 'edit_post', $user->ID, $post->ID ); * map_meta_cap( 'edit_post_meta', $user->ID, $post->ID, $meta_key ); * * This function does not check whether the user has the required capabilities, * it just returns what the required capabilities are. * * @since 2.0.0 * @since 4.9.6 Added the `export_others_personal_data`, `erase_others_personal_data`, * and `manage_privacy_options` capabilities. * @since 5.1.0 Added the `update_php` capability. * @since 5.2.0 Added the `resume_plugin` and `resume_theme` capabilities. * @since 5.3.0 Formalized the existing and already documented `...$args` parameter * by adding it to the function signature. * @since 5.7.0 Added the `create_app_password`, `list_app_passwords`, `read_app_password`, * `edit_app_password`, `delete_app_passwords`, `delete_app_password`, * and `update_https` capabilities. * * @global array $post_type_meta_caps Used to get post type meta capabilities. * * @param string $cap Capability being checked. * @param int $user_id User ID. * @param mixed ...$args Optional further parameters, typically starting with an object ID. * @return string[] Primitive capabilities required of the user. */ function map_meta_cap( $cap, $user_id, ...$args ) { $caps = array(); switch ( $cap ) { case 'remove_user': // In multisite the user must be a super admin to remove themselves. if ( isset( $args[0] ) && $user_id == $args[0] && ! is_super_admin( $user_id ) ) { $caps[] = 'do_not_allow'; } else { $caps[] = 'remove_users'; } break; case 'promote_user': case 'add_users': $caps[] = 'promote_users'; break; case 'edit_user': case 'edit_users': // Allow user to edit themselves. if ( 'edit_user' === $cap && isset( $args[0] ) && $user_id == $args[0] ) { break; } // In multisite the user must have manage_network_users caps. If editing a super admin, the user must be a super admin. if ( is_multisite() && ( ( ! is_super_admin( $user_id ) && 'edit_user' === $cap && is_super_admin( $args[0] ) ) || ! user_can( $user_id, 'manage_network_users' ) ) ) { $caps[] = 'do_not_allow'; } else { $caps[] = 'edit_users'; // edit_user maps to edit_users. } break; case 'delete_post': case 'delete_page': if ( ! isset( $args[0] ) ) { if ( 'delete_post' === $cap ) { /* translators: %s: Capability name. */ $message = __( 'When checking for the %s capability, you must always check it against a specific post.' ); } else { /* translators: %s: Capability name. */ $message = __( 'When checking for the %s capability, you must always check it against a specific page.' ); } _doing_it_wrong( __FUNCTION__, sprintf( $message, '' . $cap . '' ), '6.1.0' ); $caps[] = 'do_not_allow'; break; } $post = get_post( $args[0] ); if ( ! $post ) { $caps[] = 'do_not_allow'; break; } if ( 'revision' === $post->post_type ) { $caps[] = 'do_not_allow'; break; } if ( ( get_option( 'page_for_posts' ) == $post->ID ) || ( get_option( 'page_on_front' ) == $post->ID ) ) { $caps[] = 'manage_options'; break; } $post_type = get_post_type_object( $post->post_type ); if ( ! $post_type ) { /* translators: 1: Post type, 2: Capability name. */ $message = __( 'The post type %1$s is not registered, so it may not be reliable to check the capability %2$s against a post of that type.' ); _doing_it_wrong( __FUNCTION__, sprintf( $message, '' . $post->post_type . '', '' . $cap . '' ), '4.4.0' ); $caps[] = 'edit_others_posts'; break; } if ( ! $post_type->map_meta_cap ) { $caps[] = $post_type->cap->$cap; // Prior to 3.1 we would re-call map_meta_cap here. if ( 'delete_post' === $cap ) { $cap = $post_type->cap->$cap; } break; } // If the post author is set and the user is the author... if ( $post->post_author && $user_id == $post->post_author ) { // If the post is published or scheduled... if ( in_array( $post->post_status, array( 'publish', 'future' ), true ) ) { $caps[] = $post_type->cap->delete_published_posts; } elseif ( 'trash' === $post->post_status ) { $status = get_post_meta( $post->ID, '_wp_trash_meta_status', true ); if ( in_array( $status, array( 'publish', 'future' ), true ) ) { $caps[] = $post_type->cap->delete_published_posts; } else { $caps[] = $post_type->cap->delete_posts; } } else { // If the post is draft... $caps[] = $post_type->cap->delete_posts; } } else { // The user is trying to edit someone else's post. $caps[] = $post_type->cap->delete_others_posts; // The post is published or scheduled, extra cap required. if ( in_array( $post->post_status, array( 'publish', 'future' ), true ) ) { $caps[] = $post_type->cap->delete_published_posts; } elseif ( 'private' === $post->post_status ) { $caps[] = $post_type->cap->delete_private_posts; } } /* * Setting the privacy policy page requires `manage_privacy_options`, * so deleting it should require that too. */ if ( (int) get_option( 'wp_page_for_privacy_policy' ) === $post->ID ) { $caps = array_merge( $caps, map_meta_cap( 'manage_privacy_options', $user_id ) ); } break; /* * edit_post breaks down to edit_posts, edit_published_posts, or * edit_others_posts. */ case 'edit_post': case 'edit_page': if ( ! isset( $args[0] ) ) { if ( 'edit_post' === $cap ) { /* translators: %s: Capability name. */ $message = __( 'When checking for the %s capability, you must always check it against a specific post.' ); } else { /* translators: %s: Capability name. */ $message = __( 'When checking for the %s capability, you must always check it against a specific page.' ); } _doing_it_wrong( __FUNCTION__, sprintf( $message, '' . $cap . '' ), '6.1.0' ); $caps[] = 'do_not_allow'; break; } $post = get_post( $args[0] ); if ( ! $post ) { $caps[] = 'do_not_allow'; break; } if ( 'revision' === $post->post_type ) { $post = get_post( $post->post_parent ); if ( ! $post ) { $caps[] = 'do_not_allow'; break; } } $post_type = get_post_type_object( $post->post_type ); if ( ! $post_type ) { /* translators: 1: Post type, 2: Capability name. */ $message = __( 'The post type %1$s is not registered, so it may not be reliable to check the capability %2$s against a post of that type.' ); _doing_it_wrong( __FUNCTION__, sprintf( $message, '' . $post->post_type . '', '' . $cap . '' ), '4.4.0' ); $caps[] = 'edit_others_posts'; break; } if ( ! $post_type->map_meta_cap ) { $caps[] = $post_type->cap->$cap; // Prior to 3.1 we would re-call map_meta_cap here. if ( 'edit_post' === $cap ) { $cap = $post_type->cap->$cap; } break; } // If the post author is set and the user is the author... if ( $post->post_author && $user_id == $post->post_author ) { // If the post is published or scheduled... if ( in_array( $post->post_status, array( 'publish', 'future' ), true ) ) { $caps[] = $post_type->cap->edit_published_posts; } elseif ( 'trash' === $post->post_status ) { $status = get_post_meta( $post->ID, '_wp_trash_meta_status', true ); if ( in_array( $status, array( 'publish', 'future' ), true ) ) { $caps[] = $post_type->cap->edit_published_posts; } else { $caps[] = $post_type->cap->edit_posts; } } else { // If the post is draft... $caps[] = $post_type->cap->edit_posts; } } else { // The user is trying to edit someone else's post. $caps[] = $post_type->cap->edit_others_posts; // The post is published or scheduled, extra cap required. if ( in_array( $post->post_status, array( 'publish', 'future' ), true ) ) { $caps[] = $post_type->cap->edit_published_posts; } elseif ( 'private' === $post->post_status ) { $caps[] = $post_type->cap->edit_private_posts; } } /* * Setting the privacy policy page requires `manage_privacy_options`, * so editing it should require that too. */ if ( (int) get_option( 'wp_page_for_privacy_policy' ) === $post->ID ) { $caps = array_merge( $caps, map_meta_cap( 'manage_privacy_options', $user_id ) ); } break; case 'read_post': case 'read_page': if ( ! isset( $args[0] ) ) { if ( 'read_post' === $cap ) { /* translators: %s: Capability name. */ $message = __( 'When checking for the %s capability, you must always check it against a specific post.' ); } else { /* translators: %s: Capability name. */ $message = __( 'When checking for the %s capability, you must always check it against a specific page.' ); } _doing_it_wrong( __FUNCTION__, sprintf( $message, '' . $cap . '' ), '6.1.0' ); $caps[] = 'do_not_allow'; break; } $post = get_post( $args[0] ); if ( ! $post ) { $caps[] = 'do_not_allow'; break; } if ( 'revision' === $post->post_type ) { $post = get_post( $post->post_parent ); if ( ! $post ) { $caps[] = 'do_not_allow'; break; } } $post_type = get_post_type_object( $post->post_type ); if ( ! $post_type ) { /* translators: 1: Post type, 2: Capability name. */ $message = __( 'The post type %1$s is not registered, so it may not be reliable to check the capability %2$s against a post of that type.' ); _doing_it_wrong( __FUNCTION__, sprintf( $message, '' . $post->post_type . '', '' . $cap . '' ), '4.4.0' ); $caps[] = 'edit_others_posts'; break; } if ( ! $post_type->map_meta_cap ) { $caps[] = $post_type->cap->$cap; // Prior to 3.1 we would re-call map_meta_cap here. if ( 'read_post' === $cap ) { $cap = $post_type->cap->$cap; } break; } $status_obj = get_post_status_object( get_post_status( $post ) ); if ( ! $status_obj ) { /* translators: 1: Post status, 2: Capability name. */ $message = __( 'The post status %1$s is not registered, so it may not be reliable to check the capability %2$s against a post with that status.' ); _doing_it_wrong( __FUNCTION__, sprintf( $message, '' . get_post_status( $post ) . '', '' . $cap . '' ), '5.4.0' ); $caps[] = 'edit_others_posts'; break; } if ( $status_obj->public ) { $caps[] = $post_type->cap->read; break; } if ( $post->post_author && $user_id == $post->post_author ) { $caps[] = $post_type->cap->read; } elseif ( $status_obj->private ) { $caps[] = $post_type->cap->read_private_posts; } else { $caps = map_meta_cap( 'edit_post', $user_id, $post->ID ); } break; case 'publish_post': if ( ! isset( $args[0] ) ) { /* translators: %s: Capability name. */ $message = __( 'When checking for the %s capability, you must always check it against a specific post.' ); _doing_it_wrong( __FUNCTION__, sprintf( $message, '' . $cap . '' ), '6.1.0' ); $caps[] = 'do_not_allow'; break; } $post = get_post( $args[0] ); if ( ! $post ) { $caps[] = 'do_not_allow'; break; } $post_type = get_post_type_object( $post->post_type ); if ( ! $post_type ) { /* translators: 1: Post type, 2: Capability name. */ $message = __( 'The post type %1$s is not registered, so it may not be reliable to check the capability %2$s against a post of that type.' ); _doing_it_wrong( __FUNCTION__, sprintf( $message, '' . $post->post_type . '', '' . $cap . '' ), '4.4.0' ); $caps[] = 'edit_others_posts'; break; } $caps[] = $post_type->cap->publish_posts; break; case 'edit_post_meta': case 'delete_post_meta': case 'add_post_meta': case 'edit_comment_meta': case 'delete_comment_meta': case 'add_comment_meta': case 'edit_term_meta': case 'delete_term_meta': case 'add_term_meta': case 'edit_user_meta': case 'delete_user_meta': case 'add_user_meta': $object_type = explode( '_', $cap )[1]; if ( ! isset( $args[0] ) ) { if ( 'post' === $object_type ) { /* translators: %s: Capability name. */ $message = __( 'When checking for the %s capability, you must always check it against a specific post.' ); } elseif ( 'comment' === $object_type ) { /* translators: %s: Capability name. */ $message = __( 'When checking for the %s capability, you must always check it against a specific comment.' ); } elseif ( 'term' === $object_type ) { /* translators: %s: Capability name. */ $message = __( 'When checking for the %s capability, you must always check it against a specific term.' ); } else { /* translators: %s: Capability name. */ $message = __( 'When checking for the %s capability, you must always check it against a specific user.' ); } _doing_it_wrong( __FUNCTION__, sprintf( $message, '' . $cap . '' ), '6.1.0' ); $caps[] = 'do_not_allow'; break; } $object_id = (int) $args[0]; $object_subtype = get_object_subtype( $object_type, $object_id ); if ( empty( $object_subtype ) ) { $caps[] = 'do_not_allow'; break; } $caps = map_meta_cap( "edit_{$object_type}", $user_id, $object_id ); $meta_key = isset( $args[1] ) ? $args[1] : false; if ( $meta_key ) { $allowed = ! is_protected_meta( $meta_key, $object_type ); if ( ! empty( $object_subtype ) && has_filter( "auth_{$object_type}_meta_{$meta_key}_for_{$object_subtype}" ) ) { /** * Filters whether the user is allowed to edit a specific meta key of a specific object type and subtype. * * The dynamic portions of the hook name, `$object_type`, `$meta_key`, * and `$object_subtype`, refer to the metadata object type (comment, post, term or user), * the meta key value, and the object subtype respectively. * * @since 4.9.8 * * @param bool $allowed Whether the user can add the object meta. Default false. * @param string $meta_key The meta key. * @param int $object_id Object ID. * @param int $user_id User ID. * @param string $cap Capability name. * @param string[] $caps Array of the user's capabilities. */ $allowed = apply_filters( "auth_{$object_type}_meta_{$meta_key}_for_{$object_subtype}", $allowed, $meta_key, $object_id, $user_id, $cap, $caps ); } else { /** * Filters whether the user is allowed to edit a specific meta key of a specific object type. * * Return true to have the mapped meta caps from `edit_{$object_type}` apply. * * The dynamic portion of the hook name, `$object_type` refers to the object type being filtered. * The dynamic portion of the hook name, `$meta_key`, refers to the meta key passed to map_meta_cap(). * * @since 3.3.0 As `auth_post_meta_{$meta_key}`. * @since 4.6.0 * * @param bool $allowed Whether the user can add the object meta. Default false. * @param string $meta_key The meta key. * @param int $object_id Object ID. * @param int $user_id User ID. * @param string $cap Capability name. * @param string[] $caps Array of the user's capabilities. */ $allowed = apply_filters( "auth_{$object_type}_meta_{$meta_key}", $allowed, $meta_key, $object_id, $user_id, $cap, $caps ); } if ( ! empty( $object_subtype ) ) { /** * Filters whether the user is allowed to edit meta for specific object types/subtypes. * * Return true to have the mapped meta caps from `edit_{$object_type}` apply. * * The dynamic portion of the hook name, `$object_type` refers to the object type being filtered. * The dynamic portion of the hook name, `$object_subtype` refers to the object subtype being filtered. * The dynamic portion of the hook name, `$meta_key`, refers to the meta key passed to map_meta_cap(). * * @since 4.6.0 As `auth_post_{$post_type}_meta_{$meta_key}`. * @since 4.7.0 Renamed from `auth_post_{$post_type}_meta_{$meta_key}` to * `auth_{$object_type}_{$object_subtype}_meta_{$meta_key}`. * @deprecated 4.9.8 Use {@see 'auth_{$object_type}_meta_{$meta_key}_for_{$object_subtype}'} instead. * * @param bool $allowed Whether the user can add the object meta. Default false. * @param string $meta_key The meta key. * @param int $object_id Object ID. * @param int $user_id User ID. * @param string $cap Capability name. * @param string[] $caps Array of the user's capabilities. */ $allowed = apply_filters_deprecated( "auth_{$object_type}_{$object_subtype}_meta_{$meta_key}", array( $allowed, $meta_key, $object_id, $user_id, $cap, $caps ), '4.9.8', "auth_{$object_type}_meta_{$meta_key}_for_{$object_subtype}" ); } if ( ! $allowed ) { $caps[] = $cap; } } break; case 'edit_comment': if ( ! isset( $args[0] ) ) { /* translators: %s: Capability name. */ $message = __( 'When checking for the %s capability, you must always check it against a specific comment.' ); _doing_it_wrong( __FUNCTION__, sprintf( $message, '' . $cap . '' ), '6.1.0' ); $caps[] = 'do_not_allow'; break; } $comment = get_comment( $args[0] ); if ( ! $comment ) { $caps[] = 'do_not_allow'; break; } $post = get_post( $comment->comment_post_ID ); /* * If the post doesn't exist, we have an orphaned comment. * Fall back to the edit_posts capability, instead. */ if ( $post ) { $caps = map_meta_cap( 'edit_post', $user_id, $post->ID ); } else { $caps = map_meta_cap( 'edit_posts', $user_id ); } break; case 'unfiltered_upload': if ( defined( 'ALLOW_UNFILTERED_UPLOADS' ) && ALLOW_UNFILTERED_UPLOADS && ( ! is_multisite() || is_super_admin( $user_id ) ) ) { $caps[] = $cap; } else { $caps[] = 'do_not_allow'; } break; case 'edit_css': case 'unfiltered_html': // Disallow unfiltered_html for all users, even admins and super admins. if ( defined( 'DISALLOW_UNFILTERED_HTML' ) && DISALLOW_UNFILTERED_HTML ) { $caps[] = 'do_not_allow'; } elseif ( is_multisite() && ! is_super_admin( $user_id ) ) { $caps[] = 'do_not_allow'; } else { $caps[] = 'unfiltered_html'; } break; case 'edit_files': case 'edit_plugins': case 'edit_themes': // Disallow the file editors. if ( defined( 'DISALLOW_FILE_EDIT' ) && DISALLOW_FILE_EDIT ) { $caps[] = 'do_not_allow'; } elseif ( ! wp_is_file_mod_allowed( 'capability_edit_themes' ) ) { $caps[] = 'do_not_allow'; } elseif ( is_multisite() && ! is_super_admin( $user_id ) ) { $caps[] = 'do_not_allow'; } else { $caps[] = $cap; } break; case 'update_plugins': case 'delete_plugins': case 'install_plugins': case 'upload_plugins': case 'update_themes': case 'delete_themes': case 'install_themes': case 'upload_themes': case 'update_core': /* * Disallow anything that creates, deletes, or updates core, plugin, or theme files. * Files in uploads are excepted. */ if ( ! wp_is_file_mod_allowed( 'capability_update_core' ) ) { $caps[] = 'do_not_allow'; } elseif ( is_multisite() && ! is_super_admin( $user_id ) ) { $caps[] = 'do_not_allow'; } elseif ( 'upload_themes' === $cap ) { $caps[] = 'install_themes'; } elseif ( 'upload_plugins' === $cap ) { $caps[] = 'install_plugins'; } else { $caps[] = $cap; } break; case 'install_languages': case 'update_languages': if ( ! wp_is_file_mod_allowed( 'can_install_language_pack' ) ) { $caps[] = 'do_not_allow'; } elseif ( is_multisite() && ! is_super_admin( $user_id ) ) { $caps[] = 'do_not_allow'; } else { $caps[] = 'install_languages'; } break; case 'activate_plugins': case 'deactivate_plugins': case 'activate_plugin': case 'deactivate_plugin': $caps[] = 'activate_plugins'; if ( is_multisite() ) { // update_, install_, and delete_ are handled above with is_super_admin(). $menu_perms = get_site_option( 'menu_items', array() ); if ( empty( $menu_perms['plugins'] ) ) { $caps[] = 'manage_network_plugins'; } } break; case 'resume_plugin': $caps[] = 'resume_plugins'; break; case 'resume_theme': $caps[] = 'resume_themes'; break; case 'delete_user': case 'delete_users': // If multisite only super admins can delete users. if ( is_multisite() && ! is_super_admin( $user_id ) ) { $caps[] = 'do_not_allow'; } else { $caps[] = 'delete_users'; // delete_user maps to delete_users. } break; case 'create_users': if ( ! is_multisite() ) { $caps[] = $cap; } elseif ( is_super_admin( $user_id ) || get_site_option( 'add_new_users' ) ) { $caps[] = $cap; } else { $caps[] = 'do_not_allow'; } break; case 'manage_links': if ( get_option( 'link_manager_enabled' ) ) { $caps[] = $cap; } else { $caps[] = 'do_not_allow'; } break; case 'customize': $caps[] = 'edit_theme_options'; break; case 'delete_site': if ( is_multisite() ) { $caps[] = 'manage_options'; } else { $caps[] = 'do_not_allow'; } break; case 'edit_term': case 'delete_term': case 'assign_term': if ( ! isset( $args[0] ) ) { /* translators: %s: Capability name. */ $message = __( 'When checking for the %s capability, you must always check it against a specific term.' ); _doing_it_wrong( __FUNCTION__, sprintf( $message, '' . $cap . '' ), '6.1.0' ); $caps[] = 'do_not_allow'; break; } $term_id = (int) $args[0]; $term = get_term( $term_id ); if ( ! $term || is_wp_error( $term ) ) { $caps[] = 'do_not_allow'; break; } $tax = get_taxonomy( $term->taxonomy ); if ( ! $tax ) { $caps[] = 'do_not_allow'; break; } if ( 'delete_term' === $cap && ( get_option( 'default_' . $term->taxonomy ) == $term->term_id || get_option( 'default_term_' . $term->taxonomy ) == $term->term_id ) ) { $caps[] = 'do_not_allow'; break; } $taxo_cap = $cap . 's'; $caps = map_meta_cap( $tax->cap->$taxo_cap, $user_id, $term_id ); break; case 'manage_post_tags': case 'edit_categories': case 'edit_post_tags': case 'delete_categories': case 'delete_post_tags': $caps[] = 'manage_categories'; break; case 'assign_categories': case 'assign_post_tags': $caps[] = 'edit_posts'; break; case 'create_sites': case 'delete_sites': case 'manage_network': case 'manage_sites': case 'manage_network_users': case 'manage_network_plugins': case 'manage_network_themes': case 'manage_network_options': case 'upgrade_network': $caps[] = $cap; break; case 'setup_network': if ( is_multisite() ) { $caps[] = 'manage_network_options'; } else { $caps[] = 'manage_options'; } break; case 'update_php': if ( is_multisite() && ! is_super_admin( $user_id ) ) { $caps[] = 'do_not_allow'; } else { $caps[] = 'update_core'; } break; case 'update_https': if ( is_multisite() && ! is_super_admin( $user_id ) ) { $caps[] = 'do_not_allow'; } else { $caps[] = 'manage_options'; $caps[] = 'update_core'; } break; case 'export_others_personal_data': case 'erase_others_personal_data': case 'manage_privacy_options': $caps[] = is_multisite() ? 'manage_network' : 'manage_options'; break; case 'create_app_password': case 'list_app_passwords': case 'read_app_password': case 'edit_app_password': case 'delete_app_passwords': case 'delete_app_password': $caps = map_meta_cap( 'edit_user', $user_id, $args[0] ); break; default: // Handle meta capabilities for custom post types. global $post_type_meta_caps; if ( isset( $post_type_meta_caps[ $cap ] ) ) { return map_meta_cap( $post_type_meta_caps[ $cap ], $user_id, ...$args ); } // Block capabilities map to their post equivalent. $block_caps = array( 'edit_blocks', 'edit_others_blocks', 'publish_blocks', 'read_private_blocks', 'delete_blocks', 'delete_private_blocks', 'delete_published_blocks', 'delete_others_blocks', 'edit_private_blocks', 'edit_published_blocks', ); if ( in_array( $cap, $block_caps, true ) ) { $cap = str_replace( '_blocks', '_posts', $cap ); } // If no meta caps match, return the original cap. $caps[] = $cap; } /** * Filters the primitive capabilities required of the given user to satisfy the * capability being checked. * * @since 2.8.0 * * @param string[] $caps Primitive capabilities required of the user. * @param string $cap Capability being checked. * @param int $user_id The user ID. * @param array $args Adds context to the capability check, typically * starting with an object ID. */ return apply_filters( 'map_meta_cap', $caps, $cap, $user_id, $args ); } /** * Returns whether the current user has the specified capability. * * This function also accepts an ID of an object to check against if the capability is a meta capability. Meta * capabilities such as `edit_post` and `edit_user` are capabilities used by the `map_meta_cap()` function to * map to primitive capabilities that a user or role has, such as `edit_posts` and `edit_others_posts`. * * Example usage: * * current_user_can( 'edit_posts' ); * current_user_can( 'edit_post', $post->ID ); * current_user_can( 'edit_post_meta', $post->ID, $meta_key ); * * While checking against particular roles in place of a capability is supported * in part, this practice is discouraged as it may produce unreliable results. * * Note: Will always return true if the current user is a super admin, unless specifically denied. * * @since 2.0.0 * @since 5.3.0 Formalized the existing and already documented `...$args` parameter * by adding it to the function signature. * @since 5.8.0 Converted to wrapper for the user_can() function. * * @see WP_User::has_cap() * @see map_meta_cap() * * @param string $capability Capability name. * @param mixed ...$args Optional further parameters, typically starting with an object ID. * @return bool Whether the current user has the given capability. If `$capability` is a meta cap and `$object_id` is * passed, whether the current user has the given meta capability for the given object. */ function current_user_can( $capability, ...$args ) { return user_can( wp_get_current_user(), $capability, ...$args ); } /** * Returns whether the current user has the specified capability for a given site. * * This function also accepts an ID of an object to check against if the capability is a meta capability. Meta * capabilities such as `edit_post` and `edit_user` are capabilities used by the `map_meta_cap()` function to * map to primitive capabilities that a user or role has, such as `edit_posts` and `edit_others_posts`. * * Example usage: * * current_user_can_for_blog( $blog_id, 'edit_posts' ); * current_user_can_for_blog( $blog_id, 'edit_post', $post->ID ); * current_user_can_for_blog( $blog_id, 'edit_post_meta', $post->ID, $meta_key ); * * @since 3.0.0 * @since 5.3.0 Formalized the existing and already documented `...$args` parameter * by adding it to the function signature. * @since 5.8.0 Wraps current_user_can() after switching to blog. * * @param int $blog_id Site ID. * @param string $capability Capability name. * @param mixed ...$args Optional further parameters, typically starting with an object ID. * @return bool Whether the user has the given capability. */ function current_user_can_for_blog( $blog_id, $capability, ...$args ) { $switched = is_multisite() ? switch_to_blog( $blog_id ) : false; $can = current_user_can( $capability, ...$args ); if ( $switched ) { restore_current_blog(); } return $can; } /** * Returns whether the author of the supplied post has the specified capability. * * This function also accepts an ID of an object to check against if the capability is a meta capability. Meta * capabilities such as `edit_post` and `edit_user` are capabilities used by the `map_meta_cap()` function to * map to primitive capabilities that a user or role has, such as `edit_posts` and `edit_others_posts`. * * Example usage: * * author_can( $post, 'edit_posts' ); * author_can( $post, 'edit_post', $post->ID ); * author_can( $post, 'edit_post_meta', $post->ID, $meta_key ); * * @since 2.9.0 * @since 5.3.0 Formalized the existing and already documented `...$args` parameter * by adding it to the function signature. * * @param int|WP_Post $post Post ID or post object. * @param string $capability Capability name. * @param mixed ...$args Optional further parameters, typically starting with an object ID. * @return bool Whether the post author has the given capability. */ function author_can( $post, $capability, ...$args ) { $post = get_post( $post ); if ( ! $post ) { return false; } $author = get_userdata( $post->post_author ); if ( ! $author ) { return false; } return $author->has_cap( $capability, ...$args ); } /** * Returns whether a particular user has the specified capability. * * This function also accepts an ID of an object to check against if the capability is a meta capability. Meta * capabilities such as `edit_post` and `edit_user` are capabilities used by the `map_meta_cap()` function to * map to primitive capabilities that a user or role has, such as `edit_posts` and `edit_others_posts`. * * Example usage: * * user_can( $user->ID, 'edit_posts' ); * user_can( $user->ID, 'edit_post', $post->ID ); * user_can( $user->ID, 'edit_post_meta', $post->ID, $meta_key ); * * @since 3.1.0 * @since 5.3.0 Formalized the existing and already documented `...$args` parameter * by adding it to the function signature. * * @param int|WP_User $user User ID or object. * @param string $capability Capability name. * @param mixed ...$args Optional further parameters, typically starting with an object ID. * @return bool Whether the user has the given capability. */ function user_can( $user, $capability, ...$args ) { if ( ! is_object( $user ) ) { $user = get_userdata( $user ); } if ( empty( $user ) ) { // User is logged out, create anonymous user object. $user = new WP_User( 0 ); $user->init( new stdClass() ); } return $user->has_cap( $capability, ...$args ); } /** * Retrieves the global WP_Roles instance and instantiates it if necessary. * * @since 4.3.0 * * @global WP_Roles $wp_roles WordPress role management object. * * @return WP_Roles WP_Roles global instance if not already instantiated. */ function wp_roles() { global $wp_roles; if ( ! isset( $wp_roles ) ) { $wp_roles = new WP_Roles(); } return $wp_roles; } /** * Retrieves role object. * * @since 2.0.0 * * @param string $role Role name. * @return WP_Role|null WP_Role object if found, null if the role does not exist. */ function get_role( $role ) { return wp_roles()->get_role( $role ); } /** * Adds a role, if it does not exist. * * @since 2.0.0 * * @param string $role Role name. * @param string $display_name Display name for role. * @param bool[] $capabilities List of capabilities keyed by the capability name, * e.g. array( 'edit_posts' => true, 'delete_posts' => false ). * @return WP_Role|void WP_Role object, if the role is added. */ function add_role( $role, $display_name, $capabilities = array() ) { if ( empty( $role ) ) { return; } return wp_roles()->add_role( $role, $display_name, $capabilities ); } /** * Removes a role, if it exists. * * @since 2.0.0 * * @param string $role Role name. */ function remove_role( $role ) { wp_roles()->remove_role( $role ); } /** * Retrieves a list of super admins. * * @since 3.0.0 * * @global array $super_admins * * @return string[] List of super admin logins. */ function get_super_admins() { global $super_admins; if ( isset( $super_admins ) ) { return $super_admins; } else { return get_site_option( 'site_admins', array( 'admin' ) ); } } /** * Determines whether user is a site admin. * * @since 3.0.0 * * @param int|false $user_id Optional. The ID of a user. Defaults to false, to check the current user. * @return bool Whether the user is a site admin. */ function is_super_admin( $user_id = false ) { if ( ! $user_id ) { $user = wp_get_current_user(); } else { $user = get_userdata( $user_id ); } if ( ! $user || ! $user->exists() ) { return false; } if ( is_multisite() ) { $super_admins = get_super_admins(); if ( is_array( $super_admins ) && in_array( $user->user_login, $super_admins, true ) ) { return true; } } else { if ( $user->has_cap( 'delete_users' ) ) { return true; } } return false; } /** * Grants Super Admin privileges. * * @since 3.0.0 * * @global array $super_admins * * @param int $user_id ID of the user to be granted Super Admin privileges. * @return bool True on success, false on failure. This can fail when the user is * already a super admin or when the `$super_admins` global is defined. */ function grant_super_admin( $user_id ) { // If global super_admins override is defined, there is nothing to do here. if ( isset( $GLOBALS['super_admins'] ) || ! is_multisite() ) { return false; } /** * Fires before the user is granted Super Admin privileges. * * @since 3.0.0 * * @param int $user_id ID of the user that is about to be granted Super Admin privileges. */ do_action( 'grant_super_admin', $user_id ); // Directly fetch site_admins instead of using get_super_admins(). $super_admins = get_site_option( 'site_admins', array( 'admin' ) ); $user = get_userdata( $user_id ); if ( $user && ! in_array( $user->user_login, $super_admins, true ) ) { $super_admins[] = $user->user_login; update_site_option( 'site_admins', $super_admins ); /** * Fires after the user is granted Super Admin privileges. * * @since 3.0.0 * * @param int $user_id ID of the user that was granted Super Admin privileges. */ do_action( 'granted_super_admin', $user_id ); return true; } return false; } /** * Revokes Super Admin privileges. * * @since 3.0.0 * * @global array $super_admins * * @param int $user_id ID of the user Super Admin privileges to be revoked from. * @return bool True on success, false on failure. This can fail when the user's email * is the network admin email or when the `$super_admins` global is defined. */ function revoke_super_admin( $user_id ) { // If global super_admins override is defined, there is nothing to do here. if ( isset( $GLOBALS['super_admins'] ) || ! is_multisite() ) { return false; } /** * Fires before the user's Super Admin privileges are revoked. * * @since 3.0.0 * * @param int $user_id ID of the user Super Admin privileges are being revoked from. */ do_action( 'revoke_super_admin', $user_id ); // Directly fetch site_admins instead of using get_super_admins(). $super_admins = get_site_option( 'site_admins', array( 'admin' ) ); $user = get_userdata( $user_id ); if ( $user && 0 !== strcasecmp( $user->user_email, get_site_option( 'admin_email' ) ) ) { $key = array_search( $user->user_login, $super_admins, true ); if ( false !== $key ) { unset( $super_admins[ $key ] ); update_site_option( 'site_admins', $super_admins ); /** * Fires after the user's Super Admin privileges are revoked. * * @since 3.0.0 * * @param int $user_id ID of the user Super Admin privileges were revoked from. */ do_action( 'revoked_super_admin', $user_id ); return true; } } return false; } /** * Filters the user capabilities to grant the 'install_languages' capability as necessary. * * A user must have at least one out of the 'update_core', 'install_plugins', and * 'install_themes' capabilities to qualify for 'install_languages'. * * @since 4.9.0 * * @param bool[] $allcaps An array of all the user's capabilities. * @return bool[] Filtered array of the user's capabilities. */ function wp_maybe_grant_install_languages_cap( $allcaps ) { if ( ! empty( $allcaps['update_core'] ) || ! empty( $allcaps['install_plugins'] ) || ! empty( $allcaps['install_themes'] ) ) { $allcaps['install_languages'] = true; } return $allcaps; } /** * Filters the user capabilities to grant the 'resume_plugins' and 'resume_themes' capabilities as necessary. * * @since 5.2.0 * * @param bool[] $allcaps An array of all the user's capabilities. * @return bool[] Filtered array of the user's capabilities. */ function wp_maybe_grant_resume_extensions_caps( $allcaps ) { // Even in a multisite, regular administrators should be able to resume plugins. if ( ! empty( $allcaps['activate_plugins'] ) ) { $allcaps['resume_plugins'] = true; } // Even in a multisite, regular administrators should be able to resume themes. if ( ! empty( $allcaps['switch_themes'] ) ) { $allcaps['resume_themes'] = true; } return $allcaps; } /** * Filters the user capabilities to grant the 'view_site_health_checks' capabilities as necessary. * * @since 5.2.2 * * @param bool[] $allcaps An array of all the user's capabilities. * @param string[] $caps Required primitive capabilities for the requested capability. * @param array $args { * Arguments that accompany the requested capability check. * * @type string $0 Requested capability. * @type int $1 Concerned user ID. * @type mixed ...$2 Optional second and further parameters, typically object ID. * } * @param WP_User $user The user object. * @return bool[] Filtered array of the user's capabilities. */ function wp_maybe_grant_site_health_caps( $allcaps, $caps, $args, $user ) { if ( ! empty( $allcaps['install_plugins'] ) && ( ! is_multisite() || is_super_admin( $user->ID ) ) ) { $allcaps['view_site_health_checks'] = true; } return $allcaps; } return; // Dummy gettext calls to get strings in the catalog. /* translators: User role for administrators. */ _x( 'Administrator', 'User role' ); /* translators: User role for editors. */ _x( 'Editor', 'User role' ); /* translators: User role for authors. */ _x( 'Author', 'User role' ); /* translators: User role for contributors. */ _x( 'Contributor', 'User role' ); /* translators: User role for subscribers. */ _x( 'Subscriber', 'User role' ); /** * User API: WP_User class * * @package WordPress * @subpackage Users * @since 4.4.0 */ /** * Core class used to implement the WP_User object. * * @since 2.0.0 * * @property string $nickname * @property string $description * @property string $user_description * @property string $first_name * @property string $user_firstname * @property string $last_name * @property string $user_lastname * @property string $user_login * @property string $user_pass * @property string $user_nicename * @property string $user_email * @property string $user_url * @property string $user_registered * @property string $user_activation_key * @property string $user_status * @property int $user_level * @property string $display_name * @property string $spam * @property string $deleted * @property string $locale * @property string $rich_editing * @property string $syntax_highlighting * @property string $use_ssl */ #[AllowDynamicProperties] class WP_User { /** * User data container. * * @since 2.0.0 * @var stdClass */ public $data; /** * The user's ID. * * @since 2.1.0 * @var int */ public $ID = 0; /** * Capabilities that the individual user has been granted outside of those inherited from their role. * * @since 2.0.0 * @var bool[] Array of key/value pairs where keys represent a capability name * and boolean values represent whether the user has that capability. */ public $caps = array(); /** * User metadata option name. * * @since 2.0.0 * @var string */ public $cap_key; /** * The roles the user is part of. * * @since 2.0.0 * @var string[] */ public $roles = array(); /** * All capabilities the user has, including individual and role based. * * @since 2.0.0 * @var bool[] Array of key/value pairs where keys represent a capability name * and boolean values represent whether the user has that capability. */ public $allcaps = array(); /** * The filter context applied to user data fields. * * @since 2.9.0 * @var string */ public $filter = null; /** * The site ID the capabilities of this user are initialized for. * * @since 4.9.0 * @var int */ private $site_id = 0; /** * @since 3.3.0 * @var array */ private static $back_compat_keys; /** * Constructor. * * Retrieves the userdata and passes it to WP_User::init(). * * @since 2.0.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param int|string|stdClass|WP_User $id User's ID, a WP_User object, or a user object from the DB. * @param string $name Optional. User's username * @param int $site_id Optional Site ID, defaults to current site. */ public function __construct( $id = 0, $name = '', $site_id = '' ) { global $wpdb; if ( ! isset( self::$back_compat_keys ) ) { $prefix = $wpdb->prefix; self::$back_compat_keys = array( 'user_firstname' => 'first_name', 'user_lastname' => 'last_name', 'user_description' => 'description', 'user_level' => $prefix . 'user_level', $prefix . 'usersettings' => $prefix . 'user-settings', $prefix . 'usersettingstime' => $prefix . 'user-settings-time', ); } if ( $id instanceof WP_User ) { $this->init( $id->data, $site_id ); return; } elseif ( is_object( $id ) ) { $this->init( $id, $site_id ); return; } if ( ! empty( $id ) && ! is_numeric( $id ) ) { $name = $id; $id = 0; } if ( $id ) { $data = self::get_data_by( 'id', $id ); } else { $data = self::get_data_by( 'login', $name ); } if ( $data ) { $this->init( $data, $site_id ); } else { $this->data = new stdClass(); } } /** * Sets up object properties, including capabilities. * * @since 3.3.0 * * @param object $data User DB row object. * @param int $site_id Optional. The site ID to initialize for. */ public function init( $data, $site_id = '' ) { if ( ! isset( $data->ID ) ) { $data->ID = 0; } $this->data = $data; $this->ID = (int) $data->ID; $this->for_site( $site_id ); } /** * Returns only the main user fields. * * @since 3.3.0 * @since 4.4.0 Added 'ID' as an alias of 'id' for the `$field` parameter. * * @global wpdb $wpdb WordPress database abstraction object. * * @param string $field The field to query against: Accepts 'id', 'ID', 'slug', 'email' or 'login'. * @param string|int $value The field value. * @return object|false Raw user object. */ public static function get_data_by( $field, $value ) { global $wpdb; // 'ID' is an alias of 'id'. if ( 'ID' === $field ) { $field = 'id'; } if ( 'id' === $field ) { // Make sure the value is numeric to avoid casting objects, for example, to int 1. if ( ! is_numeric( $value ) ) { return false; } $value = (int) $value; if ( $value < 1 ) { return false; } } else { $value = trim( $value ); } if ( ! $value ) { return false; } switch ( $field ) { case 'id': $user_id = $value; $db_field = 'ID'; break; case 'slug': $user_id = wp_cache_get( $value, 'userslugs' ); $db_field = 'user_nicename'; break; case 'email': $user_id = wp_cache_get( $value, 'useremail' ); $db_field = 'user_email'; break; case 'login': $value = sanitize_user( $value ); $user_id = wp_cache_get( $value, 'userlogins' ); $db_field = 'user_login'; break; default: return false; } if ( false !== $user_id ) { $user = wp_cache_get( $user_id, 'users' ); if ( $user ) { return $user; } } $user = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->users WHERE $db_field = %s LIMIT 1", $value ) ); if ( ! $user ) { return false; } update_user_caches( $user ); return $user; } /** * Magic method for checking the existence of a certain custom field. * * @since 3.3.0 * * @param string $key User meta key to check if set. * @return bool Whether the given user meta key is set. */ public function __isset( $key ) { if ( 'id' === $key ) { _deprecated_argument( 'WP_User->id', '2.1.0', sprintf( /* translators: %s: WP_User->ID */ __( 'Use %s instead.' ), 'WP_User->ID' ) ); $key = 'ID'; } if ( isset( $this->data->$key ) ) { return true; } if ( isset( self::$back_compat_keys[ $key ] ) ) { $key = self::$back_compat_keys[ $key ]; } return metadata_exists( 'user', $this->ID, $key ); } /** * Magic method for accessing custom fields. * * @since 3.3.0 * * @param string $key User meta key to retrieve. * @return mixed Value of the given user meta key (if set). If `$key` is 'id', the user ID. */ public function __get( $key ) { if ( 'id' === $key ) { _deprecated_argument( 'WP_User->id', '2.1.0', sprintf( /* translators: %s: WP_User->ID */ __( 'Use %s instead.' ), 'WP_User->ID' ) ); return $this->ID; } if ( isset( $this->data->$key ) ) { $value = $this->data->$key; } else { if ( isset( self::$back_compat_keys[ $key ] ) ) { $key = self::$back_compat_keys[ $key ]; } $value = get_user_meta( $this->ID, $key, true ); } if ( $this->filter ) { $value = sanitize_user_field( $key, $value, $this->ID, $this->filter ); } return $value; } /** * Magic method for setting custom user fields. * * This method does not update custom fields in the database. It only stores * the value on the WP_User instance. * * @since 3.3.0 * * @param string $key User meta key. * @param mixed $value User meta value. */ public function __set( $key, $value ) { if ( 'id' === $key ) { _deprecated_argument( 'WP_User->id', '2.1.0', sprintf( /* translators: %s: WP_User->ID */ __( 'Use %s instead.' ), 'WP_User->ID' ) ); $this->ID = $value; return; } $this->data->$key = $value; } /** * Magic method for unsetting a certain custom field. * * @since 4.4.0 * * @param string $key User meta key to unset. */ public function __unset( $key ) { if ( 'id' === $key ) { _deprecated_argument( 'WP_User->id', '2.1.0', sprintf( /* translators: %s: WP_User->ID */ __( 'Use %s instead.' ), 'WP_User->ID' ) ); } if ( isset( $this->data->$key ) ) { unset( $this->data->$key ); } if ( isset( self::$back_compat_keys[ $key ] ) ) { unset( self::$back_compat_keys[ $key ] ); } } /** * Determines whether the user exists in the database. * * @since 3.4.0 * * @return bool True if user exists in the database, false if not. */ public function exists() { return ! empty( $this->ID ); } /** * Retrieves the value of a property or meta key. * * Retrieves from the users and usermeta table. * * @since 3.3.0 * * @param string $key Property * @return mixed */ public function get( $key ) { return $this->__get( $key ); } /** * Determines whether a property or meta key is set. * * Consults the users and usermeta tables. * * @since 3.3.0 * * @param string $key Property. * @return bool */ public function has_prop( $key ) { return $this->__isset( $key ); } /** * Returns an array representation. * * @since 3.5.0 * * @return array Array representation. */ public function to_array() { return get_object_vars( $this->data ); } /** * Makes private/protected methods readable for backward compatibility. * * @since 4.3.0 * * @param string $name Method to call. * @param array $arguments Arguments to pass when calling. * @return mixed|false Return value of the callback, false otherwise. */ public function __call( $name, $arguments ) { if ( '_init_caps' === $name ) { return $this->_init_caps( ...$arguments ); } return false; } /** * Sets up capability object properties. * * Will set the value for the 'cap_key' property to current database table * prefix, followed by 'capabilities'. Will then check to see if the * property matching the 'cap_key' exists and is an array. If so, it will be * used. * * @since 2.1.0 * @deprecated 4.9.0 Use WP_User::for_site() * * @global wpdb $wpdb WordPress database abstraction object. * * @param string $cap_key Optional capability key */ protected function _init_caps( $cap_key = '' ) { global $wpdb; _deprecated_function( __METHOD__, '4.9.0', 'WP_User::for_site()' ); if ( empty( $cap_key ) ) { $this->cap_key = $wpdb->get_blog_prefix( $this->site_id ) . 'capabilities'; } else { $this->cap_key = $cap_key; } $this->caps = $this->get_caps_data(); $this->get_role_caps(); } /** * Retrieves all of the capabilities of the user's roles, and merges them with * individual user capabilities. * * All of the capabilities of the user's roles are merged with the user's individual * capabilities. This means that the user can be denied specific capabilities that * their role might have, but the user is specifically denied. * * @since 2.0.0 * * @return bool[] Array of key/value pairs where keys represent a capability name * and boolean values represent whether the user has that capability. */ public function get_role_caps() { $switch_site = false; if ( is_multisite() && get_current_blog_id() !== $this->site_id ) { $switch_site = true; switch_to_blog( $this->site_id ); } $wp_roles = wp_roles(); // Filter out caps that are not role names and assign to $this->roles. if ( is_array( $this->caps ) ) { $this->roles = array_filter( array_keys( $this->caps ), array( $wp_roles, 'is_role' ) ); } // Build $allcaps from role caps, overlay user's $caps. $this->allcaps = array(); foreach ( (array) $this->roles as $role ) { $the_role = $wp_roles->get_role( $role ); $this->allcaps = array_merge( (array) $this->allcaps, (array) $the_role->capabilities ); } $this->allcaps = array_merge( (array) $this->allcaps, (array) $this->caps ); if ( $switch_site ) { restore_current_blog(); } return $this->allcaps; } /** * Adds role to user. * * Updates the user's meta data option with capabilities and roles. * * @since 2.0.0 * * @param string $role Role name. */ public function add_role( $role ) { if ( empty( $role ) ) { return; } if ( in_array( $role, $this->roles, true ) ) { return; } $this->caps[ $role ] = true; update_user_meta( $this->ID, $this->cap_key, $this->caps ); $this->get_role_caps(); $this->update_user_level_from_caps(); /** * Fires immediately after the user has been given a new role. * * @since 4.3.0 * * @param int $user_id The user ID. * @param string $role The new role. */ do_action( 'add_user_role', $this->ID, $role ); } /** * Removes role from user. * * @since 2.0.0 * * @param string $role Role name. */ public function remove_role( $role ) { if ( ! in_array( $role, $this->roles, true ) ) { return; } unset( $this->caps[ $role ] ); update_user_meta( $this->ID, $this->cap_key, $this->caps ); $this->get_role_caps(); $this->update_user_level_from_caps(); /** * Fires immediately after a role as been removed from a user. * * @since 4.3.0 * * @param int $user_id The user ID. * @param string $role The removed role. */ do_action( 'remove_user_role', $this->ID, $role ); } /** * Sets the role of the user. * * This will remove the previous roles of the user and assign the user the * new one. You can set the role to an empty string and it will remove all * of the roles from the user. * * @since 2.0.0 * * @param string $role Role name. */ public function set_role( $role ) { if ( 1 === count( $this->roles ) && current( $this->roles ) === $role ) { return; } foreach ( (array) $this->roles as $oldrole ) { unset( $this->caps[ $oldrole ] ); } $old_roles = $this->roles; if ( ! empty( $role ) ) { $this->caps[ $role ] = true; $this->roles = array( $role => true ); } else { $this->roles = array(); } update_user_meta( $this->ID, $this->cap_key, $this->caps ); $this->get_role_caps(); $this->update_user_level_from_caps(); foreach ( $old_roles as $old_role ) { if ( ! $old_role || $old_role === $role ) { continue; } /** This action is documented in wp-includes/class-wp-user.php */ do_action( 'remove_user_role', $this->ID, $old_role ); } if ( $role && ! in_array( $role, $old_roles, true ) ) { /** This action is documented in wp-includes/class-wp-user.php */ do_action( 'add_user_role', $this->ID, $role ); } /** * Fires after the user's role has changed. * * @since 2.9.0 * @since 3.6.0 Added $old_roles to include an array of the user's previous roles. * * @param int $user_id The user ID. * @param string $role The new role. * @param string[] $old_roles An array of the user's previous roles. */ do_action( 'set_user_role', $this->ID, $role, $old_roles ); } /** * Chooses the maximum level the user has. * * Will compare the level from the $item parameter against the $max * parameter. If the item is incorrect, then just the $max parameter value * will be returned. * * Used to get the max level based on the capabilities the user has. This * is also based on roles, so if the user is assigned the Administrator role * then the capability 'level_10' will exist and the user will get that * value. * * @since 2.0.0 * * @param int $max Max level of user. * @param string $item Level capability name. * @return int Max Level. */ public function level_reduction( $max, $item ) { if ( preg_match( '/^level_(10|[0-9])$/i', $item, $matches ) ) { $level = (int) $matches[1]; return max( $max, $level ); } else { return $max; } } /** * Updates the maximum user level for the user. * * Updates the 'user_level' user metadata (includes prefix that is the * database table prefix) with the maximum user level. Gets the value from * the all of the capabilities that the user has. * * @since 2.0.0 * * @global wpdb $wpdb WordPress database abstraction object. */ public function update_user_level_from_caps() { global $wpdb; $this->user_level = array_reduce( array_keys( $this->allcaps ), array( $this, 'level_reduction' ), 0 ); update_user_meta( $this->ID, $wpdb->get_blog_prefix() . 'user_level', $this->user_level ); } /** * Adds capability and grant or deny access to capability. * * @since 2.0.0 * * @param string $cap Capability name. * @param bool $grant Whether to grant capability to user. */ public function add_cap( $cap, $grant = true ) { $this->caps[ $cap ] = $grant; update_user_meta( $this->ID, $this->cap_key, $this->caps ); $this->get_role_caps(); $this->update_user_level_from_caps(); } /** * Removes capability from user. * * @since 2.0.0 * * @param string $cap Capability name. */ public function remove_cap( $cap ) { if ( ! isset( $this->caps[ $cap ] ) ) { return; } unset( $this->caps[ $cap ] ); update_user_meta( $this->ID, $this->cap_key, $this->caps ); $this->get_role_caps(); $this->update_user_level_from_caps(); } /** * Removes all of the capabilities of the user. * * @since 2.1.0 * * @global wpdb $wpdb WordPress database abstraction object. */ public function remove_all_caps() { global $wpdb; $this->caps = array(); delete_user_meta( $this->ID, $this->cap_key ); delete_user_meta( $this->ID, $wpdb->get_blog_prefix() . 'user_level' ); $this->get_role_caps(); } /** * Returns whether the user has the specified capability. * * This function also accepts an ID of an object to check against if the capability is a meta capability. Meta * capabilities such as `edit_post` and `edit_user` are capabilities used by the `map_meta_cap()` function to * map to primitive capabilities that a user or role has, such as `edit_posts` and `edit_others_posts`. * * Example usage: * * $user->has_cap( 'edit_posts' ); * $user->has_cap( 'edit_post', $post->ID ); * $user->has_cap( 'edit_post_meta', $post->ID, $meta_key ); * * While checking against a role in place of a capability is supported in part, this practice is discouraged as it * may produce unreliable results. * * @since 2.0.0 * @since 5.3.0 Formalized the existing and already documented `...$args` parameter * by adding it to the function signature. * * @see map_meta_cap() * * @param string $cap Capability name. * @param mixed ...$args Optional further parameters, typically starting with an object ID. * @return bool Whether the user has the given capability, or, if an object ID is passed, whether the user has * the given capability for that object. */ public function has_cap( $cap, ...$args ) { if ( is_numeric( $cap ) ) { _deprecated_argument( __FUNCTION__, '2.0.0', __( 'Usage of user levels is deprecated. Use capabilities instead.' ) ); $cap = $this->translate_level_to_cap( $cap ); } $caps = map_meta_cap( $cap, $this->ID, ...$args ); // Multisite super admin has all caps by definition, Unless specifically denied. if ( is_multisite() && is_super_admin( $this->ID ) ) { if ( in_array( 'do_not_allow', $caps, true ) ) { return false; } return true; } // Maintain BC for the argument passed to the "user_has_cap" filter. $args = array_merge( array( $cap, $this->ID ), $args ); /** * Dynamically filter a user's capabilities. * * @since 2.0.0 * @since 3.7.0 Added the `$user` parameter. * * @param bool[] $allcaps Array of key/value pairs where keys represent a capability name * and boolean values represent whether the user has that capability. * @param string[] $caps Required primitive capabilities for the requested capability. * @param array $args { * Arguments that accompany the requested capability check. * * @type string $0 Requested capability. * @type int $1 Concerned user ID. * @type mixed ...$2 Optional second and further parameters, typically object ID. * } * @param WP_User $user The user object. */ $capabilities = apply_filters( 'user_has_cap', $this->allcaps, $caps, $args, $this ); // Everyone is allowed to exist. $capabilities['exist'] = true; // Nobody is allowed to do things they are not allowed to do. unset( $capabilities['do_not_allow'] ); // Must have ALL requested caps. foreach ( (array) $caps as $cap ) { if ( empty( $capabilities[ $cap ] ) ) { return false; } } return true; } /** * Converts numeric level to level capability name. * * Prepends 'level_' to level number. * * @since 2.0.0 * * @param int $level Level number, 1 to 10. * @return string */ public function translate_level_to_cap( $level ) { return 'level_' . $level; } /** * Sets the site to operate on. Defaults to the current site. * * @since 3.0.0 * @deprecated 4.9.0 Use WP_User::for_site() * * @param int $blog_id Optional. Site ID, defaults to current site. */ public function for_blog( $blog_id = '' ) { _deprecated_function( __METHOD__, '4.9.0', 'WP_User::for_site()' ); $this->for_site( $blog_id ); } /** * Sets the site to operate on. Defaults to the current site. * * @since 4.9.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param int $site_id Site ID to initialize user capabilities for. Default is the current site. */ public function for_site( $site_id = '' ) { global $wpdb; if ( ! empty( $site_id ) ) { $this->site_id = absint( $site_id ); } else { $this->site_id = get_current_blog_id(); } $this->cap_key = $wpdb->get_blog_prefix( $this->site_id ) . 'capabilities'; $this->caps = $this->get_caps_data(); $this->get_role_caps(); } /** * Gets the ID of the site for which the user's capabilities are currently initialized. * * @since 4.9.0 * * @return int Site ID. */ public function get_site_id() { return $this->site_id; } /** * Gets the available user capabilities data. * * @since 4.9.0 * * @return bool[] List of capabilities keyed by the capability name, * e.g. `array( 'edit_posts' => true, 'delete_posts' => false )`. */ private function get_caps_data() { $caps = get_user_meta( $this->ID, $this->cap_key, true ); if ( ! is_array( $caps ) ) { return array(); } return $caps; } } /** * WordPress Query API * * The query API attempts to get which part of WordPress the user is on. It * also provides functionality for getting URL query information. * * @link https://developer.wordpress.org/themes/basics/the-loop/ More information on The Loop. * * @package WordPress * @subpackage Query */ /** * Retrieves the value of a query variable in the WP_Query class. * * @since 1.5.0 * @since 3.9.0 The `$default_value` argument was introduced. * * @global WP_Query $wp_query WordPress Query object. * * @param string $query_var The variable key to retrieve. * @param mixed $default_value Optional. Value to return if the query variable is not set. * Default empty string. * @return mixed Contents of the query variable. */ function get_query_var( $query_var, $default_value = '' ) { global $wp_query; return $wp_query->get( $query_var, $default_value ); } /** * Retrieves the currently queried object. * * Wrapper for WP_Query::get_queried_object(). * * @since 3.1.0 * * @global WP_Query $wp_query WordPress Query object. * * @return WP_Term|WP_Post_Type|WP_Post|WP_User|null The queried object. */ function get_queried_object() { global $wp_query; return $wp_query->get_queried_object(); } /** * Retrieves the ID of the currently queried object. * * Wrapper for WP_Query::get_queried_object_id(). * * @since 3.1.0 * * @global WP_Query $wp_query WordPress Query object. * * @return int ID of the queried object. */ function get_queried_object_id() { global $wp_query; return $wp_query->get_queried_object_id(); } /** * Sets the value of a query variable in the WP_Query class. * * @since 2.2.0 * * @global WP_Query $wp_query WordPress Query object. * * @param string $query_var Query variable key. * @param mixed $value Query variable value. */ function set_query_var( $query_var, $value ) { global $wp_query; $wp_query->set( $query_var, $value ); } /** * Sets up The Loop with query parameters. * * Note: This function will completely override the main query and isn't intended for use * by plugins or themes. Its overly-simplistic approach to modifying the main query can be * problematic and should be avoided wherever possible. In most cases, there are better, * more performant options for modifying the main query such as via the {@see 'pre_get_posts'} * action within WP_Query. * * This must not be used within the WordPress Loop. * * @since 1.5.0 * * @global WP_Query $wp_query WordPress Query object. * * @param array|string $query Array or string of WP_Query arguments. * @return WP_Post[]|int[] Array of post objects or post IDs. */ function query_posts( $query ) { $GLOBALS['wp_query'] = new WP_Query(); return $GLOBALS['wp_query']->query( $query ); } /** * Destroys the previous query and sets up a new query. * * This should be used after query_posts() and before another query_posts(). * This will remove obscure bugs that occur when the previous WP_Query object * is not destroyed properly before another is set up. * * @since 2.3.0 * * @global WP_Query $wp_query WordPress Query object. * @global WP_Query $wp_the_query Copy of the global WP_Query instance created during wp_reset_query(). */ function wp_reset_query() { $GLOBALS['wp_query'] = $GLOBALS['wp_the_query']; wp_reset_postdata(); } /** * After looping through a separate query, this function restores * the $post global to the current post in the main query. * * @since 3.0.0 * * @global WP_Query $wp_query WordPress Query object. */ function wp_reset_postdata() { global $wp_query; if ( isset( $wp_query ) ) { $wp_query->reset_postdata(); } } /* * Query type checks. */ /** * Determines whether the query is for an existing archive page. * * Archive pages include category, tag, author, date, custom post type, * and custom taxonomy based archives. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 1.5.0 * * @see is_category() * @see is_tag() * @see is_author() * @see is_date() * @see is_post_type_archive() * @see is_tax() * @global WP_Query $wp_query WordPress Query object. * * @return bool Whether the query is for an existing archive page. */ function is_archive() { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_archive(); } /** * Determines whether the query is for an existing post type archive page. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 3.1.0 * * @global WP_Query $wp_query WordPress Query object. * * @param string|string[] $post_types Optional. Post type or array of posts types * to check against. Default empty. * @return bool Whether the query is for an existing post type archive page. */ function is_post_type_archive( $post_types = '' ) { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_post_type_archive( $post_types ); } /** * Determines whether the query is for an existing attachment page. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 2.0.0 * * @global WP_Query $wp_query WordPress Query object. * * @param int|string|int[]|string[] $attachment Optional. Attachment ID, title, slug, or array of such * to check against. Default empty. * @return bool Whether the query is for an existing attachment page. */ function is_attachment( $attachment = '' ) { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_attachment( $attachment ); } /** * Determines whether the query is for an existing author archive page. * * If the $author parameter is specified, this function will additionally * check if the query is for one of the authors specified. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 1.5.0 * * @global WP_Query $wp_query WordPress Query object. * * @param int|string|int[]|string[] $author Optional. User ID, nickname, nicename, or array of such * to check against. Default empty. * @return bool Whether the query is for an existing author archive page. */ function is_author( $author = '' ) { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_author( $author ); } /** * Determines whether the query is for an existing category archive page. * * If the $category parameter is specified, this function will additionally * check if the query is for one of the categories specified. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 1.5.0 * * @global WP_Query $wp_query WordPress Query object. * * @param int|string|int[]|string[] $category Optional. Category ID, name, slug, or array of such * to check against. Default empty. * @return bool Whether the query is for an existing category archive page. */ function is_category( $category = '' ) { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_category( $category ); } /** * Determines whether the query is for an existing tag archive page. * * If the $tag parameter is specified, this function will additionally * check if the query is for one of the tags specified. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 2.3.0 * * @global WP_Query $wp_query WordPress Query object. * * @param int|string|int[]|string[] $tag Optional. Tag ID, name, slug, or array of such * to check against. Default empty. * @return bool Whether the query is for an existing tag archive page. */ function is_tag( $tag = '' ) { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_tag( $tag ); } /** * Determines whether the query is for an existing custom taxonomy archive page. * * If the $taxonomy parameter is specified, this function will additionally * check if the query is for that specific $taxonomy. * * If the $term parameter is specified in addition to the $taxonomy parameter, * this function will additionally check if the query is for one of the terms * specified. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 2.5.0 * * @global WP_Query $wp_query WordPress Query object. * * @param string|string[] $taxonomy Optional. Taxonomy slug or slugs to check against. * Default empty. * @param int|string|int[]|string[] $term Optional. Term ID, name, slug, or array of such * to check against. Default empty. * @return bool Whether the query is for an existing custom taxonomy archive page. * True for custom taxonomy archive pages, false for built-in taxonomies * (category and tag archives). */ function is_tax( $taxonomy = '', $term = '' ) { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_tax( $taxonomy, $term ); } /** * Determines whether the query is for an existing date archive. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 1.5.0 * * @global WP_Query $wp_query WordPress Query object. * * @return bool Whether the query is for an existing date archive. */ function is_date() { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_date(); } /** * Determines whether the query is for an existing day archive. * * A conditional check to test whether the page is a date-based archive page displaying posts for the current day. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 1.5.0 * * @global WP_Query $wp_query WordPress Query object. * * @return bool Whether the query is for an existing day archive. */ function is_day() { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_day(); } /** * Determines whether the query is for a feed. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 1.5.0 * * @global WP_Query $wp_query WordPress Query object. * * @param string|string[] $feeds Optional. Feed type or array of feed types * to check against. Default empty. * @return bool Whether the query is for a feed. */ function is_feed( $feeds = '' ) { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_feed( $feeds ); } /** * Is the query for a comments feed? * * @since 3.0.0 * * @global WP_Query $wp_query WordPress Query object. * * @return bool Whether the query is for a comments feed. */ function is_comment_feed() { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_comment_feed(); } /** * Determines whether the query is for the front page of the site. * * This is for what is displayed at your site's main URL. * * Depends on the site's "Front page displays" Reading Settings 'show_on_front' and 'page_on_front'. * * If you set a static page for the front page of your site, this function will return * true when viewing that page. * * Otherwise the same as {@see is_home()}. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 2.5.0 * * @global WP_Query $wp_query WordPress Query object. * * @return bool Whether the query is for the front page of the site. */ function is_front_page() { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_front_page(); } /** * Determines whether the query is for the blog homepage. * * The blog homepage is the page that shows the time-based blog content of the site. * * is_home() is dependent on the site's "Front page displays" Reading Settings 'show_on_front' * and 'page_for_posts'. * * If a static page is set for the front page of the site, this function will return true only * on the page you set as the "Posts page". * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 1.5.0 * * @see is_front_page() * @global WP_Query $wp_query WordPress Query object. * * @return bool Whether the query is for the blog homepage. */ function is_home() { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_home(); } /** * Determines whether the query is for the Privacy Policy page. * * The Privacy Policy page is the page that shows the Privacy Policy content of the site. * * is_privacy_policy() is dependent on the site's "Change your Privacy Policy page" Privacy Settings 'wp_page_for_privacy_policy'. * * This function will return true only on the page you set as the "Privacy Policy page". * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 5.2.0 * * @global WP_Query $wp_query WordPress Query object. * * @return bool Whether the query is for the Privacy Policy page. */ function is_privacy_policy() { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_privacy_policy(); } /** * Determines whether the query is for an existing month archive. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 1.5.0 * * @global WP_Query $wp_query WordPress Query object. * * @return bool Whether the query is for an existing month archive. */ function is_month() { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_month(); } /** * Determines whether the query is for an existing single page. * * If the $page parameter is specified, this function will additionally * check if the query is for one of the pages specified. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 1.5.0 * * @see is_single() * @see is_singular() * @global WP_Query $wp_query WordPress Query object. * * @param int|string|int[]|string[] $page Optional. Page ID, title, slug, or array of such * to check against. Default empty. * @return bool Whether the query is for an existing single page. */ function is_page( $page = '' ) { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_page( $page ); } /** * Determines whether the query is for a paged result and not for the first page. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 1.5.0 * * @global WP_Query $wp_query WordPress Query object. * * @return bool Whether the query is for a paged result. */ function is_paged() { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_paged(); } /** * Determines whether the query is for a post or page preview. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 2.0.0 * * @global WP_Query $wp_query WordPress Query object. * * @return bool Whether the query is for a post or page preview. */ function is_preview() { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_preview(); } /** * Is the query for the robots.txt file? * * @since 2.1.0 * * @global WP_Query $wp_query WordPress Query object. * * @return bool Whether the query is for the robots.txt file. */ function is_robots() { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_robots(); } /** * Is the query for the favicon.ico file? * * @since 5.4.0 * * @global WP_Query $wp_query WordPress Query object. * * @return bool Whether the query is for the favicon.ico file. */ function is_favicon() { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_favicon(); } /** * Determines whether the query is for a search. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 1.5.0 * * @global WP_Query $wp_query WordPress Query object. * * @return bool Whether the query is for a search. */ function is_search() { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_search(); } /** * Determines whether the query is for an existing single post. * * Works for any post type, except attachments and pages * * If the $post parameter is specified, this function will additionally * check if the query is for one of the Posts specified. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 1.5.0 * * @see is_page() * @see is_singular() * @global WP_Query $wp_query WordPress Query object. * * @param int|string|int[]|string[] $post Optional. Post ID, title, slug, or array of such * to check against. Default empty. * @return bool Whether the query is for an existing single post. */ function is_single( $post = '' ) { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_single( $post ); } /** * Determines whether the query is for an existing single post of any post type * (post, attachment, page, custom post types). * * If the $post_types parameter is specified, this function will additionally * check if the query is for one of the Posts Types specified. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 1.5.0 * * @see is_page() * @see is_single() * @global WP_Query $wp_query WordPress Query object. * * @param string|string[] $post_types Optional. Post type or array of post types * to check against. Default empty. * @return bool Whether the query is for an existing single post * or any of the given post types. */ function is_singular( $post_types = '' ) { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_singular( $post_types ); } /** * Determines whether the query is for a specific time. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 1.5.0 * * @global WP_Query $wp_query WordPress Query object. * * @return bool Whether the query is for a specific time. */ function is_time() { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_time(); } /** * Determines whether the query is for a trackback endpoint call. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 1.5.0 * * @global WP_Query $wp_query WordPress Query object. * * @return bool Whether the query is for a trackback endpoint call. */ function is_trackback() { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_trackback(); } /** * Determines whether the query is for an existing year archive. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 1.5.0 * * @global WP_Query $wp_query WordPress Query object. * * @return bool Whether the query is for an existing year archive. */ function is_year() { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_year(); } /** * Determines whether the query has resulted in a 404 (returns no results). * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 1.5.0 * * @global WP_Query $wp_query WordPress Query object. * * @return bool Whether the query is a 404 error. */ function is_404() { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_404(); } /** * Is the query for an embedded post? * * @since 4.4.0 * * @global WP_Query $wp_query WordPress Query object. * * @return bool Whether the query is for an embedded post. */ function is_embed() { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '3.1.0' ); return false; } return $wp_query->is_embed(); } /** * Determines whether the query is the main query. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 3.3.0 * * @global WP_Query $wp_query WordPress Query object. * * @return bool Whether the query is the main query. */ function is_main_query() { global $wp_query; if ( ! isset( $wp_query ) ) { _doing_it_wrong( __FUNCTION__, __( 'Conditional query tags do not work before the query is run. Before then, they always return false.' ), '6.1.0' ); return false; } if ( 'pre_get_posts' === current_filter() ) { _doing_it_wrong( __FUNCTION__, sprintf( /* translators: 1: pre_get_posts, 2: WP_Query->is_main_query(), 3: is_main_query(), 4: Documentation URL. */ __( 'In %1$s, use the %2$s method, not the %3$s function. See %4$s.' ), 'pre_get_posts', 'WP_Query->is_main_query()', 'is_main_query()', __( 'https://developer.wordpress.org/reference/functions/is_main_query/' ) ), '3.7.0' ); } return $wp_query->is_main_query(); } /* * The Loop. Post loop control. */ /** * Determines whether current WordPress query has posts to loop over. * * @since 1.5.0 * * @global WP_Query $wp_query WordPress Query object. * * @return bool True if posts are available, false if end of the loop. */ function have_posts() { global $wp_query; if ( ! isset( $wp_query ) ) { return false; } return $wp_query->have_posts(); } /** * Determines whether the caller is in the Loop. * * For more information on this and similar theme functions, check out * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/ * Conditional Tags} article in the Theme Developer Handbook. * * @since 2.0.0 * * @global WP_Query $wp_query WordPress Query object. * * @return bool True if caller is within loop, false if loop hasn't started or ended. */ function in_the_loop() { global $wp_query; if ( ! isset( $wp_query ) ) { return false; } return $wp_query->in_the_loop; } /** * Rewind the loop posts. * * @since 1.5.0 * * @global WP_Query $wp_query WordPress Query object. */ function rewind_posts() { global $wp_query; if ( ! isset( $wp_query ) ) { return; } $wp_query->rewind_posts(); } /** * Iterate the post index in the loop. * * @since 1.5.0 * * @global WP_Query $wp_query WordPress Query object. */ function the_post() { global $wp_query; if ( ! isset( $wp_query ) ) { return; } $wp_query->the_post(); } /* * Comments loop. */ /** * Determines whether current WordPress query has comments to loop over. * * @since 2.2.0 * * @global WP_Query $wp_query WordPress Query object. * * @return bool True if comments are available, false if no more comments. */ function have_comments() { global $wp_query; if ( ! isset( $wp_query ) ) { return false; } return $wp_query->have_comments(); } /** * Iterate comment index in the comment loop. * * @since 2.2.0 * * @global WP_Query $wp_query WordPress Query object. */ function the_comment() { global $wp_query; if ( ! isset( $wp_query ) ) { return; } $wp_query->the_comment(); } /** * Redirect old slugs to the correct permalink. * * Attempts to find the current slug from the past slugs. * * @since 2.1.0 */ function wp_old_slug_redirect() { if ( is_404() && '' !== get_query_var( 'name' ) ) { // Guess the current post type based on the query vars. if ( get_query_var( 'post_type' ) ) { $post_type = get_query_var( 'post_type' ); } elseif ( get_query_var( 'attachment' ) ) { $post_type = 'attachment'; } elseif ( get_query_var( 'pagename' ) ) { $post_type = 'page'; } else { $post_type = 'post'; } if ( is_array( $post_type ) ) { if ( count( $post_type ) > 1 ) { return; } $post_type = reset( $post_type ); } // Do not attempt redirect for hierarchical post types. if ( is_post_type_hierarchical( $post_type ) ) { return; } $id = _find_post_by_old_slug( $post_type ); if ( ! $id ) { $id = _find_post_by_old_date( $post_type ); } /** * Filters the old slug redirect post ID. * * @since 4.9.3 * * @param int $id The redirect post ID. */ $id = apply_filters( 'old_slug_redirect_post_id', $id ); if ( ! $id ) { return; } $link = get_permalink( $id ); if ( get_query_var( 'paged' ) > 1 ) { $link = user_trailingslashit( trailingslashit( $link ) . 'page/' . get_query_var( 'paged' ) ); } elseif ( is_embed() ) { $link = user_trailingslashit( trailingslashit( $link ) . 'embed' ); } /** * Filters the old slug redirect URL. * * @since 4.4.0 * * @param string $link The redirect URL. */ $link = apply_filters( 'old_slug_redirect_url', $link ); if ( ! $link ) { return; } wp_redirect( $link, 301 ); // Permanent redirect. exit; } } /** * Find the post ID for redirecting an old slug. * * @since 4.9.3 * @access private * * @see wp_old_slug_redirect() * @global wpdb $wpdb WordPress database abstraction object. * * @param string $post_type The current post type based on the query vars. * @return int The Post ID. */ function _find_post_by_old_slug( $post_type ) { global $wpdb; $query = $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta, $wpdb->posts WHERE ID = post_id AND post_type = %s AND meta_key = '_wp_old_slug' AND meta_value = %s", $post_type, get_query_var( 'name' ) ); /* * If year, monthnum, or day have been specified, make our query more precise * just in case there are multiple identical _wp_old_slug values. */ if ( get_query_var( 'year' ) ) { $query .= $wpdb->prepare( ' AND YEAR(post_date) = %d', get_query_var( 'year' ) ); } if ( get_query_var( 'monthnum' ) ) { $query .= $wpdb->prepare( ' AND MONTH(post_date) = %d', get_query_var( 'monthnum' ) ); } if ( get_query_var( 'day' ) ) { $query .= $wpdb->prepare( ' AND DAYOFMONTH(post_date) = %d', get_query_var( 'day' ) ); } $key = md5( $query ); $last_changed = wp_cache_get_last_changed( 'posts' ); $cache_key = "find_post_by_old_slug:$key:$last_changed"; $cache = wp_cache_get( $cache_key, 'post-queries' ); if ( false !== $cache ) { $id = $cache; } else { $id = (int) $wpdb->get_var( $query ); wp_cache_set( $cache_key, $id, 'post-queries' ); } return $id; } /** * Find the post ID for redirecting an old date. * * @since 4.9.3 * @access private * * @see wp_old_slug_redirect() * @global wpdb $wpdb WordPress database abstraction object. * * @param string $post_type The current post type based on the query vars. * @return int The Post ID. */ function _find_post_by_old_date( $post_type ) { global $wpdb; $date_query = ''; if ( get_query_var( 'year' ) ) { $date_query .= $wpdb->prepare( ' AND YEAR(pm_date.meta_value) = %d', get_query_var( 'year' ) ); } if ( get_query_var( 'monthnum' ) ) { $date_query .= $wpdb->prepare( ' AND MONTH(pm_date.meta_value) = %d', get_query_var( 'monthnum' ) ); } if ( get_query_var( 'day' ) ) { $date_query .= $wpdb->prepare( ' AND DAYOFMONTH(pm_date.meta_value) = %d', get_query_var( 'day' ) ); } $id = 0; if ( $date_query ) { $query = $wpdb->prepare( "SELECT post_id FROM $wpdb->postmeta AS pm_date, $wpdb->posts WHERE ID = post_id AND post_type = %s AND meta_key = '_wp_old_date' AND post_name = %s" . $date_query, $post_type, get_query_var( 'name' ) ); $key = md5( $query ); $last_changed = wp_cache_get_last_changed( 'posts' ); $cache_key = "find_post_by_old_date:$key:$last_changed"; $cache = wp_cache_get( $cache_key, 'post-queries' ); if ( false !== $cache ) { $id = $cache; } else { $id = (int) $wpdb->get_var( $query ); if ( ! $id ) { // Check to see if an old slug matches the old date. $id = (int) $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->posts, $wpdb->postmeta AS pm_slug, $wpdb->postmeta AS pm_date WHERE ID = pm_slug.post_id AND ID = pm_date.post_id AND post_type = %s AND pm_slug.meta_key = '_wp_old_slug' AND pm_slug.meta_value = %s AND pm_date.meta_key = '_wp_old_date'" . $date_query, $post_type, get_query_var( 'name' ) ) ); } wp_cache_set( $cache_key, $id, 'post-queries' ); } } return $id; } /** * Set up global post data. * * @since 1.5.0 * @since 4.4.0 Added the ability to pass a post ID to `$post`. * * @global WP_Query $wp_query WordPress Query object. * * @param WP_Post|object|int $post WP_Post instance or Post ID/object. * @return bool True when finished. */ function setup_postdata( $post ) { global $wp_query; if ( ! empty( $wp_query ) && $wp_query instanceof WP_Query ) { return $wp_query->setup_postdata( $post ); } return false; } /** * Generates post data. * * @since 5.2.0 * * @global WP_Query $wp_query WordPress Query object. * * @param WP_Post|object|int $post WP_Post instance or Post ID/object. * @return array|false Elements of post, or false on failure. */ function generate_postdata( $post ) { global $wp_query; if ( ! empty( $wp_query ) && $wp_query instanceof WP_Query ) { return $wp_query->generate_postdata( $post ); } return false; } /** * Class for generating SQL clauses that filter a primary query according to date. * * WP_Date_Query is a helper that allows primary query classes, such as WP_Query, to filter * their results by date columns, by generating `WHERE` subclauses to be attached to the * primary SQL query string. * * Attempting to filter by an invalid date value (eg month=13) will generate SQL that will * return no results. In these cases, a _doing_it_wrong() error notice is also thrown. * See WP_Date_Query::validate_date_values(). * * @link https://developer.wordpress.org/reference/classes/wp_query/ * * @since 3.7.0 */ #[AllowDynamicProperties] class WP_Date_Query { /** * Array of date queries. * * See WP_Date_Query::__construct() for information on date query arguments. * * @since 3.7.0 * @var array */ public $queries = array(); /** * The default relation between top-level queries. Can be either 'AND' or 'OR'. * * @since 3.7.0 * @var string */ public $relation = 'AND'; /** * The column to query against. Can be changed via the query arguments. * * @since 3.7.0 * @var string */ public $column = 'post_date'; /** * The value comparison operator. Can be changed via the query arguments. * * @since 3.7.0 * @var string */ public $compare = '='; /** * Supported time-related parameter keys. * * @since 4.1.0 * @var string[] */ public $time_keys = array( 'after', 'before', 'year', 'month', 'monthnum', 'week', 'w', 'dayofyear', 'day', 'dayofweek', 'dayofweek_iso', 'hour', 'minute', 'second' ); /** * Constructor. * * Time-related parameters that normally require integer values ('year', 'month', 'week', 'dayofyear', 'day', * 'dayofweek', 'dayofweek_iso', 'hour', 'minute', 'second') accept arrays of integers for some values of * 'compare'. When 'compare' is 'IN' or 'NOT IN', arrays are accepted; when 'compare' is 'BETWEEN' or 'NOT * BETWEEN', arrays of two valid values are required. See individual argument descriptions for accepted values. * * @since 3.7.0 * @since 4.0.0 The $inclusive logic was updated to include all times within the date range. * @since 4.1.0 Introduced 'dayofweek_iso' time type parameter. * * @param array $date_query { * Array of date query clauses. * * @type array ...$0 { * @type string $column Optional. The column to query against. If undefined, inherits the value of * the `$default_column` parameter. See WP_Date_Query::validate_column() and * the {@see 'date_query_valid_columns'} filter for the list of accepted values. * Default 'post_date'. * @type string $compare Optional. The comparison operator. Accepts '=', '!=', '>', '>=', '<', '<=', * 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN'. Default '='. * @type string $relation Optional. The boolean relationship between the date queries. Accepts 'OR' or 'AND'. * Default 'OR'. * @type array ...$0 { * Optional. An array of first-order clause parameters, or another fully-formed date query. * * @type string|array $before { * Optional. Date to retrieve posts before. Accepts `strtotime()`-compatible string, * or array of 'year', 'month', 'day' values. * * @type string $year The four-digit year. Default empty. Accepts any four-digit year. * @type string $month Optional when passing array.The month of the year. * Default (string:empty)|(array:1). Accepts numbers 1-12. * @type string $day Optional when passing array.The day of the month. * Default (string:empty)|(array:1). Accepts numbers 1-31. * } * @type string|array $after { * Optional. Date to retrieve posts after. Accepts `strtotime()`-compatible string, * or array of 'year', 'month', 'day' values. * * @type string $year The four-digit year. Accepts any four-digit year. Default empty. * @type string $month Optional when passing array. The month of the year. Accepts numbers 1-12. * Default (string:empty)|(array:12). * @type string $day Optional when passing array.The day of the month. Accepts numbers 1-31. * Default (string:empty)|(array:last day of month). * } * @type string $column Optional. Used to add a clause comparing a column other than * the column specified in the top-level `$column` parameter. * See WP_Date_Query::validate_column() and * the {@see 'date_query_valid_columns'} filter for the list * of accepted values. Default is the value of top-level `$column`. * @type string $compare Optional. The comparison operator. Accepts '=', '!=', '>', '>=', * '<', '<=', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN'. 'IN', * 'NOT IN', 'BETWEEN', and 'NOT BETWEEN'. Comparisons support * arrays in some time-related parameters. Default '='. * @type bool $inclusive Optional. Include results from dates specified in 'before' or * 'after'. Default false. * @type int|int[] $year Optional. The four-digit year number. Accepts any four-digit year * or an array of years if `$compare` supports it. Default empty. * @type int|int[] $month Optional. The two-digit month number. Accepts numbers 1-12 or an * array of valid numbers if `$compare` supports it. Default empty. * @type int|int[] $week Optional. The week number of the year. Accepts numbers 0-53 or an * array of valid numbers if `$compare` supports it. Default empty. * @type int|int[] $dayofyear Optional. The day number of the year. Accepts numbers 1-366 or an * array of valid numbers if `$compare` supports it. * @type int|int[] $day Optional. The day of the month. Accepts numbers 1-31 or an array * of valid numbers if `$compare` supports it. Default empty. * @type int|int[] $dayofweek Optional. The day number of the week. Accepts numbers 1-7 (1 is * Sunday) or an array of valid numbers if `$compare` supports it. * Default empty. * @type int|int[] $dayofweek_iso Optional. The day number of the week (ISO). Accepts numbers 1-7 * (1 is Monday) or an array of valid numbers if `$compare` supports it. * Default empty. * @type int|int[] $hour Optional. The hour of the day. Accepts numbers 0-23 or an array * of valid numbers if `$compare` supports it. Default empty. * @type int|int[] $minute Optional. The minute of the hour. Accepts numbers 0-59 or an array * of valid numbers if `$compare` supports it. Default empty. * @type int|int[] $second Optional. The second of the minute. Accepts numbers 0-59 or an * array of valid numbers if `$compare` supports it. Default empty. * } * } * } * @param string $default_column Optional. Default column to query against. See WP_Date_Query::validate_column() * and the {@see 'date_query_valid_columns'} filter for the list of accepted values. * Default 'post_date'. */ public function __construct( $date_query, $default_column = 'post_date' ) { if ( empty( $date_query ) || ! is_array( $date_query ) ) { return; } if ( isset( $date_query['relation'] ) ) { $this->relation = $this->sanitize_relation( $date_query['relation'] ); } else { $this->relation = 'AND'; } // Support for passing time-based keys in the top level of the $date_query array. if ( ! isset( $date_query[0] ) ) { $date_query = array( $date_query ); } if ( ! empty( $date_query['column'] ) ) { $date_query['column'] = esc_sql( $date_query['column'] ); } else { $date_query['column'] = esc_sql( $default_column ); } $this->column = $this->validate_column( $this->column ); $this->compare = $this->get_compare( $date_query ); $this->queries = $this->sanitize_query( $date_query ); } /** * Recursive-friendly query sanitizer. * * Ensures that each query-level clause has a 'relation' key, and that * each first-order clause contains all the necessary keys from `$defaults`. * * @since 4.1.0 * * @param array $queries * @param array $parent_query * @return array Sanitized queries. */ public function sanitize_query( $queries, $parent_query = null ) { $cleaned_query = array(); $defaults = array( 'column' => 'post_date', 'compare' => '=', 'relation' => 'AND', ); // Numeric keys should always have array values. foreach ( $queries as $qkey => $qvalue ) { if ( is_numeric( $qkey ) && ! is_array( $qvalue ) ) { unset( $queries[ $qkey ] ); } } // Each query should have a value for each default key. Inherit from the parent when possible. foreach ( $defaults as $dkey => $dvalue ) { if ( isset( $queries[ $dkey ] ) ) { continue; } if ( isset( $parent_query[ $dkey ] ) ) { $queries[ $dkey ] = $parent_query[ $dkey ]; } else { $queries[ $dkey ] = $dvalue; } } // Validate the dates passed in the query. if ( $this->is_first_order_clause( $queries ) ) { $this->validate_date_values( $queries ); } // Sanitize the relation parameter. $queries['relation'] = $this->sanitize_relation( $queries['relation'] ); foreach ( $queries as $key => $q ) { if ( ! is_array( $q ) || in_array( $key, $this->time_keys, true ) ) { // This is a first-order query. Trust the values and sanitize when building SQL. $cleaned_query[ $key ] = $q; } else { // Any array without a time key is another query, so we recurse. $cleaned_query[] = $this->sanitize_query( $q, $queries ); } } return $cleaned_query; } /** * Determines whether this is a first-order clause. * * Checks to see if the current clause has any time-related keys. * If so, it's first-order. * * @since 4.1.0 * * @param array $query Query clause. * @return bool True if this is a first-order clause. */ protected function is_first_order_clause( $query ) { $time_keys = array_intersect( $this->time_keys, array_keys( $query ) ); return ! empty( $time_keys ); } /** * Determines and validates what comparison operator to use. * * @since 3.7.0 * * @param array $query A date query or a date subquery. * @return string The comparison operator. */ public function get_compare( $query ) { if ( ! empty( $query['compare'] ) && in_array( $query['compare'], array( '=', '!=', '>', '>=', '<', '<=', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN' ), true ) ) { return strtoupper( $query['compare'] ); } return $this->compare; } /** * Validates the given date_query values and triggers errors if something is not valid. * * Note that date queries with invalid date ranges are allowed to * continue (though of course no items will be found for impossible dates). * This method only generates debug notices for these cases. * * @since 4.1.0 * * @param array $date_query The date_query array. * @return bool True if all values in the query are valid, false if one or more fail. */ public function validate_date_values( $date_query = array() ) { if ( empty( $date_query ) ) { return false; } $valid = true; /* * Validate 'before' and 'after' up front, then let the * validation routine continue to be sure that all invalid * values generate errors too. */ if ( array_key_exists( 'before', $date_query ) && is_array( $date_query['before'] ) ) { $valid = $this->validate_date_values( $date_query['before'] ); } if ( array_key_exists( 'after', $date_query ) && is_array( $date_query['after'] ) ) { $valid = $this->validate_date_values( $date_query['after'] ); } // Array containing all min-max checks. $min_max_checks = array(); // Days per year. if ( array_key_exists( 'year', $date_query ) ) { /* * If a year exists in the date query, we can use it to get the days. * If multiple years are provided (as in a BETWEEN), use the first one. */ if ( is_array( $date_query['year'] ) ) { $_year = reset( $date_query['year'] ); } else { $_year = $date_query['year']; } $max_days_of_year = gmdate( 'z', mktime( 0, 0, 0, 12, 31, $_year ) ) + 1; } else { // Otherwise we use the max of 366 (leap-year). $max_days_of_year = 366; } $min_max_checks['dayofyear'] = array( 'min' => 1, 'max' => $max_days_of_year, ); // Days per week. $min_max_checks['dayofweek'] = array( 'min' => 1, 'max' => 7, ); // Days per week. $min_max_checks['dayofweek_iso'] = array( 'min' => 1, 'max' => 7, ); // Months per year. $min_max_checks['month'] = array( 'min' => 1, 'max' => 12, ); // Weeks per year. if ( isset( $_year ) ) { /* * If we have a specific year, use it to calculate number of weeks. * Note: the number of weeks in a year is the date in which Dec 28 appears. */ $week_count = gmdate( 'W', mktime( 0, 0, 0, 12, 28, $_year ) ); } else { // Otherwise set the week-count to a maximum of 53. $week_count = 53; } $min_max_checks['week'] = array( 'min' => 1, 'max' => $week_count, ); // Days per month. $min_max_checks['day'] = array( 'min' => 1, 'max' => 31, ); // Hours per day. $min_max_checks['hour'] = array( 'min' => 0, 'max' => 23, ); // Minutes per hour. $min_max_checks['minute'] = array( 'min' => 0, 'max' => 59, ); // Seconds per minute. $min_max_checks['second'] = array( 'min' => 0, 'max' => 59, ); // Concatenate and throw a notice for each invalid value. foreach ( $min_max_checks as $key => $check ) { if ( ! array_key_exists( $key, $date_query ) ) { continue; } // Throw a notice for each failing value. foreach ( (array) $date_query[ $key ] as $_value ) { $is_between = $_value >= $check['min'] && $_value <= $check['max']; if ( ! is_numeric( $_value ) || ! $is_between ) { $error = sprintf( /* translators: Date query invalid date message. 1: Invalid value, 2: Type of value, 3: Minimum valid value, 4: Maximum valid value. */ __( 'Invalid value %1$s for %2$s. Expected value should be between %3$s and %4$s.' ), '' . esc_html( $_value ) . '', '' . esc_html( $key ) . '', '' . esc_html( $check['min'] ) . '', '' . esc_html( $check['max'] ) . '' ); _doing_it_wrong( __CLASS__, $error, '4.1.0' ); $valid = false; } } } // If we already have invalid date messages, don't bother running through checkdate(). if ( ! $valid ) { return $valid; } $day_month_year_error_msg = ''; $day_exists = array_key_exists( 'day', $date_query ) && is_numeric( $date_query['day'] ); $month_exists = array_key_exists( 'month', $date_query ) && is_numeric( $date_query['month'] ); $year_exists = array_key_exists( 'year', $date_query ) && is_numeric( $date_query['year'] ); if ( $day_exists && $month_exists && $year_exists ) { // 1. Checking day, month, year combination. if ( ! wp_checkdate( $date_query['month'], $date_query['day'], $date_query['year'], sprintf( '%s-%s-%s', $date_query['year'], $date_query['month'], $date_query['day'] ) ) ) { $day_month_year_error_msg = sprintf( /* translators: 1: Year, 2: Month, 3: Day of month. */ __( 'The following values do not describe a valid date: year %1$s, month %2$s, day %3$s.' ), '' . esc_html( $date_query['year'] ) . '', '' . esc_html( $date_query['month'] ) . '', '' . esc_html( $date_query['day'] ) . '' ); $valid = false; } } elseif ( $day_exists && $month_exists ) { /* * 2. checking day, month combination * We use 2012 because, as a leap year, it's the most permissive. */ if ( ! wp_checkdate( $date_query['month'], $date_query['day'], 2012, sprintf( '2012-%s-%s', $date_query['month'], $date_query['day'] ) ) ) { $day_month_year_error_msg = sprintf( /* translators: 1: Month, 2: Day of month. */ __( 'The following values do not describe a valid date: month %1$s, day %2$s.' ), '' . esc_html( $date_query['month'] ) . '', '' . esc_html( $date_query['day'] ) . '' ); $valid = false; } } if ( ! empty( $day_month_year_error_msg ) ) { _doing_it_wrong( __CLASS__, $day_month_year_error_msg, '4.1.0' ); } return $valid; } /** * Validates a column name parameter. * * Column names without a table prefix (like 'post_date') are checked against a list of * allowed and known tables, and then, if found, have a table prefix (such as 'wp_posts.') * prepended. Prefixed column names (such as 'wp_posts.post_date') bypass this allowed * check, and are only sanitized to remove illegal characters. * * @since 3.7.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param string $column The user-supplied column name. * @return string A validated column name value. */ public function validate_column( $column ) { global $wpdb; $valid_columns = array( 'post_date', 'post_date_gmt', 'post_modified', 'post_modified_gmt', 'comment_date', 'comment_date_gmt', 'user_registered', 'registered', 'last_updated', ); // Attempt to detect a table prefix. if ( ! str_contains( $column, '.' ) ) { /** * Filters the list of valid date query columns. * * @since 3.7.0 * @since 4.1.0 Added 'user_registered' to the default recognized columns. * @since 4.6.0 Added 'registered' and 'last_updated' to the default recognized columns. * * @param string[] $valid_columns An array of valid date query columns. Defaults * are 'post_date', 'post_date_gmt', 'post_modified', * 'post_modified_gmt', 'comment_date', 'comment_date_gmt', * 'user_registered', 'registered', 'last_updated'. */ if ( ! in_array( $column, apply_filters( 'date_query_valid_columns', $valid_columns ), true ) ) { $column = 'post_date'; } $known_columns = array( $wpdb->posts => array( 'post_date', 'post_date_gmt', 'post_modified', 'post_modified_gmt', ), $wpdb->comments => array( 'comment_date', 'comment_date_gmt', ), $wpdb->users => array( 'user_registered', ), $wpdb->blogs => array( 'registered', 'last_updated', ), ); // If it's a known column name, add the appropriate table prefix. foreach ( $known_columns as $table_name => $table_columns ) { if ( in_array( $column, $table_columns, true ) ) { $column = $table_name . '.' . $column; break; } } } // Remove unsafe characters. return preg_replace( '/[^a-zA-Z0-9_$\.]/', '', $column ); } /** * Generates WHERE clause to be appended to a main query. * * @since 3.7.0 * * @return string MySQL WHERE clause. */ public function get_sql() { $sql = $this->get_sql_clauses(); $where = $sql['where']; /** * Filters the date query WHERE clause. * * @since 3.7.0 * * @param string $where WHERE clause of the date query. * @param WP_Date_Query $query The WP_Date_Query instance. */ return apply_filters( 'get_date_sql', $where, $this ); } /** * Generates SQL clauses to be appended to a main query. * * Called by the public WP_Date_Query::get_sql(), this method is abstracted * out to maintain parity with the other Query classes. * * @since 4.1.0 * * @return string[] { * Array containing JOIN and WHERE SQL clauses to append to the main query. * * @type string $join SQL fragment to append to the main JOIN clause. * @type string $where SQL fragment to append to the main WHERE clause. * } */ protected function get_sql_clauses() { $sql = $this->get_sql_for_query( $this->queries ); if ( ! empty( $sql['where'] ) ) { $sql['where'] = ' AND ' . $sql['where']; } return $sql; } /** * Generates SQL clauses for a single query array. * * If nested subqueries are found, this method recurses the tree to * produce the properly nested SQL. * * @since 4.1.0 * * @param array $query Query to parse. * @param int $depth Optional. Number of tree levels deep we currently are. * Used to calculate indentation. Default 0. * @return array { * Array containing JOIN and WHERE SQL clauses to append to a single query array. * * @type string $join SQL fragment to append to the main JOIN clause. * @type string $where SQL fragment to append to the main WHERE clause. * } */ protected function get_sql_for_query( $query, $depth = 0 ) { $sql_chunks = array( 'join' => array(), 'where' => array(), ); $sql = array( 'join' => '', 'where' => '', ); $indent = ''; for ( $i = 0; $i < $depth; $i++ ) { $indent .= ' '; } foreach ( $query as $key => $clause ) { if ( 'relation' === $key ) { $relation = $query['relation']; } elseif ( is_array( $clause ) ) { // This is a first-order clause. if ( $this->is_first_order_clause( $clause ) ) { $clause_sql = $this->get_sql_for_clause( $clause, $query ); $where_count = count( $clause_sql['where'] ); if ( ! $where_count ) { $sql_chunks['where'][] = ''; } elseif ( 1 === $where_count ) { $sql_chunks['where'][] = $clause_sql['where'][0]; } else { $sql_chunks['where'][] = '( ' . implode( ' AND ', $clause_sql['where'] ) . ' )'; } $sql_chunks['join'] = array_merge( $sql_chunks['join'], $clause_sql['join'] ); // This is a subquery, so we recurse. } else { $clause_sql = $this->get_sql_for_query( $clause, $depth + 1 ); $sql_chunks['where'][] = $clause_sql['where']; $sql_chunks['join'][] = $clause_sql['join']; } } } // Filter to remove empties. $sql_chunks['join'] = array_filter( $sql_chunks['join'] ); $sql_chunks['where'] = array_filter( $sql_chunks['where'] ); if ( empty( $relation ) ) { $relation = 'AND'; } // Filter duplicate JOIN clauses and combine into a single string. if ( ! empty( $sql_chunks['join'] ) ) { $sql['join'] = implode( ' ', array_unique( $sql_chunks['join'] ) ); } // Generate a single WHERE clause with proper brackets and indentation. if ( ! empty( $sql_chunks['where'] ) ) { $sql['where'] = '( ' . "\n " . $indent . implode( ' ' . "\n " . $indent . $relation . ' ' . "\n " . $indent, $sql_chunks['where'] ) . "\n" . $indent . ')'; } return $sql; } /** * Turns a single date clause into pieces for a WHERE clause. * * A wrapper for get_sql_for_clause(), included here for backward * compatibility while retaining the naming convention across Query classes. * * @since 3.7.0 * * @param array $query Date query arguments. * @return array { * Array containing JOIN and WHERE SQL clauses to append to the main query. * * @type string[] $join Array of SQL fragments to append to the main JOIN clause. * @type string[] $where Array of SQL fragments to append to the main WHERE clause. * } */ protected function get_sql_for_subquery( $query ) { return $this->get_sql_for_clause( $query, '' ); } /** * Turns a first-order date query into SQL for a WHERE clause. * * @since 4.1.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param array $query Date query clause. * @param array $parent_query Parent query of the current date query. * @return array { * Array containing JOIN and WHERE SQL clauses to append to the main query. * * @type string[] $join Array of SQL fragments to append to the main JOIN clause. * @type string[] $where Array of SQL fragments to append to the main WHERE clause. * } */ protected function get_sql_for_clause( $query, $parent_query ) { global $wpdb; // The sub-parts of a $where part. $where_parts = array(); $column = ( ! empty( $query['column'] ) ) ? esc_sql( $query['column'] ) : $this->column; $column = $this->validate_column( $column ); $compare = $this->get_compare( $query ); $inclusive = ! empty( $query['inclusive'] ); // Assign greater- and less-than values. $lt = '<'; $gt = '>'; if ( $inclusive ) { $lt .= '='; $gt .= '='; } // Range queries. if ( ! empty( $query['after'] ) ) { $where_parts[] = $wpdb->prepare( "$column $gt %s", $this->build_mysql_datetime( $query['after'], ! $inclusive ) ); } if ( ! empty( $query['before'] ) ) { $where_parts[] = $wpdb->prepare( "$column $lt %s", $this->build_mysql_datetime( $query['before'], $inclusive ) ); } // Specific value queries. $date_units = array( 'YEAR' => array( 'year' ), 'MONTH' => array( 'month', 'monthnum' ), '_wp_mysql_week' => array( 'week', 'w' ), 'DAYOFYEAR' => array( 'dayofyear' ), 'DAYOFMONTH' => array( 'day' ), 'DAYOFWEEK' => array( 'dayofweek' ), 'WEEKDAY' => array( 'dayofweek_iso' ), ); // Check of the possible date units and add them to the query. foreach ( $date_units as $sql_part => $query_parts ) { foreach ( $query_parts as $query_part ) { if ( isset( $query[ $query_part ] ) ) { $value = $this->build_value( $compare, $query[ $query_part ] ); if ( $value ) { switch ( $sql_part ) { case '_wp_mysql_week': $where_parts[] = _wp_mysql_week( $column ) . " $compare $value"; break; case 'WEEKDAY': $where_parts[] = "$sql_part( $column ) + 1 $compare $value"; break; default: $where_parts[] = "$sql_part( $column ) $compare $value"; } break; } } } } if ( isset( $query['hour'] ) || isset( $query['minute'] ) || isset( $query['second'] ) ) { // Avoid notices. foreach ( array( 'hour', 'minute', 'second' ) as $unit ) { if ( ! isset( $query[ $unit ] ) ) { $query[ $unit ] = null; } } $time_query = $this->build_time_query( $column, $compare, $query['hour'], $query['minute'], $query['second'] ); if ( $time_query ) { $where_parts[] = $time_query; } } /* * Return an array of 'join' and 'where' for compatibility * with other query classes. */ return array( 'where' => $where_parts, 'join' => array(), ); } /** * Builds and validates a value string based on the comparison operator. * * @since 3.7.0 * * @param string $compare The compare operator to use. * @param string|array $value The value. * @return string|false|int The value to be used in SQL or false on error. */ public function build_value( $compare, $value ) { if ( ! isset( $value ) ) { return false; } switch ( $compare ) { case 'IN': case 'NOT IN': $value = (array) $value; // Remove non-numeric values. $value = array_filter( $value, 'is_numeric' ); if ( empty( $value ) ) { return false; } return '(' . implode( ',', array_map( 'intval', $value ) ) . ')'; case 'BETWEEN': case 'NOT BETWEEN': if ( ! is_array( $value ) || 2 !== count( $value ) ) { $value = array( $value, $value ); } else { $value = array_values( $value ); } // If either value is non-numeric, bail. foreach ( $value as $v ) { if ( ! is_numeric( $v ) ) { return false; } } $value = array_map( 'intval', $value ); return $value[0] . ' AND ' . $value[1]; default: if ( ! is_numeric( $value ) ) { return false; } return (int) $value; } } /** * Builds a MySQL format date/time based on some query parameters. * * You can pass an array of values (year, month, etc.) with missing parameter values being defaulted to * either the maximum or minimum values (controlled by the $default_to parameter). Alternatively you can * pass a string that will be passed to date_create(). * * @since 3.7.0 * * @param string|array $datetime An array of parameters or a strtotime() string. * @param bool $default_to_max Whether to round up incomplete dates. Supported by values * of $datetime that are arrays, or string values that are a * subset of MySQL date format ('Y', 'Y-m', 'Y-m-d', 'Y-m-d H:i'). * Default: false. * @return string|false A MySQL format date/time or false on failure. */ public function build_mysql_datetime( $datetime, $default_to_max = false ) { if ( ! is_array( $datetime ) ) { /* * Try to parse some common date formats, so we can detect * the level of precision and support the 'inclusive' parameter. */ if ( preg_match( '/^(\d{4})$/', $datetime, $matches ) ) { // Y $datetime = array( 'year' => (int) $matches[1], ); } elseif ( preg_match( '/^(\d{4})\-(\d{2})$/', $datetime, $matches ) ) { // Y-m $datetime = array( 'year' => (int) $matches[1], 'month' => (int) $matches[2], ); } elseif ( preg_match( '/^(\d{4})\-(\d{2})\-(\d{2})$/', $datetime, $matches ) ) { // Y-m-d $datetime = array( 'year' => (int) $matches[1], 'month' => (int) $matches[2], 'day' => (int) $matches[3], ); } elseif ( preg_match( '/^(\d{4})\-(\d{2})\-(\d{2}) (\d{2}):(\d{2})$/', $datetime, $matches ) ) { // Y-m-d H:i $datetime = array( 'year' => (int) $matches[1], 'month' => (int) $matches[2], 'day' => (int) $matches[3], 'hour' => (int) $matches[4], 'minute' => (int) $matches[5], ); } // If no match is found, we don't support default_to_max. if ( ! is_array( $datetime ) ) { $wp_timezone = wp_timezone(); // Assume local timezone if not provided. $dt = date_create( $datetime, $wp_timezone ); if ( false === $dt ) { return gmdate( 'Y-m-d H:i:s', false ); } return $dt->setTimezone( $wp_timezone )->format( 'Y-m-d H:i:s' ); } } $datetime = array_map( 'absint', $datetime ); if ( ! isset( $datetime['year'] ) ) { $datetime['year'] = current_time( 'Y' ); } if ( ! isset( $datetime['month'] ) ) { $datetime['month'] = ( $default_to_max ) ? 12 : 1; } if ( ! isset( $datetime['day'] ) ) { $datetime['day'] = ( $default_to_max ) ? (int) gmdate( 't', mktime( 0, 0, 0, $datetime['month'], 1, $datetime['year'] ) ) : 1; } if ( ! isset( $datetime['hour'] ) ) { $datetime['hour'] = ( $default_to_max ) ? 23 : 0; } if ( ! isset( $datetime['minute'] ) ) { $datetime['minute'] = ( $default_to_max ) ? 59 : 0; } if ( ! isset( $datetime['second'] ) ) { $datetime['second'] = ( $default_to_max ) ? 59 : 0; } return sprintf( '%04d-%02d-%02d %02d:%02d:%02d', $datetime['year'], $datetime['month'], $datetime['day'], $datetime['hour'], $datetime['minute'], $datetime['second'] ); } /** * Builds a query string for comparing time values (hour, minute, second). * * If just hour, minute, or second is set than a normal comparison will be done. * However if multiple values are passed, a pseudo-decimal time will be created * in order to be able to accurately compare against. * * @since 3.7.0 * * @global wpdb $wpdb WordPress database abstraction object. * * @param string $column The column to query against. Needs to be pre-validated! * @param string $compare The comparison operator. Needs to be pre-validated! * @param int|null $hour Optional. An hour value (0-23). * @param int|null $minute Optional. A minute value (0-59). * @param int|null $second Optional. A second value (0-59). * @return string|false A query part or false on failure. */ public function build_time_query( $column, $compare, $hour = null, $minute = null, $second = null ) { global $wpdb; // Have to have at least one. if ( ! isset( $hour ) && ! isset( $minute ) && ! isset( $second ) ) { return false; } // Complex combined queries aren't supported for multi-value queries. if ( in_array( $compare, array( 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN' ), true ) ) { $return = array(); $value = $this->build_value( $compare, $hour ); if ( false !== $value ) { $return[] = "HOUR( $column ) $compare $value"; } $value = $this->build_value( $compare, $minute ); if ( false !== $value ) { $return[] = "MINUTE( $column ) $compare $value"; } $value = $this->build_value( $compare, $second ); if ( false !== $value ) { $return[] = "SECOND( $column ) $compare $value"; } return implode( ' AND ', $return ); } // Cases where just one unit is set. if ( isset( $hour ) && ! isset( $minute ) && ! isset( $second ) ) { $value = $this->build_value( $compare, $hour ); if ( false !== $value ) { return "HOUR( $column ) $compare $value"; } } elseif ( ! isset( $hour ) && isset( $minute ) && ! isset( $second ) ) { $value = $this->build_value( $compare, $minute ); if ( false !== $value ) { return "MINUTE( $column ) $compare $value"; } } elseif ( ! isset( $hour ) && ! isset( $minute ) && isset( $second ) ) { $value = $this->build_value( $compare, $second ); if ( false !== $value ) { return "SECOND( $column ) $compare $value"; } } // Single units were already handled. Since hour & second isn't allowed, minute must to be set. if ( ! isset( $minute ) ) { return false; } $format = ''; $time = ''; // Hour. if ( null !== $hour ) { $format .= '%H.'; $time .= sprintf( '%02d', $hour ) . '.'; } else { $format .= '0.'; $time .= '0.'; } // Minute. $format .= '%i'; $time .= sprintf( '%02d', $minute ); if ( isset( $second ) ) { $format .= '%s'; $time .= sprintf( '%02d', $second ); } return $wpdb->prepare( "DATE_FORMAT( $column, %s ) $compare %f", $format, $time ); } /** * Sanitizes a 'relation' operator. * * @since 6.0.3 * * @param string $relation Raw relation key from the query argument. * @return string Sanitized relation. Either 'AND' or 'OR'. */ public function sanitize_relation( $relation ) { if ( 'OR' === strtoupper( $relation ) ) { return 'OR'; } else { return 'AND'; } } }