Совсем недавно столкнулся с ошибкой при использовании модулей Drupal Commerce и Inline Entity Form. Суть ошибки заключается в том, что у товара имеется 2 атрибута: цвет и размер. В качестве атрибутов используется два словаря "цвета" и "размеры" соответственно. При создании товаров в обоих полях используются виджеты Autocomplete term widget.
Вот тут и начинаются все проблемы, при создании нового товара, в момент добавления вариантов если атрибуты добавляются первый раз (их еще нет в словаре), они (атрибуты) начинают дублироваться и логика работы ломается:

Выделенных красным цветом атрибутов в словаре нет и вот, что получилось при просмотре карточки товара:

Я нашел два варианта решения данной проблемы:
- Не использовать виджет Autocomplete term widget;
- Искать и решать проблему.
Мне первый способ не подошел, потому что клиенту нужен был именно автокомплит (оно и понятно, найти нужный атрибут среди сотни не так уж и просто). Я начал искать причину и выяснил, что проблема логична и не является проблемой модулей Drupal Commerce и Inline Entity Form. Так что же происходит и почему возникает баг? А происходит следующее:
- Когда пользователь пишет название атрибута (в нашем случае название термина), такое название ищется в словаре. В случае если похожие названия были найдены, пользователю предоставляются варианты для выбора;
- Если пользователь выбирает существующий термин, кликнув по одному из названий или пишет точно такое же имя, то термину присваивается id существующего термина и в момент сохранения сущности термин в словарь не добавляется;
- Если термин в словаре не найден, то термину в качестве tid временно присваивается 'autocreate' (
$term->tid = 'autocreate'
) и в момент сохранения сущности термин сохраняется в словарь как новый.
Как раз в последнем случае и возникает баг, мы добавляем первый раз несуществующий атрибут (термин), ему в качестве tid присваивается $term->tid = 'autocreate'
, затем второму варианту добавляем этот же атрибут (термин), но так как атрибута еще нет в словаре, ему так же в качестве tid присваивается $term->tid = 'autocreate'
и т.д., а в момент сохранения сущности ядро смотрит tid атрибутов и так как они равны 'autocreate' создает новые дублирующиеся атрибуты (термины).
Для устранения проблемы я написал маленький модуль (данный способ работает только в Drupal 7.8 и выше). Первым делом имплементируем хук hook_field_widget_form_alter:
/**
* Implements hook_field_widget_form_alter().
*/
function moduleName_field_widget_form_alter(&$element, &$form_state, $context) {
// Здесь мы добавляем дополнительный валидатор ко всем полям таксономии у которых в качестве виджета используется
// "Autocomplete term widget".
if ($context['field']['type'] == 'taxonomy_term_reference' && $context['instance']['widget']['type'] == 'taxonomy_autocomplete') {
$element['#element_validate'][] = '_moduleName_taxonomy_autocomplete_validate';
}
}
Далее добавляем свой валидатор:
/**
* Custom taxonomy autocomplete validation callback.
*/
function _moduleName_taxonomy_autocomplete_validate($element, &$form_state) {
$value = drupal_array_get_nested_value($form_state['values'], $element['#parents']);
foreach ($value as &$item) {
// Если у термина в качестве tid установлено значение 'autocreate', то добавляем к термину флаг,
// который будем использовать для дополнительной валидации в момент сохранения.
if ($item['tid'] == 'autocreate') {
$item['need_validate'] = TRUE;
}
}
form_set_value($element, $value, $form_state);
}
Осталось дело за малым, отлавливаем сохранение термина таксономии, проверяем наличие флага и если он есть, то делаем дополнительную проверку существования термина:
/**
* Implements hook_field_attach_presave().
*/
function moduleName_field_attach_presave($entity_type, $entity) {
// Если тип сохраняемой сущности термин и у него имеется флаг, который мы установили
// в предыдущей функции, то делаем дополнительную проверку.
if ($entity_type == 'taxonomy_term' && !empty($entity->need_validate)) {
unset($entity->need_validate);
// Проверяем в базе наличие термина с таким же именем.
$term = db_select('taxonomy_term_data', 't')
->fields('t', array('tid', 'name'))
->condition('t.vid', $entity->vid)
->condition('t.name', drupal_strtolower(trim($entity->name)))
->execute()
->fetchAssoc();
// Если термин с таким именем уже существует, то присваиваем сохраняемому термину
// имя и id существующего термина.
if (!empty($term)) {
$entity->name = $term['name'];
$entity->tid = $term['tid'];
}
}
}