J'ai un type de message personnalisé qui contient un certain nombre de boîtes de méta. L’un d’eux est un champ expiration_date qui stocke ses informations dans la base de données sous forme de chaîne au format
Feb 1, 2017
J'essaie d'écrire une requête qui filtrera les éléments qui ont une date dans le passé, en ne montrant que ceux à venir. J'ai essayé plusieurs versions de la requête suivante et aucune ne fonctionne:
$myquery = array (
'post_type' => $cpt,
'numberposts'=>-1,
'meta_key' => 'expiration_date',
'meta_value' => date('M j, Y'),
'meta_compare' => '<'
),
);
La requête renvoie tous les articles, quelle que soit leur date, ou zéro si j'inverse le carot (<)
Je sais que pour utiliser les champs de date, WP s'attend à ce qu'il soit dans un format différent (AAAA-MM-JJ), mais ce n'est pas ainsi que cette application a été construite (par quelqu'un d'autre). ci-dessus est ce que je travaille avec.
La question est donc la suivante: existe-t-il un moyen de convertir la date et l'heure AVANT la comparaison? Si je retire le champ par lui-même dans n'importe quel enregistrement, je peux le convertir (en utilisant strtotime), mais bien sûr, j'essaie de filtrer les valeurs en fonction de la date avant affichage afin d'avoir le bon numéro dans l'ensemble pour pouvoir travailler avec début.
STR_TO_DATE
MySQL a une fonction STR_TO_DATE
que vous pouvez utiliser pour la portée.
Pour l'utiliser, vous devez filtrer la clause WHERE
d'un WP_Query
, probablement à l'aide de posts_where
filter.
Cette fonction permet de formater une valeur de colonne sous forme de date en utilisant un format spécifique.
Par défaut, WordPress utilise la fonction CAST
MySQL qui s'attend à ce que la valeur soit au format Y-m-d H:i:s
(selon les arguments PHP date
).
STR_TO_DATE
, au contraire, permet de passer un format spécifique. Le format "espaces réservés" n'est pas le même que PHP utilise, vous pouvez voir les espaces réservés supportés au format MySQL ici: https://dev.mysql.com/doc/refman/5.5/fr/date-and -time-functions.html # format_date_fonction .
Ce que je peux suggérer comme flux de travail est:
WP_Query
posts_where
pour ajouter une clause WHERE
qui utilise STR_TO_DATE
Notez que STR_TO_DATE
peut également être utilisé pour ordonner des valeurs. Si vous souhaitez le faire, vous devrez utiliser posts_orderby
filter.
Voici une classe (PHP 7+) qui enveloppe le flux de travail décrit ci-dessus pour faciliter sa réutilisation:
class DateFieldQueryFilter {
const COMPARE_TYPES = [ '<', '>', '=', '<=', '>=' ];
private $date_key;
private $date_value;
private $format;
private $compare = '=';
private $order_by_meta = '';
public function __construct(
string $date_key,
string $date_value,
string $mysql_format,
string $compare = '='
) {
$this->date_key = $date_key;
$this->date_value = $date_value;
$this->format = $mysql_format;
in_array($compare, self::COMPARE_TYPES, TRUE) and $this->compare = $compare;
}
public function orderByMeta(string $direction = 'DESC'): DateFieldQueryFilter {
if (in_array(strtoupper($direction), ['ASC', 'DESC'], TRUE)) {
$this->order_by_meta = $direction;
}
return $this;
}
public function createWpQuery(array $args = []): \WP_Query {
$args['meta_key'] = $this->date_key;
$this->whereFilter('add');
$this->order_by_meta and $this->orderByFilter('add');
$query = new \WP_Query($args);
$this->whereFilter('remove');
$this->order_by_meta and $this->orderByFilter('remove');
return $query;
}
private function whereFilter(string $action) {
static $filter;
if (! $filter && $action === 'add') {
$filter = function ($where) {
global $wpdb;
$where and $where .= ' AND ';
$sql = "STR_TO_DATE({$wpdb->postmeta}.meta_value, %s) ";
$sql .= "{$this->compare} %s";
return $where . $wpdb->prepare($sql, $this->format, $this->date_value);
};
}
$action === 'add'
? add_filter('posts_where', $filter)
: remove_filter('posts_where', $filter);
}
private function orderByFilter(string $action) {
static $filter;
if (! $filter && $action === 'add') {
$filter = function () {
global $wpdb;
$sql = "STR_TO_DATE({$wpdb->postmeta}.meta_value, %s) ";
$sql .= $this->order_by_meta;
return $wpdb->prepare($sql, $this->format);
};
}
$action === 'add'
? add_filter('posts_orderby', $filter)
: remove_filter('posts_orderby', $filter);
}
}
Désolé si cela n'inclut pas beaucoup de commentaires ou d'explications, il est difficile d'écrire du code ici.
Le "noyau" de la classe est le filtre qui est ajouté à posts_where
avant l'exécution de la requête, puis supprimé. Il est:
function ($where) {
global $wpdb;
$where and $where .= ' AND ';
// something like: STR_TO_DATE(wp_postmeta.meta_value,'%b %e, %Y') < 'Feb 1, 2017'
$sql = "STR_TO_DATE({$wpdb->postmeta}.meta_value, %s) {$this->compare} %s";
return $where . $wpdb->prepare($sql, $this->format, $this->date_value);
};
Vous pouvez voir ici comment nous demandons à WordPress d’interroger les publications. Notez que STR_TO_DATE
est un calcul qui ne vient pas gratuitement, et sera assez lourd (spécialement pour le processeur) dans le cas de plusieurs milliers (ou millions) de lignes.
À propos, la classe peut ensuite être utilisée comme ceci:
$query_filter = new DateFieldQueryFilter (
'expiration_date', // meta key
date('M j, Y'), // meta value
'%b %e, %Y', // date format using MySQL placeholders
'<' // comparison to use
);
$query = $query_filter->createWpQuery(['posts_per_page' => - 1]);
while($query->have_posts()) {
$query->the_post();
// rest of the loop here
}
Si vous souhaitez commander par un champ WordPress standard, par exemple post date, post title ..., vous pouvez simplement transmettre les arguments de requête order
et orderby
liés à la méthode DateFieldQueryFilter::createWpQuery()
.
Si vous souhaitez commander avec la même méta-valeur, la classe fournit DateFieldQueryFilter::orderByMeta()
pour l'étendue.
Par exemple. dans le dernier extrait, vous créeriez la requête comme ceci:
$query = $query_filter
->orderByMeta('DESC')
->createWpQuery(['posts_per_page' => - 1]);
Même si cela fonctionne, lorsque cela est possible, les dates doivent être enregistrées dans une base de données au format MySQL ou à des horodatages, car cela simplifie beaucoup toute opération de filtrage ou de commande.
En fait, il est beaucoup plus facile de convertir la date au format souhaité pour que la sortie après soit extraite de la base de données.
Même si le calcul a lieu dans un sens ou dans l’autre, faire après ne sera effectué que sur les enregistrements retournés, mais laisser MySQL effectuer le calcul dont il a besoin pour agir sur ** tous * les enregistrements, nécessitant beaucoup plus de travail.
Si vous stockez les dates au format MySQL, WordPress fournit une fonction, mysql2date()
, qui peut être utilisée pour les convertir au format souhaité, ainsi que pour les paramètres régionaux.
Avez-vous essayé d'utiliser meta_query dans WP_Query?
$args = array(
'post_type' => $cpt,
'meta_query' => array(
array(
'key' => 'expiration_date',
'value' => date('M j, Y'),
'compare' => '<',
),
),
);
$query = new WP_Query( $args );
assurez-vous également que la date dans la table postmeta est du même format que celui que vous transmettez en tant que valeur dans meta_query.
J'ai fini par contourner le problème du format de date initial en modifiant le format du champ expiration_date en AAAA-MM-JJ (puis en modifiant l'affichage de ce champ ailleurs afin qu'il reste dans le format d'origine lorsque les utilisateurs le consultent). Alors maintenant, une meta_query normale utilisant DATETIME fonctionnera:
$myquery = array (
'post_type' => $cpt,
'numberposts'=>-1,
'meta_key' => 'expiration_date',
'type'=> 'DATETIME',
'meta_value' => date("Y-m-d"),
'meta_compare' => '<'
),
);
J'aimerais quand même une réponse à ce qui précède, s'il en existe une, où la valeur peut être convertie à la chaîne avant la comparaison. Je suppose donc que je vais accepter d'accepter ma propre réponse un peu dans l'espoir.
Peut-être que vous pourriez essayer quelque chose comme ça?
$args2 = array(
'post_type' => $cpt,
'posts_per_page'=> -1,
'meta-key' => 'expiration_date',
'meta_query' => Array (
Array ('key' => 'expiration_date',
'compare' => '>=',
'value' => date('M j, Y',strtotime('now')), //strtotime for value for now, date() to format the timestamp
'type' => 'DATETIME'
)
),
'orderby' => 'meta_value',
'order' => 'ASC' //or DESC
);
Cela me permet d’obtenir uniquement des événements qui sont encore à venir.
EDIT: Je dois m'excuser, cela ne fonctionne pas. Je conduisais à un rendez-vous, quand j'ai réalisé que le tri avait été fait. Au bas de mon dossier, il y avait une déclaration if laissée de mon essai précédent, qui faisait le tri et la commande. Suppression de cette ligne et elle ne fonctionne plus ...
La solution intelligente consisterait à utiliser le format de champ expiration_date
pour YYYY-MM-DD
.
De cette façon, vous pourrez créer de futures méta-requêtes WordPress sans le problème.
La conversion devrait être un jeu d'enfant.
Vous pouvez faire date_parse_from_format pour convertir le format de date:
date_parse_from_format('M j, Y', 'Feb 1, 2017');
Ensuite, vous pouvez reformer le tableau en date ("Y-m-d").
La structure complète du tableau est la suivante:
[year] =>
[month] =>
[day] =>
[hour] =>
[minute] =>
[second] =>
[fraction] =>
[warning_count] =>
[warnings] => Array()
[error_count] =>
[errors] => Array()
[is_localtime] =>
[zone_type] =>
[zone] =>
[is_dst] =>