WP_Meta_Query{} WP 3.2.0

Клас створює JOIN і WHERE частини SQL запиту, які на додаток до основного запиту фільтруватимуть результат за вказаним ключем і значенням метаполя.

WP_Meta_Query – це допоміжний клас, який використовується в комбінації з класами: WP_Comment_Query та допомагає їм фільтрувати результат на основі вказаних метаданих.

Для WP_Meta_Query є функція обгортка: get_meta_sql()

нотатки
  1. Результат не додає пробіли по краях рядків до JOIN та WHERE. Тому потрібно додати пробіли вручну, щоб не пошкодити SQL запит.

  2. Якщо ви використовуєте valueі compare, то результат WHERE запиту буде виглядати якось так:
    AND (wp_postmeta.meta_key = 'foo_key' OR (mt1.meta_key = 'bar_key' AND CAST(mt1.meta_value AS CHAR) LIKE '%foo%') )

    Зверніть увагу, що mt1 тут – це аліас мета таблиці, а значить wp_postmeta.meta_key та mt1 це різні таблиці. Тому в JOIN частині вам потрібно двічі об’єднати мета таблиці з основною, це виглядає так:

    INNER JOIN wp_postmeta ON wp_posts.ID = wp_postmeta.post_id INNER JOIN wp_postmeta AS mt1 ON (wp_posts.ID = mt1.post_id)


Використання

Варіант 1:

$mq = new WP_Meta_Query( $meta_query );

Варіант 2:

$mq = New WP_Meta_Query();
$mq->parse_query_vars( $meta_query );


Аргументи параметра $meta_query

$meta_query передається у клас і основі цих даних будуються частини запиту.

$meta_query це масив, де кожен елемент є масивом із параметрами окремого запиту, тобто. це масив масивів. Така конструкція дозволяє створювати численні запити. Ось як це виглядає:

$meta_query = [
	'relation' => 'OR', // за умовчанням 'AND'

	// Перший мета запит
	'Ключ для orderby' => [
		'key' => 'color',
		'value' => 'blue',
		'compare' => '!=', // за умовчанням '='
	],

	// Другий мета запит
	[
		'key' => 'price',
		'value' => 20,
		'compare' => '>',
		'type' => 'NUMERIC', // за замовчуванням 'CHAR'
	],

	// Третій мета запит. і т.д...
];

Якщо вказати ключ для вкладеного масиву, цей ключ можна використовувати в параметрі ‘orderby’ в батьківському запиті для сортування за поточним метаполем.

relation
(рядок)

Визначає логічний зв’язок між вкладеними масивами. Цей параметр вказується як рядок у головному масиві meta_queryта вказує як порівнювати між собою вкладені параметри запиту (вкладені масиви з параметрами запиту). Може бути:

  • OR– вибрати мета-поля відповідні хоти під один масив параметрів;
  • AND(за замовчуванням) – вибрати мета поля, що підходять для всіх масивів параметрів.

    • key (рядок | масив)
      Ключ поля. Можна вказати кілька ключів у масиві.

    • compare_key (рядок) (з версії 5.1)
      Як порівнювати вказане в key значення. Може бути:

      • =– Так само.
      • !=– не дорівнює. (WP 5.3)
      • LIKE– Містить зазначену підрядок.
      • NOT LIKE– НЕ містить зазначений підрядок. (WP 5.3)
      • IN– дорівнює одному зі значень. (WP 5.3)
      • NOT IN– НЕ одно одному зі значень. (WP 5.3) (WP 5.3)
      • REGEXP– збігається з регулярним виразом. (WP 5.3)
      • NOT REGEXP– НЕ збігається з регулярним виразом. (WP 5.3)
      • RLIKE– синонім REGEXP. (WP 5.3)
      • EXISTS-Аліас =. (WP 5.3)
      • NOT EXISTS-Аліас !=. (WP 5.3)

      За замовчуванням: ‘=’, або ‘IN’, коли $key містить масив

    • type_key (рядок) (з версії 5.3)
      Тип даних MySQL, який потрібно використовувати функції CAST(). Якщо вказано, то значення колонки meta_keyбуде спочатку перетворено на вказаний тип і тільки потім порівнюватиметься з вказаним значенням. Може бути:

      • BINARY– потрібно коли у регулярних висловлюваннях важливий регістр символів.

      За замовчуванням: ”

    • value (рядок/масив)
      Значення поля. Вказувати не потрібно, якщо у порівнянні використовується: EXISTSабо NOT EXISTS(з версії 3.9).

    • compare (рядок)
      Як порівнювати вказане значення.
      Може бути:

      • =– Так само.
      • !=– не дорівнює.
      • >– Більше.
      • >=– більше або дорівнює.
      • <– менше.
      • <=– менше або дорівнює.
      • LIKE– вказаний у value підрядок є в рядку. Як ‘%значення%’ у запиті sql.
      • NOT LIKE– теж що LIKE тільки навпаки.
      • IN– у value вказуються кілька значень у масиві і пошук йде хоча б за одним із значень.
      • NOT IN– будь-яке значення, крім тих, що зазначені у вигляді масиву в value.
      • BETWEEN– у value вказуються 2 значення масиві: більше і менше і пошук йде між цих значень. наприклад: ‘value’ => array(5, 25) – від 5 до 25 (включно).
      • NOT BETWEEN– будь-яке значення, за межами діапазону вказаного у value, наприклад: ‘value’ => array(5, 25) – менше 5 і більше 25.
      • EXISTS– виведе все, де існує вказаний в ‘key’ ключ. ‘value’ не вказується у разі. (З версії 3.5)
      • NOT EXISTS– виведе все, де вказаний в ‘key’ ключ не існує. ‘value’ не вказується у разі. (З версії 3.5)
      • REGEXP– у value вказується регулярне вираз пошуку, наприклад: ‘value’ => ‘^bar’ , (знайде рядок: ‘bar is’). (З версії 3.7)
      • NOT REGEXP– у value вказується регулярний вираз для пошуку, знайде все, що не входить до цього виразу. (З версії 3.7)
      • RLIKE– синонім REGEXP. (З версії 3.7)

      За замовчуванням: ‘=’ або ‘IN’, коли вказівник вказує масив.

    • type (рядок)
      Тип даних, у який потрібно перевести value для порівняння. Якщо, наприклад, у полі вказуються тільки числа, потрібно використовувати NUMERIC , щоб числа не порівнювалися як рядки.

      Зазначений тут тип безпосередньо передається в функцію mysql CAST() .

      Може бути:

      • NUMERIC– цілі числа. Можна вказати точність DECIMAL(p,s).
      • DECIMAL– дробові числа. Можна вказати точність: DECIMAL(p,s). Наприклад: DECIMAL(5,2) – число з макс 5 цифрами всього (3 до роздільника) та 2 цифри після роздільника.
      • SIGNED– цілі числа, позитивні та негативні
      • UNSIGNED– цілі числа, лише позитивні
      • CHAR– рядок не чутливий до регістру
      • BINARY– рядок чутливий до регістру
      • DATETIME– дата і час
      • DATE– лише дата
      • TIME– Тільки час

      Якщо вказати будь-яке інше значення (або взагалі не вказати значення), воно перетвориться на CHAR.

      За замовчуванням:CHAR .

    Тип DATEпрацює при порівнянні BETWEENтільки якщо дата вказується у форматі Y-m-dта порівнюється з аналогічним форматом. Для типу DATETIMEце формат Y-m-d H:i:s.


