Как добавить rel= "nofollow" к ссылкам с preg_replace()

Нижеприведенная функция предназначена для применения атрибутов rel="nofollow" ко всем внешним ссылкам и внутренних ссылок, если только путь не соответствует предварительно определенному корневому URL, указанному ниже $my_folder.

Итак, заданные переменные...

$my_folder = 'http://localhost/mytest/go/';
$blog_url = 'http://localhost/mytest';

И содержимое...

<a href="#" onclick="location.href='http://localhost/mytest/'; return false;">internal</a>

<a href="#" onclick="location.href='http://localhost/mytest/go/hostgator'; return false;">internal cloaked link</a>

<a href="#" onclick="location.href='http://cnn.com'; return false;">external</a>

Конечный результат, после замены должен быть...

<a href="#" onclick="location.href='http://localhost/mytest/'; return false;">internal</a>

<a href="#" onclick="location.href='http://localhost/mytest/go/hostgator'; return false;" rel="nofollow">internal cloaked link</a>

<a href="#" onclick="location.href='http://cnn.com'; return false;" rel="nofollow">external</a>

Обратите внимание, что первая ссылка не изменяется, так как ее внутренняя ссылка.

Ссылка на вторую строку также является внутренней ссылкой, но поскольку она соответствует нашей строке $my_folder, она также получает nofollow.

Третья ссылка самая простая, так как она не соответствует blog_url, ее явно внешняя ссылка.

Однако в script ниже ВСЕ мои ссылки получают nofollow. Как я могу исправить script делать то, что хочу?

function save_rseo_nofollow($content) {
$my_folder =  $rseo['nofollow_folder'];
$blog_url = get_bloginfo('url');
    preg_match_all('~<a.*>~isU',$content["post_content"],$matches);
    for ( $i = 0; $i <= sizeof($matches[0]); $i++){
        if ( !preg_match( '~nofollow~is',$matches[0][$i])
            && (preg_match('~' . $my_folder . '~', $matches[0][$i]) 
               || !preg_match( '~'.$blog_url.'~',$matches[0][$i]))){
            $result = trim($matches[0][$i],">");
            $result .= ' rel="nofollow">';
            $content["post_content"] = str_replace($matches[0][$i], $result, $content["post_content"]);
        }
    }
    return $content;
}

Ответ 1

Попытайтесь сделать его более читаемым первым и только после этого сделайте более сложными правила if:

function save_rseo_nofollow($content) {
    $content["post_content"] =
    preg_replace_callback('~<(a\s[^>]+)>~isU', "cb2", $content["post_content"]);
    return $content;
}

function cb2($match) { 
    list($original, $tag) = $match;   // regex match groups

    $my_folder =  "/hostgator";       // re-add quirky config here
    $blog_url = "http://localhost/";

    if (strpos($tag, "nofollow")) {
        return $original;
    }
    elseif (strpos($tag, $blog_url) && (!$my_folder || !strpos($tag, $my_folder))) {
        return $original;
    }
    else {
        return "<$tag rel='nofollow'>";
    }
}

Дает следующий результат:

[post_content] =>
  <a href="http://localhost/mytest/">internal</a>
  <a href="http://localhost/mytest/go/hostgator" rel=nofollow>internal cloaked link</a>    
  <a href="http://cnn.com" rel=nofollow>external</a>

Проблема в вашем исходном коде могла бы быть $rseo, которая не была объявлена ​​нигде.

Ответ 2

Вот решение DOMDocument...

$str = '<a href="http://localhost/mytest/">internal</a>

<a href="http://localhost/mytest/go/hostgator">internal cloaked link</a>

<a href="http://cnn.com" rel="me">external</a>

<a href="http://google.com">external</a>

<a href="http://example.com" rel="nofollow">external</a>

<a href="http://stackoverflow.com" rel="junk in the rel">external</a>
';
$dom = new DOMDocument();

$dom->preserveWhitespace = FALSE;

$dom->loadHTML($str);

$a = $dom->getElementsByTagName('a');

$host = strtok($_SERVER['HTTP_HOST'], ':');

