From e01cd343bc4410625c404fcf5658ef98fda429ac Mon Sep 17 00:00:00 2001 From: gcch Date: Mon, 13 Apr 2026 11:45:00 +0200 Subject: [PATCH] fonc: travail en cours sur scripts page Produit --- .php-cs-fixer.dist.php | 498 +++++++++--------- bun.lock | 24 +- composer.lock | 118 ++++- config/application.php | 34 +- package.json | 8 +- scripts/remove-scaled-images.php | 40 +- web/app/themes/haiku-atelier-2024/404.php | 11 +- .../themes/haiku-atelier-2024/front-page.php | 16 +- .../themes/haiku-atelier-2024/page-about.php | 18 +- .../haiku-atelier-2024/page-checkout.php | 156 +++--- .../haiku-atelier-2024/page-failed-order.php | 8 +- .../page-terms-and-conditions.php | 8 +- .../src/inc/ChampsPersonnalises.php | 57 +- .../src/inc/ControlesPersonnalises.php | 97 ++-- .../haiku-atelier-2024/src/inc/Data/Cart.php | 207 ++++---- .../src/inc/Data/ProductVariation.php | 54 +- .../inc/Data/ProductVariationAttribute.php | 19 +- .../src/inc/Fonctionnalites.php | 43 +- .../haiku-atelier-2024/src/inc/Taxonomies.php | 51 +- .../haiku-atelier-2024/src/inc/WP/Post.php | 68 ++- .../src/scripts-effect/schemas/api.ts | 10 + .../src/scripts-effect/schemas/product.ts | 20 + .../src/scripts/page-produit/runtime.ts | 13 + .../service-dom.ts} | 227 ++++---- .../scripts/page-produit/service-elements.ts | 70 +++ .../src/scripts/page-produit/types.d.ts | 6 + .../src/scripts/scripts-page-produit.ts | 57 +- .../emails/customer-completed-order.php | 16 +- .../woocommerce/emails/customer-invoice.php | 79 ++- .../emails/customer-processing-order.php | 71 ++- 30 files changed, 1110 insertions(+), 994 deletions(-) create mode 100644 web/app/themes/haiku-atelier-2024/src/scripts-effect/schemas/api.ts create mode 100644 web/app/themes/haiku-atelier-2024/src/scripts-effect/schemas/product.ts create mode 100644 web/app/themes/haiku-atelier-2024/src/scripts/page-produit/runtime.ts rename web/app/themes/haiku-atelier-2024/src/scripts/{scripts-page-produit-service.ts => page-produit/service-dom.ts} (52%) create mode 100644 web/app/themes/haiku-atelier-2024/src/scripts/page-produit/service-elements.ts create mode 100644 web/app/themes/haiku-atelier-2024/src/scripts/page-produit/types.d.ts diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 7211fb82..80e9eb2d 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -7,257 +7,253 @@ use PhpCsFixer\Finder; use PhpCsFixer\Runner; $finder = new Finder()->in(__DIR__)->exclude([ - 'vendor', - 'web/vendor', - 'web/wp', - 'web/app/languages', - 'web/app/plugins', - 'web/app/mu-plugins', + 'vendor', + 'web/vendor', + 'web/wp', + 'web/app/languages', + 'web/app/plugins', + 'web/app/mu-plugins', ]); return new Config() - ->setRiskyAllowed(true) - ->setRules([ - 'array_syntax' => ['syntax' => 'short'], - 'assign_null_coalescing_to_coalesce_equal' => true, - 'attribute_empty_parentheses' => ['use_parentheses' => true], - 'blank_line_after_namespace' => true, - 'blank_lines_before_namespace' => ['min_line_breaks' => 1, 'max_line_breaks' => 2], - 'cast_spaces' => true, - 'class_attributes_separation' => ['elements' => [ - 'case' => 'none', - 'const' => 'none', - 'method' => 'one', - 'property' => 'one', - 'trait_import' => 'none', - ]], - 'class_reference_name_casing' => true, - 'clean_namespace' => true, - 'combine_consecutive_issets' => true, - 'combine_consecutive_unsets' => true, - 'combine_nested_dirname' => true, - 'comment_to_phpdoc' => true, - 'constant_case' => true, - 'date_time_immutable' => true, - 'declare_equal_normalize' => true, - 'declare_parentheses' => true, - 'declare_strict_types' => true, - 'dir_constant' => true, - 'echo_tag_syntax' => true, - 'encoding' => true, - 'ereg_to_preg' => true, - 'error_suppression' => true, - 'explicit_indirect_variable' => true, - 'explicit_string_variable' => true, - 'final_class' => true, - 'final_internal_class' => true, - 'full_opening_tag' => true, - 'fully_qualified_strict_types' => ['import_symbols' => true], - 'function_to_constant' => true, - 'global_namespace_import' => ['import_classes' => true, 'import_constants' => true, 'import_functions' => true], - 'heredoc_to_nowdoc' => true, - 'integer_literal_case' => true, - 'lambda_not_used_import' => true, - 'list_syntax' => true, - 'logical_operators' => true, - 'long_to_shorthand_operator' => true, - 'lowercase_cast' => true, - 'lowercase_keywords' => true, - 'lowercase_static_reference' => true, - 'magic_constant_casing' => true, - 'magic_method_casing' => true, - 'mb_str_functions' => true, - 'modernize_strpos' => ['modernize_stripos' => true], - 'modernize_types_casting' => true, - 'modifier_keywords' => true, - 'multiline_comment_opening_closing' => true, - 'native_constant_invocation' => true, - 'native_function_casing' => true, - 'native_function_invocation' => [ - 'include' => ['@compiler_optimized'], - 'scope' => 'namespaced', - 'strict' => true, - ], - 'native_type_declaration_casing' => true, - 'new_expression_parentheses' => true, - 'no_alias_functions' => ['sets' => ['@all']], - 'no_alias_language_construct_call' => true, - 'no_alternative_syntax' => true, - 'no_binary_string' => true, - 'no_closing_tag' => true, - 'no_empty_comment' => true, - 'no_homoglyph_names' => true, - 'no_leading_import_slash' => true, - 'no_mixed_echo_print' => ['use' => 'echo'], - 'no_multiline_whitespace_around_double_arrow' => true, - 'no_multiple_statements_per_line' => true, - 'no_null_property_initialization' => true, - 'no_php4_constructor' => true, - 'no_short_bool_cast' => true, - 'no_trailing_comma_in_singleline' => true, - 'no_trailing_whitespace_in_comment' => true, - 'no_unneeded_braces' => ['namespaces' => true], - 'no_unneeded_control_parentheses' => ['statements' => [ - 'break', - 'clone', - 'continue', - 'echo_print', - 'negative_instanceof', - 'others', - 'return', - 'switch_case', - 'yield', - 'yield_from', - ]], - 'no_unneeded_final_method' => true, - 'no_unneeded_import_alias' => true, - 'no_unreachable_default_argument_value' => true, - 'no_unset_cast' => true, - 'no_unset_on_property' => true, - 'no_unused_imports' => true, - 'no_useless_concat_operator' => true, - 'no_useless_nullsafe_operator' => true, - 'no_useless_printf' => true, - 'no_useless_return' => true, - 'no_useless_sprintf' => true, - 'no_whitespace_before_comma_in_array' => ['after_heredoc' => true], - 'non_printable_character' => true, - 'normalize_index_brace' => true, - 'nullable_type_declaration' => ['syntax' => 'union'], - 'nullable_type_declaration_for_default_null_value' => true, - 'numeric_literal_separator' => ['override_existing' => true, 'strategy' => 'use_separator'], - 'ordered_attributes' => true, - 'ordered_class_elements' => ['case_sensitive' => false, 'sort_algorithm' => 'alpha'], - 'ordered_imports' => ['case_sensitive' => true], - 'ordered_interfaces' => true, - 'ordered_traits' => true, - 'ordered_types' => ['null_adjustment' => 'always_last'], - 'phpdoc_readonly_class_comment_to_keyword' => true, - 'phpdoc_to_param_type' => true, - 'phpdoc_to_property_type' => true, - 'phpdoc_to_return_type' => true, - 'pow_to_exponentiation' => true, - 'protected_to_private' => true, - 'psr_autoloading' => true, - 'random_api_migration' => ['replacements' => [ - 'getrandmax' => 'mt_getrandmax', - 'rand' => 'mt_rand', - 'srand' => 'mt_srand', - ]], - 'return_assignment' => true, - 'self_accessor' => true, - 'self_static_accessor' => true, - 'set_type_to_cast' => true, - 'short_scalar_cast' => true, - 'simple_to_complex_string_variable' => true, - 'simplified_null_return' => true, - 'single_class_element_per_statement' => true, - 'single_import_per_statement' => true, - 'single_line_after_imports' => true, - 'single_line_comment_spacing' => true, - 'single_line_comment_style' => true, - 'single_line_empty_body' => true, - 'single_trait_insert_per_statement' => true, - 'standardize_not_equals' => true, - 'static_lambda' => true, - 'strict_comparison' => true, - 'strict_param' => true, - 'string_implicit_backslashes' => true, - 'string_length_to_empty' => true, - 'switch_continue_to_break' => true, - 'ternary_to_null_coalescing' => true, - 'trim_array_spaces' => true, - 'use_arrow_functions' => true, - 'void_return' => true, - 'whitespace_after_comma_in_array' => ['ensure_single_space' => true], - // --- - // Each line of multi-line DocComments must have an asterisk [PSR-5] and must be aligned with the first one. - 'align_multiline_comment' => ['comment_type' => 'all_multiline'], - // There should not be blank lines between docblock and the documented element. - 'no_blank_lines_after_phpdoc' => true, - // There should not be empty PHPDoc blocks. - 'no_empty_phpdoc' => true, - // Removes @param, @return and @var tags that don't provide any useful information. - 'no_superfluous_phpdoc_tags' => [ - 'allow_hidden_params' => false, - 'allow_mixed' => false, - 'allow_unused_params' => false, - ], - // PHPDoc should contain @param for all params. - 'phpdoc_add_missing_param_annotation' => ['only_untyped' => false], - // All items of the given PHPDoc tags must be either left-aligned or (by default) aligned vertically. - 'phpdoc_align' => true, - // PHPDoc annotation descriptions should not be a sentence. - 'phpdoc_annotation_without_dot' => true, - // PHPDoc array type must be used instead of T[]. - 'phpdoc_array_type' => true, - // Docblocks should have the same indentation as the documented subject. - 'phpdoc_indent' => true, - // Fixes PHPDoc inline tags. - 'phpdoc_inline_tag_normalizer' => true, - // Changes doc blocks from single to multi line, or reversed. Works for class constants, properties and methods only. - 'phpdoc_line_span' => ['const' => 'single', 'method' => 'multi', 'property' => 'single'], - // PHPDoc list type must be used instead of array without a key. - 'phpdoc_list_type' => false, - // @access annotations must be removed from PHPDoc. - 'phpdoc_no_access' => true, - // No alias PHPDoc tags should be used. - 'phpdoc_no_alias_tag' => true, - // @return void and @return null annotations must be removed from PHPDoc. - 'phpdoc_no_empty_return' => false, - // @package and @subpackage annotations must be removed from PHPDoc. - 'phpdoc_no_package' => true, - // Classy that does not inherit must not have @inheritdoc tags. - 'phpdoc_no_useless_inheritdoc' => true, - // Annotations in PHPDoc should be ordered in defined sequence. - 'phpdoc_order' => true, - // Order PHPDoc tags by value. - 'phpdoc_order_by_value' => true, - // Orders all @param annotations in DocBlocks according to method signature. - 'phpdoc_param_order' => true, - // The type of @return annotations of methods returning a reference to itself must the configured one. - 'phpdoc_return_self_reference' => true, - // Scalar types should always be written in the same form. int not integer, bool not boolean, float not real or double. - 'phpdoc_scalar' => ['types' => [ - 'boolean', - 'callback', - 'double', - 'integer', - 'never-return', - 'never-returns', - 'no-return', - 'real', - 'str', - ]], - // Annotations in PHPDoc should be grouped together so that annotations of the same type immediately follow each other. Annotations of a different type are separated by a single blank line. - 'phpdoc_separation' => [ - 'groups' => [ - ['Annotation', 'NamedArgumentConstructor', 'Target'], - ['author', 'copyright', 'license'], - ['category', 'package', 'subpackage'], - ['property', 'property-read', 'property-write'], - ['deprecated', 'link', 'see', 'since'], - ], - 'skip_unlisted_annotations' => false, - ], - // Single line @var PHPDoc should have proper spacing. - 'phpdoc_single_line_var_spacing' => true, - // PHPDoc summary should end in either a full stop, exclamation mark, or question mark. - 'phpdoc_summary' => true, - // Docblocks should only be used on structural elements. - 'phpdoc_to_comment' => false, - // PHPDoc should start and end with content, excluding the very first and last line of the docblocks. - 'phpdoc_trim' => true, - // Removes extra blank lines after summary and after description in PHPDoc. - 'phpdoc_trim_consecutive_blank_line_separation' => true, - // The correct case must be used for standard PHP types in PHPDoc. - 'phpdoc_types' => true, - // Sorts PHPDoc types. - 'phpdoc_types_order' => ['null_adjustment' => 'always_last'], - // @var and @type annotations must have type and name in the correct order. - 'phpdoc_var_annotation_correct_order' => true, - // @var and @type annotations of classy properties should not contain the name. - 'phpdoc_var_without_name' => true, - ]) - ->setFinder($finder) - ->setParallelConfig(Runner\Parallel\ParallelConfigFactory::detect()); + ->setRiskyAllowed(true) + ->setRules([ + 'array_syntax' => ['syntax' => 'short'], + 'assign_null_coalescing_to_coalesce_equal' => true, + 'attribute_empty_parentheses' => ['use_parentheses' => true], + 'blank_line_after_namespace' => true, + 'blank_lines_before_namespace' => ['min_line_breaks' => 1, 'max_line_breaks' => 2], + 'cast_spaces' => true, + 'class_attributes_separation' => ['elements' => [ + 'case' => 'none', + 'const' => 'none', + 'method' => 'one', + 'property' => 'one', + 'trait_import' => 'none', + ]], + 'class_reference_name_casing' => true, + 'clean_namespace' => true, + 'combine_consecutive_issets' => true, + 'combine_consecutive_unsets' => true, + 'combine_nested_dirname' => true, + 'comment_to_phpdoc' => true, + 'constant_case' => true, + 'date_time_immutable' => true, + 'declare_equal_normalize' => true, + 'declare_parentheses' => true, + 'declare_strict_types' => true, + 'dir_constant' => true, + 'echo_tag_syntax' => true, + 'encoding' => true, + 'ereg_to_preg' => true, + 'error_suppression' => true, + 'explicit_indirect_variable' => true, + 'explicit_string_variable' => true, + 'final_class' => true, + 'final_internal_class' => true, + 'full_opening_tag' => true, + 'fully_qualified_strict_types' => ['import_symbols' => true], + 'function_to_constant' => true, + 'global_namespace_import' => ['import_classes' => true, 'import_constants' => true, 'import_functions' => true], + 'heredoc_to_nowdoc' => true, + 'integer_literal_case' => true, + 'lambda_not_used_import' => true, + 'list_syntax' => true, + 'logical_operators' => true, + 'long_to_shorthand_operator' => true, + 'lowercase_cast' => true, + 'lowercase_keywords' => true, + 'lowercase_static_reference' => true, + 'magic_constant_casing' => true, + 'magic_method_casing' => true, + 'mb_str_functions' => true, + 'modernize_strpos' => ['modernize_stripos' => true], + 'modernize_types_casting' => true, + 'modifier_keywords' => true, + 'multiline_comment_opening_closing' => true, + 'native_constant_invocation' => true, + 'native_function_casing' => true, + 'native_function_invocation' => ['include' => ['@compiler_optimized'], 'scope' => 'namespaced', 'strict' => true], + 'native_type_declaration_casing' => true, + 'new_expression_parentheses' => true, + 'no_alias_functions' => ['sets' => ['@all']], + 'no_alias_language_construct_call' => true, + 'no_alternative_syntax' => true, + 'no_binary_string' => true, + 'no_closing_tag' => true, + 'no_empty_comment' => true, + 'no_homoglyph_names' => true, + 'no_leading_import_slash' => true, + 'no_mixed_echo_print' => ['use' => 'echo'], + 'no_multiline_whitespace_around_double_arrow' => true, + 'no_multiple_statements_per_line' => true, + 'no_null_property_initialization' => true, + 'no_php4_constructor' => true, + 'no_short_bool_cast' => true, + 'no_trailing_comma_in_singleline' => true, + 'no_trailing_whitespace_in_comment' => true, + 'no_unneeded_braces' => ['namespaces' => true], + 'no_unneeded_control_parentheses' => ['statements' => [ + 'break', + 'clone', + 'continue', + 'echo_print', + 'negative_instanceof', + 'others', + 'return', + 'switch_case', + 'yield', + 'yield_from', + ]], + 'no_unneeded_final_method' => true, + 'no_unneeded_import_alias' => true, + 'no_unreachable_default_argument_value' => true, + 'no_unset_cast' => true, + 'no_unset_on_property' => true, + 'no_unused_imports' => true, + 'no_useless_concat_operator' => true, + 'no_useless_nullsafe_operator' => true, + 'no_useless_printf' => true, + 'no_useless_return' => true, + 'no_useless_sprintf' => true, + 'no_whitespace_before_comma_in_array' => ['after_heredoc' => true], + 'non_printable_character' => true, + 'normalize_index_brace' => true, + 'nullable_type_declaration' => ['syntax' => 'union'], + 'nullable_type_declaration_for_default_null_value' => true, + 'numeric_literal_separator' => ['override_existing' => true, 'strategy' => 'use_separator'], + 'ordered_attributes' => true, + 'ordered_class_elements' => ['case_sensitive' => false, 'sort_algorithm' => 'alpha'], + 'ordered_imports' => ['case_sensitive' => true], + 'ordered_interfaces' => true, + 'ordered_traits' => true, + 'ordered_types' => ['null_adjustment' => 'always_last'], + 'phpdoc_readonly_class_comment_to_keyword' => true, + 'phpdoc_to_param_type' => true, + 'phpdoc_to_property_type' => true, + 'phpdoc_to_return_type' => true, + 'pow_to_exponentiation' => true, + 'protected_to_private' => true, + 'psr_autoloading' => true, + 'random_api_migration' => ['replacements' => [ + 'getrandmax' => 'mt_getrandmax', + 'rand' => 'mt_rand', + 'srand' => 'mt_srand', + ]], + 'return_assignment' => true, + 'self_accessor' => true, + 'self_static_accessor' => true, + 'set_type_to_cast' => true, + 'short_scalar_cast' => true, + 'simple_to_complex_string_variable' => true, + 'simplified_null_return' => true, + 'single_class_element_per_statement' => true, + 'single_import_per_statement' => true, + 'single_line_after_imports' => true, + 'single_line_comment_spacing' => true, + 'single_line_comment_style' => true, + 'single_line_empty_body' => true, + 'single_trait_insert_per_statement' => true, + 'standardize_not_equals' => true, + 'static_lambda' => true, + 'strict_comparison' => true, + 'strict_param' => true, + 'string_implicit_backslashes' => true, + 'string_length_to_empty' => true, + 'switch_continue_to_break' => true, + 'ternary_to_null_coalescing' => true, + 'trim_array_spaces' => true, + 'use_arrow_functions' => true, + 'void_return' => true, + 'whitespace_after_comma_in_array' => ['ensure_single_space' => true], + // --- + // Each line of multi-line DocComments must have an asterisk [PSR-5] and must be aligned with the first one. + 'align_multiline_comment' => ['comment_type' => 'all_multiline'], + // There should not be blank lines between docblock and the documented element. + 'no_blank_lines_after_phpdoc' => true, + // There should not be empty PHPDoc blocks. + 'no_empty_phpdoc' => true, + // Removes @param, @return and @var tags that don't provide any useful information. + 'no_superfluous_phpdoc_tags' => [ + 'allow_hidden_params' => false, + 'allow_mixed' => false, + 'allow_unused_params' => false, + ], + // PHPDoc should contain @param for all params. + 'phpdoc_add_missing_param_annotation' => ['only_untyped' => false], + // All items of the given PHPDoc tags must be either left-aligned or (by default) aligned vertically. + 'phpdoc_align' => true, + // PHPDoc annotation descriptions should not be a sentence. + 'phpdoc_annotation_without_dot' => true, + // PHPDoc array type must be used instead of T[]. + 'phpdoc_array_type' => true, + // Docblocks should have the same indentation as the documented subject. + 'phpdoc_indent' => true, + // Fixes PHPDoc inline tags. + 'phpdoc_inline_tag_normalizer' => true, + // Changes doc blocks from single to multi line, or reversed. Works for class constants, properties and methods only. + 'phpdoc_line_span' => ['const' => 'single', 'method' => 'multi', 'property' => 'single'], + // PHPDoc list type must be used instead of array without a key. + 'phpdoc_list_type' => false, + // @access annotations must be removed from PHPDoc. + 'phpdoc_no_access' => true, + // No alias PHPDoc tags should be used. + 'phpdoc_no_alias_tag' => true, + // @return void and @return null annotations must be removed from PHPDoc. + 'phpdoc_no_empty_return' => false, + // @package and @subpackage annotations must be removed from PHPDoc. + 'phpdoc_no_package' => true, + // Classy that does not inherit must not have @inheritdoc tags. + 'phpdoc_no_useless_inheritdoc' => true, + // Annotations in PHPDoc should be ordered in defined sequence. + 'phpdoc_order' => true, + // Order PHPDoc tags by value. + 'phpdoc_order_by_value' => true, + // Orders all @param annotations in DocBlocks according to method signature. + 'phpdoc_param_order' => true, + // The type of @return annotations of methods returning a reference to itself must the configured one. + 'phpdoc_return_self_reference' => true, + // Scalar types should always be written in the same form. int not integer, bool not boolean, float not real or double. + 'phpdoc_scalar' => ['types' => [ + 'boolean', + 'callback', + 'double', + 'integer', + 'never-return', + 'never-returns', + 'no-return', + 'real', + 'str', + ]], + // Annotations in PHPDoc should be grouped together so that annotations of the same type immediately follow each other. Annotations of a different type are separated by a single blank line. + 'phpdoc_separation' => [ + 'groups' => [ + ['Annotation', 'NamedArgumentConstructor', 'Target'], + ['author', 'copyright', 'license'], + ['category', 'package', 'subpackage'], + ['property', 'property-read', 'property-write'], + ['deprecated', 'link', 'see', 'since'], + ], + 'skip_unlisted_annotations' => false, + ], + // Single line @var PHPDoc should have proper spacing. + 'phpdoc_single_line_var_spacing' => true, + // PHPDoc summary should end in either a full stop, exclamation mark, or question mark. + 'phpdoc_summary' => true, + // Docblocks should only be used on structural elements. + 'phpdoc_to_comment' => false, + // PHPDoc should start and end with content, excluding the very first and last line of the docblocks. + 'phpdoc_trim' => true, + // Removes extra blank lines after summary and after description in PHPDoc. + 'phpdoc_trim_consecutive_blank_line_separation' => true, + // The correct case must be used for standard PHP types in PHPDoc. + 'phpdoc_types' => true, + // Sorts PHPDoc types. + 'phpdoc_types_order' => ['null_adjustment' => 'always_last'], + // @var and @type annotations must have type and name in the correct order. + 'phpdoc_var_annotation_correct_order' => true, + // @var and @type annotations of classy properties should not contain the name. + 'phpdoc_var_without_name' => true, + ]) + ->setFinder($finder) + ->setParallelConfig(Runner\Parallel\ParallelConfigFactory::detect()); diff --git a/bun.lock b/bun.lock index 91e07de0..18fe1f35 100644 --- a/bun.lock +++ b/bun.lock @@ -8,7 +8,7 @@ "@mobily/ts-belt": "v4.0.0-rc.5", "@sentry/browser": "^10.47.0", "a11y-dialog": "^8.1.5", - "effect": "^4.0.0-beta.46", + "effect": "^4.0.0-beta.48", "lit-html": "^3.3.2", "purify-ts": "2.1.2", "ts-pattern": "^5.9.0", @@ -34,9 +34,9 @@ "eslint-plugin-perfectionist": "^5.8.0", "eslint-plugin-sonarjs": "^4.0.2", "fdir": "^6.5.0", - "globals": "^17.4.0", + "globals": "^17.5.0", "jiti": "^2.6.1", - "knip": "^6.3.1", + "knip": "^6.4.1", "lightningcss": "^1.32.0", "lightningcss-cli": "^1.32.0", "oxlint": "^1.59.0", @@ -46,7 +46,7 @@ "prettier-plugin-pkg": "^0.22.1", "prettier-plugin-sh": "^0.18.1", "sass-embedded": "^1.99.0", - "stylelint": "^17.6.0", + "stylelint": "^17.7.0", "stylelint-config-clean-order": "^8.0.1", "stylelint-config-sass-guidelines": "^13.0.0", "stylelint-config-standard-scss": "^17.0.0", @@ -796,7 +796,7 @@ "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], - "effect": ["effect@4.0.0-beta.46", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.5.3", "find-my-way-ts": "^0.1.6", "ini": "^6.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^1.11.8", "multipasta": "^0.2.7", "toml": "^3.0.0", "uuid": "^13.0.0", "yaml": "^2.8.2" } }, "sha512-3f6gXvvUMtEueCRY0tU76Vq2Pej1SAwwE+s0Owd5nD53yS5n4RZhUA1rlCGFuSbQFA225pGy8vO72+lpvu7u5A=="], + "effect": ["effect@4.0.0-beta.48", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "fast-check": "^4.6.0", "find-my-way-ts": "^0.1.6", "ini": "^6.0.0", "kubernetes-types": "^1.30.0", "msgpackr": "^1.11.9", "multipasta": "^0.2.7", "toml": "^4.1.1", "uuid": "^13.0.0", "yaml": "^2.8.3" } }, "sha512-MMAM/ZabuNdNmgXiin+BAanQXK7qM8mlt7nfXDoJ/Gn9V8i89JlCq+2N0AiWmqFLXjGLA0u3FjiOjSOYQk5uMw=="], "electron-to-chromium": ["electron-to-chromium@1.5.334", "", {}, "sha512-mgjZAz7Jyx1SRCwEpy9wefDS7GvNPazLthHg8eQMJ76wBdGQQDW33TCrUTvQ4wzpmOrv2zrFoD3oNufMdyMpog=="], @@ -930,7 +930,7 @@ "global-prefix": ["global-prefix@3.0.0", "", { "dependencies": { "ini": "^1.3.5", "kind-of": "^6.0.2", "which": "^1.3.1" } }, "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg=="], - "globals": ["globals@17.4.0", "", {}, "sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw=="], + "globals": ["globals@17.5.0", "", {}, "sha512-qoV+HK2yFl/366t2/Cb3+xxPUo5BuMynomoDmiaZBIdbs+0pYbjfZU+twLhGKp4uCZ/+NbtpVepH5bGCxRyy2g=="], "globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="], @@ -1076,7 +1076,7 @@ "kind-of": ["kind-of@6.0.3", "", {}, "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="], - "knip": ["knip@6.3.1", "", { "dependencies": { "@nodelib/fs.walk": "^1.2.3", "fast-glob": "^3.3.3", "formatly": "^0.3.0", "get-tsconfig": "4.13.7", "jiti": "^2.6.0", "minimist": "^1.2.8", "oxc-parser": "^0.121.0", "oxc-resolver": "^11.19.1", "picocolors": "^1.1.1", "picomatch": "^4.0.1", "smol-toml": "^1.6.1", "strip-json-comments": "5.0.3", "unbash": "^2.2.0", "yaml": "^2.8.2", "zod": "^4.1.11" }, "bin": { "knip": "bin/knip.js", "knip-bun": "bin/knip-bun.js" } }, "sha512-22kLJloVcOVOAudCxlFOC0ICAMme7dKsS7pVTEnrmyKGpswb8ieznvAiSKUeFVDJhb01ect6dkDc1Ha1g1sPpg=="], + "knip": ["knip@6.4.1", "", { "dependencies": { "@nodelib/fs.walk": "^1.2.3", "fast-glob": "^3.3.3", "formatly": "^0.3.0", "get-tsconfig": "4.13.7", "jiti": "^2.6.0", "minimist": "^1.2.8", "oxc-parser": "^0.121.0", "oxc-resolver": "^11.19.1", "picocolors": "^1.1.1", "picomatch": "^4.0.1", "smol-toml": "^1.6.1", "strip-json-comments": "5.0.3", "unbash": "^2.2.0", "yaml": "^2.8.2", "zod": "^4.1.11" }, "bin": { "knip": "bin/knip.js", "knip-bun": "bin/knip-bun.js" } }, "sha512-Ry+ywmDFSZvKp/jx7LxMgsZWRTs931alV84e60lh0Stf6kSRYqSIUTkviyyDFRcSO3yY1Kpbi83OirN+4lA2Xw=="], "known-css-properties": ["known-css-properties@0.37.0", "", {}, "sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ=="], @@ -1484,7 +1484,7 @@ "style-search": ["style-search@0.1.0", "", {}, "sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg=="], - "stylelint": ["stylelint@17.6.0", "", { "dependencies": { "@csstools/css-calc": "^3.1.1", "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-syntax-patches-for-csstree": "^1.1.1", "@csstools/css-tokenizer": "^4.0.0", "@csstools/media-query-list-parser": "^5.0.0", "@csstools/selector-resolve-nested": "^4.0.0", "@csstools/selector-specificity": "^6.0.0", "colord": "^2.9.3", "cosmiconfig": "^9.0.1", "css-functions-list": "^3.3.3", "css-tree": "^3.2.1", "debug": "^4.4.3", "fast-glob": "^3.3.3", "fastest-levenshtein": "^1.0.16", "file-entry-cache": "^11.1.2", "global-modules": "^2.0.0", "globby": "^16.1.1", "globjoin": "^0.1.4", "html-tags": "^5.1.0", "ignore": "^7.0.5", "import-meta-resolve": "^4.2.0", "is-plain-object": "^5.0.0", "mathml-tag-names": "^4.0.0", "meow": "^14.1.0", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "picocolors": "^1.1.1", "postcss": "^8.5.8", "postcss-safe-parser": "^7.0.1", "postcss-selector-parser": "^7.1.1", "postcss-value-parser": "^4.2.0", "string-width": "^8.2.0", "supports-hyperlinks": "^4.4.0", "svg-tags": "^1.0.0", "table": "^6.9.0", "write-file-atomic": "^7.0.1" }, "bin": { "stylelint": "bin/stylelint.mjs" } }, "sha512-tokrsMIVAR9vAQ/q3UVEr7S0dGXCi7zkCezPRnS2kqPUulvUh5Vgfwngrk4EoAoW7wnrThqTdnTFN5Ra7CaxIg=="], + "stylelint": ["stylelint@17.7.0", "", { "dependencies": { "@csstools/css-calc": "^3.1.1", "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-syntax-patches-for-csstree": "^1.1.2", "@csstools/css-tokenizer": "^4.0.0", "@csstools/media-query-list-parser": "^5.0.0", "@csstools/selector-resolve-nested": "^4.0.0", "@csstools/selector-specificity": "^6.0.0", "colord": "^2.9.3", "cosmiconfig": "^9.0.1", "css-functions-list": "^3.3.3", "css-tree": "^3.2.1", "debug": "^4.4.3", "fast-glob": "^3.3.3", "fastest-levenshtein": "^1.0.16", "file-entry-cache": "^11.1.2", "global-modules": "^2.0.0", "globby": "^16.2.0", "globjoin": "^0.1.4", "html-tags": "^5.1.0", "ignore": "^7.0.5", "import-meta-resolve": "^4.2.0", "is-plain-object": "^5.0.0", "mathml-tag-names": "^4.0.0", "meow": "^14.1.0", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "picocolors": "^1.1.1", "postcss": "^8.5.8", "postcss-safe-parser": "^7.0.1", "postcss-selector-parser": "^7.1.1", "postcss-value-parser": "^4.2.0", "string-width": "^8.2.0", "supports-hyperlinks": "^4.4.0", "svg-tags": "^1.0.0", "table": "^6.9.0", "write-file-atomic": "^7.0.1" }, "bin": { "stylelint": "bin/stylelint.mjs" } }, "sha512-n/+4RheCRl+cecGnF+S/Adz59iCRaK9BVznJYB+a7GOksfwNzjiOPnYv17pTO0HgRse9IiqbMtekGNhOb2tVYQ=="], "stylelint-config-clean-order": ["stylelint-config-clean-order@8.0.1", "", { "peerDependencies": { "stylelint": ">=16", "stylelint-order": ">=6" } }, "sha512-zKjp7BiINXRZOG9m0fE/6UKoM6clPekL+LoAiHMCiQU2hgirKL5G0mKc5Z0ygIhQXfb1+DTRDM0mu6Ecdv4q8g=="], @@ -1532,7 +1532,7 @@ "to-valid-identifier": ["to-valid-identifier@1.0.0", "", { "dependencies": { "@sindresorhus/base62": "^1.0.0", "reserved-identifiers": "^1.0.0" } }, "sha512-41wJyvKep3yT2tyPqX/4blcfybknGB4D+oETKLs7Q76UiPqRpUJK3hr1nxelyYO0PHKVzJwlu0aCeEAsGI6rpw=="], - "toml": ["toml@3.0.0", "", {}, "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w=="], + "toml": ["toml@4.1.1", "", {}, "sha512-EBJnVBr3dTXdA89WVFoAIPUqkBjxPMwRqsfuo1r240tKFHXv3zgca4+NJib/h6TyvGF7vOawz0jGuryJCdNHrw=="], "ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="], @@ -1632,6 +1632,10 @@ "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + "@gcch/configuration-eslint/globals": ["globals@17.4.0", "", {}, "sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw=="], + + "@gcch/configuration-oxlint/globals": ["globals@17.4.0", "", {}, "sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw=="], + "@keyv/bigmap/keyv": ["keyv@5.6.0", "", { "dependencies": { "@keyv/serialize": "^1.1.1" } }, "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw=="], "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], @@ -1656,6 +1660,8 @@ "eslint-plugin-jsx-a11y/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + "eslint-plugin-sonarjs/globals": ["globals@17.4.0", "", {}, "sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw=="], + "eslint-plugin-unicorn/globals": ["globals@16.5.0", "", {}, "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ=="], "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], diff --git a/composer.lock b/composer.lock index 9a85f36f..745e769e 100644 --- a/composer.lock +++ b/composer.lock @@ -3629,15 +3629,15 @@ }, { "name": "wpackagist-plugin/query-monitor", - "version": "4.0.5", + "version": "4.0.6", "source": { "type": "svn", "url": "https://plugins.svn.wordpress.org/query-monitor/", - "reference": "tags/4.0.5" + "reference": "tags/4.0.6" }, "dist": { "type": "zip", - "url": "https://downloads.wordpress.org/plugin/query-monitor.4.0.5.zip" + "url": "https://downloads.wordpress.org/plugin/query-monitor.4.0.6.zip" }, "require": { "composer/installers": "^1.0 || ^2.0" @@ -4077,6 +4077,75 @@ ], "time": "2024-05-06T16:37:16+00:00" }, + { + "name": "ergebnis/agent-detector", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/ergebnis/agent-detector.git", + "reference": "5b654a9f1ff8a5d2ce6a57568df5ae8801c87f64" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ergebnis/agent-detector/zipball/5b654a9f1ff8a5d2ce6a57568df5ae8801c87f64", + "reference": "5b654a9f1ff8a5d2ce6a57568df5ae8801c87f64", + "shasum": "" + }, + "require": { + "php": "~7.4.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0 || ~8.6.0" + }, + "require-dev": { + "ergebnis/composer-normalize": "^2.50.0", + "ergebnis/license": "^2.7.0", + "ergebnis/php-cs-fixer-config": "^6.60.2", + "ergebnis/phpstan-rules": "^2.13.1", + "ergebnis/phpunit-slow-test-detector": "^2.24.0", + "ergebnis/rector-rules": "^1.16.0", + "fakerphp/faker": "^1.24.1", + "infection/infection": "^0.26.6", + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^2.1.46", + "phpstan/phpstan-deprecation-rules": "^2.0.4", + "phpstan/phpstan-phpunit": "^2.0.16", + "phpstan/phpstan-strict-rules": "^2.0.10", + "phpunit/phpunit": "^9.6.34", + "rector/rector": "^2.4.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.0-dev" + }, + "composer-normalize": { + "indent-size": 2, + "indent-style": "space" + } + }, + "autoload": { + "psr-4": { + "Ergebnis\\AgentDetector\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Andreas Möller", + "email": "am@localheinz.com", + "homepage": "https://localheinz.com" + } + ], + "description": "Provides a detector for detecting the presence of an agent.", + "homepage": "https://github.com/ergebnis/agent-detector", + "support": { + "issues": "https://github.com/ergebnis/agent-detector/issues", + "security": "https://github.com/ergebnis/agent-detector/blob/main/.github/SECURITY.md", + "source": "https://github.com/ergebnis/agent-detector" + }, + "time": "2026-04-10T13:45:13+00:00" + }, { "name": "evenement/evenement", "version": "v3.0.2", @@ -4187,22 +4256,23 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.94.2", + "version": "v3.95.1", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "7787ceff91365ba7d623ec410b8f429cdebb4f63" + "reference": "a9727678fbd12997f1d9de8f4a37824ed9df1065" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/7787ceff91365ba7d623ec410b8f429cdebb4f63", - "reference": "7787ceff91365ba7d623ec410b8f429cdebb4f63", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/a9727678fbd12997f1d9de8f4a37824ed9df1065", + "reference": "a9727678fbd12997f1d9de8f4a37824ed9df1065", "shasum": "" }, "require": { "clue/ndjson-react": "^1.3", "composer/semver": "^3.4", "composer/xdebug-handler": "^3.0.5", + "ergebnis/agent-detector": "^1.1.1", "ext-filter": "*", "ext-hash": "*", "ext-json": "*", @@ -4227,18 +4297,18 @@ "symfony/stopwatch": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0" }, "require-dev": { - "facile-it/paraunit": "^1.3.1 || ^2.7.1", - "infection/infection": "^0.32.3", - "justinrainbow/json-schema": "^6.6.4", + "facile-it/paraunit": "^1.3.1 || ^2.8.0", + "infection/infection": "^0.32.6", + "justinrainbow/json-schema": "^6.8.0", "keradus/cli-executor": "^2.3", "mikey179/vfsstream": "^1.6.12", "php-coveralls/php-coveralls": "^2.9.1", - "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.7", - "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.7", - "phpunit/phpunit": "^9.6.34 || ^10.5.63 || ^11.5.51", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.8", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.8", + "phpunit/phpunit": "^9.6.34 || ^10.5.63 || ^11.5.55", "symfony/polyfill-php85": "^1.33", - "symfony/var-dumper": "^5.4.48 || ^6.4.32 || ^7.4.4 || ^8.0.4", - "symfony/yaml": "^5.4.45 || ^6.4.30 || ^7.4.1 || ^8.0.1" + "symfony/var-dumper": "^5.4.48 || ^6.4.32 || ^7.4.4 || ^8.0.8", + "symfony/yaml": "^5.4.45 || ^6.4.30 || ^7.4.1 || ^8.0.8" }, "suggest": { "ext-dom": "For handling output formats in XML", @@ -4279,7 +4349,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.94.2" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.95.1" }, "funding": [ { @@ -4287,7 +4357,7 @@ "type": "github" } ], - "time": "2026-02-20T16:13:53+00:00" + "time": "2026-04-12T17:00:09+00:00" }, { "name": "php-standard-library/phpstan-extension", @@ -7411,16 +7481,16 @@ }, { "name": "webmozart/assert", - "version": "2.2.0", + "version": "2.3.0", "source": { "type": "git", "url": "https://github.com/webmozarts/assert.git", - "reference": "1b99650e7ffcad232624a260bc7fbdec2ffc407c" + "reference": "eb0d790f735ba6cff25c683a85a1da0eadeff9e4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/1b99650e7ffcad232624a260bc7fbdec2ffc407c", - "reference": "1b99650e7ffcad232624a260bc7fbdec2ffc407c", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/eb0d790f735ba6cff25c683a85a1da0eadeff9e4", + "reference": "eb0d790f735ba6cff25c683a85a1da0eadeff9e4", "shasum": "" }, "require": { @@ -7467,9 +7537,9 @@ ], "support": { "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/2.2.0" + "source": "https://github.com/webmozarts/assert/tree/2.3.0" }, - "time": "2026-04-09T16:54:47+00:00" + "time": "2026-04-11T10:33:05+00:00" } ], "aliases": [], @@ -7484,5 +7554,5 @@ "php": ">=8.5" }, "platform-dev": {}, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" } diff --git a/config/application.php b/config/application.php index d175ad5c..bc828422 100755 --- a/config/application.php +++ b/config/application.php @@ -37,16 +37,16 @@ $webroot_dir = $root_dir . '/web'; * .env.local will override .env if it exists */ if (file_exists($root_dir . '/.env')) { - $env_files = file_exists($root_dir . '/.env.local') ? ['.env', '.env.local'] : ['.env']; + $env_files = file_exists($root_dir . '/.env.local') ? ['.env', '.env.local'] : ['.env']; - $dotenv = Dotenv\Dotenv::createImmutable($root_dir, $env_files, false); + $dotenv = Dotenv\Dotenv::createImmutable($root_dir, $env_files, false); - $dotenv->load(); + $dotenv->load(); - $dotenv->required(['WP_HOME', 'WP_SITEURL']); - if (!env('DATABASE_URL')) { - $dotenv->required(['DB_NAME', 'DB_USER', 'DB_PASSWORD']); - } + $dotenv->required(['WP_HOME', 'WP_SITEURL']); + if (!env('DATABASE_URL')) { + $dotenv->required(['DB_NAME', 'DB_USER', 'DB_PASSWORD']); + } } /* @@ -57,7 +57,7 @@ define('WP_ENV', env('WP_ENV') ?: 'production'); // Infer WP_ENVIRONMENT_TYPE based on WP_ENV if (!env('WP_ENVIRONMENT_TYPE') && in_array(WP_ENV, ['production', 'staging', 'development', 'local'], true)) { - Config::define('WP_ENVIRONMENT_TYPE', WP_ENV); + Config::define('WP_ENVIRONMENT_TYPE', WP_ENV); } // URLs @@ -71,7 +71,7 @@ Config::define('WP_CONTENT_URL', Config::get('WP_HOME') . Config::get('CONTENT_D // DB settings if (env('DB_SSL')) { - Config::define('MYSQL_CLIENT_FLAGS', MYSQLI_CLIENT_SSL); + Config::define('MYSQL_CLIENT_FLAGS', MYSQLI_CLIENT_SSL); } Config::define('DB_NAME', env('DB_NAME')); @@ -83,12 +83,12 @@ Config::define('DB_COLLATE', ''); $table_prefix = env('DB_PREFIX') ?: 'wp_'; if (env('DATABASE_URL')) { - $dsn = (object) parse_url(env('DATABASE_URL')); + $dsn = (object) parse_url(env('DATABASE_URL')); - Config::define('DB_NAME', mb_substr($dsn->path, 1)); - Config::define('DB_USER', $dsn->user); - Config::define('DB_PASSWORD', $dsn->pass ?? null); - Config::define('DB_HOST', isset($dsn->port) ? "{$dsn->host}:{$dsn->port}" : $dsn->host); + Config::define('DB_NAME', mb_substr($dsn->path, 1)); + Config::define('DB_USER', $dsn->user); + Config::define('DB_PASSWORD', $dsn->pass ?? null); + Config::define('DB_HOST', isset($dsn->port) ? "{$dsn->host}:{$dsn->port}" : $dsn->host); } // Authentication Unique Keys and Salts @@ -129,18 +129,18 @@ Config::define('WP_PLUGIN_DIR', Config::get('WP_CONTENT_DIR') . '/plugins'); * See https://codex.wordpress.org/Function_Reference/is_ssl#Notes */ if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && 'https' === $_SERVER['HTTP_X_FORWARDED_PROTO']) { - $_SERVER['HTTPS'] = 'on'; + $_SERVER['HTTPS'] = 'on'; } $env_config = __DIR__ . '/environments/' . WP_ENV . '.php'; if (file_exists($env_config)) { - include_once $env_config; + include_once $env_config; } Config::apply(); // Bootstrap WordPress if (!defined('ABSPATH')) { - define('ABSPATH', $webroot_dir . '/wp/'); + define('ABSPATH', $webroot_dir . '/wp/'); } diff --git a/package.json b/package.json index 9b9db90b..2817ca75 100755 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "@mobily/ts-belt": "v4.0.0-rc.5", "@sentry/browser": "^10.47.0", "a11y-dialog": "^8.1.5", - "effect": "^4.0.0-beta.46", + "effect": "^4.0.0-beta.48", "lit-html": "^3.3.2", "purify-ts": "2.1.2", "ts-pattern": "^5.9.0", @@ -40,9 +40,9 @@ "eslint-plugin-perfectionist": "^5.8.0", "eslint-plugin-sonarjs": "^4.0.2", "fdir": "^6.5.0", - "globals": "^17.4.0", + "globals": "^17.5.0", "jiti": "^2.6.1", - "knip": "^6.3.1", + "knip": "^6.4.1", "lightningcss": "^1.32.0", "lightningcss-cli": "^1.32.0", "oxlint": "^1.59.0", @@ -52,7 +52,7 @@ "prettier-plugin-pkg": "^0.22.1", "prettier-plugin-sh": "^0.18.1", "sass-embedded": "^1.99.0", - "stylelint": "^17.6.0", + "stylelint": "^17.7.0", "stylelint-config-clean-order": "^8.0.1", "stylelint-config-sass-guidelines": "^13.0.0", "stylelint-config-standard-scss": "^17.0.0", diff --git a/scripts/remove-scaled-images.php b/scripts/remove-scaled-images.php index ac57fe94..d4bd6024 100755 --- a/scripts/remove-scaled-images.php +++ b/scripts/remove-scaled-images.php @@ -14,32 +14,32 @@ global $wpdb; $wp_postmeta = "{$wpdb->prefix}postmeta"; try { - $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - // Error Handling - $sql = "UPDATE {$wp_postmeta} SET meta_value = REPLACE(meta_value,'-scaled.jpg','.jpg') WHERE meta_key='_wp_attached_file' AND meta_value LIKE '%-scaled.jpg%'"; - $result = $pdo->exec($sql); - print_r($result); + $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + // Error Handling + $sql = "UPDATE {$wp_postmeta} SET meta_value = REPLACE(meta_value,'-scaled.jpg','.jpg') WHERE meta_key='_wp_attached_file' AND meta_value LIKE '%-scaled.jpg%'"; + $result = $pdo->exec($sql); + print_r($result); } catch (PDOException $e) { - print_r($e->getMessage()); + print_r($e->getMessage()); } // replace _wp_attachment_metadata meta_key. $image_metas = []; try { - $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); - // Error Handling - $sql = "SELECT * FROM {$wp_postmeta} WHERE meta_value LIKE '%-scaled.jpg%' AND meta_key='_wp_attachment_metadata'"; - $statement = $pdo->query($sql); - $image_metas = $statement->fetchAll(); - foreach ($image_metas as $meta) { - $meta_value = unserialize($meta['meta_value']); - $file = $meta_value['file']; - $meta_value['file'] = str_replace('-scaled.jpg', '.jpg', $file); - update_post_meta($meta['post_id'], $meta['meta_key'], $meta_value); - $result = get_post_meta($meta['post_id'], $meta['meta_key']); - print_r($result); - } + $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + // Error Handling + $sql = "SELECT * FROM {$wp_postmeta} WHERE meta_value LIKE '%-scaled.jpg%' AND meta_key='_wp_attachment_metadata'"; + $statement = $pdo->query($sql); + $image_metas = $statement->fetchAll(); + foreach ($image_metas as $meta) { + $meta_value = unserialize($meta['meta_value']); + $file = $meta_value['file']; + $meta_value['file'] = str_replace('-scaled.jpg', '.jpg', $file); + update_post_meta($meta['post_id'], $meta['meta_key'], $meta_value); + $result = get_post_meta($meta['post_id'], $meta['meta_key']); + print_r($result); + } } catch (PDOException $e) { - print_r($e->getMessage()); + print_r($e->getMessage()); } diff --git a/web/app/themes/haiku-atelier-2024/404.php b/web/app/themes/haiku-atelier-2024/404.php index b0fe35de..f6296dd4 100755 --- a/web/app/themes/haiku-atelier-2024/404.php +++ b/web/app/themes/haiku-atelier-2024/404.php @@ -22,12 +22,11 @@ $templates = ['404.twig']; * * @throws Exception une exception est levée s'il est impossible d'obtenir la date de modification du fichier à charger */ -function load_page_resources(): void -{ - Resource::enqueue_style_file( - handle: 'haiku-atelier-2024-styles-page-a-propos', - path: '/assets/css/pages/page-modele-simple.css', - ); +function load_page_resources(): void { + Resource::enqueue_style_file( + handle: 'haiku-atelier-2024-styles-page-a-propos', + path: '/assets/css/pages/page-modele-simple.css', + ); } add_action('wp_enqueue_scripts', load_page_resources(...)); diff --git a/web/app/themes/haiku-atelier-2024/front-page.php b/web/app/themes/haiku-atelier-2024/front-page.php index 0d9f16cc..5b890db9 100755 --- a/web/app/themes/haiku-atelier-2024/front-page.php +++ b/web/app/themes/haiku-atelier-2024/front-page.php @@ -17,14 +17,14 @@ $context = Timber::context(); $templates = ['accueil.twig']; add_action('wp_enqueue_scripts', function (): void { - Resource::enqueue_style_file( - handle: 'haiku-atelier-2024-styles-page-accueil', - path: '/assets/css/pages/page-accueil.css', - ); - Resource::enqueue_script_module_file( - id: 'haiku-atelier-2024-scripts-page-accueil', - path: '/assets/js/scripts-page-accueil.js', - ); + Resource::enqueue_style_file( + handle: 'haiku-atelier-2024-styles-page-accueil', + path: '/assets/css/pages/page-accueil.css', + ); + Resource::enqueue_script_module_file( + id: 'haiku-atelier-2024-scripts-page-accueil', + path: '/assets/js/scripts-page-accueil.js', + ); }); Timber::render(data: $context, filenames: $templates); diff --git a/web/app/themes/haiku-atelier-2024/page-about.php b/web/app/themes/haiku-atelier-2024/page-about.php index ce53c103..6d8690be 100755 --- a/web/app/themes/haiku-atelier-2024/page-about.php +++ b/web/app/themes/haiku-atelier-2024/page-about.php @@ -24,20 +24,20 @@ $templates = ['a-propos.twig']; $image_dimensions = getimagesize(filename: get_template_directory() . '/assets/img/about/haikuabout.png'); if (is_bool($image_dimensions)) { - throw new Exception("Impossible d'obtenir les dimensions de l'image principale de la page."); + throw new Exception("Impossible d'obtenir les dimensions de l'image principale de la page."); } $context['image_dimensions'] = $image_dimensions; add_action('wp_enqueue_scripts', function (): void { - Resource::enqueue_style_file( - handle: 'haiku-atelier-2024-styles-page-a-propos', - path: '/assets/css/pages/page-a-propos.css', - ); - Resource::enqueue_script_module_file( - id: 'haiku-atelier-2024-scripts-page-a-propos', - path: '/assets/js/scripts-page-a-propos.js', - ); + Resource::enqueue_style_file( + handle: 'haiku-atelier-2024-styles-page-a-propos', + path: '/assets/css/pages/page-a-propos.css', + ); + Resource::enqueue_script_module_file( + id: 'haiku-atelier-2024-scripts-page-a-propos', + path: '/assets/js/scripts-page-a-propos.js', + ); }); Timber::render(data: $context, filenames: $templates); diff --git a/web/app/themes/haiku-atelier-2024/page-checkout.php b/web/app/themes/haiku-atelier-2024/page-checkout.php index e53ed6c3..fa08f444 100755 --- a/web/app/themes/haiku-atelier-2024/page-checkout.php +++ b/web/app/themes/haiku-atelier-2024/page-checkout.php @@ -22,22 +22,20 @@ use WC_Session_Handler; header('Content-Type: application/json; charset=utf-8'); // TODO: Appliquer le bon calcul pour les montants vs. percentages -function get_discount_amount(WC_Coupon $coupon) -{ - if ($coupon->get_discount_type() === 'fixed_cart') { - return $coupon->get_amount() * 100; - } else { - return $coupon->get_amount(); - } +function get_discount_amount(WC_Coupon $coupon) { + if ($coupon->get_discount_type() === 'fixed_cart') { + return $coupon->get_amount() * 100; + } else { + return $coupon->get_amount(); + } } -function get_discount_duration(WC_Coupon $coupon): string -{ - if ($coupon->get_discount_type() === 'fixed_cart') { - return 'once'; - } else { - return 'forever'; - } +function get_discount_duration(WC_Coupon $coupon): string { + if ($coupon->get_discount_type() === 'fixed_cart') { + return 'once'; + } else { + return 'forever'; + } } // Récupère les informations nécessaires @@ -46,37 +44,37 @@ $session_wc = WC()->session; /** @var array $urls URLs utilisables pour rediriger l'Utilisateur. */ $urls = [ - 'accueil' => get_page_link(get_page_by_path('home')), - 'succes_commande' => get_page_link(get_page_by_path('successful-order')), - 'echec_commande' => get_page_link(get_page_by_path('failed-order')), + 'accueil' => get_page_link(get_page_by_path('home')), + 'succes_commande' => get_page_link(get_page_by_path('successful-order')), + 'echec_commande' => get_page_link(get_page_by_path('failed-order')), ]; // Redirige à la page d'accueil si le Panier est vide if (WC()->cart->is_empty()) { - header('Location: ' . $urls['accueil']); + header('Location: ' . $urls['accueil']); - return; + return; } // Vérifie que les paramètres d'URLs nécessaires soient présents /** @var string $order_id */ $order_id = $_GET['order_id']; if (!$order_id) { - $reponse = ['succes' => false, 'status' => 'order_key is missing']; - echo json_encode($reponse); - http_response_code(400); + $reponse = ['succes' => false, 'status' => 'order_key is missing']; + echo json_encode($reponse); + http_response_code(400); - return; + return; } /** @var string $order_key */ $order_key = $_GET['order_key']; if (!$order_key) { - $reponse = ['succes' => false, 'status' => 'order_key is missing']; - echo json_encode($reponse); - http_response_code(400); + $reponse = ['succes' => false, 'status' => 'order_key is missing']; + echo json_encode($reponse); + http_response_code(400); - return; + return; } // Récupère le Panier et l'Email du Client @@ -88,29 +86,29 @@ $email_client = WC()->session->get('customer')['email']; /** @var list $articles */ $articles = collect($panier->get_cart()) - ->map(static function ($article_panier) { - $titre_produit = match ('variable' === $article_panier['data']?->get_type()) { - true => $article_panier['data']?->get_title() - . ' (' - . explode(': ', (string) $article_panier['data']?->get_attribute_summary())[1] - . ')', - false => $article_panier['data']?->get_title(), - }; + ->map(static function ($article_panier) { + $titre_produit = match ('variable' === $article_panier['data']?->get_type()) { + true => $article_panier['data']?->get_title() + . ' (' + . explode(': ', (string) $article_panier['data']?->get_attribute_summary())[1] + . ')', + false => $article_panier['data']?->get_title(), + }; - return [ - 'price_data' => [ - 'currency' => 'EUR', - 'product_data' => [ - 'name' => $titre_produit, - 'images' => [wp_get_attachment_image_url($article_panier['data']?->get_image_id())], - ], - 'unit_amount' => $article_panier['data']?->get_price() * 100, - ], - 'quantity' => $article_panier['quantity'], - ]; - }) - ->values() - ->toArray(); + return [ + 'price_data' => [ + 'currency' => 'EUR', + 'product_data' => [ + 'name' => $titre_produit, + 'images' => [wp_get_attachment_image_url($article_panier['data']?->get_image_id())], + ], + 'unit_amount' => $article_panier['data']?->get_price() * 100, + ], + 'quantity' => $article_panier['quantity'], + ]; + }) + ->values() + ->toArray(); // Récupère la Commande et la Méthode de Livraison /** @var WC_Order $commande */ @@ -120,7 +118,7 @@ $methode_livraison = ['nom' => $commande->get_shipping_method(), 'cout' => $comm // Le nom de la méthode de livraison ne peut être une chaîne vide. if (empty($methode_livraison['nom'])) { - $methode_livraison['nom'] = 'Free'; + $methode_livraison['nom'] = 'Free'; } // Sélectionne la clé API Stripe @@ -129,39 +127,39 @@ Stripe::setApiKey(Config::get('STRIPE_API_SECRET')); // Met à jour les Codes promos $coupons_stripe = collect(Coupon::all()->data); $coupons_wc = collect(WC()->cart->get_coupons()) - ->map(static fn(WC_Coupon $coupon): array => [ - 'currency' => 'EUR', - 'duration' => get_discount_duration($coupon), - 'fixed_cart' === $coupon->get_discount_type() ? 'amount_off' : 'percent_off' => get_discount_amount($coupon), - 'id' => $coupon->get_code(), - 'name' => $coupon->get_code(), - ]) - ->each(static function (array $item) use ($coupons_stripe): void { - // Si le code promo n'existe pas, le créer - if (!$coupons_stripe->contains('name', $item['name'])) { - Coupon::create($item); - } - }); + ->map(static fn(WC_Coupon $coupon): array => [ + 'currency' => 'EUR', + 'duration' => get_discount_duration($coupon), + 'fixed_cart' === $coupon->get_discount_type() ? 'amount_off' : 'percent_off' => get_discount_amount($coupon), + 'id' => $coupon->get_code(), + 'name' => $coupon->get_code(), + ]) + ->each(static function (array $item) use ($coupons_stripe): void { + // Si le code promo n'existe pas, le créer + if (!$coupons_stripe->contains('name', $item['name'])) { + Coupon::create($item); + } + }); $reductions_stripe = $coupons_wc - ->map(static fn($coupon): array => ['coupon' => $coupon['name']]) - ->values() - ->toArray(); + ->map(static fn($coupon): array => ['coupon' => $coupon['name']]) + ->values() + ->toArray(); /** @var Session $session_checkout_stripe */ $session_checkout_stripe = Session::create([ - 'cancel_url' => $urls['echec_commande'], - 'customer_email' => $email_client, - 'discounts' => $reductions_stripe, - 'line_items' => $articles, - 'mode' => 'payment', - 'success_url' => $urls['succes_commande'] . '?session_id={CHECKOUT_SESSION_ID}', - 'metadata' => ['order_id' => $order_id, 'order_key' => $order_key], - 'shipping_options' => [['shipping_rate_data' => [ - 'display_name' => $methode_livraison['nom'], - 'fixed_amount' => ['amount' => $methode_livraison['cout'], 'currency' => 'EUR'], - 'tax_behavior' => 'inclusive', - 'type' => 'fixed_amount', - ]]], + 'cancel_url' => $urls['echec_commande'], + 'customer_email' => $email_client, + 'discounts' => $reductions_stripe, + 'line_items' => $articles, + 'mode' => 'payment', + 'success_url' => $urls['succes_commande'] . '?session_id={CHECKOUT_SESSION_ID}', + 'metadata' => ['order_id' => $order_id, 'order_key' => $order_key], + 'shipping_options' => [['shipping_rate_data' => [ + 'display_name' => $methode_livraison['nom'], + 'fixed_amount' => ['amount' => $methode_livraison['cout'], 'currency' => 'EUR'], + 'tax_behavior' => 'inclusive', + 'type' => 'fixed_amount', + ]]], ], ['idempotency_key' => Uuid::v4()]); // echo json_encode($session_checkout_stripe); header('HTTP/1.1 303 See Other'); diff --git a/web/app/themes/haiku-atelier-2024/page-failed-order.php b/web/app/themes/haiku-atelier-2024/page-failed-order.php index 1dc703c5..ea428114 100755 --- a/web/app/themes/haiku-atelier-2024/page-failed-order.php +++ b/web/app/themes/haiku-atelier-2024/page-failed-order.php @@ -17,10 +17,10 @@ $context = Timber::context(); $templates = ['echec-commande.twig']; add_action('wp_enqueue_scripts', function (): void { - Resource::enqueue_style_file( - handle: 'haiku-atelier-2024-styles-page-modele-simple', - path: '/assets/css/pages/page-modele-simple.css', - ); + Resource::enqueue_style_file( + handle: 'haiku-atelier-2024-styles-page-modele-simple', + path: '/assets/css/pages/page-modele-simple.css', + ); }); // Rendu diff --git a/web/app/themes/haiku-atelier-2024/page-terms-and-conditions.php b/web/app/themes/haiku-atelier-2024/page-terms-and-conditions.php index d5584c86..0b9d5998 100755 --- a/web/app/themes/haiku-atelier-2024/page-terms-and-conditions.php +++ b/web/app/themes/haiku-atelier-2024/page-terms-and-conditions.php @@ -17,10 +17,10 @@ $context = Timber::context(); $templates = ['cgv.twig']; add_action('wp_enqueue_scripts', function (): void { - Resource::enqueue_style_file( - handle: '/assets/css/pages/page-modele-simple.css', - path: '/assets/css/pages/page-modele-simple.css', - ); + Resource::enqueue_style_file( + handle: '/assets/css/pages/page-modele-simple.css', + path: '/assets/css/pages/page-modele-simple.css', + ); }); // Rendu diff --git a/web/app/themes/haiku-atelier-2024/src/inc/ChampsPersonnalises.php b/web/app/themes/haiku-atelier-2024/src/inc/ChampsPersonnalises.php index e64ba66b..9d509e72 100755 --- a/web/app/themes/haiku-atelier-2024/src/inc/ChampsPersonnalises.php +++ b/web/app/themes/haiku-atelier-2024/src/inc/ChampsPersonnalises.php @@ -9,39 +9,36 @@ declare(strict_types=1); use Carbon_Fields\Container; use Carbon_Fields\Field; -function cree_champs_personnalises_produit(): void -{ - Container::make('post_meta', "Product's Details") - ->where('post_type', '=', 'product') - ->add_fields([ - // Galerie des photos Produit - Field::make('media_gallery', 'photos_colonne_gauche', __('Left Column Photos')) - ->set_type(['image']) - ->set_duplicates_allowed(false), - // Galerie des photos portées - Field::make('media_gallery', 'photos_colonne_droite', __('Right Column Photos')) - ->set_type(['image']) - ->set_duplicates_allowed(false), - // Texte des détails du Produit - Field::make('rich_text', 'haiku_details_produit', __("Product's Details")), - ]); -} - -function cree_champ_personnalise_commande($order): void -{ - woocommerce_wp_text_input([ - 'id' => 'tracking_number', - 'label' => 'Tracking Number:', - 'value' => $order->get_meta('tracking_number'), - 'wrapper_class' => 'form-field-wide', +function cree_champs_personnalises_produit(): void { + Container::make('post_meta', "Product's Details") + ->where('post_type', '=', 'product') + ->add_fields([ + // Galerie des photos Produit + Field::make('media_gallery', 'photos_colonne_gauche', __('Left Column Photos')) + ->set_type(['image']) + ->set_duplicates_allowed(false), + // Galerie des photos portées + Field::make('media_gallery', 'photos_colonne_droite', __('Right Column Photos')) + ->set_type(['image']) + ->set_duplicates_allowed(false), + // Texte des détails du Produit + Field::make('rich_text', 'haiku_details_produit', __("Product's Details")), ]); } -function maj_champ_personnalise_commande($order_id): void -{ - $order = wc_get_order($order_id); - $order->update_meta_data('tracking_number', wc_clean($_POST['tracking_number'])); - $order->save(); +function cree_champ_personnalise_commande($order): void { + woocommerce_wp_text_input([ + 'id' => 'tracking_number', + 'label' => 'Tracking Number:', + 'value' => $order->get_meta('tracking_number'), + 'wrapper_class' => 'form-field-wide', + ]); +} + +function maj_champ_personnalise_commande($order_id): void { + $order = wc_get_order($order_id); + $order->update_meta_data('tracking_number', wc_clean($_POST['tracking_number'])); + $order->save(); } add_action('carbon_fields_register_fields', 'cree_champs_personnalises_produit'); diff --git a/web/app/themes/haiku-atelier-2024/src/inc/ControlesPersonnalises.php b/web/app/themes/haiku-atelier-2024/src/inc/ControlesPersonnalises.php index 9209a485..78d633cd 100755 --- a/web/app/themes/haiku-atelier-2024/src/inc/ControlesPersonnalises.php +++ b/web/app/themes/haiku-atelier-2024/src/inc/ControlesPersonnalises.php @@ -8,74 +8,69 @@ declare(strict_types=1); -function enregistre_controle_personnalise_tinymce(): void -{ +function enregistre_controle_personnalise_tinymce(): void { + /** + * TinyMCE Custom Control. + * + * @author Anthony Hortin + * @license http://www.gnu.org/licenses/gpl-2.0.html + * + * @see https://github.com/maddisondesigns + */ + final class ControlesPersonnalises extends WP_Customize_Control { + /** The type of control being rendered. */ + public $type = 'editeur_tinymce'; + /** - * TinyMCE Custom Control. - * - * @author Anthony Hortin - * @license http://www.gnu.org/licenses/gpl-2.0.html - * - * @see https://github.com/maddisondesigns + * Enqueue our scripts and styles. */ - final class ControlesPersonnalises extends WP_Customize_Control - { - /** The type of control being rendered. */ - public $type = 'editeur_tinymce'; + public function enqueue(): void { + wp_enqueue_script( + handle: 'controle-personnalise-tinymce', + src: get_template_directory_uri() . '/assets/vendor/controle-personnalise-tinymce.js', + deps: ['jquery'], + ver: '1.3', + args: true, + ); + wp_enqueue_editor(); + } - /** - * Enqueue our scripts and styles. - */ - public function enqueue(): void - { - wp_enqueue_script( - handle: 'controle-personnalise-tinymce', - src: get_template_directory_uri() . '/assets/vendor/controle-personnalise-tinymce.js', - deps: ['jquery'], - ver: '1.3', - args: true, - ); - wp_enqueue_editor(); - } - - /** - * Render the control in the customizer. - */ - public function render_content(): void - { ?> + /** + * Render the control in the customizer. + */ + public function render_content(): void { ?>
label); ?> description)) { ?> description); ?>
json['skyrockettinymcetoolbar1'] = isset($this->input_attrs['toolbar1']) - ? esc_attr($this->input_attrs['toolbar1']) - : 'bold italic bullist numlist alignleft aligncenter alignright link'; + $this->json['skyrockettinymcetoolbar1'] = isset($this->input_attrs['toolbar1']) + ? esc_attr($this->input_attrs['toolbar1']) + : 'bold italic bullist numlist alignleft aligncenter alignright link'; - $this->json['skyrockettinymcetoolbar2'] = isset($this->input_attrs['toolbar2']) - ? esc_attr($this->input_attrs['toolbar2']) - : ''; - $this->json['skyrocketmediabuttons'] = isset($this->input_attrs['mediaButtons']) - && $this->input_attrs['mediaButtons'] === true - ? true - : false; - } + $this->json['skyrockettinymcetoolbar2'] = isset($this->input_attrs['toolbar2']) + ? esc_attr($this->input_attrs['toolbar2']) + : ''; + $this->json['skyrocketmediabuttons'] = isset($this->input_attrs['mediaButtons']) + && $this->input_attrs['mediaButtons'] === true + ? true + : false; } + } } add_action('customize_register', 'enregistre_controle_personnalise_tinymce'); diff --git a/web/app/themes/haiku-atelier-2024/src/inc/Data/Cart.php b/web/app/themes/haiku-atelier-2024/src/inc/Data/Cart.php index 248f4bc2..1ad27c0d 100644 --- a/web/app/themes/haiku-atelier-2024/src/inc/Data/Cart.php +++ b/web/app/themes/haiku-atelier-2024/src/inc/Data/Cart.php @@ -11,116 +11,113 @@ use function is_float; use function is_int; use function is_string; -final readonly class Cart -{ - public function __construct() {} +final readonly class Cart { + public function __construct() {} - /** La valeur par défaut d'une donnée invalide du Panier. */ - private const string DEFAULT_VALUE = '0.00'; + /** La valeur par défaut d'une donnée invalide du Panier. */ + private const string DEFAULT_VALUE = '0.00'; - /** - * Retourne la liste des pays acceptés pour la livraison. - * - * @return array - */ - public static function get_allowed_countries(): array - { - return [ - 'AD', - 'AL', - 'AM', - 'AR', - 'AT', - 'AU', - 'BA', - 'BE', - 'BG', - 'BR', - 'CA', - 'CH', - 'CL', - 'CR', - 'CU', - 'CY', - 'CZ', - 'DE', - 'DK', - 'DZ', - 'EE', - 'EG', - 'ES', - 'FI', - 'FR', - 'GF', - 'GP', - 'GR', - 'HR', - 'HU', - 'IE', - 'IS', - 'IT', - 'JP', - 'KR', - 'LB', - 'LI', - 'LT', - 'LU', - 'LV', - 'MA', - 'MD', - 'ME', - 'MF', - 'MQ', - 'MT', - 'MX', - 'NC', - 'NL', - 'NO', - 'NZ', - 'PF', - 'PL', - 'PM', - 'PS', - 'PT', - 'RE', - 'RO', - 'SE', - 'SI', - 'SK', - 'SM', - 'TN', - 'TR', - 'TW', - 'US', - 'YT', - 'ZA', - ]; + /** + * Retourne la liste des pays acceptés pour la livraison. + * + * @return array + */ + public static function get_allowed_countries(): array { + return [ + 'AD', + 'AL', + 'AM', + 'AR', + 'AT', + 'AU', + 'BA', + 'BE', + 'BG', + 'BR', + 'CA', + 'CH', + 'CL', + 'CR', + 'CU', + 'CY', + 'CZ', + 'DE', + 'DK', + 'DZ', + 'EE', + 'EG', + 'ES', + 'FI', + 'FR', + 'GF', + 'GP', + 'GR', + 'HR', + 'HU', + 'IE', + 'IS', + 'IT', + 'JP', + 'KR', + 'LB', + 'LI', + 'LT', + 'LU', + 'LV', + 'MA', + 'MD', + 'ME', + 'MF', + 'MQ', + 'MT', + 'MX', + 'NC', + 'NL', + 'NO', + 'NZ', + 'PF', + 'PL', + 'PM', + 'PS', + 'PT', + 'RE', + 'RO', + 'SE', + 'SI', + 'SK', + 'SM', + 'TN', + 'TR', + 'TW', + 'US', + 'YT', + 'ZA', + ]; + } + + public static function parse_cart_value(int|float|string|bool $cart_value): string { + if (is_int($cart_value) || is_float($cart_value)) { + return self::format_number($cart_value); } - public static function parse_cart_value(int|float|string|bool $cart_value): string - { - if (is_int($cart_value) || is_float($cart_value)) { - return self::format_number($cart_value); - } + if (is_string($cart_value)) { + $number = Number::parseInt($cart_value); + $number = is_bool($number) ? 0 : $number; - if (is_string($cart_value)) { - $number = Number::parseInt($cart_value); - $number = is_bool($number) ? 0 : $number; - - return self::format_number($number); - } - - return '0.00'; + return self::format_number($number); } - private static function format_number(int|float $number): string - { - $formatted_number = Number::format( - number: $number, - // precision et max_precision sont mutuellement exclusifs. - precision: 2, - locale: 'fr', - ); - return is_bool($formatted_number) ? self::DEFAULT_VALUE : $formatted_number; - } + return '0.00'; + } + + private static function format_number(int|float $number): string { + $formatted_number = Number::format( + number: $number, + // precision et max_precision sont mutuellement exclusifs. + precision: 2, + locale: 'fr', + ); + + return is_bool($formatted_number) ? self::DEFAULT_VALUE : $formatted_number; + } } diff --git a/web/app/themes/haiku-atelier-2024/src/inc/Data/ProductVariation.php b/web/app/themes/haiku-atelier-2024/src/inc/Data/ProductVariation.php index eb4e5b95..11104db0 100644 --- a/web/app/themes/haiku-atelier-2024/src/inc/Data/ProductVariation.php +++ b/web/app/themes/haiku-atelier-2024/src/inc/Data/ProductVariation.php @@ -6,34 +6,32 @@ namespace HaikuAtelier\Data; use WC_Product; -final readonly class ProductVariation -{ - /** - * @param int $id L'ID de la Variation - * @param string $price Le prix de la Variation - * @param list $attributes Les attributs appliqués à la Variation - */ - private function __construct( - public int $id, - public string $price, - public array $attributes, - ) {} +final readonly class ProductVariation { + /** + * @param int $id L'ID de la Variation + * @param string $price Le prix de la Variation + * @param list $attributes Les attributs appliqués à la Variation + */ + private function __construct( + public int $id, + public string $price, + public array $attributes, + ) {} - /** - * Créé une nouvelle instance de `ProductVariation` à partir d'un `WC_Product`. - */ - public static function new(WC_Product $product): self - { - $id = $product->get_id(); - $price = $product->get_price(); - /** @var list */ - $attributes = array_map( - /** @phpstan-ignore argument.type (Impossible à satisfaire) */ - static fn(string $key, string $value) => new ProductVariationAttribute($key, $value), - array_keys($product->get_attributes()), - array_values($product->get_attributes()), - ); + /** + * Créé une nouvelle instance de `ProductVariation` à partir d'un `WC_Product`. + */ + public static function new(WC_Product $product): self { + $id = $product->get_id(); + $price = $product->get_price(); + /** @var list */ + $attributes = array_map( + /** @phpstan-ignore argument.type (Impossible à satisfaire) */ + static fn(string $key, string $value) => new ProductVariationAttribute($key, $value), + array_keys($product->get_attributes()), + array_values($product->get_attributes()), + ); - return new self($id, $price, $attributes); - } + return new self($id, $price, $attributes); + } } diff --git a/web/app/themes/haiku-atelier-2024/src/inc/Data/ProductVariationAttribute.php b/web/app/themes/haiku-atelier-2024/src/inc/Data/ProductVariationAttribute.php index 3bff266e..988ee3b4 100644 --- a/web/app/themes/haiku-atelier-2024/src/inc/Data/ProductVariationAttribute.php +++ b/web/app/themes/haiku-atelier-2024/src/inc/Data/ProductVariationAttribute.php @@ -4,14 +4,13 @@ declare(strict_types=1); namespace HaikuAtelier\Data; -final readonly class ProductVariationAttribute -{ - /** - * @param string $attribute Le slug de l'Attribut - * @param string $value Le slug de la valeur de l'Attribut - */ - public function __construct( - public string $attribute, - public string $value, - ) {} +final readonly class ProductVariationAttribute { + /** + * @param string $attribute Le slug de l'Attribut + * @param string $value Le slug de la valeur de l'Attribut + */ + public function __construct( + public string $attribute, + public string $value, + ) {} } diff --git a/web/app/themes/haiku-atelier-2024/src/inc/Fonctionnalites.php b/web/app/themes/haiku-atelier-2024/src/inc/Fonctionnalites.php index fc5e8947..771f1c3d 100755 --- a/web/app/themes/haiku-atelier-2024/src/inc/Fonctionnalites.php +++ b/web/app/themes/haiku-atelier-2024/src/inc/Fonctionnalites.php @@ -9,9 +9,8 @@ declare(strict_types=1); namespace HaikuAtelier; // Désactive divers transformations du contenu par WordPress -function desactive_wpautop(): void -{ - remove_filter('the_content', 'wpautop'); +function desactive_wpautop(): void { + remove_filter('the_content', 'wpautop'); } /** @@ -21,17 +20,16 @@ function desactive_wpautop(): void * * @return array le même tableau avec des configurations en plus */ -function desactive_transformation_contenu_tinymce(array $configuration): array -{ - // Ne supprime pas les retours à la ligne - $configuration['remove_linebreaks'] = false; - // Convertis les caractères de retours à la ligne en
- $configuration['convert_newlines_to_brs'] = true; - // Supprime les
redondants - $configuration['remove_redundant_brs'] = false; +function desactive_transformation_contenu_tinymce(array $configuration): array { + // Ne supprime pas les retours à la ligne + $configuration['remove_linebreaks'] = false; + // Convertis les caractères de retours à la ligne en
+ $configuration['convert_newlines_to_brs'] = true; + // Supprime les
redondants + $configuration['remove_redundant_brs'] = false; - // Retourne $configuration à WordPress - return $configuration; + // Retourne $configuration à WordPress + return $configuration; } /** @@ -41,22 +39,19 @@ function desactive_transformation_contenu_tinymce(array $configuration): array * * @return array le même tableau avec SVG en plus */ -function autorise_import_svg_mediatheque(array $file_types): array -{ - $new_filetypes = []; - $new_filetypes['svg'] = 'image/svg+xml'; +function autorise_import_svg_mediatheque(array $file_types): array { + $new_filetypes = []; + $new_filetypes['svg'] = 'image/svg+xml'; - return [...$file_types, ...$new_filetypes]; + return [...$file_types, ...$new_filetypes]; } -function retire_motifs_blocs_gutenberg(): void -{ - remove_theme_support('core-block-patterns'); +function retire_motifs_blocs_gutenberg(): void { + remove_theme_support('core-block-patterns'); } -function retire_styles_core_block(): void -{ - wp_dequeue_style('core-block-supports'); +function retire_styles_core_block(): void { + wp_dequeue_style('core-block-supports'); } // Désactive les appels à l'API de la mise à jour des traductions diff --git a/web/app/themes/haiku-atelier-2024/src/inc/Taxonomies.php b/web/app/themes/haiku-atelier-2024/src/inc/Taxonomies.php index 176f36f6..727459f0 100755 --- a/web/app/themes/haiku-atelier-2024/src/inc/Taxonomies.php +++ b/web/app/themes/haiku-atelier-2024/src/inc/Taxonomies.php @@ -14,33 +14,32 @@ use function register_taxonomy; /** * Enregistre la Taxonomie « Collection ». */ -function enregistre_taxonomie_collection(): void -{ - $labels = [ - 'add_new_item' => __('Add New Collection'), - 'all_items' => __('All Collections'), - 'edit_item' => __('Edit Collection'), - 'menu_name' => __('Collections'), - 'name' => __('Collections'), - 'new_item_name' => __('New Collection Name'), - 'search_items' => __('Search Collections'), - 'singular_name' => __('Collection'), - 'update_item' => __('Update Collection'), - ]; - $args = [ - 'description' => __('An ensemble of pieces thematically or chronologically grouped together.'), - 'hierarchical' => false, - 'labels' => $labels, - 'publicly_queryable' => false, - 'query_var' => true, - 'rewrite' => ['slug' => 'collection'], - 'show_admin_column' => true, - 'show_in_menu' => true, - 'show_in_quick_edit' => true, - 'show_ui' => true, - ]; +function enregistre_taxonomie_collection(): void { + $labels = [ + 'add_new_item' => __('Add New Collection'), + 'all_items' => __('All Collections'), + 'edit_item' => __('Edit Collection'), + 'menu_name' => __('Collections'), + 'name' => __('Collections'), + 'new_item_name' => __('New Collection Name'), + 'search_items' => __('Search Collections'), + 'singular_name' => __('Collection'), + 'update_item' => __('Update Collection'), + ]; + $args = [ + 'description' => __('An ensemble of pieces thematically or chronologically grouped together.'), + 'hierarchical' => false, + 'labels' => $labels, + 'publicly_queryable' => false, + 'query_var' => true, + 'rewrite' => ['slug' => 'collection'], + 'show_admin_column' => true, + 'show_in_menu' => true, + 'show_in_quick_edit' => true, + 'show_ui' => true, + ]; - register_taxonomy('collection', ['product'], $args); + register_taxonomy('collection', ['product'], $args); } add_action('init', enregistre_taxonomie_collection(...)); diff --git a/web/app/themes/haiku-atelier-2024/src/inc/WP/Post.php b/web/app/themes/haiku-atelier-2024/src/inc/WP/Post.php index 0decb949..116eb07b 100644 --- a/web/app/themes/haiku-atelier-2024/src/inc/WP/Post.php +++ b/web/app/themes/haiku-atelier-2024/src/inc/WP/Post.php @@ -14,50 +14,46 @@ use function is_array; use function Psl\Option\none; use function Psl\Option\some; -final readonly class Post -{ - /** - * @return Option\Option - */ - 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); +final readonly class Post { + /** + * @return Option\Option + */ + 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); + if ($value === false) { + return none(); } - /** - * @return Option\Option> - */ - public static function get_post_meta_array(int $post_id, string $key): Option\Option - { - /** @var array|false */ - $value = get_post_meta($post_id, $key, false); + return some($value); + } - if (is_array($value)) { - return some($value); - } + /** + * @return Option\Option> + */ + public static function get_post_meta_array(int $post_id, string $key): Option\Option { + /** @var array|false */ + $value = get_post_meta($post_id, $key, false); - return none(); + if (is_array($value)) { + return some($value); } - /** - * @return Option\Option> - */ - public static function get_terms(int $post_id, string $taxonomy_name): Option\Option - { - /** @var false|list|WP_Error */ - $terms = get_the_terms($post_id, $taxonomy_name); + return none(); + } - if (is_array($terms)) { - return some($terms); - } + /** + * @return Option\Option> + */ + public static function get_terms(int $post_id, string $taxonomy_name): Option\Option { + /** @var false|list|WP_Error */ + $terms = get_the_terms($post_id, $taxonomy_name); - return none(); + if (is_array($terms)) { + return some($terms); } + + return none(); + } } diff --git a/web/app/themes/haiku-atelier-2024/src/scripts-effect/schemas/api.ts b/web/app/themes/haiku-atelier-2024/src/scripts-effect/schemas/api.ts new file mode 100644 index 00000000..97c8b1ff --- /dev/null +++ b/web/app/themes/haiku-atelier-2024/src/scripts-effect/schemas/api.ts @@ -0,0 +1,10 @@ +import { Schema } from "effect"; +import { ProductAttribute } from "./product.ts"; + +class AddProductToCart extends Schema.Class("AddProductToCart")({ + id: Schema.Int, + quantity: Schema.Int.check(Schema.isGreaterThan(0)), + variation: Schema.Array(ProductAttribute), +}) {} + +export { AddProductToCart }; diff --git a/web/app/themes/haiku-atelier-2024/src/scripts-effect/schemas/product.ts b/web/app/themes/haiku-atelier-2024/src/scripts-effect/schemas/product.ts new file mode 100644 index 00000000..1c8055a4 --- /dev/null +++ b/web/app/themes/haiku-atelier-2024/src/scripts-effect/schemas/product.ts @@ -0,0 +1,20 @@ +// oxlint-disable no-magic-numbers -- Pas besoin ici. +import { Schema } from "effect"; + +class ProductAttribute extends Schema.Class("ProductAttribute")({ + /** L'identifiant _(slug)_ de l'Attribut. */ + attribute: Schema.String, + /** La valeur de l'attribut. */ + value: Schema.String, +}) {} + +class ProductVariation extends Schema.Class("ProductVariation")({ + /** Les Attributs présents pour cette Variation. */ + attributes: Schema.Array(ProductAttribute), + /** L'identifiant numérique unique de la Variation. */ + id: Schema.Int.check(Schema.isGreaterThan(0)), + /** Le prix de la Variation. */ + price: Schema.NonEmptyString, +}) {} + +export { ProductAttribute, ProductVariation }; diff --git a/web/app/themes/haiku-atelier-2024/src/scripts/page-produit/runtime.ts b/web/app/themes/haiku-atelier-2024/src/scripts/page-produit/runtime.ts new file mode 100644 index 00000000..72941df3 --- /dev/null +++ b/web/app/themes/haiku-atelier-2024/src/scripts/page-produit/runtime.ts @@ -0,0 +1,13 @@ +import { Console, Layer, ManagedRuntime, pipe } from "effect"; +import ProductPageDOM from "./service-dom.ts"; +import ProductPageElements from "./service-elements.ts"; + +const ProductPageRuntime = ManagedRuntime.make( + pipe( + ProductPageDOM.layer, + Layer.provide(ProductPageElements.layer), + Layer.tapError(error => Console.error("ProductPageRuntime", "Impossible de créer le Layer :", error)), + ), +); + +export default ProductPageRuntime; diff --git a/web/app/themes/haiku-atelier-2024/src/scripts/scripts-page-produit-service.ts b/web/app/themes/haiku-atelier-2024/src/scripts/page-produit/service-dom.ts similarity index 52% rename from web/app/themes/haiku-atelier-2024/src/scripts/scripts-page-produit-service.ts rename to web/app/themes/haiku-atelier-2024/src/scripts/page-produit/service-dom.ts index 72469a32..672b94d0 100644 --- a/web/app/themes/haiku-atelier-2024/src/scripts/scripts-page-produit-service.ts +++ b/web/app/themes/haiku-atelier-2024/src/scripts/page-produit/service-dom.ts @@ -6,97 +6,28 @@ import { Effect, HashMap, Layer, - ManagedRuntime, Option, pipe, + Ref, + Schema, + SchemaIssue, Stream, } from "effect"; -import type { NonEmptyReadonlyArray } from "effect/Array"; import type { NoSuchElementError } from "effect/Cause"; -import { getAllSelectorFromDocument, getFirstSelectorFromDocument } from "../scripts-effect/lib/dom.ts"; +import { AddProductToCart } from "../../scripts-effect/schemas/api.ts"; +import { ProductAttribute, ProductVariation } from "../../scripts-effect/schemas/product.ts"; import { ATTRIBUT_ARIA_CONTROLS, ATTRIBUT_ARIA_EXPANDED, ATTRIBUT_DESACTIVE, ATTRIBUT_HIDDEN, - DOM_BOUTON_AJOUT_PANIER, - DOM_BOUTONS_ACCORDEON, - DOM_CONTENUS_ACCORDEON, - DOM_PRIX_PRODUIT, -} from "./constantes/dom.ts"; -import type { WCStoreCartAddItemArgsItems } from "./lib/types/api/cart-add-item.d.ts"; - -/** Représente un ensemble bouton-contenu d'une Section dans la description du Produit. */ -type DetailEnsemble = { - button: HTMLButtonElement; - content: HTMLDivElement; -}; - -class ProductPageElements extends Context.Service< - ProductPageElements, - { - AddToCartButton: HTMLButtonElement; - Details: HashMap.HashMap; - DetailsButtons: NonEmptyReadonlyArray; - DetailsContents: NonEmptyReadonlyArray; - ProductPrice: HTMLParagraphElement; - ProductRawJson: HTMLScriptElement; - VariationChoiceForm: HTMLFormElement; - VariationSelectors: ReadonlyArray; - } ->()("haikuatelier.fr/Produit/ProductPageElements") { - static readonly layer = Layer.effect( - ProductPageElements, - Effect.gen(function*() { - const AddToCartButton = yield* getFirstSelectorFromDocument(DOM_BOUTON_AJOUT_PANIER); - const DetailsButtons = yield* getAllSelectorFromDocument(DOM_BOUTONS_ACCORDEON); - const DetailsContents = yield* getAllSelectorFromDocument(DOM_CONTENUS_ACCORDEON); - const ProductPrice = yield* getFirstSelectorFromDocument(DOM_PRIX_PRODUIT); - const ProductRawJson = yield* getFirstSelectorFromDocument("#product-json"); - const VariationChoiceForm = yield* getFirstSelectorFromDocument("#variation-choice"); - const VariationSelectors = yield* pipe( - getAllSelectorFromDocument(".selecteur-produit select"), - Option.orElseSome(() => FxArray.empty()), - ); - - const Details = yield* pipe( - DetailsButtons, - FxArray.map( - (button: HTMLButtonElement, index: number): Effect.Effect<[string, DetailEnsemble], NoSuchElementError> => - Effect.gen(function*() { - const contentId = yield* Option.fromNullishOr(button.getAttribute(ATTRIBUT_ARIA_CONTROLS)); - const content = yield* FxArray.get(DetailsContents, index); - - return [contentId, { button, content } satisfies DetailEnsemble]; - }), - ), - Effect.all, - Effect.map(HashMap.fromIterable), - ); - - return ProductPageElements.of({ - AddToCartButton, - Details, - DetailsButtons, - DetailsContents, - ProductPrice, - ProductRawJson, - VariationChoiceForm, - VariationSelectors, - }); - }), - ); -} +} from "../constantes/dom.ts"; +import ProductPageElements from "./service-elements.ts"; +import type { DetailEnsemble } from "./types.d.ts"; class ProductPageDOM extends Context.Service< ProductPageDOM, { - initPriceUpdatesOnVariationChange: () => Effect.Effect; - onVariationChangeHandler: () => Effect.Effect; - /** - * Récupère les Attributs du Produit depuis les Elements au sein du DOM. - */ - getProductAttributesFromDOM: () => Effect.Effect>; /** * Initialise l'état initial du Bouton d'ajout au Panier. */ @@ -110,26 +41,30 @@ class ProductPageDOM extends Context.Service< */ initDetailInteractions: () => Effect.Effect; /** - * Met à jour l'état des Sections de la Description du Produit. + * Initialise la mise à jour du Prix affiché en fonction du choix de la Varation de Produit. */ - onDetailButtonClickHandler: (evt: Event) => Effect.Effect; - /** - * Met à jour l'état du Bouton d'ajout au Panier. - */ - onFormChangeHandler: (evt: Event) => Effect.Effect; + initPriceUpdatesOnVariationChange: () => Effect.Effect; /** * Replie toutes les sections de la description du Produit. */ - toggleAllDetails: () => Effect.Effect; + initAddToCartButtonClicks: () => unknown; + ProductVariations: ReadonlyArray; + CurrentVariation: Ref.Ref>; } >()("haikuatelier.fr/Produit/ProductPageDOM") { static readonly layer = Layer.effect( ProductPageDOM, Effect.gen(function*() { - const { AddToCartButton, Details, ProductPrice, DetailsButtons, ProductRawJson, VariationChoiceForm, VariationSelectors } = - yield* ProductPageElements; - - const onFormChangeHandler = Effect.fnUntraced(function*(evt: Event) { + const { + AddToCartButton, + Details, + DetailsButtons, + ProductPrice, + ProductRawJson, + VariationChoiceForm, + VariationSelectors, + } = yield* ProductPageElements; + const onFormChangeHandler = Effect.fn("onFormChangeHandler")(function*(evt: Event): Effect.fn.Return { // La cible ne peut qu'être un Formulaire. const target: HTMLFormElement = evt.target as HTMLFormElement; const isClickAllowed = target.checkValidity() === false; @@ -152,7 +87,9 @@ class ProductPageDOM extends Context.Service< ); }); - const onDetailButtonClickHandler = Effect.fnUntraced(function*(evt: Event) { + const onDetailButtonClickHandler = Effect.fn("onDetailButtonClickHandler")(function*( + evt: Event, + ): Effect.fn.Return { // Empêche la pollution de l'historique de navigation evt.preventDefault(); @@ -183,13 +120,67 @@ class ProductPageDOM extends Context.Service< return yield* Effect.void; }); - const getProductAttributesFromDOM: () => Effect.Effect> = () => - Effect.sync(() => + const ProductVariations: ReadonlyArray = yield* pipe( + JSON.parse(ProductRawJson.textContent)?.variations, + json => Schema.decodeUnknownEffect(Schema.Array(ProductVariation))(json, { onExcessProperty: "ignore" }), + Effect.mapError(error => SchemaIssue.makeFormatterStandardSchemaV1()(error.issue)), + Effect.tapCause(Console.error), + ); + + const getChosenProductAttributesFromDOM = Effect.fn("getChosenProductAttributesFromDOM")(function*() { + return yield* pipe( FxArray.map(VariationSelectors, (select: HTMLSelectElement) => ({ attribute: select.id, value: select.value, - })) + })), + variations => Schema.decodeEffect(Schema.Array(ProductAttribute))(variations), + Effect.mapError(error => SchemaIssue.makeFormatterDefault()(error.issue)), + Effect.tapCause(Console.error), ); + }); + + const CurrentVariation = yield* Ref.make(Option.none()); + + const onVariationChangeHandler = Effect.fn("onVariationChangeHandler")(function*(): Effect.fn.Return< + void, + NoSuchElementError | string + > { + yield* Console.debug("onVariationChangeHandler"); + // Ne fais rien si le Formulaire n'est pas valide. + if (VariationChoiceForm.checkValidity() === false) { + yield* Console.debug("onVariationChangeHandler", "Le formulaire est invalide."); + return yield* Effect.void; + } + + const equivalence = Schema.toEquivalence(Schema.Array(ProductAttribute)); + const chosenProductAttributes = yield* getChosenProductAttributesFromDOM(); + const chosenVariation: ProductVariation = yield* FxArray.findFirst( + ProductVariations, + (variation: ProductVariation) => equivalence(variation.attributes, chosenProductAttributes), + ); + + // Met à jour la valeur de la Variation choisie dans le Service. + yield* Ref.set(Option.some(chosenVariation))(CurrentVariation); + + const newPrice = chosenVariation.price; + ProductPrice.textContent = `${newPrice}€`; + + return yield* Effect.void; + }, Effect.tapCause(Console.error)); + + const onAddToCartButtonHandler = Effect.fn("onAddToCartButtonHandler")(function*() { + const chosenVariation = yield* Ref.getUnsafe(CurrentVariation); + const productDetails = yield* Schema.decodeEffect(AddProductToCart)( + { + id: chosenVariation.id, + quantity: 1, + variation: chosenVariation.attributes, + }, + { errors: "all" }, + ); + + console.debug(productDetails); + }); const initAddToCartButtonInitialState = Effect.fn("initAddToCartButtonInitialState")(function*() { /** Est-ce que le Produit affiché est en stock ? */ @@ -216,31 +207,23 @@ class ProductPageDOM extends Context.Service< ); }); - const initPriceUpdatesOnVariationChange = Effect.fn("initPriceUpdatesOnVariationChange")(function*(){ + const initAddToCartButtonClicks = Effect.fn("initAddToCartButtonClicks")(function*() { return yield* pipe( - Stream.fromEventListener(VariationChoiceForm, "change"), - Stream.tap(onVariationChangeHandler), + Stream.fromEventListener(AddToCartButton, "click"), + Stream.tap(onAddToCartButtonHandler), Stream.runDrain, - ) + ); }); - const onVariationChangeHandler = Effect.fn("onVariationChangeHandler")(function*(){ - if (VariationChoiceForm.checkValidity() === false) { - return yield* Effect.void; - } - - const variations = JSON.parse(ProductRawJson.textContent)?.variations as ReadonlyArray; - const chosenAttributes = yield* getProductAttributesFromDOM(); - - const equivalence = FxArray.makeEquivalence<{attribute: string,value: string}>((a,b) => { - return a.attribute === b.attribute && a.value === b.value; - }); - const chosenVariation = yield* FxArray.findFirst(variations, variation => equivalence(variation.attributes, chosenAttributes)); - const newPrice = chosenVariation.price; - - ProductPrice.textContent = `${newPrice}€`; - return yield* Effect.void; - }); + const initPriceUpdatesOnVariationChange = Effect.fn("initPriceUpdatesOnVariationChange")( + function*(): Effect.fn.Return { + return yield* pipe( + Stream.fromEventListener(VariationChoiceForm, "change"), + Stream.tap(onVariationChangeHandler), + Stream.runDrain, + ); + }, + ); const initDetailInteractions = Effect.fn("initDetailInteractions")(function*() { return yield* pipe( @@ -256,26 +239,16 @@ class ProductPageDOM extends Context.Service< }); return ProductPageDOM.of({ - getProductAttributesFromDOM, + CurrentVariation, + ProductVariations, + initAddToCartButtonClicks, initAddToCartButtonInitialState, initAddToCartButtonUpdates, initDetailInteractions, initPriceUpdatesOnVariationChange, - onDetailButtonClickHandler, - onFormChangeHandler, - onVariationChangeHandler, - toggleAllDetails, }); }), ); } -const ProductPageRuntime = ManagedRuntime.make( - pipe( - ProductPageDOM.layer, - Layer.provide(ProductPageElements.layer), - Layer.tapError(error => Console.error("ManagedRuntime", "Impossible de créer le Layer :", error.name)), - ), -); - -export { type DetailEnsemble, ProductPageDOM, ProductPageElements, ProductPageRuntime }; +export default ProductPageDOM; diff --git a/web/app/themes/haiku-atelier-2024/src/scripts/page-produit/service-elements.ts b/web/app/themes/haiku-atelier-2024/src/scripts/page-produit/service-elements.ts new file mode 100644 index 00000000..bb673c5f --- /dev/null +++ b/web/app/themes/haiku-atelier-2024/src/scripts/page-produit/service-elements.ts @@ -0,0 +1,70 @@ +import { Array as FxArray, Context, Effect, HashMap, Layer, Option, pipe } from "effect"; +import type { NonEmptyReadonlyArray } from "effect/Array"; +import type { NoSuchElementError } from "effect/Cause"; +import { getAllSelectorFromDocument, getFirstSelectorFromDocument } from "../../scripts-effect/lib/dom.ts"; +import { + ATTRIBUT_ARIA_CONTROLS, + DOM_BOUTON_AJOUT_PANIER, + DOM_BOUTONS_ACCORDEON, + DOM_CONTENUS_ACCORDEON, + DOM_PRIX_PRODUIT, +} from "../constantes/dom.ts"; +import type { DetailEnsemble } from "./types.d.ts"; + +class ProductPageElements extends Context.Service< + ProductPageElements, + { + AddToCartButton: HTMLButtonElement; + Details: HashMap.HashMap; + DetailsButtons: NonEmptyReadonlyArray; + DetailsContents: NonEmptyReadonlyArray; + ProductPrice: HTMLParagraphElement; + ProductRawJson: HTMLScriptElement; + VariationChoiceForm: HTMLFormElement; + VariationSelectors: ReadonlyArray; + } +>()("haikuatelier.fr/Produit/ProductPageElements") { + static readonly layer = Layer.effect( + ProductPageElements, + Effect.gen(function*() { + const AddToCartButton = yield* getFirstSelectorFromDocument(DOM_BOUTON_AJOUT_PANIER); + const DetailsButtons = yield* getAllSelectorFromDocument(DOM_BOUTONS_ACCORDEON); + const DetailsContents = yield* getAllSelectorFromDocument(DOM_CONTENUS_ACCORDEON); + const ProductPrice = yield* getFirstSelectorFromDocument(DOM_PRIX_PRODUIT); + const ProductRawJson = yield* getFirstSelectorFromDocument("#product-json"); + const VariationChoiceForm = yield* getFirstSelectorFromDocument("#variation-choice"); + const VariationSelectors = yield* pipe( + getAllSelectorFromDocument(".selecteur-produit select"), + Option.orElseSome(() => FxArray.empty()), + ); + + const Details = yield* pipe( + DetailsButtons, + FxArray.map( + (button: HTMLButtonElement, index: number): Effect.Effect<[string, DetailEnsemble], NoSuchElementError> => + Effect.gen(function*() { + const contentId = yield* Option.fromNullishOr(button.getAttribute(ATTRIBUT_ARIA_CONTROLS)); + const content = yield* FxArray.get(DetailsContents, index); + + return [contentId, { button, content } satisfies DetailEnsemble]; + }), + ), + Effect.all, + Effect.map(HashMap.fromIterable), + ); + + return ProductPageElements.of({ + AddToCartButton, + Details, + DetailsButtons, + DetailsContents, + ProductPrice, + ProductRawJson, + VariationChoiceForm, + VariationSelectors, + }); + }), + ); +} + +export default ProductPageElements; diff --git a/web/app/themes/haiku-atelier-2024/src/scripts/page-produit/types.d.ts b/web/app/themes/haiku-atelier-2024/src/scripts/page-produit/types.d.ts new file mode 100644 index 00000000..f1940311 --- /dev/null +++ b/web/app/themes/haiku-atelier-2024/src/scripts/page-produit/types.d.ts @@ -0,0 +1,6 @@ +/** Représente un ensemble bouton-contenu d'une Section dans la description du Produit. */ +type DetailEnsemble = { + button: HTMLButtonElement; + content: HTMLDivElement; +}; +export { DetailEnsemble }; diff --git a/web/app/themes/haiku-atelier-2024/src/scripts/scripts-page-produit.ts b/web/app/themes/haiku-atelier-2024/src/scripts/scripts-page-produit.ts index 74a5dc35..1b911096 100755 --- a/web/app/themes/haiku-atelier-2024/src/scripts/scripts-page-produit.ts +++ b/web/app/themes/haiku-atelier-2024/src/scripts/scripts-page-produit.ts @@ -1,10 +1,8 @@ // Scripts pour la Page Produit -import { pipe } from "@mobily/ts-belt"; -import { Console, Effect, pipe as epipe } from "effect"; - - -import { ProductPageRuntime } from "./scripts-page-produit-service.ts"; +import { Console, Effect } from "effect"; +import ProductPageRuntime from "./page-produit/runtime.ts"; +import ProductPageDOM from "./page-produit/service-dom.ts"; /** États utiles pour les scripts de la page. */ type EtatsPage = { @@ -17,31 +15,6 @@ type EtatsPage = { // @ts-expect-error -- États injectés par le modèle PHP const ETATS_PAGE: EtatsPage = _etats; -const areArraysEqual = (array1: Array, array2: Array): boolean => { - if (array1 !== array2) { - const a1 = JSON.stringify(array1.toSorted()); - const a2 = JSON.stringify(array2.toSorted()); - return a1 === a2; - } - return true; -}; - -const updatePriceOnAttributeChange = (): void => { - E.VARIATION_CHOICE_FORM.addEventListener("change", (): void => { - if (E.VARIATION_CHOICE_FORM.checkValidity() === false) { - return; - } - - const productVariations: Array = epipe(E.PRODUCT_JSON.textContent, JSON.parse)?.variations; - const chosenAttributes = getAttributesFromDom(); - - const chosenVariation = productVariations.find(v => areArraysEqual(v.attributes, chosenAttributes)); - const newPrice: string = chosenVariation.price; - - E.PRIX_PRODUIT.textContent = `${newPrice}€`; - }); -}; - // const ajouteProduitAuPanier = (event: MouseEvent): void => { // event.preventDefault(); // console.debug("getAttributeValuesFromDom", getAttributesFromDom()); @@ -131,8 +104,24 @@ const updatePriceOnAttributeChange = (): void => { // }; document.addEventListener("DOMContentLoaded", (): void => { - ProductPageRuntime.runFork(pipe(initAddToCartButton(), Effect.tapCause(Console.error))); - ProductPageRuntime.runFork(pipe(initAddToCartInteractionUpdates(), Effect.tapCause(Console.error))); - ProductPageRuntime.runFork(pipe(initDetailInteractions(), Effect.tapCause(Console.error))); - updatePriceOnAttributeChange(); + console.debug("oups"); + Effect.gen(function*() { + const DOM = yield* ProductPageDOM; + console.debug("oups"); + + const effects = Effect.all( + [ + DOM.initAddToCartButtonInitialState(), + DOM.initAddToCartButtonUpdates(), + DOM.initDetailInteractions(), + DOM.initPriceUpdatesOnVariationChange(), + DOM.initAddToCartButtonClicks(), + ], + { + concurrency: "unbounded", + }, + ); + + yield* effects.pipe(Effect.tapCause(Console.error)); + }).pipe(ProductPageRuntime.runFork); }); diff --git a/web/app/themes/haiku-atelier-2024/woocommerce/emails/customer-completed-order.php b/web/app/themes/haiku-atelier-2024/woocommerce/emails/customer-completed-order.php index db6b572b..61fbe911 100755 --- a/web/app/themes/haiku-atelier-2024/woocommerce/emails/customer-completed-order.php +++ b/web/app/themes/haiku-atelier-2024/woocommerce/emails/customer-completed-order.php @@ -12,7 +12,7 @@ use Illuminate\Support\Str; use Timber\Timber; if (!defined('ABSPATH')) { - exit(); + exit(); } // Initialise Timber @@ -30,13 +30,13 @@ $commande = $order; $date = new Carbon($commande->get_date_created()); $email = [ - 'commande' => ['date' => $date->toDateString(), 'id' => $commande->get_id()], - 'livraison' => [ - 'transporteur' => Str::of($commande->get_shipping_method())->replace(' (Free)', ''), - 'numero_suivi' => blank($commande->get_meta('tracking_number')) - ? 'UNKNOWN_TRACKING_NUMBER' - : $commande->get_meta('tracking_number'), - ], + 'commande' => ['date' => $date->toDateString(), 'id' => $commande->get_id()], + 'livraison' => [ + 'transporteur' => Str::of($commande->get_shipping_method())->replace(' (Free)', ''), + 'numero_suivi' => blank($commande->get_meta('tracking_number')) + ? 'UNKNOWN_TRACKING_NUMBER' + : $commande->get_meta('tracking_number'), + ], ]; $context['commande'] = $email; diff --git a/web/app/themes/haiku-atelier-2024/woocommerce/emails/customer-invoice.php b/web/app/themes/haiku-atelier-2024/woocommerce/emails/customer-invoice.php index cd2e6b05..cbdcd419 100755 --- a/web/app/themes/haiku-atelier-2024/woocommerce/emails/customer-invoice.php +++ b/web/app/themes/haiku-atelier-2024/woocommerce/emails/customer-invoice.php @@ -13,7 +13,7 @@ use Illuminate\Support\Str; use Timber\Timber; if (!defined('ABSPATH')) { - exit(); + exit(); } // Initialise Timber @@ -31,49 +31,44 @@ $commande = $order; $date = new Carbon($commande->get_date_created()); $email = [ - 'adresses' => [ - 'facturation' => $commande->get_address('billing'), - 'livraison' => $commande->get_address('shipping'), - ], - 'commande' => ['date' => $date->toDateString(), 'id' => $commande->get_id()], - 'livraison' => [ - 'methode' => $commande->get_shipping_method(), - 'numero_suivi' => $commande->get_meta('tracking_number'), - ], - 'paiement' => ['methode' => ''], - 'produits' => collect($commande->get_items())->map(static function (WC_Order_Item_Product $article) { - $produit = $article->get_product(); + 'adresses' => ['facturation' => $commande->get_address('billing'), 'livraison' => $commande->get_address('shipping')], + 'commande' => ['date' => $date->toDateString(), 'id' => $commande->get_id()], + 'livraison' => [ + 'methode' => $commande->get_shipping_method(), + 'numero_suivi' => $commande->get_meta('tracking_number'), + ], + 'paiement' => ['methode' => ''], + 'produits' => collect($commande->get_items())->map(static function (WC_Order_Item_Product $article) { + $produit = $article->get_product(); - if (is_bool($produit) || $produit === null) { - return []; - } + if (is_bool($produit) || $produit === null) { + return []; + } - return [ - // Récupère l'Attribut d'un Produit variable ou renvoie un tableau vide - 'attribut' => $produit->is_type('variable') - ? collect($produit->get_attributes()) - ->mapWithKeys(static fn($_atr, $cle): array => [ - 'nom' => Str::lower(wc_attribute_label($cle, $produit)), - 'valeur' => $produit->get_attribute($cle), - ]) - ->toArray() - : [], - 'lien' => $produit->get_permalink(), - 'nom' => $produit->get_title(), - 'prix_total' => $article->get_total(), - 'quantite' => $article->get_quantity(), - ]; - }), - 'totaux' => [ - 'sous_total_livraison' => '0' === $commande->get_shipping_total() - ? 'Free' - : $commande->get_shipping_total() . '€', - 'sous_total_produits' => $commande->get_subtotal() . '€', - 'sous_total_reduction' => '0.00' === $commande->get_discount_total() - ? '0' - : Number::format((float) $commande->get_discount_total(), maxPrecision: 2) . '€', - 'total' => Number::format((float) $commande->get_total(), maxPrecision: 2) . '€', - ], + return [ + // Récupère l'Attribut d'un Produit variable ou renvoie un tableau vide + 'attribut' => $produit->is_type('variable') + ? collect($produit->get_attributes()) + ->mapWithKeys(static fn($_atr, $cle): array => [ + 'nom' => Str::lower(wc_attribute_label($cle, $produit)), + 'valeur' => $produit->get_attribute($cle), + ]) + ->toArray() + : [], + 'lien' => $produit->get_permalink(), + 'nom' => $produit->get_title(), + 'prix_total' => $article->get_total(), + 'quantite' => $article->get_quantity(), + ]; + }), + 'totaux' => [ + 'sous_total_livraison' => '0' === $commande->get_shipping_total() ? 'Free' : $commande->get_shipping_total() . '€', + 'sous_total_produits' => $commande->get_subtotal() . '€', + 'sous_total_reduction' => '0.00' === $commande->get_discount_total() + ? '0' + : Number::format((float) $commande->get_discount_total(), maxPrecision: 2) . '€', + 'total' => Number::format((float) $commande->get_total(), maxPrecision: 2) . '€', + ], ]; // Transforme les codes de pays en noms de pays $email['adresses']['livraison']['country'] = WC()->countries->countries[$commande->get_shipping_country()]; diff --git a/web/app/themes/haiku-atelier-2024/woocommerce/emails/customer-processing-order.php b/web/app/themes/haiku-atelier-2024/woocommerce/emails/customer-processing-order.php index ec64d829..830883aa 100755 --- a/web/app/themes/haiku-atelier-2024/woocommerce/emails/customer-processing-order.php +++ b/web/app/themes/haiku-atelier-2024/woocommerce/emails/customer-processing-order.php @@ -13,7 +13,7 @@ use Illuminate\Support\Str; use Timber\Timber; if (!defined('ABSPATH')) { - exit(); + exit(); } // Initialise Timber @@ -31,45 +31,40 @@ $commande = $order; $date = new Carbon($commande->get_date_created()); $email = [ - 'adresses' => [ - 'facturation' => $commande->get_address('billing'), - 'livraison' => $commande->get_address('shipping'), - ], - 'commande' => ['date' => $date->toDateString(), 'id' => $commande->get_id()], - 'paiement' => ['methode' => ''], - 'produits' => collect($commande->get_items())->map(static function (WC_Order_Item_Product $article) { - $produit = $article->get_product(); + 'adresses' => ['facturation' => $commande->get_address('billing'), 'livraison' => $commande->get_address('shipping')], + 'commande' => ['date' => $date->toDateString(), 'id' => $commande->get_id()], + 'paiement' => ['methode' => ''], + 'produits' => collect($commande->get_items())->map(static function (WC_Order_Item_Product $article) { + $produit = $article->get_product(); - if (is_bool($produit) || $produit === null) { - return []; - } + if (is_bool($produit) || $produit === null) { + return []; + } - return [ - // Récupère l'Attribut d'un Produit variable ou renvoie un tableau vide - 'attribut' => $article->is_type('variable') - ? collect($produit->get_attributes()) - ->mapWithKeys(static fn($_atr, $cle): array => [ - 'nom' => Str::lower(wc_attribute_label($cle, $produit)), - 'valeur' => $produit->get_attribute($cle), - ]) - ->toArray() - : [], - 'lien' => $produit->get_permalink(), - 'nom' => $produit->get_title(), - 'prix_total' => $article->get_total(), - 'quantite' => $article->get_quantity(), - ]; - }), - 'totaux' => [ - 'sous_total_livraison' => '0' === $commande->get_shipping_total() - ? 'Free' - : $commande->get_shipping_total() . '€', - 'sous_total_produits' => $commande->get_subtotal() . '€', - 'sous_total_reduction' => '0.00' === $commande->get_discount_total() - ? '0' - : Number::format((float) $commande->get_discount_total(), maxPrecision: 2) . '€', - 'total' => Number::format((float) $commande->get_total(), maxPrecision: 2) . '€', - ], + return [ + // Récupère l'Attribut d'un Produit variable ou renvoie un tableau vide + 'attribut' => $article->is_type('variable') + ? collect($produit->get_attributes()) + ->mapWithKeys(static fn($_atr, $cle): array => [ + 'nom' => Str::lower(wc_attribute_label($cle, $produit)), + 'valeur' => $produit->get_attribute($cle), + ]) + ->toArray() + : [], + 'lien' => $produit->get_permalink(), + 'nom' => $produit->get_title(), + 'prix_total' => $article->get_total(), + 'quantite' => $article->get_quantity(), + ]; + }), + 'totaux' => [ + 'sous_total_livraison' => '0' === $commande->get_shipping_total() ? 'Free' : $commande->get_shipping_total() . '€', + 'sous_total_produits' => $commande->get_subtotal() . '€', + 'sous_total_reduction' => '0.00' === $commande->get_discount_total() + ? '0' + : Number::format((float) $commande->get_discount_total(), maxPrecision: 2) . '€', + 'total' => Number::format((float) $commande->get_total(), maxPrecision: 2) . '€', + ], ]; // Transforme les codes de pays en noms de pays $email['adresses']['livraison']['country'] = WC()->countries->countries[$commande->get_shipping_country()];