wp/wp-includes/meta.php
changeset 7 cf61fcea0001
parent 5 5e2f62d02dcd
child 9 177826044cd9
equal deleted inserted replaced
6:490d5cc509ed 7:cf61fcea0001
     1 <?php
     1 <?php
     2 /**
     2 /**
     3  * Metadata API
     3  * Core Metadata API
     4  *
     4  *
     5  * Functions for retrieving and manipulating metadata of various WordPress object types. Metadata
     5  * Functions for retrieving and manipulating metadata of various WordPress object types. Metadata
     6  * for an object is a represented by a simple key-value pair. Objects may contain multiple
     6  * for an object is a represented by a simple key-value pair. Objects may contain multiple
     7  * metadata entries that share the same key and differ only in their value.
     7  * metadata entries that share the same key and differ only in their value.
     8  *
     8  *
     9  * @package WordPress
     9  * @package WordPress
    10  * @subpackage Meta
    10  * @subpackage Meta
       
    11  */
       
    12 
       
    13 /**
       
    14  * Add metadata for the specified object.
       
    15  *
    11  * @since 2.9.0
    16  * @since 2.9.0
    12  */
       
    13 
       
    14 /**
       
    15  * Add metadata for the specified object.
       
    16  *
       
    17  * @since 2.9.0
       
    18  *
    17  *
    19  * @global wpdb $wpdb WordPress database abstraction object.
    18  * @global wpdb $wpdb WordPress database abstraction object.
    20  *
    19  *
    21  * @param string $meta_type Type of object metadata is for (e.g., comment, post, or user)
    20  * @param string $meta_type  Type of object metadata is for (e.g., comment, post, or user)
    22  * @param int $object_id ID of the object metadata is for
    21  * @param int    $object_id  ID of the object metadata is for
    23  * @param string $meta_key Metadata key
    22  * @param string $meta_key   Metadata key
    24  * @param mixed $meta_value Metadata value. Must be serializable if non-scalar.
    23  * @param mixed  $meta_value Metadata value. Must be serializable if non-scalar.
    25  * @param bool $unique Optional, default is false. Whether the specified metadata key should be
    24  * @param bool   $unique     Optional, default is false.
    26  * 		unique for the object. If true, and the object already has a value for the specified
    25  *                           Whether the specified metadata key should be unique for the object.
    27  * 		metadata key, no change will be made
    26  *                           If true, and the object already has a value for the specified metadata key,
    28  * @return int|bool The meta ID on success, false on failure.
    27  *                           no change will be made.
       
    28  * @return int|false The meta ID on success, false on failure.
    29  */
    29  */
    30 function add_metadata($meta_type, $object_id, $meta_key, $meta_value, $unique = false) {
    30 function add_metadata($meta_type, $object_id, $meta_key, $meta_value, $unique = false) {
    31 	global $wpdb;
    31 	global $wpdb;
    32 
    32 
    33 	if ( ! $meta_type || ! $meta_key || ! is_numeric( $object_id ) ) {
    33 	if ( ! $meta_type || ! $meta_key || ! is_numeric( $object_id ) ) {
    41 
    41 
    42 	$table = _get_meta_table( $meta_type );
    42 	$table = _get_meta_table( $meta_type );
    43 	if ( ! $table ) {
    43 	if ( ! $table ) {
    44 		return false;
    44 		return false;
    45 	}
    45 	}
       
    46 
       
    47 	$meta_subtype = get_object_subtype( $meta_type, $object_id );
    46 
    48 
    47 	$column = sanitize_key($meta_type . '_id');
    49 	$column = sanitize_key($meta_type . '_id');
    48 
    50 
    49 	// expected_slashed ($meta_key)
    51 	// expected_slashed ($meta_key)
    50 	$meta_key = wp_unslash($meta_key);
    52 	$meta_key = wp_unslash($meta_key);
    51 	$meta_value = wp_unslash($meta_value);
    53 	$meta_value = wp_unslash($meta_value);
    52 	$meta_value = sanitize_meta( $meta_key, $meta_value, $meta_type );
    54 	$meta_value = sanitize_meta( $meta_key, $meta_value, $meta_type, $meta_subtype );
    53 
    55 
    54 	/**
    56 	/**
    55 	 * Filter whether to add metadata of a specific type.
    57 	 * Filters whether to add metadata of a specific type.
    56 	 *
    58 	 *
    57 	 * The dynamic portion of the hook, `$meta_type`, refers to the meta
    59 	 * The dynamic portion of the hook, `$meta_type`, refers to the meta
    58 	 * object type (comment, post, or user). Returning a non-null value
    60 	 * object type (comment, post, or user). Returning a non-null value
    59 	 * will effectively short-circuit the function.
    61 	 * will effectively short-circuit the function.
    60 	 *
    62 	 *
   130  *
   132  *
   131  * @since 2.9.0
   133  * @since 2.9.0
   132  *
   134  *
   133  * @global wpdb $wpdb WordPress database abstraction object.
   135  * @global wpdb $wpdb WordPress database abstraction object.
   134  *
   136  *
   135  * @param string $meta_type Type of object metadata is for (e.g., comment, post, or user)
   137  * @param string $meta_type  Type of object metadata is for (e.g., comment, post, or user)
   136  * @param int $object_id ID of the object metadata is for
   138  * @param int    $object_id  ID of the object metadata is for
   137  * @param string $meta_key Metadata key
   139  * @param string $meta_key   Metadata key
   138  * @param mixed $meta_value Metadata value. Must be serializable if non-scalar.
   140  * @param mixed  $meta_value Metadata value. Must be serializable if non-scalar.
   139  * @param mixed $prev_value Optional. If specified, only update existing metadata entries with
   141  * @param mixed  $prev_value Optional. If specified, only update existing metadata entries with
   140  * 		the specified value. Otherwise, update all entries.
   142  * 		                     the specified value. Otherwise, update all entries.
   141  * @return int|bool Meta ID if the key didn't exist, true on successful update, false on failure.
   143  * @return int|bool Meta ID if the key didn't exist, true on successful update, false on failure.
   142  */
   144  */
   143 function update_metadata($meta_type, $object_id, $meta_key, $meta_value, $prev_value = '') {
   145 function update_metadata($meta_type, $object_id, $meta_key, $meta_value, $prev_value = '') {
   144 	global $wpdb;
   146 	global $wpdb;
   145 
   147 
   155 	$table = _get_meta_table( $meta_type );
   157 	$table = _get_meta_table( $meta_type );
   156 	if ( ! $table ) {
   158 	if ( ! $table ) {
   157 		return false;
   159 		return false;
   158 	}
   160 	}
   159 
   161 
       
   162 	$meta_subtype = get_object_subtype( $meta_type, $object_id );
       
   163 
   160 	$column = sanitize_key($meta_type . '_id');
   164 	$column = sanitize_key($meta_type . '_id');
   161 	$id_column = 'user' == $meta_type ? 'umeta_id' : 'meta_id';
   165 	$id_column = 'user' == $meta_type ? 'umeta_id' : 'meta_id';
   162 
   166 
   163 	// expected_slashed ($meta_key)
   167 	// expected_slashed ($meta_key)
       
   168 	$raw_meta_key = $meta_key;
   164 	$meta_key = wp_unslash($meta_key);
   169 	$meta_key = wp_unslash($meta_key);
   165 	$passed_value = $meta_value;
   170 	$passed_value = $meta_value;
   166 	$meta_value = wp_unslash($meta_value);
   171 	$meta_value = wp_unslash($meta_value);
   167 	$meta_value = sanitize_meta( $meta_key, $meta_value, $meta_type );
   172 	$meta_value = sanitize_meta( $meta_key, $meta_value, $meta_type, $meta_subtype );
   168 
   173 
   169 	/**
   174 	/**
   170 	 * Filter whether to update metadata of a specific type.
   175 	 * Filters whether to update metadata of a specific type.
   171 	 *
   176 	 *
   172 	 * The dynamic portion of the hook, `$meta_type`, refers to the meta
   177 	 * The dynamic portion of the hook, `$meta_type`, refers to the meta
   173 	 * object type (comment, post, or user). Returning a non-null value
   178 	 * object type (comment, post, or user). Returning a non-null value
   174 	 * will effectively short-circuit the function.
   179 	 * will effectively short-circuit the function.
   175 	 *
   180 	 *
   196 		}
   201 		}
   197 	}
   202 	}
   198 
   203 
   199 	$meta_ids = $wpdb->get_col( $wpdb->prepare( "SELECT $id_column FROM $table WHERE meta_key = %s AND $column = %d", $meta_key, $object_id ) );
   204 	$meta_ids = $wpdb->get_col( $wpdb->prepare( "SELECT $id_column FROM $table WHERE meta_key = %s AND $column = %d", $meta_key, $object_id ) );
   200 	if ( empty( $meta_ids ) ) {
   205 	if ( empty( $meta_ids ) ) {
   201 		return add_metadata($meta_type, $object_id, $meta_key, $passed_value);
   206 		return add_metadata( $meta_type, $object_id, $raw_meta_key, $passed_value );
   202 	}
   207 	}
   203 
   208 
   204 	$_meta_value = $meta_value;
   209 	$_meta_value = $meta_value;
   205 	$meta_value = maybe_serialize( $meta_value );
   210 	$meta_value = maybe_serialize( $meta_value );
   206 
   211 
   225 		 * @param int    $object_id  Object ID.
   230 		 * @param int    $object_id  Object ID.
   226 		 * @param string $meta_key   Meta key.
   231 		 * @param string $meta_key   Meta key.
   227 		 * @param mixed  $meta_value Meta value.
   232 		 * @param mixed  $meta_value Meta value.
   228 		 */
   233 		 */
   229 		do_action( "update_{$meta_type}_meta", $meta_id, $object_id, $meta_key, $_meta_value );
   234 		do_action( "update_{$meta_type}_meta", $meta_id, $object_id, $meta_key, $_meta_value );
   230 	}
   235 
   231 
   236 		if ( 'post' == $meta_type ) {
   232 	if ( 'post' == $meta_type ) {
       
   233 		foreach ( $meta_ids as $meta_id ) {
       
   234 			/**
   237 			/**
   235 			 * Fires immediately before updating a post's metadata.
   238 			 * Fires immediately before updating a post's metadata.
   236 			 *
   239 			 *
   237 			 * @since 2.9.0
   240 			 * @since 2.9.0
   238 			 *
   241 			 *
   264 		 * @param int    $object_id  Object ID.
   267 		 * @param int    $object_id  Object ID.
   265 		 * @param string $meta_key   Meta key.
   268 		 * @param string $meta_key   Meta key.
   266 		 * @param mixed  $meta_value Meta value.
   269 		 * @param mixed  $meta_value Meta value.
   267 		 */
   270 		 */
   268 		do_action( "updated_{$meta_type}_meta", $meta_id, $object_id, $meta_key, $_meta_value );
   271 		do_action( "updated_{$meta_type}_meta", $meta_id, $object_id, $meta_key, $_meta_value );
   269 	}
   272 
   270 
   273 		if ( 'post' == $meta_type ) {
   271 	if ( 'post' == $meta_type ) {
       
   272 		foreach ( $meta_ids as $meta_id ) {
       
   273 			/**
   274 			/**
   274 			 * Fires immediately after updating a post's metadata.
   275 			 * Fires immediately after updating a post's metadata.
   275 			 *
   276 			 *
   276 			 * @since 2.9.0
   277 			 * @since 2.9.0
   277 			 *
   278 			 *
   292  *
   293  *
   293  * @since 2.9.0
   294  * @since 2.9.0
   294  *
   295  *
   295  * @global wpdb $wpdb WordPress database abstraction object.
   296  * @global wpdb $wpdb WordPress database abstraction object.
   296  *
   297  *
   297  * @param string $meta_type Type of object metadata is for (e.g., comment, post, or user)
   298  * @param string $meta_type  Type of object metadata is for (e.g., comment, post, or user)
   298  * @param int $object_id ID of the object metadata is for
   299  * @param int    $object_id  ID of the object metadata is for
   299  * @param string $meta_key Metadata key
   300  * @param string $meta_key   Metadata key
   300  * @param mixed $meta_value Optional. Metadata value. Must be serializable if non-scalar. If specified, only delete metadata entries
   301  * @param mixed  $meta_value Optional. Metadata value. Must be serializable if non-scalar. If specified, only delete
   301  * 		with this value. Otherwise, delete all entries with the specified meta_key.
   302  *                           metadata entries with this value. Otherwise, delete all entries with the specified meta_key.
   302  * @param bool $delete_all Optional, default is false. If true, delete matching metadata entries
   303  *                           Pass `null, `false`, or an empty string to skip this check. (For backward compatibility,
   303  * 		for all objects, ignoring the specified object_id. Otherwise, only delete matching
   304  *                           it is not possible to pass an empty string to delete those entries with an empty string
   304  * 		metadata entries for the specified object_id.
   305  *                           for a value.)
       
   306  * @param bool   $delete_all Optional, default is false. If true, delete matching metadata entries for all objects,
       
   307  *                           ignoring the specified object_id. Otherwise, only delete matching metadata entries for
       
   308  *                           the specified object_id.
   305  * @return bool True on successful delete, false on failure.
   309  * @return bool True on successful delete, false on failure.
   306  */
   310  */
   307 function delete_metadata($meta_type, $object_id, $meta_key, $meta_value = '', $delete_all = false) {
   311 function delete_metadata($meta_type, $object_id, $meta_key, $meta_value = '', $delete_all = false) {
   308 	global $wpdb;
   312 	global $wpdb;
   309 
   313 
   326 	// expected_slashed ($meta_key)
   330 	// expected_slashed ($meta_key)
   327 	$meta_key = wp_unslash($meta_key);
   331 	$meta_key = wp_unslash($meta_key);
   328 	$meta_value = wp_unslash($meta_value);
   332 	$meta_value = wp_unslash($meta_value);
   329 
   333 
   330 	/**
   334 	/**
   331 	 * Filter whether to delete metadata of a specific type.
   335 	 * Filters whether to delete metadata of a specific type.
   332 	 *
   336 	 *
   333 	 * The dynamic portion of the hook, `$meta_type`, refers to the meta
   337 	 * The dynamic portion of the hook, `$meta_type`, refers to the meta
   334 	 * object type (comment, post, or user). Returning a non-null value
   338 	 * object type (comment, post, or user). Returning a non-null value
   335 	 * will effectively short-circuit the function.
   339 	 * will effectively short-circuit the function.
   336 	 *
   340 	 *
   354 	$query = $wpdb->prepare( "SELECT $id_column FROM $table WHERE meta_key = %s", $meta_key );
   358 	$query = $wpdb->prepare( "SELECT $id_column FROM $table WHERE meta_key = %s", $meta_key );
   355 
   359 
   356 	if ( !$delete_all )
   360 	if ( !$delete_all )
   357 		$query .= $wpdb->prepare(" AND $type_column = %d", $object_id );
   361 		$query .= $wpdb->prepare(" AND $type_column = %d", $object_id );
   358 
   362 
   359 	if ( $meta_value )
   363 	if ( '' !== $meta_value && null !== $meta_value && false !== $meta_value )
   360 		$query .= $wpdb->prepare(" AND meta_value = %s", $meta_value );
   364 		$query .= $wpdb->prepare(" AND meta_value = %s", $meta_value );
   361 
   365 
   362 	$meta_ids = $wpdb->get_col( $query );
   366 	$meta_ids = $wpdb->get_col( $query );
   363 	if ( !count( $meta_ids ) )
   367 	if ( !count( $meta_ids ) )
   364 		return false;
   368 		return false;
   365 
   369 
   366 	if ( $delete_all )
   370 	if ( $delete_all ) {
   367 		$object_ids = $wpdb->get_col( $wpdb->prepare( "SELECT $type_column FROM $table WHERE meta_key = %s", $meta_key ) );
   371 		if ( '' !== $meta_value && null !== $meta_value && false !== $meta_value ) {
       
   372 			$object_ids = $wpdb->get_col( $wpdb->prepare( "SELECT $type_column FROM $table WHERE meta_key = %s AND meta_value = %s", $meta_key, $meta_value ) );
       
   373 		} else {
       
   374 			$object_ids = $wpdb->get_col( $wpdb->prepare( "SELECT $type_column FROM $table WHERE meta_key = %s", $meta_key ) );
       
   375 		}
       
   376 	}
   368 
   377 
   369 	/**
   378 	/**
   370 	 * Fires immediately before deleting metadata of a specific type.
   379 	 * Fires immediately before deleting metadata of a specific type.
   371 	 *
   380 	 *
   372 	 * The dynamic portion of the hook, `$meta_type`, refers to the meta
   381 	 * The dynamic portion of the hook, `$meta_type`, refers to the meta
   442  * Retrieve metadata for the specified object.
   451  * Retrieve metadata for the specified object.
   443  *
   452  *
   444  * @since 2.9.0
   453  * @since 2.9.0
   445  *
   454  *
   446  * @param string $meta_type Type of object metadata is for (e.g., comment, post, or user)
   455  * @param string $meta_type Type of object metadata is for (e.g., comment, post, or user)
   447  * @param int $object_id ID of the object metadata is for
   456  * @param int    $object_id ID of the object metadata is for
   448  * @param string $meta_key Optional. Metadata key. If not specified, retrieve all metadata for
   457  * @param string $meta_key  Optional. Metadata key. If not specified, retrieve all metadata for
   449  * 		the specified object.
   458  * 		                    the specified object.
   450  * @param bool $single Optional, default is false. If true, return only the first value of the
   459  * @param bool   $single    Optional, default is false.
   451  * 		specified meta_key. This parameter has no effect if meta_key is not specified.
   460  *                          If true, return only the first value of the specified meta_key.
       
   461  *                          This parameter has no effect if meta_key is not specified.
   452  * @return mixed Single metadata value, or array of values
   462  * @return mixed Single metadata value, or array of values
   453  */
   463  */
   454 function get_metadata($meta_type, $object_id, $meta_key = '', $single = false) {
   464 function get_metadata($meta_type, $object_id, $meta_key = '', $single = false) {
   455 	if ( ! $meta_type || ! is_numeric( $object_id ) ) {
   465 	if ( ! $meta_type || ! is_numeric( $object_id ) ) {
   456 		return false;
   466 		return false;
   460 	if ( ! $object_id ) {
   470 	if ( ! $object_id ) {
   461 		return false;
   471 		return false;
   462 	}
   472 	}
   463 
   473 
   464 	/**
   474 	/**
   465 	 * Filter whether to retrieve metadata of a specific type.
   475 	 * Filters whether to retrieve metadata of a specific type.
   466 	 *
   476 	 *
   467 	 * The dynamic portion of the hook, `$meta_type`, refers to the meta
   477 	 * The dynamic portion of the hook, `$meta_type`, refers to the meta
   468 	 * object type (comment, post, or user). Returning a non-null value
   478 	 * object type (comment, post, or user). Returning a non-null value
   469 	 * will effectively short-circuit the function.
   479 	 * will effectively short-circuit the function.
   470 	 *
   480 	 *
   471 	 * @since 3.1.0
   481 	 * @since 3.1.0
   472 	 *
   482 	 *
   473 	 * @param null|array|string $value     The value get_metadata() should
   483 	 * @param null|array|string $value     The value get_metadata() should return - a single metadata value,
   474 	 *                                     return - a single metadata value,
       
   475 	 *                                     or an array of values.
   484 	 *                                     or an array of values.
   476 	 * @param int               $object_id Object ID.
   485 	 * @param int               $object_id Object ID.
   477 	 * @param string            $meta_key  Meta key.
   486 	 * @param string            $meta_key  Meta key.
   478 	 * @param string|array      $single    Meta value, or an array of values.
   487 	 * @param bool              $single    Whether to return only the first value of the specified $meta_key.
   479 	 */
   488 	 */
   480 	$check = apply_filters( "get_{$meta_type}_metadata", null, $object_id, $meta_key, $single );
   489 	$check = apply_filters( "get_{$meta_type}_metadata", null, $object_id, $meta_key, $single );
   481 	if ( null !== $check ) {
   490 	if ( null !== $check ) {
   482 		if ( $single && is_array( $check ) )
   491 		if ( $single && is_array( $check ) )
   483 			return $check[0];
   492 			return $check[0];
   513  * Determine if a meta key is set for a given object
   522  * Determine if a meta key is set for a given object
   514  *
   523  *
   515  * @since 3.3.0
   524  * @since 3.3.0
   516  *
   525  *
   517  * @param string $meta_type Type of object metadata is for (e.g., comment, post, or user)
   526  * @param string $meta_type Type of object metadata is for (e.g., comment, post, or user)
   518  * @param int $object_id ID of the object metadata is for
   527  * @param int    $object_id ID of the object metadata is for
   519  * @param string $meta_key Metadata key.
   528  * @param string $meta_key  Metadata key.
   520  * @return boolean true of the key is set, false if not.
   529  * @return bool True of the key is set, false if not.
   521  */
   530  */
   522 function metadata_exists( $meta_type, $object_id, $meta_key ) {
   531 function metadata_exists( $meta_type, $object_id, $meta_key ) {
   523 	if ( ! $meta_type || ! is_numeric( $object_id ) ) {
   532 	if ( ! $meta_type || ! is_numeric( $object_id ) ) {
   524 		return false;
   533 		return false;
   525 	}
   534 	}
   550 /**
   559 /**
   551  * Get meta data by meta ID
   560  * Get meta data by meta ID
   552  *
   561  *
   553  * @since 3.3.0
   562  * @since 3.3.0
   554  *
   563  *
   555  * @param string $meta_type Type of object metadata is for (e.g., comment, post, or user)
   564  * @global wpdb $wpdb WordPress database abstraction object.
   556  * @param int $meta_id ID for a specific meta row
   565  *
   557  * @return object Meta object or false.
   566  * @param string $meta_type Type of object metadata is for (e.g., comment, post, term, or user).
       
   567  * @param int    $meta_id   ID for a specific meta row
       
   568  * @return object|false Meta object or false.
   558  */
   569  */
   559 function get_metadata_by_mid( $meta_type, $meta_id ) {
   570 function get_metadata_by_mid( $meta_type, $meta_id ) {
   560 	global $wpdb;
   571 	global $wpdb;
   561 
   572 
   562 	if ( ! $meta_type || ! is_numeric( $meta_id ) ) {
   573 	if ( ! $meta_type || ! is_numeric( $meta_id ) || floor( $meta_id ) != $meta_id ) {
   563 		return false;
   574 		return false;
   564 	}
   575 	}
   565 
   576 
   566 	$meta_id = absint( $meta_id );
   577 	$meta_id = intval( $meta_id );
   567 	if ( ! $meta_id ) {
   578 	if ( $meta_id <= 0 ) {
   568 		return false;
   579 		return false;
   569 	}
   580 	}
   570 
   581 
   571 	$table = _get_meta_table( $meta_type );
   582 	$table = _get_meta_table( $meta_type );
   572 	if ( ! $table ) {
   583 	if ( ! $table ) {
   589 /**
   600 /**
   590  * Update meta data by meta ID
   601  * Update meta data by meta ID
   591  *
   602  *
   592  * @since 3.3.0
   603  * @since 3.3.0
   593  *
   604  *
   594  * @param string $meta_type Type of object metadata is for (e.g., comment, post, or user)
   605  * @global wpdb $wpdb WordPress database abstraction object.
   595  * @param int $meta_id ID for a specific meta row
   606  *
       
   607  * @param string $meta_type  Type of object metadata is for (e.g., comment, post, or user)
       
   608  * @param int    $meta_id    ID for a specific meta row
   596  * @param string $meta_value Metadata value
   609  * @param string $meta_value Metadata value
   597  * @param string $meta_key Optional, you can provide a meta key to update it
   610  * @param string $meta_key   Optional, you can provide a meta key to update it
   598  * @return bool True on successful update, false on failure.
   611  * @return bool True on successful update, false on failure.
   599  */
   612  */
   600 function update_metadata_by_mid( $meta_type, $meta_id, $meta_value, $meta_key = false ) {
   613 function update_metadata_by_mid( $meta_type, $meta_id, $meta_value, $meta_key = false ) {
   601 	global $wpdb;
   614 	global $wpdb;
   602 
   615 
   603 	// Make sure everything is valid.
   616 	// Make sure everything is valid.
   604 	if ( ! $meta_type || ! is_numeric( $meta_id ) ) {
   617 	if ( ! $meta_type || ! is_numeric( $meta_id ) || floor( $meta_id ) != $meta_id ) {
   605 		return false;
   618 		return false;
   606 	}
   619 	}
   607 
   620 
   608 	$meta_id = absint( $meta_id );
   621 	$meta_id = intval( $meta_id );
   609 	if ( ! $meta_id ) {
   622 	if ( $meta_id <= 0 ) {
   610 		return false;
   623 		return false;
   611 	}
   624 	}
   612 
   625 
   613 	$table = _get_meta_table( $meta_type );
   626 	$table = _get_meta_table( $meta_type );
   614 	if ( ! $table ) {
   627 	if ( ! $table ) {
   629 			$meta_key = $original_key;
   642 			$meta_key = $original_key;
   630 		} elseif ( ! is_string( $meta_key ) ) {
   643 		} elseif ( ! is_string( $meta_key ) ) {
   631 			return false;
   644 			return false;
   632 		}
   645 		}
   633 
   646 
       
   647 		$meta_subtype = get_object_subtype( $meta_type, $object_id );
       
   648 
   634 		// Sanitize the meta
   649 		// Sanitize the meta
   635 		$_meta_value = $meta_value;
   650 		$_meta_value = $meta_value;
   636 		$meta_value = sanitize_meta( $meta_key, $meta_value, $meta_type );
   651 		$meta_value  = sanitize_meta( $meta_key, $meta_value, $meta_type, $meta_subtype );
   637 		$meta_value = maybe_serialize( $meta_value );
   652 		$meta_value  = maybe_serialize( $meta_value );
   638 
   653 
   639 		// Format the data query arguments.
   654 		// Format the data query arguments.
   640 		$data = array(
   655 		$data = array(
   641 			'meta_key' => $meta_key,
   656 			'meta_key' => $meta_key,
   642 			'meta_value' => $meta_value
   657 			'meta_value' => $meta_value
   680 /**
   695 /**
   681  * Delete meta data by meta ID
   696  * Delete meta data by meta ID
   682  *
   697  *
   683  * @since 3.3.0
   698  * @since 3.3.0
   684  *
   699  *
   685  * @param string $meta_type Type of object metadata is for (e.g., comment, post, or user)
   700  * @global wpdb $wpdb WordPress database abstraction object.
   686  * @param int $meta_id ID for a specific meta row
   701  *
       
   702  * @param string $meta_type Type of object metadata is for (e.g., comment, post, term, or user).
       
   703  * @param int    $meta_id   ID for a specific meta row
   687  * @return bool True on successful delete, false on failure.
   704  * @return bool True on successful delete, false on failure.
   688  */
   705  */
   689 function delete_metadata_by_mid( $meta_type, $meta_id ) {
   706 function delete_metadata_by_mid( $meta_type, $meta_id ) {
   690 	global $wpdb;
   707 	global $wpdb;
   691 
   708 
   692 	// Make sure everything is valid.
   709 	// Make sure everything is valid.
   693 	if ( ! $meta_type || ! is_numeric( $meta_id ) ) {
   710 	if ( ! $meta_type || ! is_numeric( $meta_id ) || floor( $meta_id ) != $meta_id ) {
   694 		return false;
   711 		return false;
   695 	}
   712 	}
   696 
   713 
   697 	$meta_id = absint( $meta_id );
   714 	$meta_id = intval( $meta_id );
   698 	if ( ! $meta_id ) {
   715 	if ( $meta_id <= 0 ) {
   699 		return false;
   716 		return false;
   700 	}
   717 	}
   701 
   718 
   702 	$table = _get_meta_table( $meta_type );
   719 	$table = _get_meta_table( $meta_type );
   703 	if ( ! $table ) {
   720 	if ( ! $table ) {
   767  *
   784  *
   768  * @since 2.9.0
   785  * @since 2.9.0
   769  *
   786  *
   770  * @global wpdb $wpdb WordPress database abstraction object.
   787  * @global wpdb $wpdb WordPress database abstraction object.
   771  *
   788  *
   772  * @param string $meta_type Type of object metadata is for (e.g., comment, post, or user)
   789  * @param string    $meta_type  Type of object metadata is for (e.g., comment, post, or user)
   773  * @param int|array $object_ids array or comma delimited list of object IDs to update cache for
   790  * @param int|array $object_ids Array or comma delimited list of object IDs to update cache for
   774  * @return mixed Metadata cache for the specified objects, or false on failure.
   791  * @return array|false Metadata cache for the specified objects, or false on failure.
   775  */
   792  */
   776 function update_meta_cache($meta_type, $object_ids) {
   793 function update_meta_cache($meta_type, $object_ids) {
   777 	global $wpdb;
   794 	global $wpdb;
   778 
   795 
   779 	if ( ! $meta_type || ! $object_ids ) {
   796 	if ( ! $meta_type || ! $object_ids ) {
   838 
   855 
   839 	return $cache;
   856 	return $cache;
   840 }
   857 }
   841 
   858 
   842 /**
   859 /**
       
   860  * Retrieves the queue for lazy-loading metadata.
       
   861  *
       
   862  * @since 4.5.0
       
   863  *
       
   864  * @return WP_Metadata_Lazyloader $lazyloader Metadata lazyloader queue.
       
   865  */
       
   866 function wp_metadata_lazyloader() {
       
   867 	static $wp_metadata_lazyloader;
       
   868 
       
   869 	if ( null === $wp_metadata_lazyloader ) {
       
   870 		$wp_metadata_lazyloader = new WP_Metadata_Lazyloader();
       
   871 	}
       
   872 
       
   873 	return $wp_metadata_lazyloader;
       
   874 }
       
   875 
       
   876 /**
   843  * Given a meta query, generates SQL clauses to be appended to a main query.
   877  * Given a meta query, generates SQL clauses to be appended to a main query.
   844  *
   878  *
   845  * @since 3.2.0
   879  * @since 3.2.0
   846  *
   880  *
   847  * @see WP_Meta_Query
   881  * @see WP_Meta_Query
   857 	$meta_query_obj = new WP_Meta_Query( $meta_query );
   891 	$meta_query_obj = new WP_Meta_Query( $meta_query );
   858 	return $meta_query_obj->get_sql( $type, $primary_table, $primary_id_column, $context );
   892 	return $meta_query_obj->get_sql( $type, $primary_table, $primary_id_column, $context );
   859 }
   893 }
   860 
   894 
   861 /**
   895 /**
   862  * Class for generating SQL clauses that filter a primary query according to metadata keys and values.
       
   863  *
       
   864  * `WP_Meta_Query` is a helper that allows primary query classes, such as {@see WP_Query} and {@see WP_User_Query},
       
   865  * to filter their results by object metadata, by generating `JOIN` and `WHERE` subclauses to be attached
       
   866  * to the primary SQL query string.
       
   867  *
       
   868  * @since 3.2.0
       
   869  */
       
   870 class WP_Meta_Query {
       
   871 	/**
       
   872 	 * Array of metadata queries.
       
   873 	 *
       
   874 	 * See {@see WP_Meta_Query::__construct()} for information on meta query arguments.
       
   875 	 *
       
   876 	 * @since 3.2.0
       
   877 	 * @access public
       
   878 	 * @var array
       
   879 	 */
       
   880 	public $queries = array();
       
   881 
       
   882 	/**
       
   883 	 * The relation between the queries. Can be one of 'AND' or 'OR'.
       
   884 	 *
       
   885 	 * @since 3.2.0
       
   886 	 * @access public
       
   887 	 * @var string
       
   888 	 */
       
   889 	public $relation;
       
   890 
       
   891 	/**
       
   892 	 * Database table to query for the metadata.
       
   893 	 *
       
   894 	 * @since 4.1.0
       
   895 	 * @access public
       
   896 	 * @var string
       
   897 	 */
       
   898 	public $meta_table;
       
   899 
       
   900 	/**
       
   901 	 * Column in meta_table that represents the ID of the object the metadata belongs to.
       
   902 	 *
       
   903 	 * @since 4.1.0
       
   904 	 * @access public
       
   905 	 * @var string
       
   906 	 */
       
   907 	public $meta_id_column;
       
   908 
       
   909 	/**
       
   910 	 * Database table that where the metadata's objects are stored (eg $wpdb->users).
       
   911 	 *
       
   912 	 * @since 4.1.0
       
   913 	 * @access public
       
   914 	 * @var string
       
   915 	 */
       
   916 	public $primary_table;
       
   917 
       
   918 	/**
       
   919 	 * Column in primary_table that represents the ID of the object.
       
   920 	 *
       
   921 	 * @since 4.1.0
       
   922 	 * @access public
       
   923 	 * @var string
       
   924 	 */
       
   925 	public $primary_id_column;
       
   926 
       
   927 	/**
       
   928 	 * A flat list of table aliases used in JOIN clauses.
       
   929 	 *
       
   930 	 * @since 4.1.0
       
   931 	 * @access protected
       
   932 	 * @var array
       
   933 	 */
       
   934 	protected $table_aliases = array();
       
   935 
       
   936 	/**
       
   937 	 * A flat list of clauses, keyed by clause 'name'.
       
   938 	 *
       
   939 	 * @since 4.2.0
       
   940 	 * @access protected
       
   941 	 * @var array
       
   942 	 */
       
   943 	protected $clauses = array();
       
   944 
       
   945 	/**
       
   946 	 * Constructor.
       
   947 	 *
       
   948 	 * @since 3.2.0
       
   949 	 * @since 4.2.0 Introduced support for naming query clauses by associative array keys.
       
   950 	 *
       
   951 	 * @access public
       
   952 	 *
       
   953 	 * @param array $meta_query {
       
   954 	 *     Array of meta query clauses. When first-order clauses use strings as their array keys, they may be
       
   955 	 *     referenced in the 'orderby' parameter of the parent query.
       
   956 	 *
       
   957 	 *     @type string $relation Optional. The MySQL keyword used to join
       
   958 	 *                            the clauses of the query. Accepts 'AND', or 'OR'. Default 'AND'.
       
   959 	 *     @type array {
       
   960 	 *         Optional. An array of first-order clause parameters, or another fully-formed meta query.
       
   961 	 *
       
   962 	 *         @type string $key     Meta key to filter by.
       
   963 	 *         @type string $value   Meta value to filter by.
       
   964 	 *         @type string $compare MySQL operator used for comparing the $value. Accepts '=',
       
   965 	 *                               '!=', '>', '>=', '<', '<=', 'LIKE', 'NOT LIKE', 'IN', 'NOT IN',
       
   966 	 *                               'BETWEEN', 'NOT BETWEEN', 'REGEXP', 'NOT REGEXP', or 'RLIKE'.
       
   967 	 *                               Default is 'IN' when `$value` is an array, '=' otherwise.
       
   968 	 *         @type string $type    MySQL data type that the meta_value column will be CAST to for
       
   969 	 *                               comparisons. Accepts 'NUMERIC', 'BINARY', 'CHAR', 'DATE',
       
   970 	 *                               'DATETIME', 'DECIMAL', 'SIGNED', 'TIME', or 'UNSIGNED'.
       
   971 	 *                               Default is 'CHAR'.
       
   972 	 *     }
       
   973 	 * }
       
   974 	 */
       
   975 	public function __construct( $meta_query = false ) {
       
   976 		if ( !$meta_query )
       
   977 			return;
       
   978 
       
   979 		if ( isset( $meta_query['relation'] ) && strtoupper( $meta_query['relation'] ) == 'OR' ) {
       
   980 			$this->relation = 'OR';
       
   981 		} else {
       
   982 			$this->relation = 'AND';
       
   983 		}
       
   984 
       
   985 		$this->queries = $this->sanitize_query( $meta_query );
       
   986 	}
       
   987 
       
   988 	/**
       
   989 	 * Ensure the 'meta_query' argument passed to the class constructor is well-formed.
       
   990 	 *
       
   991 	 * Eliminates empty items and ensures that a 'relation' is set.
       
   992 	 *
       
   993 	 * @since 4.1.0
       
   994 	 * @access public
       
   995 	 *
       
   996 	 * @param array $queries Array of query clauses.
       
   997 	 * @return array Sanitized array of query clauses.
       
   998 	 */
       
   999 	public function sanitize_query( $queries ) {
       
  1000 		$clean_queries = array();
       
  1001 
       
  1002 		if ( ! is_array( $queries ) ) {
       
  1003 			return $clean_queries;
       
  1004 		}
       
  1005 
       
  1006 		foreach ( $queries as $key => $query ) {
       
  1007 			if ( 'relation' === $key ) {
       
  1008 				$relation = $query;
       
  1009 
       
  1010 			} elseif ( ! is_array( $query ) ) {
       
  1011 				continue;
       
  1012 
       
  1013 			// First-order clause.
       
  1014 			} elseif ( $this->is_first_order_clause( $query ) ) {
       
  1015 				if ( isset( $query['value'] ) && array() === $query['value'] ) {
       
  1016 					unset( $query['value'] );
       
  1017 				}
       
  1018 
       
  1019 				$clean_queries[ $key ] = $query;
       
  1020 
       
  1021 			// Otherwise, it's a nested query, so we recurse.
       
  1022 			} else {
       
  1023 				$cleaned_query = $this->sanitize_query( $query );
       
  1024 
       
  1025 				if ( ! empty( $cleaned_query ) ) {
       
  1026 					$clean_queries[ $key ] = $cleaned_query;
       
  1027 				}
       
  1028 			}
       
  1029 		}
       
  1030 
       
  1031 		if ( empty( $clean_queries ) ) {
       
  1032 			return $clean_queries;
       
  1033 		}
       
  1034 
       
  1035 		// Sanitize the 'relation' key provided in the query.
       
  1036 		if ( isset( $relation ) && 'OR' === strtoupper( $relation ) ) {
       
  1037 			$clean_queries['relation'] = 'OR';
       
  1038 
       
  1039 		/*
       
  1040 		 * If there is only a single clause, call the relation 'OR'.
       
  1041 		 * This value will not actually be used to join clauses, but it
       
  1042 		 * simplifies the logic around combining key-only queries.
       
  1043 		 */
       
  1044 		} elseif ( 1 === count( $clean_queries ) ) {
       
  1045 			$clean_queries['relation'] = 'OR';
       
  1046 
       
  1047 		// Default to AND.
       
  1048 		} else {
       
  1049 			$clean_queries['relation'] = 'AND';
       
  1050 		}
       
  1051 
       
  1052 		return $clean_queries;
       
  1053 	}
       
  1054 
       
  1055 	/**
       
  1056 	 * Determine whether a query clause is first-order.
       
  1057 	 *
       
  1058 	 * A first-order meta query clause is one that has either a 'key' or
       
  1059 	 * a 'value' array key.
       
  1060 	 *
       
  1061 	 * @since 4.1.0
       
  1062 	 * @access protected
       
  1063 	 *
       
  1064 	 * @param array $query Meta query arguments.
       
  1065 	 * @return bool Whether the query clause is a first-order clause.
       
  1066 	 */
       
  1067 	protected function is_first_order_clause( $query ) {
       
  1068 		return isset( $query['key'] ) || isset( $query['value'] );
       
  1069 	}
       
  1070 
       
  1071 	/**
       
  1072 	 * Constructs a meta query based on 'meta_*' query vars
       
  1073 	 *
       
  1074 	 * @since 3.2.0
       
  1075 	 * @access public
       
  1076 	 *
       
  1077 	 * @param array $qv The query variables
       
  1078 	 */
       
  1079 	public function parse_query_vars( $qv ) {
       
  1080 		$meta_query = array();
       
  1081 
       
  1082 		/*
       
  1083 		 * For orderby=meta_value to work correctly, simple query needs to be
       
  1084 		 * first (so that its table join is against an unaliased meta table) and
       
  1085 		 * needs to be its own clause (so it doesn't interfere with the logic of
       
  1086 		 * the rest of the meta_query).
       
  1087 		 */
       
  1088 		$primary_meta_query = array();
       
  1089 		foreach ( array( 'key', 'compare', 'type' ) as $key ) {
       
  1090 			if ( ! empty( $qv[ "meta_$key" ] ) ) {
       
  1091 				$primary_meta_query[ $key ] = $qv[ "meta_$key" ];
       
  1092 			}
       
  1093 		}
       
  1094 
       
  1095 		// WP_Query sets 'meta_value' = '' by default.
       
  1096 		if ( isset( $qv['meta_value'] ) && '' !== $qv['meta_value'] && ( ! is_array( $qv['meta_value'] ) || $qv['meta_value'] ) ) {
       
  1097 			$primary_meta_query['value'] = $qv['meta_value'];
       
  1098 		}
       
  1099 
       
  1100 		$existing_meta_query = isset( $qv['meta_query'] ) && is_array( $qv['meta_query'] ) ? $qv['meta_query'] : array();
       
  1101 
       
  1102 		if ( ! empty( $primary_meta_query ) && ! empty( $existing_meta_query ) ) {
       
  1103 			$meta_query = array(
       
  1104 				'relation' => 'AND',
       
  1105 				$primary_meta_query,
       
  1106 				$existing_meta_query,
       
  1107 			);
       
  1108 		} elseif ( ! empty( $primary_meta_query ) ) {
       
  1109 			$meta_query = array(
       
  1110 				$primary_meta_query,
       
  1111 			);
       
  1112 		} elseif ( ! empty( $existing_meta_query ) ) {
       
  1113 			$meta_query = $existing_meta_query;
       
  1114 		}
       
  1115 
       
  1116 		$this->__construct( $meta_query );
       
  1117 	}
       
  1118 
       
  1119 	/**
       
  1120 	 * Return the appropriate alias for the given meta type if applicable.
       
  1121 	 *
       
  1122 	 * @since 3.7.0
       
  1123 	 * @access public
       
  1124 	 *
       
  1125 	 * @param string $type MySQL type to cast meta_value.
       
  1126 	 * @return string MySQL type.
       
  1127 	 */
       
  1128 	public function get_cast_for_type( $type = '' ) {
       
  1129 		if ( empty( $type ) )
       
  1130 			return 'CHAR';
       
  1131 
       
  1132 		$meta_type = strtoupper( $type );
       
  1133 
       
  1134 		if ( ! preg_match( '/^(?:BINARY|CHAR|DATE|DATETIME|SIGNED|UNSIGNED|TIME|NUMERIC(?:\(\d+(?:,\s?\d+)?\))?|DECIMAL(?:\(\d+(?:,\s?\d+)?\))?)$/', $meta_type ) )
       
  1135 			return 'CHAR';
       
  1136 
       
  1137 		if ( 'NUMERIC' == $meta_type )
       
  1138 			$meta_type = 'SIGNED';
       
  1139 
       
  1140 		return $meta_type;
       
  1141 	}
       
  1142 
       
  1143 	/**
       
  1144 	 * Generates SQL clauses to be appended to a main query.
       
  1145 	 *
       
  1146 	 * @since 3.2.0
       
  1147 	 * @access public
       
  1148 	 *
       
  1149 	 * @param string $type              Type of meta, eg 'user', 'post'.
       
  1150 	 * @param string $primary_table     Database table where the object being filtered is stored (eg wp_users).
       
  1151 	 * @param string $primary_id_column ID column for the filtered object in $primary_table.
       
  1152 	 * @param object $context           Optional. The main query object.
       
  1153 	 * @return array {
       
  1154 	 *     Array containing JOIN and WHERE SQL clauses to append to the main query.
       
  1155 	 *
       
  1156 	 *     @type string $join  SQL fragment to append to the main JOIN clause.
       
  1157 	 *     @type string $where SQL fragment to append to the main WHERE clause.
       
  1158 	 * }
       
  1159 	 */
       
  1160 	public function get_sql( $type, $primary_table, $primary_id_column, $context = null ) {
       
  1161 		if ( ! $meta_table = _get_meta_table( $type ) ) {
       
  1162 			return false;
       
  1163 		}
       
  1164 
       
  1165 		$this->meta_table     = $meta_table;
       
  1166 		$this->meta_id_column = sanitize_key( $type . '_id' );
       
  1167 
       
  1168 		$this->primary_table     = $primary_table;
       
  1169 		$this->primary_id_column = $primary_id_column;
       
  1170 
       
  1171 		$sql = $this->get_sql_clauses();
       
  1172 
       
  1173 		/*
       
  1174 		 * If any JOINs are LEFT JOINs (as in the case of NOT EXISTS), then all JOINs should
       
  1175 		 * be LEFT. Otherwise posts with no metadata will be excluded from results.
       
  1176 		 */
       
  1177 		if ( false !== strpos( $sql['join'], 'LEFT JOIN' ) ) {
       
  1178 			$sql['join'] = str_replace( 'INNER JOIN', 'LEFT JOIN', $sql['join'] );
       
  1179 		}
       
  1180 
       
  1181 		/**
       
  1182 		 * Filter the meta query's generated SQL.
       
  1183 		 *
       
  1184 		 * @since 3.1.0
       
  1185 		 *
       
  1186 		 * @param array $args {
       
  1187 		 *     An array of meta query SQL arguments.
       
  1188 		 *
       
  1189 		 *     @type array  $clauses           Array containing the query's JOIN and WHERE clauses.
       
  1190 		 *     @type array  $queries           Array of meta queries.
       
  1191 		 *     @type string $type              Type of meta.
       
  1192 		 *     @type string $primary_table     Primary table.
       
  1193 		 *     @type string $primary_id_column Primary column ID.
       
  1194 		 *     @type object $context           The main query object.
       
  1195 		 * }
       
  1196 		 */
       
  1197 		return apply_filters_ref_array( 'get_meta_sql', array( $sql, $this->queries, $type, $primary_table, $primary_id_column, $context ) );
       
  1198 	}
       
  1199 
       
  1200 	/**
       
  1201 	 * Generate SQL clauses to be appended to a main query.
       
  1202 	 *
       
  1203 	 * Called by the public {@see WP_Meta_Query::get_sql()}, this method
       
  1204 	 * is abstracted out to maintain parity with the other Query classes.
       
  1205 	 *
       
  1206 	 * @since 4.1.0
       
  1207 	 * @access protected
       
  1208 	 *
       
  1209 	 * @return array {
       
  1210 	 *     Array containing JOIN and WHERE SQL clauses to append to the main query.
       
  1211 	 *
       
  1212 	 *     @type string $join  SQL fragment to append to the main JOIN clause.
       
  1213 	 *     @type string $where SQL fragment to append to the main WHERE clause.
       
  1214 	 * }
       
  1215 	 */
       
  1216 	protected function get_sql_clauses() {
       
  1217 		/*
       
  1218 		 * $queries are passed by reference to get_sql_for_query() for recursion.
       
  1219 		 * To keep $this->queries unaltered, pass a copy.
       
  1220 		 */
       
  1221 		$queries = $this->queries;
       
  1222 		$sql = $this->get_sql_for_query( $queries );
       
  1223 
       
  1224 		if ( ! empty( $sql['where'] ) ) {
       
  1225 			$sql['where'] = ' AND ' . $sql['where'];
       
  1226 		}
       
  1227 
       
  1228 		return $sql;
       
  1229 	}
       
  1230 
       
  1231 	/**
       
  1232 	 * Generate SQL clauses for a single query array.
       
  1233 	 *
       
  1234 	 * If nested subqueries are found, this method recurses the tree to
       
  1235 	 * produce the properly nested SQL.
       
  1236 	 *
       
  1237 	 * @since 4.1.0
       
  1238 	 * @access protected
       
  1239 	 *
       
  1240 	 * @param array $query Query to parse, passed by reference.
       
  1241 	 * @param int   $depth Optional. Number of tree levels deep we currently are.
       
  1242 	 *                     Used to calculate indentation. Default 0.
       
  1243 	 * @return array {
       
  1244 	 *     Array containing JOIN and WHERE SQL clauses to append to a single query array.
       
  1245 	 *
       
  1246 	 *     @type string $join  SQL fragment to append to the main JOIN clause.
       
  1247 	 *     @type string $where SQL fragment to append to the main WHERE clause.
       
  1248 	 * }
       
  1249 	 */
       
  1250 	protected function get_sql_for_query( &$query, $depth = 0 ) {
       
  1251 		$sql_chunks = array(
       
  1252 			'join'  => array(),
       
  1253 			'where' => array(),
       
  1254 		);
       
  1255 
       
  1256 		$sql = array(
       
  1257 			'join'  => '',
       
  1258 			'where' => '',
       
  1259 		);
       
  1260 
       
  1261 		$indent = '';
       
  1262 		for ( $i = 0; $i < $depth; $i++ ) {
       
  1263 			$indent .= "  ";
       
  1264 		}
       
  1265 
       
  1266 		foreach ( $query as $key => &$clause ) {
       
  1267 			if ( 'relation' === $key ) {
       
  1268 				$relation = $query['relation'];
       
  1269 			} elseif ( is_array( $clause ) ) {
       
  1270 
       
  1271 				// This is a first-order clause.
       
  1272 				if ( $this->is_first_order_clause( $clause ) ) {
       
  1273 					$clause_sql = $this->get_sql_for_clause( $clause, $query, $key );
       
  1274 
       
  1275 					$where_count = count( $clause_sql['where'] );
       
  1276 					if ( ! $where_count ) {
       
  1277 						$sql_chunks['where'][] = '';
       
  1278 					} elseif ( 1 === $where_count ) {
       
  1279 						$sql_chunks['where'][] = $clause_sql['where'][0];
       
  1280 					} else {
       
  1281 						$sql_chunks['where'][] = '( ' . implode( ' AND ', $clause_sql['where'] ) . ' )';
       
  1282 					}
       
  1283 
       
  1284 					$sql_chunks['join'] = array_merge( $sql_chunks['join'], $clause_sql['join'] );
       
  1285 				// This is a subquery, so we recurse.
       
  1286 				} else {
       
  1287 					$clause_sql = $this->get_sql_for_query( $clause, $depth + 1 );
       
  1288 
       
  1289 					$sql_chunks['where'][] = $clause_sql['where'];
       
  1290 					$sql_chunks['join'][]  = $clause_sql['join'];
       
  1291 				}
       
  1292 			}
       
  1293 		}
       
  1294 
       
  1295 		// Filter to remove empties.
       
  1296 		$sql_chunks['join']  = array_filter( $sql_chunks['join'] );
       
  1297 		$sql_chunks['where'] = array_filter( $sql_chunks['where'] );
       
  1298 
       
  1299 		if ( empty( $relation ) ) {
       
  1300 			$relation = 'AND';
       
  1301 		}
       
  1302 
       
  1303 		// Filter duplicate JOIN clauses and combine into a single string.
       
  1304 		if ( ! empty( $sql_chunks['join'] ) ) {
       
  1305 			$sql['join'] = implode( ' ', array_unique( $sql_chunks['join'] ) );
       
  1306 		}
       
  1307 
       
  1308 		// Generate a single WHERE clause with proper brackets and indentation.
       
  1309 		if ( ! empty( $sql_chunks['where'] ) ) {
       
  1310 			$sql['where'] = '( ' . "\n  " . $indent . implode( ' ' . "\n  " . $indent . $relation . ' ' . "\n  " . $indent, $sql_chunks['where'] ) . "\n" . $indent . ')';
       
  1311 		}
       
  1312 
       
  1313 		return $sql;
       
  1314 	}
       
  1315 
       
  1316 	/**
       
  1317 	 * Generate SQL JOIN and WHERE clauses for a first-order query clause.
       
  1318 	 *
       
  1319 	 * "First-order" means that it's an array with a 'key' or 'value'.
       
  1320 	 *
       
  1321 	 * @since 4.1.0
       
  1322 	 * @access public
       
  1323 	 *
       
  1324 	 * @param array  $clause       Query clause, passed by reference.
       
  1325 	 * @param array  $parent_query Parent query array.
       
  1326 	 * @param string $clause_key   Optional. The array key used to name the clause in the original `$meta_query`
       
  1327 	 *                             parameters. If not provided, a key will be generated automatically.
       
  1328 	 * @return array {
       
  1329 	 *     Array containing JOIN and WHERE SQL clauses to append to a first-order query.
       
  1330 	 *
       
  1331 	 *     @type string $join  SQL fragment to append to the main JOIN clause.
       
  1332 	 *     @type string $where SQL fragment to append to the main WHERE clause.
       
  1333 	 * }
       
  1334 	 */
       
  1335 	public function get_sql_for_clause( &$clause, $parent_query, $clause_key = '' ) {
       
  1336 		global $wpdb;
       
  1337 
       
  1338 		$sql_chunks = array(
       
  1339 			'where' => array(),
       
  1340 			'join' => array(),
       
  1341 		);
       
  1342 
       
  1343 		if ( isset( $clause['compare'] ) ) {
       
  1344 			$clause['compare'] = strtoupper( $clause['compare'] );
       
  1345 		} else {
       
  1346 			$clause['compare'] = isset( $clause['value'] ) && is_array( $clause['value'] ) ? 'IN' : '=';
       
  1347 		}
       
  1348 
       
  1349 		if ( ! in_array( $clause['compare'], array(
       
  1350 			'=', '!=', '>', '>=', '<', '<=',
       
  1351 			'LIKE', 'NOT LIKE',
       
  1352 			'IN', 'NOT IN',
       
  1353 			'BETWEEN', 'NOT BETWEEN',
       
  1354 			'EXISTS', 'NOT EXISTS',
       
  1355 			'REGEXP', 'NOT REGEXP', 'RLIKE'
       
  1356 		) ) ) {
       
  1357 			$clause['compare'] = '=';
       
  1358 		}
       
  1359 
       
  1360 		$meta_compare = $clause['compare'];
       
  1361 
       
  1362 		// First build the JOIN clause, if one is required.
       
  1363 		$join = '';
       
  1364 
       
  1365 		// We prefer to avoid joins if possible. Look for an existing join compatible with this clause.
       
  1366 		$alias = $this->find_compatible_table_alias( $clause, $parent_query );
       
  1367 		if ( false === $alias ) {
       
  1368 			$i = count( $this->table_aliases );
       
  1369 			$alias = $i ? 'mt' . $i : $this->meta_table;
       
  1370 
       
  1371 			// JOIN clauses for NOT EXISTS have their own syntax.
       
  1372 			if ( 'NOT EXISTS' === $meta_compare ) {
       
  1373 				$join .= " LEFT JOIN $this->meta_table";
       
  1374 				$join .= $i ? " AS $alias" : '';
       
  1375 				$join .= $wpdb->prepare( " ON ($this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column AND $alias.meta_key = %s )", $clause['key'] );
       
  1376 
       
  1377 			// All other JOIN clauses.
       
  1378 			} else {
       
  1379 				$join .= " INNER JOIN $this->meta_table";
       
  1380 				$join .= $i ? " AS $alias" : '';
       
  1381 				$join .= " ON ( $this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column )";
       
  1382 			}
       
  1383 
       
  1384 			$this->table_aliases[] = $alias;
       
  1385 			$sql_chunks['join'][] = $join;
       
  1386 		}
       
  1387 
       
  1388 		// Save the alias to this clause, for future siblings to find.
       
  1389 		$clause['alias'] = $alias;
       
  1390 
       
  1391 		// Determine the data type.
       
  1392 		$_meta_type = isset( $clause['type'] ) ? $clause['type'] : '';
       
  1393 		$meta_type  = $this->get_cast_for_type( $_meta_type );
       
  1394 		$clause['cast'] = $meta_type;
       
  1395 
       
  1396 		// Fallback for clause keys is the table alias.
       
  1397 		if ( ! $clause_key ) {
       
  1398 			$clause_key = $clause['alias'];
       
  1399 		}
       
  1400 
       
  1401 		// Ensure unique clause keys, so none are overwritten.
       
  1402 		$iterator = 1;
       
  1403 		$clause_key_base = $clause_key;
       
  1404 		while ( isset( $this->clauses[ $clause_key ] ) ) {
       
  1405 			$clause_key = $clause_key_base . '-' . $iterator;
       
  1406 			$iterator++;
       
  1407 		}
       
  1408 
       
  1409 		// Store the clause in our flat array.
       
  1410 		$this->clauses[ $clause_key ] =& $clause;
       
  1411 
       
  1412 		// Next, build the WHERE clause.
       
  1413 
       
  1414 		// meta_key.
       
  1415 		if ( array_key_exists( 'key', $clause ) ) {
       
  1416 			if ( 'NOT EXISTS' === $meta_compare ) {
       
  1417 				$sql_chunks['where'][] = $alias . '.' . $this->meta_id_column . ' IS NULL';
       
  1418 			} else {
       
  1419 				$sql_chunks['where'][] = $wpdb->prepare( "$alias.meta_key = %s", trim( $clause['key'] ) );
       
  1420 			}
       
  1421 		}
       
  1422 
       
  1423 		// meta_value.
       
  1424 		if ( array_key_exists( 'value', $clause ) ) {
       
  1425 			$meta_value = $clause['value'];
       
  1426 
       
  1427 			if ( in_array( $meta_compare, array( 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN' ) ) ) {
       
  1428 				if ( ! is_array( $meta_value ) ) {
       
  1429 					$meta_value = preg_split( '/[,\s]+/', $meta_value );
       
  1430 				}
       
  1431 			} else {
       
  1432 				$meta_value = trim( $meta_value );
       
  1433 			}
       
  1434 
       
  1435 			switch ( $meta_compare ) {
       
  1436 				case 'IN' :
       
  1437 				case 'NOT IN' :
       
  1438 					$meta_compare_string = '(' . substr( str_repeat( ',%s', count( $meta_value ) ), 1 ) . ')';
       
  1439 					$where = $wpdb->prepare( $meta_compare_string, $meta_value );
       
  1440 					break;
       
  1441 
       
  1442 				case 'BETWEEN' :
       
  1443 				case 'NOT BETWEEN' :
       
  1444 					$meta_value = array_slice( $meta_value, 0, 2 );
       
  1445 					$where = $wpdb->prepare( '%s AND %s', $meta_value );
       
  1446 					break;
       
  1447 
       
  1448 				case 'LIKE' :
       
  1449 				case 'NOT LIKE' :
       
  1450 					$meta_value = '%' . $wpdb->esc_like( $meta_value ) . '%';
       
  1451 					$where = $wpdb->prepare( '%s', $meta_value );
       
  1452 					break;
       
  1453 
       
  1454 				// EXISTS with a value is interpreted as '='.
       
  1455 				case 'EXISTS' :
       
  1456 					$meta_compare = '=';
       
  1457 					$where = $wpdb->prepare( '%s', $meta_value );
       
  1458 					break;
       
  1459 
       
  1460 				// 'value' is ignored for NOT EXISTS.
       
  1461 				case 'NOT EXISTS' :
       
  1462 					$where = '';
       
  1463 					break;
       
  1464 
       
  1465 				default :
       
  1466 					$where = $wpdb->prepare( '%s', $meta_value );
       
  1467 					break;
       
  1468 
       
  1469 			}
       
  1470 
       
  1471 			if ( $where ) {
       
  1472 				$sql_chunks['where'][] = "CAST($alias.meta_value AS {$meta_type}) {$meta_compare} {$where}";
       
  1473 			}
       
  1474 		}
       
  1475 
       
  1476 		/*
       
  1477 		 * Multiple WHERE clauses (for meta_key and meta_value) should
       
  1478 		 * be joined in parentheses.
       
  1479 		 */
       
  1480 		if ( 1 < count( $sql_chunks['where'] ) ) {
       
  1481 			$sql_chunks['where'] = array( '( ' . implode( ' AND ', $sql_chunks['where'] ) . ' )' );
       
  1482 		}
       
  1483 
       
  1484 		return $sql_chunks;
       
  1485 	}
       
  1486 
       
  1487 	/**
       
  1488 	 * Get a flattened list of sanitized meta clauses.
       
  1489 	 *
       
  1490 	 * This array should be used for clause lookup, as when the table alias and CAST type must be determined for
       
  1491 	 * a value of 'orderby' corresponding to a meta clause.
       
  1492 	 *
       
  1493 	 * @since 4.2.0
       
  1494 	 * @access public
       
  1495 	 *
       
  1496 	 * @return array Meta clauses.
       
  1497 	 */
       
  1498 	public function get_clauses() {
       
  1499 		return $this->clauses;
       
  1500 	}
       
  1501 
       
  1502 	/**
       
  1503 	 * Identify an existing table alias that is compatible with the current
       
  1504 	 * query clause.
       
  1505 	 *
       
  1506 	 * We avoid unnecessary table joins by allowing each clause to look for
       
  1507 	 * an existing table alias that is compatible with the query that it
       
  1508 	 * needs to perform.
       
  1509 	 *
       
  1510 	 * An existing alias is compatible if (a) it is a sibling of `$clause`
       
  1511 	 * (ie, it's under the scope of the same relation), and (b) the combination
       
  1512 	 * of operator and relation between the clauses allows for a shared table join.
       
  1513 	 * In the case of {@see WP_Meta_Query}, this only applies to 'IN' clauses that
       
  1514 	 * are connected by the relation 'OR'.
       
  1515 	 *
       
  1516 	 * @since 4.1.0
       
  1517 	 * @access protected
       
  1518 	 *
       
  1519 	 * @param  array       $clause       Query clause.
       
  1520 	 * @param  array       $parent_query Parent query of $clause.
       
  1521 	 * @return string|bool Table alias if found, otherwise false.
       
  1522 	 */
       
  1523 	protected function find_compatible_table_alias( $clause, $parent_query ) {
       
  1524 		$alias = false;
       
  1525 
       
  1526 		foreach ( $parent_query as $sibling ) {
       
  1527 			// If the sibling has no alias yet, there's nothing to check.
       
  1528 			if ( empty( $sibling['alias'] ) ) {
       
  1529 				continue;
       
  1530 			}
       
  1531 
       
  1532 			// We're only interested in siblings that are first-order clauses.
       
  1533 			if ( ! is_array( $sibling ) || ! $this->is_first_order_clause( $sibling ) ) {
       
  1534 				continue;
       
  1535 			}
       
  1536 
       
  1537 			$compatible_compares = array();
       
  1538 
       
  1539 			// Clauses connected by OR can share joins as long as they have "positive" operators.
       
  1540 			if ( 'OR' === $parent_query['relation'] ) {
       
  1541 				$compatible_compares = array( '=', 'IN', 'BETWEEN', 'LIKE', 'REGEXP', 'RLIKE', '>', '>=', '<', '<=' );
       
  1542 
       
  1543 			// Clauses joined by AND with "negative" operators share a join only if they also share a key.
       
  1544 			} elseif ( isset( $sibling['key'] ) && isset( $clause['key'] ) && $sibling['key'] === $clause['key'] ) {
       
  1545 				$compatible_compares = array( '!=', 'NOT IN', 'NOT LIKE' );
       
  1546 			}
       
  1547 
       
  1548 			$clause_compare  = strtoupper( $clause['compare'] );
       
  1549 			$sibling_compare = strtoupper( $sibling['compare'] );
       
  1550 			if ( in_array( $clause_compare, $compatible_compares ) && in_array( $sibling_compare, $compatible_compares ) ) {
       
  1551 				$alias = $sibling['alias'];
       
  1552 				break;
       
  1553 			}
       
  1554 		}
       
  1555 
       
  1556 		/**
       
  1557 		 * Filter the table alias identified as compatible with the current clause.
       
  1558 		 *
       
  1559 		 * @since 4.1.0
       
  1560 		 *
       
  1561 		 * @param string|bool $alias        Table alias, or false if none was found.
       
  1562 		 * @param array       $clause       First-order query clause.
       
  1563 		 * @param array       $parent_query Parent of $clause.
       
  1564 		 * @param object      $this         WP_Meta_Query object.
       
  1565 		 */
       
  1566 		return apply_filters( 'meta_query_find_compatible_table_alias', $alias, $clause, $parent_query, $this ) ;
       
  1567 	}
       
  1568 }
       
  1569 
       
  1570 /**
       
  1571  * Retrieve the name of the metadata table for the specified object type.
   896  * Retrieve the name of the metadata table for the specified object type.
  1572  *
   897  *
  1573  * @since 2.9.0
   898  * @since 2.9.0
  1574  *
   899  *
  1575  * @global wpdb $wpdb WordPress database abstraction object.
   900  * @global wpdb $wpdb WordPress database abstraction object.
  1576  *
   901  *
  1577  * @param string $type Type of object to get metadata table for (e.g., comment, post, or user)
   902  * @param string $type Type of object to get metadata table for (e.g., comment, post, or user)
  1578  * @return mixed Metadata table name, or false if no metadata table exists
   903  * @return string|false Metadata table name, or false if no metadata table exists
  1579  */
   904  */
  1580 function _get_meta_table($type) {
   905 function _get_meta_table($type) {
  1581 	global $wpdb;
   906 	global $wpdb;
  1582 
   907 
  1583 	$table_name = $type . 'meta';
   908 	$table_name = $type . 'meta';
  1599  */
   924  */
  1600 function is_protected_meta( $meta_key, $meta_type = null ) {
   925 function is_protected_meta( $meta_key, $meta_type = null ) {
  1601 	$protected = ( '_' == $meta_key[0] );
   926 	$protected = ( '_' == $meta_key[0] );
  1602 
   927 
  1603 	/**
   928 	/**
  1604 	 * Filter whether a meta key is protected.
   929 	 * Filters whether a meta key is protected.
  1605 	 *
   930 	 *
  1606 	 * @since 3.2.0
   931 	 * @since 3.2.0
  1607 	 *
   932 	 *
  1608 	 * @param bool   $protected Whether the key is protected. Default false.
   933 	 * @param bool   $protected Whether the key is protected. Default false.
  1609 	 * @param string $meta_key  Meta key.
   934 	 * @param string $meta_key  Meta key.
  1614 
   939 
  1615 /**
   940 /**
  1616  * Sanitize meta value.
   941  * Sanitize meta value.
  1617  *
   942  *
  1618  * @since 3.1.3
   943  * @since 3.1.3
  1619  *
   944  * @since 4.9.8 The `$object_subtype` parameter was added.
  1620  * @param string $meta_key Meta key
   945  *
  1621  * @param mixed $meta_value Meta value to sanitize
   946  * @param string $meta_key       Meta key.
  1622  * @param string $meta_type Type of meta
   947  * @param mixed  $meta_value     Meta value to sanitize.
  1623  * @return mixed Sanitized $meta_value
   948  * @param string $object_type    Type of object the meta is registered to.
  1624  */
   949  *
  1625 function sanitize_meta( $meta_key, $meta_value, $meta_type ) {
   950  * @return mixed Sanitized $meta_value.
       
   951  */
       
   952 function sanitize_meta( $meta_key, $meta_value, $object_type, $object_subtype = '' ) {
       
   953 	if ( ! empty( $object_subtype ) && has_filter( "sanitize_{$object_type}_meta_{$meta_key}_for_{$object_subtype}" ) ) {
       
   954 
       
   955 		/**
       
   956 		 * Filters the sanitization of a specific meta key of a specific meta type and subtype.
       
   957 		 *
       
   958 		 * The dynamic portions of the hook name, `$object_type`, `$meta_key`,
       
   959 		 * and `$object_subtype`, refer to the metadata object type (comment, post, term or user),
       
   960 		 * the meta key value, and the object subtype respectively.
       
   961 		 *
       
   962 		 * @since 4.9.8
       
   963 		 *
       
   964 		 * @param mixed  $meta_value     Meta value to sanitize.
       
   965 		 * @param string $meta_key       Meta key.
       
   966 		 * @param string $object_type    Object type.
       
   967 		 * @param string $object_subtype Object subtype.
       
   968 		 */
       
   969 		return apply_filters( "sanitize_{$object_type}_meta_{$meta_key}_for_{$object_subtype}", $meta_value, $meta_key, $object_type, $object_subtype );
       
   970 	}
  1626 
   971 
  1627 	/**
   972 	/**
  1628 	 * Filter the sanitization of a specific meta key of a specific meta type.
   973 	 * Filters the sanitization of a specific meta key of a specific meta type.
  1629 	 *
   974 	 *
  1630 	 * The dynamic portions of the hook name, `$meta_type`, and `$meta_key`,
   975 	 * The dynamic portions of the hook name, `$meta_type`, and `$meta_key`,
  1631 	 * refer to the metadata object type (comment, post, or user) and the meta
   976 	 * refer to the metadata object type (comment, post, or user) and the meta
  1632 	 * key value,
   977 	 * key value, respectively.
  1633 	 * respectively.
       
  1634 	 *
   978 	 *
  1635 	 * @since 3.3.0
   979 	 * @since 3.3.0
  1636 	 *
   980 	 *
  1637 	 * @param mixed  $meta_value Meta value to sanitize.
   981 	 * @param mixed  $meta_value      Meta value to sanitize.
  1638 	 * @param string $meta_key   Meta key.
   982 	 * @param string $meta_key        Meta key.
  1639 	 * @param string $meta_type  Meta type.
   983 	 * @param string $object_type     Object type.
  1640 	 */
   984 	 */
  1641 	return apply_filters( "sanitize_{$meta_type}_meta_{$meta_key}", $meta_value, $meta_key, $meta_type );
   985 	return apply_filters( "sanitize_{$object_type}_meta_{$meta_key}", $meta_value, $meta_key, $object_type );
  1642 }
   986 }
  1643 
   987 
  1644 /**
   988 /**
  1645  * Register meta key
   989  * Registers a meta key.
       
   990  *
       
   991  * It is recommended to register meta keys for a specific combination of object type and object subtype. If passing
       
   992  * an object subtype is omitted, the meta key will be registered for the entire object type, however it can be partly
       
   993  * overridden in case a more specific meta key of the same name exists for the same object type and a subtype.
       
   994  *
       
   995  * If an object type does not support any subtypes, such as users or comments, you should commonly call this function
       
   996  * without passing a subtype.
  1646  *
   997  *
  1647  * @since 3.3.0
   998  * @since 3.3.0
  1648  *
   999  * @since 4.6.0 {@link https://core.trac.wordpress.org/ticket/35658 Modified
  1649  * @param string $meta_type Type of meta
  1000  *              to support an array of data to attach to registered meta keys}. Previous arguments for
  1650  * @param string $meta_key Meta key
  1001  *              `$sanitize_callback` and `$auth_callback` have been folded into this array.
  1651  * @param string|array $sanitize_callback A function or method to call when sanitizing the value of $meta_key.
  1002  * @since 4.9.8 The `$object_subtype` argument was added to the arguments array.
  1652  * @param string|array $auth_callback Optional. A function or method to call when performing edit_post_meta, add_post_meta, and delete_post_meta capability checks.
  1003  *
  1653  */
  1004  * @param string $object_type    Type of object this meta is registered to.
  1654 function register_meta( $meta_type, $meta_key, $sanitize_callback, $auth_callback = null ) {
  1005  * @param string $meta_key       Meta key to register.
  1655 	if ( is_callable( $sanitize_callback ) )
  1006  * @param array  $args {
  1656 		add_filter( "sanitize_{$meta_type}_meta_{$meta_key}", $sanitize_callback, 10, 3 );
  1007  *     Data used to describe the meta key when registered.
  1657 
  1008  *
  1658 	if ( empty( $auth_callback ) ) {
  1009  *     @type string $object_subtype    A subtype; e.g. if the object type is "post", the post type. If left empty,
  1659 		if ( is_protected_meta( $meta_key, $meta_type ) )
  1010  *                                     the meta key will be registered on the entire object type. Default empty.
  1660 			$auth_callback = '__return_false';
  1011  *     @type string $type              The type of data associated with this meta key.
  1661 		else
  1012  *                                     Valid values are 'string', 'boolean', 'integer', and 'number'.
  1662 			$auth_callback = '__return_true';
  1013  *     @type string $description       A description of the data attached to this meta key.
  1663 	}
  1014  *     @type bool   $single            Whether the meta key has one value per object, or an array of values per object.
  1664 
  1015  *     @type string $sanitize_callback A function or method to call when sanitizing `$meta_key` data.
  1665 	if ( is_callable( $auth_callback ) )
  1016  *     @type string $auth_callback     Optional. A function or method to call when performing edit_post_meta, add_post_meta, and delete_post_meta capability checks.
  1666 		add_filter( "auth_{$meta_type}_meta_{$meta_key}", $auth_callback, 10, 6 );
  1017  *     @type bool   $show_in_rest      Whether data associated with this meta key can be considered public.
  1667 }
  1018  * }
       
  1019  * @param string|array $deprecated Deprecated. Use `$args` instead.
       
  1020  *
       
  1021  * @return bool True if the meta key was successfully registered in the global array, false if not.
       
  1022  *                       Registering a meta key with distinct sanitize and auth callbacks will fire those
       
  1023  *                       callbacks, but will not add to the global registry.
       
  1024  */
       
  1025 function register_meta( $object_type, $meta_key, $args, $deprecated = null ) {
       
  1026 	global $wp_meta_keys;
       
  1027 
       
  1028 	if ( ! is_array( $wp_meta_keys ) ) {
       
  1029 		$wp_meta_keys = array();
       
  1030 	}
       
  1031 
       
  1032 	$defaults = array(
       
  1033 		'object_subtype'    => '',
       
  1034 		'type'              => 'string',
       
  1035 		'description'       => '',
       
  1036 		'single'            => false,
       
  1037 		'sanitize_callback' => null,
       
  1038 		'auth_callback'     => null,
       
  1039 		'show_in_rest'      => false,
       
  1040 	);
       
  1041 
       
  1042 	// There used to be individual args for sanitize and auth callbacks
       
  1043 	$has_old_sanitize_cb = false;
       
  1044 	$has_old_auth_cb = false;
       
  1045 
       
  1046 	if ( is_callable( $args ) ) {
       
  1047 		$args = array(
       
  1048 			'sanitize_callback' => $args,
       
  1049 		);
       
  1050 
       
  1051 		$has_old_sanitize_cb = true;
       
  1052 	} else {
       
  1053 		$args = (array) $args;
       
  1054 	}
       
  1055 
       
  1056 	if ( is_callable( $deprecated ) ) {
       
  1057 		$args['auth_callback'] = $deprecated;
       
  1058 		$has_old_auth_cb = true;
       
  1059 	}
       
  1060 
       
  1061 	/**
       
  1062 	 * Filters the registration arguments when registering meta.
       
  1063 	 *
       
  1064 	 * @since 4.6.0
       
  1065 	 *
       
  1066 	 * @param array  $args        Array of meta registration arguments.
       
  1067 	 * @param array  $defaults    Array of default arguments.
       
  1068 	 * @param string $object_type Object type.
       
  1069 	 * @param string $meta_key    Meta key.
       
  1070 	 */
       
  1071 	$args = apply_filters( 'register_meta_args', $args, $defaults, $object_type, $meta_key );
       
  1072 	$args = wp_parse_args( $args, $defaults );
       
  1073 
       
  1074 	$object_subtype = ! empty( $args['object_subtype'] ) ? $args['object_subtype'] : '';
       
  1075 
       
  1076 	// If `auth_callback` is not provided, fall back to `is_protected_meta()`.
       
  1077 	if ( empty( $args['auth_callback'] ) ) {
       
  1078 		if ( is_protected_meta( $meta_key, $object_type ) ) {
       
  1079 			$args['auth_callback'] = '__return_false';
       
  1080 		} else {
       
  1081 			$args['auth_callback'] = '__return_true';
       
  1082 		}
       
  1083 	}
       
  1084 
       
  1085 	// Back-compat: old sanitize and auth callbacks are applied to all of an object type.
       
  1086 	if ( is_callable( $args['sanitize_callback'] ) ) {
       
  1087 		if ( ! empty( $object_subtype ) ) {
       
  1088 			add_filter( "sanitize_{$object_type}_meta_{$meta_key}_for_{$object_subtype}", $args['sanitize_callback'], 10, 4 );
       
  1089 		} else {
       
  1090 			add_filter( "sanitize_{$object_type}_meta_{$meta_key}", $args['sanitize_callback'], 10, 3 );
       
  1091 		}
       
  1092 	}
       
  1093 
       
  1094 	if ( is_callable( $args['auth_callback'] ) ) {
       
  1095 		if ( ! empty( $object_subtype ) ) {
       
  1096 			add_filter( "auth_{$object_type}_meta_{$meta_key}_for_{$object_subtype}", $args['auth_callback'], 10, 6 );
       
  1097 		} else {
       
  1098 			add_filter( "auth_{$object_type}_meta_{$meta_key}", $args['auth_callback'], 10, 6 );
       
  1099 		}
       
  1100 	}
       
  1101 
       
  1102 	// Global registry only contains meta keys registered with the array of arguments added in 4.6.0.
       
  1103 	if ( ! $has_old_auth_cb && ! $has_old_sanitize_cb ) {
       
  1104 		unset( $args['object_subtype'] );
       
  1105 
       
  1106 		$wp_meta_keys[ $object_type ][ $object_subtype ][ $meta_key ] = $args;
       
  1107 
       
  1108 		return true;
       
  1109 	}
       
  1110 
       
  1111 	return false;
       
  1112 }
       
  1113 
       
  1114 /**
       
  1115  * Checks if a meta key is registered.
       
  1116  *
       
  1117  * @since 4.6.0
       
  1118  * @since 4.9.8 The `$object_subtype` parameter was added.
       
  1119  *
       
  1120  * @param string $object_type    The type of object.
       
  1121  * @param string $meta_key       The meta key.
       
  1122  * @param string $object_subtype Optional. The subtype of the object type.
       
  1123  *
       
  1124  * @return bool True if the meta key is registered to the object type and, if provided,
       
  1125  *              the object subtype. False if not.
       
  1126  */
       
  1127 function registered_meta_key_exists( $object_type, $meta_key, $object_subtype = '' ) {
       
  1128 	$meta_keys = get_registered_meta_keys( $object_type, $object_subtype );
       
  1129 
       
  1130 	return isset( $meta_keys[ $meta_key ] );
       
  1131 }
       
  1132 
       
  1133 /**
       
  1134  * Unregisters a meta key from the list of registered keys.
       
  1135  *
       
  1136  * @since 4.6.0
       
  1137  * @since 4.9.8 The `$object_subtype` parameter was added.
       
  1138  *
       
  1139  * @param string $object_type    The type of object.
       
  1140  * @param string $meta_key       The meta key.
       
  1141  * @param string $object_subtype Optional. The subtype of the object type.
       
  1142  * @return bool True if successful. False if the meta key was not registered.
       
  1143  */
       
  1144 function unregister_meta_key( $object_type, $meta_key, $object_subtype = '' ) {
       
  1145 	global $wp_meta_keys;
       
  1146 
       
  1147 	if ( ! registered_meta_key_exists( $object_type, $meta_key, $object_subtype ) ) {
       
  1148 		return false;
       
  1149 	}
       
  1150 
       
  1151 	$args = $wp_meta_keys[ $object_type ][ $object_subtype ][ $meta_key ];
       
  1152 
       
  1153 	if ( isset( $args['sanitize_callback'] ) && is_callable( $args['sanitize_callback'] ) ) {
       
  1154 		if ( ! empty( $object_subtype ) ) {
       
  1155 			remove_filter( "sanitize_{$object_type}_meta_{$meta_key}_for_{$object_subtype}", $args['sanitize_callback'] );
       
  1156 		} else {
       
  1157 			remove_filter( "sanitize_{$object_type}_meta_{$meta_key}", $args['sanitize_callback'] );
       
  1158 		}
       
  1159 	}
       
  1160 
       
  1161 	if ( isset( $args['auth_callback'] ) && is_callable( $args['auth_callback'] ) ) {
       
  1162 		if ( ! empty( $object_subtype ) ) {
       
  1163 			remove_filter( "auth_{$object_type}_meta_{$meta_key}_for_{$object_subtype}", $args['auth_callback'] );
       
  1164 		} else {
       
  1165 			remove_filter( "auth_{$object_type}_meta_{$meta_key}", $args['auth_callback'] );
       
  1166 		}
       
  1167 	}
       
  1168 
       
  1169 	unset( $wp_meta_keys[ $object_type ][ $object_subtype ][ $meta_key ] );
       
  1170 
       
  1171 	// Do some clean up
       
  1172 	if ( empty( $wp_meta_keys[ $object_type ][ $object_subtype ] ) ) {
       
  1173 		unset( $wp_meta_keys[ $object_type ][ $object_subtype ] );
       
  1174 	}
       
  1175 	if ( empty( $wp_meta_keys[ $object_type ] ) ) {
       
  1176 		unset( $wp_meta_keys[ $object_type ] );
       
  1177 	}
       
  1178 
       
  1179 	return true;
       
  1180 }
       
  1181 
       
  1182 /**
       
  1183  * Retrieves a list of registered meta keys for an object type.
       
  1184  *
       
  1185  * @since 4.6.0
       
  1186  * @since 4.9.8 The `$object_subtype` parameter was added.
       
  1187  *
       
  1188  * @param string $object_type    The type of object. Post, comment, user, term.
       
  1189  * @param string $object_subtype Optional. The subtype of the object type.
       
  1190  * @return array List of registered meta keys.
       
  1191  */
       
  1192 function get_registered_meta_keys( $object_type, $object_subtype = '' ) {
       
  1193 	global $wp_meta_keys;
       
  1194 
       
  1195 	if ( ! is_array( $wp_meta_keys ) || ! isset( $wp_meta_keys[ $object_type ] ) || ! isset( $wp_meta_keys[ $object_type ][ $object_subtype ] ) ) {
       
  1196 		return array();
       
  1197 	}
       
  1198 
       
  1199 	return $wp_meta_keys[ $object_type ][ $object_subtype ];
       
  1200 }
       
  1201 
       
  1202 /**
       
  1203  * Retrieves registered metadata for a specified object.
       
  1204  *
       
  1205  * The results include both meta that is registered specifically for the
       
  1206  * object's subtype and meta that is registered for the entire object type.
       
  1207  *
       
  1208  * @since 4.6.0
       
  1209  *
       
  1210  * @param string $object_type Type of object to request metadata for. (e.g. comment, post, term, user)
       
  1211  * @param int    $object_id   ID of the object the metadata is for.
       
  1212  * @param string $meta_key    Optional. Registered metadata key. If not specified, retrieve all registered
       
  1213  *                            metadata for the specified object.
       
  1214  * @return mixed A single value or array of values for a key if specified. An array of all registered keys
       
  1215  *               and values for an object ID if not. False if a given $meta_key is not registered.
       
  1216  */
       
  1217 function get_registered_metadata( $object_type, $object_id, $meta_key = '' ) {
       
  1218 	$object_subtype = get_object_subtype( $object_type, $object_id );
       
  1219 
       
  1220 	if ( ! empty( $meta_key ) ) {
       
  1221 		if ( ! empty( $object_subtype ) && ! registered_meta_key_exists( $object_type, $meta_key, $object_subtype ) ) {
       
  1222 			$object_subtype = '';
       
  1223 		}
       
  1224 
       
  1225 		if ( ! registered_meta_key_exists( $object_type, $meta_key, $object_subtype ) ) {
       
  1226 			return false;
       
  1227 		}
       
  1228 
       
  1229 		$meta_keys     = get_registered_meta_keys( $object_type, $object_subtype );
       
  1230 		$meta_key_data = $meta_keys[ $meta_key ];
       
  1231 
       
  1232 		$data = get_metadata( $object_type, $object_id, $meta_key, $meta_key_data['single'] );
       
  1233 
       
  1234 		return $data;
       
  1235 	}
       
  1236 
       
  1237 	$data = get_metadata( $object_type, $object_id );
       
  1238 	if ( ! $data ) {
       
  1239 		return array();
       
  1240 	}
       
  1241 
       
  1242 	$meta_keys = get_registered_meta_keys( $object_type );
       
  1243 	if ( ! empty( $object_subtype ) ) {
       
  1244 		$meta_keys = array_merge( $meta_keys, get_registered_meta_keys( $object_type, $object_subtype ) );
       
  1245 	}
       
  1246 
       
  1247 	return array_intersect_key( $data, $meta_keys );
       
  1248 }
       
  1249 
       
  1250 /**
       
  1251  * Filter out `register_meta()` args based on a whitelist.
       
  1252  * `register_meta()` args may change over time, so requiring the whitelist
       
  1253  * to be explicitly turned off is a warranty seal of sorts.
       
  1254  *
       
  1255  * @access private
       
  1256  * @since 4.6.0
       
  1257  *
       
  1258  * @param array $args         Arguments from `register_meta()`.
       
  1259  * @param array $default_args Default arguments for `register_meta()`.
       
  1260  *
       
  1261  * @return array Filtered arguments.
       
  1262  */
       
  1263 function _wp_register_meta_args_whitelist( $args, $default_args ) {
       
  1264 	return array_intersect_key( $args, $default_args );
       
  1265 }
       
  1266 
       
  1267 /**
       
  1268  * Returns the object subtype for a given object ID of a specific type.
       
  1269  *
       
  1270  * @since 4.9.8
       
  1271  *
       
  1272  * @param string $object_type Type of object to request metadata for. (e.g. comment, post, term, user)
       
  1273  * @param int    $object_id   ID of the object to retrieve its subtype.
       
  1274  * @return string The object subtype or an empty string if unspecified subtype.
       
  1275  */
       
  1276 function get_object_subtype( $object_type, $object_id ) {
       
  1277 	$object_id      = (int) $object_id;
       
  1278 	$object_subtype = '';
       
  1279 
       
  1280 	switch ( $object_type ) {
       
  1281 		case 'post':
       
  1282 			$post_type = get_post_type( $object_id );
       
  1283 
       
  1284 			if ( ! empty( $post_type ) ) {
       
  1285 				$object_subtype = $post_type;
       
  1286 			}
       
  1287 			break;
       
  1288 
       
  1289 		case 'term':
       
  1290 			$term = get_term( $object_id );
       
  1291 			if ( ! $term instanceof WP_Term ) {
       
  1292 				break;
       
  1293 			}
       
  1294 
       
  1295 			$object_subtype = $term->taxonomy;
       
  1296 			break;
       
  1297 
       
  1298 		case 'comment':
       
  1299 			$comment = get_comment( $object_id );
       
  1300 			if ( ! $comment ) {
       
  1301 				break;
       
  1302 			}
       
  1303 
       
  1304 			$object_subtype = 'comment';
       
  1305 			break;
       
  1306 
       
  1307 		case 'user':
       
  1308 			$user = get_user_by( 'id', $object_id );
       
  1309 			if ( ! $user ) {
       
  1310 				break;
       
  1311 			}
       
  1312 
       
  1313 			$object_subtype = 'user';
       
  1314 			break;
       
  1315 	}
       
  1316 
       
  1317 	/**
       
  1318 	 * Filters the object subtype identifier for a non standard object type.
       
  1319 	 *
       
  1320 	 * The dynamic portion of the hook, `$object_type`, refers to the object
       
  1321 	 * type (post, comment, term, or user).
       
  1322 	 *
       
  1323 	 * @since 4.9.8
       
  1324 	 *
       
  1325 	 * @param string $object_subtype Empty string to override.
       
  1326 	 * @param int    $object_id      ID of the object to get the subtype for.
       
  1327 	 */
       
  1328 	return apply_filters( "get_object_subtype_{$object_type}", $object_subtype, $object_id );
       
  1329 }