foreach($a as $anchor) {
        $href = $anchor->attributes->getNamedItem('href')->nodeValue;

        if (preg_match('/^https?:\/\/' . preg_quote($host, '/') . '/', $href)) {
           continue;
        }

        $noFollowRel = 'nofollow';
        $oldRelAtt = $anchor->attributes->getNamedItem('rel');

        if ($oldRelAtt == NULL) {
            $newRel = $noFollowRel;
        } else {
            $oldRel = $oldRelAtt->nodeValue;
            $oldRel = explode(' ', $oldRel);
            if (in_array($noFollowRel, $oldRel)) {
                continue;
            }
            $oldRel[] = $noFollowRel;
            $newRel = implode($oldRel,  ' ');
        }

        $newRelAtt = $dom->createAttribute('rel');
        $noFollowNode = $dom->createTextNode($newRel);
        $newRelAtt->appendChild($noFollowNode);
        $anchor->appendChild($newRelAtt);

}

var_dump($dom->saveHTML());

Выход

string(509) "<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html><body>
<a href="http://localhost/mytest/">internal</a>

<a href="http://localhost/mytest/go/hostgator">internal cloaked link</a>

<a href="http://cnn.com" rel="me nofollow">external</a>

<a href="http://google.com" rel="nofollow">external</a>

<a href="http://example.com" rel="nofollow">external</a>

<a href="http://stackoverflow.com" rel="junk in the rel nofollow">external</a>
</body></html>
"

Ответ 3

Попробуйте это (PHP 5.3 +):

  • пропустить выбранный адрес
  • позволяет вручную установить параметр rel

и код:

function nofollow($html, $skip = null) {
    return preg_replace_callback(
        "#(<a[^>]+?)>#is", function ($mach) use ($skip) {
            return (
                !($skip && strpos($mach[1], $skip) !== false) &&
                strpos($mach[1], 'rel=') === false
            ) ? $mach[1] . ' rel="nofollow">' : $mach[0];
        },
        $html
    );
}

Примеры:

echo nofollow('<a href="link somewhere" rel="something">something</a>');
// will be same because it already contains rel parameter

echo nofollow('<a href="http://www.cnn.com">something</a>'); // ad
// add rel="nofollow" parameter to anchor

echo nofollow('<a href="http://localhost">something</a>', 'localhost');
// skip this link as internall link

Ответ 4

Использование регулярных выражений для правильной работы должно быть довольно сложным. Было бы проще использовать фактический парсер, например, один из расширение DOM. DOM не очень удобен для начинающих, поэтому вы можете загружать HTML с помощью DOM, а затем выполнять изменения с помощью SimpleXML. Они поддерживаются одной и той же библиотекой, поэтому ее легко использовать с другой.

Вот как это может выглядеть:

$my_folder = 'http://localhost/mytest/go/';
$blog_url = 'http://localhost/mytest';

$html = '<html><body>
<a href="http://localhost/mytest/">internal</a>
<a href="http://localhost/mytest/go/hostgator">internal cloaked link</a>
<a href="http://cnn.com">external</a>
</body></html>';

$dom = new DOMDocument;
$dom->loadHTML($html);

$sxe = simplexml_import_dom($dom);

// grab all <a> nodes with an href attribute
foreach ($sxe->xpath('//a[@href]') as $a)
{
    if (substr($a['href'], 0, strlen($blog_url)) === $blog_url
     && substr($a['href'], 0, strlen($my_folder)) !== $my_folder)
    {
        // skip all links that start with the URL in $blog_url, as long as they
        // don't start with the URL from $my_folder;
        continue;
    }

    if (empty($a['rel']))
    {
        $a['rel'] = 'nofollow';
    }
    else
    {
        $a['rel'] .= ' nofollow';
    }
}

$new_html = $dom->saveHTML();
echo $new_html;

Как вы можете видеть, это действительно коротко и просто. В зависимости от ваших потребностей вы можете использовать preg_match() вместо объектов strpos(), например:

    // change the regexp to your own rules, here we match everything under
    // "http://localhost/mytest/" as long as it not followed by "go"
    if (preg_match('#^http://localhost/mytest/(?!go)#', $a['href']))
    {
        continue;
    }

Примечание

Я пропустил последний блок кода в OP, когда я впервые прочитал вопрос. Код, который я опубликовал (и в основном любое решение на основе DOM), лучше подходит для обработки целой страницы, а не блока HTML. В противном случае DOM попытается "исправить" ваш HTML и может добавить тег <body>, DOCTYPE и т.д.

