Drupal 7 页面传递回调

或者,如何使用完全不同的模板呈现 Drupal 页面。

我最近有一个需求,我需要让 Drupal 呈现一个与网站的正常页面布局完全分开的 HTML 页面。这实际上是 API 回调的一部分,但这让我参与了研究 Drupal 7 中传递回调的工作方式。没有必要仅仅为了渲染带有一些自定义 HTML 的单个页面的工作而创建一个新主题,尤其是因为 Drupal 有提供这种内置的机制。

如果您做过很多 Drupal 编程,您可能已经使用了传递回调,但实际上并没有意识到它。调用函数喜欢drupal_access_denied()drupal_not_found()会分别发出403或404页,但他们通过烧成页面,这使得交付回调的工作,然后发送页面输出到浏览器。这些函数通过调用函数来工作,drupal_deliver_page()函数又会触发当前选择的传递回调函数被调用。默认情况下, this is drupal_deliver_html_page(),它使用当前主题呈现 HTML 输出,添加适当的标题,并将此内容发送到浏览器。

有两种方法可以更改传递回调,使用哪一种取决于您的需要。

实施 hook_page_delivery_callback_alter()

就在drupal_deliver_page()finally 调用之前drupal_deliver_html_page(),安装的模块有机会通过hook_page_delivery_callback_alter()钩子改变传递回调。这个钩子允许你根据某些参数改变传递回调函数。下面显示了用于更改站点首页的传递回调函数的钩子。稍后我们将介绍自定义交付功能的内容。

/**
 * Implements hook_page_delivery_callback_alter().
 */
function mymodule_page_delivery_callback_alter(&$delivery_callback) {
  if (drupal_is_front_page()) {
    $delivery_callback = 'mymodule_deliver_page';
  }
}

这个钩子不仅可以用来改变页面模板。因为它是在页面回调运行之后调用的,但在页面(以及大部分,如果不是全部内容)呈现之前,它是在页面呈现之前更改某些内容的好方法。例如,我发现这个钩子是改变页面菜单位置的好方法。以以下愚蠢的示例为例,这会移动 Cron 设置页面,使其看起来位于模块页面下方。

/**
 * Implements hook_page_delivery_callback_alter().
 */
function mymodule_page_delivery_callback_alter(&$delivery_callback) {
  if (implode(arg(), '/') == 'admin/config/system/cron') {
    menu_tree_set_path('management', 'admin/modules');
  }
}

这本身并没有用,但是让某些页面出现在某些菜单项下是难以正确解决的常见 Drupal 问题之一。这个钩子提供了一个很好的方法来做到这一点。

菜单传递回调

提供自定义回调的第二种方法是使用菜单挂钩项的传递回调属性。此选项意味着菜单回调的输出将自动传递到此传递回调,而无需定义任何挂钩。以下示例在菜单挂钩和非常简单的页面回调函数中显示了这一点。

function mymodule_menu() {
  $items['mymodule/page'] = array(
    'title' => 'A Page',
    'page callback' => 'mymodule_return',
    'access callback' => TRUE,
    'delivery callback' => 'mymodule_deliver_page',
    'type' => MENU_CALLBACK,
  );
 
  return $items;
}
 
function mymodule_return() {
  return '<p>' . t('The current timestamp is') . ' : ' . time().'</p>';
}

这个菜单页面回调不需要做任何特别的事情,但它产生的所有内容都被馈送到传递回调函数。页面回调函数也不需要有什么特别之处,它可以像往常一样返回一个字符串或一个 Drupal 渲染数组。在当前状态下,上面的代码将产生一个白屏,并且在看门狗表中将看到错误消息“callback mymodule_deliver_page not found: mymodule/page”。这是因为缺少deliver 回调函数,所以我们来看看创建一个。

创建交付回调函数

为了使上述代码示例正常工作,您需要创建一个传递回调函数。用最简单的术语来说,传递回调函数必须简单地打印出页面的内容。下面虽然简单,但满足了这个要求。

function mymodule_deliver_page($page_callback_result) {
  print render($page_callback_result);
}

有了这个,上面的代码示例现在可以工作了。尽管页面将是空白的,因为这仅提供内容,而没有其他 HTML 或样式。

