FINAL suepr merge step : added all modules to this super repos

This commit is contained in:
Bachir Soussi Chiadmi
2015-04-19 16:46:59 +02:00
7585 changed files with 1723356 additions and 18 deletions

View File

@@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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.
<signature of Ty Coon>, 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.

View File

@@ -0,0 +1,26 @@
This module for Drupal 7.x. provides complete control of Email settings with
Drupal and Mailjet.
It simply replaces your default SMTP settings in order to make all your emails
go through Mailjet: this will improve your deliverability and allow you to
optimize your campaigns.
Mailjet helps to send and track emails in real time, while ensuring their
deliverability.
You'll take advantage of our reporting tools and get advanced statistics to
monitor and optimize your emails.
Installation
------------
1) Download a release.
2) Upload all its content in your Drupal sites/all/modules/ directory.
3) Make sure you are using libraries module and you have correctly installed PHPMailer with "phpmailer" as key.
4) Log in as administrator in Drupal.
5) Enable the Mailjet settings module on the Administer > Site building > Modules page.
6) Fill in required settings on the Administer > Site configuration > Mailjet settings page.
Author
------
Mailjet SAS
plugins@mailjet.com

View File

@@ -0,0 +1,142 @@
<?php
/**
* @file
* The backoffice of module.
*/
/**
* Implements hook_settings().
*/
function mailjet_admin_settings() {
$form['onoff'] = array(
'#type' => 'fieldset',
'#title' => t('General settings'),
);
$form['onoff']['mailjet_on'] = array(
'#type' => 'radios',
'#title' => t('Send emails through Mailjet'),
'#default_value' => variable_get('mailjet_on', FALSE),
'#options' => array(1 => t('Yes'), 0 => t('No')),
);
$form['onoff']['mailjet_from'] = array(
'#type' => 'textfield',
'#title' => t('E-mail from address'),
'#default_value' => variable_get('mailjet_from', variable_get('site_mail', '')),
'#description' => t('The e-mail address that all e-mails will be from.'),
'#required' => TRUE,
);
$form['onoff']['mailjet_test'] = array(
'#type' => 'radios',
'#title' => t('Send test mail now'),
'#default_value' => variable_get('mailjet_test', FALSE),
'#options' => array(1 => t('Yes'), 0 => t('No')),
);
$form['onoff']['mailjet_test_address'] = array(
'#type' => 'textfield',
'#title' => t('Recipient of test mail'),
'#default_value' => variable_get('mailjet_test_address', ''),
);
$form['auth'] = array(
'#type' => 'fieldset',
'#title' => t('Mailjet settings'),
);
$form['auth']['mailjet_username'] = array(
'#type' => 'textfield',
'#title' => t('API Key'),
'#default_value' => variable_get('mailjet_username', ''),
'#required' => TRUE,
);
$form['auth']['mailjet_password'] = array(
'#type' => 'textfield',
'#title' => t('Secret Key'),
'#default_value' => variable_get('mailjet_password', ''),
'#required' => TRUE,
);
$test_address = variable_get('mailjet_test_address', '');
if (variable_get('mailjet_test', FALSE) && valid_email_address($test_address)) {
$mailjet_on = variable_get('mailjet_on', FALSE);
variable_set('mail_system', array('default-system' => 'MailjetSmtpMailSystem'));
variable_set('mailjet_test', FALSE);
global $language;
$params['subject'] = t('Your test mail from Mailjet');
$params['body'] = array(t('Your Mailjet configuration is ok!'));
$mail = drupal_mail('mailjet', 'mailjet-test', $test_address, $language, $params);
variable_set('mailjet_on', $mailjet_on);
if (isset($mail['result']) && $mail['result']) {
drupal_set_message(t('A test e-mail has been sent to @email.', array('@email' => check_plain($test_address))));
}
}
if (variable_get('mailjet_on', FALSE)) {
variable_set('mail_system', array('default-system' => 'MailjetSmtpMailSystem'));
}
else {
variable_set('mail_system', array('default-system' => 'DefaultMailSystem'));
}
return system_settings_form($form);
}
/**
* Implements hook_admin_settings_validat().
*/
function mailjet_admin_settings_validate($form, &$form_state) {
if (!valid_email_address($form_state['values']['mailjet_from'])) {
form_set_error('mailjet_from', t('The provided from e-mail address is not valid.'));
}
if ($form_state['values']['mailjet_test'] && !valid_email_address($form_state['values']['mailjet_test_address'])) {
form_set_error('mailjet_test_address', t('The provided test e-mail address is not valid.'));
}
$configs = array(array('ssl://', 465),
array('tls://', 587),
array('', 587),
array('', 588),
array('tls://', 25),
array('', 25));
$host = variable_get('mailjet_host', 'in.mailjet.com');
$connected = FALSE;
for ($i = 0; $i < count($configs); ++$i) {
$soc = @ fsockopen($configs[$i][0] . $host, $configs[$i][1], $errno, $errstr, 5);
if ($soc) {
fClose($soc);
$connected = TRUE;
break;
}
}
if ($connected) {
if ('ssl://' == $configs[$i][0]) {
variable_set('mailjet_protocol', 'ssl');
}
elseif ('tls://' == $configs[$i][0]) {
variable_set('mailjet_protocol', 'tls');
}
else {
variable_set('mailjet_protocol', 'standard');
}
variable_set('mailjet_port', $configs[$i][1]);
}
else {
form_set_error('mailjet_on', t('Please contact Mailjet support to sort this out.<br /><br />Error @errno - @errstr', array ('@errno' => $errno, '@errstr' => $errstr)));
}
}

View File

@@ -0,0 +1,14 @@
name = Mailjet
description = "Send your emails by your Mailjet API."
core = 7.x
package = Mail
configure = admin/config/system/mailjet
files[] = mailjet.mail.inc
dependencies[] = libraries
; Information added by drupal.org packaging script on 2012-05-07
version = "7.x-1.1"
core = "7.x"
project = "Mailjet"
datestamp = "1336426255"

View File

@@ -0,0 +1,46 @@
<?php
/**
* @file
* Functions called while installing, uninstalling or activation of the module.
*/
/**
* Implements hook_install().
*/
function mailjet_install() {
variable_set('mailjet_debug', FALSE);
}
/**
* Implements hook_uninstall().
*/
function mailjet_uninstall() {
variable_del('mailjet_from');
variable_del('mailjet_host');
variable_del('mailjet_on');
variable_del('mailjet_password');
variable_del('mailjet_port');
variable_del('mailjet_protocol');
variable_del('mailjet_test');
variable_del('mailjet_test_address');
variable_del('mailjet_username');
variable_del('mailjet_allowhtml');
variable_del('mailjet_debug');
}
/**
* Implements hook_disable().
*/
function mailjet_disable() {
variable_set('mailjet_on', FALSE);
variable_set('mail_system', array('default-system' => 'DefaultMailSystem'));
}
/**
* Implements hook_update().
*/
function mailjet_update_7000() {
if (variable_get('mailjet_on', FALSE)) {
variable_set('mail_system', array('default-system' => 'MailjetSmtpMailSystem'));
}
}

View File

@@ -0,0 +1,592 @@
<?php
/**
* @file
* Mail processing.
*/
class MailjetSmtpMailSystem implements MailSystemInterface {
protected $AllowHtml;
/**
* Concatenate and wrap the e-mail body for either
* plain-text or HTML emails.
*
* @param array $message
* A message array, as described in hook_mail_alter().
*
* @return string
* The formatted $message.
*/
public function format(array $message) {
$this->AllowHtml = variable_get('mailjet_allowhtml', TRUE);
// Join the body array into one string.
$message['body'] = implode("\n\n", $message['body']);
if (! $this->AllowHtml) {
// Convert any HTML to plain-text.
$message['body'] = drupal_html_to_text($message['body']);
// Wrap the mail body for sending.
$message['body'] = drupal_wrap_mail($message['body']);
}
return $message;
}
/**
* Send the e-mail message.
*
* @see drupal_mail()
*
* @param array $message
* A message array, as described in hook_mail_alter().
*
* @return bool
* TRUE if the mail was successfully accepted, otherwise FALSE.
*/
public function mail(array $message) {
$id = $message['id'];
$to = $message['to'];
$from = $message['from'];
$subject = $message['subject'];
$body = $message['body'];
$headers = $message['headers'];
if (isset($message['params']['subject'])) {
$subject = $message['params']['subject'];
}
if (isset($message['params']['body'][0])) {
$body = $message['params']['body'][0];
}
$path = libraries_get_path('phpmailer');
if (file_exists($path . '/class.phpmailer.php')) {
require_once $path . '/class.phpmailer.php';
}
else {
drupal_set_message(t('Unable to send mail : PHPMailer library does not exist.'), 'error');
return FALSE;
}
// Create a new PHPMailer object - autoloaded from registry.
$mailer = new PHPMailer();
$from_name = variable_get('site_name', '');
// Hack to fix reply-to issue.
$properfrom = variable_get('site_mail', '');
if (!empty($properfrom)) {
$headers['From'] = $properfrom;
}
if (!isset($headers['Reply-To']) || empty($headers['Reply-To'])) {
if (strpos($from, '<')) {
$reply = preg_replace('/>.*/', '', preg_replace('/.*</', '', $from));
}
else {
$reply = $from;
}
$headers['Reply-To'] = $reply;
}
// Blank value will let the e-mail address appear.
if ($from == NULL || $from == '') {
// If from e-mail address is blank, use smtp_from config option.
if (($from = variable_get('mailjet_from', '')) == '') {
// If smtp_from config option is blank, use site_email.
if (($from = variable_get('site_email', '')) == '') {
drupal_set_message(t('There is no submitted from address.'), 'error');
if (variable_get('mailjet_debug')) {
watchdog('mailjet', 'There is no submitted from address.', array(), WATCHDOG_ERROR);
}
return FALSE;
}
}
}
if (preg_match('/^"?.*"?\s*<.*>$/', $from)) {
// . == Matches any single character except line break characters \r and
// \n.
// * == Repeats the previous item zero or more times.
$from_name = preg_replace('/"?([^("\t\n)]*)"?.*$/', '$1', $from);
$from = preg_replace("/(.*)\<(.*)\>/i", '$2', $from);
}
elseif (!valid_email_address($from)) {
drupal_set_message(t('The submitted from address (@from) is not valid.', array('@from' => $from)), 'error');
if (variable_get('mailjet_debug')) {
watchdog('mailjet', 'The submitted from address (@from) is not valid.', array('@from' => $from), WATCHDOG_ERROR);
}
return FALSE;
}
// Defines the From value $from_nameto what we expect.
$mailer->SetFrom($from, $from_name);
// Create the list of 'To:' recipients.
$torecipients = explode(',', $to);
foreach ($torecipients as $torecipient) {
if (strpos($torecipient, '<') !== FALSE) {
$toparts = explode(' <', $torecipient);
$toname = $toparts[0];
$toaddr = rtrim($toparts[1], '>');
}
else {
$toname = '';
$toaddr = $torecipient;
}
$mailer->AddAddress($toaddr, $toname);
}
// Parse the headers of the message and set the PHPMailer object's settings
// accordingly.
foreach ($headers as $key => $value) {
switch (drupal_strtolower($key)) {
case 'from':
if ($from == NULL or $from == '') {
// If a from value was already given, then set based on header.
// Should be the most common situation since drupal_mail moves the
// from to headers.
$from = $value;
$mailer->From = $value;
// Then from can be out of sync with from_name !
$mailer->FromName = '';
$mailer->Sender = $value;
}
break;
case 'content-type':
// Parse several values on the Content-type header, storing them in
// an array like key=value -> $vars['key']='value'
$vars = explode('; ', $value);
foreach ($vars as $i => $var) {
if ($cut = strpos($var, '=')) {
$new_var = drupal_strtolower(drupal_substr($var, $cut + 1));
$new_key = drupal_substr($var, 0, $cut);
unset($vars[$i]);
$vars[$new_key] = $new_var;
}
}
// Set the charset based on the provided value, if there is one.
$mailer->CharSet = isset($vars['charset']) ? $vars['charset'] : 'utf-8';
switch ($vars[0]) {
case 'text/plain':
// The message includes only a plain text part.
$mailer->IsHTML(FALSE);
$content_type = 'text/plain';
break;
case 'text/html':
// The message includes only an HTML part.
$mailer->IsHTML(TRUE);
$content_type = 'text/html';
break;
case 'multipart/related':
// Get the boundary ID from the Content-Type header.
$boundary = $this->getSubstrings($value, 'boundary', '"', '"');
// The message includes an HTML part w/inline attachments.
$mailer->ContentType = $content_type = 'multipart/related; boundary="' . $boundary . '"';
break;
case 'multipart/alternative':
// The message includes both a plain text and an HTML part.
$mailer->ContentType = $content_type = 'multipart/alternative';
// Get the boundary ID from the Content-Type header.
$boundary = $this->getSubstrings($value, 'boundary', '"', '"');
break;
case 'multipart/mixed':
// The message includes one or more attachments.
$mailer->ContentType = $content_type = 'multipart/mixed';
// Get the boundary ID from the Content-Type header.
$boundary = $this->getSubstrings($value, 'boundary', '"', '"');
break;
default:
// Everything else is unsuppored by PHPMailer.
drupal_set_message(t('The Content-Type of your message is not supported by PHPMailer and will be sent as text/plain instead.'), 'error');
if (variable_get('mailjet_debug')) {
watchdog('mailjet', 'The Content-Type of your message is not supported by PHPMailer and will be sent as text/plain instead.', array(), WATCHDOG_ERROR);
}
// Force the Content-Type to be text/plain.
$mailer->IsHTML(FALSE);
$content_type = 'text/plain';
}
break;
case 'reply-to':
// Only add a "reply-to" if it's not the same as "return-path".
if ($value != $headers['Return-Path']) {
if (strpos($value, '<') !== FALSE) {
$reply_to_parts = explode('<', $value);
$reply_to_name = trim($reply_to_parts[0]);
$reply_to_name = trim($reply_to_name, '"');
$reply_to_addr = rtrim($reply_to_parts[1], '>');
$mailer->AddReplyTo($reply_to_addr, $reply_to_name);
}
else {
$mailer->AddReplyTo($value);
}
}
break;
case 'content-transfer-encoding':
$mailer->Encoding = $value;
break;
case 'return-path':
case 'mime-version':
case 'x-mailer':
break;
case 'errors-to':
$mailer->AddCustomHeader('Errors-To: ' . $value);
break;
case 'cc':
$ccrecipients = explode(',', $value);
foreach ($ccrecipients as $ccrecipient) {
if (strpos($ccrecipient, '<') !== FALSE) {
$ccparts = explode(' <', $ccrecipient);
$ccname = $ccparts[0];
$ccaddr = rtrim($ccparts[1], '>');
}
else {
$ccname = '';
$ccaddr = $ccrecipient;
}
$mailer->AddBCC($ccaddr, $ccname);
}
break;
case 'bcc':
$bccrecipients = explode(',', $value);
foreach ($bccrecipients as $bccrecipient) {
if (strpos($bccrecipient, '<') !== FALSE) {
$bccparts = explode(' <', $bccrecipient);
$bccname = $bccparts[0];
$bccaddr = rtrim($bccparts[1], '>');
}
else {
$bccname = '';
$bccaddr = $bccrecipient;
}
$mailer->AddBCC($bccaddr, $bccname);
}
break;
default:
// The header key is not special - add it as is.
$mailer->AddCustomHeader($key . ': ' . $value);
}
}
$mailer->AddCustomHeader('X-Mailer:Mailjet-for-Drupal/1.0');
$mailer->Subject = $subject;
// Processes the message's body.
switch ($content_type) {
case 'multipart/related':
$mailer->Body = $body;
break;
case 'multipart/alternative':
// Split the body based on the boundary ID.
$body_parts = $this->boundarySplit($body, $boundary);
foreach ($body_parts as $body_part) {
// If plain/text within the body part, add it to $mailer->AltBody.
if (strpos($body_part, 'text/plain')) {
// Clean up the text.
$body_part = trim($this->removeHeaders(trim($body_part)));
// Include it as part of the mail object.
$mailer->AltBody = $body_part;
}
// If plain/html within the body part, add it to $mailer->Body.
elseif (strpos($body_part, 'text/html')) {
// Clean up the text.
$body_part = trim($this->removeHeaders(trim($body_part)));
// Include it as part of the mail object.
$mailer->Body = $body_part;
}
}
break;
case 'multipart/mixed':
// Split the body based on the boundary ID.
$body_parts = $this->boundarySplit($body, $boundary);
// Determine if there is an HTML part for when adding the plain
// text part.
$text_plain = FALSE;
$text_html = FALSE;
foreach ($body_parts as $body_part) {
if (strpos($body_part, 'text/plain')) {
$text_plain = TRUE;
}
if (strpos($body_part, 'text/html')) {
$text_html = TRUE;
}
}
foreach ($body_parts as $body_part) {
// If test/plain within the body part, add it to either
// $mailer->AltBody or $mailer->Body, depending on whether there is
// also a text/html part ot not.
if (strpos($body_part, 'multipart/alternative')) {
// Clean up the text.
$body_part = trim($this->removeHeaders(trim($body_part)));
// Get boundary ID from the Content-Type header.
$boundary2 = $this->getSubstrings($body_part, 'boundary', '"', '"');
// Split the body based on the boundary ID.
$body_parts2 = $this->boundarySplit($body_part, $boundary2);
foreach ($body_parts2 as $body_part2) {
// If plain/text within the body part, add it to $mailer->AltBody.
if (strpos($body_part2, 'text/plain')) {
// Clean up the text.
$body_part2 = trim($this->removeHeaders(trim($body_part2)));
// Include it as part of the mail object.
$mailer->AltBody = $body_part2;
$mailer->ContentType = 'multipart/mixed';
}
// If plain/html within the body part, add it to $mailer->Body.
elseif (strpos($body_part2, 'text/html')) {
// Clean up the text.
$body_part2 = trim($this->removeHeaders(trim($body_part2)));
// Include it as part of the mail object.
$mailer->Body = $body_part2;
$mailer->ContentType = 'multipart/mixed';
}
}
}
// If text/plain within the body part, add it to $mailer->Body.
elseif (strpos($body_part, 'text/plain')) {
// Clean up the text.
$body_part = trim($this->removeHeaders(trim($body_part)));
if ($text_html) {
$mailer->AltBody = $body_part;
$mailer->IsHTML(TRUE);
$mailer->ContentType = 'multipart/mixed';
}
else {
$mailer->Body = $body_part;
$mailer->IsHTML(FALSE);
$mailer->ContentType = 'multipart/mixed';
}
}
// If text/html within the body part, add it to $mailer->Body.
elseif (strpos($body_part, 'text/html')) {
// Clean up the text.
$body_part = trim($this->removeHeaders(trim($body_part)));
// Include it as part of the mail object.
$mailer->Body = $body_part;
$mailer->IsHTML(TRUE);
$mailer->ContentType = 'multipart/mixed';
}
// Add the attachment.
elseif (strpos($body_part, 'Content-Disposition: attachment;')) {
$file_path = $this->getSubstrings($body_part, 'filename=', '"', '"');
$file_name = $this->getSubstrings($body_part, ' name=', '"', '"');
$file_encoding = $this->getSubstrings($body_part, 'Content-Transfer-Encoding', ' ', "\n");
$file_type = $this->getSubstrings($body_part, 'Content-Type', ' ', ';');
if (file_exists($file_path)) {
if (!$mailer->AddAttachment($file_path, $file_name, $file_encoding, $filetype)) {
drupal_set_message(t('Attahment could not be found or accessed.'));
}
}
else {
// Clean up the text.
$body_part = trim($this->removeHeaders(trim($body_part)));
if (drupal_strtolower($file_encoding) == 'base64') {
$attachment = base64_decode($body_part);
}
elseif (drupal_strtolower($file_encoding) == 'quoted-printable') {
$attachment = quoted_printable_decode($body_part);
}
else {
$attachment = $body_part;
}
$attachment_new_filename = tempnam(realpath(file_directory_temp()), 'smtp');
$file_path = file_save_data($attachment, $attachment_new_filename, FILE_EXISTS_RENAME);
if (!$mailer->AddAttachment($file_path, $file_name)) {
drupal_set_message(t('Attachment could not be found or accessed.'));
}
}
}
}
break;
default:
$mailer->Body = $body;
break;
}
// Set the authentication settings.
$username = variable_get('mailjet_username', '');
$password = variable_get('mailjet_password', '');
// If username and password are given, use SMTP authentication.
if ($username != '' && $password != '') {
$mailer->SMTPAuth = TRUE;
$mailer->Username = $username;
$mailer->Password = $password;
}
// Set the protocol prefix for the smtp host.
switch (variable_get('mailjet_protocol', 'standard')) {
case 'ssl':
$mailer->SMTPSecure = 'ssl';
break;
case 'tls':
$mailer->SMTPSecure = 'tls';
break;
default:
$mailer->SMTPSecure = '';
}
// Set other connection settings.
$mailer->Host = variable_get('mailjet_host', 'in.mailjet.com');
$mailer->Port = variable_get('mailjet_port', '25');
$mailer->Mailer = 'smtp';
if (variable_get('mailjet_debug')) {
watchdog('mailjet', 'Sending mail to: @to', array('@to' => $to));
}
// Try to send e-mail. If it fails, set watchdog entry.
if (!$mailer->Send()) {
if (variable_get('mailjet_debug')) {
watchdog('mailjet',
'Error sending e-mail from @from to @to : @error_message',
array(
'@from' => $from,
'@to' => $to,
'@error_message' => $mailer->ErrorInfo),
WATCHDOG_ERROR);
}
return FALSE;
}
$mailer->SmtpClose();
return TRUE;
}
/**
* Splits the input into parts based on the given boundary.
*
* Swiped from Mail::MimeDecode, with modifications based on Drupal's coding
* standards and this bug report: http://pear.php.net/bugs/bug.php?id=6495
*
* @param string $input
* A string containing the body text to parse.
* @param string $boundary
* A string with the boundary string to parse on.
*
* @return array
* An array containing the resulting mime parts
*/
protected function boundarySplit($input, $boundary) {
$parts = array();
$bs_possible = drupal_substr($boundary, 2, -2);
$bs_check = '\"' . $bs_possible . '\"';
if ($boundary == $bs_check) {
$boundary = $bs_possible;
}
$tmp = explode('--' . $boundary, $input);
for ($i = 1; $i < count($tmp); $i++) {
if (trim($tmp[$i])) {
$parts[] = $tmp[$i];
}
}
return $parts;
}
/**
* Strips the headers from the body part.
*
* @param string $input
* A string containing the body part to strip.
*
* @return string
* A string with the stripped body part.
*/
protected function removeHeaders($input) {
$part_array = explode("\n", $input);
if (strpos($part_array[0], 'Content') !== FALSE) {
if (strpos($part_array[1], 'Content') !== FALSE) {
if (strpos($part_array[2], 'Content') !== FALSE) {
array_shift($part_array);
array_shift($part_array);
array_shift($part_array);
}
else {
array_shift($part_array);
array_shift($part_array);
}
}
else {
array_shift($part_array);
}
}
$output = implode("\n", $part_array);
return $output;
}
/**
* Returns a string that is contained within another string.
*
* Returns the string from within $source that is some where after $target
* and is between $beginning_character and $ending_character.
*
* @param string $source
* A string containing the text to look through.
* @param string $target
* A string containing the text in $source to start looking from.
* @param string $beginning_character
* A string containing the character just before the sought after text.
* @param string $ending_character
* A string containing the character just after the sought after text.
*
* @return string
* A string with the text found between the $beginning_character and the
* $ending_character.
*/
protected function getSubstrings($source, $target, $beginning_character, $ending_character) {
$search_start = strpos($source, $target) + 1;
$first_character = strpos($source, $beginning_character, $search_start) + 1;
$second_character = strpos($source, $ending_character, $first_character) + 1;
$substring = drupal_substr($source, $first_character, $second_character - $first_character);
$string_length = drupal_strlen($substring) - 1;
if ($substring[$string_length] == $ending_character) {
$substring = drupal_substr($substring, 0, $string_length);
}
return $substring;
}
}

View File

@@ -0,0 +1,44 @@
<?php
/**
* @file
* Hooks and functions of module.
*/
/**
* Implements hook_help().
*/
function mailjet_help($path, $arg) {
switch ($path) {
case 'admin/help#mailjet':
return t('Send your emails by your Mailjet API.');
}
}
/**
* Implements hook_menu().
*/
function mailjet_menu() {
$items['admin/config/system/mailjet'] = array(
'title' => 'Mailjet settings',
'page callback' => 'drupal_get_form',
'page arguments' => array('mailjet_admin_settings'),
'access arguments' => array('administer mailjet module'),
'description' => 'Send your emails by your Mailjet API.',
'file' => 'mailjet.admin.inc',
);
return $items;
}
/**
* Implements hook_permission().
*
* Defines a permission for managing the mailjet variables.
*/
function mailjet_permission() {
return array(
'administer mailjet module' => array(
'title' => t('Administer Mailjet settings module'),
'description' => t('Perform administration tasks for Mailjet settings module.'),
),
);
}

View File

@@ -0,0 +1,634 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply he same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed te
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
auat material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of thisense from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction,nsaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent agaiu convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that paed, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may r published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
hese Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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 3 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, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program 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, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<htses/>.
The GNU 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. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

View File

@@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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.
<signature of Ty Coon>, 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.

View File

@@ -0,0 +1,10 @@
OVERVIEW
========
The Advanced Mail Reroute module is designed to be used on both development and production sites by allowing site admins to allow, block or reroute all outgoing emails based on the id of the email. More complex reroute rules will be added in the future.
AUTHOR/MAINTAINER
=================
-Rohin Knight
rohin.knight@gmail.com

View File

@@ -0,0 +1,13 @@
name = Advanced Mail Reroute
description = Reroute E-mails based on their mail keys.
package = Mail
configure = admin/config/advanced_mail_reroute_rules
core = 7.x
version = "7.x-1.x-dev"
; Information added by drupal.org packaging script on 2013-09-30
version = "7.x-1.0-alpha1+2-dev"
core = "7.x"
project = "advanced_mail_reroute"
datestamp = "1380553038"

View File

@@ -0,0 +1,99 @@
<?php
/**
* @file
* Install file for Mail reroute module.
*/
/*
* Implementation of hook_install().
*
function advanced_mail_reroute_install() {
}
*/
/**
* Implementation of hook_uninstall().
*/
function advanced_mail_reroute_uninstall() {
$vars = array(
'advanced_mail_reroute_default_rule',
'advanced_mail_reroute_primary_email',
'advanced_mail_reroute_override',
'advanced_mail_reroute_enable_logging'
);
foreach ($vars as $var) {
variable_del($var);
}
}
/**
* Implementation of hook_schema()
*/
function advanced_mail_reroute_schema() {
$schema = array();
$schema['advanced_mail_reroute_rules'] = array(
'description' => 'Stores reroute rules for advanced_mail_reroute module.',
'fields' => array(
'mailkey' => array(
'type' => 'varchar',
'length' => 255,
'not null' => TRUE
),
'reroute_rule' => array(
'type' => 'int',
'size' => 'tiny',
'not null' => TRUE
),
'email' => array(
'type' => 'text',
'not null' => TRUE
)
),
'primary key' => array('mailkey'),
'indexes' => array(
'mailkey' => array('mailkey')
)
);
$schema['advanced_mail_reroute_log'] = array(
'description' => 'Stores logs of activity recorded by advanced_mail_reroute module.',
'fields' => array(
'id' => array(
'type' => 'serial',
'unsigned' => TRUE,
'not null' => TRUE
),
'timestamp' => array(
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE
),
'mailkey' => array(
'type' => 'text',
'not null' => TRUE
),
'from_email' => array(
'type' => 'text',
'not null' => TRUE
),
'to_email' => array(
'type' => 'text',
'not null' => TRUE
),
'reroute_rule' => array(
'type' => 'int',
'size' => 'tiny',
'not null' => TRUE
),
'reroute_email' => array(
'type' => 'text',
'not null' => TRUE
)
),
'primary key' => array('id')
);
return $schema;
}

View File

@@ -0,0 +1,26 @@
function advanced_mail_reroute_enable_disable_email_field(select_field) {
if ($(select_field).children("[@selected]").text() == "Reroute") {
var email_field = $(select_field).parent().parent().parent().find('.advanced-mail-reroute-email');
email_field.removeAttr('disabled');
email_field.css({'background-color' : '#fff'});
}
else {
var email_field = $(select_field).parent().parent().parent().find('.advanced-mail-reroute-email');
email_field.attr('disabled', 'disabled');
email_field.css({'background-color' : '#ddd'});
email_field.val('');
}
}
if (Drupal.jsEnabled) {
$(document).ready(function() {
$(".advanced-mail-reroute-rule")
.change(function() {
advanced_mail_reroute_enable_disable_email_field(this);
})
.each(function() {
advanced_mail_reroute_enable_disable_email_field(this);
});
});
};

View File

@@ -0,0 +1,473 @@
<?php
/**
* @file
* Reroute E-mails based on specific rules.
*/
//=============================================================================
// Constant Initalization
//=============================================================================
define('ADVANCED_MAIL_REROUTE_ALLOW', 0);
define('ADVANCED_MAIL_REROUTE_BLOCK', 1);
define('ADVANCED_MAIL_REROUTE_REROUTE', 2);
define('ADVANCED_MAIL_REROUTE_OVERRIDE_DISABLED', 3);
define('ADVANCED_MAIL_REROUTE_DEFAULT_RULE', 'Allow');
//=============================================================================
// Hook Implementations
//=============================================================================
/**
* Implementation of hook_menu()
*/
function advanced_mail_reroute_menu() {
$items = array();
$items['admin/config/advanced_mail_reroute_rules'] = array(
'title' => 'Advanced mail reroute rules',
'description' => t('Configure which mail will be allowed through, blocked or rerouted'),
'page callback' => 'drupal_get_form',
'page arguments' => array('advanced_mail_reroute_settings_form'),
'access arguments' => array('administer advanced mail reroute')
);
$items['admin/reports/advanced_mail_reroute_log'] = array(
'title' => 'Advanced mail reroute log',
'description' => t('View which mail was allowed through, blocked or rerouted'),
'page callback' => 'advanced_mail_reroute_log',
'access arguments' => array('administer site configuration'),
);
return $items;
}
/**
* Implementation of hook_help()
*/
function advanced_mail_reroute_help($section='') {
$output = '';
switch ($section) {
case "admin/help#advanced_mail_reroute":
$output = '<p>'. t("Used to reroute all mail or specific mail based on their mail id.") .'</p>';
break;
}
return $output;
}
/**
* Implementation of hook_theme
*/
function advanced_mail_reroute_theme($existing, $type, $theme, $path) {
return array(
'advanced_mail_reroute_table' => array(
'arguments' => array('element' => NULL),
)
);
}
/**
* Implementation of hook_perm()
*/
function advanced_mail_reroute_perm() {
return array('administer advanced mail reroute');
}
/**
* Implementation of hook_elements()
*/
function advanced_mail_reroute_elements() {
return array(
'advanced_mail_reroute_table' => array(
'#input' => TRUE,
'#process' => array('advanced_mail_reroute_table_process'),
'#element_validate' => array('advanced_mail_reroute_table_validate')
)
);
}
/**
* Implementation of hook_mail_alter()
*/
function advanced_mail_reroute_mail_alter(&$message) {
$num_rows = db_result(db_query("SELECT COUNT(*) FROM {advanced_mail_reroute_rules} WHERE mailkey='%s'", $message['id']));
// fetch or create new rule
if ($num_rows > 0) {
$result = db_query("SELECT reroute_rule, email FROM {advanced_mail_reroute_rules} WHERE mailkey='%s'", $message['id']);
$rule = db_fetch_array($result);
}
else {
$rule = array(
'reroute_rule' => variable_get('advanced_mail_reroute_default_rule', ADVANCED_MAIL_REROUTE_ALLOW),
'email' => ''
);
db_query("INSERT INTO {advanced_mail_reroute_rules} (mailkey, reroute_rule, email) VALUES ('%s', %d, '')", $message['id'], $rule['reroute_rule']);
}
// apply override rule
$reroute_override_rule = variable_get('advanced_mail_reroute_override', ADVANCED_MAIL_REROUTE_OVERRIDE_DISABLED);
if ($reroute_override_rule != ADVANCED_MAIL_REROUTE_OVERRIDE_DISABLED) {
$rule['reroute_rule'] = $reroute_override_rule;
if ($rule['reroute_rule'] == ADVANCED_MAIL_REROUTE_REROUTE) {
$rule['email'] = variable_get('advanced_mail_reroute_primary_email', variable_get('site_mail', NULL));
}
}
else if ($rule['reroute_rule'] == ADVANCED_MAIL_REROUTE_REROUTE && empty($rule['email'])) {
$rule['email'] = variable_get('advanced_mail_reroute_primary_email', variable_get('site_mail', NULL));
}
// log message
if (variable_get('advanced_mail_reroute_log_enabled', TRUE)) {
_advanced_mail_reroute_log($message['id'], $message['from'], $message['to'], $rule['reroute_rule'], $rule['email']);
}
// perform action
switch ($rule['reroute_rule']) {
case ADVANCED_MAIL_REROUTE_ALLOW:
break;
case ADVANCED_MAIL_REROUTE_BLOCK:
$message['to'] = NULL;
// Remove warning about Drupal being unable to send email.
$warnings = drupal_get_messages('warning');
foreach($warnings['warning'] as $warning) {
if($error != 'Unable to send e-mail') {
drupal_set_message($warning, 'warning');
}
}
break;
case ADVANCED_MAIL_REROUTE_REROUTE:
_advanced_mail_reroute_reroute($message, $rule['email']);
break;
}
}
//=============================================================================
// Callback Functions
//=============================================================================
function advanced_mail_reroute_settings_form() {
drupal_add_js(drupal_get_path('module', 'advanced_mail_reroute') .'/advanced_mail_reroute.js');
$override_value_to_index = array(
ADVANCED_MAIL_REROUTE_OVERRIDE_DISABLED => 0,
ADVANCED_MAIL_REROUTE_ALLOW => 1,
ADVANCED_MAIL_REROUTE_BLOCK => 2,
ADVANCED_MAIL_REROUTE_REROUTE => 3
);
$override_value = variable_get('advanced_mail_reroute_override', ADVANCED_MAIL_REROUTE_OVERRIDE_DISABLED);
$form['#tree'] = TRUE;
$form['main_settings'] = array(
'#type' => 'fieldset',
'#title' => t('Main settings'),
'#collapsible' => TRUE
);
$form['main_settings']['override'] = array(
'#type' => 'select',
'#title' => t('Override rule'),
'#options' => array(t('- Disabled -'), t('Allow all emails through'), t('Block all emails'), t('Redirect all emails to primary email address')),
'#default_value' => $override_value_to_index[$override_value],
'#description' => t('This will override any settings in the Mailkey rules table')
);
$form['main_settings']['primary_email'] = array(
'#type' => 'textfield',
'#title' => t('Primary E-mail'),
'#default_value' => variable_get('advanced_mail_reroute_primary_email', variable_get('site_mail', '')),
'#description' => t('By default all E-mails will be sent to this address'),
);
$form['main_settings']['enable_logging'] = array(
'#type' => 'checkbox',
'#title' => t('Enable logging'),
'#default_value' => variable_get('advanced_mail_reroute_enable_logging', TRUE),
);
$mailkey_rules_description = t('This table is automatically updated as new mailkeys are discovered.');
if (variable_get('advanced_mail_reroute_override', ADVANCED_MAIL_REROUTE_OVERRIDE_DISABLED) != ADVANCED_MAIL_REROUTE_OVERRIDE_DISABLED) {
$mailkey_rules_description .= ' <div style="color: red; display: block;">'. t('Override is currently enabled. All rules below will be ignored.') .'</div>';
}
$form['mailkey_rules'] = array(
'#type' => 'fieldset',
'#title' => t('Reroute rules'),
'#collapsible' => TRUE,
'#description' => $mailkey_rules_description,
);
$form['mailkey_rules']['default_rule'] = array(
'#type' => 'select',
'#title' => t('Default rule for new mailkeys'),
'#options' => array('Allow', 'Block', 'Reroute'),
'#default_value' => variable_get('advanced_mail_reroute_default_rule', ADVANCED_MAIL_REROUTE_DEFAULT_RULE),
);
$form['mailkey_rules']['table'] = array(
'#type' => 'advanced_mail_reroute_table'
);
$form['new_rule'] = array(
'#type' => 'fieldset',
'#title' => t('New rule'),
'#collapsible' => TRUE,
'#collapsed' => TRUE
);
$form['new_rule']['mailkey'] = array(
'#type' => 'textfield',
'#title' => t('Mailkey')
);
$form['new_rule']['rule'] = array(
'#type' => 'select',
'#title' => t('Rule'),
'#options' => array('Allow', 'Block', 'Reroute'),
'#default_value' => variable_get('advanced_mail_reroute_default_rule', ADVANCED_MAIL_REROUTE_DEFAULT_RULE),
'#attributes' => array('class' => 'advanced-mail-reroute-rule')
);
$form['new_rule']['email'] = array(
'#type' => 'textfield',
'#title' => t('E-mail'),
'#description' => t("When reroute is selected you may enter an optional email if"),
'#attributes' => array('class' => 'advanced-mail-reroute-email')
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Save'),
);
return $form;
}
function advanced_mail_reroute_settings_form_validate($form_id, $form_values) {
if (!valid_email_address($form_values['values']['main_settings']['primary_email'])) {
form_set_error('main_settings][primary_email', t('Invalid email entered for primary email'));
}
$new_rule = $form_values['values']['new_rule'];
if (!empty($new_rule['mailkey']) && $new_rule['rule'] == ADVANCED_MAIL_REROUTE_REROUTE &&
!empty($new_rule['email']) && !valid_email_address($new_rule['email'])) {
form_set_error('new_rule][email', t('Invalid email entered for new mailkey rule'));
}
}
function advanced_mail_reroute_settings_form_submit($form_id, $form_state) {
$override_index_to_value = array(
0 => ADVANCED_MAIL_REROUTE_OVERRIDE_DISABLED,
1 => ADVANCED_MAIL_REROUTE_ALLOW,
2 => ADVANCED_MAIL_REROUTE_BLOCK,
3 => ADVANCED_MAIL_REROUTE_REROUTE
);
$override_index = $form_state['values']['main_settings']['override'];
variable_set('advanced_mail_reroute_override', $override_index_to_value[$override_index]);
variable_set('advanced_mail_reroute_primary_email', $form_state['values']['main_settings']['primary_email']);
variable_set('advanced_mail_reroute_enable_logging', $form_state['values']['main_settings']['enable_logging']);
variable_set('advanced_mail_reroute_default_rule', $form_state['values']['mailkey_rules']['default_rule']);
$new_rule = $form_state['values']['new_rule'];
if (!empty($new_rule['mailkey'])) {
$email = ($new_rule['rule'] == ADVANCED_MAIL_REROUTE_REROUTE ? $new_rule['email'] : '');
db_query("INSERT INTO {advanced_mail_reroute_rules} (mailkey, reroute_rule, email) VALUES ('%s', %d, '%s')",
$new_rule['mailkey'], $new_rule['rule'], $new_rule['email']);
}
$table = $form_state['values']['mailkey_rules']['table']['mailkeys'];
if (!empty($table)) {
foreach ($table as $key => $value) {
if ($value['remove']) {
db_query("DELETE FROM {advanced_mail_reroute_rules} WHERE mailkey ='%s'", $value['mailkey']);
}
else {
$email = ($value['rule'] == ADVANCED_MAIL_REROUTE_REROUTE ? $value['email'] : '');
db_query("UPDATE {advanced_mail_reroute_rules} SET reroute_rule = %d, email = '%s' WHERE mailkey = '%s'", $value['rule'], $email, $value['mailkey']);
}
}
}
}
function advanced_mail_reroute_table_process($element, $form_state) {
$result = db_query('SELECT mailkey, reroute_rule, email FROM {advanced_mail_reroute_rules}');
while ($row = db_fetch_object($result)) {
$element['mailkeys']['M'. $row->mailkey] = array( // prefix with 'M' so mailkeys which begin with a '#' won't get discarded in the theme function
'mailkey' => array(
'#type' => 'hidden',
'#value' => $row->mailkey,
),
'rule' => array(
'#type' => 'select',
'#options' => array('Allow', 'Block', 'Reroute'),
'#default_value' => $row->reroute_rule,
'#attributes' => array('class' => 'advanced-mail-reroute-rule')
),
'email' => array(
'#type' => 'textfield',
'#default_value' => $row->email,
'#size' => 25,
'#attributes' => array('class' => 'advanced-mail-reroute-email')
),
'remove' => array(
'#type' => 'checkbox',
'#default_value' => 0
)
);
}
return $element;
}
function advanced_mail_reroute_table_validate($element, $form_state) {
if (!empty($form_state['values']['new_rule']['mailkey'])) {
$num_rows = db_result(db_query("SELECT COUNT(*) FROM {advanced_mail_reroute_rules} WHERE mailkey='%s'", $form_state['values']['new_rule']['mailkey']));
if ($num_rows > 0) {
form_set_error('new_rule][mailkey', t('Mailkey for new rule already exists'));
}
}
if (!empty($form_state['values']['mailkey_rules']['table']['mailkeys'])) {
foreach ($form_state['values']['mailkey_rules']['table']['mailkeys'] as $key => $value) {
if ($value['rule'] == ADVANCED_MAIL_REROUTE_REROUTE &&
!empty($value['email']) &&
!$value['remove'] &&
!valid_email_address($value['email'])) {
form_set_error($key, t("Invalid email entered for mailkey rule '". substr($key, 1) ."'"));
}
}
}
}
function theme_advanced_mail_reroute_table($element) {
if (isset($element['mailkeys'])) {
foreach ($element['mailkeys'] as $mailkey => $row) {
if ($mailkey[0] == 'M') { // drupal inserts a lot of other stuff beginning with '#' so we want to skip past that.
$rows[] = array(
$row['mailkey']['#value'],
theme('select', $row['rule']),
theme('textfield', $row['email']),
theme('checkbox', $row['remove'])
);
}
}
}
if (!isset($rows)) {
$rows[] = array(array('data' => t('No rules available.'), 'colspan' => 5));
}
$header = array(t('Mailkey'), t('Rule'), t('E-mail'), t('Remove'));
$output = theme('table', $header, $rows);
return $output;
}
/*
function advanced_mail_reroute_log() {
$actions = array('Allowed', 'Blocked', 'Rerouted');
$header = array(
array('data' => t('Time'), 'field' => 'timestamp', 'sort' => 'desc'),
array('data' => t('Mailkey'), 'field' => 'mailkey'),
array('data' => t('From'), 'field' => 'from_email'),
array('data' => t('To'), 'field' => 'to_email'),
array('data' => t('Action'), 'field' => 'reroute_rule')
);
$result = pager_query("SELECT timestamp, mailkey, from_email, to_email, reroute_rule, reroute_email FROM {advanced_mail_reroute_log}"
. tablesort_sql($header), 30, 0, "SELECT COUNT(id) FROM {advanced_mail_reroute_log}");
while ($row = db_fetch_object($result)) {
$action_taken = $actions[$row->reroute_rule];
if ($row->reroute_rule == ADVANCED_MAIL_REROUTE_REROUTE) {
$action_taken .= ' to '. $row->reroute_email;
}
$rows[] = array(date('Y-m-d H:i:s', $row->timestamp), $row->mailkey, $row->from_email, $row->to_email, $action_taken);
}
if (!$rows) {
$rows[] = array(array('data' => t('No log messages available.'), 'colspan' => 5));
}
$content = theme('table', $header, $rows);
$content .= theme('pager', NULL, 30, 0);
return $content;
}*/
function advanced_mail_reroute_log() {
//if ($node = node_load(arg(1))) {
$header = array(
array('data' => t('Time'), 'field' => 'timestamp', 'sort' => 'desc'),
array('data' => t('Mailkey'), 'field' => 'mailkey'),
array('data' => t('From'), 'field' => 'from_email'),
array('data' => t('To'), 'field' => 'to_email'),
array('data' => t('Action'), 'field' => 'reroute_rule')
);
$query = db_select('advanced_mail_reroute_log', 'amr', array('target' => 'slave'))->extend('PagerDefault')->extend('TableSort');
$query
->fields('amr', array('timestamp', 'mailkey', 'from_email', 'to_email', 'reroute_rule', 'reroute_email'))
->limit(30)
->orderByHeader($header);
$result = $query->execute();
$rows = array();
foreach ($result as $log) {
$rows[] = array(
array('data' => format_date($log->timestamp, 'short'), 'class' => array('nowrap')),
);
}
//drupal_set_title($node->title);
$build['advanced_mail_reroute_table'] = array(
'#theme' => 'table',
'#header' => $header,
'#rows' => $rows,
'#empty' => t('No logs available.'),
);
$build['advanced_mail_reroute_pager'] = array('#theme' => 'pager');
return $build;
//}
//else {
// drupal_not_found();
//}
}
//=============================================================================
// Private Functions
//=============================================================================
function _advanced_mail_reroute_log($mailkey, $from, $to, $reroute_rule, $reroute_email = '') {
db_query("INSERT INTO {advanced_mail_reroute_log} (timestamp, mailkey, from_email, to_email, reroute_rule, reroute_email)
VALUES (%d, '%s', '%s', '%s', %d, '%s')", time(), $mailkey, $from, $to, $reroute_rule, $reroute_email);
}
function _advanced_mail_reroute_reroute(&$message, $reroute_to) {
$message['body'] = is_array($message['body']) ? implode("\n\n", $message['body']) : $message['body'];
global $base_url;
$message['subject'] = 'Rerouted email: '. $message['subject'];
// Format copied from reroute_email module: http://drupal.org/project/reroute_email
$body_header = "This email was rerouted.\n";
$body_header .= "Web site: @site\n";
$body_header .= "Mail key: @key\n";
$body_header .= "Originally to: <@to>\n";
$body_header .= "-----------------------\n";
$body_header = t($body_header, array('@site' => $base_url, '@to' => $message['to'], '@key' => $message['id']));
$message['body'] = $body_header . (is_array($message['body']) ? implode("\n\n", $message['body']) : $message['body']);
$message['to'] = $reroute_to;
}

View File

@@ -0,0 +1,74 @@
<h3>Change Log Summary</h3>
<p>-- <em>(Note: See the <a href="http://drupalcode.org/project/htmlmail.git/log/refs/heads/7.x-2.x">git repository</a> for full version)</em></p>
<dl>
<dt>November 4, 2008</dt>
<dd>
<p><a href="http://drupalcode.org/project/htmlmail.git/commit/64a69aff375ffe42d311963d748866085281896e">HTML Mail created</a> by <a href="http://drupal.org/user/1171">Chris Herberte</a>.</p>
</dd>
<dt>July 19, 2009</dt>
<dd>
<p><a href="http://drupal.org/node/524718">5.x-1.1</a> released <em>(288 <a href="http://www.dwheeler.com/sloccount/">SLOC</a>, 60k)</em> as an improved replacement for <a href="http://api.drupal.org/api/drupal/includes--common.inc/function/drupal_mail/5"><code>drupal_mail()</code></a>.</p>
</dd>
<dt>February 26, 2010</dt>
<dd>
<p><a href="http://drupal.org/node/726398">6.x-1.3</a> released <em>(348 <a href="http://www.dwheeler.com/sloccount/">SLOC</a>, 108k)</em> and eventually installed by <a href="http://drupal.org/project/usage/726398">over 5,000 sites</a>.</p>
</dd>
<dt>January 1, 2011</dt>
<dd>
<p><a href="http://drupal.org/node/1012246">Patches to improve theming</a> of the <a href="http://drupal.org/node/355250">7.x-1.x-dev version</a> submitted by <a href="http://drupal.org/user/36148">Bob Vincent (pillarsdotnet)</a>.</p>
</dd>
<dt>March 10, 2011</dt>
<dd>
<p><a href="http://drupalcode.org/project/mailsystem.git/commit/5cc8201c5e48b56efecc139c9a51dd49775aebaf">Mail System created</a> to allow mail-sending modules to cooperate and co-exist.</p>
</dd>
<dt>March 11, 2011</dt>
<dd>
<p><a href="http://drupal.org/user/36148">Bob Vincent</a> granted co-maintainer access.</p>
</dd>
<dd>
<p><a href="http://drupal.org/node/1088882">7.x-1.1</a> released <em>(589 <a href="http://www.dwheeler.com/sloccount/">SLOC</a>, 76k)</em>.</p>
</dd>
<dt>March 16, 2011</dt>
<dd>
<p><a href="http://drupalcode.org/project/htmlmail.git/commit/a0083eea7b575a702d3aecdd0578378277c7c8d4">Emogrifier separated</a> into <a href="http://drupal.org/project/emogrifier">its own module</a>.</p>
</dd>
<dt>March 18, 2011</dt>
<dd>
<p><a href="http://drupalcode.org/project/htmlmail.git/commit/6c0463849493f8b528be8d4099ab0c6fbc976fe2">7.x-2.x</a> and <a href="http://drupalcode.org/project/htmlmail.git/commit/a4b36c6de5f241ceccd0aeea4599d35de066fa9c">6.x-2.x</a> branches created.</p>
</dd>
<dt>March 20, 2011</dt>
<dd>
<p><a href="http://drupalcode.org/project/htmlmail.git/commit/f481fc8997a1345e9490e80043c616a5805d6e44">Echo separated</a> into <a href="http://drupal.org/project/echo">its own module</a>.</p>
</dd>
<dt>March 25, 2011</dt>
<dd>
<p><a href="http://drupalcode.org/project/htmlmail.git/commit/0ebec6e83e688b6d51e35554618727dc3133c970">MIME capability added</a>.</p>
</dd>
<dt>March 26, 2011</dt>
<dd>
<p><a href="http://drupalcode.org/project/htmlmail.git/commit/104e8916c9ba92486a227786b6781cac38e60905">MIME support delegated</a> to <a href="http://pear.php.net">PEAR</a> <a href="http://pear.php.net/manual/en/package.mail.mail-mimepart.mail-mimepart.php">Mail_mimePart</a> and <a href="http://pear.php.net/manual/en/package.mail.mail-mimedecode.php">Mail_mimeDecode</a> classes.</p>
</dd>
<dt>April 3, 2011</dt>
<dd>
<p><a href="http://drupalcode.org/project/htmlmail.git/commit/a17e7996d0d119012205cf47195064848e59d937">Mime support separated</a> into <a href="http://drupal.org/project/mailmime">its own module</a>.</p>
</dd>
<dt>April 4, 2011</dt>
<dd>
<p><a href="http://drupalcode.org/project/htmlmail.git/commit/f419fb3cf18276cca9f0d3dbc2c80e6e6a6bbda9">7.x-2.x changes backported to 6.x-2.x</a>.</p>
</dd>
<dt>April 6, 2011</dt>
<dd>
<p><a href="http://drupal.org/node/1118032">6.x-2.4</a> <em>(420 <a href="http://www.dwheeler.com/sloccount">SLOC</a>, 92k)</em> and <a href="http://drupal.org/node/1118034">7.x-2.4</a> <em>(414 <a href="http://www.dwheeler.com/sloccount">SLOC</a>, 92k)</em> released.</p>
</dd>
<dd>
<p><a href="http://drupal.org/project/htmlmail">Project page</a> updated to remove support for <a href="http://drupalcode.org/project/htmlmail.git/shortlog/refs/heads/6.x-1.x">6.x-1.x</a> and <a href="http://drupalcode.org/project/htmlmail.git/shortlog/refs/heads/7.x-1.x">7.x-1.x</a> branches.</p>
</dd>
<dt>April 7, 2011</dt>
<dd>
<p>Dependency on <a href="http://drupal.org/project/echo">Echo</a> and <a href="http://drupal.org/project/mailmime">Mail MIME</a> modules <a href="http://drupalcode.org/project/htmlmail.git/commit/20abfd24ad0006c9312fd8f0a5edcdaed8e5920b">removed</a>.</p>
</dd>
<dt>April 9, 2011</dt>
<dd>
<p><a href="http://drupalcode.org/project/htmlmail.git/commit/dd53d4f28cdbe893bb32e31a03ceba34c5240402">Autodetection of template files</a> in both module and selected theme directories.</p>
</dd>
</dl>

View File

@@ -0,0 +1,71 @@
### Change Log Summary
-- *(Note: See the [git repository](http://drupalcode.org/project/htmlmail.git/log/refs/heads/7.x-2.x) for full version)*
November 4, 2008
: [HTML Mail created](http://drupalcode.org/project/htmlmail.git/commit/64a69aff375ffe42d311963d748866085281896e) by [Chris Herberte](http://drupal.org/user/1171).
July 19, 2009
: [5.x-1.1](http://drupal.org/node/524718) released *(288 [SLOC](http://www.dwheeler.com/sloccount/), 60k)* as an improved replacement for [`drupal_mail()`](http://api.drupal.org/api/drupal/includes--common.inc/function/drupal_mail/5).
February 26, 2010
: [6.x-1.3](http://drupal.org/node/726398) released *(348 [SLOC](http://www.dwheeler.com/sloccount/), 108k)* and eventually installed by [over 5,000 sites](http://drupal.org/project/usage/726398).
January 1, 2011
: [Patches to improve theming](http://drupal.org/node/1012246) of the [7.x-1.x-dev version](http://drupal.org/node/355250) submitted by [Bob Vincent (pillarsdotnet)](http://drupal.org/user/36148).
March 10, 2011
: [Mail System created](http://drupalcode.org/project/mailsystem.git/commit/5cc8201c5e48b56efecc139c9a51dd49775aebaf) to allow mail-sending modules to cooperate and co-exist.
March 11, 2011
: [Bob Vincent](http://drupal.org/user/36148) granted co-maintainer access.
: [7.x-1.1](http://drupal.org/node/1088882) released *(589 [SLOC](http://www.dwheeler.com/sloccount/), 76k)*.
March 16, 2011
: [Emogrifier separated](http://drupalcode.org/project/htmlmail.git/commit/a0083eea7b575a702d3aecdd0578378277c7c8d4) into [its own module](http://drupal.org/project/emogrifier).
March 18, 2011
: [7.x-2.x](http://drupalcode.org/project/htmlmail.git/commit/6c0463849493f8b528be8d4099ab0c6fbc976fe2) and [6.x-2.x](http://drupalcode.org/project/htmlmail.git/commit/a4b36c6de5f241ceccd0aeea4599d35de066fa9c) branches created.
March 20, 2011
: [Echo separated](http://drupalcode.org/project/htmlmail.git/commit/f481fc8997a1345e9490e80043c616a5805d6e44) into [its own module](http://drupal.org/project/echo).
March 25, 2011
: [MIME capability added](http://drupalcode.org/project/htmlmail.git/commit/0ebec6e83e688b6d51e35554618727dc3133c970).
March 26, 2011
: [MIME support delegated](http://drupalcode.org/project/htmlmail.git/commit/104e8916c9ba92486a227786b6781cac38e60905) to [PEAR](http://pear.php.net) [Mail_mimePart](http://pear.php.net/manual/en/package.mail.mail-mimepart.mail-mimepart.php) and [Mail_mimeDecode](http://pear.php.net/manual/en/package.mail.mail-mimedecode.php) classes.
April 3, 2011
: [Mime support separated](http://drupalcode.org/project/htmlmail.git/commit/a17e7996d0d119012205cf47195064848e59d937) into [its own module](http://drupal.org/project/mailmime).
April 4, 2011
: [7.x-2.x changes backported to 6.x-2.x](http://drupalcode.org/project/htmlmail.git/commit/f419fb3cf18276cca9f0d3dbc2c80e6e6a6bbda9).
April 6, 2011
: [6.x-2.4](http://drupal.org/node/1118032) *(420 [SLOC](http://www.dwheeler.com/sloccount), 92k)* and [7.x-2.4](http://drupal.org/node/1118034) *(414 [SLOC](http://www.dwheeler.com/sloccount), 92k)* released.
: [Project page](http://drupal.org/project/htmlmail) updated to remove support for [6.x-1.x](http://drupalcode.org/project/htmlmail.git/shortlog/refs/heads/6.x-1.x) and [7.x-1.x](http://drupalcode.org/project/htmlmail.git/shortlog/refs/heads/7.x-1.x) branches.
April 7, 2011
: Dependency on [Echo](http://drupal.org/project/echo) and [Mail MIME](http://drupal.org/project/mailmime) modules [removed](http://drupalcode.org/project/htmlmail.git/commit/20abfd24ad0006c9312fd8f0a5edcdaed8e5920b).
April 9, 2011
: [Autodetection of template files](http://drupalcode.org/project/htmlmail.git/commit/dd53d4f28cdbe893bb32e31a03ceba34c5240402) in both module and selected theme directories.

View File

@@ -0,0 +1,107 @@
Change Log Summary
-- (Note: See the [1]git repository for full version)
November 4, 2008
[2]HTML Mail created by [3]Chris Herberte.
July 19, 2009
[4]5.x-1.1 released (288 [5]SLOC, 60k) as an improved
replacement for [6]drupal_mail().
February 26, 2010
[7]6.x-1.3 released (348 [8]SLOC, 108k) and eventually installed
by [9]over 5,000 sites.
January 1, 2011
[10]Patches to improve theming of the [11]7.x-1.x-dev version
submitted by [12]Bob Vincent (pillarsdotnet).
March 10, 2011
[13]Mail System created to allow mail-sending modules to
cooperate and co-exist.
March 11, 2011
[14]Bob Vincent granted co-maintainer access.
[15]7.x-1.1 released (589 [16]SLOC, 76k).
March 16, 2011
[17]Emogrifier separated into [18]its own module.
March 18, 2011
[19]7.x-2.x and [20]6.x-2.x branches created.
March 20, 2011
[21]Echo separated into [22]its own module.
March 25, 2011
[23]MIME capability added.
March 26, 2011
[24]MIME support delegated to [25]PEAR [26]Mail_mimePart and
[27]Mail_mimeDecode classes.
April 3, 2011
[28]Mime support separated into [29]its own module.
April 4, 2011
[30]7.x-2.x changes backported to 6.x-2.x.
April 6, 2011
[31]6.x-2.4 (420 [32]SLOC, 92k) and [33]7.x-2.4 (414 [34]SLOC,
92k) released.
[35]Project page updated to remove support for [36]6.x-1.x and
[37]7.x-1.x branches.
April 7, 2011
Dependency on [38]Echo and [39]Mail MIME modules [40]removed.
April 9, 2011
[41]Autodetection of template files in both module and selected
theme directories.
References
1. http://drupalcode.org/project/htmlmail.git/log/refs/heads/7.x-2.x
2. http://drupalcode.org/project/htmlmail.git/commit/64a69aff375ffe42d311963d748866085281896e
3. http://drupal.org/user/1171
4. http://drupal.org/node/524718
5. http://www.dwheeler.com/sloccount/
6. http://api.drupal.org/api/drupal/includes--common.inc/function/drupal_mail/5
7. http://drupal.org/node/726398
8. http://www.dwheeler.com/sloccount/
9. http://drupal.org/project/usage/726398
10. http://drupal.org/node/1012246
11. http://drupal.org/node/355250
12. http://drupal.org/user/36148
13. http://drupalcode.org/project/mailsystem.git/commit/5cc8201c5e48b56efecc139c9a51dd49775aebaf
14. http://drupal.org/user/36148
15. http://drupal.org/node/1088882
16. http://www.dwheeler.com/sloccount/
17. http://drupalcode.org/project/htmlmail.git/commit/a0083eea7b575a702d3aecdd0578378277c7c8d4
18. http://drupal.org/project/emogrifier
19. http://drupalcode.org/project/htmlmail.git/commit/6c0463849493f8b528be8d4099ab0c6fbc976fe2
20. http://drupalcode.org/project/htmlmail.git/commit/a4b36c6de5f241ceccd0aeea4599d35de066fa9c
21. http://drupalcode.org/project/htmlmail.git/commit/f481fc8997a1345e9490e80043c616a5805d6e44
22. http://drupal.org/project/echo
23. http://drupalcode.org/project/htmlmail.git/commit/0ebec6e83e688b6d51e35554618727dc3133c970
24. http://drupalcode.org/project/htmlmail.git/commit/104e8916c9ba92486a227786b6781cac38e60905
25. http://pear.php.net/
26. http://pear.php.net/manual/en/package.mail.mail-mimepart.mail-mimepart.php
27. http://pear.php.net/manual/en/package.mail.mail-mimedecode.php
28. http://drupalcode.org/project/htmlmail.git/commit/a17e7996d0d119012205cf47195064848e59d937
29. http://drupal.org/project/mailmime
30. http://drupalcode.org/project/htmlmail.git/commit/f419fb3cf18276cca9f0d3dbc2c80e6e6a6bbda9
31. http://drupal.org/node/1118032
32. http://www.dwheeler.com/sloccount
33. http://drupal.org/node/1118034
34. http://www.dwheeler.com/sloccount
35. http://drupal.org/project/htmlmail
36. http://drupalcode.org/project/htmlmail.git/shortlog/refs/heads/6.x-1.x
37. http://drupalcode.org/project/htmlmail.git/shortlog/refs/heads/7.x-1.x
38. http://drupal.org/project/echo
39. http://drupal.org/project/mailmime
40. http://drupalcode.org/project/htmlmail.git/commit/20abfd24ad0006c9312fd8f0a5edcdaed8e5920b
41. http://drupalcode.org/project/htmlmail.git/commit/dd53d4f28cdbe893bb32e31a03ceba34c5240402

View File

@@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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.
<signature of Ty Coon>, 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.

View File

@@ -0,0 +1,183 @@
<h2><a href="http://drupal.org/project/htmlmail">HTML Mail</a></h2>
<p>Lets you theme your messages the same way you theme the rest of your website.</p>
<h3><a href="http://www.dict.org/bin/Dict?Form=Dict2&amp;Database=*&amp;Query=requirement">Requirement</a></h3>
<ul>
<li><a href="http://drupal.org/project/mailsystem">Mail System 7.x-2.x</a></li>
</ul>
<h3><a href="http://drupal.org/documentation/install/modules-themes/modules-7">Installation</a></h3>
<p>The following additional modules, while not required, are highly recommended:</p>
<ul>
<li>
<dl>
<dt><a href="http://drupal.org/project/echo">Echo</a></dt>
<dd>Wraps your messages in a drupal theme. Now you can "brand" your messages with the same logo, header, fonts, and styles as your website.</dd>
</dl>
</li>
<li>
<dl>
<dt><a href="http://drupal.org/project/emogrifier">Emogrifier</a></dt>
<dd>Converts stylesheets to inline style rules, for consistent display on mobile devices and webmail.</dd>
</dl>
</li>
<li>
<dl>
<dt><a href="http://drupal.org/project/mailmime">Mail MIME</a></dt>
<dd>Provides a text/plain alternative to text/html emails, and automatically converts image references to inline image attachments.</dd>
</dl>
</li>
<li>
<dl>
<dt><a href="http://drupal.org/project/pathologic">Pathologic</a></dt>
<dd>Converts urls from relative to absolute, so clickable links in your email messages work as intended.</dd>
</dl>
</li>
<li>
<dl>
<dt><a href="http://drupal.org/project/filter_transliteration">Transliteration</a></dt>
<dd>
<p>Converts non-ASCII characters to their US-ASCII equivalents, such as from Microsoft "smart-quotes" to regular quotes.</p>
</dd>
<dd>
<p><em>Also available as a <a href="http://drupal.org/node/1095278#comment-4219530">patch</a>.</em></p>
</dd>
</dl>
</li>
</ul>
<h3><a href="http://drupal.org/node/250790">Updating from previous versions</a></h3>
<p>The <a href="http://drupal.org/node/1106064">7.x-2.x</a> branch shares 94% of its code with the <a href="http://drupal.org/node/1119548">6.x-2.x</a> branch, but only 15% of its code with the <a href="http://drupal.org/node/355250">7.x-1.x</a> branch, and a tiny 8% of its code with the <a href="http://drupal.org/node/329828">6.x-1.x</a> branch.</p>
<p>Let your compatibility expectations be adjusted accordingly.</p>
<ul>
<li>
<p>Check the module dependencies, as they have changed. The latest version of <a href="http://drupal.org/project/htmlmail">HTML Mail</a> depends on the <a href="http://drupal.org/project/mailsystem">Mail System</a> module (7.x-2.2 or later) and will not work without it.</p>
</li>
<li>
<p>Run <code>update.php</code> <em>immediately</em> after uploading new code.</p>
</li>
<li>
<p>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 <a href="http://drupal.org/project/echo">Echo</a> module.</p>
</li>
<li>
<p>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 <u>Instructions</u> link) and as comments within the <code>htmlmail.tpl.php</code> file itself.</p>
</li>
<li>
<p>The following options have been removed from the module settings page. In their place, any combination of <a href="http://drupal.org/project/modules/?filters=type%3Aproject_project%20tid%3A63%20hash%3A1hbejm%20-bs_project_sandbox%3A1%20bs_project_has_releases%3A1">over 200 filter modules</a> may be used to create an email-specific <a href="http://drupal.org/node/778976">text format</a> for post-template filtering.</p>
<ul>
<li><a href="http://api.drupal.org/api/drupal/modules--filter--filter.module/function/_filter_autop/7">Line break converter</a></li>
<li><a href="http://api.drupal.org/api/drupal/modules--filter--filter.module/function/_filter_url/7">URL Filter</a></li>
<li><a href="http://drupal.org/project/rel_to_abs">Relative Path to Absolute URLs</a></li>
<li><a href="http://www.pelagodesign.com/sidecar/emogrifier/">Emogrifier</a></li>
<li><a href="http://drupal.org/project/token">Token support</a></li>
</ul>
</li>
<li>
<p>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 <a href="http://drupal.org/project/mailmime">Mail MIME</a> module.</p>
</li>
</ul>
<h3><a href="http://drupal.org/files/images/htmlmail_settings_2.thumbnail.png">Configuration</a></h3>
<p>Visit the <a href="http://drupal.org/project/mailsystem">Mail System</a> settings page at <u>admin/config/system/mailsystem</u> to select which parts of Drupal will use <a href="http://drupal.org/project/htmlmail">HTML Mail</a> instead of the <a href="http://api.drupal.org/api/drupal/modules--system--system.mail.inc/class/DefaultMailSystem/7">default</a> <a href="http://api.drupal.org/api/drupal/includes--mail.inc/function/drupal_mail_system/7">mail system</a>.</p>
<p>Visit the <a href="http://drupal.org/project/htmlmail">HTML Mail</a> settings page at <u>admin/config/system/htmlmail</u> to select a theme and post-filter for your messages.</p>
<h3><a href="http://drupal.org/documentation/theme">Theming</a></h3>
<p>The email message text goes through three transformations before sending:</p>
<ol style="list-style-type: decimal;">
<li>
<h3>Template File</h3>
<p>A template file is applied to your message header, subject, and body text. The default template is the included <code>htmlmail.tpl.php</code> file. You may copy this file to your <cite>email theme</cite> 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.</p>
</li>
<li>
<h3>Theming</h3>
<p>You may choose a theme that will hold your templates from Step 1 above. If the <a href="http://drupal.org/project/echo">Echo</a> module is installed, this theme will also be used to wrap your templated text in a webpage. You use any one of <a href="http://drupal.org/project/themes">over 800</a> themes to style your messages, or <a href="http://drupal.org/documentation/theme">create your own</a> for even more power and flexibility.</p>
</li>
<li>
<h3>Post-filtering</h3>
<p>You may choose a <a href="http://drupal.org/node/778976">text format</a> to be used for filtering email messages <em>after</em> theming. This allows you to use any combination of <a href="http://drupal.org/project/modules/?filters=type%3Aproject_project%20tid%3A63%20hash%3A1hbejm%20-bs_project_sandbox%3A1%20bs_project_has_releases%3A1">over 200 filter modules</a> to make final changes to your message before sending.</p>
<p>Here is a recommended configuration:</p>
<ul>
<li>
<p><a href="http://drupal.org/project/emogrifier">Emogrifier</a> Converts stylesheets to inline style rules for consistent display on mobile devices and webmail.</p>
</li>
<li>
<p><a href="http://drupal.org/project/filter_transliteration">Transliteration</a> Converts non-ASCII text to US-ASCII equivalents. This helps prevent Microsoft "smart-quotes" from appearing as question-marks in Mozilla Thunderbird.</p>
</li>
<li>
<p><a href="http://drupal.org/project/pathologic">Pathologic</a> Converts relative URLS to absolute URLS so that clickable links in your message will work as intended.</p>
</li>
</ul>
</li>
</ol>
<h3>Troubleshooting</h3>
<ul>
<li>
<p>Double-check the <a href="http://drupal.org/project/mailsystem">Mail System</a> module settings and and make sure you selected <u><code>HTMLMailSystem</code></u> for your <u>Site-wide default mail system</u>.</p>
</li>
<li>
<p>Try selecting the <u><code>[ ]</code> <em>(Optional)</em> Debug</u> checkbox at the <a href="http://drupal.org/project/htmlmail">HTML Mail</a> module settings page and re-sending your message.</p>
</li>
<li>
<p>Clear your cache after changing any <u><code>.tpl.php</code></u> files.</p>
</li>
<li>
<p>If you use a post-filter, make sure your filter settings page looks like <a href="http://drupal.org/node/1130960">this</a>.</p>
</li>
<li>
<p>Visit the <a href="http://drupal.org/project/issues/htmlmail">issue queue</a> for support and feature requests.</p>
</li>
</ul>
<h3>Related Modules</h3>
<dl>
<dt><strong>Echo</strong></dt>
<dd>
<p>http://drupal.org/project/echo</p>
</dd>
<dt><strong>Emogrifier</strong></dt>
<dd>
<p>http://drupal.org/project/emogrifier</p>
</dd>
<dt><strong>HTML Purifier</strong></dt>
<dd>
<p>http://drupal.org/project/htmlpurifier</p>
</dd>
<dt><strong>htmLawed</strong></dt>
<dd>
<p>http://drupal.org/project/htmlawed</p>
</dd>
<dt><strong>Mail MIME</strong></dt>
<dd>
<p>http://drupal.org/project/mailmime</p>
</dd>
<dt><strong>Mail System</strong></dt>
<dd>
<p>http://drupal.org/project/mailsystem</p>
</dd>
<dt><strong>Pathologic</strong></dt>
<dd>
<p>http://drupal.org/project/pathologic</p>
</dd>
<dt><strong>Transliteration</strong></dt>
<dd>
<p>http://drupal.org/project/transliteration</p>
</dd>
</dl>
<h3><a href="http://drupal.org/project/documentation">Documentation</a></h3>
<dl>
<dt><strong><a href="http://api.drupal.org/api/drupal/modules--filter--filter.module/6">filter.module</a></strong></dt>
<dd><a href="http://api.drupal.org/api/drupal/modules--filter--filter.module/7">api.drupal.org/api/drupal/modules--filter--filter.module</a></dd>
<dd>
<p><a href="http://api.drupal.org/api/drupal/modules--filter--filter.module/group/standard_filters/7">api.drupal.org/api/drupal/modules--filter--filter.module/group/standard_filters/7</a></p>
</dd>
<dt><strong><a href="http://drupal.org/documentation/install/modules-themes/modules-7">Installing contributed modules</a></strong></dt>
<dd>
<p><a href="http://drupal.org/documentation/install/modules-themes/modules-7">drupal.org/documentation/install/modules-themes/modules-7</a></p>
</dd>
<dt><strong><a href="http://drupal.org/documentation/theme">Theming guide</a></strong></dt>
<dd>
<p><a href="http://drupal.org/documentation/theme">drupal.org/documentation/theme</a></p>
</dd>
</dl>
<h3>Original Author</h3>
<ul>
<li><a href="http://drupal.org/user/1171">Chris Herberte</a></li>
</ul>
<h3>Current Maintainer</h3>
<ul>
<li><a href="http://drupal.org/user/36148">Bob Vincent</a></li>
</ul>

View File

@@ -0,0 +1,216 @@
## [HTML Mail](http://drupal.org/project/htmlmail)
Lets you theme your messages the same way you theme the rest of your website.
### [Requirement](http://www.dict.org/bin/Dict?Form=Dict2&Database=*&Query=requirement)
* [Mail System 7.x-2.x](http://drupal.org/project/mailsystem)
### [Installation](http://drupal.org/documentation/install/modules-themes/modules-7)
The following additional modules, while not required, are highly recommended:
* [Echo](http://drupal.org/project/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.
* [Emogrifier](http://drupal.org/project/emogrifier)
: Converts stylesheets to inline style rules, for consistent display on
mobile devices and webmail.
* [Mail MIME](http://drupal.org/project/mailmime)
: Provides a text/plain alternative to text/html emails, and automatically
converts image references to inline image attachments.
* [Pathologic](http://drupal.org/project/pathologic)
: Converts urls from relative to absolute, so clickable links in your
email messages work as intended.
* [Transliteration](http://drupal.org/project/filter_transliteration)
: Converts non-ASCII characters to their US-ASCII equivalents, such
as from Microsoft "smart-quotes" to regular quotes.
: *Also available as a [patch](http://drupal.org/node/1095278#comment-4219530).*
### [Updating from previous versions](http://drupal.org/node/250790)
The [7.x-2.x](http://drupal.org/node/1106064) branch shares 94% of its code
with the [6.x-2.x](http://drupal.org/node/1119548) branch, but only 15% of
its code with the [7.x-1.x](http://drupal.org/node/355250) branch, and a tiny
8% of its code with the [6.x-1.x](http://drupal.org/node/329828) branch.
Let your compatibility expectations be adjusted accordingly.
* Check the module dependencies, as they have changed. The latest version of
[HTML Mail](http://drupal.org/project/htmlmail) depends on the
[Mail System](http://drupal.org/project/mailsystem) 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](http://drupal.org/project/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 <u>Instructions</u> 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](http://drupal.org/project/modules/?filters=type%3Aproject_project%20tid%3A63%20hash%3A1hbejm%20-bs_project_sandbox%3A1%20bs_project_has_releases%3A1)
may be used to create an email-specific
[text format](http://drupal.org/node/778976)
for post-template filtering.
* [Line break converter](http://api.drupal.org/api/drupal/modules--filter--filter.module/function/_filter_autop/7)
* [URL Filter](http://api.drupal.org/api/drupal/modules--filter--filter.module/function/_filter_url/7)
* [Relative Path to Absolute URLs](http://drupal.org/project/rel_to_abs)
* [Emogrifier](http://www.pelagodesign.com/sidecar/emogrifier/)
* [Token support](http://drupal.org/project/token)
* 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](http://drupal.org/project/mailmime) module.
### [Configuration](http://drupal.org/files/images/htmlmail_settings_2.thumbnail.png)
Visit the [Mail System](http://drupal.org/project/mailsystem) settings page at
<u>admin/config/system/mailsystem</u>
to select which parts of Drupal will use
[HTML Mail](http://drupal.org/project/htmlmail)
instead of the
[default](http://api.drupal.org/api/drupal/modules--system--system.mail.inc/class/DefaultMailSystem/7)
[mail system](http://api.drupal.org/api/drupal/includes--mail.inc/function/drupal_mail_system/7).
Visit the [HTML Mail](http://drupal.org/project/htmlmail) settings page at
<u>admin/config/system/htmlmail</u>
to select a theme and post-filter for your messages.
### [Theming](http://drupal.org/documentation/theme)
The email message text goes through three transformations before sending:
1. <h3>Template File</h3>
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 <cite>email theme</cite> 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. <h3>Theming</h3>
You may choose a theme that will hold your templates from Step 1 above. If
the [Echo](http://drupal.org/project/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](http://drupal.org/project/themes) themes to style your
messages, or [create your own](http://drupal.org/documentation/theme) for
even more power and flexibility.
3. <h3>Post-filtering</h3>
You may choose a
[text format](http://drupal.org/node/778976)
to be used for filtering email messages *after* theming.
This allows you to use any combination of
[over 200 filter modules](http://drupal.org/project/modules/?filters=type%3Aproject_project%20tid%3A63%20hash%3A1hbejm%20-bs_project_sandbox%3A1%20bs_project_has_releases%3A1)
to make final changes to your message before sending.
Here is a recommended configuration:
* [Emogrifier](http://drupal.org/project/emogrifier)
Converts stylesheets to inline style rules for consistent display on
mobile devices and webmail.
* [Transliteration](http://drupal.org/project/filter_transliteration)
Converts non-ASCII text to US-ASCII equivalents. This helps prevent
Microsoft "smart-quotes" from appearing as question-marks in
Mozilla Thunderbird.
* [Pathologic](http://drupal.org/project/pathologic)
Converts relative URLS to absolute URLS so that clickable links in
your message will work as intended.
### Troubleshooting
* Check the [online documentation](http://drupal.org/node/1124376),
especially the [screenshots](http://drupal.org/node/1124934).
* There is a special documentation page for
[Using HTML Mail together with SMTP Authentication Support](http://drupal.org/node/1200142).
* [Simplenews](http://drupal.org/project/simplenews) users attempting advanced
theming should read [this page](http://drupal.org/node/1260178).
* Double-check the [Mail System](http://drupal.org/project/mailsystem)
module settings and and make sure you selected
<u><code>HTMLMailSystem</code></u> for your
<u>Site-wide default mail system</u>.
* Try selecting the <u><code>[ ]</code> *(Optional)* Debug</u> checkbox
at the [HTML Mail](http://drupal.org/project/htmlmail) module
settings page and re-sending your message.
* Clear your cache after changing any <u><code>.tpl.php</code></u>
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)

View File

@@ -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

View File

@@ -0,0 +1,32 @@
<?php
/**
* @file
* Sample template for HTML Mail test messages.
*/
?>
<h1><a href="http://drupal.org/project/htmlmail">HTML Mail</a> test message</h1>
<div class="htmlmail-body">
<?php echo $body; ?>
</div>
<?php if ($debug): ?>
<hr />
<div class="htmlmail-debug">
<dl><dt><p>
To customize this test message:
</p></dt><dd><ol><li><p><?php if (empty($theme)): ?>
Visit <u>admin/config/system/htmlmail</u>
and select a theme to hold your custom email template files.
</p></dt><dd><ol><li><p><?php elseif (empty($theme_path)): ?>
Visit <u>admin/appearance</u>
to enable your selected <u><?php echo ucfirst($theme); ?></u> theme.
</p></dt><dd><ol><li><p><?php endif; ?>
Copy the
<a href="http://drupalcode.org/project/htmlmail.git/blob_plain/refs/heads/7.x-2.x:/htmlmail--htmlmail.tpl.php"><code>htmlmail--htmlmail.tpl.php</code></a>
file to your <u><?php echo ucfirst($theme); ?></u> theme directory
<u><code><?php echo $theme_path; ?></code></u>.
</p></li><li><p>
Edit the copied file.
</p></li></ol></dd></dl>
</div>
<?php endif;

View File

@@ -0,0 +1,144 @@
<?php
/**
* @file
* Sample template for sending Simplenews messages with HTML Mail.
*
* The following variables are available in this template:
*
* - $message_id: The email message id, or "simplenews_$key"
* - $module: The sending module, which is 'simplenews'.
* - $key: The simplenews action, which may be any of the following:
* - node: Send a newsletter to its subscribers.
* - subscribe: New subscriber confirmation message.
* - test: Send a test newsletter to the test address.
* - unsubscribe: Unsubscribe confirmation message.
* - $headers: An array of email (name => value) pairs.
* - $from: The configured sender address.
* - $to: The recipient subscriber email address.
* - $subject: The message subject line.
* - $body: The formatted message body.
* - $language: The language object for this message.
* - $params: An array containing the following keys:
* - context: An array containing the following keys:
* - account: The recipient subscriber account object, which contains
* the following useful properties:
* - snid: The simplenews subscriber id, or NULL for test messages.
* - name: The subscriber username, or NULL.
* - activated: The date this subscription became active, or NULL.
* - uid: The subscriber user id, or NULL.
* - mail: The subscriber email address; same as $message['to'].
* - language: The subscriber language code.
* - tids: An array of taxonomy term ids.
* - newsletter_subscription: An array of subscription ids.
* - node: The simplenews newsletter node object, which contains the
* following useful properties:
* - changed: The node last-modified date, as a unix timestamp.
* - created: The node creation date, as a unix timestamp.
* - name: The username of the node publisher.
* - nid: The node id.
* - title: The node title.
* - uid: The user ID of the node publisher.
* - newsletter: The simplenews newsletter object, which contains the
* following useful properties:
* - nid: The node ID of the newsletter node.
* - name: The short name of the newsletter.
* - description: The long name or description of the newsletter.
* - $template_path: The relative path to the template directory.
* - $template_url: The absolute url to the template directory.
* - $theme: The name of the selected Email theme.
* - $theme_path: The relative path to the Email theme directory.
* - $theme_url: The absolute url to the Email theme directory.
*/
$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));
?>
<?php if ($key == 'node' || $key == 'test'): ?>
<div class="htmlmail-simplenews-link">
<a href="<?php echo url('node/' . $params['simplenews_source']->getNode()->nid, array('absolute' => TRUE)); ?>">
Click here to view this message on the web.
</a>
</div>
<?php endif; ?>
<div class="htmlmail-simplenews-body htmlmail-body">
<?php echo $body; ?>
</div>
<?php if ($debug):
$module_template = 'htmlmail--simplenews.tpl.php';
$message_template = "htmlmail--simplenews--$key.tpl.php";
?>
<hr />
<div class="htmlmail-simplenews-debug htmlmail-debug">
<dl><dt><p>
To customize your simplenews messages:
</p></dt><dd><ol><li><p><?php if (empty($theme)): ?>
Visit <u>admin/config/system/htmlmail</u>
and select a theme to hold your custom email template files.
</p></li><li><p><?php elseif (empty($theme_path)): ?>
Visit <u>admin/appearance</u>
to enable your selected <u><?php echo drupal_ucfirst($theme); ?></u> theme.
</p></li><li><?php endif;
if ("$template_path/$template_name" == "$theme_path/$message_template"): ?><p>
Edit your<br />
<u><code><?php echo "$template_path/$template_name"; ?></code></u>
<br />file.
</p></li><li><?php
else:
if (!file_exists("$theme_path/htmlmail.tpl.php")): ?><p>
Copy<br />
<u><code><?php echo "$module_path/htmlmail.tpl.php"; ?></code></u>
<br />to<br />
<u><code><?php echo "$theme_path/htmlmail.tpl.php"; ?></code></u>
</p></li><li><?php
endif;
if (!file_exists("$theme_path/$module_template")): ?><p>
For general Simplenews message customization, copy<br />
<u><code><?php echo "$module_path/htmlmail.tpl.php"; ?></code></u>
<br />to<br />
<code><?php echo "$theme_path/$module_template"; ?></code>
</p></li><li><?php
endif;
if (!file_exists("$theme_path/$message_template")): ?><p>
For message-specific customization, copy<br />
<u><code><?php echo "$module_path/htmlmail.tpl.php"; ?></code></u>
<br />to one of the following:
</p><ul><li><dl><dt><p>
<u><code>htmlmail--simplenews--node.tpl.php</code></u>
</p></dt><dd><p>
Regular newsletter template.
</p></dd></dl></li><li><dl><dt><p>
<u><code>htmlmail--simplenews--subscribe.tpl.php</code></u>
</p></dt><dd><p>
New subscriber confirmation message.
</p></dd></dl></li><li><dl><dt><p>
<u><code>htmlmail--simplenews--test.tpl.php</code></u>
</p></dt><dd><p>
Test newsletter.
</p></dd></dl></li><li><dl><dt><p>
<u><code>htmlmail--simplenews--unsubscribe.tpl.php</code></u>
</p></dt><dd><p>
Unsubscribe confirmation message.
</p></dd></dl></li></ul></li><li><?php
endif; ?><p>
Edit the copied file.
</p></li><li><?php
endif; ?><p>
Send a test message to make sure your customizations worked.
</p></li><li><p>
If you think your customizations would be of use to others,
please contribute your file as a feature request in the
<a href="http://drupal.org/node/add/project-issue/htmlmail">issue queue</a>.
</p></li></ol></dd><dt><p>
The simplenews module sets the <u><code>$params</code></u> variable.
For this message,
</p></dt><dd><p><code><pre>
$params = <?php echo check_plain(print_r($params, 1)); ?>
</pre></code></p></dd></dl>
</div>
<?php endif;

View File

@@ -0,0 +1,112 @@
<?php
/**
* @file
* Sample template for sending user password reset messages with HTML Mail
*
* The following variables are available in this template:
*
* - $message_id: The email message id, which is 'user_password_reset'
* - $module: The sending module, which is 'user'.
* - $key: The user email action, which is 'password_reset'.
* - $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: An array containing the following keys:
* - account: The user object whose password is being requested, which
* contains the following useful properties:
* - uid: The user-id number.
* - name: The user login name.
* - mail: The user email address. Should be the same as $to.
* - theme: The user-chosen theme, or a blank string if unset.
* - signature: The user signature block.
* - signature_format: The text input filter used to format the signature.
* - created: Account creation date, as a unix timestamp.
* - access: Account access date, as a unix timestamp.
* - login: Account login date, as a unix timestamp.
* - status: Integer 0 = disabled; 1 = enabled.
* - timezone: User timezone, or NULL if unset.
* - language: User language, or blank string if unset.
* - picture: Path to user picture, or blank string if unset.
* - init: The email address used to initially register this account.
* - data: User profile data, as a serialized string.
* - roles: Array of roles assigned to this user, as (rid => role_name)
* pairs.
* - $template_path: The relative path to the template directory.
* - $template_url: The absolute url to the template directory.
* - $theme: The name of the selected Email theme.
* - $theme_path: The relative path to the Email theme directory.
* - $theme_url: The absolute url to the Email theme directory.
*/
$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));
?>
<div class="htmlmail-user-password-reset-body htmlmail-user-body htmlmail-body">
<?php echo $body; ?>
</div>
<?php if ($debug):
$module_template = "htmlmail--user.tpl.php";
$message_template = 'htmlmail--user--password_reset.tpl.php';
?>
<hr />
<div class="htmlmail-user-password-reset-debug htmlmail-user-debug htmlmail-debug">
<dl><dt><p>
To customize your user password reset messages:
</p></dt><dd><ol><li><p><?php if (empty($theme)): ?>
Visit <u>admin/config/system/htmlmail</u>
and select a theme to hold your custom email template files.
</p></li><li><p><?php elseif (empty($theme_path)): ?>
Visit <u>admin/build/themes</u>
to enable your selected <u><?php echo drupal_ucfirst($theme); ?></u> theme.
</p></li><li><?php endif;
if ("$template_path/$template_name" == "$theme_path/$message_template"): ?><p>
Edit your<br />
<u><code><?php echo "$template_path/$template_name"; ?></code></u>
<br />file.
</p></li><li><?php
else:
if (!file_exists("$theme_path/htmlmail.tpl.php")): ?><p>
Copy<br />
<u><code><?php echo "$module_path/htmlmail.tpl.php"; ?></code></u>
<br />to<br />
<u><code><?php echo "$theme_path/htmlmail.tpl.php"; ?></code></u>
</p></li><li><?php
endif;
if (!file_exists("$theme_path/$module_template")): ?><p>
For general user-module message customization, copy<br />
<u><code><?php echo "$module_path/htmlmail.tpl.php"; ?></code></u>
<br />to<br />
<code><?php echo "$theme_path/$module_template"; ?></code>
</p></li><li><?php
endif;
if (!file_exists("$theme_path/$message_template")): ?><p>
Copy<br />
<u><code><?php echo "$template_path/$template_name"; ?></code></u>
<br />to<br />
<u><code><?php echo "$theme_path/$message_template"; ?></code></u>.
</p></li><li><?php endif; ?><p>
Edit the copied file.
</p></li><li><?php
endif; ?><p>
Send a test message to make sure your customizations worked.
</p></li><li><p>
If you think your customizations would be of use to others,
please contribute your file as a feature request in the
<a href="http://drupal.org/node/add/project-issue/htmlmail">issue queue</a>.
</p></li></ol></dd><dt><p>
The user module sets the <u><code>$params</code></u> variable.
For this message,
</p></dt><dd><p><code><pre>
$params = <?php echo check_plain(print_r($params, 1)); ?>
</pre></code></p></dd></dl>
</div>
<?php endif;

View File

@@ -0,0 +1,314 @@
<?php
/**
* @file
* Admin forms for HTML Mail
*/
/**
* Implements hook_admin_settings().
*/
function htmlmail_admin_settings() {
$formats = array('0' => t('Unfiltered'));
foreach (filter_formats() as $id => $filter) {
$formats[$id] = $filter->name;
}
$form['template'] = array(
'#type' => 'fieldset',
'#title' => t('Step 1'),
'#collapsible' => FALSE,
);
$form['template']['htmlmail_template'] = array(
'#type' => 'fieldset',
'#prefix' => '<strong>' . t('Template file') . ':</strong><br />'
. t('A template file is applied to your message header, subject, and body text. You may copy the <a href="!uri">!template</a> file to your default theme directory and use it to customize your messages.',
array(
'!uri' => url('http://drupalcode.org/project/htmlmail.git/blob_plain/refs/heads/7.x-2.x:/htmlmail.tpl.php'),
'!template' => '<code>htmlmail.tpl.php</code>'
)
),
'#title' => t('Instructions'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
$form['template']['htmlmail_template']['instructions'] = array(
'#type' => 'item',
'#suffix' => t('!Instructions
<p>When formatting an email message with a given <code>$module</code> and <code>$key</code>, <a href="http://drupal.org/project/htmlmail">HTML Mail</a> will use the first template file it finds from the following list:</p>
<ol style="list-style-type: decimal;">
<li><code>htmlmail--$module--$key.tpl.php</code></li>
<li><code>htmlmail--$module.tpl.php</code></li>
<li><code>htmlmail.tpl.php</code></li>
</ol>
<p>For each filename, <a href="http://drupal.org/project/htmlmail">HTML Mail</a> looks first in the chosen <em>Email theme</em> directory, then in its own module directory, before proceeding to the next filename.</p>
<p>For example, if <code>example_module</code> sends mail with:</p>
<pre>
<code>drupal_mail("example_module", "outgoing_message" ...)
</code>
</pre>
<p>the possible template file names would be:</p>
<ol style="list-style-type: decimal;">
<li><code>htmlmail--example_module--outgoing_message.tpl.php</code></li>
<li><code>htmlmail--example_module.tpl.php</code></li>
<li><code>htmlmail.tpl.php</code></li>
</ol>
<p>Template files are cached, so remember to clear the cache by visiting <u>admin/config/development/performance</u> after changing any <code>.tpl.php</code> files.</p>
<p>The following variables available in this template:</p>
<dl>
<dt><strong><code>$body</code></strong></dt>
<dd>
<p>The message body text.</p>
</dd>
<dt><strong><code>$module</code></strong></dt>
<dd>
<p>The first argument to <a href="http://api.drupal.org/api/drupal/includes--mail.inc/function/drupal_mail/7"><code>drupal_mail()</code></a>, which is, by convention, the machine-readable name of the sending module.</p>
</dd>
<dt><strong><code>$key</code></strong></dt>
<dd>
<p>The second argument to <a href="http://api.drupal.org/api/drupal/includes--mail.inc/function/drupal_mail/7"><code>drupal_mail()</code></a>, which should give some indication of why this email is being sent.</p>
</dd>
<dt><strong><code>$message_id</code></strong></dt>
<dd>
<p>The email message id, which should be equal to <code>"{$module}_{$key}"</code>.</p>
</dd>
<dt><strong><code>$headers</code></strong></dt>
<dd>
<p>An array of email <code>(name =&gt; value)</code> pairs.</p>
</dd>
<dt><strong><code>$from</code></strong></dt>
<dd>
<p>The configured sender address.</p>
</dd>
<dt><strong><code>$to</code></strong></dt>
<dd>
<p>The recipient email address.</p>
</dd>
<dt><strong><code>$subject</code></strong></dt>
<dd>
<p>The message subject line.</p>
</dd>
<dt><strong><code>$body</code></strong></dt>
<dd>
<p>The formatted message body.</p>
</dd>
<dt><strong><code>$language</code></strong></dt>
<dd>
<p>The language object for this message.</p>
</dd>
<dt><strong><code>$params</code></strong></dt>
<dd>
<p>Any module-specific parameters.</p>
</dd>
<dt><strong><code>$template_name</code></strong></dt>
<dd>
<p>The basename of the active template.</p>
</dd>
<dt><strong><code>$template_path</code></strong></dt>
<dd>
<p>The relative path to the template directory.</p>
</dd>
<dt><strong><code>$template_url</code></strong></dt>
<dd>
<p>The absolute URL to the template directory.</p>
</dd>
<dt><strong><code>$theme</code></strong></dt>
<dd>
<p>The name of the <em>Email theme</em> used to hold template files. If the <a href="http://drupal.org/project/echo">Echo</a> module is enabled this theme will also be used to transform the message body into a fully-themed webpage.</p>
</dd>
<dt><strong><code>$theme_path</code></strong></dt>
<dd>
<p>The relative path to the selected <em>Email theme</em> directory.</p>
</dd>
<dt><strong><code>$theme_url</code></strong></dt>
<dd>
<p>The absolute URL to the selected <em>Email theme</em> directory.</p>
</dd>
<dt><strong><code>$debug</code></strong></dt>
<dd>
<p><code>TRUE</code> to add some useful debugging info to the bottom of the message.</p>
</dd>
</dl>
<p>Other modules may also add or modify theme variables by implementing a <code>MODULENAME_preprocess_htmlmail(&amp;$variables)</code> <a href="http://api.drupal.org/api/drupal/modules--system--theme.api.php/function/hook_preprocess_HOOK/7">hook function</a>.</p>',
array('!Instructions' => '')
),
);
$form['template']['htmlmail_debug'] = array(
'#type' => 'checkbox',
'#prefix' => '<br />',
'#title' => '<em>' . t('(Optional)') . '</em> ' . t('Debug'),
'#default_value' => variable_get('htmlmail_debug', '0'),
'#description' => t('Add debugging info (Set <code>$debug</code> to <code>TRUE</code>).'),
);
$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' => '<p>'
. t('Choose the theme that will hold your customized templates from Step 1 above.')
. '</p><p>'
. (module_exists('echo') ?
t('The templated text will be styled by your chosen theme. This lets you use any one of <a href="!themes">over 800</a> themes to style your messages. Creating an email-specific sub-theme lets you use the full power of the <a href="!theme_system">drupal theme system</a> 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 <a href="!echo">Echo</a> 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'
)
)
)
. '</p><p>'
. (module_exists('mailmime') ?
t('Since you have the <a href="!mailmime">Mail MIME</a> 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 <a href="!mailmime">Mail MIME</a> 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. <a href="!mailmime">Mail MIME</a> also allows <a href="!htmlmail">HTML Mail</a> to handle MIME-formatted messages sent by other modules such as <a href="!print">Send by-email</a>.',
array(
'!mailmime' => 'http://drupal.org/project/mailmime',
'!print' => 'http://drupal.org/project/print',
)
)
)
. '</p>',
);
$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' => '<p>'
. t('You may choose a <a href="!formats">text format</a> to be used for filtering email messages <em>after</em> theming. This allows you to use any combination of <a href="!filters">over 200 filter modules</a> 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'),
)
)
. '</p><p>'
. t('Here is a recommended configuration:')
. '</p><ul><li><dl><dt>'
. t('<a href="!emogrifier">Emogrifier</a>',
array('!emogrifier' => url('http://drupal.org/project/emogrifier'))
)
. '</dt><dd>'
. t('Converts stylesheets to inline style rules for consistent display on mobile devices and webmail.')
. '</dd></dl></li><li><dl><dt>'
. t('<a href="!transliteration">Transliteration</a>',
array('!transliteration' => url('http;//drupal.org/project/filter_transliteration'))
)
. '</dt><dd>'
. t('Converts non-ASCII text to US-ASCII equivalents. This helps prevent Microsoft <q>smart-quotes</q> from appearing as question-marks in Mozilla Thunderbird.'
)
. '</dd></dl></li><li><dl><dt>'
. t('<a href="!pathologic">Pathologic</a>',
array('!pathologic' => url('http://drupal.org/project/pathologic'))
)
. '</dt><dd>'
. t('Converts relative URLS to absolute URLS so that clickable links in your message will work as intended.')
. '</dd></dl></ul>'
);
return system_settings_form($form);
}
/**
* Builds a form for sending a test message.
*/
function htmlmail_test_form($form_values = NULL) {
$defaults = variable_get(
'htmlmail_test',
array(
'to' => variable_get('site_mail', 'user@example.com'),
'subject' => 'test',
'body' => array(
'value' => 'test',
),
)
);
$defaults['body']['format'] = filter_fallback_format();
$form['to'] = array(
'#type' => 'textfield',
'#title' => t('To'),
'#default_value' => $defaults['to'],
'#maxlength' => 128,
'#required' => TRUE,
);
$form['subject'] = array(
'#type' => 'textfield',
'#title' => t('Subject'),
'#default_value' => $defaults['subject'],
'#maxlength' => 128,
'#required' => TRUE,
);
$form['body'] = array(
'#type' => 'text_format',
'#title' => t('Body'),
'#rows' => 20,
'#default_value' => $defaults['body']['value'],
'#format' => $defaults['body']['format'],
'#required' => TRUE,
);
$mailsystem = mailsystem_get();
if (empty($mailsystem['htmlmail'])) {
$mailsystem['htmlmail'] = 'HTMLMailSystem';
}
$form['class'] = array(
'#type' => 'select',
'#title' => t('Test mail sending class'),
'#default_value' => $mailsystem['htmlmail'],
'#options' => array_combine(mailsystem_get_classes(), mailsystem_get_classes()),
'#description' => 'Select the MailSystemInterface implementation to be tested.',
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Send test message'),
);
return $form;
}
/**
* Sends the test messsage and saves the contents for re-use.
*/
function htmlmail_test_form_submit($form, &$form_state) {
// Get the form values.
$defaults = array(
'to' => $form_state['values']['to'],
'subject' => $form_state['values']['subject'],
'body' => $form_state['values']['body'],
);
// Set the defaults for reuse.
variable_set('htmlmail_test', $defaults);
// Set the mail sending class.
mailsystem_set(array('htmlmail' => $form_state['values']['class']));
// Send the email.
$params = array(
'subject' => $form_state['values']['subject'],
'body' => check_markup(
$form_state['values']['body']['value'],
$form_state['values']['body']['format']
),
);
if (
drupal_mail(
'htmlmail',
'test',
$form_state['values']['to'],
language_default(),
$params
)
) {
drupal_set_message(t('HTML Mail test message sent.'));
}
}

View File

@@ -0,0 +1,15 @@
package = Mail
name = HTML Mail
description = Enables HTML in system emails.
php = 5.0
files[] = htmlmail.mail.inc
dependencies[] = mailsystem
core = 7.x
configure = admin/config/system/htmlmail
; Information added by drupal.org packaging script on 2012-04-05
version = "7.x-2.65"
core = "7.x"
project = "htmlmail"
datestamp = "1333660850"

View File

@@ -0,0 +1,112 @@
<?php
/**
* @file
* Installation for HTML Mail module.
*/
/**
* Implements hook_requirements().
*
* Ensures that the Mail System module is available, and
* that HTML Mail uses its own MailSystemInterface class.
*/
function htmlmail_requirements($phase) {
$result = array();
if ($phase === 'install') {
return $result;
}
if (module_load_include('inc', 'mailsystem', 'html_to_text') !== FALSE) {
return $result;
}
$args = array(
'%htmlmail' => 'HTML Mail',
'!htmlmail' => 'http://drupal.org/project/htmlmail',
'%mailsystem' => 'Mail System',
'!mailsystem' => 'http://drupal.org/project/mailsystem',
);
$result['htmlmail_mailsystem'] = array(
'title' => t('%mailsystem module', $args),
'value' => t('7.x-1.x'),
'description' => t(
'<a href="!htmlmail">%htmlmail</a> new requires <a href="!mailsystem">%mailsystem</a> 7.x-2.6 or later. Please download and install a recent version of <a href+"!mailsystem">%mailsystem</a>, then re-enable the <a href="!htmlmail">%htmlmail</a> module.', $args
),
'severity' => REQUIREMENT_ERROR,
);
return $result;
}
/**
* Implements hook_update_N().
*
* Removes variables that are no longer used.
*/
function htmlmail_update_7200() {
variable_del('htmlmail_header');
variable_del('htmlmail_footer');
variable_del('htmlmail_css');
}
/**
* Implements hook_update_N().
*
* Rename HTMLMailMailSystem to HTMLMailSystem.
*/
function htmlmail_update_7201() {
module_load_include('module', 'mailsystem');
foreach (mailsystem_get() as $name => $value) {
if ($value == 'HTMLMailMailSystem') {
mailsystem_set(array($name => 'HTMLMailSystem'));
}
}
}
/**
* Implements hook_update_N().
*
* Increase module weight so dependent modules get loaded first.
*/
function htmlmail_update_7202() {
db_query("UPDATE {system} SET weight = 10 WHERE type = 'module' AND name = 'htmlmail'");
}
function htmlmail_update_7203() {
if ($requirements = htmlmail_requirements('runtime')) {
$requirement = array_shift($requirements);
throw new DrupalUpdateException($requirement['description']);
}
}
/**
* Implements hook_enable().
*/
function htmlmail_enable() {
module_load_include('module', 'mailsystem');
mailsystem_set(array('htmlmail' => 'HTMLMailSystem'));
}
/**
* Implements hook_disable().
*/
function htmlmail_disable() {
// Check is necessary because a 7.x-1.x to 7.x-2.x upgrade
// may not have mailsystem installed.
if (function_exists('mailsystem_clear')) {
mailsystem_clear(array('htmlmail' => 'HTMLMailSystem'));
}
}
/**
* Implements hook_install().
*/
function htmlmail_install() {
htmlmail_update_7202();
}
/**
* Implements hook_uninstall().
*/
function htmlmail_uninstall() {
db_query("DELETE FROM {variable} WHERE name LIKE 'htmlmail_%'");
cache_clear_all('variables', 'cache');
}

View File

@@ -0,0 +1,322 @@
<?php
/**
* @file
* Formats and sends mail using the MailMIME class.
*
* @see http://drupal.org/node/900794
* @see http://api.drupal.org/api/drupal/includes--mail.inc/interface/MailSystemInterface/7
* @see http://drupal.org/project/mailmime
*/
/**
* Implements MailSystemInterface.
*/
class HTMLMailSystem implements MailSystemInterface {
/**
* Format emails according to module settings.
*
* Parses the message headers and body into a MailMIME object. If another module
* subsequently modifies the body, then format() should be called again before
* sending. This is safe because the $message['body'] is not modified.
*
* @param $message
* An associative array with at least the following parts:
* - headers: An array of (name => value) email headers.
* - body: The text/plain or text/html message part.
*
* @return
* The formatted $message, ready for sending.
*/
public function format(array $message) {
$eol = variable_get('mail_line_endings', MAIL_LINE_ENDINGS);
// @todo Remove this when issue #209672 gets resolved.
$default_from = variable_get('site_mail', ini_get('sendmail_from'));
if ( !empty($message['headers']['From'])
&& $message['headers']['From'] == $default_from
&& valid_email_address($default_from)
) {
$message['headers']['From'] = '"'
. str_replace('"', '', variable_get('site_name', 'Drupal'))
. '" <' . $default_from . '>';
}
// Collapse the message body array.
if (module_exists('mailmime')) {
$body = $this->formatMailMIME($message);
$plain = $message['MailMIME']->getTXTBody();
}
else {
if (is_array($message['body'])) {
$message['body'] = implode("<br />$eol<br />$eol", $message['body']);
}
$body = theme('htmlmail', $message);
if ($message['body'] && !$body) {
watchdog(
'htmlmail',
'The %theme function did not return any text. Please check your template file for errors.',
array('%theme' => "theme('htmlmail', \$message)"),
WATCHDOG_WARNING
);
$body = $message['body'];
}
// @todo Change to drupal_html_to_text when issue #299138 gets resolved.
$plain = mailsystem_html_to_text($body);
if ($body && !$plain) {
watchdog(
'htmlmail',
'The %convert function did not return any text. Please report this error to the %mailsystem issue queue.',
array('%convert' => 'mailsystem_html_to_text()', '%mailsystem' => 'Mail system'),
WATCHDOG_WARNING,
'http://drupal.org/node/add/project-issue/mailsystem'
);
}
}
// Check to see whether recipient allows non-plaintext.
if ($body && htmlmail_is_allowed($message['to'])) {
// Optionally apply the selected web theme.
if (module_exists('echo') && $theme = htmlmail_get_selected_theme($message)) {
$themed_body = echo_themed_page($message['subject'], $body, $theme);
if ($themed_body) {
$body = $themed_body;
}
else {
watchdog(
'htmlmail',
'The %echo function did not return any text. Please check the page template of your %theme theme for errors.',
array('%echo' => 'echo_themed_page()', '%theme' => $theme),
WATCHDOG_WARNING
);
}
}
// Optionally apply the selected output filter.
if ($filter = variable_get('htmlmail_postfilter')) {
$filtered_body = check_markup($body, $filter);
if ($filtered_body) {
$body = $filtered_body;
}
else {
watchdog(
'htmlmail',
'The %check function did not return any text. Please check your %filter output filter for errors.',
array('%check' => 'check_markup()', '%filter' => $filter),
WATCHDOG_WARNING
);
}
}
// Store the fully-themed HTML body.
if (isset($message['MailMIME'])) {
$mime = &$message['MailMIME'];
$mime->setHTMLBody($body);
list($message['headers'], $message['body']) = $mime->toEmail($message['headers']);
if (!$message['body']) {
watchdog(
'htmlmail',
'The %toemail function did not return any text. Please report this errot to the %mailmime issue queue.',
array('%toemail' => 'MailMIME::toEmail()', '%mailmime' => 'Mail MIME'),
WATCHDOG_WARNING,
'http://drupal.org/node/add/project-issue/mailmime'
);
}
}
else {
$message['headers']['Content-Type'] = 'text/html; charset=utf-8';
$message['body'] = $body;
}
}
else {
if (isset($message['MailMIME'])) {
$mime = &$message['MailMIME'];
$mime->setHTMLBody('');
$mime->setContentType('text/plain', array('charset' => 'utf-8'));
list($message['headers'], $message['body']) = $mime->toEmail($message['headers']);
if (!$message['body']) {
watchdog(
'htmlmail',
'The %toemail function did not return any text. Please report this errot to the %mailmime issue queue.',
array('%toemail' => 'MailMIME::toEmail()', '%mailmime' => 'Mail MIME'),
WATCHDOG_WARNING,
'http://drupal.org/node/add/project-issue/mailmime'
);
}
}
else {
$message['body'] = $plain;
$message['headers']['Content-Type'] = 'text/plain; charset=utf-8';
}
}
return $message;
}
/**
* Use the MailMime class to format the message body.
*
* @see http://drupal.org/project/mailmime
*/
public function formatMailMIME(array &$message) {
$eol = variable_get('mail_line_endings', MAIL_LINE_ENDINGS);
$message['body'] = MailMIME::concat($message['body']);
// Build a full email message string.
$email = MailMIME::encodeEmail($message['headers'], $message['body']);
// Parse it into MIME parts.
if (!($mime = MailMIME::parse($email))) {
watchdog(
'HTMLMailSystem',
'Could not parse email message.',
array(),
WATCHDOG_ERROR
);
return $message;
}
// Work on a copy so that the original $message['body'] remains unchanged.
$email = $message;
if ( !($email['body'] = $mime->getHTMLBody())
&& !($email['body'] = $mime->getTXTBody())
) {
$email['body'] = '';
}
else {
// Wrap formatted plaintext in <pre> tags.
if ( $email['body'] === strip_tags($email['body']) // No html tags.
&& preg_match('/.' . $eol . './', $email['body']) // At least one embedded newline.
) {
$email['body'] = '<pre>' . $email['body'] . '</pre>';
}
}
// 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:<br /><pre>@call</pre><br />returned FALSE.<br /><pre>@trace</pre>', 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);
}
}

View File

@@ -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
<u>admin/config/development/performance</u>
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).

View File

@@ -0,0 +1,233 @@
<?php
/**
* @file
* Sends system emails in HTML.
*/
/**
* Implements hook_permission().
*
* Defines a permission for setting the per-user plaintext option.
*/
function htmlmail_permission() {
$args = array(
'!htmlmail' => 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 <a href="!htmlmail">%htmlmail</a> module.', $args
),
),
);
}
/**
* Implements hook_help().
*/
function htmlmail_help($path, $arg) {
switch ($path) {
case 'admin/config/system/htmlmail':
return '<h2>' . t('Theming') . '</h2><p>' . t('The email message goes through three transformations before sending:') . '</p>';
case 'admin/help#htmlmail':
return '<p>'
. t('<a href="!htmlmail">HTML Mail</a> lets you theme your messages the same way you theme the rest of your website.',
array('!htmlmail' => 'http://drupal.org/project/htmlmail')
) . '</p>';
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']);
}

View File

@@ -0,0 +1,178 @@
<?php
/**
* @file
* Default template for HTML Mail
*
* DO NOT EDIT THIS FILE. Copy it to your theme directory, and edit the copy.
*
* ========================================================= Begin instructions.
*
* When formatting an email message with a given $module and $key, [1]HTML
* Mail 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, [2]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:
* 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 [3]drupal_mail(), which is, by convention,
* the machine-readable name of the sending module.
*
* $key
* The second argument to [4]drupal_mail(), 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
* [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));
?>
<div class="htmlmail-body">
<?php echo $body; ?>
</div>
<?php if ($debug):
$module_template = "htmlmail--$module.tpl.php";
$message_template = "htmlmail--$module--$key.tpl.php";
?>
<hr />
<div class="htmlmail-debug">
<dl><dt><p>
To customize this message:
</p></dt><dd><ol><li><p><?php if (empty($theme)): ?>
Visit <u>admin/config/system/htmlmail</u>
and select a theme to hold your custom email template files.
</p></li><li><p><?php elseif (empty($theme_path)): ?>
Visit <u>admin/appearance</u>
to enable your selected
<u><?php echo drupal_ucfirst($theme); ?></u> theme.
</p></li><li><?php endif;
if ("$template_path/$template_name" == "$theme_path/$message_template"): ?><p>
Edit your<br />
<code><?php echo "$template_path/$template_name"; ?></code>
<br />file.
</p></li><li><?php
else:
if (!file_exists("$theme_path/htmlmail.tpl.php")): ?><p>
Copy<br />
<code><?php echo "$module_path/htmlmail.tpl.php"; ?></code>
<br />to<br />
<code><?php echo "$theme_path/htmlmail.tpl.php"; ?></code>
</p></li><li><?php
endif;
if (!file_exists("$theme_path/$module_template")): ?><p>
For module-specific customization, copy<br />
<code><?php echo "$module_path/htmlmail.tpl.php"; ?></code>
<br />to<br />
<code><?php echo "$theme_path/$module_template"; ?></code>
</p></li><li><?php
endif;
if (!file_exists("$theme_path/$message_template")): ?><p>
For message-specific customization, copy<br />
<code><?php echo "$module_path/htmlmail.tpl.php"; ?></code>
<br />to<br />
<code><?php echo "$theme_path/$message_template"; ?></code>
</p></li><li><?php endif; ?><p>
Edit the copied file.
</p></li><li><?php
endif; ?><p>
Send a test message to make sure your customizations worked.
</p></li><li><p>
If you think your customizations would be of use to others,
please contribute your file as a feature request in the
<a href="http://drupal.org/node/add/project-issue/htmlmail">issue queue</a>.
</p></li></ol></dd><?php if (!empty($params)): ?><dt><p>
The <?php echo $module; ?> module sets the <u><code>$params</code></u>
variable. For this message,
</p></dt><dd><p><code><pre>
$params = <?php echo check_plain(print_r($params, 1)); ?>
</pre></code></p></dd><?php endif; ?></dl>
</div>
<?php endif;

View File

@@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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.
<signature of Ty Coon>, 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.

View File

@@ -0,0 +1,4 @@
This module defines drupal_mail_wrapper(), which takes over the handling of mail sending. This wrapper creates a node of type 'Logged Mail', a node type created by the module, sends the e-mail, and displays the e-mail and its details by using devel module API. All 3 tasks can be enabled/disabled from 'admin/settings/maillog'.
The invoking of the handler depends on a system variable 'smtp_library'. Unfortunately, some modules may change the value of this variable, rendering the module dysfunctional. Simply disabling and re-enabling the module should fix this problem. Check 'admin/reports/status' to see the status of the SMTP library.

View File

@@ -0,0 +1,107 @@
<?php
/**
* An interface for pluggable mail back-ends.
*/
class MaillogMailSystem implements MailSystemInterface {
/**
* Format a message composed by drupal_mail() prior sending.
*
* @param $message
* A message array, as described in hook_mail_alter().
*
* @return
* The formatted $message.
*/
public function format(array $message) {
$default = new DefaultMailSystem();
return $default->format($message);
}
/**
* Send a message composed by drupal_mail().
*
* @param $message
* Message array with at least the following elements:
* - id: A unique identifier of the e-mail type. Examples: 'contact_user_copy',
* 'user_password_reset'.
* - to: The mail address or addresses where the message will be sent to.
* The formatting of this string must comply with RFC 2822. Some examples:
* - user@example.com
* - user@example.com, anotheruser@example.com
* - User <user@example.com>
* - User <user@example.com>, Another User <anotheruser@example.com>
* - subject: Subject of the e-mail to be sent. This must not contain any
* newline characters, or the mail may not be sent properly.
* - body: Message to be sent. Accepts both CRLF and LF line-endings.
* E-mail bodies must be wrapped. You can use drupal_wrap_mail() for
* smart plain text wrapping.
* - headers: Associative array containing all additional mail headers not
* defined by one of the other parameters. PHP's mail() looks for Cc
* and Bcc headers and sends the mail to addresses in these headers too.
*
* @return
* TRUE if the mail was successfully accepted for delivery, otherwise FALSE.
*/
public function mail(array $message) {
// Log the e-mail
if (variable_get('maillog_log', TRUE)) {
$record = new stdClass;
// In case the subject/from/to is already encoded, decode with mime_header_decode
$record->header_message_id = isset($message['headers']['Message-ID']) ? $message['headers']['Message-ID'] : NULL;
$record->subject = $message['subject'];
$record->subject = mime_header_decode($record->subject);
$record->body = $message['body'];
$record->header_from = isset($message['from']) ? $message['from'] : NULL;
$record->header_from = mime_header_decode($record->header_from);
$header_to = array();
if (isset($message['to'])) {
if (is_array($message['to'])) {
foreach($message['to'] as $value) {
$header_to[] = mime_header_decode($value);
}
}
else {
$header_to[] = mime_header_decode($message['to']);
}
}
$record->header_to = implode(', ', $header_to);
$record->header_reply_to = isset($message['headers']['Reply-To']) ? $message['headers']['Reply-To'] : '';
$record->header_all = serialize($message['headers']);
$record->sent_date = REQUEST_TIME;
drupal_write_record('maillog', $record);
}
// Display the e-mail using Devel module
if (variable_get('maillog_devel', TRUE) && function_exists('dpm')) {
$devel_msg = array();
$devel_msg[t('Subject')] = $message['subject'];
$devel_msg[t('From')] = $message['from'];
$devel_msg[t('To')] = $message['to'];
$devel_msg[t('Reply-To')] = isset($message['reply_to']) ? $message['reply_to'] : NULL;
$devel_msg[t('Header')] = $message['headers'];
$devel_msg[t('Body')] = $message['body'];
dpm($devel_msg, 'maillog');
}
if (variable_get('maillog_send', TRUE)) {
$default = new DefaultMailSystem();
$result = $default->mail($message);
}
elseif (user_access('administer maillog')) {
$message = t('Sending of e-mail messages is disabled by Maillog module. Go <a href="@href">here</a> to enable.', array('@href' => url('admin/reports/maillog')));
drupal_set_message($message, 'warning', TRUE);
}
else {
global $user;
watchdog('maillog', 'Attempted to send an email, but sending emails is disabled.');
}
return isset($result) ? $result : TRUE;
}
}

View File

@@ -0,0 +1,210 @@
<?php
/**
* @file
* Make the fields from node type 'Logged Mail' available in views
*/
/**
* Implementation of hook_views_data().
*/
function maillog_views_data() {
$data['maillog']['table']['group'] = t('Maillog');
$data['maillog']['table']['base'] = array(
'field' => 'idmaillog',
'title' => t('Maillog'),
'help' => t("This table contains the logged e-mails."),
'weight' => -10,
);
$data['maillog']['idmaillog'] = array(
'title' => t('Maillog_ID'),
'help' => t('The primary key of the maillog table.'),
'field' => array(
'handler' => 'views_handler_field',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'filter' => array(
'handler' => 'views_handler_filter_string',
),
'argument' => array(
'handler' => 'views_handler_argument_string',
),
);
$data['maillog']['header_message_id'] = array(
'title' => t('Message_ID'),
'help' => t("The 'Message_ID' e-mail address."),
'field' => array(
'handler' => 'views_handler_field',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'filter' => array(
'handler' => 'views_handler_filter_string',
),
'argument' => array(
'handler' => 'views_handler_argument_string',
),
);
$data['maillog']['header_from'] = array(
'title' => t('From'),
'help' => t("The 'From' field of the e-mail address."),
'field' => array(
'handler' => 'views_handler_field',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'filter' => array(
'handler' => 'views_handler_filter_string',
),
'argument' => array(
'handler' => 'views_handler_argument_string',
),
);
$data['maillog']['header_to'] = array(
'title' => t('To'),
'help' => t("The 'To' field of the e-mail address."),
'field' => array(
'handler' => 'views_handler_field',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'filter' => array(
'handler' => 'views_handler_filter_string',
),
'argument' => array(
'handler' => 'views_handler_argument_string',
),
);
$data['maillog']['header_reply_to'] = array(
'title' => t('Reply To'),
'help' => t("The 'Reply-To' field of the e-mail address."),
'field' => array(
'handler' => 'views_handler_field',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'filter' => array(
'handler' => 'views_handler_filter_string',
),
'argument' => array(
'handler' => 'views_handler_argument_string',
),
);
$data['maillog']['header_all'] = array(
'title' => t('Header'),
'help' => t("The 'Header' field of the e-mail."),
'field' => array(
'handler' => 'views_handler_field',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'filter' => array(
'handler' => 'views_handler_filter_string',
),
'argument' => array(
'handler' => 'views_handler_argument_string',
),
);
$data['maillog']['body'] = array(
'title' => t('Body'),
'help' => t("The 'Body' field of the e-mail."),
'field' => array(
'handler' => 'views_handler_field',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'filter' => array(
'handler' => 'views_handler_filter_string',
),
'argument' => array(
'handler' => 'views_handler_argument_string',
),
);
$data['maillog']['subject'] = array(
'title' => t('Subject'),
'help' => t("The 'Subject' field of the e-mail."),
'field' => array(
'handler' => 'views_handler_field',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort',
),
'filter' => array(
'handler' => 'views_handler_filter_string',
),
'argument' => array(
'handler' => 'views_handler_argument_string',
),
);
$data['maillog']['sent_date'] = array(
'title' => t('Date'),
'help' => t("The 'Date' field of the e-mail."),
'field' => array(
'handler' => 'views_handler_field_date',
'click sortable' => TRUE,
),
'sort' => array(
'handler' => 'views_handler_sort_date',
),
'filter' => array(
'handler' => 'views_handler_filter_date',
),
'argument' => array(
'handler' => 'views_handler_argument_string',
),
);
$data['maillog']['delete_maillog'] = array(
'field' => array(
'title' => t('Delete link'),
'help' => t('Provide a simple link to delete an eMail entry from the maillog table.'),
'handler' => 'maillog_handler_field_maillog_link_delete',
),
);
return $data;
}
/**
* Implementation of hook_views_handlers() to register all of the basic handlers
* views uses.
*/
function maillog_views_handlers() {
return array(
'info' => array(
'path' => drupal_get_path('module', 'maillog') . '/includes',
),
'handlers' => array(
// field handlers
'maillog_handler_field_maillog_link_delete' => array(
'parent' => 'views_handler_field',
),
),
);
}

View File

@@ -0,0 +1,243 @@
<?php
/**
* @file
* Creates the default view for the 'Log Mail' module
*/
/**
* Implementation of hook_views_default_views().
*/
function maillog_views_default_views() {
$view = new view;
$view->name = 'Maillog';
$view->description = 'Displays the list of e-mails logged by the \'Mail Log\' module.';
$view->tag = '';
$view->view_php = '';
$view->base_table = 'maillog';
$view->is_cacheable = FALSE;
$view->api_version = 2;
$view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
$handler = $view->new_display('default', 'Defaults', 'default');
$handler->override_option('fields', array(
'idmaillog' => array(
'label' => 'Maillog_ID',
'alter' => array(
'alter_text' => 0,
'text' => '',
'make_link' => 0,
'path' => '',
'link_class' => '',
'alt' => '',
'prefix' => '',
'suffix' => '',
'target' => '',
'help' => '',
'trim' => 0,
'max_length' => '',
'word_boundary' => 1,
'ellipsis' => 1,
'html' => 0,
'strip_tags' => 0,
),
'empty' => '',
'hide_empty' => 0,
'empty_zero' => 0,
'exclude' => 1,
'id' => 'idmaillog',
'table' => 'maillog',
'field' => 'idmaillog',
'relationship' => 'none',
),
'sent_date' => array(
'label' => 'Date',
'alter' => array(
'alter_text' => 0,
'text' => '',
'make_link' => 0,
'path' => '',
'link_class' => '',
'alt' => '',
'prefix' => '',
'suffix' => '',
'target' => '',
'help' => '',
'trim' => 0,
'max_length' => '',
'word_boundary' => 1,
'ellipsis' => 1,
'html' => 0,
'strip_tags' => 0,
),
'empty' => '',
'hide_empty' => 0,
'empty_zero' => 0,
'exclude' => 0,
'id' => 'sent_date',
'table' => 'maillog',
'field' => 'sent_date',
'relationship' => 'none',
),
'header_message_id' => array(
'id' => 'header_message_id',
'table' => 'maillog',
'field' => 'header_message_id',
),
'subject' => array(
'label' => 'Subject',
'alter' => array(
'alter_text' => 0,
'text' => '',
'make_link' => 1,
'path' => 'maillog/details/[idmaillog]',
'link_class' => '',
'alt' => '',
'prefix' => '',
'suffix' => '',
'target' => '',
'help' => '',
'trim' => 0,
'max_length' => '',
'word_boundary' => 1,
'ellipsis' => 1,
'html' => 0,
'strip_tags' => 0,
),
'empty' => '',
'hide_empty' => 0,
'empty_zero' => 0,
'exclude' => 0,
'id' => 'subject',
'table' => 'maillog',
'field' => 'subject',
'relationship' => 'none',
),
'header_from' => array(
'label' => 'From',
'alter' => array(
'alter_text' => 0,
'text' => '',
'make_link' => 0,
'path' => '',
'link_class' => '',
'alt' => '',
'prefix' => '',
'suffix' => '',
'target' => '',
'help' => '',
'trim' => 0,
'max_length' => '',
'word_boundary' => 1,
'ellipsis' => 1,
'html' => 0,
'strip_tags' => 0,
),
'empty' => '',
'hide_empty' => 0,
'empty_zero' => 0,
'exclude' => 0,
'id' => 'header_from',
'table' => 'maillog',
'field' => 'header_from',
'relationship' => 'none',
),
'header_to' => array(
'label' => 'To',
'alter' => array(
'alter_text' => 0,
'text' => '',
'make_link' => 0,
'path' => '',
'link_class' => '',
'alt' => '',
'prefix' => '',
'suffix' => '',
'target' => '',
'help' => '',
'trim' => 0,
'max_length' => '',
'word_boundary' => 1,
'ellipsis' => 1,
'html' => 0,
'strip_tags' => 0,
),
'empty' => '',
'hide_empty' => 0,
'empty_zero' => 0,
'exclude' => 0,
'id' => 'header_to',
'table' => 'maillog',
'field' => 'header_to',
'relationship' => 'none',
),
'delete_maillog' => array(
'label' => 'Delete link',
'alter' => array(
'alter_text' => 0,
'text' => '',
'make_link' => 0,
'path' => '',
'link_class' => '',
'alt' => '',
'prefix' => '',
'suffix' => '',
'target' => '',
'help' => '',
'trim' => 0,
'max_length' => '',
'word_boundary' => 1,
'ellipsis' => 1,
'html' => 0,
'strip_tags' => 0,
),
'empty' => '',
'hide_empty' => 0,
'empty_zero' => 0,
'exclude' => 0,
'id' => 'delete_maillog',
'table' => 'maillog',
'field' => 'delete_maillog',
'relationship' => 'none',
),
));
$handler->override_option('sorts', array(
'sent_date' => array(
'order' => 'DESC',
'id' => 'sent_date',
'table' => 'maillog',
'field' => 'sent_date',
'relationship' => 'none',
),
));
$handler->override_option('access', array(
'type' => 'perm',
'perm' => 'view maillog',
));
$handler->override_option('cache', array(
'type' => 'none',
));
$handler->override_option('title', 'Maillog');
$handler->override_option('empty', 'Maillog is currently empty. Send a mail!');
$handler->override_option('empty_format', '1');
$handler->override_option('use_pager', '1');
$handler->override_option('style_plugin', 'table');
$handler = $view->new_display('page', 'Page', 'page_1');
$handler->override_option('path', 'admin/reports/maillog');
$handler->override_option('menu', array(
'type' => 'normal',
'title' => 'Maillog',
'description' => 'Show the logged mails',
'weight' => '0',
'name' => 'management',
));
$handler->override_option('tab_options', array(
'type' => 'none',
'title' => '',
'description' => '',
'weight' => 0,
'name' => 'navigation',
));
$views[$view->name] = $view;
return $views;
}

View File

@@ -0,0 +1,31 @@
<?php
/**
* @file
* Handler for providing an 'Delete link' in views
*/
class maillog_handler_field_maillog_entry_link_delete extends views_handler_field {
function construct() {
parent::construct();
$this->additional_fields['maillog_id'] = 'maillog_id';
}
/**
* Called to add the field to a query.
*/
function query() {
$this->ensure_my_table();
$this->add_additional_fields();
}
function render($values) {
// ensure user has access to edit this node.
if (!user_access('delete maillog')) {
return;
}
$text = !empty($this->options['text']) ? $this->options['text'] : t('delete');
return l($text, "maillog/delete/$values->maillog_id", array('query' => drupal_get_destination()));
}
}

View File

@@ -0,0 +1,31 @@
<?php
/**
* @file
* Handler for providing an 'Delete link' in views
*/
class maillog_handler_field_maillog_link_delete extends views_handler_field {
function construct() {
parent::construct();
$this->additional_fields['idmaillog'] = 'idmaillog';
}
/**
* Called to add the field to a query.
*/
function query() {
$this->ensure_my_table();
$this->add_additional_fields();
}
function render($values) {
// ensure user has access to edit this node.
if (!user_access('delete maillog')) {
return;
}
$text = !empty($this->options['text']) ? $this->options['text'] : t('delete');
return l($text, "maillog/delete/$values->idmaillog", array('query' => drupal_get_destination()));
}
}

View File

@@ -0,0 +1,19 @@
name = Maillog / Mail Developer
description = Utility to log all Mails for debugging purposes. It's possible to suppress mail delivery for e.g. dev or staging systems.
package = Mail
core = 7.x
dependencies[] = views
configure = admin/config/development/maillog
files[] = includes/maillog.mail.inc
files[] = includes/maillog_handler_field_maillog_entry_link_delete.inc
files[] = includes/maillog_handler_field_maillog_link_delete.inc
; Information added by drupal.org packaging script on 2012-02-13
version = "7.x-1.x-dev"
core = "7.x"
project = "maillog"
datestamp = "1329135676"

View File

@@ -0,0 +1,97 @@
<?php
/**
* @file
* Provides the installation routines for the maillog module
*/
/**
* Implementation of hook_enable().
*/
function maillog_enable() {
$mail_system = variable_get('mail_system', array('default-system' => 'DefaultMailSystem'));
$mail_system['maillog'] = 'MaillogMailSystem';
$mail_system['default-system'] = $mail_system['maillog'];
variable_set('mail_system', $mail_system);
}
/**
* Implementation of hook_disable().
*
* Removing smtp_libraries settings.
*
* If the smptp_library variable refers to the maillog_smtp_library the variable will be resetted. It does not make sense to backup the smtp_library
* when enabling the maillog module, because before restoring when the maillog module gets disabled another module could changed the smtp variable.
*/
function maillog_disable() {
$mail_system = variable_get('mail_system', array('default-system' => 'DefaultMailSystem'));
unset($mail_system['maillog']);
$mail_system['default-system'] = 'DefaultMailSystem';
variable_set('mail_system', $mail_system);
}
/**
* Implementation of hook_schema().
*/
function maillog_schema() {
$schema['maillog'] = array(
'description' => t("Stores outgoing e-mail details for nodes of type 'maillog'."),
'fields' => array(
'idmaillog' => array(
'type' => 'serial',
'unsigned' => TRUE,
'not null' => TRUE,
'description' => t("The mail_log {node}.nid"),
),
'header_message_id' => array(
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
'description' => t("The 'message-id' field of the e-mail."),
),
'header_from' => array(
'type' => 'text',
'not null' => TRUE,
'description' => t("The 'From' field of the e-mail."),
),
'header_to' => array(
'type' => 'text',
'not null' => TRUE,
'description' => t("The 'To' field of the e-mail."),
),
'header_reply_to' => array(
'type' => 'text',
'not null' => TRUE,
'description' => t("The 'Reply-To' field of the e-mail."),
),
'header_all' => array(
'type' => 'text',
'not null' => TRUE,
'description' => t("The 'Header' field of the e-mail."),
),
'subject' => array(
'description' => t("The 'Subject' fieldof the e-mail."),
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'body' => array(
'description' => 'The body of this version.',
'type' => 'text',
'not null' => TRUE,
'size' => 'big'
),
'sent_date' => array(
'description' => 'The Unix timestamp when the mail was sent.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
),
'primary key' => array('idmaillog'),
);
return $schema;
}

View File

@@ -0,0 +1,284 @@
<?php
/**
* @file
* Provides a 'maillog' node type
*
* @todo: better implementation of detail link
*
*
* Extensibility of maillog engines is based on the mimemail engine hooks.
* See mimemail_get_engines in mimemail.module with
* Please install mimemail to make the outgoing engine pluggable.
*
* mail flow:
* drupal_mail -> maillog:drupal_mail_wrapper -> maillog_mail_send
* mimemail -> maillog_mailengine -> maillog_mail_send [-> ANY_engine]
*/
/**
* Implementation of hook_permision().
*/
function maillog_permission() {
return array('view maillog' => array(
'title' => t('View Maillog'),
'description' => t('Allow users to view a list of recently logged mails.'),
),
'delete maillog' => array(
'title' => t('Delete Entries from the log'),
'description' => t('Allow users to delete logged mails.'),
),
'administer maillog' => array(
'title' => t('Administer Maillog'),
'description' => t('Allow users to change maillog seetings.'),
)
);
}
function maillog_maillog_delete($maillog) {
$idmaillog = $maillog['idmaillog'];
$result = db_query("DELETE FROM {maillog} WHERE idmaillog = :id", array(':id' => $idmaillog));
if ($result == FALSE) {
drupal_set_message(t('A Problem occured when deleting Mail with idmaillog !idmaillog !', array('!idmaillog' => $idmaillog)));
}
else {
drupal_set_message(t('Mail with idmaillog !idmaillog has been deleted!', array('!idmaillog' => $idmaillog)));
}
drupal_goto('admin/reports/maillog');
}
/**
* Implementation of hook_menu().
*/
function maillog_menu() {
$items = array();
$items['admin/config/development/maillog'] = array(
'title' => t('Maillog Settings'),
'description' => t('Configure the settings of Maillog module.'),
'page callback' => 'drupal_get_form',
'page arguments' => array('maillog_admin_settings'),
'access arguments' => array('administer maillog'),
'type' => MENU_NORMAL_ITEM,
);
$items['maillog/delete/%maillog_maillog'] = array(
'title' => t("Delete Maillog from 'maillog' Table"),
'description' => t("Delete the Maillog with the idmaillog given by the parameter from 'maillog' Table"),
'page callback' => 'maillog_maillog_delete',
'page arguments' => array(2),
'access arguments' => array('delete maillog'),
'type' => MENU_CALLBACK,
);
$items['maillog/details/%maillog_maillog'] = array(
'title callback' => 'maillog_maillog_title',
'title arguments' => array(2),
'description' => t("Delete the Maillog with the idmaillog given by the parameter from 'maillog' Table"),
'page callback' => 'maillog_maillog_page',
'page arguments' => array(2),
'access arguments' => array('view maillog'),
'type' => MENU_CALLBACK,
);
return $items;
}
/**
*
*/
function maillog_maillog_title($maillog) {
return $maillog['subject'];
}
/**
*
*/
function maillog_maillog_load($idmaillog) {
$result = db_query("SELECT idmaillog, header_from, header_to, header_reply_to, header_all, subject, body FROM {maillog} WHERE idmaillog=:id", array(
':id' => $idmaillog,
));
if ($result == FALSE) {
$maillog = NULL;
}
else {
$maillog = $result->fetchAssoc();
// unserialize values
$maillog['header_all'] = unserialize($maillog['header_all']);
}
return $maillog;
}
/**
*
*/
function maillog_maillog_page($maillog) {
return theme('maillog', array('maillog' => $maillog));
}
/**
* Implementation of the module settings form.
*/
function maillog_admin_settings() {
$form = array();
$form['maillog_send'] = array(
'#type' => 'checkbox',
'#title' => t("Allow the e-mails to be sent."),
'#default_value' => variable_get('maillog_send', TRUE),
);
$form['maillog_log'] = array(
'#type' => 'checkbox',
'#title' => t("Create table entries in maillog table for each e-mail."),
'#default_value' => variable_get('maillog_log', TRUE),
);
$form['maillog_devel'] = array(
'#type' => 'checkbox',
'#title' => t("Display the e-mails on page using devel module (if enabled)."),
'#default_value' => variable_get('maillog_devel', TRUE),
);
if (module_exists('mimemail')) {
$engines = mimemail_get_engines();
// maillog will be unset, because ist would cause an recursion
unset($engines['maillog']);
$form['maillog_engine'] = array(
'#type' => 'select',
'#title' => t("Select the mailengine which should be used."),
'#default_value' => variable_get('maillog_engine', 'mimemail'),
'#options' => $engines,
);
}
return system_settings_form($form);
}
/**
* Implementation of hook_views_api().
*/
function maillog_views_api() {
return array(
'api' => 3,
'path' => drupal_get_path('module', 'maillog') . '/includes/',
);
}
/**
* Implementation of hook_theme().
*/
function maillog_theme() {
return array(
'maillog_header_from' => array(
'variables' => array('header_from' => NULL),
),
'maillog_header_to' => array(
'variables' => array('header_to' => NULL),
),
'maillog_header_reply_to' => array(
'variables' => array('header_reply_to' => NULL),
),
'maillog_header_all' => array(
'variables' => array('header_all' => NULL),
),
'maillog_body' => array(
'variables' => array('body' => NULL),
),
'maillog' => array(
'variables' => array('maillog' => NULL),
),
);
}
/**
* Render the 'From' field in the node type 'Logged Mail'
*/
function theme_maillog_header_from($variables) {
$output = '';
$output .= '<div class="field mail-log-header-from">';
$output .= '<div class="field-label">' . t('From') . ':</div>';
$output .= '<div class="field-item">' . check_plain($variables['header_from']) . '</div>';
$output .= '</div>';
return $output;
}
/**
*
*/
function theme_maillog($variables) {
$output = theme( 'maillog_header_from', array('header_from' => $variables['maillog']['header_from']) );
$output .= theme( 'maillog_header_to', array('header_to' => $variables['maillog']['header_to']) );
$output .= theme( 'maillog_header_reply_to', array('header_reply_to' => $variables['maillog']['header_reply_to']) );
$output .= theme( 'maillog_header_all', array('header_all' => $variables['maillog']['header_all']) );
$output .= theme( 'maillog_body', array('body' => $variables['maillog']['body']) );
return $output;
}
/**
* Render the 'To' field in the node type 'Logged Mail'
*/
function theme_maillog_header_to($variables) {
$output = '';
$output .= '<div class="field mail-log-header-to">';
$output .= '<div class="field-label">' . t('To') . ':</div>';
$output .= '<div class="field-item">' . check_plain($variables['header_to']) . '</div>';
$output .= '</div>';
return $output;
}
/**
* Render the 'Reply-To' field in the node type 'Logged Mail'
*/
function theme_maillog_header_reply_to($variables) {
$output = '';
$output .= '<div class="field mail-log-header-reply-to">';
$output .= '<div class="field-label">' . t('Reply To') . ':</div>';
$output .= '<div class="field-item">' . check_plain($variables['header_reply_to']) . '</div>';
$output .= '</div>';
return $output;
}
/**
* Render the 'Header' field in the node type 'Logged Mail'
*/
function theme_maillog_header_all($variables) {
$output = '';
$output .= '<div class="field mail-log-header-all">';
$output .= '<div class="field-label">' . t('Header') . ':</div>';
$output .= '<div class="field-item">';
foreach ($variables['header_all'] as $header_all_name => $header_all_value) {
$output .= '<div class="mail-log-header-all-subitem">';
$output .= check_plain($header_all_name) . ': ' . check_plain($header_all_value);
$output .= '</div>';
}
$output .= '</div>';
$output .= '</div>';
return $output;
}
/**
* Render the 'Body' field in the node type 'Logged Mail'
*/
function theme_maillog_body($variables) {
$output = '';
$output .= '<div class="field mail-log-body">';
$output .= '<div class="field-label">' . t('Body') . ':</div>';
$output .= '<div class="field-item">';
$output .= '<pre>';
$output .= check_plain($variables['body']);
$output .= '</pre>';
$output .= '</div>';
$output .= '</div>';
return $output;
}

View File

@@ -0,0 +1,24 @@
#!/bin/bash
drush --always-set vset devel_old_smtp_library "foo/bar";
drush --always-set vset smtp_library "foo/bar";
drush vget smtp_library
echo -e "\n\t\tUninstall with variables set to 'foo/bar':\n"
echo y | drush dis maillog
echo y | drush pm-uninstall maillog
echo -e "\n\t\t Show variables:\n"
drush vget smtp_library
echo y | drush en maillog
echo -e "\n\t\t Show variables after installation:\n"
drush vget smtp_library
drush --always-set vset devel_old_smtp_library "sites/all/modules/maillog/includes/maillog.inc";
drush vget smtp_library
echo -e "\n\t\tUninstall with variables set to 'sites/all/modules/maillog/includes/maillog.inc:\n"
echo y | drush dis maillog
echo y | drush pm-uninstall maillog
echo -e "\n\t\t Show variables:\n"
drush vget smtp_library
echo y | drush en maillog
echo -e "\n\t\t Show variables after installation:\n"
drush vget smtp_library

View File

@@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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.
<signature of Ty Coon>, 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.

View File

@@ -0,0 +1,113 @@
<h2 id="mail-system"><a href="http://drupal.org/project/mailsystem">Mail System</a></h2>
<p>Provides an Administrative UI and Developers API for safely updating the <a href="http://api.drupal.org/api/drupal/includes--mail.inc/function/drupal_mail_system/7">mail_system</a> configuration variable.</p>
<h3 id="administrative-ui">Administrative UI</h3>
<p>The administrative interface is at <code>admin/config/system/mailsystem</code>. A <a href="http://drupal.org/node/1134044">screenshot</a> is available.</p>
<h3 id="used-by">Used by:</h3>
<ul>
<li><a href="http://drupal.org/project/htmlmail">HTML Mail</a></li>
<li><a href="http://drupal.org/project/mimemail">Mime Mail 7.x-1.x-dev</a></li>
<li><a href="http://drupal.org/project/postmark">Postmark 7.x-1.x</a></li>
</ul>
<h3 id="developers-api">Developers API</h3>
<p>A module <code>example</code> with a <a href="http://api.drupal.org/api/drupal/includes--mail.inc/interface/MailSystemInterface/7"><code>MailSystemInterface</code></a> implementation called <code>ExampleMailSystem</code> should add the following in its <code>example.install</code> file:</p>
<pre>
<code>/**
* Implements hook_enable().
*/
function example_enable() {
mailsystem_set(array('example' => 'ExampleMailSystem'));
}
/**
* Implements hook_disable().
*/
function example_disable() {
mailsystem_clear(array('example' => 'ExampleMailSystem'));
}
</code>
</pre>
<p>The above settings allow mail sent by <code>example</code> to use <code>ExampleMailSystem</code>. To make <code>ExampleMailSystem</code> the site-wide default for sending mail:</p>
<pre>
<code>mailsystem_set(array(mailsystem_default_id() => 'ExampleMailSystem'));
</code>
</pre>
<p>To restore the default mail system:</p>
<pre>
<code>mailsystem_set(array(mailsystem_default_id() => mailsystem_default_value()));
</code>
</pre>
<p>Or simply:</p>
<pre>
<code>mailsystem_set(mailsystem_defaults());
</code>
</pre>
<p>If module <code>example</code> relies on dependency <code>foo</code> and its <code>FooMailSystem</code> class, then the <code>example.install</code> code should like like this:</p>
<pre>
<code>/**
* Implements hook_enable().
*/
function example_enable() {
mailsystem_set(array('example' => 'FooMailSystem'));
}
/**
* Implements hook_disable().
*/
function example_disable() {
mailsystem_clear(array('example' => ''));
}
</code>
</pre>
<p>If module <code>example</code> only wants to use <code>FooMailSystem</code> when sending emails with a key of <code>examail</code>, then the <code>example.install</code> code should look like this:</p>
<pre>
<code>/**
* Implements hook_enable().
*/
function example_enable() {
mailsystem_set(array('example_examail' => 'FooMailSystem'));
}
/**
* Implements hook_disable().
*/
function example_disable() {
mailsystem_clear(array('example_examail' => ''));
}
</code>
</pre>
<h4 id="new-in-2.x-branch"><em>(New in 2.x branch)</em></h4>
<p>To change the site-wide defaults to use the <code>FooMailSystem</code> for formatting messages and the <code>BarMailSystem</code> for sending them:</p>
<pre>
<code>mailsystem_set(
array(
mailsystem_default_id() =&gt; array(
'format' =&gt; 'FooMailSystem',
'mail' =&gt; 'BarMailSystem',
),
)
);
</code>
</pre>
<p>To change the site-wide defaults to use the <code>FooMailSystem</code> for sending messages, while continuing to use the current system for formatting them:</p>
<pre>
<code>mailsystem_set(
array(
mailsystem_default_id() =&gt; array(
'mail' =&gt; 'FooMailsystem',
),
)
);
</code>
</pre>
<h3 id="references">References</h3>
<dl>
<dt><strong><a href="http://api.drupal.org/api/drupal/includes--mail.inc/function/drupal_mail_system/7"><code>drupal_mail_system()</code> API documentation</a></strong>:</dt>
<dd>
<p><a href="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</a></p>
</dd>
<dt><strong><a href="http://api.drupal.org/api/drupal/includes--mail.inc/interface/MailSystemInterface/7"><code>MailSystemInterface</code> API documentation</a></strong>:</dt>
<dd>
<p><a href="http://api.drupal.org/api/drupal/includes--mail.inc/interface/MailSystemInterface/7">api.drupal.org/api/drupal/includes--mail.inc/interface/MailSystemInterface/7</a></p>
</dd>
<dt><strong><a href="http://drupal.org/node/900794">Creating HTML formatted mails in Drupal 7</a></strong>:</dt>
<dd>
<p><a href="http://drupal.org/node/900794">drupal.org/node/900794</a></p>
</dd>
</dl>

View File

@@ -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)

View File

@@ -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

View File

@@ -0,0 +1,742 @@
<?php
/**
* @file
* Copy of drupal_html_to_text improvements from issue #299138.
*/
/**
* Perform format=flowed soft wrapping for mail (RFC 3676).
*
* We use delsp=yes wrapping, but only break non-spaced languages when
* absolutely necessary to avoid compatibility issues.
*
* We deliberately use variable_get('mail_line_endings', MAIL_LINE_ENDINGS)
* rather than "\r\n".
*
* @param $text
* The plain text to process.
* @param array $options
* (optional) An array containing one or more of the following keys:
* - indent: A string to indent the text with. Only '>' 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:
*
* <a> <address> <b> <blockquote> <br /> <caption> <cite> <dd> <div> <dl> <dt>
* <em> <h1> <h2> <h3> <h4> <h5> <h6> <hr /> <i> <li> <ol> <p> <pre> <strong>
* <table> <tbody> <td> <tfoot> <thead> <tr> <u> <ul>
*
* The following tag attributes are supported:
* - <a href=...>: Hyperlink destination urls.
* - <li value=...>: Ordered list item numbers.
* - <ol start=...>: Ordered list start number.
*
* @param $string
* The string to be transformed.
* @param $allowed_tags
* (optional) If supplied, a list of tags that will be transformed. If
* omitted, all supported tags are transformed.
*
* @return
* The transformed string.
*
* @see drupal_mail()
*/
function mailsystem_html_to_text($string, $allowed_tags = NULL) {
$eol = variable_get('mail_line_endings', MAIL_LINE_ENDINGS);
// Cache list of supported tags.
static $supported_tags;
if (!isset($supported_tags)) {
$supported_tags = array(
'a', 'address', 'b', 'blockquote', 'br', 'cite', 'dd', 'div', 'dl',
'dt', 'em', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'li',
'ol', 'p', 'pre', 'strong', 'table', 'td', 'tr', 'u', 'ul',
);
}
// Make sure only supported tags are kept.
$allowed_tags = isset($allowed_tags) ? array_intersect($supported_tags, $allowed_tags) : $supported_tags;
// Parse $string into a DOM tree.
$dom = filter_dom_load($string);
$notes = array();
// Recursively convert the DOM tree into plain text.
$text = _mailsystem_html_to_text($dom->documentElement, $allowed_tags, $notes);
// Hard-wrap at 1000 characters (including the line break sequence)
// and space-stuff special lines.
$text = mailsystem_wrap_mail($text, array('max' => 1000 - strlen($eol), 'hard' => TRUE));
// Change non-breaking spaces back to regular spaces, and trim line breaks.
// chr(160) is the non-breaking space character.
$text = str_replace(chr(160), ' ', trim($text, $eol));
// Add footnotes;
if ($notes) {
// Add a blank line before the footnote list.
$text .= $eol;
foreach ($notes as $url => $note) {
$text .= $eol . '[' . $note . '] ' . $url;
}
}
return $text;
}
/**
* Helper function for drupal_html_to_text().
*
* Recursively converts $node to text, wrapping and indenting as necessary.
*
* @param $node
* The source DOMNode.
* @param $allowed_tags
* A list of tags that will be transformed.
* @param array &$notes
* A writeable array of footnote reference numbers, keyed by their
* respective hyperlink destination urls.
* @param $line_length
* The maximum length of a line, for wrapping. Defaults to 80 characters.
* @param array $parents
* The list of ancestor tags, from nearest to most distant. Defaults to an
* empty array().
* @param $count
* The number to use for the next list item within an ordered list. Defaults
* to 1.
*/
function _mailsystem_html_to_text(DOMNode $node, array $allowed_tags, array &$notes, $line_length = 80, array $parents = array(), &$count = NULL) {
if (!isset($count)) {
$count = 1;
}
$eol = variable_get('mail_line_endings', MAIL_LINE_ENDINGS);
if ($node->nodeType === XML_TEXT_NODE) {
// For text nodes, we just copy the text content.
$text = $node->textContent;
// Convert line breaks and trim trailing spaces.
$text = preg_replace('/ *\r?\n/', $eol, $text);
if (in_array('pre', $parents)) {
// Within <pre> tags, all spaces become non-breaking.
// chr(160) is the non-breaking space character.
$text = str_replace(' ', chr(160), $text);
}
else {
// Outside <pre> tags, collapse whitespace.
$text = preg_replace('/[[:space:]]+/', ' ', $text);
}
return $text;
}
// Non-text node.
$tag = '';
$text = '';
$child_text = '';
$child_count = 1;
$indent = '';
$prefix = '';
$suffix = '';
$pad = '';
if (isset($node->tagName) && in_array($node->tagName, $allowed_tags)) {
$tag = $node->tagName;
switch ($tag) {
// Turn links with valid hrefs into footnotes.
case 'a':
$test = !empty($node->attributes);
$test = $test && ($href = $node->attributes->getNamedItem('href'));
$test = $test && ($url = url(preg_replace('|^' . base_path() . '|', '', $href->nodeValue), array('absolute' => TRUE)));
$test = $test && valid_url($url);
if ($test) {
// Only add links that have not already been added.
if (isset($notes[$url])) {
$note = $notes[$url];
}
else {
$note = count($notes) + 1;
$notes[$url] = $note;
}
$suffix = ' [' . $note . ']';
}
break;
// Generic block-level tags.
case 'address':
case 'caption':
case 'div':
case 'p':
case 'pre':
// Start on a new line except as the first child of a list item.
if (!isset($parents[0]) || $parents[0] !== 'li' || !$node->isSameNode($node->parentNode->firstChild)) {
$text = $eol;
}
$suffix = $eol;
break;
// Forced line break.
case 'br':
$text = $eol;
break;
// Boldface by wrapping with "*" characters.
case 'b':
case 'strong':
$prefix = '*';
$suffix = '*';
break;
// Italicize by wrapping with "/" characters.
case 'cite':
case 'em':
case 'i':
$prefix = '/';
$suffix = '/';
break;
// Underline by wrapping with "_" characters.
case 'u':
$prefix = '_';
$suffix = '_';
break;
// Blockquotes are indented by "> " at each level.
case 'blockquote':
$text = $eol;
// chr(160) is the non-breaking space character.
$indent = '>' . chr(160);
$suffix = $eol;
break;
// Dictionary definitions are indented by four spaces.
case 'dd':
// chr(160) is the non-breaking space character.
$indent = chr(160) . chr(160) . chr(160) . chr(160);
$suffix = $eol;
break;
// Dictionary list.
case 'dl':
// Start on a new line as the first child of a list item.
if (!isset($parents[0]) || $parents[0] !== 'li' || !$node->isSameNode($node->parentNode->firstChild)) {
$text = $eol;
}
$suffix = $eol;
break;
// Dictionary term.
case 'dt':
$suffix = $eol;
break;
// Header level 1 is prefixed by eight "=" characters.
case 'h1':
$text = "$eol$eol";
// chr(160) is the non-breaking space character.
$indent = '========' . chr(160);
$pad = chr(160) . '=';
$suffix = $eol;
break;
// Header level 2 is prefixed by six "-" characters.
case 'h2':
$text = "$eol$eol";
// chr(160) is the non-breaking space character.
$indent = '------' . chr(160);
$pad = chr(160) . '-';
$suffix = $eol;
break;
// Header level 3 is prefixed by four "." characters and a space.
case 'h3':
$text = "$eol$eol";
// chr(160) is the non-breaking space character.
$indent = '....' . chr(160);
$suffix = $eol;
break;
// Header level 4 is prefixed by three "." characters and a space.
case 'h4':
$text = "$eol$eol";
// chr(160) is the non-breaking space character.
$indent = '...' . chr(160);
$suffix = $eol;
break;
// Header level 5 is prefixed by two "." character and a space.
case 'h5':
$text = "$eol$eol";
// chr(160) is the non-breaking space character.
$indent = '..' . chr(160);
$suffix = $eol;
break;
// Header level 6 is prefixed by one "." character and a space.
case 'h6':
$text = "$eol$eol";
// chr(160) is the non-breaking space character.
$indent = '.' . chr(160);
$suffix = $eol;
break;
// Horizontal rulers become a line of "-" characters.
case 'hr':
$text = $eol;
$child_text = '-';
$pad = '-';
$suffix = $eol;
break;
// List items are treated differently depending on the parent tag.
case 'li':
// Ordered list item.
if (reset($parents) === 'ol') {
// Check the value attribute.
$test = !empty($node->attributes);
$test = $test && ($value = $node->attributes->getNamedItem('value'));
if ($test) {
$count = $value->nodeValue;
}
// chr(160) is the non-breaking space character.
$indent = ($count < 10 ? chr(160) : '') . chr(160) . "$count)" . chr(160);
$count++;
}
// Unordered list item.
else {
// chr(160) is the non-breaking space character.
$indent = chr(160) . '*' . chr(160);
}
$suffix = $eol;
break;
// Ordered lists.
case 'ol':
// Start on a new line as the first child of a list item.
if (!isset($parents[0]) || $parents[0] !== 'li' || !$node->isSameNode($node->parentNode->firstChild)) {
$text = $eol;
}
// Check the start attribute.
$test = !empty($node->attributes);
$test = $test && ($value = $node->attributes->getNamedItem('start'));
if ($test) {
$child_count = $value->nodeValue;
}
break;
// Tables require special handling.
case 'table':
return _mailsystem_html_to_text_table($node, $allowed_tags, $notes, $line_length);
// Separate adjacent table cells by two non-breaking spaces.
case 'td':
if (!empty($node->nextSibling)) {
// chr(160) is the non-breaking space character.
$suffix = chr(160) . chr(160);
}
break;
// End each table row with a newline.
case 'tr':
$suffix = $eol;
break;
// Unordered lists.
case 'ul':
// Start on a new line as the first child of a list item.
if (!isset($parents[0]) || $parents[0] !== 'li' || !$node->isSameNode($node->parentNode->firstChild)) {
$text = $eol;
}
break;
default:
// Coder review complains if there is no default case.
break;
}
// Only add allowed tags to the $parents array.
array_unshift($parents, $tag);
}
// Copy each child node to output.
if ($node->hasChildNodes()) {
foreach ($node->childNodes as $child) {
$child_text .= _mailsystem_html_to_text($child, $allowed_tags, $notes, $line_length - drupal_strlen($indent), $parents, $child_count); }
}
// We only add prefix and suffix if the child nodes were non-empty.
if ($child_text > '') {
// We capitalize the contents of h1 and h2 tags.
if ($tag === 'h1' || $tag === 'h2') {
$child_text = drupal_strtoupper($child_text);
}
// Don't add a newline to an existing newline.
if ($suffix === $eol && drupal_substr($child_text, - drupal_strlen($eol)) === $eol) {
$suffix = '';
}
// Trim spaces around newlines except with <pre> or inline tags.
if (!in_array($tag, array('a', 'b', 'cite', 'em', 'i', 'pre', 'strong', 'u'))) {
$child_text = preg_replace('/ *' . $eol . ' */', $eol, $child_text);
}
// Soft-wrap at effective line length, but don't space-stuff.
$child_text = mailsystem_wrap_mail(
$prefix . $child_text,
array(
// chr(160) is the non-breaking space character.
'break' => chr(160) . $eol,
'indent' => $indent,
'max' => $line_length,
'pad' => $pad,
'stuff' => FALSE,
)
) . $suffix;
if ($tag === 'pre') {
// Perform RFC-3676 soft-wrapping.
// chr(160) is the non-breaking space character.
$child_text = str_replace(chr(160), ' ', $child_text);
$child_text = mailsystem_wrap_mail(
$child_text,
array('max' => $line_length, 'stuff' => FALSE)
);
// chr(160) is the non-breaking space character.
$child_text = str_replace(' ', chr(160), $child_text);
}
$text .= $child_text;
}
return $text;
}
/**
* Helper function for _mailsystem_html_to_text().
*
* Renders a <table> DOM Node into plain text. Attributes such as rowspan,
* colspan, padding, border, etc. are ignored.
*
* @param DOMNode $node
* The DOMNode corresponding to the <table> tag and its contents.
* @param $allowed_tags
* The list of allowed tags passed to _mailsystem_html_to_text().
* @param array &$notes
* A writeable array of footnote reference numbers, keyed by their
* respective hyperlink destination urls.
* @param $table_width
* The desired maximum table width, after word-wrapping each table cell.
*
* @return
* A plain text representation of the table.
*
* @see _mailsystem_html_to_text()
*/
function _mailsystem_html_to_text_table(DOMNode $node, $allowed_tags = NULL, array &$notes = array(), $table_width = 80) {
$eol = variable_get('mail_line_endings', MAIL_LINE_ENDINGS);
$header = array();
$footer = array();
$body = array();
$text = $eol;
$current = $node;
while (TRUE) {
if (isset($current->tagName)) {
switch ($current->tagName) {
case 'caption': // The table caption is added first.
$text = _mailsystem_html_to_text($current, $allowed_tags, $notes, $table_width);
break;
case 'tr':
switch ($current->parentNode->tagName) {
case 'thead':
$header[] = $current;
break;
case 'tfoot':
$footer[] = $current;
break;
default: // Either 'tbody' or 'table'
$body[] = $current;
break;
}
break;
default:
if ($current->hasChildNodes()) {
$current = $current->firstChild;
continue 2;
}
}
}
do {
if ($current->nextSibling) {
$current = $current->nextSibling;
continue 2;
}
$current = $current->parentNode;
} while ($current && !$current->isSameNode($node));
break;
}
// Merge the thead, tbody, and tfoot sections together.
if ($rows = array_merge($header, $body, $footer)) {
$num_rows = count($rows);
// First just count the number of columns.
$num_cols = 0;
foreach ($rows as $row) {
$row_cols = 0;
foreach ($row->childNodes as $cell) {
if (isset($cell->tagName) && in_array($cell->tagName, array('td', 'th'))) {
$row_cols++;
}
}
$num_cols = max($num_cols, $row_cols);
}
// If any columns were found, calculate each column height and width.
if ($num_cols) {
// Set up a binary search for best wrap width for each column.
$max = max($table_width - $num_cols - 1, 1);
$max_wraps = array_fill(0, $num_cols, $max);
$try = max(intval(($table_width - 1) / $num_cols - 1), 1);
$try_wraps = array_fill(0, $num_cols, $try);
$min_wraps = array_fill(0, $num_cols, 1);
// Start searching...
$change = FALSE;
do {
$change = FALSE;
$widths = array_fill(0, $num_cols, 0);
$heights = array_fill(0, $num_rows, 0);
$table = array_fill(0, $num_rows, array_fill(0, $num_cols, ''));
$breaks = array_fill(0, $num_cols, FALSE);
foreach ($rows as $i => $row) {
$j = 0;
foreach ($row->childNodes as $cell) {
if (!isset($cell->tagName) || !in_array($cell->tagName, array('td', 'th'))) {
// Skip text nodes.
continue;
}
// Render the cell contents.
$cell = _mailsystem_html_to_text($cell, $allowed_tags, $notes, $try_wraps[$j]);
// Trim leading line-breaks and trailing whitespace.
// chr(160) is the non-breaking space character.
$cell = rtrim(ltrim($cell, $eol), ' ' . $eol . chr(160));
$table[$i][$j] = $cell;
if ($cell > '') {
// Split the cell into lines.
$lines = explode($eol, $cell);
// The row height is the maximum number of lines among all the
// cells in that row.
$heights[$i] = max($heights[$i], count($lines));
foreach ($lines as $line) {
$this_width = drupal_strlen($line);
// The column width is the maximum line width among all the
// lines in that column.
if ($this_width > $widths[$j]) {
$widths[$j] = $this_width;
// If the longest line in a column contains at least one
// space character, then the table can be made narrower.
$breaks[$j] = strpos(' ', $line) !== FALSE;
}
}
}
$j++;
}
}
// Calculate the total table width;
$this_width = array_sum($widths) + $num_cols + 1;
if ($this_width > $table_width) {
// Wider than desired.
if (!in_array(TRUE, $breaks)) {
// If there are no more break points, then the table is already as
// narrow as it can get, so we're done.
break;
}
foreach ($try_wraps as $i => $wrap) {
$max_wraps[$i] = min($max_wraps[$i], $wrap);
if ($breaks[$i]) {
$new_wrap = intval(($min_wraps[$i] + $max_wraps[$i]) / 2);
$new_wrap = min($new_wrap, $widths[$i] - 1);
$new_wrap = max($new_wrap, $min_wraps[$i]);
}
else {
// There's no point in trying to make the column narrower than
// the widest un-wrappable line in the column.
$min_wraps[$i] = $widths[$i];
$new_wrap = $widths[$i];
}
if ($try_wraps[$i] > $new_wrap) {
$try_wraps[$i] = $new_wrap;
$change = TRUE;
}
}
}
elseif ($this_width < $table_width) {
// Narrower than desired.
foreach ($try_wraps as $i => $wrap) {
if ($min_wraps[$i] < $wrap) {
$min_wraps[$i] = $wrap;
}
$new_wrap = intval(($min_wraps[$i] + $max_wraps[$i]) / 2);
$new_wrap = max($new_wrap, $widths[$i] + 1);
$new_wrap = min($new_wrap, $max_wraps[$i]);
if ($try_wraps[$i] < $new_wrap) {
$try_wraps[$i] = $new_wrap;
$change = TRUE;
}
}
}
} while ($change);
// Pad each cell to column width and line height.
for ($i = 0; $i < $num_rows; $i++) {
if ($heights[$i]) {
for ($j = 0; $j < $num_cols; $j++) {
$cell = $table[$i][$j];
// Pad each cell to the maximum number of lines in that row.
$lines = array_pad(explode($eol, $cell), $heights[$i], '');
foreach ($lines as $k => $line) {
// Pad each line to the maximum width in that column.
$repeat = $widths[$j] - drupal_strlen($line);
if ($repeat > 0) {
// chr(160) is the non-breaking space character.
$lines[$k] .= str_repeat(chr(160), $repeat);
}
}
$table[$i][$j] = $lines;
}
}
}
// Generate the row separator line.
$separator = '+';
for($i = 0; $i < $num_cols; $i++) {
$separator .= str_repeat('-', $widths[$i]) . '+';
}
$separator .= $eol;
for ($i = 0; $i < $num_rows; $i++) {
$text .= $separator;
if (!$heights[$i]) {
continue;
}
$row = $table[$i];
// For each row, iterate first by lines within the row.
for ($k = 0; $k < $heights[$i]; $k++) {
// Add a vertical-bar at the beginning of each row line.
$row_line = '|';
$trimmed = '';
// Within each row line, iterate by cells within that line.
for ($j = 0; $j < $num_cols; $j++) {
// Add a vertical bar at the end of each cell line.
$row_line .= $row[$j][$k] . '|';
// chr(160) is the non-breaking space character.
$trimmed .= trim($row[$j][$k], ' ' . $eol . chr(160));
}
if ($trimmed > '') {
// Only print rows that are non-empty.
$text .= $row_line . $eol;
}
}
}
// Final output ends with a row separator.
$text .= $separator;
}
}
// Make sure formatted table content doesn't line-wrap.
// chr(160) is the non-breaking space character.
return str_replace(' ', chr(160), $text);
}
/**
* Helper function for array_walk in drupal_wrap_mail().
*
* Inserts $values['break'] sequences to break up $line into parts of no more
* than $values['wrap'] characters. Only breaks at space characters, unless
* $values['hard'] is TRUE.
*/
function _mailsystem_wrap_mail_line(&$line, $key, $values) {
$line = wordwrap($line, $values['wrap'], $values['break'], $values['hard']);
}
/**
* Helper function for array_walk in drupal_wrap_mail().
*
* If $values['pad'] is non-empty, $values['indent'] will be added at the start
* of each line, and $values['pad'] at the end, repeating the last character of
* $values['pad'] until the line length equals $values['max'].
*
* If $values['pad'] is empty, $values['indent'] will be added at the start of
* the first line, and $values['clean'] at the start of subsequent lines.
*
* If $values['stuff'] is true, then an extra space character will be added at
* the start of any line beginning with a space, a '>', or the word 'From'.
*
* @see http://www.ietf.org/rfc/rfc3676.txt
*/
function _mailsystem_indent_mail_line(&$line, $key, $values) {
if ($line == '') {
return;
}
if ($values['pad']) {
$line = $values['indent'] . $line;
$count = $values['max'] - drupal_strlen($line) - drupal_strlen($values['pad']);
if ($count >= 0) {
$line .= $values['pad'] . str_repeat($values['pad_repeat'], $count);
}
}
else {
$line = $values[$key === 0 ? 'indent' : 'clean'] . $line;
}
if ($values['stuff']) {
// chr(160) is the non-breaking space character.
$line = preg_replace('/^(' . chr(160) . '| |>|From)/', ' $1', $line);
}
}
/**
* Helper function for drupal_wrap_mail() and drupal_html_to_text().
*
* Replace all non-quotation markers from a given piece of indentation with
* non-breaking space characters.
*/
function _mailsystem_html_to_text_clean($indent) {
// chr(160) is the non-breaking space character.
return preg_replace('/[^>]/', chr(160), $indent);
}

View File

@@ -0,0 +1,202 @@
<?php
/**
* @file
* Administrative form for setting the mail_system variable.
*/
function mailsystem_admin_settings() {
$args = array(
'!interface' => url('http://api.drupal.org/api/drupal/includes--mail.inc/interface/MailSystemInterface/7'),
'@interface' => 'MailSystemInterface',
'!format' => url('http://api.drupal.org/api/drupal/includes--mail.inc/function/MailSystemInterface%3A%3Aformat/7'),
'@format' => 'format()',
'!mail' => url('http://api.drupal.org/api/drupal/includes--mail.inc/function/MailSystemInterface%3A%3Amail/7'),
'@mail' => 'mail()',
'!default_class' => url('http://api.drupal.org/api/drupal/modules--system--system.mail.inc/class/DefaultMailSystem/7'),
'@default_class' => mailsystem_default_value(),
'%module' => 'module',
'%key' => 'key',
);
$form = array('#submit' => array('mailsystem_admin_settings_submit'));
$mail_system = mailsystem_get();
$mail_defaults = mailsystem_defaults();
$mailsystem_classes = mailsystem_get_classes();
$descriptions = array();
foreach (system_rebuild_module_data() as $item) {
if ($item->status) {
$descriptions[$item->name] = (
empty($item->info['package'])
? '' : $item->info['package']
) . ' » ' . t('!module module', array('!module' => $item->info['name']));
}
}
asort($descriptions);
$form['mailsystem'] = array(
'#type' => 'fieldset',
'#title' => t('Mail System Settings'),
'#description' => t(
'Drupal provides a default <a href="!interface"><code>@interface</code></a> class called <a href="!default_class"><code>@default_class</code></a>. Modules may provide additional classes. Each <a href="!interface"><code>@interface</code></a> class may be associated with one or more identifiers, composed of a %module and an optional %key. Each email being sent also has a %module and a %key. To decide which class to use, Drupal uses the following search order: <ol><li>The class associated with the %module and %key, if any.</li><li>The class associated with the %module, if any.</li><li>The site-wide default <a href="!interface"><code>@interface</code></a> class.</li></ol>', $args
),
'#collapsible' => FALSE,
'#tree' => TRUE,
);
$form['mailsystem'][mailsystem_default_id()] = array(
'#type' => 'select',
'#title' => t(
'Site-wide default <a href="!interface"><code>@interface</code></a> class', $args
),
'#options' => $mailsystem_classes,
'#default_value' => $mail_system[mailsystem_default_id()],
);
$mailsystem_classes = array(
mailsystem_default_id() => t('Remove this setting.')
) + $mailsystem_classes;
foreach (array_diff_key($mail_system, $mail_defaults) as $id => $class) {
// Separate $id into $module and $key.
$module = $id;
while ($module && empty($descriptions[$module])) {
// Remove a key from the end
$module = implode('_', explode('_', $module, -1));
}
// If an array key of the $mail_system variable is neither "default-system"
// nor begins with a module name, then it should be unset.
if (empty($module)) {
watchdog('mailsystem', "Removing bogus mail_system key %id.", array('%id' => $id), WATCHDOG_WARNING);
unset($mail_system[$id]);
continue;
}
// Set $title to the human-readable module name.
$title = preg_replace('/^.* » /', '', $descriptions[$module]);
if ($key = substr($id, strlen($module) + 1)) {
$title .= " ($key key)";
}
$title .= ' class';
$form['mailsystem'][$id] = array(
'#type' => 'select',
'#title' => $title,
'#options' => $mailsystem_classes,
'#default_value' => $class,
);
}
// Generate a list of themes which may used to render emails.
$theme_options = array('current' => t('Current'), 'default' => t('Default'));
if (module_exists('domain_theme')) {
$theme_options['domain'] = t('Domain Theme');
}
// Get a list of all themes.
$themes = list_themes();
foreach ($themes as $name => $theme) {
if ($theme->status == 1) {
$theme_options[$name] = $theme->info['name'];
}
}
$form['mailsystem']['mailsystem_theme'] = array(
'#type' => 'select',
'#title' => t('Theme to render the emails'),
'#description' => t('Select the theme that will be used to render the emails. This can be either the current theme, the default theme, the domain theme or any active theme.'),
'#options' => $theme_options,
'#default_value' => variable_get('mailsystem_theme', 'current'),
);
$form['class'] = array(
'#type' => 'fieldset',
'#title' => t('New Class'),
'#description' => t(
'Create a new <a href="!interface"><code>@interface</code></a> that inherits its methods from other classes. The new class will be named after the other classes it uses.', $args
),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#tree' => TRUE,
);
$mailsystem_classes[mailsystem_default_id()] = '--Select--';
$form['class']['format'] = array(
'#type' => 'select',
'#title' => t(
'Class to use for the <a href="!format"><code>@format</code></a> method', $args
),
'#options' => $mailsystem_classes,
);
$form['class']['mail'] = array(
'#type' => 'select',
'#title' => t(
'Class to use for the <a href="!mail"><code>@mail</code></a> method', $args
),
'#options' => $mailsystem_classes,
);
$form['identifier'] = array(
'#type' => 'fieldset',
'#title' => t('New Setting'),
'#description' => t('Add a new %module and %key to the settings list.',
array(
'%module' => 'module',
'%key' => 'key',
)
),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#tree' => TRUE,
);
array_unshift($descriptions, t('-- Select --'));
$form['identifier']['module'] = array(
'#type' => 'select',
'#title' => t('Module'),
'#options' => $descriptions,
);
$form['identifier']['key'] = array(
'#type' => 'textfield',
'#title' => t('Key'),
'#size' => 80,
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Save settings'),
);
return $form;
}
/**
* Processes mailsystem_admin_settings form.
*/
function mailsystem_admin_settings_submit($form, &$form_state) {
variable_set('mailsystem_theme', $form_state['values']['mailsystem']['mailsystem_theme']);
// Rebuild the theme registry to make changes needed by theme rendering.
drupal_theme_rebuild();
unset($form_state['values']['mailsystem']['mailsystem_theme']);
$default_id = mailsystem_default_id();
$mail_system = array(
$default_id => (
empty($form_state['values'][$default_id])
? mailsystem_default_value()
: $form_state['values'][$default_id]
)
);
foreach (element_children($form_state['values']['mailsystem']) as $module) {
$class = $form_state['values']['mailsystem'][$module];
if (!empty($class) && $class != $default_id) {
$mail_system[$module] = $class;
}
}
unset($form_state['values']['mailsystem']);
if ($form_state['values']['class']['format'] === mailsystem_default_id()) {
unset($form_state['values']['class']['format']);
}
if ($form_state['values']['class']['mail'] === mailsystem_default_id()) {
unset($form_state['values']['class']['mail']);
}
if ($form_state['values']['class']) {
$new_class = mailsystem_create_class($form_state['values']['class']);
}
else {
$new_class = $mail_system[mailsystem_default_id()];
}
unset($form_state['values']['class']);
if ($id = $form_state['values']['identifier']['module']) {
if (!empty($form_state['values']['identifier']['key'])) {
$id .= '_' . $form_state['values']['identifier']['key'];
}
$mail_system[$id] = $new_class;
}
unset($form_state['values']['identifier']);
variable_set('mail_system', $mail_system);
drupal_set_message(t('The configuration options have been saved.'));
}

View File

@@ -0,0 +1,14 @@
package = Mail
name = Mail System
description = Provides a user interface for per-module and site-wide mail_system selection.
php = 5.0
core = 7.x
configure = admin/config/system/mailsystem
dependencies[] = filter
; Information added by drupal.org packaging script on 2012-04-10
version = "7.x-2.34"
core = "7.x"
project = "mailsystem"
datestamp = "1334082653"

View File

@@ -0,0 +1,353 @@
<?php
/**
* @file
* Provide UI for controlling the mail_system variable.
*/
/**
* Implements hook_init().
*
* Caches the list of MailSystemInterface classes, and removes classes
* from the mail_system variable which are no longer available.
*
* @see mailsystem_get_classes()
*/
function mailsystem_init() {
mailsystem_get_classes();
// @todo Remove this when issue #299138 gets resolved.
if (!function_exists('mailsystem_html_to_text')) {
module_load_include('inc', 'mailsystem', 'html_to_text');
}
}
/**
* Implements hook_permission().
*
* Defines a permission for managing the mail_system variable.
*/
function mailsystem_permission() {
return array(
'administer mailsystem' => array(
'title' => t('Administer Mail System'),
'description' => t(
'Select the default, per-module, and per-mailing <a href="!interface"><code>@interface</code></a> to use for formatting and sending email messages.',
array(
'!interface' => url('http://api.drupal.org/api/drupal/includes--mail.inc/interface/MailSystemInterface/7'),
'@interface' => 'MailSystemInterface',
)
),
),
);
}
/**
* Implements hook_menu().
*/
function mailsystem_menu() {
$items['admin/config/system/mailsystem'] = array(
'title' => 'Mail System',
'description' => 'Configure per-module Mail System settings.',
'page callback' => 'drupal_get_form',
'page arguments' => array('mailsystem_admin_settings'),
'access arguments' => array('administer mailsystem'),
'file' => 'mailsystem.admin.inc',
);
return $items;
}
/**
* Returns the id for the default mail_system setting.
*/
function mailsystem_default_id() {
// @todo: Is there a way to get this from core?
return 'default-system';
}
/**
* Returns the value for the default mail_system setting.
*/
function mailsystem_default_value() {
// @todo: Is there a way to get this from core?
return 'DefaultMailSystem';
}
/**
* Returns the default settings for the mail_system variable.
*/
function mailsystem_defaults() {
return array(mailsystem_default_id() => mailsystem_default_value());
}
/**
* Returns the current mail_system settings.
*
* @return The contents of the mail_system variable merged with its defaults.
*/
function mailsystem_get() {
return array_merge(
mailsystem_defaults(),
variable_get('mail_system', mailsystem_defaults())
);
}
/**
* Returns the default list of MailSystemInterface methods.
*
* @return
* An array whose keys are the names of the methods defined by
* MailSystemInterface and whose values are the default class used to
* provide that method.
*/
function mailsystem_default_methods() {
$mail_system = mailsystem_get();
$default_class = $mail_system[mailsystem_default_id()];
$methods = get_class_methods('MailSystemInterface');
return array_combine(
$methods,
array_fill(0, count($methods), $default_class)
);
}
/**
* Creates and registers a new MailSystemInterface class.
*
* The newly-created class gets its name and each of its class methods from the
* other classes specified by the $class parameter.
*
* @param $class An associative array of ($method_name => $class_name) tuples,
* where each $method_name is the name of a class method to be created, and
* each $class_name is the name of a class to use for that method.
*
* @return
* The name of the newly-created class if successful; otherwise FALSE.
*/
function mailsystem_create_class($classes) {
// Merge in defaults.
$classes += mailsystem_default_methods();
ksort($classes);
// Do not create a new class whose methods all derive from the same class.
if (count(array_unique($classes)) === 1) {
return FALSE;
}
$class_name = implode('__', $classes);
// Ensure that the mailsystem directory exists.
$class_dir = file_build_uri('mailsystem');
if (!file_prepare_directory($class_dir, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
return FALSE;
}
// Build the class filename.
$class_file = drupal_realpath($class_dir) . DIRECTORY_SEPARATOR . "$class_name.mail.inc";
// Strip DRUPAL_ROOT.
$drupal_root = drupal_realpath(DRUPAL_ROOT) . DIRECTORY_SEPARATOR;
$class_file = preg_replace('#^' . preg_quote($drupal_root, '#') . '#', '', $class_file);
// Build the class implementation as a string.
$class_contents = '<?php
class ' . $class_name . ' implements MailSystemInterface {';
// Create a protected variable to hold each method class.
foreach (array_keys($classes) as $method) {
$class_contents .= '
protected $' . $method . 'Class;';
}
// Create a class construction function to populate the variables.
$class_contents .= '
public function __construct() {';
foreach ($classes as $method => $class) {
$class_contents .= '
if (drupal_autoload_class(\'' . $class . '\')) {
$this->' . $method . 'Class = new ' . $class . ';
}
else {
$this->' . $method . 'Class = new ' . mailsystem_default_value() . ';
}';
}
$class_contents .= '
}';
// Create each class method.
foreach (array_keys($classes) as $method) {
$class_contents .= '
public function ' . $method . '(array $message) {
return $this->' . $method . 'Class->' . $method . '($message);
}';
}
$class_contents .= '
}
';
if (file_unmanaged_save_data($class_contents, $class_file, FILE_EXISTS_REPLACE)) {
// Remove any conflicting registry entries to avoid a database error.
$class_condition = db_and()
->condition('name', $class_name)
->condition('type', 'class');
$file_condition = db_and()
->condition('filename', $class_file);
db_delete('registry_file')
->condition($file_condition);
db_delete('registry')->condition(
db_or()->condition($class_condition)
->condition($file_condition)
);
// Make sure that registry functions are available.
require_once 'includes/registry.inc';
// Parse the newly-created class file and add it to the registry.
_registry_parse_file($class_file, $class_contents, 'mailsystem');
// Clear the mailsystem cache so that it will pick up the new class.
drupal_static_reset('mailsystem_get_classes');
drupal_set_message(
t('Class <code>%class</code> written to <code>%file</code>.',
array('%class' => $class_name, '%file' => $class_file)
)
);
}
return $class_name;
}
/**
* Helps other modules safely set their own key within mail_system. This
* function should be called from hook_enable() implementations.
*
* @param $setting An associative array ($id => $value) where:
* - $id is the machine-readable module name optionally followed by '_'
* and a key.
* - $value is one of
* - (string) The name of a class that implements MailSystemInterface.
* - (array) An associative array whose keys are the names of methods
* defined by MailSystemInterface and whose values are the names of
* the class to use for that method.
*
* @see drupal_mail(), mailsystem_default_methods()
*/
function mailsystem_set(array $setting) {
$mail_system = mailsystem_get();
foreach ($setting as $key => $class) {
if (is_array($class)) {
unset($setting[$key]);
if ($new_class = mailsystem_create_class($class)) {
$setting[$key] = $new_class;
}
}
}
variable_set('mail_system', array_merge(mailsystem_get(), $setting));
}
/**
* Helps other modules safely remove their settings from mail_system. This
* function should be called from the other module's hook_disable() function.
*
* @param $setting An associative array ($module => $classname) describing
* a module and associated MailSystemInterface class that are being disabled.
* - $module is the machine-readable module name.
* - $classname is a class that implements MailSystemInterface.
*
* If $classname is empty, only the $module entry is removed.
*
* @param $class
* The name of the class to be removed, if any.
*/
function mailsystem_clear(array $setting) {
variable_set(
'mail_system',
array_merge(
mailsystem_defaults(),
array_diff_key(array_diff(mailsystem_get(), $setting), $setting)
)
);
}
/**
* Returns a list of classes which implement MailSystemInterface.
*/
function &mailsystem_get_classes() {
$mailsystem_classes = &drupal_static(__FUNCTION__);
if (!isset($mailsystem_classes)) {
$mailsystem_classes = array();
// @todo Is there a better way to find all mail-related classes?
$declared_classes = get_declared_classes();
$all_classes = array_combine(
$declared_classes,
array_fill(0, count($declared_classes), 0)
);
$mail_classes = db_select('registry', 'registry')
->distinct()
->fields('registry', array('name', 'filename'))
->where("type=:type AND ( filename like :filename OR name like :name )",
// Making the HUGE assumption that all classes which implement
// MailSystemInterface have filenames containing '.mail.' or
// classnames ending in 'MailSystem'.
array(
':type' => 'class',
':name' => '%MailSystem',
':filename' => '%.mail.%',
)
)
->execute()
->fetchAllKeyed();
foreach ($mail_classes as $classname => $classfile) {
if ( file_exists($classfile)
&& drupal_autoload_class($classname)
) {
$all_classes[$classname] = 1;
}
}
foreach ($all_classes as $classname => $autoload) {
if ( ($autoload || preg_match('/MailSystem/', $classname))
&& ($object = new $classname)
&& ($object instanceof MailSystemInterface)
) {
$mailsystem_classes[$classname] = $classname;
}
elseif ($autoload) {
// Clear classes that are no longer available.
db_delete('registry')
->condition('name', $classname)
->execute();
}
}
foreach (array_unique(mailsystem_get()) as $classname) {
if (class_exists($classname)) {
$mailsystem_classes[$classname] = $classname;
}
else {
mailsystem_clear(array(mailsystem_default_id() => $classname));
}
}
ksort($mailsystem_classes);
}
return $mailsystem_classes;
}
/**
* Implements hook_theme_registry_alter().
*/
function mailsystem_theme_registry_alter(&$theme_registry) {
module_load_include('inc', 'mailsystem', 'mailsystem.theme');
return mailsystem_theme_theme_registry_alter($theme_registry);
}
/**
* Retrieves the key of the theme used to render the emails.
*
* @todo Add some kind of hook to let other modules alter this behavior.
*/
function mailsystem_get_mail_theme() {
global $theme_key;
$theme = variable_get('mailsystem_theme', 'current');
switch ($theme) {
case 'default':
$theme = variable_get('theme_default', NULL);
break;
case 'current':
$theme = $theme_key;
break;
case 'domain':
// Fetch the theme for the current domain.
if (module_exists('domain_theme')) {
// Assign the selected theme, based on the active domain.
global $_domain;
$domain_theme = domain_theme_lookup($_domain['domain_id']);
// The above returns -1 on failure.
$theme = ($domain_theme != -1) ? $domain_theme['theme'] : $theme_key;
}
break;
}
return $theme;
}

View File

@@ -0,0 +1,82 @@
<?php
/**
* @file
* The theme system, which controls the output of email messages.
*/
/**
* Implements hook_theme_registry_alter().
*/
function mailsystem_theme_theme_registry_alter(&$theme_registry) {
global $theme_key;
static $recursion_prevention = FALSE;
// Prevent recursive execution.
if ($recursion_prevention) {
return;
}
$recursion_prevention = TRUE;
$mailsystem_theme = mailsystem_get_mail_theme();
// Only take action if the mailsystem theme is not the current theme.
if ($mailsystem_theme != $theme_key) {
$themes = list_themes();
// Get the mailsystem theme to be used for rendering emails.
if (isset($themes[$mailsystem_theme])) {
$theme = clone $themes[$mailsystem_theme];
if (isset($theme)) {
// Establish variables for further processing.
$base_theme = array();
if (isset($theme->base_themes)) {
foreach (array_keys($theme->base_themes) as $base) {
$base_theme[$base] = clone $themes[$base];
}
}
if (isset($theme->base_theme) && !isset($base_theme[$theme->base_theme])) {
$base_theme[$theme->base_theme] = clone $themes[$theme->base_theme];
}
if (isset($theme->engine)) {
$theme_engine = $theme->engine;
}
// Include template files to let _theme_load_registry add preprocess
// functions.
include_once(drupal_get_path('theme', $theme->name) . '/template.php');
foreach ($base_theme as $base) {
include_once(drupal_get_path('theme', $base->name) . '/template.php');
}
// Get the theme_registry cache.
$cache = _theme_load_registry($theme, $base_theme, $theme_engine);
// Change the registry for hooks with a 'mail theme' element.
foreach ($theme_registry as $name => $hook) {
if (!empty($hook['mail theme'])) {
if (isset($cache[$name])) {
$cache[$name]['includes'][] = drupal_get_path('theme', $theme->name) . '/template.php';
foreach ($base_theme as $base) {
$cache[$name]['includes'][] = drupal_get_path('theme', $base->name) . '/template.php';
}
// Change the current registry for the new record.
$theme_registry[$name] = $cache[$name];
}
// Look for template suggestions.
foreach ($cache as $cache_name => $cache_hook) {
if (strpos($cache_name, $name . '__') !== FALSE) {
$cache_hook['includes'][] = drupal_get_path('theme', $theme->name) . '/template.php';
foreach ($base_theme as $base) {
$cache_hook['includes'][] = drupal_get_path('theme', $base->name) . '/template.php';
}
// Change the current registry for the new record.
$theme_registry[$cache_name] = $cache_hook;
}
}
}
}
}
}
}
$recursion_prevention = FALSE;
}

View File

@@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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.
<signature of Ty Coon>, 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.

View File

@@ -0,0 +1,129 @@
## SUMMARY
Integrates Drupal's mail system with Mandrill transactional emails, a service
by the folks behind MailChimp. Learn more about Mandrill and how to sign up on
[their website](http://mandrill.com). (Or don't, but then this module isn't
terribly useful...)
## INSTALLATION NOTES
* If you previously installed version 1.3, you will get the following error
message when enabling the mandrill_template module:
DatabaseSchemaObjectExistsException:
Table <em class="placeholder">mandrill_template_map</em> already exists.
This is ugly but irrelevant, everything should function normally.
* If you are upgrading from one of many previous versions, You may also find
an extra Mail System class in the Mail System configuration called "Mandrill
module class". It's harmless, but feel free to delete it.
## CONFIGURATION
### Set Mandrill API Key
Start by loading up the Mandrill admin page at Configuration -> Web
Services (or admin/config/services/mandrill) and adding your API key from
http://mandrillapp.com. Then you'll see more configuration options.
### Email Options
* **From address:** The email address that emails should be sent from
* **From name:** The name to use for sending (optional)
* **Subaccount:** This selection box appears if you have configured subaccounts
on your Mandrill account, and can be used to select the outgoing subaccount to
use for Mandrill sending.
* **_Input format_:** An optional input format to apply to the message body
before sending emails
### Send Options
* **Track opens:** Toggles open tracking for messages
* **Track clicks:** Toggles click tracking for messages
* **Strip query string:** Strips the query string from URLs when aggregating
tracked URL data
* **Log sends that are not registered in mailsystem:** Useful for configuring
Mail System and getting more granular control over emails coming from various
modules. Enable this and set the system default in Mail System to Mandrill,
then trigger emails from various modules and functions on your site. You'll
see Mandrill writing log messages identifying the modules and keys that are
triggering each email. Now you can add these keys in Mail System and control
each email-generating module/key pair specifically. WARNING: If you leave this
enabled, you may slow your site significantly and clog your log files. Enable
only during configuration.
### Google Analytics
* **Domains:** One or more domains for which any matching URLs will
automatically have Google Analytics parameters appended to their query string.
Separate each domain with a comma.
* **Campaign:** The value to set for the utm_campaign tracking parameter. If
empty, the from address of the message will be used instead.
### Asynchronous Options
* **Queue Outgoing Messages** Drops all messages sent through Mandrill into a
queue without sending them. When Cron is triggered, a number of queued messages
are sent equal to the specified Batch Size.
* **Batch Size** The number of messages to send when Cron triggers. Must be
greater than 0.
### SEND TEST EMAIL
The Send Test Email function is pretty self-explanatory. The To: field will
accept multiple addresses formatted in any Drupal mail system approved way.
By configuring the Mandrill Test module/key pair in Mail System, you can
use this tool to test outgoing mail for any installed mailer.
### Update Mail System settings
Mandrill Mail interface is enabled by using the
[Mail System module](http://drupal.org/project/mailsystem). Go to the
[Mail System configuration page](admin/config/system/mailsystem) to start
sending emails through Mandrill. Once you do this, you'll see a list of the
module keys that are using Mandrill listed near the top of the Mandrill
settings page.
Once you set the site-wide default (and any other module classes that may be
listed) to MandrillMailSystem, your site will immediately start using Mandrill
to deliver all outgoing email.
### Module/key pairs
The key is optional: not every module or email uses a key. That is why on the
mail system settings page, you may see some modules listed without keys. For
more details about this, see the help text on the mail system configuration
page.
# Sub-modules
## Templates
In order to use the mandrill_template module, start by creating some templates
in your Mandrill account. Once you do, you can add one or more Mandrill
Template Maps for that template, specifying where in the template to place
the email content and which module/key pair should be sent using the template.
If you want to send multiple module/key pairs through the same Template, you
can make Mandrill the default mail system and make that Template Map the
default template, or you can clone the Template Map for each module/key pair
and assign them individually.
You should also consider enabling the css-inline feature in your Mandrill
account under Settings -> Sending Options. For more info, see
"http://help.mandrill.com/entries/24460141-Does-Mandrill-inline-CSS-automatically-".
## Reports
The mandrill_reports sub-module provides reports on various metrics. It may
take a long time to load. This module is due for some attention.
### Dashboard
Displays charts that show volume and engagement, along with a tabular list of
URL interactions for the past 30 days.
### Account Summary
Shows account information, quotas, and all-time usage stats.
## Activity
The Mandrill Activity sub-modules allows users to view email activity for any
Drupal entity with a valid email address. Configuration and usage details are in
sub-module's README file.
## Advanced Options
If you would like to use additional template (or other) Mandrill API
variables not implemented in this module, set them in hook_mail_alter under:
$params['mandrill']. Have a look at mandrill.mail.inc to learn more.
(Search for "mandrill parameters".)

View File

@@ -0,0 +1,517 @@
<?php
/**
* @file
* Wrapper class around the Mandrill API.
*/
/**
* Class MandrillException.
*/
class MandrillException extends Exception {
}
/**
* Class DrupalMandrill.
*/
class DrupalMandrill {
const API_VERSION = '1.0';
const END_POINT = 'https://mandrillapp.com/api/';
protected $api;
/**
* Default to a 300 second timeout on server calls
*/
protected $timeout = 300;
/**
* Constructor to set internal values.
*
* @param string $api_key
* Mandrill API key.
* @param int $timeout
* Server timeout.
*
* @throws MandrillException.
*/
public function __construct($api_key, $timeout = 300) {
if (empty($api_key)) {
throw new MandrillException('Invalid API key');
}
try {
$response = $this->request('users/ping', array('key' => $api_key));
if ($response != 'PONG!') {
throw new MandrillException('Invalid API key: ' . $response);
}
$this->api = $api_key;
$this->timeout = $timeout;
}
catch (Exception $e) {
throw new MandrillException($e->getMessage());
}
}
/**
* Make a request to Mandrill's API.
*
* Every API call uses this function to actually make the request to
* Mandrill's servers.
*
* @link https://mandrillapp.com/api/docs/
*
* @param string $method
* API method name
* @param array $args
* query arguments
* @param string $http
* GET or POST request type
* @param string $output
* API response format (json,php,xml,yaml). json and xml are decoded into
* arrays automatically.
*
* @return array
* Array on success.
*
* @throws MandrillException.
*/
protected function request($method, $args = array(), $http = 'POST', $output = 'json') {
if (!isset($args['key'])) {
$args['key'] = $this->api;
}
$api_version = self::API_VERSION;
$dot_output = ('json' == $output) ? '' : ".{$output}";
$url = self::END_POINT . "{$api_version}/{$method}{$dot_output}";
$params = drupal_json_encode($args);
switch ($http) {
case 'GET':
$url .= '?' . $params;
$response = drupal_http_request($url, array(
'method' => 'GET',
'timeout' => $this->timeout,
));
break;
case 'POST':
$response = drupal_http_request($url, array(
'method' => 'POST',
'data' => $params,
'timeout' => $this->timeout,
));
break;
default:
throw new MandrillException('Unknown request type');
}
$response_code = $response->code;
if (0 == $response_code) {
return $response->error;
}
$body = $response->data;
switch ($output) {
case 'json':
$body = json_decode($body, TRUE);
break;
case 'php':
$body = unserialize($body);
break;
}
if (200 == $response_code) {
return $body;
}
else {
$message = isset($body['message']) ? $body['message'] : $body;
if (is_array($message)) {
$message = "Unspecified Error";
}
throw new MandrillException($message, $response_code);
}
}
/**
* @link https://mandrillapp.com/api/docs/users.html#method=ping
*
* @return array|MandrillException
*/
public function users_ping() {
return $this->request('users/ping');
}
/**
* @link https://mandrillapp.com/api/docs/users.html#method=info
*
* @return array|MandrillException
*/
public function users_info() {
return $this->request('users/info');
}
/**
* @link https://mandrillapp.com/api/docs/users.html#method=senders
*
* @return array|MandrillException
*/
public function users_senders() {
return $this->request('users/senders');
}
/**
* @link https://mandrillapp.com/api/docs/senders.html#method=domains
*
* @return array|MandrillException
*/
public function senders_domains() {
return $this->request('senders/domains');
}
/**
* @link https://mandrillapp.com/api/docs/senders.html#method=list
*
* @return array|MandrillException
*/
public function senders_list() {
return $this->request('senders/list');
}
/**
* @link https://mandrillapp.com/api/docs/senders.html#method=info
*
* @return array|MandrillException
*/
public function senders_info($email) {
return $this->request('senders/info', array('address' => $email));
}
/**
* @link https://mandrillapp.com/api/docs/senders.html#method=time-series
*
* @return array|MandrillException
*/
public function senders_time_series($email) {
return $this->request('senders/time-series', array('address' => $email));
}
/**
* @link https://mandrillapp.com/api/docs/subaccounts.html#method=method-list
*
* @return array|MandrillException
*/
public function subaccounts() {
return $this->request('subaccounts/list');
}
/**
* @link https://mandrillapp.com/api/docs/tags.html#method=list
*
* @return array|MandrillException
*/
public function tags_list() {
return $this->request('tags/list');
}
/**
* @link https://mandrillapp.com/api/docs/tags.html#method=info
*
* @return array|MandrillException
*/
public function tags_info($tag) {
return $this->request('tags/info', array('tag' => $tag));
}
/**
* @link https://mandrillapp.com/api/docs/tags.html#method=time-series
*
* @return array|MandrillException
*/
public function tags_time_series($tag) {
return $this->request('tags/time-series', array('tag' => $tag));
}
/**
* @link https://mandrillapp.com/api/docs/tags.html#method=all-time-series
*
* @return array|MandrillException
*/
public function tags_all_time_series() {
return $this->request('tags/all-time-series');
}
/**
* @link https://mandrillapp.com/api/docs/templates.html#method=add
*
* @return array|MandrillException
*/
public function templates_add($name, $code) {
return $this->request('templates/add', array(
'name' => $name,
'code' => $code,
));
}
/**
* @link https://mandrillapp.com/api/docs/templates.html#method=update
*
* @return array|MandrillException
*/
public function templates_update($name, $code) {
return $this->request('templates/update', array(
'name' => $name,
'code' => $code,
));
}
/**
* @link https://mandrillapp.com/api/docs/templates.html#method=delete
*
* @return array|MandrillException
*/
public function templates_delete($name) {
return $this->request('templates/delete', array('name' => $name));
}
/**
* @link https://mandrillapp.com/api/docs/templates.html#method=info
*
* @return array|MandrillException
*/
public function templates_info($name) {
return $this->request('templates/info', array('name' => $name));
}
/**
* @link https://mandrillapp.com/api/docs/templates.html#method=list
*
* @return array|MandrillException
*/
public function templates_list() {
return $this->request('templates/list');
}
/**
* @link https://mandrillapp.com/api/docs/templates.html#method=time-series
*
* @return array|MandrillException
*/
public function templates_time_series($name) {
return $this->request('templates/time-series', array('name' => $name));
}
/**
* @link https://mandrillapp.com/api/docs/urls.html#method=list
*
* @return array|MandrillException
*/
public function urls_list() {
return $this->request('urls/list');
}
/**
* @link https://mandrillapp.com/api/docs/urls.html#method=time-series
*
* @return array|MandrillException
*/
public function urls_time_series($url) {
return $this->request('urls/time-series', array('url' => $url));
}
/**
* @link https://mandrillapp.com/api/docs/urls.html#method=search
*
* @return array|MandrillException
*/
public function urls_search($q) {
return $this->request('urls/search', array('q' => $q));
}
/**
* @link https://mandrillapp.com/api/docs/webhooks.html#method=add
*
* @return array|MandrillException
*/
public function webhooks_add($url, $events) {
return $this->request('webhooks/add', array(
'url' => $url,
'events' => $events,
));
}
/**
* @link https://mandrillapp.com/api/docs/webhooks.html#method=update
*
* @return array|MandrillException
*/
public function webhooks_update($id, $url, $events) {
return $this->request('webhooks/update', array(
'id' => $id,
'url' => $url,
'events' => $events,
));
}
/**
* @link https://mandrillapp.com/api/docs/webhooks.html#method=delete
*
* @return array|MandrillException
*/
public function webhooks_delete($id) {
return $this->request('webhooks/delete', array('id' => $id));
}
/**
* @link https://mandrillapp.com/api/docs/webhooks.html#method=info
*
* @return array|MandrillException
*/
public function webhooks_info($id) {
return $this->request('webhooks/info', array('id' => $id));
}
/**
* @link https://mandrillapp.com/api/docs/webhooks.html#method=list
*
* @return array|MandrillException
*/
public function webhooks_list() {
return $this->request('webhooks/list');
}
/**
* @link https://mandrillapp.com/api/docs/messages.html#method=search
*
* @return array|MandrillException
*/
public function messages_search($query, $date_from = '', $date_to = '', $tags = array(), $senders = array(), $limit = 100) {
return $this->request('messages/search', compact('query', 'date_from', 'date_to', 'tags', 'senders', 'limit'));
}
/**
* @link https://mandrillapp.com/api/docs/messages.html#method=send
*
* @return array|MandrillException
*/
public function messages_send($message) {
return $this->request('messages/send', array('message' => $message));
}
/**
* @link https://mandrillapp.com/api/docs/messages.html#method=send-template
*
* @return array|MandrillException
*/
public function messages_send_template($template_name, $template_content, $message) {
return $this->request('messages/send-template', compact('template_name', 'template_content', 'message'));
}
/**
* Return an array structure for a message attachment.
*
* @param string $path
* Attachment path.
*
* @return array
* Attachment structure.
*
* @throws MandrillException
* @throws Exception
*/
public static function getAttachmentStruct($path) {
$struct = array();
try {
if (!@is_file($path)) {
throw new Exception($path . ' is not a valid file.');
}
$filename = basename($path);
if (!function_exists('get_magic_quotes')) {
function get_magic_quotes() {
return FALSE;
}
}
if (!function_exists('set_magic_quotes')) {
function set_magic_quotes($value) {
return TRUE;
}
}
if (strnatcmp(phpversion(), '6') >= 0) {
$magic_quotes = get_magic_quotes_runtime();
set_magic_quotes_runtime(0);
}
$file_buffer = file_get_contents($path);
$file_buffer = chunk_split(base64_encode($file_buffer), 76, "\n");
if (strnatcmp(phpversion(), '6') >= 0) {
set_magic_quotes_runtime($magic_quotes);
}
$mime_type = file_get_mimetype($path);
if (!DrupalMandrill::isValidContentType($mime_type)) {
throw new Exception($mime_type . ' is not a valid content type (it should be ' . implode('*,', self::getValidContentTypes()) . ').');
}
$struct['type'] = $mime_type;
$struct['name'] = $filename;
$struct['content'] = $file_buffer;
}
catch (Exception $e) {
throw new MandrillException('Error creating the attachment structure: ' . $e->getMessage());
}
return $struct;
}
/**
* Helper to determine attachment is valid.
*
* @static
*
* @param $ct
*
* @return bool
*/
protected static function isValidContentType($ct) {
$valids = self::getValidContentTypes();
foreach ($valids as $vct) {
if (strpos($ct, $vct) !== FALSE) {
return TRUE;
}
}
return FALSE;
}
/**
* Return an array of valid content types.
*
* @static
*
* @return array
* Valid content types to attach to an email.
*/
protected static function getValidContentTypes() {
$valid_types = array(
'image/',
'text/',
'application/pdf',
'application/x-zip',
);
drupal_alter('mandrill_valid_attachment_types', $valid_types);
return $valid_types;
}
}

View File

@@ -0,0 +1,184 @@
<?php
/**
* @file
* Implements Mandrill as a Drupal MailSystemInterface
*/
/**
* Modify the drupal mail system to use Mandrill when sending emails.
*/
class MandrillMailSystem implements MailSystemInterface {
/**
* Concatenate and wrap the email body for either plain-text or HTML emails.
*
* @param array $message
* A message array, as described in hook_mail_alter().
*
* @return array
* The formatted $message.
*/
public function format(array $message) {
// Join the body array into one string.
if (is_array($message['body'])) {
$message['body'] = implode("\n\n", $message['body']);
}
return $message;
}
/**
* Send the email message.
*
* @see drupal_mail()
*
* @param array $message
* A message array, as described in hook_mail_alter().
*
* @return bool
* TRUE if the mail was successfully accepted, otherwise FALSE.
*/
public function mail(array $message) {
// Optionally log mail keys not using Mandrill already. Helpful in
// configuring Mandrill.
if (variable_get('mandrill_log_defaulted_sends', FALSE)) {
$systems = mailsystem_get();
$registered = FALSE;
foreach ($systems as $key => $system) {
if ($message['id'] == $key) {
$registered = TRUE;
}
if (!$registered) {
watchdog(
'mandrill',
"Module: %module Key: %key invoked Mandrill to send email because Mandrill is configured as the default mail system. Specify alternate configuration for this module & key in !mailsystem if this is not desirable.",
array(
'%module' => $message['module'],
'%key' => $message['key'],
'!mailsystem' => l(t('Mail System'), 'admin/config/system/mailsystem'),
),
WATCHDOG_INFO
);
}
}
}
$mailer = mandrill_get_api_object();
// Apply input format to body.
$format = variable_get('mandrill_filter_format', '');
if (!empty($format)) {
$message['body'] = check_markup($message['body'], $format);
}
// Extract an array of recipients.
$to = mandrill_get_to($message['to']);
// Prepare headers, defaulting the reply-to to the from address since
// Mandrill needs the from address to be configured separately.
// Note that only Reply-To and X-* headers are allowed.
$headers = isset($message['headers']) ? $message['headers'] : array();
if (!empty($message['from']) && empty($headers['Reply-To'])) {
$headers['Reply-To'] = $message['from'];
}
// Prepare attachments.
$attachments = array();
if (isset($message['attachments']) && !empty($message['attachments'])) {
foreach ($message['attachments'] as $attachment) {
if (is_file($attachment)) {
$attachments[] = $mailer->getAttachmentStruct($attachment);
}
}
}
// Determine if content should be available for this message.
$blacklisted_keys = explode(',', mandrill_mail_key_blacklist());
$view_content = TRUE;
foreach ($blacklisted_keys as $key) {
if ($message['id'] == drupal_strtolower(trim($key))) {
$view_content = FALSE;
break;
}
}
// The Mime Mail module (mimemail) expects attachments as an array of file
// arrays in $message['params']['attachments']. As many modules assume you
// will be using Mime Mail to handle attachments, we need to parse this
// array as well.
if (isset($message['params']['attachments']) && !empty($message['params']['attachments'])) {
foreach ($message['params']['attachments'] as $attachment) {
$attachment_path = drupal_realpath($attachment['uri']);
if (is_file($attachment_path)) {
$struct = $mailer->getAttachmentStruct($attachment_path);
// Allow for customised filenames.
if (!empty($attachment['filename'])) {
$struct['name'] = $attachment['filename'];
}
$attachments[] = $struct;
}
}
// Remove the file objects from $message['params']['attachments'].
// (This prevents double-attaching in the drupal_alter hook below.)
unset($message['params']['attachments']);
}
// Account for the plaintext parameter provided by the mimemail module.
$plain_text = empty($message['params']['plaintext']) ? drupal_html_to_text($message['body']) : $message['params']['plaintext'];
// Get metadata.
$metadata = isset($message['metadata']) ? $message['metadata'] : array();
$from = mandrill_from();
$mandrill_message = array(
'html' => $message['body'],
'text' => $plain_text,
'subject' => $message['subject'],
'from_email' => $from['email'],
'from_name' => $from['name'],
'to' => $to,
'headers' => $headers,
'track_opens' => variable_get('mandrill_track_opens', TRUE),
'track_clicks' => variable_get('mandrill_track_clicks', TRUE),
// We're handling this with drupal_html_to_text().
'auto_text' => FALSE,
'url_strip_qs' => variable_get('mandrill_url_strip_qs', FALSE),
'bcc_address' => isset($message['bcc_email']) ? $message['bcc_email'] : NULL,
'tags' => array($message['id']),
'google_analytics_domains' => (variable_get('mandrill_analytics_domains', NULL)) ? explode(',', variable_get('mandrill_analytics_domains')) : array(),
'google_analytics_campaign' => variable_get('mandrill_analytics_campaign', ''),
'attachments' => $attachments,
'view_content_link' => $view_content,
'metadata' => $metadata,
);
$subaccount = variable_get('mandrill_subaccount', FALSE);
if ($subaccount) {
$mandrill_message['subaccount'] = $subaccount;
}
// Allow other modules to alter the Mandrill message, and sender/args.
$mandrill_params = array(
'message' => $mandrill_message,
'function' => 'mandrill_sender_plain',
'args' => array(),
);
drupal_alter('mandrill_mail', $mandrill_params, $message);
// Queue for processing during cron or send immediately.
$status = NULL;
if (mandrill_process_async()) {
$queue = DrupalQueue::get(MANDRILL_QUEUE, TRUE);
$queue->createItem($mandrill_params);
if (variable_get('mandrill_batch_log_queued', TRUE)) {
watchdog('mandrill', 'Message from %from to %to queued for delivery.',
array(
'%from' => $from['email'],
'%to' => $to[0]['email'],
), WATCHDOG_NOTICE);
}
return TRUE;
}
else {
return mandrill_mailsend($mandrill_params['message'], $mandrill_params['function'], $mandrill_params['args']);
}
}
}

View File

@@ -0,0 +1,300 @@
<?php
/**
* @file
* Administrative forms for Mandrill module.
*/
/**
* Administrative settings.
*
* @return array
* An array containing form items to place on the module settings page.
*/
function mandrill_admin_settings($form, &$form_state) {
$key = variable_get('mandrill_api_key');
$form['mandrill_api_key'] = array(
'#title' => t('Mandrill API Key'),
'#type' => 'textfield',
'#description' => t('Create or grab your API key from the !link.',
array('!link' => l(t('Mandrill settings'), 'https://mandrillapp.com/settings/index'))),
'#default_value' => $key,
);
if ($key) {
$mailsystem_config_keys = mailsystem_get();
$in_use = FALSE;
$usage_rows = array();
foreach ($mailsystem_config_keys as $key => $sys) {
if ($sys === 'MandrillMailSystem' && $key != 'mandrill_test') {
$in_use = TRUE;
$usage_rows[] = array(
$key,
$sys,
);
}
}
if ($in_use) {
$usage_array = array(
'#theme' => 'table',
'#header' => array(
t('Module Key'),
t('Mail System'),
),
'#rows' => $usage_rows,
);
$form['mandrill_status'] = array(
'#type' => 'markup',
'#markup' => t('Mandrill is currently configured to be used by the following Module Keys. To change these settings or configure additional systems to use Mandrill, use !link.<br /><br />!table',
array(
'!link' => l(t('Mail System'), 'admin/config/system/mailsystem'),
'!table' => drupal_render($usage_array),
)),
);
}
elseif (!$form_state['rebuild']) {
drupal_set_message(t('PLEASE NOTE: Mandrill is not currently configured for use by Drupal. In order to route your email through Mandrill, you must configure at least one MailSystemInterface (other than mandrill) to use "MandrillMailSystem" in !link, or you will only be able to send Test Emails through Mandrill.',
array('!link' => l(t('Mail System'), 'admin/config/system/mailsystem'))), 'warning');
}
$form['email_options'] = array(
'#type' => 'fieldset',
'#collapsible' => TRUE,
'#title' => t('Email options'),
);
$from = mandrill_from();
$form['email_options']['mandrill_from'] = array(
'#title' => t('From address'),
'#type' => 'textfield',
'#description' => t('The sender email address. If this address has not been verified, messages will be queued and not sent until it is. This address will appear in the "from" field, and any emails sent through Mandrill with a "from" address will have that address moved to the Reply-To field.'),
'#default_value' => $from['email'],
);
$form['email_options']['mandrill_from_name'] = array(
'#type' => 'textfield',
'#title' => t('From name'),
'#default_value' => $from['name'],
'#description' => t('Optionally enter a from name to be used.'),
);
$subaccounts = mandrill_get_subaccounts();
$sub_acct_options = array('' => '-- Select --');
if (count($subaccounts)) {
$sub_acct_options = array('' => '-- Select --');
foreach ($subaccounts as $acct) {
if ($acct['status'] == 'active') {
$sub_acct_options[$acct['id']] = $acct['name'] . ' (' . $acct['reputation'] . ')';
}
}
}
elseif (variable_get('mandrill_subaccount', FALSE)) {
variable_set('mandrill_subaccount', FALSE);
}
if (count($sub_acct_options) > 1) {
$form['email_options']['mandrill_subaccount'] = array(
'#type' => 'select',
'#title' => t('Subaccount'),
'#options' => $sub_acct_options,
'#default_value' => variable_get('mandrill_subaccount', ''),
'#description' => t('Choose a subaccount to send through.'),
);
}
$formats = filter_formats();
$options = array('' => t('-- Select --'));
foreach ($formats as $v => $format) {
$options[$v] = $format->name;
}
$form['email_options']['mandrill_filter_format'] = array(
'#type' => 'select',
'#title' => t('Input format'),
'#description' => t('If selected, the input format to apply to the message body before sending to the Mandrill API.'),
'#options' => $options,
'#default_value' => array(variable_get('mandrill_filter_format', 'full_html')),
);
$form['send_options'] = array(
'#title' => t('Send options'),
'#type' => 'fieldset',
'#collapsible' => TRUE,
);
$form['send_options']['mandrill_track_opens'] = array(
'#title' => t('Track opens'),
'#type' => 'checkbox',
'#description' => t('Whether or not to turn on open tracking for messages.'),
'#default_value' => variable_get('mandrill_track_opens', TRUE),
);
$form['send_options']['mandrill_track_clicks'] = array(
'#title' => t('Track clicks'),
'#type' => 'checkbox',
'#description' => t('Whether or not to turn on click tracking for messages.'),
'#default_value' => variable_get('mandrill_track_clicks', TRUE),
);
$form['send_options']['mandrill_url_strip_qs'] = array(
'#title' => t('Strip query string'),
'#type' => 'checkbox',
'#description' => t('Whether or not to strip the query string from URLs when aggregating tracked URL data.'),
'#default_value' => variable_get('mandrill_url_strip_qs', FALSE),
);
$form['send_options']['mandrill_mail_key_blacklist'] = array(
'#title' => t('Content logging blacklist'),
'#type' => 'textarea',
'#description' => t('Comma delimited list of Drupal mail keys to exclude content logging for. CAUTION: Removing the default password reset key may expose a security risk.'),
'#default_value' => mandrill_mail_key_blacklist(),
);
$form['send_options']['mandrill_log_defaulted_sends'] = array(
'#title' => t('Log sends from module/key pairs that are not registered independently in mailsystem.'),
'#type' => 'checkbox',
'#description' => t('If you select Mandrill as the site-wide default email sender in !mailsystem and check this box, any messages that are sent through Mandrill using module/key pairs that are not specifically registered in mailsystem will cause a message to be written to the !systemlog (type: Mandrill, severity: info). Enable this to identify keys and modules for automated emails for which you would like to have more granular control. It is not recommended to leave this box checked for extended periods, as it slows Mandrill and can clog your logs.',
array(
'!mailsystem' => l(t('Mail System'), 'admin/config/system/mailsystem'),
'!systemlog' => l(t('system log'), 'admin/reports/dblog'),
)),
'#default_value' => variable_get('mandrill_log_defaulted_sends', FALSE),
);
$form['analytics'] = array(
'#type' => 'fieldset',
'#collapsible' => TRUE,
'#title' => t('Google analytics'),
);
$form['analytics']['mandrill_analytics_domains'] = array(
'#title' => t('Google analytics domains'),
'#type' => 'textfield',
'#description' => t('One or more domains for which any matching URLs will automatically have Google Analytics parameters appended to their query string. Separate each domain with a comma.'),
'#default_value' => variable_get('mandrill_analytics_domains', ''),
);
$form['analytics']['mandrill_analytics_campaign'] = array(
'#title' => t('Google analytics campaign'),
'#type' => 'textfield',
'#description' => t("The value to set for the utm_campaign tracking parameter. If this isn't provided the messages from address will be used instead."),
'#default_value' => variable_get('mandrill_analytics_campaign', ''),
);
$form['asynchronous_options'] = array(
'#title' => t('Asynchronous options'),
'#type' => 'fieldset',
'#collapsible' => TRUE,
'#attributes' => array(
'id' => array('mandrill-async-options'),
),
);
$form['asynchronous_options']['mandrill_process_async'] = array(
'#title' => t('Queue outgoing messages'),
'#type' => 'checkbox',
'#description' => t('When set, emails will not be immediately sent. Instead, they will be placed in a queue and sent when cron is triggered.'),
'#default_value' => mandrill_process_async(),
);
$form['asynchronous_options']['mandrill_batch_log_queued'] = array(
'#title' => t('Log queued emails in watchdog'),
'#type' => 'checkbox',
'#description' => t('Do you want to create a watchdog entry when an email is queued to be sent?'),
'#default_value' => variable_get('mandrill_batch_log_queued', TRUE),
'#states' => array(
'invisible' => array(
':input[name="mandrill_process_async"]' => array('checked' => FALSE),
),
),
);
$form['asynchronous_options']['mandrill_queue_worker_timeout'] = array(
'#title' => t('Queue worker timeout'),
'#type' => 'textfield',
'#size' => '12',
'#description' => t('Number of seconds to spend processing messages during cron. Zero or negative values are not allowed.'),
'#required' => TRUE,
'#element_validate' => array('element_validate_integer_positive'),
'#default_value' => variable_get('mandrill_queue_worker_timeout', 15),
'#states' => array(
'invisible' => array(
':input[name="mandrill_process_async"]' => array('checked' => FALSE),
),
),
);
}
return system_settings_form($form);
}
/**
* Javascript callback for the mandrill_admin_settings form.
*
* @param array $form
* a drupal form
* @param array $form_state
* drupal form_state
*
* @return array
* a form section
*/
function mandrill_admin_settings_form_callback($form, &$form_state) {
return $form['asynchronous_options'];
}
/**
* Return a form for sending a test email.
*/
function mandrill_test_form($form, &$form_state) {
drupal_set_title(t('Send test email'));
$form['mandrill_test_address'] = array(
'#type' => 'textfield',
'#title' => t('Email address to send a test email to'),
'#default_value' => variable_get('site_mail', ''),
'#description' => t('Type in an address to have a test email sent there.'),
'#required' => TRUE,
);
$form['mandrill_test_body'] = array(
'#type' => 'textarea',
'#title' => t('Test body contents'),
'#default_value' => t('If you receive this message it means your site is capable of using Mandrill to send email. This url is here to test click tracking: !link',
array('!link' => l(t('link'), 'http://www.drupal.org/project/mandrill'))),
);
$form['include_attachment'] = array(
'#title' => t('Include attachment'),
'#type' => 'checkbox',
'#description' => t('If checked, the Drupal icon will be included as an attachment with the test email.'),
'#default_value' => TRUE,
);
$form['test_submit'] = array(
'#type' => 'submit',
'#value' => t('Send test email'),
);
$form['test_cancel'] = array(
'#type' => 'link',
'#href' => 'admin/config/services/mandrill',
'#title' => t('Cancel'),
);
return $form;
}
/**
* Submit handler for mandrill_test_form(), sends the test email.
*/
function mandrill_test_form_submit($form, &$form_state) {
// If an address was given, send a test email message.
$test_address = $form_state['values']['mandrill_test_address'];
global $language;
$params['subject'] = t('Drupal Mandrill test email');
$params['body'] = $form_state['values']['mandrill_test_body'];
$params['include_attachment'] = $form_state['values']['include_attachment'];
$mailsystem = mailsystem_get();
// Check for empty mailsystem config for Mandrill:
if (empty($mailsystem['mandrill_test'])) {
drupal_set_message(t('Automatically setting Mandrill tests to go through Mandrill API: MandrillMailSystem was not previously configured in Mail System.'));
mailsystem_set(array('mandrill_test' => 'MandrillMailSystem'));
}
// Check for wrong mailsystem config for Mandrill, if not empty, and issue a
// warning:
elseif ($mailsystem['mandrill_test'] != 'MandrillMailSystem') {
drupal_set_message(
t('Mail System is configured to send Mandrill Test messages through %system, not Mandrill. To send tests through Mandrill, go to !link and change the setting.',
array(
'%system' => $mailsystem['mandrill_test'],
'!link' => l(t('Mandrill'), 'https://mandrillapp.com/templates'))),
'warning');
// Hack because we are apparently formatting the body differently than
// default drupal messages.
$params['body'] = array('0' => $params['body']);
}
$result = drupal_mail('mandrill', 'test', $test_address, $language, $params);
if (isset($result['result']) && $result['result'] == 'true') {
drupal_set_message(t('Mandrill test email sent from %from to %to.', array('%from' => $result['from'], '%to' => $result['to'])), 'status');
}
}

View File

@@ -0,0 +1,50 @@
<?php
/**
* @file
* This file contains no working PHP code; it exists to provide additional
* documentation for doxygen as well as to document hooks in the standard
* Drupal manner.
*/
/**
* Allows other modules to alter the Mandrill message and sender arguments.
*
* @array $mandrill_params
* The mandril message array
* @see MandrillMailSystem::mail()
*
* @array $message
* The drupal_mail message array.
* @see drupal_mail()
*/
function hook_mandrill_mail_alter(&$mandrill_params, $message) {
// No example.
}
/**
* Allows other modules to alter the allowed attachment file types.
*
* @array $types
* An array of file types indexed numerically.
*/
function hook_mandrill_valid_attachment_types_alter(&$types) {
// Example, allow word docs:
$types[] = 'application/msword';
// Allow openoffice docs:
$types[] = 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
}
/**
* Allow other modules to respond to the result of sending an email.
*
* @param array $result
* Associative array containing the send result, including the status.
*/
function hook_mandrill_mailsend_result($result) {
if ($result['status'] == 'rejected') {
// Delete user.
$user = user_load_by_mail($result['email']);
user_delete($user->uid);
}
}

View File

@@ -0,0 +1,18 @@
name = Mandrill
description = "Allow for site emails to be sent through Mandrill."
core = 7.x
package = MailChimp
configure = admin/config/services/mandrill
dependencies[] = mailsystem (>=2.x)
files[] = lib/mandrill.mail.inc
files[] = lib/mandrill.inc
; Information added by Drupal.org packaging script on 2014-05-09
version = "7.x-1.6"
core = "7.x"
project = "mandrill"
datestamp = "1399658028"

View File

@@ -0,0 +1,96 @@
<?php
/**
* @file
* Install, update and uninstall functions for the mandrill module.
*/
/**
* Implements hook_uninstall().
*/
function mandrill_uninstall() {
// Delete other variables:
variable_del('mandrill_analytics_campaign');
variable_del('mandrill_analytics_domains');
variable_del('mandrill_api_key');
variable_del('mandrill_filter_format');
variable_del('mandrill_from');
variable_del('mandrill_from_name');
variable_del('mandrill_mail_key_blacklist');
variable_del('mandrill_test_address');
variable_del('mandrill_test_body');
variable_del('mandrill_track_clicks');
variable_del('mandrill_track_opens');
variable_del('mandrill_url_strip_qs');
variable_del('mandrill_process_async');
}
/**
* Implements hook_enable().
*/
function mandrill_enable() {
mailsystem_set(array('mandrill_test' => 'MandrillMailSystem'));
}
/**
* Implements hook_disable().
*/
function mandrill_disable() {
// Tell mailsystem to remove mandrill and restore to defaults:
mailsystem_clear(array('mandrill_test' => 'MandrillMailSystem'));
watchdog('mandrill', 'Mandrill has been disabled.');
}
/**
* Deletes obsolete variables.
*/
function mandrill_update_7001() {
variable_del('mandrill_status');
}
/**
* Rebuilds the registry, as we've moved some files around.
*/
function mandrill_update_7002() {
// Rebuild the registry, we've moved some files around.
registry_rebuild();
}
/**
* Implements hook_requirements().
*/
function mandrill_requirements($phase) {
$requirements = array();
// Ensure translations don't break at install time.
$t = get_t();
if ($phase == 'update') {
if (!module_exists('mailsystem')) {
$requirements['mandrill'] = array(
'title' => $t('Mandrill'),
'value' => '7.x-1.4',
'description' => $t('Mail System module is required for Mandrill 7.x-1.3 or higher.'),
'severity' => REQUIREMENT_ERROR,
);
}
}
return $requirements;
}
/**
* Delete obsolete batch limit variable.
*/
function mandrill_update_7003() {
variable_del('mandrill_batch_limit');
}
/**
* Clear cache to pick up new class name for DrupalMandrill class.
*/
function mandrill_update_7004() {
cache_clear_all();
}

View File

@@ -0,0 +1,406 @@
<?php
/**
* @file
* Enables Drupal to send email directly through Mandrill.
*
* Overriding mail handling in Drupal to make Mandrill the default
* transport layer, requires to change the mail_system variable's
* default value array('default-system' => 'DefaultMailSystem').
* This module uses array('default-system' => 'MailChimpMandrillMailSystem').
*/
define('MANDRILL_QUEUE', 'mandrill_queue');
define('MANDRILL_EMAIL_REGEX', '/^\s*(.+?)\s*<\s*([^>]+)\s*>$/');
/**
* Implements hook_help().
*/
function mandrill_help($path, $arg) {
switch ($path) {
case 'admin/help#mandrill':
return t('Allow for site emails to be sent through Mandrill.');
}
}
/**
* Implements hook_menu().
*/
function mandrill_menu() {
$items = array();
$items['admin/config/services/mandrill'] = array(
'title' => 'Mandrill',
'page callback' => 'drupal_get_form',
'page arguments' => array('mandrill_admin_settings'),
'access arguments' => array('administer mandrill'),
'description' => 'Send emails through the Mandrill transactional email service.',
'file' => 'mandrill.admin.inc',
'type' => MENU_NORMAL_ITEM,
);
$items['admin/config/services/mandrill/settings'] = array(
'title' => 'Settings',
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => 0,
);
$items['admin/config/services/mandrill/test'] = array(
'title' => 'Send test email',
'page callback' => 'drupal_get_form',
'page arguments' => array('mandrill_test_form'),
'access callback' => 'mandrill_test_access',
'description' => 'Send a test email using the Mandrill API.',
'file' => 'mandrill.admin.inc',
'type' => MENU_LOCAL_TASK,
'weight' => 1,
);
return $items;
}
/**
* Access callback for sending test email.
*
* @return bool
* True if current user has access to send test messages
*/
function mandrill_test_access() {
$a = user_access('administer mandrill');
$b = variable_get('mandrill_api_key');
return $a & !empty($b);
}
/**
* Implements hook_permission().
*/
function mandrill_permission() {
return array(
'administer mandrill' => array(
'title' => t('Administer Mandrill'),
'description' => t('Perform administration tasks for the Mandrill email service.'),
"restrict access" => TRUE,
),
);
}
/**
* Implements hook_cron_queue_info().
*/
function mandrill_cron_queue_info() {
$queues = array();
$queues[MANDRILL_QUEUE] = array(
'worker callback' => 'mandrill_queue_worker_mailsend',
'time' => variable_get('mandrill_queue_worker_timeout', 15),
);
return $queues;
}
/**
* Sends a queued email.
* @see mandrill_cron_queue_info()
*/
function mandrill_queue_worker_mailsend($data) {
// Send the message stored in the queue item.
mandrill_mailsend(
$data['message'],
$data['function'],
$data['args']
);
}
/**
* Implements hook_mail().
*/
function mandrill_mail($key, &$message, $params) {
if ($key == 'test') {
$message['subject'] = $params['subject'];
$message['body'] = $params['body'];
if ($params['include_attachment']) {
$message['attachments'][] = drupal_realpath('misc/druplicon.png');
$message['body'] .= ' ' . t('The Drupal icon is included as an attachment to test the attachment functionality.');
}
}
}
/**
* Abstracts sending of messages, allowing queueing option.
*
* @param array $message
* A message array formatted for Mandrill's sending API, plus 2 additional
* indexes for the send_function and an array of $args, if needed by the send
* function.
*
* @return bool
* TRUE if no exception thrown
*/
function mandrill_mailsend($message, $function, $args = array()) {
try {
if (!function_exists($function)) {
watchdog('mandrill', 'Error sending email from %from to %to. Function %function not found.',
array(
'%from' => $message['from_email'],
'%to' => $message['to'],
'%function' => $function,
),
WATCHDOG_ERROR
);
return FALSE;
}
$params = array($message) + $args;
$results = call_user_func_array($function, $params);
foreach ($results as $result) {
// Allow other modules to react based on a send result.
module_invoke_all('mandrill_mailsend_result', $result);
switch ($result['status']) {
case "error":
case "invalid":
case "rejected":
$to = isset($result['email']) ? $result['email'] : 'recipient';
$status = isset($result['status']) ? $result['status'] : 'message';
$error_message = isset($result['message']) ? $result['message'] : 'no message';
watchdog('mandrill', 'Failed sending email from %from to %to. @status: @message',
array(
'%from' => $message['from_email'],
'%to' => $to,
'@status' => $status,
'@message' => $error_message,
),
WATCHDOG_ERROR
);
return FALSE;
case "queued":
watchdog('mandrill', 'Email from %from to %to queued by Mandrill App.',
array(
'%from' => $message['from_email'],
'%to' => $result['email'],
),
WATCHDOG_INFO
);
break;
}
}
return TRUE;
}
catch (MandrillException $e) {
watchdog('mandrill', 'Error sending email from %from to %to. @code: @message',
array(
'%from' => $message['from_email'],
'%to' => $message['to'],
'@code' => $e->getCode(),
'@message' => $e->getMessage(),
),
WATCHDOG_ERROR
);
return FALSE;
}
}
/**
* The actual function that calls the API send message.
*
* This is the default function used by mandrill_mailsend().
*
* @array $message
* Associative array containing message data.
*
* @return array
* Results of sending the message.
*
* @throws MandrillException
*/
function mandrill_sender_plain($message) {
if ($mailer = mandrill_get_api_object()) {
return $mailer->messages_send($message);
}
else {
throw new MandrillException('Missing API key.');
}
}
/**
* Return Mandrill API object for communication with the mailchimp server.
*
* @param bool $reset
* Pass in TRUE to reset the statically cached object.
* @param string $classname
* The Mandrill class to use for sending emails. Passing a parameter allows
* the class used to be overridden, E.g., for tests.
*
* @throws MandrillException
*
* @return Mandrill|bool
* Mandrill Object upon success
* FALSE if variable_get('mandrill_api_key') is unset
*/
function mandrill_get_api_object($reset = FALSE, $classname = 'DrupalMandrill') {
$api =& drupal_static(__FUNCTION__, NULL);
if ($api === NULL || $reset === TRUE) {
$api_key = variable_get('mandrill_api_key', '');
$api_timeout = variable_get('mandrill_api_timeout', 60);
if (empty($api_key)) {
return FALSE;
}
$api = new $classname($api_key, $api_timeout);
}
return $api;
}
/**
* Display the names of the modules that are using Mailsystem.
*
* This is consistent with with Mailsystem's display. In the future, if
* Mailsystem were to provide an API for their labeling, that should be used.
*
* @return array
* Array of all module names indexing to their "display" names,
* and some special items for non-module values like null, default-system,
* and some clarification talked onto the end of the Mandrill module's name.
*/
function mandrill_get_module_key_names() {
$name_array = array(
'' => '--none--',
'default-system' => "Site-wide default",
);
$descriptions = array();
foreach (system_rebuild_module_data() as $item) {
if ($item->status) {
$descriptions[$item->name] = (empty($item->info['package']) ? '' : $item->info['package']) . ' » ' . t('!module module', array('!module' => $item->info['name']));
}
}
asort($descriptions);
$mailsystem_settings = mailsystem_get();
unset($mailsystem_settings['default-system']);
foreach ($mailsystem_settings as $id => $class) {
// Separate $id into $module and $key.
$module = $id;
while ($module && empty($descriptions[$module])) {
// Remove a key from the end.
$module = implode('_', explode('_', $module, -1));
}
// If an array key of the $mail_system variable is neither "default-system"
// nor begins with a module name, then it should be unset.
if (empty($module)) {
// This shouldn't happen.
}
// Set $title to the human-readable module name.
$title = preg_replace('/^.* » /', '', $descriptions[$module]);
if ($key = substr($id, strlen($module) + 1)) {
$title .= " ($key key)";
}
$name_array[$id] = $title;
}
return $name_array;
}
/**
* Get a list of mandrill template objects.
*
* @return array
* An of available templates with complete data or NULL if none are available.
*/
function mandrill_get_templates() {
// Only show the template settings if the mandrill api can be called.
$templates = NULL;
try {
$mailer = mandrill_get_api_object();
$templates = $mailer->templates_list();
}
catch (MandrillException $e) {
drupal_set_message(t('Mandrill: %message', array('%message' => check_plain($e->getMessage()))), 'error');
watchdog_exception('mandrill', $e);
}
return $templates;
}
/**
* Get a list of subaccounts.
*/
function mandrill_get_subaccounts() {
$subaccounts = array();
try {
$mandrill = mandrill_get_api_object();
$subaccounts = $mandrill->subaccounts();
}
catch (MandrillException $e) {
drupal_set_message(t('Mandrill: %message', array('%message' => check_plain($e->getMessage()))), 'error');
watchdog_exception('mandrill', $e);
}
return $subaccounts;
}
/**
* Helper to return a comma delimited list of mail keys to not log content for.
*
* @return string
* a comma delimited list of mail keys
*/
function mandrill_mail_key_blacklist() {
return variable_get('mandrill_mail_key_blacklist', 'user_password_reset');
}
/**
* Helper to generate an array of recipients.
*
* @param mixed $to
* a comma delimited list of email addresses in 1 of 2 forms:
* user@domain.com
* any number of names <user@domain.com>
*
* @return array
* array of email addresses
*/
function mandrill_get_to($to) {
$recipients = array();
$to_array = explode(',', $to);
foreach ($to_array as $email) {
if (preg_match(MANDRILL_EMAIL_REGEX, $email, $matches)) {
$recipients[] = array(
'email' => $matches[2],
'name' => $matches[1],
);
}
else {
$recipients[] = array('email' => $email);
}
}
return $recipients;
}
/**
* Determine if mail should be processed asynchronously.
*
* @return bool
* True if asyncronous processing is enabled
*/
function mandrill_process_async() {
return variable_get('mandrill_process_async', FALSE);
}
/**
* Returns an array containing the from information for a Mandrill message.
*
* @return array
* array(
* 'email' => 'admin@example.com',
* 'name' => 'My Site',
* )
*/
function mandrill_from() {
$default_from = variable_get('site_mail', ini_get('sendmail_from'));
$email = variable_get('mandrill_from', $default_from);
$name = variable_get('mandrill_from_name', variable_get('site_name'));
return array(
'email' => $email,
'name' => $name,
);
}

View File

@@ -0,0 +1,109 @@
<?php
/**
* @file
* Mandrill variable hooks.
*/
/**
* Implements hook_variable_group_info().
*/
function mandrill_variable_group_info() {
$groups['mandrill'] = array(
'title' => t('Mandrill'),
'description' => t('Settings related to Mandrill.'),
'access' => 'administer mandrill',
'path' => array('admin/config/services/mandrill'),
);
return $groups;
}
/**
* Implements hook_variable_info().
*/
function mandrill_variable_info($options) {
$variables = array();
$variables['mandrill_api_key'] = array(
'title' => t('Mandrill API Key', array(), $options),
'description' => t('Create or grab your API key from the !link.',
array('!link' => l(t('Mandrill settings'), 'https://mandrillapp.com/settings/index')),
$options),
'type' => 'string',
'group' => 'mandrill',
'default' => variable_get('site_mail'),
);
$variables['mandrill_from'] = array(
'title' => t('From address', array(), $options),
'description' => t('The sender email address. If this address has not been verified, messages will be queued and not sent until it is.', array(), $options),
'type' => 'string',
'group' => 'mandrill',
'default' => '',
);
$variables['mandrill_from_name'] = array(
'title' => t('From name', array(), $options),
'description' => t('Optionally enter a from name to be used.', array(), $options),
'type' => 'string',
'group' => 'mandrill',
'default' => '',
);
$formats = filter_formats();
$mandrill_filter_format_options = array();
foreach ($formats as $v => $format) {
$mandrill_filter_format_options[$v] = $format->name;
}
$variables['mandrill_filter_format'] = array(
'title' => t('Input format', array(), $options),
'description' => t('If selected, the input format to apply to the message body before sending to the Mandrill API.', array(), $options),
'type' => 'select',
'options' => $mandrill_filter_format_options,
'group' => 'mandrill',
'default' => 'full_html',
);
$variables['mandrill_track_opens'] = array(
'title' => t('Track opens', array(), $options),
'description' => t('Whether or not to turn on open tracking for messages.', array(), $options),
'type' => 'boolean',
'group' => 'mandrill',
'default' => TRUE,
);
$variables['mandrill_track_clicks'] = array(
'title' => t('mandrill_track_clicks', array(), $options),
'description' => t('Whether or not to turn on click tracking for messages.', array(), $options),
'type' => 'boolean',
'group' => 'mandrill',
'default' => TRUE,
);
$variables['mandrill_url_strip_qs'] = array(
'title' => t('Strip query string', array(), $options),
'description' => t('Whether or not to strip the query string from URLs when aggregating tracked URL data.', array(), $options),
'type' => 'boolean',
'group' => 'mandrill',
'default' => FALSE,
);
$variables['mandrill_mail_key_blacklist'] = array(
'title' => t('Content logging blacklist', array(), $options),
'description' => t('Comma delimited list of Drupal mail keys to exclude content logging for. CAUTION: Removing the default password reset key may expose a security risk.', array(), $options),
'type' => 'text',
'group' => 'mandrill',
'default' => mandrill_mail_key_blacklist(),
);
$variables['mandrill_analytics_domains'] = array(
'title' => t('Google analytics domains', array(), $options),
'description' => t('One or more domains for which any matching URLs will automatically have Google Analytics parameters appended to their query string. Separate each domain with a comma.', array(), $options),
'type' => 'string',
'group' => 'mandrill',
'default' => '',
);
$variables['mandrill_analytics_campaign'] = array(
'title' => t('Google analytics campaign', array(), $options),
'description' => t("The value to set for the utm_campaign tracking parameter. If this isn't provided the messages from address will be used instead.", array(), $options),
'type' => 'string',
'group' => 'mandrill',
'default' => '',
);
return $variables;
}

View File

@@ -0,0 +1,19 @@
View Mandrill activity for any entity with a valid email address.
Activity keys off of an email address.
## Installation
1. Enable the Mandrill Activity module.
2. To use Mandrill Activity module, you will need to install and enable Mandrill
and Entity API module [http://drupal.org/project/entity]([http://drupal.org/project/entity).
## Usage
1. Define which entity types you want to show Mandrill activity for at
/admin/config/services/mandrill/activity.
* Select a Drupal entity type.
* Select a bundle.
* Select the email entity property.
2. Configure permissions for viewing Mandrill activity.
3. Once setup, a new Mandrill activity local task will appear for any configured
entity.

View File

@@ -0,0 +1,40 @@
<?php
/**
* @file
* Class defining a MandrillActivityEntity.
*/
/**
* Class MandrillActivityEntity
*
* Extend Entity to define a MandrillActivityEntity.
*/
class MandrillActivityEntity extends Entity {
public
$mandrill_activity_entity_id,
$name,
$label,
$entity_type,
$bundle,
$email_property,
$entity_path,
$enabled;
/**
* Override constructor to set entity type.
*/
public function __construct(array $values = array()) {
parent::__construct($values, 'mandrill_activity_entity');
}
/**
* Return a label for this entity.
*
* @return string
* Label.
*/
public function label() {
return $this->label;
}
}

View File

@@ -0,0 +1,22 @@
<?php
/**
* @file
* Class defining MandrillActivityUIController.
*/
/**
* Override EntityDefaultUIController to customize our menu items.
*/
class MandrillActivityUIController extends EntityDefaultUIController {
/**
* Override hook_menu() to add a proper description.
*/
public function hook_menu() {
$items = parent::hook_menu();
$items[$this->path]['description'] = 'Manage Mandrill Activity entity settings.';
$items[$this->path]['type'] = MENU_LOCAL_TASK;
return $items;
}
}

View File

@@ -0,0 +1,228 @@
<?php
/**
* @file
* Administration pages for mandrill_activity module.
*/
/**
* Returns a form for a mandrill_activity_entity.
*/
function mandrill_activity_entity_form($form, &$form_state, MandrillActivityEntity $mandrill_activity_entity = NULL, $op = 'edit') {
if ($op == 'clone') {
$mandrill_activity_entity->label .= ' (cloned)';
$mandrill_activity_entity->name = '';
}
$entitynotnull = isset($mandrill_activity_entity->mandrill_activity_entity_id);
$form_state['mandrill_activity'] = $mandrill_activity_entity;
$form['label'] = array(
'#title' => t('Label'),
'#type' => 'textfield',
'#default_value' => $entitynotnull ? $mandrill_activity_entity->label : '',
'#description' => t('The human-readable name of this activity entity.'),
'#required' => TRUE,
'#weight' => -10,
);
$form['name'] = array(
'#title' => t('Name'),
'#type' => 'machine_name',
'#default_value' => $entitynotnull ? $mandrill_activity_entity->name : '',
'#description' => t('machine name should only contain lowercase letters & underscores'),
'#disabled' => !empty($mandrill_activity_entity->name),
'#required' => TRUE,
'#machine_name' => array(
'exists' => 'mandrill_activity_load',
'source' => array('label'),
),
'#weight' => -9,
);
$form['drupal_entity'] = array(
'#title' => t('Drupal entity'),
'#type' => 'fieldset',
'#attributes' => array(
'id' => array('Mandrill-activity-drupal-entity'),
),
'#weight' => -8,
);
// Prep the entity type list before creating the form item:
$entity_types = array('' => t('-- Select --'));
foreach (entity_get_info() as $entity_type => $info) {
// Ignore Mandrill entity types.
if (strpos($entity_type, 'mandrill') === FALSE) {
$entity_types[$entity_type] = $info['label'];
}
}
asort($entity_types);
$form['drupal_entity']['entity_type'] = array(
'#title' => t('Entity type'),
'#type' => 'select',
'#options' => $entity_types,
'#default_value' => $entitynotnull ? $mandrill_activity_entity->entity_type : 0,
'#required' => TRUE,
'#description' => t('Select an entity type to enable Mandrill history on.'),
'#ajax' => array(
'callback' => 'mandrill_activity_mapping_form_callback',
'wrapper' => 'Mandrill-activity-drupal-entity',
),
'#weight' => -8,
);
$form_entity_type = & $form_state['values']['entity_type'];
if (!$form_entity_type && $entitynotnull) {
$form_entity_type = $mandrill_activity_entity->entity_type;
}
if ($form_entity_type) {
// Prep the bundle list before creating the form item:
$bundles = array('' => t('-- Select --'));
$info = entity_get_info($form_entity_type);
foreach ($info['bundles'] as $key => $bundle) {
$bundles[$key] = $bundle['label'];
}
asort($bundles);
$form['drupal_entity']['bundle'] = array(
'#title' => t('Entity bundle'),
'#type' => 'select',
'#required' => TRUE,
'#description' => t('Select a Drupal entity bundle with an email address to report on.'),
'#options' => $bundles,
'#default_value' => $entitynotnull ? $mandrill_activity_entity->bundle : 0,
'#weight' => -7,
'#ajax' => array(
'callback' => 'mandrill_activity_mapping_form_callback',
'wrapper' => 'Mandrill-activity-drupal-entity',
),
);
$form_bundle = & $form_state['values']['bundle'];
if (!$form_bundle && $entitynotnull) {
$form_bundle = $mandrill_activity_entity->bundle;
}
if ($form_bundle) {
// Prep the field & properties list before creating the form item:
$fields = mandrill_activity_email_fieldmap_options($form_entity_type, $form_bundle);
$form['drupal_entity']['email_property'] = array(
'#title' => t('Email Property'),
'#type' => 'select',
'#required' => TRUE,
'#description' => t('Select the field which contains the email address'),
'#options' => $fields,
'#default_value' => $entitynotnull ? $mandrill_activity_entity->email_property : 0,
'#maxlength' => 127,
'#weight' => -6,
);
}
}
$form['enabled'] = array(
'#title' => t('Enabled'),
'#type' => 'checkbox',
'#default_value' => $entitynotnull ? $mandrill_activity_entity->enabled : TRUE,
'#weight' => -5,
);
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array(
'#value' => t('Save Entity'),
'#type' => 'submit',
);
return $form;
}
/**
* Ajax callback for mandrill_activity_mapping_form().
*/
function mandrill_activity_mapping_form_callback($form, &$form_state) {
return $form['drupal_entity'];
}
/**
* Validation callback for mandrill_activity_entity_form().
*/
function mandrill_activity_entity_form_validate($form, &$form_state) {
$extant_mc_entities = entity_load('mandrill_activity_entity');
$form_id = $form_state['mandrill_activity_entity']->mandrill_activity_entity_id;
$form_bundle = $form_state['values']['bundle'];
$form_entity_id = $form_state['values']['entity_type'];
foreach ($extant_mc_entities as $extant_ent) {
if ($form_bundle == $extant_ent->bundle &&
$form_entity_id == $extant_ent->entity_type &&
$form_id != $extant_ent->mandrill_activity_entity_id) {
form_set_error(
'bundle',
t('A Mandrill Activity Entity already exists for this Bundle. Either select a different Bundle or edit the !link for this bundle.',
array(
'!link' => l(t('existing Mandrill Activity Entity'), "admin/config/services/mandrill/activity/manage/{$extant_ent->name}"),
)
));
}
}
}
/**
* Submit handler for mandrill_activity_entity_form().
*/
function mandrill_activity_entity_form_submit($form, &$form_state) {
$values = $form_state['values'];
if ($form_state['op'] == 'add' || $form_state['op'] == 'clone') {
$dummy = entity_create($form_state['values']['entity_type'], array('type' => $form_state['values']['bundle']));
$uri = entity_uri($form_state['values']['entity_type'], $dummy);
$values['entity_path'] = $uri['path'];
$activity_entity = entity_create('mandrill_activity_entity', $values);
}
else {
$activity_entity = $form_state['mandrill_activity_entity'];
foreach ($values as $key => $val) {
$activity_entity->{$key} = $val;
}
}
$activity_entity->save();
// Ensure the new local task appears on the entity.
menu_rebuild();
$form_state['redirect'] = 'admin/config/services/Mandrill/activity';
}
/**
* Return all possible Drupal properties for a given entity type.
*
* @string $entity_type
* Name of entity whose properties to list.
* @string $entity_bundle
* Entity bundle to get properties for.
*
* @return array
* List of entities that can be used as an #options list.
*/
function mandrill_activity_email_fieldmap_options($entity_type, $entity_bundle = NULL) {
$options = array('' => t('-- Select --'));
$properties = entity_get_all_property_info($entity_type);
if (isset($entity_bundle)) {
$info = entity_get_property_info($entity_type);
$properties = $info['properties'];
if (isset($info['bundles'][$entity_bundle])) {
$properties += $info['bundles'][$entity_bundle]['properties'];
}
}
foreach ($properties as $key => $property) {
$type = isset($property['type']) ? entity_property_extract_innermost_type($property['type']) : 'text';
$is_entity = ($type == 'entity') || (bool) entity_get_info($type);
// Leave entities out of this.
if (!$is_entity) {
if (isset($property['field']) && $property['field'] && !empty($property['property info'])) {
foreach ($property['property info'] as $sub_key => $sub_prop) {
$options[$property['label']][$key . ':' . $sub_key] = $sub_prop['label'];
}
}
else {
$options[$key] = $property['label'];
}
}
}
return $options;
}

View File

@@ -0,0 +1,18 @@
name = Mandrill Activity
description = View activity for an email address associated with any entity.
package = Mandrill
core = 7.x
dependencies[] = mandrill
dependencies[] = entity
files[] = lib/mandrill_activity.entity.inc
files[] = lib/mandrill_activity.ui_controller.inc
; Information added by Drupal.org packaging script on 2014-05-09
version = "7.x-1.6"
core = "7.x"
project = "mandrill"
datestamp = "1399658028"

View File

@@ -0,0 +1,102 @@
<?php
/**
* @file
* Install hooks for mandrill_activity.
*/
/**
* Implements hook_schema().
*/
function mandrill_activity_schema() {
$schema['mandrill_activity_entity'] = array(
'description' => 'Mandrill activity enabled entities.',
'fields' => array(
'mandrill_activity_entity_id' => array(
'type' => 'serial',
'not null' => TRUE,
'description' => 'Primary Key: Unique mandrill_activity_entity entity ID.',
),
'name' => array(
'description' => 'The machine-readable name of this mandrill_activity_entity.',
'type' => 'varchar',
'length' => 128,
'not null' => TRUE,
),
'label' => array(
'description' => 'The human-readable name of this mandrill_activity_entity.',
'type' => 'varchar',
'length' => 128,
'not null' => TRUE,
),
'entity_type' => array(
'description' => 'The Drupal entity type (e.g. "node", "user").',
'type' => 'varchar',
'length' => 128,
'not null' => TRUE,
'default' => '',
),
'bundle' => array(
'description' => 'The Drupal bundle (e.g. "page", "user")',
'type' => 'varchar',
'length' => 128,
'not null' => TRUE,
'default' => '',
),
'entity_path' => array(
'description' => 'The path to view individual entities of the selected type.',
'type' => 'varchar',
'length' => 128,
'not null' => TRUE,
'default' => '',
),
'email_property' => array(
'description' => 'The property that contains the email address to track',
'type' => 'varchar',
'length' => 128,
'not null' => TRUE,
'default' => '',
),
'enabled' => array(
'type' => 'int',
'not null' => TRUE,
'default' => 0,
'size' => 'tiny',
'description' => 'Whether or not this Mandrill activity stream is enabled.',
),
// Following fields are for supporting exportable status.
'locked' => array(
'description' => 'A boolean indicating whether the administrator may delete this mapping.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
'size' => 'tiny',
),
'status' => array(
'type' => 'int',
'not null' => TRUE,
// Set the default to ENTITY_CUSTOM without using the constant as it is
// not safe to use it at this point.
'default' => 0x01,
'size' => 'tiny',
'description' => 'The exportable status of the entity.',
),
'module' => array(
'description' => 'The name of the providing module if the entity has been defined in code.',
'type' => 'varchar',
'length' => 255,
'not null' => FALSE,
),
),
'primary key' => array('mandrill_activity_entity_id'),
'unique keys' => array(
'name' => array('name'),
'entity_type_bundle' => array(
'entity_type',
'bundle',
),
),
);
return $schema;
}

View File

@@ -0,0 +1,198 @@
<?php
/**
* @file
* Main module functions for mandrill_activity.
*/
/**
* Implements hook_entity_info().
*/
function mandrill_activity_entity_info() {
$return = array(
'mandrill_activity_entity' => array(
'label' => t('Mandrill Activity Entity'),
'controller class' => 'EntityAPIControllerExportable',
'entity class' => 'MandrillActivityEntity',
'base table' => 'mandrill_activity_entity',
'uri callback' => 'entity_class_uri',
'fieldable' => FALSE,
'exportable' => TRUE,
'module' => 'mandrill_activity',
'entity keys' => array(
'id' => 'mandrill_activity_entity_id',
'name' => 'name',
'label' => 'label',
),
// Enable the entity API's admin UI.
'admin ui' => array(
'path' => 'admin/config/services/mandrill/activity',
'file' => 'mandrill_activity.admin.inc',
'controller class' => 'MandrillActivityUIController',
),
'label callback' => 'entity_class_label',
'access callback' => 'mandrill_activity_entity_access',
),
);
return $return;
}
/**
* Access callback for mandrill_activity_entity.
*/
function mandrill_activity_entity_access() {
return user_access('administer mandrill activity');
}
/**
* Implements hook_menu().
*/
function mandrill_activity_menu() {
$items = array();
$mandrill_activity_entities = mandrill_activity_load();
foreach ($mandrill_activity_entities as $mandrill_activity_entity) {
$arg = substr_count($mandrill_activity_entity->entity_path, '/');
$items[$mandrill_activity_entity->entity_path . '%entity_object/mandrill_activity'] = array(
'title' => 'Mandrill Activity',
'load arguments' => array($mandrill_activity_entity->entity_type),
'page callback' => 'mandrill_activity_page',
'page arguments' => array($arg, $mandrill_activity_entity),
'access callback' => 'mandrill_activity_access',
'access arguments' => array($mandrill_activity_entity),
'type' => MENU_LOCAL_TASK,
);
}
return $items;
}
/**
* Access callback for activity menu items.
*/
function mandrill_activity_access(MandrillActivityEntity $mandrill_activity_entity) {
if ($mandrill_activity_entity->enabled && user_access('access Mandrill activity')) {
return TRUE;
}
return FALSE;
}
/**
* Loads a single mandrill_activity_entity or all of them if no name provided.
*
* @string $name
* Machine name of Mandrill Activity Entity to load.
*
* @return MandrillActivityEntity|array
* Array of MandrillActivityEntity's or a single MandrillActivityEntity.
*/
function mandrill_activity_load($name = NULL) {
$types = entity_load_multiple_by_name('mandrill_activity_entity', isset($name) ? array($name) : FALSE);
return isset($name) ? reset($types) : $types;
}
/**
* Content for the Mandrill log tab on entity instance pages.
*
* @object $entity
* Entity to load activity for.
*
* @param MandrillActivityEntity $mandrill_activity_entity
* Loaded MandrillActivityEntity object.
*
* @return array
* Render array.
*/
function mandrill_activity_page($entity, MandrillActivityEntity $mandrill_activity_entity) {
$entity_wrapper = entity_metadata_wrapper($mandrill_activity_entity->entity_type, $entity);
$email_property = $mandrill_activity_entity->email_property;
$email_property_array = explode(':', $email_property);
$parent = $entity_wrapper;
foreach ($email_property_array as $drupal_field) {
if ($parent instanceof EntityListWrapper) {
$child_wrapper = $parent->get(0)->{$drupal_field};
}
else {
$child_wrapper = $parent->{$drupal_field};
}
$parent = $child_wrapper;
}
$email = $parent->value();
// Validate email address.
if (!valid_email_address($email)) {
return array(
'error_notice' => array(
'#markup' => t('%email does not contain a valid email address. Unable to lookup Mandrill activity history without a valid email.',
array('%email' => $email_property)
),
),
);
}
$header = array(
t('Subject'),
t('Timestamp'),
t('State'),
t('Opens'),
t('Clicks'),
t('Tags'),
);
$rows = array();
// Loop through all activities, creating rows for each.
$activity = mandrill_activity_get_activity($email);
foreach ($activity as $sent_email) {
$rows[] = array(
$sent_email['subject'],
format_date($sent_email['ts'], 'short'),
$sent_email['state'],
$sent_email['opens'],
$sent_email['clicks'],
implode(', ', $sent_email['tags']),
);
}
$display['mandrill_activity'] = array(
'message' => array(
'#markup' => t('The 100 most recent Emails sent to %email via Mandrill.', array('%email' => $email)),
),
'activity' => array(
'#theme' => 'table',
'#header' => $header,
'#rows' => $rows,
),
);
return $display;
}
/**
* Return all activity on all lists for a given email address.
*
* @string $email
* Email to load Mandrill activity for.
*
* @return array
* Array of activity.
*/
function mandrill_activity_get_activity($email) {
$mandrill = mandrill_get_api_object();
return $mandrill->messages_search("email:{$email}");
}
/**
* Implements hook_permission().
*/
function mandrill_activity_permission() {
$return = array();
$return['access mandrill activity'] = array(
'title' => t('Access Mandrill activity'),
'description' => t('View own Mandrill activity history.'),
);
$return['administer mandrill activity'] = array(
'title' => t('Administer Mandrill activity entities'),
'description' => t('Add, Delete, and Configure Mandrill Activity entity settings.'),
);
return $return;
}

View File

@@ -0,0 +1,13 @@
name = Mandrill Reports
description = "Providing reporting on activity through Mandrill."
core = 7.x
package = MailChimp
dependencies[] = mandrill
; Information added by Drupal.org packaging script on 2014-05-09
version = "7.x-1.6"
core = "7.x"
project = "mandrill"
datestamp = "1399658028"

View File

@@ -0,0 +1,50 @@
(function ($) {
Drupal.behaviors.mandrill_reports = {
attach: function (context, settings) {
google.load("visualization", "1", {packages:["corechart"], "callback":drawCharts});
function drawCharts() {
var dataTableVol = new google.visualization.DataTable();
dataTableVol.addColumn('datetime', Drupal.t('Date'));
dataTableVol.addColumn('number', Drupal.t('Delivered'));
dataTableVol.addColumn('number', Drupal.t('Bounced'));
dataTableVol.addColumn('number', Drupal.t('Rejected'));
for (var key in settings.mandrill_reports.volume) {
dataTableVol.addRow([
new Date(settings.mandrill_reports.volume[key]['date']),
settings.mandrill_reports.volume[key]['sent'],
settings.mandrill_reports.volume[key]['bounced'],
settings.mandrill_reports.volume[key]['rejected']
]);
}
var options = {
pointSize: 5,
hAxis: {format: 'MM/dd/y hh:mm aaa'}
};
var chart = new google.visualization.LineChart(document.getElementById('mandrill-volume-chart'));
chart.draw(dataTableVol, options);
var dataTableEng = new google.visualization.DataTable();
dataTableEng.addColumn('datetime', Drupal.t('Date'));
dataTableEng.addColumn('number', Drupal.t('Open rate'));
dataTableEng.addColumn('number', Drupal.t('Click rate'));
for (var key in settings.mandrill_reports.engagement) {
dataTableEng.addRow([
new Date(settings.mandrill_reports.engagement[key]['date']),
settings.mandrill_reports.engagement[key]['open_rate'],
settings.mandrill_reports.engagement[key]['click_rate']
]);
}
var chart = new google.visualization.LineChart(document.getElementById('mandrill-engage-chart'));
chart.draw(dataTableEng, options);
}
}
}
})(jQuery);

View File

@@ -0,0 +1,255 @@
<?php
/**
* @file
* Main module functions for mandrill_reports.
*/
/**
* Implements hook_menu().
*/
function mandrill_reports_menu() {
$items = array();
$items['admin/reports/mandrill'] = array(
'title' => 'Mandrill',
'page callback' => 'mandrill_reports_dashboard_page',
'access arguments' => array('view mandrill reports'),
'description' => 'View Mandrill dashboard.',
);
$items['admin/reports/mandrill/dashboard'] = array(
'title' => 'Dashboard',
'type' => MENU_DEFAULT_LOCAL_TASK,
'weight' => -10,
);
$items['admin/reports/mandrill/summary'] = array(
'title' => 'Account summary',
'page callback' => 'mandrill_reports_summary_page',
'access arguments' => array('view mandrill reports'),
'description' => 'View account summary.',
'type' => MENU_LOCAL_TASK,
);
return $items;
}
/**
* Implements hook_permission().
*/
function mandrill_reports_permission() {
return array(
'view mandrill reports' => array(
'title' => t('View Mandrill reports'),
'description' => t('View all Mandrill reports.'),
),
);
}
/**
* Return an associative array containing raw stats.
*
* @return array
* Associative array containing report data.
*/
function mandrill_reports_data() {
if ($cache = cache_get('mandrill_report_data')) {
return $cache->data;
}
$data = array();
$api = mandrill_get_api_object();
if ($api) {
// Basic user info.
$data['user'] = $api->users_info();
// Tags.
$tags = $api->tags_list();
foreach ($tags as $tag) {
if (!empty($tag['tag'])) {
$data['tags'][$tag['tag']] = $api->tags_info($tag['tag']);
$data['tags'][$tag['tag']]['time_series'] = $api->tags_time_series($tag['tag']);
}
}
// All time.
$data['all_time_series'] = $api->tags_all_time_series();
// Senders: data is not being used, and the API currently (3/12/13) behaves
// badly if you have a bad email address anywhere in your sender history, so
// commenting out for now:
$senders = $api->senders_list();
foreach ($senders as $sender) {
try {
$data['senders'][$sender['address']] = $api->senders_info($sender['address']);
$data['senders'][$sender['address']]['time_series'] = $api->senders_time_series($sender['address']);
}
catch (MandrillException $e) {
watchdog('mandrill', 'An error occurred requesting sender information from Mandrill for address %address. "%message"', array(
'%address' => $sender['address'],
'%message' => $e->getMessage(),
), WATCHDOG_ERROR);
}
}
// Urls.
$urls = $api->urls_list();
foreach ($urls as $url) {
// Api has been intermittently tacking on incomplete $url arrays,
// so we have to check validity first:
if (isset($url['url'])) {
$data['urls'][$url['url']] = $url;
$data['urls'][$url['url']]['time_series'] = $api->urls_time_series($url['url']);
}
}
cache_set('mandrill_report_data', $data, 'cache', CACHE_TEMPORARY);
}
else {
drupal_set_message(t('Please enter a Mandrill API key to use reports.'));
drupal_goto('admin/config/services/mandrill');
}
return $data;
}
/**
* Page callback for rendering stats.
*
* @return array
* Render array.
*/
function mandrill_reports_dashboard_page() {
$data = mandrill_reports_data();
$settings = array();
// All time series chart data.
foreach ($data['all_time_series'] as $series) {
$settings['mandrill_reports']['volume'][] = array(
'date' => $series['time'],
'sent' => $series['sent'],
'bounced' => $series['hard_bounces'] + $series['soft_bounces'],
'rejected' => $series['rejects'],
);
$settings['mandrill_reports']['engagement'][] = array(
'date' => $series['time'],
'open_rate' => $series['sent'] == 0 ? 0 : $series['unique_opens'] / $series['sent'],
'click_rate' => $series['sent'] == 0 ? 0 : $series['unique_clicks'] / $series['sent'],
);
}
// Url table.
$rows = array();
$header = array(
t('URL'),
t('Delivered'),
t('Unique clicks'),
t('Total Clicks'),
);
foreach ($data['urls'] as $url) {
$percent = number_format($url['unique_clicks'] / $url['sent'], 2) * 100;
$rows[] = array(
l($url['url'], $url['url']),
$url['sent'],
$url['unique_clicks'] . "({$percent}%)",
$url['clicks']);
}
$path = drupal_get_path('module', 'mandrill_reports');
$render = array(
'#attached' => array(
'js' => array(
array(
'data' => 'https://www.google.com/jsapi',
'type' => 'external',
),
$path . '/mandrill_reports.js',
array(
'data' => $settings,
'type' => 'setting',
),
),
),
'message' => array(
'#markup' => t(
'The following reports are based on Mandrill data from the last 30 days. For more comprehensive data, please visit your !dashboard. !cache to ensure the data is current.',
array(
'!dashboard' => l(t('Mandrill Dashboard'), 'https://mandrillapp.com/'),
'!cache' => l(t('Clear your cache'), 'admin/config/development/performance'),
)
),
),
'volume' => array(
'#prefix' => '<h2>' . t('Sending Volume') . '</h2>',
'#markup' => '<div id="mandrill-volume-chart"></div>',
),
'engagement' => array(
'#prefix' => '<h2>' . t('Average Open and Click Rate') . '</h2>',
'#markup' => '<div id="mandrill-engage-chart"></div>',
),
'urls' => array(
'#prefix' => '<h2>' . t('Tracked URLs') . '</h2>',
'#theme' => 'table',
'#header' => $header,
'#rows' => $rows,
),
);
return $render;
}
/**
* Displays summary information for the active API user.
*/
function mandrill_reports_summary_page() {
$data = mandrill_reports_data();
$info = $data['user'];
$header = array(
t('Range'),
t('Sent'),
t('hard_bounces'),
t('soft_bounces'),
t('Rejects'),
t('Complaints'),
t('Unsubs'),
t('Opens'),
t('unique_opens'),
t('Clicks'),
t('unique_clicks'),
);
$rows = array();
foreach ($info['stats'] as $key => $stat) {
$rows[] = array(
str_replace('_', ' ', $key),
$stat['sent'],
$stat['hard_bounces'],
$stat['soft_bounces'],
$stat['rejects'],
$stat['complaints'],
$stat['unsubs'],
$stat['opens'],
$stat['unique_opens'],
$stat['clicks'],
$stat['unique_clicks'],
);
}
$render = array(
'info' => array(
'#theme' => 'table',
'#rows' => array(
array(t('Username'), $info['username']),
array(t('Reputation'), $info['reputation']),
array(t('Hourly quota'), $info['hourly_quota']),
array(t('Backlog'), $info['backlog']),
),
'#header' => array(t('Attribute'), t('Value')),
'#caption' => t('Active API user information.'),
),
'stats' => array(
'#theme' => 'table',
'#rows' => $rows,
'#header' => $header,
'#caption' => t("This table contains an aggregate summary of the account's sending stats."),
),
);
return $render;
}

View File

@@ -0,0 +1,48 @@
<?php
/**
* @file
* Mandrill Template entity class
*/
class MandrillTemplateMap extends Entity {
public
$mandrill_template_map_entity_id,
$name,
$label,
$mailsystem_key,
$template_id,
$main_section,
$sections;
/**
* Return a new entity instance of type mandrill_template_map.
*/
public function __construct(array $values = array()) {
parent::__construct($values, 'mandrill_template_map');
}
/**
* Specify URI.
*/
protected function defaultUri() {
return array('path' => 'admin/config/services/mandrill/templates/' . $this->identifier());
}
/**
* Generate the arguments that are required for sending templates.
*
* @return array
* An associative array containing message template arguments.
*/
public function message_send_arguments($message) {
return array(
'id' => $this->template_id,
'template_content' => array(
'0' => array(
'name' => $this->main_section,
'content_postprocess' => 'html',
),
),
);
}
}

View File

@@ -0,0 +1,42 @@
<?php
/**
* @file
* The UI controller for the template map entity.
*/
/**
* Override EntityDefaultUIController to customize our menu items.
*/
class MandrillTemplateMapUIController extends EntityDefaultUIController {
/**
* Overrides EntityDefaultUIController::hook_menu().
*/
public function hook_menu() {
$items = parent::hook_menu();
$items[$this->path]['description'] = 'Manage Mandrill Template Mapping entity settings.';
$items[$this->path]['type'] = MENU_LOCAL_TASK;
return $items;
}
/**
* Overrides EntityDefaultUIController::overviewTableHeaders().
*/
protected function overviewTableHeaders($conditions, $rows, $additional_header = array()) {
$additional_header[] = t('Mandrill Template');
$additional_header[] = t('Primary Content Zone');
$additional_header[] = t('In Use By');
$header = parent::overviewTableHeaders($conditions, $rows, $additional_header);
return $header;
}
/**
* Overrides EntityDefaultUIController::overviewTableRow().
*/
protected function overviewTableRow($conditions, $id, $entity, $additional_cols = array()) {
$additional_cols[] = $entity->template_id;
$additional_cols[] = $entity->main_section;
$additional_cols[] = $entity->mailsystem_key;
return parent::overviewTableRow($conditions, $id, $entity, $additional_cols);
}
}

View File

@@ -0,0 +1,207 @@
<?php
/**
* @file
* Administrative forms for Mandrill Template module.
*/
/**
* Return a form for adding/editing a Mandrill template map.
*/
function mandrill_template_map_form($form, &$form_state, MandrillTemplateMap $map = NULL, $op = 'edit') {
$form_state['op'] = $op;
if ($form_state['op'] == 'clone') {
$map->label .= ' (cloned)';
$map->name = '';
}
// Store the existing map for updating on submit.
if (isset($map)) {
$form_state['map'] = $map;
}
else {
$form_state['op'] = 'add';
}
$form['label'] = array(
'#type' => 'textfield',
'#title' => t('Label'),
'#description' => t('The name of this Template Mapping.'),
'#size' => 35,
'#maxlength' => 32,
'#default_value' => $map ? $map->label : '',
'#required' => TRUE,
);
// Machine-readable list name.
$form['name'] = array(
'#type' => 'machine_name',
'#default_value' => isset($map->name) ? $map->name : '',
'#maxlength' => 32,
'#machine_name' => array(
'exists' => 'mandrill_template_map_load_entities',
'source' => array('label'),
),
'#description' => t('A unique machine-readable name for this template map. It must only contain lowercase letters, numbers, and underscores.'),
);
$form['map_settings'] = array(
'#type' => 'fieldset',
'#title' => t('Template Map Settings'),
'#collapsible' => FALSE,
'#attributes' => array(
'id' => array('mandrill-template-mapping'),
),
);
$templates = mandrill_get_templates();
$template_names = array();
foreach ($templates as $template) {
$template_names[$template['slug']] = $template;
}
// Check if the currently configured template still exists.
if (!empty($map->template_id) && !array_key_exists($map->template_id, $template_names)) {
drupal_set_message(t('The configured Mandrill template is no longer available, please select a valid one.'), 'warning');
}
if (!empty($templates)) {
$options = array('' => t('-- Select --'));
foreach ($templates as $template) {
$options[$template['slug']] = $template['name'];
}
$form['map_settings']['template_id'] = array(
'#type' => 'select',
'#title' => t('Email Template'),
'#description' => t('Select a Mandrill template.'),
'#options' => $options,
'#default_value' => isset($map->template_id) ? $map->template_id : '',
'#required' => TRUE,
'#ajax' => array(
'callback' => 'mandrill_template_map_form_callback',
'wrapper' => 'mandrill-template-mapping',
),
);
$form_template_id = & $form_state['values']['template_id'];
if (!$form_template_id && isset($map->mandrill_template_map_entity_id)) {
$form_template_id = $map->template_id;
}
if ($form_template_id) {
$regions = array('' => t('-- Select --')) + _mandrill_parse_regions($template_names[$form_template_id]['publish_code']);
$form['map_settings']['main_section'] = array(
'#type' => 'select',
'#title' => t('Template region'),
'#description' => t('Select the template region to use for email content. <i>Note that you can populate more regions by attaching an array to your message with the index "mandrill_template_content", using region names as indexes to the content for that region.'),
'#options' => $regions,
'#default_value' => isset($map->main_section) ? $map->main_section : '',
'#required' => TRUE,
);
}
$usable_keys = mandrill_template_map_usage();
$module_names = mandrill_get_module_key_names();
$mandrill_in_use = FALSE;
$available_modules = FALSE;
$mailsystem_options = array('' => t('-- None --'));
foreach ($usable_keys as $key => $sys) {
$mandrill_in_use = TRUE;
if ($sys === NULL || (isset($map) && $sys == $map->mandrill_template_map_entity_id)) {
$mailsystem_options[$key] = $module_names[$key];
$available_modules = TRUE;
}
}
if ($mandrill_in_use) {
$form['mailsystem_key'] = array(
'#type' => 'select',
'#title' => t('Email key'),
'#description' => t(
'Select a module and mail key to use this template for outgoing email. Note that if an email has been selected in another Template Mapping, it will not appear in this list. These keys are defined through the !MailSystem interface.',
array('!MailSystem' => l(t('MailSystem'), 'admin/config/system/mailsystem'))
),
'#options' => $mailsystem_options,
'#default_value' => isset($map->mailsystem_key) ? $map->mailsystem_key : '',
);
if (!$available_modules) {
drupal_set_message(t("All email-using modules that have been assigned to Mandrill are already assigned to other template maps"), 'warning');
}
}
if (!$mandrill_in_use) {
drupal_set_message(t("You have not assigned any Modules to use Mandrill: to use this template, make sure Mandrill is assigned in Mailsystem."), 'warning');
}
}
else {
$form['email_options']['#description'] = t('The template selection is only available if the Mandrill API is correctly configured and available.');
}
$form['actions'] = array('#type' => 'actions');
$form['actions']['save'] = array(
'#type' => 'submit',
'#value' => t('Save'),
);
$form['actions']['cancel'] = array(
'#type' => 'link',
'#title' => t('Cancel'),
'#href' => 'admin/config/services/mandrill/templates',
);
return $form;
}
/**
* Validate handler for mandrill_template_map().
*/
function mandrill_template_map_form_validate($form, &$form_state) {
$values = $form_state['values'];
// Test to see if mandrill_template_map value will exceed row length of 255
// characters
if (strlen($values['mailsystem_key']) > 255) {
form_set_error('mailsystem_key', t('The length of the mailsytem_key name is too long.'));
}
}
/**
* Submit handler for mandrill_template_map(), creates the entity.
*/
function mandrill_template_map_form_submit($form, &$form_state) {
$values = $form_state['values'];
if ($form_state['op'] == 'add' || $form_state['op'] == 'clone') {
$map = entity_create('mandrill_template_map', $values);
}
else {
$map = $form_state['mandrill_template_map'];
foreach ($values as $key => $val) {
$map->{$key} = $val;
}
}
$map->save();
drupal_set_message(t('Mandrill template mapping saved.'));
$form_state['redirect'] = 'admin/config/services/mandrill/templates';
}
/**
* Javascript callback for the mandrill_template_map_form.
*/
function mandrill_template_map_form_callback($form, &$form_state) {
return $form['map_settings'];
}
/**
* Parse a Mandrill template to extract its regions.
*
* The Mandrill API does not provide an elegant data structure
* containing the mc:edit tags for a given template, but rather
* a big pile of ugly HTML containing the tags. We need to parse
* through it.
*/
function _mandrill_parse_regions($html, $tag = 'mc:edit') {
$instances = array();
$offset = 0;
$inst = NULL;
while ($offset = strpos($html, $tag, $offset)) {
$start = 1 + strpos($html, '"', $offset);
$length = strpos($html, '"', $start) - $start;
$inst = substr($html, $start, $length);
$instances[$inst] = $inst;
$offset = $start + $length;
}
return $instances;
}

View File

@@ -0,0 +1,19 @@
name = Mandrill Template
description = "Use Mandrill templates for messages sent through Mandrill."
core = 7.x
package = MailChimp
configure = admin/config/services/mandrill/templates
dependencies[] = entity (>=1.0)
dependencies[] = mandrill
files[] = lib/mandrill_template_map.entity.inc
files[] = lib/mandrill_template_map.ui_controller.inc
; Information added by Drupal.org packaging script on 2014-05-09
version = "7.x-1.6"
core = "7.x"
project = "mandrill"
datestamp = "1399658028"

View File

@@ -0,0 +1,131 @@
<?php
/**
* @file
* Install, update and uninstall functions for the mandrill_template module.
*/
/**
* Implements hook_schema().
*/
function mandrill_template_schema() {
$schema['mandrill_template_map'] = array(
'description' => 'The base table for the mandrill template module.',
'fields' => array(
'mandrill_template_map_entity_id' => array(
'description' => 'The primary identifier for a mandrill_template_map.',
'type' => 'serial',
'unsigned' => TRUE,
'not null' => TRUE,
),
'name' => array(
'description' => 'The name of this mandrill_template_map',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'label' => array(
'description' => 'The label for this mandrill_template_map',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'mailsystem_key' => array(
'description' => 'The mailsystem_key that is using this mandrill_template_map.',
'type' => 'varchar',
'length' => 255,
'not null' => FALSE,
'default' => '',
),
'template_id' => array(
'description' => 'The unique identifier of the Mandrill Template this mapping uses.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'main_section' => array(
'description' => 'The name of the section where primary email content should go.',
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
),
'author_uid' => array(
'description' => 'The uid of the user who created this mandrill_template_map.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'sections' => array(
'description' => 'An array of content sections for the template.',
'type' => 'blob',
'size' => 'big',
'not null' => TRUE,
'serialize' => TRUE,
),
'created' => array(
'description' => 'The Unix timestamp when the mandrill_template_map was created.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
'updated' => array(
'description' => 'The Unix timestamp when the mandrill_template_map was most recently saved.',
'type' => 'int',
'not null' => TRUE,
'default' => 0,
),
// Following fields are for supporting exportable status.
'status' => array(
'type' => 'int',
'not null' => TRUE,
// Set the default to ENTITY_CUSTOM without using the constant as it is
// not safe to use it at this point.
'default' => 0x01,
'size' => 'tiny',
'description' => 'The exportable status of the entity.',
),
'module' => array(
'description' => 'The name of the providing module if the entity has been defined in code.',
'type' => 'varchar',
'length' => 255,
'not null' => FALSE,
),
),
'indexes' => array(
'mandrill_template_map_updated' => array('updated'),
'mandrill_template_map_created' => array('created'),
'mandrill_template_map_author' => array('author_uid'),
),
'unique keys' => array(
'mandrill_template_map_entity_id' => array('mandrill_template_map_entity_id'),
),
'foreign keys' => array(
'mandrill_template_map_author' => array(
'table' => 'users',
'columns' => array('author_uid' => 'uid'),
),
),
'primary key' => array('mandrill_template_map_entity_id'),
);
return $schema;
}
/**
* Extends the mailsystem_key field to support longer template key names.
*/
function mandrill_template_update_7001() {
db_change_field('mandrill_template_map', 'mailsystem_key', 'mailsystem_key',
array(
'description' => 'The mailsystem_key that is using this mandrill_template_map.',
'type' => 'varchar',
'length' => 255,
'not null' => FALSE,
'default' => '',
)
);
}

View File

@@ -0,0 +1,218 @@
<?php
/**
* @file
* Enables Drupal to send email using Mandrill's template system.
*/
/**
* Implements hook_entity_info().
*/
function mandrill_template_entity_info() {
$return = array(
'mandrill_template_map' => array(
'label' => t('Mandrill Template Map'),
'plural label' => t('Mandrill Template Maps'),
'controller class' => 'EntityAPIControllerExportable',
'entity class' => 'MandrillTemplateMap',
'base table' => 'mandrill_template_map',
'uri callback' => 'entity_class_uri',
'fieldable' => FALSE,
'exportable' => TRUE,
'module' => 'mandrill_template',
'access callback' => 'mandrill_template_map_access',
'entity keys' => array(
'id' => 'mandrill_template_map_entity_id',
'label' => 'label',
'name' => 'name',
),
'admin ui' => array(
'path' => 'admin/config/services/mandrill/templates',
'file' => 'mandrill_template.admin.inc',
'controller class' => 'MandrillTemplateMapUIController',
),
),
);
return $return;
}
/**
* Access callback for mandrill_template_map.
*
* @return bool
* True if current user has acces to template maps, else false
*/
function mandrill_template_map_access() {
$a = user_access('configure mandrill templates');
$b = variable_get('mandrill_api_key');
return $a & !empty($b);
}
/**
* Implements hook_permission().
*/
function mandrill_template_permission() {
return array(
'configure mandrill templates' => array(
'title' => t('Configure Mandrill Templates'),
'description' => t('Select & configure which Mandrill Templates to use for messages going through Mandrill.'),
"restrict access" => FALSE,
),
);
}
/**
* Loads a single mandrill_template_map or all of them if no name provided.
*
* @param null $name
* name of the template map entity
*
* @return array
* array of template_map entities
*/
function mandrill_template_map_load_entities($name = NULL) {
$maps = entity_load_multiple_by_name('mandrill_template_map', isset($name) ? array($name) : FALSE);
return isset($name) ? reset($maps) : $maps;
}
/**
* Tells you which template_map is configured for a mailsystem key, if any.
*
* Will search for a default-system mapping if none is availble for a given key.
*
* @param string $mailsystem
* Mail key to search for a template mapping.
*
* @return entity
* Mandrill template map.
*/
function mandrill_template_map_load_by_mailsystem($mailsystem) {
// Append the default-system condition as a fallback.
$params = array($mailsystem, 'default-system');
$query = new EntityFieldQuery();
$query_result = $query
->entityCondition('entity_type', 'mandrill_template_map')
->propertyCondition('mailsystem_key', $params, 'IN')
->execute();
if (!empty($query_result['mandrill_template_map'])) {
$template_maps = entity_load('mandrill_template_map', array_keys($query_result['mandrill_template_map']));
if (count($template_maps) > 1) {
foreach ($template_maps as $template_map) {
if ($template_map->mailsystem_key == $mailsystem) {
return $template_map;
}
}
}
else {
return reset($template_maps);
}
}
return NULL;
}
/**
* Get all mailsystem keys pointed at mandrill and their template mappings.
*
* @return array
* Returns an array with indexes matching each module which is assigned
* to use Mandrill for email sends, and values equal to the template_map_ids
* that are assigned to those modules. If no template maps are assigned, the
* value is set to NULL.
*/
function mandrill_template_map_usage() {
$system_assignments = mailsystem_get();
// Filter out the systems that aren't using Mandrill:
foreach ($system_assignments as $system => $assignment) {
if ($assignment != 'MandrillMailSystem') {
unset($system_assignments[$system]);
}
else {
$system_assignments[$system] = NULL;
}
}
$maps = mandrill_template_map_load_entities();
foreach ($maps as $map) {
if (isset($map->mailsystem_key) && array_key_exists($map->mailsystem_key, $system_assignments)) {
$system_assignments[$map->mailsystem_key] = $map->mandrill_template_map_entity_id;
}
}
uksort($system_assignments, '_mandrill_template_map_mailsystem_sort');
return $system_assignments;
}
/**
* Implements hook_mandrill_mail_alter().
*
* Determine if an email is configured to use a Mandrill template and change the
* mandrill_send_function parameter as needed.
*/
function mandrill_template_mandrill_mail_alter(&$mandrill_params, $message) {
$template_map = mandrill_template_map_load_by_mailsystem($message['id']);
if ($template_map) {
$mandrill_params['function'] = 'mandrill_template_sender';
$mandrill_params['args'] = array(
'template_id' => $template_map->template_id,
'template_content' => array(
array(
'name' => $template_map->main_section,
'content' => $message['body'],
),
),
);
if (isset($message['mandrill_template_content'])) {
$mandrill_params['args']['template_content'] = array_merge($message['mandrill_template_content'], $mandrill_params['args']['template_content']);
}
}
}
/**
* Send a templated Mandrill message.
*
* This function checks for appropriate settings in the message, then uses the
* template API call to send the message if the settings are valid.
*
* @param array $message
* Mandrill message to send.
* @param string $template_id
* Name of the template to use.
* @param array $template_content
* Associative array mapping template regions and content.
*
* @return array
* Message response.
*
* @throws MandrillException
*/
function mandrill_template_sender($message, $template_id, $template_content) {
if ($mailer = mandrill_get_api_object()) {
return $mailer->messages_send_template($template_id, $template_content, $message);
}
return NULL;
}
/**
* Simple sorting algorithm to organize mailsystems arrays in a logical way.
*
* @param string $a
* a mailsystem key name
* @param string $b
* a mailsystem key name
*
* @return int
* Negative if $a should come before $b, else positive.
*/
function _mandrill_template_map_mailsystem_sort($a, $b) {
$first = "default-system";
$last = "mandrill_test";
if ($a == $first || $b == $last) {
return -1;
}
if ($b == $first || $a == $last) {
return 1;
}
// Otherwise sort alphabetically, case-agnostic
return strcasecmp($a, $b);
}

View File

@@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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.
<signature of Ty Coon>, 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.

View File

@@ -0,0 +1,15 @@
name = Mandrill Simplenews Report
description = Provides user (email address) centric reports. Includes total number of newsletters sent, last interaction date, list of newsletters sent with their subject, open and click count based on reports pulled from Mandrill web services.
core = 7.x
package = Mandrill
dependencies[] = simplenews
dependencies[] = elysia_cron
dependencies[] = mandrill
files[] = mandrill_simplenews_report.mandrill.inc
; Information added by Drupal.org packaging script on 2014-01-22
version = "7.x-1.0-alpha+0-dev"
core = "7.x"
project = "mandrill_simplenews_report"
datestamp = "1390384106"

View File

@@ -0,0 +1,103 @@
<?php
/**
* @file
* Creates the table with mail, count of emails sent and tid fields.
*/
/**
* Implements hook_schema().
*/
function mandrill_simplenews_report_schema() {
$schema['mandrill_simplenews_report_newsletter_report'] = array(
'fields' => array(
'id' => array(
'type' => 'serial',
'not null' => TRUE,
),
'report_id' => array(
'type' => 'varchar',
'length' => 32,
'not null'=> TRUE,
),
'tid' => array(
'type' => 'int',
'not null' => TRUE,
),
'created' => array(
'type' => 'int',
'not null' => TRUE,
),
),
'primary key' => array('id'),
'mysql_engine' => 'MyISAM',
);
$schema['mandrill_simplenews_report_newsletter_sent'] = array(
'fields' => array(
'id' => array(
'type' => 'serial',
'not null' => TRUE,
),
'sent_timestamp' => array(
'type' => 'int',
'not null' => TRUE,
),
'sent_to_mail' => array(
'type' => 'varchar',
'length' => 254,
'not null'=> TRUE,
),
'from_mail' => array(
'type' => 'varchar',
'length' => 254,
'not null' => TRUE,
),
'subject' => array(
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
),
'tid' => array(
'type' => 'int',
'not null' => TRUE,
),
'open_count' => array(
'type' => 'int',
'not null' => TRUE,
),
'click_count' => array(
'type' => 'int',
'not null' => TRUE,
),
),
'primary key' => array('id'),
'indexes' => array(
'sent_timestamp' => array('sent_timestamp'),
'sent_to_mail' => array('sent_to_mail'),
'from_mail' => array('from_mail'),
),
'mysql_engine' => 'MyISAM',
);
$schema['mandrill_simplenews_report_subscription_extra'] = array(
'fields' => array(
'snid' => array(
'type' => 'int',
'not null' => TRUE,
),
'tid' => array(
'type' => 'int',
'not null' => TRUE,
),
'last_interaction' => array(
'type' => 'int',
'not null' => TRUE,
),
),
'indexes' => array(
'snid' => array('snid'),
'tid' => array('tid'),
),
);
return $schema;
}

View File

@@ -0,0 +1,151 @@
<?php
/**
* @file
* Class file that extends mandrill api class.
*/
class MandrillSimplenewsReport extends Mandrill {
/**
* Request mandrill to prepare report and import the same to local table
*/
public function activity_report_import($date_from = NULL, $date_to = NULL) {
// Part #1: Initiate request to export most recent report
// (part #2: To save the same in local table
// when request state changes from "waiting" to "completed").
// By default request for last 24 hours report.
if (!isset($data_from)) {
$date_from = REQUEST_TIME - 86400;
}
if (!isset($date_to)) {
$date_to = REQUEST_TIME;
}
// Request report per newsletter category, as the report returned is not
// very structured. This helps us to see the total sent/open count by
// newsletter for the instance, when same email address is subscribed
// to more than one newsletter.
$tids = db_query('SELECT tid, from_address FROM simplenews_category');
foreach ($tids as $tid) {
$_params = array(
'date_from' => format_date($date_from, 'custom', 'Y-m-d H:i:s'),
'date_to' => format_date($date_to, 'custom', 'Y-m-d H:i:s'),
'senders' => array($tid->from_address),
'states' => array('sent'),
'tags' => array('simplenews_node'),
);
// Initiate report export request
$new_request = $this->request('exports/activity.json', $_params);
if (is_array($new_request) && empty($new_request['id'])) {
$message = 'Unexpected response from Mandrill "exports/activity.json" api call';
$message .= '<br/><pre>' . print_r($new_request, TRUE) . '</pre>';
watchdog('mandrill_simplenews_report', $message, array(), WATCHDOG_ERROR);
continue;
}
// Save the request id to queue table until it changes from
// "waiting" to "completed" state.
db_insert('mandrill_simplenews_report_newsletter_report')
->fields(array(
'report_id' => $new_request['id'],
'tid' => $tid->tid,
'created' => strtotime($new_request['created_at']),
))
->execute();
}
// End of part #1.
// Part #2: Check if activity.csv is ready for already requested reports.
// Fetch the requets id from local table, download the report, if ready
// import the same data to {mandrill_simplenews_report_newsletter_sent} table.
$reports = db_query_range('SELECT report_id, tid
FROM {mandrill_simplenews_report_newsletter_report}
WHERE created < :one_hour
ORDER BY created ASC', 0, 10, array(':one_hour' => REQUEST_TIME - 3600));
$import_count = 0;
foreach ($reports as $report) {
if ($import_count >= 4) {
// To prevent overloadding the server with too many imports.
watchdog('mandrill_simplenews_report', 'Reports import job completed Successfully.');
return;
}
$info = $this->request('exports/info.json', array('id' => $report->report_id));
if (isset($info['state']) && $info['state'] != 'complete') {
// Report is not ready yet.
continue;
}
if ($info['state'] == 'complete' && !valid_url($info['result_url'])) {
$message = 'Unexpected response from Mandrill "exports/info.json" api call';
$message .= '<br/><pre>' . print_r($info, TRUE) . '</pre>';
watchdog('mandrill_simplenews_report', $message, array(), WATCHDOG_ERROR);
continue;
}
// Converts Remote URL to Drupal stream wrapper path
$file_srp = system_retrieve_file($info['result_url']);
// Get Drupal stream wrapper path to local file path
$local_file = drupal_realpath($file_srp);
$za = new ZipArchive();
$res = $za->open($local_file);
if ($res != TRUE) {
$message = 'ZipArchive is unable to open file (local path) !local_file
(file stream url !file_srp, mandrill export url !result_url)';
$watchdog_variables = array(
'!local_file' => $local_file,
'!file_srp' => $file_srp,
'!result_url' => $info['result_url'],
);
watchdog('mandrill_simplenews_report', $message, $watchdog_variables, WATCHDOG_ERROR);
continue;
}
$extract_to = str_replace('.zip', '', $local_file);
$za->extractTo($extract_to);
$za->close();
$csv_file = $extract_to . "/activity.csv";
if (($handle = fopen($csv_file, "r")) == FALSE) {
$message = 'Unable to open activity.csv file (local path) !local_file
(file stream url !file_srp, mandrill export url !result_url, extracted to
folder !extracted_to, csv file full path !csv_file)';
$watchdog_variables = array(
'!local_file' => $local_file,
'!file_srp' => $file_srp,
'!result_url' => $info['result_url'],
'!extracted_to' => $extract_to,
'!csv_file' => $csv_file,
);
watchdog('mandrill_simplenews_report', $message, $watchdog_variables, WATCHDOG_ERROR);
continue;
}
$line = 0;
while (($data = fgetcsv($handle, 1000, ",")) !== FALSE) {
if ($line != 0) {
db_merge('mandrill_simplenews_report_newsletter_sent')
->key(array(
'sent_timestamp' => strtotime($data[0]),
'sent_to_mail' => $data[1],
'tid' => $report->tid,
))
->fields(array(
'sent_timestamp' => strtotime($data[0]),
'sent_to_mail' => $data[1],
'from_mail' => $data[2],
'subject' => $data[3],
'tid' => $report->tid,
'open_count' => $data[7],
'click_count' => $data[8],
))
->execute();
}
$line++;
}
fclose($handle);
// Delete the report .zip file and extracted folder.
file_unmanaged_delete_recursive($file_srp);
file_unmanaged_delete_recursive($extract_to);
db_delete('mandrill_simplenews_report_newsletter_report')
->condition('report_id', $report->report_id)
->execute();
$import_count++;
}
}
}

View File

@@ -0,0 +1,253 @@
<?php
/**
* @file
* Mandrill simplenews user activity report.
* Provides user (email address) centric reports. Includes total number of
* newsletters sent, last interaction date, list of newsletters sent with
* their subject, open and click count based on report pulled from
* Mandrill web services.
*/
/**
* Implements hook_menu().
*/
function mandrill_simplenews_report_menu() {
$items = array();
$items['admin/reports/mandrill/simplenews'] = array(
'title' => 'Simplenews',
'page callback' => array('mandrill_simplenews_report_newsletter'),
'access arguments' => array('view mandrill reports'),
'type' => MENU_LOCAL_TASK,
);
$items['admin/reports/mandrill/simplenews/%taxonomy_term'] = array(
'title' => 'Newsletter subscription',
'page callback' => array('mandrill_simplenews_report_newsletter_subscription'),
'page arguments' => array(4),
'access arguments' => array('view mandrill reports'),
'type' => MENU_CALLBACK,
);
$items['admin/reports/mandrill/simplenews/%taxonomy_term/%/%'] = array(
'title' => 'Mandrill User Activity Report',
'page callback' => array('mandrill_simplenews_report_user_activity'),
'page arguments' => array(4, 5, 6),
'access arguments' => array('view mandrill reports'),
'type' => MENU_CALLBACK,
);
return $items;
}
/**
* Page callback to show the simplenews user subscription details in mandrill reports.
*/
function mandrill_simplenews_report_newsletter() {
drupal_set_title(t('Newsletter Categories'));
$items = array();
foreach (simplenews_categories_load_multiple() as $list) {
$item = l($list->name, 'admin/reports/mandrill/simplenews/' . $list->tid);
$item .= !empty($list->description) ? '<div class="description">' . $list->description . '</div>' : '';
$items[] = $item;
}
$variables = array(
'title' => t('Click newsletter categories below to see their reports'),
'items' => $items,
'attributes' => array(
'class' => array('admin-list'),
),
);
return theme('item_list', $variables);
}
/**
* Page callback to display subscribed user list for newsletter category.
*/
function mandrill_simplenews_report_newsletter_subscription($term) {
$tid = $term->tid;
$title = t('Newsletter !title', array('!title' => $term->name));
drupal_set_title($title);
$breadcrumb = array(
l(t('Home'), '<front>'),
l(t('Administration'), 'admin'),
l(t('Reports'), 'admin/reports'),
l(t('Mandrill'), 'admin/reports/mandrill'),
l(t('Newsletter'), 'admin/reports/mandrill/simplenews'),
);
drupal_set_breadcrumb($breadcrumb);
$rows = array();
$header = array(
'mail' => array('data' => t('Email'), 'field' => 'sn.mail', 'sort' => 'asc'),
'username' => array('data' => t('Username'), 'field' => 'u.name'),
'status' => array('data' => t('Status'), 'field' => 'sn.activated'),
'emails' => array('data' => t('Total no. of emails sent')),
'last_interaction' => array('data' => t('Last interaction'), 'field' => 'ms.last_interaction'),
'report' => array('data' => t('Report')),
);
$query = db_select('simplenews_subscriber', 'sn')->extend('PagerDefault')->extend('TableSort');
$query->leftJoin('users', 'u', 'sn.uid = u.uid');
$query->innerJoin('simplenews_subscription', 'su', 'sn.snid = su.snid');
$query->leftJoin('mandrill_simplenews_report_subscription_extra', 'ms', 'su.snid = ms.snid');
$query->condition('su.status', SIMPLENEWS_SUBSCRIPTION_STATUS_SUBSCRIBED);
$query->condition('su.tid', $tid);
$query->condition('ms.tid', $tid);
$query->addField('u', 'name', 'name');
$query->addField('ms', 'last_interaction', 'last_interaction');
$subscription = $query
->fields('sn', array('snid', 'activated', 'mail', 'uid', 'language', 'created'))
->limit(30)
->orderByHeader($header)
->execute();
foreach ($subscription as $subscriber) {
$sent_mail_count = db_query("SELECT COUNT(id) FROM {mandrill_simplenews_report_newsletter_sent}
WHERE tid = :tid AND sent_to_mail = :sent_to_mail",
array(':tid' => $tid, ':sent_to_mail' => $subscriber->mail))->fetchField();
$last_interaction = $subscriber->last_interaction;
$rows[] = array(
'mail' => $subscriber->mail,
'username' => $subscriber->name,
'status' => theme('simplenews_status', array('source' => 'activated', 'status' => $subscriber->activated)),
'emails' => $sent_mail_count,
'last_interaction' => ((string) (int) $last_interaction === $last_interaction) ? format_date($last_interaction) : format_date($subscriber->timestamp),
'report' => ($sent_mail_count > 0) ? l(t('See User Activity Report'), 'admin/reports/mandrill/simplenews/' . $tid . '/' . $subscriber->mail . '/' . $subscriber->snid ) : '',
);
}
// @todo: add pager
$empty = t('No subscribers available');
return theme('table', array('header' => $header, 'rows' => $rows, 'empty' => $empty));
}
/**
* Page callback to display mandrill user activity reports.
*/
function mandrill_simplenews_report_user_activity($term, $mail, $snid) {
$tid = $term->tid;
$output = '';
$newsletter_name = $term->name;
$breadcrumb = array(
l(t('Home'), '<front>'),
l(t('Administration'), 'admin'),
l(t('Reports'), 'admin/reports'),
l(t('Mandrill'), 'admin/reports/mandrill'),
l(t('Newsletter'), 'admin/reports/mandrill/simplenews'),
l($term->name, 'admin/reports/mandrill/simplenews/' . $tid),
);
drupal_set_breadcrumb($breadcrumb);
drupal_set_title(t('User !mail in !newsletter', array('!mail' => $mail, '!newsletter' => $newsletter_name)));
// get the number of emails sent to the subscriber.
$sent_mail_count = db_query("SELECT COUNT(id) FROM {mandrill_simplenews_report_newsletter_sent}
WHERE tid = :tid AND sent_to_mail = :sent_to_mail",
array(':tid' => $tid, ':sent_to_mail' => $mail))->fetchField();
// get the user's subscription date and time for the newsletter term.
$created = db_query("SELECT timestamp FROM {simplenews_subscription} WHERE tid = :tid AND snid = :snid", array(':tid' => $tid, ':snid' => $snid))->fetchField();
// append the member since and mail sent details along with more details.
$caption = '<p>' . t('Member Since : !created', array('!created' => format_date($created))) . '</p>';
$caption .= '<p>' . t('Total number of emails sent : !mail_count', array('!mail_count' => $sent_mail_count)) . '</p>';
$header = array(
array('data' => t('Newsletter Subject'), 'field' => 'sn.subject'),
array('data' => t('Sent time'), 'field' => 'sn.sent_timestamp', 'sort' => 'asc'),
array('data' => t('Opens'), 'field' => 'sn.open_count'),
array('data' => t('Clicks'), 'field' => 'sn.click_count'),
);
// get the user activity details from the table.
$query = db_select('mandrill_simplenews_report_newsletter_sent', 'sn')
->extend('PagerDefault')
->extend('TableSort');
$query->fields('sn', array('subject', 'sent_timestamp', 'open_count', 'click_count'));
$query->condition('sent_to_mail', $mail);
$query->condition('tid', $tid);
$result = $query
->limit(50)
->orderByHeader($header)
->execute();
$rows = array();
foreach ($result as $row) {
$rows[]['data'] = array(
'newsletter_title' => check_plain($row->subject),
'newsletter_sent_time' => format_date($row->sent_timestamp, 'medium'),
'open_count' => $row->open_count,
'click_count' => $row->click_count,
);
}
return array(
'pager_table' => array(
'#theme' => 'table',
'#header' => $header,
'#rows' => $rows,
'#caption' => $caption,
'#empty' => t('No results founds.'),
),
'pager_pager' => array('#theme' => 'pager')
);
}
/**
* Implements hook_cronapi()
*/
function mandrill_simplenews_report_cronapi($op, $job = NULL) {
$items['mandrill_simplenews_email_report'] = array(
'description' => t('To make mandrill api calls to get sent out emails report.'),
'rule' => '0 */6 * * *',
'weight' => 11,
'callback' => 'mandrill_simplenews_mandrill_email_report_cron',
);
$items['mandrill_simplenews_last_interaction'] = array(
'description' => t('To update the user last interaction time on newsletters'),
'rule' => '0 */24 * * *',
'weight' => 13,
'callback' => 'mandrill_simplenews_update_last_interaction_cron',
);
return $items;
}
/**
* Cron Function callback to get the email report.
*/
function mandrill_simplenews_mandrill_email_report_cron() {
$api_key = variable_get('mandrill_api_key', '');
$api_timeout = variable_get('mandrill_api_timeout', 120);
$maildrop_mandrill = new MandrillSimplenewsReport($api_key, $api_timeout);
$maildrop_mandrill->activity_report_import();
}
/**
* Cron Function callback to get the last interaction of user on newsletters.
*/
function mandrill_simplenews_update_last_interaction_cron() {
// query the mail and tid from the simplenews_subscription table.
$results = db_query("SELECT sn.snid, sn.mail, su.tid, su.timestamp
FROM simplenews_subscription su
INNER JOIN simplenews_subscriber sn ON su.snid = sn.snid
WHERE status = :status", array(':status' => 1));
foreach ($results as $result) {
// get the last time when the user opened the newsletter mail.
$last_interaction = db_query_range("SELECT sent_timestamp
FROM {mandrill_simplenews_report_newsletter_sent}
WHERE tid = :tid AND open_count > :open_count AND sent_to_mail = :sent_to_mail
ORDER BY sent_timestamp DESC", 0, 1,
array(':tid' => $result->tid, ':open_count' => 0, ':sent_to_mail' => $result->mail))->fetchField();
$time = ((string) (int) $last_interaction === $last_interaction) ? $last_interaction : $result->timestamp;
// if time is not empty, update in md_simplenews_subscription_extra table.
if ($time) {
$update_subscription = db_merge('mandrill_simplenews_report_subscription_extra')
->key(array(
'tid' => $result->tid,
'snid' => $result->snid,
))
->fields(array(
'snid' => $result->snid,
'tid' => $result->tid,
'last_interaction' => $time,
))
->execute();
}
}
watchdog('mandrill_simplenews_last_interaction', 'last interaction time updated successfully');
}

View File

@@ -0,0 +1,177 @@
Mime Mail 7.x-1.x, xxxx-xx-xx
-----------------------
- #2020875 by das-peter, Propaganistas: Provide option to set language in Rules actions.
- #2045699 by sgabe | Punk_UnDeaD: Boundaries are not unique on Windows.
- #1798324 by sgabe, kienan | ShaneOnABike: Return-Path is incorrectly using sender name.
- #1790098 by sgabe | edb, Shellingfox: Custom 'from' address comes out as 'Array'.
- #1979776 by sgabe | cswolf: Hash mark link gets replaced with site URL.
- #1963412 by sgabe | djg_tram: Content-Disposition should default to 'inline'.
- #1469828 by sgabe | shadowhitman: Allow to use simple address format only for recipient.
- #1947018 by sgabe | bendev: Allow sending plain text messages with Rules.
- #962226 by sgabe | rchuber: Allow custom mailkeys for system and Rules action messages.
- #1873348 by sgabe | tutumlum: Cannot use tokens in subject and body of HTML email action.
- #1439918 by sgabe | Lukas von Blarer: 'Link images only' is not working if the file exists as-is.
- #1538004 by sgabe | djg_tram: Change template naming logic to use module as well.
- #1719256 by lirantal, sgabe: Handle different files with the same file name.
- #1922530 by berliner: Callto links in mail body wrongly replaced.
- #1911558 by Simon Georges, JulienD: Remove useless files[] directive from .info files.
- #1877928 by sgabe | parasite: Replacing underscore in key is not needed.
- #1898140 by MiroslavBanov: Engine variable set to NULL on settings page.
- #1780412 by sgabe, kid_icarus: Option to exclude blocked users from a role.
- #1814922 by marcusx: Rule sanitizes the $body if populated by a parameter.
- #1813348 by sgabe | jdhildeb: Sendmail invoked with empty Return-Path.
- #1469022 by sgabe, das-peter | MI: Add 'Reply-to' field to Rules and system actions.
- #1773698 by jherencia: Alternatives for mimemail-message.tpl.php do not work.
- #1585546 by kotnik, bojanz: Rules actions must be in root module directory.
Mime Mail 7.x-1.0-alpha2, 2012-08-22
-----------------------
- #1722188 by sgabe | christian death: Split has been deprecated.
- #1643750 by sgabe | MRV: Remove class attributes from the compressed message.
- #321026 by sgabe, LUTi | attiks: HTML messages are broken by line breaks.
- #1605230 by sgabe | mrbubbs: Extra space in subject caused by wordwrap.
- #1662682 by sgabe, itamar: Value may be left unset in requirements check.
- #1504782 by rjkuyvenhoven: Update support for Fusion based themes.
- #1597896 by sgabe | joewickert: Plus symbol encoded as space in URLs.
- #1515660 by sgabe | philsward: Missing upgrade path for Rules actions.
- #81707 by sgabe | FredCK, Peters196: Auto-detect appropriate line endings.
- #1475664 by sgabe | pumpkinkid: Getting 'Array to string conversion' error.
- #1301876 by sean_fremouw: Regex in mimemail_headers() strips allowed characters.
- #1432502 by El Bandito: Quotations are not needed to specify an attachment.
- #1349728 by jherencia: Possibility to configure the theme that will render the email.
- #1391680 by marcdecaluwe: Headers not correctly set.
- #1283620 by Cyberwolf: Expose email settings user field to field API.
- #1372660 by eueco: Set the proper line ending when calling chunk_split().
- #1388786 by tostinni: mimemail_html_body() fails to retrieve file's URI.
Mime Mail 7.x-1.0-alpha1, 2011-12-18
-----------------------
- #1372088 by marcus.n3rd.26: Use uri to load mail.css when sending mail.
- #1305824 by sgabe: Leave MIME type and use only path to specify an attachment.
- #1370422 by awagner: Missing delimiter in file_scan_directory().
- #1275734 by gnindl: Scan recursively for mail.css.
- #1304332 by sgabe: Token replacement and PHP evaluation in Rules action messages.
- #1305830 by sgabe | ibes: Set default filename and mimetype to enforce auto detection.
- #1289584 by sgabe | oguerreiro: Check if 'styles' is set.
- #1288546 by sgabe | carn1x: Unknown Rules actions.
- #1066438 by quicksketch, sgabe, guillaumev, oadaeh: Initial support of attachments.
- #1258302 by ralf.strobel: Replace 'arguments' with 'variables' in hook_theme().
- #1190144 by Cyberwolf, sgabe: Trim less-than and grater-than chars from Return-Path.
- #1140538 by sgabe: Site style sheet isn't included.
- #1232266 by InternetDevels.Com: Engine select form element has wrong array key.
Mime Mail 6.x-1.0, 2011-11-19
-----------------------
- #1232264 by InternetDevels.Com: Check for not just NULL but empty From address.
- #1201154 by guillaumev: Check if attachments is an array and isn't empty.
- #1203234 by sgabe | Offlein: Store input format setting for Rules and actions.
- #1227242 by sgabe: Remove unnecessary reference signs.
- #1076520 by joelstein: Absolute site stylesheets not included.
- #1258062 by oadaeh: Don't allow an empty e-mail address with the default engine.
- #1270686 by gmania: Don't add Content-Disposition to multipart/alternative parts.
- #1260302 by sgabe | prokop1000: Replace encoding detection with UTF-8.
- #1270656 by sgabe: From header can be an array which causes errors.
- #1301868 by sean_fremouw: Headers overwritten.
- #1308628 by sgabe, chriscohen: List function throws notice.
- #1301924 by sgabe, ibes: Use array for body in Rules and system actions.
- #417462 by plach, Lukas von Blarer, sgabe: Language prefix is not taken into account.
- #1181170 by sgabe, Cyberwolf, ibes | windm: Add permission to set user specific settings.
- #1309248 by sgabe, gmania: Generate not existing ImageCache images before embedding.
- #1304134 by sdague: Add preference to link images.
- #1275080 by gmania: Remove the depricated Errors-To header.
Mime Mail 6.x-1.0-beta2, 2011-06-22
----------------------
- #1181486 by sgabe: HTML Message not saving in Rules Action form.
- #1164870 by itserich: Recipient is not an array anymore.
- #1186690 by samhassell: Can't send multiple attachments.
Mime Mail 6.x-1.0-beta1, 2011-06-04
----------------------
- #911612 by geneticdrift: Hidden attachments in some email clients.
- #1090286 by sgabe: Prepare action messages with drupal_mail() to allow alteration.
- #1137358 by sgabe: Tokens don't work in the body of Rules action messages
- #1150224 by sgabe: Run filters on the message body of Rules and system actions.
- #1090286 by sgabe: Remove process function, fix sending message to a role action.
- #1116930 by Pol, sgabe: No text alternative if the CSS is too large.
- #808518 by sgabe: Return only the result from drupal_mail_wrapper().
- #808518 by claudiu.cristea, sgabe: Split mail preparation from sending.
- #1108324 by sgabe: Add input filter to HTML message for system and Rules actions.
- #1114536 by rjbrown99: Pass recipient to the template.
- #971272 by sgabe: Allow to specify sender's name for Rules action messages.
- #1167576 by Pol: Accept plaintext and text parameters for system messages.
- #338460 by hopla: Doens't look for mail.css in Zen sub-themes.
- #261028 by sgabe, gnosis, mfb, mrfelton, LUTi: SMTP Return-Path Setting.
- #1175378 by sgabe, samalone: Include module CSS files in email.
Mime Mail 6.x-1.0-alpha8, 2011-03-24
----------------------
- #374615 by joelstein: Set starter default value for plain text user reference.
- #1076222 by papasse, Aron Novak: Check the module path on settings submission.
- #920904 by fmjrey: Fusion local.css not taken into account.
- #443964 by sgabe, pillarsdotnet: Skip style sheets with print media.
- #932962 by clydefrog, arvana, sgabe: Allow attachments to be added by contents.
- #907716 by isaac.niebeling: Allow non-web-accessible files as attachments.
- #758922 by eft, sgabe: Use simple address format for PHP mail() on Windows.
Mime Mail 6.x-1.0-alpha7, 2011-01-31
----------------------
- #950456 by stella, sgabe: Check if body encoding can be, and is it detected
- #364198 by mfb, sgabe | HS: CSS code in email
- #835734 by sgabe | sylvaticus: In some cases CSS optimization causes WSOD
- #438058 by AlexisWilke, DanChadwick: Remove line feeds in subject
- #979748 by Romka: Missing include in mimemail_mailengine()
- #700996 by smk-ka: Custom inline style properties overwritten
- #960374 by kim-day: Don't set BCC and CC headers if they are empty
- #961536 by clydefrog: Check if sender is empty, not just null
- #852698 by sgabe | interestingaftermath: Specify sender's name
- #685574 by sgabe, Wim Leers | Michelle: Optional site's css embedding
- #758754 by sgabe | mennonot: Add 'Send HTML e-mail' action
- #501722 by jpetso, fago, criz, sgabe, aantonop: HTML mail actions for Rules
- #729658 by sgabe, Agileware: Allow better integration with Domain Access module
- #960726 by sgabe, clydefrog: Send plaintext message if the HTML body is empty
Mime Mail 6.x-1.0-alpha6, 2010-09-13
----------------------
- #629038 by Robbert: Attachments dont respect list setting
- #882960 by sgabe, glynster: CSS Mail Style Sheet Overrides
- #319229 by javierreartes, tobiasb, sgabe, crifi: Set $attachments in drupal_wrap_mail()
- #903536 by sgabe: Use variable_del() to remove smtp_library()
- #456242 by sgabe, kenorb: Use proper operators in if statements with strpos()
- #882528 by sgabe | Carsten: Template suggestions based on mailkey
- #752838 by sgabe | dsms: Pass $subject to the template
- #319384 by sgabe | mariuss: Add $mailkey to body tag as CSS class
- #796510 by sgabe | smk-ka: Update CSS Compressor
- #614782 by sgabe, Sutharsan: Update README.txt
Mime Mail 6.x-1.0-alpha5, 2010-08-12
----------------------
- #850674 by sgabe, AlexisWilke: Prepare function name testing '_prepare'...
- #448996 by mfb, hanoii, Sylvain Lecoy: Wrong implementation of hook_mail_alter()
- #319229 by sgabe, jm.federico, joostvdl, donquixote, fehin, sunfire-design, mariuss: src='Array' if path to image is broken
- #517306 by sgabe, rdosser: Mime Mail Compress mangles absolute URLs in CSS properties
- #597448 by sgabe, rmjiv: Unsafe regex pattern in mimemail_extract_files()
- #535466 by andreiashu, sgabe: WSOD when using Mime Mail Compress without DOM extension
- #513138 by sgabe, peterx: Undefined variables in mimemail.inc
- #304476 by sgabe, Thomas_Zahreddin, aaron: PHP Error when Stylesheets don't exist
- #710116 by sgabe, neoglez: Wrong implementation/namespace conflict of mimemail_prepare()
Mime Mail 6.x-1.0-alpha4, 2010-07-10
----------------------
- #642800 by scronide: Enforce requirement of PHP 5.x for Mime Mail Compress
- #740856 by sgabe, Vicbus: Check if the file part is set in the body
- #567594 by hanoii: $mailkey is not properly set in drupal_mail_wrapper()
- #768794 by sgabe, danyg: Check if the name is empty when the address is an object
- #700996 by sgabe, -Mania-: Custom inline style properties overwritten when using CSS Compressor
- #729334 by plach: Flawed CSS to XPath conversion for class selectors in Mime Mail CSS Compressor
- #456260 by sgabe, kenorb, kscheirer, mitchmac: WSOD: smtp_library variable is not removed when Mime Mail has been disabled
- #698794 by sgabe, mobilis: Attachment Content-Type-fix
- #629038 by jackinloadup, sgabe: Attachments don't respect list setting
Mime Mail 6.x-1.0-alpha3, 2010-06-16
----------------------
- #358439 by folkertdv: Images are only in the first message
- #448670 by sgabe, gregarios, moritzz: Spaces and Line Breaks are removed from CSS definitions
- #372710 by LUTi, sgabe, perarnet: HTML emails are text-only in Hotmail
- #583920 by Sutharsan, sgabe: Can't override mimemail.tpl.php
- #127876 by sgabe, Sutharsan, jerdavis: Plain text with/without attachment

View File

@@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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.
<signature of Ty Coon>, 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.

View File

@@ -0,0 +1,128 @@
-- SUMMARY --
This is a Mime Mail component module (for use by other modules).
* It permits users to recieve HTML email and can be used by other modules. The mail
functionality accepts an HTML message body, mime-endcodes it and sends it.
* If the HTML has embedded graphics, these graphics are MIME-encoded and included
as a message attachment.
* Adopts your site's style by automatically including your theme's stylesheet files in a
themeable HTML message format
* If the recipient's preference is available and they prefer plaintext, the HTML will be
converted to plain text and sent as-is. Otherwise, the email will be sent in themeable
HTML with a plaintext alternative.
For a full description of the module, visit the project page:
http://drupal.org/project/mimemail
To submit bug reports and feature suggestions, or to track changes:
http://drupal.org/project/issues/mimemail
-- REQUIREMENTS --
Mail System module - http://drupal.org/project/mailsystem
-- INSTALLATION --
Hopefully, you know the drill by now :)
1. Download the module and extract the files.
2. Upload the entire mimemail folder into your Drupal sites/all/modules/
or sites/my.site.folder/modules/ directory if you are running a multi-site
installation of Drupal and you want this module to be specific to a
particular site in your installation.
3. Enable the Mime Mail module by navigating to:
Administration > Modules
4. Adjust settings by navigating to:
Administration > Configuration > Mime Mail
-- USAGE --
This module may be required by other modules, but in favor of the recently
added system actions and Rules integration, it can be useful by itself too.
Once installed, any module can send MIME-encoded messages by specifing
MimeMailSystem as the responsible mail system for a particular message
or all mail sent by one module.
This can be done through the web by visiting admin/config/system/mailsystem
or in a program as follows:
mailsystem_set(array(
'{$module}_{$key}' => 'MimeMailSystem', // Just messages with $key sent by $module.
'{$module}' => 'MimeMailSystem', // All messages sent by $module.
));
You can use the following optional parameters to build the e-mail:
'plain':
Boolean, whether to send messages in plaintext-only (optional, default is FALSE).
'plaintext':
Plaintext portion of a multipart e-mail (optional).
'attachments':
Array of arrays with the path or content, name and MIME type of the file (optional).
'headers':
A keyed array with headers (optional).
You can set these in $params either before calling drupal_mail() or in hook_mail()
and of course hook_mail_alter().
Normally, Mime Mail uses email addresses in the form of "name" <address@host.com>,
but PHP running on Windows servers requires extra SMTP handling to use this format.
If you are running your site on a Windows server and don't have an SMTP solution such
as the SMTP module installed, you may need to set the 'Use the simple format of
user@example.com for all email addresses' option on the configuration settings page.
This module creates a user preference for receiving plaintext-only messages.
This preference will be honored by all messages if the format is not explicitly set
and the user has access to edit this preference (allowed by default).
Email messages are formatted using the mimemail-message.tpl.php template.
This includes a CSS style sheet and uses an HTML version of the text.
The included CSS is either:
the mail.css file found anywhere in your theme folder or
the combined CSS style sheets of your theme if enabled.
To create a custom mail template copy the mimemail-message.tpl.php file from
the mimemail/theme directory into your default theme's folder. Both general and
by-mailkey theming can be performed:
mimemail-message--[module]--[key].tpl.php (for messages with a specific module and key)
mimemail-message--[module].tpl.php (for messages with a specific module)
mimemail-message--[key].tpl.php (for messages with a specific key)
mimemail-message.tpl.php (for all messages)
Messages can be rendered using different themes. You can choose the following
settings to render the e-mail:
'current': Theme currently used by the user who runs drupal_mail().
'default': Default theme, obtained via variable theme_default.
'domain': Theme obtained via Domain Theme module.
or any other active theme.
Images with absolute URL will be available as remote content. To embed images
into emails you have to use a relative URL or an internal path.
For example:
instead of http://www.mysite.com/sites/default/files/mypicture.jpg
use /home/www/public_html/drupal/sites/default/files/mypicture.jpg
or /sites/default/files/mypicture.jpg
Since some email clients (namely Outlook 2007 and GMail) is tend to only regard
inline CSS, you can use the Compressor to convert CSS styles into inline style
attributes. It transmogrifies the HTML source by parsing the CSS and inserting the
CSS definitions into tags within the HTML based on the CSS selectors. To use the
Compressor, just enable it.
-- CREDITS --
MAINTAINER: Allie Micka < allie at pajunas dot com >
* Allie Micka
Mime enhancements and HTML mail code
* Gerhard Killesreiter
Original mail and mime code
* Robert Castelo
HTML to Text and other functionality

View File

@@ -0,0 +1,150 @@
<?php
/**
* @file
* Configuration settings page for sending MIME-encoded emails.
*/
/**
* Configuration form.
*/
function mimemail_admin_settings() {
// Check for the existence of a mail.css file in the default theme folder.
$theme = variable_get('theme_default', NULL);
$mailstyle = drupal_get_path('theme', $theme) . '/mail.css';
// Disable site style sheets including option if found.
if (is_file($mailstyle)) {
variable_set('mimemail_sitestyle', 0);
$disable_sitestyle = TRUE;
}
else {
$disable_sitestyle = FALSE;
}
$form = array();
$form['mimemail']['mimemail_name'] = array(
'#type' => 'textfield',
'#title' => t('Sender name'),
'#default_value' => variable_get('mimemail_name', variable_get('site_name', 'Drupal')),
'#size' => 60,
'#maxlength' => 128,
'#description' => t('The name that all site emails will be from when using default engine.'),
);
$form['mimemail']['mimemail_mail'] = array(
'#type' => 'textfield',
'#title' => t('Sender e-mail address'),
'#default_value' => variable_get('mimemail_mail', variable_get('site_mail', ini_get('sendmail_from'))),
'#size' => 60,
'#maxlength' => 128,
'#description' => t('The email address that all site e-mails will be from when using default engine.'),
);
$form['mimemail']['mimemail_simple_address'] = array(
'#type' => 'checkbox',
'#title' => t('Use simple address format'),
'#default_value' => variable_get('mimemail_simple_address', FALSE),
'#description' => t('Use the simple format of user@example.com for all recipient email addresses.'),
);
$form['mimemail']['mimemail_sitestyle'] = array(
'#type' => 'checkbox',
'#title' => t('Include site style sheets'),
'#default_value' => variable_get('mimemail_sitestyle', TRUE),
'#description' => t('Gather all style sheets when no mail.css found in the default theme directory.'),
'#disabled' => $disable_sitestyle,
);
$form['mimemail']['mimemail_textonly'] = array(
'#type' => 'checkbox',
'#title' => t('Send plain text email only'),
'#default_value' => variable_get('mimemail_textonly', FALSE),
'#description' => t('This option disables the use of email messages with graphics and styles. All messages will be converted to plain text.'),
);
$form['mimemail']['mimemail_linkonly'] = array(
'#type' => 'checkbox',
'#title' => t('Link images only'),
'#default_value' => variable_get('mimemail_linkonly', 0),
'#description' => t('This option disables the embedding of images. All image will be available as external content. This can make email messages much smaller.'),
);
if (module_exists('mimemail_compress')) {
$form['mimemail']['mimemail_preserve_class'] = array(
'#type' => 'checkbox',
'#title' => t('Preserve class attributes'),
'#default_value' => variable_get('mimemail_preserve_class', 0),
'#description' => t('This option disables the removing of class attributes from the message source. Useful for debugging the style of the message.'),
);
}
// Get a list of all formats.
$formats = filter_formats();
foreach ($formats as $format) {
$format_options[$format->format] = $format->name;
}
$form['mimemail']['mimemail_format'] = array(
'#type' => 'select',
'#title' => t('E-mail format'),
'#options' => $format_options,
'#default_value' => variable_get('mimemail_format', filter_fallback_format()),
'#access' => count($formats) > 1,
'#attributes' => array('class' => array('filter-list')),
);
$form['mimemail']['advanced'] = array(
'#type' => 'fieldset',
'#title' => t('Advanced settings'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
$form['mimemail']['advanced']['mimemail_incoming'] = array(
'#type' => 'checkbox',
'#title' => t('Process incoming messages posted to this site'),
'#default_value' => variable_get('mimemail_incoming', FALSE),
'#description' => t('This is an advanced setting that should not be enabled unless you know what you are doing.'),
);
$form['mimemail']['advanced']['mimemail_key'] = array(
'#type' => 'textfield',
'#title' => t('Message validation string'),
'#default_value' => variable_get('mimemail_key', md5(rand())),
'#required' => TRUE,
'#description' => t('This string will be used to validate incoming messages. It can be anything, but must be used on both sides of the transfer.'),
);
// Get the available mail engines.
$engines = mimemail_get_engines();
foreach ($engines as $module => $engine) {
$engine_options[$module] = $engine['name'] . ': ' . $engine['description'];
}
// Hide the settings if only 1 engine is available.
if (count($engines) == 1) {
reset($engines);
variable_set('mimemail_engine', key($engines));
$form['mimemail']['mimemail_engine'] = array(
'#type' => 'hidden',
'#value' => variable_get('mimemail_engine', 'mimemail'),
);
}
else {
$form['mimemail']['mimemail_engine'] = array(
'#type' => 'select',
'#title' => t('E-mail engine'),
'#default_value' => variable_get('mimemail_engine', 'mimemail'),
'#options' => $engine_options,
'#description' => t('Choose an engine for sending mails from your site.'),
);
}
if (variable_get('mimemail_engine', 'mail')) {
$settings = module_invoke(variable_get('mimemail_engine', 'mimemail'), 'mailengine', 'settings');
if ($settings) {
$form['mimemail']['engine_settings'] = array(
'#type' => 'fieldset',
'#title' => t('Engine specific settings'),
);
foreach ($settings as $name => $value) {
$form['mimemail']['engine_settings'][$name] = $value;
}
}
}
else {
drupal_set_message(t('Please choose a mail engine.'), 'error');
}
return system_settings_form($form);
}

View File

@@ -0,0 +1,203 @@
<?php
/**
* @file
* Functions that handle inbound messages to mimemail.
*/
/**
* Receive messages POSTed from an external source.
*
* This function enables messages to be sent via POST or some other RFC822
* source input (e.g. directly from a mail server).
*
* @return
* The POSTed message.
*/
function mimemail_post() {
$message = $_POST['message'];
$token = $_POST['token'];
$hash = md5(variable_get('mimemail_key', '**') . $message);
if ($hash != $token) {
watchdog('access denied', 'Authentication error for POST e-mail', WATCHDOG_WARNING);
return drupal_access_denied();
}
return mimemail_incoming($message);
}
/**
* Parses an externally received message.
*
* @param $message
* The message to parse.
*/
function mimemail_incoming($message) {
$mail = mimemail_parse($message);
foreach (module_implements('mimemail_incoming_alter') as $module) {
call_user_func_array($module . '_mimemail_incoming_alter', $mail);
}
module_invoke_all('mimemail_incoming', $mail);
}
/**
* Parses a message into its parts.
*
* @param string $message
* The message to parse.
*
* @return array
* The parts of the message.
*/
function mimemail_parse($message) {
// Provides a "headers", "content-type" and "body" element.
$mail = mimemail_parse_headers($message);
// Get an address-only version of "From" (useful for user_load() and such).
$mail['from'] = preg_replace('/.*\b([a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4})\b.*/i', '\1', drupal_strtolower($mail['headers']['From']));
// Get a subject line, which may be cleaned up/modified later.
$mail['subject'] = $mail['headers']['Subject'];
// Make an array to hold any non-content attachments.
$mail['attachments'] = array();
// We're dealing with a multi-part message.
$mail['parts'] = mimemail_parse_boundary($mail);
foreach ($mail['parts'] as $i => $part_body) {
$part = mimemail_parse_headers($part_body);
$sub_parts = mimemail_parse_boundary($part);
// Content is encoded in a multipart/alternative section.
if (count($sub_parts) > 1) {
foreach ($sub_parts as $j => $sub_part_body) {
$sub_part = mimemail_parse_headers($sub_part_body);
if ($sub_part['content-type'] == 'text/plain') {
$mail['text'] = mimemail_parse_content($sub_part);
}
if ($sub_part['content-type'] == 'text/html') {
$mail['html'] = mimemail_parse_content($sub_part);
}
else {
$mail['attachments'][] = mimemail_parse_attachment($sub_part);
}
}
}
if (($part['content-type'] == 'text/plain') && !isset($mail['text'])) {
$mail['text'] = mimemail_parse_content($part);
}
elseif (($part['content-type'] == 'text/html') && !isset($mail['html'])) {
$mail['html'] = mimemail_parse_content($part);
}
else {
$mail['attachments'][] = mimemail_parse_attachment($part);
}
}
// Make sure our text and html parts are accounted for.
if (isset($mail['html']) && !isset($mail['text'])) {
$mail['text'] = preg_replace('|<style.*</style>|mis', '', $mail['html']);
$mail['text'] = drupal_html_to_text($mail['text']);
}
elseif (isset($mail['text']) && !isset($mail['html'])) {
$mail['html'] = check_markup($mail['text'], variable_get('mimemail_format', filter_fallback_format()));
}
// Last ditch attempt - use the body as-is.
if (!isset($mail['text'])) {
$mail['text'] = mimemail_parse_content($mail);
$mail['html'] = check_markup($mail['text'], variable_get('mimemail_format', filter_fallback_format()));
}
return $mail;
}
/**
* Split a multi-part message using MIME boundaries.
*/
function mimemail_parse_boundary($part) {
$m = array();
if (preg_match('/.*boundary="?([^";]+)"?.*/', $part['headers']['Content-Type'], $m)) {
$boundary = "\n--" . $m[1];
$body = str_replace("$boundary--", '', $part['body']);
return array_slice(explode($boundary, $body), 1);
}
return array($part['body']);
}
/**
* Split a message (or message part) into its headers and body section.
*/
function mimemail_parse_headers($message) {
// Split out body and headers.
if (preg_match("/^(.*?)\r?\n\r?\n(.*)/s", $message, $match)) {
list($hdr, $body) = array($match[1], $match[2]);
}
// Un-fold the headers.
$hdr = preg_replace(array("/\r/", "/\n(\t| )+/"), array('', ' '), $hdr);
$headers = array();
foreach (explode("\n", trim($hdr)) as $row) {
$split = strpos($row, ':');
$name = trim(drupal_substr($row, 0, $split));
$val = trim(drupal_substr($row, $split+1));
$headers[$name] = $val;
}
$type = (preg_replace('/\s*([^;]+).*/', '\1', $headers['Content-Type']));
return array('headers' => $headers, 'body' => $body, 'content-type' => $type);
}
/**
* Return a decoded MIME part in UTF-8.
*/
function mimemail_parse_content($part) {
$content = $part['body'];
// Decode this part.
if ($encoding = drupal_strtolower($part['headers']['Content-Transfer-Encoding'])) {
switch ($encoding) {
case 'base64':
$content = base64_decode($content);
break;
case 'quoted-printable':
$content = quoted_printable_decode($content);
break;
// 7bit is the RFC default.
case '7bit':
break;
}
}
// Try to convert character set to UTF-8.
if (preg_match('/.*charset="?([^";]+)"?.*/', $part['headers']['Content-Type'], $m)) {
$content = drupal_convert_to_utf8($content, $m[1]);
}
return $content;
}
/**
* Convert a MIME part into a file array.
*/
function mimemail_parse_attachment($part) {
$m = array();
if (preg_match('/.*filename="?([^";])"?.*/', $part['headers']['Content-Disposition'], $m)) {
$name = $m[1];
}
elseif (preg_match('/.*name="?([^";])"?.*/', $part['headers']['Content-Type'], $m)) {
$name = $m[1];
}
return array(
'filename' => $name,
'filemime' => $part['content-type'],
'content' => mimemail_parse_content($part),
);
}

View File

@@ -0,0 +1,64 @@
<?php
/**
* @file
* Mime Mail implementations of MailSystemInterface.
*/
/**
* Modify the Drupal mail system to send HTML emails.
*/
class MimeMailSystem implements MailSystemInterface {
/**
* Concatenate and wrap the e-mail body for HTML mails.
*
* @param array $message
* A message array, as described in hook_mail_alter() with optional
* parameters described in mimemail_prepare_message().
*
* @return array
* The formatted $message.
*/
public function format(array $message) {
if (is_array($message['body'])) {
$message['body'] = implode("\n\n", $message['body']);
}
if (preg_match('/plain/', $message['headers']['Content-Type'])) {
$message['body'] = check_markup($message['body'], variable_get('mimemail_format', filter_fallback_format()));
}
$engine = variable_get('mimemail_engine', 'mimemail');
$mailengine = $engine . '_mailengine';
$engine_prepare_message = $engine . '_prepare_message';
if (function_exists($engine_prepare_message)) {
$message = $engine_prepare_message($message);
}
else {
$message = mimemail_prepare_message($message);
}
return $message;
}
/**
* Send an HTML e-mail message, using Drupal variables and default settings.
*
* @param array $message
* A message array, as described in hook_mail_alter() with optional
* parameters described in mimemail_prepare_message().
*
* @return boolean
* TRUE if the mail was successfully accepted, otherwise FALSE.
*/
public function mail(array $message) {
$engine = variable_get('mimemail_engine', 'mimemail');
$mailengine = $engine . '_mailengine';
if (!$engine || !function_exists($mailengine)) {
return FALSE;
}
return $mailengine('send', $message);
}
}

View File

@@ -0,0 +1,554 @@
<?php
/**
* @file
* Common mail functions for sending e-mail. Originally written by Gerhard.
*
* Allie Micka <allie at pajunas dot com>
*/
/**
* Attempts to RFC822-compliant headers for the mail message or its MIME parts.
*
* @todo Could use some enhancement and stress testing.
*
* @param array $headers
* An array of headers.
*
* @return string
* A string containing the headers.
*/
function mimemail_rfc_headers($headers) {
$header = '';
$crlf = variable_get('mimemail_crlf', MAIL_LINE_ENDINGS);
foreach ($headers as $key => $value) {
$key = trim($key);
// Collapse spaces and get rid of newline characters.
$value = preg_replace('/(\s+|\n|\r|^\s|\s$)/', ' ', $value);
// Fold headers if they're too long.
if (drupal_strlen($value) > 60) {
// If there's a semicolon, use that to separate.
if (count($array = preg_split('/;\s*/', $value)) > 1) {
$value = trim(join(";$crlf ", $array));
}
else {
$value = wordwrap($value, 50, "$crlf ", FALSE);
}
}
$header .= "$key: $value$crlf";
}
return trim($header);
}
/**
* Gives useful defaults for standard email headers.
*
* @param array $headers
* Message headers.
* @param string $from
* The address of the sender.
*
* @return array
* Overwrited headers.
*/
function mimemail_headers($headers, $from = NULL) {
$default_from = variable_get('site_mail', ini_get('sendmail_from'));
// Overwrite standard headers.
if ($from) {
if (!isset($headers['From']) || $headers['From'] == $default_from) {
$headers['From'] = $from;
}
if (!isset($headers['Sender']) || $headers['Sender'] == $default_from) {
$headers['Sender'] = $from;
}
// This may not work. The MTA may rewrite the Return-Path.
if (!isset($headers['Return-Path']) || $headers['Return-Path'] == $default_from) {
preg_match('/[a-z\d\-\.\+_]+@(?:[a-z\d\-]+\.)+[a-z\d]{2,4}/i', $from, $matches);
$headers['Return-Path'] = "<$matches[0]>";
}
}
// Convert From header if it is an array.
if (is_array($headers['From'])) {
$headers['From'] = mimemail_address($headers['From']);
}
// Run all headers through mime_header_encode() to convert non-ascii
// characters to an rfc compliant string, similar to drupal_mail().
foreach ($headers as $key => $value) {
$headers[$key] = mime_header_encode($value);
}
return $headers;
}
/**
* Extracts links to local images from HTML documents.
*
* @param string $html
* A string containing the HTML source of the message.
*
* @return array
* An array containing the document body and the extracted files like the following.
* array(
* array(
* 'name' => document name
* 'content' => html text, local image urls replaced by Content-IDs,
* 'Content-Type' => 'text/html; charset=utf-8')
* array(
* 'name' => file name,
* 'file' => reference to local file,
* 'Content-ID' => generated Content-ID,
* 'Content-Type' => derived using mime_content_type if available, educated guess otherwise
* )
* )
*/
function mimemail_extract_files($html) {
$pattern = '/(<link[^>]+href=[\'"]?|<object[^>]+codebase=[\'"]?|@import |[\s]src=[\'"]?)([^\'>"]+)([\'"]?)/mis';
$content = preg_replace_callback($pattern, '_mimemail_replace_files', $html);
$encoding = '8Bit';
$body = explode("\n", $content);
foreach ($body as $line) {
if (drupal_strlen($line) > 998) {
$encoding = 'base64';
break;
}
}
if ($encoding == 'base64') {
$content = rtrim(chunk_split(base64_encode($content)));
}
$document = array(array(
'Content-Type' => "text/html; charset=utf-8",
'Content-Transfer-Encoding' => $encoding,
'content' => $content,
));
$files = _mimemail_file();
return array_merge($document, $files);
}
/**
* Callback function for preg_replace_callback().
*/
function _mimemail_replace_files($matches) {
return stripslashes($matches[1]) . _mimemail_file($matches[2]) . stripslashes($matches[3]);
}
/**
* Helper function to extract local files.
*
* @param string $url
* (optional) The URI or the absolute URL to the file.
* @param string $content
* (optional) The actual file content.
* @param string $name
* (optional) The file name.
* @param string $type
* (optional) The file type.
* @param string $disposition
* (optional) The content disposition. Defaults to inline.
*
* @return
* The Content-ID and/or an array of the files on success or the URL on failure.
*/
function _mimemail_file($url = NULL, $content = NULL, $name = '', $type = '', $disposition = 'inline') {
static $files = array();
static $ids = array();
if ($url) {
$image = preg_match('!\.(png|gif|jpg|jpeg)$!i', $url);
$linkonly = variable_get('mimemail_linkonly', 0);
// The file exists on the server as-is. Allows for non-web-accessible files.
if (@is_file($url) && $image && !$linkonly) {
$file = $url;
}
else {
$url = _mimemail_url($url, 'TRUE');
// The $url is absolute, we're done here.
$scheme = file_uri_scheme($url);
if ($scheme == 'http' || $scheme == 'https' || preg_match('!mailto:!', $url)) {
return $url;
}
// The $url is a non-local URI that needs to be converted to a URL.
else {
$file = (drupal_realpath($url)) ? drupal_realpath($url) : file_create_url($url);
}
}
}
// We have the actual content.
elseif ($content) {
$file = $content;
}
if (isset($file) && (@is_file($file) || $content)) {
if (!$name) {
$name = (@is_file($file)) ? basename($file) : 'attachment.dat';
}
if (!$type) {
$type = ($name) ? file_get_mimetype($name) : file_get_mimetype($file);
}
$id = md5($file) .'@'. $_SERVER['HTTP_HOST'];
// Prevent duplicate items.
if (isset($ids[$id])) {
return 'cid:'. $ids[$id];
}
$new_file = array(
'name' => $name,
'file' => $file,
'Content-ID' => $id,
'Content-Disposition' => $disposition,
'Content-Type' => $type,
);
$files[] = $new_file;
$ids[$id] = $id;
return 'cid:' . $id;
}
// The $file does not exist and no $content, return the $url if possible.
elseif ($url) {
return $url;
}
$ret = $files;
$files = array();
$ids = array();
return $ret;
}
/**
* Build a multipart body.
*
* @param array $parts
* An associative array containing the parts to be included:
* - name: A string containing the name of the attachment.
* - content: A string containing textual content.
* - file: A string containing file content.
* - Content-Type: A string containing the content type of either file or content. Mandatory
* for content, optional for file. If not present, it will be derived from file the file if
* mime_content_type is available. If not, application/octet-stream is used.
* - Content-Disposition: (optional) A string containing the disposition. Defaults to inline.
* - Content-Transfer-Encoding: (optional) Base64 is assumed for files, 8bit for other content.
* - Content-ID: (optional) for in-mail references to attachements.
* Name is mandatory, one of content and file is required, they are mutually exclusive.
* @param string $content_type
* (optional) A string containing the content-type for the combined message. Defaults to
* multipart/mixed.
*
* @return array
* An associative array containing the following elements:
* - body: A string containing the MIME-encoded multipart body of a mail.
* - headers: An array that includes some headers for the mail to be sent.
*/
function mimemail_multipart_body($parts, $content_type = 'multipart/mixed; charset=utf-8', $sub_part = FALSE) {
$boundary = md5(uniqid($_SERVER['REQUEST_TIME'], TRUE));
$body = '';
$headers = array(
'Content-Type' => "$content_type; boundary=\"$boundary\"",
);
if (!$sub_part) {
$headers['MIME-Version'] = '1.0';
$body = "This is a multi-part message in MIME format.\n";
}
foreach ($parts as $part) {
$part_headers = array();
if (isset($part['Content-ID'])) {
$part_headers['Content-ID'] = '<' . $part['Content-ID'] . '>';
}
if (isset($part['Content-Type'])) {
$part_headers['Content-Type'] = $part['Content-Type'];
}
if (isset($part['Content-Disposition'])) {
$part_headers['Content-Disposition'] = $part['Content-Disposition'];
}
elseif (strpos($part['Content-Type'], 'multipart/alternative') === FALSE) {
$part_headers['Content-Disposition'] = 'inline';
}
if (isset($part['Content-Transfer-Encoding'])) {
$part_headers['Content-Transfer-Encoding'] = $part['Content-Transfer-Encoding'];
}
// Mail content provided as a string.
if (isset($part['content']) && $part['content']) {
if (!isset($part['Content-Transfer-Encoding'])) {
$part_headers['Content-Transfer-Encoding'] = '8bit';
}
$part_body = $part['content'];
if (isset($part['name'])) {
$part_headers['Content-Type'] .= '; name="' . $part['name'] . '"';
$part_headers['Content-Disposition'] .= '; filename="' . $part['name'] . '"';
}
// Mail content references in a filename.
}
else {
if (!isset($part['Content-Transfer-Encoding'])) {
$part_headers['Content-Transfer-Encoding'] = 'base64';
}
if (!isset($part['Content-Type'])) {
$part['Content-Type'] = file_get_mimetype($part['file']);
}
if (isset($part['name'])) {
$part_headers['Content-Type'] .= '; name="' . $part['name'] . '"';
$part_headers['Content-Disposition'] .= '; filename="' . $part['name'] . '"';
}
if (isset($part['file'])) {
$file = (is_file($part['file'])) ? file_get_contents($part['file']) : $part['file'];
$part_body = chunk_split(base64_encode($file), 76, variable_get('mimemail_crlf', "\n"));
}
}
$body .= "\n--$boundary\n";
$body .= mimemail_rfc_headers($part_headers) . "\n\n";
$body .= isset($part_body) ? $part_body : '';
}
$body .= "\n--$boundary--\n";
return array('headers' => $headers, 'body' => $body);
}
/**
* Callback for preg_replace_callback().
*/
function _mimemail_expand_links($matches) {
return $matches[1] . _mimemail_url($matches[2]);
}
/**
* Generate a multipart message body with a text alternative for some HTML text.
*
* @param string $body
* The HTML message body.
* @param string $subject
* The message subject.
* @param boolean $plain
* (optional) Whether the recipient prefers plaintext-only messages. Defaults to FALSE.
* @param string $plaintext
* (optional) The plaintext message body.
* @param array $attachments
* (optional) The files to be attached to the message.
*
* @return array
* An associative array containing the following elements:
* - body: A string containing the MIME-encoded multipart body of a mail.
* - headers: An array that includes some headers for the mail to be sent.
*
* The first mime part is a multipart/alternative containing mime-encoded sub-parts for
* HTML and plaintext. Each subsequent part is the required image or attachment.
*/
function mimemail_html_body($body, $subject, $plain = FALSE, $plaintext = NULL, $attachments = array()) {
if (empty($plaintext)) {
// @todo Remove once filter_xss() can handle direct descendant selectors in inline CSS.
// @see http://drupal.org/node/1116930
// @see http://drupal.org/node/370903
// Pull out the message body.
preg_match('|<body.*?</body>|mis', $body, $matches);
$plaintext = drupal_html_to_text($matches[0]);
}
if ($plain) {
// Plain mail without attachment.
if (empty($attachments)) {
$content_type = 'text/plain';
return array(
'body' => $plaintext,
'headers' => array('Content-Type' => 'text/plain; charset=utf-8'),
);
}
// Plain mail with attachement.
else {
$content_type = 'multipart/mixed';
$parts = array(array(
'content' => $plaintext,
'Content-Type' => 'text/plain; charset=utf-8',
));
}
}
else {
$content_type = 'multipart/mixed';
$plaintext_part = array('Content-Type' => 'text/plain; charset=utf-8', 'content' => $plaintext);
// Expand all local links.
$pattern = '/(<a[^>]+href=")([^"]*)/mi';
$body = preg_replace_callback($pattern, '_mimemail_expand_links', $body);
$mime_parts = mimemail_extract_files($body);
$content = array($plaintext_part, array_shift($mime_parts));
$content = mimemail_multipart_body($content, 'multipart/alternative', TRUE);
$parts = array(array('Content-Type' => $content['headers']['Content-Type'], 'content' => $content['body']));
if ($mime_parts) {
$parts = array_merge($parts, $mime_parts);
$content = mimemail_multipart_body($parts, 'multipart/related; type="multipart/alternative"', TRUE);
$parts = array(array('Content-Type' => $content['headers']['Content-Type'], 'content' => $content['body']));
}
}
if (is_array($attachments) && !empty($attachments)) {
foreach ($attachments as $a) {
$a = (object) $a;
$path = isset($a->uri) ? $a->uri : (isset($a->filepath) ? $a->filepath : NULL);
$content = isset($a->filecontent) ? $a->filecontent : NULL;
$name = isset($a->filename) ? $a->filename : NULL;
$type = isset($a->filemime) ? $a->filemime : NULL;
_mimemail_file($path, $content, $name, $type, 'attachment');
$parts = array_merge($parts, _mimemail_file());
}
}
return mimemail_multipart_body($parts, $content_type);
}
/**
* Helper function to format URLs.
*
* @param string $url
* The file path.
*
* @return string
* A processed URL.
*/
function _mimemail_url($url, $embed_file = NULL) {
global $base_url;
$url = urldecode($url);
// If the URL is absolute or a mailto, return it as-is.
if (strpos($url, '://') !== FALSE || preg_match('!(mailto|callto|tel)\:!', $url)) {
$url = str_replace(' ', '%20', $url);
return $url;
}
// If the image embedding is disabled, return the absolute URL for the image.
elseif (variable_get('mimemail_linkonly', 0) && preg_match('!\.(png|gif|jpg|jpeg)$!i', $url)) {
$url = $base_url . $url;
$url = str_replace(' ', '%20', $url);
return $url;
}
$url = preg_replace('!^' . base_path() . '!', '', $url, 1);
// If we're processing to embed the file, we're done here so return.
if ($embed_file) {
return $url;
}
if (!preg_match('!^\?q=*!', $url)) {
$strip_clean = TRUE;
}
$url = str_replace('?q=', '', $url);
@list($url, $fragment) = explode('#', $url, 2);
@list($path, $query) = explode('?', $url, 2);
// If we're dealing with an intra-document reference, return it.
if (empty($path)) {
return '#' . $fragment;
}
// Get a list of enabled languages.
$languages = language_list('enabled');
$languages = $languages[1];
// Default language settings.
$prefix = '';
$language = language_default();
// Check for language prefix.
$args = explode('/', $path);
foreach ($languages as $lang) {
if ($args[0] == $lang->prefix) {
$prefix = array_shift($args);
$language = $lang;
$path = implode('/', $args);
break;
}
}
$options = array(
'query' => ($query) ? drupal_get_query_array($query) : array(),
'fragment' => $fragment,
'absolute' => TRUE,
'language' => $language,
'prefix' => $prefix,
);
$url = url($path, $options);
// If url() added a ?q= where there should not be one, remove it.
if (isset($strip_clean) && $strip_clean) {
$url = preg_replace('!\?q=!', '', $url);
}
$url = str_replace('+', '%2B', $url);
return $url;
}
/**
* Formats an address string.
*
* @todo Could use some enhancement and stress testing.
*
* @param mixed $address
* A user object, a text email address or an array containing name, mail.
* @param boolean $simplify
* Determines if the address needs to be simplified. Defaults to FALSE.
*
* @return string
* A formatted address string or FALSE.
*/
function mimemail_address($address, $simplify = FALSE) {
if (is_array($address)) {
// It's an array containing 'mail' and/or 'name'.
if (isset($address['mail'])) {
$output = '';
if (empty($address['name']) || $simplify) {
return $address['mail'];
}
else {
return '"' . addslashes(mime_header_encode($address['name'])) . '" <' . $address['mail'] . '>';
}
}
// It's an array of address items.
$addresses = array();
foreach ($address as $a) {
$addresses[] = mimemail_address($a);
}
return $addresses;
}
// It's a user object.
if (is_object($address) && isset($address->mail)) {
if (empty($address->name) || $simplify) {
return $address->mail;
}
else {
return '"' . addslashes(mime_header_encode($address->name)) . '" <' . $address->mail . '>';
}
}
// It's formatted or unformatted string.
// @todo: shouldn't assume it's valid - should try to re-parse
if (is_string($address)) {
return $address;
}
return FALSE;
}

View File

@@ -0,0 +1,21 @@
name = Mime Mail
description = Send MIME-encoded emails with embedded images and attachments.
dependencies[] = mailsystem
package = Mail
core = 7.x
configure = admin/config/system/mimemail
files[] = includes/mimemail.mail.inc
; Tests
files[] = tests/mimemail.test
files[] = tests/mimemail_rules.test
files[] = tests/mimemail_compress.test
; Information added by drupal.org packaging script on 2013-09-06
version = "7.x-1.0-alpha2+30-dev"
core = "7.x"
project = "mimemail"
datestamp = "1378431585"

View File

@@ -0,0 +1,111 @@
<?php
/**
* @file
* Install, update and uninstall functions for Mime Mail module.
*/
/**
* Implements hook_install().
*/
function mimemail_install() {
user_role_grant_permissions(DRUPAL_AUTHENTICATED_RID, array('edit mimemail user settings'));
}
/**
* Implements hook_enable().
*/
function mimemail_enable() {
module_load_include('module', 'mailsystem');
mailsystem_set(
array(
mailsystem_default_id() => 'MimeMailSystem',
'mimemail' => 'MimeMailSystem',
)
);
}
/**
* Implements hook_disable().
*/
function mimemail_disable() {
mailsystem_clear(array('mimemail' => 'MimeMailSystem'));
variable_set('mimemail_alter', FALSE);
}
/**
* Implements hook_uninstall().
*/
function mimemail_uninstall() {
$variables = array(
'mimemail_alter',
'mimemail_crlf',
'mimemail_engine',
'mimemail_incoming',
'mimemail_key',
'mimemail_textonly',
'mimemail_sitestyle',
'mimemail_name',
'mimemail_mail',
'mimemail_format',
'mimemail_simple_address',
'mimemail_linkonly',
'mimemail_preserve_class'
);
foreach ($variables as $variable) {
variable_del($variable);
}
}
/**
* Implements hook_requirements().
*
* Ensures that the newly-required Mail System module is available, or else
* disables the Mime Mail module and returns an informative error message.
*/
function mimemail_requirements($phase) {
if ($phase === 'install' || module_exists('mailsystem')) {
return array();
}
$args = array(
'!mailsystem' => url('http://drupal.org/project/mailsystem'),
'%mailsystem' => 'Mail System',
'!mimemail' => url('http://drupal.org/project/mimemail'),
'%mimemail' => 'Mime Mail',
);
if ( module_enable(array('mailsystem'))
&& module_load_include('module', 'mailsystem')
) {
drupal_set_message(
t('The %mailsystem module has been enabled because the %mimemail module now requires it.', $args)
);
return array();
}
return array(
'mimemail_mailsystem' => array(
'title' => t('%mailsystem module', $args),
'value' => t('Not installed'),
'description' => t(
'The <a href="!smtp">%mimemail</a> module dependencies have changed. Please download and install the required <a href="!mailsystem">%mailsystem</a> module, then re-enable the <a href="!mimemail">%mimemail</a> module.', $args
),
'severity' => REQUIREMENT_ERROR,
),
);
}
/**
* Check installation requirements.
*/
function mimemail_update_7000() {
if ($requirements = mimemail_requirements('runtime')) {
throw new DrupalUpdateException($requirements['mimemail_mailsystem']['description']);
}
}
/**
* Deletes useless variables.
*/
function mimemail_update_7001() {
variable_del('mimemail_theme');
}

View File

@@ -0,0 +1,384 @@
<?php
/**
* @file
* Component module for sending Mime-encoded emails.
*/
/**
* Implements hook_menu().
*/
function mimemail_menu() {
$path = drupal_get_path('module', 'mimemail') . '/includes';
// Configuration links.
$items['admin/config/system/mimemail'] = array(
'title' => 'Mime Mail',
'description' => 'Manage mime mail system settings.',
'page callback' => 'drupal_get_form',
'page arguments' => array('mimemail_admin_settings'),
'access arguments' => array('administer site configuration'),
'file' => 'mimemail.admin.inc',
'file path' => $path,
);
$items['mimemail'] = array(
'page callback' => 'mimemail_post',
'access callback' => 'mimemail_incoming_access',
'type' => MENU_CALLBACK,
'file' => 'mimemail.incoming.inc',
'file path' => $path,
);
return $items;
}
/**
* Implements hook_permission().
*/
function mimemail_permission() {
return array(
'edit mimemail user settings' => array(
'title' => t('Edit Mime Mail user settings'),
'description' => t('Edit user specific settings for Mime Mail.'),
),
);
}
/**
* Access callback to process incoming messages.
*/
function mimemail_incoming_access() {
return variable_get('mimemail_incoming', FALSE);
}
/**
* Implements hook_field_extra_fields().
*/
function mimemail_field_extra_fields() {
$extra['user']['user'] = array(
'form' => array(
'mimemail' => array(
'label' => t('Email'),
'description' => t('Mime Mail module settings form elements.'),
'weight' => 0,
),
),
'display' => array(
'mimemail' => array(
'label' => t('Email'),
'description' => t('Mime Mail module settings form elements.'),
'weight' => 0,
),
),
);
return $extra;
}
/**
* Implements hook_user_view().
*/
function mimemail_user_view($account, $view_mode, $langcode) {
$account->content['mimemail'] = array(
'#type' => 'user_profile_category',
'#title' => t('Email'),
);
$account->content['mimemail']['textonly'] = array(
'#type' => 'user_profile_item',
'#title' => t('Plaintext email only'),
'#markup' => empty($account->data['mimemail_textonly']) ? t('No') : t('Yes'),
);
}
/**
* Implements hook_form_FORM_ID_alter().
*
* Adds the Mime Mail settings on the user settings page.
*/
function mimemail_form_user_profile_form_alter(&$form, &$form_state) {
if ($form['#user_category'] == 'account') {
$account = $form['#user'];
$form['mimemail'] = array(
'#type' => 'fieldset',
'#title' => t('Email settings'),
'#weight' => 5,
'#collapsible' => TRUE,
'#access' => user_access('edit mimemail user settings'),
);
$form['mimemail']['mimemail_textonly'] = array(
'#type' => 'checkbox',
'#title' => t('Plaintext email only'),
'#default_value' => !empty($account->data['mimemail_textonly']) ? $account->data['mimemail_textonly'] : FALSE,
'#description' => t('Check this option if you do not wish to receive email messages with graphics and styles.'),
);
}
}
/**
* Implements hook_user_presave().
*/
function mimemail_user_presave(&$edit, $account, $category) {
$edit['data']['mimemail_textonly'] = isset($edit['mimemail_textonly']) ? $edit['mimemail_textonly'] : 0;
}
/**
* Implements hook_theme().
*/
function mimemail_theme() {
module_load_include('inc', 'mimemail', 'theme/mimemail.theme');
return mimemail_theme_theme();
}
/**
* Implements hook_mail().
*/
function mimemail_mail($key, &$message, $params) {
$context = $params['context'];
// Prepare the array of the attachments.
$attachments = array();
$attachments_string = trim($params['attachments']);
if (!empty($attachments_string)) {
$attachment_lines = array_filter(explode("\n", trim($attachments_string)));
foreach ($attachment_lines as $filepath) {
$attachments[] = array(
'filepath' => trim($filepath),
);
}
}
// We handle different address headers if set.
$address_headers = array(
'cc' => 'Cc',
'bcc' => 'Bcc',
'reply-to' => 'Reply-to',
);
foreach ($address_headers as $param_key => $address_header) {
$params[$param_key] = empty($params[$param_key]) ? array() : explode(',', $params[$param_key]);
if (!empty($params[$param_key])) {
foreach ($params[$param_key] as $key => $address) {
$params[$param_key][$key] = token_replace($address, $context);
}
$message['headers'][$address_header] = implode(',', $params[$param_key]);
}
}
$message['to'] = token_replace($message['to'], $context);
$message['subject'] = token_replace($context['subject'], $context);
$message['body'][] = token_replace($context['body'], $context);
$message['params']['plaintext'] = token_replace($params['plaintext'], $context);
$message['params']['attachments'] = $attachments;
}
/**
* Retreives a list of all available mailer engines.
*
* @return array
* Mailer engine names.
*/
function mimemail_get_engines() {
$engines = array();
foreach (module_implements('mailengine') as $module) {
$engines[$module] = module_invoke($module, 'mailengine', 'list');
}
return $engines;
}
/**
* Implements hook_mailengine().
*
* @param string $op
* The operation to perform on the message.
* @param array $message
* The message to perform the operation on.
*
* @return boolean
* Returns TRUE if the operation was successful or FALSE if it was not.
*/
function mimemail_mailengine($op, $message = array()) {
module_load_include('inc', 'mimemail');
switch ($op) {
case 'list':
$engine = array(
'name' => t('Mime Mail'),
'description' => t("Default mailing engine."),
);
return $engine;
case 'settings':
// Not implemented.
break;
case 'multiple':
case 'single':
case 'send':
// Default values.
$default = array(
'to' => '',
'subject' => '',
'body' => '',
'from' => '',
'headers' => ''
);
$message = array_merge($default, $message);
// If 'Return-Path' isn't already set in php.ini, we pass it separately
// as an additional parameter instead of in the header.
// However, if PHP's 'safe_mode' is on, this is not allowed.
if (isset($message['headers']['Return-Path']) && !ini_get('safe_mode')) {
$return_path_set = strpos(ini_get('sendmail_path'), ' -f');
if (!$return_path_set) {
$return_path = trim($message['headers']['Return-Path'], '<>');
unset($message['headers']['Return-Path']);
}
}
$crlf = variable_get('mimemail_crlf', MAIL_LINE_ENDINGS);
$recipients = (!is_array($message['to'])) ? array($message['to']) : $message['to'];
$subject = mime_header_encode($message['subject']);
$body = preg_replace('@\r?\n@', $crlf, $message['body']);
$headers = mimemail_rfc_headers($message['headers']);
$result = TRUE;
foreach ($recipients as $to) {
if (isset($return_path) && !empty($return_path)) {
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', $return_path);
$result = @mail($to, $subject, $body, $headers) && $result;
ini_set('sendmail_from', $old_from);
}
else {
// On most non-Windows systems, the "-f" option to the sendmail command
// is used to set the Return-Path.
$result = @mail($to, $subject, $body, $headers, '-f' . $return_path) && $result;
}
}
else {
// The optional $additional_parameters argument to mail() is not allowed
// if safe_mode is enabled. Passing any value throws a PHP warning and
// makes mail() return FALSE.
$result = @mail($to, $subject, $body, $headers) && $result;
}
}
return $result;
}
return FALSE;
}
/**
* Prepares the message for sending.
*
* @param array $message
* An array containing the message data. The optional parameters are:
* - plain: Whether to send the message as plaintext only or HTML. If evaluates to TRUE,
* then the message will be sent as plaintext.
* - plaintext: Optional plaintext portion of a multipart email.
* - attachments: An array of arrays which describe one or more attachments.
* Existing files can be added by path, dinamically-generated files
* can be added by content. The internal array contains the following elements:
* - filepath: Relative Drupal path to an existing file (filecontent is NULL).
* - filecontent: The actual content of the file (filepath is NULL).
* - filename: The filename of the file.
* - filemime: The MIME type of the file.
* The array of arrays looks something like this:
* Array
* (
* [0] => Array
* (
* [filepath] => '/sites/default/files/attachment.txt'
* [filecontent] => 'My attachment.'
* [filename] => 'attachment.txt'
* [filemime] => 'text/plain'
* )
* )
*
* @return array
* All details of the message.
*/
function mimemail_prepare_message($message) {
module_load_include('inc', 'mimemail');
$module = $message['module'];
$key = $message['key'];
$to = $message['to'];
$from = $message['from'];
$subject = $message['subject'];
$body = $message['body'];
$headers = isset($message['params']['headers']) ? $message['params']['headers'] : array();
$plain = isset($message['params']['plain']) ? $message['params']['plain'] : NULL;
$plaintext = isset($message['params']['plaintext']) ? $message['params']['plaintext'] : NULL;
$attachments = isset($message['params']['attachments']) ? $message['params']['attachments'] : array();
$site_name = variable_get('site_name', 'Drupal');
$site_mail = variable_get('site_mail', ini_get('sendmail_from'));
$simple_address = variable_get('mimemail_simple_address', 0);
// Override site mails default sender when using default engine.
if ((empty($from) || $from == $site_mail)
&& variable_get('mimemail_engine', 'mimemail') == 'mimemail') {
$mimemail_name = variable_get('mimemail_name', $site_name);
$mimemail_mail = variable_get('mimemail_mail', $site_mail);
$from = array(
'name' => !empty($mimemail_name) ? $mimemail_name : $site_name,
'mail' => !empty($mimemail_mail) ? $mimemail_mail : $site_mail,
);
}
// Body is empty, this is a plaintext message.
if (empty($body)) {
$plain = TRUE;
}
// Try to determine recipient's text mail preference.
elseif (is_null($plain)) {
if (is_object($to) && isset($to->data['mimemail_textonly'])) {
$plain = $to->data['mimemail_textonly'];
}
elseif (is_string($to) && valid_email_address($to)) {
if (is_object($account = user_load_by_mail($to)) && isset($account->data['mimemail_textonly'])) {
$plain = $account->data['mimemail_textonly'];
// Might as well pass the user object to the address function.
$to = $account;
}
}
}
$subject = str_replace(array(" \n", "\n"), '', trim(drupal_html_to_text($subject)));
$hook = array(
'mimemail_message__' . $key,
'mimemail_message__' . $module .'__'. $key,
);
$variables = array(
'module' => $module,
'key' => $key,
'recipient' => $to,
'subject' => $subject,
'body' => $body
);
$body = theme($hook, $variables);
foreach (module_implements('mail_post_process') as $module) {
$function = $module . '_mail_post_process';
$function($body, $key);
}
$plain = $plain || variable_get('mimemail_textonly', 0);
$from = mimemail_address($from);
$mail = mimemail_html_body($body, $subject, $plain, $plaintext, $attachments);
$headers = array_merge($message['headers'], $headers, $mail['headers']);
$message['to'] = mimemail_address($to, $simple_address);
$message['from'] = $from;
$message['subject'] = $subject;
$message['body'] = $mail['body'];
$message['headers'] = mimemail_headers($headers, $from);
return $message;
}

View File

@@ -0,0 +1,353 @@
<?php
/**
* @file
* Rules actions for sending Mime-encoded emails.
*
* @addtogroup rules
* @{
*/
/**
* Implements hook_rules_action_info().
*/
function mimemail_rules_action_info() {
return array(
'mimemail' => array(
'label' => t('Send HTML e-mail'),
'group' => t('System'),
'parameter' => array(
'key' => array(
'type' => 'text',
'label' => t('Key'),
'description' => t('A key to identify the e-mail sent.'),
),
'to' => array(
'type' => 'text',
'label' => t('To'),
'description' => t("The mail's recipient address. The formatting of this string must comply with RFC 2822."),
),
'cc' => array(
'type' => 'text',
'label' => t('CC Recipient'),
'description' => t("The mail's carbon copy address. You may separate multiple addresses with comma."),
'optional' => TRUE,
),
'bcc' => array(
'type' => 'text',
'label' => t('BCC Recipient'),
'description' => t("The mail's blind carbon copy address. You may separate multiple addresses with comma."),
'optional' => TRUE,
),
'from_name' => array(
'type' => 'text',
'label' => t('Sender name'),
'description' => t("The sender's name. Leave it empty to use the site-wide configured name."),
'optional' => TRUE,
),
'from_mail' => array(
'type' => 'text',
'label' => t('Sender e-mail address'),
'description' => t("The sender's address. Leave it empty to use the site-wide configured address."),
'optional' => TRUE,
),
'reply_to' => array(
'type' => 'text',
'label' => t('Reply e-mail address'),
'description' => t("The address to reply to. Leave it empty to use the sender's address."),
'optional' => TRUE,
),
'subject' => array(
'type' => 'text',
'label' => t('Subject'),
'description' => t("The mail's subject."),
'translatable' => TRUE,
),
'body' => array(
'type' => 'text',
'label' => t('Body'),
'description' => t("The mail's message HTML body."),
'sanitize' => TRUE,
'optional' => TRUE,
'translatable' => TRUE,
),
'plaintext' => array(
'type' => 'text',
'label' => t('Plain text body'),
'description' => t("The mail's message plaintext body."),
'optional' => TRUE,
'translatable' => TRUE,
),
'attachments' => array(
'type' => 'text',
'label' => t('Attachments'),
'description' => t("The mail's attachments, one file per line e.g. \"files/images/mypic.png\" without quotes."),
'optional' => TRUE,
),
'language' => array(
'type' => 'token',
'label' => t('Language'),
'description' => t('If specified, the language used for getting the mail message and subject.'),
'options list' => 'entity_metadata_language_list',
'optional' => TRUE,
'default value' => LANGUAGE_NONE,
'default mode' => 'selector',
),
),
'base' => 'rules_action_mimemail',
'access callback' => 'rules_system_integration_access',
),
'mimemail_to_users_of_role' => array(
'label' => t('Send HTML mail to all users of a role'),
'group' => t('System'),
'parameter' => array(
'key' => array(
'type' => 'text',
'label' => t('Key'),
'description' => t('A key to identify the e-mail sent.'),
),
'roles' => array(
'type' => 'list<integer>',
'label' => t('Roles'),
'options list' => 'entity_metadata_user_roles',
'description' => t('Select the roles whose users should receive the mail.'),
),
'active' => array(
'type' => 'boolean',
'label' =>('Send to active users'),
'description' => t('Send mail only to active users.'),
),
'from_name' => array(
'type' => 'text',
'label' => t('Sender name'),
'description' => t("The sender's name. Leave it empty to use the site-wide configured name."),
'optional' => TRUE,
),
'from_mail' => array(
'type' => 'text',
'label' => t('Sender e-mail address'),
'description' => t("The sender's address. Leave it empty to use the site-wide configured address."),
'optional' => TRUE,
),
'subject' => array(
'type' => 'text',
'label' => t('Subject'),
'description' => t("The mail's subject."),
'translatable' => TRUE,
),
'body' => array(
'type' => 'text',
'label' => t('Body'),
'description' => t("The mail's message HTML body."),
'optional' => TRUE,
'translatable' => TRUE,
),
'plaintext' => array(
'type' => 'text',
'label' => t('Plaintext body'),
'description' => t("The mail's message plaintext body."),
'optional' => TRUE,
'translatable' => TRUE,
),
'attachments' => array(
'type' => 'text',
'label' => t('Attachments'),
'description' => t("The mail's attachments, one file per line e.g. \"files/images/mypic.png\" without quotes."),
'optional' => TRUE,
),
'language_user' => array(
'type' => 'boolean',
'label' => t("Send mail in each recipient's language"),
'description' => t("If checked, the mail message and subject will be sent in each user's preferred language. <strong>You can safely leave the language selector below empty if this option is selected.</strong>"),
),
'language' => array(
'type' => 'token',
'label' => t('Fixed language'),
'description' => t('If specified, the fixed language used for getting the mail message and subject.'),
'options list' => 'entity_metadata_language_list',
'optional' => TRUE,
'default value' => LANGUAGE_NONE,
'default mode' => 'selector',
),
),
'base' => 'rules_action_mimemail_to_users_of_role',
'access callback' => 'rules_system_integration_access',
),
);
}
/**
* Implements hook_rules_action_base_upgrade_map_name().
*/
function mimemail_rules_action_mail_upgrade_map_name($element) {
return 'mimemail';
}
/**
* Implements hook_rules_action_base_upgrade_map_name().
*/
function mimemail_rules_action_mail_to_user_upgrade_map_name($element) {
return 'mimemail';
}
/**
* Implements hook_rules_action_base_upgrade_map_name().
*/
function mimemail_rules_action_mail_to_users_of_role_upgrade_map_name($element) {
return 'mimemail_to_users_of_role';
}
/**
* Implements hook_rules_action_base_upgrade().
*/
function mimemail_rules_action_mail_upgrade($element, RulesPlugin $target) {
$target->settings['key'] = $element['#settings']['key'];
$target->settings['from_name'] = $element['#settings']['sender'];
$target->settings['from_mail'] = $element['#settings']['from'];
$target->settings['body'] = $element['#settings']['message_html'];
$target->settings['plaintext'] = $element['#settings']['message_plaintext'];
}
/**
* Implements hook_rules_action_base_upgrade().
*/
function mimemail_rules_action_mail_to_user_upgrade($element, RulesPlugin $target) {
switch ($element['#settings']['#argument map']['user']) {
case 'author':
$token = 'node:author';
break;
case 'author_unchanged':
$token = 'node-unchanged:author';
break;
case 'user':
$token = 'site:current-user';
break;
}
$target->settings['to:select'] = $token . ':mail';
mimemail_rules_action_mail_upgrade($element, $target);
}
/**
* Implements hook_rules_action_base_upgrade().
*/
function mimemail_rules_action_mail_to_users_of_role_upgrade($element, RulesPlugin $target) {
$target->settings['roles'] = $element['#settings']['recipients'];
mimemail_rules_action_mail_upgrade($element, $target);
}
/**
* Action Implementation: Send HTML mail.
*/
function rules_action_mimemail($key, $to, $cc = NULL, $bcc = NULL, $from_name = NULL, $from_mail = NULL, $reply_to = NULL, $subject, $body, $plaintext = NULL, $attachments = array(), $langcode, $settings, RulesState $state, RulesPlugin $element) {
module_load_include('inc', 'mimemail');
// Set the sender name and from address.
if (empty($from_mail)) {
$from = NULL;
}
else {
$from = array(
'name' => $from_name,
'mail' => $from_mail,
);
// Create an address string.
$from = mimemail_address($from);
}
// Figure out the language to use - fallback is the system default.
$languages = language_list();
$language = isset($languages[$langcode]) ? $languages[$langcode] : language_default();
$params = array(
'context' => array(
'subject' => $subject,
'body' => $body,
'action' => $element,
'state' => $state,
),
'cc' => $cc,
'bcc' => $bcc,
'reply-to' => $reply_to,
'plaintext' => $plaintext,
'attachments' => $attachments,
);
drupal_mail('mimemail', $key, $to, $language, $params, $from);
}
/**
* Action: Send HTML mail to all users of a specific role group(s).
*/
function rules_action_mimemail_to_users_of_role($key, $roles, $active, $from_name = NULL, $from_mail = NULL, $subject, $body, $plaintext = NULL, $attachments = array(), $use_userlang = FALSE, $langcode= NULL, $settings, RulesState $state, RulesPlugin $element) {
module_load_include('inc', 'mimemail');
// Set the sender name and from address.
if (empty($from_mail)) {
$from = NULL;
}
else {
$from = array(
'name' => $from_name,
'mail' => $from_mail,
);
// Create an address string.
$from = mimemail_address($from);
}
$query = db_select('users', 'u');
$query->fields('u', array('mail', 'language'));
if ($active) {
$query->condition('u.status', 1, '=');
}
if (in_array(DRUPAL_AUTHENTICATED_RID, $roles)) {
$query->condition('u.uid', 0, '>');
}
else {
$query->join('users_roles', 'r', 'u.uid = r.uid');
$query->condition('r.rid', $roles, 'IN');
$query->distinct();
}
$result = $query->execute();
$params = array(
'context' => array(
'subject' => $subject,
'body' => $body,
'action' => $element,
'state' => $state,
),
'plaintext' => $plaintext,
'attachments' => $attachments,
);
// Create language list before initializing foreach.
$languages = language_list();
$message = array('result' => TRUE);
foreach ($result as $row) {
// Decide which language to use.
if (!$use_userlang || empty($row->language) || !isset($languages[$row->language])) {
$language = isset($languages[$langcode]) ? $languages[$langcode] : language_default();
}
else {
$language = $languages[$row->language];
}
$message = drupal_mail('mimemail', $key, $row->mail, $language, $params, $from);
if (!$message['result']) {
break;
}
}
if ($message['result']) {
$role_names = array_intersect_key(user_roles(TRUE), array_flip($roles));
watchdog('rules', 'Successfully sent HTML email to the role(s) %roles.', array('%roles' => implode(', ', $role_names)));
}
}
/**
* @}
*/

View File

@@ -0,0 +1,14 @@
name = "Mime Mail Action"
description = "Provide actions for Mime Mail."
package = Mail
dependencies[] = mimemail
dependencies[] = trigger
core = 7.x
; Information added by drupal.org packaging script on 2013-09-06
version = "7.x-1.0-alpha2+30-dev"
core = "7.x"
project = "mimemail"
datestamp = "1378431585"

View File

@@ -0,0 +1,193 @@
<?php
/**
* @file
* Provide actions for Mime Mail.
*/
/**
* Implements hook_action_info().
*/
function mimemail_action_info() {
return array(
'mimemail_send_email_action' => array(
'type' => 'system',
'label' => t('Send HTML e-mail'),
'configurable' => TRUE,
'triggers' => array('any'),
),
);
}
/**
* Implements a configurable Drupal action. Sends an email.
*/
function mimemail_send_email_action($entity, $context) {
if (empty($context['node'])) {
$context['node'] = $entity;
}
$to = token_replace($context['to'], $context);
// If the recipient is a registered user with a language preference, use
// the recipient's preferred language. Otherwise, use the system default
// language.
$account = user_load_by_mail($to);
if ($account) {
$language = user_preferred_language($account);
}
else {
$language = language_default();
}
$params = array(
'context' => array(
'subject' => token_replace($context['subject'], $context),
'body' => token_replace($context['body'], $context),
),
'key' => $context['key'],
'cc' => $context['cc'],
'bcc' => $context['bcc'],
'reply-to' => $context['reply-to'],
'plaintext' => token_replace($context['plaintext'], $context),
'attachments' => $context['attachments'],
);
drupal_mail('mimemail', $context['key'], $to, $language, $params);
}
/**
* Form for configurable Drupal action to send an HTML mail.
*/
function mimemail_send_email_action_form($context) {
$context += array(
'key' => '',
'to' => '',
'cc' => '',
'bcc' => '',
'reply-to' => '',
'subject' => '',
'body' => '',
'format' => filter_fallback_format(),
'plaintext' => '',
'attachments' => ''
);
$form['key'] = array(
'#type' => 'textfield',
'#title' => t('Key'),
'#default_value' => $context['key'],
'#description' => t('A key to identify the e-mail sent.'),
'#required' => TRUE,
);
$form['to'] = array(
'#type' => 'textfield',
'#title' => t('Recipient'),
'#default_value' => $context['to'],
'#maxlength' => 254,
'#description' => t('The email address to which the message should be sent OR enter [node:author:mail], [comment:author:mail], etc. if you would like to send an e-mail to the author of the original post.'),
'#required' => TRUE,
);
$form['cc'] = array(
'#type' => 'textfield',
'#title' => t('CC Recipient'),
'#default_value' => $context['cc'],
'#description' => t("The mail's carbon copy address. You may separate multiple addresses with comma."),
'#required' => FALSE,
);
$form['bcc'] = array(
'#type' => 'textfield',
'#title' => t('BCC Recipient'),
'#default_value' => $context['bcc'],
'#description' => t("The mail's blind carbon copy address. You may separate multiple addresses with comma."),
'#required' => FALSE,
);
$form['reply-to'] = array(
'#type' => 'textfield',
'#title' => t('Reply e-mail address'),
'#default_value' => $context['reply-to'],
'#description' => t("The address to reply to. Leave it empty to use the sender's address."),
'#required' => FALSE,
);
$form['subject'] = array(
'#type' => 'textfield',
'#title' => t('Subject'),
'#maxlength' => 254,
'#default_value' => $context['subject'],
'#description' => t("The subject of the message."),
);
$form['body'] = array(
'#type' => 'text_format',
'#title' => t('Body'),
'#default_value' => $context['body'],
'#format' => $context['format'],
'#description' => t('The HTML message that should be sent. You may include placeholders like [node:title], [user:name], and [comment:body] to represent data that will be different each time message is sent. Not all placeholders will be available in all contexts.'),
);
$form['plaintext'] = array(
'#type' => 'textarea',
'#title' => t('Plain text body'),
'#default_value' => $context['plaintext'],
'#description' => t('Optional plaintext portion of a multipart message. You may include placeholders like [node:title], [user:name], and [comment:body] to represent data that will be different each time message is sent. Not all placeholders will be available in all contexts.'),
);
$form['attachments'] = array(
'#type' => 'textarea',
'#title' => t('Attachments'),
'#default_value' => $context['attachments'],
'#description' => t('A list of attachments, one file per line e.g. "files/images/mypic.png" without quotes.'),
);
return $form;
}
/**
* Validate the action form.
*/
function mimemail_send_email_action_validate($form, $form_state) {
$to = trim($form_state['values']['to']);
if (!valid_email_address($to) && strpos($to, ':mail') === FALSE) {
form_set_error('to', t('Enter a valid email address or use a token e-mail address such as %author.', array('%author' => '[node:author:mail]')));
}
$cc = explode(',', $form_state['values']['cc']);
foreach ($cc as $recipient) {
$recipient = trim($recipient);
if (!empty($recipient) && !valid_email_address($recipient) && strpos($recipient, ':mail') === FALSE) {
form_set_error('cc', t('Enter a valid email address or use a token e-mail address such as %author.', array('%author' => '[node:author:mail]')));
}
}
$bcc = explode(',', $form_state['values']['bcc']);
foreach ($bcc as $recipient) {
$recipient = trim($recipient);
if (!empty($recipient) && !valid_email_address($recipient) && strpos($recipient, ':mail') === FALSE) {
form_set_error('bcc', t('Enter a valid email address or use a token e-mail address such as %author.', array('%author' => '[node:author:mail]')));
}
}
$reply_to = trim($form_state['values']['reply-to']);
if (!empty($reply_to) && !valid_email_address($reply_to) && strpos($reply_to, ':mail') === FALSE) {
form_set_error('reply-to', t('Enter a valid email address or use a token e-mail address such as %author.', array('%author' => '[node:author:mail]')));
}
}
/**
* Handle submission of the action form.
*/
function mimemail_send_email_action_submit($form, $form_state) {
$form_values = $form_state['values'];
$params = array(
'key' => $form_values['key'],
'to' => $form_values['to'],
'cc' => $form_values['cc'],
'bcc' => $form_values['bcc'],
'reply-to' => $form_values['reply-to'],
'subject' => $form_values['subject'],
'body' => $form_values['body']['value'],
'format' => $form_values['body']['format'],
'plaintext' => $form_values['plaintext'],
'attachments' => $form_values['attachments'],
);
return $params;
}

View File

@@ -0,0 +1,259 @@
<?php
/**
* @file
* Converts CSS styles into inline style attributes.
*
* Code based on Emogrifier by Pelago Design (http://www.pelagodesign.com).
*/
/**
* Separate CSS from HTML for processing
*/
function mimemail_compress_clean_message($message) {
$parts = array();
preg_match('|(<style[^>]+)>(.*)</style>|mis', $message, $matches);
if (isset($matches[0]) && isset($matches[2])) {
$css = str_replace('<!--', '', $matches[2]);
$css = str_replace('-->', '', $css);
$css = preg_replace('|\{|', "\n{\n", $css);
$css = preg_replace('|\}|', "\n}\n", $css);
$html = str_replace($matches[0], '', $message);
$parts = array('html' => $html, 'css' => $css);
}
return $parts;
}
/**
* Compress HTML and CSS into combined message
*/
class mimemail_compress {
private $html = '';
private $css = '';
private $unprocessable_tags = array('wbr');
public function mimemail_compress($html = '', $css = '') {
$this->html = $html;
$this->css = $css;
}
// There are some HTML tags that DOMDocument cannot process,
// and will throw an error if it encounters them.
// These functions allow you to add/remove them if necessary.
// It only strips them from the code (does not remove actual nodes).
public function add_unprocessable_tag($tag) {
$this->unprocessable_tags[] = $tag;
}
public function remove_unprocessable_tag($tag) {
if (($key = array_search($tag, $this->unprocessable_tags)) !== FALSE) {
unset($this->unprocessableHTMLTags[$key]);
}
}
public function compress() {
if (!class_exists('DOMDocument', FALSE)) {
return $this->html;
}
$body = $this->html;
// Process the CSS here, turning the CSS style blocks into inline CSS.
if (count($this->unprocessable_tags)) {
$unprocessable_tags = implode('|', $this->unprocessable_tags);
$body = preg_replace("/<($unprocessable_tags)[^>]*>/i", '', $body);
}
$err = error_reporting(0);
$doc = new DOMDocument();
// Try to set character encoding.
if (function_exists('mb_convert_encoding')) {
$body = mb_convert_encoding($body, 'HTML-ENTITIES', "UTF-8");
$doc->encoding= "UTF-8";
}
$doc->strictErrorChecking = FALSE;
$doc->formatOutput = TRUE;
$doc->loadHTML($body);
$doc->normalizeDocument();
$xpath = new DOMXPath($doc);
// Get rid of comments.
$css = preg_replace('/\/\*.*\*\//sU', '', $this->css);
// Process the CSS file for selectors and definitions.
preg_match_all('/^\s*([^{]+){([^}]+)}/mis', $css, $matches);
$all_selectors = array();
foreach ($matches[1] as $key => $selector_string) {
// If there is a blank definition, skip.
if (!strlen(trim($matches[2][$key]))) continue;
// Else split by commas and duplicate attributes so we can sort by selector precedence.
$selectors = explode(',', $selector_string);
foreach ($selectors as $selector) {
// Don't process pseudo-classes.
if (strpos($selector, ':') !== FALSE) continue;
$all_selectors[] = array(
'selector' => $selector,
'attributes' => $matches[2][$key],
'index' => $key, // Keep track of where it appears in the file, since order is important.
);
}
}
// Now sort the selectors by precedence.
usort($all_selectors, array('self', 'sort_selector_precedence'));
// Before we begin processing the CSS file, parse the document for inline
// styles and append the normalized properties (i.e., 'display: none'
// instead of 'DISPLAY: none') as selectors with full paths (highest
// precedence), so they override any file-based selectors.
$nodes = @$xpath->query('//*[@style]');
if ($nodes->length > 0) {
foreach ($nodes as $node) {
$style = preg_replace_callback('/[A-z\-]+(?=\:)/S', create_function('$matches', 'return strtolower($matches[0]);'), $node->getAttribute('style'));
$all_selectors[] = array(
'selector' => $this->calculateXPath($node),
'attributes' => $style,
);
}
}
foreach ($all_selectors as $value) {
// Query the body for the xpath selector.
$nodes = $xpath->query($this->css_to_xpath(trim($value['selector'])));
foreach ($nodes as $node) {
// If it has a style attribute, get it, process it, and append (overwrite) new stuff.
if ($node->hasAttribute('style')) {
// Break it up into an associative array.
$old_style = $this->css_style_to_array($node->getAttribute('style'));
$new_style = $this->css_style_to_array($value['attributes']);
// New styles overwrite the old styles (not technically accurate, but close enough).
$compressed = array_merge($old_style, $new_style);
$style = '';
foreach ($compressed as $k => $v) {
$style .= (drupal_strtolower($k) . ':' . $v . ';');
}
}
else {
// Otherwise create a new style.
$style = trim($value['attributes']);
}
$node->setAttribute('style', $style);
}
}
// This removes styles from your email that contain display:none. You could comment these out if you want.
$nodes = $xpath->query('//*[contains(translate(@style," ",""), "display:none")]');
foreach ($nodes as $node) {
$node->parentNode->removeChild($node);
}
if (variable_get('mimemail_preserve_class', 0) == FALSE) {
$nodes = $xpath->query('//*[@class]');
foreach ($nodes as $node) {
$node->removeAttribute('class');
}
}
error_reporting($err);
return $doc->saveHTML();
}
private static function sort_selector_precedence($a, $b) {
$precedenceA = self::get_selector_precedence($a['selector']);
$precedenceB = self::get_selector_precedence($b['selector']);
// We want these sorted ascendingly so selectors with lesser precedence get processed first and selectors with greater precedence get sorted last.
return ($precedenceA == $precedenceB) ? ($a['index'] < $b['index'] ? -1 : 1) : ($precedenceA < $precedenceB ? -1 : 1);
}
private static function get_selector_precedence($selector) {
$precedence = 0;
$value = 100;
// Ids: worth 100, classes: worth 10, elements: worth 1.
$search = array('\#', '\.', '');
foreach ($search as $s) {
if (trim($selector == '')) break;
$num = 0;
$selector = preg_replace('/' . $s . '\w+/', '', $selector, -1, $num);
$precedence += ($value * $num);
$value /= 10;
}
return $precedence;
}
/**
* Right now we only support CSS 1 selectors, but include CSS2/3 selectors are fully possible.
*
* @see http://plasmasturm.org/log/444
*/
private function css_to_xpath($selector) {
if (drupal_substr($selector, 0, 1) == '/') {
// Already an XPath expression.
return $selector;
}
// Returns an Xpath selector.
$search = array(
'/\s+>\s+/', // Matches any F element that is a child of an element E.
'/(\w+)\s+\+\s+(\w+)/', // Matches any F element that is a child of an element E.
'/\s+/', // Matches any F element that is a descendant of an E element.
'/(\w)\[(\w+)\]/', // Matches element with attribute.
'/(\w)\[(\w+)\=[\'"]?(\w+)[\'"]?\]/', // Matches element with EXACT attribute.
'/(\w+)?\#([\w\-]+)/e', // Matches id attributes.
'/(\w+|\*)?((\.[\w\-]+)+)/e', // Matches class attributes.
);
$replace = array(
'/',
'\\1/following-sibling::*[1]/self::\\2',
'//',
'\\1[@\\2]',
'\\1[@\\2="\\3"]',
"(strlen('\\1') ? '\\1' : '*').'[@id=\"\\2\"]'",
"(strlen('\\1') ? '\\1' : '*').'[contains(concat(\" \",normalize-space(@class),\" \"),concat(\" \",\"'.implode('\",\" \"))][contains(concat(\" \",normalize-space(@class),\" \"),concat(\" \",\"',explode('.',substr('\\2',1))).'\",\" \"))]'",
);
return '//' . preg_replace($search, $replace, trim($selector));
}
private function css_style_to_array($style) {
$definitions = explode(';', $style);
$css_styles = array();
foreach ($definitions as $def) {
if (empty($def) || strpos($def, ':') === FALSE) continue;
list($key, $value) = explode(':', $def, 2);
if (empty($key) || empty($value)) continue;
$css_styles[trim($key)] = trim($value);
}
return $css_styles;
}
/**
* Get the full path to a DOM node.
*
* @param DOMNode $node
* The node to analyze.
*
* @return string
* The full xpath to a DOM node.
*
* @see http://stackoverflow.com/questions/2643533/php-getting-xpath-of-a-domnode
*/
function calculateXPath(DOMNode $node) {
$xpath = '';
$q = new DOMXPath($node->ownerDocument);
do {
$position = 1 + $q->query('preceding-sibling::*[name()="' . $node->nodeName . '"]', $node)->length;
$xpath = '/' . $node->nodeName . '[' . $position . ']' . $xpath;
$node = $node->parentNode;
}
while (!$node instanceof DOMDocument);
return $xpath;
}
}

View File

@@ -0,0 +1,14 @@
name = Mime Mail CSS Compressor
description = Converts CSS to inline styles in an HTML message. (Requires the PHP DOM extension.)
package = Mail
dependencies[] = mimemail
core = 7.x
files[] = mimemail_compress.inc
; Information added by drupal.org packaging script on 2013-09-06
version = "7.x-1.0-alpha2+30-dev"
core = "7.x"
project = "mimemail"
datestamp = "1378431585"

View File

@@ -0,0 +1,31 @@
<?php
/**
* @file
* Install, update and uninstall functions for Mime Mail Compress module.
*/
/**
* Implements hook_requirements().
*/
function mimemail_compress_requirements($phase) {
$requirements = array();
// Ensure translations don't break at install time.
$t = get_t();
// Test PHP DOM extension.
if (extension_loaded('dom')) {
$requirements['dom']['value'] = $t('Enabled');
}
else {
$requirements['dom'] = array(
'description' => $t('Mime Mail Compress requires the PHP DOM extension to be enabled.'),
'severity' => REQUIREMENT_ERROR,
'value' => $t('Disabled'),
);
}
$requirements['dom']['title'] = $t('PHP DOM extension');
return $requirements;
}

View File

@@ -0,0 +1,21 @@
<?php
/**
* @file
* Component module for sending Mime-encoded emails.
*/
/**
* Implements mail_post_process().
*/
function mimemail_compress_mail_post_process(&$message, $mailkey) {
module_load_include('inc', 'mimemail_compress');
// Separate CSS from HTML for processing.
$parts = mimemail_compress_clean_message($message);
// Compress HTML and CSS into combined message.
if (!empty($parts)) {
$output = new mimemail_compress($parts['html'], $parts['css']);
$output = $output->compress();
$message = $output;
}
}

View File

@@ -0,0 +1,46 @@
<?php
/**
* @file
* Functionality tests for the Mime Mail module.
*
* @ingroup mimemail
*/
require_once(dirname(__FILE__) . '/../mimemail.inc');
/**
* Tests helper functions from the Mime Mail module.
*/
class MimeMailUnitTestCase extends DrupalUnitTestCase {
public static function getInfo() {
return array(
'name' => 'Mime Mail unit tests',
'description' => 'Test that Mime Mail helper functions work properly.',
'group' => 'Mime Mail',
);
}
function setUp() {
drupal_load('module', 'mimemail');
parent::setUp();
}
function testHeaders() {
// Test the regular expression for extracting the mail address.
$chars = array('-', '.', '+', '_');
$name = $this->randomString();
$local = $this->randomName() . $chars[array_rand($chars)] . $this->randomName();
$domain = $this->randomName() . '-' . $this->randomName() . '.' . $this->randomName(rand(2,4));
$headers = mimemail_headers(array(), "$name <$local@$domain>");
$result = $headers['Return-Path'];
$expected = "<$local@$domain>";
$this->assertIdentical($result, $expected, 'Return-Path header field correctly set.');
}
function testUrl() {
$result = _mimemail_url('#');
$this->assertIdentical($result, '#', 'Hash mark URL without fragment left intact.');
}
}

Some files were not shown because too many files have changed in this diff Show More