11

I'd like to provide template implementation in my module and allow themes to override it. Basically, I add a suggestion by this simplified code:

function attach_preprocess_node(&$vars) {
  $vars['theme_hook_suggestions'][] = 'node__test';
}

(I don't want to use hook_theme to add a new theme because I want to reuse preprocess node functions. The theme name are awkward but I don't want to write node_attach_% to avoid confusion with node type.)

Then I use hook_theme_registry_alter() to add module path:

function attach_theme_registry_alter(&$theme_registry) {
  $path = drupal_get_path('module', 'attach') . '/themes';
  $theme_registry_copy = $theme_registry;
  _theme_process_registry($theme_registry_copy, 'phptemplate', 'theme_engine', 'node', drupal_get_path('module', 'node'));
  $theme_registry += array_diff_key($theme_registry_copy, $theme_registry);
  if (!isset($theme_registry['node']['theme paths'])) {
    $theme_registry['node']['theme paths'] = array();
  }
  if (!isset($theme_registry['node']['theme paths'])) {
    $first_element = array_shift($theme_registry['node']['theme paths']);
    if ($first_element) {
      array_unshift($theme_registry['node']['theme paths'], $first_element, $path);
    }
    else {
      array_unshift($theme_registry['node']['theme paths'], $path);
    }
  }
}

However it does not work. That means: the file themes/node--super.tpl.php is not used. It is used only if I copied it to the theme folder.

1
  • Clarification: I want Drupal to look in module directory (and also in theme directories) for a template defined by code (here: node template). I don't want to define a new template.
    – jcisio
    Commented Mar 24, 2012 at 22:42

5 Answers 5

4
+50

Basically you can save yourself a bit of headache by implementing hook_theme() instead of altering the registry.

I suggest a look at theming_example in the Examples project, handily reproduced on this API doc page, perhaps with especially helpful code on this page.

function theming_example_list_page() {
  $items = array(
    t('First item'),
    t('Second item'),
    t('Third item'),
    t('Fourth item'),
  );

  // First we'll create a render array that simply uses theme_item_list.
  $title = t("A list returned to be rendered using theme('item_list')");
  $build['render_version'] = array(
    // We use #theme here instead of #theme_wrappers because theme_item_list()
    // is the classic type of theme function that does not just assume a
    // render array, but instead has its own properties (#type, #title, #items).
    '#theme' => 'item_list',
    // '#type' => 'ul',  // The default type is 'ul'
    // We can easily make sure that a css or js file is present using #attached. 
    '#attached' => array('css' => array(drupal_get_path('module', 'theming_example') . '/theming_example.css')), 
    '#title' => $title, 
    '#items' => $items, 
    '#attributes' => array('class' => array('render-version-list')),
  );

  // Now we'll create a render array which uses our own list formatter,
  // theme('theming_example_list').
  $title = t("The same list rendered by theme('theming_example_list')");
  $build['our_theme_function'] = array(
    '#theme' => 'theming_example_list', 
    '#attached' => array('css' => array(drupal_get_path('module', 'theming_example') . '/theming_example.css')), 
    '#title' => $title, 
    '#items' => $items,
  );
  return $build;
}

This is all for Drupal 7.

6
  • As I said in the question, I don't want to use hook_theme() because I want to reuse $variables in the node template. Those variables are generated in hook_(pre)process of many modules who don't know the existence of my theme (if I define one).
    – jcisio
    Commented Mar 18, 2012 at 21:56
  • The way you define a theme is... with hook_theme(). :-) You can define theming functions in hook_theme(). Name them anything you want. Make shim functions if you want. API docs: "hook_theme_HOOK() ...should only be used if a module needs to override or add to the theme preprocessing for a theme hook it didn't define."
    – paul-m
    Commented Mar 19, 2012 at 2:22
  • I don't want to define a theme. If you define your theme 'mynode' instead of reusing 'node', you don't have any variable in your .tpl.php file.
    – jcisio
    Commented Mar 20, 2012 at 9:38
  • 1
    This doesn't seem true : theme calls are cumulative so implementing hook_theme should give you the $existing parameter which lets you modify the thing, not overriding it. If it's not the case then perhaps you're hitting a bug.
    – Countzero
    Commented Mar 23, 2012 at 9:03
  • @Countzero when you declare a theme node_attach, then all hook_preprocess_node functions won't happen with your new theme. I.e. you don't have anything in your node-attach.tpl.php.
    – jcisio
    Commented Mar 28, 2012 at 10:16
