• On a souvent besoin de connaitre le nombre d'entrées dans une table, avec certaines conditions. Par exemple le nombre d'articles par catégories dans le cas d'un blog. Faire un SELECT COUNT(*) peut vite devenir très lourd quand le nombre d'entrées devient significatif, surtout si le moteur utilisé est InnoDB.

    En effet, on peut arriver vite à de gros problèmes de performances avec des COUNT(*) s'exécutant en plusieurs secondes si vous commencez à avoir beaucoup d'entrées (1M entrées par exemple).

    Je vous propose donc une astuce pour remplacer avantageusement ces disgracieux COUNT(*). On va garder ici l'exemple des articles et des catégories, avec une table posts et une table categories de structure basique :

    CREATE TABLE `categories` (
     `id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
     `title` VARCHAR(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
     `nbposts` INT UNSIGNED NOT NULL DEFAULT '0'
     ) ENGINE = INNODB CHARACTER SET utf8 COLLATE utf8_general_ci;
     
     CREATE TABLE `posts` (
     `id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
     `category_id` INT UNSIGNED NOT NULL,
     `title` VARCHAR(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
     `content` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
     `time` INT UNSIGNED NOT NULL,
     INDEX (`category_id`),
     INDEX (`time`)
     ) ENGINE = INNODB CHARACTER SET utf8 COLLATE utf8_general_ci;
     
     ALTER TABLE `posts` ADD FOREIGN KEY (`category_id`) REFERENCES `categories` (`id`) ON DELETE CASCADE;
    [code=mysql]CREATE TABLE `categories` (
    `id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    `title` VARCHAR(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
    `nbposts` INT UNSIGNED NOT NULL DEFAULT '0'
    ) ENGINE = INNODB CHARACTER SET utf8 COLLATE utf8_general_ci;

    CREATE TABLE `posts` (
    `id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
    `category_id` INT UNSIGNED NOT NULL,
    `title` VARCHAR(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
    `content` TEXT CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
    `time` INT UNSIGNED NOT NULL,
    INDEX (`category_id`),
    INDEX (`time`)
    ) ENGINE = INNODB CHARACTER SET utf8 COLLATE utf8_general_ci;

    ALTER TABLE `posts` ADD FOREIGN KEY (`category_id`) REFERENCES `categories` (`id`) ON DELETE CASCADE;[/code]

    Le champ nbposts va nous servir à garder en mémoire le nombre d'articles dans chaque catégorie. Certains diront que du coup on peut perdre de la consistance, eh bien oui... mais il faudrait avoir un sacré coup de pas de bol ou modifier la valeur du champ à la bourrin pour fausser les valeurs.

    Pour actualiser le champ nbposts, on va utiliser des triggers ("déclencheurs" en français), qui l'incrémenteront à chaque insertion et le décrémenteront à chaque suppression :

    DELIMITER ;;
     CREATE TRIGGER `posts_insert_after` AFTER INSERT ON `posts`
     FOR EACH ROW BEGIN
     UPDATE `categories` SET `nbposts` = `nbposts`+1 WHERE id=NEW.`category_id` LIMIT 1;
     END ;;
     CREATE TRIGGER `posts_delete_after` AFTER DELETE ON `posts`
     FOR EACH ROW BEGIN
     UPDATE `categories` SET `nbposts` = `nbposts`-1 WHERE id=OLD.`category_id` LIMIT 1;
     END ;;
     DELIMITER ;
    [code=mysql]DELIMITER ;;
    CREATE TRIGGER `posts_insert_after` AFTER INSERT ON `posts`
    FOR EACH ROW BEGIN
    UPDATE `categories` SET `nbposts` = `nbposts`+1 WHERE id=NEW.`category_id` LIMIT 1;
    END ;;
    CREATE TRIGGER `posts_delete_after` AFTER DELETE ON `posts`
    FOR EACH ROW BEGIN
    UPDATE `categories` SET `nbposts` = `nbposts`-1 WHERE id=OLD.`category_id` LIMIT 1;
    END ;;
    DELIMITER ;[/code]

    Et voilà, votre champ nbposts s'actualise maintenant automatiquement, plus besoin de faire des COUNT(*) !


    5 commentaires
  • J'ai été confronté à un problème de ralentissements sur EklaBlog dûs à une requête SELECT utilisant de multiples jointures. Nous allons voir dans cet article comment optimiser cela.

    La requête en elle-même n'est pas très complexe et utilise uniquement des clés (primary et indexes), mais elle est très lente pour plusieurs raisons :

    • Jointure sur 5 tables
    • Recherche sur 4 champs chacun dans une table différente
    • Tri par ORDER BY

    Elle ressemble en gros à ceci :

    SELECT a.id, a.blog, a.category_id, a.title, a.content, a.author, a.creadt, c.url
        FROM mod_article a 
        INNER JOIN categories b ON b.id=a.category_id 
        INNER JOIN blogs c ON c.id=b.blog_id 
        INNER JOIN articles_tags at ON at.article_id=a.id
        INNER JOIN tags t ON t.id=at.tag_id
        WHERE t.name="skreo" AND a.online="1" AND b.protect="" AND c.protect="" 
        ORDER BY a.creadt DESC
        LIMIT 10
    [code=mysql]SELECT a.id, a.blog, a.category_id, a.title, a.content, a.author, a.creadt, c.url
        FROM mod_article a 
        INNER JOIN categories b ON b.id=a.category_id 
        INNER JOIN blogs c ON c.id=b.blog_id 
        INNER JOIN articles_tags at ON at.article_id=a.id
        INNER JOIN tags t ON t.id=at.tag_id
        WHERE t.name="skreo" AND a.online="1" AND b.protect="" AND c.protect="" 
        ORDER BY a.creadt DESC
        LIMIT 10[/code]

    Les tables concernées sont relativement grosses :

    • 130 000 entrées dans tags
    • 1 400 000 entrées dans articles_tags
    • 400 000 entrées dans articles
    • 70 000 entrées dans categories
    • 130 000 entrées dans tags
    • 19 000 entrées dans blogs

    D'où un temps d'exécution de la requête pouvant atteindre 30 secondes...

    Lors d'un test en local, que je répèterai dans la suite de l'article, j'obtiens une exécution en 0.24s en moyenne quand le pc ne fait rien d'autre.

     

    Solution possible

    Une solution possible que m'a indiqué mon ami Guilhem est de stocker un résultat intermédiaire de la requête dans une table de type MEMORY (en RAM) :

    CREATE TABLE tags_lookup (
        article_id int(10) unsigned NOT NULL,
        tag_id int(10) unsigned NOT NULL,
        creadt int(10) unsigned NOT NULL,
        blog_id int(10) unsigned NOT NULL,
        KEY tag_id (tag_id),
        KEY creadt (creadt),
        KEY blog_id (blog_id)
    ) ENGINE=MEMORY
    [code=mysql]CREATE TABLE tags_lookup (
        article_id int(10) unsigned NOT NULL,
        tag_id int(10) unsigned NOT NULL,
        creadt int(10) unsigned NOT NULL,
        blog_id int(10) unsigned NOT NULL,
        KEY tag_id (tag_id),
        KEY creadt (creadt),
        KEY blog_id (blog_id)
    ) ENGINE=MEMORY[/code]

    Il faut ensuite remplir la remplir :

    # On vide la table
    TRUNCATE TABLE tags_lookup;
    # Puis on la remplit entièrement
    INSERT INTO tags_lookup
        SELECT a.id AS article_id, at.tag_id, a.creadt, c.id AS blog_id
        FROM articles a, categories b, blogs c, articles_tags at
        WHERE a.online="1" AND b.protect="" AND c.protect=""
        AND b.id=a.category_id AND c.id=a.blog_id AND at.article_id=a.id;
    [code=mysql]# On vide la table
    TRUNCATE TABLE tags_lookup;
    # Puis on la remplit entièrement
    INSERT INTO tags_lookup
        SELECT a.id AS article_id, at.tag_id, a.creadt, c.id AS blog_id
        FROM articles a, categories b, blogs c, articles_tags at
        WHERE a.online="1" AND b.protect="" AND c.protect=""
        AND b.id=a.category_id AND c.id=a.blog_id AND at.article_id=a.id;[/code]

    Il faudra exécuter les requêtes ci-dessus régulièrement pour mettre à jour le contenu de la table, à l'aide d'un bash par exemple.

    Pour obtenir les informations qu'on voulait au début, on peut alors exécuter cette requête :

    SELECT a.id, a.blog, a.category_id, a.title, a.content, a.author, a.creadt, c.url
        FROM tags t
        INNER JOIN tags_lookup s ON s.tag_id=t.id
        INNER JOIN articles a ON a.id=s.article_id
        INNER JOIN categories b ON b.id=a.category_id 
        INNER JOIN blogs c ON c.id=a.blog _id
        WHERE t.name="skreo"
        ORDER BY s.creadt DESC
        LIMIT 10
    [code=mysql]SELECT a.id, a.blog, a.category_id, a.title, a.content, a.author, a.creadt, c.url
        FROM tags t
        INNER JOIN tags_lookup s ON s.tag_id=t.id
        INNER JOIN articles a ON a.id=s.article_id
        INNER JOIN categories b ON b.id=a.category_id 
        INNER JOIN blogs c ON c.id=a.blog _id
        WHERE t.name="skreo"
        ORDER BY s.creadt DESC
        LIMIT 10[/code]

    Lors de mon test, j'obtiens cette fois-ci 0.15s d'exécution en moyenne, ce qui est déjà mieux.

    Mais le gros avantage d'une telle structure, c'est qu'on va pouvoir faire une recherche beaucoup plus facile à comprendre pour le serveur MySQL, grâce à des requêtes imbriquées :

    SELECT a.id, a.blog, a.category_id, a.title, a.content, a.author, a.creadt, c.url
        FROM (
            SELECT article_id
            FROM tags_lookup
            WHERE tag_id=(
                SELECT id
                FROM tags
                WHERE name="skreo"
                LIMIT 1
            )
            ORDER BY creadt DESC
            LIMIT 10
        ) s
        INNER JOIN articles a ON a.id=s.article_id
        INNER JOIN categories b ON b.id=a.category_id
        INNER JOIN blogs c ON c.id=a.blog_id
        ORDER BY a.creadt DESC
        LIMIT 10;
    [code=mysql]SELECT a.id, a.blog, a.category_id, a.title, a.content, a.author, a.creadt, c.url
        FROM (
            SELECT article_id
            FROM tags_lookup
            WHERE tag_id=(
                SELECT id
                FROM tags
                WHERE name="skreo"
                LIMIT 1
            )
            ORDER BY creadt DESC
            LIMIT 10
        ) s
        INNER JOIN articles a ON a.id=s.article_id
        INNER JOIN categories b ON b.id=a.category_id
        INNER JOIN blogs c ON c.id=a.blog_id
        ORDER BY a.creadt DESC
        LIMIT 10;[/code]

    Toujours avec la même base de données, j'obtiens cette fois-ci une exécution presque instantanée : entre 0 et 0.02 secondes !

     

    Conclusion

    En mettant en place une table en mémoire vive rassemblant des données correspondant à une partie du traitement d'une grosse requête, on peut donc éviter des jointures très lourdes.

    Cette méthode a bien évidemment comme inconvénient d'avoir des données pas toujours ultra fraiches en mémoire, mais ça peut être un bon compromis quand on voit la rapidité des requêtes qui en résultent.

    Vous avez d'autres solutions intéressantes ? une meilleure idée ?


    9 commentaires
  • [Sécurité PHP] Toujours vérifier le Referer lors d'un envoi par POST

    © Photo de divarvel

    C'est un aspect très souvent négligé de la sécurité des sites web, et qui est pourtant critique.

    Par exemple, si Facebook n'avait pas prévu ce genre de sécurité, nous pourrions partir du postulat qu'il y a une chance non négligeable qu'un visiteur soit connecté à Facebook (par session ou cookies). Prenons alors une page web visitée par quelques dizaines ou centaines de visiteurs par jour, un blog par exemple. Sur cette page, nous allons mettre un formulaire identique à celui permettant de modifier le profil sur Facebook. Une ligne de javascript suffira à envoyer le formulaire ( $("id_form").submit() ), avec tout plein de grossièretés qui viendront égayer le profil de l'utilisateur

    C'est de cette manière qu'Olivier Duffez s'est fait pirater son compte Gmail puis le nom de domaine Webrankinfo.com ! En effet, le pirate s'est débrouillé pour que M. Duffez se retrouve sur une page envoyant un formulaire vers la page de création de filtre de Gmail. Et comme une session Gmail était ouverte sur l'ordinateur de M. Duffez, le formulaire en question a pu créer un filtre qui transférait tous les e-mails reçus au spammeur. Le spammeur a ensuite utilisé le formulaire "Mot de passe oublié" du registrar chez lequel est enregistré webrankinfo.com, et a donc reçu l'e-mail permettant de modifier le mot de passe.

    Alors ? N'est-ce pas critique comme faille ?
    Ce type d'attaque est appelé Cross-Site Request Forgery, et n'est pas très connu en raison d'un manque de communication à ce propos.
    Voyons comment l'étudier concrètement, et comment éviter ce genre de déboires.

     

    Pour vous montrer un exemple concret, créons une page affichant les données reçues par POST :
    http://data0.eklablog.com/skreo/perso/test_referer.php

    Code de la page :

    <pre>
    Données reçues par POST :
    $_POST = <?php
    print_r($_POST);
    ?>
    </pre>
    [code=php]<pre>
    Données reçues par POST :
    $_POST = <?php
    print_r($_POST);
    ?>
    </pre>[/code]

     

    Ensuite, sur un nom de domaine différent (ici skreo.net), créons un petit formulaire qui envoie des données à cette page :

    <form action="http://data0.eklablog.com/skreo/perso/test_referer.php" method="post" id="form_test_referer">
        Variable "var" : <input type="text" value="" name="var" /><br />
        <input type="submit" value="Envoyer" />
    </form>
    [code=html]<form action="http://data0.eklablog.com/skreo/perso/test_referer.php" method="post" id="form_test_referer">
        Variable "var" : <input type="text" value="" name="var" /><br />
        <input type="submit" value="Envoyer" />
    </form>[/code]

    Ce qui donne ceci :

    Variable "var" :

    Vous pouvez tester ce formulaire, vous voyez bien qu'on peut envoyer n'importe quelles données par POST vers un autre domaine. Un petit bout de code Javascript à la suite du formulaire permet d'envoyer le formulaire immédiatement sans même demander l'avis du visiteur :

    <script type="text/javascript">
    document.getElementById("form_test_referer").submit();
    </script>
    [code=html]<script type="text/javascript">
    document.getElementById("form_test_referer").submit();
    </script>[/code]

     

    Voyons maintenant comment empêcher dans la majorité des cas ce genre de hacks, avec l'aide du Referer. Je dis bien "dans la majorité des cas", car parfois le visiteur règle son navigateur internet pour que le referer soit vide.

    Nous allons donc comparer le Referer avec le nom de domaine de la page courante, et vider la variable $_POST s'il est différent. Reprenons notre fichier test_referer.php et nommons le test_referer2.php :

    <?php

    // Vérification du Referer pour les variables passées en POST
     if(isset($_SERVER['HTTP_REFERER'])
      && $_SERVER['HTTP_REFERER']!=''
      && substr($_SERVER['HTTP_REFERER'], 7, strlen($_SERVER['SERVER_NAME'])) != $_SERVER['SERVER_NAME']){
         $_POST = array();
     }

    ?>
    <pre>
    Données reçues par POST :
    $_POST = <?php
    print_r($_POST);
    ?>
    </pre>
    [code=php]<?php

    // Vérification du Referer pour les variables passées en POST
    if(isset($_SERVER['HTTP_REFERER'])
      && $_SERVER['HTTP_REFERER']!=''
      && substr($_SERVER['HTTP_REFERER'], 7, strlen($_SERVER['SERVER_NAME'])) != $_SERVER['SERVER_NAME']){
        $_POST = array();
    }

    ?>
    <pre>
    Données reçues par POST :
    $_POST = <?php
    print_r($_POST);
    ?>
    </pre>[/code]

    Reprenons notre formulaire pour tester, mais cette fois vers test_referer2.php :

    Variable "var" :

     

    Et le tour est joué !
    La page accepte toujours les envois de données par POST provenant du même nom de domaine, mais n'accepte pas les autres !

    Si vous avez un site web, je vous conseille très fortement de mettre en place cette sécurité, si ce n'est pas déjà le cas


    20 commentaires
  • Je me suis souvenu d'un article très intéressant sur lequel j'étais tombé il y a un ou deux mois, présentant une classe javascript permettant de détecter quels réseaux sociaux utilisent les visiteurs. En regardant le code de plus prêt, je l'ai trouvé compliqué pour pas grand chose, avec l'utilisation d'une iframe et un code pas très optimisé.

    Voici donc un code que je viens de pondre, parce que je pense qu'il pourra être utile, aussi bien pour vous que pour moi  J'utilise ici le framework Mootools 1.2 , mais je pense que c'est facilement adaptable à d'autres. Le principe est simple, on utilise une "faille" du CSS qui consiste à récupérer le style de liens après avoir appliqué un style particulier aux liens visités ("a:visited").

    Une petite démo pour commencer : checkvisited.html

    On va donc commencer par faire un peu de CSS :

    a.test_visited { display: block; }
    a.test_visited:visited { display: none; }
    [code=css]a.test_visited { display: block; }
    a.test_visited:visited { display: none; }[/code]

    C'est tout pour le CSS !
    Maintenant, le Javascript :

    // Implémentation de la fonction checkVisited
    // pour les chaînes de caractères (String)
    String.implement({
        checkVisited : function(){
            var e = (new Element("a", {
                    href: this,
                    "class" : "test_visited"
                }))
                .inject(document.body),
                b = e.getStyle("display")=="none";
            e.destroy();
            return b;
        }
    });

    // Implémentation de la fonction checkVisited pour
    // les tableaux (Array) : tester une liste d'urls
    Array.implement({
        checkVisited : function(){
            var b = false;
            this.each(function(s){
                if(s.checkVisited())
                    b = true;
            })
            return b;
        }
    });
    [code=javascript]// Implémentation de la fonction checkVisited
    // pour les chaînes de caractères (String)
    String.implement({
        checkVisited : function(){
            var e = (new Element("a", {
                    href: this,
                    "class" : "test_visited"
                }))
                .inject(document.body),
                b = e.getStyle("display")=="none";
            e.destroy();
            return b;
        }
    });

    // Implémentation de la fonction checkVisited pour
    // les tableaux (Array) : tester une liste d'urls
    Array.implement({
        checkVisited : function(){
            var b = false;
            this.each(function(s){
                if(s.checkVisited())
                    b = true;
            })
            return b;
        }
    });[/code]

    Ces deux implémentations permettent de vérifier une URL ou une liste d'URLs.

    On va reprendre l'exemple des réseaux sociaux que traite Aza Raskin dans son article. D'abord, on crée un conteneur pour l'affichage du résultat :

    <div id="visited_sites">
    </div>
    [code=html]<div id="visited_sites">
    </div>[/code]

    Ensuite, on crée la liste et on la traite :

    // Liste de réseaux sociaux
    var sites = {
        "Digg": ["http://digg.com", "http://digg.com/login"],
        "Reddit": ["http://reddit.com", "http://reddit.com/new/", "http://reddit.com/controversial/", "http://reddit.com/top/", "http://reddit.com/r/reddit.com/", "http://reddit.com/r/programming/"],
        "StumbleUpon": ["http://stumbleupon.com"],
        "Yahoo Buzz": ["http://buzz.yahoo.com"],
        "Facebook": ["http://facebook.com/home.php", "http://facebook.com", "https://login.facebook.com/login.php"],
        "Del.icio.us": ["https://secure.del.icio.us/login", "http://del.icio.us/", "http://delicious.com"],
        "MySpace": ["http://www.myspace.com/"],
        "Technorati": ["http://www.technorati.com"],
        "Newsvine": ["https://www.newsvine.com", "https://www.newsvine.com/_tools/user/login"],
        "Songza": ["http://songza.com"],
        "Slashdot": ["http://slashdot.org/"],
        "Ma.gnolia": ["http://ma.gnolia.com/"],
        "Blinklist": ["http://www.blinklist.com"],
        "Furl": ["http://furl.net", "http://furl.net/members/login"],
        "Mister Wong": ["http://www.mister-wong.com"],
        "Current": ["http://current.com", "http://current.com/login.html"],
        "Menaeme": ["http://meneame.net", "http://meneame.net/login.php"],
        "Oknotizie": ["http://oknotizie.alice.it", "http://oknotizie.alice.it/login.html.php"],
        "Diigo": ["http://www.diigo.com/", "https://secure.diigo.com/sign-in"],
        "Funp": ["http://funp.com", "http://funp.com/account/loginpage.php"],
        "Blogmarks": ["http://blogmarks.net"],
        "Yahoo Bookmarks": ["http://bookmarks.yahoo.com"],
        "Xanga": ["http://xanga.com"],
        "Blogger": ["http://blogger.com"],
        "Last.fm": ["http://www.last.fm/", "https://www.last.fm/login/"],
        "N4G": ["http://www.n4g.com"],
        "Faves": ["http://faves.com", "http://faves.com/home", "https://secure.faves.com/signIn"],
        "Simpy": ["http://www.simpy.com", "http://www.simpy.com/login"],
        "Yigg": ["http://www.yigg.de"],
        "Kirtsy": ["http://www.kirtsy.com", "http://www.kirtsy.com/login.php"],
        "Fark": ["http://www.fark.com", "http://cgi.fark.com/cgi/fark/users.pl?self=1"],
        "Mixx": ["https://www.mixx.com/login/dual", "http://www.mixx.com"],
        "Google Bookmarks": ["http://www.google.com/bookmarks", "http://www.google.com/ig/add?moduleurl=bookmarks.xml&hl=en"],
        "Subbmitt": ["http://subbmitt.com/"]
    },
        t = []; // Variable dans laquelle on va stocker la liste de sites visités par l'utilisateur

    // On vérifie pour chaque site de la liste
    for(var i in sites){
        if(sites[i].checkVisited())
            t.push(i);
    }

    // On affiche le résultat
    $("visited_sites").set("html", "<strong>Sites visités : </strong>" + t.join(", "));
    [code=php]// Liste de réseaux sociaux
    var sites = {
        "Digg": ["http://digg.com", "http://digg.com/login"],
        "Reddit": ["http://reddit.com", "http://reddit.com/new/", "http://reddit.com/controversial/", "http://reddit.com/top/", "http://reddit.com/r/reddit.com/", "http://reddit.com/r/programming/"],
        "StumbleUpon": ["http://stumbleupon.com"],
        "Yahoo Buzz": ["http://buzz.yahoo.com"],
        "Facebook": ["http://facebook.com/home.php", "http://facebook.com", "https://login.facebook.com/login.php"],
        "Del.icio.us": ["https://secure.del.icio.us/login", "http://del.icio.us/", "http://delicious.com"],
        "MySpace": ["http://www.myspace.com/"],
        "Technorati": ["http://www.technorati.com"],
        "Newsvine": ["https://www.newsvine.com", "https://www.newsvine.com/_tools/user/login"],
        "Songza": ["http://songza.com"],
        "Slashdot": ["http://slashdot.org/"],
        "Ma.gnolia": ["http://ma.gnolia.com/"],
        "Blinklist": ["http://www.blinklist.com"],
        "Furl": ["http://furl.net", "http://furl.net/members/login"],
        "Mister Wong": ["http://www.mister-wong.com"],
        "Current": ["http://current.com", "http://current.com/login.html"],
        "Menaeme": ["http://meneame.net", "http://meneame.net/login.php"],
        "Oknotizie": ["http://oknotizie.alice.it", "http://oknotizie.alice.it/login.html.php"],
        "Diigo": ["http://www.diigo.com/", "https://secure.diigo.com/sign-in"],
        "Funp": ["http://funp.com", "http://funp.com/account/loginpage.php"],
        "Blogmarks": ["http://blogmarks.net"],
        "Yahoo Bookmarks": ["http://bookmarks.yahoo.com"],
        "Xanga": ["http://xanga.com"],
        "Blogger": ["http://blogger.com"],
        "Last.fm": ["http://www.last.fm/", "https://www.last.fm/login/"],
        "N4G": ["http://www.n4g.com"],
        "Faves": ["http://faves.com", "http://faves.com/home", "https://secure.faves.com/signIn"],
        "Simpy": ["http://www.simpy.com", "http://www.simpy.com/login"],
        "Yigg": ["http://www.yigg.de"],
        "Kirtsy": ["http://www.kirtsy.com", "http://www.kirtsy.com/login.php"],
        "Fark": ["http://www.fark.com", "http://cgi.fark.com/cgi/fark/users.pl?self=1"],
        "Mixx": ["https://www.mixx.com/login/dual", "http://www.mixx.com"],
        "Google Bookmarks": ["http://www.google.com/bookmarks", "http://www.google.com/ig/add?moduleurl=bookmarks.xml&hl=en"],
        "Subbmitt": ["http://subbmitt.com/"]
    },
        t = []; // Variable dans laquelle on va stocker la liste de sites visités par l'utilisateur

    // On vérifie pour chaque site de la liste
    for(var i in sites){
        if(sites[i].checkVisited())
            t.push(i);
    }

    // On affiche le résultat
    $("visited_sites").set("html", "<strong>Sites visités : </strong>" + t.join(", "));[/code]

    C'est fini !

    Chez moi, ça fonctionne impeccablement avec Firefox 3, Opéra 9.5, IE 6, et IE 7.
    Si vous avez un problème d'incompatibilité ou une suggestion, n'hésitez pas ! Par exemple, on pourrait, comme Aza, traiter pour une URL quelconque l'URL avec et sans les "www".

    Merci de me faire un petit lien de retour si vous utilisez ce script quelque part


    4 commentaires
  • Voici ma fonction pour enlever tous les accents d'une chaîne de caractères tout en respectant l'encodage (cette fonction traite très bien les textes UTF-8 par exemple) :

    function removeAccents($txt){
        $txt = str_replace('œ', 'oe', $txt);
        $txt = str_replace('Œ', 'Oe', $txt);
        $txt = str_replace('æ', 'ae', $txt);
        $txt = str_replace('Æ', 'Ae', $txt);
        mb_regex_encoding('UTF-8');
        $txt = mb_ereg_replace('[ÀÁÂÃÄÅĀĂǍẠẢẤẦẨẪẬẮẰẲẴẶǺĄ]', 'A', $txt);
        $txt = mb_ereg_replace('[àáâãäåāăǎạảấầẩẫậắằẳẵặǻą]', 'a', $txt);
        $txt = mb_ereg_replace('[ÇĆĈĊČ]', 'C', $txt);
        $txt = mb_ereg_replace('[çćĉċč]', 'c', $txt);
        $txt = mb_ereg_replace('[ÐĎĐ]', 'D', $txt);
        $txt = mb_ereg_replace('[ďđ]', 'd', $txt);
        $txt = mb_ereg_replace('[ÈÉÊËĒĔĖĘĚẸẺẼẾỀỂỄỆ]', 'E', $txt);
        $txt = mb_ereg_replace('[èéêëēĕėęěẹẻẽếềểễệ]', 'e', $txt);
        $txt = mb_ereg_replace('[ĜĞĠĢ]', 'G', $txt);
        $txt = mb_ereg_replace('[ĝğġģ]', 'g', $txt);
        $txt = mb_ereg_replace('[ĤĦ]', 'H', $txt);
        $txt = mb_ereg_replace('[ĥħ]', 'h', $txt);
        $txt = mb_ereg_replace('[ÌÍÎÏĨĪĬĮİǏỈỊ]', 'I', $txt);
        $txt = mb_ereg_replace('[ìíîïĩīĭįıǐỉị]', 'i', $txt);
        $txt = str_replace('Ĵ', 'J', $txt);
        $txt = str_replace('ĵ', 'j', $txt);
        $txt = str_replace('Ķ', 'K', $txt);
        $txt = str_replace('ķ', 'k', $txt);
        $txt = mb_ereg_replace('[ĹĻĽĿŁ]', 'L', $txt);
        $txt = mb_ereg_replace('[ĺļľŀł]', 'l', $txt);
        $txt = mb_ereg_replace('[ÑŃŅŇ]', 'N', $txt);
        $txt = mb_ereg_replace('[ñńņňʼn]', 'n', $txt);
        $txt = mb_ereg_replace('[ÒÓÔÕÖØŌŎŐƠǑǾỌỎỐỒỔỖỘỚỜỞỠỢ]', 'O', $txt);
        $txt = mb_ereg_replace('[òóôõöøōŏőơǒǿọỏốồổỗộớờởỡợð]', 'o', $txt);
        $txt = mb_ereg_replace('[ŔŖŘ]', 'R', $txt);
        $txt = mb_ereg_replace('[ŕŗř]', 'r', $txt);
        $txt = mb_ereg_replace('[ŚŜŞŠ]', 'S', $txt);
        $txt = mb_ereg_replace('[śŝşš]', 's', $txt);
        $txt = mb_ereg_replace('[ŢŤŦ]', 'T', $txt);
        $txt = mb_ereg_replace('[ţťŧ]', 't', $txt);
        $txt = mb_ereg_replace('[ÙÚÛÜŨŪŬŮŰŲƯǓǕǗǙǛỤỦỨỪỬỮỰ]', 'U', $txt);
        $txt = mb_ereg_replace('[ùúûüũūŭůűųưǔǖǘǚǜụủứừửữự]', 'u', $txt);
        $txt = mb_ereg_replace('[ŴẀẂẄ]', 'W', $txt);
        $txt = mb_ereg_replace('[ŵẁẃẅ]', 'w', $txt);
        $txt = mb_ereg_replace('[ÝŶŸỲỸỶỴ]', 'Y', $txt);
        $txt = mb_ereg_replace('[ýÿŷỹỵỷỳ]', 'y', $txt);
        $txt = mb_ereg_replace('[ŹŻŽ]', 'Z', $txt);
        $txt = mb_ereg_replace('[źżž]', 'z', $txt);
        return $txt;
    }
    [code=php]function removeAccents($txt){
        $txt = str_replace('œ', 'oe', $txt);
        $txt = str_replace('Œ', 'Oe', $txt);
        $txt = str_replace('æ', 'ae', $txt);
        $txt = str_replace('Æ', 'Ae', $txt);
        mb_regex_encoding('UTF-8');
        $txt = mb_ereg_replace('[ÀÁÂÃÄÅĀĂǍẠẢẤẦẨẪẬẮẰẲẴẶǺĄ]', 'A', $txt);
        $txt = mb_ereg_replace('[àáâãäåāăǎạảấầẩẫậắằẳẵặǻą]', 'a', $txt);
        $txt = mb_ereg_replace('[ÇĆĈĊČ]', 'C', $txt);
        $txt = mb_ereg_replace('[çćĉċč]', 'c', $txt);
        $txt = mb_ereg_replace('[ÐĎĐ]', 'D', $txt);
        $txt = mb_ereg_replace('[ďđ]', 'd', $txt);
        $txt = mb_ereg_replace('[ÈÉÊËĒĔĖĘĚẸẺẼẾỀỂỄỆ]', 'E', $txt);
        $txt = mb_ereg_replace('[èéêëēĕėęěẹẻẽếềểễệ]', 'e', $txt);
        $txt = mb_ereg_replace('[ĜĞĠĢ]', 'G', $txt);
        $txt = mb_ereg_replace('[ĝğġģ]', 'g', $txt);
        $txt = mb_ereg_replace('[ĤĦ]', 'H', $txt);
        $txt = mb_ereg_replace('[ĥħ]', 'h', $txt);
        $txt = mb_ereg_replace('[ÌÍÎÏĨĪĬĮİǏỈỊ]', 'I', $txt);
        $txt = mb_ereg_replace('[ìíîïĩīĭįıǐỉị]', 'i', $txt);
        $txt = str_replace('Ĵ', 'J', $txt);
        $txt = str_replace('ĵ', 'j', $txt);
        $txt = str_replace('Ķ', 'K', $txt);
        $txt = str_replace('ķ', 'k', $txt);
        $txt = mb_ereg_replace('[ĹĻĽĿŁ]', 'L', $txt);
        $txt = mb_ereg_replace('[ĺļľŀł]', 'l', $txt);
        $txt = mb_ereg_replace('[ÑŃŅŇ]', 'N', $txt);
        $txt = mb_ereg_replace('[ñńņňʼn]', 'n', $txt);
        $txt = mb_ereg_replace('[ÒÓÔÕÖØŌŎŐƠǑǾỌỎỐỒỔỖỘỚỜỞỠỢ]', 'O', $txt);
        $txt = mb_ereg_replace('[òóôõöøōŏőơǒǿọỏốồổỗộớờởỡợð]', 'o', $txt);
        $txt = mb_ereg_replace('[ŔŖŘ]', 'R', $txt);
        $txt = mb_ereg_replace('[ŕŗř]', 'r', $txt);
        $txt = mb_ereg_replace('[ŚŜŞŠ]', 'S', $txt);
        $txt = mb_ereg_replace('[śŝşš]', 's', $txt);
        $txt = mb_ereg_replace('[ŢŤŦ]', 'T', $txt);
        $txt = mb_ereg_replace('[ţťŧ]', 't', $txt);
        $txt = mb_ereg_replace('[ÙÚÛÜŨŪŬŮŰŲƯǓǕǗǙǛỤỦỨỪỬỮỰ]', 'U', $txt);
        $txt = mb_ereg_replace('[ùúûüũūŭůűųưǔǖǘǚǜụủứừửữự]', 'u', $txt);
        $txt = mb_ereg_replace('[ŴẀẂẄ]', 'W', $txt);
        $txt = mb_ereg_replace('[ŵẁẃẅ]', 'w', $txt);
        $txt = mb_ereg_replace('[ÝŶŸỲỸỶỴ]', 'Y', $txt);
        $txt = mb_ereg_replace('[ýÿŷỹỵỷỳ]', 'y', $txt);
        $txt = mb_ereg_replace('[ŹŻŽ]', 'Z', $txt);
        $txt = mb_ereg_replace('[źżž]', 'z', $txt);
        return $txt;
    }[/code]

    On peut faire plus simple, certes, mais contrairement à ce qu'on peut voir habituellement, cette fonction traite véritablement tous les accents existants, et fonctionne avec tous les encodages.


    3 commentaires


    Suivre le flux RSS des articles de cette rubrique
    Suivre le flux RSS des commentaires de cette rubrique