You are here

messaging_template.inc in Messaging 6.3

Same filename and directory in other branches
  1. 7 messaging_template/messaging_template.inc

Base classes for messaging templates

Though the API is language enabled, this base class will only work for a single language that must be defined when creating the class.

Templates can be nested and have multiple parts

File

messaging_template/messaging_template.inc
View source
<?php

/**
 * @file
 * Base classes for messaging templates
 * 
 * Though the API is language enabled, this base class will only work for a single
 * language that must be defined when creating the class.
 * 
 * Templates can be nested and have multiple parts
 * 
 */

/**
 * Methods a template element must implement so it can be nested and rendered
 * 
 * @see Messaging_Template
 * @see Messaging_Template_Part
 */
interface Messaging_Template_Element {

  // Get text content as array or string
  public function get_content($method, $language);

  // Whether this template needs object replacement
  public function needs_replace();

  // Reset all rendered text elements
  public function reset($recurse = FALSE);

}

/**
 * Static template functions that interact with the module API
 */
class Messaging_Template_Engine {

  // Template information
  protected static $info;

  // Built templates
  protected static $templates;

  // Part keys indexed by group/type
  protected static $keys;

  // Default templates from modules
  protected static $defaults;

  // Global class debug
  protected static $_debug = FALSE;

  /**
   * Get template type info
   */
  static function get_info($type) {
    if (!isset(self::$info)) {
      self::$info = messaging_template_info(NULL, NULL);
    }
    return isset(self::$info[$type]) ? self::$info[$type] : NULL;
  }

  /**
   * Create template, just allowed types
   */
  static function create_template($type = 'messaging-template', $parent = NULL, $method = NULL, $language = NULL) {

    // Check this is a known type, if not return NULL
    if (self::get_info($type)) {
      return new Message_Template($type, $parent, $method, $language);
    }
    else {
      return NULL;
    }
  }

  /**
   * Get template part, FALSE if not found
   *
   * @param $key
   *   Part key, like 'subject', 'header', 'footer', ...
   * @param $type
   *   Template type, like 'messaging-template', 'notifications-event'...
   */
  static function get_template_part($key, $type, $method, $language) {
    $lang = $language->language;
    self::_debug('Getting template part', array(
      'type' => $type,
      'key' => $key,
      'method' => $method,
      'language' => $lang,
    ));
    self::build_template($type, $method, $language);
    if (!isset(self::$templates[$lang][$type][$method][$key])) {

      // Try method default for the same type
      if ($method_default = self::method_default($method)) {
        $part = self::get_template_part($key, $type, $method_default, $language);
      }
      elseif (!($part = self::get_default($type, $key, $language))) {
        $part = FALSE;
      }
      self::$templates[$lang][$type][$method][$key] = $part;
    }
    elseif (self::$templates[$lang][$type][$method][$key]->options == MESSAGING_TEMPLATE_FALLBACK) {

      // Resolve fallbacks first time the part is retrieved
      if ($fallback = self::type_fallback($type)) {
        self::$templates[$lang][$type][$method][$key] = self::get_template_part($key, $fallback, $method, $language);
      }
      else {
        self::$templates[$lang][$type][$method][$key] = FALSE;
      }
    }
    return self::$templates[$lang][$type][$method][$key];
  }

  /**
   * Build a given template
   */
  static function build_template($type, $method, $language) {
    $lang = $language->language;
    if (!isset(self::$templates[$lang][$type][$method])) {
      self::_debug('Building template', array(
        'type' => $type,
        'method' => $method,
      ));
      self::$templates[$lang][$type][$method] = messaging_template_get_parts($type, $method, $language);
    }
  }