6

Maybe this one works:

/**
 * Implements hook_theme().
 */
function MODULE_theme($existing, $type, $theme, $path) {
  return array (
    'node__CONTENTTYPE' => array (
      'variables' => array( . . . ),
      'template' => 'node--CONTENTTYPE' ,
      'base hook' => 'node',
      'path' => drupal_get_path('module', 'MODULE'),
    ),
  );
}

Important here is the key 'base hook'.

2
  • Here's the issue for adding documentation for base hook: drupal.org/node/2106635
    – Andy
    Commented Jan 4, 2014 at 19:17
  • +1 upvote - this and the derivative answer from batigotix I found to work. Thanks. Commented Nov 13, 2015 at 11:55
2

I like dashohoxha's solution of the hook_theme implementation but was not able to get it working. After some more googling I found a variation that worked nice for me:

/**
 * Implements hook_theme().
 */
function mymodule_theme($existing, $type, $theme, $path) {
  $theme = array();
  $theme['node__blog_post'] = array(
    'render element' => 'content',
    'base hook' => 'node',
    'template' => 'node--blog_post',
    'path' => drupal_get_path('module', 'mymodule') . '/templates',
   );
  return $theme;
}

Notes: my custom module is called 'mymodule' and my custom content type is called 'blog_post'. The tpl.php that I use is called 'node--blog_post.tpl.php' and it is located in the 'templates' sub folder of my module.

1
2

Here is my snippet to declare views templates stored in the "template" folder of my "custom_module":

/**
 * Implements hook_theme_registry_alter().
 */
function custom_module_theme_registry_alter(&$theme_registry) {
  $extension   = '.tpl.php';
  $module_path = drupal_get_path('module', 'custom_module');
  $files       = file_scan_directory($module_path . '/templates', '/' . preg_quote($extension) . '$/');

  foreach ($files as $file) {
    $template = drupal_basename($file->filename, $extension);
    $theme    = str_replace('-', '_', $template);
    list($base_theme, $specific) = explode('__', $theme, 2);

    // Don't override base theme.
    if (!empty($specific) && isset($theme_registry[$base_theme])) {
      $theme_info = array(
        'template'   => $template,
        'path'       => drupal_dirname($file->uri),
        'variables'  => $theme_registry[$base_theme]['variables'],
        'base hook'  => $base_theme,
        // Other available value: theme_engine.
        'type'       => 'module',
        'theme path' => $module_path,
      );

      $theme_registry[$theme] = $theme_info;
    }
  }
}

Hope it helps someone.

1
-1

I asked this once on Stack Overflow. Basically, you have to implement hook_theme_registry_alter() to get your path added to the theme hook template path; then, on hook_enable(), you call drupal_theme_rebuild() to clear the theme registry cache and get your path scanned for templates.

6
  • Maybe what's holding you back is just clearing the caches. Commented Mar 22, 2012 at 14:42
  • So, basically the same solution. I tried "drush cc all" not less than 50 times, tested it on a new install site etc. without success. I'll modify and compact my code into a minimal module so that everyone can test.
    – jcisio
    Commented Mar 22, 2012 at 17:10
  • hook_enable() is invoked when a module is enabled; if the module is already enabled, it must be disabled, and then enabled again.
    – avpaderno
    Commented Mar 28, 2012 at 9:41
  • @kiamlaluno: iḿ using hook_enable to clear the caches, if it is already installed then user might just clear the caches manually. Commented Mar 29, 2012 at 17:15
  • NO, -1 points. That solution is so old (2009), I'm not even sure it was meant for D7. While your old solution is tailored for Views, it is not ideal for non-Views situations where developers may want to pack for more than 1 default template per theme key in their modules. Imagine implementing your solution for 100 dynamic theme suggestions for a single theme key. Applied outside of a Views context I would call your solution a counter-pattern. Commented May 2, 2014 at 3:27

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.