13 |
13 |
14 /** |
14 /** |
15 * Add metadata for the specified object. |
15 * Add metadata for the specified object. |
16 * |
16 * |
17 * @since 2.9.0 |
17 * @since 2.9.0 |
18 * @uses $wpdb WordPress database object for queries. |
18 * |
19 * @uses do_action() Calls 'added_{$meta_type}_meta' with meta_id of added metadata entry, |
19 * @global wpdb $wpdb WordPress database abstraction object. |
20 * object ID, meta key, and meta value |
|
21 * |
20 * |
22 * @param string $meta_type Type of object metadata is for (e.g., comment, post, or user) |
21 * @param string $meta_type Type of object metadata is for (e.g., comment, post, or user) |
23 * @param int $object_id ID of the object metadata is for |
22 * @param int $object_id ID of the object metadata is for |
24 * @param string $meta_key Metadata key |
23 * @param string $meta_key Metadata key |
25 * @param mixed $meta_value Metadata value. Must be serializable if non-scalar. |
24 * @param mixed $meta_value Metadata value. Must be serializable if non-scalar. |
26 * @param bool $unique Optional, default is false. Whether the specified metadata key should be |
25 * @param bool $unique Optional, default is false. Whether the specified metadata key should be |
27 * unique for the object. If true, and the object already has a value for the specified |
26 * unique for the object. If true, and the object already has a value for the specified |
28 * metadata key, no change will be made |
27 * metadata key, no change will be made |
29 * @return int|bool The meta ID on successful update, false on failure. |
28 * @return int|bool The meta ID on success, false on failure. |
30 */ |
29 */ |
31 function add_metadata($meta_type, $object_id, $meta_key, $meta_value, $unique = false) { |
30 function add_metadata($meta_type, $object_id, $meta_key, $meta_value, $unique = false) { |
32 if ( !$meta_type || !$meta_key ) |
|
33 return false; |
|
34 |
|
35 if ( !$object_id = absint($object_id) ) |
|
36 return false; |
|
37 |
|
38 if ( ! $table = _get_meta_table($meta_type) ) |
|
39 return false; |
|
40 |
|
41 global $wpdb; |
31 global $wpdb; |
|
32 |
|
33 if ( ! $meta_type || ! $meta_key || ! is_numeric( $object_id ) ) { |
|
34 return false; |
|
35 } |
|
36 |
|
37 $object_id = absint( $object_id ); |
|
38 if ( ! $object_id ) { |
|
39 return false; |
|
40 } |
|
41 |
|
42 $table = _get_meta_table( $meta_type ); |
|
43 if ( ! $table ) { |
|
44 return false; |
|
45 } |
42 |
46 |
43 $column = sanitize_key($meta_type . '_id'); |
47 $column = sanitize_key($meta_type . '_id'); |
44 |
48 |
45 // expected_slashed ($meta_key) |
49 // expected_slashed ($meta_key) |
46 $meta_key = wp_unslash($meta_key); |
50 $meta_key = wp_unslash($meta_key); |
47 $meta_value = wp_unslash($meta_value); |
51 $meta_value = wp_unslash($meta_value); |
48 $meta_value = sanitize_meta( $meta_key, $meta_value, $meta_type ); |
52 $meta_value = sanitize_meta( $meta_key, $meta_value, $meta_type ); |
49 |
53 |
|
54 /** |
|
55 * Filter whether to add metadata of a specific type. |
|
56 * |
|
57 * The dynamic portion of the hook, `$meta_type`, refers to the meta |
|
58 * object type (comment, post, or user). Returning a non-null value |
|
59 * will effectively short-circuit the function. |
|
60 * |
|
61 * @since 3.1.0 |
|
62 * |
|
63 * @param null|bool $check Whether to allow adding metadata for the given type. |
|
64 * @param int $object_id Object ID. |
|
65 * @param string $meta_key Meta key. |
|
66 * @param mixed $meta_value Meta value. Must be serializable if non-scalar. |
|
67 * @param bool $unique Whether the specified meta key should be unique |
|
68 * for the object. Optional. Default false. |
|
69 */ |
50 $check = apply_filters( "add_{$meta_type}_metadata", null, $object_id, $meta_key, $meta_value, $unique ); |
70 $check = apply_filters( "add_{$meta_type}_metadata", null, $object_id, $meta_key, $meta_value, $unique ); |
51 if ( null !== $check ) |
71 if ( null !== $check ) |
52 return $check; |
72 return $check; |
53 |
73 |
54 if ( $unique && $wpdb->get_var( $wpdb->prepare( |
74 if ( $unique && $wpdb->get_var( $wpdb->prepare( |
72 |
104 |
73 $mid = (int) $wpdb->insert_id; |
105 $mid = (int) $wpdb->insert_id; |
74 |
106 |
75 wp_cache_delete($object_id, $meta_type . '_meta'); |
107 wp_cache_delete($object_id, $meta_type . '_meta'); |
76 |
108 |
|
109 /** |
|
110 * Fires immediately after meta of a specific type is added. |
|
111 * |
|
112 * The dynamic portion of the hook, `$meta_type`, refers to the meta |
|
113 * object type (comment, post, or user). |
|
114 * |
|
115 * @since 2.9.0 |
|
116 * |
|
117 * @param int $mid The meta ID after successful update. |
|
118 * @param int $object_id Object ID. |
|
119 * @param string $meta_key Meta key. |
|
120 * @param mixed $meta_value Meta value. |
|
121 */ |
77 do_action( "added_{$meta_type}_meta", $mid, $object_id, $meta_key, $_meta_value ); |
122 do_action( "added_{$meta_type}_meta", $mid, $object_id, $meta_key, $_meta_value ); |
78 |
123 |
79 return $mid; |
124 return $mid; |
80 } |
125 } |
81 |
126 |
82 /** |
127 /** |
83 * Update metadata for the specified object. If no value already exists for the specified object |
128 * Update metadata for the specified object. If no value already exists for the specified object |
84 * ID and metadata key, the metadata will be added. |
129 * ID and metadata key, the metadata will be added. |
85 * |
130 * |
86 * @since 2.9.0 |
131 * @since 2.9.0 |
87 * @uses $wpdb WordPress database object for queries. |
132 * |
88 * @uses do_action() Calls 'update_{$meta_type}_meta' before updating metadata with meta_id of |
133 * @global wpdb $wpdb WordPress database abstraction object. |
89 * metadata entry to update, object ID, meta key, and meta value |
|
90 * @uses do_action() Calls 'updated_{$meta_type}_meta' after updating metadata with meta_id of |
|
91 * updated metadata entry, object ID, meta key, and meta value |
|
92 * |
134 * |
93 * @param string $meta_type Type of object metadata is for (e.g., comment, post, or user) |
135 * @param string $meta_type Type of object metadata is for (e.g., comment, post, or user) |
94 * @param int $object_id ID of the object metadata is for |
136 * @param int $object_id ID of the object metadata is for |
95 * @param string $meta_key Metadata key |
137 * @param string $meta_key Metadata key |
96 * @param mixed $meta_value Metadata value. Must be serializable if non-scalar. |
138 * @param mixed $meta_value Metadata value. Must be serializable if non-scalar. |
97 * @param mixed $prev_value Optional. If specified, only update existing metadata entries with |
139 * @param mixed $prev_value Optional. If specified, only update existing metadata entries with |
98 * the specified value. Otherwise, update all entries. |
140 * the specified value. Otherwise, update all entries. |
99 * @return bool True on successful update, false on failure. |
141 * @return int|bool Meta ID if the key didn't exist, true on successful update, false on failure. |
100 */ |
142 */ |
101 function update_metadata($meta_type, $object_id, $meta_key, $meta_value, $prev_value = '') { |
143 function update_metadata($meta_type, $object_id, $meta_key, $meta_value, $prev_value = '') { |
102 if ( !$meta_type || !$meta_key ) |
|
103 return false; |
|
104 |
|
105 if ( !$object_id = absint($object_id) ) |
|
106 return false; |
|
107 |
|
108 if ( ! $table = _get_meta_table($meta_type) ) |
|
109 return false; |
|
110 |
|
111 global $wpdb; |
144 global $wpdb; |
|
145 |
|
146 if ( ! $meta_type || ! $meta_key || ! is_numeric( $object_id ) ) { |
|
147 return false; |
|
148 } |
|
149 |
|
150 $object_id = absint( $object_id ); |
|
151 if ( ! $object_id ) { |
|
152 return false; |
|
153 } |
|
154 |
|
155 $table = _get_meta_table( $meta_type ); |
|
156 if ( ! $table ) { |
|
157 return false; |
|
158 } |
112 |
159 |
113 $column = sanitize_key($meta_type . '_id'); |
160 $column = sanitize_key($meta_type . '_id'); |
114 $id_column = 'user' == $meta_type ? 'umeta_id' : 'meta_id'; |
161 $id_column = 'user' == $meta_type ? 'umeta_id' : 'meta_id'; |
115 |
162 |
116 // expected_slashed ($meta_key) |
163 // expected_slashed ($meta_key) |
117 $meta_key = wp_unslash($meta_key); |
164 $meta_key = wp_unslash($meta_key); |
118 $passed_value = $meta_value; |
165 $passed_value = $meta_value; |
119 $meta_value = wp_unslash($meta_value); |
166 $meta_value = wp_unslash($meta_value); |
120 $meta_value = sanitize_meta( $meta_key, $meta_value, $meta_type ); |
167 $meta_value = sanitize_meta( $meta_key, $meta_value, $meta_type ); |
121 |
168 |
|
169 /** |
|
170 * Filter whether to update metadata of a specific type. |
|
171 * |
|
172 * The dynamic portion of the hook, `$meta_type`, refers to the meta |
|
173 * object type (comment, post, or user). Returning a non-null value |
|
174 * will effectively short-circuit the function. |
|
175 * |
|
176 * @since 3.1.0 |
|
177 * |
|
178 * @param null|bool $check Whether to allow updating metadata for the given type. |
|
179 * @param int $object_id Object ID. |
|
180 * @param string $meta_key Meta key. |
|
181 * @param mixed $meta_value Meta value. Must be serializable if non-scalar. |
|
182 * @param mixed $prev_value Optional. If specified, only update existing |
|
183 * metadata entries with the specified value. |
|
184 * Otherwise, update all entries. |
|
185 */ |
122 $check = apply_filters( "update_{$meta_type}_metadata", null, $object_id, $meta_key, $meta_value, $prev_value ); |
186 $check = apply_filters( "update_{$meta_type}_metadata", null, $object_id, $meta_key, $meta_value, $prev_value ); |
123 if ( null !== $check ) |
187 if ( null !== $check ) |
124 return (bool) $check; |
188 return (bool) $check; |
125 |
189 |
126 // Compare existing value to new value if no prev value given and the key exists only once. |
190 // Compare existing value to new value if no prev value given and the key exists only once. |
144 if ( !empty( $prev_value ) ) { |
210 if ( !empty( $prev_value ) ) { |
145 $prev_value = maybe_serialize($prev_value); |
211 $prev_value = maybe_serialize($prev_value); |
146 $where['meta_value'] = $prev_value; |
212 $where['meta_value'] = $prev_value; |
147 } |
213 } |
148 |
214 |
149 do_action( "update_{$meta_type}_meta", $meta_id, $object_id, $meta_key, $_meta_value ); |
215 foreach ( $meta_ids as $meta_id ) { |
150 |
216 /** |
151 if ( 'post' == $meta_type ) |
217 * Fires immediately before updating metadata of a specific type. |
152 do_action( 'update_postmeta', $meta_id, $object_id, $meta_key, $meta_value ); |
218 * |
|
219 * The dynamic portion of the hook, `$meta_type`, refers to the meta |
|
220 * object type (comment, post, or user). |
|
221 * |
|
222 * @since 2.9.0 |
|
223 * |
|
224 * @param int $meta_id ID of the metadata entry to update. |
|
225 * @param int $object_id Object ID. |
|
226 * @param string $meta_key Meta key. |
|
227 * @param mixed $meta_value Meta value. |
|
228 */ |
|
229 do_action( "update_{$meta_type}_meta", $meta_id, $object_id, $meta_key, $_meta_value ); |
|
230 } |
|
231 |
|
232 if ( 'post' == $meta_type ) { |
|
233 foreach ( $meta_ids as $meta_id ) { |
|
234 /** |
|
235 * Fires immediately before updating a post's metadata. |
|
236 * |
|
237 * @since 2.9.0 |
|
238 * |
|
239 * @param int $meta_id ID of metadata entry to update. |
|
240 * @param int $object_id Object ID. |
|
241 * @param string $meta_key Meta key. |
|
242 * @param mixed $meta_value Meta value. |
|
243 */ |
|
244 do_action( 'update_postmeta', $meta_id, $object_id, $meta_key, $meta_value ); |
|
245 } |
|
246 } |
153 |
247 |
154 $result = $wpdb->update( $table, $data, $where ); |
248 $result = $wpdb->update( $table, $data, $where ); |
155 if ( ! $result ) |
249 if ( ! $result ) |
156 return false; |
250 return false; |
157 |
251 |
158 wp_cache_delete($object_id, $meta_type . '_meta'); |
252 wp_cache_delete($object_id, $meta_type . '_meta'); |
159 |
253 |
160 do_action( "updated_{$meta_type}_meta", $meta_id, $object_id, $meta_key, $_meta_value ); |
254 foreach ( $meta_ids as $meta_id ) { |
161 |
255 /** |
162 if ( 'post' == $meta_type ) |
256 * Fires immediately after updating metadata of a specific type. |
163 do_action( 'updated_postmeta', $meta_id, $object_id, $meta_key, $meta_value ); |
257 * |
|
258 * The dynamic portion of the hook, `$meta_type`, refers to the meta |
|
259 * object type (comment, post, or user). |
|
260 * |
|
261 * @since 2.9.0 |
|
262 * |
|
263 * @param int $meta_id ID of updated metadata entry. |
|
264 * @param int $object_id Object ID. |
|
265 * @param string $meta_key Meta key. |
|
266 * @param mixed $meta_value Meta value. |
|
267 */ |
|
268 do_action( "updated_{$meta_type}_meta", $meta_id, $object_id, $meta_key, $_meta_value ); |
|
269 } |
|
270 |
|
271 if ( 'post' == $meta_type ) { |
|
272 foreach ( $meta_ids as $meta_id ) { |
|
273 /** |
|
274 * Fires immediately after updating a post's metadata. |
|
275 * |
|
276 * @since 2.9.0 |
|
277 * |
|
278 * @param int $meta_id ID of updated metadata entry. |
|
279 * @param int $object_id Object ID. |
|
280 * @param string $meta_key Meta key. |
|
281 * @param mixed $meta_value Meta value. |
|
282 */ |
|
283 do_action( 'updated_postmeta', $meta_id, $object_id, $meta_key, $meta_value ); |
|
284 } |
|
285 } |
164 |
286 |
165 return true; |
287 return true; |
166 } |
288 } |
167 |
289 |
168 /** |
290 /** |
169 * Delete metadata for the specified object. |
291 * Delete metadata for the specified object. |
170 * |
292 * |
171 * @since 2.9.0 |
293 * @since 2.9.0 |
172 * @uses $wpdb WordPress database object for queries. |
294 * |
173 * @uses do_action() Calls 'deleted_{$meta_type}_meta' after deleting with meta_id of |
295 * @global wpdb $wpdb WordPress database abstraction object. |
174 * deleted metadata entries, object ID, meta key, and meta value |
|
175 * |
296 * |
176 * @param string $meta_type Type of object metadata is for (e.g., comment, post, or user) |
297 * @param string $meta_type Type of object metadata is for (e.g., comment, post, or user) |
177 * @param int $object_id ID of the object metadata is for |
298 * @param int $object_id ID of the object metadata is for |
178 * @param string $meta_key Metadata key |
299 * @param string $meta_key Metadata key |
179 * @param mixed $meta_value Optional. Metadata value. Must be serializable if non-scalar. If specified, only delete metadata entries |
300 * @param mixed $meta_value Optional. Metadata value. Must be serializable if non-scalar. If specified, only delete metadata entries |
458 |
679 |
459 /** |
680 /** |
460 * Delete meta data by meta ID |
681 * Delete meta data by meta ID |
461 * |
682 * |
462 * @since 3.3.0 |
683 * @since 3.3.0 |
463 * |
|
464 * @uses get_metadata_by_mid() Calls get_metadata_by_mid() to fetch the meta key, value |
|
465 * and object_id of the given meta_id. |
|
466 * |
684 * |
467 * @param string $meta_type Type of object metadata is for (e.g., comment, post, or user) |
685 * @param string $meta_type Type of object metadata is for (e.g., comment, post, or user) |
468 * @param int $meta_id ID for a specific meta row |
686 * @param int $meta_id ID for a specific meta row |
469 * @return bool True on successful delete, false on failure. |
687 * @return bool True on successful delete, false on failure. |
470 */ |
688 */ |
471 function delete_metadata_by_mid( $meta_type, $meta_id ) { |
689 function delete_metadata_by_mid( $meta_type, $meta_id ) { |
472 global $wpdb; |
690 global $wpdb; |
473 |
691 |
474 // Make sure everything is valid. |
692 // Make sure everything is valid. |
475 if ( ! $meta_type ) |
693 if ( ! $meta_type || ! is_numeric( $meta_id ) ) { |
476 return false; |
694 return false; |
477 |
695 } |
478 if ( ! $meta_id = absint( $meta_id ) ) |
696 |
479 return false; |
697 $meta_id = absint( $meta_id ); |
480 |
698 if ( ! $meta_id ) { |
481 if ( ! $table = _get_meta_table( $meta_type ) ) |
699 return false; |
482 return false; |
700 } |
|
701 |
|
702 $table = _get_meta_table( $meta_type ); |
|
703 if ( ! $table ) { |
|
704 return false; |
|
705 } |
483 |
706 |
484 // object and id columns |
707 // object and id columns |
485 $column = sanitize_key($meta_type . '_id'); |
708 $column = sanitize_key($meta_type . '_id'); |
486 $id_column = 'user' == $meta_type ? 'umeta_id' : 'meta_id'; |
709 $id_column = 'user' == $meta_type ? 'umeta_id' : 'meta_id'; |
487 |
710 |
488 // Fetch the meta and go on if it's found. |
711 // Fetch the meta and go on if it's found. |
489 if ( $meta = get_metadata_by_mid( $meta_type, $meta_id ) ) { |
712 if ( $meta = get_metadata_by_mid( $meta_type, $meta_id ) ) { |
490 $object_id = $meta->{$column}; |
713 $object_id = $meta->{$column}; |
491 |
714 |
|
715 /** This action is documented in wp-includes/meta.php */ |
492 do_action( "delete_{$meta_type}_meta", (array) $meta_id, $object_id, $meta->meta_key, $meta->meta_value ); |
716 do_action( "delete_{$meta_type}_meta", (array) $meta_id, $object_id, $meta->meta_key, $meta->meta_value ); |
493 |
717 |
494 // Old-style action. |
718 // Old-style action. |
495 if ( 'post' == $meta_type || 'comment' == $meta_type ) |
719 if ( 'post' == $meta_type || 'comment' == $meta_type ) { |
|
720 /** |
|
721 * Fires immediately before deleting post or comment metadata of a specific type. |
|
722 * |
|
723 * The dynamic portion of the hook, `$meta_type`, refers to the meta |
|
724 * object type (post or comment). |
|
725 * |
|
726 * @since 3.4.0 |
|
727 * |
|
728 * @param int $meta_id ID of the metadata entry to delete. |
|
729 */ |
496 do_action( "delete_{$meta_type}meta", $meta_id ); |
730 do_action( "delete_{$meta_type}meta", $meta_id ); |
|
731 } |
497 |
732 |
498 // Run the query, will return true if deleted, false otherwise |
733 // Run the query, will return true if deleted, false otherwise |
499 $result = (bool) $wpdb->delete( $table, array( $id_column => $meta_id ) ); |
734 $result = (bool) $wpdb->delete( $table, array( $id_column => $meta_id ) ); |
500 |
735 |
501 // Clear the caches. |
736 // Clear the caches. |
502 wp_cache_delete($object_id, $meta_type . '_meta'); |
737 wp_cache_delete($object_id, $meta_type . '_meta'); |
503 |
738 |
|
739 /** This action is documented in wp-includes/meta.php */ |
504 do_action( "deleted_{$meta_type}_meta", (array) $meta_id, $object_id, $meta->meta_key, $meta->meta_value ); |
740 do_action( "deleted_{$meta_type}_meta", (array) $meta_id, $object_id, $meta->meta_key, $meta->meta_value ); |
505 |
741 |
506 // Old-style action. |
742 // Old-style action. |
507 if ( 'post' == $meta_type || 'comment' == $meta_type ) |
743 if ( 'post' == $meta_type || 'comment' == $meta_type ) { |
|
744 /** |
|
745 * Fires immediately after deleting post or comment metadata of a specific type. |
|
746 * |
|
747 * The dynamic portion of the hook, `$meta_type`, refers to the meta |
|
748 * object type (post or comment). |
|
749 * |
|
750 * @since 3.4.0 |
|
751 * |
|
752 * @param int $meta_ids Deleted metadata entry ID. |
|
753 */ |
508 do_action( "deleted_{$meta_type}meta", $meta_id ); |
754 do_action( "deleted_{$meta_type}meta", $meta_id ); |
|
755 } |
509 |
756 |
510 return $result; |
757 return $result; |
511 |
758 |
512 } |
759 } |
513 |
760 |
639 * @var string |
887 * @var string |
640 */ |
888 */ |
641 public $relation; |
889 public $relation; |
642 |
890 |
643 /** |
891 /** |
644 * Constructor |
892 * Database table to query for the metadata. |
645 * |
893 * |
646 * @param array $meta_query (optional) A meta query |
894 * @since 4.1.0 |
647 */ |
895 * @access public |
648 function __construct( $meta_query = false ) { |
896 * @var string |
|
897 */ |
|
898 public $meta_table; |
|
899 |
|
900 /** |
|
901 * Column in meta_table that represents the ID of the object the metadata belongs to. |
|
902 * |
|
903 * @since 4.1.0 |
|
904 * @access public |
|
905 * @var string |
|
906 */ |
|
907 public $meta_id_column; |
|
908 |
|
909 /** |
|
910 * Database table that where the metadata's objects are stored (eg $wpdb->users). |
|
911 * |
|
912 * @since 4.1.0 |
|
913 * @access public |
|
914 * @var string |
|
915 */ |
|
916 public $primary_table; |
|
917 |
|
918 /** |
|
919 * Column in primary_table that represents the ID of the object. |
|
920 * |
|
921 * @since 4.1.0 |
|
922 * @access public |
|
923 * @var string |
|
924 */ |
|
925 public $primary_id_column; |
|
926 |
|
927 /** |
|
928 * A flat list of table aliases used in JOIN clauses. |
|
929 * |
|
930 * @since 4.1.0 |
|
931 * @access protected |
|
932 * @var array |
|
933 */ |
|
934 protected $table_aliases = array(); |
|
935 |
|
936 /** |
|
937 * A flat list of clauses, keyed by clause 'name'. |
|
938 * |
|
939 * @since 4.2.0 |
|
940 * @access protected |
|
941 * @var array |
|
942 */ |
|
943 protected $clauses = array(); |
|
944 |
|
945 /** |
|
946 * Constructor. |
|
947 * |
|
948 * @since 3.2.0 |
|
949 * @since 4.2.0 Introduced support for naming query clauses by associative array keys. |
|
950 * |
|
951 * @access public |
|
952 * |
|
953 * @param array $meta_query { |
|
954 * Array of meta query clauses. When first-order clauses use strings as their array keys, they may be |
|
955 * referenced in the 'orderby' parameter of the parent query. |
|
956 * |
|
957 * @type string $relation Optional. The MySQL keyword used to join |
|
958 * the clauses of the query. Accepts 'AND', or 'OR'. Default 'AND'. |
|
959 * @type array { |
|
960 * Optional. An array of first-order clause parameters, or another fully-formed meta query. |
|
961 * |
|
962 * @type string $key Meta key to filter by. |
|
963 * @type string $value Meta value to filter by. |
|
964 * @type string $compare MySQL operator used for comparing the $value. Accepts '=', |
|
965 * '!=', '>', '>=', '<', '<=', 'LIKE', 'NOT LIKE', 'IN', 'NOT IN', |
|
966 * 'BETWEEN', 'NOT BETWEEN', 'REGEXP', 'NOT REGEXP', or 'RLIKE'. |
|
967 * Default is 'IN' when `$value` is an array, '=' otherwise. |
|
968 * @type string $type MySQL data type that the meta_value column will be CAST to for |
|
969 * comparisons. Accepts 'NUMERIC', 'BINARY', 'CHAR', 'DATE', |
|
970 * 'DATETIME', 'DECIMAL', 'SIGNED', 'TIME', or 'UNSIGNED'. |
|
971 * Default is 'CHAR'. |
|
972 * } |
|
973 * } |
|
974 */ |
|
975 public function __construct( $meta_query = false ) { |
649 if ( !$meta_query ) |
976 if ( !$meta_query ) |
650 return; |
977 return; |
651 |
978 |
652 if ( isset( $meta_query['relation'] ) && strtoupper( $meta_query['relation'] ) == 'OR' ) { |
979 if ( isset( $meta_query['relation'] ) && strtoupper( $meta_query['relation'] ) == 'OR' ) { |
653 $this->relation = 'OR'; |
980 $this->relation = 'OR'; |
654 } else { |
981 } else { |
655 $this->relation = 'AND'; |
982 $this->relation = 'AND'; |
656 } |
983 } |
657 |
984 |
658 $this->queries = array(); |
985 $this->queries = $this->sanitize_query( $meta_query ); |
659 |
986 } |
660 foreach ( $meta_query as $key => $query ) { |
987 |
661 if ( ! is_array( $query ) ) |
988 /** |
|
989 * Ensure the 'meta_query' argument passed to the class constructor is well-formed. |
|
990 * |
|
991 * Eliminates empty items and ensures that a 'relation' is set. |
|
992 * |
|
993 * @since 4.1.0 |
|
994 * @access public |
|
995 * |
|
996 * @param array $queries Array of query clauses. |
|
997 * @return array Sanitized array of query clauses. |
|
998 */ |
|
999 public function sanitize_query( $queries ) { |
|
1000 $clean_queries = array(); |
|
1001 |
|
1002 if ( ! is_array( $queries ) ) { |
|
1003 return $clean_queries; |
|
1004 } |
|
1005 |
|
1006 foreach ( $queries as $key => $query ) { |
|
1007 if ( 'relation' === $key ) { |
|
1008 $relation = $query; |
|
1009 |
|
1010 } elseif ( ! is_array( $query ) ) { |
662 continue; |
1011 continue; |
663 |
1012 |
664 $this->queries[] = $query; |
1013 // First-order clause. |
665 } |
1014 } elseif ( $this->is_first_order_clause( $query ) ) { |
|
1015 if ( isset( $query['value'] ) && array() === $query['value'] ) { |
|
1016 unset( $query['value'] ); |
|
1017 } |
|
1018 |
|
1019 $clean_queries[ $key ] = $query; |
|
1020 |
|
1021 // Otherwise, it's a nested query, so we recurse. |
|
1022 } else { |
|
1023 $cleaned_query = $this->sanitize_query( $query ); |
|
1024 |
|
1025 if ( ! empty( $cleaned_query ) ) { |
|
1026 $clean_queries[ $key ] = $cleaned_query; |
|
1027 } |
|
1028 } |
|
1029 } |
|
1030 |
|
1031 if ( empty( $clean_queries ) ) { |
|
1032 return $clean_queries; |
|
1033 } |
|
1034 |
|
1035 // Sanitize the 'relation' key provided in the query. |
|
1036 if ( isset( $relation ) && 'OR' === strtoupper( $relation ) ) { |
|
1037 $clean_queries['relation'] = 'OR'; |
|
1038 |
|
1039 /* |
|
1040 * If there is only a single clause, call the relation 'OR'. |
|
1041 * This value will not actually be used to join clauses, but it |
|
1042 * simplifies the logic around combining key-only queries. |
|
1043 */ |
|
1044 } elseif ( 1 === count( $clean_queries ) ) { |
|
1045 $clean_queries['relation'] = 'OR'; |
|
1046 |
|
1047 // Default to AND. |
|
1048 } else { |
|
1049 $clean_queries['relation'] = 'AND'; |
|
1050 } |
|
1051 |
|
1052 return $clean_queries; |
|
1053 } |
|
1054 |
|
1055 /** |
|
1056 * Determine whether a query clause is first-order. |
|
1057 * |
|
1058 * A first-order meta query clause is one that has either a 'key' or |
|
1059 * a 'value' array key. |
|
1060 * |
|
1061 * @since 4.1.0 |
|
1062 * @access protected |
|
1063 * |
|
1064 * @param array $query Meta query arguments. |
|
1065 * @return bool Whether the query clause is a first-order clause. |
|
1066 */ |
|
1067 protected function is_first_order_clause( $query ) { |
|
1068 return isset( $query['key'] ) || isset( $query['value'] ); |
666 } |
1069 } |
667 |
1070 |
668 /** |
1071 /** |
669 * Constructs a meta query based on 'meta_*' query vars |
1072 * Constructs a meta query based on 'meta_*' query vars |
670 * |
1073 * |
671 * @since 3.2.0 |
1074 * @since 3.2.0 |
672 * @access public |
1075 * @access public |
673 * |
1076 * |
674 * @param array $qv The query variables |
1077 * @param array $qv The query variables |
675 */ |
1078 */ |
676 function parse_query_vars( $qv ) { |
1079 public function parse_query_vars( $qv ) { |
677 $meta_query = array(); |
1080 $meta_query = array(); |
678 |
1081 |
679 // Simple query needs to be first for orderby=meta_value to work correctly |
1082 /* |
|
1083 * For orderby=meta_value to work correctly, simple query needs to be |
|
1084 * first (so that its table join is against an unaliased meta table) and |
|
1085 * needs to be its own clause (so it doesn't interfere with the logic of |
|
1086 * the rest of the meta_query). |
|
1087 */ |
|
1088 $primary_meta_query = array(); |
680 foreach ( array( 'key', 'compare', 'type' ) as $key ) { |
1089 foreach ( array( 'key', 'compare', 'type' ) as $key ) { |
681 if ( !empty( $qv[ "meta_$key" ] ) ) |
1090 if ( ! empty( $qv[ "meta_$key" ] ) ) { |
682 $meta_query[0][ $key ] = $qv[ "meta_$key" ]; |
1091 $primary_meta_query[ $key ] = $qv[ "meta_$key" ]; |
683 } |
1092 } |
684 |
1093 } |
685 // WP_Query sets 'meta_value' = '' by default |
1094 |
686 if ( isset( $qv[ 'meta_value' ] ) && '' !== $qv[ 'meta_value' ] && ( ! is_array( $qv[ 'meta_value' ] ) || $qv[ 'meta_value' ] ) ) |
1095 // WP_Query sets 'meta_value' = '' by default. |
687 $meta_query[0]['value'] = $qv[ 'meta_value' ]; |
1096 if ( isset( $qv['meta_value'] ) && '' !== $qv['meta_value'] && ( ! is_array( $qv['meta_value'] ) || $qv['meta_value'] ) ) { |
688 |
1097 $primary_meta_query['value'] = $qv['meta_value']; |
689 if ( !empty( $qv['meta_query'] ) && is_array( $qv['meta_query'] ) ) { |
1098 } |
690 $meta_query = array_merge( $meta_query, $qv['meta_query'] ); |
1099 |
|
1100 $existing_meta_query = isset( $qv['meta_query'] ) && is_array( $qv['meta_query'] ) ? $qv['meta_query'] : array(); |
|
1101 |
|
1102 if ( ! empty( $primary_meta_query ) && ! empty( $existing_meta_query ) ) { |
|
1103 $meta_query = array( |
|
1104 'relation' => 'AND', |
|
1105 $primary_meta_query, |
|
1106 $existing_meta_query, |
|
1107 ); |
|
1108 } elseif ( ! empty( $primary_meta_query ) ) { |
|
1109 $meta_query = array( |
|
1110 $primary_meta_query, |
|
1111 ); |
|
1112 } elseif ( ! empty( $existing_meta_query ) ) { |
|
1113 $meta_query = $existing_meta_query; |
691 } |
1114 } |
692 |
1115 |
693 $this->__construct( $meta_query ); |
1116 $this->__construct( $meta_query ); |
694 } |
1117 } |
695 |
1118 |
696 /** |
1119 /** |
697 * Given a meta type, return the appropriate alias if applicable |
1120 * Return the appropriate alias for the given meta type if applicable. |
698 * |
1121 * |
699 * @since 3.7.0 |
1122 * @since 3.7.0 |
700 * |
1123 * @access public |
701 * @param string $type MySQL type to cast meta_value |
1124 * |
702 * @return string MySQL type |
1125 * @param string $type MySQL type to cast meta_value. |
703 */ |
1126 * @return string MySQL type. |
704 function get_cast_for_type( $type = '' ) { |
1127 */ |
|
1128 public function get_cast_for_type( $type = '' ) { |
705 if ( empty( $type ) ) |
1129 if ( empty( $type ) ) |
706 return 'CHAR'; |
1130 return 'CHAR'; |
707 |
1131 |
708 $meta_type = strtoupper( $type ); |
1132 $meta_type = strtoupper( $type ); |
709 |
1133 |
710 if ( ! in_array( $meta_type, array( 'BINARY', 'CHAR', 'DATE', 'DATETIME', 'DECIMAL', 'SIGNED', 'TIME', 'UNSIGNED', 'NUMERIC' ) ) ) |
1134 if ( ! preg_match( '/^(?:BINARY|CHAR|DATE|DATETIME|SIGNED|UNSIGNED|TIME|NUMERIC(?:\(\d+(?:,\s?\d+)?\))?|DECIMAL(?:\(\d+(?:,\s?\d+)?\))?)$/', $meta_type ) ) |
711 return 'CHAR'; |
1135 return 'CHAR'; |
712 |
1136 |
713 if ( 'NUMERIC' == $meta_type ) |
1137 if ( 'NUMERIC' == $meta_type ) |
714 $meta_type = 'SIGNED'; |
1138 $meta_type = 'SIGNED'; |
715 |
1139 |
720 * Generates SQL clauses to be appended to a main query. |
1144 * Generates SQL clauses to be appended to a main query. |
721 * |
1145 * |
722 * @since 3.2.0 |
1146 * @since 3.2.0 |
723 * @access public |
1147 * @access public |
724 * |
1148 * |
725 * @param string $type Type of meta |
1149 * @param string $type Type of meta, eg 'user', 'post'. |
726 * @param string $primary_table |
1150 * @param string $primary_table Database table where the object being filtered is stored (eg wp_users). |
727 * @param string $primary_id_column |
1151 * @param string $primary_id_column ID column for the filtered object in $primary_table. |
728 * @param object $context (optional) The main query object |
1152 * @param object $context Optional. The main query object. |
729 * @return array( 'join' => $join_sql, 'where' => $where_sql ) |
1153 * @return array { |
730 */ |
1154 * Array containing JOIN and WHERE SQL clauses to append to the main query. |
731 function get_sql( $type, $primary_table, $primary_id_column, $context = null ) { |
1155 * |
|
1156 * @type string $join SQL fragment to append to the main JOIN clause. |
|
1157 * @type string $where SQL fragment to append to the main WHERE clause. |
|
1158 * } |
|
1159 */ |
|
1160 public function get_sql( $type, $primary_table, $primary_id_column, $context = null ) { |
|
1161 if ( ! $meta_table = _get_meta_table( $type ) ) { |
|
1162 return false; |
|
1163 } |
|
1164 |
|
1165 $this->meta_table = $meta_table; |
|
1166 $this->meta_id_column = sanitize_key( $type . '_id' ); |
|
1167 |
|
1168 $this->primary_table = $primary_table; |
|
1169 $this->primary_id_column = $primary_id_column; |
|
1170 |
|
1171 $sql = $this->get_sql_clauses(); |
|
1172 |
|
1173 /* |
|
1174 * If any JOINs are LEFT JOINs (as in the case of NOT EXISTS), then all JOINs should |
|
1175 * be LEFT. Otherwise posts with no metadata will be excluded from results. |
|
1176 */ |
|
1177 if ( false !== strpos( $sql['join'], 'LEFT JOIN' ) ) { |
|
1178 $sql['join'] = str_replace( 'INNER JOIN', 'LEFT JOIN', $sql['join'] ); |
|
1179 } |
|
1180 |
|
1181 /** |
|
1182 * Filter the meta query's generated SQL. |
|
1183 * |
|
1184 * @since 3.1.0 |
|
1185 * |
|
1186 * @param array $args { |
|
1187 * An array of meta query SQL arguments. |
|
1188 * |
|
1189 * @type array $clauses Array containing the query's JOIN and WHERE clauses. |
|
1190 * @type array $queries Array of meta queries. |
|
1191 * @type string $type Type of meta. |
|
1192 * @type string $primary_table Primary table. |
|
1193 * @type string $primary_id_column Primary column ID. |
|
1194 * @type object $context The main query object. |
|
1195 * } |
|
1196 */ |
|
1197 return apply_filters_ref_array( 'get_meta_sql', array( $sql, $this->queries, $type, $primary_table, $primary_id_column, $context ) ); |
|
1198 } |
|
1199 |
|
1200 /** |
|
1201 * Generate SQL clauses to be appended to a main query. |
|
1202 * |
|
1203 * Called by the public {@see WP_Meta_Query::get_sql()}, this method |
|
1204 * is abstracted out to maintain parity with the other Query classes. |
|
1205 * |
|
1206 * @since 4.1.0 |
|
1207 * @access protected |
|
1208 * |
|
1209 * @return array { |
|
1210 * Array containing JOIN and WHERE SQL clauses to append to the main query. |
|
1211 * |
|
1212 * @type string $join SQL fragment to append to the main JOIN clause. |
|
1213 * @type string $where SQL fragment to append to the main WHERE clause. |
|
1214 * } |
|
1215 */ |
|
1216 protected function get_sql_clauses() { |
|
1217 /* |
|
1218 * $queries are passed by reference to get_sql_for_query() for recursion. |
|
1219 * To keep $this->queries unaltered, pass a copy. |
|
1220 */ |
|
1221 $queries = $this->queries; |
|
1222 $sql = $this->get_sql_for_query( $queries ); |
|
1223 |
|
1224 if ( ! empty( $sql['where'] ) ) { |
|
1225 $sql['where'] = ' AND ' . $sql['where']; |
|
1226 } |
|
1227 |
|
1228 return $sql; |
|
1229 } |
|
1230 |
|
1231 /** |
|
1232 * Generate SQL clauses for a single query array. |
|
1233 * |
|
1234 * If nested subqueries are found, this method recurses the tree to |
|
1235 * produce the properly nested SQL. |
|
1236 * |
|
1237 * @since 4.1.0 |
|
1238 * @access protected |
|
1239 * |
|
1240 * @param array $query Query to parse, passed by reference. |
|
1241 * @param int $depth Optional. Number of tree levels deep we currently are. |
|
1242 * Used to calculate indentation. Default 0. |
|
1243 * @return array { |
|
1244 * Array containing JOIN and WHERE SQL clauses to append to a single query array. |
|
1245 * |
|
1246 * @type string $join SQL fragment to append to the main JOIN clause. |
|
1247 * @type string $where SQL fragment to append to the main WHERE clause. |
|
1248 * } |
|
1249 */ |
|
1250 protected function get_sql_for_query( &$query, $depth = 0 ) { |
|
1251 $sql_chunks = array( |
|
1252 'join' => array(), |
|
1253 'where' => array(), |
|
1254 ); |
|
1255 |
|
1256 $sql = array( |
|
1257 'join' => '', |
|
1258 'where' => '', |
|
1259 ); |
|
1260 |
|
1261 $indent = ''; |
|
1262 for ( $i = 0; $i < $depth; $i++ ) { |
|
1263 $indent .= " "; |
|
1264 } |
|
1265 |
|
1266 foreach ( $query as $key => &$clause ) { |
|
1267 if ( 'relation' === $key ) { |
|
1268 $relation = $query['relation']; |
|
1269 } elseif ( is_array( $clause ) ) { |
|
1270 |
|
1271 // This is a first-order clause. |
|
1272 if ( $this->is_first_order_clause( $clause ) ) { |
|
1273 $clause_sql = $this->get_sql_for_clause( $clause, $query, $key ); |
|
1274 |
|
1275 $where_count = count( $clause_sql['where'] ); |
|
1276 if ( ! $where_count ) { |
|
1277 $sql_chunks['where'][] = ''; |
|
1278 } elseif ( 1 === $where_count ) { |
|
1279 $sql_chunks['where'][] = $clause_sql['where'][0]; |
|
1280 } else { |
|
1281 $sql_chunks['where'][] = '( ' . implode( ' AND ', $clause_sql['where'] ) . ' )'; |
|
1282 } |
|
1283 |
|
1284 $sql_chunks['join'] = array_merge( $sql_chunks['join'], $clause_sql['join'] ); |
|
1285 // This is a subquery, so we recurse. |
|
1286 } else { |
|
1287 $clause_sql = $this->get_sql_for_query( $clause, $depth + 1 ); |
|
1288 |
|
1289 $sql_chunks['where'][] = $clause_sql['where']; |
|
1290 $sql_chunks['join'][] = $clause_sql['join']; |
|
1291 } |
|
1292 } |
|
1293 } |
|
1294 |
|
1295 // Filter to remove empties. |
|
1296 $sql_chunks['join'] = array_filter( $sql_chunks['join'] ); |
|
1297 $sql_chunks['where'] = array_filter( $sql_chunks['where'] ); |
|
1298 |
|
1299 if ( empty( $relation ) ) { |
|
1300 $relation = 'AND'; |
|
1301 } |
|
1302 |
|
1303 // Filter duplicate JOIN clauses and combine into a single string. |
|
1304 if ( ! empty( $sql_chunks['join'] ) ) { |
|
1305 $sql['join'] = implode( ' ', array_unique( $sql_chunks['join'] ) ); |
|
1306 } |
|
1307 |
|
1308 // Generate a single WHERE clause with proper brackets and indentation. |
|
1309 if ( ! empty( $sql_chunks['where'] ) ) { |
|
1310 $sql['where'] = '( ' . "\n " . $indent . implode( ' ' . "\n " . $indent . $relation . ' ' . "\n " . $indent, $sql_chunks['where'] ) . "\n" . $indent . ')'; |
|
1311 } |
|
1312 |
|
1313 return $sql; |
|
1314 } |
|
1315 |
|
1316 /** |
|
1317 * Generate SQL JOIN and WHERE clauses for a first-order query clause. |
|
1318 * |
|
1319 * "First-order" means that it's an array with a 'key' or 'value'. |
|
1320 * |
|
1321 * @since 4.1.0 |
|
1322 * @access public |
|
1323 * |
|
1324 * @param array $clause Query clause, passed by reference. |
|
1325 * @param array $parent_query Parent query array. |
|
1326 * @param string $clause_key Optional. The array key used to name the clause in the original `$meta_query` |
|
1327 * parameters. If not provided, a key will be generated automatically. |
|
1328 * @return array { |
|
1329 * Array containing JOIN and WHERE SQL clauses to append to a first-order query. |
|
1330 * |
|
1331 * @type string $join SQL fragment to append to the main JOIN clause. |
|
1332 * @type string $where SQL fragment to append to the main WHERE clause. |
|
1333 * } |
|
1334 */ |
|
1335 public function get_sql_for_clause( &$clause, $parent_query, $clause_key = '' ) { |
732 global $wpdb; |
1336 global $wpdb; |
733 |
1337 |
734 if ( ! $meta_table = _get_meta_table( $type ) ) |
1338 $sql_chunks = array( |
735 return false; |
1339 'where' => array(), |
736 |
1340 'join' => array(), |
737 $meta_id_column = sanitize_key( $type . '_id' ); |
1341 ); |
738 |
1342 |
739 $join = array(); |
1343 if ( isset( $clause['compare'] ) ) { |
740 $where = array(); |
1344 $clause['compare'] = strtoupper( $clause['compare'] ); |
741 |
1345 } else { |
742 $key_only_queries = array(); |
1346 $clause['compare'] = isset( $clause['value'] ) && is_array( $clause['value'] ) ? 'IN' : '='; |
743 $queries = array(); |
1347 } |
744 |
1348 |
745 // Split out the queries with empty arrays as value |
1349 if ( ! in_array( $clause['compare'], array( |
746 foreach ( $this->queries as $k => $q ) { |
1350 '=', '!=', '>', '>=', '<', '<=', |
747 if ( isset( $q['value'] ) && is_array( $q['value'] ) && empty( $q['value'] ) ) { |
1351 'LIKE', 'NOT LIKE', |
748 $key_only_queries[$k] = $q; |
1352 'IN', 'NOT IN', |
749 unset( $this->queries[$k] ); |
1353 'BETWEEN', 'NOT BETWEEN', |
|
1354 'EXISTS', 'NOT EXISTS', |
|
1355 'REGEXP', 'NOT REGEXP', 'RLIKE' |
|
1356 ) ) ) { |
|
1357 $clause['compare'] = '='; |
|
1358 } |
|
1359 |
|
1360 $meta_compare = $clause['compare']; |
|
1361 |
|
1362 // First build the JOIN clause, if one is required. |
|
1363 $join = ''; |
|
1364 |
|
1365 // We prefer to avoid joins if possible. Look for an existing join compatible with this clause. |
|
1366 $alias = $this->find_compatible_table_alias( $clause, $parent_query ); |
|
1367 if ( false === $alias ) { |
|
1368 $i = count( $this->table_aliases ); |
|
1369 $alias = $i ? 'mt' . $i : $this->meta_table; |
|
1370 |
|
1371 // JOIN clauses for NOT EXISTS have their own syntax. |
|
1372 if ( 'NOT EXISTS' === $meta_compare ) { |
|
1373 $join .= " LEFT JOIN $this->meta_table"; |
|
1374 $join .= $i ? " AS $alias" : ''; |
|
1375 $join .= $wpdb->prepare( " ON ($this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column AND $alias.meta_key = %s )", $clause['key'] ); |
|
1376 |
|
1377 // All other JOIN clauses. |
|
1378 } else { |
|
1379 $join .= " INNER JOIN $this->meta_table"; |
|
1380 $join .= $i ? " AS $alias" : ''; |
|
1381 $join .= " ON ( $this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column )"; |
750 } |
1382 } |
751 } |
1383 |
752 |
1384 $this->table_aliases[] = $alias; |
753 // Split out the meta_key only queries (we can only do this for OR) |
1385 $sql_chunks['join'][] = $join; |
754 if ( 'OR' == $this->relation ) { |
1386 } |
755 foreach ( $this->queries as $k => $q ) { |
1387 |
756 if ( ! isset( $q['value'] ) && ! empty( $q['key'] ) ) |
1388 // Save the alias to this clause, for future siblings to find. |
757 $key_only_queries[$k] = $q; |
1389 $clause['alias'] = $alias; |
758 else |
1390 |
759 $queries[$k] = $q; |
1391 // Determine the data type. |
|
1392 $_meta_type = isset( $clause['type'] ) ? $clause['type'] : ''; |
|
1393 $meta_type = $this->get_cast_for_type( $_meta_type ); |
|
1394 $clause['cast'] = $meta_type; |
|
1395 |
|
1396 // Fallback for clause keys is the table alias. |
|
1397 if ( ! $clause_key ) { |
|
1398 $clause_key = $clause['alias']; |
|
1399 } |
|
1400 |
|
1401 // Ensure unique clause keys, so none are overwritten. |
|
1402 $iterator = 1; |
|
1403 $clause_key_base = $clause_key; |
|
1404 while ( isset( $this->clauses[ $clause_key ] ) ) { |
|
1405 $clause_key = $clause_key_base . '-' . $iterator; |
|
1406 $iterator++; |
|
1407 } |
|
1408 |
|
1409 // Store the clause in our flat array. |
|
1410 $this->clauses[ $clause_key ] =& $clause; |
|
1411 |
|
1412 // Next, build the WHERE clause. |
|
1413 |
|
1414 // meta_key. |
|
1415 if ( array_key_exists( 'key', $clause ) ) { |
|
1416 if ( 'NOT EXISTS' === $meta_compare ) { |
|
1417 $sql_chunks['where'][] = $alias . '.' . $this->meta_id_column . ' IS NULL'; |
|
1418 } else { |
|
1419 $sql_chunks['where'][] = $wpdb->prepare( "$alias.meta_key = %s", trim( $clause['key'] ) ); |
760 } |
1420 } |
761 } else { |
1421 } |
762 $queries = $this->queries; |
1422 |
763 } |
1423 // meta_value. |
764 |
1424 if ( array_key_exists( 'value', $clause ) ) { |
765 // Specify all the meta_key only queries in one go |
1425 $meta_value = $clause['value']; |
766 if ( $key_only_queries ) { |
|
767 $join[] = "INNER JOIN $meta_table ON $primary_table.$primary_id_column = $meta_table.$meta_id_column"; |
|
768 |
|
769 foreach ( $key_only_queries as $key => $q ) |
|
770 $where["key-only-$key"] = $wpdb->prepare( "$meta_table.meta_key = %s", trim( $q['key'] ) ); |
|
771 } |
|
772 |
|
773 foreach ( $queries as $k => $q ) { |
|
774 $meta_key = isset( $q['key'] ) ? trim( $q['key'] ) : ''; |
|
775 $meta_type = $this->get_cast_for_type( isset( $q['type'] ) ? $q['type'] : '' ); |
|
776 |
|
777 $meta_value = isset( $q['value'] ) ? $q['value'] : null; |
|
778 |
|
779 if ( isset( $q['compare'] ) ) |
|
780 $meta_compare = strtoupper( $q['compare'] ); |
|
781 else |
|
782 $meta_compare = is_array( $meta_value ) ? 'IN' : '='; |
|
783 |
|
784 if ( ! in_array( $meta_compare, array( |
|
785 '=', '!=', '>', '>=', '<', '<=', |
|
786 'LIKE', 'NOT LIKE', |
|
787 'IN', 'NOT IN', |
|
788 'BETWEEN', 'NOT BETWEEN', |
|
789 'NOT EXISTS', |
|
790 'REGEXP', 'NOT REGEXP', 'RLIKE' |
|
791 ) ) ) |
|
792 $meta_compare = '='; |
|
793 |
|
794 $i = count( $join ); |
|
795 $alias = $i ? 'mt' . $i : $meta_table; |
|
796 |
|
797 if ( 'NOT EXISTS' == $meta_compare ) { |
|
798 $join[$i] = "LEFT JOIN $meta_table"; |
|
799 $join[$i] .= $i ? " AS $alias" : ''; |
|
800 $join[$i] .= " ON ($primary_table.$primary_id_column = $alias.$meta_id_column AND $alias.meta_key = '$meta_key')"; |
|
801 |
|
802 $where[$k] = ' ' . $alias . '.' . $meta_id_column . ' IS NULL'; |
|
803 |
|
804 continue; |
|
805 } |
|
806 |
|
807 $join[$i] = "INNER JOIN $meta_table"; |
|
808 $join[$i] .= $i ? " AS $alias" : ''; |
|
809 $join[$i] .= " ON ($primary_table.$primary_id_column = $alias.$meta_id_column)"; |
|
810 |
|
811 $where[$k] = ''; |
|
812 if ( !empty( $meta_key ) ) |
|
813 $where[$k] = $wpdb->prepare( "$alias.meta_key = %s", $meta_key ); |
|
814 |
|
815 if ( is_null( $meta_value ) ) { |
|
816 if ( empty( $where[$k] ) ) |
|
817 unset( $join[$i] ); |
|
818 continue; |
|
819 } |
|
820 |
1426 |
821 if ( in_array( $meta_compare, array( 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN' ) ) ) { |
1427 if ( in_array( $meta_compare, array( 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN' ) ) ) { |
822 if ( ! is_array( $meta_value ) ) |
1428 if ( ! is_array( $meta_value ) ) { |
823 $meta_value = preg_split( '/[,\s]+/', $meta_value ); |
1429 $meta_value = preg_split( '/[,\s]+/', $meta_value ); |
824 |
|
825 if ( empty( $meta_value ) ) { |
|
826 unset( $join[$i] ); |
|
827 continue; |
|
828 } |
1430 } |
829 } else { |
1431 } else { |
830 $meta_value = trim( $meta_value ); |
1432 $meta_value = trim( $meta_value ); |
831 } |
1433 } |
832 |
1434 |
833 if ( 'IN' == substr( $meta_compare, -2) ) { |
1435 switch ( $meta_compare ) { |
834 $meta_compare_string = '(' . substr( str_repeat( ',%s', count( $meta_value ) ), 1 ) . ')'; |
1436 case 'IN' : |
835 } elseif ( 'BETWEEN' == substr( $meta_compare, -7) ) { |
1437 case 'NOT IN' : |
836 $meta_value = array_slice( $meta_value, 0, 2 ); |
1438 $meta_compare_string = '(' . substr( str_repeat( ',%s', count( $meta_value ) ), 1 ) . ')'; |
837 $meta_compare_string = '%s AND %s'; |
1439 $where = $wpdb->prepare( $meta_compare_string, $meta_value ); |
838 } elseif ( 'LIKE' == substr( $meta_compare, -4 ) ) { |
1440 break; |
839 $meta_value = '%' . like_escape( $meta_value ) . '%'; |
1441 |
840 $meta_compare_string = '%s'; |
1442 case 'BETWEEN' : |
841 } else { |
1443 case 'NOT BETWEEN' : |
842 $meta_compare_string = '%s'; |
1444 $meta_value = array_slice( $meta_value, 0, 2 ); |
|
1445 $where = $wpdb->prepare( '%s AND %s', $meta_value ); |
|
1446 break; |
|
1447 |
|
1448 case 'LIKE' : |
|
1449 case 'NOT LIKE' : |
|
1450 $meta_value = '%' . $wpdb->esc_like( $meta_value ) . '%'; |
|
1451 $where = $wpdb->prepare( '%s', $meta_value ); |
|
1452 break; |
|
1453 |
|
1454 // EXISTS with a value is interpreted as '='. |
|
1455 case 'EXISTS' : |
|
1456 $meta_compare = '='; |
|
1457 $where = $wpdb->prepare( '%s', $meta_value ); |
|
1458 break; |
|
1459 |
|
1460 // 'value' is ignored for NOT EXISTS. |
|
1461 case 'NOT EXISTS' : |
|
1462 $where = ''; |
|
1463 break; |
|
1464 |
|
1465 default : |
|
1466 $where = $wpdb->prepare( '%s', $meta_value ); |
|
1467 break; |
|
1468 |
843 } |
1469 } |
844 |
1470 |
845 if ( ! empty( $where[$k] ) ) |
1471 if ( $where ) { |
846 $where[$k] .= ' AND '; |
1472 $sql_chunks['where'][] = "CAST($alias.meta_value AS {$meta_type}) {$meta_compare} {$where}"; |
847 |
1473 } |
848 $where[$k] = ' (' . $where[$k] . $wpdb->prepare( "CAST($alias.meta_value AS {$meta_type}) {$meta_compare} {$meta_compare_string})", $meta_value ); |
1474 } |
849 } |
1475 |
850 |
1476 /* |
851 $where = array_filter( $where ); |
1477 * Multiple WHERE clauses (for meta_key and meta_value) should |
852 |
1478 * be joined in parentheses. |
853 if ( empty( $where ) ) |
1479 */ |
854 $where = ''; |
1480 if ( 1 < count( $sql_chunks['where'] ) ) { |
855 else |
1481 $sql_chunks['where'] = array( '( ' . implode( ' AND ', $sql_chunks['where'] ) . ' )' ); |
856 $where = ' AND (' . implode( "\n{$this->relation} ", $where ) . ' )'; |
1482 } |
857 |
1483 |
858 $join = implode( "\n", $join ); |
1484 return $sql_chunks; |
859 if ( ! empty( $join ) ) |
1485 } |
860 $join = ' ' . $join; |
1486 |
861 |
1487 /** |
862 return apply_filters_ref_array( 'get_meta_sql', array( compact( 'join', 'where' ), $this->queries, $type, $primary_table, $primary_id_column, $context ) ); |
1488 * Get a flattened list of sanitized meta clauses. |
|
1489 * |
|
1490 * This array should be used for clause lookup, as when the table alias and CAST type must be determined for |
|
1491 * a value of 'orderby' corresponding to a meta clause. |
|
1492 * |
|
1493 * @since 4.2.0 |
|
1494 * @access public |
|
1495 * |
|
1496 * @return array Meta clauses. |
|
1497 */ |
|
1498 public function get_clauses() { |
|
1499 return $this->clauses; |
|
1500 } |
|
1501 |
|
1502 /** |
|
1503 * Identify an existing table alias that is compatible with the current |
|
1504 * query clause. |
|
1505 * |
|
1506 * We avoid unnecessary table joins by allowing each clause to look for |
|
1507 * an existing table alias that is compatible with the query that it |
|
1508 * needs to perform. |
|
1509 * |
|
1510 * An existing alias is compatible if (a) it is a sibling of `$clause` |
|
1511 * (ie, it's under the scope of the same relation), and (b) the combination |
|
1512 * of operator and relation between the clauses allows for a shared table join. |
|
1513 * In the case of {@see WP_Meta_Query}, this only applies to 'IN' clauses that |
|
1514 * are connected by the relation 'OR'. |
|
1515 * |
|
1516 * @since 4.1.0 |
|
1517 * @access protected |
|
1518 * |
|
1519 * @param array $clause Query clause. |
|
1520 * @param array $parent_query Parent query of $clause. |
|
1521 * @return string|bool Table alias if found, otherwise false. |
|
1522 */ |
|
1523 protected function find_compatible_table_alias( $clause, $parent_query ) { |
|
1524 $alias = false; |
|
1525 |
|
1526 foreach ( $parent_query as $sibling ) { |
|
1527 // If the sibling has no alias yet, there's nothing to check. |
|
1528 if ( empty( $sibling['alias'] ) ) { |
|
1529 continue; |
|
1530 } |
|
1531 |
|
1532 // We're only interested in siblings that are first-order clauses. |
|
1533 if ( ! is_array( $sibling ) || ! $this->is_first_order_clause( $sibling ) ) { |
|
1534 continue; |
|
1535 } |
|
1536 |
|
1537 $compatible_compares = array(); |
|
1538 |
|
1539 // Clauses connected by OR can share joins as long as they have "positive" operators. |
|
1540 if ( 'OR' === $parent_query['relation'] ) { |
|
1541 $compatible_compares = array( '=', 'IN', 'BETWEEN', 'LIKE', 'REGEXP', 'RLIKE', '>', '>=', '<', '<=' ); |
|
1542 |
|
1543 // Clauses joined by AND with "negative" operators share a join only if they also share a key. |
|
1544 } elseif ( isset( $sibling['key'] ) && isset( $clause['key'] ) && $sibling['key'] === $clause['key'] ) { |
|
1545 $compatible_compares = array( '!=', 'NOT IN', 'NOT LIKE' ); |
|
1546 } |
|
1547 |
|
1548 $clause_compare = strtoupper( $clause['compare'] ); |
|
1549 $sibling_compare = strtoupper( $sibling['compare'] ); |
|
1550 if ( in_array( $clause_compare, $compatible_compares ) && in_array( $sibling_compare, $compatible_compares ) ) { |
|
1551 $alias = $sibling['alias']; |
|
1552 break; |
|
1553 } |
|
1554 } |
|
1555 |
|
1556 /** |
|
1557 * Filter the table alias identified as compatible with the current clause. |
|
1558 * |
|
1559 * @since 4.1.0 |
|
1560 * |
|
1561 * @param string|bool $alias Table alias, or false if none was found. |
|
1562 * @param array $clause First-order query clause. |
|
1563 * @param array $parent_query Parent of $clause. |
|
1564 * @param object $this WP_Meta_Query object. |
|
1565 */ |
|
1566 return apply_filters( 'meta_query_find_compatible_table_alias', $alias, $clause, $parent_query, $this ) ; |
863 } |
1567 } |
864 } |
1568 } |
865 |
1569 |
866 /** |
1570 /** |
867 * Retrieve the name of the metadata table for the specified object type. |
1571 * Retrieve the name of the metadata table for the specified object type. |
868 * |
1572 * |
869 * @since 2.9.0 |
1573 * @since 2.9.0 |
870 * @uses $wpdb WordPress database object for queries. |
1574 * |
|
1575 * @global wpdb $wpdb WordPress database abstraction object. |
871 * |
1576 * |
872 * @param string $type Type of object to get metadata table for (e.g., comment, post, or user) |
1577 * @param string $type Type of object to get metadata table for (e.g., comment, post, or user) |
873 * @return mixed Metadata table name, or false if no metadata table exists |
1578 * @return mixed Metadata table name, or false if no metadata table exists |
874 */ |
1579 */ |
875 function _get_meta_table($type) { |
1580 function _get_meta_table($type) { |