IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage PHP Discussion :

Votre avis critique sur cette classe 'Languages'


Sujet :

Langage PHP

  1. #1
    Membre éclairé
    Homme Profil pro
    Ingénieur en électrotechnique retraité
    Inscrit en
    Décembre 2008
    Messages
    1 617
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 72
    Localisation : France, Bas Rhin (Alsace)

    Informations professionnelles :
    Activité : Ingénieur en électrotechnique retraité

    Informations forums :
    Inscription : Décembre 2008
    Messages : 1 617
    Points : 823
    Points
    823
    Par défaut Votre avis critique sur cette classe 'Languages'
    Bonjour à tous,
    J'aimerais recueillir vos avis critiques sur cette classe.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    <?php
     
    /* This class checks available languages of browser
     * and handle choice of preferred language
     * 
     * Version 2.0
     * Author Marc Paris
     * Language identifiers are according to ISO 639-1 standard
    */
     
     
    namespace Languages;
     
    class Languages
    {
     
    	/*
    	 string language selected language
    	 */
    	private $language;
     
    	/*
    	 string allowedLanguages
    	  (languages which are available on the website)
    	 */
    	private $allowedLanguages;
     
     
    	/*
    	 __construct save language in cookie
    	 if days is <0 cookie is deleted
    	 if days = 0 previous cookie is hold
    	 if days >0 cookie is overwrite
    	 */
    	public function __construct(int $days=0, $lang='')
    	{
    		if (empty($lang))
    			$lang = $this->setLanguage($this->getLanguages()[0][0]);
     
    		if (isset($_COOKIE['language']) && $days <0)
    		{
    			unset($_COOKIE['language']);
    		}
    		if (isset($_COOKIE['language']) && $days == 0)
    		{
    			if (strlen($_COOKIE['language']) == 2)
    				$this->setLanguage($_COOKIE['language']);
    			else
    				throw new \Exception(sprintf("Cookie language '%s' is unknown in %", $_COOKIE['language'], __METHOD__));
    		}
    		if ($days > 0)
    		{
    			setcookie('language', $lang, $days*24*3600);
    			$this->setLanguage($lang);
    		}
     
    	}
     
    	/*
    	 getLanguages search all available browser languages
    	 return userLanguages
    	 */
    	public static function getLanguages()
    	{
    		//check to see if browser languages are setted
    		if ( isset( $_SERVER["HTTP_ACCEPT_LANGUAGE"] ) )
    		{
    			$languages = strtolower( $_SERVER["HTTP_ACCEPT_LANGUAGE"] );
    			// need to remove spaces from strings to avoid error
    			$languages = str_replace( ' ', '', $languages );
    			$languages = explode( ",", $languages );
     
    			foreach ( $languages as $language )
    			{
    				// pull out the language, place languages into array of full and primary
    				// string structure:
    				$temp_array = [];
    				// slice out the part before ; on first step, the part before - on second, place into array
    				$temp_array[0] = substr( $language, 0, strcspn( $language, ';' ) );//full language
    				$temp_array[1] = substr( $language, 0, 2 );// cut out primary language
    				//place this array into main $userLanguages language array
    				$userLanguages[] = $temp_array;
    			}
    			unset($language);
    		}
    		// else if no language found
    		else
    		{
    			$userLanguages[0] = array( '','','','' ); //return blank array
    		}
     
    		return $userLanguages;
    	}
     
    	/*
    	 setAllowedLanguages
    	 parameter langs: languages to be allowed
    	 */
    	public function addAllowedLanguages(string|array $lang)
    	{
    		if (is_string($lang))
    			$lang = (array) $lang;
    		foreach($lang as $lng)
    		{
    			if (strlen($lng) != 2)
    				throw new \Exception(sprintf("Wrong parameter value in %s. Each item must be 2 characters long.",__METHOD__));
    		}
    		$this->allowedLanguages[] = $lang;
    	}
     
    	/*
    	 getAllowedLanguages
    	 return allowed languages
    	 */
    	public function getAllowedLanguages()
    	{
    		return $this->allowedLanguages;
    	}
     
    	/*
    	 setLanguage set selected Language
    	 parameter lang: language to be setted
    	 */
    	public function setLanguage(string $lang)
    	{
    		if (strlen($lang) != 2)
    			throw new \Exception(sprintf("Wrong parameter value in %s. Parameter must be 2 characters long.",__METHOD__));
    		$this->language = $lang;
    		if(!checkLanguage($lang))
    			return false;
    		return true;
    	}
     
    	/*
    	 checkLanguage checks if selected Language is allowed
    	 parameter lang: language to be checked
    	 */
    	public function checkLanguage(string $lang)
    	{
    		if(in_array($lang,$this->allowedLanguages))
    			return true;
    		return false;
    	}
     
    	/*
    	 getLanguage
    	 return selected language
    	 */
    	public function getLanguage()
    	{
    		return $this->language;
    	}
     
    }
     
    // make the class directly available on the global namespace
    class_alias('Languages\Languages', 'Languages', false);

  2. #2
    Membre chevronné
    Profil pro
    Inscrit en
    Mai 2006
    Messages
    721
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Mai 2006
    Messages : 721
    Points : 1 880
    Points
    1 880
    Par défaut
    Bonjour,

    En êtes-vous l'auteur ?
    Il serait intéressant de décrire en deux lignes ce qu'elle fait, ainsi on pourrait l'analyser pour déterminer si l'implémentation est conforme au but fixé.

    Pas que je suis fainéante, j'aime faire du code review mais j'aime bien connaître le contexte, et même la raison (quel est le problème que l'on veut résoudre).

  3. #3
    Membre expert
    Avatar de cavo789
    Homme Profil pro
    Développeur Web
    Inscrit en
    Mai 2004
    Messages
    1 793
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Belgique

    Informations professionnelles :
    Activité : Développeur Web

    Informations forums :
    Inscription : Mai 2004
    Messages : 1 793
    Points : 3 064
    Points
    3 064
    Par défaut
    Je suis fainéant itou et ce code, au niveau de sa syntaxe (et j'ai juste regardé ça) est vieux.

    Sous php 8 on peut faire nettement mieux (clean code). Donc, mon avis : il s'agit d'un vieux code abandonné et qui donnera des soucis très vite.

  4. #4
    Membre éclairé
    Homme Profil pro
    Ingénieur en électrotechnique retraité
    Inscrit en
    Décembre 2008
    Messages
    1 617
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 72
    Localisation : France, Bas Rhin (Alsace)

    Informations professionnelles :
    Activité : Ingénieur en électrotechnique retraité

    Informations forums :
    Inscription : Décembre 2008
    Messages : 1 617
    Points : 823
    Points
    823
    Par défaut
    Dans un premier temps merci pour vos remarques.
    La méthode getLanguages() provient d'un code récupéré que j'ai adapté et modifié. Le reste est de mon cru.
    Le but de cette classe est de gérer la langue à utiliser pour un site en fonction de la ou des langues utilisateur (en paramètre du navigateur), des traductions disponibles sur le site (méthodes *allowedLanguages) et d'un éventuel choix de langue par l'utilisateur. Le constructeur recherche si une langue est déjà enregistrée en cookie et la modifie éventuellement.
    J'ai essayé de mettre des explications en tête de chaque fonction lorsque cela me paraissait nécessaire.

  5. #5
    Membre expert
    Avatar de cavo789
    Homme Profil pro
    Développeur Web
    Inscrit en
    Mai 2004
    Messages
    1 793
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Belgique

    Informations professionnelles :
    Activité : Développeur Web

    Informations forums :
    Inscription : Mai 2004
    Messages : 1 793
    Points : 3 064
    Points
    3 064
    Par défaut
    Le code n'est donc pas abandonné 🤭

    Tu écris en php comme on pouvait le faire en php 5... Le langage a énormément évolué depuis et la qualité du code, aujourd'hui, est bien meilleure et robuste.

    Tu oublies les types de données, les return type, la syntaxe des if est incorrecte (il faut toujours passe à la ligne), etc. Beaucoup de choses pourraient être amélioré.

  6. #6
    Membre chevronné
    Profil pro
    Inscrit en
    Mai 2006
    Messages
    721
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Mai 2006
    Messages : 721
    Points : 1 880
    Points
    1 880
    Par défaut
    Disclaimer: je n'ai pas cherché à faire tourner ce code, donc je pars du principe qu'il fonctionne comme prévu et qu'il n'y a pas de gros bug évident.

    Commentaires en vrac, sans ordre particulier.

    Il y a très longtemps, j'ai codé un truc un peu similaire mais plus basique, qui consistait en gros à parser HTTP_ACCEPT_LANGUAGE et renvoyer un tableau associatif avec les langues et le facteur de pondération. De mémoire, c'était un regex en une ligne pour parser.
    La décision de rediriger le client, je la faisais hors de ma fonction.

    Typiquement, sur les sites que j'ai créé, une fois le langage détecté ou choisi par le visiteur, je redirige vers domain.com/en, /fr etc.
    Un cookie n'est pas forcément requis si on fait des redirections.
    Donc pour moi ce code n'est pas utile et ne m'apporte pas de flexibilité.


    Le nom de la classe est beaucoup trop générique et pas assez descriptif.
    Je l'appellerais peut-être BrowserLanguage ?

    Il y a un peu de répétition, par exemples lignes 37-44:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    if (empty($lang))
        $lang = $this->setLanguage($this->getLanguages()[0][0]);
     
    if (isset($_COOKIE['language']) && $days <0)
    {
        unset($_COOKIE['language']);
    }
    if (isset($_COOKIE['language']) && $days == 0)
    {
    ...
    On peut aisément refactorer cette section pour supprimer les ifs inutiles.


    Je pense qu'il ne faut pas abréger les noms de variables inutilement, économiser quelques lettres n'apporte rien et diminue la lisibilité:
    => appelons la variable $language tout simplement

    Il y a une utilisation abusive de unset, par exemple unset($_COOKIE['language']); et puis unset($language); ailleurs.
    Les flux de décisions sont dispersés.


    Au niveau des conventions de nommage, je pense que les noms de fonctions devraient être plus explicites.
    Par exemple getLanguages devrait plutôt s'appeler getBrowserLanguages, car sans regarder les commentaires on n'a aucune idée a priori de ce que fait cette fonction.

    C'est d'autant plus important si on veut publier le code dans un git public, pour que quelqu'un ait envie de le forker et contribuer il vaut mieux que le code soit intuitif pour les profanes. Et évidemment, on codera de préférence en anglais, ce qui est le cas ici (et logique pour un code dont l'objectif est de gérer l'internationalisation).

    Si on reste dans la même veine, checkLanguage pourrait s'appeler isLanguageAllowed car "check language" n'est pas du tout évocateur. Sans regarder le code on ne peut pas deviner ce que ça fait. Si la fonction est nommée avec soin, le code devient plus parlant et moins confus.
    Heureusement qu'il y a des commentaires.

    De même private $language; devient: private $selectedLanguage; en respectant votre convention de nommage.

    Ce qui me dérange un peu c'est qu'on fait un setcookie dans __construct, je m'attendrais plutôt à ce que ça se fasse dans setLanguage, qui du reste est appelé dans la foulée (L53-54). En réalité, __construct fait déjà trop de choses et setLanguage n'a rien à y faire. C'est juste l'endroit où on initialise la classe et ce n'est pas un endroit où on implemente le "business logic".
    Pire, cette fonction y apparaît non pas une fois mais trois.

    La fonction addAllowedLanguages semble actuellement inutilisée, si ce code est complet. Ça ressemble à un artefact d'un dév précédent, il faut donc supprimer le "dead code" qui est une distraction, éventuellement le committer dans son git pour garder l'historique.
    getAllowedLanguages: pareil... non référencé ailleurs, donc à nettoyer.

    Je ne suis pas tellement emballée par ce code. Je pense que je chercherais d'autres implémentations, quitte à l'améliorer à ma sauce.
    Ce n'est pas une classe structurée dans les règles de l'art, mais je pense aussi que ça a été codé avant de mettre les idées à plat.

    Comme quoi, même dans un code succint on peut trouver beaucoup de choses à commenter

  7. #7
    Membre chevronné
    Profil pro
    Inscrit en
    Mai 2006
    Messages
    721
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Mai 2006
    Messages : 721
    Points : 1 880
    Points
    1 880
    Par défaut
    Ah encore un truc que j'ai oublié de mentionner: la fonction Locale::acceptFromHttp pourrait peut-être être utilisée ici.

  8. #8
    Membre éclairé
    Homme Profil pro
    Ingénieur en électrotechnique retraité
    Inscrit en
    Décembre 2008
    Messages
    1 617
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 72
    Localisation : France, Bas Rhin (Alsace)

    Informations professionnelles :
    Activité : Ingénieur en électrotechnique retraité

    Informations forums :
    Inscription : Décembre 2008
    Messages : 1 617
    Points : 823
    Points
    823
    Par défaut
    @binarygirl:
    Merci pour tous ces conseils et ces remarques détaillés. J'ai en particulier apprécié les conseils sur le nommage. Je réécris le tout et reviens ici si nécessaire.
    Pour l'instant, je considère le sujet comme résolu.

  9. #9
    Membre éclairé
    Homme Profil pro
    Ingénieur en électrotechnique retraité
    Inscrit en
    Décembre 2008
    Messages
    1 617
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 72
    Localisation : France, Bas Rhin (Alsace)

    Informations professionnelles :
    Activité : Ingénieur en électrotechnique retraité

    Informations forums :
    Inscription : Décembre 2008
    Messages : 1 617
    Points : 823
    Points
    823
    Par défaut Classe revue et refondue
    @binarygirl
    J'ai complètement revu mon code en fonction de tes remarques. Bien que sans lien direct avec le reste, j'ai volontairement laissé la fonction handleCookieLanguage(int $days) ici car je trouve une certaine cohérence d'utilisation avec le reste.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    namespace Languages;
     
    class Languages
    {
     
    	const LANGUAGE_LENGTH_WARNING = "All languages must be exactly 2 characters long in %s.";
     
    	private $availableTranslations;
     
    	private $defaultTranslation;
     
    	private $selectedLanguage;
     
    	public function __construct(array $availableTranslations, string $defaultTranslation='en')
    	{
    		if (strlen($defaultTranslation) !=2)
    			throw new \Exception(sprinf(self::LANGUAGE_LENGTH_WARNING, __METHOD__));
    		foreach($availableTranslations as $value)
    		{
    			if(strlen($value) !=2)
    				throw new \Exception(sprinf(self::LANGUAGE_LENGTH_WARNING, __METHOD__));
    		}
    		$this->availableTranslations	= $availableTranslations;
    		$this->defaultTranslation	= $defaultTranslation;
    		$this->isUserLanguageAvailable();
    	}
     
    	public function setUserLanguage()
    	{
    		// First priority: User choose a new Language
    		if (isset($_GET['language']))
    			$this->selectedLanguage = $_GET['language'];
    		// 2nd priority: Language were already selected
    		elseif (isset($_SESSION['language']))
    			$this->selectedLanguage = $_SESSION['language'];
    		// 3rd priority: A language were already selected in a previous session
    		elseif (isset($_COOKIE['language']))
    			$this->selectedLanguage = $_COOKIE['language'];
    		else
    		{
    			$this->selectedLanguage = $this->getMainBrowserLanguage();
    			$this->selectedLanguage = $this->isUserLanguageAvailable();
    		}
    		return $this->selectedLanguage;
    	}
     
    	public function getMainBrowserLanguage()
    	{
    		return \Locale::acceptFromHttp($_SERVER['HTTP_ACCEPT_LANGUAGE']);
    	}
     
    	public function isUserLanguageAvailable()
    	{
    		if(!in_array($this->selectedLanguage,$this->availableTranslations))
    			$this->selectedLanguage = $this->defaultTranslation;
    		return $this->selectedLanguage;
    	}
     
    	/*
    	 handleCookieLanguage save selected language in cookie
    	 if days is <0 cookie is deleted
    	 if days = 0 no action is done
    	 if days >0 cookie is overwrite
    	 */
    	public function handleCookieLanguage(int $days)
    	{
    		$holdTime = $days*24*3600;
    		if ($days <0)
    			unset($_COOKIE['language']);
    		elseif ($days > 0)
    			setcookie('language', $this->setUserLanguage(), time()+$holdTime);
    	}
    }
     
    // make the class directly available on the global namespace
    class_alias('Languages\Languages', 'Languages', false);

  10. #10
    Membre chevronné
    Profil pro
    Inscrit en
    Mai 2006
    Messages
    721
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Mai 2006
    Messages : 721
    Points : 1 880
    Points
    1 880
    Par défaut
    Je n'ai pas essayé de faire tourner ce code, donc c'est juste une première impression superficielle.

    Concernant les ifs, je pense que ce serait bien d'harmoniser la notation.
    Personnellement je préfère utiliser les brackets systématiquement, comme ça les branchements sont bien délimités. Je dois dire que cette notion me perturbe et je la trouve moins lisible.

    Au niveau du code, il y a des choses qui ne vont pas:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    	public function setUserLanguage()
    	{
    		// First priority: User choose a new Language
    		if (isset($_GET['language']))
    			$this->selectedLanguage = $_GET['language'];
    		// 2nd priority: Language were already selected
    		elseif (isset($_SESSION['language']))
    			$this->selectedLanguage = $_SESSION['language'];
    		// 3rd priority: A language were already selected in a previous session
    		elseif (isset($_COOKIE['language']))
    			$this->selectedLanguage = $_COOKIE['language'];
    Pourquoi utiliser une session ? Le cookie est suffisant.
    De toute façon, sans cookie pas de session, à moins d'utiliser une implémentation qui génère des session ID dans les URLs.
    Du reste, je ne vois pas dans ce code où la session serait créée pour enregistrer ce paramètre.

    Quant à la fonction handleCookieLanguage, je ne comprends pas pourquoi il faut la laisser si elle n'est pas utilisée. A la limite un coup de git stash, comme ça on peut récupérer ce bout de code plus tard.

    Pour ce qui est de isUserLanguageAvailable, je m'attendrais "logiquement" à ce que la fonction renvoie un boolean. Quand une fonction commence par "is" on s'attend instinctivement à ce que ça renvoie True ou False après avoir évalué une condition donnée.

    En fait, ce serait bien de monter un exemple de page qui utilise cette routine. Je ne sais pas si ça a été fait.
    Avoir un exemple d'utilisation concrète permettrait de mieux éprouver ce code. Mais si le code n'est pas fonctionnel, je pense que c'est trop tôt pour commenter son fonctionnement.

    Et ce serait bien d'avoir un bloc de commentaires au début pour expliquer comment la fonction marche et comment l'utiliser.

  11. #11
    Membre expert
    Avatar de cavo789
    Homme Profil pro
    Développeur Web
    Inscrit en
    Mai 2004
    Messages
    1 793
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Belgique

    Informations professionnelles :
    Activité : Développeur Web

    Informations forums :
    Inscription : Mai 2004
    Messages : 1 793
    Points : 3 064
    Points
    3 064
    Par défaut
    En matière de clean code, voici une excellente référence https://github.com/piotrplenik/clean-code-php. Une fonction ayant un préfixe is est en effet supposé retourner un boolean et rien d'autre.

  12. #12
    Membre éclairé
    Homme Profil pro
    Ingénieur en électrotechnique retraité
    Inscrit en
    Décembre 2008
    Messages
    1 617
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 72
    Localisation : France, Bas Rhin (Alsace)

    Informations professionnelles :
    Activité : Ingénieur en électrotechnique retraité

    Informations forums :
    Inscription : Décembre 2008
    Messages : 1 617
    Points : 823
    Points
    823
    Par défaut
    @cavo789: Merci pour cet excellent conseil. J'ai parcouru en attendant d'approfondir.

    @binarygirl: Dans la suite de ce post, j'appelle code principal, le code qui utilise la classe.

    Citation Envoyé par binarygirl Voir le message
    Concernant les ifs, je pense que ce serait bien d'harmoniser la notation.
    Personnellement je préfère utiliser les brackets systématiquement, comme ça les branchements sont bien délimités. Je dois dire que cette notion me perturbe et je la trouve moins lisible.
    Oui, c'est effectivement une bonne idée. Un autre avantage est qu'il est plus facile d'ajouter une ligne de code comme un simple var_dump().

    Citation Envoyé par binarygirl Voir le message
    Au niveau du code, il y a des choses qui ne vont pas:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    	public function setUserLanguage()
    	{
    		// First priority: User choose a new Language
    		if (isset($_GET['language']))
    			$this->selectedLanguage = $_GET['language'];
    		// 2nd priority: Language were already selected
    		elseif (isset($_SESSION['language']))
    			$this->selectedLanguage = $_SESSION['language'];
    		// 3rd priority: A language were already selected in a previous session
    		elseif (isset($_COOKIE['language']))
    			$this->selectedLanguage = $_COOKIE['language'];
    Pourquoi utiliser une session ? Le cookie est suffisant.De toute façon, sans cookie pas de session, à moins d'utiliser une implémentation qui génère des session ID dans les URLs.Du reste, je ne vois pas dans ce code où la session serait créée pour enregistrer ce paramètre.
    Du reste, je ne vois pas dans ce code où la session serait créée pour enregistrer ce paramètre.
    Parce que la session est immédiatement utilisable et qu'un session_start() est prévu dans le code d'utilisation de la classe. De plus, je souhaite laisser le code principal libre d'utiliser ou non le cookie en vue de garder en mémoire le choix d'une précédente session.

    Citation Envoyé par binarygirl Voir le message
    Quant à la fonction handleCookieLanguage, je ne comprends pas pourquoi il faut la laisser si elle n'est pas utilisée. A la limite un coup de git stash, comme ça on peut récupérer ce bout de code plus tard.
    Elle est utilisable par le code principal pour effacer ou générer le cookie. Elle n'est effectivement pas utilisée dans la classe elle-même mais il y a une unité d'intérêt pour le code principal qui peut décider ou non de conserver ou non la langue pour une prochaine session. Quant au git stash, ce serait encore une chose de plus à apprendre et j'en ai bien assez.

    Citation Envoyé par binarygirl Voir le message
    Pour ce qui est de isUserLanguageAvailable, je m'attendrais "logiquement" à ce que la fonction renvoie un boolean. Quand une fonction commence par "is" on s'attend instinctivement à ce que ça renvoie True ou False après avoir évalué une condition donnée.
    Je suis conscient du défaut de nommage mais je n'ai pas trouvé de nom judicieux pour cette fonction.

    Citation Envoyé par binarygirl Voir le message
    En fait, ce serait bien de monter un exemple de page qui utilise cette routine. Je ne sais pas si ça a été fait.
    Avoir un exemple d'utilisation concrète permettrait de mieux éprouver ce code. Mais si le code n'est pas fonctionnel, je pense que c'est trop tôt pour commenter son fonctionnement.
    Je l'ai fait et ça fonctionne comme je veux.

    Citation Envoyé par binarygirl Voir le message
    Et ce serait bien d'avoir un bloc de commentaires au début pour expliquer comment la fonction marche et comment l'utiliser.
    Voici:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <?php
     
    session_start();
    require_once("classes/moimp/SearchLang.php");
    //use SearchLang\SearchLang;				// Inutile grâce à l'alias de classe
     
    unset($_SESSION['language']);
    var_dump($_SESSION);						// empty
    $language = new SearchLang(['fr','de']);
    echo $language->setUserLanguage();			// 'fr'
    var_dump($_SESSION);						// empty
    $userLng = $_SESSION['language'] = (string) $language->setUserLanguage();
    var_dump($userLng);							// empty
    var_dump($_SESSION);						// 'language' => 'fr'
     
    $language->handleCookieLanguage(-1);
    // ou au choix:
    //$language->handleCookieLanguage(40);
     
    ?>

  13. #13
    Membre chevronné
    Profil pro
    Inscrit en
    Mai 2006
    Messages
    721
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Mai 2006
    Messages : 721
    Points : 1 880
    Points
    1 880
    Par défaut
    Citation Envoyé par moimp Voir le message
    Voici une réponse provisoire car je suis dérangé.
    J'espère ne pas vous avoir dérangé

    Citation Envoyé par moimp Voir le message
    Parce que la session est immédiatement utilisable et qu'un session_start() est prévu dans le code d'utilisation de la classe. De plus, je souhaite laisser le code principal libre d'utiliser ou non le cookie en vue de garder en mémoire le choix d'une précédente session.
    Et ce session_start il ira ou alors ? Il doit logiquement aller dans cette classe, car sinon votre classe a des dépendances externes, qui ne sont pas documentées du reste. Sans ce session_start, ce bout de code est inopérant et donc non fiable.

    Je pense qu'il y a une dispersion au niveau de la stratégie. Si vous voulez gérer les sessions, alors laissez tomber le cookie individuel: il ne sert plus à rien. De toute façon les sessions reposent sur l'utilisation d'un cookie (voire plusieurs). La seule différence, c'est que la session maintient un contexte côté serveur de la relation avec le client.

    Du coup, vous pouvez simplifier votre code.

    Et si le navigateur n'accepte pas les cookies, alors la session ne marchera pas non plus (évidemment, c'est un scénario qui doit être testé, afin de s'assurer que le code ne plante pas dans des conditions défavorables).

    Le header HTTP_ACCEPT_LANGUAGE ne sera pas forcément toujours disponible, par exemple si c'est un robot qui visite le site, alors acceptFromHttp va renvoyer null et il faut gérer ce cas. Comment le code va-t-il se comporter dans cette situation ?

    Dans la V1, au moins vous contrôliez l'existence de ce header, donc vous avez créé une régression dans la nouvelle version.

    Citation Envoyé par moimp Voir le message
    Quant au git stash, ce serait encore une chose de plus à apprendre et j'en ai bien assez.
    Je vous conseille de vous mettre à git rapidement.
    Quand on développe, git est incontournable, même quand on travaille en solo. Car ça permet d'avoir un historique (et de retomber sur ses pattes quand on fait une boulette), on peut créer des branches pour tester un feature expérimental, tout en laissant la branche principale intacte, ce qui permet de jongler entre différents scenarii.

    Et vu que vous êtes sûrement en train de triturer le code dans tous les sens, c'est d'autant plus utile

    Et on peut aussi déployer son code plus facilement sur les serveurs, soit à la mano (git pull), ou encore mieux avec Ansible, ça ne doit pas forcément aller jusqu'à la mise en place d'une chaîne CI/CD complexe avec Gitlab, Jenkins etc.

    Faire du FTP n'a plus de sens sauf pour un tout petit test rapide.

    Citation Envoyé par moimp Voir le message
    Je suis conscient du défaut de nommage mais je n'ai pas trouvé de nom judicieux pour cette fonction.
    Un dicton du métier veut que: "une fonction doit faire une seule chose et le faire bien". Donc l'art (et la difficulté) réside dans la manière de répartir les différentes tâches que doit accomplir l'application.
    Dans le cas présent, c'est relativement clair: la fonction devrait renvoyer un booléen et les décisions qui en découlent se font dans le reste du code, éventuellement par le biais d'une fonction distincte.

    PS: je recommande chaudement le lien posté par @cavo789.
    D'ailleurs je pensais justement à des interfaces, puisque votre code se base sur deux scenarii au moins: un paramètre GET ou un cookie. Il y a donc de légères différences d'implementation entre ces deux méthodes.

  14. #14
    Membre éclairé
    Homme Profil pro
    Ingénieur en électrotechnique retraité
    Inscrit en
    Décembre 2008
    Messages
    1 617
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 72
    Localisation : France, Bas Rhin (Alsace)

    Informations professionnelles :
    Activité : Ingénieur en électrotechnique retraité

    Informations forums :
    Inscription : Décembre 2008
    Messages : 1 617
    Points : 823
    Points
    823
    Par défaut
    La reprise de mon message précédent et votre réponse se sont croisés ce qui ne facilite pas le suivi. Les gens qui m'aident ne me dérangent jamais

    Pour bien comprendre la classe est sur un fichier séparé inclus dans le fichier principal comme sur l'exemple de code dans mon post précédent (après reprise et modification). Le session_start() s'y trouve. La session est fermée en fin de session (à la fermeture du navigateur) alors que le cookie est enregistré sur l'ordinateur. C'est peut-être là que j'ai un problème. La session est directement utilisable alors que le cookie ne le devient qu'après rechargement de la page. J'utilise la variable de session dans toutes les pages de mon site à la fois parce que j'ai des pages dans un répertoire de langue mais aussi pour récupérer des textes en bdd.

    Sur ma page index, je vérifie toujours que les cookies sont bien acceptés. Je fais d'ailleurs la même chose pour les scripts.

    Le header HTTP_ACCEPT_LANGUAGE ne sera pas forcément toujours disponible, par exemple si c'est un robot qui visite le site, alors acceptFromHttp va renvoyer null et il faut gérer ce cas. Comment le code va-t-il se comporter dans cette situation ?
    Excellente remarque . Je m'en occupe.

    J'ai regardé le lien proposé par @cavo789. Il contient des choses que je fais déjà mais beaucoup d'autres que je dois comprendre et prendre en compte. Les interfaces en font partie.

  15. #15
    Membre chevronné
    Profil pro
    Inscrit en
    Mai 2006
    Messages
    721
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Mai 2006
    Messages : 721
    Points : 1 880
    Points
    1 880
    Par défaut
    Citation Envoyé par moimp Voir le message
    Pour bien comprendre la classe est sur un fichier séparé inclus dans le fichier principal comme sur l'exemple de code dans mon post précédent (après reprise et modification). Le session_start() s'y trouve.
    Naturellement, la classe mérite son propre fichier. 100% d'accord.

    Donc, si j'ai bien compris: la classe est appelée en include par un fichier qu'on va appeler x.php, et c'est dans ce fichier x.php que se trouve session_start ?
    Si c'est le cas, c'est une erreur de conception, puisqu'il y a un manque de séparation.

    L'intérêt d'une classe, ou d'un module, ou même d'une simple fonction est de pouvoir être réutilisée. Elle devrait être capable de fonctionner ailleurs avec un simple include. Si elle liée à un fichier particulier pour son fonctionnement, c'est un problème.

    C'est comme si j'écrivais une fonction qui dépend d'une variable globale définie en dehors, au lieu de recevoir un argument.
    On ne peut pas directement réutiliser cette fonction ailleurs.

    J'espère avoir mal compris, ce qui est parfaitement possible.
    Bon, je vous laisse travailler et on verra le PoC plus tard.

  16. #16
    Membre éclairé
    Homme Profil pro
    Ingénieur en électrotechnique retraité
    Inscrit en
    Décembre 2008
    Messages
    1 617
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 72
    Localisation : France, Bas Rhin (Alsace)

    Informations professionnelles :
    Activité : Ingénieur en électrotechnique retraité

    Informations forums :
    Inscription : Décembre 2008
    Messages : 1 617
    Points : 823
    Points
    823
    Par défaut
    J'ai simplifié mon code en prenant comme langue par défaut, le premier item des langues acceptées disposant de traduction sur le site (argument du constructeur).
    En supprimant la recherche des langues acceptées par le navigateur.
    En tenant compte de vos remarques (en particulier sur la variable de session).
    Pourtant, je ne suis pas satisfait de la fonction setUserLanguage() que j'aurais voulu améliorer en fonction des règles du clean code préconisé par @cavo789 avec l'opérateur de coalescence.
    Je ne suis pas sûr d'avoir tout bien vérifié.
    Nouvelle version:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    namespace SearchLang;
     
    class SearchLang
    {
     
    	const LANGUAGE_LENGTH_WARNING = "All languages must be exactly 2 characters long in %s.";
     
    	/*
    	 array list of available translations on this site
    	 */
    	private array $availableTranslations;
     
    	/*
    	 2 characters string
    	 contains the default translation code to use
    	 */
    	private string $defaultTranslation;
     
    	/*
    	 2 characters string
    	 selected language
    	 */
    	private string $selectedLanguage;
     
     
    	public function __construct(array $availableTranslations)
    	{
    		foreach($availableTranslations as $value)
    		{
    			if(strlen($value) !=2)
    			{
    				throw new \Exception(sprinf(self::LANGUAGE_LENGTH_WARNING, __METHOD__));
    			}
    		}
    		$this->availableTranslations	= $availableTranslations;
    		$this->defaultTranslation		= $availableTranslations[0];
    		$this->selectedLanguage			= $this->defaultTranslation;
    	}
     
    	/*
    	 Set the selected user language to be used under priorities
    	 */
    	public function setUserLanguage()
    	{
    		// First priority: User choose a new Language
    		if (isset($_GET['language']))
    		{
    			$this->selectedLanguage = $_GET['language'];
    		}
    		// 2nd priority: A language were already selected in a previous session
    		elseif (isset($_COOKIE['language']))
    		{
    			$this->selectedLanguage = $_COOKIE['language'];
    		}
    		else
    		{
    			if ( ! $this->isUserLanguageAvailable())
    			{
    				$this->selectedLanguage = $this->defaultTranslation;
    			}
    		}
    		return $this->selectedLanguage;
    	}
     
    	/*
    	 Check if the selected language has an available translation on the whole site
    	 depends of given translations as __construct parameter
    	 */
    	public function isUserLanguageAvailable()
    	{
    		if (in_array($this->selectedLanguage,$this->availableTranslations))
    		{
    			return true;
    		}
    		return false;
    	}
     
    	/*
    	 handleCookieLanguage save selected language in cookie
    	 if days is <0 cookie is deleted
    	 if days = 0 no action is done
    	 if days >0 cookie is overwrite
    	 */
    	public function handleCookieLanguage(int $days)
    	{
    		$holdTime = $days*24*3600;
    		if ($days <0)
    			unset($_COOKIE['language']);
    		elseif ($days > 0)
    			setcookie('language', $this->setUserLanguage(), time()+$holdTime);
    	}
    }
     
    // make the class directly available on the global namespace
    class_alias('SearchLang\SearchLang', 'SearchLang', false);

  17. #17
    Membre chevronné
    Profil pro
    Inscrit en
    Mai 2006
    Messages
    721
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Mai 2006
    Messages : 721
    Points : 1 880
    Points
    1 880
    Par défaut
    Je sais que je suis chiante (je n'arrête jamais ) mais on peut encore simplifier la simplification

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    public function isUserLanguageAvailable()
    {
        if (in_array($this->selectedLanguage,$this->availableTranslations))
        {
            return true;
        }
        return false;
    }
    On renvoie une expression booléenne donc:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    public function isUserLanguageAvailable()
    {
        return in_array($this->selectedLanguage,$this->availableTranslations);
    }
    Du coup il faut se demander si la fonction est encore utile vu qu'elle ne contient plus qu'une ligne.
    Par ailleurs, je m'attendrais à ce qu'elle reçoive en argument la langue demandée. Cela rendrait le flux plus transparent aussi. Le fait d'avoir une variable (ou une propriété) assignée à plein d'endroits différents engendre de la confusion, et souvent des bugs.

    Dans la fonction setUserLanguage on a une série de branchements que j'ai envie de refactorer: cela peut et cela doit être plus simple.
    Ce qui est obvious c'est qu'il y a de la répétition.

    On pourrait penser à utiliser un Ternary operator.
    Mais en fait il y a lieu d'utiliser un "Null coalescing operator" (PHP >= 7.4) pour choisir la première valeur "non nulle" dans une liste d'expressions.
    Çà pourrait alors se résumer à un truc comme ça:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    $this->selectedLanguage = $_GET['language'] ?? $_COOKIE['language'] ?? $this->defaultTranslation;
    En testant éventuellement que la langue demandée est bien disponible.

    Attention: code non testé.

    Il est à noter que dans le constructeur, il y a déjà $this->selectedLanguage = $this->defaultTranslation; donc pas besoin de répéter puisqu'il y a déjà une valeur de fallback définie à l'origine.


    Et pour revenir au constructeur:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    $this->defaultTranslation		= $availableTranslations[0];
    $this->selectedLanguage			= $this->defaultTranslation;
    Supprimons les variables inutiles qui n'apportent rien et ne font que compliquer le flux (source de bugs de logique):

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    $this->selectedLanguage			= $this->$availableTranslations[0];
    Bien sûr, cela suppose que le tableau n'est pas vide et qu'il y a donc une validation a priori, sauf si on une liste hardcodée.

    Pour résumer, il y encore trop de branchements à mon avis, et évidemment il faudra tester ce code dans tous les cas de figure pour vérifier que la logique est bien implémentée.
    Il faut éviter les if/elseif/else si on peut, et surtout éviter de les imbriquer, car cela rend le flux d'exécution moins clair et on risque de créer des cas de figure non couverts => comportement imprévisible.

  18. #18
    Membre éclairé
    Homme Profil pro
    Ingénieur en électrotechnique retraité
    Inscrit en
    Décembre 2008
    Messages
    1 617
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 72
    Localisation : France, Bas Rhin (Alsace)

    Informations professionnelles :
    Activité : Ingénieur en électrotechnique retraité

    Informations forums :
    Inscription : Décembre 2008
    Messages : 1 617
    Points : 823
    Points
    823
    Par défaut
    Citation Envoyé par binarygirl Voir le message
    Je sais que je suis chiante (je n'arrête jamais ) mais on peut encore simplifier la simplification
    C'est peut-être parfois un peu agaçant mais si je veux progresser et apprendre, il est nécessaire d'avoir une critique intransigeante.

    J'ai laisser tomber la fonction isUserLanguageAvailable() puisque après modification, la traduction (la langue) par défaut est la première des langues disponibles. Sinon la réduction à une seule ligne était effectivement très judicieuse.

    Citation Envoyé par binarygirl Voir le message
    Dans la fonction setUserLanguage on a une série de branchements que j'ai envie de refactorer: cela peut et cela doit être plus simple.
    Ce qui est obvious c'est qu'il y a de la répétition.

    On pourrait penser à utiliser un Ternary operator.
    Mais en fait il y a lieu d'utiliser un "Null coalescing operator" (PHP >= 7.4) pour choisir la première valeur "non nulle" dans une liste d'expressions.
    Çà pourrait alors se résumer à un truc comme ça:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    $this->selectedLanguage = $_GET['language'] ?? $_COOKIE['language'] ?? $this->defaultTranslation;
    En testant éventuellement que la langue demandée est bien disponible.

    Attention: code non testé.
    C'est quelque chose que j'ai déjà testé (après avoir lu les recommandations clean code). Malheureusement, l'expérience n'a pas réussi le test. C'est pourquoi je suis revenu en arrière.

    Citation Envoyé par binarygirl Voir le message
    Et pour revenir au constructeur:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    $this->defaultTranslation		= $availableTranslations[0];
    $this->selectedLanguage			= $this->defaultTranslation;
    Supprimons les variables inutiles qui n'apportent rien et ne font que compliquer le flux (source de bugs de logique):

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    $this->selectedLanguage			= $this->$availableTranslations[0];
    Bien sûr, cela suppose que le tableau n'est pas vide et qu'il y a donc une validation a priori, sauf si on une liste hardcodée.
    Ici je pars du principe que l'auteur du code n'envoie en argument du constructeur que des traductions existantes. Je ne vois d'ailleurs pas comment faire autrement puisque la vérification dépend de l'architecture des sites qui utilisent la classe.

  19. #19
    Membre éclairé
    Homme Profil pro
    Ingénieur en électrotechnique retraité
    Inscrit en
    Décembre 2008
    Messages
    1 617
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 72
    Localisation : France, Bas Rhin (Alsace)

    Informations professionnelles :
    Activité : Ingénieur en électrotechnique retraité

    Informations forums :
    Inscription : Décembre 2008
    Messages : 1 617
    Points : 823
    Points
    823
    Par défaut
    J'ai tout repris et maintenant tout fonctionne avec ce code fortement réduit:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    <?php
     
    /* This class handle choice of preferred language
     * 
     * Version 2.0
     * Author moimp
     * Language identifiers are according to ISO 639-1 standard
    */
     
     
    namespace SearchLang;
     
    class SearchLang
    {
     
    	const LANGUAGE_LENGTH_WARNING = "All languages must be exactly 2 characters long in %s.";
     
    	/*
    	 array list of available translations on this site
    	 */
    	private array $availableTranslations;
     
    	/*
    	 2 characters string
    	 selected language
    	 */
    	private string $selectedLanguage;
     
     
    	public function __construct(array $availableTranslations)
    	{
    		foreach($availableTranslations as $value)
    		{
    			if(strlen($value) !=2)
    			{
    				throw new \Exception(sprinf(self::LANGUAGE_LENGTH_WARNING, __METHOD__));
    			}
    		}
    		$this->availableTranslations	= $availableTranslations;
    		$this->selectedLanguage			= $availableTranslations[0];
    	}
     
    	/*
    	 Set the selected user language to be used under priorities
    	 */
    	public function setUserLanguage()
    	{
    		$this->selectedLanguage = $_GET['language'] ?? $_COOKIE['language'] ?? $this->selectedLanguage;
    		return $this->selectedLanguage;
    	}
     
    	/*
    	 handleCookieLanguage save selected language in cookie
    	 if days is <0 cookie is deleted
    	 if days = 0 no action is done
    	 if days >0 cookie is overwrite
    	 */
    	public function handleCookieLanguage(int $days)
    	{
    		$holdTime = $days*24*3600;
    		if ($days <0)
    			unset($_COOKIE['language']);
    		elseif ($days > 0)
    			setcookie('language', $this->setUserLanguage(), time()+$holdTime);
    	}
    }
     
    // make the class directly available on the global namespace
    class_alias('SearchLang\SearchLang', 'SearchLang', false);
    et le code de test:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <?php
    require_once("classes/moimp/SearchLang.php");
     
    var_dump($_COOKIE['language']);
    $_GET['language'] = 'en';
    var_dump($_GET);
    $test = new SearchLang(['de','fr','en']);
    //$test->handleCookieLanguage(-1);
    $test->handleCookieLanguage(1);
    echo $test->setUserLanguage();
    Si tu n'as rien à ajouter, je te remercie mille fois.
    Et si tu es d'accord, je me permettrai de te soumettre d'autres codes...??

  20. #20
    Membre chevronné
    Profil pro
    Inscrit en
    Mai 2006
    Messages
    721
    Détails du profil
    Informations personnelles :
    Localisation : Belgique

    Informations forums :
    Inscription : Mai 2006
    Messages : 721
    Points : 1 880
    Points
    1 880
    Par défaut
    Volontiers, mais je fais davantage de code review en Python (à mon niveau), le PHP j'y touche plus trop depuis un moment et beaucoup ici sont donc plus calés que moi (sauf quand c'est pour tourmenter les programmeurs évidemment )
    Je ne manque pas relever les failles de sécurité aussi

+ Répondre à la discussion
Cette discussion est résolue.
Page 1 sur 2 12 DernièreDernière

Discussions similaires

  1. Votre avis/critiques sur mon CV
    Par riyahi dans le forum CV
    Réponses: 26
    Dernier message: 02/12/2008, 15h51
  2. Votre avis svp sur ce pc portable
    Par Amalsim dans le forum Ordinateurs
    Réponses: 1
    Dernier message: 14/09/2008, 15h06
  3. Besoin de votre avis svp sur mon site www.monwebcv.com
    Par italiasky dans le forum Mon site
    Réponses: 14
    Dernier message: 19/03/2007, 11h57

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo