Предположим, мы создали модуль и в нем определили новый тип нода. Пусть это будет нод «Город» с внутренним именем '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'),
);
//...
}
Тогда все заработает.