在创建您自己的传递回调之前,了解 Drupal 在这方面的工作方式很重要。Drupal 7 中 HTML 页面的传递回调函数被调用drupal_deliver_html_page(). 这个函数在这里重现有点太长了,但是它通过适应 Drupal 在页面渲染过程中返回的不同类型的变量来工作。如果页面回调返回一个整数,则该函数会将其视为错误,并将使用错误代码返回该错误的正确标题和页面。这是一个有用的效果,因为它意味着要生成访问被拒绝的页面,您只需要从页面回调中返回 MENU_ACCESS_DENIED 常量。MENU_ACCESS_DENIED 是一个整数常量,这意味着 Drupal 将返回 403 HTTP 状态代码和访问被拒绝页面。如果页面回调返回一个字符串或数组,则将其传递给drupal_render_page()函数,该函数将正常呈现页面。

我们可以将drupal_deliver_html_page()功能减少到最基本的要求,并在单个模板中呈现我们的页面内容。下面将在一个简单的 HTML 结构中呈现页面内容之前发出几个标题。最后的drupal_page_footer()函数是一个 Drupal 实用程序函数,它编写会话并调用任何关闭函数,这些函数应该始终在页面末尾调用。

function mymodule_deliver_page($page_callback_result) {
  if (is_null(drupal_get_http_header('Content-Type'))) {
    drupal_add_http_header('Content-Type', 'text/html; charset=utf-8');
  }
 
  // 为浏览器和搜索引擎发送适当的 HTTP 标头。
  global $language;
  drupal_add_http_header('Content-Language', $language->language);
 
  // 呈现页面和内容
  print '<html><head></head><body>';
  print render($page_callback_result);
  print '</body></html>';
 
  drupal_page_footer();
}

为了使其更易于维护,您显然会将其包装在主题函数中,但此示例作为一个简单示例工作。我使用了上述代码的一个版本(通过模板文件传递输出)来生成 API 回调所需的输出。这里的结果是,如果输出由于某种原因需要更改,那么所有需要更改的就是模板文件,其他所有内容都由模块处理。

还要记住,我们在这里基本上绕过了 Drupal 访问被拒绝的逻辑,因此您需要非常确保您返回的输出是合适的。如果您使用hook_page_delivery_callback_alter()钩子来改变页面交付,这一点尤其重要。您可以很容易地打开未发布的内容或用户个人资料的详细信息,以便匿名查看,所以要小心!

返回 JSON 输出

也许对传递回调最有用的事情是以不同的格式返回输出。这可以通过稍微调整mymodule_deliver_page()函数来轻松完成,以便它侦听不同类型的标题,为遇到的每种 mime 类型稍微调整输出。

<?php
function mymodule_deliver_page($page_callback_result) {
  $content_type = drupal_get_http_header('Content-Type');
 
  if (is_null($content_type)) {
    drupal_add_http_header('Content-Type', 'text/html; charset=utf-8');
  }
 
  // 为浏览器和搜索引擎发送适当的 HTTP 标头。
  global $language;
  drupal_add_http_header('Content-Language', $language->language);
 
  switch ($content_type) {
    case 'application/json':
      print json_encode($page_callback_result);
      break;
    case 'application/xml':
      print '<?xml version="1.0" encoding="UTF-8"><root>' . render($page_callback_result) . '</root>';
      break;
    default:
      print '<html><head></head><body>';
      print render($page_callback_result);
      print '</body></html>';
  }
  drupal_page_footer();
}

为了生成 JSON 输出,我们现在需要做的就是确保页面回调设置一个“application/json”标头。

function mymodule_return() {
  drupal_add_http_header('Content-Type', 'application/json');
  return array(t('The current timestamp is') . ' : ' . time());
}

这将生成以下输出。

["The current timestamp is : 1440147347"]

我们还可以通过一些简单的更改切换到 XML 输出。

function mymodule_return() {
  drupal_add_http_header('Content-Type', 'application/xml');
  return t('The current timestamp is') . ' : ' . time();
}

这使得在 Drupal 中生成简单的 API 服务变得非常容易。Drupal 假定您的内容将是 HTML,但是通过几行代码,您可以让 Drupal 以您喜欢的任何格式返回内容。