PageRenderTime 56ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/wp-includes/meta.php

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