You are here

contemplate.module in Content Templates (Contemplate) 7

Same filename and directory in other branches
  1. 5 contemplate.module
  2. 6 contemplate.module

Create templates to customize teaser and body content.

File

contemplate.module
View source
<?php

// by Jeff Robbins - Lullabot - www.lullabot.com
define('CONTEMPLATE_TEASER_ENABLED', 0x1);
define('CONTEMPLATE_BODY_ENABLED', 0x2);
define('CONTEMPLATE_RSS_ENABLED', 0x4);

/**
 * @file
 * Create templates to customize teaser and body content.
 */

/**
 * Implements hook_help().
 */
function contemplate_help($path, $arg) {
  switch ($path) {
    case 'admin/structure/types/manage/%/template':
      return t('<p>Enable the textareas by enabling the checkbox above each. Expand the variables section to display. Then click on content attributes to insert the appropriate PHP <code>print</code> statements at your cursor position in the textarea.</p><p>It is also possible to create disk-based templates.') . (module_exists('help') ? t('Find more information about that <a href="!link">here</a>', array(
        '!link' => url('admin/help/contemplate', array(
          'fragment' => 'disk-based',
        )),
      )) : t('For help on this enable the \'help\' module, and you will find a link here.')) . t('<p>Please note that by creating a template for this content type, you are taking full control of its output and you will need to manually add all of the fields that you would like to see in the output. Click <em>reset</em> to remove template control for this content type.</p>') . (module_exists('help') ? theme('more_help_link', array(
        'url' => 'admin/help/contemplate',
      )) : '');
      break;
    case 'admin/structure/types/templates':
      return '<p>' . t('Configure Content Templates for each content type available, click on the !link link to create and enable a template for that type.', array(
        '!link' => '<strong><i>edit template</i></strong>',
      )) . '</p>';
      break;
    case 'admin/config/contemplate':
      return '<p>' . t('This is the settings page for Content Template (contemplate) module, to enable templates for each content type !link', array(
        '!link' => l('click here', 'admin/structure/types/templates'),
      )) . '</p>';
      break;
    case 'admin/help#contemplate':
      $paragraphs = array();
      $paragraphs[] = t('The Content Templates (a.k.a. contemplate) module allows modification of the teaser and body fields using administrator defined templates. These templates use PHP code and all of the node object variables are available for use in the template. An example node object is displayed and it is as simple as clicking on its properties to add them to the current template.');
      $paragraphs[] = t('This module was written to solve a need with the Content Construction Kit (CCK), where it had a tendency toward outputting content in a not-very-pretty way. And as such, it dovetails nicely with CCK, adding a "template" tab to CCK content-type editing pages and pre-populating the templates with CCK\'s default layout. This makes it easy to rearrange fields, output different fields for teaser and body, remove the field title headers, output fields wrapped for use with tabs.module (part of JSTools), or anything you need.');
      $paragraphs[] = t('But Content Templates can actually be used on any content type and allows modification of the teaser and body properties before they go out in an RSS feed or are handed off to the theme.');
      $paragraphs[] = '<h3>' . t('Creating templates') . '</h3>';
      $paragraphs[] = t('Enter PHP code similar to <a href="http://drupal.org/node/11816">PHPTemplate</a>. The main difference is that you only have access to the $node object. However, PHPTemplate templates only affect output to the page. Contemplate affects display in all site themes as well as RSS feeds and search results.');
      $paragraphs[] = '<h3 id="disk-based">' . t('Disk-based templates') . '</h3>';
      $paragraphs[] = t('It is also possible to create disk-based template files. To do this, copy the contents of a contemplate textarea and paste it into a file called "node-<em>nodetype</em>-<em>field</em>.tpl.php" where <em>nodetype</em> is the content type and <em>field</em> is either "body", "teaser", or "rss". It is also possible to create a template called "node-<em>nodetype</em>.tpl.php" which will affect all cases, and "node.tpl.php" which will affect all node types.');
      $paragraphs[] = t('Place these files into a directory called "contemplates" inside of either your "sites/all" directory or "sites/<em>yoursite</em>" depending on your setup. It is also possible to have multiple "contemplate" directories with "sites/all/contemplates" being the fallback for templates the contemplate does not find in the more specific site directory.');
      $paragraphs[] = t('When adding or removing template files, you will need to visit the <a href="!link">Content Templates admin page</a> to refresh the template list cache. You do not need to do this again when making changes to the content of the templates.', array(
        '!link' => url('admin/structure/types/templates'),
      ));
      $output = "";
      foreach ($paragraphs as $paragraph) {
        $output .= '<p>' . $paragraph . '</p>';
      }
      return $output;
      break;
  }
}

/**
 * Menu callback; loads a flexifilter object
 */
function contemplate_node_type_load($index, $map_array) {
  $types = node_type_get_types();
  $type = $map_array[$index];
  if (!$types[$type]) {

    // if the argument isn't a valid node type, return FALSE
    return FALSE;
  }
  else {
    return $type;
  }
}

/**
 * Implements hook_menu().
 */
function contemplate_menu() {
  $items = array();
  $items['admin/structure/types/templates'] = array(
    'title' => t('Content Templates'),
    'description' => 'Create templates to customize output of teaser and body content.',
    //'page callback'     => 'contemplate_edit_type',
    'page callback' => 'contemplate_admin',
    'access arguments' => array(
      'administer templates',
    ),
    'type' => MENU_LOCAL_TASK,
    'weight' => 7,
  );
  $items['admin/structure/types/manage/%node_type/template'] = array(
    'title' => t('Content Template'),
    'page callback' => 'contemplate_edit_type',
    'page arguments' => array(
      4,
    ),
    'access arguments' => array(
      'administer templates',
    ),
    'type' => MENU_LOCAL_TASK,
    'weight' => 10,
  );
  $items['admin/config/contemplate'] = array(
    'title' => t('Content Template Settings'),
    'page callback' => 'drupal_get_form',
    'description' => t('Manage Content Template settings'),
    'page arguments' => array(
      'contemplate_system_settings',
    ),
    'access arguments' => array(
      'administer templates',
    ),
  );
  $items['contemplate/ajax/preview/%node/%'] = array(
    'title' => 'preview of the node when passed through',
    'description' => 'so the user can see what the node will look like with the current code',
    'page callback' => 'contemplate_ajax_preview',
    'page arguments' => array(
      3,
      4,
    ),
    'access arguments' => array(
      'administer templates',
    ),
    'type' => MENU_CALLBACK,
  );
  return $items;
}

/**
 * provide ajax callback for template editors
 */
function contemplate_ajax_preview($node, $type) {
  $template = file_get_contents('php://input');
  $node->build = node_view($node, $type);
  $xml_elements = array();
  $fieldset = array(
    '#type' => 'fieldset',
    '#title' => t('Content Template results'),
    //'#description'  => t('This is how your content will look after the template is applied, except some CSS differences.'),
    '#collapsible' => TRUE,
    '#collapsed' => FALSE,
    '#value' => contemplate_eval($template, $node, $xml_elements),
  );
  $data = array(
    'messages' => theme('status_messages', array()),
    'content' => theme('fieldset', array(
      'element' => $fieldset,
    )),
  );
  drupal_json_output($data);
}

/**
 * build the system settings form
 */
function contemplate_system_settings() {
  $form = array();
  $form['contemplate_remove_node_content'] = array(
    '#type' => 'checkbox',
    '#title' => t('Remove $node->content from variable list.'),
    '#description' => t('When this is enabled you will not see all available variables while creating/editing content templates. When this is disabled you may have Fatal Errors due to going over PHP memory limit. Even though the contents of $node->content is not displayed in the variable list, they are still avaible to be used in the Content Template, they just overflow PHPs memory limit when used with many CCK Fields.'),
  );
  if (variable_get('contemplate_remove_node_content', FALSE)) {
    $form['contemplate_remove_node_content']['#attributes'] = array(
      'checked' => 'true',
    );
  }
  $form['contemplate_max_recursion_depth'] = array(
    '#type' => 'textfield',
    '#title' => t('Max recursion depth'),
    '#description' => t('How far into the node object should contemplate look? Having this set too high could cause PHP memory errors. If the above checkbox "Remove $node->content from variable list" is checked this won\'t have much of an affect.'),
    '#default_value' => variable_get('contemplate_max_recursion_depth', 10),
    '#size' => 5,
  );
  $form['contemplate_before_search_index'] = array(
    '#type' => 'checkbox',
    '#title' => t('Process template before indexing for search'),
    '#description' => t('Do you want to process the template before the nodes are indexed for search?'),
  );
  if (variable_get('contemplate_before_search_index', FALSE)) {
    $form['contemplate_before_search_index']['#attributes'] = array(
      'checked' => 'true',
    );
  }
  return system_settings_form($form);
}

/**
 * Implements hook_perm().
 */
function contemplate_permission() {
  return array(
    'administer templates' => array(
      'title' => t('Administer Content Templates'),
      'description' => t('Create, delete, make changes to content templates'),
    ),
  );
}

/**
 * Implements hook_node_view().
 */
function contemplate_node_view($node, $build_mode) {
  switch ($build_mode) {
    case 'rss':
      if ($template = contemplate_get_template($node->type)) {
        if (CONTEMPLATE_RSS_ENABLED & $template['flags'] && trim($template['rss'])) {

          // only if there's content in teaser field
          $xml_elements = array();
          $node->build = node_view($node, 'teaser');
          $rss = contemplate_eval($template['rss'], $node, $xml_elements);

          // set both teaser and body because we don't know how they've set Drupal
          $node->teaser = $rss;
          $node->body = $rss;
          if (trim($template['enclosure'])) {
            if ($file = contemplate_eval_enclosure($template['enclosure'], $node, $xml_elements)) {
              $xml_elements[] = array(
                'key' => 'enclosure',
                'attributes' => array(
                  'url' => file_create_url($file->filepath),
                  'length' => $file->filesize,
                  'type' => $file->filemime,
                ),
              );
            }
          }
          $node->rss_elements = $xml_elements;
        }
      }
      break;
  }
}

/**
 * Implements hook_node_update_index().
 */
function contemplatehook_node_update_index(&$node) {
  if (variable_get('contemplate_before_search_index', FALSE)) {
    contemplate_nodeapi($node, 'alter');
    return $node->body;
  }
}

