УрокТонкости программного создания страниц на сайте

Первое, о чём я хочу сегодня рассказать - создание страниц с переменными аргументами. То есть, например, для страниц node/10, node/20, node/123 схема была задана лишь один раз, а используется она для всех страниц, которые подходят под шаблон node/%. Задаются подобные страницы следующим образом:

function mymodule_menu() {
  $items = array();
  $items['foo/%/bar'] = array(
      'page callback' => 'mymodule_foobar_page',
      'page arguments' => array(1),
    );
  return $items; 
}

При задании такой схемы страницы, функция mymodule_foobar_page() будет вызываться для страниц, урл которых начинается на foo, далее идёт любое значение (не обязательно численное), далее bar. Примеры: foo/1/bar, foo/my/bar, foo/10000000/bar. А вот такой пример под нашу схему уже не подходит: foo/1/2/bar.

Здесь появился ключ page arguments, указывающий номер аргументов из урла, которые будут переданы в функцию mymodule_foobar_page(). В нашем случае я указал единицу - значит, что будет передан "неизвестный" аргумент (который идёт с символом %). Не забывайте, что у программистов счёт начинается с нуля :) Если бы я указал 'page arguments' => array(0), то в качестве аргумента передалось бы слово 'foo'. А если бы я указал 'page arguments' => array(0, 1), то передались бы оба агрумента - слово 'foo', и переменное значение.

Пример. Если страница имеет урл foo/10/bar, то при 'page arguments' => array(1) колбэк из схемы страницы вернёт значение 10:

function mymodule_foobar_page($argument) {
  return $argument;
}

Если вы хотите, чтобы в качестве переменного значения могли использоваться только числовые значения, а на любые другие Друпал говорил, что страница не найдена, то функцию mymodule_foobar_page() необходимо начать вот с такого кода:

function mymodule_foobar_page($argument) {
  if (!ctype_digit($argument)) {
    // Если переменный агрумент не цифра,
    // то отдаём заголовок 404 (страница не найдена)
    drupal_not_found();
  }
  else {
    // Если цифра - продолжаем 
    $output = 'Цифра ' . $argument;
    return $output;
  }
}

Теперь об интересной фишке хука меню. Он умеет загружать объекты автоматически. Например, имеется такая схема:

function mymodule_menu() {
  $items = array();
  $items['foo/%bar'] = array(
      'page callback' => 'mymodule_foobar_page',
      'page arguments' => array(1),
    );
  return $items; 
}

Здесь переменное значение я указал не просто символом %, а %bar. В принципе, если это было сделанно не зная возможности автоматической загрузки, то вы о нём даже не догадаетесь - страница будет работать точно так же, как раньше. Однако если в модуль вы добавите функцию bar_load(), то в функцию mymodule_foobar_page() придёт не значение из урла, а результат выполнения bar_load'a.

Рассмотрим пример автоматической загрузки на примере нод. Создаём страницу, на которой будет отображаться материал:

function mymodule_menu() {
  $items = array();
  $items['foo/%node'] = array(
      'page callback' => 'mymodule_foobar_page',
      'page arguments' => array(1),
    );
  return $items; 
}

Обратите внимание, здесь в качестве переменного аргумента я указал %node. Теперь, если вы зайдёте на страницу, к примеру, foo/10, то в функцию mymodule_foobar_page() будет передано не число 10, а загруженный объект ноды с ID 10. А всё потому, что ядро Друпала уже включает в себя функцию node_load(). В качестве аргумента ей передаётся значение, указанное в page arguments - и нода загружается. После чего передаётся в функцию из page callback'a.

Заголовок страниц

Теперь давайте поговорим о заголовке страницы. Он указывается в ключе title и для него срабатывает title callback, который по умолчанию вызывает для title функцию t(). Вот эти два куска кода идентичны:

function mymodule_menu() {
  $items = array();
  $items['foo/%node'] = array(
      'title' => 'Node title',
      'page callback' => 'mymodule_foobar_page',
      'page arguments' => array(1),
    );
  return $items; 
}

и

function mymodule_menu() {
  $items = array();
  $items['foo/%node'] = array(
      'title' => 'Node title',
      'title callback' => 't',
      'page callback' => 'mymodule_foobar_page',
      'page arguments' => array(1),
    );
  return $items; 
}

Именно по этой причине при создании страницы в title не нужно пихать функцию t(). Она вызовется и без вашего участия.

Если вы хотите, чтобы заголовок задавался в зависимости от переменного аргумента, то стоит воспользоваться ключом title arguments:

function mymodule_menu() {
  $items = array();
  $items['foo/%node'] = array(
      'title callback' => 'mymodule_title_callback',
      'title arguments' => array(1),
      'page callback' => 'mymodule_foobar_page',
      'page arguments' => array(1),
    );
  return $items; 
}

В этом случае в функцию mymodule_title_callback() будет передан объект ноды (и мы уже знаем, почему). Вам останется вернуть лишь его заголовок:

function mymodule_title_callback($node) {
  return $node->title;
}

Абсолютно идентичные вызовы функций с параметрами происходит для следующих ключей:

  • access callback и access arguments (проверка доступа на просмотр текущей страницы).
  • theme callback и theme arguments (вызов функции темизации для рендера контента страницы).

Типы страниц

