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

Ярлык

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

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

Код на GitHub

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

<?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 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 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 вместо адреса вашего сайта.

Другие статьи

Практически в каждом проекте возникает необходимость реализовать регистрацию и авторизацию пользователей. В рецепте RBAC и описание ролей в файле подробно описана реализация распределения доступа пользователям по ролям. Этот рецепт является уточнением рецепта Аутентификация и авторизация, в котором и описано использование доступа по ролям. Попробуем сделать настройку ролей пользователей более гибкой.

Как многим известно, для хранения настроек приложения в Yii выделен специальный раздел `params` в конфигурационном файле. Это решение достаточно простое, но оно не позволяет легко менять настройки самому пользователю в панели управления сайта. Очередной вопрос на русском форуме Yii натолкнул меня поделиться своим вариантом решения упомянутого там вопроса.

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

Если Вы вдруг решили подключить ноутбук к компьютеру с Windows7, то при обнаружении новой сети система спросит у Вас, какой тип сети установить (Домашняя, Рабочая или Общественная). Это стандартно. Но пользователи Win7 столкнутся с казусом, что при подключении в «Центре управления сетями и общим доступом» новая сеть становится неопознанной и общественной, если не указать для IPv4-адреса основной шлюз.

Комментарии

 

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">';
}

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

Ответить

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

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


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



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