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,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.');
}
}

View File

@@ -0,0 +1,31 @@
<?php
/**
* @file
* Functionality tests for the Mime Mail Compress module.
*
* @ingroup mimemail
*/
require_once(dirname(__FILE__) . '/../modules/mimemail_compress/mimemail_compress.inc');
/**
* Tests helper functions from the Mime Mail Compress module.
*/
class MimeMailCompressUnitTestCase extends DrupalUnitTestCase {
public static function getInfo() {
return array(
'name' => 'Mime Mail Compress unit tests',
'description' => 'Test that Mime Mail Compress helper functions work properly.',
'group' => 'Mime Mail',
);
}
function setUp() {
drupal_load('module', 'mimemail_compress');
parent::setUp();
}
}

View File

@@ -0,0 +1,225 @@
<?php
/**
* @file
* Functionality tests for the Rules integration in the Mime Mail module.
*
* @ingroup mimemail
*/
/**
* Tests the Rules integration.
*/
class MimeMailRulesTestCase extends DrupalWebTestCase {
/**
* The user with administration permissions.
* @var object
*/
protected $adminUser;
public static function getInfo() {
return array(
'name' => 'Rules integration',
'description' => 'Test the Rules integration.',
'group' => 'Mime Mail',
);
}
public function setUp(array $modules = array()) {
$modules[] = 'mailsystem';
$modules[] = 'locale';
$modules[] = 'entity';
$modules[] = 'entity_token';
$modules[] = 'rules';
$modules[] = 'mimemail';
parent::setUp($modules);
// Create and login user.
$this->adminUser = $this->drupalCreateUser(array(
'access administration pages',
'edit mimemail user settings',
'administer languages',
'administer rules',
'bypass rules access',
'access rules debug',
));
$this->drupalLogin($this->adminUser);
// Enable another language too.
foreach (array('de', 'it') as $langcode) {
$edit = array();
$edit['langcode'] = $langcode;
$this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
}
// Make sure we are not using a stale list.
drupal_static_reset('language_list');
}
/**
* Create mimemail action rule and fire it.
*/
public function testMimemailAction() {
$settings = array(
'key' => 'mail-key-' . $this->randomName(),
'to' => $this->randomName() . '@example.com',
'from' => $this->randomName() . '@example.com',
'subject' => $this->randomName(),
'body' => $this->randomName(60) . '<div></div><br /><hr>',
'plaintext' => $this->randomName(30) . '<div></div><br /><hr>',
);
// Set no language for the mail and check if the system default is used.
$rule = rule();
$rule->action('mimemail', array(
'key' => $settings['key'],
'to' => $settings['to'],
'from_mail' => $settings['from'],
'subject' => $settings['subject'],
'body' => $settings['body'],
'plaintext' => $settings['plaintext'],
'language' => '',
))->save();
$rule->execute();
$mails = $this->drupalGetMails(array('key' => $settings['key']));
$this->assertEqual(count($mails), 1);
$mail = reset($mails);
$this->assertEqual($mail['to'], $settings['to']);
$this->assertEqual($mail['from'], $settings['from']);
$this->assertEqual($mail['subject'], $settings['subject']);
$this->assertEqual($mail['params']['context']['body'], $settings['body']);
$this->assertEqual($mail['params']['plaintext'], $settings['plaintext']);
$this->assertEqual($mail['language']->language, language_default('language'));
// Explicitly set another language for the mail.
$rule_action = $rule->elementMap()->lookup(3);
unset($rule_action->settings['language:select']);
$rule_action->settings['language'] = 'de';
$rule_action->settings['key'] = $settings['key'];
$rule->save();
$rule->execute();
$mails = $this->drupalGetMails(array('key' => $settings['key']));
$this->assertEqual(count($mails), 2);
$mail = end($mails);
$this->assertEqual($mail['language']->language, 'de');
}
/**
* Create mimemail to users by role action rule and fire it.
*/
public function testMimemaiToUsersOfRoleAction() {
$languages = language_list();
// Add more uses and roles.
$users = array(
$this->randomName() . '@example.com' => 'en',
$this->randomName() . '@example.com' => 'de',
$this->randomName() . '@example.com' => 'it',
$this->randomName() . '@example.com' => '',
$this->randomName() . '@example.com' => 'invalid',
);
$mimemail_role = $this->drupalCreateRole(array());
foreach ($users as $email => $language) {
$user = $this->drupalCreateUser(array(
'access administration pages',
));
$user->language = $language;
$user->mail = $email;
$user->roles[$mimemail_role] = $mimemail_role;
user_save($user);
}
$settings = array(
'key' => 'mail-key-' . $this->randomName(),
'from' => $this->randomName() . '@example.com',
'subject' => $this->randomName(),
'body' => $this->randomName(60) . '<div></div><br /><hr>',
'plaintext' => $this->randomName(30) . '<div></div><br /><hr>',
);
// Rest the collected mails.
variable_set('drupal_test_email_collector', array());
// Send mails to all users of a role and respect the language of the users.
// Don't enforce a specific language as fallback use the system default.
$rule = rule();
$rule->action('mimemail_to_users_of_role', array(
'key' => $settings['key'],
'from_mail' => $settings['from'],
'subject' => $settings['subject'],
'body' => $settings['body'],
'plaintext' => $settings['plaintext'],
'roles' => array($mimemail_role => $mimemail_role),
'active' => TRUE,
'language_user' => TRUE,
'language' => '',
));
$rule->save();
$rule->execute();
$mails = $this->drupalGetMails(array('key' => $settings['key']));
$this->assertEqual(count($mails), count($users));
$mail = reset($mails);
$this->assertEqual($mail['from'], $settings['from']);
$this->assertEqual($mail['subject'], $settings['subject']);
$this->assertEqual($mail['params']['context']['body'], $settings['body']);
$this->assertEqual($mail['params']['plaintext'], $settings['plaintext']);
foreach ($mails as $mail) {
// If the user hasn't a proper language the system default has to be used
// if the rules action doesn't provide a language to use.
$user_language = (!empty($languages[$users[$mail['to']]])) ? $users[$mail['to']] : language_default('language');
$this->assertEqual($mail['language']->language, $user_language);
}
// Send mails to all users of a role and respect the language of the users.
// Enforce German as fallback language if an user doesn't have a language.
// Rest the collected mails.
variable_set('drupal_test_email_collector', array());
$rule->elementMap()->lookup(3)->settings['language'] = 'de';
$rule->save();
$rule->execute();
$mails = $this->drupalGetMails(array('key' => $settings['key']));
$this->assertEqual(count($mails), count($users));
foreach ($mails as $mail) {
// If the user hasn't a proper language the language set in the rules
// action has to be used.
$user_language = (!empty($languages[$users[$mail['to']]])) ? $users[$mail['to']] : 'de';
$this->assertEqual($mail['language']->language, $user_language);
}
// Send mails to all users of a role but use the same language for all.
// Rest the collected mails.
variable_set('drupal_test_email_collector', array());
$rule->elementMap()->lookup(3)->settings['language_user'] = FALSE;
$rule->elementMap()->lookup(3)->settings['language'] = 'it';
$rule->save();
$rule->execute();
$mails = $this->drupalGetMails(array('key' => $settings['key']));
foreach ($mails as $mail) {
$this->assertEqual($mail['language']->language, 'it');
}
// Send mails to all users of a role except deactivated users.
// Rest the collected mails.
variable_set('drupal_test_email_collector', array());
// Disable one of the users.
reset($users);
$user = user_load_by_mail(key($users));
$user->status = 0;
user_save($user);
$rule->execute();
$mails = $this->drupalGetMails(array('key' => $settings['key']));
$this->assertEqual(count($mails), count($users) - 1);
// Rest the collected mails.
variable_set('drupal_test_email_collector', array());
// Send mails to all users, also to deactivated ones.
$rule->elementMap()->lookup(3)->settings['active'] = FALSE;
$rule->save();
$rule->execute();
$mails = $this->drupalGetMails(array('key' => $settings['key']));
// One user is disabled but it should be ignored.
$this->assertEqual(count($mails), count($users));
}
}