/**
 * Admin page... list out the node types
 *
 * @return html Content Template Admin table
 */
function contemplate_admin() {
  $destination = drupal_get_destination();
  contemplate_refresh_files();
  $types = node_type_get_types();
  $templates = contemplate_get_templates();
  $enabled = t('Enabled: Textfield');
  $disabled = t('Disabled');
  $file = t('Enabled: Disk');
  foreach ($types as $type) {
    $type_url_str = str_replace('_', '-', $type->type);
    $rows[] = array(
      $type->name,
      isset($templates[$type->type]['teaser-enabled']) ? $templates[$type->type]['teaser-enabled'] ? $templates[$type->type]['teaser-file'] ? $file : $enabled : $disabled : '',
      isset($templates[$type->type]['body-enabled']) ? $templates[$type->type]['body-enabled'] ? $templates[$type->type]['body-file'] ? $file : $enabled : $disabled : '',
      isset($templates[$type->type]['rss-enabled']) ? $templates[$type->type]['rss-enabled'] ? $templates[$type->type]['rss-file'] ? $file : $enabled : $disabled : '',
      l(isset($templates[$type->type]) ? t('edit template') : t('create template'), 'admin/structure/types/manage/' . $type_url_str . '/template'),
      isset($templates[$type->type]) ? l(t('delete template'), 'admin/structure/types/manage/' . $type_url_str . '/template/delete', array(
        'query' => $destination,
      )) : '',
    );
  }
  $header = array(
    t('content type'),
    t('Teaser'),
    t('Body'),
    t('RSS'),
    '',
    '',
  );
  return theme('table', array(
    'header' => $header,
    'rows' => $rows,
  ));
}

/**
 * Menu Callback
 * Confirm the deletion of the custom Content Template
 *
 * @param string $type
 */
function contemplate_delete_type_form(&$form_state, $type = NULL) {
  $form['type'] = array(
    '#type' => 'value',
    '#value' => $type->type,
  );
  return confirm_form($form, t('Are you sure you want to delete the template for %type?', array(
    '%type' => $type->name,
  )), isset($_GET['destination']) ? $_GET['destination'] : 'admin/content/types/templates', t('This action cannot be undone.'), t('Delete'), t('Cancel'));
}
function contemplate_delete_type_form_submit($form, &$form_state) {
  return contemplate_delete($form_state['values']['type']);
}

/**
 * Menu callback
 * Edit the template for a specific node-type
 *
 * @param string $type
 */
