WordPress 4.0
[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  * @uses $wpdb WordPress database object for queries.
19  *
20  * @param string $meta_type Type of object metadata is for (e.g., comment, post, or user)
21  * @param int $object_id ID of the object metadata is for
22  * @param string $meta_key Metadata key
23  * @param mixed $meta_value Metadata value. Must be serializable if non-scalar.
24  * @param bool $unique Optional, default is false. Whether the specified metadata key should be
25  *              unique for the object. If true, and the object already has a value for the specified
26  *              metadata key, no change will be made
27  * @return int|bool The meta ID on success, false on failure.
28  */
29 function add_metadata($meta_type, $object_id, $meta_key, $meta_value, $unique = false) {
30         global $wpdb;
31
32         if ( ! $meta_type || ! $meta_key || ! is_numeric( $object_id ) ) {
33                 return false;
34         }
35
36         $object_id = absint( $object_id );
37         if ( ! $object_id ) {
38                 return false;
39         }
40
41         $table = _get_meta_table( $meta_type );
42         if ( ! $table ) {
43                 return false;
44         }
45
46         $column = sanitize_key($meta_type . '_id');
47
48         // expected_slashed ($meta_key)
49         $meta_key = wp_unslash($meta_key);
50         $meta_value = wp_unslash($meta_value);
51         $meta_value = sanitize_meta( $meta_key, $meta_value, $meta_type );
52
53         /**
54          * Filter whether to add metadata of a specific type.
55          *
56          * The dynamic portion of the hook, $meta_type, refers to the meta
57          * object type (comment, post, or user). Returning a non-null value
58          * will effectively short-circuit the function.
59          *
60          * @since 3.1.0
61          *
62          * @param null|bool $check      Whether to allow adding metadata for the given type.
63          * @param int       $object_id  Object ID.
64          * @param string    $meta_key   Meta key.
65          * @param mixed     $meta_value Meta value. Must be serializable if non-scalar.
66          * @param bool      $unique     Whether the specified meta key should be unique
67          *                              for the object. Optional. Default false.
68          */
69         $check = apply_filters( "add_{$meta_type}_metadata", null, $object_id, $meta_key, $meta_value, $unique );
70         if ( null !== $check )
71                 return $check;
72
73         if ( $unique && $wpdb->get_var( $wpdb->prepare(
74                 "SELECT COUNT(*) FROM $table WHERE meta_key = %s AND $column = %d",
75                 $meta_key, $object_id ) ) )
76                 return false;
77
78         $_meta_value = $meta_value;
79         $meta_value = maybe_serialize( $meta_value );
80
81         /**
82          * Fires immediately before meta of a specific type is added.
83          *
84          * The dynamic portion of the hook, $meta_type, refers to the meta
85          * object type (comment, post, or user).
86          *
87          * @since 3.1.0
88          *
89          * @param int    $object_id  Object ID.
90          * @param string $meta_key   Meta key.
91          * @param mixed  $meta_value Meta value.
92          */
93         do_action( "add_{$meta_type}_meta", $object_id, $meta_key, $_meta_value );
94
95         $result = $wpdb->insert( $table, array(
96                 $column => $object_id,
97                 'meta_key' => $meta_key,
98                 'meta_value' => $meta_value
99         ) );
100
101         if ( ! $result )
102                 return false;
103
104         $mid = (int) $wpdb->insert_id;
105
106         wp_cache_delete($object_id, $meta_type . '_meta');
107
108         /**
109          * Fires immediately after meta of a specific type is added.
110          *
111          * The dynamic portion of the hook, $meta_type, refers to the meta
112          * object type (comment, post, or user).
113          *
114          * @since 2.9.0
115          *
116          * @param int    $mid        The meta ID after successful update.
117          * @param int    $object_id  Object ID.
118          * @param string $meta_key   Meta key.
119          * @param mixed  $meta_value Meta value.
120          */
121         do_action( "added_{$meta_type}_meta", $mid, $object_id, $meta_key, $_meta_value );
122
123         return $mid;
124 }
125
126 /**
127  * Update metadata for the specified object. If no value already exists for the specified object
128  * ID and metadata key, the metadata will be added.
129  *
130  * @since 2.9.0
131  * @uses $wpdb WordPress database object for queries.
132  *
133  * @param string $meta_type Type of object metadata is for (e.g., comment, post, or user)
134  * @param int $object_id ID of the object metadata is for
135  * @param string $meta_key Metadata key
136  * @param mixed $meta_value Metadata value. Must be serializable if non-scalar.
137  * @param mixed $prev_value Optional. If specified, only update existing metadata entries with
138  *              the specified value. Otherwise, update all entries.
139  * @return int|bool Meta ID if the key didn't exist, true on successful update, false on failure.
140  */
141 function update_metadata($meta_type, $object_id, $meta_key, $meta_value, $prev_value = '') {
142         global $wpdb;
143
144         if ( ! $meta_type || ! $meta_key || ! is_numeric( $object_id ) ) {
145                 return false;
146         }
147
148         $object_id = absint( $object_id );
149         if ( ! $object_id ) {
150                 return false;
151         }
152
153         $table = _get_meta_table( $meta_type );
154         if ( ! $table ) {
155                 return false;
156         }
157
158         $column = sanitize_key($meta_type . '_id');
159         $id_column = 'user' == $meta_type ? 'umeta_id' : 'meta_id';
160
161         // expected_slashed ($meta_key)
162         $meta_key = wp_unslash($meta_key);
163         $passed_value = $meta_value;
164         $meta_value = wp_unslash($meta_value);
165         $meta_value = sanitize_meta( $meta_key, $meta_value, $meta_type );
166
167         /**
168          * Filter whether to update metadata of a specific type.
169          *
170          * The dynamic portion of the hook, $meta_type, refers to the meta
171          * object type (comment, post, or user). Returning a non-null value
172          * will effectively short-circuit the function.
173          *
174          * @since 3.1.0
175          *
176          * @param null|bool $check      Whether to allow updating metadata for the given type.
177          * @param int       $object_id  Object ID.
178          * @param string    $meta_key   Meta key.
179          * @param mixed     $meta_value Meta value. Must be serializable if non-scalar.
180          * @param mixed     $prev_value Optional. If specified, only update existing
181          *                              metadata entries with the specified value.
182          *                              Otherwise, update all entries.
183          */
184         $check = apply_filters( "update_{$meta_type}_metadata", null, $object_id, $meta_key, $meta_value, $prev_value );
185         if ( null !== $check )
186                 return (bool) $check;
187
188         // Compare existing value to new value if no prev value given and the key exists only once.
189         if ( empty($prev_value) ) {
190                 $old_value = get_metadata($meta_type, $object_id, $meta_key);
191                 if ( count($old_value) == 1 ) {
192                         if ( $old_value[0] === $meta_value )
193                                 return false;
194                 }
195         }
196
197         if ( ! $meta_id = $wpdb->get_var( $wpdb->prepare( "SELECT $id_column FROM $table WHERE meta_key = %s AND $column = %d", $meta_key, $object_id ) ) )
198                 return add_metadata($meta_type, $object_id, $meta_key, $passed_value);
199
200         $_meta_value = $meta_value;
201         $meta_value = maybe_serialize( $meta_value );
202
203         $data  = compact( 'meta_value' );
204         $where = array( $column => $object_id, 'meta_key' => $meta_key );
205
206         if ( !empty( $prev_value ) ) {
207                 $prev_value = maybe_serialize($prev_value);
208                 $where['meta_value'] = $prev_value;
209         }
210
211         /**
212          * Fires immediately before updating metadata of a specific type.
213          *
214          * The dynamic portion of the hook, $meta_type, refers to the meta
215          * object type (comment, post, or user).
216          *
217          * @since 2.9.0
218          *
219          * @param int    $meta_id    ID of the metadata entry to update.
220          * @param int    $object_id  Object ID.
221          * @param string $meta_key   Meta key.
222          * @param mixed  $meta_value Meta value.
223          */
224         do_action( "update_{$meta_type}_meta", $meta_id, $object_id, $meta_key, $_meta_value );
225
226         if ( 'post' == $meta_type )
227                 /**
228                  * Fires immediately before updating a post's metadata.
229                  *
230                  * @since 2.9.0
231                  *
232                  * @param int    $meta_id    ID of metadata entry to update.
233                  * @param int    $object_id  Object ID.
234                  * @param string $meta_key   Meta key.
235                  * @param mixed  $meta_value Meta value.
236                  */
237                 do_action( 'update_postmeta', $meta_id, $object_id, $meta_key, $meta_value );
238
239         $result = $wpdb->update( $table, $data, $where );
240         if ( ! $result )
241                 return false;
242
243         wp_cache_delete($object_id, $meta_type . '_meta');
244
245         /**
246          * Fires immediately after updating metadata of a specific type.
247          *
248          * The dynamic portion of the hook, $meta_type, refers to the meta
249          * object type (comment, post, or user).
250          *
251          * @since 2.9.0
252          *
253          * @param int    $meta_id    ID of updated metadata entry.
254          * @param int    $object_id  Object ID.
255          * @param string $meta_key   Meta key.
256          * @param mixed  $meta_value Meta value.
257          */
258         do_action( "updated_{$meta_type}_meta", $meta_id, $object_id, $meta_key, $_meta_value );
259
260         if ( 'post' == $meta_type ) {
261                 /**
262                  * Fires immediately after updating a post's metadata.
263                  *
264                  * @since 2.9.0
265                  *
266                  * @param int    $meta_id    ID of updated metadata entry.
267                  * @param int    $object_id  Object ID.
268                  * @param string $meta_key   Meta key.
269                  * @param mixed  $meta_value Meta value.
270                  */
271                 do_action( 'updated_postmeta', $meta_id, $object_id, $meta_key, $meta_value );
272         }
273
274         return true;
275 }
276
277 /**
278  * Delete metadata for the specified object.
279  *
280  * @since 2.9.0
281  * @uses $wpdb WordPress database object for queries.
282  *
283  * @param string $meta_type Type of object metadata is for (e.g., comment, post, or user)
284  * @param int $object_id ID of the object metadata is for
285  * @param string $meta_key Metadata key
286  * @param mixed $meta_value Optional. Metadata value. Must be serializable if non-scalar. If specified, only delete metadata entries
287  *              with this value. Otherwise, delete all entries with the specified meta_key.
288  * @param bool $delete_all Optional, default is false. If true, delete matching metadata entries
289  *              for all objects, ignoring the specified object_id. Otherwise, only delete matching
290  *              metadata entries for the specified object_id.
291  * @return bool True on successful delete, false on failure.
292  */
293 function delete_metadata($meta_type, $object_id, $meta_key, $meta_value = '', $delete_all = false) {
294         global $wpdb;
295
296         if ( ! $meta_type || ! $meta_key || ! is_numeric( $object_id ) && ! $delete_all ) {
297                 return false;
298         }
299
300         $object_id = absint( $object_id );
301         if ( ! $object_id && ! $delete_all ) {
302                 return false;
303         }
304
305         $table = _get_meta_table( $meta_type );
306         if ( ! $table ) {
307                 return false;
308         }
309
310         $type_column = sanitize_key($meta_type . '_id');
311         $id_column = 'user' == $meta_type ? 'umeta_id' : 'meta_id';
312         // expected_slashed ($meta_key)
313         $meta_key = wp_unslash($meta_key);
314         $meta_value = wp_unslash($meta_value);
315
316         /**
317          * Filter whether to delete metadata of a specific type.
318          *
319          * The dynamic portion of the hook, $meta_type, refers to the meta
320          * object type (comment, post, or user). Returning a non-null value
321          * will effectively short-circuit the function.
322          *
323          * @since 3.1.0
324          *
325          * @param null|bool $delete     Whether to allow metadata deletion of the given type.
326          * @param int       $object_id  Object ID.
327          * @param string    $meta_key   Meta key.
328          * @param mixed     $meta_value Meta value. Must be serializable if non-scalar.
329          * @param bool      $delete_all Whether to delete the matching metadata entries
330          *                              for all objects, ignoring the specified $object_id.
331          *                              Default false.
332          */
333         $check = apply_filters( "delete_{$meta_type}_metadata", null, $object_id, $meta_key, $meta_value, $delete_all );
334         if ( null !== $check )
335                 return (bool) $check;
336
337         $_meta_value = $meta_value;
338         $meta_value = maybe_serialize( $meta_value );
339
340         $query = $wpdb->prepare( "SELECT $id_column FROM $table WHERE meta_key = %s", $meta_key );
341
342         if ( !$delete_all )
343                 $query .= $wpdb->prepare(" AND $type_column = %d", $object_id );
344
345         if ( $meta_value )
346                 $query .= $wpdb->prepare(" AND meta_value = %s", $meta_value );
347
348         $meta_ids = $wpdb->get_col( $query );
349         if ( !count( $meta_ids ) )
350                 return false;
351
352         if ( $delete_all )
353                 $object_ids = $wpdb->get_col( $wpdb->prepare( "SELECT $type_column FROM $table WHERE meta_key = %s", $meta_key ) );
354
355         /**
356          * Fires immediately before deleting metadata of a specific type.
357          *
358          * The dynamic portion of the hook, $meta_type, refers to the meta
359          * object type (comment, post, or user).
360          *
361          * @since 3.1.0
362          *
363          * @param array  $meta_ids   An array of metadata entry IDs to delete.
364          * @param int    $object_id  Object ID.
365          * @param string $meta_key   Meta key.
366          * @param mixed  $meta_value Meta value.
367          */
368         do_action( "delete_{$meta_type}_meta", $meta_ids, $object_id, $meta_key, $_meta_value );
369
370         // Old-style action.
371         if ( 'post' == $meta_type ) {
372                 /**
373                  * Fires immediately before deleting metadata for a post.
374                  *
375                  * @since 2.9.0
376                  *
377                  * @param array $meta_ids An array of post metadata entry IDs to delete.
378                  */
379                 do_action( 'delete_postmeta', $meta_ids );
380         }
381
382         $query = "DELETE FROM $table WHERE $id_column IN( " . implode( ',', $meta_ids ) . " )";
383
384         $count = $wpdb->query($query);
385
386         if ( !$count )
387                 return false;
388
389         if ( $delete_all ) {
390                 foreach ( (array) $object_ids as $o_id ) {
391                         wp_cache_delete($o_id, $meta_type . '_meta');
392                 }
393         } else {
394                 wp_cache_delete($object_id, $meta_type . '_meta');
395         }
396
397         /**
398          * Fires immediately after deleting metadata of a specific type.
399          *
400          * The dynamic portion of the hook name, $meta_type, refers to the meta
401          * object type (comment, post, or user).
402          *
403          * @since 2.9.0
404          *
405          * @param array  $meta_ids   An array of deleted metadata entry IDs.
406          * @param int    $object_id  Object ID.
407          * @param string $meta_key   Meta key.
408          * @param mixed  $meta_value Meta value.
409          */
410         do_action( "deleted_{$meta_type}_meta", $meta_ids, $object_id, $meta_key, $_meta_value );
411
412         // Old-style action.
413         if ( 'post' == $meta_type ) {
414                 /**
415                  * Fires immediately after deleting metadata for a post.
416                  *
417                  * @since 2.9.0
418                  *
419                  * @param array $meta_ids An array of deleted post metadata entry IDs.
420                  */
421                 do_action( 'deleted_postmeta', $meta_ids );
422         }
423
424         return true;
425 }
426
427 /**
428  * Retrieve metadata for the specified object.
429  *
430  * @since 2.9.0
431  *
432  * @param string $meta_type Type of object metadata is for (e.g., comment, post, or user)
433  * @param int $object_id ID of the object metadata is for
434  * @param string $meta_key Optional. Metadata key. If not specified, retrieve all metadata for
435  *              the specified object.
436  * @param bool $single Optional, default is false. If true, return only the first value of the
437  *              specified meta_key. This parameter has no effect if meta_key is not specified.
438  * @return string|array Single metadata value, or array of values
439  */
440 function get_metadata($meta_type, $object_id, $meta_key = '', $single = false) {
441         if ( ! $meta_type || ! is_numeric( $object_id ) ) {
442                 return false;
443         }
444
445         $object_id = absint( $object_id );
446         if ( ! $object_id ) {
447                 return false;
448         }
449
450         /**
451          * Filter whether to retrieve metadata of a specific type.
452          *
453          * The dynamic portion of the hook, $meta_type, refers to the meta
454          * object type (comment, post, or user). Returning a non-null value
455          * will effectively short-circuit the function.
456          *
457          * @since 3.1.0
458          *
459          * @param null|array|string $value     The value get_metadata() should
460          *                                     return - a single metadata value,
461          *                                     or an array of values.
462          * @param int               $object_id Object ID.
463          * @param string            $meta_key  Meta key.
464          * @param string|array      $single    Meta value, or an array of values.
465          */
466         $check = apply_filters( "get_{$meta_type}_metadata", null, $object_id, $meta_key, $single );
467         if ( null !== $check ) {
468                 if ( $single && is_array( $check ) )
469                         return $check[0];
470                 else
471                         return $check;
472         }
473
474         $meta_cache = wp_cache_get($object_id, $meta_type . '_meta');
475
476         if ( !$meta_cache ) {
477                 $meta_cache = update_meta_cache( $meta_type, array( $object_id ) );
478                 $meta_cache = $meta_cache[$object_id];
479         }
480
481         if ( !$meta_key )
482                 return $meta_cache;
483
484         if ( isset($meta_cache[$meta_key]) ) {
485                 if ( $single )
486                         return maybe_unserialize( $meta_cache[$meta_key][0] );
487                 else
488                         return array_map('maybe_unserialize', $meta_cache[$meta_key]);
489         }
490
491         if ($single)
492                 return '';
493         else
494                 return array();
495 }
496
497 /**
498  * Determine if a meta key is set for a given object
499  *
500  * @since 3.3.0
501  *
502  * @param string $meta_type Type of object metadata is for (e.g., comment, post, or user)
503  * @param int $object_id ID of the object metadata is for
504  * @param string $meta_key Metadata key.
505  * @return boolean true of the key is set, false if not.
506  */
507 function metadata_exists( $meta_type, $object_id, $meta_key ) {
508         if ( ! $meta_type || ! is_numeric( $object_id ) ) {
509                 return false;
510         }
511
512         $object_id = absint( $object_id );
513         if ( ! $object_id ) {
514                 return false;
515         }
516
517         /** This filter is documented in wp-includes/meta.php */
518         $check = apply_filters( "get_{$meta_type}_metadata", null, $object_id, $meta_key, true );
519         if ( null !== $check )
520                 return (bool) $check;
521
522         $meta_cache = wp_cache_get( $object_id, $meta_type . '_meta' );
523
524         if ( !$meta_cache ) {
525                 $meta_cache = update_meta_cache( $meta_type, array( $object_id ) );
526                 $meta_cache = $meta_cache[$object_id];
527         }
528
529         if ( isset( $meta_cache[ $meta_key ] ) )
530                 return true;
531
532         return false;
533 }
534
535 /**
536  * Get meta data by meta ID
537  *
538  * @since 3.3.0
539  *
540  * @param string $meta_type Type of object metadata is for (e.g., comment, post, or user)
541  * @param int $meta_id ID for a specific meta row
542  * @return object Meta object or false.
543  */
544 function get_metadata_by_mid( $meta_type, $meta_id ) {
545         global $wpdb;
546
547         if ( ! $meta_type || ! is_numeric( $meta_id ) ) {
548                 return false;
549         }
550
551         $meta_id = absint( $meta_id );
552         if ( ! $meta_id ) {
553                 return false;
554         }
555
556         $table = _get_meta_table( $meta_type );
557         if ( ! $table ) {
558                 return false;
559         }
560
561         $id_column = ( 'user' == $meta_type ) ? 'umeta_id' : 'meta_id';
562
563         $meta = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $table WHERE $id_column = %d", $meta_id ) );
564
565         if ( empty( $meta ) )
566                 return false;
567
568         if ( isset( $meta->meta_value ) )
569                 $meta->meta_value = maybe_unserialize( $meta->meta_value );
570
571         return $meta;
572 }
573
574 /**
575  * Update meta data by meta ID
576  *
577  * @since 3.3.0
578  *
579  * @uses get_metadata_by_mid() Calls get_metadata_by_mid() to fetch the meta key, value
580  *              and object_id of the given meta_id.
581  *
582  * @param string $meta_type Type of object metadata is for (e.g., comment, post, or user)
583  * @param int $meta_id ID for a specific meta row
584  * @param string $meta_value Metadata value
585  * @param string $meta_key Optional, you can provide a meta key to update it
586  * @return bool True on successful update, false on failure.
587  */
588 function update_metadata_by_mid( $meta_type, $meta_id, $meta_value, $meta_key = false ) {
589         global $wpdb;
590
591         // Make sure everything is valid.
592         if ( ! $meta_type || ! is_numeric( $meta_id ) ) {
593                 return false;
594         }
595
596         $meta_id = absint( $meta_id );
597         if ( ! $meta_id ) {
598                 return false;
599         }
600
601         $table = _get_meta_table( $meta_type );
602         if ( ! $table ) {
603                 return false;
604         }
605
606         $column = sanitize_key($meta_type . '_id');
607         $id_column = 'user' == $meta_type ? 'umeta_id' : 'meta_id';
608
609         // Fetch the meta and go on if it's found.
610         if ( $meta = get_metadata_by_mid( $meta_type, $meta_id ) ) {
611                 $original_key = $meta->meta_key;
612                 $object_id = $meta->{$column};
613
614                 // If a new meta_key (last parameter) was specified, change the meta key,
615                 // otherwise use the original key in the update statement.
616                 if ( false === $meta_key ) {
617                         $meta_key = $original_key;
618                 } elseif ( ! is_string( $meta_key ) ) {
619                         return false;
620                 }
621
622                 // Sanitize the meta
623                 $_meta_value = $meta_value;
624                 $meta_value = sanitize_meta( $meta_key, $meta_value, $meta_type );
625                 $meta_value = maybe_serialize( $meta_value );
626
627                 // Format the data query arguments.
628                 $data = array(
629                         'meta_key' => $meta_key,
630                         'meta_value' => $meta_value
631                 );
632
633                 // Format the where query arguments.
634                 $where = array();
635                 $where[$id_column] = $meta_id;
636
637                 /** This action is documented in wp-includes/meta.php */
638                 do_action( "update_{$meta_type}_meta", $meta_id, $object_id, $meta_key, $_meta_value );
639
640                 if ( 'post' == $meta_type ) {
641                         /** This action is documented in wp-includes/meta.php */
642                         do_action( 'update_postmeta', $meta_id, $object_id, $meta_key, $meta_value );
643                 }
644
645                 // Run the update query, all fields in $data are %s, $where is a %d.
646                 $result = $wpdb->update( $table, $data, $where, '%s', '%d' );
647                 if ( ! $result )
648                         return false;
649
650                 // Clear the caches.
651                 wp_cache_delete($object_id, $meta_type . '_meta');
652
653                 /** This action is documented in wp-includes/meta.php */
654                 do_action( "updated_{$meta_type}_meta", $meta_id, $object_id, $meta_key, $_meta_value );
655
656                 if ( 'post' == $meta_type ) {
657                         /** This action is documented in wp-includes/meta.php */
658                         do_action( 'updated_postmeta', $meta_id, $object_id, $meta_key, $meta_value );
659                 }
660
661                 return true;
662         }
663
664         // And if the meta was not found.
665         return false;
666 }
667
668 /**
669  * Delete meta data by meta ID
670  *
671  * @since 3.3.0
672  *
673  * @uses get_metadata_by_mid() Calls get_metadata_by_mid() to fetch the meta key, value
674  *              and object_id of the given meta_id.
675  *
676  * @param string $meta_type Type of object metadata is for (e.g., comment, post, or user)
677  * @param int $meta_id ID for a specific meta row
678  * @return bool True on successful delete, false on failure.
679  */
680 function delete_metadata_by_mid( $meta_type, $meta_id ) {
681         global $wpdb;
682
683         // Make sure everything is valid.
684         if ( ! $meta_type || ! is_numeric( $meta_id ) ) {
685                 return false;
686         }
687
688         $meta_id = absint( $meta_id );
689         if ( ! $meta_id ) {
690                 return false;
691         }
692
693         $table = _get_meta_table( $meta_type );
694         if ( ! $table ) {
695                 return false;
696         }
697
698         // object and id columns
699         $column = sanitize_key($meta_type . '_id');
700         $id_column = 'user' == $meta_type ? 'umeta_id' : 'meta_id';
701
702         // Fetch the meta and go on if it's found.
703         if ( $meta = get_metadata_by_mid( $meta_type, $meta_id ) ) {
704                 $object_id = $meta->{$column};
705
706                 /** This action is documented in wp-includes/meta.php */
707                 do_action( "delete_{$meta_type}_meta", (array) $meta_id, $object_id, $meta->meta_key, $meta->meta_value );
708
709                 // Old-style action.
710                 if ( 'post' == $meta_type || 'comment' == $meta_type ) {
711                         /**
712                          * Fires immediately before deleting post or comment metadata of a specific type.
713                          *
714                          * The dynamic portion of the hook, $meta_type, refers to the meta
715                          * object type (post or comment).
716                          *
717                          * @since 3.4.0
718                          *
719                          * @param int $meta_id ID of the metadata entry to delete.
720                          */
721                         do_action( "delete_{$meta_type}meta", $meta_id );
722                 }
723
724                 // Run the query, will return true if deleted, false otherwise
725                 $result = (bool) $wpdb->delete( $table, array( $id_column => $meta_id ) );
726
727                 // Clear the caches.
728                 wp_cache_delete($object_id, $meta_type . '_meta');
729
730                 /** This action is documented in wp-includes/meta.php */
731                 do_action( "deleted_{$meta_type}_meta", (array) $meta_id, $object_id, $meta->meta_key, $meta->meta_value );
732
733                 // Old-style action.
734                 if ( 'post' == $meta_type || 'comment' == $meta_type ) {
735                         /**
736                          * Fires immediately after deleting post or comment metadata of a specific type.
737                          *
738                          * The dynamic portion of the hook, $meta_type, refers to the meta
739                          * object type (post or comment).
740                          *
741                          * @since 3.4.0
742                          *
743                          * @param int $meta_ids Deleted metadata entry ID.
744                          */
745                         do_action( "deleted_{$meta_type}meta", $meta_id );
746                 }
747
748                 return $result;
749
750         }
751
752         // Meta id was not found.
753         return false;
754 }
755
756 /**
757  * Update the metadata cache for the specified objects.
758  *
759  * @since 2.9.0
760  * @uses $wpdb WordPress database object for queries.
761  *
762  * @param string $meta_type Type of object metadata is for (e.g., comment, post, or user)
763  * @param int|array $object_ids array or comma delimited list of object IDs to update cache for
764  * @return mixed Metadata cache for the specified objects, or false on failure.
765  */
766 function update_meta_cache($meta_type, $object_ids) {
767         global $wpdb;
768
769         if ( ! $meta_type || ! $object_ids ) {
770                 return false;
771         }
772
773         $table = _get_meta_table( $meta_type );
774         if ( ! $table ) {
775                 return false;
776         }
777
778         $column = sanitize_key($meta_type . '_id');
779
780         if ( !is_array($object_ids) ) {
781                 $object_ids = preg_replace('|[^0-9,]|', '', $object_ids);
782                 $object_ids = explode(',', $object_ids);
783         }
784
785         $object_ids = array_map('intval', $object_ids);
786
787         $cache_key = $meta_type . '_meta';
788         $ids = array();
789         $cache = array();
790         foreach ( $object_ids as $id ) {
791                 $cached_object = wp_cache_get( $id, $cache_key );
792                 if ( false === $cached_object )
793                         $ids[] = $id;
794                 else
795                         $cache[$id] = $cached_object;
796         }
797
798         if ( empty( $ids ) )
799                 return $cache;
800
801         // Get meta info
802         $id_list = join( ',', $ids );
803         $id_column = 'user' == $meta_type ? 'umeta_id' : 'meta_id';
804         $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 );
805
806         if ( !empty($meta_list) ) {
807                 foreach ( $meta_list as $metarow) {
808                         $mpid = intval($metarow[$column]);
809                         $mkey = $metarow['meta_key'];
810                         $mval = $metarow['meta_value'];
811
812                         // Force subkeys to be array type:
813                         if ( !isset($cache[$mpid]) || !is_array($cache[$mpid]) )
814                                 $cache[$mpid] = array();
815                         if ( !isset($cache[$mpid][$mkey]) || !is_array($cache[$mpid][$mkey]) )
816                                 $cache[$mpid][$mkey] = array();
817
818                         // Add a value to the current pid/key:
819                         $cache[$mpid][$mkey][] = $mval;
820                 }
821         }
822
823         foreach ( $ids as $id ) {
824                 if ( ! isset($cache[$id]) )
825                         $cache[$id] = array();
826                 wp_cache_add( $id, $cache[$id], $cache_key );
827         }
828
829         return $cache;
830 }
831
832 /**
833  * Given a meta query, generates SQL clauses to be appended to a main query
834  *
835  * @since 3.2.0
836  *
837  * @see WP_Meta_Query
838  *
839  * @param array $meta_query A meta query
840  * @param string $type Type of meta
841  * @param string $primary_table
842  * @param string $primary_id_column
843  * @param object $context (optional) The main query object
844  * @return array( 'join' => $join_sql, 'where' => $where_sql )
845  */
846 function get_meta_sql( $meta_query, $type, $primary_table, $primary_id_column, $context = null ) {
847         $meta_query_obj = new WP_Meta_Query( $meta_query );
848         return $meta_query_obj->get_sql( $type, $primary_table, $primary_id_column, $context );
849 }
850
851 /**
852  * Container class for a multiple metadata query
853  *
854  * @since 3.2.0
855  */
856 class WP_Meta_Query {
857         /**
858         * List of metadata queries. A single query is an associative array:
859         * - 'key' string The meta key
860         * - 'value' string|array The meta value
861         * - 'compare' (optional) string How to compare the key to the value.
862         *              Possible values: '=', '!=', '>', '>=', '<', '<=', 'LIKE', 'NOT LIKE', 'IN', 'NOT IN',
863         *              'BETWEEN', 'NOT BETWEEN', 'REGEXP', 'NOT REGEXP', 'RLIKE'.
864         *              Default: '='
865         * - 'type' string (optional) The type of the value.
866         *              Possible values: 'NUMERIC', 'BINARY', 'CHAR', 'DATE', 'DATETIME', 'DECIMAL', 'SIGNED', 'TIME', 'UNSIGNED'.
867         *              Default: 'CHAR'
868         *
869         * @since 3.2.0
870         * @access public
871         * @var array
872         */
873         public $queries = array();
874
875         /**
876          * The relation between the queries. Can be one of 'AND' or 'OR'.
877          *
878          * @since 3.2.0
879          * @access public
880          * @var string
881          */
882         public $relation;
883
884         /**
885          * Constructor
886          *
887          * @param array $meta_query (optional) A meta query
888          */
889         public function __construct( $meta_query = false ) {
890                 if ( !$meta_query )
891                         return;
892
893                 if ( isset( $meta_query['relation'] ) && strtoupper( $meta_query['relation'] ) == 'OR' ) {
894                         $this->relation = 'OR';
895                 } else {
896                         $this->relation = 'AND';
897                 }
898
899                 $this->queries = array();
900
901                 foreach ( $meta_query as $key => $query ) {
902                         if ( ! is_array( $query ) )
903                                 continue;
904
905                         $this->queries[] = $query;
906                 }
907         }
908
909         /**
910          * Constructs a meta query based on 'meta_*' query vars
911          *
912          * @since 3.2.0
913          * @access public
914          *
915          * @param array $qv The query variables
916          */
917         public function parse_query_vars( $qv ) {
918                 $meta_query = array();
919
920                 // Simple query needs to be first for orderby=meta_value to work correctly
921                 foreach ( array( 'key', 'compare', 'type' ) as $key ) {
922                         if ( !empty( $qv[ "meta_$key" ] ) )
923                                 $meta_query[0][ $key ] = $qv[ "meta_$key" ];
924                 }
925
926                 // WP_Query sets 'meta_value' = '' by default
927                 if ( isset( $qv[ 'meta_value' ] ) && '' !== $qv[ 'meta_value' ] && ( ! is_array( $qv[ 'meta_value' ] ) || $qv[ 'meta_value' ] ) )
928                         $meta_query[0]['value'] = $qv[ 'meta_value' ];
929
930                 if ( !empty( $qv['meta_query'] ) && is_array( $qv['meta_query'] ) ) {
931                         $meta_query = array_merge( $meta_query, $qv['meta_query'] );
932                 }
933
934                 $this->__construct( $meta_query );
935         }
936
937         /**
938          * Given a meta type, return the appropriate alias if applicable
939          *
940          * @since 3.7.0
941          *
942          * @param string $type MySQL type to cast meta_value
943          * @return string MySQL type
944          */
945         public function get_cast_for_type( $type = '' ) {
946                 if ( empty( $type ) )
947                         return 'CHAR';
948
949                 $meta_type = strtoupper( $type );
950
951                 if ( ! preg_match( '/^(?:BINARY|CHAR|DATE|DATETIME|SIGNED|UNSIGNED|TIME|NUMERIC(?:\(\d+(?:,\s?\d+)?\))?|DECIMAL(?:\(\d+(?:,\s?\d+)?\))?)$/', $meta_type ) )
952                         return 'CHAR';
953
954                 if ( 'NUMERIC' == $meta_type )
955                         $meta_type = 'SIGNED';
956
957                 return $meta_type;
958         }
959
960         /**
961          * Generates SQL clauses to be appended to a main query.
962          *
963          * @since 3.2.0
964          * @access public
965          *
966          * @param string $type Type of meta
967          * @param string $primary_table
968          * @param string $primary_id_column
969          * @param object $context (optional) The main query object
970          * @return array( 'join' => $join_sql, 'where' => $where_sql )
971          */
972         public function get_sql( $type, $primary_table, $primary_id_column, $context = null ) {
973                 global $wpdb;
974
975                 if ( ! $meta_table = _get_meta_table( $type ) )
976                         return false;
977
978                 $meta_id_column = sanitize_key( $type . '_id' );
979
980                 $join = array();
981                 $where = array();
982
983                 $key_only_queries = array();
984                 $queries = array();
985
986                 // Split out the queries with empty arrays as value
987                 foreach ( $this->queries as $k => $q ) {
988                         if ( isset( $q['value'] ) && is_array( $q['value'] ) && empty( $q['value'] ) ) {
989                                 $key_only_queries[$k] = $q;
990                                 unset( $this->queries[$k] );
991                         }
992                 }
993
994                 // Split out the meta_key only queries (we can only do this for OR)
995                 if ( 'OR' == $this->relation ) {
996                         foreach ( $this->queries as $k => $q ) {
997                                 if ( ( empty( $q['compare'] ) || 'NOT EXISTS' != $q['compare'] ) && ! array_key_exists( 'value', $q ) && ! empty( $q['key'] ) )
998                                         $key_only_queries[$k] = $q;
999                                 else
1000                                         $queries[$k] = $q;
1001                         }
1002                 } else {
1003                         $queries = $this->queries;
1004                 }
1005
1006                 // Specify all the meta_key only queries in one go
1007                 if ( $key_only_queries ) {
1008                         $join[]  = "INNER JOIN $meta_table ON $primary_table.$primary_id_column = $meta_table.$meta_id_column";
1009
1010                         foreach ( $key_only_queries as $key => $q )
1011                                 $where["key-only-$key"] = $wpdb->prepare( "$meta_table.meta_key = %s", trim( $q['key'] ) );
1012                 }
1013
1014                 foreach ( $queries as $k => $q ) {
1015                         $meta_key = isset( $q['key'] ) ? trim( $q['key'] ) : '';
1016                         $meta_type = $this->get_cast_for_type( isset( $q['type'] ) ? $q['type'] : '' );
1017
1018                         if ( array_key_exists( 'value', $q ) && is_null( $q['value'] ) )
1019                                 $q['value'] = '';
1020
1021                         $meta_value = isset( $q['value'] ) ? $q['value'] : null;
1022
1023                         if ( isset( $q['compare'] ) )
1024                                 $meta_compare = strtoupper( $q['compare'] );
1025                         else
1026                                 $meta_compare = is_array( $meta_value ) ? 'IN' : '=';
1027
1028                         if ( ! in_array( $meta_compare, array(
1029                                 '=', '!=', '>', '>=', '<', '<=',
1030                                 'LIKE', 'NOT LIKE',
1031                                 'IN', 'NOT IN',
1032                                 'BETWEEN', 'NOT BETWEEN',
1033                                 'NOT EXISTS',
1034                                 'REGEXP', 'NOT REGEXP', 'RLIKE'
1035                         ) ) )
1036                                 $meta_compare = '=';
1037
1038                         $i = count( $join );
1039                         $alias = $i ? 'mt' . $i : $meta_table;
1040
1041                         if ( 'NOT EXISTS' == $meta_compare ) {
1042                                 $join[$i]  = "LEFT JOIN $meta_table";
1043                                 $join[$i] .= $i ? " AS $alias" : '';
1044                                 $join[$i] .= " ON ($primary_table.$primary_id_column = $alias.$meta_id_column AND $alias.meta_key = '$meta_key')";
1045
1046                                 $where[$k] = ' ' . $alias . '.' . $meta_id_column . ' IS NULL';
1047
1048                                 continue;
1049                         }
1050
1051                         $join[$i]  = "INNER JOIN $meta_table";
1052                         $join[$i] .= $i ? " AS $alias" : '';
1053                         $join[$i] .= " ON ($primary_table.$primary_id_column = $alias.$meta_id_column)";
1054
1055                         $where[$k] = '';
1056                         if ( !empty( $meta_key ) )
1057                                 $where[$k] = $wpdb->prepare( "$alias.meta_key = %s", $meta_key );
1058
1059                         if ( is_null( $meta_value ) ) {
1060                                 if ( empty( $where[$k] ) )
1061                                         unset( $join[$i] );
1062                                 continue;
1063                         }
1064
1065                         if ( in_array( $meta_compare, array( 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN' ) ) ) {
1066                                 if ( ! is_array( $meta_value ) )
1067                                         $meta_value = preg_split( '/[,\s]+/', $meta_value );
1068
1069                                 if ( empty( $meta_value ) ) {
1070                                         unset( $join[$i] );
1071                                         continue;
1072                                 }
1073                         } else {
1074                                 $meta_value = trim( $meta_value );
1075                         }
1076
1077                         if ( 'IN' == substr( $meta_compare, -2) ) {
1078                                 $meta_compare_string = '(' . substr( str_repeat( ',%s', count( $meta_value ) ), 1 ) . ')';
1079                         } elseif ( 'BETWEEN' == substr( $meta_compare, -7) ) {
1080                                 $meta_value = array_slice( $meta_value, 0, 2 );
1081                                 $meta_compare_string = '%s AND %s';
1082                         } elseif ( 'LIKE' == $meta_compare || 'NOT LIKE' == $meta_compare ) {
1083                                 $meta_value = '%' . $wpdb->esc_like( $meta_value ) . '%';
1084                                 $meta_compare_string = '%s';
1085                         } else {
1086                                 $meta_compare_string = '%s';
1087                         }
1088
1089                         if ( ! empty( $where[$k] ) )
1090                                 $where[$k] .= ' AND ';
1091
1092                         $where[$k] = ' (' . $where[$k] . $wpdb->prepare( "CAST($alias.meta_value AS {$meta_type}) {$meta_compare} {$meta_compare_string})", $meta_value );
1093                 }
1094
1095                 $where = array_filter( $where );
1096
1097                 if ( empty( $where ) )
1098                         $where = '';
1099                 else
1100                         $where = ' AND (' . implode( "\n{$this->relation} ", $where ) . ' )';
1101
1102                 $join = implode( "\n", $join );
1103                 if ( ! empty( $join ) )
1104                         $join = ' ' . $join;
1105
1106                 /**
1107                  * Filter the meta query's generated SQL.
1108                  *
1109                  * @since 3.1.0
1110                  *
1111                  * @param array $args {
1112                  *     An array of arguments.
1113                  *
1114                  *     @type array  $clauses           Array containing the query's JOIN and WHERE clauses.
1115                  *     @type array  $queries           Array of meta queries.
1116                  *     @type string $type              Type of meta.
1117                  *     @type string $primary_table     Primary table.
1118                  *     @type string $primary_id_column Primary column ID.
1119                  *     @type object $context           The main query object.
1120                  * }
1121                  */
1122                 return apply_filters_ref_array( 'get_meta_sql', array( compact( 'join', 'where' ), $this->queries, $type, $primary_table, $primary_id_column, $context ) );
1123         }
1124 }
1125
1126 /**
1127  * Retrieve the name of the metadata table for the specified object type.
1128  *
1129  * @since 2.9.0
1130  * @uses $wpdb WordPress database object for queries.
1131  *
1132  * @param string $type Type of object to get metadata table for (e.g., comment, post, or user)
1133  * @return mixed Metadata table name, or false if no metadata table exists
1134  */
1135 function _get_meta_table($type) {
1136         global $wpdb;
1137
1138         $table_name = $type . 'meta';
1139
1140         if ( empty($wpdb->$table_name) )
1141                 return false;
1142
1143         return $wpdb->$table_name;
1144 }
1145
1146 /**
1147  * Determine whether a meta key is protected.
1148  *
1149  * @since 3.1.3
1150  *
1151  * @param string $meta_key Meta key
1152  * @return bool True if the key is protected, false otherwise.
1153  */
1154 function is_protected_meta( $meta_key, $meta_type = null ) {
1155         $protected = ( '_' == $meta_key[0] );
1156
1157         /**
1158          * Filter whether a meta key is protected.
1159          *
1160          * @since 3.2.0
1161          *
1162          * @param bool   $protected Whether the key is protected. Default false.
1163          * @param string $meta_key  Meta key.
1164          * @param string $meta_type Meta type.
1165          */
1166         return apply_filters( 'is_protected_meta', $protected, $meta_key, $meta_type );
1167 }
1168
1169 /**
1170  * Sanitize meta value.
1171  *
1172  * @since 3.1.3
1173  *
1174  * @param string $meta_key Meta key
1175  * @param mixed $meta_value Meta value to sanitize
1176  * @param string $meta_type Type of meta
1177  * @return mixed Sanitized $meta_value
1178  */
1179 function sanitize_meta( $meta_key, $meta_value, $meta_type ) {
1180
1181         /**
1182          * Filter the sanitization of a specific meta key of a specific meta type.
1183          *
1184          * The dynamic portions of the hook name, $meta_type and $meta_key, refer to the
1185          * metadata object type (comment, post, or user) and the meta key value,
1186          * respectively.
1187          *
1188          * @since 3.3.0
1189          *
1190          * @param mixed  $meta_value Meta value to sanitize.
1191          * @param string $meta_key   Meta key.
1192          * @param string $meta_type  Meta type.
1193          */
1194         return apply_filters( "sanitize_{$meta_type}_meta_{$meta_key}", $meta_value, $meta_key, $meta_type );
1195 }
1196
1197 /**
1198  * Register meta key
1199  *
1200  * @since 3.3.0
1201  *
1202  * @param string $meta_type Type of meta
1203  * @param string $meta_key Meta key
1204  * @param string|array $sanitize_callback A function or method to call when sanitizing the value of $meta_key.
1205  * @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.
1206  * @param array $args Arguments
1207  */
1208 function register_meta( $meta_type, $meta_key, $sanitize_callback, $auth_callback = null ) {
1209         if ( is_callable( $sanitize_callback ) )
1210                 add_filter( "sanitize_{$meta_type}_meta_{$meta_key}", $sanitize_callback, 10, 3 );
1211
1212         if ( empty( $auth_callback ) ) {
1213                 if ( is_protected_meta( $meta_key, $meta_type ) )
1214                         $auth_callback = '__return_false';
1215                 else
1216                         $auth_callback = '__return_true';
1217         }
1218
1219         if ( is_callable( $auth_callback ) )
1220                 add_filter( "auth_{$meta_type}_meta_{$meta_key}", $auth_callback, 10, 6 );
1221 }