Перенаправление внешних ссылок на промежуточную страницу

Ярлык

В комментариях к записи о HTML Purifier был задан вопрос о том, каким образом можно переделать внешние адреса на переход через промежуточную страницу на своём сайте. Сделаем функционал, обрабатывающий внешние ссылки в тексте и переадресующий их на страницу подтверждения перехода на другие сайты.

Для решения этой задачи напишем компонент, подставляющий произвольный префикс к адресам всех внешних ссылок в контенте сайта. Будем считать внешними все ссылки, начинающиеся с http://, https:// и ftp://. Остальные ссылки вроде mailto: и прочих будем игнорировать. Для удобства сделаем настраиваемыми протоколы и префикс:

Код на GitHub

В простейшем случае обработку ссылок можно производить прямо при выводе текста в представлении:

<?php
<?php echo DOuterLinker::load()->process($post->text); ?>

Для настройки компонента можно использовать методы addProtocols(), setProtocols() и setPrefix() в любом сочетании:

echo DOuterLinker::load()->setPrefix('/link?a=')->process($html);
echo DOuterLinker::load()->addProtocols(array('dc'))->setPrefix('/link?a=')->process($html);
echo DOuterLinker::load()->setProtocols(array('http', 'https'))->process($html);

Также можно работать классически

$linker = new DOuterLinker();
$linker->setProtocols(array('http'));
$linker->setPrefix('/link?a=');
echo $linker->process($html);

Чтобы не записывать одни и те же настройки каждый раз можно переопределить их в своём классе OuterLinker

class OuterLinker extends DOuterLinker
{
    protected $_protocols = array('http', 'https');
    protected $_prefix = '/site/link?url=';
}

и использовать его вместо оригинального:

<?php
<?php echo OuterLinker::load()->process($post->text); ?>

Для уменьшения нагрузки лучше обрабатывать текст всего один раз перед сохранением записи в базу данных.

Рассмотрим организацию преобразования текста перед сохранением записи на примере модели фреймворка Yii.

Пример использования в модели Yii

Пусть в нашей модели есть поля text для исходного HTML кода и purified_text для обработанного. Добавим в модель методы beforeSave() и afterFind(), в которых будем производить замену ссылок:

class Post extends CActiveRecord
{
    protected function beforeSave()
    {
        if (parent::beforeSave())
        {          
            $this->purified_text = DOuterLinker::load()->process($this->text);
            return true;        
        } 
        else
            return false;
    }
 
    protected function afterFind()
    {               
        if (!$this->purified_text)
            $this->purified_text = DOuterLinker::load()->process($this->text);  
 
        parent::afterFind();
    }
}

Теперь в представлении нужно вывести результат:

<?php
<?php echo $post->purified_text; ?>

Если необходимо использовать данную функцию совместно с DPurifyTextBehavior, то методы нужно немного изменить. Обработка ссылок должна производиться после работы DPurifyTextBehavior. Чтобы не сохранять два раза результат в момент afterFind отключим автосохранение у поведения 'updateOnAfterFind'=>false и будем сохранять его вручную вызовом $this->updateModel() (это вызов метода DPurifyTextBehavior::updateModel()):

class Post extends CActiveRecord
{
    public function behaviors()
    {
        return array(
            'PurifyText'=>array(
                'class'=>'DPurifyTextBehavior',
                'sourceAttribute'=>'text',
                'destinationAttribute'=>'purified_text',
                'purifierOptions'=> array(
                    'Attr.AllowedRel'=>array('nofollow'),
                    'HTML.SafeObject'=>true,
                    'Output.FlashCompat'=>true,
                ),
                // отключим автосохранение результата
                // так как будем это делать вручную
                'updateOnAfterFind'=>false,
            ),
        );
    }
 
    protected function beforeSave()
    {       
        // сначала отработают все поведения
        if (parent::beforeSave())
        {          
            // потом мы произведём замену ссылок
            if ($this->purified_text)
                $this->purified_text = DOuterLinker::load()->process($this->purified_text);
 
            return true;
        } 
        else
            return false;
    }
 