function contemplate_edit_type_form($form, &$form_state, $type = NULL) {
  $example = contemplate_examples($type);
  $template = contemplate_get_template($type);
  $intro = t('<p>An example node has been loaded and its properties appear below. Click on the the property names to add them to your template.</p>');
  $example_help = t('Click property to add to your template. Fields marked with <span style="color:red">**</span> are insecure and need to be wrapped with either <a href="http://api.drupal.org/api/7/function/check_plain">check_plain()</a> or <a href="http://api.drupal.org/api/7/function/check_markup">check_markup()</a>');

  // http://dgtlmoon.com/simpletest_disabled_field_failed_to_set_field
  // make sure calls from simpletest dont place 'disabled' attribute
  $enabled_form_attribute = preg_match('/simpletest/i', $_SERVER['HTTP_USER_AGENT']) ? array() : array(
    'disabled' => 'true',
  );
  if ($default = contemplate_get_fields($type)) {
    $default_teaser = $default_body = $default;
  }
  else {
    $default_teaser = "<?php print \$teaser ?>\n";
    $default_body = "<?php print \$body ?>\n";
  }
  $form['teaser'] = array(
    '#type' => 'fieldset',
    '#title' => t('Teaser'),
    '#collapsible' => TRUE,
    '#collapsed' => CONTEMPLATE_TEASER_ENABLED & $template['flags'] ? FALSE : TRUE,
  );
  if (empty($template['teaser-file'])) {
    $form['teaser']['teaser-enable'] = array(
      '#type' => 'checkbox',
      '#title' => '<strong>' . t('Affect teaser output') . '</strong>',
      '#default_value' => CONTEMPLATE_TEASER_ENABLED & $template['flags'] ? TRUE : FALSE,
      '#attributes' => array(
        'rel' => 'toggletarget|#edit-teaserfield',
      ),
    );
    $form['teaser']['teaserfield'] = array(
      '#type' => 'textarea',
      '#title' => t('Teaser Template'),
      '#default_value' => !empty($template['teaser']) ? $template['teaser'] : $default_teaser,
      '#rows' => 15,
      '#description' => t('Leave this field blank to leave teaser unaffected.'),
      '#prefix' => '<div class="contemplate-input">',
      '#suffix' => '</div>',
      '#attributes' => CONTEMPLATE_TEASER_ENABLED & $template['flags'] ? '' : $enabled_form_attribute,
    );
  }
  else {
    $form['teaser']['teaserfield'] = array(
      '#value' => t('<p>This template is being read from<br />%file<br />Please make changes to this file or remove it to continue editing here.</p>', array(
        '%file' => $template['teaser-file']->filename,
      )),
    );
  }
  $form['teaser']['teaser_preview'] = array(
    '#type' => 'submit',
    '#value' => t('Preview'),
  );
  $form['teaser']['teaser_example'] = array(
    '#type' => 'fieldset',
    '#title' => t('Teaser Variables'),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
  );
  $form['teaser']['teaser_example']['example'] = array(
    '#markup' => '<div class="contemplate-tips form-item"><div id="edit-teaserfield-keys" class="contemplate-scroller resizable-div">' . $intro . $example['teaser'] . '</div><div class="description">' . $example_help . '</div></div>' . $example['teaser_krumo'],
  );
  $form['body'] = array(
    '#type' => 'fieldset',
    '#title' => t('Body'),
    '#collapsible' => TRUE,
    '#collapsed' => CONTEMPLATE_BODY_ENABLED & $template['flags'] ? FALSE : TRUE,
  );
  if (empty($template['body-file'])) {
    $form['body']['body-enable'] = array(
      '#type' => 'checkbox',
      '#title' => '<strong>' . t('Affect body output') . '</strong>',
      '#default_value' => CONTEMPLATE_BODY_ENABLED & $template['flags'] ? TRUE : FALSE,
      '#attributes' => array(
        'rel' => 'toggletarget|#edit-bodyfield',
      ),
    );
    $form['body']['bodyfield'] = array(
      '#type' => 'textarea',
      '#title' => t('Body Template'),
      '#default_value' => !empty($template['body']) ? $template['body'] : $default_body,
      '#rows' => 15,
      '#description' => t('Leave this field blank to leave body unaffected.'),
      '#prefix' => '<div class="contemplate-input">',
      '#suffix' => '</div>',
      '#attributes' => CONTEMPLATE_BODY_ENABLED & $template['flags'] ? '' : $enabled_form_attribute,
    );
  }
  else {
    $form['body']['bodyfield'] = array(
      '#value' => t('<p>This template is being read from<br />%file<br />Please make changes to this file or remove it to continue editing here.</p>', array(
        '%file' => $template['body-file']->filename,
      )),
    );
  }
  $form['body']['body_preview'] = array(
    '#type' => 'submit',
    '#value' => t('Preview'),
  );
  $form['body']['body_example'] = array(
    '#type' => 'fieldset',
    '#title' => t('Body Variables'),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
  );
  $form['body']['body_example']['example'] = array(
    '#markup' => '<div class="contemplate-tips form-item"><div id="edit-bodyfield-keys" class="contemplate-scroller resizable-div">' . $intro . $example['body'] . '</div><div class="description">' . $example_help . '</div></div>' . $example['body_krumo'],
  );

  /* START RSS STUFF */
  $form['rss'] = array(
    '#type' => 'fieldset',
    '#title' => t('RSS'),
    '#collapsible' => TRUE,
    '#collapsed' => CONTEMPLATE_RSS_ENABLED & $template['flags'] ? FALSE : TRUE,
  );
  if (empty($template['rss-file'])) {
    $form['rss']['rss-enable'] = array(
      '#type' => 'checkbox',
      '#title' => '<strong>' . t('Affect RSS output') . '</strong>',
      '#default_value' => CONTEMPLATE_RSS_ENABLED & $template['flags'] ? TRUE : FALSE,
      '#attributes' => array(
        'rel' => 'toggletarget|#edit-rssfield',
      ),
      '#description' => t('Note that if you do not enable this, Drupal will use either the teaser or body as specified in your <a href="@url">RSS publishing settings</a>.', array(
        '@url' => url('admin/content/rss-publishing'),
      )),
    );
    $form['rss']['rssfield'] = array(
      '#type' => 'textarea',
      '#title' => t('RSS Template'),
      '#default_value' => !empty($template['rss']) ? $template['rss'] : $default_body,
      '#rows' => 15,
      '#description' => t('Leave this field blank to leave RSS unaffected. To add <a href="!link">XML elements</a> create an array $xml_elements.', array(
        '!link' => 'http://api.drupal.org/api/function/format_xml_elements/6',
      )),
      '#prefix' => '<div class="contemplate-input">',
      '#suffix' => '</div>',
      '#attributes' => CONTEMPLATE_RSS_ENABLED & $template['flags'] ? '' : $enabled_form_attribute,
    );
  }
  else {
    $form['rss']['rssfield'] = array(
      '#value' => t('<p>This template is being read from<br />%file<br />Please make changes to this file or remove it to continue editing here.</p>', array(
        '%file' => $template['rss-file']->filename,
      )),
    );
  }
  $form['rss']['rss_example'] = array(
    '#type' => 'fieldset',
    '#title' => t('RSS Variables'),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
  );
  $form['rss']['rss_example']['example'] = array(
    '#markup' => '<div class="contemplate-tips form-item"><div id="edit-rss-keys" class="contemplate-scroller resizable-div">' . $intro . $example['rss'] . '</div><div class="description">' . $example_help . '</div></div>' . $example['teaser_krumo'],
  );
  $form['rss'][] = array(
    '#type' => 'markup',
    '#value' => '<div style="clear:both"></div>',
  );
  global $_contemplate_fids;
  if (is_array($_contemplate_fids)) {
    $_contemplate_fids = drupal_map_assoc(array_unique($_contemplate_fids));
    $_contemplate_fids = array(
      0 => t('&lt;none&gt; (other modules may add)'),
    ) + $_contemplate_fids;
    $form['rss']['enclosure'] = array(
      '#type' => 'radios',
      '#title' => t('RSS enclosures'),
      '#options' => $_contemplate_fids,
      '#default_value' => !empty($template['enclosure']) ? $template['enclosure'] : '',
    );
  }

  /* END RSS STUFF */
  $form['type'] = array(
    '#type' => 'hidden',
    '#value' => $type,
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Save'),
  );
  $form['reset'] = array(
    '#type' => 'submit',
    '#value' => t('Delete'),
    '#attributes' => array(
      'onclick' => 'return(confirm("' . t("Are you sure you want to reset this form?\\nAny customizations will be lost.") . '"));',
    ),
  );
  return $form;
}
function contemplate_edit_type($type = array()) {
  if (empty($type)) {

    // if the argument isn't a valid node type, output admin page
    return contemplate_admin();
  }
  drupal_set_title(t('Template for %type', array(
    '%type' => $type->name,
  )), PASS_THROUGH);
  if (arg(0) == 'admin' && arg(1) == 'content' && arg(4) == 'template') {
    $breadcrumbs = drupal_get_breadcrumb();
    $type_crumb = array_pop($breadcrumbs);
    array_push($breadcrumbs, l(t('Templates'), 'admin/structure/types/templates'), $type_crumb);
    drupal_set_breadcrumb($breadcrumbs);
  }
  if (arg(5) == 'delete') {
    return drupal_get_form('contemplate_delete_type_form', $type);
  }
  else {
    contemplate_refresh_files();
    return drupal_get_form('contemplate_edit_type_form', $type->type);
  }
}

