Не работает поле типа file (загрузка в него файла через AJAX) для анонимного посетителя в D7

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

Аватар пользователя heron77 heron77 4 марта 2022 в 11:50

Drupal пользуюсь с 2012 года больше как админ, меньше как программист. И вот только в 2022 (через 10 лет) столкнулся с интересной проблемой в D7 (на D9 пока не проверял).

Суть проблемы - невозможно подгрузить в форму файл в любое второе файловое поле в режиме AJAX для не авторизованного пользователя. Причина этой проблемы зарыта глубоко. Я уже сталкивался с этой причиной, но совершенно по другому поводу. Это если кратко.

Полностью описание проблемы выглядит вот так.

1. Создаем любую форму (я создавал через Entityform, но с нодой такая же фигня будет) в которой больше, чем одно поле типа file. Например, это будет два поля (а хоть и 10 - без разницы). Пусть оба поля будут множественными, то есть позволят загружать не 1 файл в поле, а несколько. Можно сделать их и для 1 значения - тоже без разницы, но с множественным полем один эффект интересный проявляется.

2. Делаем форму доступной для заполнения анонимным посетителем.

3. Сначала проверяем без AJAX. Загружаем в первое попавшееся поле (в любое из двух, но выбранного в первую очередь для загрузки файла) файл. Как обычно кликаем на кнопку "Обзор"(в FF или "Выбрать файлы" в Chrome) выбираем файл. Точно так же делаем на втором файловом поле. Нажимаем кнопку "Отправить" внизу формы. И вуаля!!! Файлы нормально отправились в оба поля и загрузились на сайт в соответствующую папку. Все прекрасно работает. Если установлен модуль Multiupload, то точно также в каждое поле выбираем по нескольку файлов одновременно и то же самое - все прекрасно работает

4. Теперь проверим, а как там AJAX? (Утром мажу бутерброд — Сразу мысль: а как народ?). Делаем точно так же как и в п.3 (выше) для любого из полей кликаем по кнопке "Обзор", выбираем файл. НЕ НАЖИМАЕМ кнопку "Отправить" (sic!), а кликаем по кнопке "Закачать". Отлично - файл закачался (это как раз AJAX отработал)! На этом же самом поле опять нажимаем "Обзор", выбираем другой файл и опять по кнопке "Закачать". И опять отлично все закачалось в ЭТО же поле! А вот со вторым полем такой финт ушами уже проделать не получится!!! При попытке закачать во второе поле файл таким же образом - "Обзор"+"Закачать" поле исчезнет и вместо него выскочит огненная надпись не имеющая никакого отношения к возникшей ситуации: "Неустранимые ошибки. Размер загружаемого файла вероятно, превысила максимально допустимый размер файла (100 МБ), который поддерживает данный сервер." (у меня на серваке ограничение в 100М) (in english: "An unrecoverable error occurred. The uploaded file likely exceeded the maximum file size (@SiZE) that this server supports.")

Резюмируем:
После того, как в любое первое поле загружен файл(файлы), во второе любое поле уже загрузить ничего не возможно анониму через AJAX

Причина понятна и на самом деле давно известна, хоть в данном случае и не сразу очевидна. Пока не проверял в D9, но в D7 давно существует один досадный недостаток. D7 ничего не хочет хранить о пользователе-анониме. Как будто это не один из самых навороченных CMS/CMF, а какой-то статичный HTML. С этой ерундой сталкивался когда лет 8 назад пытался реализовать переключение основной темы на тему для слабовидящих. Без костылей такое переключение для анонимов у меня не работало. То есть, тема переключалась, но после нажатия на любую ссылку и переходе на другую страницу возвращалась основная тема. D7 просто не запоминал какая тема включена у анонима. Естественно, что для любого авторизованного все работало "из коробки".

Продолжаем разбор файловых полей в форме. То сообщение об ошибке, которое я упомянул в п.4. Выдает (что логично) модуль ядра D7 file. А именно функция file_ajax_upload() (что тоже логично). Вот только смысл сообщения вообще не разу не логичен, если посмотреть в каком случае он выскакивает.

Привожу кусок кода этой функции, чтобы читателям не надо было лезть в исходники. Как раз там все в самом начале.

<?php
function file_ajax_upload() {
  
$form_parents func_get_args();
  
$form_build_id = (string) array_pop($form_parents);
  
  
// Sanitize form parents before using them.
  
$form_parents array_filter($form_parents'element_child');
    
  if (empty(
$_POST['form_build_id']) || $form_build_id != $_POST['form_build_id']) {
    
// Invalid request.
    
drupal_set_message(t('An unrecoverable error occurred. The uploaded file likely exceeded the maximum file size (@SiZE) that this server supports.'arr
    $commands 
= array();
    
$commands[] = ajax_command_replace(NULLtheme('status_messages'));
    return array(
'#type' => 'ajax''#commands' => $commands);
  }
?>

Кому лень изучать, объясняю.
В переменную $form_build_id загружается уникальный id существующей формы. Выглядит он примерно вот так: "form-GkKEX24dWY24r3M3cILOPUOvRZUDWlB7I7HAOAgccMY"
И в переменной $_POST['form_build_id'] тоже хранится (передался в POST-запросе) уникальный идентификатор той же самой формы. А теперь сюрприз, сюрприз!!! Для анонима $form_build_id будет заполнен идентификатором, который автоматически сгенерировался при загрузке файла в первое поле, а вот для второго поля он еще не был сгенерирован, а D7-то не помнит нихера (Дед старый! Не помнит нихера! (с) Брат-2) и для второго поля генерирует его заново и естественно совершенно другой - не совпадающий с первым. Далее обе переменные сравниваются и раз они не совпадают - выдается сообщение об ошибке.

Только я не понял, а причем тут размер файла?!?!?!

Теперь вопрос знатокам.
1. Кто, что порекомендует в данной ситуации?
2. Как правильно для этой функции file_ajax_upload() нарисовать ХУК, чтобы перед ее выполнением сделать соответствующие проверки и исправления значений переменных.

P.S. Так, как у меня очень сильно горит по срокам нормальная работа этой функции, то пока буду влезать в ядро и патчить эту конкретную функцию. Код позже выложу сюда в комментариях для обсуждения и возможной корректировки.

Комментарии

Аватар пользователя heron77 heron77 4 марта 2022 в 12:07

Пока решил сделать так. Проверяю пользователя. Если это аноним, то просто закидываю значение из $form_build_id в $_POST['form_build_id'] перед условием проверки на совпадение . Во так:

<?php
  $form_parents 
array_filter($form_parents'element_child');

  global 

$user;
  
$user_id $user->uid;
  if (
$user_id == && !empty($_POST['form_build_id']) && !empty($form_build_id)) 
        
$_POST['form_build_id'] = $form_build_id;

  if (empty(

$_POST['form_build_id']) || $form_build_id != $_POST['form_build_id']) { //...

?>