    protected function afterFind()
    {
        // запомним, заполнено ли поле с результатом
        $isEmpty = $this->purified_text ? true : false;
 
        // запустим все поведения
        parent::afterFind();
 
        // а потом если результат обновился
        if ($isEmpty && $this->purified_text)
        {
            // произведём замену ссылок
            $this->purified_text = DOuterLinker::load()->setPrefix('site/link?url=')->process($this->purified_text);
            // и вызовем метод DPurifyTextBehavior::updateModel() сохранения результата
            $this->updateModel();
        }       
    }
}

Теперь в тексте все внешние ссылки

<a href="http://www.yandex.ru?q=query&lang=ru">Yandex</a>

преобразуются в

<a href="/site/link?url=http://www.yandex.ru%3Fq%3Dquery%26lang%3Dru">Yandex</a>

Теперь достаточно добавить перенаправляющий экшен

class SiteController extends Controller
{
    public function actionLink($url)
    {
        // ...
    }
}

в котором уже выводить свой текст и настоящую ссылку.

Кроме своей страницы все ссылки можно перенаправлять и на чужую:

echo DOuterLinker::load()->setPrefix('http://anonym.to/?')->process($html);

Это пример использования сервиса anonim.to, производящего перенаправление на переданный ему адрес. Сайты, на которые Вы ссылаетесь, не смогут понять с какого сайта к ним перешли, так как в поле HTTP_REFERER у них будет отображаться http://anonim.to вместо адреса вашего сайта.

Комментарии

 

TranceSmile

Спасибо. Отличная статья

Ответить

 

asma

спасибо. интересные статьи!

Ответить

 

Anonimus

Жаль, что я не знаком с ООП, программирую только структурно

Ответить

 

Anonimus

Действительно жаль. Держите нас в курсе.

Ответить

 

mono

В конце DOuterLinker.php лучше сдеать так, чтобы закрывать только действительно внешние ссылки

$pos1 = stripos($url, $_SERVER['SERVER_NAME']);
if ($pos1 !== false) {
        return '<a href=' . $protocol . '://' . $url . '>';
}else{
	 return '<a' . $before . 'href=' . $beginquote . $this->_prefix . $protocol . '://' . urlencode($url) . $endquote . $after . ' target="_blank">';
}

Смысл в том чтобы добавить проверку на наличие в сылке своего домена.

Ответить

 

Иван

Добрый день!
Проблема:
На странице есть ссылка, которая меняет статус какой-либо модели:

Html::a('Сменить статус', ['model/set-status', 'id' => $model->id, 'status' => 2]);


Содержимое страницы обновляется через Ajax.
В контроллере:

public function actionSetStatus($id, $status)
{
    $model = Model::findModel($id);
    $model->status = $status;
    $model->save();
    ....
    return $this->render('view', [....]);    
}

При этом, когда кликаешь по этой ссылке, адрес страницы соответственно меняется с ".../model/view?id=1" на ".../model/set-status?id=1&status=2"

Подскажите пожалуйста как сделать так чтобы страница обновилась через Ajax и не поменялась адресная строка? И если есть возможность то не использовать POST-запрос

Ответить

 

Дмитрий Елисеев
Html::a('Сменить статус', ['model/set-status', 'id' => $model->id, 'status' => 2], [
    'data-method' => 'post',
]);
return $this->redirect('view', ['id' => $id]);
Ответить

 

Гость666

Подскажите пожалуйста каким плагином реализована такая задержка при переходе по внешней ссылке с показом рекламы на этом сайте? https://xn-----7kckegeaw8apdfn0d9a0j.xn--p1ai/goto?link=

Ответить

 

Дмитрий Елисеев

Обычный JavaScript:

setTimeout(function () {
    window.location.href = url;
}, 10000);
Ответить

Оставить комментарий

Войти | Завести аккаунт | Войти через


(никто не увидит)





Можно использовать теги <p> <ul> <li> <b> <i> <a> <pre>