/**
 * Get a single template
 *
 * @return array template
 */
function contemplate_get_template($type) {

  //only load each template once per page hit
  static $types = array();
  if (!isset($types[$type])) {
    $types[$type] = array();
    $types[$type]['flags'] = 0;

    // first check to see what's stored in the contemplate table
    $result = db_query("SELECT * FROM {contemplate} WHERE type = :type", array(
      ':type' => $type,
    ));
    foreach ($result as $record) {
      $types[$type] = (array) $record;
    }
    if (isset($types[$type]['flags']) && empty($types[$type]['flags'])) {
      $types[$type]['flags'] = 0;
    }

    // now check to see if there are files
    $fields = array(
      'teaser' => CONTEMPLATE_TEASER_ENABLED,
      'body' => CONTEMPLATE_BODY_ENABLED,
      'rss' => CONTEMPLATE_RSS_ENABLED,
    );
    foreach ($fields as $field => $enable) {
      if ($file = contemplate_get_file($type, $field)) {
        $types[$type][$field] = $file->contents;
        $types[$type][$field . '-file'] = $file;
        $types[$type]['flags'] |= $enable;

        // if there is a file, the field is always enabled...
      }
      else {
        $types[$type][$field . '-file'] = false;
      }
      $types[$type][$field . '-enabled'] = $types[$type]['flags'] & $enable ? TRUE : FALSE;
    }
  }
  return $types[$type];
}

/**
 * Get all of the current templates
 * Only used on admin page
 *
 * @return array of template arrays
 */
function contemplate_get_templates() {
  foreach (node_type_get_types() as $r) {
    $templates[$r->type] = contemplate_get_template($r->type);
  }
  return $templates;
}
function contemplate_edit_type_form_submit($form, &$form_state) {
  $op = isset($_POST['op']) ? $_POST['op'] : '';
  if ($op == t('Delete')) {
    contemplate_delete($form_state['values']['type']);
    drupal_set_message(t('%type template has been reset.', array(
      '%type' => drupal_ucfirst($form_state['values']['type']),
    )));
  }
  else {
    contemplate_save($form_state);
    drupal_set_message(t('%type template saved.', array(
      '%type' => drupal_ucfirst($form_state['values']['type']),
    )));
  }
}
function contemplate_save($edit) {
  $type = $edit['values']['type'];
  $flags = false;
  $teaserfield = !empty($edit['values']['teaserfield']) ? $edit['values']['teaserfield'] : '';
  $bodyfield = !empty($edit['values']['bodyfield']) ? $edit['values']['bodyfield'] : '';
  $rssfield = !empty($edit['values']['rssfield']) ? $edit['values']['rssfield'] : '';
  $enclosure = !empty($edit['values']['enclosure']) ? $edit['values']['enclosure'] : '';
  $flags |= !empty($edit['values']['teaser-enable']) ? CONTEMPLATE_TEASER_ENABLED : 0;
  $flags |= !empty($edit['values']['body-enable']) ? CONTEMPLATE_BODY_ENABLED : 0;
  $flags |= !empty($edit['values']['rss-enable']) ? CONTEMPLATE_RSS_ENABLED : 0;
  contemplate_delete($type);
  $id = db_insert('contemplate')
    ->fields(array(
    'type' => $type,
    'teaser' => $teaserfield,
    'body' => $bodyfield,
    'rss' => $rssfield,
    'enclosure' => $enclosure,
    'flags' => $flags,
  ))
    ->execute();
  return $id;
}
function contemplate_delete($type) {
  return db_delete('contemplate')
    ->condition('type', $type)
    ->execute();
}

