«Сделайте мне красиво»: User:Name в качестве аргумента Views

Главные вкладки

Аватар пользователя ingumsky@drupal.org ingumsky@drupal.org 27 августа 2011 в 18:43

Я думаю, каждый друпалер использует на своих сайтах модули Views и Pathauto. Да, на некоторых сайтах можно без них обойтись, но, фактически, оба этих модуля давно входят в джентльменский набор при запуске нового сайта, поэтому нет нужды объяснять, для чего они используются. Некоторое время назад я столкнулся с проблемой, которая не то, чтобы была критичной, но беспокоила моё эстетическое чувство и не давала мне считать свою работу выполненной. Дело было связано с тем, что views и pathauto по-разному представляют пользователю одни и те же вещи — как-то заголовки материалов и имена пользователей.

До некоторых пор это не особенно бросается в глаза, но представим себе следующую ситуацию — у вас есть вьюс, с помощью которого вы выводите список материалов по адресу example.net/content. Если задан аргумент, вьюс выводит только материалы за авторством конкретного пользователя. Самый простой способ сделать это — указать в качестве аргумента User:Uid. В результате получается, что при запросе вида example.net/content/1 будут отображаться все материалы, созданные суперпользователем, при запросе example.net/content/150, пользователем с uid 150 и так далее. Это хороший способ, у которого есть один минус — URL example.net/content/150 «некрасив» и мало, о чём говорит стороннему наблюдателю (хотя я часто использую такие конструкции для вьюсов, «закрытых» для пользователя).

Сделать URL «более говорящим» можно достаточно легко, если использовать в качестве аргумента не User:Uid, а User:Name. В результате мы получаем красивые адреса вроде example.net/content/ingumsky, едва взглянув на которые можно понять, что содержимое страницы будет иметь отношение к тому или иному пользователю (другое дело, какое отношение они к нему имеют :). В принципе, такого решения уже вполне достаточно, если бы не одно «но»...