Використання

Є 2 варіанти передати параметри до класу:

#1. Передача параметрів у конструктор

У цьому випадку ми передаємо масив параметрів у сам клас.

$meta_query = [
	'relation' => 'OR', // необов'язково, за умовчанням 'AND'
	[
		'key' => 'key_name',
		'value' => 'значення поля',
		'compare' => '=' // не обов'язково, за умовчанням '=' або 'IN' (якщо value масив)
	],
];

$mq = new WP_Meta_Query( $meta_query );

#2. Передача параметрів метод parse_query_vars()

parse_query_vars() потрібен, коли використовується “простий” спосіб передачі параметрів: як одномірного масиву. Або якщо ви не впевнені, як будуть передаватися параметри і вони можуть передаватися у вигляді одновимірного масиву.

У цьому випадку кожен параметр може починатися з ‘meta_*’ і такий параметр має бути в першому масиві:

$meta_query = array(
	'relation' => 'OR',

	'meta_key' => 'ключ',
	'meta_value' => 'значення',
	'meta_type' => 'CHAR',
	'meta_compare' => '!=',

	// Тут може бути ще один вкладений масив властивостей
	array(
		'key' => 'ключ',
		'value' => 'значення',
	)
);

$mq = New WP_Meta_Query();
$mq->parse_query_vars( $meta_query );

Отримання SQL запиту

Після того, як параметри передані, SQL запит можна отримати за допомогою методу get_sql():

$mq_sql = $mq->get_sql( $type, $primary_table, $primary_id_column, $context );
$type
(рядок) (обов’язковий)
Тип метаданих. Наприклад: ‘user’, ‘post’, ‘comment’.

В об’єкті
$wpdb має бути зареєстрована таблиця з назвою:
$type .'meta'наприклад, якщо ми вкажемо тут ‘foo’, то має існувати таблиця
$wpdb->foometa . Якщо такої таблиці немає, цей клас працювати не буде!
$primary_table
(рядок) (обов’язковий)
Назва основної таблиці, до якої належить таблиця метаданих. Наприклад
wp_posts ,
wp_comments ,
wp_users .
$primary_id_column
(рядок) (обов’язковий)
Назва ключової стовпчика основної таблиці, вказаної в
$primary_table . Для
wp_posts
ID , для
wp_users
ID , для
wp_comments
comment_ID .
$context
(об’єкт)
Об’єкт основного запиту. Цей параметр ніде не використовується, а передається у фільтр
get_meta_sql .


Типово: null

Якщо з якихось причин функція не змогла створити правильний запит, вона поверне false.

В інших випадках повернеться масив такого виду:

array(
	'join' => 'JOIN SQL рядок',
	'where' => 'WHERE SQL рядок'
)


Приклади

0

#1 Демонстрація використання

$meta_query = array(
	'relation' => 'OR', // необов'язково, за умовчанням 'AND'
	array(
		'key' => 'key_name',
		'value' => 'значення поля',
		'compare' => '=' // не обов'язково, за умовчанням '=' або 'IN' (якщо value масив)
	)
);

$query_obj = новий WP_Meta_Query( $meta_query );

$mq_sql = $query_obj->get_sql( 'post', 'wp_posts', 'ID' );

// використовуємо в основному запит
$mq_sql['join'];
$mq_sql['where'];

У результаті $mq_sql міститиме:

Array
(
	[join] => INNER JOIN wp_postmeta ON ( wp_posts.ID = wp_postmeta.post_id )
	[where] => AND (
		( wp_postmeta.meta_key = 'key_name' AND CAST(wp_postmeta.meta_value AS CHAR) = 'значення поля' )
	)
)
0

#2 Запит з одновимірним масивом $meta_query

$meta_query можна вказувати дані запиту не як вкладений масив, а відразу в поточному масиві. Такі дані треба передувати ‘meta_*’ і передавати їх потрібно метод parse_query_vars( $meta_query ); :

Цей приклад створить запит лише з урахуванням ключа. Якщо потрібен повний запит, додайте meta_value і meta_compare:

Global $wpdb;

$meta_query = array(
	'meta_key' => 'some_key_name',
);
$mq_obj = новий WP_Meta_Query();

$mq_obj->parse_query_vars( $meta_query );

$mq_sql = $mq_obj->get_sql( 'post', $wpdb->posts, 'ID' );

$mq_sql міститиме:

Array (
	[join] => INNER JOIN wp_postmeta ON ( wp_posts.ID = wp_postmeta.post_id )
	[where] => AND ( wp_postmeta.meta_key = 'some_key_name' )
)
0

#3 Складний запит із багатовимірним масивом $meta_query

Цей приклад показує як WP_Meta_Query обробляє змінну ‘meta_query’, яка вказана у загальних параметрах масиву та містить дані мета запитів.

$ args = array (
	'post_type' => 'mypost',
	'meta_query' => array(
		'relation' => 'OR',
		array(
			'key' => 'foo_key',
			'value' => 'foo',
			'compare' => 'LIKE',
		),
		array(
			'key' => 'bar_key',
		),
	)
);
$meta_query = новий WP_Meta_Query();