/**
 * List the available template files
 *
 * @return
 * array of template file information
 */
function contemplate_available_files() {
  static $data;
  if (!isset($data)) {
    $conf = conf_path();
    $result = db_query("SELECT data FROM {contemplate_files} WHERE site = :site", array(
      ':site' => $conf,
    ));
    foreach ($result as $record) {
      $data = unserialize($record->data);
    }
  }
  return $data;
}

/**
 * Refresh the listing of template files for the current site
 *
 * This refreshes the file listing whenever a ConTemplate admin screen is called
 */
function contemplate_refresh_files() {
  $data = drupal_system_listing('/\\.tpl\\.php$/', 'contemplates', 'name', 0);
  $conf = conf_path();
  db_delete('contemplate_files')
    ->condition('site', $conf)
    ->execute();
  if (!empty($data)) {
    $fields = array(
      'site' => $conf,
      'data' => serialize($data),
    );
    db_insert('contemplate_files')
      ->fields($fields)
      ->execute();
  }
}

/**
 * Given a node type and field type, return the content of the most specific
 * file
 *
 * @param string $node_type
 * type of node
 *
 * @param string $field
 * field type we're looking for
 * possible values are 'body', 'teaser', and 'rss'
 *
 * @return
 * either path to the most specific file (string)
 * or FALSE if no file is found
 *
 */
function contemplate_get_file($type, $field) {
  $files = contemplate_available_files();
  if (isset($files['node-' . $type . '-' . $field . '.tpl'])) {
    $file = $files['node-' . $type . '-' . $field . '.tpl'];
  }
  elseif (isset($files['node-' . $type . '.tpl'])) {
    $file = $files['node-' . $type . '.tpl'];
  }
  elseif (isset($files['node.tpl'])) {
    $file = $files['node.tpl'];
  }
  if (isset($file) && $file) {
    $file->contents = file_get_contents($file->uri);
    $return = $file;
  }
  else {
    $return = FALSE;
  }
  return $return;
}

/**
 * Load an example node and display its parts
 * - used only on template edit page
 *
 * @param $type
 *   node type
 * @return array
 */
function contemplate_node_views($type) {

  // get the nid of the latest node of this type
  $result = db_query("SELECT nid FROM {node} WHERE type = :type ORDER BY created DESC", array(
    ':type' => $type,
  ));
  foreach ($result as $record) {
    $nid = $record->nid;
  }
  if (isset($nid) && $nid) {

    //$bodynode = contemplate_template_node_view(node_load($nid), 'full');

    //$teasernode = contemplate_template_node_view(node_load($nid), 'teaser');
    $teasernode = $bodynode = node_load($nid);
    $bodynode->build = node_view($bodynode, 'full');
    $bodynode->build = node_view($teasernode, 'teaser');
    return array(
      'body' => $bodynode,
      'teaser' => $teasernode,
    );
  }
  else {
    return FALSE;
  }
}

/**
 * Load an example node and display its parts
 * - used only on template edit page
 *
 * @param $type
 *   node type
 * @return
 *   an array containing the 'rss', 'body' and 'teaser'
 *   versions of the node
 */
function contemplate_examples($type) {
  $path = drupal_get_path('module', 'contemplate');
  drupal_add_js($path . '/contemplate.js');
  drupal_add_js($path . '/divresizer.js');
  drupal_add_css($path . '/contemplate.css');
  $recursion_limit_hit = 0;
  if ($views = contemplate_node_views($type)) {
    $settings = array(
      'contemplate' => array(
        'example_nid' => $views['body']->nid,
      ),
    );
    drupal_add_js($settings, 'setting');
    $boutput = contemplate_array_variables((array) $views['body'], 'bodyfield', FALSE, FALSE, 0, $recursion_limit_hit);
    $toutput = contemplate_array_variables((array) $views['teaser'], 'teaserfield', FALSE, FALSE, 0, $recursion_limit_hit);
    $routput = contemplate_array_variables((array) $views['teaser'], 'rss', FALSE, FALSE, 0, $recursion_limit_hit);
  }
  else {
    $error = t('No %type content items exist to use as an example. Please create a %type item and then come back here to see an output of its parts.', array(
      '%type' => $type,
    ));
    $toutput = $boutput = $routput = $error;
  }
  return array(
    'body' => $boutput,
    'teaser' => $toutput,
    'rss' => $routput,
    'body_krumo' => function_exists('kprint_r') ? kprint_r($views['body'], TRUE, '$node') : '',
    'teaser_krumo' => function_exists('kprint_r') ? kprint_r($views['teaser'], TRUE, '$node') : '',
  );
}

/**
 * Recursive (and therefore magical) function goes through node object returns
 * html representation of the node strings are clickable and insert into
 * teaser/body fields
 *
 * @param $array
 *   array to recurse through
 * @param $target
 *   target field for javascript insert
 * @param $parents
 *   used by recursion
 * @param $object
 *   used by recursion
 * @return string - html dictionary list of $node elements
 */
