You are here

simplenews.source.inc in Simplenews 7

Same filename and directory in other branches
  1. 7.2 includes/simplenews.source.inc

Contains SimplenewsSource interface and implementations.

File

includes/simplenews.source.inc
View source
<?php

/**
 * @file
 * Contains SimplenewsSource interface and implementations.
 */

/**
 * The source used to build a newsletter mail.
 *
 * @ingroup source
 */
interface SimplenewsSourceInterface {

  /**
   * Returns the mail headers.
   *
   * @param $headers
   *   The default mail headers.
   *
   * @return
   *   Mail headers as an array.
   */
  function getHeaders(array $headers);

  /**
   * Returns the mail subject.
   */
  function getSubject();

  /**
   * Returns the mail body.
   *
   * The body should either be plaintext or html, depending on the format.
   */
  function getBody();

  /**
   * Returns the plaintext body.
   */
  function getPlainBody();

  /**
   * Returns the mail footer.
   *
   * The footer should either be plaintext or html, depending on the format.
   */
  function getFooter();

  /**
   * Returns the plain footer.
   */
  function getPlainFooter();

  /**
   * Returns the mail format.
   *
   * @return
   *   The mail format as string, either 'plain' or 'html'.
   */
  function getFormat();

  /**
   * Returns the recipent of this newsletter mail.
   *
   * @return
   *   The recipient mail address(es) of this newsletter as a string.
   */
  function getRecipient();

  /**
   * The language that should be used for this newsletter mail.
   */
  function getLanguage();

  /**
   * Returns an array of attachments for this newsletter mail.
   *
   * @return
   *   An array of managed file objects with properties uri, filemime and so on.
   */
  function getAttachments();

  /**
   * Returns the token context to be used with token replacements.
   *
   * @return
   *   An array of objects as required by token_replace().
   */
  function getTokenContext();

  /**
   * Returns the mail key to be used for drupal_mail().
   *
   * @return
   *   The mail key, either test or node.
   */
  function getKey();

  /**
   * Returns the formatted from mail address.
   */
  function getFromFormatted();

  /**
   * Returns the plain mail address.
   */
  function getFromAddress();

}

/**
 * Source interface based on a node.
 *
 * This is the interface that needs to be implemented to be compatible with
 * the default simplenews spool implementation and therefore exposed in
 * hook_simplenews_source_cache_info().
 *
 * @ingroup source
 */
interface SimplenewsSourceNodeInterface extends SimplenewsSourceInterface {

  /**
   * Create a source based on a node and subscriber.
   */
  function __construct($node, $subscriber);

  /**
   * Returns the actually used node of this source.
   */
  function getNode();

  /**
   * Returns the subscriber object.
   */
  function getSubscriber();

}

/**
 * Interface for a simplenews source cache implementation.
 *
 * This is only compatible with the SimplenewsSourceNodeInterface interface.
 *
 * @ingroup source
 */
interface SimplenewsSourceCacheInterface {

  /**
   * Create a new instance, allows to initialize based on the used
   * source.
   */
  function __construct(SimplenewsSourceNodeInterface $source);

  /**
   * Return a cached element, if existing.
   *
   * Although group and key can be used to identify the requested cache, the
   * implementations are responsible to create a unique cache key themself using
   * the $source. For example based on the node id and the language.
   *
   * @param $group
   *   Group of the cache key, which allows cache implementations to decide what
   *   they want to cache. Currently used groups:
   *     - data: Raw data, e.g. attachments.
   *     - build: Built and themed content, before personalizations like tokens.
   *     - final: The final returned data. Caching this means that newsletter
   *       can not be personalized anymore.
   * @param $key
   *   Identifies the requested element, e.g. body, footer or attachments.
   */
  function get($group, $key);

