Параметрический вызов формы добавления нода на разных страницах

Предположим, мы создали модуль и в нем определили новый тип нода. Пусть это будет нод «Город» с внутренним именем 'city'. В форме добавления нашего нода с адреса 'node/add/city', кроме всего прочего, мы сделаем селектор стран, чтобы можно было выбирать, в какой стране находится город:

function city_form(&$node, $form_state) {
  //...
  $form['countries'] = array(
    '#type' => 'select',
    '#title' => t('Country'),
    '#options' => _get_countries_list(), //возвращает список стран в формате code => t('Country')
    '#default_value' => variable_get('cotranslate_default_country', 'ru'),
  );
  //...
  return $form;

Теперь предположим, что мы создали страницы с описаниями разных стран. И хотим чтобы пользователь мог добавить город не через меню «Создать материал» — «Город», а со страницы с описанием страны. Что логично: вот пользователь смотрит на страницу «Россия» и сразу хочет добавить свой родной город.

Первое, что мы можем по этому поводу сделать — это разместить ссылку на 'node/add/city'. Но в таком случае пользователю придется в форме добавления города выбирать страну из списка, а ведь он уже сделал этот выбор, нажав «Добавить город» именно на странице с описанием нужной страны. Очевидно, следует учесть выбор пользователя и при создании формы установить заданную страну как дефолтную в списке 'countries'.

Перво-наперво, изменим ссылку на форму добавления нода. Вместо 'node/add/city' будем писать 'node/add/city/<country>', где вместо <country> подставим код страны. Например, 'node/add/city/ru'. Тогда можно просто воспользоваться элементом адреса в функции создания формы:

function city_form(&$node, $form_state) {
  //...
  $countries_list = _get_countries_list();
  if ((arg(3)) && isset($countries_list[arg(3)])) {
    $default_country = arg(3);
  } else {
    $default_country = variable_get('cotranslate_default_country', 'ru');
  }
  $form['countries'] = array(
    '#type' => 'select',
    '#title' => t('Country'),
    '#options' => $countries_list,
    '#default_value' =>$default_country,
  );
  //...
  return $form;

Довольно простое решение. Теперь усложним задачу. Допустим, что описание каждой страны у нас находится по адресу '/countries/<country>' (например, '/countries/ru'), а города — по адресам 'countries/<country>/cities/<city>'. Тогда ссылка «Добавить город», ведущая на адрес 'node/add/city/<country>' несколько не вписывается в логику адресов. Логичнее было бы разместить форму по адресу 'countries/<country>/cities/new'. Для этого создадим в нашей реализации hook_menu() соответствующую запись:

function city_menu() {
  //...
  $items['countries/%/cities/new'] = array(
    'title' => 'Add city',
    'description' => 'Add city to country',
    'page callback' => 'node_add',
    'page arguments' => array('city'),
    'access arguments' => array('access content'),
    'type' => MENU_CALLBACK,
  );
  //...
}

Функция node_add('city') создаст на заданных страницах форму добавления нода «Город». Казалось бы, теперь нужно только заменить arg(3) на arg(1) в функции city_form() и все будет работать. Однако, вместо этого, при попытке входа на страницу 'countries/ru/cities/new' мы увидим WSOD, а в отчетах появится сообщение об ошибке:

warning: call_user_func_array(): First argument is expected to be a valid callback, 'node_add' was given in /srv/www/htdocs/mysite/includes/menu.inc on line 348.


Оказывается, node_add() для нас недоступна. Имитировать вызов node_add(), скопировав ее код в другую функцию бесполезно — Друпал ругнется на вызов city_node_add в функции drupal_get_form(). Как же быть?

Непродолжительное копание в API показало, что у элементов меню, добавляемых в hook_menu(), есть интересные поля 'file' и 'file path'. Первое позволяет задать файл, в котором находится callback для этого элемента меню, а второе — путь к этому callback-у. Наш hook_menu() изменится так:

function city_menu() {
  //...
  $items['countries/%/cities/new'] = array(
    'title' => 'Add city',
    'description' => 'Add city to country',
    'page callback' => 'node_add',
    'page arguments' => array('city'),
    'access arguments' => array('access content'),
    'type' => MENU_CALLBACK,
    'file' => 'node.pages.inc',
    'file path' => drupal_get_path('module', 'node'),
  );
  //...
}

Тогда все заработает.

Комментарии