web-dev-qa-db-fra.com

Remplacer les URL dans le texte par des liens HTML

Voici un design cependant: Par exemple, je mets un lien tel que

http://example.com

dans textarea. Comment obtenir PHP pour détecter qu’il s’agit d’un lien http:// et l’imprimer ensuite en tant que

print "<a href='http://www.example.com'>http://www.example.com</a>";

_ {Je me souviens d'avoir déjà fait quelque chose comme ça auparavant, cependant, ce n'était pas une preuve stupide qu'il continuait à casser pour des liens complexes.}

Une autre bonne idée serait si vous avez un lien tel que

http://example.com/test.php?val1=bla&val2blablabla%20bla%20bla.bl

corrige le pour qu'il le fasse

print "<a href='http://example.com/test.php?val1=bla&val2=bla%20bla%20bla.bla'>";
print "http://example.com/test.php";
print "</a>";

Celui-ci est juste un après la pensée .. stackoverflow pourrait également probablement l'utiliser aussi: D

Des idées

55
Angel.King.47

Regardons les exigences. Vous avez du texte brut fourni par l'utilisateur que vous souhaitez afficher avec des URL en hyperlien.

  1. Le préfixe de protocole "http: //" devrait être facultatif.
  2. Les domaines et les adresses IP doivent être acceptés.
  3. Tout domaine de premier niveau valide doit être accepté, par exemple. .aero et .xn - jxalpdlp.
  4. Les numéros de port devraient être autorisés.
  5. Les URL doivent être autorisées dans les contextes de phrases normales. Par exemple, dans "Visit stackoverflow.com.", La dernière période ne fait pas partie de l'URL.
  6. Vous voudrez probablement autoriser également les URL "https: //" et peut-être d'autres.
  7. Comme toujours lorsque vous affichez du texte fourni par l'utilisateur en HTML, vous souhaitez empêcher le script intersite (XSS). De plus, vous souhaiterez que les esperluettes dans les URL soient correctement échappées en tant que & amp;.
  8. Vous n'avez probablement pas besoin de support pour les adresses IPv6.
  9. Edit: Comme indiqué dans les commentaires, la prise en charge des adresses électroniques est définitivement un avantage.
  10. Edit: seule la saisie de texte brut doit être prise en charge - les balises HTML de l'entrée ne doivent pas être honorées. (La version de Bitbucket prend en charge l’entrée HTML.)

Edit: consultez Bitbucket pour la dernière version, avec prise en charge des adresses électroniques, des URL authentifiées, des URL entre guillemets et parenthèses, de la saisie HTML et d'une liste de TLD mise à jour.

Veuillez signaler les bogues et les demandes d'amélioration à l'aide de le suivi des problèmes de Bitbucket . Ils sont plus faciles à suivre de cette façon (et n'encombrent pas la zone de commentaire).} _

Voici ma prise:

<?php
$text = <<<EOD
Here are some URLs:
stackoverflow.com/questions/1188129/pregreplace-to-detect-html-php
Here's the answer: http://www.google.com/search?rls=en&q=42&ie=utf-8&oe=utf-8&hl=en. What was the question?
A quick look at http://en.wikipedia.org/wiki/URI_scheme#Generic_syntax is helpful.
There is no place like 127.0.0.1! Except maybe http://news.bbc.co.uk/1/hi/england/surrey/8168892.stm?
Ports: 192.168.0.1:8080, https://example.net:1234/.
Beware of Greeks bringing internationalized top-level domains: xn--hxajbheg2az3al.xn--jxalpdlp.
And remember.Nobody is perfect.

<script>alert('Remember kids: Say no to XSS-attacks! Always HTML escape untrusted input!');</script>
EOD;

$rexProtocol = '(https?://)?';
$rexDomain   = '((?:[-a-zA-Z0-9]{1,63}\.)+[-a-zA-Z0-9]{2,63}|(?:[0-9]{1,3}\.){3}[0-9]{1,3})';
$rexPort     = '(:[0-9]{1,5})?';
$rexPath     = '(/[!$-/0-9:;=@_\':;!a-zA-Z\x7f-\xff]*?)?';
$rexQuery    = '(\?[!$-/0-9:;=@_\':;!a-zA-Z\x7f-\xff]+?)?';
$rexFragment = '(#[!$-/0-9:;=@_\':;!a-zA-Z\x7f-\xff]+?)?';

// Solution 1:

function callback($match)
{
    // Prepend http:// if no protocol specified
    $completeUrl = $match[1] ? $match[0] : "http://{$match[0]}";

    return '<a href="' . $completeUrl . '">'
        . $match[2] . $match[3] . $match[4] . '</a>';
}

print "<pre>";
print preg_replace_callback("&\\b$rexProtocol$rexDomain$rexPort$rexPath$rexQuery$rexFragment(?=[?.!,;:\"]?(\s|$))&",
    'callback', htmlspecialchars($text));
print "</pre>";
  • Pour échapper correctement aux caractères <et &, je passe tout le texte dans htmlspecialchars avant le traitement. Ce n'est pas idéal, car l'échappement HTML peut provoquer une mauvaise détection des limites d'URL.
  • Comme le montre le "Et souvenez-vous. Personne n'est parfait." ligne (dans laquelle Remember. Personne n'est traité comme une URL en raison de l’espace manquant), une vérification supplémentaire des domaines de premier niveau valides peut être nécessaire.

Edit: le code suivant corrige les deux problèmes ci-dessus, mais est un peu plus détaillé puisque je suis plus ou moins en train de ré-implémenter preg_replace_callback en utilisant preg_match.

// Solution 2:

$validTlds = array_fill_keys(explode(" ", ".aero .asia .biz .cat .com .coop .edu .gov .info .int .jobs .mil .mobi .museum .name .net .org .pro .tel .travel .ac .ad .ae .af .ag .ai .al .am .an .ao .aq .ar .as .at .au .aw .ax .az .ba .bb .bd .be .bf .bg .bh .bi .bj .bm .bn .bo .br .bs .bt .bv .bw .by .bz .ca .cc .cd .cf .cg .ch .ci .ck .cl .cm .cn .co .cr .cu .cv .cx .cy .cz .de .dj .dk .dm .do .dz .ec .ee .eg .er .es .et .eu .fi .fj .fk .fm .fo .fr .ga .gb .Gd .ge .gf .gg .gh .gi .gl .gm .gn .gp .gq .gr .gs .gt .gu .gw .gy .hk .hm .hn .hr .ht .hu .id .ie .il .im .in .io .iq .ir .is .it .je .jm .jo .jp .ke .kg .kh .ki .km .kn .kp .kr .kw .ky .kz .la .lb .lc .li .lk .lr .ls .lt .lu .lv .ly .ma .mc .md .me .mg .mh .mk .ml .mm .mn .mo .mp .mq .mr .ms .mt .mu .mv .mw .mx .my .mz .na .nc .ne .nf .ng .ni .nl .no .np .nr .nu .nz .om .pa .pe .pf .pg .ph .pk .pl .pm .pn .pr .ps .pt .pw .py .qa .re .ro .rs .ru .rw .sa .sb .sc .sd .se .sg .sh .si .sj .sk .sl .sm .sn .so .sr .st .su .sv .sy .sz .tc .td .tf .tg .th .tj .tk .tl .tm .tn .to .tp .tr .tt .tv .tw .tz .ua .ug .uk .us .uy .uz .va .vc .ve .vg .vi .vn .vu .wf .ws .ye .yt .yu .za .zm .zw .xn--0zwm56d .xn--11b5bs3a9aj6g .xn--80akhbyknj4f .xn--9t4b11yi5a .xn--deba0ad .xn--g6w251d .xn--hgbk6aj7f53bba .xn--hlcj6aya9esc7a .xn--jxalpdlp .xn--kgbechtv .xn--zckzah .arpa"), true);

$position = 0;
while (preg_match("{\\b$rexProtocol$rexDomain$rexPort$rexPath$rexQuery$rexFragment(?=[?.!,;:\"]?(\s|$))}", $text, &$match, PREG_OFFSET_CAPTURE, $position))
{
    list($url, $urlPosition) = $match[0];

    // Print the text leading up to the URL.
    print(htmlspecialchars(substr($text, $position, $urlPosition - $position)));

    $domain = $match[2][0];
    $port   = $match[3][0];
    $path   = $match[4][0];

    // Check if the TLD is valid - or that $domain is an IP address.
    $tld = strtolower(strrchr($domain, '.'));
    if (preg_match('{\.[0-9]{1,3}}', $tld) || isset($validTlds[$tld]))
    {
        // Prepend http:// if no protocol specified
        $completeUrl = $match[1][0] ? $url : "http://$url";

        // Print the hyperlink.
        printf('<a href="%s">%s</a>', htmlspecialchars($completeUrl), htmlspecialchars("$domain$port$path"));
    }
    else
    {
        // Not a valid URL.
        print(htmlspecialchars($url));
    }

    // Continue text parsing from after the URL.
    $position = $urlPosition + strlen($url);
}

// Print the remainder of the text.
print(htmlspecialchars(substr($text, $position)));
117
Søren Løvborg

Voici quelque chose que j'ai trouvé qui est essayé et testé

function make_links_blank($text)
{
  return  preg_replace(
     array(
       '/(?(?=<a[^>]*>.+<\/a>)
             (?:<a[^>]*>.+<\/a>)
             |
             ([^="\']?)((?:https?|ftp|bf2|):\/\/[^<> \n\r]+)
         )/iex',
       '/<a([^>]*)target="?[^"\']+"?/i',
       '/<a([^>]+)>/i',
       '/(^|\s)(www.[^<> \n\r]+)/iex',
       '/(([_A-Za-z0-9-]+)(\\.[_A-Za-z0-9-]+)*@([A-Za-z0-9-]+)
       (\\.[A-Za-z0-9-]+)*)/iex'
       ),
     array(
       "stripslashes((strlen('\\2')>0?'\\1<a href=\"\\2\">\\2</a>\\3':'\\0'))",
       '<a\\1',
       '<a\\1 target="_blank">',
       "stripslashes((strlen('\\2')>0?'\\1<a href=\"http://\\2\">\\2</a>\\3':'\\0'))",
       "stripslashes((strlen('\\2')>0?'<a href=\"mailto:\\0\">\\0</a>':'\\0'))"
       ),
       $text
   );
}

Ça marche pour moi. Et cela fonctionne pour les emails et les URL, Désolé de répondre à ma propre question. :(

Mais celui-ci est le seul qui fonctionne

Voici le lien où je l'ai trouvé: http://www.experts-exchange.com/Web_Development/Web_Languages-Standards/PHP/Q_21878567.html

Sry à l'avance pour que ce soit un échange d'experts.

14
Angel.King.47

Vous parlez de moyens de faire avancer des choses complexes qui conviennent à certaines situations, mais nous avons surtout besoin d’une solution simple et négligente. Que diriez-vous simplement de cela?

preg_replace('/(http[s]{0,1}\:\/\/\S{4,})\s{0,}/ims', '<a href="$1" target="_blank">$1</a> ', $text_msg);

Essayez-le et laissez-moi savoir quelle URL folle il ne satisfait pas.

11
Raheel Hasan

Voici le code utilisant les expressions rationnelles dans la fonction

<?php
//Function definations
function MakeUrls($str)
{
$find=array('`((?:https?|ftp)://\S+[[:alnum:]]/?)`si','`((?<!//)(www\.\S+[[:alnum:]]/?))`si');

$replace=array('<a href="$1" target="_blank">$1</a>', '<a href="http://$1" target="_blank">$1</a>');

return preg_replace($find,$replace,$str);
}
//Function testing
$str="www.cloudlibz.com";
$str=MakeUrls($str);
echo $str;
?>
3
Dharmendra Jadon

J'utilise cette fonction, ça marche pour moi

function AutoLinkUrls($str,$popup = FALSE){
    if (preg_match_all("#(^|\s|\()((http(s?)://)|(www\.))(\w+[^\s\)\<]+)#i", $str, $matches)){
        $pop = ($popup == TRUE) ? " target=\"_blank\" " : "";
        for ($i = 0; $i < count($matches['0']); $i++){
            $period = '';
            if (preg_match("|\.$|", $matches['6'][$i])){
                $period = '.';
                $matches['6'][$i] = substr($matches['6'][$i], 0, -1);
            }
            $str = str_replace($matches['0'][$i],
                    $matches['1'][$i].'<a href="http'.
                    $matches['4'][$i].'://'.
                    $matches['5'][$i].
                    $matches['6'][$i].'"'.$pop.'>http'.
                    $matches['4'][$i].'://'.
                    $matches['5'][$i].
                    $matches['6'][$i].'</a>'.
                    $period, $str);
        }//end for
    }//end if
    return $str;
}//end AutoLinkUrls

Tous les crédits vont à - http://snipplr.com/view/68586/

Prendre plaisir!

2
Armand

Ce RegEx doit correspondre à n’importe quel lien, à l’exception de ces nouveaux domaines de niveau supérieur à plus de 3 caractères ...

{
 \\ b 
 # Faites correspondre la partie principale (proto: // nomhôte, ou simplement nomhôte) 
 (
 # http: // ou https: // partie principale 
 (https?): // [- \\ w] + (\\. \\ w [- \\ w] *) + 
 | 
 # ou essayez de trouver un nom d’hôte avec une sous-expression plus spécifique 
 (? i: [a-z0-9] (?: [: a-z0-9] * z0-9])? \\.) + # sous-domaines 
 # Nous terminons maintenant avec .com, etc. Pour ceux-ci, utilisez minuscule 
 (? -i: com \\ b 
 | edu \\ b 
 | biz \\ b 
 | gov \\ b 
 | in (?: t | fo) \\ b # .int ou .info 
 | mil \\ b 
 | net \\ b 
 | org \\ b 
 | [az] [az] \\. [az] [az] \\ b # code pays à deux lettres 
) 
) 
 
 # Autoriser un numéro de port optionnel 
 (: \\ d +)? 

 # Le reste de l'URL est facultatif et commence par /
 (
/
 # Le reste est une heuristique pour ce qui semble bien fonctionner 
 [^.!,?; "\\ '()\[\]\{\}\s\x7F - \\ xFF] * 
 (
 [.!,?] + [^.!,?; "\\ '() \\ [\\]\{\\}\s \\ x7F - \\ xFF ] + 
) * 
)? 
} ix 

Ce n'est pas écrit par moi, je ne sais pas trop d'où je viens, désolé de ne pouvoir donner aucun crédit ...

1
fresskoma

cela devrait vous donner des adresses email:

$string = "bah bah [email protected] foo";
$match = preg_match('/[^\x00-\x20()<>@,;:\\".[\]\x7f-\xff]+(?:\.[^\x00-\x20()<>@,;:\\".[\]\x7f-\xff]+)*\@[^\x00-\x20()<>@,;:\\".[\]\x7f-\xff]+(?:\.[^\x00-\x20()<>@,;:\\".[\]\x7f-\xff]+)+/', $string, $array);
print_r($array);

// outputs:
Array
(
    [0] => [email protected]
)
1
Stephen Fuhry

Comme je l'ai mentionné dans l'un des commentaires ci-dessus, mon VPS, qui exécute php 7, a démarré Émettant des avertissements Avertissement: preg_replace (): le modificateur/e n'est plus pris en charge, utilisez plutôt preg_replace_callback . Le tampon après le remplacement était vide/faux.

J'ai réécrit le code et apporté quelques améliorations ... Si vous pensez que vous devriez être dans la section auteur, n'hésitez pas à éditer le commentaire au-dessus de la fonction make_links_blank name . Je n'utilise pas intentionnellement le php de fermeture?> Pour évitez d'insérer des espaces dans la sortie.

<?php

class App_Updater_String_Util {
    public static function get_default_link_attribs( $regex_matches = [] ) {
        $t = ' target="_blank" ';
        return $t;
    }

    /**
     * App_Updater_String_Util::set_protocol();
     * @param string $link
     * @return string
     */
    public static function set_protocol( $link ) {
        if ( ! preg_match( '#^https?#si', $link ) ) {
            $link = 'http://' . $link;
        }
        return $link;
    }

/**
     * Goes through text and makes whatever text that look like a link an html link
     * which opens in a new tab/window (by adding target attribute).
     * 
     * Usage: App_Updater_String_Util::make_links_blank( $text );
     * 
     * @param str $text
     * @return str
     * @see http://stackoverflow.com/questions/1188129/replace-urls-in-text-with-html-links
     * @author Angel.King.47 | http://dashee.co.uk
     * @author Svetoslav Marinov (Slavi) | http://orbisius.com
     */
    public static function make_links_blank( $text ) {
        $patterns = [
            '#(?(?=<a[^>]*>.+?<\/a>)
                 (?:<a[^>]*>.+<\/a>)
                 |
                 ([^="\']?)((?:https?|ftp):\/\/[^<> \n\r]+)
             )#six' => function ( $matches ) {
                $r1 = empty( $matches[1] ) ? '' : $matches[1];
                $r2 = empty( $matches[2] ) ? '' : $matches[2];
                $r3 = empty( $matches[3] ) ? '' : $matches[3];

                $r2 = empty( $r2 ) ? '' : App_Updater_String_Util::set_protocol( $r2 );
                $res = ! empty( $r2 ) ? "$r1<a href=\"$r2\">$r2</a>$r3" : $matches[0];
                $res = stripslashes( $res );

                return $res;
             },

            '#(^|\s)((?:https?://|www\.|https?://www\.)[^<>\ \n\r]+)#six' => function ( $matches ) {
                $r1 = empty( $matches[1] ) ? '' : $matches[1];
                $r2 = empty( $matches[2] ) ? '' : $matches[2];
                $r3 = empty( $matches[3] ) ? '' : $matches[3];

                $r2 = ! empty( $r2 ) ? App_Updater_String_Util::set_protocol( $r2 ) : '';
                $res = ! empty( $r2 ) ? "$r1<a href=\"$r2\">$r2</a>$r3" : $matches[0];
                $res = stripslashes( $res );

                return $res;
            },

            // Remove any target attribs (if any)
            '#<a([^>]*)target="?[^"\']+"?#si' => '<a\\1',

            // Put the target attrib
            '#<a([^>]+)>#si' => '<a\\1 target="_blank">',

            // Make emails clickable Mailto links
            '/(([\w\-]+)(\\.[\w\-]+)*@([\w\-]+)
                (\\.[\w\-]+)*)/six' => function ( $matches ) {

                $r = $matches[0];
                $res = ! empty( $r ) ? "<a href=\"mailto:$r\">$r</a>" : $r;
                $res = stripslashes( $res );

                return $res;
            },
        ];

        foreach ( $patterns as $regex => $callback_or_replace ) {
            if ( is_callable( $callback_or_replace ) ) {
                $text = preg_replace_callback( $regex, $callback_or_replace, $text );
            } else {
                $text = preg_replace( $regex, $callback_or_replace, $text );
            }
        }

        return $text;
    }
}
1
Svetoslav Marinov

Je sais que cette réponse a été acceptée et que cette question est assez ancienne, mais elle peut être utile pour d'autres personnes à la recherche d'autres implémentations.

Ceci est une version modifiée du code posté par: Angel.King.47 le 27 juillet 2009:

$text = preg_replace(
 array(
   '/(^|\s|>)(www.[^<> \n\r]+)/iex',
   '/(^|\s|>)([_A-Za-z0-9-]+(\\.[A-Za-z]{2,3})?\\.[A-Za-z]{2,4}\\/[^<> \n\r]+)/iex',
   '/(?(?=<a[^>]*>.+<\/a>)(?:<a[^>]*>.+<\/a>)|([^="\']?)((?:https?):\/\/([^<> \n\r]+)))/iex'
 ),  
 array(
   "stripslashes((strlen('\\2')>0?'\\1<a href=\"http://\\2\" target=\"_blank\">\\2</a>&nbsp;\\3':'\\0'))",
   "stripslashes((strlen('\\2')>0?'\\1<a href=\"http://\\2\" target=\"_blank\">\\2</a>&nbsp;\\4':'\\0'))",
   "stripslashes((strlen('\\2')>0?'\\1<a href=\"\\2\" target=\"_blank\">\\3</a>&nbsp;':'\\0'))",
 ),  
 $text
);

Changements:

  • J'ai supprimé les règles n ° 2 et n ° 3 (je ne sais pas dans quelles situations sont utiles). 
  • Suppression de l'analyse du courrier électronique car je n'en ai vraiment pas besoin.
  • J'ai ajouté une règle supplémentaire permettant la reconnaissance des URL sous la forme: [domaine] /* (Sans www). Par exemple: "example.com/faq/" (Multiple tld: domain. {2-3}. {2-4} /)
  • Lors de l'analyse de chaînes commençant par "http: //", il est supprimé du libellé du lien.
  • Ajout de "target = '_ blank'" à tous les liens.
  • Les URL peuvent être spécifiées juste après une balise (?). Par exemple: <b> www.example.com </ b>

Comme l'a dit "Søren Løvborg", cette fonction n'échappe pas aux URL. J'ai essayé sa classe mais cela n'a pas fonctionné comme prévu (si vous ne faites pas confiance à vos utilisateurs, essayez d'abord son code). 

1
lepe

Quelque chose dans le genre de:

<?php
if(preg_match('@^http://(.*)\s|$@g', $textarea_url, $matches)) {
    echo '<a href=http://", $matches[1], '">', $matches[1], '</a>';
}
?>
0
OneOfOne

Si vous voulez faire confiance à l'IANA, vous pouvez obtenir votre liste actuelle de TLD officiellement utilisés, tels que:

  $validTLDs = 
explode("\n", file_get_contents('http://data.iana.org/TLD/tlds-alpha-by-domain.txt')); //get the official list of valid tlds
  array_shift($validTLDs); //throw away first line containing meta data
  array_pop($validTLDs); //throw away last element which is empty

La solution n ° 2 de Søren Løvborg est un peu moins explicite et vous évite les tracas de la mise à jour de la liste. Aujourd'hui, les nouveaux tld sont jetés si négligemment;) 

0
Max

Cela a fonctionné pour moi (transformé l'une des réponses en une fonction PHP)

function make_urls_from_text ($text){
   return preg_replace('/(http[s]{0,1}\:\/\/\S{4,})\s{0,}/ims', '<a href="$1" target="_blank">$1 </a>', $text);
}
0
Shawn Gervais

This class change les URL en texte et en conservant l'URL d'origine telle qu'elle est J'espère que cela vous aidera et vous fera gagner du temps.

class RegClass 
{ 

     function preg_callback_url($matches) 
     { 
        //var_dump($matches); 
        //Get the matched URL  text <a>text</a>
        $text = $matches[2];
        //Get the matched URL link <a href ="http://www.test.com">text</a>
        $url = $matches[1];

        if($url=='href ="http://www.test.com"'){
         //replace all a tag as it is
         return '<a href='.$url.' rel="nofollow"> '.$text.' </a>'; 

         }else{
         //replace all a tag to text
         return " $text " ;
         }
} 
function ParseText($text){ 

    $text = preg_replace( "/www\./", "http://www.", $text );
        $regex ="/http:\/\/http:\/\/www\./"
    $text = preg_replace( $regex, "http://www.", $text );
        $regex2 = "/https:\/\/http:\/\/www\./";
    $text = preg_replace( $regex2, "https://www.", $text );

        return preg_replace_callback('/<a\s(.+?)>(.+?)<\/a>/is',
                array( &$this,        'preg_callback_url'), $text); 
      } 

} 
$regexp = new RegClass();
echo $regexp->ParseText($text);
0
amarjit singh