  /**
   * Write an element to the cache.
   *
   * Although group and key can be used to identify the requested cache, the
   * implementations are responsible to create a unique cache key themself using
   * the $source. For example based on the node id and the language.
   *
   * @param $group
   *   Group of the cache key, which allows cache implementations to decide what
   *   they want to cache. Currently used groups:
   *     - data: Raw data, e.g. attachments.
   *     - build: Built and themed content, before personalizations like tokens.
   *     - final: The final returned data. Caching this means that newsletter
   *       can not be personalized anymore.
   * @param $key
   *   Identifies the requested element, e.g. body, footer or attachments.
   * @param $data
   *   The data to be saved in the cache.
   */
  function set($group, $key, $data);

}

/**
 * A Simplenews spool implementation is a factory for Simplenews sources.
 *
 * Their main functionility is to return a number of sources based on the passed
 * in array of mail spool rows. Additionally, it needs to return the processed
 * mail rows after a source was sent.
 *
 * @todo: Move spool functions into this interface.
 *
 * @ingroup spool
 */
interface SimplenewsSpoolInterface {

  /**
   * Initalizes the spool implementation.
   *
   * @param $spool_list
   *   An array of rows from the {simplenews_mail_spool} table.
   */
  function __construct($pool_list);

  /**
   * Returns a Simplenews source to be sent.
   *
   * A single source may represent any number of mail spool rows, e.g. by
   * addressing them as BCC.
   */
  function nextSource();

  /**
   * Returns the processed mail spool rows, keyed by the msid.
   *
   * Only rows that were processed while preparing the previously returned
   * source must be returned.
   *
   * @return
   *   An array of mail spool rows, keyed by the msid. Can optionally have set
   *   the following additional properties.
   *     - actual_nid: In case of content translation, the source node that was
   *       used for this mail.
   *     - error: FALSE if the prepration for this row failed. For example set
   *       when the corresponding node failed to load.
   *     - status: A simplenews spool status to indicate the status.
   */
  function getProcessed();

}

/**
 * Simplenews Spool implementation.
 *
 * @ingroup spool
 */
class SimplenewsSpool implements SimplenewsSpoolInterface {

  /**
   * Array with mail spool rows being processed.
   *
   * @var array
   */
  protected $spool_list;

  /**
   * Array of the processed mail spool rows.
   */
  protected $processed = array();

  /**
   * Implements SimplenewsSpoolInterface::_construct($spool_list);
   */
  public function __construct($spool_list) {
    $this->spool_list = $spool_list;
  }

  /**
   * Implements SimplenewsSpoolInterface::nextSource();
   */
  public function nextSource() {

    // Get the current mail spool row and update the internal pointer to the
    // next row.
    $return = each($this->spool_list);

    // If we're done, return false.
    if (!$return) {
      return FALSE;
    }
    $spool_data = $return['value'];

    // Store this spool row as processed.
    $this->processed[$spool_data->msid] = $spool_data;
    $node = node_load($spool_data->nid);
    if (!$node) {

      // If node the load failed, set the processed status done and proceed with
      // the next mail.
      $this->processed[$spool_data->msid]->result = array(
        'status' => SIMPLENEWS_SPOOL_DONE,
        'error' => TRUE,
      );
      return $this
        ->nextSource();
    }
    if ($spool_data->data) {
      $subscriber = $spool_data->data;
    }
    else {
      $subscriber = simplenews_subscriber_load_by_mail($spool_data->mail);
    }
    if (!$subscriber) {

      // If loading the subscriber failed, set the processed status done and
      // proceed with the next mail.
      $this->processed[$spool_data->msid]->result = array(
        'status' => SIMPLENEWS_SPOOL_DONE,
        'error' => TRUE,
      );
      return $this
        ->nextSource();
    }
    $source_class = $this
      ->getSourceImplementation($spool_data);
    $source = new $source_class($node, $subscriber);

    // Set which node is actually used. In case of a translation set, this might
    // not be the same node.
    $this->processed[$spool_data->msid]->actual_nid = $source
      ->getNode()->nid;
    return $source;
  }

