fonc(produit) implémente le multi-variations

This commit is contained in:
gcch 2025-12-15 15:34:50 +01:00
commit 05baad8fdd
26 changed files with 1320 additions and 184 deletions

View file

@ -1,4 +1,4 @@
<?php
<?php declare(strict_types=1);
use PhpCsFixer\Config;
use PhpCsFixer\Finder;

View file

@ -1,5 +1,5 @@
{
"$schema": "/phpactor.schema.json",
"$schema": "./phpactor.schema.json",
"indexer.exclude_patterns": [
"/vendor/**/Tests/**/*",
"/vendor/**/tests/**/*",
@ -7,7 +7,7 @@
"/vendor/composer/**/*"
],
"language_server.diagnostic_outsource_timeout": 5,
"language_server.diagnostics_on_update": true,
"language_server.diagnostics_on_update": false,
"language_server.diagnostics_on_save": true,
"language_server_highlight.enabled": true,
"language_server_php_cs_fixer.enabled": true,

View file

@ -44,7 +44,7 @@
"oxlint": "^1.31.0",
"picomatch": "^4.0.3",
"playwright": "^1.57.0",
"prettier": "^3.7.4",
"prettier": "^4.0.0-alpha.13",
"prettier-plugin-pkg": "^0.21.2",
"prettier-plugin-sh": "^0.18.0",
"sass-embedded": "^1.93.3",
@ -1182,7 +1182,7 @@
"prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
"prettier": ["prettier@3.7.4", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA=="],
"prettier": ["prettier@4.0.0-alpha.13", "http://localhost:4873/prettier/-/prettier-4.0.0-alpha.13.tgz", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-177K/2S5iYDKtvZkDC2ZMqpyXtoiiVQBVQZpcQsRs+ZIIUQxsXomWIjquAlwt2pXpio9riz5IgtaUEnoZH44tg=="],
"prettier-plugin-pkg": ["prettier-plugin-pkg@0.21.2", "", { "peerDependencies": { "prettier": "^3.0.3" } }, "sha512-CSlM5+51B7yTKcoRWT4M3ImcdFHD5NUz0Xu2t8J03B761zu6J3BjSo/XleKp2kB0tH49K7oG5Uuqn6ldI5LRLg=="],

View file

@ -1,4 +1,5 @@
{
"autoload": { "psr-4": { "HaikuAtelier\\": "web/app/themes/haiku-atelier-2024/src/inc/" } },
"authors": [
{ "email": "scott.walkinshaw@gmail.com", "homepage": "https://github.com/swalkinshaw", "name": "Scott Walkinshaw" },
{ "email": "ben@benword.com", "homepage": "https://github.com/retlehs", "name": "Ben Word" }
@ -35,17 +36,18 @@
{ "only": ["wpackagist-plugin/*", "wpackagist-theme/*"], "type": "composer", "url": "https://wpackagist.org" }
],
"require": {
"php": ">=8.5",
"azjezz/psl": "^4.2",
"composer/installers": "^2.3",
"crell/fp": "^1.0",
"htmlburger/carbon-fields": "^3.6",
"illuminate/support": "^12.18",
"illuminate/support": "^12.43",
"laravel/helpers": "^1.7.1",
"log1x/wp-smtp": "^1.0.2",
"lstrojny/functional-php": "^1.17",
"mnsami/composer-custom-directory-installer": "^2.0",
"nesbot/carbon": "^3.8.2",
"oscarotero/env": "^2.1.1",
"php": ">=8.4",
"roots/bedrock-autoloader": "^1.0.4",
"roots/bedrock-disallow-indexing": "^2.0",
"roots/wordpress": "^6.8.1",
@ -68,8 +70,10 @@
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.89",
"php-standard-library/phpstan-extension": "^2.0",
"phpstan/extension-installer": "^1.4.3",
"phpstan/phpstan": "^2.0.3",
"rector/rector": "^2.2",
"roave/security-advisories": "dev-latest",
"szepeviktor/phpstan-wordpress": "2.x-dev",
"vincentlanglet/twig-cs-fixer": "^3.10"

298
composer.lock generated
View file

@ -4,8 +4,84 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "d87a6e00e0233e0f2255f18aadf2d42c",
"content-hash": "656fefb6a4e896ee0c5f5d87f36db997",
"packages": [
{
"name": "azjezz/psl",
"version": "4.2.0",
"source": {
"type": "git",
"url": "https://github.com/azjezz/psl.git",
"reference": "15153a64c9824335ce11654522e7d88de762d39e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/azjezz/psl/zipball/15153a64c9824335ce11654522e7d88de762d39e",
"reference": "15153a64c9824335ce11654522e7d88de762d39e",
"shasum": ""
},
"require": {
"ext-bcmath": "*",
"ext-intl": "*",
"ext-json": "*",
"ext-mbstring": "*",
"ext-sodium": "*",
"php": "~8.3.0 || ~8.4.0 || ~8.5.0",
"revolt/event-loop": "^1.0.7"
},
"require-dev": {
"carthage-software/mago": "^1.0.0-beta.32",
"infection/infection": "^0.31.2",
"php-coveralls/php-coveralls": "^2.7.0",
"phpbench/phpbench": "^1.4.0",
"phpunit/phpunit": "^9.6.22"
},
"suggest": {
"php-standard-library/phpstan-extension": "PHPStan integration",
"php-standard-library/psalm-plugin": "Psalm integration"
},
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/hhvm/hsl",
"name": "hhvm/hsl"
}
},
"autoload": {
"files": [
"src/bootstrap.php"
],
"psr-4": {
"Psl\\": "src/Psl"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "azjezz",
"email": "azjezz@protonmail.com"
}
],
"description": "PHP Standard Library",
"support": {
"issues": "https://github.com/azjezz/psl/issues",
"source": "https://github.com/azjezz/psl/tree/4.2.0"
},
"funding": [
{
"url": "https://github.com/azjezz",
"type": "github"
},
{
"url": "https://github.com/veewee",
"type": "github"
}
],
"time": "2025-10-25T08:31:40+00:00"
},
{
"name": "carbonphp/carbon-doctrine-types",
"version": "3.2.0",
@ -585,7 +661,7 @@
},
{
"name": "illuminate/collections",
"version": "v12.42.0",
"version": "v12.43.1",
"source": {
"type": "git",
"url": "https://github.com/illuminate/collections.git",
@ -644,7 +720,7 @@
},
{
"name": "illuminate/conditionable",
"version": "v12.42.0",
"version": "v12.43.1",
"source": {
"type": "git",
"url": "https://github.com/illuminate/conditionable.git",
@ -690,7 +766,7 @@
},
{
"name": "illuminate/contracts",
"version": "v12.42.0",
"version": "v12.43.1",
"source": {
"type": "git",
"url": "https://github.com/illuminate/contracts.git",
@ -738,7 +814,7 @@
},
{
"name": "illuminate/macroable",
"version": "v12.42.0",
"version": "v12.43.1",
"source": {
"type": "git",
"url": "https://github.com/illuminate/macroable.git",
@ -784,7 +860,7 @@
},
{
"name": "illuminate/reflection",
"version": "v12.42.0",
"version": "v12.43.1",
"source": {
"type": "git",
"url": "https://github.com/illuminate/reflection.git",
@ -835,16 +911,16 @@
},
{
"name": "illuminate/support",
"version": "v12.42.0",
"version": "v12.43.1",
"source": {
"type": "git",
"url": "https://github.com/illuminate/support.git",
"reference": "d35411be5657e0b5560a5885f3c9140e4cbe0be5"
"reference": "20014564c32e2e8c6a57e03d065bcf8706a458e7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/illuminate/support/zipball/d35411be5657e0b5560a5885f3c9140e4cbe0be5",
"reference": "d35411be5657e0b5560a5885f3c9140e4cbe0be5",
"url": "https://api.github.com/repos/illuminate/support/zipball/20014564c32e2e8c6a57e03d065bcf8706a458e7",
"reference": "20014564c32e2e8c6a57e03d065bcf8706a458e7",
"shasum": ""
},
"require": {
@ -911,7 +987,7 @@
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
"time": "2025-12-09T15:26:52+00:00"
"time": "2025-12-14T15:58:12+00:00"
},
{
"name": "laravel/helpers",
@ -1604,6 +1680,78 @@
},
"time": "2021-10-29T13:26:27+00:00"
},
{
"name": "revolt/event-loop",
"version": "v1.0.8",
"source": {
"type": "git",
"url": "https://github.com/revoltphp/event-loop.git",
"reference": "b6fc06dce8e9b523c9946138fa5e62181934f91c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/revoltphp/event-loop/zipball/b6fc06dce8e9b523c9946138fa5e62181934f91c",
"reference": "b6fc06dce8e9b523c9946138fa5e62181934f91c",
"shasum": ""
},
"require": {
"php": ">=8.1"
},
"require-dev": {
"ext-json": "*",
"jetbrains/phpstorm-stubs": "^2019.3",
"phpunit/phpunit": "^9",
"psalm/phar": "^5.15"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "1.x-dev"
}
},
"autoload": {
"psr-4": {
"Revolt\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Aaron Piotrowski",
"email": "aaron@trowski.com"
},
{
"name": "Cees-Jan Kiewiet",
"email": "ceesjank@gmail.com"
},
{
"name": "Christian Lück",
"email": "christian@clue.engineering"
},
{
"name": "Niklas Keller",
"email": "me@kelunik.com"
}
],
"description": "Rock-solid event loop for concurrent PHP applications.",
"keywords": [
"async",
"asynchronous",
"concurrency",
"event",
"event-loop",
"non-blocking",
"scheduler"
],
"support": {
"issues": "https://github.com/revoltphp/event-loop/issues",
"source": "https://github.com/revoltphp/event-loop/tree/v1.0.8"
},
"time": "2025-08-27T21:33:23+00:00"
},
{
"name": "roots/bedrock-autoloader",
"version": "1.0.4",
@ -3930,16 +4078,16 @@
},
{
"name": "friendsofphp/php-cs-fixer",
"version": "v3.92.1",
"version": "v3.92.2",
"source": {
"type": "git",
"url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
"reference": "ed33ad03313a019533ba065eba6c86af0a382873"
"reference": "64fab3553dce507ce247f7d1a7d65f74ef658c3f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/ed33ad03313a019533ba065eba6c86af0a382873",
"reference": "ed33ad03313a019533ba065eba6c86af0a382873",
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/64fab3553dce507ce247f7d1a7d65f74ef658c3f",
"reference": "64fab3553dce507ce247f7d1a7d65f74ef658c3f",
"shasum": ""
},
"require": {
@ -4022,7 +4170,7 @@
],
"support": {
"issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues",
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.92.1"
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.92.2"
},
"funding": [
{
@ -4030,7 +4178,61 @@
"type": "github"
}
],
"time": "2025-12-15T23:09:01+00:00"
"time": "2025-12-17T00:04:16+00:00"
},
{
"name": "php-standard-library/phpstan-extension",
"version": "2.0.2",
"source": {
"type": "git",
"url": "https://github.com/php-standard-library/phpstan-extension.git",
"reference": "eaf787ddf91f1b22b3a4da052f42294e0a75836e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-standard-library/phpstan-extension/zipball/eaf787ddf91f1b22b3a4da052f42294e0a75836e",
"reference": "eaf787ddf91f1b22b3a4da052f42294e0a75836e",
"shasum": ""
},
"require": {
"php": "^7.4 || ^8.0",
"phpstan/phpstan": "^2.0"
},
"conflict": {
"azjezz/psl": "<1.6||>=5.0"
},
"require-dev": {
"azjezz/psl": "^1.6||^2.0||^3.0||^4.0",
"composer/semver": "^3.3",
"nikic/php-parser": "^4.14.0",
"php-parallel-lint/php-parallel-lint": "^1.2",
"phpstan/phpstan-phpunit": "^2.0",
"phpstan/phpstan-strict-rules": "^2.0",
"phpunit/phpunit": "^9.6"
},
"type": "phpstan-extension",
"extra": {
"phpstan": {
"includes": [
"extension.neon"
]
}
},
"autoload": {
"psr-4": {
"Psl\\PHPStan\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "PHPStan PSL extension",
"support": {
"issues": "https://github.com/php-standard-library/phpstan-extension/issues",
"source": "https://github.com/php-standard-library/phpstan-extension/tree/2.0.2"
},
"time": "2025-11-30T13:33:52+00:00"
},
{
"name": "php-stubs/wordpress-stubs",
@ -4810,6 +5012,66 @@
],
"time": "2024-06-11T12:45:25+00:00"
},
{
"name": "rector/rector",
"version": "2.2.14",
"source": {
"type": "git",
"url": "https://github.com/rectorphp/rector.git",
"reference": "6d56bb0e94d4df4f57a78610550ac76ab403657d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/rectorphp/rector/zipball/6d56bb0e94d4df4f57a78610550ac76ab403657d",
"reference": "6d56bb0e94d4df4f57a78610550ac76ab403657d",
"shasum": ""
},
"require": {
"php": "^7.4|^8.0",
"phpstan/phpstan": "^2.1.33"
},
"conflict": {
"rector/rector-doctrine": "*",
"rector/rector-downgrade-php": "*",
"rector/rector-phpunit": "*",
"rector/rector-symfony": "*"
},
"suggest": {
"ext-dom": "To manipulate phpunit.xml via the custom-rule command"
},
"bin": [
"bin/rector"
],
"type": "library",
"autoload": {
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "Instant Upgrade and Automated Refactoring of any PHP code",
"homepage": "https://getrector.com/",
"keywords": [
"automation",
"dev",
"migration",
"refactoring"
],
"support": {
"issues": "https://github.com/rectorphp/rector/issues",
"source": "https://github.com/rectorphp/rector/tree/2.2.14"
},
"funding": [
{
"url": "https://github.com/tomasvotruba",
"type": "github"
}
],
"time": "2025-12-09T10:57:55+00:00"
},
{
"name": "roave/security-advisories",
"version": "dev-latest",
@ -7106,7 +7368,7 @@
"prefer-stable": true,
"prefer-lowest": false,
"platform": {
"php": ">=8.4"
"php": ">=8.5"
},
"platform-dev": {},
"plugin-api-version": "2.6.0"

View file

@ -76,12 +76,12 @@
},
"newLineKind": "lf",
"plugins": [
"https://plugins.dprint.dev/typescript-0.95.12.wasm",
"https://plugins.dprint.dev/typescript-0.95.13.wasm",
"https://plugins.dprint.dev/json-0.21.0.wasm",
"https://plugins.dprint.dev/markdown-0.20.0.wasm",
"https://plugins.dprint.dev/toml-0.7.0.wasm",
"https://plugins.dprint.dev/g-plane/malva-v0.15.0.wasm",
"https://plugins.dprint.dev/g-plane/markup_fmt-v0.24.0.wasm",
"https://plugins.dprint.dev/g-plane/malva-v0.15.1.wasm",
"https://plugins.dprint.dev/g-plane/markup_fmt-v0.25.3.wasm",
"https://plugins.dprint.dev/g-plane/pretty_yaml-v0.5.1.wasm",
"https://plugins.dprint.dev/exec-0.6.0.json@a054130d458f124f9b5c91484833828950723a5af3f8ff2bd1523bd47b83b364"
],

View file

@ -48,7 +48,7 @@
"oxlint": "^1.31.0",
"picomatch": "^4.0.3",
"playwright": "^1.57.0",
"prettier": "^3.7.4",
"prettier": "^4.0.0-alpha.13",
"prettier-plugin-pkg": "^0.21.2",
"prettier-plugin-sh": "^0.18.0",
"sass-embedded": "^1.93.3",

492
phpactor.schema.json Normal file
View file

@ -0,0 +1,492 @@
{
"$schema": "https:\/\/json-schema.org\/draft-07\/schema",
"title": "Phpactor Configuration Schema",
"type": "object",
"properties": {
"$schema": {
"description": "Path to JSON schema, which can be used for config autocompletion, use phpactor config:initialize to update",
"default": ""
},
"console_dumper_default": {
"description": "Name of the \"dumper\" (renderer) to use for some CLI commands",
"default": "indented"
},
"xdebug_disable": { "description": "If XDebug should be automatically disabled", "default": true },
"command": { "description": "Internal use only - name of the command which was executed" },
"core.min_memory_limit": {
"description": "Ensure that PHP has a memory_limit of at least this amount in bytes",
"default": 1610612736
},
"class_to_file.project_root": {
"description": "Root path of the project (e.g. where composer.json is)",
"default": "%project_root%"
},
"class_to_file.brute_force_conversion": {
"description": "If composer not found, fallback to scanning all files (very time consuming depending on project size)",
"default": true
},
"code_transform.class_new.variants": {
"description": "Variants which should be suggested when class-create is invoked",
"default": []
},
"code_transform.template_paths": {
"description": "Paths in which to look for code templates",
"default": ["%project_config%\/templates", "%config%\/templates"]
},
"code_transform.indentation": {
"description": "Indentation chars to use in code generation and transformation",
"default": " "
},
"code_transform.refactor.generate_accessor.prefix": {
"description": "Prefix to use for generated accessors",
"default": ""
},
"code_transform.refactor.generate_accessor.upper_case_first": {
"description": "If the first letter of a generated accessor should be made uppercase",
"default": false
},
"code_transform.refactor.generate_mutator.prefix": {
"description": "Prefix to use for generated mutators",
"default": "set"
},
"code_transform.refactor.generate_mutator.upper_case_first": {
"description": "If the first letter of a generated mutator should be made uppercase",
"default": true
},
"code_transform.refactor.generate_mutator.fluent": {
"description": "If the mutator should be fluent",
"default": false
},
"code_transform.import_globals": {
"description": "Import functions even if they are in the global namespace",
"default": false
},
"code_transform.refactor.object_fill.hint": {
"description": "Object fill refactoring: show hint as a comment",
"default": true
},
"code_transform.refactor.object_fill.named_parameters": {
"description": "Object fill refactoring: use named parameters",
"default": true
},
"completion_worse.completor.doctrine_annotation.enabled": {
"description": "Enable or disable the ``doctrine_annotation`` completor.\n\nCompletion for annotations provided by the Doctrine annotation library.",
"default": true
},
"completion_worse.completor.imported_names.enabled": {
"description": "Enable or disable the ``imported_names`` completor.\n\nCompletion for names imported into the current namespace.",
"default": true
},
"completion_worse.completor.worse_parameter.enabled": {
"description": "Enable or disable the ``worse_parameter`` completor.\n\nCompletion for method or function parameters.",
"default": true
},
"completion_worse.completor.named_parameter.enabled": {
"description": "Enable or disable the ``named_parameter`` completor.\n\nCompletion for named parameters.",
"default": true
},
"completion_worse.completor.constructor.enabled": {
"description": "Enable or disable the ``constructor`` completor.\n\nCompletion for constructors.",
"default": true
},
"completion_worse.completor.class_member.enabled": {
"description": "Enable or disable the ``class_member`` completor.\n\nCompletion for class members.",
"default": true
},
"completion_worse.completor.scf_class.enabled": {
"description": "Enable or disable the ``scf_class`` completor.\n\nBrute force completion for class names (not recommended).",
"default": true
},
"completion_worse.completor.local_variable.enabled": {
"description": "Enable or disable the ``local_variable`` completor.\n\nCompletion for local variables.",
"default": true
},
"completion_worse.completor.subscript.enabled": {
"description": "Enable or disable the ``subscript`` completor.\n\nCompletion for subscript (array access from array shapes).",
"default": true
},
"completion_worse.completor.declared_function.enabled": {
"description": "Enable or disable the ``declared_function`` completor.\n\nCompletion for functions defined in the Phpactor runtime.",
"default": true
},
"completion_worse.completor.declared_constant.enabled": {
"description": "Enable or disable the ``declared_constant`` completor.\n\nCompletion for constants defined in the Phpactor runtime.",
"default": true
},
"completion_worse.completor.declared_class.enabled": {
"description": "Enable or disable the ``declared_class`` completor.\n\nCompletion for classes defined in the Phpactor runtime.",
"default": true
},
"completion_worse.completor.expression_name_search.enabled": {
"description": "Enable or disable the ``expression_name_search`` completor.\n\nCompletion for class names, constants and functions at expression positions that are located in the index.",
"default": true
},
"completion_worse.completor.use.enabled": {
"description": "Enable or disable the ``use`` completor.\n\nCompletion for use imports.",
"default": true
},
"completion_worse.completor.attribute.enabled": {
"description": "Enable or disable the ``attribute`` completor.\n\nCompletion for attribute class names.",
"default": true
},
"completion_worse.completor.class_like.enabled": {
"description": "Enable or disable the ``class_like`` completor.\n\nCompletion for class like contexts.",
"default": true
},
"completion_worse.completor.type.enabled": {
"description": "Enable or disable the ``type`` completor.\n\nCompletion for scalar types.",
"default": true
},
"completion_worse.completor.keyword.enabled": {
"description": "Enable or disable the ``keyword`` completor.\n\nCompletion for keywords (not very accurate).",
"default": true
},
"completion_worse.completor.docblock.enabled": {
"description": "Enable or disable the ``docblock`` completor.\n\nDocblock completion.",
"default": true
},
"completion_worse.completor.constant.enabled": { "description": null, "default": false },
"completion_worse.completor.class.limit": {
"description": "Suggestion limit for the filesystem based SCF class_completor",
"default": 100
},
"completion_worse.name_completion_priority": {
"description": "Strategy to use when ordering completion results for classes and functions:\n\n- `proximity`: Classes and functions will be ordered by their proximity to the text document being edited.\n- `none`: No ordering will be applied.",
"default": "proximity"
},
"completion_worse.snippets": { "description": "Enable or disable completion snippets", "default": true },
"completion_worse.experimantal": { "description": "Enable experimental functionality", "default": false },
"completion_worse.debug": { "description": "Include debug info in completion results", "default": false },
"completion.dedupe": { "description": "If results should be de-duplicated", "default": true },
"completion.dedupe_match_fqn": {
"description": "If ``completion.dedupe``, consider the class FQN in addition to the completion suggestion",
"default": true
},
"completion.limit": { "description": "Sets a limit on the number of completion suggestions for any request" },
"completion.label_formatter": {
"description": "Definition of how to format entries in the completion list",
"default": "helpful",
"enum": ["helpful", "fqn"]
},
"navigator.destinations": { "description": null, "default": [] },
"navigator.autocreate": { "description": null, "default": [] },
"rpc.store_replay": { "description": "Should replays be stored?", "default": false },
"rpc.replay_path": { "description": "Path where the replays should be stored", "default": "%cache%\/replay.json" },
"source_code_filesystem.project_root": { "description": null, "default": "%project_root%" },
"language_server_code_transform.import_globals": {
"description": "Show hints for non-imported global classes and functions",
"default": false
},
"worse_reflection.enable_cache": { "description": "If reflection caching should be enabled", "default": true },
"worse_reflection.cache_lifetime": {
"description": "If caching is enabled, limit the amount of time a cache entry can stay alive",
"default": 1
},
"worse_reflection.enable_context_location": {
"description": "If source code is passed to a ``Reflector`` then temporarily make it available as a\nsource location. Note this should NOT be enabled if the source code can be\nlocated in another (e.g. when running a Language Server)",
"default": true
},
"worse_reflection.cache_dir": {
"description": "Cache directory for stubs",
"default": "%cache%\/worse-reflection"
},
"worse_reflection.stub_dir": {
"description": "Location of the core PHP stubs - these will be scanned and cached on the first request",
"default": "%application_root%\/vendor\/jetbrains\/phpstorm-stubs"
},
"worse_reflection.diagnostics.undefined_variable.suggestion_levenshtein_disatance": {
"description": "Levenshtein distance to use when suggesting corrections for variable names",
"type": ["integer"],
"default": 4
},
"file_path_resolver.project_root": { "description": null, "default": "\/opt\/phpactor" },
"file_path_resolver.app_name": { "description": null, "default": "phpactor" },
"file_path_resolver.application_root": { "description": null },
"file_path_resolver.enable_cache": { "description": null, "default": true },
"file_path_resolver.enable_logging": { "description": null, "default": true },
"logging.enabled": { "description": null, "type": ["boolean"], "default": false },
"logging.fingers_crossed": { "description": null, "type": ["boolean"], "default": false },
"logging.path": { "description": null, "type": ["string"], "default": "application.log" },
"logging.level": {
"description": null,
"type": ["string"],
"default": "warning",
"enum": ["emergency", "alert", "critical", "error", "warning", "notice", "info", "debug"]
},
"logger.name": { "description": null, "type": ["string"], "default": "logger" },
"logging.formatter": { "description": null },
"composer.enable": {
"description": "Include of the projects autoloader to facilitate class location. Note that when including an autoloader code _may_ be executed. This option may be disabled when using the indexer",
"default": true
},
"composer.autoloader_path": {
"description": "Path to project's autoloader, can be an array",
"default": "%project_root%\/vendor\/autoload.php"
},
"composer.autoload_deregister": {
"description": "Immediately de-register the autoloader once it has been included (prevent conflicts with Phpactor's autoloader). Some platforms may require this to be disabled",
"default": true
},
"composer.class_maps_only": {
"description": "Register the composer class maps only, do not register the autoloader - RECOMMENDED",
"default": true
},
"console.verbosity": { "description": "Verbosity level", "default": 32, "enum": [16, 32, 64, 128, 256] },
"console.decorated": {
"description": "Whether to decorate messages (null for auto-guessing)",
"enum": [true, false, null]
},
"php.version": {
"description": "Consider this value to be the project\\'s version of PHP (e.g. `7.4`). If omitted\nit will check `composer.json` (by the configured platform then the PHP requirement) before\nfalling back to the PHP version of the current process."
},
"language_server.catch_errors": { "description": null, "default": true },
"language_server.enable_workspace": {
"description": "If workspace management \/ text synchronization should be enabled (this isn't required for some language server implementations, e.g. static analyzers)",
"default": true
},
"language_server.session_parameters": {
"description": "Phpactor parameters (config) that apply only to the language server session",
"default": []
},
"language_server.method_alias_map": {
"description": "Allow method names to be re-mapped. Useful for maintaining backwards compatibility",
"default": []
},
"language_server.diagnostic_sleep_time": {
"description": "Amount of time to wait before analyzing the code again for diagnostics",
"default": 1000
},
"language_server.diagnostics_on_update": {
"description": "Perform diagnostics when the text document is updated",
"default": true
},
"language_server.diagnostics_on_save": {
"description": "Perform diagnostics when the text document is saved",
"default": true
},
"language_server.diagnostics_on_open": {
"description": "Perform diagnostics when opening a text document",
"default": true
},
"language_server.diagnostic_providers": {
"description": "Specify which diagnostic providers should be active (default to all)"
},
"language_server.diagnostic_outsource": {
"description": "If applicable diagnostics should be \"outsourced\" to a different process",
"default": true
},
"language_server.diagnostic_exclude_paths": {
"description": "List of paths to exclude from diagnostics, e.g. `vendor\/**\/*`",
"default": []
},
"language_server.file_events": { "description": "Register to receive file events", "default": true },
"language_server.file_event_globs": { "description": null, "default": ["**\/*.php"] },
"language_server.profile": { "description": "Logs timing information for incoming LSP requests", "default": false },
"language_server.trace": {
"description": "Log incoming and outgoing messages (needs log formatter to be set to ``json``)",
"default": false
},
"language_server.shutdown_grace_period": {
"description": "Amount of time (in milliseconds) to wait before responding to a shutdown notification",
"default": 200
},
"language_server.phpactor_bin": {
"description": "Internal use only - name path to Phpactor binary",
"default": "\/opt\/phpactor\/lib\/Extension\/LanguageServer\/..\/..\/..\/bin\/phpactor"
},
"language_server.self_destruct_timeout": {
"description": "Wait this amount of time (in milliseconds) after a shutdown request before self-destructing",
"default": 2500
},
"language_server.diagnostic_outsource_timeout": {
"description": "Kill the diagnostics process if it outlives this timeout",
"default": 5
},
"language_server_completion.trim_leading_dollar": {
"description": "If the leading dollar should be trimmed for variable completion suggestions",
"default": false
},
"language_server_reference_reference_finder.reference_timeout": {
"description": "Stop searching for references after this time (in seconds) has expired",
"default": 60
},
"language_server_worse_reflection.workspace_index.update_interval": {
"description": "Minimum interval to update the workspace index as documents are updated (in milliseconds)",
"default": 100
},
"language_server_worse_reflection.inlay_hints.enable": {
"description": "Enable inlay hints (experimental)",
"default": false
},
"language_server_worse_reflection.inlay_hints.types": {
"description": "Show inlay type hints for variables",
"default": false
},
"language_server_worse_reflection.inlay_hints.params": {
"description": "Show inlay hints for parameters",
"default": true
},
"language_server_worse_reflection.diagnostics.enable": { "description": "Enable diagnostics", "default": true },
"language_server_indexer.workspace_symbol_search_limit": { "description": null, "default": 250 },
"language_server_indexer.reindex_timeout": {
"description": "Unconditionally reindex modified files every N seconds",
"default": 300
},
"language_server_code_transform.import_name.report_non_existing_names": {
"description": "Show an error if a diagnostic name cannot be resolved - can produce false positives",
"default": true
},
"language_server_configuration.auto_config": {
"description": "Prompt to enable extensions which apply to your project on language server start",
"type": ["boolean"],
"default": true
},
"indexer.enabled_watchers": {
"description": "List of allowed watchers. The first watcher that supports the current system will be used",
"type": ["object"],
"default": ["inotify", "watchman", "find", "php"]
},
"indexer.index_path": {
"description": "Path where the index should be saved",
"type": ["string"],
"default": "%cache%\/index\/%project_id%"
},
"indexer.include_patterns": {
"description": "Glob patterns to include while indexing",
"type": ["object"],
"default": ["\/**\/*.php", "\/**\/*.phar"]
},
"indexer.exclude_patterns": {
"description": "Glob patterns to exclude while indexing",
"type": ["object"],
"default": ["\/vendor\/**\/Tests\/**\/*", "\/vendor\/**\/tests\/**\/*", "\/vendor\/composer\/**\/*"]
},
"indexer.stub_paths": {
"description": "Paths to external folders to index. They will be indexed only once, if you want to take any changes into account you will have to reindex your project manually.",
"type": ["object"],
"default": []
},
"indexer.poll_time": {
"description": "For polling indexers only: the time, in milliseconds, between polls (e.g. filesystem scans)",
"type": ["integer"],
"default": 5000
},
"indexer.buffer_time": {
"description": "For real-time indexers only: the time, in milliseconds, to buffer the results",
"type": ["integer"],
"default": 500
},
"indexer.follow_symlinks": {
"description": "To allow indexer to follow symlinks",
"type": ["boolean"],
"default": false
},
"indexer.project_root": {
"description": "The root path to use for scanning the index",
"type": ["string"],
"default": "%project_root%"
},
"indexer.reference_finder.deep": {
"description": "Recurse over class implementations to resolve all references",
"type": ["boolean"],
"default": true
},
"indexer.implementation_finder.deep": {
"description": "Recurse over class implementations to resolve all class implementations (not just the classes directly implementing the subject)",
"type": ["boolean"],
"default": true
},
"indexer.supported_extensions": {
"description": "File extensions (e.g. `php`) for files that should be indexed",
"type": ["object"],
"default": ["php", "phar"]
},
"object_renderer.template_paths.markdown": {
"description": "Paths in which to look for templates for hover information.",
"default": ["%project_config%\/templates\/markdown", "%config%\/templates\/markdown"]
},
"language_server_phpstan.bin": {
"description": "Path to the PHPStan executable",
"default": "%project_root%\/vendor\/bin\/phpstan"
},
"language_server_phpstan.level": { "description": "Override the PHPStan level" },
"language_server_phpstan.config": { "description": "Override the PHPStan configuration file" },
"language_server_phpstan.mem_limit": { "description": "Override the PHPStan memory limit" },
"language_server_psalm.bin": {
"description": "Path to psalm if different from vendor\/bin\/psalm",
"type": ["string"],
"default": "%project_root%\/vendor\/bin\/psalm"
},
"language_server_psalm.show_info": {
"description": "If infos from psalm should be displayed",
"type": ["boolean"],
"default": true
},
"language_server_psalm.use_cache": {
"description": "If the Psalm cache should be used (see the `--no-cache` option)",
"type": ["boolean"],
"default": true
},
"language_server_psalm.error_level": {
"description": "Override level at which Psalm should report errors (lower => more errors)"
},
"language_server_psalm.threads": {
"description": "Set the number of threads Psalm should use. Warning: NULL will use as many as possible and may crash your computer",
"type": ["integer"],
"default": 1
},
"language_server_psalm.timeout": {
"description": "Kill the psalm process after this number of seconds",
"type": ["integer"],
"default": 15
},
"language_server_php_cs_fixer.bin": {
"description": "Path to the php-cs-fixer executable",
"default": "%project_root%\/vendor\/bin\/php-cs-fixer"
},
"language_server_php_cs_fixer.env": {
"description": "Environment for PHP CS Fixer (e.g. to set PHP_CS_FIXER_IGNORE_ENV)",
"default": { "XDEBUG_MODE": "off", "PHP_CS_FIXER_IGNORE_ENV": true }
},
"language_server_php_cs_fixer.show_diagnostics": {
"description": "Whether PHP CS Fixer diagnostics are shown",
"default": true
},
"language_server_php_cs_fixer.config": {
"description": "Set custom PHP CS config path. Ex., %project_root%\/.php-cs-fixer.php"
},
"php_code_sniffer.bin": {
"description": "Path to the phpcs executable",
"default": "%project_root%\/vendor\/bin\/phpcs"
},
"php_code_sniffer.env": {
"description": "Environment for PHP_CodeSniffer (e.g. to set XDEBUG_MODE)",
"default": { "XDEBUG_MODE": "off" }
},
"php_code_sniffer.show_diagnostics": {
"description": "Whether PHP_CodeSniffer diagnostics are shown",
"default": true
},
"php_code_sniffer.args": { "description": "Additional arguments to pass to the PHPCS process", "default": [] },
"php_code_sniffer.cwd": { "description": "Working directory for PHPCS" },
"behat.config_path": {
"description": "Path to the main behat.yml (including the filename behat.yml)",
"default": "%project_root%\/behat.yml"
},
"behat.symfony.di_xml_path": {
"description": "If using Symfony, set this path to the XML container dump to find contexts which are defined as services"
},
"symfony.xml_path": {
"description": "Path to the Symfony container XML dump file",
"default": "%project_root%\/var\/cache\/dev\/App_KernelDevDebugContainer.xml"
},
"completion_worse.completor.symfony.enabled": {
"description": "Enable\/disable the Symfony completor - depends on Symfony extension being enabled",
"default": true
},
"public_services_only": {
"description": "Only consider public services when providing analysis for the service locator",
"default": false
}
}
}

24
rector.php Normal file
View file

@ -0,0 +1,24 @@
<?php declare(strict_types=1);
use Rector\Config\RectorConfig;
return RectorConfig::configure()
->withPaths([__DIR__ . '/web/app/themes/haiku-atelier-2024'])
->withSkip([__DIR__ . '/vendor', __DIR__ . '/node_modules'])
->withPhpSets(php85: true)
->withCodeQualityLevel(10)
->withCodingStyleLevel(10)
->withDeadCodeLevel(10)
->withTypeCoverageDocblockLevel(10)
->withTypeCoverageLevel(10)
->withImportNames(
importDocBlockNames: true,
importNames: true,
importShortClasses: true,
removeUnusedImports: true,
)
->withPreparedSets(
carbon: true,
instanceOf: true,
privatization: true,
);

View file

@ -1 +0,0 @@
/var/www/wordpress/web/app/plugins/query-monitor/wp-content/db.php

View file

@ -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

View file

@ -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,
);

View 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,
);
}
}

View file

@ -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,
);
}
}

View 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,
);
}
}

View file

@ -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],

View file

@ -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 [];
}
}

View 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();
}
}

View 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();
}
}

View file

@ -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);

View file

@ -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();
});

View file

@ -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;

View file

@ -24,7 +24,7 @@
loading="eager"
src="{{ rel_url }}.jpg"
width="{{ width }}"
onload="this.style.opacity=1"
onload="this.style.opacity = 1"
>
</picture>
{% endmacro %}

View file

@ -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"
>

View file

@ -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"

View file

@ -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>