$meta_query->parse_query_vars( $args );

$mq_sql = $meta_query->get_sql( 'post', $wpdb->posts, 'ID' );

У результаті $mq_sql міститиме:

Array(

	[join] => INNER JOIN wp_postmeta ON ( wp_posts.ID = wp_postmeta.post_id )
	[where] => AND (
		( wp_postmeta.meta_key = 'foo_key' AND CAST(wp_postmeta.meta_value AS CHAR) LIKE '%foo%' )
		OR
		wp_postmeta.meta_key = 'bar_key'
	)
)
0

#4 Складний багаторівневий запит

Окрім вкладених масивів, можна задавати і дві вкладеності, змінюючи оператор relation, тим самим впливаючи на залежність умов.

Наприклад, нам потрібно отримати мішки серогокольору, вагою 200кг, в яких знаходиться бетонабо матеріал не вказаний:

$args = [
	'post_type' => 'mypost',
	'meta_query' => array(
		'relation' => 'AND', // це за умовчанням - тут для наочності...
		array(
			'relation' => 'OR',
			array(
				'key' => 'material',
				'value' => 'бетон',
			),
			array(
				'key' => 'material',
				'compare' => 'NOT EXISTS'
			),
		),
		array(
			'key' => 'color',
			'value' => 'сірий',
		),
		array(
			'key' => 'weight',
			'value' => '200',
		),
	)
];
$meta_query = новий WP_Meta_Query();

$meta_query->parse_query_vars( $args );

$mq_sql = $meta_query->get_sql( 'post', $wpdb->posts, 'ID' );

$mq_sql міститиме:

Array
(
	[join] => LEFT JOIN wp_postmeta ON ( wp_posts.ID = wp_postmeta.post_id )
			   LEFT JOIN wp_postmeta AS mt1 ON (wp_posts.ID = mt1.post_id AND mt1.meta_key = 'material' )
			   LEFT JOIN wp_postmeta AS mt2 ON (wp_posts.ID = mt2.post_id)
			   LEFT JOIN wp_postmeta AS mt3 ON (wp_posts.ID = mt3.post_id)
	[where] => AND (
  (
	( wp_postmeta.meta_key = 'material' AND wp_postmeta.meta_value = 'бетон' )
	OR
	mt1.post_id IS NULL
  )
  AND
  ( mt2.meta_key = 'color' AND mt2.meta_value = 'сірий' )
  AND
  ( mt3.meta_key = 'weight' AND mt3.meta_value = '200' )
)
)
0

#5 Ще приклади

Можна переглянути в описі параметра meta_query у WP_Query.


Властивості класу

$queries
(масив)
Містить параметри запиту
$meta_query . Опис параметрів
$meta_query .
$relation
(рядок)
Зв’язок між кількома вказаними параметрами запитів. Можливо ‘AND’ або ‘OR’.
$meta_table
(рядок)
Назва таблиці метаданих. Наприклад: wp_postmeta.
$meta_id_column
(рядок)
Назва ключової колонки – мета таблиці, вказаної в
$meta_table . Пр. meta_id чи umeta_id (users).
$primary_table
(рядок)
Назва основної таблиці, до якої належить таблиця метаданих. Наприклад, wp_posts, wp_comments, wp_users.
$primary_id_column
(рядок)
Назва ключової стовпчика основної таблиці, вказаної в
$primary_table . Для wp_posts – ID, для wp_users – ID, для wp_comments – comment_ID.
$table_aliases
(масив) (protected)
Службова властивість. Список альтернативних назв таблиць, що використовуються в JOIN.
$clauses
(масив) (protected)
Службова властивість. Список окремих елементів запиту. У ключі вказується ‘name’.
$has_or_relation
(логічний) (protected)
Службова властивість. Чи містить запит хоч одну OR зв’язок.


Типово: false


Методи класу

__construct( $meta_query = false )
Конструктор.


Повертає: нічого
sanitize_query( $queries )
Перевіряє параметри ‘meta_query’. Виправляє якщо потрібно: прибирає порожні значення у перевіряє що параметр ‘relation’ встановлений.


Повертає чистий масив параметрів.
is_first_order_clause( $query )
(protected)
Перевіряє, чи встановлений у параметрах запит першим мета запитом. Першим вважається будь-яка перша група параметрів із ключем key або value. $query передаються
параметри.


Повертає: true/false
parse_query_vars( $qv )
Виправляє параметри стандартних на основі переданих
meta_* параметрів.
$qv – масив параметрів запиту.


Повертає: нічого
get_cast_for_type( $type = ” )
Отримує відповідний альт. назву для переданого типу, якщо є.
$type – рядок тип який потрібно перекласти value.


Повертає: рядок, MySQL тип
get_sql( $type, $primary_table, $primary_id_column, $context = null )
Створює частини SQL запиту на основі встановлених параметрів, які потрібно додати до основного запиту. Це основна функція, яка повертає результати роботи всього класу. Опис параметрів див. під шаблоном використання.


Повертає: false/масив. Масив містить JOIN та WHERE частини для використання у SQL запиті. Пр: array( join => рядок, where => рядок )
get_sql_clauses()
(protected)
Службовий метод. Генерує частини SQL запиту. Використовується у методі
get_sql() .


Повертає: масив: array( join => рядок, where => рядок )
get_sql_for_query( & $query, $depth = 0 )
(protected)
Службовий метод. Генерує частини для окремого запиту SQL. Якщо зазначені запити, цей метод рекурсивно обробить кожен із них.
$query – запит, який потрібно обробити.
$depth – рівень вкладеності, на якому зараз перебуваємо.


Повертає: масив: array( join => рядок, where => рядок )
get_sql_for_clause( & $clause, $parent_query, $clause_key = ” )
Створює SQL JOIN та WHERE частини запиту для першого умови запиту.


Повертає: масив: array( join => рядок, where => рядок )
get_clauses()
Повертає властивість
$clauses .


Повертає: масив мета частин запиту.
find_compatible_table_alias( $clause, $parent_query )
(protected)
Службовий метод. Визначає аліас таблиці, що відноситься до
$clause .