  /**
   * Implements SimplenewsSpoolInterface::getProcessed();
   */
  function getProcessed() {
    $processed = $this->processed;
    $this->processed = array();
    return $processed;
  }

  /**
   * Return the Simplenews source implementation for the given mail spool row.
   */
  protected function getSourceImplementation($spool_data) {
    return variable_get('simplenews_source', 'SimplenewsSourceNode');
  }

}

/**
 * Simplenews source implementation based on nodes for a single subscriber.
 *
 * @ingroup source
 */
class SimplenewsSourceNode implements SimplenewsSourceNodeInterface {

  /**
   * The node object.
   */
  protected $node;

  /**
   * The cached build render array.
   */
  protected $build;

  /**
   * The newsletter category.
   */
  protected $category;

  /**
   * The subscriber and therefore recipient of this mail.
   */
  protected $subscriber;

  /**
   * The mail key used for drupal_mail().
   */
  protected $key = 'test';

  /**
   * The simplenews newsletter.
   */
  protected $newsletter;

  /**
   * Cache implementation used for this source.
   *
   * @var SimplenewsSourceCacheInterface
   */
  protected $cache;

  /**
   * Implements SimplenewsSourceInterface::_construct();
   */
  public function __construct($node, $subscriber) {
    $this
      ->setSubscriber($subscriber);
    $this
      ->setNode($node);
    $this->newsletter = simplenews_newsletter_load($node->nid);
    $this->category = simplenews_category_load($this->newsletter->tid);
    $this
      ->initCache();
  }

  /**
   * Set the node of this source.
   *
   * If the node is part of a translation set, switch to the node for the
   * requested language, if existent.
   */
  public function setNode($node) {
    $langcode = $this
      ->getLanguage();
    $nid = $node->nid;
    if (module_exists('translation')) {

      // If the node has translations and a translation is required
      // the equivalent of the node in the required language is used
      // or the base node (nid == tnid) is used.
      if ($tnid = $node->tnid) {
        if ($langcode != $node->language) {
          $translations = translation_node_get_translations($tnid);

          // A translation is available in the preferred language.
          if ($translation = $translations[$langcode]) {
            $nid = $translation->nid;
            $langcode = $translation->language;
          }
          else {

            // No translation found which matches the preferred language.
            foreach ($translations as $translation) {
              if ($translation->nid == $tnid) {
                $nid = $tnid;
                $langcode = $translation->language;
                break;
              }
            }
          }
        }
      }
    }

    // If a translation of the node is used, load this node.
    if ($nid != $node->nid) {
      $this->node = node_load($nid);
    }
    else {
      $this->node = $node;
    }
  }

  /**
   * Initialize the cache implementation.
   */
  protected function initCache() {
    $class = variable_get('simplenews_source_cache', 'SimplenewsSourceCacheBuild');
    $this->cache = new $class($this);
  }

  /**
   * Returns the corresponding category.
   */
  public function getCategory() {
    return $this->category;
  }

  /**
   * Set the active subscriber.
   */
  public function setSubscriber($subscriber) {
    $this->subscriber = $subscriber;
  }

  /**
   * Return the subscriber object.
   */
  public function getSubscriber() {
    return $this->subscriber;
  }

