Сайт-визитка

от 14 000 рублей

Каталог

от 19 000 рублей

Магазин

от 30 000 рублей

Немного динамического раутинга в Drupal 7

В Друпале, как известно, адреса основных сущностей, таких как пользователи и материалы, имеют вид 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 — то что мы хотели.