Повертає: рядок/false
has_or_relation()
Повертає властивість
has_or_relation() ;


Повертає: true/false

список змін

З версії 3.2.0Введено.

Код WP_Meta_Query {} WP 6.0.2

class WP_Meta_Query {
	/**
	 * Array of metadata queries.
	 *
	 * See WP_Meta_Query::__construct() for information on meta query arguments.
	 *
	 * @ Since 3.2.0
	 * @var array
	 */
	$queries = array();

	/**
	 * The relation between the queries. Can be one of 'AND' or 'OR'.
	 *
	 * @ Since 3.2.0
	 * @var string
	 */
	public $relation;

	/**
	 * Database table to query for the metadata.
	 *
	 * @ Since 4.1.0
	 * @var string
	 */
	public $meta_table;

	/**
	 * Column in meta_table that represents the ID of the object the metadata belongs to.
	 *
	 * @ Since 4.1.0
	 * @var string
	 */
	public $meta_id_column;

	/**
	 * Database table that where the metadata's objects are stored (eg $wpdb->users).
	 *
	 * @ Since 4.1.0
	 * @var string
	 */
	public $primary_table;

	/**
	 * Column in primary_table that represents the ID of the object.
	 *
	 * @ Since 4.1.0
	 * @var string
	 */
	public $primary_id_column;

	/**
	 * A flat list of table aliases used в JOIN clauses.
	 *
	 * @ Since 4.1.0
	 * @var array
	 */
	protected $table_aliases = array();

	/**
	 * A flat list of clauses, keyed by clause 'name'.
	 *
	 * @ Since 4.2.0
	 * @var array
	 */
	protected $clauses = array();

	/**
	 * Whether the query contains any OR relations.
	 *
	 * @ Since 4.3.0
	 * @var bool
	 */
	protected $has_or_relation = false;

	/**
	 * Constructor.
	 *
	 * @ Since 3.2.0
	 * @since 4.2.0 Introduced support for naming query clauses by associative array keys.
	 * @since 5.1.0 Introduced `$compare_key` clause parameter, який enables LIKE key matches.
	 * @since 5.3.0 Increased number of operators available to `$compare_key`. Introduced `$type_key`,
	 * які обумовлюють '$key' для того, щоб cast to a new data type for comparisons.
	 *
	 * @param array $meta_query {
	 * Array of meta query clauses. Коли перші-ордери clauses або sub-clauses use strings as
	 * їх array keys, they may be referenced in the 'orderby' parameter of the parent query.
	 *
	 * @type string $relation Optional. MySQL keyword використовує ключ до ключових слів.
	 * Accepts 'AND' or 'OR'. Default 'AND'.
	 * @type array ...$0 {
	 * Optional. На array of first-order clause parameters, або інші fully-formed meta query.
	 *
	 * @type string|string[] $key Meta key or keys to filter by.
	 * @type string $compare_key MySQL operator використаний для comparing the $key. Accepts:
	 * - '='
	 * - '!='
	 * - 'LIKE'
	 * - 'NOT LIKE'
	 * - 'IN'
	 * - 'NOT IN'
	 * - 'REGEXP'
	 * - 'NOT REGEXP'
	 * - 'RLIKE',
	 * - 'EXISTS' (alias of '=')
	 * - 'NOT EXISTS' (alias of '!=')
	 * Default is 'IN' when `$key` is an array, '=' otherwise.
	 * @type string $type_key MySQL data type that the meta_key column will be CAST to for
	 * comparisons. Прийоми 'BINARY' для випадку-sensitive regular expression
	 * comparisons. Default is ''.
	 * @type string|string[] $value Meta value or values ​​to filter by.
	 * @type string $compare MySQL operator використаний для comparing the $value. Accepts:
	 * - '=',
	 * - '!='
	 * - '>'
	 * - '>='
	 * - '<'
	 * - '<='
	 * - 'LIKE'
	 * - 'NOT LIKE'
	 * - 'IN'
	 * - 'NOT IN'
	 * - 'BETWEEN'
	 * - 'NOT BETWEEN'
	 * - 'REGEXP'
	 * - 'NOT REGEXP'
	 * - 'RLIKE'
	 * - 'EXISTS'
	 * - 'NOT EXISTS'
	 * Default is 'IN' when `$value` is an array, '=' otherwise.
	 * @type string $type MySQL data type that the meta_value column will be CAST to for
	 * comparisons. Accepts:
	 * - 'NUMERIC'
	 * - 'BINARY'
	 * - 'CHAR'
	 * - 'DATE'
	 * - 'DATETIME'
	 * - 'DECIMAL'
	 * - 'SIGNED'
	 * - 'TIME'
	 * - 'UNSIGNED'
	 * Default is 'CHAR'.
	 * }
	 * }
	 */
	public function __construct( $meta_query = false ) {
		if ( ! $meta_query ) {
			return;
		}

		if ( isset( $meta_query['relation'] ) && 'OR' === strtoupper( $meta_query['relation'] ) ) {
			$this->relation = 'OR';
		} else {
			$this->relation = 'AND';
		}

		$this->queries = $this->sanitize_query( $meta_query );
	}

	/**
	 * Ensure the 'meta_query' argument passed to class constructor is well-formed.
	 *
	 * Eliminates empty items and ensures that a 'relation' is set.
	 *
	 * @ Since 4.1.0
	 *
	 * @param array $queries Array of query clauses.
	 * @return array Sanitized array of query clauses.
	 */
	public function sanitize_query( $queries ) {
		$clean_queries = array();

		if ( ! is_array ( $ queries ) ) {
			return $clean_queries;
		}

		foreach ( $queries as $key => $query ) {
			if ( 'relation' === $key ) {
				$relation = $query;

			} elseif (! is_array($query)) {
				continue;

				/ / First-order clause.
			} elseif ( $this->is_first_order_clause( $query ) ) {
				if ( isset( $query['value'] ) && array() === $query['value'] ) {
					unset($query['value']);
				}

				$clean_queries[ $key ] = $query;

				/ / Otherwise, it's a nested query, so we recurse.
			} else {
				$cleaned_query = $this->sanitize_query( $query );

				if ( ! empty( $cleaned_query ) ) {
					$clean_queries[ $key ] = $cleaned_query;
				}
			}
		}

		if ( empty( $clean_queries ) ) {
			return $clean_queries;
		}

		// Sanitize the 'relation' key provided in the query.
		if ( isset( $relation ) && 'OR' === strtoupper( $relation ) ) {
			$clean_queries['relation'] = 'OR';
			$this->has_or_relation = true;

			/*
			* Якщо є тільки один ключ, називається 'OR'.
			* Ця величина не може бути використана як правило, але це
			* Simplifies the logical around combining key-only queries.
			*/
		} elseif ( 1 === count( $clean_queries ) ) {
			$clean_queries['relation'] = 'OR';

			// Default to AND.
		} else {
			$clean_queries['relation'] = 'AND';
		}

		return $clean_queries;
	}