  /**
   * Implements SimplenewsSourceInterface::getHeaders().
   */
  public function getHeaders(array $headers) {

    // If receipt is requested, add headers.
    if ($this->category->receipt) {
      $headers['Disposition-Notification-To'] = $this
        ->getFromAddress();
      $headers['X-Confirm-Reading-To'] = $this
        ->getFromAddress();
    }

    // Add priority if set.
    switch ($this->category->priority) {
      case SIMPLENEWS_PRIORITY_HIGHEST:
        $headers['Priority'] = 'High';
        $headers['X-Priority'] = '1';
        $headers['X-MSMail-Priority'] = 'Highest';
        break;
      case SIMPLENEWS_PRIORITY_HIGH:
        $headers['Priority'] = 'urgent';
        $headers['X-Priority'] = '2';
        $headers['X-MSMail-Priority'] = 'High';
        break;
      case SIMPLENEWS_PRIORITY_NORMAL:
        $headers['Priority'] = 'normal';
        $headers['X-Priority'] = '3';
        $headers['X-MSMail-Priority'] = 'Normal';
        break;
      case SIMPLENEWS_PRIORITY_LOW:
        $headers['Priority'] = 'non-urgent';
        $headers['X-Priority'] = '4';
        $headers['X-MSMail-Priority'] = 'Low';
        break;
      case SIMPLENEWS_PRIORITY_LOWEST:
        $headers['Priority'] = 'non-urgent';
        $headers['X-Priority'] = '5';
        $headers['X-MSMail-Priority'] = 'Lowest';
        break;
    }

    // Add user specific header data.
    $headers['From'] = $this
      ->getFromFormatted();
    $headers['List-Unsubscribe'] = '<' . token_replace('[simplenews-subscriber:unsubscribe-url]', $this
      ->getTokenContext(), array(
      'sanitize' => FALSE,
    )) . '>';

    // Add general headers
    $headers['Precedence'] = 'bulk';
    return $headers;
  }

  /**
   * Implements SimplenewsSourceInterface::getTokenContext().
   */
  function getTokenContext() {
    return array(
      'category' => $this
        ->getCategory(),
      'simplenews_subscriber' => $this
        ->getSubscriber(),
      'node' => $this
        ->getNode(),
    );
  }

  /**
   * Set the mail key.
   */
  function setKey($key) {
    $this->key = $key;
  }

  /**
   * Implements SimplenewsSourceInterface::getKey().
   */
  function getKey() {
    return $this->key;
  }

  /**
   * Implements SimplenewsSourceInterface::getFromFormatted().
   */
  function getFromFormatted() {

    // Windows based PHP systems don't accept formatted email addresses.
    if (drupal_substr(PHP_OS, 0, 3) == 'WIN') {
      return $this
        ->getFromAddress();
    }
    return '"' . addslashes(mime_header_encode($this
      ->getCategory()->from_name)) . '" <' . $this
      ->getFromAddress() . '>';
  }

  /**
   * Implements SimplenewsSourceInterface::getFromAddress().
   */
  function getFromAddress() {
    return $this
      ->getCategory()->from_address;
  }

  /**
   * Implements SimplenewsSourceInterface::getRecipient().
   */
  function getRecipient() {
    return $this
      ->getSubscriber()->mail;
  }

  /**
   * Implements SimplenewsSourceInterface::getFormat().
   */
  function getFormat() {
    return $this
      ->getCategory()->format;
  }

  /**
   * Implements SimplenewsSourceInterface::getLanguage().
   */
  function getLanguage() {
    return $this
      ->getSubscriber()->language;
  }

  /**
   * Implements SimplenewsSourceSpoolInterface::getNode().
   */
  function getNode() {
    return $this->node;
  }

  /**
   * Implements SimplenewsSourceInterface::getSubject().
   */
  function getSubject() {

    // Build email subject and perform some sanitizing.
    $langcode = $this
      ->getLanguage();
    $language_list = language_list();

    // Use the requested language if enabled.
    $language = isset($language_list[$langcode]) ? $language_list[$langcode] : NULL;
    $subject = token_replace($this
      ->getCategory()->email_subject, $this
      ->getTokenContext(), array(
      'sanitize' => FALSE,
      'language' => $language,
    ));

    // Line breaks are removed from the email subject to prevent injection of
    // malicious data into the email header.
    $subject = str_replace(array(
      "\r",
      "\n",
    ), '', $subject);
    return $subject;
  }

