8

Is there a way to override the output of a page from a contributed module and issue your own html.tpl.php, effectively taking control of the output from the theme?

I want to do this in effort to create a unique login/registration experience, but can only seem to override the page level template, not the html level template. I see that the print module kind of does this, but is that the way to approach it?

7
  • I don't have time to flesh it out right now, but I think you could use hook_menu_alter() to change the delivery callback for the user/login path to your own version of drupal_deliver_html_page(). That should give you absolute control over what is rendered to the screen, although it will mean setting appropriate headers yourself
    – Clive
    Jan 3, 2013 at 18:52
  • Yeah, thats where I started heading, but wasn't sure if you would need to do all that legwork.
    – Kevin
    Jan 3, 2013 at 19:14
  • I'm not sure there's a way round it to be honest, a good example from core is the ajax_deliver() function, which gets the same $page_callback_result as drupal_html_deliver_page() but processes it differently. I'm not sure if you can interrupt the process any further down in a meaningful way before the theme engine gets involved
    – Clive
    Jan 3, 2013 at 19:30
  • Is there any specific reason to alter the output of html.tpl.php? There are many functions that alter what that template file outputs.
    – apaderno
    Jan 7, 2013 at 1:41
  • @kiamlaluno, This is an interesting question. I'm also finding a way to cease the Drupal page rendering before the theme engine gets involved. The purpose is to render a page (some kind of web service) just like JSON output or whatever provided by Views Datasource when using_views_api_mode is OFF.
    – Sithu
    Jan 7, 2013 at 5:43

3 Answers 3

4

According to this answer, you could simply print the page content in the menu page callback rather than returning it.

To get data from Drupal's database and/or produced in PHP, you need a page callback (in a custom module) that output the data without the full layout rendering. This is easily doable by printing the content of the page directly in your page callback instead of returning it.

I guess the Print module implemented the printer-friendly page in this way. The following is the code snippet from the module.

function print_menu() {
  $items = array();

  $items[PRINT_PATH] = array(
    'title' => 'Printer-friendly',
    'page callback' => 'print_controller_html',
    'access arguments' => array('access print'),
    'type' => MENU_CALLBACK,
    'file' => 'print.pages.inc',
  );
  ........   
}   

/**
 * Generate an HTML version of the printer-friendly page
 *
 * @see print_controller()
 */