	/**
	 * Determine whether a query clause is first-order.
	 *
	 * У першій-ордері meta query clause is one that has either a 'key' or
	 * a 'value' array key.
	 *
	 * @ Since 4.1.0
	 *
	 * @param array $query Meta query arguments.
	 * @return bool Whether the query clause is a first-order clause.
	 */
	protected function is_first_order_clause( $query ) {
		return isset($query['key']) || isset($query['value']);
	}

	/**
	 * Constructs a meta query базується на 'meta_*' query vars
	 *
	 * @ Since 3.2.0
	 *
	 * @param array $qv The query variables
	 */
	public function parse_query_vars( $qv ) {
		$meta_query = array();

		/*
		 * Для orderby=meta_value для роботи коректно, простий пошук потребує be
		 * first (so that its table join is against an unaliased meta table) and
		 * Потрібно, щоб його власний clause (so it doesn't interfere with the logic of
		 * the rest of the meta_query).
		 */
		$primary_meta_query = array();
		foreach ( array( 'key', 'compare', 'type', 'compare_key', 'type_key' ) as $key ) {
			if ( ! empty( $qv[ "meta_$key" ] ) ) {
				$primary_meta_query[ $key ] = $qv[ "meta_$key" ];
			}
		}

		// WP_Query sets 'meta_value' = '' by default.
		if ( isset( $qv['meta_value'] ) && '' !== $qv['meta_value'] && ( ! is_array( $qv['meta_value'] ) || $qv['meta_value'] ) ) {
			$primary_meta_query['value'] = $qv['meta_value'];
		}

		$existing_meta_query = isset( $qv['meta_query'] ) && is_array( $qv['meta_query'] ) ? $qv['meta_query'] : array();

		if ( ! empty( $primary_meta_query ) && ! empty( $existing_meta_query ) ) {
			$meta_query = array(
				'relation' => 'AND',
				$primary_meta_query,
				$existing_meta_query,
			);
		} elseif (! empty($primary_meta_query)) {
			$meta_query = array(
				$primary_meta_query,
			);
		} elseif (! empty($existing_meta_query)) {
			$meta_query = $existing_meta_query;
		}

		$this->__construct( $meta_query );
	}

	/**
	 * Return the appropriate alias for given meta type if applicable.
	 *
	 * @ Since 3.7.0
	 *
	 * @param string $type MySQL type to cast meta_value.
	 * @return string MySQL type.
	 */
	public function get_cast_for_type( $type = '' ) {
		if ( empty( $type ) ) {
			return 'CHAR';
		}

		$meta_type = strtoupper( $type );

		if ( ! preg_match( '/^(?:BINARY|CHAR|DATE|DATETIME|SIGNED|UNSIGNED|TIME|NUMERIC(?:(d+(?:,s?d+)?)))?|DECIMAL (?:(d+(?:,s?d+)?))?)$/', $meta_type ) ) {
			return 'CHAR';
		}

		if ( 'NUMERIC' === $meta_type ) {
			$meta_type = 'SIGNED';
		}

		return $meta_type;
	}

	/**
	 * Generates SQL clauses to be appended to a main query.
	 *
	 * @ Since 3.2.0
	 *
	 * @param string $type Type of meta. Можливі ціни включаються, але не обмежені
	 * to 'post', 'comment', 'blog', 'term', 'user'.
	 * @param string $primary_table Database table where the object being filtered is stored (eg wp_users).
	 * @param string $primary_id_column ID column for filtered object in $primary_table.
	 * @param object $context Optional. Основний ключ об'єкта, що відповідає type, for
	 * example a `WP_Query`, `WP_User_Query`, або `WP_Site_Query`.
	 * @return string[]|false {
	 * Array containing JOIN and WHERE SQL clauses to append to the main query,
	 * or false if не table exists for the requested meta type.
	 *
	 * @type string $join SQL fragment доappend to the main JOIN clause.
	 * @type string $where SQL fragment доappend to the main WHERE clause.
	 * }
	 */
	public function get_sql( $type, $primary_table, $primary_id_column, $context = null ) {
		$meta_table = _get_meta_table($type);
		if ( ! $meta_table ) {
			return false;
		}

		$this->table_aliases = array();

		$this->meta_table = $meta_table;
		$this->meta_id_column = sanitize_key( $type . '_id' );

		$this->primary_table = $primary_table;
		$this->primary_id_column = $primary_id_column;

		$sql = $this->get_sql_clauses();

		/*
		 * Якщо JOINs є LEFT JOINs (як у випадку з NOT EXISTS), то всі JOINs should
		 * be LEFT. Іншіwise posts with no metadata will be excluded from results.
		 */
		if ( false !== strpos( $sql['join'], 'LEFT JOIN' ) ) {
			$sql['join'] = str_replace( 'INNER JOIN', 'LEFT JOIN', $sql['join'] );
		}

		/**
		 * Filters the meta query's generated SQL.
		 *
		 * @ Since 3.1.0
		 *
		 * @param string[] $sql Array містить JOIN і WHERE clauses.
		 * @param array $queries Array of meta queries.
		 * @param string $type Type of meta. Можливі ціни включаються, але не обмежені
		 * to 'post', 'comment', 'blog', 'term', 'user'.
		 * @param string $primary_table Primary table.
		 * @param string $primary_id_column Primary column ID.
		 * @param object $context main query object that corresponds to the type, for
		 * example a `WP_Query`, `WP_User_Query`, або `WP_Site_Query`.
		 */
		return apply_filters_ref_array( 'get_meta_sql', array( $sql, $this->queries, $type, $primary_table, $primary_id_column, $context ) );
	}