  /**
   * Set up the necessary language and user context.
   */
  protected function setContext() {

    // Switch to the user
    if ($this->uid = $this
      ->getSubscriber()->uid) {
      simplenews_impersonate_user($this->uid);
    }

    // Change language if the requested language is enabled.
    $language = $this
      ->getLanguage();
    $languages = language_list();
    if (isset($languages[$language])) {
      $this->original_language = $GLOBALS['language'];
      $GLOBALS['language'] = $languages[$language];
      $GLOBALS['language_url'] = $languages[$language];

      // Overwrites the current content language for i18n_select.
      if (module_exists('i18n_select')) {
        $GLOBALS['language_content'] = $languages[$language];
      }
    }
  }

  /**
   * Reset the context.
   */
  protected function resetContext() {

    // Switch back to the previous user.
    if ($this->uid) {
      simplenews_revert_user();
    }

    // Switch language back.
    if (!empty($this->original_language)) {
      $GLOBALS['language'] = $this->original_language;
      $GLOBALS['language_url'] = $this->original_language;
      if (module_exists('i18n_select')) {
        $GLOBALS['language_content'] = $this->original_language;
      }
    }
  }

  /**
   * Build the node object.
   *
   * The resulting build array is cached as it is used in multiple places.
   * @param $format
   *   (Optional) Override the default format. Defaults to getFormat().
   */
  protected function build($format = NULL) {
    if (empty($format)) {
      $format = $this
        ->getFormat();
    }
    if (!empty($this->build[$format])) {
      return $this->build[$format];
    }

    // Build message body
    // Supported view modes: 'email_plain', 'email_html', 'email_textalt'
    $build = node_view($this->node, 'email_' . $format);
    unset($build['#theme']);
    foreach (field_info_instances('node', $this->node->type) as $field_name => $field) {
      if (isset($build[$field_name])) {
        $build[$field_name]['#theme'] = 'simplenews_field';
      }
    }
    $this->build[$format] = $build;
    return $this->build[$format];
  }

  /**
   * Build the themed newsletter body.
   *
   * @param $format
   *   (Optional) Override the default format. Defaults to getFormat().
   */
  protected function buildBody($format = NULL) {
    if (empty($format)) {
      $format = $this
        ->getFormat();
    }
    if ($cache = $this->cache
      ->get('build', 'body:' . $format)) {
      return $cache;
    }
    $body = theme('simplenews_newsletter_body', array(
      'build' => $this
        ->build($format),
      'category' => $this
        ->getCategory(),
      'language' => $this
        ->getLanguage(),
      'simplenews_subscriber' => $this
        ->getSubscriber(),
    ));
    $this->cache
      ->set('build', 'body:' . $format, $body);
    return $body;
  }

  /**
   * Implements SimplenewsSourceInterface::getBody().
   */
  public function getBody() {
    return $this
      ->getBodyWithFormat($this
      ->getFormat());
  }

  /**
   * Implements SimplenewsSourceInterface::getBody().
   */
  public function getPlainBody() {
    return $this
      ->getBodyWithFormat('plain');
  }

  /**
   * Get the body with the requested format.
   *
   * @param $format
   *   Either html or plain.
   *
   * @return
   *   The rendered mail body as a string.
   */
  protected function getBodyWithFormat($format) {

    // Switch to correct user and language context.
    $this
      ->setContext();
    if ($cache = $this->cache
      ->get('final', 'body:' . $format)) {
      return $cache;
    }
    $body = $this
      ->buildBody($format);

    // Build message body, replace tokens.
    $body = token_replace($body, $this
      ->getTokenContext(), array(
      'sanitize' => FALSE,
    ));
    if ($format == 'plain') {

      // Convert HTML to text if requested to do so.
      $body = simplenews_html_to_text($body, $this
        ->getCategory()->hyperlinks);
    }
    $this->cache
      ->set('final', 'body:' . $format, $body);
    $this
      ->resetContext();
    return $body;
  }

