Changeset 123168 in spip-zone


Ignore:
Timestamp:
Mar 27, 2020, 12:05:13 PM (3 months ago)
Author:
Charles Razack
Message:

Nouvelle option à prix_formater pour retourner simplement le nombre flottant arrondi selon la devise. C'est utile quand il ne s'agit pas d'afficher le prix, mais de retourner sa valeur, par exemple pour le #FORMULAIRE_PAYER de bank. On simplifie la signature de la fonction : il n'y a qu'un seul paramètre sous forme de tableau dans lequel on passe toutes les options. Donc l'exemple précédent donne : . Redécoupage des certaines fonctions, dont une nouvelle fonction qui donne toutes les infos sur une devise donnée.

Location:
_plugins_/prix
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • _plugins_/prix

    • Property subgit:lock:c79140b9e3e8f1a95b71274a9f8615b38c6d84de deleted
    • Property subgit:lock:775d07a3077ed8b91cbff10cc3f54f8e580a84b1 set to 2020-03-27T13:05:20.189
  • _plugins_/prix/demo/prix.html

    r122831 r123168  
    3737                #SET{lesprix,#LISTE{"123456.789","9876.54","-2.99"}}
    3838                #SET{prix,#ENV{prix,#GET{lesprix/0}}|floatval}
    39                 #SET{options,#ENV{style}|?{#ARRAY{style,#ENV{style}},#ARRAY}}
    4039                <section class="section">
    4140
     
    4443                        <p>Exemple de formatage des prix selon les devises et les langues.</p>
    4544
    46                         <pre class="ui violet inverted segment"><code class="ui code">\[\(\#VALEUR|prix_formater{EUR,fr-FR,\#GET{options}}\)\]</code></pre>
     45                        <pre class="ui violet inverted segment"><code class="ui code">\[\(\#VALEUR|prix_formater\{\#ARRAY\{currency,EUR,locale,fr-CA\}\}\)\]</code></pre>
    4746
    4847                        <table class="ui collapsing sortable table">
     
    8180                                                <td>#VALEUR{symbole}</td>
    8281                                                <td class="right aligned">
    83                                                         [(#GET{prix}|prix_formater{#CLE,#ENV{locale},#GET{options}})]
     82                                                        #SET{options,#ARRAY{currency,#CLE,locale,#ENV{locale},style,#ENV{style}}}
     83                                                        [(#GET{prix}|prix_formater{#GET{options}})]
    8484                                                </td>
    8585                                        </tr>
  • _plugins_/prix/paquet.xml

    r123012 r123168  
    22        prefix="prix"
    33        categorie="outil"
    4         version="1.2.2"
     4        version="1.2.3"
    55        etat="dev"
    66        compatibilite="[3.2.0;3.2.*]"
  • _plugins_/prix/prix_fonctions.php

    r123012 r123168  
    9999
    100100/**
    101  * Formater un nombre pour l'afficher comme un prix avec une devise
     101 * Formater un nombre pour l'afficher comme un prix selon une devise
     102 *
     103 * @note
     104 * Fonction déportée dans la fonction surchargeable `filtre_prix_formater`.
    102105 *
    103106 * @uses filtres_prix_formater_dist
    104107 */
    105 function prix_formater($prix, $devise = '', $langue = '', $options = array()) {
     108function prix_formater($prix, $options = array()) {
    106109        $fonction_formater = charger_fonction('prix_formater', 'filtres/');
    107         return $fonction_formater($prix, $devise, $langue, $options);
    108 }
    109 
    110 /**
    111  * Formater un nombre pour l'afficher comme un prix avec une devise
    112  *
    113  * Déport de la fonction `prix_formater`.
    114  * Fonction surchargeable avec `function filtres_prix_formater`.
     110        return $fonction_formater($prix, $options);
     111}
     112
     113/**
     114 * Formater un nombre pour l'afficher comme un prix.
     115 *
     116 * Le prix retourné respecte les règles d'affichages propres à chaque langue et devise :
     117 * nombre de décimales, virgules et/ou points, emplacement de la devise, etc.
     118 *
     119 * L'option `currency_display` permet d'avoir un format spécifique aux factures.
     120 * L'option `float_only` permet d'avoir le nombre flottant arrondi selon la devise.
     121 *
     122 * @note
     123 * Nécessite soit l'extension bcmath, soit l'extension intl.
     124 *
     125 * @example prix_formater($prix, array('currency'=>'EUR', 'locale'=>'fr-CA'))
    115126 *
    116127 * @see https://github.com/commerceguys/intl/blob/master/src/Formatter/CurrencyFormatterInterface.php#L8
     128 * @see https://www.php.net/manual/fr/numberformatter.formatcurrency.php
    117129 *
    118130 * @uses prix_devise_defaut
    119131 * @uses prix_locale_defaut
    120  *
    121  * @param Float $prix
     132 * @uses prix_devise_info
     133 * @uses prix_langue_vers_locale
     134 * @uses prix_filtrer_options_formater
     135 * @uses prix_alias_options_formater
     136 *
     137 * @param float $prix
    122138 *     Valeur du prix à formater
    123  * @param String $devise
    124  *     Code alphabétique à 3 lettres de la devise
    125  * @param String $locale
    126  *     Identifiant d'une locale (fr-CA) ou code de langue spip (fr_tu)
    127  * @param Array $options
     139 * @param array $options
    128140 *     Tableau d'options :
     141 *     - currency|devise :         (String) devise, code alphabétique à 3 lettres.
     142 *                                 Défaut : celle par défaut configurée
     143 *     - float|flottant :          (Bool) pour retourner le nombre flottant arrondi selon la devise
     144 *                                 au lieu d'une chaîne de texte.
     145 *                                 Défaut : false
     146 *     - locale :                  (String) identifiant d'une locale (fr-CA) ou code de langue SPIP (fr_tu)
    129147 *     - style :                   (String) standard | accounting.
    130148 *                                 Défaut : standard
    131  *     - use_grouping :            (Bool) grouper les séparateur.
     149 *     - use_grouping :            (Bool) grouper les séparateurs.
    132150 *                                 Défaut : true
    133151 *     - rounding_mode :           constante PHP_ROUND_ ou `none`.
     
    139157 *     - currency_display :        (String) symbol | code | none.
    140158 *                                 Défaut : symbol
    141  * @return String
     159 * @return string|float
    142160 *     Retourne une chaine contenant le prix formaté avec une devise
    143161 */
    144 function filtres_prix_formater_dist($prix, $devise = '', $locale = '', $options = array()) {
     162function filtres_prix_formater_dist($prix, $options = array()) {
    145163        prix_loader();
    146164
     165        // Alias des options
     166        $options = prix_alias_options_formater($options);
     167        var_dump($options);
     168
     169        // S'assurer d'avoir un nombre flottant
    147170        $prix = floatval(str_replace(',', '.', $prix));
    148         $prix_formate = $prix;
    149 
    150         // Devise à utiliser
    151         $devise = $devise ?: prix_devise_defaut();
    152 
    153         // Locale à utiliser
    154         $locale = $locale ?: prix_locale_defaut();
    155 
    156         // De préférence, on utilise la librairie Intl de Commerceguys
    157         if (extension_loaded('bcmath')) {
    158                 // Options : langue, style, etc.
    159                 $options_formatter = array(
    160                         'locale'           => $locale,
    161                         'currency_display' => 'code', // pour l'accessibilité
    162                 );
    163                 if (is_array($options)) {
    164                         $options_formatter = array_merge($options_formatter, $options);
     171
     172        // Devise à utiliser et sa fraction (ex. : nb pour passer des euros aux centimes)
     173        $devise = (!empty($options['currency']) ? $options['currency'] : prix_devise_defaut());
     174        $fraction = intval(prix_devise_info($devise, 'fraction'));
     175
     176        // S'il faut retourner directement le nombre flottant, on arrondit simplement selon la devise.
     177        if (!empty($options['float'])) {
     178                $prix_formate = round($prix, $fraction);
     179
     180        // Sinon lançons la machine
     181        } else {
     182
     183                // Locale à utiliser
     184                $locale = (!empty($options['locale']) ? $options['locale'] : prix_locale_defaut());
     185                $locale = prix_langue_vers_locale($locale);
     186
     187                // 1) De préférence, on utilise la librairie Intl de Commerceguys
     188                if (extension_loaded('bcmath')) {
     189
     190                        // Options : on pose celles de base puis on ajoute celles passées en paramètre.
     191                        $options_base = array(
     192                                'locale'           => $locale,
     193                                'currency_display' => 'code', // pour l'accessibilité
     194                        );
     195                        if (is_array($options)) {
     196                                $options = prix_filtrer_options_formater($options);
     197                                $options = array_merge($options_base, $options);
     198                        } else {
     199                                $options = $options_base;
     200                        }
     201
     202                        // Formatons
     203                        $numberFormatRepository = new CommerceGuys\Intl\NumberFormat\NumberFormatRepository;
     204                        $currencyRepository = new CommerceGuys\Intl\Currency\CurrencyRepository;
     205                        $currencyFormatter = new CommerceGuys\Intl\Formatter\CurrencyFormatter($numberFormatRepository, $currencyRepository, $options);
     206                        $prix_formate = $currencyFormatter->format($prix, $devise);
     207
     208                // 2) Sinon on se rabat sur la librairie Intl PECL
     209                } elseif (extension_loaded('intl')) {
     210                        $currencyFormatter = new NumberFormatter($locale, NumberFormatter::CURRENCY);
     211                        $prix_formate = $currencyFormatter->formatCurrency($prix, $devise);
     212
     213                // 3) Sinon, on fait le formatage du pauvre
     214                } else {
     215                        $prix_formate = str_replace('.', ',', round($prix, $fraction)) . '&nbsp;' . $devise;
    165216                }
    166 
    167                 $numberFormatRepository = new CommerceGuys\Intl\NumberFormat\NumberFormatRepository;
    168                 $currencyRepository = new CommerceGuys\Intl\Currency\CurrencyRepository;
    169                 $currencyFormatter = new CommerceGuys\Intl\Formatter\CurrencyFormatter($numberFormatRepository, $currencyRepository, $options_formatter);
    170                 $prix_formate = $currencyFormatter->format($prix, $devise);
    171 
    172         // Sinon on se rabat sur la librairie Intl de php
    173         } elseif (extension_loaded('intl')) {
    174                 $formatter = new NumberFormatter( $locale, NumberFormatter::CURRENCY );
    175                 $prix_formate = numfmt_format_currency($formatter, $prix, $devise);
    176 
    177         // Sinon, on fait le minimum syndical
    178         } else {
    179                 $prix_formate = str_replace('.', ',', $prix) . '&nbsp;' . $devise;
    180217        }
    181218
     
    186223 * Liste les devises et les informations associées
    187224 *
     225 * @uses prix_devise_info()
     226 *
    188227 * @return Array
    189  *     Tableau associatif avec les codes alphabétiques en clés, et des sous-tableaux :
    190  *     - nom : nom de la devise
    191  *     - code : code alphabétique (répété au cas où)
    192  *     - code_num : code numérique
    193  *     - symbole : symbole associé
    194  *     - fraction : fraction pour passer à l'unité inférieure (centimes et cie)
    195  *     - langue : code de langue utilisée
     228 *     Tableau associatif avec les codes alphabétiques en clés et les infos en sous-tableaux
    196229 */
    197230function prix_lister_devises() {
     
    200233        $devises = array();
    201234
    202         // Définitions des devises depuis resources/currency.
    203235        $currencyRepository = new CommerceGuys\Intl\Currency\CurrencyRepository;
    204236        $codes_devises = $currencyRepository->getList();
    205237
    206         // On veut la langue de l'utilisateur pour les noms
    207         $langue_spip = $GLOBALS['spip_lang'];
    208         $locale = substr($langue_spip, strpos($langue_spip, '_'));
    209 
    210238        foreach ($codes_devises as $code => $nom) {
    211                 $devise = $currencyRepository->get($code, $locale);
    212                 $devises[$code] = array(
    213                         'code'     => $code, // ça ne mange pas de pain de le remettre
    214                         'code_num' => $devise->getNumericCode(),
    215                         'nom'      => $devise->getName(),
    216                         'fraction' => $devise->getFractionDigits(),
    217                         'symbole'  => $devise->getSymbol(),
    218                         'locale'   => $devise->getLocale(),
    219                 );
     239                $devises[$code] = prix_devise_info($code);
    220240        }
    221241
     
    239259        $repo_locales = $languageRepository->getlist();
    240260
    241         // Prendre la langue de l'utilisateur pour les noms
     261        // Prendre la langue du visiteur pour les noms
    242262        $langue_spip = $GLOBALS['spip_lang'];
    243         $locale_utilisateur = prix_langue_vers_locale($langue_spip);
     263        $locale_visiteur = prix_langue_vers_locale($langue_spip);
    244264
    245265        foreach ($repo_locales as $locale => $nom) {
    246                 $language = $languageRepository->get($locale, $locale_utilisateur);
     266                $language = $languageRepository->get($locale, $locale_visiteur);
    247267                $langues[$locale] = $language->getName();
    248268        }
    249269
    250270        return $langues;
     271}
     272
     273/**
     274 * Renvoie une ou toutes les infos sur une devise
     275 *
     276 * @param string $code
     277 *    Code alphabétique à 3 lettres de la devise
     278 * @param string $info
     279 *    Info précise éventuelle :
     280 *    - nom : nom de la devise
     281 *    - code : code alphabétique (remis au cas où)
     282 *    - code_num : code numérique
     283 *    - symbole : symbole associé
     284 *    - fraction : fraction pour passer à l'unité inférieure (centimes et cie)
     285 *    - langue : code de langue utilisée
     286 * @return string|array
     287 */
     288function prix_devise_info($code, $info = '') {
     289        prix_loader();
     290
     291        // Langue du visiteur pour les noms
     292        $langue_spip = $GLOBALS['spip_lang'];
     293        $locale_visiteur = prix_langue_vers_locale($langue_spip);
     294
     295        $currencyRepository = new CommerceGuys\Intl\Currency\CurrencyRepository;
     296        $devise = $currencyRepository->get($code, $locale_visiteur);
     297        $infos = array(
     298                'code'     => $code,
     299                'code_num' => $devise->getNumericCode(),
     300                'nom'      => $devise->getName(),
     301                'fraction' => $devise->getFractionDigits(),
     302                'symbole'  => $devise->getSymbol(),
     303                'locale'   => $devise->getLocale(),
     304        );
     305
     306        $retour = (isset($infos[$info]) ? $infos[$info] : $infos);
     307
     308        return $retour;
    251309}
    252310
     
    287345        $locales_config = lire_config('prix/locales', array());
    288346
    289         // Normalement l'admin a configuré la locale correspondante à chaque code langue de spip
    290         if (!empty($locales_config[$langue_spip])) {
    291                 $locale = $locales_config[$langue_spip];
    292         // Sinon tant pis, on donne juste le code pays tiré du code langue de spip
    293         } else {
    294                 $locale = prix_langue_vers_locale($langue_spip);
    295         }
     347        // Normalement l'admin a configuré la locale correspondante à chaque code langue de spip.
     348        // Sinon tant pis, on donne juste le code pays tiré du code langue de spip.
     349        $locale = $locales_config[$langue_spip] ?: prix_langue_vers_locale($langue_spip);
    296350
    297351        return $locale;
     
    299353
    300354/**
    301  * Donne la locale correspondante un code langue de SPIP pour le formatage des prix.
    302  *
    303  * L'objectif est d'obtenir une locale qui fait partie de la liste prise en charge par Intl
    304  *
    305  * On extrait le code pays afin d'obtenir la locale "générale" (norme ISO 639).
    306  * Il s'agit des 2 à 3 lettres précédentes l'underscore : fr_tu → fr.
     355 * Retourne une locale reconnue par Intl.
     356 *
     357 * Si c'est un code langue de spip, on ne garde que le code du pays (norme ISO 639).
    307358 *
    308359 * @see https://github.com/commerceguys/intl/blob/master/src/Language/LanguageRepository.php#L46
    309360 * @see https://blog.smellup.net/106
    310361 *
    311  * @param string $langue_spip
     362 * @param string $code_langue
    312363 * @return string
    313364 */
    314 function prix_langue_vers_locale($langue_spip) {
    315 
    316         // Extraire le code pays pour avoir la locale "générale" : fr_tu → fr
    317         $locale = strtolower(strtok($langue_spip, '_'));
    318 
    319         // Exceptions : certains codes pays des langues de spip ne font pas partie de la liste des locales.
    320         // On fait une correspondance manuellement en prenant la locale la plus proche.
    321         // (ça n'indique pas que ce sont des langues identiques, mais suffisamment proches pour le formatage des prix)
    322         $exceptions = array(
    323                 'oc'  => 'fr', // occitan
    324                 'ay'  => 'ayr', // aymara
    325                 'co'  => 'fr', // corse
    326                 'cpf' => 'fr', // créole et pidgins (rcf)
    327                 'fon' => '', // fongbè
    328                 'roa' => 'pdc', // langues romanes
     365function prix_langue_vers_locale($code_langue) {
     366
     367        include_spip('inc/config');
     368        $locale = $code_langue;
     369        $is_langue_spip = in_array($code_langue, explode(',', lire_config('langues_proposees')));
     370
     371        if ($is_langue_spip) {
     372                // Extraire le code pays pour avoir la locale "générale" : fr_tu → fr
     373                $locale = strtolower(strtok($code_langue, '_'));
     374
     375                // Exceptions : certains codes pays des langues de spip ne font pas partie de la liste des locales.
     376                // On fait une correspondance manuellement en prenant la locale la plus proche.
     377                // (ça n'indique pas que ce sont des langues identiques, mais suffisamment proches pour le formatage des prix)
     378                $exceptions = array(
     379                        'oc'  => 'fr', // occitan
     380                        'ay'  => 'ayr', // aymara
     381                        'co'  => 'fr', // corse
     382                        'cpf' => 'fr', // créole et pidgins (rcf)
     383                        'fon' => '', // fongbè
     384                        'roa' => 'pdc', // langues romanes
     385                );
     386                if (!empty($exceptions[$locale])) {
     387                        $locale = $exceptions[$locale];
     388                }
     389        }
     390
     391        return $locale;
     392}
     393
     394/**
     395 * Fonction privée pour filtrer le tableau d'options du formatter
     396 *
     397 * Retire les options inconnues et typecaste les valeurs pour éviter les exceptions invalid argument.
     398 *
     399 * Le tableau d'options peut être issu d'un squelette,
     400 * et dans ce cas par défaut les valeurs sont des chaînes de texte à défaut de |filtre ou de #EVAL.
     401 *
     402 * @param array $valeurs
     403 * @return array
     404 */
     405function prix_filtrer_options_formater($options) {
     406        $options_valides = array(
     407                'locale',
     408                'style',
     409                'use_grouping',
     410                'rounding_mode',
     411                'minimum_fraction_digits',
     412                'maximum_fraction_digits',
     413                'currency_display',
    329414        );
    330         if (!empty($exceptions[$locale])) {
    331                 $locale = $exceptions[$locale];
    332         }
    333 
    334         return $locale;
     415        foreach ($options as $k => $v) {
     416                // option inconnue, chaine vide ou null : on retire la valeur
     417                if (!in_array($k, $options_valides) or is_null($v) or $v == '') {
     418                        unset($options[$k]);
     419                        // nombre flottant / entier
     420                } elseif (is_numeric($v)) {
     421                        if (intval($v) == $v) {
     422                                $options[$k] = intval($v);
     423                        } else {
     424                                $options[$k] = floatval($v);
     425                        }
     426                // booléens
     427                } elseif (in_array($v, array('true', 'oui'))) {
     428                        $options[$k] = true;
     429                } elseif (in_array($v, array('false', 'non'))) {
     430                        $options[$k] = false;
     431                }
     432        }
     433        return $options;
     434}
     435
     436/**
     437 * Fonction privée pour changer les alias dans le tableau d'option du formatter
     438 *
     439 * @param array $options
     440 * @return array
     441 */
     442function prix_alias_options_formater($options) {
     443        $options_alias = array(
     444                'currency' => 'devise',
     445                'float'    => 'flottant',
     446        );
     447        foreach ($options as $k => $v) {
     448                foreach ($options_alias as $option => $alias) {
     449                        if ($k == $alias) {
     450                                $options[$option] = $v;
     451                                unset($options[$k]);
     452                                break;
     453                        }
     454                }
     455        }
     456        return $options;
    335457}
    336458
Note: See TracChangeset for help on using the changeset viewer.