Drupal 5 - UIEForum i Captcha

Tags:

Niedawno spotkałem się z problemem jak "zmusić" UIEForum do poprawnej współpracy z captcha. Rozwiązanie jest dość proste, jednak dojście do niego wymagało przestudiowania obu w dodatku pisanych innymi standardami modułów. W tym artykule zamieszczam obszerny opis, ponieważ nie tylko samo rozwiązanie będzie przydatne, ale także wiedza na temat zasad działania poszczególnych funkcji w Drupalu, które przy okazji omówię.

W Drupalu mamy kilka możliwości zintegrowania forum. W samym CMSie jest domyślne forum, oparte na Drupalowym systemie node, jest ono doskonale z nim zintegrowane, niestety jego funkcjonalność jest mocno ograniczona. Dla kilku popularnych skryptów for (Simple Machines Forum, phpBB) powstały tzw. mosty, pozwalające je połączyć z Drupalem. Takie rozwiązanie z kolei oferuje znacznie większą, dobrze znaną użytkownikom funkcjonalność, jednak nie pozwala to m.in. na korzystanie z niektórych udogodnień Drupala, jak tryb multisite, poza tym często sprawia kłopoty przy instalacji czy administracji. Dlatego dużą popularnością cieszy sie moduł UIEforum, który jest rozwiązaniem dedykowanym dla Drupala, przy czym w wyglądzie i działaniu bardzo przypomina popularne skrypty for.

Jednak moduł ten w znacznym stopniu jest niezgodny ze standardami zalezanymi przez Drupala, m.in. tylko częściowo korzysta z FormsAPI. Jednym z popularnych problemów tym spowodowanych jest fakt, że moduły captcha, które pozwalają dodać do dowolnych formularzy używających FormsAPI pole na wpisanie kodu (np. z obrazka, bądź działania matematycznego).

UIEForum korzysta z FormsAPI na etapie generowania samego formularza, więc pole captcha poprawnie jest dodawane przy np. pisaniu postu. Jednak dane wysłane przez formularz nie są już obsługiwane przez specjalnie do tego celu zaprojektowane funkcje Drupala (hook_validate/drupal_validate_form i hook_submit/drupal_submit_form). Zamiast tego UIEForum posiada własne funkcje, operujące na zmiennej superglobalnej $_POST. Jest to spotykana praktyka wśród programistów początkujących w Drupalu, ponieważ dane przesłane przez formularz nie są w kodzie "widoczne", tylko większość operacji na nich wykonuje się z użyciem funkcji z API Drupala.

Przyjrzyjmy się jak działaniu modułu podczas dodawania postów. W pliku uieforum.module, funkcja _uieforum_all(), kod:

  1. {
  2.       $editpost = false;
  3.       if (uie_id('c') == 'newthread' || uie_id('c') == 'newpost' || uie_id('c') == 'editpost')
  4.       {
  5.         if (uie_id('c') == 'editpost') $editpost = true;
  6.         if (uie_id('c') == 'newthread' || uie_id('c') == 'editpost') uie_id('c', 'newpost');
  7.       }
  8.       include(uie_id('c').'.php');
  9.     }

inkluduje w przypadku edycji/dodawania posta plik newpost.php.

Jeśli formularz nie został jeszcze wysłany, to nie jest wykonywany kod po warunku

  1. if (form_get_errors() == null && isset($_POST['NewPost']))
  2.   {

i $SHOW_POST_INCLUDE=true w warunku
  1. if($SHOW_POST_INCLUDE)
  2.   {
  3.   ...
  4.   include "post_include.php";
  5.  

W pliku post_include.php interesuje nas linia
  1.   $form = drupal_get_form('uieforum_submit_post_form', $editpost);

Funkcja drupal_get_form wykonuje kolejno:
1) pobiera z uieforum_submit_post_form($editpost) (podane w parametrach) tablicę odwzorowującą formularz
2) wywołuje funkcję drupal_prepare_form(), która m.in. dodaje zmiany w formularzu dodawane przez inne moduły, czyli w tym momencie jest dodawane do formularza pole captcha
3) następnie wykonuje drupal_process_form(), która sprawdza poprawność wysłanych formularzem danych (o ile zostały wysłane), i jeśli wymagane pola są niewypełnione to za pomocą form_set_error() zapisuje błędy, które można potem odczytać za pomocą form_get_errors()
4) na końcu generuje kod HTML formularza, który tutaj jest przekazywany do zmiennej $form

Do tego momentu wszystko odbywa się z użyciem Drupalowego FormsAPI. Jednak po wysłaniu formularza, w uieforum.module na początku funkcji _uieforum_all(), uieforum_process() wywołuje funkcję sprawdzającą poprawność wysłanego formularza - uieforum_validate_form() (plik functions.inc.php). Ta funkcja pobiera dane bezpośrednio z $_POST (zamiast z uzupełnianej automatycznie przez drupalowe API tablicy reprezentującej strukturę formularza), i w razie błędów w wypełnieniu formularza wywopłuje wspomnianą form_set_error(). Tutaj jest pierwsze odstępstwo od FormsAPI, ponieważ do tego celu służy albo wywoływany automatycznie hook_validate, albo drupal_validate_form. Ma to taką konsekwencję, że sprawdzana jest tylko poprawność pól definiowanych przez moduł UIEForum, a dodatkowych jak captcha już nie.

Więc nawet jeśli captcha zostanie błędnie wypełnione, to w newpost.php kod po warunku

  1. if (form_get_errors() == null && isset($_POST['NewPost']))
  2.   {

zostanie wykonany. Ten kod wykonuje dalsze sprawdzanie m.in. uprawnień dostępu, i dodaje post za pomocą funkcji odpowiednio uieforum_create_thread(), uieforum_edit_post(), uieforum_add_post(). Jest to kolejne odstępstwo od FormwAPI - tutaj powinnien zostać użyty automatycznie wywoływany hook_submit, bądź drupal_submit_form. Jednak jest to w naszym przypadku mało istotne.

Aby captcha było sprawdzane, należy przed tym warunkiem dodać kod, który będzie wywoływał sprawdzanie formularza wraz z dodanymi przez inne moduły (poprzez mechanizm drupal_form_alter) polami. Jest to m.in. wspomniana funkcja drupal_get_form(), jednak z niewiadomych mi przyczyn przy wywołaniu jej przed wspomnianym warunkiem, nie działa poprawnie dodawanie formularza. Zamiast tego przed linią

  1. if (form_get_errors() == null && isset($_POST['NewPost']))

wstawimy taki okrojony kod tej funkcji:
  1. if (isset($_POST['NewPost'])) {
  2.     $aaa = drupal_retrieve_form('uieforum_submit_post_form');
  3.     drupal_prepare_form('uieforum_submit_post_form',$aaa);
  4.     drupal_process_form($_SESSION['form'][$_POST['form_build_id']]['args'][0],$aaa);
  5.   }

Działanie tych funkcji już opisałem, w skrócie dodajemy tutaj dodatkowe pola do tablicy reprezentującej formularz, a następnie wykonujemy sprawdzanie danych wysłąnych przez formularz. Dzięki temu przy błędnie wypełnionym captcha, form_get_errors zwraca już ten błąd, i wyświetlany jest formularz do poprawienia zamiast zapisywania go.