  /**
   * Builds the themed footer.
   *
   * @param $format
   *   (Optional) Set the format of this footer build, overrides the default
   *   format.
   */
  protected function buildFooter($format = NULL) {
    if (empty($format)) {
      $format = $this
        ->getFormat();
    }
    if ($cache = $this->cache
      ->get('build', 'footer:' . $format)) {
      return $cache;
    }

    // Build and buffer message footer
    $footer = theme('simplenews_newsletter_footer', array(
      'build' => $this
        ->build($format),
      'category' => $this
        ->getCategory(),
      'context' => $this
        ->getTokenContext(),
      'key' => $this
        ->getKey(),
      'language' => $this
        ->getLanguage(),
      'format' => $format,
    ));
    $this->cache
      ->set('build', 'footer:' . $format, $footer);
    return $footer;
  }

  /**
   * Implements SimplenewsSourceInterface::getFooter().
   */
  public function getFooter() {
    return $this
      ->getFooterWithFormat($this
      ->getFormat());
  }

  /**
   * Implements SimplenewsSourceInterface::getPlainFooter().
   */
  public function getPlainFooter() {
    return $this
      ->getFooterWithFormat('plain');
  }

  /**
   * Get the footer in the specified format.
   *
   * @param $format
   *   Either html or plain.
   *
   * @return
   *   The footer for the requested format.
   */
  protected function getFooterWithFormat($format) {

    // Switch to correct user and language context.
    $this
      ->setContext();
    if ($cache = $this->cache
      ->get('final', 'footer:' . $format)) {
      return $cache;
    }
    $final_footer = token_replace($this
      ->buildFooter($format), $this
      ->getTokenContext(), array(
      'sanitize' => FALSE,
    ));
    $this->cache
      ->set('final', 'footer:' . $format, $final_footer);
    $this
      ->resetContext();
    return $final_footer;
  }

  /**
   * Implements SimplenewsSourceInterface::getAttachments().
   */
  function getAttachments() {
    if ($cache = $this->cache
      ->get('data', 'attachments')) {
      return $cache;
    }
    $attachments = array();
    $build = $this
      ->build();
    $fids = array();
    foreach (field_info_instances('node', $this->node->type) as $field_name => $field_instance) {

      // @todo: Find a better way to support more field types.
      // Only add fields of type file which are enabled for the current view
      // mode as attachments.
      $field = field_info_field($field_name);
      if ($field['type'] == 'file' && isset($build[$field_name])) {
        if ($items = field_get_items('node', $this->node, $field_name)) {
          foreach ($items as $item) {
            $fids[] = $item['fid'];
          }
        }
      }
    }
    if (!empty($fids)) {
      $attachments = file_load_multiple($fids);
    }
    $this->cache
      ->set('data', 'attachments', $attachments);
    return $attachments;
  }

}

/**
 * Abstract implementation of the source caching that does static caching.
 *
 * Subclasses need to implement the abstract function isCacheable() to decide
 * what should be cached.
 *
 * @ingroup source
 */
abstract class SimplenewsSourceCacheStatic implements SimplenewsSourceCacheInterface {

  /**
   * The simplenews source for which this cache is used.
   *
   * @var SimplenewsSourceNodeInterface
   */
  protected $source;

  /**
   * The cache identifier for the given source.
   */
  protected $cid;

  /**
   * The static cache.
   */
  protected static $cache = array();

  /**
   * Implements SimplenewsSourceNodeInterface::__construct().
   */
  public function __construct(SimplenewsSourceNodeInterface $source) {
    $this->source = $source;
    self::$cache =& drupal_static(__CLASS__, array());
  }

  /**
   * Returns the cache identifier for the current source.
   */
  protected function getCid() {
    if (empty($this->cid)) {
      $this->cid = $this->source
        ->getNode()->nid . ':' . $this->source
        ->getLanguage();
    }
    return $this->cid;
  }