function contemplate_array_variables($array, $target, $parents = FALSE, $object = FALSE, $depth = 0, &$recursion_limit_hit) {
  global $_contemplate_fids;
  if ($depth >= variable_get('contemplate_max_recursion_depth', 10)) {
    $recursion_limit_hit++;
    return '';
  }

  /**
   * CCK fields are causing problems, they are added into the original array as $array['field_*']=>array();
   * and they are also added into $array['content']['field_*']=>array() this array has not only
   * the information for the CCK field, but also information for the whole node , even itself,
   * $array['content']['field_*']['#node'] recursively, this leads to memory issues while traversing
   * the array recursively
   * Drupal 6
   */
  if (isset($array['content']) && $parents == FALSE && $object == FALSE) {

    // by default we will remove the content array, the user can choose to have this not done
    if (variable_get('contemplate_remove_node_content', FALSE)) {
      unset($array['content']);
    }
  }
  if (is_object($array)) {
    $array = (array) $array;
  }
  if (is_array($array)) {
    $output = "<dl>\n";
    foreach ($array as $field => $value) {
      if ($parents) {
        if ($object) {
          $field = $parents . '->' . $field;
        }
        else {
          if (is_int($field)) {
            $field = $parents . '[' . $field . ']';
          }
          elseif ($field == 'und') {

            // show $node->language for insertion into template http://drupal.org/node/1040748
            $field = $parents . '[$node->language]';
          }
          else {
            if ($field == 'fid') {

              // make a note of the fields named "fid"
              $_contemplate_fids[] = "\$node->" . $parents . '[\'' . $field . '\']';
            }
            $field = $parents . '[\'' . $field . '\']';
          }
        }
      }
      $type = '';
      if (!is_string($value)) {
        $type = ' (' . gettype($value) . ')';
      }
      if (!is_array($value) && !is_object($value)) {
        if ($field == 'title' || drupal_substr($field, -9) == "['value']") {
          $security = t('<span style="color:red;font-weight:bold">**</span>');
          $insert = "'<?php print check_plain(\$node->" . addslashes($field) . ") ?>'";
        }
        else {
          $security = '';
          $insert = "'<?php print \$node->" . addslashes($field) . " ?>'";
        }
        $output .= "<dt><a href=\"#\" onclick=\"return insertAtCursor(document.getElementById('edit-{$target}'), {$insert});\" title=\"insert this variable into {$target}\">\$node->{$field}</a>{$security}{$type}</dt>\n";
      }
      else {
        $output .= "<dt>\$node->{$field}{$type}</dt>\n";
      }
      $output .= "<dd>\n";
      if (is_array($value)) {
        $output .= contemplate_array_variables($value, $target, $field, FALSE, $depth + 1, $recursion_limit_hit);
      }
      elseif (is_object($value)) {
        $output .= contemplate_array_variables((array) $value, $target, $field, TRUE, $depth + 1, $recursion_limit_hit);
      }
      else {
        $value = is_bool($value) ? $value ? 'TRUE' : 'FALSE' : $value;
        $output .= htmlspecialchars(print_r($value, TRUE)) . "\n";
      }
      $output .= "</dd>\n";
    }
    $output .= "</dl>\n";
  }
  return $output;
}

/**
 * Copy of drupal_eval(), but extracts the node object so that variables are available to the template
 *
 * @param $tmplt
 *   text
 *   the template code
 * @param $obj
 *   object
 *   an object to extract into the local variables
 * @return
 *   string
 *   executed template code
 */
function contemplate_eval($tmplt, $obj, &$xml_elements) {
  global $user;
  extract((array) $obj);
  $node = $obj;

  // attempt to get the language that we need to display to the user
  // default to the node language
  $default_language = language_default('language');
  $display_language = !empty($user->language) ? $user->language : $default_language;
  $node_language = !empty($node->language) ? $node->language : 'und';
  $language_to_display = isset($node->body[$display_language]) ? $display_language : (isset($node->body[$default_language]) ? $default_language : $node_language);

  //CCK supports nodes without body.
  $teaser = isset($node->body) ? $node->body[$language_to_display][0]['safe_summary'] : '';
  $body = isset($node->body) ? $node->body[$language_to_display][0]['safe_summary'] : '';
  ob_start();
  print eval('?>' . $tmplt);
  $output = ob_get_contents();
  ob_end_clean();
  return $output;
}

/**
 * Eval the RSS enclosure field
 */
function contemplate_eval_enclosure($field, $node) {
  $tmplt = '<?php print ' . $field . ' ?>';
  $xml_elements = array();
  $fid = contemplate_eval($tmplt, $node, $xml_elements);
  if (is_numeric($fid)) {
    $file = db_fetch_object(db_query('SELECT * FROM {files} WHERE fid = :fid', array(
      ':fid' => $fid,
    )));
    return $file;
  }
  return FALSE;
}

/**
 * get fields for the template edit form
 */
function contemplate_get_fields($type_name) {
  if (module_exists('field')) {
    $return = array();
    $return[] = '<?php $fields = field_attach_view(\'node\', $node, \'full\', \'en\'); ?>' . "\n";
    $instances = field_read_instances(array(
      'entity_type' => 'node',
      'bundle' => $type_name,
    ));
    foreach ($instances as $field) {
      $return[] = theme('contemplate_field', $field);
    }
    if (count($return)) {
      $return = implode("\n", $return);
    }
    else {

      // return, no instances were found but field module is enabled
      $return = FALSE;
    }
  }
  else {

    // no field module, dont bother
    $return = FALSE;
  }

  // field module was present and we found some fields and themed them
  return $return;
}