Страницы в Друпале бывают 6 типов:

  • MENU_NORMAL_ITEM: страница, которая будет являться одним из пунктов системного меню.
  • MENU_CALLBACK: обычная страница, ни к чему не привязанная.
  • MENU_SUGGESTED_ITEM: страница, "предлагаемая" модулями.
  • MENU_LOCAL_ACTION: локальными действиями являются страницы, которые описывают действия для родительской страницы. Пример - добавление нового пользователя или блока.
  • MENU_LOCAL_TASK: Страница, которая будет являться табом. Пример - табы Редактировать и Просмотр в полном представлении ноды.
  • MENU_DEFAULT_LOCAL_TASK: Таб по умолчанию (тот, который будет открываться первым). Например, для полного представления ноды - это таб Просмотр

Пример создания вкладок первого и второго уровня:

/**
 * Implements hook_menu().
 */
function mymodule_menu() {
  $items = array();
 
  $items['foo'] = array(
    'title callback' => 'Основная страница', 
    'page callback' => 'mymodule_main_page', 
    'access callback' => TRUE, 
  );
 
  // Создаём первый таб первого уровня, который будет автоматически 
  // открываться при переходе на страницу foo
  $items['foo/bar'] = array(
    'title' => 'Заголовок 1', 
    'type' => MENU_DEFAULT_LOCAL_TASK, 
    'weight' => 0,
  );
 
  // Второй таб первого уровня, который будет виден на странице foo
  $items['foo/bar2'] = array(
    'title' => 'Заголовок 2', 
    'page callback' => 'mymodule_bar2_page', 
    'access callback' => TRUE, 
    'weight' =>1, 
    'type' => MENU_LOCAL_TASK, 
  );
 
  // Первый таб второго уровня для таба foo/bar
  $items['foo/bar/abc'] = array(
    'title' => 'Заголовок 1.1',
    'page callback' => 'mymodule_bar_abc_page',
    'access callback' => TRUE,
    'weight' => 0,
    'tab parent' => 'foo/bar',
    'type' => MENU_DEFAULT_LOCAL_TASK, 
  );
 
// Второй таб второго уровня для таба foo/bar
  $items['foo/bar/abc2'] = array(
    'title' => 'Заголовок 1.1',
    'page callback' => 'mymodule_bar_abc2_page',
    'access callback' => TRUE,
    'weight' => 0,
    'tab parent' => 'foo/bar',
    'type' => MENU_DEFAULT_LOCAL_TASK, 
  );
 
  return $items;
}

Такой структурой была задана схема страниц, состоящая из двух первичных табов для страницы foo, и два вторичных таба, видимых при активном табе foo/bar.

Из основного - это всё. Я рассказал не про все возможности схемы страниц в Друпале. Существует ещё несколько дополнительных ключей, о которых я не упомянул. К ним относятся:

  • 'description' - описание страницы
  • 'delivery callback' - функция, которая обрабатывает страницу и отдаёт её браузеру. По умолчанию это drupal_deliver_html_page()
  • 'file' - название файла, в котором находятся колбэки из данной схемы страниц
  • 'file path' - путь, где располагается файл с колбэками
  • 'load arguments' - массив аргументов, которые передаются для автоматической загрузки. Он используется для случаев, когда вызов автоматической загрузки не является явным (например, в урле используется не %node, а просто %)
  • 'weight' - определяет позицию страницы по отношению к другим. Например, для табов он определяет, какая страница будет первым табом, вторым и т.д.
  • 'menu_name' - задаёт заголовок в меню, если страница была в него помещена.
  • 'context' - описывает контекст появления страницы. Бывает двух типов - MENU_CONTEXT_PAGE (по умолчанию) и MENU_CONTEXT_INLINE.
  • 'tab_root' - для страниц типа MENU_LOCAL_TASK и MENU_DEFAULT_LOCAL_TASK определяет ближайшую родительскую страницу, которая не является табом.
  • 'position' - позиция блока на странице с администрированием блоков. Бывает 'left' или 'right'.
  • 'options' - массив параметров, передаваемых в функцию l() для ссылки на страницу из меню.

Комментарии

Аватар пользователя jedi-it
jedi-it написал:

Здорово, спасибо за описания. Сейчас некоторые люди изучают фреймворки, микрофреймворки чтобы сократить рабочий процесс, но никто никогда не смотрит на друпал как на фреймворк *или микро-фреймворк. Недавно смотрел YII, как там делаются формы и другое, скажу что очень похоже на друпал, плюс многое не предусмотренно что есть тут. Поистине это необычный гибкий фреймворк слеюущий "философии" KISS)

14.11.2011 08:30
Аватар пользователя HOSTGAME
HOSTGAME написал:

Огромное спасибо! Лучший сайт блог про Друпал!
Хорошо что про семерку пишите, про нее мало инфы в интернете

19.11.2011 16:06
Аватар пользователя Xvedar
Xvedar написал:

А вы уверены, что в примере создания вкладок первого и второго уровня у вас все правильно? У меня как-то не выполняется 'page callback' в табах второго уровня, когда 'type' стоит в MENU_DEFAULT_LOCAL_TASK.
У меня еще возник вопрос в процессе работы с табами. Если например взять ваш пример с табами и в табах второго уровня выставить 'access arguments' => array('abc') и 'access arguments' => array('abc2') то есть определить hook_permission(), а в админке выставить одному табу полный доступ, а другому закрыть доступ, то в итого ни один таб не отображаются? В остальных случаях работает корректно.
function hook_permission()
{
return array(
'abc' => array( 'title' => t('abc') ),
'abc2' => array( 'title' => t('abc2') ),
);
}

21.08.2012 21:45
Аватар пользователя Gnom7i
Gnom7i написал:

Благодарю за комментарий! Действительно нужно убрать дефаулты у вкладок второго уровня.

20.03.2016 11:33
Аватар пользователя Василий
Василий написал:

Спасибо, очень помогли мне!

23.03.2013 08:37

Комментировать