  /**
   * Get default provided by modules
   */
  static function get_default($type, $key, $language) {
    $lang = $language->language;
    self::_debug('Getting template default', array(
      'type' => $type,
      'key' => $key,
      'language' => $lang,
    ));
    if (!isset(self::$defaults[$lang][$type])) {
      self::$defaults[$lang][$type] = messaging_template_get_defaults($type, $language);
    }
    if (!isset(self::$defaults[$lang][$type][$key])) {
      if ($type_fallback = self::type_fallback($type)) {
        self::$defaults[$lang][$type][$key] = self::get_default($type_fallback, $key, $language);
      }
      else {
        self::$defaults[$lang][$type][$key] = FALSE;
      }
    }
    return self::$defaults[$lang][$type][$key];
  }

  /**
   * Get part keys for a given template
   */
  protected function get_keys($type) {
    if (!isset(self::$keys[$type])) {
      self::$keys[$type] = messaging_template_get_keys($type);
    }
    return array_keys(self::$keys[$type]);
  }

  /**
   * Get method fallback
   */
  static function method_default($method) {
    return messaging_template_method_default($method);
  }

  /**
   * Get type fallback
   */
  static function type_fallback($type) {
    return messaging_template_fallback($type);
  }

  /**
   * Render recursively an array of elements
   */
  static function render_elements(&$elements, $method, $language, $objects = array()) {
    $replace = $texts = array();
    foreach ($elements as $key => $element) {
      if (is_object($element)) {
        $needs_replace = $element
          ->needs_replace();
        $value = $element
          ->get_content($method, $language);
      }
      elseif (is_array($element)) {
        $needs_replace = FALSE;
        $value = self::render_elements($element, $method, $language, $objects);
      }
      else {
        $needs_replace = TRUE;
        $value = $element;
      }

      // If needs replace add $texts placeholder to preserve the order
      if ($needs_replace) {
        $replace[$key] = $value;
        $texts[$key] = NULL;
      }
      else {
        $texts[$key] = $value;
      }
    }
    if ($replace) {
      $texts = array_merge($texts, self::text_replace($replace, $objects, $method, $language));
    }
    return $texts;
  }

  /**
   * Replace text with object tokens
   */
  static function text_replace($text, $objects, $language) {
    return messaging_template_text_replace($text, $objects, $language);
  }

  /**
   * Debug, static version
   */
  static function _debug($txt, $variables = array()) {
    if (self::$_debug) {
      messaging_debug($txt, $variables);
    }
  }

}

/**
 * Messaging Template class
 */
class Messaging_Template extends Messaging_Template_Engine implements Messaging_Message_Template, Messaging_Template_Element {

  // Basic template parameters
  public $type = NULL;
  public $language = NULL;
  public $method = NULL;

  // Parent template and template engine
  protected $parent;

  // Template raw elements. Each of them may be:
  // - A text part string, ready for replacement
  // - An array like (text_part_key, default)
  // - A Template object
  protected $elements = array();

  // Objects for replacement
  protected $objects = array();

  // Text templates for each part indexed by method and language code
  public $texts = array();

  // Predefined texts to use instead of text parts
  public $presets = array();

  // Debug option on/off
  public $debug = TRUE;

  /**
   * Class constructor, create a template of given type
   */
  function __construct($type = 'messaging-template', $parent = NULL, $method = NULL, $language = NULL) {
    $this->type = $type;
    if ($parent) {
      $this->parent = $parent;
      $this->method = $method;
      $this->language = $language;
    }
    else {
      $this->language = $language ? $language : language_default();
      $this->method = $method ? $method : 'default';
      $this->objects = array(
        'global' => NULL,
      );
    }
  }

  /**
   * Get child template derived from this one
   */
  function get_template($type = 'messaging-template', $method = NULL, $language = NULL) {

    // Check this is a known type, if not return NULL,
    return self::create_template($type, $this, $method, $language);
  }

  /**
   * Get template part, FALSE if not found
   */
  function get_part($key, $type = NULL, $method = NULL, $language = NULL) {
    return self::get_template_part($key, $type ? $type : $this->type, $method ? $method : $this
      ->get_method(), $language ? $language : $this
      ->get_language());
  }

  /**
   * Set object for token replacement
   */
  function set_object($type, $object = NULL) {
    $this->objects[$type] = $object;
  }