View File

@@ -0,0 +1,40 @@
<?php
/**
* @file
* Default theme implementation to format an HTML mail.
*
* Copy this file in your default theme folder to create a custom themed mail.
* Rename it to mimemail-message--[module]--[key].tpl.php to override it for a
* specific mail.
*
* Available variables:
* - $recipient: The recipient of the message
* - $subject: The message subject
* - $body: The message body
* - $css: Internal style sheets
* - $module: The sending module
* - $key: The message identifier
*
* @see template_preprocess_mimemail_message()
*/
?>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<?php if ($css): ?>
<style type="text/css">
<!--
<?php print $css ?>
-->
</style>
<?php endif; ?>
</head>
<body id="mimemail-body" <?php if ($module && $key): print 'class="'. $module .'-'. $key .'"'; endif; ?>>
<div id="center">
<div id="main">
<?php print $body ?>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,154 @@
<?php
/**
* @file
* The theme system, which controls the output of the messages.
*/
function mimemail_theme_theme() {
$path = drupal_get_path('module', 'mimemail') . '/theme';
return array(
'mimemail_message' => array(
'variables' => array('module' => NULL, 'key' => NULL, 'recipient' => NULL, 'subject' => NULL, 'body' => NULL),
'template' => 'mimemail-message',
'pattern' => 'mimemail_message__',
'file' => 'mimemail.theme.inc',
'mail theme' => TRUE,
'path' => $path,
)
);
}
/**
* A preprocess function for theme('mimemail_message').
*
* The $variables array initially contains the following arguments:
* - $recipient: The recipient of the message
* - $key: The mailkey associated with the message
* - $subject: The message subject
* - $body: The message body
*
* @see mimemail-message.tpl.php
*/
function template_preprocess_mimemail_message(&$variables) {
$theme = mailsystem_get_mail_theme();
$themepath = drupal_get_path('theme', $theme);
$sitestyle = variable_get('mimemail_sitestyle', 1);
$mailstyles = file_scan_directory($themepath, '#^mail\.css*$#');
// Check recursively for the existence of a mail.css file in the theme folder.
if (!empty($mailstyles)) {
foreach ($mailstyles as $mailstyle) {
$styles = $mailstyle->uri;
}
}
// If no mail.css was found and the site style sheets including is enabled,
// gather all style sheets and embed a version of all style definitions.
elseif ($sitestyle) {
// Grab local.css if it exists (support for Fusion based themes).
$local = $themepath . '/css/local.css';
if (@file_exists($local)) {
$css_all = drupal_add_css($local, array('group' => CSS_THEME));
}
else {
$css_all = drupal_add_css();
}
$css_files = array();
foreach ($css_all as $key => $options) {
if ($options['group'] == CSS_THEME && $options['type'] == 'file' &&
($options['media'] == 'all' || $options['media'] == 'screen')) {
$css_files[$key] = $options;
}
}
if (variable_get('preprocess_css', FALSE)) {
$pattern = '|<link.*href="' . $GLOBALS['base_url'] . '/([^"?]*)[?"].*|';
$replacement = '\1';
}
else {
$pattern = array(
'/<([^<>]*)>/', // Remove the style tag.
'/@import\s+url\("([^"]+)"\);+/', // Remove the import directive.
'|' . $GLOBALS['base_url'] . '/([^"?]*)[?"].*|' // Remove the base URL.
);
$replacement = array('', '\1', '\1');
}
$styles = preg_replace($pattern, $replacement, drupal_get_css($css_files));
}
$css = '';
if (isset($styles)) {
// Process each style sheet.
foreach (explode("\n", $styles) as $style) {
if (!empty($style) && @file_exists($style)) {
$css .= @file_get_contents($style);
}
}
// Regexp to match comment blocks.
$comment = '/\*[^*]*\*+(?:[^/*][^*]*\*+)*/';
// Regexp to match double quoted strings.
$double_quot = '"[^"\\\\]*(?:\\\\.[^"\\\\]*)*"';
// Regexp to match single quoted strings.
$single_quot = "'[^'\\\\]*(?:\\\\.[^'\\\\]*)*'";
// Perform some safe CSS optimizations (derived from core CSS aggregation).
$css = preg_replace_callback(
"<$double_quot|$single_quot|$comment>Sus", // Match all comment blocks along
"_mimemail_process_comment", // with double/single quoted strings
$css); // and feed them to _mimemail_process_comment().
$css = preg_replace(
'<\s*([@{}:;,]|\)\s|\s\()\s*[^\n\S]>S', // Remove whitespace around separators,
'\1', // but keep space around parentheses
$css); // and new lines between definitions.
// End the file with a new line.
$css .= "\n";
// Wordwrap to adhere to RFC821
$css = wordwrap($css, 700);
}
// Set styles for the message.
$variables['css'] = $css;
// Set template alternatives.
$variables['theme_hook_suggestions'][] = 'mimemail_message__' . str_replace('-', '_', $variables['key']);
// Process identifiers to be proper CSS classes.
$variables['module'] = str_replace('_', '-', $variables['module']);
$variables['key'] = str_replace('_', '-', $variables['key']);
}
/**
* Process comment blocks. (derived from core CSS aggregation)
*
* This is the callback function for the preg_replace_callback()
* used in drupal_load_stylesheet_content(). Support for comment
* hacks is implemented here.
*/
function _mimemail_process_comment($matches) {
static $keep_nextone = FALSE;
// Quoted string, keep it.
if ($matches[0][0] == "'" || $matches[0][0] == '"') {
return $matches[0];
}
// End of IE-Mac hack, keep it.
if ($keep_nextone) {
$keep_nextone = FALSE;
return $matches[0];
}
switch (strrpos($matches[0], '\\')) {
case FALSE :
// No backslash, strip it.
return '';
case drupal_strlen($matches[0])-3 :
// Ends with \*/ so is a multi line IE-Mac hack, keep the next one also.
$keep_nextone = TRUE;
return '/*_\*/';
default :
// Single line IE-Mac hack.
return '/*\_*/';
}
}