覆盖Drupal 6自动选择元素验证

尽管最近在Drupal 6中创建了一个大型而复杂的表单,但是我遇到了一个问题,这个问题花了我几个小时才能解决,因此我将解决方案发布在这里,以防其他人同样陷入困境。我也将其写下来,这样我就可以记住该策略,以防万一我不得不再次做同样的事情。

我正在做的是创建活动预订表。该表格允许用户从选择列表中的多个时段中进行选择,但每次预订的数量均有限。当用户提交表单并且所有分配给他们所选时间的空位都已被预订满时,他们会看到一条消息,要求他们选择其他时间空位。原始时间已从选择列表中删除,以便用户不再尝试再次选择它。很好,一切都按预期工作,但是当发生这种情况时,我还收到了Drupal的以下错误消息。

An illegal choice has been detected. Please contact the site administrator.

此错误消息是_form_validate()Drupal进行的内部表单验证(通过函数)产生的,以确保您为select元素发布的项目与列表中的项目之一匹配。如果您允许用户选择一个项目,然后在发布请求后重新创建表单时将其删除,则您将收到此消息,因为选择表单中不存在发布值。这是一种安全措施,可防止用户入侵HTML并将自己的项目添加到选择列表中。Drupal会仔细检查输入的元素是否与列表中的现有元素之一匹配,如果不匹配,将使验证失败。

在尝试了各种不同的策略以消除这种情况下的这种情况后,我最终想到了按正常方式创建表单并将临时值添加到表单选择列表中的想法。然后使用表单主题挂钩来扫描并删除已被人为添加的选择项。这使我可以绕过(至少在一定程度上)内部的Drupal表单验证,并包括我自己的表单验证,以确保不允许再次预订所有已预订的空位。

我没有从客户项目中复制代码,而是创建了一个小示例模块,该模块执行了上面已详述的操作,以便可以描述每个步骤。我已经使用随机数生成了select元素,其效果与使用实数据相同。第一步是创建一个名为select_validate的模块文件夹以及.info和.module文件。这是.info文件的内容。

; $Id:$
name = Select Validate
description = "Select validate"
core = 6.x

通过创建菜单挂钩(带有关联的页面回调)和主题挂钩来启动模块文件,稍后我们将使用它们。由于尚未创建select_validate_select_form()定义,因此该代码将不会运行。

<?php
 
/**
 * Implementation of hook_menu
 *
 * @return array An array of menu items.
 */
function select_validate_menu() {
  $items = array();
 
  $items['select_validate'] = array(
    'page callback' => 'select_validate_print_form',
    'title' => 'Menu Example',
    'access callback' => TRUE,
  );
 
  return $items;
}
 
/**
 * Callback function for the select_validate page.
 *
 * @return string The contents of the page.
 */
function select_validate_print_form() {
  $output = '';
  $output .= drupal_get_form('select_validate_select_form');
  return $output;
}
 
/**
 * Implementation of hook_theme.
 *
 * @return array An associative array of theme hook information.
 */
function select_validate_theme() {
    return array(
        'select_validate_select_form' => array(
            'arguments' => array('form' => NULL),
        ),
    );
}

下一步是创建表单定义功能。该函数将创建一个简单的表单,其中包含由5个随机数组成的单个选择元素。重要的部分在第23行附近,在该行中,我在此select语句中添加了对以前发布的值的检查。如果此处不存在选择列表中存在的任何内容则将它们添加到选项的$random_list数组中,并带有“ DELETE”标签。然后,我们可以在对表单进行主题设置时使用此字符串来删除该值。

/**
 * Generates the select validate form.
 * 
 * @param array $form_state The form state array.
 *
 * @return array An associative array of form items.
 */
function select_validate_select_form(&$form_state) {
    $form = array();
 
    $form['#method'] = 'post';
    $form['#validate'][] = 'select_validate_select_form_validate';
    $form['#submit'][] = 'select_validate_select_form_submit';
 
    $random_list = array('' => '--');
 
    for ($i = 0; $i < 5; ++$i) {
        $rand = rand();
        $random_list[$rand] = $rand;
    }
 
    // 检查以前发布的值。
    if (isset($form_state['post']['randselect']) && !in_array($form_state['post']['randselect'], array_keys($random_list))) {
         $random_list[$form_state['post']['randselect']] = 'DELETE';
    }
 
    $form['randselect'] = array(
        '#type' => 'select',
        '#title' => t('Please select'),
        '#required' => TRUE,
        '#options' => $random_list,
    );
 
    $form['submit'] = array(
        '#type' => 'submit',
        '#value' => t('Go'),
    );
 
    return $form;
}

下一步是为我们的表单创建主题功能。因为我们用与表单相同的ID(或函数名)设置了主题注册表,所以我们可以只包含一个称为的函数theme_select_validate_select_form(),它将自动使用。通过向select_validate_select_form()函数添加以下代码行,可以在表单定义中对表单的主题进行硬编码。

$form['#theme'] = 'select_validate_select_form';

主题功能仅用于扫描randselect选择元素以查找带有“ DELETE”标签的选项。如果找到它,则在发送表单以通过drupal_render()调用呈现之前将其删除。

/**
 * Theme the select validate form.
 *
 * @param array $form An associative array of form items.
 *
 * @return string The rendered form.
 */
function theme_select_validate_select_form($form) {
    foreach ($form['randselect']['#options'] as $id => $option) {
        if ($option == 'DELETE') {
            unset($form['randselect']['#options'][$id]);
        }
    }
 
    $output = drupal_render($form);
    return $output;
}

为了使上述代码完全运行,表单必须通过验证。下面的两个函数绑定到表单的validate和Submit调用中,但是在这种情况下,validate函数将始终引发错误。只需要证明上面的代码有效,并允许其他人根据自己的需要修改此代码,因此在此实例中不需要任何其他操作。

/**
 * Validation function for the select validate form.
 *
 * @param array $form An associative array of form elements.
 * @param array $form_state An associative array of the form state.
 */
function select_validate_select_form_validate($form, &$form_state) {
 
    form_set_error('randselect', t('Please select another option.'));
}
 
/**
 * Submit function for the select validate form.
 *
 * @param array $form An associative array of form elements.
 * @param array $form_state An associative array of the form state.
 */
function select_validate_select_form_submit($form, &$form_state) {
    // 保存数据
}

最后一点是,尽管可以执行此操作,但仅应在确实需要时使用它。内部验证功能是有充分理由的,在不十分确定自己的验证功能会很痛苦(或至少很危险)的情况下,像这样覆盖它们。我故意没有研究hacking内核,因为内部_form_validate()功能的动作对于阻止将胭脂项目添加到选择表单中很有用。