857 $meta_query_obj = new WP_Meta_Query( $meta_query ); |
891 $meta_query_obj = new WP_Meta_Query( $meta_query ); |
858 return $meta_query_obj->get_sql( $type, $primary_table, $primary_id_column, $context ); |
892 return $meta_query_obj->get_sql( $type, $primary_table, $primary_id_column, $context ); |
859 } |
893 } |
860 |
894 |
861 /** |
895 /** |
862 * Class for generating SQL clauses that filter a primary query according to metadata keys and values. |
|
863 * |
|
864 * `WP_Meta_Query` is a helper that allows primary query classes, such as {@see WP_Query} and {@see WP_User_Query}, |
|
865 * to filter their results by object metadata, by generating `JOIN` and `WHERE` subclauses to be attached |
|
866 * to the primary SQL query string. |
|
867 * |
|
868 * @since 3.2.0 |
|
869 */ |
|
870 class WP_Meta_Query { |
|
871 /** |
|
872 * Array of metadata queries. |
|
873 * |
|
874 * See {@see WP_Meta_Query::__construct()} for information on meta query arguments. |
|
875 * |
|
876 * @since 3.2.0 |
|
877 * @access public |
|
878 * @var array |
|
879 */ |
|
880 public $queries = array(); |
|
881 |
|
882 /** |
|
883 * The relation between the queries. Can be one of 'AND' or 'OR'. |
|
884 * |
|
885 * @since 3.2.0 |
|
886 * @access public |
|
887 * @var string |
|
888 */ |
|
889 public $relation; |
|
890 |
|
891 /** |
|
892 * Database table to query for the metadata. |
|
893 * |
|
894 * @since 4.1.0 |
|
895 * @access public |
|
896 * @var string |
|
897 */ |
|
898 public $meta_table; |
|
899 |
|
900 /** |
|
901 * Column in meta_table that represents the ID of the object the metadata belongs to. |
|
902 * |
|
903 * @since 4.1.0 |
|
904 * @access public |
|
905 * @var string |
|
906 */ |
|
907 public $meta_id_column; |
|
908 |
|
909 /** |
|
910 * Database table that where the metadata's objects are stored (eg $wpdb->users). |
|
911 * |
|
912 * @since 4.1.0 |
|
913 * @access public |
|
914 * @var string |
|
915 */ |
|
916 public $primary_table; |
|
917 |
|
918 /** |
|
919 * Column in primary_table that represents the ID of the object. |
|
920 * |
|
921 * @since 4.1.0 |
|
922 * @access public |
|
923 * @var string |
|
924 */ |
|
925 public $primary_id_column; |
|
926 |
|
927 /** |
|
928 * A flat list of table aliases used in JOIN clauses. |
|
929 * |
|
930 * @since 4.1.0 |
|
931 * @access protected |
|
932 * @var array |
|
933 */ |
|
934 protected $table_aliases = array(); |
|
935 |
|
936 /** |
|
937 * A flat list of clauses, keyed by clause 'name'. |
|
938 * |
|
939 * @since 4.2.0 |
|
940 * @access protected |
|
941 * @var array |
|
942 */ |
|
943 protected $clauses = array(); |
|
944 |
|
945 /** |
|
946 * Constructor. |
|
947 * |
|
948 * @since 3.2.0 |
|
949 * @since 4.2.0 Introduced support for naming query clauses by associative array keys. |
|
950 * |
|
951 * @access public |
|
952 * |
|
953 * @param array $meta_query { |
|
954 * Array of meta query clauses. When first-order clauses use strings as their array keys, they may be |
|
955 * referenced in the 'orderby' parameter of the parent query. |
|
956 * |
|
957 * @type string $relation Optional. The MySQL keyword used to join |
|
958 * the clauses of the query. Accepts 'AND', or 'OR'. Default 'AND'. |
|
959 * @type array { |
|
960 * Optional. An array of first-order clause parameters, or another fully-formed meta query. |
|
961 * |
|
962 * @type string $key Meta key to filter by. |
|
963 * @type string $value Meta value to filter by. |
|
964 * @type string $compare MySQL operator used for comparing the $value. Accepts '=', |
|
965 * '!=', '>', '>=', '<', '<=', 'LIKE', 'NOT LIKE', 'IN', 'NOT IN', |
|
966 * 'BETWEEN', 'NOT BETWEEN', 'REGEXP', 'NOT REGEXP', or 'RLIKE'. |
|
967 * Default is 'IN' when `$value` is an array, '=' otherwise. |
|
968 * @type string $type MySQL data type that the meta_value column will be CAST to for |
|
969 * comparisons. Accepts 'NUMERIC', 'BINARY', 'CHAR', 'DATE', |
|
970 * 'DATETIME', 'DECIMAL', 'SIGNED', 'TIME', or 'UNSIGNED'. |
|
971 * Default is 'CHAR'. |
|
972 * } |
|
973 * } |
|
974 */ |
|
975 public function __construct( $meta_query = false ) { |
|
976 if ( !$meta_query ) |
|
977 return; |
|
978 |
|
979 if ( isset( $meta_query['relation'] ) && strtoupper( $meta_query['relation'] ) == 'OR' ) { |
|
980 $this->relation = 'OR'; |
|
981 } else { |
|
982 $this->relation = 'AND'; |
|
983 } |
|
984 |
|
985 $this->queries = $this->sanitize_query( $meta_query ); |
|
986 } |
|
987 |
|
988 /** |
|
989 * Ensure the 'meta_query' argument passed to the class constructor is well-formed. |
|
990 * |
|
991 * Eliminates empty items and ensures that a 'relation' is set. |
|
992 * |
|
993 * @since 4.1.0 |
|
994 * @access public |
|
995 * |
|
996 * @param array $queries Array of query clauses. |
|
997 * @return array Sanitized array of query clauses. |
|
998 */ |
|
999 public function sanitize_query( $queries ) { |
|
1000 $clean_queries = array(); |
|
1001 |
|
1002 if ( ! is_array( $queries ) ) { |
|
1003 return $clean_queries; |
|
1004 } |
|
1005 |
|
1006 foreach ( $queries as $key => $query ) { |
|
1007 if ( 'relation' === $key ) { |
|
1008 $relation = $query; |
|
1009 |
|
1010 } elseif ( ! is_array( $query ) ) { |
|
1011 continue; |
|
1012 |
|
1013 // First-order clause. |
|
1014 } elseif ( $this->is_first_order_clause( $query ) ) { |
|
1015 if ( isset( $query['value'] ) && array() === $query['value'] ) { |
|
1016 unset( $query['value'] ); |
|
1017 } |
|
1018 |
|
1019 $clean_queries[ $key ] = $query; |
|
1020 |
|
1021 // Otherwise, it's a nested query, so we recurse. |
|
1022 } else { |
|
1023 $cleaned_query = $this->sanitize_query( $query ); |
|
1024 |
|
1025 if ( ! empty( $cleaned_query ) ) { |
|
1026 $clean_queries[ $key ] = $cleaned_query; |
|
1027 } |
|
1028 } |
|
1029 } |
|
1030 |
|
1031 if ( empty( $clean_queries ) ) { |
|
1032 return $clean_queries; |
|
1033 } |
|
1034 |
|
1035 // Sanitize the 'relation' key provided in the query. |
|
1036 if ( isset( $relation ) && 'OR' === strtoupper( $relation ) ) { |
|
1037 $clean_queries['relation'] = 'OR'; |
|
1038 |
|
1039 /* |
|
1040 * If there is only a single clause, call the relation 'OR'. |
|
1041 * This value will not actually be used to join clauses, but it |
|
1042 * simplifies the logic around combining key-only queries. |
|
1043 */ |
|
1044 } elseif ( 1 === count( $clean_queries ) ) { |
|
1045 $clean_queries['relation'] = 'OR'; |
|
1046 |
|
1047 // Default to AND. |
|
1048 } else { |
|
1049 $clean_queries['relation'] = 'AND'; |
|
1050 } |
|
1051 |
|
1052 return $clean_queries; |
|
1053 } |
|
1054 |
|
1055 /** |
|
1056 * Determine whether a query clause is first-order. |
|
1057 * |
|
1058 * A first-order meta query clause is one that has either a 'key' or |
|
1059 * a 'value' array key. |
|
1060 * |
|
1061 * @since 4.1.0 |
|
1062 * @access protected |
|
1063 * |
|
1064 * @param array $query Meta query arguments. |
|
1065 * @return bool Whether the query clause is a first-order clause. |
|
1066 */ |
|
1067 protected function is_first_order_clause( $query ) { |
|
1068 return isset( $query['key'] ) || isset( $query['value'] ); |
|
1069 } |
|
1070 |
|
1071 /** |
|
1072 * Constructs a meta query based on 'meta_*' query vars |
|
1073 * |
|
1074 * @since 3.2.0 |
|
1075 * @access public |
|
1076 * |
|
1077 * @param array $qv The query variables |
|
1078 */ |
|
1079 public function parse_query_vars( $qv ) { |
|
1080 $meta_query = array(); |
|
1081 |
|
1082 /* |
|
1083 * For orderby=meta_value to work correctly, simple query needs to be |
|
1084 * first (so that its table join is against an unaliased meta table) and |
|
1085 * needs to be its own clause (so it doesn't interfere with the logic of |
|
1086 * the rest of the meta_query). |
|
1087 */ |
|
1088 $primary_meta_query = array(); |
|
1089 foreach ( array( 'key', 'compare', 'type' ) as $key ) { |
|
1090 if ( ! empty( $qv[ "meta_$key" ] ) ) { |
|
1091 $primary_meta_query[ $key ] = $qv[ "meta_$key" ]; |
|
1092 } |
|
1093 } |
|
1094 |
|
1095 // WP_Query sets 'meta_value' = '' by default. |
|
1096 if ( isset( $qv['meta_value'] ) && '' !== $qv['meta_value'] && ( ! is_array( $qv['meta_value'] ) || $qv['meta_value'] ) ) { |
|
1097 $primary_meta_query['value'] = $qv['meta_value']; |
|
1098 } |
|
1099 |
|
1100 $existing_meta_query = isset( $qv['meta_query'] ) && is_array( $qv['meta_query'] ) ? $qv['meta_query'] : array(); |
|
1101 |
|
1102 if ( ! empty( $primary_meta_query ) && ! empty( $existing_meta_query ) ) { |
|
1103 $meta_query = array( |
|
1104 'relation' => 'AND', |
|
1105 $primary_meta_query, |
|
1106 $existing_meta_query, |
|
1107 ); |
|
1108 } elseif ( ! empty( $primary_meta_query ) ) { |
|
1109 $meta_query = array( |
|
1110 $primary_meta_query, |
|
1111 ); |
|
1112 } elseif ( ! empty( $existing_meta_query ) ) { |
|
1113 $meta_query = $existing_meta_query; |
|
1114 } |
|
1115 |
|
1116 $this->__construct( $meta_query ); |
|
1117 } |
|
1118 |
|
1119 /** |
|
1120 * Return the appropriate alias for the given meta type if applicable. |
|
1121 * |
|
1122 * @since 3.7.0 |
|
1123 * @access public |
|
1124 * |
|
1125 * @param string $type MySQL type to cast meta_value. |
|
1126 * @return string MySQL type. |
|
1127 */ |
|
1128 public function get_cast_for_type( $type = '' ) { |
|
1129 if ( empty( $type ) ) |
|
1130 return 'CHAR'; |
|
1131 |
|
1132 $meta_type = strtoupper( $type ); |
|
1133 |
|
1134 if ( ! preg_match( '/^(?:BINARY|CHAR|DATE|DATETIME|SIGNED|UNSIGNED|TIME|NUMERIC(?:\(\d+(?:,\s?\d+)?\))?|DECIMAL(?:\(\d+(?:,\s?\d+)?\))?)$/', $meta_type ) ) |
|
1135 return 'CHAR'; |
|
1136 |
|
1137 if ( 'NUMERIC' == $meta_type ) |
|
1138 $meta_type = 'SIGNED'; |
|
1139 |
|
1140 return $meta_type; |
|
1141 } |
|
1142 |
|
1143 /** |
|
1144 * Generates SQL clauses to be appended to a main query. |
|
1145 * |
|
1146 * @since 3.2.0 |
|
1147 * @access public |
|
1148 * |
|
1149 * @param string $type Type of meta, eg 'user', 'post'. |
|
1150 * @param string $primary_table Database table where the object being filtered is stored (eg wp_users). |
|
1151 * @param string $primary_id_column ID column for the filtered object in $primary_table. |
|
1152 * @param object $context Optional. The main query object. |
|
1153 * @return array { |
|
1154 * Array containing JOIN and WHERE SQL clauses to append to the main query. |
|
1155 * |
|
1156 * @type string $join SQL fragment to append to the main JOIN clause. |
|
1157 * @type string $where SQL fragment to append to the main WHERE clause. |
|
1158 * } |
|
1159 */ |
|
1160 public function get_sql( $type, $primary_table, $primary_id_column, $context = null ) { |
|
1161 if ( ! $meta_table = _get_meta_table( $type ) ) { |
|
1162 return false; |
|
1163 } |
|
1164 |
|
1165 $this->meta_table = $meta_table; |
|
1166 $this->meta_id_column = sanitize_key( $type . '_id' ); |
|
1167 |
|
1168 $this->primary_table = $primary_table; |
|
1169 $this->primary_id_column = $primary_id_column; |
|
1170 |
|
1171 $sql = $this->get_sql_clauses(); |
|
1172 |
|
1173 /* |
|
1174 * If any JOINs are LEFT JOINs (as in the case of NOT EXISTS), then all JOINs should |
|
1175 * be LEFT. Otherwise posts with no metadata will be excluded from results. |
|
1176 */ |
|
1177 if ( false !== strpos( $sql['join'], 'LEFT JOIN' ) ) { |
|
1178 $sql['join'] = str_replace( 'INNER JOIN', 'LEFT JOIN', $sql['join'] ); |
|
1179 } |
|
1180 |
|
1181 /** |
|
1182 * Filter the meta query's generated SQL. |
|
1183 * |
|
1184 * @since 3.1.0 |
|
1185 * |
|
1186 * @param array $args { |
|
1187 * An array of meta query SQL arguments. |
|
1188 * |
|
1189 * @type array $clauses Array containing the query's JOIN and WHERE clauses. |
|
1190 * @type array $queries Array of meta queries. |
|
1191 * @type string $type Type of meta. |
|
1192 * @type string $primary_table Primary table. |
|
1193 * @type string $primary_id_column Primary column ID. |
|
1194 * @type object $context The main query object. |
|
1195 * } |
|
1196 */ |
|
1197 return apply_filters_ref_array( 'get_meta_sql', array( $sql, $this->queries, $type, $primary_table, $primary_id_column, $context ) ); |
|
1198 } |
|
1199 |
|
1200 /** |
|
1201 * Generate SQL clauses to be appended to a main query. |
|
1202 * |
|
1203 * Called by the public {@see WP_Meta_Query::get_sql()}, this method |
|
1204 * is abstracted out to maintain parity with the other Query classes. |
|
1205 * |
|
1206 * @since 4.1.0 |
|
1207 * @access protected |
|
1208 * |
|
1209 * @return array { |
|
1210 * Array containing JOIN and WHERE SQL clauses to append to the main query. |
|
1211 * |
|
1212 * @type string $join SQL fragment to append to the main JOIN clause. |
|
1213 * @type string $where SQL fragment to append to the main WHERE clause. |
|
1214 * } |
|
1215 */ |
|
1216 protected function get_sql_clauses() { |
|
1217 /* |
|
1218 * $queries are passed by reference to get_sql_for_query() for recursion. |
|
1219 * To keep $this->queries unaltered, pass a copy. |
|
1220 */ |
|
1221 $queries = $this->queries; |
|
1222 $sql = $this->get_sql_for_query( $queries ); |
|
1223 |
|
1224 if ( ! empty( $sql['where'] ) ) { |
|
1225 $sql['where'] = ' AND ' . $sql['where']; |
|
1226 } |
|
1227 |
|
1228 return $sql; |
|
1229 } |
|
1230 |
|
1231 /** |
|
1232 * Generate SQL clauses for a single query array. |
|
1233 * |
|
1234 * If nested subqueries are found, this method recurses the tree to |
|
1235 * produce the properly nested SQL. |
|
1236 * |
|
1237 * @since 4.1.0 |
|
1238 * @access protected |
|
1239 * |
|
1240 * @param array $query Query to parse, passed by reference. |
|
1241 * @param int $depth Optional. Number of tree levels deep we currently are. |
|
1242 * Used to calculate indentation. Default 0. |
|
1243 * @return array { |
|
1244 * Array containing JOIN and WHERE SQL clauses to append to a single query array. |
|
1245 * |
|
1246 * @type string $join SQL fragment to append to the main JOIN clause. |
|
1247 * @type string $where SQL fragment to append to the main WHERE clause. |
|
1248 * } |
|
1249 */ |
|
1250 protected function get_sql_for_query( &$query, $depth = 0 ) { |
|
1251 $sql_chunks = array( |
|
1252 'join' => array(), |
|
1253 'where' => array(), |
|
1254 ); |
|
1255 |
|
1256 $sql = array( |
|
1257 'join' => '', |
|
1258 'where' => '', |
|
1259 ); |
|
1260 |
|
1261 $indent = ''; |
|
1262 for ( $i = 0; $i < $depth; $i++ ) { |
|
1263 $indent .= " "; |
|
1264 } |
|
1265 |
|
1266 foreach ( $query as $key => &$clause ) { |
|
1267 if ( 'relation' === $key ) { |
|
1268 $relation = $query['relation']; |
|
1269 } elseif ( is_array( $clause ) ) { |
|
1270 |
|
1271 // This is a first-order clause. |
|
1272 if ( $this->is_first_order_clause( $clause ) ) { |
|
1273 $clause_sql = $this->get_sql_for_clause( $clause, $query, $key ); |
|
1274 |
|
1275 $where_count = count( $clause_sql['where'] ); |
|
1276 if ( ! $where_count ) { |
|
1277 $sql_chunks['where'][] = ''; |
|
1278 } elseif ( 1 === $where_count ) { |
|
1279 $sql_chunks['where'][] = $clause_sql['where'][0]; |
|
1280 } else { |
|
1281 $sql_chunks['where'][] = '( ' . implode( ' AND ', $clause_sql['where'] ) . ' )'; |
|
1282 } |
|
1283 |
|
1284 $sql_chunks['join'] = array_merge( $sql_chunks['join'], $clause_sql['join'] ); |
|
1285 // This is a subquery, so we recurse. |
|
1286 } else { |
|
1287 $clause_sql = $this->get_sql_for_query( $clause, $depth + 1 ); |
|
1288 |
|
1289 $sql_chunks['where'][] = $clause_sql['where']; |
|
1290 $sql_chunks['join'][] = $clause_sql['join']; |
|
1291 } |
|
1292 } |
|
1293 } |
|
1294 |
|
1295 // Filter to remove empties. |
|
1296 $sql_chunks['join'] = array_filter( $sql_chunks['join'] ); |
|
1297 $sql_chunks['where'] = array_filter( $sql_chunks['where'] ); |
|
1298 |
|
1299 if ( empty( $relation ) ) { |
|
1300 $relation = 'AND'; |
|
1301 } |
|
1302 |
|
1303 // Filter duplicate JOIN clauses and combine into a single string. |
|
1304 if ( ! empty( $sql_chunks['join'] ) ) { |
|
1305 $sql['join'] = implode( ' ', array_unique( $sql_chunks['join'] ) ); |
|
1306 } |
|
1307 |
|
1308 // Generate a single WHERE clause with proper brackets and indentation. |
|
1309 if ( ! empty( $sql_chunks['where'] ) ) { |
|
1310 $sql['where'] = '( ' . "\n " . $indent . implode( ' ' . "\n " . $indent . $relation . ' ' . "\n " . $indent, $sql_chunks['where'] ) . "\n" . $indent . ')'; |
|
1311 } |
|
1312 |
|
1313 return $sql; |
|
1314 } |
|
1315 |
|
1316 /** |
|
1317 * Generate SQL JOIN and WHERE clauses for a first-order query clause. |
|
1318 * |
|
1319 * "First-order" means that it's an array with a 'key' or 'value'. |
|
1320 * |
|
1321 * @since 4.1.0 |
|
1322 * @access public |
|
1323 * |
|
1324 * @param array $clause Query clause, passed by reference. |
|
1325 * @param array $parent_query Parent query array. |
|
1326 * @param string $clause_key Optional. The array key used to name the clause in the original `$meta_query` |
|
1327 * parameters. If not provided, a key will be generated automatically. |
|
1328 * @return array { |
|
1329 * Array containing JOIN and WHERE SQL clauses to append to a first-order query. |
|
1330 * |
|
1331 * @type string $join SQL fragment to append to the main JOIN clause. |
|
1332 * @type string $where SQL fragment to append to the main WHERE clause. |
|
1333 * } |
|
1334 */ |
|
1335 public function get_sql_for_clause( &$clause, $parent_query, $clause_key = '' ) { |
|
1336 global $wpdb; |
|
1337 |
|
1338 $sql_chunks = array( |
|
1339 'where' => array(), |
|
1340 'join' => array(), |
|
1341 ); |
|
1342 |
|
1343 if ( isset( $clause['compare'] ) ) { |
|
1344 $clause['compare'] = strtoupper( $clause['compare'] ); |
|
1345 } else { |
|
1346 $clause['compare'] = isset( $clause['value'] ) && is_array( $clause['value'] ) ? 'IN' : '='; |
|
1347 } |
|
1348 |
|
1349 if ( ! in_array( $clause['compare'], array( |
|
1350 '=', '!=', '>', '>=', '<', '<=', |
|
1351 'LIKE', 'NOT LIKE', |
|
1352 'IN', 'NOT IN', |
|
1353 'BETWEEN', 'NOT BETWEEN', |
|
1354 'EXISTS', 'NOT EXISTS', |
|
1355 'REGEXP', 'NOT REGEXP', 'RLIKE' |
|
1356 ) ) ) { |
|
1357 $clause['compare'] = '='; |
|
1358 } |
|
1359 |
|
1360 $meta_compare = $clause['compare']; |
|
1361 |
|
1362 // First build the JOIN clause, if one is required. |
|
1363 $join = ''; |
|
1364 |
|
1365 // We prefer to avoid joins if possible. Look for an existing join compatible with this clause. |
|
1366 $alias = $this->find_compatible_table_alias( $clause, $parent_query ); |
|
1367 if ( false === $alias ) { |
|
1368 $i = count( $this->table_aliases ); |
|
1369 $alias = $i ? 'mt' . $i : $this->meta_table; |
|
1370 |
|
1371 // JOIN clauses for NOT EXISTS have their own syntax. |
|
1372 if ( 'NOT EXISTS' === $meta_compare ) { |
|
1373 $join .= " LEFT JOIN $this->meta_table"; |
|
1374 $join .= $i ? " AS $alias" : ''; |
|
1375 $join .= $wpdb->prepare( " ON ($this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column AND $alias.meta_key = %s )", $clause['key'] ); |
|
1376 |
|
1377 // All other JOIN clauses. |
|
1378 } else { |
|
1379 $join .= " INNER JOIN $this->meta_table"; |
|
1380 $join .= $i ? " AS $alias" : ''; |
|
1381 $join .= " ON ( $this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column )"; |
|
1382 } |
|
1383 |
|
1384 $this->table_aliases[] = $alias; |
|
1385 $sql_chunks['join'][] = $join; |
|
1386 } |
|
1387 |
|
1388 // Save the alias to this clause, for future siblings to find. |
|
1389 $clause['alias'] = $alias; |
|
1390 |
|
1391 // Determine the data type. |
|
1392 $_meta_type = isset( $clause['type'] ) ? $clause['type'] : ''; |
|
1393 $meta_type = $this->get_cast_for_type( $_meta_type ); |
|
1394 $clause['cast'] = $meta_type; |
|
1395 |
|
1396 // Fallback for clause keys is the table alias. |
|
1397 if ( ! $clause_key ) { |
|
1398 $clause_key = $clause['alias']; |
|
1399 } |
|
1400 |
|
1401 // Ensure unique clause keys, so none are overwritten. |
|
1402 $iterator = 1; |
|
1403 $clause_key_base = $clause_key; |
|
1404 while ( isset( $this->clauses[ $clause_key ] ) ) { |
|
1405 $clause_key = $clause_key_base . '-' . $iterator; |
|
1406 $iterator++; |
|
1407 } |
|
1408 |
|
1409 // Store the clause in our flat array. |
|
1410 $this->clauses[ $clause_key ] =& $clause; |
|
1411 |
|
1412 // Next, build the WHERE clause. |
|
1413 |
|
1414 // meta_key. |
|
1415 if ( array_key_exists( 'key', $clause ) ) { |
|
1416 if ( 'NOT EXISTS' === $meta_compare ) { |
|
1417 $sql_chunks['where'][] = $alias . '.' . $this->meta_id_column . ' IS NULL'; |
|
1418 } else { |
|
1419 $sql_chunks['where'][] = $wpdb->prepare( "$alias.meta_key = %s", trim( $clause['key'] ) ); |
|
1420 } |
|
1421 } |
|
1422 |
|
1423 // meta_value. |
|
1424 if ( array_key_exists( 'value', $clause ) ) { |
|
1425 $meta_value = $clause['value']; |
|
1426 |
|
1427 if ( in_array( $meta_compare, array( 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN' ) ) ) { |
|
1428 if ( ! is_array( $meta_value ) ) { |
|
1429 $meta_value = preg_split( '/[,\s]+/', $meta_value ); |
|
1430 } |
|
1431 } else { |
|
1432 $meta_value = trim( $meta_value ); |
|
1433 } |
|
1434 |
|
1435 switch ( $meta_compare ) { |
|
1436 case 'IN' : |
|
1437 case 'NOT IN' : |
|
1438 $meta_compare_string = '(' . substr( str_repeat( ',%s', count( $meta_value ) ), 1 ) . ')'; |
|
1439 $where = $wpdb->prepare( $meta_compare_string, $meta_value ); |
|
1440 break; |
|
1441 |
|
1442 case 'BETWEEN' : |
|
1443 case 'NOT BETWEEN' : |
|
1444 $meta_value = array_slice( $meta_value, 0, 2 ); |
|
1445 $where = $wpdb->prepare( '%s AND %s', $meta_value ); |
|
1446 break; |
|
1447 |
|
1448 case 'LIKE' : |
|
1449 case 'NOT LIKE' : |
|
1450 $meta_value = '%' . $wpdb->esc_like( $meta_value ) . '%'; |
|
1451 $where = $wpdb->prepare( '%s', $meta_value ); |
|
1452 break; |
|
1453 |
|
1454 // EXISTS with a value is interpreted as '='. |
|
1455 case 'EXISTS' : |
|
1456 $meta_compare = '='; |
|
1457 $where = $wpdb->prepare( '%s', $meta_value ); |
|
1458 break; |
|
1459 |
|
1460 // 'value' is ignored for NOT EXISTS. |
|
1461 case 'NOT EXISTS' : |
|
1462 $where = ''; |
|
1463 break; |
|
1464 |
|
1465 default : |
|
1466 $where = $wpdb->prepare( '%s', $meta_value ); |
|
1467 break; |
|
1468 |
|
1469 } |
|
1470 |
|
1471 if ( $where ) { |
|
1472 $sql_chunks['where'][] = "CAST($alias.meta_value AS {$meta_type}) {$meta_compare} {$where}"; |
|
1473 } |
|
1474 } |
|
1475 |
|
1476 /* |
|
1477 * Multiple WHERE clauses (for meta_key and meta_value) should |
|
1478 * be joined in parentheses. |
|
1479 */ |
|
1480 if ( 1 < count( $sql_chunks['where'] ) ) { |
|
1481 $sql_chunks['where'] = array( '( ' . implode( ' AND ', $sql_chunks['where'] ) . ' )' ); |
|
1482 } |
|
1483 |
|
1484 return $sql_chunks; |
|
1485 } |
|
1486 |
|
1487 /** |
|
1488 * Get a flattened list of sanitized meta clauses. |
|
1489 * |
|
1490 * This array should be used for clause lookup, as when the table alias and CAST type must be determined for |
|
1491 * a value of 'orderby' corresponding to a meta clause. |
|
1492 * |
|
1493 * @since 4.2.0 |
|
1494 * @access public |
|
1495 * |
|
1496 * @return array Meta clauses. |
|
1497 */ |
|
1498 public function get_clauses() { |
|
1499 return $this->clauses; |
|
1500 } |
|
1501 |
|
1502 /** |
|
1503 * Identify an existing table alias that is compatible with the current |
|
1504 * query clause. |
|
1505 * |
|
1506 * We avoid unnecessary table joins by allowing each clause to look for |
|
1507 * an existing table alias that is compatible with the query that it |
|
1508 * needs to perform. |
|
1509 * |
|
1510 * An existing alias is compatible if (a) it is a sibling of `$clause` |
|
1511 * (ie, it's under the scope of the same relation), and (b) the combination |
|
1512 * of operator and relation between the clauses allows for a shared table join. |
|
1513 * In the case of {@see WP_Meta_Query}, this only applies to 'IN' clauses that |
|
1514 * are connected by the relation 'OR'. |
|
1515 * |
|
1516 * @since 4.1.0 |
|
1517 * @access protected |
|
1518 * |
|
1519 * @param array $clause Query clause. |
|
1520 * @param array $parent_query Parent query of $clause. |
|
1521 * @return string|bool Table alias if found, otherwise false. |
|
1522 */ |
|
1523 protected function find_compatible_table_alias( $clause, $parent_query ) { |
|
1524 $alias = false; |
|
1525 |
|
1526 foreach ( $parent_query as $sibling ) { |
|
1527 // If the sibling has no alias yet, there's nothing to check. |
|
1528 if ( empty( $sibling['alias'] ) ) { |
|
1529 continue; |
|
1530 } |
|
1531 |
|
1532 // We're only interested in siblings that are first-order clauses. |
|
1533 if ( ! is_array( $sibling ) || ! $this->is_first_order_clause( $sibling ) ) { |
|
1534 continue; |
|
1535 } |
|
1536 |
|
1537 $compatible_compares = array(); |
|
1538 |
|
1539 // Clauses connected by OR can share joins as long as they have "positive" operators. |
|
1540 if ( 'OR' === $parent_query['relation'] ) { |
|
1541 $compatible_compares = array( '=', 'IN', 'BETWEEN', 'LIKE', 'REGEXP', 'RLIKE', '>', '>=', '<', '<=' ); |
|
1542 |
|
1543 // Clauses joined by AND with "negative" operators share a join only if they also share a key. |
|
1544 } elseif ( isset( $sibling['key'] ) && isset( $clause['key'] ) && $sibling['key'] === $clause['key'] ) { |
|
1545 $compatible_compares = array( '!=', 'NOT IN', 'NOT LIKE' ); |
|
1546 } |
|
1547 |
|
1548 $clause_compare = strtoupper( $clause['compare'] ); |
|
1549 $sibling_compare = strtoupper( $sibling['compare'] ); |
|
1550 if ( in_array( $clause_compare, $compatible_compares ) && in_array( $sibling_compare, $compatible_compares ) ) { |
|
1551 $alias = $sibling['alias']; |
|
1552 break; |
|
1553 } |
|
1554 } |
|
1555 |
|
1556 /** |
|
1557 * Filter the table alias identified as compatible with the current clause. |
|
1558 * |
|
1559 * @since 4.1.0 |
|
1560 * |
|
1561 * @param string|bool $alias Table alias, or false if none was found. |
|
1562 * @param array $clause First-order query clause. |
|
1563 * @param array $parent_query Parent of $clause. |
|
1564 * @param object $this WP_Meta_Query object. |
|
1565 */ |
|
1566 return apply_filters( 'meta_query_find_compatible_table_alias', $alias, $clause, $parent_query, $this ) ; |
|
1567 } |
|
1568 } |
|
1569 |
|
1570 /** |
|
1571 * Retrieve the name of the metadata table for the specified object type. |
896 * Retrieve the name of the metadata table for the specified object type. |
1572 * |
897 * |
1573 * @since 2.9.0 |
898 * @since 2.9.0 |
1574 * |
899 * |
1575 * @global wpdb $wpdb WordPress database abstraction object. |
900 * @global wpdb $wpdb WordPress database abstraction object. |
1576 * |
901 * |
1577 * @param string $type Type of object to get metadata table for (e.g., comment, post, or user) |
902 * @param string $type Type of object to get metadata table for (e.g., comment, post, or user) |
1578 * @return mixed Metadata table name, or false if no metadata table exists |
903 * @return string|false Metadata table name, or false if no metadata table exists |
1579 */ |
904 */ |
1580 function _get_meta_table($type) { |
905 function _get_meta_table($type) { |
1581 global $wpdb; |
906 global $wpdb; |
1582 |
907 |
1583 $table_name = $type . 'meta'; |
908 $table_name = $type . 'meta'; |
1614 |
939 |
1615 /** |
940 /** |
1616 * Sanitize meta value. |
941 * Sanitize meta value. |
1617 * |
942 * |
1618 * @since 3.1.3 |
943 * @since 3.1.3 |
1619 * |
944 * @since 4.9.8 The `$object_subtype` parameter was added. |
1620 * @param string $meta_key Meta key |
945 * |
1621 * @param mixed $meta_value Meta value to sanitize |
946 * @param string $meta_key Meta key. |
1622 * @param string $meta_type Type of meta |
947 * @param mixed $meta_value Meta value to sanitize. |
1623 * @return mixed Sanitized $meta_value |
948 * @param string $object_type Type of object the meta is registered to. |
1624 */ |
949 * |
1625 function sanitize_meta( $meta_key, $meta_value, $meta_type ) { |
950 * @return mixed Sanitized $meta_value. |
|
951 */ |
|
952 function sanitize_meta( $meta_key, $meta_value, $object_type, $object_subtype = '' ) { |
|
953 if ( ! empty( $object_subtype ) && has_filter( "sanitize_{$object_type}_meta_{$meta_key}_for_{$object_subtype}" ) ) { |
|
954 |
|
955 /** |
|
956 * Filters the sanitization of a specific meta key of a specific meta type and subtype. |
|
957 * |
|
958 * The dynamic portions of the hook name, `$object_type`, `$meta_key`, |
|
959 * and `$object_subtype`, refer to the metadata object type (comment, post, term or user), |
|
960 * the meta key value, and the object subtype respectively. |
|
961 * |
|
962 * @since 4.9.8 |
|
963 * |
|
964 * @param mixed $meta_value Meta value to sanitize. |
|
965 * @param string $meta_key Meta key. |
|
966 * @param string $object_type Object type. |
|
967 * @param string $object_subtype Object subtype. |
|
968 */ |
|
969 return apply_filters( "sanitize_{$object_type}_meta_{$meta_key}_for_{$object_subtype}", $meta_value, $meta_key, $object_type, $object_subtype ); |
|
970 } |
1626 |
971 |
1627 /** |
972 /** |
1628 * Filter the sanitization of a specific meta key of a specific meta type. |
973 * Filters the sanitization of a specific meta key of a specific meta type. |
1629 * |
974 * |
1630 * The dynamic portions of the hook name, `$meta_type`, and `$meta_key`, |
975 * The dynamic portions of the hook name, `$meta_type`, and `$meta_key`, |
1631 * refer to the metadata object type (comment, post, or user) and the meta |
976 * refer to the metadata object type (comment, post, or user) and the meta |
1632 * key value, |
977 * key value, respectively. |
1633 * respectively. |
|
1634 * |
978 * |
1635 * @since 3.3.0 |
979 * @since 3.3.0 |
1636 * |
980 * |
1637 * @param mixed $meta_value Meta value to sanitize. |
981 * @param mixed $meta_value Meta value to sanitize. |
1638 * @param string $meta_key Meta key. |
982 * @param string $meta_key Meta key. |
1639 * @param string $meta_type Meta type. |
983 * @param string $object_type Object type. |
1640 */ |
984 */ |
1641 return apply_filters( "sanitize_{$meta_type}_meta_{$meta_key}", $meta_value, $meta_key, $meta_type ); |
985 return apply_filters( "sanitize_{$object_type}_meta_{$meta_key}", $meta_value, $meta_key, $object_type ); |
1642 } |
986 } |
1643 |
987 |
1644 /** |
988 /** |
1645 * Register meta key |
989 * Registers a meta key. |
|
990 * |
|
991 * It is recommended to register meta keys for a specific combination of object type and object subtype. If passing |
|
992 * an object subtype is omitted, the meta key will be registered for the entire object type, however it can be partly |
|
993 * overridden in case a more specific meta key of the same name exists for the same object type and a subtype. |
|
994 * |
|
995 * If an object type does not support any subtypes, such as users or comments, you should commonly call this function |
|
996 * without passing a subtype. |
1646 * |
997 * |
1647 * @since 3.3.0 |
998 * @since 3.3.0 |
1648 * |
999 * @since 4.6.0 {@link https://core.trac.wordpress.org/ticket/35658 Modified |
1649 * @param string $meta_type Type of meta |
1000 * to support an array of data to attach to registered meta keys}. Previous arguments for |
1650 * @param string $meta_key Meta key |
1001 * `$sanitize_callback` and `$auth_callback` have been folded into this array. |
1651 * @param string|array $sanitize_callback A function or method to call when sanitizing the value of $meta_key. |
1002 * @since 4.9.8 The `$object_subtype` argument was added to the arguments array. |
1652 * @param string|array $auth_callback Optional. A function or method to call when performing edit_post_meta, add_post_meta, and delete_post_meta capability checks. |
1003 * |
1653 */ |
1004 * @param string $object_type Type of object this meta is registered to. |
1654 function register_meta( $meta_type, $meta_key, $sanitize_callback, $auth_callback = null ) { |
1005 * @param string $meta_key Meta key to register. |
1655 if ( is_callable( $sanitize_callback ) ) |
1006 * @param array $args { |
1656 add_filter( "sanitize_{$meta_type}_meta_{$meta_key}", $sanitize_callback, 10, 3 ); |
1007 * Data used to describe the meta key when registered. |
1657 |
1008 * |
1658 if ( empty( $auth_callback ) ) { |
1009 * @type string $object_subtype A subtype; e.g. if the object type is "post", the post type. If left empty, |
1659 if ( is_protected_meta( $meta_key, $meta_type ) ) |
1010 * the meta key will be registered on the entire object type. Default empty. |
1660 $auth_callback = '__return_false'; |
1011 * @type string $type The type of data associated with this meta key. |
1661 else |
1012 * Valid values are 'string', 'boolean', 'integer', and 'number'. |
1662 $auth_callback = '__return_true'; |
1013 * @type string $description A description of the data attached to this meta key. |
1663 } |
1014 * @type bool $single Whether the meta key has one value per object, or an array of values per object. |
1664 |
1015 * @type string $sanitize_callback A function or method to call when sanitizing `$meta_key` data. |
1665 if ( is_callable( $auth_callback ) ) |
1016 * @type string $auth_callback Optional. A function or method to call when performing edit_post_meta, add_post_meta, and delete_post_meta capability checks. |
1666 add_filter( "auth_{$meta_type}_meta_{$meta_key}", $auth_callback, 10, 6 ); |
1017 * @type bool $show_in_rest Whether data associated with this meta key can be considered public. |
1667 } |
1018 * } |
|
1019 * @param string|array $deprecated Deprecated. Use `$args` instead. |
|
1020 * |
|
1021 * @return bool True if the meta key was successfully registered in the global array, false if not. |
|
1022 * Registering a meta key with distinct sanitize and auth callbacks will fire those |
|
1023 * callbacks, but will not add to the global registry. |
|
1024 */ |
|
1025 function register_meta( $object_type, $meta_key, $args, $deprecated = null ) { |
|
1026 global $wp_meta_keys; |
|
1027 |
|
1028 if ( ! is_array( $wp_meta_keys ) ) { |
|
1029 $wp_meta_keys = array(); |
|
1030 } |
|
1031 |
|
1032 $defaults = array( |
|
1033 'object_subtype' => '', |
|
1034 'type' => 'string', |
|
1035 'description' => '', |
|
1036 'single' => false, |
|
1037 'sanitize_callback' => null, |
|
1038 'auth_callback' => null, |
|
1039 'show_in_rest' => false, |
|
1040 ); |
|
1041 |
|
1042 // There used to be individual args for sanitize and auth callbacks |
|
1043 $has_old_sanitize_cb = false; |
|
1044 $has_old_auth_cb = false; |
|
1045 |
|
1046 if ( is_callable( $args ) ) { |
|
1047 $args = array( |
|
1048 'sanitize_callback' => $args, |
|
1049 ); |
|
1050 |
|
1051 $has_old_sanitize_cb = true; |
|
1052 } else { |
|
1053 $args = (array) $args; |
|
1054 } |
|
1055 |
|
1056 if ( is_callable( $deprecated ) ) { |
|
1057 $args['auth_callback'] = $deprecated; |
|
1058 $has_old_auth_cb = true; |
|
1059 } |
|
1060 |
|
1061 /** |
|
1062 * Filters the registration arguments when registering meta. |
|
1063 * |
|
1064 * @since 4.6.0 |
|
1065 * |
|
1066 * @param array $args Array of meta registration arguments. |
|
1067 * @param array $defaults Array of default arguments. |
|
1068 * @param string $object_type Object type. |
|
1069 * @param string $meta_key Meta key. |
|
1070 */ |
|
1071 $args = apply_filters( 'register_meta_args', $args, $defaults, $object_type, $meta_key ); |
|
1072 $args = wp_parse_args( $args, $defaults ); |
|
1073 |
|
1074 $object_subtype = ! empty( $args['object_subtype'] ) ? $args['object_subtype'] : ''; |
|
1075 |
|
1076 // If `auth_callback` is not provided, fall back to `is_protected_meta()`. |
|
1077 if ( empty( $args['auth_callback'] ) ) { |
|
1078 if ( is_protected_meta( $meta_key, $object_type ) ) { |
|
1079 $args['auth_callback'] = '__return_false'; |
|
1080 } else { |
|
1081 $args['auth_callback'] = '__return_true'; |
|
1082 } |
|
1083 } |
|
1084 |
|
1085 // Back-compat: old sanitize and auth callbacks are applied to all of an object type. |
|
1086 if ( is_callable( $args['sanitize_callback'] ) ) { |
|
1087 if ( ! empty( $object_subtype ) ) { |
|
1088 add_filter( "sanitize_{$object_type}_meta_{$meta_key}_for_{$object_subtype}", $args['sanitize_callback'], 10, 4 ); |
|
1089 } else { |
|
1090 add_filter( "sanitize_{$object_type}_meta_{$meta_key}", $args['sanitize_callback'], 10, 3 ); |
|
1091 } |
|
1092 } |
|
1093 |
|
1094 if ( is_callable( $args['auth_callback'] ) ) { |
|
1095 if ( ! empty( $object_subtype ) ) { |
|
1096 add_filter( "auth_{$object_type}_meta_{$meta_key}_for_{$object_subtype}", $args['auth_callback'], 10, 6 ); |
|
1097 } else { |
|
1098 add_filter( "auth_{$object_type}_meta_{$meta_key}", $args['auth_callback'], 10, 6 ); |
|
1099 } |
|
1100 } |
|
1101 |
|
1102 // Global registry only contains meta keys registered with the array of arguments added in 4.6.0. |
|
1103 if ( ! $has_old_auth_cb && ! $has_old_sanitize_cb ) { |
|
1104 unset( $args['object_subtype'] ); |
|
1105 |
|
1106 $wp_meta_keys[ $object_type ][ $object_subtype ][ $meta_key ] = $args; |
|
1107 |
|
1108 return true; |
|
1109 } |
|
1110 |
|
1111 return false; |
|
1112 } |
|
1113 |
|
1114 /** |
|
1115 * Checks if a meta key is registered. |
|
1116 * |
|
1117 * @since 4.6.0 |
|
1118 * @since 4.9.8 The `$object_subtype` parameter was added. |
|
1119 * |
|
1120 * @param string $object_type The type of object. |
|
1121 * @param string $meta_key The meta key. |
|
1122 * @param string $object_subtype Optional. The subtype of the object type. |
|
1123 * |
|
1124 * @return bool True if the meta key is registered to the object type and, if provided, |
|
1125 * the object subtype. False if not. |
|
1126 */ |
|
1127 function registered_meta_key_exists( $object_type, $meta_key, $object_subtype = '' ) { |
|
1128 $meta_keys = get_registered_meta_keys( $object_type, $object_subtype ); |
|
1129 |
|
1130 return isset( $meta_keys[ $meta_key ] ); |
|
1131 } |
|
1132 |
|
1133 /** |
|
1134 * Unregisters a meta key from the list of registered keys. |
|
1135 * |
|
1136 * @since 4.6.0 |
|
1137 * @since 4.9.8 The `$object_subtype` parameter was added. |
|
1138 * |
|
1139 * @param string $object_type The type of object. |
|
1140 * @param string $meta_key The meta key. |
|
1141 * @param string $object_subtype Optional. The subtype of the object type. |
|
1142 * @return bool True if successful. False if the meta key was not registered. |
|
1143 */ |
|
1144 function unregister_meta_key( $object_type, $meta_key, $object_subtype = '' ) { |
|
1145 global $wp_meta_keys; |
|
1146 |
|
1147 if ( ! registered_meta_key_exists( $object_type, $meta_key, $object_subtype ) ) { |
|
1148 return false; |
|
1149 } |
|
1150 |
|
1151 $args = $wp_meta_keys[ $object_type ][ $object_subtype ][ $meta_key ]; |
|
1152 |
|
1153 if ( isset( $args['sanitize_callback'] ) && is_callable( $args['sanitize_callback'] ) ) { |
|
1154 if ( ! empty( $object_subtype ) ) { |
|
1155 remove_filter( "sanitize_{$object_type}_meta_{$meta_key}_for_{$object_subtype}", $args['sanitize_callback'] ); |
|
1156 } else { |
|
1157 remove_filter( "sanitize_{$object_type}_meta_{$meta_key}", $args['sanitize_callback'] ); |
|
1158 } |
|
1159 } |
|
1160 |
|
1161 if ( isset( $args['auth_callback'] ) && is_callable( $args['auth_callback'] ) ) { |
|
1162 if ( ! empty( $object_subtype ) ) { |
|
1163 remove_filter( "auth_{$object_type}_meta_{$meta_key}_for_{$object_subtype}", $args['auth_callback'] ); |
|
1164 } else { |
|
1165 remove_filter( "auth_{$object_type}_meta_{$meta_key}", $args['auth_callback'] ); |
|
1166 } |
|
1167 } |
|
1168 |
|
1169 unset( $wp_meta_keys[ $object_type ][ $object_subtype ][ $meta_key ] ); |
|
1170 |
|
1171 // Do some clean up |
|
1172 if ( empty( $wp_meta_keys[ $object_type ][ $object_subtype ] ) ) { |
|
1173 unset( $wp_meta_keys[ $object_type ][ $object_subtype ] ); |
|
1174 } |
|
1175 if ( empty( $wp_meta_keys[ $object_type ] ) ) { |
|
1176 unset( $wp_meta_keys[ $object_type ] ); |
|
1177 } |
|
1178 |
|
1179 return true; |
|
1180 } |
|
1181 |
|
1182 /** |
|
1183 * Retrieves a list of registered meta keys for an object type. |
|
1184 * |
|
1185 * @since 4.6.0 |
|
1186 * @since 4.9.8 The `$object_subtype` parameter was added. |
|
1187 * |
|
1188 * @param string $object_type The type of object. Post, comment, user, term. |
|
1189 * @param string $object_subtype Optional. The subtype of the object type. |
|
1190 * @return array List of registered meta keys. |
|
1191 */ |
|
1192 function get_registered_meta_keys( $object_type, $object_subtype = '' ) { |
|
1193 global $wp_meta_keys; |
|
1194 |
|
1195 if ( ! is_array( $wp_meta_keys ) || ! isset( $wp_meta_keys[ $object_type ] ) || ! isset( $wp_meta_keys[ $object_type ][ $object_subtype ] ) ) { |
|
1196 return array(); |
|
1197 } |
|
1198 |
|
1199 return $wp_meta_keys[ $object_type ][ $object_subtype ]; |
|
1200 } |
|
1201 |
|
1202 /** |
|
1203 * Retrieves registered metadata for a specified object. |
|
1204 * |
|
1205 * The results include both meta that is registered specifically for the |
|
1206 * object's subtype and meta that is registered for the entire object type. |
|
1207 * |
|
1208 * @since 4.6.0 |
|
1209 * |
|
1210 * @param string $object_type Type of object to request metadata for. (e.g. comment, post, term, user) |
|
1211 * @param int $object_id ID of the object the metadata is for. |
|
1212 * @param string $meta_key Optional. Registered metadata key. If not specified, retrieve all registered |
|
1213 * metadata for the specified object. |
|
1214 * @return mixed A single value or array of values for a key if specified. An array of all registered keys |
|
1215 * and values for an object ID if not. False if a given $meta_key is not registered. |
|
1216 */ |
|
1217 function get_registered_metadata( $object_type, $object_id, $meta_key = '' ) { |
|
1218 $object_subtype = get_object_subtype( $object_type, $object_id ); |
|
1219 |
|
1220 if ( ! empty( $meta_key ) ) { |
|
1221 if ( ! empty( $object_subtype ) && ! registered_meta_key_exists( $object_type, $meta_key, $object_subtype ) ) { |
|
1222 $object_subtype = ''; |
|
1223 } |
|
1224 |
|
1225 if ( ! registered_meta_key_exists( $object_type, $meta_key, $object_subtype ) ) { |
|
1226 return false; |
|
1227 } |
|
1228 |
|
1229 $meta_keys = get_registered_meta_keys( $object_type, $object_subtype ); |
|
1230 $meta_key_data = $meta_keys[ $meta_key ]; |
|
1231 |
|
1232 $data = get_metadata( $object_type, $object_id, $meta_key, $meta_key_data['single'] ); |
|
1233 |
|
1234 return $data; |
|
1235 } |
|
1236 |
|
1237 $data = get_metadata( $object_type, $object_id ); |
|
1238 if ( ! $data ) { |
|
1239 return array(); |
|
1240 } |
|
1241 |
|
1242 $meta_keys = get_registered_meta_keys( $object_type ); |
|
1243 if ( ! empty( $object_subtype ) ) { |
|
1244 $meta_keys = array_merge( $meta_keys, get_registered_meta_keys( $object_type, $object_subtype ) ); |
|
1245 } |
|
1246 |
|
1247 return array_intersect_key( $data, $meta_keys ); |
|
1248 } |
|
1249 |
|
1250 /** |
|
1251 * Filter out `register_meta()` args based on a whitelist. |
|
1252 * `register_meta()` args may change over time, so requiring the whitelist |
|
1253 * to be explicitly turned off is a warranty seal of sorts. |
|
1254 * |
|
1255 * @access private |
|
1256 * @since 4.6.0 |
|
1257 * |
|
1258 * @param array $args Arguments from `register_meta()`. |
|
1259 * @param array $default_args Default arguments for `register_meta()`. |
|
1260 * |
|
1261 * @return array Filtered arguments. |
|
1262 */ |
|
1263 function _wp_register_meta_args_whitelist( $args, $default_args ) { |
|
1264 return array_intersect_key( $args, $default_args ); |
|
1265 } |
|
1266 |
|
1267 /** |
|
1268 * Returns the object subtype for a given object ID of a specific type. |
|
1269 * |
|
1270 * @since 4.9.8 |
|
1271 * |
|
1272 * @param string $object_type Type of object to request metadata for. (e.g. comment, post, term, user) |
|
1273 * @param int $object_id ID of the object to retrieve its subtype. |
|
1274 * @return string The object subtype or an empty string if unspecified subtype. |
|
1275 */ |
|
1276 function get_object_subtype( $object_type, $object_id ) { |
|
1277 $object_id = (int) $object_id; |
|
1278 $object_subtype = ''; |
|
1279 |
|
1280 switch ( $object_type ) { |
|
1281 case 'post': |
|
1282 $post_type = get_post_type( $object_id ); |
|
1283 |
|
1284 if ( ! empty( $post_type ) ) { |
|
1285 $object_subtype = $post_type; |
|
1286 } |
|
1287 break; |
|
1288 |
|
1289 case 'term': |
|
1290 $term = get_term( $object_id ); |
|
1291 if ( ! $term instanceof WP_Term ) { |
|
1292 break; |
|
1293 } |
|
1294 |
|
1295 $object_subtype = $term->taxonomy; |
|
1296 break; |
|
1297 |
|
1298 case 'comment': |
|
1299 $comment = get_comment( $object_id ); |
|
1300 if ( ! $comment ) { |
|
1301 break; |
|
1302 } |
|
1303 |
|
1304 $object_subtype = 'comment'; |
|
1305 break; |
|
1306 |
|
1307 case 'user': |
|
1308 $user = get_user_by( 'id', $object_id ); |
|
1309 if ( ! $user ) { |
|
1310 break; |
|
1311 } |
|
1312 |
|
1313 $object_subtype = 'user'; |
|
1314 break; |
|
1315 } |
|
1316 |
|
1317 /** |
|
1318 * Filters the object subtype identifier for a non standard object type. |
|
1319 * |
|
1320 * The dynamic portion of the hook, `$object_type`, refers to the object |
|
1321 * type (post, comment, term, or user). |
|
1322 * |
|
1323 * @since 4.9.8 |
|
1324 * |
|
1325 * @param string $object_subtype Empty string to override. |
|
1326 * @param int $object_id ID of the object to get the subtype for. |
|
1327 */ |
|
1328 return apply_filters( "get_object_subtype_{$object_type}", $object_subtype, $object_id ); |
|
1329 } |