Например, представим, что на нашем сайте есть пользователи с именами Ingumsky, John Smith, jack_brown, S.White, He is Awesome, Паша. Мы включаем на сайте модуль pathauto и с его помощью создаём для каждого пользователя красивые адреса страниц профилей: example.net/users/ingumsky, example.net/users/john-smith и так далее. и здесь обнаруживается, что в адресах страниц пользователей, на страницах блогов и так далее синонимы имён пользователей отличаются от того, что принимает views в качестве аргумента. Сравните, как выглядят аргументы во Views и синонимы Pathauto [в квадратных скобках] для каждого из перечисленных выше пользователей (обращаю внимание, что в настройках аргумента включены опции «lowercase» и «turn spaces into dashes»:

example.net/content/ingumsky — материалы пользователя Ingumsky [ingumsky]
example.net/content/john-smith — материалы пользователя John Smith [john-smith]
example.net/content/jack_brown — материалы пользователя jack_brown [jackbrown]
example.net/content/s.white — материалы пользователя S.White [swhite]
example.net/content/he-is-awesome — материалы пользователя He is Awesome [he-awesome]
example.net/content/паша — материалы пользователя Паша [pasha]

Как видно, Views принимает в качестве аргумента именно имя пользователя, которое при указанных чуть выше настройках, просто переведено в нижний регистр и избавлено от пробелов (они заменены на дефисы). Но Pathauto при создании синонима вносит больше изменений в исходный текст. В результате в тех случаях, когда в именах пользователей встречаются знаки подчёркивания, точки, кириллица или даже некоторые английские слова, которые неважны для SEO (is, are и тому подобное), аргумент отличается от того синонима, который нам предлагает Pathauto.

Как я написал в самом начале записи, это не критичная проблема, но она... м-м-м... раздражает. Хочется, чтобы всё было красиво и «единообразно». Вопрос состоял в следующем: Как добиться того, чтобы скармливаемый вьюсу аргумент всегда выглядел так же, как выглядит ник в URL, созданном Pathauto?

Первоначальный поиск по этим вашим энтернетам не дал нужного мне результата, и я даже обратился за помощью к сообществу. Тот вариант решения, который мне посоветовали, показался мне излишне сложным для данного случая — к тому моменту я уже понял, в каком направлении нужно копать, и вскоре решение было найдено. Так как я уверен, что данная задача наверняка интересовала не только меня, предлагаю своё решение и вам:

1. В качестве аргумента мы будем использовать не User: Name, а User: Uid, пусть это прозвучит и несколько странно Smile Теперь нам надо сделать так, чтобы из аргумента, который принимает наш вьюс, мы смогли вытащить uid;
2. В качестве «Что делать, если аргумент не указан» ставим «Показывать всё»;
3. Проверять аргумент мы будем с помощью php, поэтому смело выбираем в списке валидаторов «PHP Code»;
4. В текстовом поле пишем:
<?php
// Проверяем, не число ли дано в качестве аргумента
// Если число, аргумент подходит.
// Это особенно удобно, чтобы проверять, работает ли ваш вьюс с таким валидатором
if (is_numeric($argument)) {
return TRUE;
}
// Смотрим, не указано ли в качестве аргумента «all»
// Если указано, аргумент подходит, и мы покажем все материалы
elseif ($argument == 'all') {
return TRUE;
} else {
// У меня синонимы профилей пользователей выглядят как users/username
// Здесь мы подставляем наш аргумент, чтобы получить такой же синоним
// В вашем случае может потребоваться заменить 'users/' на что-то другое
$dru_alias = 'users/'.$argument;
// С помощью функции drupal_lookup_path ищем, каков внутренний адрес,
// соответствующий синониму — в нашем случае получится 'user/uid'
$dru_path = drupal_lookup_path('source',$dru_alias);
// Разбираем внутренний адрес, вытаскивая из него uid
$uid = substr($dru_path, strrpos($dru_path, '/') + 1);
// Отдаём uid в качестве аргумента и сообщаем, что всё верно.
$handler->argument = $uid;
return TRUE;
}
?>
5. Profit!

В результате мы получаем views, который правильно обрабатывает аргументы, если они выглядят так же, как наши синонимы. Этот вьюс принимает так же и непосредственно uid в качестве аргумента. Это удобно, но, если у вас есть пользователи с числовыми именами, лучше избавиться от первой части условия в if.

Возможно, гуру Друпал засмеют меня и скажут, что решение было очевидным. Что ж, возможно и так. Я до этого решения дошёл далеко не сразу да и то только по наводке комментаторов с drupal.org, благодаря которым я понял, что надо смотреть в сторону валидатора аргумента. Надеюсь, что это решение поможет кому-нибудь ещё.

Комментарии

Аватар пользователя staryi staryi 27 августа 2011 в 19:09

Красава, все бы делились хотябы частью освоенного в подробном (подробном) виде, а то ведь многие даже [РЕШЕНО] написать не могут.

з.ы. Ливер чемпион ???

Аватар пользователя kalabro kalabro 27 августа 2011 в 19:59

а что за вид подробный (подробный)? Smile
а так, да, решения всегда очень интересно и полезно почитать, спасибо.

Аватар пользователя xxandeadxx xxandeadxx 27 августа 2011 в 19:14

"<a href="mailto:ingumsky@drupal.org">ingumsky@drupal.org</a>" wrote:
example.net/content/ingumsky — материалы пользователя Ingumsky [ingumsky]
example.net/content/john-smith — материалы пользователя John Smith [john-smith]
example.net/content/jack_brown — материалы пользователя jack_brown [jackbrown]
example.net/content/s.white — материалы пользователя S.White [swhite]
example.net/content/he-is-awesome — материалы пользователя He is Awesome [he-awesome]
example.net/my_view/паша — материалы пользователя Паша [pasha]

каким образом эти урлы создаются?

Аватар пользователя ingumsky@drupal.org ingumsky@drupal.org 28 августа 2011 в 15:16

"staryi" wrote:
з.ы. Ливер чемпион ???

Ливер — это печень Wink А так Ливерпуль — чемпион, да. В перспективе... Smile
"xxandeadxx" wrote:
каким образом эти урлы создаются?

Представление вида «page» c заданным путём content. Если в качестве аргумента принимать User:Name, будут формироваться URL example.net/content/username.

All
Спасибо за отзывы. Рад, что материал пригодился.

Аватар пользователя direqtor direqtor 18 сентября 2011 в 5:18

Вот уж что-то, а очевидные решения с вьюсами часто рождаются после нескольких дней траходрома.

Кстати, будут ли актуальны следующие темы с views: Двухуровневый каталог с картинками и блекджеком, Views+точки на карте?

Аватар пользователя Anton L. Safin Anton L. Safin 19 сентября 2011 в 5:10

direqtor wrote:

Кстати, будут ли актуальны следующие темы с views: Двухуровневый каталог с картинками и блекджеком, Views+точки на карте?

Второе актуально.

Аватар пользователя InternetDevels.com InternetDevels.com 18 сентября 2011 в 15:50

Если на сайте используется модуль subpath_alias, то в настройках views будет достаточно указать путь страницы "user/%/content"

тогда материалы пользователя будут доступны точно за такой же страницей что и профиль с приставкой "/content" (то есть, например, для пользователя S.White это users/swhite и users/swhite/content)
не знаю как в Вашем случаи, но в большинстве этот вариант и проще и лучше

enjoy your Drupal Smile

Аватар пользователя ingumsky@drupal.org ingumsky@drupal.org 18 сентября 2011 в 23:42

"direqtor" wrote:
Вот уж что-то, а очевидные решения с вьюсами часто рождаются после нескольких дней траходрома.

Это точно.
"direqtor" wrote:
Кстати, будут ли актуальны следующие темы с views: Двухуровневый каталог с картинками и блекджеком, Views+точки на карте?

Думаю, будут актуальны. views+точки на карте у меня есть тут: http://www.liverbird.ru/pubs Решение с темизацией шаблонов у меня, наверное, не самое изящное, но работает.
"InternetDevels.com" wrote:
Если на сайте используется модуль subpath_alias, то в настройках views будет достаточно указать путь страницы "user/%/content"

Я в курсе, но, есть причины не пользоваться им. Во-первых, это ещё один модуль — зачем ставить ещё один модуль, решающий общую задачу, когда требуется решить одну небольшую частную задачу? Во-вторых, с точки зрения семантики, «ваш» алиас и «мой» дают разный по смыслу результат: у меня, например, есть топ лучших материалов:
http://example.net/top
Я хочу сделать ещё топ для каждого пользователя:
http://example.net/users/ingumsky/top — выглядит неплохо, но конкретно для top не очень хорошо.
У меня сделано так:
http://example.net/top/by/ingumsky — по-моему, так лучше.

Аватар пользователя shp shp 19 сентября 2011 в 10:50

Только Pathauto ИМХО лучше не использовать. Лучше custom_url_rewrite_inbound()/inbound, тогда алиасов будет мало, и можно будет все подгрузить за один раз.

Аватар пользователя ingumsky@drupal.org ingumsky@drupal.org 19 сентября 2011 в 13:24

"shp" wrote:
Только Pathauto ИМХО лучше не использовать. Лучше custom_url_rewrite_inbound()/inbound, тогда алиасов будет мало, и можно будет все подгрузить за один раз.

В моём случае pathauto используется по всему сайту, так что без него всё равно никуда. Или я вас неправильно понял?

Аватар пользователя shp@drupal.org shp@drupal.org 20 сентября 2011 в 15:25

Бывает наверное, когда сложно обойтись без pathauto, но надо стараться Smile Пример: захотелось заменить node/xx на более понятное content/xx. Можно было использовать pathauto, но я сделал с помощью custom_url_rewrite_inbound()/inbound. В результате алиасов всего несколько, и можно сделать патч для drupal_lookup_path - загружать все алиасы один раз. Получим один запрос вместо 30-40 (столько раз на страницу у меня вызывается drupal_lookup_path()). Большого прироста наверное не будет (запросы простые), но все равно приятно.

P.S. Сам патч еще не сделал - просто такая идея возникла.

Аватар пользователя shp@drupal.org shp@drupal.org 21 сентября 2011 в 16:54

<?php
// Изменяем входящие пути (от пользователя).
function custom_url_rewrite_inbound(&$result$path$path_language) {
  if (
preg_match('|^content/(.+)|i'$result$matches)) {
    
$result 'node/'$matches[1];
  }
}

// Изменяем исходящие пути (ссылки, генерируемые Друпалом).
function custom_url_rewrite_outbound(&$path, &$options$original_path) {
  if (
strtolower(substr($path04)) == 'node') {
    if (
preg_match('|^node/+(.+)|i'$path$matches)) {
      
$path 'content/'$matches[1];
    }
    else if (
preg_match('|^node/*$|i'$path)) {
      
$path '';
    }
  }
}
?>

Потом еще потестирую и может доработаю. Я это по-быстрому написал и пока оставил так.

А вот и ссылочка: http://api.drupal.org/api/drupal/developer--hooks--core.php/function/cus...

Аватар пользователя liveflow liveflow 21 сентября 2011 в 18:19

Большое спасибо, понял!
Идея в том чтобы отказаться от запросов (и к БД) в drupal_lookup_path, а ссылки менять на лету на основе правил преобразования? Кстати, в семерке нет такого хука, кажется.

Аватар пользователя shp@drupal.org shp@drupal.org 23 сентября 2011 в 1:56

Quote:
Идея в том чтобы отказаться от запросов (и к БД) в drupal_lookup_path, а ссылки менять на лету на основе правил преобразования?
К сожалению, запросы все равно будут, т.к. drupal_lookup_path() по-прежнему будет вызываться. Чтобы их не было, можно доработать drupal_lookup_path(), например тупо загрузить все алиасы один раз, а потом каждый вызов drupal_lookup_path() брать их с переменной. Но это целесообразно, если таблица с алиасами не очень большая. Чтоб ее освободить, можно некоторые преобразования адресов выполнять с помощью inbound/outbound.

Quote:
Кстати, в семерке нет такого хука, кажется.
Наоборот - в 7-ке это сделали хуками, что очень удобно. В 6-ке это простые ф-ии, которые к тому же должны располагаться в settings.php, а не в модуле.

Аватар пользователя liveflow liveflow 26 сентября 2011 в 12:59

«Наоборот - в 7-ке это сделали хуками, что очень удобно»
Точно, hook_url_inbound_alter. Спасибо за подробные ответы!