  /**
   * @param $key
   *   Template part key
   * @param $default
   *   Default value if key not found
   * @param $duplicate
   *   Add duplicates (if this part has been already added)
   */
  function add_part($key, $default = NULL, $duplicate = FALSE) {
    if (isset($this->elements[$key]) && !$duplicate) {
      return;
    }
    if (isset($this->presets[$key])) {
      $this
        ->append($this->presets[$key], $key);
    }
    else {

      // Add as Template_Text_Part
      $this
        ->append(new Messaging_Template_Part($this, $key, $default), $key);
    }
  }

  // Add child template
  function add_child($template, $key = NULL) {
    $template->parent = $this;
    $this
      ->append($template, $key);
  }

  // Get text part, possibly from parent
  function xget_part($key) {
    return $this->engine
      ->get_part($key, $this->type, $this
      ->get_method(), $this
      ->get_language());
  }

  /**
   * Append element: may be a text or another template
   */
  function append($value, $key = NULL) {
    if (!isset($key)) {
      $this->elements[] = $value;
    }
    elseif (isset($this->elements[$key])) {

      // If not an array yet, make it into an array
      if (!is_array($this->elements[$key])) {
        $this->elements[$key] = array(
          $this->elements[$key],
        );
      }
      $this->elements[$key][] = $value;
    }
    else {
      $this->elements[$key] = $value;
    }
  }

  /**
   * Render all template elements
   *
   * @param $method
   *   Sending method to render for
   * @param $language
   *   Language object
   */
  function render($method, $language) {
    if (!isset($this->texts[$method][$language->language])) {
      $this->texts[$method][$language->language] = $this
        ->render_elements($this->elements, $method, $language, $this
        ->get_objects());
    }
    return $this->texts;
  }

  /**
   * Reset all template elements for new rendering
   *
   * @param $recurse
   *   Reset child templates too
   */
  function reset($recurse = FALSE) {
    $this->rendered = FALSE;
    $this->texts = array();
    if ($recurse && $this->elements) {
      $this
        ->reset_elements($this->elements);
    }
  }

  /**
   * Reset recursively an array of elements
   */
  function reset_elements(&$elements) {
    foreach ($elements as $element) {
      if (is_object($element)) {
        $element
          ->reset(TRUE);
      }
      elseif (is_array($element)) {
        reset_elements($element);
      }
    }
  }

  /**
   * Get template text part, FALSE if not found
   *
   * Here we translate MESSAGING_EMPTY into an empty string
   */
  function text_part($type, $key, $method = NULL, $language = NULL) {
    $method = $method ? $method : $this
      ->get_method();
    $language = $language ? $language : $this
      ->get_language();
    $this
      ->build_template($type, $method, $language);
    $part = $this
      ->get_part($key, $type, $method);
    $text = $part ? $part->template : FALSE;
    $this
      ->debug('Getting text part', array(
      'type' => $type,
      'key' => $key,
      'method' => $method,
      'text' => $text,
    ));
    return $text;
  }

  /**
   * Get text elements as array
   *
   * @param $key
   *   Text part key to return, like 'subject', 'body'...
   */
  function get_text($key, $method = NULL, $language = NULL) {
    $method = $method ? $method : $this
      ->get_method();
    $language = $language ? $language : $this
      ->get_language();
    $content = $this
      ->get_content($method, $language);
    return isset($content[$key]) ? $content[$key] : NULL;
  }

  /**
   * Get all text parts as array
   */
  function get_content($method, $language) {
    $this
      ->render($method, $language);
    return isset($this->texts[$method][$language->language]) ? $this->texts[$method][$language->language] : NULL;
  }

  /**
   * Get text element as string
   */
  function get_string($key, $glue = "\n", $default = '') {
    if ($text = $this
      ->get_text($key)) {
      return $this
        ->compose($text, $glue);
    }
    else {
      return $default;
    }
  }

