在模块文件中覆盖 Drupal 7 主题函数

在 Drupal 7 中覆盖主题功能时,您通常会将主题功能复制到 中template.php并更改它以满足您的需要。但这并不总是很方便,尤其是当您尝试将功能抽象到模块中并且主题层中没有依赖于模块代码的代码时。如果您将主题覆盖函数添加到您的模块文件中,那么它们将不会做任何事情,因为 Drupal 不会在那里寻找它们并且不会选择它们。

为了让 Drupal 从你的模块代码中获取你的主题功能,可以改变主题注册表。这有助于收集执行特定任务的代码,并允许您部署主题更改以及模块更新。

为了演示如何做到这一点,我将介绍如何覆盖主题函数theme_date_popup(),该函数用于打印日期弹出表单元素(来自日期弹出模块)周围的包装器。我最近需要这样做,以便覆盖在此函数中创建的类之一,因此在我这样做时记录事情是有意义的。

该theme_date_popup()主题功能保存在文件date_popup / date_popup.module日期模块中,并如下所示。

/**
 * Format a date popup element.
 *
 * Use a class that will float date and time next to each other.
 */
function theme_date_popup($vars) {
  $element = $vars['element'];
  $attributes = !empty($element['#wrapper_attributes']) ? $element['#wrapper_attributes'] : array('class' => array());
  $attributes['class'][] = 'container-inline-date';
  // 如果没有描述,浮动日期元素需要在它们下方进行一些额外的填充。
  $wrapper_attributes = array('class' => array('date-padding'));
  if (empty($element['date']['#description'])) {
    $wrapper_attributes['class'][] = 'clearfix';
  }
  // 添加一个包装器来模拟单个值字段的工作方式,以便于使用 #states。
  if (isset($element['#children'])) {
    $element['#children'] = '<div id="' . $element['#id'] . '" ' . drupal_attributes($wrapper_attributes) .'>' . $element['#children'] . '</div>';
  }
  return '<div ' . drupal_attributes($attributes) .'>' . theme('form_element', $element) . '</div>';
}

为了覆盖这个主题函数的主题注册表,我们首先需要知道它是如何注册的。这可以通过查看hook_theme()Date Popup 模块中的钩子来发现,该模块用于向 Drupal 注册此主题功能。对于 Date Popup 模块,这称为date_popup_theme()。

function date_popup_theme() {
  return array(
    'date_popup' => array('render element' => 'element'),
    );
}

我们在这里需要做的就是使用hook_theme_registry_alter()自定义模块的钩子来更改主题注册表。在这个钩子中,我们查找“date_popup”主题注册表项,并强制对 date_popup 主题函数的任何调用通过我们选择的函数。这是hook_theme_registry_alter()我们自定义模块中的完整实现。

/**
 * Implements hook_theme_registry_alter().
 */
function custom_module_theme_registry_alter(&$theme_registry) {
  if (isset($theme_registry['date_popup'])) {
    $theme_registry['date_popup']['function'] = 'custom_module_theme_date_popup';
  }
}

我们现在可以theme_date_popup()从 Date Popup 模块复制主题函数并覆盖它。只要函数名称与我们在 中设置的自定义函数名称匹配hook_theme_registry_alter(),就会使用它。在这里,我们只是向表单字段包装器添加一个自定义类。

/**
 * Overrides theme_date_popup().
 *
 * Format a date popup element.
 *
 * Use a class that will float date and time next to each other.
 */
function custom_module_theme_date_popup($vars) {
  $element = $vars['element'];
  $attributes = !empty($element['#wrapper_attributes']) ? $element['#wrapper_attributes'] : array('class' => array());
  $attributes['class'][] = 'container-inline-date';
  // 如果没有描述,浮动日期元素需要在它们下方进行一些额外的填充。
  $wrapper_attributes = array('class' => array('date-padding'));
 
  // 添加我们自己的自定义类。
  $wrapper_attributes['class'][] = 'custom-css-class';
 
  if (empty($element['date']['#description'])) {
    $wrapper_attributes['class'][] = 'clearfix';
  }
  // 添加一个包装器来模拟单个值字段的工作方式,以便于使用 #states。
  if (isset($element['#children'])) {
    $element['#children'] = '<div id="' . $element['#id'] . '" ' . drupal_attributes($wrapper_attributes) . '>' . $element['#children'] . '</div>';
  }
  return '<div ' . drupal_attributes($attributes) . '>' . theme('form_element', $element) . '</div>';
}

此技术可用于任何主题功能,并且在您希望将功能组合到单个模块中时非常有用。这还有一个好处,即不会template.php用大量覆盖来弄乱您的主题文件。

当然,这并不全是好消息。也许这里需要注意的最重要的事情是可能不清楚是什么函数覆盖了页面上的元素。这可能会给维护带来一些麻烦,因为开发人员会倾向于假设所有主题覆盖都保留在主题中。要解决这个问题,您可以 grep 调用代码hook_theme_registry_alter()以查看添加了任何自定义覆盖的位置。这可能有点麻烦,因此应在绝对需要时使用此技术。一个很好的例子是,当您创建一个将在多个站点或主题上使用的模块时,需要完成相同的覆盖才能创建所需的功能。以这种方式将您的主题覆盖添加到您的模块文件中,您可以自行激活模块,而不必更改主题。