  /**
   * Implements SimplenewsSourceNodeInterface::get().
   */
  public function get($group, $key) {
    if (!$this
      ->isCacheable($group, $key)) {
      return;
    }
    if (isset(self::$cache[$this
      ->getCid()][$group][$key])) {
      return self::$cache[$this
        ->getCid()][$group][$key];
    }
  }

  /**
   * Implements SimplenewsSourceNodeInterface::set().
   */
  public function set($group, $key, $data) {
    if (!$this
      ->isCacheable($group, $key)) {
      return;
    }
    self::$cache[$this
      ->getCid()][$group][$key] = $data;
  }

  /**
   * Return if the requested element should be cached.
   *
   * @return
   *   TRUE if it should be cached, FALSE otherwise.
   */
  abstract function isCacheable($group, $key);

}

/**
 * Cache implementation that does not cache anything at all.
 *
 * @ingroup source
 */
class SimplenewsSourceCacheNone extends SimplenewsSourceCacheStatic {

  /**
   * Implements SimplenewsSourceCacheStatic::set().
   */
  public function isCacheable($group, $key) {
    return FALSE;
  }

}

/**
 * Source cache implementation that caches build and data element.
 *
 * @ingroup source
 */
class SimplenewsSourceCacheBuild extends SimplenewsSourceCacheStatic {

  /**
   * Implements SimplenewsSourceCacheStatic::set().
   */
  function isCacheable($group, $key) {

    // Only cache for anon users.
    if (user_is_logged_in()) {
      return FALSE;
    }

    // Only cache data and build information.
    return in_array($group, array(
      'data',
      'build',
    ));
  }

}

/**
 * Example source implementation used for tests.
 *
 * @ingroup source
 */
class SimplenewsSourceTest implements SimplenewsSourceInterface {
  protected $format;
  public function __construct($format) {
    $this->format = $format;
  }
  public function getAttachments() {
    return array(
      array(
        'uri' => 'example://test.png',
        'filemime' => 'x-example',
        'filename' => 'test.png',
      ),
    );
  }
  public function getBody() {
    return $this
      ->getFormat() == 'plain' ? $this
      ->getPlainBody() : 'the body';
  }
  public function getFooter() {
    return $this
      ->getFormat() == 'plain' ? $this
      ->getPlainFooter() : 'the footer';
  }
  public function getPlainFooter() {
    return 'the plain footer';
  }
  public function getFormat() {
    return $this->format;
  }
  public function getFromAddress() {
    return 'test@example.org';
  }
  public function getFromFormatted() {
    return 'Test <test@example.org>';
  }
  public function getHeaders(array $headers) {
    $headers['X-Simplenews-Test'] = 'OK';
    return $headers;
  }
  public function getKey() {
    return 'node';
  }
  public function getLanguage() {
    return 'en';
  }
  public function getPlainBody() {
    return 'the plain body';
  }
  public function getRecipient() {
    return 'recipient@example.org';
  }
  public function getSubject() {
    return 'the subject';
  }
  public function getTokenContext() {
    return array();
  }

}

Classes

Namesort descending Description
SimplenewsSourceCacheBuild Source cache implementation that caches build and data element.
SimplenewsSourceCacheNone Cache implementation that does not cache anything at all.
SimplenewsSourceCacheStatic Abstract implementation of the source caching that does static caching.
SimplenewsSourceNode Simplenews source implementation based on nodes for a single subscriber.
SimplenewsSourceTest Example source implementation used for tests.
SimplenewsSpool Simplenews Spool implementation.

Interfaces

Namesort descending Description
SimplenewsSourceCacheInterface Interface for a simplenews source cache implementation.
SimplenewsSourceInterface The source used to build a newsletter mail.
SimplenewsSourceNodeInterface Source interface based on a node.
SimplenewsSpoolInterface A Simplenews spool implementation is a factory for Simplenews sources.