	/**
	 * Generate SQL clauses to be appended to a main query.
	 *
	 * Called by the public WP_Meta_Query::get_sql(), цей метод is abstracted
	 * out to maintain parity з іншими Query classes.
	 *
	 * @ Since 4.1.0
	 *
	 * @return string[] {
	 * Array containing JOIN and WHERE SQL clauses to append to the main query.
	 *
	 * @type string $join SQL fragment доappend to the main JOIN clause.
	 * @type string $where SQL fragment доappend to the main WHERE clause.
	 * }
	 */
	protected function get_sql_clauses() {
		/*
		 * $queries passed by reference to get_sql_for_query() for recursion.
		 * To keep $this->queries unaltered, pass a copy.
		 */
		$queries = $this->queries;
		$sql = $this->get_sql_for_query( $queries );

		if ( ! empty( $sql['where'] ) ) {
			$sql['where'] = 'AND'. $sql['where'];
		}

		return $sql;
	}

	/**
	 * Generate SQL clauses for single query array.
	 *
	 * Якщо незавершені subqueries є відомості, це метод recurses the tree to
	 * produce the property nested SQL.
	 *
	 * @ Since 4.1.0
	 *
	 * @param array $query Query to parse (passed by reference).
	 * @param int $depth Optional. Номер з рівнем рівнів глибоко ми були поточно.
	 * Використовується для визначення інденції. Default 0
	 * @return string[] {
	 * Array containing JOIN and WHERE SQL clauses to append to a single query array.
	 *
	 * @type string $join SQL fragment доappend to the main JOIN clause.
	 * @type string $where SQL fragment доappend to the main WHERE clause.
	 * }
	 */
	protected function get_sql_for_query( &$query, $depth = 0 ) {
		$sql_chunks = array(
			'join' => array(),
			'where' => array(),
		);

		$sql = array(
			'join' => '',
			'where' => '',
		);

		$indent = '';
		for ( $i = 0; $i < $depth; $i++ ) {
			$indent. = '';
		}

		foreach ( $query as $key => &$clause ) {
			if ( 'relation' === $key ) {
				$relation = $query['relation'];
			} elseif (is_array($clause)) {

				// This is a first-order clause.
				if ( $this->is_first_order_clause( $clause ) ) {
					$clause_sql = $this->get_sql_for_clause( $clause, $query, $key );

					$where_count = count( $clause_sql['where'] );
					if (! $where_count) {
						$sql_chunks['where'][] = '';
					} elseif ( 1 === $where_count ) {
						$sql_chunks['where'][] = $clause_sql['where'][0];
					} else {
						$sql_chunks['where'][] = '( ' . implode( ' AND ', $clause_sql['where'] ) . ' )';
					}

					$sql_chunks['join'] = array_merge( $sql_chunks['join'], $clause_sql['join'] );
					// This is a subquery, so we recurse.
				} else {
					$clause_sql = $this->get_sql_for_query( $clause, $depth + 1 );

					$sql_chunks['where'][] = $clause_sql['where'];
					$sql_chunks['join'][] = $clause_sql['join'];
				}
			}
		}

		// Filter to remove empties.
		$sql_chunks['join'] = array_filter( $sql_chunks['join'] );
		$sql_chunks['where'] = array_filter( $sql_chunks['where'] );

		if (empty($relation)) {
			$relation = 'AND';
		}

		// Filter duplicate JOIN clauses and combine in single string.
		if ( ! empty( $sql_chunks['join'] ) ) {
			$sql['join'] = implode( ' ', array_unique( $sql_chunks['join'] ) );
		}

		// Generate a single WHERE clause with property brackets and indentation.
		if ( ! empty( $sql_chunks['where'] ) ) {
			$sql['where'] = '( ' . "n " . $indent . implode( ' ' . "n " . $indent . $relation . ' ' . n " . $indent, $sql_chunks[ 'where'] ) . "n" . $indent . ')';
		}

		return $sql;
	}

	/**
	 * Generate SQL JOIN and WHERE clauses for first-order query clause.
	 *
	 * "First-order" means that it's array with a 'key' or 'value'.
	 *
	 * @ Since 4.1.0
	 *
	 * @global wpdb $wpdb WordPress database abstraction object.
	 *
	 * @param array $clause Query clause (passed by reference).
	 * @param array $parent_query Parent query array.
	 * @param string $clause_key Optional. Array key used to name the clause in the original `$meta_query`
	 * Параметри. Якщо не передбачено, а ключ буде створюватися автоматично.
	 * @return string[] {
	 * Array містить JOIN і WHERE SQL clauses до append to a first-order query.
	 *
	 * @type string $join SQL fragment доappend to the main JOIN clause.
	 * @type string $where SQL fragment доappend to the main WHERE clause.
	 * }
	 */
	public function get_sql_for_clause( &$clause, $parent_query, $clause_key = '' ) {
		Global $wpdb;

		$sql_chunks = array(
			'where' => array(),
			'join' => array(),
		);

		if ( isset( $clause['compare'] ) ) {
			$clause['compare'] = strtoupper( $clause['compare'] );
		} else {
			$clause['compare'] = isset( $clause['value'] ) && is_array( $clause['value'] ) ? 'IN': '=';
		}

		$non_numeric_operators = array(
			'=',
			'!=',
			'LIKE',
			'NOT LIKE',
			'IN',
			'NOT IN',
			'EXISTS',
			'NOT EXISTS',
			'RLIKE',
			'REGEXP',
			'NOT REGEXP',
		);

		$numeric_operators = array(
			'>',
			'>=',
			'<',
			'<=',
			'BETWEEN',
			'NOT BETWEEN',
		);

		if ( ! in_array( $clause['compare'], $non_numeric_operators, true ) && ! in_array( $clause['compare'], $numeric_operators, true ) ) {
			$clause['compare'] = '=';
		}

		if ( isset( $clause['compare_key'] ) ) {
			$clause['compare_key'] = strtoupper( $clause['compare_key'] );
		} else {
			$clause['compare_key'] = isset( $clause['key'] ) && is_array( $clause['key'] ) ? 'IN': '=';
		}

		if ( ! in_array( $clause['compare_key'], $non_numeric_operators, true ) ) {
			$clause['compare_key'] = '=';
		}

		$meta_compare = $clause['compare'];
		$meta_compare_key = $clause['compare_key'];

		// Перше будівництво JOIN clause, якщо один необхідний.
		$join = '';

		// We prefer to avoid joins if possible. Look for an existing join compatible with this clause.
		$alias = $this->find_compatible_table_alias( $clause, $parent_query );
		if ( false === $alias ) {
			$i = count($this->table_aliases);
			$alias = $i? 'mt'. $i : $this->meta_table;

			// JOIN clauses for NOT EXISTS мають свої власні syntax.
			if ( 'NOT EXISTS' === $meta_compare ) {
				$join .= "LEFT JOIN $this->meta_table";
				$join.=$i? "AS $alias" : '';

				if ( 'LIKE' === $meta_compare_key ) {
					$join .= $wpdb->prepare( " ON ( $this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column AND $alias.meta_key LIKE %s )", '%' . $wpdb ->esc_like( $clause['key'] ) . '%' );
				} else {
					$join .= $wpdb->prepare( " ON ( $this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column AND $alias.meta_key = %s )", $clause['key' ]);
				}

				// Всі інші JOIN clauses.
			} else {
				$join .= "INNER JOIN $this->meta_table";
				$join.=$i? "AS $alias" : '';
				$join .= " ON ( $this->primary_table.$this->primary_id_column = $alias.$this->meta_id_column )";
			}

			$this->table_aliases[] = $alias;
			$sql_chunks['join'][] = $join;
		}

		// Залишити або до цього класу, для майбутніх символів до find.
		$clause['alias'] = $alias;

		/ / Determine the data type.
		$_meta_type = isset( $clause['type'] ) ? $clause['type'] : '';
		$meta_type = $this->get_cast_for_type( $_meta_type );
		$clause['cast'] = $meta_type;

		// Fallback for clause keys is the table alias. Key must be a string.
		if ( is_int( $clause_key ) || ! $clause_key ) {
			$clause_key = $clause['alias'];
		}

		// Ensure unique clause keys, тому не є overwritten.
		$iterator = 1;
		$clause_key_base = $clause_key;
		while (isset($this->clauses[$clause_key]))) {
			$clause_key = $clause_key_base . '-'. $iterator;
			$iterator++;
		}

		// Store clause in our flat array.
		$this->clauses[ $clause_key ] =& $clause;

		// Next, build the WHERE clause.

		// meta_key.
		if ( array_key_exists( 'key', $clause ) ) {
			if ( 'NOT EXISTS' === $meta_compare ) {
				$sql_chunks['where'][] = $alias . '.' . $this->meta_id_column . 'IS NULL';
			} else {
				/**
				 * In joined clauses negative operators have to be nested into a
				 * NOT EXISTS clause and flipped, to avoid returning records with
				 * matching post IDs but different meta keys. Here we prepare the
				 * nested clause.
				 */
				if ( in_array( $meta_compare_key, array( '!=', 'NOT IN', 'NOT LIKE', 'NOT EXISTS', 'NOT REGEXP' ), true ) ) {
					// Negative clauses may be reused.
					$i = count($this->table_aliases);
					$subquery_alias = $i? 'mt'. $i : $this->meta_table;
					$this->table_aliases[] = $subquery_alias;

					$meta_compare_string_start = 'NOT EXISTS(';
					$meta_compare_string_start .= "SELECT 1 FROM $wpdb->postmeta $subquery_alias ";
					$meta_compare_string_start .= "WHERE $subquery_alias.post_ID = $alias.post_ID ";
					$meta_compare_string_end = 'LIMIT 1';
					$meta_compare_string_end .= ')';
				}

				switch ( $meta_compare_key ) {
					case '=':
					case 'EXISTS':
						$where = $wpdb->prepare( "$alias.meta_key = %s", trim( $clause['key'] ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
						break;
					case 'LIKE':
						$meta_compare_value = '%'. $wpdb->esc_like( trim( $clause['key'] ) ) . '%';
						$where = $wpdb->prepare( "$alias.meta_key LIKE %s", $meta_compare_value ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
						break;
					case 'IN':
						$meta_compare_string = "$alias.meta_key IN (" . substr( str_repeat( ',%s', count( $clause['key'] ) )), 1 ) . ')';
						$where = $wpdb->prepare( $meta_compare_string, $clause['key'] ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
						break;
					case 'RLIKE':
					case 'REGEXP':
						$operator = $meta_compare_key;
						if ( isset( $clause['type_key'] ) && 'BINARY' === strtoupper( $clause['type_key'] ) ) {
							$cast = 'BINARY';
						} else {
							$cast = '';
						}
						$where = $wpdb->prepare( "$alias.meta_key $operator $cast %s", trim( $clause['key'] ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
						break;

					case '!=':
					case 'NOT EXISTS':
						$meta_compare_string = $meta_compare_string_start. "AND $subquery_alias.meta_key = %s". $meta_compare_string_end;
						$where = $wpdb->prepare( $meta_compare_string, $clause['key'] ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
						break;
					case 'NOT LIKE':
						$meta_compare_string = $meta_compare_string_start. "AND $subquery_alias.meta_key LIKE %s". $meta_compare_string_end;

						$meta_compare_value = '%'. $wpdb->esc_like( trim( $clause['key'] ) ) . '%';
						$where = $wpdb->prepare( $meta_compare_string, $meta_compare_value ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
						break;
					case 'NOT IN':
						$array_subclause = '(' . substr( str_repeat( ',%s', count( $clause['key'] ) )), 1 ) .') ';
						$meta_compare_string = $meta_compare_string_start. "AND $subquery_alias.meta_key IN". $array_subclause . $meta_compare_string_end;
						$where = $wpdb->prepare( $meta_compare_string, $clause['key'] ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
						break;
					case 'NOT REGEXP':
						$operator = $meta_compare_key;
						if ( isset( $clause['type_key'] ) && 'BINARY' === strtoupper( $clause['type_key'] ) ) {
							$cast = 'BINARY';
						} else {
							$cast = '';
						}

						$meta_compare_string = $meta_compare_string_start. "AND $subquery_alias.meta_key REGEXP $cast %s". $meta_compare_string_end;
						$where = $wpdb->prepare( $meta_compare_string, $clause['key'] ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
						break;
				}

				$sql_chunks['where'][] = $where;
			}
		}

		// meta_value.
		if ( array_key_exists( 'value', $clause ) ) {
			$meta_value = $clause['value'];

			if ( in_array( $meta_compare, array( 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN' ), true ) ) {
				if ( ! is_array( $meta_value ) ) {
					$meta_value = preg_split( '/[,s]+/', $meta_value );
				}
			} elseif ( is_string( $meta_value ) ) {
				$meta_value = trim($meta_value);
			}

			switch ($meta_compare) {
				case 'IN':
				case 'NOT IN':
					$meta_compare_string = '(' . substr( str_repeat( ',%s', count( $meta_value ) ), 1 ) . ')';
					$where = $wpdb->prepare( $meta_compare_string, $meta_value );
					break;

				case 'BETWEEN':
				case 'NOT BETWEEN':
					$where = $wpdb->prepare( '%s AND %s', $meta_value[0], $meta_value[1] );
					break;

				case 'LIKE':
				case 'NOT LIKE':
					$meta_value = '%'. $wpdb->esc_like( $meta_value ) . '%';
					$where = $wpdb->prepare( '%s', $meta_value );
					break;

				// EXISTS with a value is interpreted as '='.
				case 'EXISTS':
					$meta_compare = '=';
					$where = $wpdb->prepare( '%s', $meta_value );
					break;

				// 'value' is ignored for NOT EXISTS.
				case 'NOT EXISTS':
					$where = '';
					break;

				default:
					$where = $wpdb->prepare( '%s', $meta_value );
					break;

			}

			if ($where) {
				if ( 'CHAR' === $meta_type ) {
					$sql_chunks['where'][] = "$alias.meta_value {$meta_compare} {$where}";
				} else {
					$sql_chunks['where'][] = "CAST($alias.meta_value AS {$meta_type}) {$meta_compare} {$where}";
				}
			}
		}

		/*
		 * Multiple WHERE clauses (for meta_key and meta_value) should
		 * be joined in parentheses.
		 */
		if ( 1 < count( $sql_chunks['where'] ) ) {
			$sql_chunks['where'] = array( '( ' . implode( ' AND ', $sql_chunks['where'] ) . ' )' );
		}

		return $sql_chunks;
	}

	/**
	 * Get a flattened list of sanitized meta clauses.
	 *
	 * Цей array повинен бути використаний для clause lookup, as коли table alias and CAST type must be determined for
	 * a value of 'orderby' corresponding to a meta clause.
	 *
	 * @ Since 4.2.0
	 *
	 * @return array Meta clauses.
	 */
	public function get_clauses() {
		return $this->clauses;
	}

	/**
	 * Identify an existing table alias that is compatible with the current
	 * query clause.
	 *
	 * We avoid unnecessary table joins з дозволом їх clause to look for
	 * an existing table alias that is compatible with the query that it
	 * needs to perform.
	 *
	 * Існує alias is compatible if (a) it is a sibling of `$clause`
	 * (ie, it's за межею самої відносної), і (b) комбінації
	 * Оператор і зв'язок між clauses allows for shared table join.
	 * У випадку WP_Meta_Query, це тільки applies to 'IN' clauses that are
	 * Connected by the relation 'OR'.
	 *
	 * @ Since 4.1.0
	 *
	 * @param array $clause Query clause.
	 * @param array $parent_query Parent query of $clause.
	 * @return string|false Table alias if found, otherwise false.
	 */
	protected function find_compatible_table_alias( $clause, $parent_query ) {
		$alias = false;

		foreach ( $parent_query as $sibling ) {
			// If the sibling has no alias yet, there's nothing to check.
			if ( empty( $sibling['alias'] ) ) {
				continue;
			}

			/ / We're тільки interested в siblings що є перші-ордер clauses.
			if ( ! is_array( $sibling ) || ! $this->is_first_order_clause( $sibling ) ) {
				continue;
			}

			$compatible_compares = array();

			// Clauses connected OR може share joins як довгий як вони мають "позитивних" операторів.
			if ( 'OR' === $parent_query['relation'] ) {
				$compatible_compares = array( '=', 'IN', 'BETWEEN', 'LIKE', 'REGEXP', 'RLIKE', '>', '>=', '<', '<=' );

				// Clauses joined by AND with "negative" операторів share a join only if they also share a key.
			} elseif ( isset( $sibling['key'] ) && isset( $clause['key'] ) && $sibling['key'] === $clause['key'] ) {
				$compatible_compares = array( '!=', 'NOT IN', 'NOT LIKE' );
			}

			$clause_compare = strtoupper( $clause['compare'] );
			$sibling_compare = strtoupper( $sibling['compare'] );
			if ( in_array( $clause_compare, $compatible_compares, true ) && in_array( $sibling_compare, $compatible_compares, true ) ) {
				$alias = preg_replace( '/W/', '_', $sibling['alias'] );
				break;
			}
		}

		/**
		 * Filters the table alias identified as compatible with the current clause.
		 *
		 * @ Since 4.1.0
		 *
		 * @param string|false $alias Table alias, або false if none was found.
		 * @param array $clause First-order query clause.
		 * @param array $parent_query Parent of $clause.
		 * @param WP_Meta_Query $query WP_Meta_Query object.
		 */
		return apply_filters( 'meta_query_find_compatible_table_alias', $alias, $clause, $parent_query, $this );
	}

	/**
	 * Checks whether the current query має any OR relations.
	 *
	 * У деяких випадках, питання про те, чи OR відносини, де в потрібній буде потреба
	 * the use of a `DISTINCT` or `GROUP BY` keyword in the `SELECT` clause. The current
	 * метод може бути використаний в цих випадках для визначення, коли така clause необхідна.
	 *
	 * @ Since 4.3.0
	 *
	 * @return bool True if the query contains any `OR` relations, otherwise false.
	 */
	public function has_or_relation() {
		return $this->has_or_relation;
	}
}

Залишити відповідь

Ваша e-mail адреса не оприлюднюватиметься. Обов’язкові поля позначені *