  /**
   * Compose string
   */
  function compose($elements, $glue = "\n") {
    $compose = array();
    foreach ($elements as $part) {
      $compose[] = is_string($part) ? $part : $this
        ->compose($part, $glue);
    }
    return implode($glue, $compose);
  }

  /**
   * Check whether this template needs aditional replacement
   */
  function needs_replace() {
    return FALSE;
  }

  // Implementation of Messaging_Template_Parent

  /**
   * Get language, default to parent's
   */
  function get_language($property = NULL) {

    // Either the template has a language or has a parent who's got it
    if (isset($this->language)) {
      return $property ? $this->language->{$property} : $this->language;
    }
    elseif (isset($this->parent)) {
      return $this->parent
        ->get_language($property);
    }
    else {
      return language_default($property);
    }
  }

  /**
   * Get sending method, default to parent's
   */
  function get_method() {
    return $this->method ? $this->method : $this->parent
      ->get_method();
  }

  /**
   * Get template objects, included parent's objects
   */
  function get_objects($get_parent = FALSE) {
    if ($get_parent && isset($this->parent)) {
      return $this->objects + $this->parent
        ->get_objects();
    }
    else {
      return $this->objects;
    }
  }

  /**
   * Debug, adding instance information
   */
  function debug($txt, $variables = array()) {
    if ($this->debug) {
      $variables += array(
        'type' => $this->type,
        'method' => $this
          ->get_method(),
      );
      messaging_debug($txt, $variables);
    }
  }

  // Magic function, format as string
  public function __toString() {
    return "Messaging_Template: type={$this->type}, subject=" . $this
      ->get_string('subject');
  }

  /**
   * Implementation of Messaging_Message_Template
   */

  /**
   * Build a new message for method, destination
   *
   * @param $method
   *   Sending method
   * @param $language
   *   Language object
   * @return Messaging_Message
   *   Message built for method, language
   */
  public function build($method = NULL, $language = NULL) {
    $build = array(
      'template' => $this,
      'method' => $method,
      'language' => $language ? $language : $this
        ->get_language('language'),
    );
    return new Messaging_Message($build);
  }

  /**
   * Get subject message parts
   *
   * @param $method
   *   Sending method
   * @param $language
   *   Language code
   * @return string or array()
   *   Subject text or text parts for rendering
   */
  function get_subject($method = NULL, $language = NULL) {
    return $this
      ->get_text('subject', $method, $language);
  }

  /**
   * Get body parts
   *
   * @param $method
   *   Sending method
   * @param $language
   *   Language code
   * @return string or array()
   *   Body text or text parts for renderin
   */
  function get_body($method = NULL, $language = NULL) {
    return $this
      ->get_text('body', $method, $language);
  }

}

/**
 * Each text part to be rendered independently
 */
class Messaging_Template_Part implements Messaging_Template_Element {

  // Part key and default value
  public $key;
  public $default;
  public $text;

  // Parent template object
  protected $template;

  // Constructor
  function __construct($template, $key, $default = NULL) {
    $this->template = $template;
    $this->key = $key;
    $this->default = $default;
  }

  // Get content as string
  function get_content($method, $language) {
    if (!isset($this->text[$method][$language->language])) {
      if ($part = $this->template
        ->get_part($this->key, NULL, $method, $language)) {
        $text = $part->template;
      }
      else {
        $text = isset($this->default) ? $this->default : '';
      }
      $this->text[$method][$language->language] = $text;
    }
    return $this->text[$method][$language->language];
  }

  // This element needs token replacement
  function needs_replace() {
    return TRUE;
  }

  // Reset text
  function reset($recurse = FALSE) {
    $this->text = NULL;
  }

}

Classes

Namesort descending Description
Messaging_Template Messaging Template class
Messaging_Template_Engine Static template functions that interact with the module API
Messaging_Template_Part Each text part to be rendered independently

Interfaces

Namesort descending Description
Messaging_Template_Element Methods a template element must implement so it can be nested and rendered