WordPress 4.3
[autoinstalls/wordpress.git] / wp-includes / meta.php
1 <?php
2 /**
3  * Metadata API
4  *
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
7  * metadata entries that share the same key and differ only in their value.
8  *
9  * @package WordPress
10  * @subpackage Meta
11  * @since 2.9.0
12  */
13
14 /**
15  * Add metadata for the specified object.
16  *
17  * @since 2.9.0
18  *
19  * @global wpdb $wpdb WordPress database abstraction object.
20  *
21  * @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
23  * @param string $meta_key   Metadata key
24  * @param mixed  $meta_value Metadata value. Must be serializable if non-scalar.
25  * @param bool   $unique     Optional, default is false.
26  *                           Whether the specified metadata key should be unique for the object.
27  *                           If true, and the object already has a value for the specified metadata key,
28  *                           no change will be made.
29  * @return int|false The meta ID on success, false on failure.
30  */
31 function add_metadata($meta_type, $object_id, $meta_key, $meta_value, $unique = false) {
32         global $wpdb;
33
34         if ( ! $meta_type || ! $meta_key || ! is_numeric( $object_id ) ) {
35                 return false;
36         }
37
38         $object_id = absint( $object_id );
39         if ( ! $object_id ) {
40                 return false;
41         }
42
43         $table = _get_meta_table( $meta_type );
44         if ( ! $table ) {
45                 return false;
46         }
47
48         $column = sanitize_key($meta_type . '_id');
49
50         // expected_slashed ($meta_key)
51         $meta_key = wp_unslash($meta_key);
52         $meta_value = wp_unslash($meta_value);
53         $meta_value = sanitize_meta( $meta_key, $meta_value, $meta_type );
54
55         /**
56          * Filter whether to add metadata of a specific type.
57          *
58          * The dynamic portion of the hook, `$meta_type`, refers to the meta
59          * object type (comment, post, or user). Returning a non-null value
60          * will effectively short-circuit the function.
61          *
62          * @since 3.1.0
63          *
64          * @param null|bool $check      Whether to allow adding metadata for the given type.
65          * @param int       $object_id  Object ID.
66          * @param string    $meta_key   Meta key.
67          * @param mixed     $meta_value Meta value. Must be serializable if non-scalar.
68          * @param bool      $unique     Whether the specified meta key should be unique
69          *                              for the object. Optional. Default false.
70          */
71         $check = apply_filters( "add_{$meta_type}_metadata", null, $object_id, $meta_key, $meta_value, $unique );
72         if ( null !== $check )
73                 return $check;
74
75         if ( $unique && $wpdb->get_var( $wpdb->prepare(
76                 "SELECT COUNT(*) FROM $table WHERE meta_key = %s AND $column = %d",
77                 $meta_key, $object_id ) ) )
78                 return false;
79
80         $_meta_value = $meta_value;
81         $meta_value = maybe_serialize( $meta_value );
82
83         /**
84          * Fires immediately before meta of a specific type is added.
85          *
86          * The dynamic portion of the hook, `$meta_type`, refers to the meta
87          * object type (comment, post, or user).
88          *
89          * @since 3.1.0
90          *
91          * @param int    $object_id  Object ID.
92          * @param string $meta_key   Meta key.
93          * @param mixed  $meta_value Meta value.
94          */
95         do_action( "add_{$meta_type}_meta", $object_id, $meta_key, $_meta_value );
96
97         $result = $wpdb->insert( $table, array(
98                 $column => $object_id,
99                 'meta_key' => $meta_key,
100                 'meta_value' => $meta_value
101         ) );
102
103         if ( ! $result )
104                 return false;
105
106         $mid = (int) $wpdb->insert_id;
107
108         wp_cache_delete($object_id, $meta_type . '_meta');
109
110         /**
111          * Fires immediately after meta of a specific type is added.
112          *
113          * The dynamic portion of the hook, `$meta_type`, refers to the meta
114          * object type (comment, post, or user).
115          *
116          * @since 2.9.0
117          *
118          * @param int    $mid        The meta ID after successful update.
119          * @param int    $object_id  Object ID.
120          * @param string $meta_key   Meta key.
121          * @param mixed  $meta_value Meta value.
122          */
123         do_action( "added_{$meta_type}_meta", $mid, $object_id, $meta_key, $_meta_value );
124
125         return $mid;
126 }
127
128 /**
129  * Update metadata for the specified object. If no value already exists for the specified object
130  * ID and metadata key, the metadata will be added.
131  *
132  * @since 2.9.0
133  *
134  * @global wpdb $wpdb WordPress database abstraction object.
135  *
136  * @param string $meta_type  Type of object metadata is for (e.g., comment, post, or user)
137  * @param int    $object_id  ID of the object metadata is for
138  * @param string $meta_key   Metadata key
139  * @param mixed  $meta_value Metadata value. Must be serializable if non-scalar.
140  * @param mixed  $prev_value Optional. If specified, only update existing metadata entries with
141  *                                   the specified value. Otherwise, update all entries.
142  * @return int|bool Meta ID if the key didn't exist, true on successful update, false on failure.
143  */
144 function update_metadata($meta_type, $object_id, $meta_key, $meta_value, $prev_value = '') {
145         global $wpdb;
146
147         if ( ! $meta_type || ! $meta_key || ! is_numeric( $object_id ) ) {
148                 return false;
149         }
150
151         $object_id = absint( $object_id );
152         if ( ! $object_id ) {
153                 return false;
154         }
155
156         $table = _get_meta_table( $meta_type );
157         if ( ! $table ) {
158                 return false;
159         }
160
161         $column = sanitize_key($meta_type . '_id');
162         $id_column = 'user' == $meta_type ? 'umeta_id' : 'meta_id';
163
164         // expected_slashed ($meta_key)
165         $meta_key = wp_unslash($meta_key);
166         $passed_value = $meta_value;
167         $meta_value = wp_unslash($meta_value);
168         $meta_value = sanitize_meta( $meta_key, $meta_value, $meta_type );
169
170         /**
171          * Filter whether to update metadata of a specific type.
172          *
173          * The dynamic portion of the hook, `$meta_type`, refers to the meta
174          * object type (comment, post, or user). Returning a non-null value
175          * will effectively short-circuit the function.
176          *
177          * @since 3.1.0
178          *
179          * @param null|bool $check      Whether to allow updating metadata for the given type.
180          * @param int       $object_id  Object ID.
181          * @param string    $meta_key   Meta key.
182          * @param mixed     $meta_value Meta value. Must be serializable if non-scalar.
183          * @param mixed     $prev_value Optional. If specified, only update existing
184          *                              metadata entries with the specified value.
185          *                              Otherwise, update all entries.
186          */
187         $check = apply_filters( "update_{$meta_type}_metadata", null, $object_id, $meta_key, $meta_value, $prev_value );
188         if ( null !== $check )
189                 return (bool) $check;
190
191         // Compare existing value to new value if no prev value given and the key exists only once.
192         if ( empty($prev_value) ) {
193                 $old_value = get_metadata($meta_type, $object_id, $meta_key);
194                 if ( count($old_value) == 1 ) {
195                         if ( $old_value[0] === $meta_value )
196                                 return false;
197                 }
198         }
199
200         $meta_ids = $wpdb->get_col( $wpdb->prepare( "SELECT $id_column FROM $table WHERE meta_key = %s AND $column = %d", $meta_key, $object_id ) );
201         if ( empty( $meta_ids ) ) {
202                 return add_metadata($meta_type, $object_id, $meta_key, $passed_value);
203         }
204
205         $_meta_value = $meta_value;
206         $meta_value = maybe_serialize( $meta_value );
207
208         $data  = compact( 'meta_value' );
209         $where = array( $column => $object_id, 'meta_key' => $meta_key );
210
211         if ( !empty( $prev_value ) ) {
212                 $prev_value = maybe_serialize($prev_value);
213                 $where['meta_value'] = $prev_value;
214         }
215
216         foreach ( $meta_ids as $meta_id ) {
217                 /**
218                  * Fires immediately before updating metadata of a specific type.
219                  *
220                  * The dynamic portion of the hook, `$meta_type`, refers to the meta
221                  * object type (comment, post, or user).
222                  *
223                  * @since 2.9.0
224                  *
225                  * @param int    $meta_id    ID of the metadata entry to update.
226                  * @param int    $object_id  Object ID.
227                  * @param string $meta_key   Meta key.
228                  * @param mixed  $meta_value Meta value.
229                  */
230                 do_action( "update_{$meta_type}_meta", $meta_id, $object_id, $meta_key, $_meta_value );
231         }
232
233         if ( 'post' == $meta_type ) {
234                 foreach ( $meta_ids as $meta_id ) {
235                         /**
236                          * Fires immediately before updating a post's metadata.
237                          *
238                          * @since 2.9.0
239                          *
240                          * @param int    $meta_id    ID of metadata entry to update.
241                          * @param int    $object_id  Object ID.
242                          * @param string $meta_key   Meta key.
243                          * @param mixed  $meta_value Meta value.
244                          */
245                         do_action( 'update_postmeta', $meta_id, $object_id, $meta_key, $meta_value );
246                 }
247         }
248
249         $result = $wpdb->update( $table, $data, $where );
250         if ( ! $result )
251                 return false;
252
253         wp_cache_delete($object_id, $meta_type . '_meta');
254
255         foreach ( $meta_ids as $meta_id ) {
256                 /**
257                  * Fires immediately after updating metadata of a specific type.
258                  *
259                  * The dynamic portion of the hook, `$meta_type`, refers to the meta
260                  * object type (comment, post, or user).
261                  *
262                  * @since 2.9.0
263                  *
264                  * @param int    $meta_id    ID of updated metadata entry.
265                  * @param int    $object_id  Object ID.
266                  * @param string $meta_key   Meta key.
267                  * @param mixed  $meta_value Meta value.
268                  */
269                 do_action( "updated_{$meta_type}_meta", $meta_id, $object_id, $meta_key, $_meta_value );
270         }
271
272         if ( 'post' == $meta_type ) {
273                 foreach ( $meta_ids as $meta_id ) {
274                         /**
275                          * Fires immediately after updating a post's metadata.
276                          *
277                          * @since 2.9.0
278                          *
279                          * @param int    $meta_id    ID of updated metadata entry.
280                          * @param int    $object_id  Object ID.
281                          * @param string $meta_key   Meta key.
282                          * @param mixed  $meta_value Meta value.
283                          */
284                         do_action( 'updated_postmeta', $meta_id, $object_id, $meta_key, $meta_value );
285                 }
286         }
287
288         return true;
289 }
290
291 /**
292  * Delete metadata for the specified object.
293  *
294  * @since 2.9.0
295  *
296  * @global wpdb $wpdb WordPress database abstraction object.
297  *
298  * @param string $meta_type  Type of object metadata is for (e.g., comment, post, or user)
299  * @param int    $object_id  ID of the object metadata is for
300  * @param string $meta_key   Metadata key
301  * @param mixed  $meta_value Optional. Metadata value. Must be serializable if non-scalar. If specified, only delete
302  *                           metadata entries with this value. Otherwise, delete all entries with the specified meta_key.
303  *                           Pass `null, `false`, or an empty string to skip this check. (For backward compatibility,
304  *                           it is not possible to pass an empty string to delete those entries with an empty string
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.
309  * @return bool True on successful delete, false on failure.
310  */
311 function delete_metadata($meta_type, $object_id, $meta_key, $meta_value = '', $delete_all = false) {
312         global $wpdb;
313
314         if ( ! $meta_type || ! $meta_key || ! is_numeric( $object_id ) && ! $delete_all ) {
315                 return false;
316         }
317
318         $object_id = absint( $object_id );
319         if ( ! $object_id && ! $delete_all ) {
320                 return false;
321         }
322
323         $table = _get_meta_table( $meta_type );
324         if ( ! $table ) {
325                 return false;
326         }
327
328         $type_column = sanitize_key($meta_type . '_id');
329         $id_column = 'user' == $meta_type ? 'umeta_id' : 'meta_id';
330         // expected_slashed ($meta_key)
331         $meta_key = wp_unslash($meta_key);
332         $meta_value = wp_unslash($meta_value);
333
334         /**
335          * Filter whether to delete metadata of a specific type.
336          *
337          * The dynamic portion of the hook, `$meta_type`, refers to the meta
338          * object type (comment, post, or user). Returning a non-null value
339          * will effectively short-circuit the function.
340          *
341          * @since 3.1.0
342          *
343          * @param null|bool $delete     Whether to allow metadata deletion of the given type.
344          * @param int       $object_id  Object ID.
345          * @param string    $meta_key   Meta key.
346          * @param mixed     $meta_value Meta value. Must be serializable if non-scalar.
347          * @param bool      $delete_all Whether to delete the matching metadata entries
348          *                              for all objects, ignoring the specified $object_id.
349          *                              Default false.
350          */
351         $check = apply_filters( "delete_{$meta_type}_metadata", null, $object_id, $meta_key, $meta_value, $delete_all );
352         if ( null !== $check )
353                 return (bool) $check;
354
355         $_meta_value = $meta_value;
356         $meta_value = maybe_serialize( $meta_value );
357
358         $query = $wpdb->prepare( "SELECT $id_column FROM $table WHERE meta_key = %s", $meta_key );
359
360         if ( !$delete_all )
361                 $query .= $wpdb->prepare(" AND $type_column = %d", $object_id );
362
363         if ( '' !== $meta_value && null !== $meta_value && false !== $meta_value )
364                 $query .= $wpdb->prepare(" AND meta_value = %s", $meta_value );
365
366         $meta_ids = $wpdb->get_col( $query );
367         if ( !count( $meta_ids ) )
368                 return false;
369
370         if ( $delete_all )
371                 $object_ids = $wpdb->get_col( $wpdb->prepare( "SELECT $type_column FROM $table WHERE meta_key = %s", $meta_key ) );
372
373         /**
374          * Fires immediately before deleting metadata of a specific type.
375          *
376          * The dynamic portion of the hook, `$meta_type`, refers to the meta
377          * object type (comment, post, or user).
378          *
379          * @since 3.1.0
380          *
381          * @param array  $meta_ids   An array of metadata entry IDs to delete.
382          * @param int    $object_id  Object ID.
383          * @param string $meta_key   Meta key.
384          * @param mixed  $meta_value Meta value.
385          */
386         do_action( "delete_{$meta_type}_meta", $meta_ids, $object_id, $meta_key, $_meta_value );
387
388         // Old-style action.
389         if ( 'post' == $meta_type ) {
390                 /**
391                  * Fires immediately before deleting metadata for a post.
392                  *
393                  * @since 2.9.0
394                  *
395                  * @param array $meta_ids An array of post metadata entry IDs to delete.
396                  */
397                 do_action( 'delete_postmeta', $meta_ids );
398         }
399
400         $query = "DELETE FROM $table WHERE $id_column IN( " . implode( ',', $meta_ids ) . " )";
401
402         $count = $wpdb->query($query);
403
404         if ( !$count )
405                 return false;
406
407         if ( $delete_all ) {
408                 foreach ( (array) $object_ids as $o_id ) {
409                         wp_cache_delete($o_id, $meta_type . '_meta');
410                 }
411         } else {
412                 wp_cache_delete($object_id, $meta_type . '_meta');
413         }
414
415         /**
416          * Fires immediately after deleting metadata of a specific type.
417          *
418          * The dynamic portion of the hook name, `$meta_type`, refers to the meta
419          * object type (comment, post, or user).
420          *
421          * @since 2.9.0
422          *
423          * @param array  $meta_ids   An array of deleted metadata entry IDs.
424          * @param int    $object_id  Object ID.
425          * @param string $meta_key   Meta key.
426          * @param mixed  $meta_value Meta value.
427          */
428         do_action( "deleted_{$meta_type}_meta", $meta_ids, $object_id, $meta_key, $_meta_value );
429
430         // Old-style action.
431         if ( 'post' == $meta_type ) {
432                 /**
433                  * Fires immediately after deleting metadata for a post.
434                  *
435                  * @since 2.9.0
436                  *
437                  * @param array $meta_ids An array of deleted post metadata entry IDs.
438                  */
439                 do_action( 'deleted_postmeta', $meta_ids );
440         }
441
442         return true;
443 }
444
445 /**
446  * Retrieve metadata for the specified object.
447  *
448  * @since 2.9.0
449  *
450  * @param string $meta_type Type of object metadata is for (e.g., comment, post, or user)
451  * @param int    $object_id ID of the object metadata is for
452  * @param string $meta_key  Optional. Metadata key. If not specified, retrieve all metadata for
453  *                                  the specified object.
454  * @param bool   $single    Optional, default is false.
455  *                          If true, return only the first value of the specified meta_key.
456  *                          This parameter has no effect if meta_key is not specified.
457  * @return mixed Single metadata value, or array of values
458  */
459 function get_metadata($meta_type, $object_id, $meta_key = '', $single = false) {
460         if ( ! $meta_type || ! is_numeric( $object_id ) ) {
461                 return false;
462         }
463
464         $object_id = absint( $object_id );
465         if ( ! $object_id ) {
466                 return false;
467         }
468
469         /**
470          * Filter whether to retrieve metadata of a specific type.
471          *
472          * The dynamic portion of the hook, `$meta_type`, refers to the meta
473          * object type (comment, post, or user). Returning a non-null value
474          * will effectively short-circuit the function.
475          *
476          * @since 3.1.0
477          *
478          * @param null|array|string $value     The value get_metadata() should
479          *                                     return - a single metadata value,
480          *                                     or an array of values.
481          * @param int               $object_id Object ID.
482          * @param string            $meta_key  Meta key.
483          * @param string|array      $single    Meta value, or an array of values.
484          */
485         $check = apply_filters( "get_{$meta_type}_metadata", null, $object_id, $meta_key, $single );
486         if ( null !== $check ) {
487                 if ( $single && is_array( $check ) )
488                         return $check[0];
489                 else
490                         return $check;
491         }
492
493         $meta_cache = wp_cache_get($object_id, $meta_type . '_meta');
494
495         if ( !$meta_cache ) {
496                 $meta_cache = update_meta_cache( $meta_type, array( $object_id ) );
497                 $meta_cache = $meta_cache[$object_id];
498         }
499
500         if ( ! $meta_key ) {
501                 return $meta_cache;
502         }
503
504         if ( isset($meta_cache[$meta_key]) ) {
505                 if ( $single )
506                         return maybe_unserialize( $meta_cache[$meta_key][0] );
507                 else
508                         return array_map('maybe_unserialize', $meta_cache[$meta_key]);
509         }
510
511         if ($single)
512                 return '';
513         else
514                 return array();
515 }
516
517 /**
518  * Determine if a meta key is set for a given object
519  *
520  * @since 3.3.0
521  *
522  * @param string $meta_type Type of object metadata is for (e.g., comment, post, or user)
523  * @param int    $object_id ID of the object metadata is for
524  * @param string $meta_key  Metadata key.
525  * @return bool True of the key is set, false if not.
526  */
527 function metadata_exists( $meta_type, $object_id, $meta_key ) {
528         if ( ! $meta_type || ! is_numeric( $object_id ) ) {
529                 return false;
530         }
531
532         $object_id = absint( $object_id );
533         if ( ! $object_id ) {
534                 return false;
535         }
536
537         /** This filter is documented in wp-includes/meta.php */
538         $check = apply_filters( "get_{$meta_type}_metadata", null, $object_id, $meta_key, true );
539         if ( null !== $check )
540                 return (bool) $check;
541
542         $meta_cache = wp_cache_get( $object_id, $meta_type . '_meta' );
543
544         if ( !$meta_cache ) {
545                 $meta_cache = update_meta_cache( $meta_type, array( $object_id ) );
546                 $meta_cache = $meta_cache[$object_id];
547         }
548
549         if ( isset( $meta_cache[ $meta_key ] ) )
550                 return true;
551
552         return false;
553 }
554
555 /**
556  * Get meta data by meta ID
557  *
558  * @since 3.3.0
559  *
560  * @global wpdb $wpdb
561  *
562  * @param string $meta_type Type of object metadata is for (e.g., comment, post, or user)
563  * @param int    $meta_id   ID for a specific meta row
564  * @return object|false Meta object or false.
565  */
566 function get_metadata_by_mid( $meta_type, $meta_id ) {
567         global $wpdb;
568
569         if ( ! $meta_type || ! is_numeric( $meta_id ) ) {
570                 return false;
571         }
572
573         $meta_id = absint( $meta_id );
574         if ( ! $meta_id ) {
575                 return false;
576         }
577
578         $table = _get_meta_table( $meta_type );
579         if ( ! $table ) {
580                 return false;
581         }
582
583         $id_column = ( 'user' == $meta_type ) ? 'umeta_id' : 'meta_id';
584
585         $meta = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table WHERE $id_column = %d", $meta_id ) );
586
587         if ( empty( $meta ) )
588                 return false;
589
590         if ( isset( $meta->meta_value ) )
591                 $meta->meta_value = maybe_unserialize( $meta->meta_value );
592
593         return $meta;
594 }
595
596 /**
597  * Update meta data by meta ID
598  *
599  * @since 3.3.0
600  *
601  * @global wpdb $wpdb
602  *
603  * @param string $meta_type  Type of object metadata is for (e.g., comment, post, or user)
604  * @param int    $meta_id    ID for a specific meta row
605  * @param string $meta_value Metadata value
606  * @param string $meta_key   Optional, you can provide a meta key to update it
607  * @return bool True on successful update, false on failure.
608  */
609 function update_metadata_by_mid( $meta_type, $meta_id, $meta_value, $meta_key = false ) {
610         global $wpdb;
611
612         // Make sure everything is valid.
613         if ( ! $meta_type || ! is_numeric( $meta_id ) ) {
614                 return false;
615         }
616
617         $meta_id = absint( $meta_id );
618         if ( ! $meta_id ) {
619                 return false;
620         }
621
622         $table = _get_meta_table( $meta_type );
623         if ( ! $table ) {
624                 return false;
625         }
626
627         $column = sanitize_key($meta_type . '_id');
628         $id_column = 'user' == $meta_type ? 'umeta_id' : 'meta_id';
629
630         // Fetch the meta and go on if it's found.
631         if ( $meta = get_metadata_by_mid( $meta_type, $meta_id ) ) {
632                 $original_key = $meta->meta_key;
633                 $object_id = $meta->{$column};
634
635                 // If a new meta_key (last parameter) was specified, change the meta key,
636                 // otherwise use the original key in the update statement.
637                 if ( false === $meta_key ) {
638                         $meta_key = $original_key;
639                 } elseif ( ! is_string( $meta_key ) ) {
640                         return false;
641                 }
642
643                 // Sanitize the meta
644                 $_meta_value = $meta_value;
645                 $meta_value = sanitize_meta( $meta_key, $meta_value, $meta_type );
646                 $meta_value = maybe_serialize( $meta_value );
647
648                 // Format the data query arguments.
649                 $data = array(
650                         'meta_key' => $meta_key,
651                         'meta_value' => $meta_value
652                 );
653
654                 // Format the where query arguments.
655                 $where = array();
656                 $where[$id_column] = $meta_id;
657
658                 /** This action is documented in wp-includes/meta.php */
659                 do_action( "update_{$meta_type}_meta", $meta_id, $object_id, $meta_key, $_meta_value );
660
661                 if ( 'post' == $meta_type ) {
662                         /** This action is documented in wp-includes/meta.php */
663                         do_action( 'update_postmeta', $meta_id, $object_id, $meta_key, $meta_value );
664                 }
665
666                 // Run the update query, all fields in $data are %s, $where is a %d.
667                 $result = $wpdb->update( $table, $data, $where, '%s', '%d' );
668                 if ( ! $result )
669                         return false;
670
671                 // Clear the caches.
672                 wp_cache_delete($object_id, $meta_type . '_meta');
673
674                 /** This action is documented in wp-includes/meta.php */
675                 do_action( "updated_{$meta_type}_meta", $meta_id, $object_id, $meta_key, $_meta_value );
676
677                 if ( 'post' == $meta_type ) {
678                         /** This action is documented in wp-includes/meta.php */
679                         do_action( 'updated_postmeta', $meta_id, $object_id, $meta_key, $meta_value );
680                 }
681
682                 return true;
683         }
684
685         // And if the meta was not found.
686         return false;
687 }
688
689 /**
690  * Delete meta data by meta ID
691  *
692  * @since 3.3.0
693  *
694  * @global wpdb $wpdb
695  *
696  * @param string $meta_type Type of object metadata is for (e.g., comment, post, or user)
697  * @param int    $meta_id   ID for a specific meta row
698  * @return bool True on successful delete, false on failure.
699  */
700 function delete_metadata_by_mid( $meta_type, $meta_id ) {
701         global $wpdb;
702
703         // Make sure everything is valid.
704         if ( ! $meta_type || ! is_numeric( $meta_id ) ) {
705                 return false;
706         }
707
708         $meta_id = absint( $meta_id );
709         if ( ! $meta_id ) {
710                 return false;
711         }
712
713         $table = _get_meta_table( $meta_type );
714         if ( ! $table ) {
715                 return false;
716         }
717
718         // object and id columns
719         $column = sanitize_key($meta_type . '_id');
720         $id_column = 'user' == $meta_type ? 'umeta_id' : 'meta_id';
721
722         // Fetch the meta and go on if it's found.
723         if ( $meta = get_metadata_by_mid( $meta_type, $meta_id ) ) {
724                 $object_id = $meta->{$column};
725
726                 /** This action is documented in wp-includes/meta.php */
727                 do_action( "delete_{$meta_type}_meta", (array) $meta_id, $object_id, $meta->meta_key, $meta->meta_value );
728
729                 // Old-style action.
730                 if ( 'post' == $meta_type || 'comment' == $meta_type ) {
731                         /**
732                          * Fires immediately before deleting post or comment metadata of a specific type.
733                          *
734                          * The dynamic portion of the hook, `$meta_type`, refers to the meta
735                          * object type (post or comment).
736                          *
737                          * @since 3.4.0
738                          *
739                          * @param int $meta_id ID of the metadata entry to delete.
740                          */
741                         do_action( "delete_{$meta_type}meta", $meta_id );
742                 }
743
744                 // Run the query, will return true if deleted, false otherwise
745                 $result = (bool) $wpdb->delete( $table, array( $id_column => $meta_id ) );
746
747                 // Clear the caches.
748                 wp_cache_delete($object_id, $meta_type . '_meta');
749
750                 /** This action is documented in wp-includes/meta.php */
751                 do_action( "deleted_{$meta_type}_meta", (array) $meta_id, $object_id, $meta->meta_key, $meta->meta_value );
752
753                 // Old-style action.
754                 if ( 'post' == $meta_type || 'comment' == $meta_type ) {
755                         /**
756                          * Fires immediately after deleting post or comment metadata of a specific type.
757                          *
758                          * The dynamic portion of the hook, `$meta_type`, refers to the meta
759                          * object type (post or comment).
760                          *
761                          * @since 3.4.0
762                          *
763                          * @param int $meta_ids Deleted metadata entry ID.
764                          */
765                         do_action( "deleted_{$meta_type}meta", $meta_id );
766                 }
767
768                 return $result;
769
770         }
771
772         // Meta id was not found.
773         return false;
774 }
775
776 /**
777  * Update the metadata cache for the specified objects.
778  *
779  * @since 2.9.0
780  *
781  * @global wpdb $wpdb WordPress database abstraction object.
782  *
783  * @param string    $meta_type  Type of object metadata is for (e.g., comment, post, or user)
784  * @param int|array $object_ids Array or comma delimited list of object IDs to update cache for
785  * @return array|false Metadata cache for the specified objects, or false on failure.
786  */
787 function update_meta_cache($meta_type, $object_ids) {
788         global $wpdb;
789
790         if ( ! $meta_type || ! $object_ids ) {
791                 return false;
792         }
793
794         $table = _get_meta_table( $meta_type );
795         if ( ! $table ) {
796                 return false;
797         }
798
799         $column = sanitize_key($meta_type . '_id');
800
801         if ( !is_array($object_ids) ) {
802                 $object_ids = preg_replace('|[^0-9,]|', '', $object_ids);
803                 $object_ids = explode(',', $object_ids);
804         }
805
806         $object_ids = array_map('intval', $object_ids);
807
808         $cache_key = $meta_type . '_meta';
809         $ids = array();
810         $cache = array();
811         foreach ( $object_ids as $id ) {
812                 $cached_object = wp_cache_get( $id, $cache_key );
813                 if ( false === $cached_object )
814                         $ids[] = $id;
815                 else
816                         $cache[$id] = $cached_object;
817         }
818
819         if ( empty( $ids ) )
820                 return $cache;
821
822         // Get meta info
823         $id_list = join( ',', $ids );
824         $id_column = 'user' == $meta_type ? 'umeta_id' : 'meta_id';
825         $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 );
826
827         if ( !empty($meta_list) ) {
828                 foreach ( $meta_list as $metarow) {
829                         $mpid = intval($metarow[$column]);
830                         $mkey = $metarow['meta_key'];
831                         $mval = $metarow['meta_value'];
832
833                         // Force subkeys to be array type:
834                         if ( !isset($cache[$mpid]) || !is_array($cache[$mpid]) )
835                                 $cache[$mpid] = array();
836                         if ( !isset($cache[$mpid][$mkey]) || !is_array($cache[$mpid][$mkey]) )
837                                 $cache[$mpid][$mkey] = array();
838
839                         // Add a value to the current pid/key:
840                         $cache[$mpid][$mkey][] = $mval;
841                 }
842         }
843
844         foreach ( $ids as $id ) {
845                 if ( ! isset($cache[$id]) )
846                         $cache[$id] = array();
847                 wp_cache_add( $id, $cache[$id], $cache_key );
848         }
849
850         return $cache;
851 }
852
853 /**
854  * Given a meta query, generates SQL clauses to be appended to a main query.
855  *
856  * @since 3.2.0
857  *
858  * @see WP_Meta_Query
859  *
860  * @param array $meta_query         A meta query.
861  * @param string $type              Type of meta.
862  * @param string $primary_table     Primary database table name.
863  * @param string $primary_id_column Primary ID column name.
864  * @param object $context           Optional. The main query object
865  * @return array Associative array of `JOIN` and `WHERE` SQL.
866  */
867 function get_meta_sql( $meta_query, $type, $primary_table, $primary_id_column, $context = null ) {
868         $meta_query_obj = new WP_Meta_Query( $meta_query );
869         return $meta_query_obj->get_sql( $type, $primary_table, $primary_id_column, $context );
870 }
871
872 /**
873  * Class for generating SQL clauses that filter a primary query according to metadata keys and values.
874  *
875  * `WP_Meta_Query` is a helper that allows primary query classes, such as {@see WP_Query} and {@see WP_User_Query},
876  * to filter their results by object metadata, by generating `JOIN` and `WHERE` subclauses to be attached
877  * to the primary SQL query string.
878  *
879  * @since 3.2.0
880  */
881 class WP_Meta_Query {
882         /**
883          * Array of metadata queries.
884          *
885          * See {@see WP_Meta_Query::__construct()} for information on meta query arguments.
886          *
887          * @since 3.2.0
888          * @access public
889          * @var array
890          */
891         public $queries = array();
892
893         /**
894          * The relation between the queries. Can be one of 'AND' or 'OR'.
895          *
896          * @since 3.2.0
897          * @access public
898          * @var string
899          */
900         public $relation;
901
902         /**
903          * Database table to query for the metadata.
904          *
905          * @since 4.1.0
906          * @access public
907          * @var string
908          */
909         public $meta_table;
910
911         /**
912          * Column in meta_table that represents the ID of the object the metadata belongs to.
913          *
914          * @since 4.1.0
915          * @access public
916          * @var string
917          */
918         public $meta_id_column;
919
920         /**
921          * Database table that where the metadata's objects are stored (eg $wpdb->users).
922          *
923          * @since 4.1.0
924          * @access public
925          * @var string
926          */
927         public $primary_table;
928
929         /**
930          * Column in primary_table that represents the ID of the object.
931          *
932          * @since 4.1.0
933          * @access public
934          * @var string
935          */
936         public $primary_id_column;
937
938         /**
939          * A flat list of table aliases used in JOIN clauses.
940          *
941          * @since 4.1.0
942          * @access protected
943          * @var array
944          */
945         protected $table_aliases = array();
946
947         /**
948          * A flat list of clauses, keyed by clause 'name'.
949          *
950          * @since 4.2.0
951          * @access protected
952          * @var array
953          */
954         protected $clauses = array();
955
956         /**
957          * Whether the query contains any OR relations.
958          *
959          * @since 4.3.0
960          * @access protected
961          * @var bool
962          */
963         protected $has_or_relation = false;
964
965         /**
966          * Constructor.
967          *
968          * @since 3.2.0
969          * @since 4.2.0 Introduced support for naming query clauses by associative array keys.
970          *
971          * @access public
972          *
973          * @param array $meta_query {
974          *     Array of meta query clauses. When first-order clauses use strings as their array keys, they may be
975          *     referenced in the 'orderby' parameter of the parent query.
976          *
977          *     @type string $relation Optional. The MySQL keyword used to join
978          *                            the clauses of the query. Accepts 'AND', or 'OR'. Default 'AND'.
979          *     @type array {
980          *         Optional. An array of first-order clause parameters, or another fully-formed meta query.
981          *
982          *         @type string $key     Meta key to filter by.
983          *         @type string $value   Meta value to filter by.
984          *         @type string $compare MySQL operator used for comparing the $value. Accepts '=',
985          *                               '!=', '>', '>=', '<', '<=', 'LIKE', 'NOT LIKE', 'IN', 'NOT IN',
986          *                               'BETWEEN', 'NOT BETWEEN', 'REGEXP', 'NOT REGEXP', or 'RLIKE'.
987          *                               Default is 'IN' when `$value` is an array, '=' otherwise.
988          *         @type string $type    MySQL data type that the meta_value column will be CAST to for
989          *                               comparisons. Accepts 'NUMERIC', 'BINARY', 'CHAR', 'DATE',
990          *                               'DATETIME', 'DECIMAL', 'SIGNED', 'TIME', or 'UNSIGNED'.
991          *                               Default is 'CHAR'.
992          *     }
993          * }
994          */
995         public function __construct( $meta_query = false ) {
996                 if ( !$meta_query )
997                         return;
998
999                 if ( isset( $meta_query['relation'] ) && strtoupper( $meta_query['relation'] ) == 'OR' ) {
1000                         $this->relation = 'OR';
1001                 } else {
1002                         $this->relation = 'AND';
1003                 }
1004
1005                 $this->queries = $this->sanitize_query( $meta_query );
1006         }
1007
1008         /**
1009          * Ensure the 'meta_query' argument passed to the class constructor is well-formed.
1010          *
1011          * Eliminates empty items and ensures that a 'relation' is set.
1012          *
1013          * @since 4.1.0
1014          * @access public
1015          *
1016          * @param array $queries Array of query clauses.
1017          * @return array Sanitized array of query clauses.
1018          */
1019         public function sanitize_query( $queries ) {
1020                 $clean_queries = array();
1021
1022                 if ( ! is_array( $queries ) ) {
1023                         return $clean_queries;
1024                 }
1025
1026                 foreach ( $queries as $key => $query ) {
1027                         if ( 'relation' === $key ) {
1028                                 $relation = $query;
1029
1030                         } elseif ( ! is_array( $query ) ) {
1031                                 continue;
1032
1033                         // First-order clause.
1034                         } elseif ( $this->is_first_order_clause( $query ) ) {
1035                                 if ( isset( $query['value'] ) && array() === $query['value'] ) {
1036                                         unset( $query['value'] );
1037                                 }
1038
1039                                 $clean_queries[ $key ] = $query;
1040
1041                         // Otherwise, it's a nested query, so we recurse.
1042                         } else {
1043                                 $cleaned_query = $this->sanitize_query( $query );
1044
1045                                 if ( ! empty( $cleaned_query ) ) {
1046                                         $clean_queries[ $key ] = $cleaned_query;
1047                                 }
1048                         }
1049                 }
1050
1051                 if ( empty( $clean_queries ) ) {
1052                         return $clean_queries;
1053                 }
1054
1055                 // Sanitize the 'relation' key provided in the query.
1056                 if ( isset( $relation ) && 'OR' === strtoupper( $relation ) ) {
1057                         $clean_queries['relation'] = 'OR';
1058                         $this->has_or_relation = true;
1059
1060                 /*
1061                  * If there is only a single clause, call the relation 'OR'.
1062                  * This value will not actually be used to join clauses, but it
1063                  * simplifies the logic around combining key-only queries.
1064                  */
1065                 } elseif ( 1 === count( $clean_queries ) ) {
1066                         $clean_queries['relation'] = 'OR';
1067
1068                 // Default to AND.
1069                 } else {
1070                         $clean_queries['relation'] = 'AND';
1071                 }
1072
1073                 return $clean_queries;
1074         }
1075
1076         /**
1077          * Determine whether a query clause is first-order.
1078          *
1079          * A first-order meta query clause is one that has either a 'key' or
1080          * a 'value' array key.
1081          *
1082          * @since 4.1.0
1083          * @access protected
1084          *
1085          * @param array $query Meta query arguments.
1086          * @return bool Whether the query clause is a first-order clause.
1087          */
1088         protected function is_first_order_clause( $query ) {
1089                 return isset( $query['key'] ) || isset( $query['value'] );
1090         }
1091
1092         /**
1093          * Constructs a meta query based on 'meta_*' query vars
1094          *
1095          * @since 3.2.0
1096          * @access public
1097          *
1098          * @param array $qv The query variables
1099          */
1100         public function parse_query_vars( $qv ) {
1101                 $meta_query = array();
1102
1103                 /*
1104                  * For orderby=meta_value to work correctly, simple query needs to be
1105                  * first (so that its table join is against an unaliased meta table) and
1106                  * needs to be its own clause (so it doesn't interfere with the logic of
1107                  * the rest of the meta_query).
1108                  */
1109                 $primary_meta_query = array();
1110                 foreach ( array( 'key', 'compare', 'type' ) as $key ) {
1111                         if ( ! empty( $qv[ "meta_$key" ] ) ) {
1112                                 $primary_meta_query[ $key ] = $qv[ "meta_$key" ];
1113                         }
1114                 }
1115
1116                 // WP_Query sets 'meta_value' = '' by default.
1117                 if ( isset( $qv['meta_value'] ) && '' !== $qv['meta_value'] && ( ! is_array( $qv['meta_value'] ) || $qv['meta_value'] ) ) {
1118                         $primary_meta_query['value'] = $qv['meta_value'];
1119                 }
1120
1121                 $existing_meta_query = isset( $qv['meta_query'] ) && is_array( $qv['meta_query'] ) ? $qv['meta_query'] : array();
1122
1123                 if ( ! empty( $primary_meta_query ) && ! empty( $existing_meta_query ) ) {
1124                         $meta_query = array(
1125                                 'relation' => 'AND',
1126                                 $primary_meta_query,
1127                                 $existing_meta_query,
1128                         );
1129                 } elseif ( ! empty( $primary_meta_query ) ) {
1130                         $meta_query = array(
1131                                 $primary_meta_query,
1132                         );
1133                 } elseif ( ! empty( $existing_meta_query ) ) {
1134                         $meta_query = $existing_meta_query;
1135                 }
1136
1137                 $this->__construct( $meta_query );
1138         }
1139
1140         /**
1141          * Return the appropriate alias for the given meta type if applicable.
1142          *
1143          * @since 3.7.0
1144          * @access public
1145          *
1146          * @param string $type MySQL type to cast meta_value.
1147          * @return string MySQL type.
1148          */
1149         public function get_cast_for_type( $type = '' ) {
1150                 if ( empty( $type ) )
1151                         return 'CHAR';
1152
1153                 $meta_type = strtoupper( $type );
1154
1155                 if ( ! preg_match( '/^(?:BINARY|CHAR|DATE|DATETIME|SIGNED|UNSIGNED|TIME|NUMERIC(?:\(\d+(?:,\s?\d+)?\))?|DECIMAL(?:\(\d+(?:,\s?\d+)?\))?)$/', $meta_type ) )
1156                         return 'CHAR';
1157
1158                 if ( 'NUMERIC' == $meta_type )
1159                         $meta_type = 'SIGNED';
1160
1161                 return $meta_type;
1162         }
1163
1164         /**
1165          * Generates SQL clauses to be appended to a main query.
1166          *
1167          * @since 3.2.0
1168          * @access public
1169          *
1170          * @param string $type              Type of meta, eg 'user', 'post'.
1171          * @param string $primary_table     Database table where the object being filtered is stored (eg wp_users).
1172          * @param string $primary_id_column ID column for the filtered object in $primary_table.
1173          * @param object $context           Optional. The main query object.
1174          * @return false|array {
1175          *     Array containing JOIN and WHERE SQL clauses to append to the main query.
1176          *
1177          *     @type string $join  SQL fragment to append to the main JOIN clause.
1178          *     @type string $where SQL fragment to append to the main WHERE clause.
1179          * }
1180          */
1181         public function get_sql( $type, $primary_table, $primary_id_column, $context = null ) {
1182                 if ( ! $meta_table = _get_meta_table( $type ) ) {
1183                         return false;
1184                 }
1185
1186                 $this->meta_table     = $meta_table;
1187                 $this->meta_id_column = sanitize_key( $type . '_id' );
1188
1189                 $this->primary_table     = $primary_table;
1190                 $this->primary_id_column = $primary_id_column;
1191
1192                 $sql = $this->get_sql_clauses();
1193
1194                 /*
1195                  * If any JOINs are LEFT JOINs (as in the case of NOT EXISTS), then all JOINs should
1196                  * be LEFT. Otherwise posts with no metadata will be excluded from results.
1197                  */
1198                 if ( false !== strpos( $sql['join'], 'LEFT JOIN' ) ) {
1199                         $sql['join'] = str_replace( 'INNER JOIN', 'LEFT JOIN', $sql['join'] );
1200                 }
1201
1202                 /**
1203                  * Filter the meta query's generated SQL.
1204                  *
1205                  * @since 3.1.0
1206                  *
1207                  * @param array $args {
1208                  *     An array of meta query SQL arguments.
1209                  *
1210                  *     @type array  $clauses           Array containing the query's JOIN and WHERE clauses.
1211                  *     @type array  $queries           Array of meta queries.
1212                  *     @type string $type              Type of meta.
1213                  *     @type string $primary_table     Primary table.
1214                  *     @type string $primary_id_column Primary column ID.
1215                  *     @type object $context           The main query object.
1216                  * }
1217                  */
1218                 return apply_filters_ref_array( 'get_meta_sql', array( $sql, $this->queries, $type, $primary_table, $primary_id_column, $context ) );
1219         }
1220
1221         /**
1222          * Generate SQL clauses to be appended to a main query.
1223          *
1224          * Called by the public {@see WP_Meta_Query::get_sql()}, this method
1225          * is abstracted out to maintain parity with the other Query classes.
1226          *
1227          * @since 4.1.0
1228          * @access protected
1229          *
1230          * @return array {
1231          *     Array containing JOIN and WHERE SQL clauses to append to the main query.
1232          *
1233          *     @type string $join  SQL fragment to append to the main JOIN clause.
1234          *     @type string $where SQL fragment to append to the main WHERE clause.
1235          * }
1236          */
1237         protected function get_sql_clauses() {
1238                 /*
1239                  * $queries are passed by reference to get_sql_for_query() for recursion.
1240                  * To keep $this->queries unaltered, pass a copy.
1241                  */
1242                 $queries = $this->queries;
1243                 $sql = $this->get_sql_for_query( $queries );
1244
1245                 if ( ! empty( $sql['where'] ) ) {
1246                         $sql['where'] = ' AND ' . $sql['where'];
1247                 }
1248
1249                 return $sql;
1250         }
1251
1252         /**
1253          * Generate SQL clauses for a single query array.
1254          *
1255          * If nested subqueries are found, this method recurses the tree to
1256          * produce the properly nested SQL.
1257          *
1258          * @since 4.1.0
1259          * @access protected
1260          *
1261          * @param array $query Query to parse, passed by reference.
1262          * @param int   $depth Optional. Number of tree levels deep we currently are.
1263          *                     Used to calculate indentation. Default 0.
1264          * @return array {
1265          *     Array containing JOIN and WHERE SQL clauses to append to a single query array.
1266          *
1267          *     @type string $join  SQL fragment to append to the main JOIN clause.
1268          *     @type string $where SQL fragment to append to the main WHERE clause.
1269          * }
1270          */
1271         protected function get_sql_for_query( &$query, $depth = 0 ) {
1272                 $sql_chunks = array(
1273                         'join'  => array(),
1274                         'where' => array(),
1275                 );
1276
1277                 $sql = array(
1278                         'join'  => '',
1279                         'where' => '',
1280                 );
1281
1282                 $indent = '';
1283                 for ( $i = 0; $i < $depth; $i++ ) {
1284                         $indent .= "  ";
1285                 }
1286
1287                 foreach ( $query as $key => &$clause ) {
1288                         if ( 'relation' === $key ) {
1289                                 $relation = $query['relation'];
1290                         } elseif ( is_array( $clause ) ) {
1291
1292                                 // This is a first-order clause.
1293                                 if ( $this->is_first_order_clause( $clause ) ) {
1294                                         $clause_sql = $this->get_sql_for_clause( $clause, $query, $key );
1295
1296                                         $where_count = count( $clause_sql['where'] );
1297                                         if ( ! $where_count ) {
1298                                                 $sql_chunks['where'][] = '';
1299                                         } elseif ( 1 === $where_count ) {
1300                                                 $sql_chunks['where'][] = $clause_sql['where'][0];
1301                                         } else {
1302                                                 $sql_chunks['where'][] = '( ' . implode( ' AND ', $clause_sql['where'] ) . ' )';
1303                                         }
1304
1305                                         $sql_chunks['join'] = array_merge( $sql_chunks['join'], $clause_sql['join'] );
1306                                 // This is a subquery, so we recurse.
1307                                 } else {
1308                                         $clause_sql = $this->get_sql_for_query( $clause, $depth + 1 );
1309
1310                                         $sql_chunks['where'][] = $clause_sql['where'];
1311                                         $sql_chunks['join'][]  = $clause_sql['join'];
1312                                 }
1313                         }
1314                 }
1315
1316                 // Filter to remove empties.
1317                 $sql_chunks['join']  = array_filter( $sql_chunks['join'] );
1318                 $sql_chunks['where'] = array_filter( $sql_chunks['where'] );
1319
1320                 if ( empty( $relation ) ) {
1321                         $relation = 'AND';
1322                 }
1323
1324                 // Filter duplicate JOIN clauses and combine into a single string.
1325                 if ( ! empty( $sql_chunks['join'] ) ) {
1326                         $sql['join'] = implode( ' ', array_unique( $sql_chunks['join'] ) );
1327                 }
1328
1329                 // Generate a single WHERE clause with proper brackets and indentation.
1330                 if ( ! empty( $sql_chunks['where'] ) ) {
1331                         $sql['where'] = '( ' . "\n  " . $indent . implode( ' ' . "\n  " . $indent . $relation . ' ' . "\n  " . $indent, $sql_chunks['where'] ) . "\n" . $indent . ')';
1332                 }
1333
1334                 return $sql;
1335         }
1336
1337         /**
1338          * Generate SQL JOIN and WHERE clauses for a first-order query clause.
1339          *
1340          * "First-order" means that it's an array with a 'key' or 'value'.
1341          *
1342          * @since 4.1.0
1343          * @access public
1344          *
1345          * @global wpdb $wpdb
1346          *
1347          * @param array  $clause       Query clause, passed by reference.
1348          * @param array  $parent_query Parent query array.
1349          * @param string $clause_key   Optional. The array key used to name the clause in the original `$meta_query`
1350          *                             parameters. If not provided, a key will be generated automatically.
1351          * @return array {
1352          *     Array containing JOIN and WHERE SQL clauses to append to a first-order query.
1353          *
1354          *     @type string $join  SQL fragment to append to the main JOIN clause.
1355          *     @type string $where SQL fragment to append to the main WHERE clause.
1356          * }
1357          */
1358         public function get_sql_for_clause( &$clause, $parent_query, $clause_key = '' ) {
1359                 global $wpdb;
1360
1361                 $sql_chunks = array(
1362                         'where' => array(),
1363                         'join' => array(),
1364                 );
1365
1366                 if ( isset( $clause['compare'] ) ) {
1367                         $clause['compare'] = strtoupper( $clause['compare'] );
1368                 } else {
1369                         $clause['compare'] = isset( $clause['value'] ) && is_array( $clause['value'] ) ? 'IN' : '=';
1370                 }
1371
1372                 if ( ! in_array( $clause['compare'], array(
1373                         '=', '!=', '>', '>=', '<', '<=',
1374                         'LIKE', 'NOT LIKE',
1375                         'IN', 'NOT IN',
1376                         'BETWEEN', 'NOT BETWEEN',
1377                         'EXISTS', 'NOT EXISTS',
1378                         'REGEXP', 'NOT REGEXP', 'RLIKE'
1379                 ) ) ) {
1380                         $clause['compare'] = '=';
1381                 }
1382
1383                 $meta_compare = $clause['compare'];
1384
1385                 // First build the JOIN clause, if one is required.
1386                 $join = '';
1387
1388                 // We prefer to avoid joins if possible. Look for an existing join compatible with this clause.
1389                 $alias = $this->find_compatible_table_alias( $clause, $parent_query );
1390                 if ( false === $alias ) {
1391                         $i = count( $this->table_aliases );
1392                         $alias = $i ? 'mt' . $i : $this->meta_table;
1393
1394                         // JOIN clauses for NOT EXISTS have their own syntax.
1395                         if ( 'NOT EXISTS' === $meta_compare ) {
1396                                 $join .= " LEFT JOIN $this->meta_table";
1397                                 $join .= $i ? " AS $alias" : '';
1398                                 $join .= $wpdb->prepare( " ON ($this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column AND $alias.meta_key = %s )", $clause['key'] );
1399
1400                         // All other JOIN clauses.
1401                         } else {
1402                                 $join .= " INNER JOIN $this->meta_table";
1403                                 $join .= $i ? " AS $alias" : '';
1404                                 $join .= " ON ( $this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column )";
1405                         }
1406
1407                         $this->table_aliases[] = $alias;
1408                         $sql_chunks['join'][] = $join;
1409                 }
1410
1411                 // Save the alias to this clause, for future siblings to find.
1412                 $clause['alias'] = $alias;
1413
1414                 // Determine the data type.
1415                 $_meta_type = isset( $clause['type'] ) ? $clause['type'] : '';
1416                 $meta_type  = $this->get_cast_for_type( $_meta_type );
1417                 $clause['cast'] = $meta_type;
1418
1419                 // Fallback for clause keys is the table alias.
1420                 if ( ! $clause_key ) {
1421                         $clause_key = $clause['alias'];
1422                 }
1423
1424                 // Ensure unique clause keys, so none are overwritten.
1425                 $iterator = 1;
1426                 $clause_key_base = $clause_key;
1427                 while ( isset( $this->clauses[ $clause_key ] ) ) {
1428                         $clause_key = $clause_key_base . '-' . $iterator;
1429                         $iterator++;
1430                 }
1431
1432                 // Store the clause in our flat array.
1433                 $this->clauses[ $clause_key ] =& $clause;
1434
1435                 // Next, build the WHERE clause.
1436
1437                 // meta_key.
1438                 if ( array_key_exists( 'key', $clause ) ) {
1439                         if ( 'NOT EXISTS' === $meta_compare ) {
1440                                 $sql_chunks['where'][] = $alias . '.' . $this->meta_id_column . ' IS NULL';
1441                         } else {
1442                                 $sql_chunks['where'][] = $wpdb->prepare( "$alias.meta_key = %s", trim( $clause['key'] ) );
1443                         }
1444                 }
1445
1446                 // meta_value.
1447                 if ( array_key_exists( 'value', $clause ) ) {
1448                         $meta_value = $clause['value'];
1449
1450                         if ( in_array( $meta_compare, array( 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN' ) ) ) {
1451                                 if ( ! is_array( $meta_value ) ) {
1452                                         $meta_value = preg_split( '/[,\s]+/', $meta_value );
1453                                 }
1454                         } else {
1455                                 $meta_value = trim( $meta_value );
1456                         }
1457
1458                         switch ( $meta_compare ) {
1459                                 case 'IN' :
1460                                 case 'NOT IN' :
1461                                         $meta_compare_string = '(' . substr( str_repeat( ',%s', count( $meta_value ) ), 1 ) . ')';
1462                                         $where = $wpdb->prepare( $meta_compare_string, $meta_value );
1463                                         break;
1464
1465                                 case 'BETWEEN' :
1466                                 case 'NOT BETWEEN' :
1467                                         $meta_value = array_slice( $meta_value, 0, 2 );
1468                                         $where = $wpdb->prepare( '%s AND %s', $meta_value );
1469                                         break;
1470
1471                                 case 'LIKE' :
1472                                 case 'NOT LIKE' :
1473                                         $meta_value = '%' . $wpdb->esc_like( $meta_value ) . '%';
1474                                         $where = $wpdb->prepare( '%s', $meta_value );
1475                                         break;
1476
1477                                 // EXISTS with a value is interpreted as '='.
1478                                 case 'EXISTS' :
1479                                         $meta_compare = '=';
1480                                         $where = $wpdb->prepare( '%s', $meta_value );
1481                                         break;
1482
1483                                 // 'value' is ignored for NOT EXISTS.
1484                                 case 'NOT EXISTS' :
1485                                         $where = '';
1486                                         break;
1487
1488                                 default :
1489                                         $where = $wpdb->prepare( '%s', $meta_value );
1490                                         break;
1491
1492                         }
1493
1494                         if ( $where ) {
1495                                 $sql_chunks['where'][] = "CAST($alias.meta_value AS {$meta_type}) {$meta_compare} {$where}";
1496                         }
1497                 }
1498
1499                 /*
1500                  * Multiple WHERE clauses (for meta_key and meta_value) should
1501                  * be joined in parentheses.
1502                  */
1503                 if ( 1 < count( $sql_chunks['where'] ) ) {
1504                         $sql_chunks['where'] = array( '( ' . implode( ' AND ', $sql_chunks['where'] ) . ' )' );
1505                 }
1506
1507                 return $sql_chunks;
1508         }
1509
1510         /**
1511          * Get a flattened list of sanitized meta clauses.
1512          *
1513          * This array should be used for clause lookup, as when the table alias and CAST type must be determined for
1514          * a value of 'orderby' corresponding to a meta clause.
1515          *
1516          * @since 4.2.0
1517          * @access public
1518          *
1519          * @return array Meta clauses.
1520          */
1521         public function get_clauses() {
1522                 return $this->clauses;
1523         }
1524
1525         /**
1526          * Identify an existing table alias that is compatible with the current
1527          * query clause.
1528          *
1529          * We avoid unnecessary table joins by allowing each clause to look for
1530          * an existing table alias that is compatible with the query that it
1531          * needs to perform.
1532          *
1533          * An existing alias is compatible if (a) it is a sibling of `$clause`
1534          * (ie, it's under the scope of the same relation), and (b) the combination
1535          * of operator and relation between the clauses allows for a shared table join.
1536          * In the case of {@see WP_Meta_Query}, this only applies to 'IN' clauses that
1537          * are connected by the relation 'OR'.
1538          *
1539          * @since 4.1.0
1540          * @access protected
1541          *
1542          * @param  array       $clause       Query clause.
1543          * @param  array       $parent_query Parent query of $clause.
1544          * @return string|bool Table alias if found, otherwise false.
1545          */
1546         protected function find_compatible_table_alias( $clause, $parent_query ) {
1547                 $alias = false;
1548
1549                 foreach ( $parent_query as $sibling ) {
1550                         // If the sibling has no alias yet, there's nothing to check.
1551                         if ( empty( $sibling['alias'] ) ) {
1552                                 continue;
1553                         }
1554
1555                         // We're only interested in siblings that are first-order clauses.
1556                         if ( ! is_array( $sibling ) || ! $this->is_first_order_clause( $sibling ) ) {
1557                                 continue;
1558                         }
1559
1560                         $compatible_compares = array();
1561
1562                         // Clauses connected by OR can share joins as long as they have "positive" operators.
1563                         if ( 'OR' === $parent_query['relation'] ) {
1564                                 $compatible_compares = array( '=', 'IN', 'BETWEEN', 'LIKE', 'REGEXP', 'RLIKE', '>', '>=', '<', '<=' );
1565
1566                         // Clauses joined by AND with "negative" operators share a join only if they also share a key.
1567                         } elseif ( isset( $sibling['key'] ) && isset( $clause['key'] ) && $sibling['key'] === $clause['key'] ) {
1568                                 $compatible_compares = array( '!=', 'NOT IN', 'NOT LIKE' );
1569                         }
1570
1571                         $clause_compare  = strtoupper( $clause['compare'] );
1572                         $sibling_compare = strtoupper( $sibling['compare'] );
1573                         if ( in_array( $clause_compare, $compatible_compares ) && in_array( $sibling_compare, $compatible_compares ) ) {
1574                                 $alias = $sibling['alias'];
1575                                 break;
1576                         }
1577                 }
1578
1579                 /**
1580                  * Filter the table alias identified as compatible with the current clause.
1581                  *
1582                  * @since 4.1.0
1583                  *
1584                  * @param string|bool $alias        Table alias, or false if none was found.
1585                  * @param array       $clause       First-order query clause.
1586                  * @param array       $parent_query Parent of $clause.
1587                  * @param object      $this         WP_Meta_Query object.
1588                  */
1589                 return apply_filters( 'meta_query_find_compatible_table_alias', $alias, $clause, $parent_query, $this ) ;
1590         }
1591
1592         /**
1593          * Checks whether the current query has any OR relations.
1594          *
1595          * In some cases, the presence of an OR relation somewhere in the query will require
1596          * the use of a `DISTINCT` or `GROUP BY` keyword in the `SELECT` clause. The current
1597          * method can be used in these cases to determine whether such a clause is necessary.
1598          *
1599          * @since 4.3.0
1600          *
1601          * @return bool True if the query contains any `OR` relations, otherwise false.
1602          */
1603         public function has_or_relation() {
1604                 return $this->has_or_relation;
1605         }
1606 }
1607
1608 /**
1609  * Retrieve the name of the metadata table for the specified object type.
1610  *
1611  * @since 2.9.0
1612  *
1613  * @global wpdb $wpdb WordPress database abstraction object.
1614  *
1615  * @param string $type Type of object to get metadata table for (e.g., comment, post, or user)
1616  * @return string|false Metadata table name, or false if no metadata table exists
1617  */
1618 function _get_meta_table($type) {
1619         global $wpdb;
1620
1621         $table_name = $type . 'meta';
1622
1623         if ( empty($wpdb->$table_name) )
1624                 return false;
1625
1626         return $wpdb->$table_name;
1627 }
1628
1629 /**
1630  * Determine whether a meta key is protected.
1631  *
1632  * @since 3.1.3
1633  *
1634  * @param string      $meta_key Meta key
1635  * @param string|null $meta_type
1636  * @return bool True if the key is protected, false otherwise.
1637  */
1638 function is_protected_meta( $meta_key, $meta_type = null ) {
1639         $protected = ( '_' == $meta_key[0] );
1640
1641         /**
1642          * Filter whether a meta key is protected.
1643          *
1644          * @since 3.2.0
1645          *
1646          * @param bool   $protected Whether the key is protected. Default false.
1647          * @param string $meta_key  Meta key.
1648          * @param string $meta_type Meta type.
1649          */
1650         return apply_filters( 'is_protected_meta', $protected, $meta_key, $meta_type );
1651 }
1652
1653 /**
1654  * Sanitize meta value.
1655  *
1656  * @since 3.1.3
1657  *
1658  * @param string $meta_key   Meta key
1659  * @param mixed  $meta_value Meta value to sanitize
1660  * @param string $meta_type  Type of meta
1661  * @return mixed Sanitized $meta_value
1662  */
1663 function sanitize_meta( $meta_key, $meta_value, $meta_type ) {
1664
1665         /**
1666          * Filter the sanitization of a specific meta key of a specific meta type.
1667          *
1668          * The dynamic portions of the hook name, `$meta_type`, and `$meta_key`,
1669          * refer to the metadata object type (comment, post, or user) and the meta
1670          * key value,
1671          * respectively.
1672          *
1673          * @since 3.3.0
1674          *
1675          * @param mixed  $meta_value Meta value to sanitize.
1676          * @param string $meta_key   Meta key.
1677          * @param string $meta_type  Meta type.
1678          */
1679         return apply_filters( "sanitize_{$meta_type}_meta_{$meta_key}", $meta_value, $meta_key, $meta_type );
1680 }
1681
1682 /**
1683  * Register meta key
1684  *
1685  * @since 3.3.0
1686  *
1687  * @param string       $meta_type         Type of meta
1688  * @param string       $meta_key          Meta key
1689  * @param string|array $sanitize_callback A function or method to call when sanitizing the value of $meta_key.
1690  * @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.
1691  */
1692 function register_meta( $meta_type, $meta_key, $sanitize_callback, $auth_callback = null ) {
1693         if ( is_callable( $sanitize_callback ) )
1694                 add_filter( "sanitize_{$meta_type}_meta_{$meta_key}", $sanitize_callback, 10, 3 );
1695
1696         if ( empty( $auth_callback ) ) {
1697                 if ( is_protected_meta( $meta_key, $meta_type ) )
1698                         $auth_callback = '__return_false';
1699                 else
1700                         $auth_callback = '__return_true';
1701         }
1702
1703         if ( is_callable( $auth_callback ) )
1704                 add_filter( "auth_{$meta_type}_meta_{$meta_key}", $auth_callback, 10, 6 );
1705 }