В Друпале, как известно, адреса основных сущностей, таких как пользователи и материалы, имеют вид user/123 и node/123 соответственно (где 123 — это id сущности). Если мы хотим, например, сделать страницу со всеми фотографиями для каждого пользователя, то скорее всего мы сделаем ее с адресом вроде user/123/photos. Ну потому что надо же откуда-то id пользователя взять и загрузить все его фотографии. И этот адрес будет хорош всем кроме одного — пользователю непонятно, почему он, Василий Петрович Пупкин, называется «123». Пользователю конечно было бы удобнее видеть свои фотографии по адресу vasya/photos. То есть неплохо было бы решить проблему ЧПУ.
Обычно (для отдельных страниц) в Друпале эта проблема решается с помощью синонимов (path aliases), то есть каждой странице (user/123) ставится в соответствие уникальный адрес (vasya), и страница /user/123 становится доступной по адресу /vasya. А чтобы избежать дубликатов страниц (т.е. одинаковых страниц с разными адресами), делается автоматический редирект с оригинальной страницы на ее синоним: при заходе на страницу user/123 пользователя перенаправляет на страницу /vasya.
Однако в случае когда страница динамическая (все фотографии данного пользователя) — получается, что нужно для каждой страницы вида user/{uid}/photos
завести свой синоним. И обновлять его, если обновится синоним для страницы пользователя. И удалять, если пользователь удалится. И добавлять, если пользователь добавится. В общем, мало того, что для каждого пользователя нам придется не один, а два синонима хранить. Так нам еще и CRUD для синонимов поддерживать понадобится. А что если мы хотим еще страницу с блогами пользователя, страницу с его друзьями? Или вообще, мы хотим страницу архива материалов адресом вида user/123/archive/2014/09
?
К счастью, создавать синоним для каждого адреса динамической страницы не придется. Вместо этого можно генерировать «красивые» адреса программно с помощью хуков hook_url_inbound_alter()
и hook_url_outbound_alter()
. Что эти хуки делают? Позволяют переписать входящие и исходящие url-ы.
Первый хук hook_url_inbound_alter(&$path, $original_path, $path_language)
отвечает за обработку входящего запроса. При этом в $original_path
содержится запрос, как он пришел в систему от пользователя, а в $path
конструируется тот путь, который будет обработан Друпалом. Иными словами, если пользователь наберет адрес /vasya/photos
, то чтобы Друпал узнал, что это вообще за страница, нам нужно в $path
положить настоящий адрес user/123/photos
. Вот так:
function mymodule_url_inbound_alter(&$path, $original_path, $path_language) {
$parts = explode('/', $original_path);
if ($parts[1] == 'photos') {
//проверим, является ли первая часть адреса синонимом для пользователя
$path_info = path_load(array('alias' => $parts[0]));
if ($path_info) {
$source_parts = explode('/', $path_info['source']);
if (($source_parts[0] == 'user') && ctype_digit($source_parts[1])) {
$path = 'user/' . $source_parts[1] . '/' . 'photos';
}
}
}
}
Теперь при заходе на страницу vasya/photos
, Друпал будет знать, что нужно показать страницу фото (user/123/photos). Но сама страница user/123/photos
будет по-прежнему существовать, что может быть нежелательно с точки зрения SEO. Добавим редирект на этот случай:
function mymodule_url_inbound_alter(&$path, $original_path, $path_language) {
$parts = explode('/', $original_path);
if ($parts[1] == 'photos') {
//проверим, является ли первая часть адреса синонимом для пользователя
$path_info = path_load(array('alias' => $parts[0]));
if ($path_info) {
$source_parts = explode('/', $path_info['source']);
if (($source_parts[0] == 'user') && ctype_digit($source_parts[1])) {
$path = 'user/' . $source_parts[1] . '/' . 'photos';
}
}
} else if (
($parts[0] == 'user') &&
isset($parts[1]) &&
ctype_digit($parts[1]) &&
isset($parts[2]) &&
($parts[2] == 'photos')) {
//получим синоним для пользователя и перейдем по ссылке синоним/photos
$user_alias = path_load($parts[0] . '/' . $parts[1]);
$goto_path = $user_alias['alias'] . '/' . 'photos';
drupal_goto($goto_path);
}
}
Друпал уже знает, как обрабатывать страницы вида vasya/photos
. Но сам пользователь о существовании таких страниц еще не знает: на сайте-то ссылки на страницы с фотографиями по-прежнему показываются как user/123/photos
. Вот чтобы это поправить, и нужен второй хук — hook_url_outbound_alter(&$path, &$options, $original_path)
. В этом хуке можно изменить все исходящие ссылки при генерации текущей страницы. Для этого достаточно, чтобы ссылка генерировалась с вызовом функции url()
, а это верно почти для всех ссылок на сайте. В $original_path
на входе у нас будет лежать user/123/photos
, в $path
мы должны положить новый url — vasya/photos
.
function mymodule_url_outbound_alter(&$path, &$options, $original_path) {
if (preg_match('|^user/([0-9]*)(/.*)?|', $path, $matches)) {
$uid = $matches[1];
if (isset($matches[2]) && ($matches[2] == '/photos')) {
$user_alias = path_load('user/' . $uid);
$path = $user_alias['alias'] . $matches[2];
}
}
}
Теперь в href
нужных ссылок вместо user/123/photos
будет vasya/photos
— то что мы хотели.