Ответ 5

<?

$str='<a href="http://localhost/mytest/">internal</a>
<a href="http://localhost/mytest/go/hostgator">internal cloaked link</a>
<a href="http://cnn.com">external</a>';

function test($x){
  if (preg_match('@localhost/mytest/(?!go/)@i',$x[0])>0) return $x[0];
  return 'rel="nofollow" '.$x[0];
}

echo preg_replace_callback('/href=[\'"][^\'"]+/i', 'test', $str);

?>

Ответ 6

Вот еще одно решение, в котором есть опция белого списка и добавлен атрибут пустого тега. А также он проверяет наличие атрибута rel перед добавлением нового.

function Add_Nofollow_Attr($Content, $Whitelist = [], $Add_Target_Blank = true) 
{
    $Whitelist[] = $_SERVER['HTTP_HOST'];
    foreach ($Whitelist as $Key => $Link) 
    {
        $Host = preg_replace('#^https?://#', '', $Link);
        $Host = "https?://". preg_quote($Host, '/');
        $Whitelist[$Key] = $Host;
    }

    if(preg_match_all("/<a .*?>/", $Content, $matches, PREG_SET_ORDER)) 
    {
        foreach ($matches as $Anchor_Tag) 
        {
            $IS_Rel_Exist = $IS_Follow_Exist = $IS_Target_Blank_Exist = $Is_Valid_Tag =  false;
            if(preg_match_all("/(\w+)\s*=\s*['|\"](.*?)['|\"]/",$Anchor_Tag[0],$All_matches2)) 
            {
                foreach ($All_matches2[1] as $Key => $Attr_Name)
                {
                    if($Attr_Name == 'href')
                    {
                        $Is_Valid_Tag = true;
                        $Url = $All_matches2[2][$Key];
                        // bypass #.. or internal links like "/"
                        if(preg_match('/^\s*[#|\/].*/', $Url)) 
                        {
                            continue 2;
                        }

                        foreach ($Whitelist as $Link) 
                        {
                            if (preg_match("#$Link#", $Url)) {
                                continue 3;
                            }
                        }
                    }
                    else if($Attr_Name == 'rel')
                    {
                        $IS_Rel_Exist = true;
                        $Rel = $All_matches2[2][$Key];
                        preg_match("/[n|d]ofollow/", $Rel, $match, PREG_OFFSET_CAPTURE);
                        if( count($match) > 0 )
                        {
                            $IS_Follow_Exist = true;
                        }
                        else
                        {
                            $New_Rel = 'rel="'. $Rel . ' nofollow"';
                        }
                    }
                    else if($Attr_Name == 'target')
                    {
                        $IS_Target_Blank_Exist = true;
                    }
                }
            }

            $New_Anchor_Tag = $Anchor_Tag;
            if(!$IS_Rel_Exist)
            {
                $New_Anchor_Tag = str_replace(">",' rel="nofollow">',$Anchor_Tag);
            }
            else if(!$IS_Follow_Exist)
            {
                $New_Anchor_Tag = preg_replace("/rel=[\"|'].*?[\"|']/",$New_Rel,$Anchor_Tag);
            }

            if($Add_Target_Blank && !$IS_Target_Blank_Exist)
            {
                $New_Anchor_Tag = str_replace(">",' target="_blank">',$New_Anchor_Tag);
            }

            $Content = str_replace($Anchor_Tag,$New_Anchor_Tag,$Content);
        }
    }
    return $Content;
}

Чтобы использовать это:

$Page_Content = '<a href="#" onclick="location.href='http://localhost/'; return false;">internal</a>
                 <a href="#" onclick="location.href='http://yoursite.com'; return false;">internal</a>
                 <a href="#" onclick="location.href='http://google.com'; return false;">google</a>
                 <a href="#" onclick="location.href='http://example.com'; return false;" rel="nofollow">example</a>
                 <a href="#" onclick="location.href='http://stackoverflow.com'; return false;" rel="random">stackoverflow</a>';

$Whitelist = ["http://yoursite.com","http://localhost"];

echo Add_Nofollow_Attr($Page_Content,$Whitelist,true);