diff --git a/comment/comment-node-form.js b/comment/comment-node-form.js new file mode 100644 index 0000000..76db240 --- /dev/null +++ b/comment/comment-node-form.js @@ -0,0 +1,32 @@ + +(function ($) { + +Drupal.behaviors.commentFieldsetSummaries = { + attach: function (context) { + $('fieldset.comment-node-settings-form', context).drupalSetSummary(function (context) { + return Drupal.checkPlain($('.form-item-comment input:checked', context).next('label').text()); + }); + + // Provide the summary for the node type form. + $('fieldset.comment-node-type-settings-form', context).drupalSetSummary(function(context) { + var vals = []; + + // Default comment setting. + vals.push($(".form-item-comment select option:selected", context).text()); + + // Threading. + var threading = $(".form-item-comment-default-mode input:checked", context).next('label').text(); + if (threading) { + vals.push(threading); + } + + // Comments per page. + var number = $(".form-item-comment-default-per-page select option:selected", context).val(); + vals.push(Drupal.t('@number comments per page', {'@number': number})); + + return Drupal.checkPlain(vals.join(', ')); + }); + } +}; + +})(jQuery); diff --git a/comment/comment-rtl.css b/comment/comment-rtl.css new file mode 100644 index 0000000..39c3929 --- /dev/null +++ b/comment/comment-rtl.css @@ -0,0 +1,5 @@ + +.indented { + margin-left: 0; + margin-right: 25px; +} diff --git a/comment/comment-wrapper.tpl.php b/comment/comment-wrapper.tpl.php new file mode 100644 index 0000000..c691459 --- /dev/null +++ b/comment/comment-wrapper.tpl.php @@ -0,0 +1,52 @@ + +
' . t('The Comment module allows users to comment on site content, set commenting defaults and permissions, and moderate comments. For more information, see the online handbook entry for Comment module.', array('@comment' => 'http://drupal.org/documentation/modules/comment/')) . '
'; + $output .= 'http://example.com/directory.'));
+ }
+}
+
+/**
+ * Prepare a comment for submission.
+ */
+function comment_submit($comment) {
+ // @todo Legacy support. Remove in Drupal 8.
+ if (is_array($comment)) {
+ $comment += array('subject' => '');
+ $comment = (object) $comment;
+ }
+
+ if (empty($comment->date)) {
+ $comment->date = 'now';
+ }
+ $comment->created = strtotime($comment->date);
+ $comment->changed = REQUEST_TIME;
+
+ // If the comment was posted by a registered user, assign the author's ID.
+ // @todo Too fragile. Should be prepared and stored in comment_form() already.
+ if (!$comment->is_anonymous && !empty($comment->name) && ($account = user_load_by_name($comment->name))) {
+ $comment->uid = $account->uid;
+ }
+ // If the comment was posted by an anonymous user and no author name was
+ // required, use "Anonymous" by default.
+ if ($comment->is_anonymous && (!isset($comment->name) || $comment->name === '')) {
+ $comment->name = variable_get('anonymous', t('Anonymous'));
+ }
+
+ // Validate the comment's subject. If not specified, extract from comment body.
+ if (trim($comment->subject) == '') {
+ // The body may be in any format, so:
+ // 1) Filter it into HTML
+ // 2) Strip out all HTML tags
+ // 3) Convert entities back to plain-text.
+ $field = field_info_field('comment_body');
+ $langcode = field_is_translatable('comment', $field) ? entity_language('comment', $comment) : LANGUAGE_NONE;
+ $comment_body = $comment->comment_body[$langcode][0];
+ if (isset($comment_body['format'])) {
+ $comment_text = check_markup($comment_body['value'], $comment_body['format']);
+ }
+ else {
+ $comment_text = check_plain($comment_body['value']);
+ }
+ $comment->subject = truncate_utf8(trim(decode_entities(strip_tags($comment_text))), 29, TRUE);
+ // Edge cases where the comment body is populated only by HTML tags will
+ // require a default subject.
+ if ($comment->subject == '') {
+ $comment->subject = t('(No subject)');
+ }
+ }
+ return $comment;
+}
+
+/**
+ * Updates the form state's comment entity by processing this submission's values.
+ *
+ * This is the default builder function for the comment form. It is called
+ * during the "Save" and "Preview" submit handlers to retrieve the entity to
+ * save or preview. This function can also be called by a "Next" button of a
+ * wizard to update the form state's entity with the current step's values
+ * before proceeding to the next step.
+ *
+ * @see comment_form()
+ */
+function comment_form_submit_build_comment($form, &$form_state) {
+ $comment = $form_state['comment'];
+ entity_form_submit_build_entity('comment', $comment, $form, $form_state);
+ comment_submit($comment);
+ return $comment;
+}
+
+/**
+ * Process comment form submissions; prepare the comment, store it, and set a redirection target.
+ */
+function comment_form_submit($form, &$form_state) {
+ $node = node_load($form_state['values']['nid']);
+ $comment = comment_form_submit_build_comment($form, $form_state);
+ if (user_access('post comments') && (user_access('administer comments') || $node->comment == COMMENT_NODE_OPEN)) {
+ // Save the anonymous user information to a cookie for reuse.
+ if (user_is_anonymous()) {
+ user_cookie_save(array_intersect_key($form_state['values'], array_flip(array('name', 'mail', 'homepage'))));
+ }
+
+ comment_save($comment);
+ $form_state['values']['cid'] = $comment->cid;
+
+ // Add an entry to the watchdog log.
+ watchdog('content', 'Comment posted: %subject.', array('%subject' => $comment->subject), WATCHDOG_NOTICE, l(t('view'), 'comment/' . $comment->cid, array('fragment' => 'comment-' . $comment->cid)));
+
+ // Explain the approval queue if necessary.
+ if ($comment->status == COMMENT_NOT_PUBLISHED) {
+ if (!user_access('administer comments')) {
+ drupal_set_message(t('Your comment has been queued for review by site administrators and will be published after approval.'));
+ }
+ }
+ else {
+ drupal_set_message(t('Your comment has been posted.'));
+ }
+ $query = array();
+ // Find the current display page for this comment.
+ $page = comment_get_display_page($comment->cid, $node->type);
+ if ($page > 0) {
+ $query['page'] = $page;
+ }
+ // Redirect to the newly posted comment.
+ $redirect = array('node/' . $node->nid, array('query' => $query, 'fragment' => 'comment-' . $comment->cid));
+ }
+ else {
+ watchdog('content', 'Comment: unauthorized comment submitted or comment submitted to a closed post %subject.', array('%subject' => $comment->subject), WATCHDOG_WARNING);
+ drupal_set_message(t('Comment: unauthorized comment submitted or comment submitted to a closed post %subject.', array('%subject' => $comment->subject)), 'error');
+ // Redirect the user to the node they are commenting on.
+ $redirect = 'node/' . $node->nid;
+ }
+ $form_state['redirect'] = $redirect;
+ // Clear the block and page caches so that anonymous users see the comment
+ // they have posted.
+ cache_clear_all();
+}
+
+/**
+ * Process variables for comment.tpl.php.
+ *
+ * @see comment.tpl.php
+ */
+function template_preprocess_comment(&$variables) {
+ $comment = $variables['elements']['#comment'];
+ $node = $variables['elements']['#node'];
+ $variables['comment'] = $comment;
+ $variables['node'] = $node;
+ $variables['author'] = theme('username', array('account' => $comment));
+
+ $variables['created'] = format_date($comment->created);
+
+ // Avoid calling format_date() twice on the same timestamp.
+ if ($comment->changed == $comment->created) {
+ $variables['changed'] = $variables['created'];
+ }
+ else {
+ $variables['changed'] = format_date($comment->changed);
+ }
+
+ $variables['new'] = !empty($comment->new) ? t('new') : '';
+ $variables['picture'] = theme_get_setting('toggle_comment_user_picture') ? theme('user_picture', array('account' => $comment)) : '';
+ $variables['signature'] = $comment->signature;
+
+ $uri = entity_uri('comment', $comment);
+ $uri['options'] += array('attributes' => array('class' => array('permalink'), 'rel' => 'bookmark'));
+
+ $variables['title'] = l($comment->subject, $uri['path'], $uri['options']);
+ $variables['permalink'] = l(t('Permalink'), $uri['path'], $uri['options']);
+ $variables['submitted'] = t('Submitted by !username on !datetime', array('!username' => $variables['author'], '!datetime' => $variables['created']));
+
+ // Preprocess fields.
+ field_attach_preprocess('comment', $comment, $variables['elements'], $variables);
+
+ // Helpful $content variable for templates.
+ foreach (element_children($variables['elements']) as $key) {
+ $variables['content'][$key] = $variables['elements'][$key];
+ }
+
+ // Set status to a string representation of comment->status.
+ if (isset($comment->in_preview)) {
+ $variables['status'] = 'comment-preview';
+ }
+ else {
+ $variables['status'] = ($comment->status == COMMENT_NOT_PUBLISHED) ? 'comment-unpublished' : 'comment-published';
+ }
+
+ // Gather comment classes.
+ // 'comment-published' class is not needed, it is either 'comment-preview' or
+ // 'comment-unpublished'.
+ if ($variables['status'] != 'comment-published') {
+ $variables['classes_array'][] = $variables['status'];
+ }
+ if ($variables['new']) {
+ $variables['classes_array'][] = 'comment-new';
+ }
+ if (!$comment->uid) {
+ $variables['classes_array'][] = 'comment-by-anonymous';
+ }
+ else {
+ if ($comment->uid == $variables['node']->uid) {
+ $variables['classes_array'][] = 'comment-by-node-author';
+ }
+ if ($comment->uid == $variables['user']->uid) {
+ $variables['classes_array'][] = 'comment-by-viewer';
+ }
+ }
+}
+
+/**
+ * Returns HTML for a "you can't post comments" notice.
+ *
+ * @param $variables
+ * An associative array containing:
+ * - node: The comment node.
+ *
+ * @ingroup themeable
+ */
+function theme_comment_post_forbidden($variables) {
+ $node = $variables['node'];
+ global $user;
+
+ // Since this is expensive to compute, we cache it so that a page with many
+ // comments only has to query the database once for all the links.
+ $authenticated_post_comments = &drupal_static(__FUNCTION__, NULL);
+
+ if (!$user->uid) {
+ if (!isset($authenticated_post_comments)) {
+ // We only output a link if we are certain that users will get permission
+ // to post comments by logging in.
+ $comment_roles = user_roles(TRUE, 'post comments');
+ $authenticated_post_comments = isset($comment_roles[DRUPAL_AUTHENTICATED_RID]);
+ }
+
+ if ($authenticated_post_comments) {
+ // We cannot use drupal_get_destination() because these links
+ // sometimes appear on /node and taxonomy listing pages.
+ if (variable_get('comment_form_location_' . $node->type, COMMENT_FORM_BELOW) == COMMENT_FORM_SEPARATE_PAGE) {
+ $destination = array('destination' => "comment/reply/$node->nid#comment-form");
+ }
+ else {
+ $destination = array('destination' => "node/$node->nid#comment-form");
+ }
+
+ if (variable_get('user_register', USER_REGISTER_VISITORS_ADMINISTRATIVE_APPROVAL)) {
+ // Users can register themselves.
+ return t('Log in or register to post comments', array('@login' => url('user/login', array('query' => $destination)), '@register' => url('user/register', array('query' => $destination))));
+ }
+ else {
+ // Only admins can add new users, no public registration.
+ return t('Log in to post comments', array('@login' => url('user/login', array('query' => $destination))));
+ }
+ }
+ }
+}
+
+/**
+ * Process variables for comment-wrapper.tpl.php.
+ *
+ * @see comment-wrapper.tpl.php
+ */
+function template_preprocess_comment_wrapper(&$variables) {
+ // Provide contextual information.
+ $variables['node'] = $variables['content']['#node'];
+ $variables['display_mode'] = variable_get('comment_default_mode_' . $variables['node']->type, COMMENT_MODE_THREADED);
+ // The comment form is optional and may not exist.
+ $variables['content'] += array('comment_form' => array());
+}
+
+/**
+ * Return an array of viewing modes for comment listings.
+ *
+ * We can't use a global variable array because the locale system
+ * is not initialized yet when the comment module is loaded.
+ */
+function _comment_get_modes() {
+ return array(
+ COMMENT_MODE_FLAT => t('Flat list'),
+ COMMENT_MODE_THREADED => t('Threaded list')
+ );
+}
+
+/**
+ * Return an array of "comments per page" settings from which the user
+ * can choose.
+ */
+function _comment_per_page() {
+ return drupal_map_assoc(array(10, 30, 50, 70, 90, 150, 200, 250, 300));
+}
+
+/**
+ * Updates the comment statistics for a given node. This should be called any
+ * time a comment is added, deleted, or updated.
+ *
+ * The following fields are contained in the node_comment_statistics table.
+ * - last_comment_timestamp: the timestamp of the last comment for this node or the node create stamp if no comments exist for the node.
+ * - last_comment_name: the name of the anonymous poster for the last comment
+ * - last_comment_uid: the uid of the poster for the last comment for this node or the node authors uid if no comments exists for the node.
+ * - comment_count: the total number of approved/published comments on this node.
+ */
+function _comment_update_node_statistics($nid) {
+ // Allow bulk updates and inserts to temporarily disable the
+ // maintenance of the {node_comment_statistics} table.
+ if (!variable_get('comment_maintain_node_statistics', TRUE)) {
+ return;
+ }
+
+ $count = db_query('SELECT COUNT(cid) FROM {comment} WHERE nid = :nid AND status = :status', array(
+ ':nid' => $nid,
+ ':status' => COMMENT_PUBLISHED,
+ ))->fetchField();
+
+ if ($count > 0) {
+ // Comments exist.
+ $last_reply = db_query_range('SELECT cid, name, changed, uid FROM {comment} WHERE nid = :nid AND status = :status ORDER BY cid DESC', 0, 1, array(
+ ':nid' => $nid,
+ ':status' => COMMENT_PUBLISHED,
+ ))->fetchObject();
+ db_update('node_comment_statistics')
+ ->fields(array(
+ 'cid' => $last_reply->cid,
+ 'comment_count' => $count,
+ 'last_comment_timestamp' => $last_reply->changed,
+ 'last_comment_name' => $last_reply->uid ? '' : $last_reply->name,
+ 'last_comment_uid' => $last_reply->uid,
+ ))
+ ->condition('nid', $nid)
+ ->execute();
+ }
+ else {
+ // Comments do not exist.
+ $node = db_query('SELECT uid, created FROM {node} WHERE nid = :nid', array(':nid' => $nid))->fetchObject();
+ db_update('node_comment_statistics')
+ ->fields(array(
+ 'cid' => 0,
+ 'comment_count' => 0,
+ 'last_comment_timestamp' => $node->created,
+ 'last_comment_name' => '',
+ 'last_comment_uid' => $node->uid,
+ ))
+ ->condition('nid', $nid)
+ ->execute();
+ }
+}
+
+/**
+ * Generate vancode.
+ *
+ * Consists of a leading character indicating length, followed by N digits
+ * with a numerical value in base 36. Vancodes can be sorted as strings
+ * without messing up numerical order.
+ *
+ * It goes:
+ * 00, 01, 02, ..., 0y, 0z,
+ * 110, 111, ... , 1zy, 1zz,
+ * 2100, 2101, ..., 2zzy, 2zzz,
+ * 31000, 31001, ...
+ */
+function int2vancode($i = 0) {
+ $num = base_convert((int) $i, 10, 36);
+ $length = strlen($num);
+
+ return chr($length + ord('0') - 1) . $num;
+}
+
+/**
+ * Decode vancode back to an integer.
+ */
+function vancode2int($c = '00') {
+ return base_convert(substr($c, 1), 36, 10);
+}
+
+/**
+ * Implements hook_action_info().
+ */
+function comment_action_info() {
+ return array(
+ 'comment_publish_action' => array(
+ 'label' => t('Publish comment'),
+ 'type' => 'comment',
+ 'configurable' => FALSE,
+ 'behavior' => array('changes_property'),
+ 'triggers' => array('comment_presave', 'comment_insert', 'comment_update'),
+ ),
+ 'comment_unpublish_action' => array(
+ 'label' => t('Unpublish comment'),
+ 'type' => 'comment',
+ 'configurable' => FALSE,
+ 'behavior' => array('changes_property'),
+ 'triggers' => array('comment_presave', 'comment_insert', 'comment_update'),
+ ),
+ 'comment_unpublish_by_keyword_action' => array(
+ 'label' => t('Unpublish comment containing keyword(s)'),
+ 'type' => 'comment',
+ 'configurable' => TRUE,
+ 'behavior' => array('changes_property'),
+ 'triggers' => array('comment_presave', 'comment_insert', 'comment_update'),
+ ),
+ 'comment_save_action' => array(
+ 'label' => t('Save comment'),
+ 'type' => 'comment',
+ 'configurable' => FALSE,
+ 'triggers' => array('comment_insert', 'comment_update'),
+ ),
+ );
+}
+
+/**
+ * Publishes a comment.
+ *
+ * @param $comment
+ * An optional comment object.
+ * @param array $context
+ * Array with components:
+ * - 'cid': Comment ID. Required if $comment is not given.
+ *
+ * @ingroup actions
+ */
+function comment_publish_action($comment, $context = array()) {
+ if (isset($comment->subject)) {
+ $subject = $comment->subject;
+ $comment->status = COMMENT_PUBLISHED;
+ }
+ else {
+ $cid = $context['cid'];
+ $subject = db_query('SELECT subject FROM {comment} WHERE cid = :cid', array(':cid' => $cid))->fetchField();
+ db_update('comment')
+ ->fields(array('status' => COMMENT_PUBLISHED))
+ ->condition('cid', $cid)
+ ->execute();
+ }
+ watchdog('action', 'Published comment %subject.', array('%subject' => $subject));
+}
+
+/**
+ * Unpublishes a comment.
+ *
+ * @param $comment
+ * An optional comment object.
+ * @param array $context
+ * Array with components:
+ * - 'cid': Comment ID. Required if $comment is not given.
+ *
+ * @ingroup actions
+ */
+function comment_unpublish_action($comment, $context = array()) {
+ if (isset($comment->subject)) {
+ $subject = $comment->subject;
+ $comment->status = COMMENT_NOT_PUBLISHED;
+ }
+ else {
+ $cid = $context['cid'];
+ $subject = db_query('SELECT subject FROM {comment} WHERE cid = :cid', array(':cid' => $cid))->fetchField();
+ db_update('comment')
+ ->fields(array('status' => COMMENT_NOT_PUBLISHED))
+ ->condition('cid', $cid)
+ ->execute();
+ }
+ watchdog('action', 'Unpublished comment %subject.', array('%subject' => $subject));
+}
+
+/**
+ * Unpublishes a comment if it contains certain keywords.
+ *
+ * @param object $comment
+ * Comment object to modify.
+ * @param array $context
+ * Array with components:
+ * - 'keywords': Keywords to look for. If the comment contains at least one
+ * of the keywords, it is unpublished.
+ *
+ * @ingroup actions
+ * @see comment_unpublish_by_keyword_action_form()
+ * @see comment_unpublish_by_keyword_action_submit()
+ */
+function comment_unpublish_by_keyword_action($comment, $context) {
+ $node = node_load($comment->nid);
+ $build = comment_view($comment, $node);
+ $text = drupal_render($build);
+ foreach ($context['keywords'] as $keyword) {
+ if (strpos($text, $keyword) !== FALSE) {
+ $comment->status = COMMENT_NOT_PUBLISHED;
+ comment_save($comment);
+ watchdog('action', 'Unpublished comment %subject.', array('%subject' => $comment->subject));
+ break;
+ }
+ }
+}
+
+/**
+ * Form builder; Prepare a form for blacklisted keywords.
+ *
+ * @ingroup forms
+ * @see comment_unpublish_by_keyword_action()
+ * @see comment_unpublish_by_keyword_action_submit()
+ */
+function comment_unpublish_by_keyword_action_form($context) {
+ $form['keywords'] = array(
+ '#title' => t('Keywords'),
+ '#type' => 'textarea',
+ '#description' => t('The comment will be unpublished if it contains any of the phrases above. Use a case-sensitive, comma-separated list of phrases. Example: funny, bungee jumping, "Company, Inc."'),
+ '#default_value' => isset($context['keywords']) ? drupal_implode_tags($context['keywords']) : '',
+ );
+
+ return $form;
+}
+
+/**
+ * Process comment_unpublish_by_keyword_action_form form submissions.
+ *
+ * @see comment_unpublish_by_keyword_action()
+ */
+function comment_unpublish_by_keyword_action_submit($form, $form_state) {
+ return array('keywords' => drupal_explode_tags($form_state['values']['keywords']));
+}
+
+/**
+ * Saves a comment.
+ *
+ * @ingroup actions
+ */
+function comment_save_action($comment) {
+ comment_save($comment);
+ cache_clear_all();
+ watchdog('action', 'Saved comment %title', array('%title' => $comment->subject));
+}
+
+/**
+ * Implements hook_ranking().
+ */
+function comment_ranking() {
+ return array(
+ 'comments' => array(
+ 'title' => t('Number of comments'),
+ 'join' => array(
+ 'type' => 'LEFT',
+ 'table' => 'node_comment_statistics',
+ 'alias' => 'node_comment_statistics',
+ 'on' => 'node_comment_statistics.nid = i.sid',
+ ),
+ // Inverse law that maps the highest reply count on the site to 1 and 0 to 0.
+ 'score' => '2.0 - 2.0 / (1.0 + node_comment_statistics.comment_count * CAST(:scale AS DECIMAL))',
+ 'arguments' => array(':scale' => variable_get('node_cron_comments_scale', 0)),
+ ),
+ );
+}
+
+/**
+ * Implements hook_rdf_mapping().
+ */
+function comment_rdf_mapping() {
+ return array(
+ array(
+ 'type' => 'comment',
+ 'bundle' => RDF_DEFAULT_BUNDLE,
+ 'mapping' => array(
+ 'rdftype' => array('sioc:Post', 'sioct:Comment'),
+ 'title' => array(
+ 'predicates' => array('dc:title'),
+ ),
+ 'created' => array(
+ 'predicates' => array('dc:date', 'dc:created'),
+ 'datatype' => 'xsd:dateTime',
+ 'callback' => 'date_iso8601',
+ ),
+ 'changed' => array(
+ 'predicates' => array('dc:modified'),
+ 'datatype' => 'xsd:dateTime',
+ 'callback' => 'date_iso8601',
+ ),
+ 'comment_body' => array(
+ 'predicates' => array('content:encoded'),
+ ),
+ 'pid' => array(
+ 'predicates' => array('sioc:reply_of'),
+ 'type' => 'rel',
+ ),
+ 'uid' => array(
+ 'predicates' => array('sioc:has_creator'),
+ 'type' => 'rel',
+ ),
+ 'name' => array(
+ 'predicates' => array('foaf:name'),
+ ),
+ ),
+ ),
+ );
+}
+
+/**
+ * Implements hook_file_download_access().
+ */
+function comment_file_download_access($field, $entity_type, $entity) {
+ if ($entity_type == 'comment') {
+ if (user_access('access comments') && $entity->status == COMMENT_PUBLISHED || user_access('administer comments')) {
+ $node = node_load($entity->nid);
+ return node_access('view', $node);
+ }
+ return FALSE;
+ }
+}
diff --git a/comment/comment.pages.inc b/comment/comment.pages.inc
new file mode 100644
index 0000000..482e3f2
--- /dev/null
+++ b/comment/comment.pages.inc
@@ -0,0 +1,123 @@
+title, 'node/' . $node->nid)));
+ $op = isset($_POST['op']) ? $_POST['op'] : '';
+ $build = array();
+
+ // The user is previewing a comment prior to submitting it.
+ if ($op == t('Preview')) {
+ if (user_access('post comments')) {
+ $build['comment_form'] = drupal_get_form("comment_node_{$node->type}_form", (object) array('pid' => $pid, 'nid' => $node->nid));
+ }
+ else {
+ drupal_set_message(t('You are not authorized to post comments.'), 'error');
+ drupal_goto("node/$node->nid");
+ }
+ }
+ else {
+ // $pid indicates that this is a reply to a comment.
+ if ($pid) {
+ if (user_access('access comments')) {
+ // Load the comment whose cid = $pid
+ $comment = db_query('SELECT c.*, u.uid, u.name AS registered_name, u.signature, u.signature_format, u.picture, u.data FROM {comment} c INNER JOIN {users} u ON c.uid = u.uid WHERE c.cid = :cid AND c.status = :status', array(
+ ':cid' => $pid,
+ ':status' => COMMENT_PUBLISHED,
+ ))->fetchObject();
+ if ($comment) {
+ // If that comment exists, make sure that the current comment and the
+ // parent comment both belong to the same parent node.
+ if ($comment->nid != $node->nid) {
+ // Attempting to reply to a comment not belonging to the current nid.
+ drupal_set_message(t('The comment you are replying to does not exist.'), 'error');
+ drupal_goto("node/$node->nid");
+ }
+ // Display the parent comment
+ $comment->node_type = 'comment_node_' . $node->type;
+ field_attach_load('comment', array($comment->cid => $comment));
+ $comment->name = $comment->uid ? $comment->registered_name : $comment->name;
+ $build['comment_parent'] = comment_view($comment, $node);
+ }
+ else {
+ drupal_set_message(t('The comment you are replying to does not exist.'), 'error');
+ drupal_goto("node/$node->nid");
+ }
+ }
+ else {
+ drupal_set_message(t('You are not authorized to view comments.'), 'error');
+ drupal_goto("node/$node->nid");
+ }
+ }
+ // This is the case where the comment is in response to a node. Display the node.
+ elseif (user_access('access content')) {
+ $build['comment_node'] = node_view($node);
+ }
+
+ // Should we show the reply box?
+ if ($node->comment != COMMENT_NODE_OPEN) {
+ drupal_set_message(t("This discussion is closed: you can't post new comments."), 'error');
+ drupal_goto("node/$node->nid");
+ }
+ elseif (user_access('post comments')) {
+ $edit = array('nid' => $node->nid, 'pid' => $pid);
+ $build['comment_form'] = drupal_get_form("comment_node_{$node->type}_form", (object) $edit);
+ }
+ else {
+ drupal_set_message(t('You are not authorized to post comments.'), 'error');
+ drupal_goto("node/$node->nid");
+ }
+ }
+
+ return $build;
+}
+
+/**
+ * Menu callback; publish specified comment.
+ *
+ * @param $cid
+ * A comment identifier.
+ */
+function comment_approve($cid) {
+ if (!isset($_GET['token']) || !drupal_valid_token($_GET['token'], "comment/$cid/approve")) {
+ return MENU_ACCESS_DENIED;
+ }
+ if ($comment = comment_load($cid)) {
+ $comment->status = COMMENT_PUBLISHED;
+ comment_save($comment);
+
+ drupal_set_message(t('Comment approved.'));
+ drupal_goto('node/' . $comment->nid);
+ }
+ return MENU_NOT_FOUND;
+}
diff --git a/comment/comment.test b/comment/comment.test
new file mode 100644
index 0000000..534b2c1
--- /dev/null
+++ b/comment/comment.test
@@ -0,0 +1,2260 @@
+admin_user = $this->drupalCreateUser(array('administer content types', 'administer comments', 'administer blocks', 'administer actions', 'administer fields'));
+ $this->web_user = $this->drupalCreateUser(array('access comments', 'post comments', 'create article content', 'edit own comments'));
+ $this->node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1, 'uid' => $this->web_user->uid));
+ }
+
+ /**
+ * Post comment.
+ *
+ * @param $node
+ * Node to post comment on.
+ * @param $comment
+ * Comment body.
+ * @param $subject
+ * Comment subject.
+ * @param $contact
+ * Set to NULL for no contact info, TRUE to ignore success checking, and
+ * array of values to set contact info.
+ */
+ function postComment($node, $comment, $subject = '', $contact = NULL) {
+ $langcode = LANGUAGE_NONE;
+ $edit = array();
+ $edit['comment_body[' . $langcode . '][0][value]'] = $comment;
+
+ $preview_mode = variable_get('comment_preview_article', DRUPAL_OPTIONAL);
+ $subject_mode = variable_get('comment_subject_field_article', 1);
+
+ // Must get the page before we test for fields.
+ if ($node !== NULL) {
+ $this->drupalGet('comment/reply/' . $node->nid);
+ }
+
+ if ($subject_mode == TRUE) {
+ $edit['subject'] = $subject;
+ }
+ else {
+ $this->assertNoFieldByName('subject', '', 'Subject field not found.');
+ }
+
+ if ($contact !== NULL && is_array($contact)) {
+ $edit += $contact;
+ }
+ switch ($preview_mode) {
+ case DRUPAL_REQUIRED:
+ // Preview required so no save button should be found.
+ $this->assertNoFieldByName('op', t('Save'), 'Save button not found.');
+ $this->drupalPost(NULL, $edit, t('Preview'));
+ // Don't break here so that we can test post-preview field presence and
+ // function below.
+ case DRUPAL_OPTIONAL:
+ $this->assertFieldByName('op', t('Preview'), 'Preview button found.');
+ $this->assertFieldByName('op', t('Save'), 'Save button found.');
+ $this->drupalPost(NULL, $edit, t('Save'));
+ break;
+
+ case DRUPAL_DISABLED:
+ $this->assertNoFieldByName('op', t('Preview'), 'Preview button not found.');
+ $this->assertFieldByName('op', t('Save'), 'Save button found.');
+ $this->drupalPost(NULL, $edit, t('Save'));
+ break;
+ }
+ $match = array();
+ // Get comment ID
+ preg_match('/#comment-([0-9]+)/', $this->getURL(), $match);
+
+ // Get comment.
+ if ($contact !== TRUE) { // If true then attempting to find error message.
+ if ($subject) {
+ $this->assertText($subject, 'Comment subject posted.');
+ }
+ $this->assertText($comment, 'Comment body posted.');
+ $this->assertTrue((!empty($match) && !empty($match[1])), 'Comment id found.');
+ }
+
+ if (isset($match[1])) {
+ return (object) array('id' => $match[1], 'subject' => $subject, 'comment' => $comment);
+ }
+ }
+
+ /**
+ * Checks current page for specified comment.
+ *
+ * @param object $comment Comment object.
+ * @param boolean $reply The comment is a reply to another comment.
+ * @return boolean Comment found.
+ */
+ function commentExists($comment, $reply = FALSE) {
+ if ($comment && is_object($comment)) {
+ $regex = '/' . ($reply ? '@verbose', array( + '@verbose' => var_export($verbose, TRUE), + )); + $this->assert('debug', $message, 'Debug'); + + // Update current environment. + $current = $info; + + return $info; + } + + /** + * Asserts that comment links appear according to the passed environment setup. + * + * @param $info + * An associative array describing the environment to pass to + * setEnvironment(). + */ + function assertCommentLinks(array $info) { + $info = $this->setEnvironment($info); + + $nid = $this->node->nid; + + foreach (array('', "node/$nid") as $path) { + $this->drupalGet($path); + + // User is allowed to view comments. + if ($info['access comments']) { + if ($path == '') { + // In teaser view, a link containing the comment count is always + // expected. + if ($info['comment count']) { + $this->assertLink(t('1 comment')); + + // For logged in users, a link containing the amount of new/unread + // comments is expected. + // See important note about comment_num_new() below. + if ($this->loggedInUser && isset($this->comment) && !isset($this->comment->seen)) { + $this->assertLink(t('1 new comment')); + $this->comment->seen = TRUE; + } + } + } + } + else { + $this->assertNoLink(t('1 comment')); + $this->assertNoLink(t('1 new comment')); + } + // comment_num_new() is based on node views, so comments are marked as + // read when a node is viewed, regardless of whether we have access to + // comments. + if ($path == "node/$nid" && $this->loggedInUser && isset($this->comment)) { + $this->comment->seen = TRUE; + } + + // User is not allowed to post comments. + if (!$info['post comments']) { + $this->assertNoLink('Add new comment'); + + // Anonymous users should see a note to log in or register in case + // authenticated users are allowed to post comments. + // @see theme_comment_post_forbidden() + if (!$this->loggedInUser) { + if (user_access('post comments', $this->web_user)) { + // The note depends on whether users are actually able to register. + if ($info['user_register']) { + $this->assertText('Log in or register to post comments'); + } + else { + $this->assertText('Log in to post comments'); + } + } + else { + $this->assertNoText('Log in or register to post comments'); + $this->assertNoText('Log in to post comments'); + } + } + } + // User is allowed to post comments. + else { + $this->assertNoText('Log in or register to post comments'); + + // "Add new comment" is always expected, except when there are no + // comments or if the user cannot see them. + if ($path == "node/$nid" && $info['form'] == COMMENT_FORM_BELOW && (!$info['comment count'] || !$info['access comments'])) { + $this->assertNoLink('Add new comment'); + } + else { + $this->assertLink('Add new comment'); + } + + // Also verify that the comment form appears according to the configured + // location. + if ($path == "node/$nid") { + $elements = $this->xpath('//form[@id=:id]', array(':id' => 'comment-form')); + if ($info['form'] == COMMENT_FORM_BELOW) { + $this->assertTrue(count($elements), 'Comment form found below.'); + } + else { + $this->assertFalse(count($elements), 'Comment form not found below.'); + } + } + } + } + } +} + +/** + * Test previewing comments. + */ +class CommentPreviewTest extends CommentHelperCase { + public static function getInfo() { + return array( + 'name' => 'Comment preview', + 'description' => 'Test comment preview.', + 'group' => 'Comment', + ); + } + + /** + * Test comment preview. + */ + function testCommentPreview() { + $langcode = LANGUAGE_NONE; + + // As admin user, configure comment settings. + $this->drupalLogin($this->admin_user); + $this->setCommentPreview(DRUPAL_OPTIONAL); + $this->setCommentForm(TRUE); + $this->setCommentSubject(TRUE); + $this->setCommentSettings('comment_default_mode', COMMENT_MODE_THREADED, 'Comment paging changed.'); + $this->drupalLogout(); + + // Login as web user and add a signature and a user picture. + $this->drupalLogin($this->web_user); + variable_set('user_signatures', 1); + variable_set('user_pictures', 1); + $test_signature = $this->randomName(); + $edit['signature[value]'] = '' . $test_signature. ''; + $edit['signature[format]'] = 'filtered_html'; + $image = current($this->drupalGetTestFiles('image')); + $edit['files[picture_upload]'] = drupal_realpath($image->uri); + $this->drupalPost('user/' . $this->web_user->uid . '/edit', $edit, t('Save')); + + // As the web user, fill in the comment form and preview the comment. + $edit = array(); + $edit['subject'] = $this->randomName(8); + $edit['comment_body[' . $langcode . '][0][value]'] = $this->randomName(16); + $this->drupalPost('node/' . $this->node->nid, $edit, t('Preview')); + + // Check that the preview is displaying the title and body. + $this->assertTitle(t('Preview comment | Drupal'), 'Page title is "Preview comment".'); + $this->assertText($edit['subject'], 'Subject displayed.'); + $this->assertText($edit['comment_body[' . $langcode . '][0][value]'], 'Comment displayed.'); + + // Check that the title and body fields are displayed with the correct values. + $this->assertFieldByName('subject', $edit['subject'], 'Subject field displayed.'); + $this->assertFieldByName('comment_body[' . $langcode . '][0][value]', $edit['comment_body[' . $langcode . '][0][value]'], 'Comment field displayed.'); + + // Check that the signature is displaying with the correct text format. + $this->assertLink($test_signature); + + // Check that the user picture is displayed. + $this->assertFieldByXPath("//div[contains(@class, 'comment-preview')]//div[contains(@class, 'user-picture')]//img", NULL, 'User picture displayed.'); + } + + /** + * Test comment edit, preview, and save. + */ + function testCommentEditPreviewSave() { + $langcode = LANGUAGE_NONE; + $web_user = $this->drupalCreateUser(array('access comments', 'post comments', 'skip comment approval')); + $this->drupalLogin($this->admin_user); + $this->setCommentPreview(DRUPAL_OPTIONAL); + $this->setCommentForm(TRUE); + $this->setCommentSubject(TRUE); + $this->setCommentSettings('comment_default_mode', COMMENT_MODE_THREADED, 'Comment paging changed.'); + + $edit = array(); + $edit['subject'] = $this->randomName(8); + $edit['comment_body[' . $langcode . '][0][value]'] = $this->randomName(16); + $edit['name'] = $web_user->name; + $edit['date'] = '2008-03-02 17:23 +0300'; + $raw_date = strtotime($edit['date']); + $expected_text_date = format_date($raw_date); + $expected_form_date = format_date($raw_date, 'custom', 'Y-m-d H:i O'); + $comment = $this->postComment($this->node, $edit['subject'], $edit['comment_body[' . $langcode . '][0][value]'], TRUE); + $this->drupalPost('comment/' . $comment->id . '/edit', $edit, t('Preview')); + + // Check that the preview is displaying the subject, comment, author and date correctly. + $this->assertTitle(t('Preview comment | Drupal'), 'Page title is "Preview comment".'); + $this->assertText($edit['subject'], 'Subject displayed.'); + $this->assertText($edit['comment_body[' . $langcode . '][0][value]'], 'Comment displayed.'); + $this->assertText($edit['name'], 'Author displayed.'); + $this->assertText($expected_text_date, 'Date displayed.'); + + // Check that the subject, comment, author and date fields are displayed with the correct values. + $this->assertFieldByName('subject', $edit['subject'], 'Subject field displayed.'); + $this->assertFieldByName('comment_body[' . $langcode . '][0][value]', $edit['comment_body[' . $langcode . '][0][value]'], 'Comment field displayed.'); + $this->assertFieldByName('name', $edit['name'], 'Author field displayed.'); + $this->assertFieldByName('date', $edit['date'], 'Date field displayed.'); + + // Check that saving a comment produces a success message. + $this->drupalPost('comment/' . $comment->id . '/edit', $edit, t('Save')); + $this->assertText(t('Your comment has been posted.'), 'Comment posted.'); + + // Check that the comment fields are correct after loading the saved comment. + $this->drupalGet('comment/' . $comment->id . '/edit'); + $this->assertFieldByName('subject', $edit['subject'], 'Subject field displayed.'); + $this->assertFieldByName('comment_body[' . $langcode . '][0][value]', $edit['comment_body[' . $langcode . '][0][value]'], 'Comment field displayed.'); + $this->assertFieldByName('name', $edit['name'], 'Author field displayed.'); + $this->assertFieldByName('date', $expected_form_date, 'Date field displayed.'); + + // Submit the form using the displayed values. + $displayed = array(); + $displayed['subject'] = (string) current($this->xpath("//input[@id='edit-subject']/@value")); + $displayed['comment_body[' . $langcode . '][0][value]'] = (string) current($this->xpath("//textarea[@id='edit-comment-body-" . $langcode . "-0-value']")); + $displayed['name'] = (string) current($this->xpath("//input[@id='edit-name']/@value")); + $displayed['date'] = (string) current($this->xpath("//input[@id='edit-date']/@value")); + $this->drupalPost('comment/' . $comment->id . '/edit', $displayed, t('Save')); + + // Check that the saved comment is still correct. + $comment_loaded = comment_load($comment->id); + $this->assertEqual($comment_loaded->subject, $edit['subject'], 'Subject loaded.'); + $this->assertEqual($comment_loaded->comment_body[$langcode][0]['value'], $edit['comment_body[' . $langcode . '][0][value]'], 'Comment body loaded.'); + $this->assertEqual($comment_loaded->name, $edit['name'], 'Name loaded.'); + $this->assertEqual($comment_loaded->created, $raw_date, 'Date loaded.'); + + } + +} + +class CommentAnonymous extends CommentHelperCase { + public static function getInfo() { + return array( + 'name' => 'Anonymous comments', + 'description' => 'Test anonymous comments.', + 'group' => 'Comment', + ); + } + + function setUp() { + parent::setUp(); + variable_set('user_register', USER_REGISTER_VISITORS); + } + + /** + * Test anonymous comment functionality. + */ + function testAnonymous() { + $this->drupalLogin($this->admin_user); + // Enabled anonymous user comments. + user_role_change_permissions(DRUPAL_ANONYMOUS_RID, array( + 'access comments' => TRUE, + 'post comments' => TRUE, + 'skip comment approval' => TRUE, + )); + $this->setCommentAnonymous('0'); // Ensure that doesn't require contact info. + $this->drupalLogout(); + + // Post anonymous comment without contact info. + $anonymous_comment1 = $this->postComment($this->node, $this->randomName(), $this->randomName()); + $this->assertTrue($this->commentExists($anonymous_comment1), 'Anonymous comment without contact info found.'); + + // Allow contact info. + $this->drupalLogin($this->admin_user); + $this->setCommentAnonymous('1'); + + // Attempt to edit anonymous comment. + $this->drupalGet('comment/' . $anonymous_comment1->id . '/edit'); + $edited_comment = $this->postComment(NULL, $this->randomName(), $this->randomName()); + $this->assertTrue($this->commentExists($edited_comment, FALSE), 'Modified reply found.'); + $this->drupalLogout(); + + // Post anonymous comment with contact info (optional). + $this->drupalGet('comment/reply/' . $this->node->nid); + $this->assertTrue($this->commentContactInfoAvailable(), 'Contact information available.'); + + $anonymous_comment2 = $this->postComment($this->node, $this->randomName(), $this->randomName()); + $this->assertTrue($this->commentExists($anonymous_comment2), 'Anonymous comment with contact info (optional) found.'); + + // Ensure anonymous users cannot post in the name of registered users. + $langcode = LANGUAGE_NONE; + $edit = array( + 'name' => $this->admin_user->name, + 'mail' => $this->randomName() . '@example.com', + 'subject' => $this->randomName(), + "comment_body[$langcode][0][value]" => $this->randomName(), + ); + $this->drupalPost('comment/reply/' . $this->node->nid, $edit, t('Save')); + $this->assertText(t('The name you used belongs to a registered user.')); + + // Require contact info. + $this->drupalLogin($this->admin_user); + $this->setCommentAnonymous('2'); + $this->drupalLogout(); + + // Try to post comment with contact info (required). + $this->drupalGet('comment/reply/' . $this->node->nid); + $this->assertTrue($this->commentContactInfoAvailable(), 'Contact information available.'); + + $anonymous_comment3 = $this->postComment($this->node, $this->randomName(), $this->randomName(), TRUE); + // Name should have 'Anonymous' for value by default. + $this->assertText(t('E-mail field is required.'), 'E-mail required.'); + $this->assertFalse($this->commentExists($anonymous_comment3), 'Anonymous comment with contact info (required) not found.'); + + // Post comment with contact info (required). + $author_name = $this->randomName(); + $author_mail = $this->randomName() . '@example.com'; + $anonymous_comment3 = $this->postComment($this->node, $this->randomName(), $this->randomName(), array('name' => $author_name, 'mail' => $author_mail)); + $this->assertTrue($this->commentExists($anonymous_comment3), 'Anonymous comment with contact info (required) found.'); + + // Make sure the user data appears correctly when editing the comment. + $this->drupalLogin($this->admin_user); + $this->drupalGet('comment/' . $anonymous_comment3->id . '/edit'); + $this->assertRaw($author_name, "The anonymous user's name is correct when editing the comment."); + $this->assertRaw($author_mail, "The anonymous user's e-mail address is correct when editing the comment."); + + // Unpublish comment. + $this->performCommentOperation($anonymous_comment3, 'unpublish'); + + $this->drupalGet('admin/content/comment/approval'); + $this->assertRaw('comments[' . $anonymous_comment3->id . ']', 'Comment was unpublished.'); + + // Publish comment. + $this->performCommentOperation($anonymous_comment3, 'publish', TRUE); + + $this->drupalGet('admin/content/comment'); + $this->assertRaw('comments[' . $anonymous_comment3->id . ']', 'Comment was published.'); + + // Delete comment. + $this->performCommentOperation($anonymous_comment3, 'delete'); + + $this->drupalGet('admin/content/comment'); + $this->assertNoRaw('comments[' . $anonymous_comment3->id . ']', 'Comment was deleted.'); + $this->drupalLogout(); + + // Reset. + user_role_change_permissions(DRUPAL_ANONYMOUS_RID, array( + 'access comments' => FALSE, + 'post comments' => FALSE, + 'skip comment approval' => FALSE, + )); + + // Attempt to view comments while disallowed. + // NOTE: if authenticated user has permission to post comments, then a + // "Login or register to post comments" type link may be shown. + $this->drupalGet('node/' . $this->node->nid); + $this->assertNoPattern('@
We have you listed as a primary contact person for !project_name. It has been 1 year since !project_name was published on the site, and it's probably time to look it over to make sure everything is still accurate.
+ +Simply click the link below, login and make any necessary changes. We will then review your changes and publish them to the site within a few days.
+ + + +Did you forget your password? You'll need that to edit your project. Click here to reset it.
+ +Have questions? Having trouble? Send a message to info@communitykc.org and we will try our best to help you out.
+ +Thanks for all the hard work you do to make life in KC better.
+ +The team at CommmunityKC.org
+Connecting projects, people and resources
diff --git a/sites/all/modules/htmlmail/CHANGELOG.html b/sites/all/modules/htmlmail/CHANGELOG.html new file mode 100644 index 0000000..3022427 --- /dev/null +++ b/sites/all/modules/htmlmail/CHANGELOG.html @@ -0,0 +1,74 @@ +-- (Note: See the git repository for full version)
+5.x-1.1 released (288 SLOC, 60k) as an improved replacement for drupal_mail().
6.x-1.3 released (348 SLOC, 108k) and eventually installed by over 5,000 sites.
+Patches to improve theming of the 7.x-1.x-dev version submitted by Bob Vincent (pillarsdotnet).
+Mail System created to allow mail-sending modules to cooperate and co-exist.
+Bob Vincent granted co-maintainer access.
+Echo separated into its own module.
+MIME support delegated to PEAR Mail_mimePart and Mail_mimeDecode classes.
+6.x-2.4 (420 SLOC, 92k) and 7.x-2.4 (414 SLOC, 92k) released.
+Project page updated to remove support for 6.x-1.x and 7.x-1.x branches.
+Autodetection of template files in both module and selected theme directories.
+Lets you theme your messages the same way you theme the rest of your website.
+The following additional modules, while not required, are highly recommended:
+Converts non-ASCII characters to their US-ASCII equivalents, such as from Microsoft "smart-quotes" to regular quotes.
+Also available as a patch.
+The 7.x-2.x branch shares 94% of its code with the 6.x-2.x branch, but only 15% of its code with the 7.x-1.x branch, and a tiny 8% of its code with the 6.x-1.x branch.
+Let your compatibility expectations be adjusted accordingly.
+Check the module dependencies, as they have changed. The latest version of HTML Mail depends on the Mail System module (7.x-2.2 or later) and will not work without it.
+Run update.php immediately after uploading new code.
The user-interface for adding email header and footer text has been removed. Headers and footers may be added by template files and/or by enabling the Echo module.
+Any customized filters should be carefully tested, as some of the template variables have changed. Full documentation is provided both on the module configuration page (Click on the Instructions link) and as comments within the htmlmail.tpl.php file itself.
The following options have been removed from the module settings page. In their place, any combination of over 200 filter modules may be used to create an email-specific text format for post-template filtering.
+ +Full MIME handling, including automatic generation of a plaintext alternative part and conversion of image references to inline image attachments, is available simply by enabling the Mail MIME module.
+Visit the Mail System settings page at admin/config/system/mailsystem to select which parts of Drupal will use HTML Mail instead of the default mail system.
+Visit the HTML Mail settings page at admin/config/system/htmlmail to select a theme and post-filter for your messages.
+The email message text goes through three transformations before sending:
+A template file is applied to your message header, subject, and body text. The default template is the included htmlmail.tpl.php file. You may copy this file to your email theme directory (selected below), and use it to customize the contents and formatting of your messages. The comments within that file contain complete documentation on its usage.
You may choose a theme that will hold your templates from Step 1 above. If the Echo module is installed, this theme will also be used to wrap your templated text in a webpage. You use any one of over 800 themes to style your messages, or create your own for even more power and flexibility.
+You may choose a text format to be used for filtering email messages after theming. This allows you to use any combination of over 200 filter modules to make final changes to your message before sending.
+Here is a recommended configuration:
+Emogrifier Converts stylesheets to inline style rules for consistent display on mobile devices and webmail.
+Transliteration Converts non-ASCII text to US-ASCII equivalents. This helps prevent Microsoft "smart-quotes" from appearing as question-marks in Mozilla Thunderbird.
+Pathologic Converts relative URLS to absolute URLS so that clickable links in your message will work as intended.
+Double-check the Mail System module settings and and make sure you selected HTMLMailSystem for your Site-wide default mail system.
Try selecting the [ ] (Optional) Debug checkbox at the HTML Mail module settings page and re-sending your message.
Clear your cache after changing any .tpl.php files.
If you use a post-filter, make sure your filter settings page looks like this.
+Visit the issue queue for support and feature requests.
+http://drupal.org/project/echo
+http://drupal.org/project/emogrifier
+http://drupal.org/project/htmlpurifier
+http://drupal.org/project/htmlawed
+http://drupal.org/project/mailmime
+http://drupal.org/project/mailsystem
+http://drupal.org/project/pathologic
+http://drupal.org/project/transliteration
+api.drupal.org/api/drupal/modules--filter--filter.module/group/standard_filters/7
+HTMLMailSystem for your
+ Site-wide default mail system.
+
+* Try selecting the [ ] *(Optional)* Debug checkbox
+ at the [HTML Mail](http://drupal.org/project/htmlmail) module
+ settings page and re-sending your message.
+
+* Clear your cache after changing any .tpl.php
+ files.
+
+* If you use a post-filter, make sure your filter settings page looks like
+ [this](http://drupal.org/node/1130960).
+
+* Visit the [issue queue](http://drupal.org/project/issues/htmlmail)
+ for support and feature requests.
+
+### Related Modules
+
+**Echo**
+: http://drupal.org/project/echo
+
+**Emogrifier**
+: http://drupal.org/project/emogrifier
+
+**HTML Purifier**
+: http://drupal.org/project/htmlpurifier
+
+**htmLawed**
+: http://drupal.org/project/htmlawed
+
+**Mail MIME**
+: http://drupal.org/project/mailmime
+
+**Mail System**
+: http://drupal.org/project/mailsystem
+
+**Pathologic**
+: http://drupal.org/project/pathologic
+
+**Transliteration**
+: http://drupal.org/project/transliteration
+
+### [Documentation](http://drupal.org/project/documentation)
+
+**[HTML Mail](http://drupal.org/node/1124376)
+
+**[filter.module](http://api.drupal.org/api/drupal/modules--filter--filter.module/6)**
+: [api.drupal.org/api/drupal/modules--filter--filter.module](http://api.drupal.org/api/drupal/modules--filter--filter.module/7)
+: [api.drupal.org/api/drupal/modules--filter--filter.module/group/standard_filters/7](http://api.drupal.org/api/drupal/modules--filter--filter.module/group/standard_filters/7)
+
+**[Installing contributed modules](http://drupal.org/documentation/install/modules-themes/modules-7)**
+: [drupal.org/documentation/install/modules-themes/modules-7](http://drupal.org/documentation/install/modules-themes/modules-7)
+
+**[Theming guide](http://drupal.org/documentation/theme)**
+: [drupal.org/documentation/theme](http://drupal.org/documentation/theme)
+
+### Original Author
+
+* [Chris Herberte](http://drupal.org/user/1171)
+
+### Current Maintainer
+
+* [Bob Vincent](http://drupal.org/user/36148)
diff --git a/sites/all/modules/htmlmail/README.txt b/sites/all/modules/htmlmail/README.txt
new file mode 100644
index 0000000..42758ef
--- /dev/null
+++ b/sites/all/modules/htmlmail/README.txt
@@ -0,0 +1,235 @@
+[1]HTML Mail
+
+ Lets you theme your messages the same way you theme the rest of your
+ website.
+
+ [2]Requirement
+
+ * [3]Mail System 7.x-2.x
+
+ [4]Installation
+
+ The following additional modules, while not required, are highly
+ recommended:
+ *
+
+ [5]Echo
+ Wraps your messages in a drupal theme. Now you can "brand"
+ your messages with the same logo, header, fonts, and
+ styles as your website.
+
+ *
+
+ [6]Emogrifier
+ Converts stylesheets to inline style rules, for consistent
+ display on mobile devices and webmail.
+
+ *
+
+ [7]Mail MIME
+ Provides a text/plain alternative to text/html emails, and
+ automatically converts image references to inline image
+ attachments.
+
+ *
+
+ [8]Pathologic
+ Converts urls from relative to absolute, so clickable
+ links in your email messages work as intended.
+
+ *
+
+ [9]Transliteration
+ Converts non-ASCII characters to their US-ASCII
+ equivalents, such as from Microsoft "smart-quotes" to
+ regular quotes.
+
+ Also available as a [10]patch.
+
+ [11]Updating from previous versions
+
+ The [12]7.x-2.x branch shares 94% of its code with the [13]6.x-2.x
+ branch, but only 15% of its code with the [14]7.x-1.x branch, and a
+ tiny 8% of its code with the [15]6.x-1.x branch.
+
+ Let your compatibility expectations be adjusted accordingly.
+ * Check the module dependencies, as they have changed. The latest
+ version of [16]HTML Mail depends on the [17]Mail System module
+ (7.x-2.2 or later) and will not work without it.
+ * Run update.php immediately after uploading new code.
+ * The user-interface for adding email header and footer text has been
+ removed. Headers and footers may be added by template files and/or
+ by enabling the [18]Echo module.
+ * Any customized filters should be carefully tested, as some of the
+ template variables have changed. Full documentation is provided
+ both on the module configuration page (Click on the Instructions
+ link) and as comments within the htmlmail.tpl.php file itself.
+ * The following options have been removed from the module settings
+ page. In their place, any combination of [19]over 200 filter
+ modules may be used to create an email-specific [20]text format for
+ post-template filtering.
+ + [21]Line break converter
+ + [22]URL Filter
+ + [23]Relative Path to Absolute URLs
+ + [24]Emogrifier
+ + [25]Token support
+ * Full MIME handling, including automatic generation of a plaintext
+ alternative part and conversion of image references to inline image
+ attachments, is available simply by enabling the [26]Mail MIME
+ module.
+
+ [27]Configuration
+
+ Visit the [28]Mail System settings page at
+ admin/config/system/mailsystem to select which parts of Drupal will use
+ [29]HTML Mail instead of the [30]default [31]mail system.
+
+ Visit the [32]HTML Mail settings page at admin/config/system/htmlmail
+ to select a theme and post-filter for your messages.
+
+ [33]Theming
+
+ The email message text goes through three transformations before
+ sending:
+ 1. Template File
+ A template file is applied to your message header, subject, and
+ body text. The default template is the included htmlmail.tpl.php
+ file. You may copy this file to your email theme directory
+ (selected below), and use it to customize the contents and
+ formatting of your messages. The comments within that file contain
+ complete documentation on its usage.
+ 2. Theming
+ You may choose a theme that will hold your templates from Step 1
+ above. If the [34]Echo module is installed, this theme will also be
+ used to wrap your templated text in a webpage. You use any one of
+ [35]over 800 themes to style your messages, or [36]create your own
+ for even more power and flexibility.
+ 3. Post-filtering
+ You may choose a [37]text format to be used for filtering email
+ messages after theming. This allows you to use any combination of
+ [38]over 200 filter modules to make final changes to your message
+ before sending.
+ Here is a recommended configuration:
+ + [39]Emogrifier Converts stylesheets to inline style rules for
+ consistent display on mobile devices and webmail.
+ + [40]Transliteration Converts non-ASCII text to US-ASCII
+ equivalents. This helps prevent Microsoft "smart-quotes" from
+ appearing as question-marks in Mozilla Thunderbird.
+ + [41]Pathologic Converts relative URLS to absolute URLS so that
+ clickable links in your message will work as intended.
+
+ Troubleshooting
+
+ * Double-check the [42]Mail System module settings and and make sure
+ you selected HTMLMailSystem for your Site-wide default mail system.
+ * Try selecting the [ ] (Optional) Debug checkbox at the [43]HTML
+ Mail module settings page and re-sending your message.
+ * Clear your cache after changing any .tpl.php files.
+ * If you use a post-filter, make sure your filter settings page looks
+ like [44]this.
+ * Visit the [45]issue queue for support and feature requests.
+
+ Related Modules
+
+ Echo
+ http://drupal.org/project/echo
+
+ Emogrifier
+ http://drupal.org/project/emogrifier
+
+ HTML Purifier
+ http://drupal.org/project/htmlpurifier
+
+ htmLawed
+ http://drupal.org/project/htmlawed
+
+ Mail MIME
+ http://drupal.org/project/mailmime
+
+ Mail System
+ http://drupal.org/project/mailsystem
+
+ Pathologic
+ http://drupal.org/project/pathologic
+
+ Transliteration
+ http://drupal.org/project/transliteration
+
+ [46]Documentation
+
+ [47]filter.module
+ [48]api.drupal.org/api/drupal/modules--filter--filter.module
+ [49]api.drupal.org/api/drupal/modules--filter--filter.module/gro
+ up/standard_filters/7
+
+ [50]Installing contributed modules
+ [51]drupal.org/documentation/install/modules-themes/modules-7
+
+ [52]Theming guide
+ [53]drupal.org/documentation/theme
+
+ Original Author
+
+ * [54]Chris Herberte
+
+ Current Maintainer
+
+ * [55]Bob Vincent
+
+References
+
+ 1. http://drupal.org/project/htmlmail
+ 2. http://www.dict.org/bin/Dict?Form=Dict2&Database=*&Query=requirement
+ 3. http://drupal.org/project/mailsystem
+ 4. http://drupal.org/documentation/install/modules-themes/modules-7
+ 5. http://drupal.org/project/echo
+ 6. http://drupal.org/project/emogrifier
+ 7. http://drupal.org/project/mailmime
+ 8. http://drupal.org/project/pathologic
+ 9. http://drupal.org/project/filter_transliteration
+ 10. http://drupal.org/node/1095278#comment-4219530
+ 11. http://drupal.org/node/250790
+ 12. http://drupal.org/node/1106064
+ 13. http://drupal.org/node/1119548
+ 14. http://drupal.org/node/355250
+ 15. http://drupal.org/node/329828
+ 16. http://drupal.org/project/htmlmail
+ 17. http://drupal.org/project/mailsystem
+ 18. http://drupal.org/project/echo
+ 19. http://drupal.org/project/modules/?filters=type%3Aproject_project%20tid%3A63%20hash%3A1hbejm%20-bs_project_sandbox%3A1%20bs_project_has_releases%3A1
+ 20. http://drupal.org/node/778976
+ 21. http://api.drupal.org/api/drupal/modules--filter--filter.module/function/_filter_autop/7
+ 22. http://api.drupal.org/api/drupal/modules--filter--filter.module/function/_filter_url/7
+ 23. http://drupal.org/project/rel_to_abs
+ 24. http://www.pelagodesign.com/sidecar/emogrifier/
+ 25. http://drupal.org/project/token
+ 26. http://drupal.org/project/mailmime
+ 27. http://drupal.org/files/images/htmlmail_settings_2.thumbnail.png
+ 28. http://drupal.org/project/mailsystem
+ 29. http://drupal.org/project/htmlmail
+ 30. http://api.drupal.org/api/drupal/modules--system--system.mail.inc/class/DefaultMailSystem/7
+ 31. http://api.drupal.org/api/drupal/includes--mail.inc/function/drupal_mail_system/7
+ 32. http://drupal.org/project/htmlmail
+ 33. http://drupal.org/documentation/theme
+ 34. http://drupal.org/project/echo
+ 35. http://drupal.org/project/themes
+ 36. http://drupal.org/documentation/theme
+ 37. http://drupal.org/node/778976
+ 38. http://drupal.org/project/modules/?filters=type%3Aproject_project%20tid%3A63%20hash%3A1hbejm%20-bs_project_sandbox%3A1%20bs_project_has_releases%3A1
+ 39. http://drupal.org/project/emogrifier
+ 40. http://drupal.org/project/filter_transliteration
+ 41. http://drupal.org/project/pathologic
+ 42. http://drupal.org/project/mailsystem
+ 43. http://drupal.org/project/htmlmail
+ 44. http://drupal.org/node/1130960
+ 45. http://drupal.org/project/issues/htmlmail
+ 46. http://drupal.org/project/documentation
+ 47. http://api.drupal.org/api/drupal/modules--filter--filter.module/6
+ 48. http://api.drupal.org/api/drupal/modules--filter--filter.module/7
+ 49. http://api.drupal.org/api/drupal/modules--filter--filter.module/group/standard_filters/7
+ 50. http://drupal.org/documentation/install/modules-themes/modules-7
+ 51. http://drupal.org/documentation/install/modules-themes/modules-7
+ 52. http://drupal.org/documentation/theme
+ 53. http://drupal.org/documentation/theme
+ 54. http://drupal.org/user/1171
+ 55. http://drupal.org/user/36148
diff --git a/sites/all/modules/htmlmail/htmlmail--htmlmail.tpl.php b/sites/all/modules/htmlmail/htmlmail--htmlmail.tpl.php
new file mode 100644
index 0000000..2d95c67
--- /dev/null
+++ b/sites/all/modules/htmlmail/htmlmail--htmlmail.tpl.php
@@ -0,0 +1,32 @@
+
++ To customize this test message: +
+ Visit admin/config/system/htmlmail + and select a theme to hold your custom email template files. +
+ Visit admin/appearance + to enable your selected theme. +
+ Copy the
+ htmlmail--htmlmail.tpl.php
+ file to your theme directory
+ .
+
+ Edit the copied file. +
+ To customize your simplenews messages: +
+ Visit admin/config/system/htmlmail + and select a theme to hold your custom email template files. +
+ Visit admin/appearance + to enable your selected theme. +
+ Edit your
+
+
file.
+
+ Copy
+
+
to
+
+
+ For general Simplenews message customization, copy
+
+
to
+
+
+ For message-specific customization, copy
+
+
to one of the following:
+
+ htmlmail--simplenews--node.tpl.php
+
+ Regular newsletter template. +
+ htmlmail--simplenews--subscribe.tpl.php
+
+ New subscriber confirmation message. +
+ htmlmail--simplenews--test.tpl.php
+
+ Test newsletter. +
+ htmlmail--simplenews--unsubscribe.tpl.php
+
+ Unsubscribe confirmation message. +
+ Edit the copied file. +
+ Send a test message to make sure your customizations worked. +
+ If you think your customizations would be of use to others, + please contribute your file as a feature request in the + issue queue. +
+ The simplenews module sets the $params variable.
+ For this message,
+
+$params =
+
+ To customize your user password reset messages: +
+ Visit admin/config/system/htmlmail + and select a theme to hold your custom email template files. +
+ Visit admin/build/themes + to enable your selected theme. +
+ Edit your
+
+
file.
+
+ Copy
+
+
to
+
+
+ For general user-module message customization, copy
+
+
to
+
+
+ Copy
+
+
to
+ .
+
+ Edit the copied file. +
+ Send a test message to make sure your customizations worked. +
+ If you think your customizations would be of use to others, + please contribute your file as a feature request in the + issue queue. +
+ The user module sets the $params variable.
+ For this message,
+
+$params =
+
htmlmail.tpl.php'
+ )
+ ),
+ '#title' => t('Instructions'),
+ '#collapsible' => TRUE,
+ '#collapsed' => TRUE,
+ );
+ $form['template']['htmlmail_template']['instructions'] = array(
+ '#type' => 'item',
+ '#suffix' => t('!Instructions
+When formatting an email message with a given $module and $key, HTML Mail will use the first template file it finds from the following list:
htmlmail--$module--$key.tpl.phphtmlmail--$module.tpl.phphtmlmail.tpl.phpFor each filename, HTML Mail looks first in the chosen Email theme directory, then in its own module directory, before proceeding to the next filename.
+For example, if example_module sends mail with:
+drupal_mail("example_module", "outgoing_message" ...)
+
+
+the possible template file names would be:
+htmlmail--example_module--outgoing_message.tpl.phphtmlmail--example_module.tpl.phphtmlmail.tpl.phpTemplate files are cached, so remember to clear the cache by visiting admin/config/development/performance after changing any .tpl.php files.
The following variables available in this template:
+$bodyThe message body text.
+$moduleThe first argument to drupal_mail(), which is, by convention, the machine-readable name of the sending module.
$keyThe second argument to drupal_mail(), which should give some indication of why this email is being sent.
$message_idThe email message id, which should be equal to "{$module}_{$key}".
$headersAn array of email (name => value) pairs.
$fromThe configured sender address.
+$toThe recipient email address.
+$subjectThe message subject line.
+$bodyThe formatted message body.
+$languageThe language object for this message.
+$paramsAny module-specific parameters.
+$template_nameThe basename of the active template.
+$template_pathThe relative path to the template directory.
+$template_urlThe absolute URL to the template directory.
+$themeThe name of the Email theme used to hold template files. If the Echo module is enabled this theme will also be used to transform the message body into a fully-themed webpage.
+$theme_pathThe relative path to the selected Email theme directory.
+$theme_urlThe absolute URL to the selected Email theme directory.
+$debugTRUE to add some useful debugging info to the bottom of the message.
Other modules may also add or modify theme variables by implementing a MODULENAME_preprocess_htmlmail(&$variables) hook function.
$debug to TRUE).'),
+ );
+ $form['theme'] = array(
+ '#type' => 'fieldset',
+ '#title' => t('Step 2'),
+ '#collapsible' => FALSE,
+ );
+ $form['theme']['htmlmail_theme'] = array(
+ '#type' => 'select',
+ '#title' => t('Email theme'),
+ '#default_value' => variable_get('htmlmail_theme', ''),
+ '#options' => htmlmail_get_allowed_themes(),
+ '#suffix' => '' + . t('Choose the theme that will hold your customized templates from Step 1 above.') + . '
' + . (module_exists('echo') ? + t('The templated text will be styled by your chosen theme. This lets you use any one of over 800 themes to style your messages. Creating an email-specific sub-theme lets you use the full power of the drupal theme system to format your messages.', + array( + '!themes' => 'http://drupal.org/project/themes', + '!theme_system' => 'http://drupal.org/documentation/theme', + ) + ) : + t('If you install and enable the Echo module, the theme you select will also be used to style your messages as if they were pages on your website.', + array( + '!echo' => 'http://drupal.org/project/echo' + ) + ) + ) + . '
' + . (module_exists('mailmime') ? + t('Since you have the Mail MIME module installed, your images will be automatically converted to inline attachments, and a plain-text alternative will be available to recipients who prefer it.', + array('!mailmime' => 'http://drupal.org/project/mailmime') + ) : + t('If you install the Mail MIME module, images in your emails will be automatically converted to inline attachments, and a plain-text alternative will be made available. This prevents your recipients from seeing broken image links and scary security warnings when they don\'t have the sender\'s address in their email addressbook. Mail MIME also allows HTML Mail to handle MIME-formatted messages sent by other modules such as Send by-email.', + array( + '!mailmime' => 'http://drupal.org/project/mailmime', + '!print' => 'http://drupal.org/project/print', + ) + ) + ) + . '
', + ); + $form['filter'] = array( + '#type' => 'fieldset', + '#title' => t('Step 3'), + '#collapsible' => FALSE, + ); + $form['filter']['htmlmail_postfilter'] = array( + '#type' => 'select', + '#title' => t('Post-filtering'), + '#default_value' => variable_get('htmlmail_postfilter', ''), + '#options' => $formats, + '#suffix' => '' + . t('You may choose a text format to be used for filtering email messages after theming. This allows you to use any combination of over 200 filter modules to make final changes to your message before sending.', + array( + '!formats' => url('admin/config/content/formats'), + '!filters' => url('http://drupal.org/project/modules/?filters=type%3Aproject_project%20tid%3A63%20hash%3A1hbejm%20-bs_project_sandbox%3A1%20bs_project_has_releases%3A1'), + ) + ) + . '
' + . t('Here is a recommended configuration:') + . '
smart-quotesfrom appearing as question-marks in Mozilla Thunderbird.' + ) + . '
tags.
+ if ( $email['body'] === strip_tags($email['body']) // No html tags.
+ && preg_match('/.' . $eol . './', $email['body']) // At least one embedded newline.
+ ) {
+ $email['body'] = '' . $email['body'] . '
';
+ }
+ }
+ // Theme with htmlmail.tpl.php.
+ $body = theme('htmlmail', $email);
+ $mime->setHTMLBody($body);
+ // @todo Change to drupal_html_to_text when issue #299138 gets resolved.
+ $mime->setTXTBody(mailsystem_html_to_text($body));
+ $message['MailMIME'] = &$mime;
+ return $body;
+ }
+
+ /**
+ * Send an email message.
+ *
+ * @param $message
+ * An associative array containing at least:
+ * - headers: An associative array of (name => value) email headers.
+ * - body: The text/plain or text/html message body.
+ * - MailMIME: The message, parsed into a MailMIME object.
+ */
+ public function mail(array $message) {
+ $eol = variable_get('mail_line_endings', MAIL_LINE_ENDINGS);
+ // Ensure that subject is non-null.
+ $message += array('subject' => t('(No subject)'));
+ // Check for empty recipient.
+ if (empty($message['to'])) {
+ if (empty($message['headers']['To'])) {
+ watchdog(
+ 'HTMLMailSystem',
+ 'Cannot send email about %subject without a recipient.',
+ array('subject' => $message['subject']),
+ WATCHDOG_ERROR
+ );
+ return FALSE;
+ }
+ $message['to'] = $message['headers']['To'];
+ }
+ if (class_exists('MailMIME')) {
+ $mime = new MailMIME();
+ $to = $mime->encodeHeader('to', $message['to']);
+ $subject = $mime->encodeHeader('subject', $message['subject']);
+ $txt_headers = $mime->txtHeaders($message['headers']);
+ }
+ else {
+ $to = mime_header_encode($message['to']);
+ $subject = mime_header_encode($message['subject']);
+ $txt_headers = $this->txtHeaders($message['headers']);
+ }
+ $body = preg_replace('#(\r\n|\r|\n)#s', $eol, $message['body']);
+ // Check for empty body.
+ if (empty($body)) {
+ watchdog(
+ 'HTMLMailSystem',
+ 'Refusing to send a blank email to %recipient about %subject.',
+ array('%recipient' => $message['to'], '%subject' => $message['subject']),
+ WATCHDOG_WARNING
+ );
+ return FALSE;
+ }
+ if (variable_get('htmlmail_debug', 0)) {
+ $params = array(
+ $to,
+ $subject,
+ drupal_substr($body, 0, min(80, strpos("\n", $body))) . '...',
+ $txt_headers
+ );
+ }
+ if (isset($message['headers']['Return-Path'])) {
+ // A return-path was set.
+ if (isset($_SERVER['WINDIR']) || strpos($_SERVER['SERVER_SOFTWARE'], 'Win32') !== FALSE) {
+ // On Windows, PHP will use the value of sendmail_from for the
+ // Return-Path header.
+ $old_from = ini_get('sendmail_from');
+ ini_set('sendmail_from', $message['headers']['Return-Path']);
+ $result = @mail($to, $subject, $body, $txt_headers);
+ ini_set('sendmail_from', $old_from);
+ }
+ elseif (ini_get('safe_mode')) {
+ // If safe mode is in effect, passing the fifth parameter to @mail
+ // will cause it to return FALSE and generate a PHP warning, even
+ // if the parameter is NULL.
+ $result = @mail($to, $subject, $body, $txt_headers);
+ }
+ else {
+ // On most non-Windows systems, the "-f" option to the sendmail command
+ // is used to set the Return-Path.
+ $extra = '-f' . $message['headers']['Return-Path'];
+ $result = @mail($to, $subject, $body, $txt_headers, $extra);
+ if (variable_get('htmlmail_debug', 0)) {
+ $params[] = $extra;
+ }
+ }
+ }
+ else {
+ // No return-path was set.
+ $result = @mail($to, $subject, $body, $txt_headers);
+ }
+ if (!$result && variable_get('htmlmail_debug', 0)) {
+ $call = '@mail(' . implode(', ', $params) . ')';
+ foreach ($params as $i => $value) {
+ $params[$i] = var_export($value, 1);
+ }
+ if (defined('DEBUG_BACKTRACE_IGNORE_ARGS')) {
+ $trace = print_r(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), 1);
+ }
+ else {
+ $trace = debug_backtrace(0);
+ for ($i = count($trace) - 1; $i >= 0; $i--) {
+ unset($trace[$i]['args']);
+ }
+ $trace = print_r($trace);
+ }
+ watchdog('htmlmail', 'Mail sending failed because:
@call
returned FALSE.
@trace
', array('@call' => $call, '@trace' => $trace));
+ }
+ return $result;
+ }
+
+ /**
+ * Converts an array of email headers to a text string.
+ *
+ * @param $headers
+ * An associative array of ('HeaderName' => 'header value') pairs.
+ *
+ * @return
+ * The concatenated headers as a single string.
+ */
+ public function txtHeaders(array $headers) {
+ $output = array();
+ foreach ($headers as $name => $value) {
+ if (is_array($value)) {
+ foreach ($value as $val) {
+ $output[] = "$name: $val";
+ }
+ }
+ else {
+ $output[] = "$name: $value";
+ }
+ }
+ return implode("\n", $output);
+ }
+}
diff --git a/sites/all/modules/htmlmail/htmlmail.markdown b/sites/all/modules/htmlmail/htmlmail.markdown
new file mode 100644
index 0000000..bcc6fa2
--- /dev/null
+++ b/sites/all/modules/htmlmail/htmlmail.markdown
@@ -0,0 +1,92 @@
+When formatting an email message with a given `$module` and `$key`,
+[HTML Mail](http://drupal.org/project/htmlmail)
+will use the first template file it finds from the following list:
+
+1. `htmlmail--$module--$key.tpl.php`
+2. `htmlmail--$module.tpl.php`
+3. `htmlmail.tpl.php`
+
+For each filename,
+[HTML Mail](http://drupal.org/project/htmlmail)
+looks first in the chosen *Email theme* directory, then in its own
+module directory, before proceeding to the next filename.
+
+For example, if `example_module` sends mail with:
+
+ drupal_mail("example_module", "outgoing_message" ...)
+
+the possible template file names would be:
+
+1. `htmlmail--example_module--outgoing_message.tpl.php`
+2. `htmlmail--example_module.tpl.php`
+3. `htmlmail.tpl.php`
+
+Template files are cached, so remember to clear the cache by visiting
+admin/config/development/performance
+after changing any `.tpl.php` files.
+
+The following variables available in this template:
+
+**`$body`**
+: The message body text.
+
+**`$module`**
+: The first argument to
+ [`drupal_mail()`](http://api.drupal.org/api/drupal/includes--mail.inc/function/drupal_mail/7),
+ which is, by convention, the machine-readable name of the sending module.
+
+**`$key`**
+: The second argument to
+ [`drupal_mail()`](http://api.drupal.org/api/drupal/includes--mail.inc/function/drupal_mail/7),
+ which should give some indication of why this email is being sent.
+
+**`$message_id`**
+: The email message id, which should be equal to `"{$module}_{$key}"`.
+
+**`$headers`**
+: An array of email `(name => value)` pairs.
+
+**`$from`**
+: The configured sender address.
+
+**`$to`**
+: The recipient email address.
+
+**`$subject`**
+: The message subject line.
+
+**`$body`**
+: The formatted message body.
+
+**`$language`**
+: The language object for this message.
+
+**`$params`**
+: Any module-specific parameters.
+
+**`$template_name`**
+: The basename of the active template.
+
+**`$template_path`**
+: The relative path to the template directory.
+
+**`$template_url`**
+: The absolute URL to the template directory.
+
+**`$theme`**
+: The name of the *Email theme* used to hold template files. If the
+ [Echo](http://drupal.org/project/echo) module is enabled this theme will
+ also be used to transform the message body into a fully-themed webpage.
+
+**`$theme_path`**
+: The relative path to the selected *Email theme* directory.
+
+**`$theme_url`**
+: The absolute URL to the selected *Email theme* directory.
+
+**`$debug`**
+: `TRUE` to add some useful debugging info to the bottom of the message.
+
+Other modules may also add or modify theme variables by implementing a
+`MODULENAME_preprocess_htmlmail(&$variables)`
+[hook function](http://api.drupal.org/api/drupal/modules--system--theme.api.php/function/hook_preprocess_HOOK/7).
diff --git a/sites/all/modules/htmlmail/htmlmail.module b/sites/all/modules/htmlmail/htmlmail.module
new file mode 100644
index 0000000..e8bbbde
--- /dev/null
+++ b/sites/all/modules/htmlmail/htmlmail.module
@@ -0,0 +1,233 @@
+ url('http://drupal.org/project/htmlmail'),
+ '%htmlmail' => 'HTML Mail',
+ );
+
+ return array(
+ 'choose htmlmail_plaintext' => array(
+ 'title' => t('Choose to receive plaintext emails via %htmlmail', $args),
+ 'description' => t(
+ 'Granting this permission allows users to choose whether to receive all their emails in plaintext, rather than the default format provided by the %htmlmail module.', $args
+ ),
+ ),
+ );
+}
+
+/**
+ * Implements hook_help().
+ */
+function htmlmail_help($path, $arg) {
+ switch ($path) {
+ case 'admin/config/system/htmlmail':
+ return '' . t('Theming') . '
' . t('The email message goes through three transformations before sending:') . '
';
+ case 'admin/help#htmlmail':
+ return ''
+ . t('HTML Mail lets you theme your messages the same way you theme the rest of your website.',
+ array('!htmlmail' => 'http://drupal.org/project/htmlmail')
+ ) . '
';
+ default:
+ return '';
+ }
+}
+
+/**
+ * Implements hook_menu().
+ */
+function htmlmail_menu() {
+ $items['admin/config/system/htmlmail'] = array(
+ 'title' => 'HTML Mail',
+ 'description' => 'Configure HTML Mail system-wide settings.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('htmlmail_admin_settings'),
+ 'access arguments' => array('administer site configuration'),
+ 'file' => 'htmlmail.admin.inc',
+ );
+ $items['admin/config/system/htmlmail/settings'] = array(
+ 'title' => 'Settings',
+ 'type' => MENU_DEFAULT_LOCAL_TASK,
+ 'weight' => '-2'
+ );
+ $items['admin/config/system/htmlmail/test'] = array(
+ 'title' => 'Send Test',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('htmlmail_test_form'),
+ 'access arguments' => array('access administration pages'),
+ 'type' => MENU_LOCAL_TASK,
+ 'file' => 'htmlmail.admin.inc',
+ );
+ return $items;
+}
+
+/**
+ * Implements hook_theme().
+ *
+ * Auto-detects htmlmail template files in the selected theme and in the
+ * htmlmail module directory.
+ */
+function htmlmail_theme() {
+ $items = array();
+ $module_path = drupal_get_path('module', 'htmlmail');
+ $pattern = '/^htmlmail.*\.tpl\.php$/';
+ $files = file_scan_directory($module_path, $pattern, array('key' => 'name'));
+ if ($theme = htmlmail_get_selected_theme()) {
+ $theme_path = drupal_get_path('theme', $theme);
+ $files = array_merge($files,
+ file_scan_directory($theme_path, $pattern, array('key' => 'name'))
+ );
+ }
+ else {
+ $theme_path = $module_path;
+ }
+ ksort($files);
+ foreach ($files as $file) {
+ $path = dirname($file->uri);
+ $template = substr($file->name, 0, -4);
+ $suggestion = str_replace('--', '__', $template);
+ $items[$suggestion] = array(
+ 'variables' => array('message' => array()),
+ 'template' => $template,
+ 'path' => $path,
+ 'theme path' => $theme_path,
+ );
+ }
+ return $items;
+}
+
+/**
+ * Process variables to format email messages.
+ *
+ * @see htmlmail.tpl.php
+ */
+function template_preprocess_htmlmail(array &$variables) {
+ $variables['debug'] = variable_get('htmlmail_debug', '0');
+ $variables['theme'] = htmlmail_get_selected_theme($variables);
+ $variables['module_path'] = drupal_get_path('module', 'htmlmail');
+ if (empty($variables['theme'])) {
+ $variables['theme'] = 'no theme';
+ $variables['theme_path'] = $variables['module_path'];
+ }
+ else {
+ $variables['theme_path'] = drupal_get_path('theme', $variables['theme']);
+ }
+ $variables['theme_url'] = url(
+ $variables['theme_path'], array('absolute' => TRUE)
+ );
+ $variables['message_id'] = $variables['module'] . '_' . $variables['key'];
+ $suggestion = 'htmlmail__' . $variables['module'];
+ $variables['theme_hook_suggestions'][] = $suggestion;
+ $suggestion .= '__' . $variables['key'];
+ $variables['theme_hook_suggestions'][] = $suggestion;
+}
+
+/**
+ * Implements hook_mail().
+ */
+function htmlmail_mail($key, &$message, $params) {
+ $message['module'] = 'htmlmail';
+ $message['key'] = $key;
+ $message['subject'] = $params['subject'];
+ $message['body'] = explode(
+ MAIL_LINE_ENDINGS . MAIL_LINE_ENDINGS,
+ $params['body']
+ );
+ return $message;
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function htmlmail_form_user_profile_form_alter(&$form, &$form_state) {
+ if ($form['#user_category'] != 'account') {
+ return;
+ }
+ if (!(user_access('choose htmlmail_plaintext') || user_access('administer users'))) {
+ return;
+ }
+ $account = $form['#user'];
+ $mail = $form['account']['mail'];
+ $form['account']['mail'] = array(
+ 'mail' => $mail,
+ 'htmlmail_plaintext' => array(
+ '#type' => 'checkbox',
+ '#title' => t('Plaintext-only emails'),
+ '#default_value' => empty($account->data['htmlmail_plaintext']) ? 0 : 1,
+ '#description' => t('The %htmlmail module can send emails with fonts, styles, and other HTML formatting. If you prefer to receive all your emails in unformatted plain text, select this option.',
+ array('%htmlmail' => 'HTML Mail')
+ ),
+ ),
+ );
+}
+
+/**
+ * Implements hook_user_presave().
+ */
+function htmlmail_user_presave(&$edit, $account, $category) {
+ if (user_access('choose htmlmail_plaintext') || user_access('administer users')) {
+ $edit['data']['htmlmail_plaintext'] = empty($edit['htmlmail_plaintext']) ? 0 : 1;
+ unset($edit['htmlmail_plaintext']);
+ }
+}
+
+/**
+ * Returns an associative array of allowed themes. The keys are the
+ * machine-readable names and the values are the .info file names.
+ * Based on code from the og_theme module.
+ */
+function &htmlmail_get_allowed_themes() {
+ $allowed = &drupal_static(__FUNCTION__);
+ if (!isset($allowed)) {
+ $allowed = array('' => t('No theme'));
+ $themes = list_themes();
+ module_load_include('inc', 'system', 'system.admin');
+ uasort($themes, 'system_sort_modules_by_info_name');
+ foreach ($themes as $key => $value) {
+ if ($value->status) {
+ $allowed[$key] = check_plain($value->info['name']);
+ }
+ }
+ }
+ return $allowed;
+}
+
+/**
+ * Returns the selected theme to use for outgoing emails.
+ */
+function htmlmail_get_selected_theme(&$message = array()) {
+ $selected = isset($message['theme'])
+ ? $message['theme'] : variable_get('htmlmail_theme', '');
+ if ($selected) {
+ // Make sure the selected theme is allowed.
+ $themes = &htmlmail_get_allowed_themes();
+ if (empty($themes[$selected])) {
+ $selected = '';
+ }
+ }
+ return $selected;
+}
+
+/**
+ * Checks whether a given recipient email prefers plaintext-only messages.
+ *
+ * @param $email
+ * The recipient email address.
+ *
+ * @return
+ * FALSE if the recipient prefers plaintext-only messages; otherwise TRUE.
+ */
+function htmlmail_is_allowed($email) {
+ return !($recipient = user_load_by_mail($email))
+ || empty($recipient->data['htmlmail_plaintext']);
+}
diff --git a/sites/all/modules/htmlmail/htmlmail.tpl.php b/sites/all/modules/htmlmail/htmlmail.tpl.php
new file mode 100644
index 0000000..1e30c01
--- /dev/null
+++ b/sites/all/modules/htmlmail/htmlmail.tpl.php
@@ -0,0 +1,178 @@
+ value) pairs.
+ *
+ * $from
+ * The configured sender address.
+ *
+ * $to
+ * The recipient email address.
+ *
+ * $subject
+ * The message subject line.
+ *
+ * $body
+ * The formatted message body.
+ *
+ * $language
+ * The language object for this message.
+ *
+ * $params
+ * Any module-specific parameters.
+ *
+ * $template_name
+ * The basename of the active template.
+ *
+ * $template_path
+ * The relative path to the template directory.
+ *
+ * $template_url
+ * The absolute URL to the template directory.
+ *
+ * $theme
+ * The name of the Email theme used to hold template files. If the
+ * [5]Echo module is enabled this theme will also be used to
+ * transform the message body into a fully-themed webpage.
+ *
+ * $theme_path
+ * The relative path to the selected Email theme directory.
+ *
+ * $theme_url
+ * The absolute URL to the selected Email theme directory.
+ *
+ * $debug
+ * TRUE to add some useful debugging info to the bottom of the
+ * message.
+ *
+ * Other modules may also add or modify theme variables by implementing a
+ * MODULENAME_preprocess_htmlmail(&$variables) [6]hook function.
+ *
+ * References
+ *
+ * 1. http://drupal.org/project/htmlmail
+ * 2. http://drupal.org/project/htmlmail
+ * 3. http://api.drupal.org/api/drupal/includes--mail.inc/function/drupal_mail/7
+ * 4. http://api.drupal.org/api/drupal/includes--mail.inc/function/drupal_mail/7
+ * 5. http://drupal.org/project/echo
+ * 6. http://api.drupal.org/api/drupal/modules--system--theme.api.php/function/hook_preprocess_HOOK/7
+ *
+ * =========================================================== End instructions.
+ */
+ $template_name = basename(__FILE__);
+ $current_path = realpath(NULL);
+ $current_len = strlen($current_path);
+ $template_path = realpath(dirname(__FILE__));
+ if (!strncmp($template_path, $current_path, $current_len)) {
+ $template_path = substr($template_path, $current_len + 1);
+ }
+ $template_url = url($template_path, array('absolute' => TRUE));
+?>
+
+
+
+
+
+
+
+ To customize this message:
+
+ Visit admin/config/system/htmlmail
+ and select a theme to hold your custom email template files.
+
+ Visit admin/appearance
+ to enable your selected
+ theme.
+
+ Edit your
+
+
file.
+
+ Copy
+
+
to
+
+
+ For module-specific customization, copy
+
+
to
+
+
+ For message-specific customization, copy
+
+
to
+
+
+ Edit the copied file.
+
+ Send a test message to make sure your customizations worked.
+
+ If you think your customizations would be of use to others,
+ please contribute your file as a feature request in the
+ issue queue.
+
+ The module sets the $params
+ variable. For this message,
+
+$params =
+
+
+
+ Copyright (C)
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ , 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/sites/all/modules/mailsystem/README.html b/sites/all/modules/mailsystem/README.html
new file mode 100644
index 0000000..6b77862
--- /dev/null
+++ b/sites/all/modules/mailsystem/README.html
@@ -0,0 +1,113 @@
+Mail System
+Provides an Administrative UI and Developers API for safely updating the mail_system configuration variable.
+Administrative UI
+The administrative interface is at admin/config/system/mailsystem. A screenshot is available.
+Used by:
+
+Developers API
+A module example with a MailSystemInterface implementation called ExampleMailSystem should add the following in its example.install file:
+
+/**
+ * Implements hook_enable().
+ */
+function example_enable() {
+ mailsystem_set(array('example' => 'ExampleMailSystem'));
+}
+/**
+ * Implements hook_disable().
+ */
+function example_disable() {
+ mailsystem_clear(array('example' => 'ExampleMailSystem'));
+}
+
+
+The above settings allow mail sent by example to use ExampleMailSystem. To make ExampleMailSystem the site-wide default for sending mail:
+
+mailsystem_set(array(mailsystem_default_id() => 'ExampleMailSystem'));
+
+
+To restore the default mail system:
+
+mailsystem_set(array(mailsystem_default_id() => mailsystem_default_value()));
+
+
+Or simply:
+
+mailsystem_set(mailsystem_defaults());
+
+
+If module example relies on dependency foo and its FooMailSystem class, then the example.install code should like like this:
+
+/**
+ * Implements hook_enable().
+ */
+function example_enable() {
+ mailsystem_set(array('example' => 'FooMailSystem'));
+}
+/**
+ * Implements hook_disable().
+ */
+function example_disable() {
+ mailsystem_clear(array('example' => ''));
+}
+
+
+If module example only wants to use FooMailSystem when sending emails with a key of examail, then the example.install code should look like this:
+
+/**
+ * Implements hook_enable().
+ */
+function example_enable() {
+ mailsystem_set(array('example_examail' => 'FooMailSystem'));
+}
+/**
+ * Implements hook_disable().
+ */
+function example_disable() {
+ mailsystem_clear(array('example_examail' => ''));
+}
+
+
+(New in 2.x branch)
+To change the site-wide defaults to use the FooMailSystem for formatting messages and the BarMailSystem for sending them:
+
+mailsystem_set(
+ array(
+ mailsystem_default_id() => array(
+ 'format' => 'FooMailSystem',
+ 'mail' => 'BarMailSystem',
+ ),
+ )
+);
+
+
+To change the site-wide defaults to use the FooMailSystem for sending messages, while continuing to use the current system for formatting them:
+
+mailsystem_set(
+ array(
+ mailsystem_default_id() => array(
+ 'mail' => 'FooMailsystem',
+ ),
+ )
+);
+
+
+References
+
+ drupal_mail_system() API documentation:
+ -
+
api.drupal.org/api/drupal/includes--mail.inc/function/drupal_mail_system/7
+
+ MailSystemInterface API documentation:
+ -
+
api.drupal.org/api/drupal/includes--mail.inc/interface/MailSystemInterface/7
+
+ - Creating HTML formatted mails in Drupal 7:
+ -
+
+
+
diff --git a/sites/all/modules/mailsystem/README.markdown b/sites/all/modules/mailsystem/README.markdown
new file mode 100644
index 0000000..6d549f9
--- /dev/null
+++ b/sites/all/modules/mailsystem/README.markdown
@@ -0,0 +1,118 @@
+## [Mail System](http://drupal.org/project/mailsystem)
+
+Provides an Administrative UI and Developers API for safely updating the
+[mail_system](http://api.drupal.org/api/drupal/includes--mail.inc/function/drupal_mail_system/7)
+configuration variable.
+
+### Administrative UI
+
+The administrative interface is at `admin/config/system/mailsystem`.
+A [screenshot](http://drupal.org/node/1134044) is available.
+
+### Used by:
+
+* [HTML Mail](http://drupal.org/project/htmlmail)
+* [Mime Mail 7.x-1.x-dev](http://drupal.org/project/mimemail)
+* [Postmark 7.x-1.x](http://drupal.org/project/postmark)
+
+### Developers API
+
+A module `example` with a
+[`MailSystemInterface`](http://api.drupal.org/api/drupal/includes--mail.inc/interface/MailSystemInterface/7)
+implementation called `ExampleMailSystem` should add the following in its
+`example.install` file:
+
+ /**
+ * Implements hook_enable().
+ */
+ function example_enable() {
+ mailsystem_set(array('example' =\> 'ExampleMailSystem'));
+ }
+
+ /**
+ * Implements hook_disable().
+ */
+ function example_disable() {
+ mailsystem_clear(array('example' =\> 'ExampleMailSystem'));
+ }
+
+The above settings allow mail sent by `example` to use `ExampleMailSystem`. To make
+`ExampleMailSystem` the site-wide default for sending mail:
+
+ mailsystem_set(array(mailsystem_default_id() =\> 'ExampleMailSystem'));
+
+To restore the default mail system:
+
+ mailsystem_set(array(mailsystem_default_id() =\> mailsystem_default_value()));
+
+Or simply:
+
+ mailsystem_set(mailsystem_defaults());
+
+If module `example` relies on dependency `foo` and its `FooMailSystem` class, then
+the `example.install` code should like like this:
+
+ /**
+ * Implements hook_enable().
+ */
+ function example_enable() {
+ mailsystem_set(array('example' =\> 'FooMailSystem'));
+ }
+
+ /**
+ * Implements hook_disable().
+ */
+ function example_disable() {
+ mailsystem_clear(array('example' =\> ''));
+ }
+
+If module `example` only wants to use `FooMailSystem` when sending emails with a key
+of `examail`, then the `example.install` code should look like this:
+
+ /**
+ * Implements hook_enable().
+ */
+ function example_enable() {
+ mailsystem_set(array('example_examail' =\> 'FooMailSystem'));
+ }
+
+ /**
+ * Implements hook_disable().
+ */
+ function example_disable() {
+ mailsystem_clear(array('example_examail' =\> ''));
+ }
+
+#### *(New in 2.x branch)*
+
+To change the site-wide defaults to use the `FooMailSystem` for formatting messages and the `BarMailSystem` for sending them:
+
+ mailsystem_set(
+ array(
+ mailsystem_default_id() => array(
+ 'format' => 'FooMailSystem',
+ 'mail' => 'BarMailSystem',
+ ),
+ )
+ );
+
+To change the site-wide defaults to use the `FooMailSystem` for sending messages, while continuing to use the current system for formatting them:
+
+ mailsystem_set(
+ array(
+ mailsystem_default_id() => array(
+ 'mail' => 'FooMailsystem',
+ ),
+ )
+ );
+
+### References
+
+**[`drupal_mail_system()` API documentation](http://api.drupal.org/api/drupal/includes--mail.inc/function/drupal_mail_system/7)**:
+: [api.drupal.org/api/drupal/includes--mail.inc/function/drupal_mail_system/7](http://api.drupal.org/api/drupal/includes--mail.inc/function/drupal_mail_system/7)
+
+**[`MailSystemInterface` API documentation](http://api.drupal.org/api/drupal/includes--mail.inc/interface/MailSystemInterface/7)**:
+: [api.drupal.org/api/drupal/includes--mail.inc/interface/MailSystemInterface/7](http://api.drupal.org/api/drupal/includes--mail.inc/interface/MailSystemInterface/7)
+
+**[Creating HTML formatted mails in Drupal 7](http://drupal.org/node/900794)**:
+: [drupal.org/node/900794](http://drupal.org/node/900794)
diff --git a/sites/all/modules/mailsystem/README.txt b/sites/all/modules/mailsystem/README.txt
new file mode 100644
index 0000000..ca302af
--- /dev/null
+++ b/sites/all/modules/mailsystem/README.txt
@@ -0,0 +1,134 @@
+[1]Mail System
+
+ Provides an Administrative UI and Developers API for safely updating
+ the [2]mail_system configuration variable.
+
+ Administrative UI
+
+ The administrative interface is at admin/config/system/mailsystem. A
+ [3]screenshot is available.
+
+ Used by:
+
+ * [4]HTML Mail
+ * [5]Mime Mail 7.x-1.x-dev
+ * [6]Postmark 7.x-1.x
+
+ Developers API
+
+ A module example with a [7]MailSystemInterface implementation called
+ ExampleMailSystem should add the following in its example.install file:
+/**
+ * Implements hook_enable().
+ */
+function example_enable() {
+ mailsystem_set(array('example' => 'ExampleMailSystem'));
+}
+/**
+ * Implements hook_disable().
+ */
+function example_disable() {
+ mailsystem_clear(array('example' => 'ExampleMailSystem'));
+}
+
+
+ The above settings allow mail sent by example to use ExampleMailSystem.
+ To make ExampleMailSystem the site-wide default for sending mail:
+mailsystem_set(array(mailsystem_default_id() => 'ExampleMailSystem'));
+
+
+ To restore the default mail system:
+mailsystem_set(array(mailsystem_default_id() => mailsystem_default_value()));
+
+
+ Or simply:
+mailsystem_set(mailsystem_defaults());
+
+
+ If module example relies on dependency foo and its FooMailSystem class,
+ then the example.install code should like like this:
+/**
+ * Implements hook_enable().
+ */
+function example_enable() {
+ mailsystem_set(array('example' => 'FooMailSystem'));
+}
+/**
+ * Implements hook_disable().
+ */
+function example_disable() {
+ mailsystem_clear(array('example' => ''));
+}
+
+
+ If module example only wants to use FooMailSystem when sending emails
+ with a key of examail, then the example.install code should look like
+ this:
+/**
+ * Implements hook_enable().
+ */
+function example_enable() {
+ mailsystem_set(array('example_examail' => 'FooMailSystem'));
+}
+/**
+ * Implements hook_disable().
+ */
+function example_disable() {
+ mailsystem_clear(array('example_examail' => ''));
+}
+
+
+ (New in 2.x branch)
+
+ To change the site-wide defaults to use the FooMailSystem for
+ formatting messages and the BarMailSystem for sending them:
+mailsystem_set(
+ array(
+ mailsystem_default_id() => array(
+ 'format' => 'FooMailSystem',
+ 'mail' => 'BarMailSystem',
+ ),
+ )
+);
+
+
+ To change the site-wide defaults to use the FooMailSystem for sending
+ messages, while continuing to use the current system for formatting
+ them:
+mailsystem_set(
+ array(
+ mailsystem_default_id() => array(
+ 'mail' => 'FooMailsystem',
+ ),
+ )
+);
+
+
+ References
+
+ [8]drupal_mail_system() API documentation:
+ [9]api.drupal.org/api/drupal/includes--mail.inc/function/drupal_
+ mail_system/7
+
+ [10]MailSystemInterface API documentation:
+ [11]api.drupal.org/api/drupal/includes--mail.inc/interface/MailS
+ ystemInterface/7
+
+ [12]Creating HTML formatted mails in Drupal 7:
+ [13]drupal.org/node/900794
+
+References
+
+ 1. http://drupal.org/project/mailsystem
+ 2. http://api.drupal.org/api/drupal/includes--mail.inc/function/drupal_mail_system/7
+ 3. http://drupal.org/node/1134044
+ 4. http://drupal.org/project/htmlmail
+ 5. http://drupal.org/project/mimemail
+ 6. http://drupal.org/project/postmark
+ 7. http://api.drupal.org/api/drupal/includes--mail.inc/interface/MailSystemInterface/7
+ 8. http://api.drupal.org/api/drupal/includes--mail.inc/function/drupal_mail_system/7
+ 9. http://api.drupal.org/api/drupal/includes--mail.inc/function/drupal_mail_system/7
+ 10. http://api.drupal.org/api/drupal/includes--mail.inc/interface/MailSystemInterface/7
+ 11. http://api.drupal.org/api/drupal/includes--mail.inc/interface/MailSystemInterface/7
+ 12. http://drupal.org/node/900794
+ 13. http://drupal.org/node/900794
diff --git a/sites/all/modules/mailsystem/html_to_text.inc b/sites/all/modules/mailsystem/html_to_text.inc
new file mode 100644
index 0000000..0857622
--- /dev/null
+++ b/sites/all/modules/mailsystem/html_to_text.inc
@@ -0,0 +1,742 @@
+' characters are
+ * repeated on subsequent wrapped lines. Others are replaced by spaces.
+ * - max: The maximum length at which to wrap each line. Defaults to 80.
+ * - stuff: Whether to space-stuff special lines. Defaults to TRUE.
+ * - hard: Whether to enforce the maximum line length even if no convenient
+ * space character is available. Defaults to FALSE.
+ * - pad: A string to use for padding short lines to 'max' characters. If
+ * more than one character, only the last will be repeated.
+ * - break: The line break sequence to insert. The default is one of the
+ * following:
+ * - "\r\n": Windows, when $text does not contain a space character.
+ * - "\n": Non-Windows, when $text does not contain a space character.
+ * - " \r\n": On Windows, when $text contains at least one space.
+ * - " \n": Non-Windows, when $text contains at least one space.
+ *
+ * @see drupal_mail()
+ */
+function mailsystem_wrap_mail($text, array $options = array()) {
+ static $defaults;
+ if (!isset($defaults)) {
+ $defaults = array(
+ 'indent' => '',
+ 'pad' => '',
+ 'pad_repeat' => '',
+ 'max' => 80,
+ 'stuff' => TRUE,
+ 'hard' => FALSE,
+ 'eol' => variable_get('mail_line_endings', MAIL_LINE_ENDINGS),
+ );
+ }
+ $options += $defaults;
+ if (!isset($options['break'])) {
+ // Allow soft-wrap spaces only when $text contains at least one space.
+ $options['break'] = (strpos($text, ' ') === FALSE ? '' : ' ') . $defaults['eol'];
+ }
+ $options['wrap'] = $options['max'] - drupal_strlen($options['indent']);
+ if ($options['pad']) {
+ $options['pad_repeat'] = drupal_substr($options['pad'], -1, 1);
+ }
+ // The 'clean' indent is applied to all lines after the first one.
+ $options['clean'] = _mailsystem_html_to_text_clean($options['indent']);
+ // Wrap lines according to RFC 3676.
+ $lines = explode($defaults['eol'], $text);
+ array_walk($lines, '_mailsystem_wrap_mail_line', $options);
+ // Expand the lines array on newly-inserted line breaks.
+ $lines = explode($defaults['eol'], implode($defaults['eol'], $lines));
+ // Apply indentation, space-stuffing, and padding.
+ array_walk($lines, '_mailsystem_indent_mail_line', $options);
+ return implode($defaults['eol'], $lines);
+}
+
+/**
+ * Transform an HTML string into plain text, preserving the structure of the
+ * markup. Useful for preparing the body of a node to be sent by e-mail.
+ *
+ * The output will be suitable for use as 'format=flowed; delsp=yes' text
+ * (RFC 3676) and can be passed directly to drupal_mail() for sending.
+ *
+ * We deliberately use variable_get('mail_line_endings', MAIL_LINE_ENDINGS)
+ * rather than "\r\n".
+ *
+ * This function provides suitable alternatives for the following tags:
+ *
+ *
-
+ *
-
+ *