/**
 * Implements hook_theme().
 */
function contemplate_theme() {
  return array(
    'contemplate_field' => array(
      'arguments' => array(
        'element' => NULL,
      ),
    ),
  );
}

/**
 * Rewrite of theme_field to output default CCK output into the template.
 *
 * @return unknown
 */
function theme_contemplate_field($field) {
  $output = '<div class="field field-type-' . strtr($field['widget']['type'], '_', '-') . ' field-' . strtr($field['field_name'], '_', '-') . '">' . "\n";
  $output .= '  <h3 class="field-label">' . $field['label'] . '</h3>' . "\n";
  $output .= '  <div class="field-items">' . "\n";

  // Drupal Fields should come as an array and be treated as such
  if (preg_match('/^field_/', $field['field_name'])) {

    // @todo: still cant get fine grain build out of complex fields, render() renders all the field values

    /*  	$output .= '    <?php foreach ( $fields as $field_name => $field ) { ?>' . "\n"; */
    $output .= '      <div class="field-item"><?php print render( $fields[\'' . $field['field_name'] . '\'] ); ?></div>' . "\n";

    /*    $output .= '    <?php } ?>' . "\n"; */
  }
  else {
    $output .= '      <div class="field-item"><?php print $node->' . $field['field_name'] . '[$node->language][0][\'safe_value\']; ?></div>' . "\n";
  }
  $output .= '  </div>' . "\n";
  $output .= '</div>' . "\n";
  return $output;
}

/**
 * Implements tempalte_prerocess_node().
 *
 * because hook_nodeapi('alter') is no longer here I decided to change the node
 * at just before the template gets invoked
 */
function contemplate_preprocess_node(&$vars) {
  $node = $vars['node'];
  $node_type = node_type_get_type($node);
  $xml_elements = array();
  if ($template = contemplate_get_template($node_type->type)) {
    switch ($vars['view_mode']) {
      case 'teaser':
        if (CONTEMPLATE_TEASER_ENABLED & $template['flags'] && trim($template['teaser'])) {

          // only if there's content in teaser field
          $node->build = node_view($node, 'teaser');
          $teaser = contemplate_eval($template['teaser'], $node, $xml_elements);

          // because we are using the template we need to remove everything else
          // from the content array except the comments and the links, then
          // replace the body
          if (isset($vars['content']['comments'])) {
            $comments = $vars['content']['comments'];
          }
          else {
            $comments = FALSE;
          }
          $vars['content']['body'][0]['#markup'] = $teaser;
          $vars['content'] = array(
            'body' => $vars['content']['body'],
            'links' => $vars['content']['links'],
          );
          if ($comments) {
            $vars['content']['comments'] = $comments;
          }
        }
        break;
      case 'full':
        if (CONTEMPLATE_BODY_ENABLED & $template['flags'] && trim($template['body'])) {

          // only if there's content in the body field
          $node->build = node_view($node, 'full');
          $body = contemplate_eval($template['body'], $node, $xml_elements);

          // because we are using the template we need to remove everything else
          // from the content array except the comments and the links, then
          // replace the body
          if (isset($vars['content']['comments'])) {
            $comments = $vars['content']['comments'];
          }
          else {
            $comments = FALSE;
          }
          $vars['content']['body'][0]['#markup'] = $body;
          $vars['content'] = array(
            'body' => $vars['content']['body'],
            'links' => $vars['content']['links'],
          );
          if ($comments) {
            $vars['content']['comments'] = $comments;
          }
        }
        break;
    }
  }
}

Functions

Namesort descending Description
contemplatehook_node_update_index Implements hook_node_update_index().
contemplate_admin Admin page... list out the node types
contemplate_ajax_preview provide ajax callback for template editors
contemplate_array_variables Recursive (and therefore magical) function goes through node object returns html representation of the node strings are clickable and insert into teaser/body fields
contemplate_available_files List the available template files
contemplate_delete
contemplate_delete_type_form Menu Callback Confirm the deletion of the custom Content Template
contemplate_delete_type_form_submit
contemplate_edit_type
contemplate_edit_type_form Menu callback Edit the template for a specific node-type
contemplate_edit_type_form_submit
contemplate_eval Copy of drupal_eval(), but extracts the node object so that variables are available to the template
contemplate_eval_enclosure Eval the RSS enclosure field
contemplate_examples Load an example node and display its parts
contemplate_get_fields get fields for the template edit form
contemplate_get_file Given a node type and field type, return the content of the most specific file
contemplate_get_template Get a single template
contemplate_get_templates Get all of the current templates Only used on admin page
contemplate_help Implements hook_help().
contemplate_menu Implements hook_menu().
contemplate_node_type_load Menu callback; loads a flexifilter object
contemplate_node_view Implements hook_node_view().
contemplate_node_views Load an example node and display its parts
contemplate_permission Implements hook_perm().
contemplate_preprocess_node Implements tempalte_prerocess_node().
contemplate_refresh_files Refresh the listing of template files for the current site
contemplate_save
contemplate_system_settings build the system settings form
contemplate_theme Implements hook_theme().
theme_contemplate_field Rewrite of theme_field to output default CCK output into the template.

Constants