function print_controller_html() {
  $args = func_get_args();
  $path = filter_xss(implode('/', $args));
  $cid = isset($_GET['comment']) ? (int)$_GET['comment'] : NULL;

  // Handle the query
  $query = $_GET;
  unset($query['q']);

  $print = print_controller($path, $query, $cid, PRINT_HTML_FORMAT);
  if ($print !== FALSE) {
    $node = $print['node'];
    $html = theme('print', array('print' => $print, 'type' => PRINT_HTML_FORMAT, 'node' => $node));
    drupal_add_http_header('Content-Type', 'text/html; charset=utf-8');
    drupal_send_headers();
    print $html;
    ......
}

According to this, the module uses the custom HTML template print.tpl.php. It is a HTML-level template. The module then gets the HTML by calling theme('print',...) and render it directly to the browser using print $html;.

Here is a general idea for your purpose: mymodule.module

/**
 * Implements hook_menu().
 */
function mymodule_menu() {
  $items = array();
  $items['mylogin'] = array(
    'title' => 'Custom Login Page',
    'page callback' => 'mymodule_custom_login_page',
    'type' => MENU_CALLBACK,
    'access callback' => TRUE,
  );

  return $items;
} 
/**
 * Implements hook_theme().
 */
function mymodule_theme() {
  return array(
    'mylogin' => array(
      'variables' => array('page' => array()),
      'template' => 'mylogin', // mylogin.tpl.php in your module folder
    ),
  );
}
/**
 * Generate a custom login page
 * @see more in print_controller_html() in print.pages.inc of the Print module 
 */
function mymodule_custom_login_page(){
    $page = _mymodule_login_page_prerequisite(); // get/prepare necessary variables, js, css for the page
    $page['form'] = drupal_render(drupal_get_form('user_login')); // get login form
    // prepare html in mylogin.tpl.php
    // See more in print.tpl.php() in the Print module  
    $html = theme('mylogin', array('page' => $page)); 

    drupal_add_http_header('Content-Type', 'text/html; charset=utf-8');
    drupal_send_headers();
    print $html; // cease Drupal page rendering and render directly to the browser
} 
/**
 * Prepare the array for the template with common details
 * @see more _print_var_generator() in print.pages.inc of the Print module
 */
function _mymodule_login_page_prerequisite(){
    global $base_url, $language; 
    $page = array();
    $page['language']   = $language->language;
    $page['head']       = drupal_get_html_head();
    $page['title']      = '';
    $page['scripts']    = drupal_get_js();
    $page['favicon']    = '';
    // if there is a custom css file for this page
    // drupal_add_css(drupal_get_path('module', 'mymodule') . '/css/mylogin.css');
    $page['css'] = drupal_get_css();
    $page['message'] = drupal_get_messages();
    $page['footer_scripts'] = drupal_get_js('footer');

    return $page;
} 

Template: mylogin.tpl.php

<?php
/**
 * @file
 * Custom login page template
 *
 * @ingroup page
 */
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="<?php print $page['language']; ?>" xml:lang="<?php print $page['language']; ?>">
  <head>
    <?php print $page['head']; ?>
    <title><?php print $page['title']; ?></title>
    <?php print $page['scripts']; ?>
    <?php print $page['favicon']; ?>
    <?php print $page['css']; ?>
  </head>
  <body>
    <h3>This is custom login page.</h3>
    <?php 
    if (!empty($page['message'])):
        foreach($page['message'] as $type => $message):
        ?>
            <div class="messages <?php print $type; ?>">
                <ul>
                <?php foreach($message as $msg): ?>
                    <li><?php print $msg; ?></li>
                <?php endforeach; ?>
                </ul>
            </div>
        <?php
        endforeach;
    endif; ?>
    <div><?php print $page['form']; ?></div>
    <?php print $page['footer_scripts']; ?>
  </body>
</html>

I hope this will customize your login page as you need.

2

Both @Sithu and @Ayesh K have provided great answers. In this example, I will combine @Ayesh's method and parts of @Sithu's code for a complete solution.

hooks_menu or hook_menu_alter functions both provide a delivery callback, which instruct Drupal on how you want your code wrapped. By default, Drupal sets delivery callback to drupal_deliver_html_page(), which roughly tells Drupal to wrap your page in html.tpl.php and page.tpl.php.

To modify the way Drupal wraps your page, copy the function drupal_deliver_html_page() into your module, and modify it. Then call your new function in delivery callback. Drupal will then use that function to wrap your page.

Example

Here's a working module. Place the following code into your /sites/all/modules/MYMODULE directory and enable the module.

Optionally, to override an existing path, substitute hook_menu with hook_menu_alter.

MYMODULE.module

<?php
function MYMODULE_menu() {
  $items['login'] = array(
    'title' => 'Login',
    'page callback' => 'MYMODULE_page',
    'delivery callback' => 'MYMODULE_deliver',
    'access callback' => TRUE,
  );
  return $items;
}

function MYMODULE_page() {
  global $user;
  if (!$user->uid) return drupal_get_form('user_login'); // Show login for guests.
  else drupal_goto('user/' . $user->uid); // Redirect members to own profile.
}

// Code taken from drupal_deliver_html_page().
function MYMODULE_deliver($page_callback_result) {
  global $language, $base_path;
  // Pass variables to the template.
  $vars = array(
    'language' => $language->language,
    'title' => 'My Custom Login',
    'favicon' => '',
    'css' => $base_path . drupal_get_path('module', 'MYMODULE') . '/MYMODULE.css',
    'messages' => theme_status_messages(array('display' => NULL)),
    'content' => drupal_render($page_callback_result),
  );
  echo theme('MYMODULE_login', array('vars' => $vars)); // Uses template defined in hook_theme().
  drupal_page_footer();
}

function MYMODULE_theme() {
  $items['MYMODULE_login'] = array(
    'template' => 'MYMODULE',
    'render element' => 'page',
  );
  return $items;
}

MYMODULE.info

name = MYMODULE
description = "Module description."
package = Custom
core = 7.x

MYMODULE.tpl.php

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML+RDFa 1.0//EN" "http://www.w3.org/MarkUp/DTD/xhtml-rdfa-1.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="<?php print $vars['language']; ?>" version="XHTML+RDFa 1.0">
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <title><?php print $vars['title']; ?></title>
  <?php print $vars['favicon']; ?>
  <link rel="stylesheet" type="text/css" href="<?php print $vars['css']; ?>">
</head>
<body>
  <?php echo $vars['messages']; ?>
  <div class="content">
    <?php print $vars['content']; ?>
  </div>
</body>
</html>

MYMODULE.css

.content { color: pink; }
3
  • Question: does this example work with Drupal's caching system, and would it have separate caches for different query strings?
    – Darvanen
    Oct 30, 2015 at 5:59
  • I believe, by default Drupal caches the form and page, respectively. I am not sure of any other caching processes. Nov 2, 2015 at 21:41
  • Thanks. I ended up making a custom cache for my page responses using the API.
    – Darvanen
    Nov 3, 2015 at 0:49
1

I think you need to invest some time finding the most appropriate hook for this. You can try

  • hook_page_alter to change "what is going to be rendered",

  • use hook_menu_alter to change the delivery callback for login and register menu routers,

  • use a page--user-login.tpl.php file to take over the page template of the login page,

  • add some template suggestions in template.php to use a different html.tpl.php file just for the login paths,

  • or finally, hook_theme_regitry_alter, to alter the theme registry and do what you wanted to do (the html.tpl.php change)

1
  • +1 Spot on! hook_theme_registry_alter() might not work as it will most likely change the template for all pages, but delivery callback will definitely work. I've additionally explored this method in my answer, here. Dec 1, 2014 at 23:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.