fonc(produit) implémente le multi-variations
This commit is contained in:
parent
1a3a431b34
commit
05baad8fdd
26 changed files with 1320 additions and 184 deletions
|
|
@ -1 +0,0 @@
|
|||
/var/www/wordpress/web/app/plugins/query-monitor/wp-content/db.php
|
||||
|
|
@ -1,11 +1,9 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Le modèle de la Page d'Accueil.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Timber\Timber;
|
||||
|
||||
// Contexte et modèles
|
||||
|
|
|
|||
|
|
@ -1,46 +1,43 @@
|
|||
<?php
|
||||
<?php declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* Le modèle de la Page d'un Produit.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
use function Crell\fp\pipe;
|
||||
use HaikuAtelier\Data\Product;
|
||||
|
||||
use Timber\Timber;
|
||||
|
||||
use function Crell\fp\pipe;
|
||||
|
||||
require_once __DIR__ . '/src/inc/HTML.php';
|
||||
|
||||
require_once __DIR__ . '/src/inc/TraitementInformations.php';
|
||||
|
||||
// Contexte et modèles
|
||||
$contexte = Timber::context();
|
||||
$modeles = ['produit.twig'];
|
||||
$context = Timber::context();
|
||||
$templates = ['produit.twig'];
|
||||
|
||||
/** @var WC_Product $produit */
|
||||
$produit = wc_get_product();
|
||||
$product = wc_get_product();
|
||||
|
||||
/** @var mixed $donnees_produit */
|
||||
$donnees_produit = recupere_informations_produit_page_produit(wc_get_product());
|
||||
if ($product === null || is_bool($product)) {
|
||||
throw new Exception("Le Produit n'existe pas.");
|
||||
}
|
||||
|
||||
/** @var bool $est_variation Le Produit est-il Variable (possède-t-il des variations ?) */
|
||||
$est_produit_variable = 'variable' === $produit->get_type();
|
||||
// $donnees_produit = recupere_informations_produit_page_produit($product);
|
||||
$donnees_produit = Product::new($product);
|
||||
|
||||
/** @var array $variations_produit Un tableau des informations d'affichage de chaque Variation du Produit */
|
||||
// Un tableau des informations d'affichage de chaque Variation du Produit
|
||||
$variations_produit = pipe(
|
||||
// Récupère les IDs des Enfants (Variations)
|
||||
wc_get_product()->get_children(),
|
||||
$product->get_children(),
|
||||
// Récupère les Variations
|
||||
fn($enfants) => array_map(
|
||||
static fn(/** @var list<int> */ $enfants): array => array_map(
|
||||
callback: wc_get_product(...),
|
||||
array: $enfants,
|
||||
),
|
||||
// Ne conserve que les Informations souhaitées
|
||||
fn($variations) => array_map(
|
||||
callback: fn($variation) => [
|
||||
// Ne conserve que les Informations souhaitées.
|
||||
static fn(/** @var list<WC_Product> */ $variations): array => array_map(
|
||||
callback: static fn(WC_Product $variation): array => [
|
||||
'id' => $variation->get_id(),
|
||||
// Ne récupère que le titre de l'Attribut unique de la Variation
|
||||
// Ne récupère que le titre de l'Attribut unique de la Variation.
|
||||
'titre' => match (true) {
|
||||
'' !== $variation->get_attribute('pa_side') => $variation->get_attribute('pa_side'),
|
||||
'' !== $variation->get_attribute('pa_stone') => $variation->get_attribute('pa_stone'),
|
||||
|
|
@ -60,14 +57,14 @@ $variations_produit = pipe(
|
|||
$prix_maximal = collect($variations_produit)->max('prix');
|
||||
|
||||
$produits_meme_collection = array_map(
|
||||
callback: 'recupere_informations_produit_page_produit',
|
||||
array: recupere_produits_meme_collection($donnees_produit['collection'])($donnees_produit['id']),
|
||||
callback: recupere_informations_produit_page_produit(...),
|
||||
array: recupere_produits_meme_collection($donnees_produit->collection)($donnees_produit->id),
|
||||
);
|
||||
|
||||
$contexte['produit'] = $donnees_produit;
|
||||
$contexte['prix_maximal'] = $prix_maximal;
|
||||
$contexte['variations_produit'] = $variations_produit;
|
||||
$contexte['produits_meme_collection'] = $produits_meme_collection;
|
||||
$context['produit'] = $donnees_produit;
|
||||
$context['prix_maximal'] = $prix_maximal;
|
||||
$context['variations_produit'] = $variations_produit;
|
||||
$context['produits_meme_collection'] = $produits_meme_collection;
|
||||
|
||||
/**
|
||||
* Charge les Scripts nécessaires pour la page Produit.
|
||||
|
|
@ -89,11 +86,15 @@ function charge_scripts_page_produit(): void {
|
|||
|
||||
add_action('wp_enqueue_scripts', 'charge_scripts_page_produit');
|
||||
|
||||
// $lal = wp_json_encode($contexte);
|
||||
// echo "<script>console.debug({$lal});</script>";
|
||||
$lal = wp_json_encode($context);
|
||||
echo "<script>console.debug({$lal});</script>";
|
||||
|
||||
$lol = wc_get_product()->get_children();
|
||||
$lol = wp_json_encode($lol);
|
||||
echo "<script>console.debug({$lol});</script>";
|
||||
|
||||
// Rendu
|
||||
Timber::render(
|
||||
filenames: $modeles,
|
||||
data: $contexte,
|
||||
filenames: $templates,
|
||||
data: $context,
|
||||
);
|
||||
|
|
|
|||
33
web/app/themes/haiku-atelier-2024/src/inc/Data/Attribute.php
Normal file
33
web/app/themes/haiku-atelier-2024/src/inc/Data/Attribute.php
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace HaikuAtelier\Data;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
use WC_Product_Attribute;
|
||||
use WP_Term;
|
||||
|
||||
final readonly class Attribute {
|
||||
/**
|
||||
* @param list<AttributeOption> $options
|
||||
*/
|
||||
public function __construct(
|
||||
public string $name,
|
||||
public string $slug,
|
||||
public array $options,
|
||||
) {}
|
||||
|
||||
public static function new(WC_Product_Attribute $attribute): self {
|
||||
$name = wc_attribute_label($attribute->get_name());
|
||||
$slug = $attribute->get_name();
|
||||
/** @var list<WP_Term> */
|
||||
$terms = $attribute->get_terms() ?? [];
|
||||
/** @var list<AttributeOption> */
|
||||
$options = Arr::map($terms, static fn(WP_Term $term): AttributeOption => AttributeOption::new($term));
|
||||
|
||||
return new self(
|
||||
name: $name,
|
||||
slug: $slug,
|
||||
options: $options,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace HaikuAtelier\Data;
|
||||
|
||||
use WP_Term;
|
||||
|
||||
final readonly class AttributeOption {
|
||||
public function __construct(
|
||||
public int $id,
|
||||
public string $name,
|
||||
public string $slug,
|
||||
) {}
|
||||
|
||||
public static function new(WP_Term $term): self {
|
||||
$id = $term->term_taxonomy_id;
|
||||
$name = $term->name;
|
||||
$slug = $term->slug;
|
||||
|
||||
return new self(
|
||||
id: $id,
|
||||
name: $name,
|
||||
slug: $slug,
|
||||
);
|
||||
}
|
||||
}
|
||||
103
web/app/themes/haiku-atelier-2024/src/inc/Data/Product.php
Normal file
103
web/app/themes/haiku-atelier-2024/src/inc/Data/Product.php
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace HaikuAtelier\Data;
|
||||
|
||||
use HaikuAtelier\WP\HaikuProduct;
|
||||
use HaikuAtelier\WP\Term;
|
||||
use Illuminate\Support\Arr;
|
||||
use Psl\Option;
|
||||
use function Psl\Option\from_nullable;
|
||||
use WC_Product;
|
||||
use WC_Product_Attribute;
|
||||
|
||||
use WP_Term;
|
||||
|
||||
final readonly class Product {
|
||||
/**
|
||||
* @param list<Attribute> $attributes
|
||||
* @param list<string> $left_column_photos
|
||||
* @param list<string> $right_column_photos
|
||||
* @param list<int> $variation_ids
|
||||
*/
|
||||
private function __construct(
|
||||
public array $attributes,
|
||||
public string $category,
|
||||
public string $collection,
|
||||
public string $details,
|
||||
public int $id,
|
||||
public string $name,
|
||||
public string $price,
|
||||
public array $left_column_photos,
|
||||
public array $right_column_photos,
|
||||
public string $default_photo,
|
||||
public string $hover_photo,
|
||||
public string $slug,
|
||||
public int $stock,
|
||||
public array $variation_ids,
|
||||
public string $url,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @return list<Attribute>
|
||||
*/
|
||||
public static function get_attributes_for_product(WC_Product $product): array {
|
||||
/** @var list<Attribute> */
|
||||
return $product->get_attributes()
|
||||
|> (static fn($attributes) => Arr::map($attributes, static fn(WC_Product_Attribute $attribute): Attribute => Attribute::new(
|
||||
$attribute,
|
||||
)));
|
||||
}
|
||||
|
||||
public static function new(WC_Product $product): self {
|
||||
/** @var list<Attribute> */
|
||||
$attributes = self::get_attributes_for_product($product);
|
||||
/** @var lowercase-string */
|
||||
$category = $product->get_id() |> wc_get_product_category_list(...) |> strtolower(...);
|
||||
|
||||
/** @var Option\Option<list<WP_Term>> */
|
||||
$collection = Term::get_terms($product->get_id(), 'collection');
|
||||
/** @var Option\Option<WP_Term> */
|
||||
$collection = $collection->andThen(
|
||||
static fn(array $terms): Option\Option => head($terms) |> from_nullable(...),
|
||||
);
|
||||
/** @var Option\Option<string> */
|
||||
$collection = $collection->map(static fn(WP_Term $term) => $term->slug);
|
||||
/** @var string */
|
||||
$collection = $collection->unwrapOr('');
|
||||
|
||||
/** @var string */
|
||||
$details = $product->get_description() |> wpautop(...);
|
||||
$id = $product->get_id();
|
||||
$name = $product->get_name();
|
||||
$price = $product->get_price();
|
||||
/** @var list<string> */
|
||||
$left_column_photos = HaikuProduct::get_left_column_photos($id);
|
||||
/** @var list<string> */
|
||||
$right_column_photos = HaikuProduct::get_right_column_photos($id);
|
||||
$default_photo = $left_column_photos[0] ?? genere_balise_img_multiformats('-1');
|
||||
$hover_photo = $right_column_photos[0] ?? genere_balise_img_multiformats('-1', true);
|
||||
$slug = $product->get_slug();
|
||||
$stock = $product->get_stock_quantity() ?? 1;
|
||||
/** @var list<int> */
|
||||
$variation_ids = $product->get_children();
|
||||
$url = $product->get_permalink();
|
||||
|
||||
return new self(
|
||||
attributes: $attributes,
|
||||
category: $category,
|
||||
collection: $collection,
|
||||
details: $details,
|
||||
id: $id,
|
||||
name: $name,
|
||||
price: $price,
|
||||
left_column_photos: $left_column_photos,
|
||||
right_column_photos: $right_column_photos,
|
||||
default_photo: $default_photo,
|
||||
hover_photo: $hover_photo,
|
||||
slug: $slug,
|
||||
stock: $stock,
|
||||
variation_ids: $variation_ids,
|
||||
url: $url,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -6,25 +6,30 @@
|
|||
declare(strict_types=1);
|
||||
|
||||
use function Crell\fp\pipe;
|
||||
use HaikuAtelier\Data\Attribute;
|
||||
|
||||
use HaikuAtelier\Data\Product;
|
||||
|
||||
// Page Shop
|
||||
|
||||
/**
|
||||
* TODO.
|
||||
*
|
||||
* @param int $id TODO
|
||||
* @param bool $lazy TODO
|
||||
* @param string $id TODO
|
||||
* @param bool $lazy TODO
|
||||
*
|
||||
* @return string TODO
|
||||
*/
|
||||
function genere_balise_img_multiformats($id, $lazy = false) {
|
||||
function genere_balise_img_multiformats(string $id, bool $lazy = false): string {
|
||||
$int_id = (int) $id;
|
||||
|
||||
if (-1 === $id) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$url = wp_get_attachment_image_url($id, 'full');
|
||||
$chemin = realpath(get_attached_file($id)) ?: realpath(get_attached_file($id));
|
||||
$alt = get_post_meta($id, '_wp_attachment_image_alt', true);
|
||||
$url = wp_get_attachment_image_url($int_id, 'full');
|
||||
$chemin = realpath(get_attached_file($int_id)) ?: realpath(get_attached_file($int_id));
|
||||
$alt = get_post_meta($int_id, '_wp_attachment_image_alt', true);
|
||||
$dimensions = $chemin ? getimagesize($chemin) : ['', ''];
|
||||
|
||||
$avif = $chemin ? realpath(pathinfo($chemin)['dirname'] . '/' . pathinfo($chemin)['filename'] . '.avif') : false;
|
||||
|
|
@ -34,27 +39,26 @@ function genere_balise_img_multiformats($id, $lazy = false) {
|
|||
// Génère un tableau avec les différents formats valides
|
||||
$formats = pipe(
|
||||
[$avif, $jxl, $webp],
|
||||
fn($tableau) => array_filter(
|
||||
static fn($tableau): array => array_filter(
|
||||
array: $tableau,
|
||||
callback: fn($chemin_format) => false !== $chemin_format,
|
||||
callback: static fn($chemin_format): bool => false !== $chemin_format,
|
||||
),
|
||||
fn($tableau) => array_map(
|
||||
static fn($tableau): array => array_map(
|
||||
array: $tableau,
|
||||
callback: fn($chemin_format) => [
|
||||
'format' => pathinfo($chemin_format)['extension'],
|
||||
callback: static fn($chemin_format): array => [
|
||||
'format' => pathinfo((string) $chemin_format)['extension'],
|
||||
'taille' => filesize($chemin_format),
|
||||
'url' =>
|
||||
pathinfo($url)['dirname']
|
||||
'url' => pathinfo($url)['dirname']
|
||||
. '/'
|
||||
. pathinfo($url)['filename']
|
||||
. '.'
|
||||
. pathinfo($chemin_format)['extension'],
|
||||
. pathinfo((string) $chemin_format)['extension'],
|
||||
],
|
||||
),
|
||||
);
|
||||
usort(
|
||||
array: $formats,
|
||||
callback: fn($a, $b) => $a['taille'] <=> $b['taille'],
|
||||
callback: static fn($a, $b): int => $a['taille'] <=> $b['taille'],
|
||||
);
|
||||
|
||||
// Construis les balises <source> avec les formats valides
|
||||
|
|
@ -84,47 +88,36 @@ function genere_balise_img_multiformats($id, $lazy = false) {
|
|||
|
||||
/**
|
||||
* TODO.
|
||||
*
|
||||
* @param WC_Product $a
|
||||
* @param WC_Product $b
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
function tri_variations_par_prix_descendant($a, $b) {
|
||||
if ($a->get_price() === $b->get_price()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $a->get_price() < $b->get_price() ? 1 : -1;
|
||||
function tri_variations_par_prix_descendant(WC_Product $a, WC_Product $b): int {
|
||||
return $b->get_price() <=> $a->get_price();
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les informations utilisées pour la grille des Produits et les retourne sous forme
|
||||
* de tableau associatif.
|
||||
*
|
||||
* @param WC_Product $produit
|
||||
*
|
||||
* @return mixed un tableau avec uniquement les informations pour la Grille de Produits
|
||||
*/
|
||||
function recupere_informations_produit_shop($produit) {
|
||||
function recupere_informations_produit_shop(WC_Product $produit): mixed {
|
||||
/** @var int $prix_maximal Le prix maximal du Produit. */
|
||||
$prix_maximal = pipe(
|
||||
// Récupère les Variations
|
||||
$produit->get_children(),
|
||||
// Récupère les informations de chaque Variation
|
||||
fn($enfants) => array_map(
|
||||
static fn($enfants): array => array_map(
|
||||
callback: wc_get_product(...),
|
||||
array: $enfants,
|
||||
),
|
||||
// Trie les Variations par prix descendant
|
||||
fn($variations) => array_map(
|
||||
callback: fn($variation) => $variation->get_price(),
|
||||
static fn($variations): array => array_map(
|
||||
callback: static fn($variation) => $variation->get_price(),
|
||||
array: $variations,
|
||||
),
|
||||
// Récupère le Prix de la Variation la plus chère
|
||||
fn($prix) => collect($prix)->max(),
|
||||
static fn($prix) => collect($prix)->max(),
|
||||
// Récupère le Prix pour la Variation la plus chère OU le prix du Produit simple
|
||||
fn($prix_variation_maximale) => $prix_variation_maximale ?? $produit->get_price(),
|
||||
static fn($prix_variation_maximale) => $prix_variation_maximale ?? $produit->get_price(),
|
||||
);
|
||||
|
||||
// TEMP: Cas de la Carte Cadeau où aucun prix ne doit être affiché. Idéalement utiliser un système d'étiquettes pour ces cas là.
|
||||
|
|
@ -158,49 +151,50 @@ function recupere_informations_produit_shop($produit) {
|
|||
|
||||
/**
|
||||
* Retourne un tableau associatif des informations affichées sur la page Produit depuis les données brutes d'un Produit.
|
||||
*
|
||||
* @param WC_Product $donnees_produit
|
||||
*/
|
||||
function recupere_informations_produit_page_produit($donnees_produit): mixed {
|
||||
function recupere_informations_produit_page_produit(WC_Product $product): mixed {
|
||||
/** @var list<Attribute> */
|
||||
$attributs = Product::get_attributes_for_product($product);
|
||||
|
||||
return [
|
||||
// Attributs du Produit
|
||||
'attributs' => wc_get_product()->get_attributes(),
|
||||
'attributs' => $attributs,
|
||||
// Catégorie du Produit
|
||||
'categorie' => pipe($donnees_produit->get_id(), wc_get_product_category_list(...), strtolower(...)),
|
||||
'categorie' => pipe($product->get_id(), wc_get_product_category_list(...), strtolower(...)),
|
||||
// Slug de la Collection - Peut ne pas avoir été défini
|
||||
'collection' => get_the_terms($donnees_produit->get_id(), 'collection')[0]->slug ?? '',
|
||||
'collection' => get_the_terms($product->get_id(), 'collection')[0]->slug ?? '',
|
||||
// Détails (Description) du Produit
|
||||
'details' => wpautop($donnees_produit->get_description()),
|
||||
'details' => wpautop($product->get_description()),
|
||||
// Identifiant du Produit
|
||||
'id' => $donnees_produit->get_id(),
|
||||
'id' => $product->get_id(),
|
||||
// Nom affiché du Produit
|
||||
'nom' => $donnees_produit->get_name(),
|
||||
'nom' => $product->get_name(),
|
||||
// Prix affiché du Produit
|
||||
'prix' => $donnees_produit->get_price(),
|
||||
'prix' => $product->get_price(),
|
||||
'photos_colonne_gauche' => array_map(
|
||||
callback: 'genere_balise_img_multiformats',
|
||||
array: get_post_meta($post_id = $donnees_produit->get_id(), $key = '_photos_colonne_gauche|||0|value'),
|
||||
callback: genere_balise_img_multiformats(...),
|
||||
array: get_post_meta($post_id = $product->get_id(), $key = '_photos_colonne_gauche|||0|value'),
|
||||
),
|
||||
'photos_colonne_droite' => array_map(
|
||||
callback: 'genere_balise_img_multiformats',
|
||||
callback: genere_balise_img_multiformats(...),
|
||||
array: carbon_get_the_post_meta('photos_colonne_droite'),
|
||||
),
|
||||
'photo_repos' => genere_balise_img_multiformats(
|
||||
get_post_meta($post_id = $donnees_produit->get_id(), $key = '_photos_colonne_gauche|||0|value')[0] ?? -1,
|
||||
get_post_meta($post_id = $product->get_id(), $key = '_photos_colonne_gauche|||0|value')[0] ?? -1,
|
||||
false,
|
||||
),
|
||||
'photo_survol' => genere_balise_img_multiformats(
|
||||
get_post_meta($post_id = $donnees_produit->get_id(), $key = '_photos_colonne_droite|||0|value')[0] ?? -1,
|
||||
get_post_meta($post_id = $product->get_id(), $key = '_photos_colonne_droite|||0|value')[0] ?? -1,
|
||||
true,
|
||||
),
|
||||
// Slug du Produit
|
||||
'slug' => $donnees_produit->get_slug(),
|
||||
'slug' => $product->get_slug(),
|
||||
// Quantité de Produit en stock
|
||||
'stock' => $donnees_produit->get_stock_quantity() ?? 1,
|
||||
'stock' => $product->get_stock_quantity() ?? 1,
|
||||
// Variations du Produit
|
||||
'variations_ids' => $donnees_produit->get_children(),
|
||||
'variations_ids' => $product->get_children(),
|
||||
// URL du Produit
|
||||
'url' => $donnees_produit->get_permalink(),
|
||||
'url' => $product->get_permalink(),
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -209,14 +203,10 @@ function recupere_informations_produit_page_produit($donnees_produit): mixed {
|
|||
* collection) et les retourne sous forme de tableau associatif.
|
||||
*
|
||||
* Pour faciliter l'usage avec `array_map`, utilise une fonction avec curryfication.
|
||||
*
|
||||
* @param string $slug_collection
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
function recupere_produits_meme_collection($slug_collection) {
|
||||
function recupere_produits_meme_collection(string $slug_collection): mixed {
|
||||
// @param int $id_produit
|
||||
return fn($id_produit) => wc_get_products([
|
||||
return static fn($id_produit) => wc_get_products([
|
||||
'exclude' => [$id_produit],
|
||||
'limit' => 4,
|
||||
'order' => 'DESC',
|
||||
|
|
@ -228,12 +218,7 @@ function recupere_produits_meme_collection($slug_collection) {
|
|||
|
||||
// Page Panier
|
||||
|
||||
/**
|
||||
* @param mixed $attributs_produit
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
function recupere_et_formate_attributs_produit($attributs_produit) {
|
||||
function recupere_et_formate_attributs_produit(mixed $attributs_produit): mixed {
|
||||
return [
|
||||
'taille' => ['nom' => 'Size', 'valeur' => $attributs_produit['pa_size'] ?? false],
|
||||
'pierre' => ['nom' => 'Stone', 'valeur' => $attributs_produit['pa_stone'] ?? false],
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace HaikuAtelier\WP;
|
||||
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
use function is_array;
|
||||
use function is_string;
|
||||
|
||||
final readonly class HaikuProduct {
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
public static function get_left_column_photos(int $post_id): array {
|
||||
/** @var list<string> */
|
||||
return Post::get_post_meta_array($post_id, '_photos_colonne_gauche|||0|value')->unwrapOr([])
|
||||
|> (static fn(array $meta) => Arr::where($meta, static fn($meta): bool => is_string($meta)))
|
||||
|> (static fn(array $array) => Arr::map($array, genere_balise_img_multiformats(...)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<string>
|
||||
*/
|
||||
public static function get_right_column_photos(int $post_id): array {
|
||||
$meta = carbon_get_post_meta($post_id, 'photos_colonne_droite');
|
||||
|
||||
if (is_array($meta)) {
|
||||
/** @var list<string> */
|
||||
return Arr::where($meta, static fn($meta): bool => is_string($meta))
|
||||
|> (static fn(array $array) => Arr::map($array, genere_balise_img_multiformats(...)));
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
55
web/app/themes/haiku-atelier-2024/src/inc/WP/Post.php
Normal file
55
web/app/themes/haiku-atelier-2024/src/inc/WP/Post.php
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace HaikuAtelier\WP;
|
||||
|
||||
use Psl\Option;
|
||||
use function Psl\Option\none;
|
||||
use function Psl\Option\some;
|
||||
|
||||
use WP_Error;
|
||||
use WP_Term;
|
||||
use function is_array;
|
||||
|
||||
final readonly class Post {
|
||||
/**
|
||||
* @return Option\Option<mixed>
|
||||
*/
|
||||
public static function get_post_meta(int $post_id, string $key): Option\Option {
|
||||
/** @var false|mixed|string */
|
||||
$value = get_post_meta($post_id, $key, true);
|
||||
|
||||
if ($value === false) {
|
||||
return none();
|
||||
}
|
||||
|
||||
return some($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Option\Option<array<mixed>>
|
||||
*/
|
||||
public static function get_post_meta_array(int $post_id, string $key): Option\Option {
|
||||
/** @var array<mixed>|false */
|
||||
$value = get_post_meta($post_id, $key, false);
|
||||
|
||||
if (is_array($value)) {
|
||||
return some($value);
|
||||
}
|
||||
|
||||
return none();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Option\Option<array<mixed>>
|
||||
*/
|
||||
public static function get_terms(int $post_id, string $taxonomy_name): Option\Option {
|
||||
/** @var false|list<WP_Term>|WP_Error */
|
||||
$terms = get_the_terms($post_id, $taxonomy_name);
|
||||
|
||||
if (is_array($terms)) {
|
||||
return some($terms);
|
||||
}
|
||||
|
||||
return none();
|
||||
}
|
||||
}
|
||||
26
web/app/themes/haiku-atelier-2024/src/inc/WP/Term.php
Normal file
26
web/app/themes/haiku-atelier-2024/src/inc/WP/Term.php
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<?php declare(strict_types=1);
|
||||
|
||||
namespace HaikuAtelier\WP;
|
||||
|
||||
use Psl\Option;
|
||||
use function Psl\Option\none;
|
||||
|
||||
use function Psl\Option\some;
|
||||
use WP_Term;
|
||||
use function is_array;
|
||||
|
||||
final readonly class Term {
|
||||
/**
|
||||
* @return Option\Option<list<WP_Term>>
|
||||
*/
|
||||
public static function get_terms(int $post_id, string $taxonomy_name): Option\Option {
|
||||
$terms = get_the_terms($post_id, $taxonomy_name);
|
||||
|
||||
if (is_array($terms)) {
|
||||
/** @var Option\Option<list<WP_Term>> */
|
||||
return some($terms);
|
||||
}
|
||||
|
||||
return none();
|
||||
}
|
||||
}
|
||||
|
|
@ -59,7 +59,10 @@
|
|||
font-size: var(--resume-police-nom-taille);
|
||||
}
|
||||
|
||||
&__selection-variation {
|
||||
&__attribut-variation {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
gap: var(--espace-m) var(--espace-l);
|
||||
font-size: var(--resume-police-selecteur-taille);
|
||||
font-weight: var(--resume-police-selecteur-graisse);
|
||||
text-transform: lowercase;
|
||||
|
|
@ -98,7 +101,7 @@
|
|||
pointer-events: none;
|
||||
content: " ";
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
top: 7px;
|
||||
right: 0.4rem;
|
||||
display: inline-block;
|
||||
width: 0.9rem;
|
||||
|
|
@ -148,7 +151,7 @@
|
|||
}
|
||||
|
||||
@media (width <= 500px) {
|
||||
.selecteur-produit__selection-variation {
|
||||
.selecteur-produit__selection-variation-attribut {
|
||||
flex-flow: column nowrap;
|
||||
row-gap: var(--espace-inter-colonne);
|
||||
|
||||
|
|
|
|||
|
|
@ -4,12 +4,13 @@ import { pipe } from "@mobily/ts-belt";
|
|||
import { forEach as arrayForEach } from "@mobily/ts-belt/Array";
|
||||
import { get as dictGet } from "@mobily/ts-belt/Dict";
|
||||
import { tap as optionTap } from "@mobily/ts-belt/Option";
|
||||
import { pipe as epipe } from "effect";
|
||||
import { EitherAsync, Maybe } from "purify-ts";
|
||||
import { match, P } from "ts-pattern";
|
||||
import { type AnySchema, ValiError } from "valibot";
|
||||
|
||||
import type { WCStoreCart } from "./lib/types/api/cart";
|
||||
import type { WCStoreCartAddItemArgs } from "./lib/types/api/cart-add-item.ts";
|
||||
import type { WCStoreCartAddItemArgs, WCStoreCartAddItemArgsItems } from "./lib/types/api/cart-add-item.ts";
|
||||
import type { FetchErrors } from "./lib/types/reseau.ts";
|
||||
|
||||
import { ROUTE_API_AJOUTE_ARTICLE_PANIER } from "./constantes/api.ts";
|
||||
|
|
@ -23,8 +24,8 @@ import {
|
|||
DOM_BOUTON_AJOUT_PANIER,
|
||||
DOM_BOUTONS_ACCORDEON,
|
||||
DOM_CONTENUS_ACCORDEON,
|
||||
DOM_PRIX_PRODUIT,
|
||||
DOM_DOM_QUANTITE,
|
||||
DOM_PRIX_PRODUIT,
|
||||
} from "./constantes/dom.ts";
|
||||
import { lanceAnimationCycleLoading } from "./lib/animations.ts";
|
||||
import { mustGetEleInDocument, mustGetElesInDocument, recupereElementDocumentEither } from "./lib/dom.ts";
|
||||
|
|
@ -64,8 +65,9 @@ const E = {
|
|||
BOUTON_AJOUT_PANIER: mustGetEleInDocument<HTMLButtonElement>(DOM_BOUTON_AJOUT_PANIER),
|
||||
BOUTONS_ACCORDEON: mustGetElesInDocument<HTMLAnchorElement>(DOM_BOUTONS_ACCORDEON),
|
||||
CONTENUS_ACCORDEON: mustGetElesInDocument<HTMLDivElement>(DOM_CONTENUS_ACCORDEON),
|
||||
PRIX_PRODUIT: mustGetEleInDocument<HTMLParagraphElement>(DOM_PRIX_PRODUIT),
|
||||
DOM_VARIATION: recupereElementDocumentEither<HTMLSelectElement>(DOM_DOM_QUANTITE),
|
||||
PRIX_PRODUIT: mustGetEleInDocument<HTMLParagraphElement>(DOM_PRIX_PRODUIT),
|
||||
VARIATION_CHOICE_FORM: mustGetEleInDocument<HTMLFormElement>("#variation-choice"),
|
||||
};
|
||||
|
||||
const gereAccordeonDetailsProduit = (): void => {
|
||||
|
|
@ -119,17 +121,39 @@ const gereAccordeonDetailsProduit = (): void => {
|
|||
});
|
||||
})
|
||||
);
|
||||
E.BOUTON_AJOUT_PANIER.addEventListener("click", (): void => ajouteProduitAuPanier());
|
||||
E.BOUTON_AJOUT_PANIER.addEventListener("click", (event: MouseEvent): void => ajouteProduitAuPanier(event));
|
||||
};
|
||||
|
||||
const ajouteProduitAuPanier = (): void => {
|
||||
const getAttributeValuesFromDom = () => {
|
||||
const selectElements = epipe(
|
||||
document.querySelectorAll<HTMLSelectElement>(".selecteur-produit select"),
|
||||
Array.from<HTMLSelectElement>,
|
||||
);
|
||||
if (selectElements.length === 0) return [];
|
||||
|
||||
const attributeValues = selectElements.map(select => {
|
||||
return {
|
||||
attribute: select.id.replace("selecteur-attribut-", ""),
|
||||
value: select.value,
|
||||
} satisfies WCStoreCartAddItemArgsItems;
|
||||
});
|
||||
|
||||
return attributeValues;
|
||||
};
|
||||
|
||||
const ajouteProduitAuPanier = (event: MouseEvent): void => {
|
||||
event.preventDefault();
|
||||
console.debug("getAttributeValuesFromDom", getAttributeValuesFromDom());
|
||||
|
||||
// Construis les arguments de la requête au backend
|
||||
const argsRequete: WCStoreCartAddItemArgs = {
|
||||
id: E.DOM_VARIATION
|
||||
.map((selecteur: HTMLSelectElement): number => Number(selecteur.value))
|
||||
// Récupère l'ID du Produit de la Page pour les Produits simples
|
||||
.orDefault(ETATS_PAGE.idProduit),
|
||||
// id: E.DOM_VARIATION
|
||||
// .map((selecteur: HTMLSelectElement): number => Number(selecteur.value))
|
||||
// // Récupère l'ID du Produit de la Page pour les Produits simples
|
||||
// .orDefault(ETATS_PAGE.idProduit),
|
||||
id: ETATS_PAGE.idProduit,
|
||||
quantity: 1,
|
||||
variation: getAttributeValuesFromDom(),
|
||||
};
|
||||
|
||||
// Réalise la Requête et traite sa Réponse
|
||||
|
|
@ -209,6 +233,28 @@ const ajouteProduitAuPanier = (): void => {
|
|||
.run();
|
||||
};
|
||||
|
||||
const initAddToCartButtonActivationOnUserChoice = (): void => {
|
||||
const selectElements: ReadonlyArray<HTMLSelectElement> = epipe(
|
||||
document.querySelectorAll<HTMLSelectElement>(".selecteur-produit select"),
|
||||
Array.from<HTMLSelectElement>,
|
||||
);
|
||||
// S'il n'y a pas de sélecteur de variation, activer le bouton.
|
||||
if (selectElements.length === 0) {
|
||||
E.BOUTON_AJOUT_PANIER.removeAttribute(ATTRIBUT_DESACTIVE);
|
||||
}
|
||||
|
||||
E.VARIATION_CHOICE_FORM.addEventListener("change", (): void => {
|
||||
const formValidity = E.VARIATION_CHOICE_FORM.checkValidity();
|
||||
|
||||
if (formValidity) {
|
||||
E.BOUTON_AJOUT_PANIER.removeAttribute(ATTRIBUT_DESACTIVE);
|
||||
} else {
|
||||
E.BOUTON_AJOUT_PANIER.setAttribute(ATTRIBUT_DESACTIVE, "");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
document.addEventListener("DOMContentLoaded", (): void => {
|
||||
gereAccordeonDetailsProduit();
|
||||
initAddToCartButtonActivationOnUserChoice();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ $informations_produits = wc_get_products([
|
|||
|
||||
/** @var InformationsProduitShop $produits Les informations strictement nécessaires pour la grille des Produits. */
|
||||
$produits = array_map(
|
||||
callback: 'recupere_informations_produit_shop',
|
||||
callback: recupere_informations_produit_shop(...),
|
||||
array: $informations_produits,
|
||||
);
|
||||
$contexte['produits'] = $produits;
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@
|
|||
loading="eager"
|
||||
src="{{ rel_url }}.jpg"
|
||||
width="{{ width }}"
|
||||
onload="this.style.opacity=1"
|
||||
onload="this.style.opacity = 1"
|
||||
>
|
||||
</picture>
|
||||
{% endmacro %}
|
||||
|
|
|
|||
|
|
@ -1,49 +1,63 @@
|
|||
{# Barre flottante avec le nom du Produit, le sélectgeur de variation et de quantité pour le Panier. #}
|
||||
{# Barre flottante avec le nom du Produit, le sélecteur de variation et de quantité pour le Panier. #}
|
||||
|
||||
<aside
|
||||
aria-label="Product's name, price and variation selection"
|
||||
class="resume-produit"
|
||||
>
|
||||
<section class="selecteur-produit">
|
||||
<h3 class="selecteur-produit__nom">{{ produit.nom }}</h3>
|
||||
<form
|
||||
class="selecteur-produit"
|
||||
id="variation-choice"
|
||||
name="variation-choice"
|
||||
>
|
||||
<h3 class="selecteur-produit__nom">{{ produit.name }}</h3>
|
||||
|
||||
<div class="selecteur-produit__selection-variation">
|
||||
{% if variations_produit|length > 1 %}
|
||||
<label
|
||||
for="selecteur-variation"
|
||||
id="label-selecteur-variation"
|
||||
>
|
||||
Option:
|
||||
</label>
|
||||
|
||||
<div class="selecteur-produit__selection-variation__selecteurs">
|
||||
<select
|
||||
aria-labelledby="label-selecteur-variation"
|
||||
id="selecteur-variation"
|
||||
name="variations"
|
||||
>
|
||||
<option
|
||||
disabled
|
||||
selected
|
||||
value=""
|
||||
>
|
||||
--
|
||||
</option>
|
||||
{% for variation in variations_produit %}
|
||||
<option
|
||||
data-prix="{{ variation.prix }}"
|
||||
value="{{ variation.id }}"
|
||||
>
|
||||
{{ variation.titre }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="selecteur-produit__attribut-variation">
|
||||
{% if produit.attributes %}
|
||||
{% for attribut in produit.attributes %}
|
||||
<div class="test">
|
||||
{{ include('parts/pages/produit/selecteur-attributs-produit.twig') }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<!--
|
||||
{% if variations_produit|length > 1 %}
|
||||
<label
|
||||
for="selecteur-variation"
|
||||
id="label-selecteur-variation"
|
||||
>
|
||||
Option:
|
||||
</label>
|
||||
|
||||
<div class="selecteur-produit__attribut-variation__selecteurs">
|
||||
<select
|
||||
aria-labelledby="label-selecteur-variation"
|
||||
id="selecteur-variation"
|
||||
name="variations"
|
||||
>
|
||||
<option
|
||||
disabled
|
||||
selected
|
||||
value=""
|
||||
>
|
||||
--
|
||||
</option>
|
||||
{% for variation in variations_produit %}
|
||||
<option
|
||||
data-prix="{{ variation.prix }}"
|
||||
value="{{ variation.id }}"
|
||||
>
|
||||
{{ variation.titre }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
{% endif %}
|
||||
-->
|
||||
</div>
|
||||
|
||||
<p class="selecteur-produit__prix">{{ prix_maximal ?? produit.prix }}€</p>
|
||||
</section>
|
||||
<p class="selecteur-produit__prix">{{ prix_maximal ?? produit.price }}€</p>
|
||||
</form>
|
||||
</aside>
|
||||
|
||||
<aside
|
||||
|
|
@ -107,12 +121,14 @@
|
|||
</div>
|
||||
|
||||
<div class="details-produit__actions">
|
||||
{# Désactive le bouton d'ajout au panier en cas d'absence de stock. #}
|
||||
{% if produit.stock > 0 %}
|
||||
<button
|
||||
class="bouton-case-pleine"
|
||||
{{ variations_produit|length > 1 ? 'disabled' : '' }}
|
||||
disabled
|
||||
for="variation-choice"
|
||||
id="bouton-ajout-panier"
|
||||
type="button"
|
||||
type="submit"
|
||||
>
|
||||
Add to cart
|
||||
</button>
|
||||
|
|
@ -120,6 +136,7 @@
|
|||
<button
|
||||
class="bouton-case-pleine"
|
||||
disabled
|
||||
for="variation-choice"
|
||||
id="bouton-ajout-panier"
|
||||
type="button"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
aria-label="Photo of the Product alone"
|
||||
class="colonne colonne-gauche"
|
||||
>
|
||||
{% for photo in produit.photos_colonne_gauche %}
|
||||
{% for photo in produit.left_column_photos %}
|
||||
<figure
|
||||
data-index="0"
|
||||
role="figure"
|
||||
|
|
@ -19,7 +19,7 @@
|
|||
aria-label="Photos of the Product worn"
|
||||
class="colonne colonne-droite"
|
||||
>
|
||||
{% for photo in produit.photos_colonne_droite %}
|
||||
{% for photo in produit.right_column_photos %}
|
||||
<figure
|
||||
data-index="{{ loop.index }}"
|
||||
role="figure"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
<div class="selecteur-produit__attribut-variation__selecteurs">
|
||||
<label
|
||||
for="selecteur-attribut-{{ attribut.slug }}"
|
||||
id="label-selecteur-attribut-{{ attribut.slug }}"
|
||||
>
|
||||
{{ attribut.name }}:
|
||||
</label>
|
||||
|
||||
<select
|
||||
aria-labelledby="label-selecteur-attribut-{{ atribut.slug }}"
|
||||
id="selecteur-attribut-{{ attribut.slug }}"
|
||||
name="attribut-{{ attribut.slug }}"
|
||||
required
|
||||
>
|
||||
<option
|
||||
disabled
|
||||
selected
|
||||
value=""
|
||||
>
|
||||
--
|
||||
</option>
|
||||
{% for term in attribut.options %}
|
||||
<option value="{{ term.slug }}">
|
||||
{{ term.name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
Loading…
Add table
Add a link
Reference in a new issue