From 436a4e24397b5aef265fd787f392b8468a043999 Mon Sep 17 00:00:00 2001 From: Chris Gross <gross.364@osu.edu> Date: Thu, 22 Oct 2015 16:52:42 -0400 Subject: [PATCH] daily build --- CHANGELOG.txt | 4 + includes/bootstrap.inc | 2 +- modules/overlay/overlay-parent.js | 9 +- profiles/wcm_base/CHANGELOG.txt | 10 + .../modules/contrib/mailsystem/LICENSE.txt | 339 +++++ .../modules/contrib/mailsystem/README.html | 113 ++ .../contrib/mailsystem/README.markdown | 118 ++ .../modules/contrib/mailsystem/README.txt | 134 ++ .../contrib/mailsystem/html_to_text.inc | 742 +++++++++++ .../contrib/mailsystem/mailsystem.admin.inc | 202 +++ .../contrib/mailsystem/mailsystem.info | 14 + .../contrib/mailsystem/mailsystem.module | 353 +++++ .../contrib/mailsystem/mailsystem.theme.inc | 82 ++ .../modules/contrib/mimemail/CHANGELOG.txt | 214 +++ .../modules/contrib/mimemail/LICENSE.txt | 339 +++++ .../modules/contrib/mimemail/README.txt | 136 ++ .../mimemail/includes/mimemail.admin.inc | 154 +++ .../mimemail/includes/mimemail.incoming.inc | 211 +++ .../mimemail/includes/mimemail.mail.inc | 64 + .../modules/contrib/mimemail/mimemail.inc | 577 ++++++++ .../modules/contrib/mimemail/mimemail.info | 22 + .../modules/contrib/mimemail/mimemail.install | 113 ++ .../modules/contrib/mimemail/mimemail.module | 392 ++++++ .../contrib/mimemail/mimemail.rules.inc | 360 +++++ .../mimemail_action/mimemail_action.info | 14 + .../mimemail_action/mimemail_action.module | 197 +++ .../mimemail_compress/mimemail_compress.inc | 267 ++++ .../mimemail_compress/mimemail_compress.info | 14 + .../mimemail_compress.install | 31 + .../mimemail_compress.module | 21 + .../mimemail_example/mimemail_example.info | 12 + .../mimemail_example/mimemail_example.install | 20 + .../mimemail_example/mimemail_example.module | 170 +++ .../contrib/mimemail/tests/mimemail.test | 99 ++ .../mimemail/tests/mimemail_compress.test | 31 + .../mimemail/tests/mimemail_rules.test | 225 ++++ .../mimemail/theme/mimemail-message.tpl.php | 40 + .../contrib/mimemail/theme/mimemail.theme.inc | 102 ++ .../contrib/webform/components/date.inc | 3 +- .../contrib/webform/components/email.inc | 11 +- .../contrib/webform/components/fieldset.inc | 3 +- .../contrib/webform/components/file.inc | 46 +- .../contrib/webform/components/grid.inc | 10 +- .../contrib/webform/components/hidden.inc | 10 +- .../contrib/webform/components/markup.inc | 65 +- .../contrib/webform/components/number.inc | 31 +- .../contrib/webform/components/pagebreak.inc | 2 +- .../contrib/webform/components/select.inc | 40 +- .../contrib/webform/components/textarea.inc | 11 +- .../contrib/webform/components/textfield.inc | 11 +- .../contrib/webform/components/time.inc | 3 +- .../contrib/webform/css/webform-admin.css | 1 + .../includes/exporters/webform_exporter.inc | 3 + .../exporters/webform_exporter_delimited.inc | 7 +- .../webform_exporter_excel_delimited.inc | 3 + .../exporters/webform_exporter_excel_xlsx.inc | 3 + .../webform/includes/webform.admin.inc | 2 +- .../webform/includes/webform.components.inc | 33 +- .../webform/includes/webform.conditionals.inc | 20 +- .../webform/includes/webform.emails.inc | 87 +- .../webform/includes/webform.pages.inc | 28 +- .../webform/includes/webform.report.inc | 31 +- .../webform/includes/webform.submissions.inc | 15 +- .../includes/webform.webformconditionals.inc | 92 +- .../contrib/webform/js/node-type-form.js | 12 +- .../contrib/webform/js/select-admin.js | 85 +- .../contrib/webform/js/webform-admin.js | 631 ++++----- .../modules/contrib/webform/js/webform.js | 1166 +++++++++-------- .../contrib/webform/tests/conditionals.test | 2 +- .../contrib/webform/views/webform.views.inc | 2 +- .../webform_handler_field_node_link_edit.inc | 4 +- ...ebform_handler_field_node_link_results.inc | 6 +- .../modules/contrib/webform/webform.api.php | 61 +- .../modules/contrib/webform/webform.drush.inc | 18 +- .../modules/contrib/webform/webform.info | 6 +- .../modules/contrib/webform/webform.install | 49 +- .../modules/contrib/webform/webform.module | 150 ++- .../contrib/webform/webform.tokens.inc | 348 ++--- .../ocio_landing_page.module | 41 + ...o_permissions.features.user_permission.inc | 41 + .../ocio_permissions/ocio_permissions.info | 6 + .../ocio_search.apachesolr_environments.inc | 5 +- ...ser_config.features.features_overrides.inc | 49 + .../ocio_user_config.features.inc | 40 + .../ocio_user_config/ocio_user_config.info | 26 + .../ocio_user_config.strongarm.inc | 2 +- .../custom/ocio_web_form/ocio_web_form.info | 3 + .../ocio_web_form/ocio_web_form.strongarm.inc | 21 + .../css/ocio-omega-base.no-query.css | 2 + .../css/ocio-omega-base.styles.css | 2 + .../preprocess/page.preprocess.inc | 24 +- .../sass/components/regions/_hero.scss | 1 + .../sass/components/regions/_pre-footer.scss | 1 + .../regions/region--masthead.tpl.php | 2 +- .../templates/system/html.tpl.php | 1 - profiles/wcm_base/wcm_base.info | 2 + profiles/wcm_base/wcm_base.make | 8 +- 97 files changed, 8027 insertions(+), 1308 deletions(-) create mode 100644 profiles/wcm_base/modules/contrib/mailsystem/LICENSE.txt create mode 100644 profiles/wcm_base/modules/contrib/mailsystem/README.html create mode 100644 profiles/wcm_base/modules/contrib/mailsystem/README.markdown create mode 100644 profiles/wcm_base/modules/contrib/mailsystem/README.txt create mode 100644 profiles/wcm_base/modules/contrib/mailsystem/html_to_text.inc create mode 100644 profiles/wcm_base/modules/contrib/mailsystem/mailsystem.admin.inc create mode 100644 profiles/wcm_base/modules/contrib/mailsystem/mailsystem.info create mode 100644 profiles/wcm_base/modules/contrib/mailsystem/mailsystem.module create mode 100644 profiles/wcm_base/modules/contrib/mailsystem/mailsystem.theme.inc create mode 100644 profiles/wcm_base/modules/contrib/mimemail/CHANGELOG.txt create mode 100644 profiles/wcm_base/modules/contrib/mimemail/LICENSE.txt create mode 100644 profiles/wcm_base/modules/contrib/mimemail/README.txt create mode 100644 profiles/wcm_base/modules/contrib/mimemail/includes/mimemail.admin.inc create mode 100644 profiles/wcm_base/modules/contrib/mimemail/includes/mimemail.incoming.inc create mode 100644 profiles/wcm_base/modules/contrib/mimemail/includes/mimemail.mail.inc create mode 100644 profiles/wcm_base/modules/contrib/mimemail/mimemail.inc create mode 100644 profiles/wcm_base/modules/contrib/mimemail/mimemail.info create mode 100644 profiles/wcm_base/modules/contrib/mimemail/mimemail.install create mode 100644 profiles/wcm_base/modules/contrib/mimemail/mimemail.module create mode 100644 profiles/wcm_base/modules/contrib/mimemail/mimemail.rules.inc create mode 100644 profiles/wcm_base/modules/contrib/mimemail/modules/mimemail_action/mimemail_action.info create mode 100644 profiles/wcm_base/modules/contrib/mimemail/modules/mimemail_action/mimemail_action.module create mode 100644 profiles/wcm_base/modules/contrib/mimemail/modules/mimemail_compress/mimemail_compress.inc create mode 100644 profiles/wcm_base/modules/contrib/mimemail/modules/mimemail_compress/mimemail_compress.info create mode 100644 profiles/wcm_base/modules/contrib/mimemail/modules/mimemail_compress/mimemail_compress.install create mode 100644 profiles/wcm_base/modules/contrib/mimemail/modules/mimemail_compress/mimemail_compress.module create mode 100644 profiles/wcm_base/modules/contrib/mimemail/modules/mimemail_example/mimemail_example.info create mode 100644 profiles/wcm_base/modules/contrib/mimemail/modules/mimemail_example/mimemail_example.install create mode 100644 profiles/wcm_base/modules/contrib/mimemail/modules/mimemail_example/mimemail_example.module create mode 100644 profiles/wcm_base/modules/contrib/mimemail/tests/mimemail.test create mode 100644 profiles/wcm_base/modules/contrib/mimemail/tests/mimemail_compress.test create mode 100644 profiles/wcm_base/modules/contrib/mimemail/tests/mimemail_rules.test create mode 100644 profiles/wcm_base/modules/contrib/mimemail/theme/mimemail-message.tpl.php create mode 100644 profiles/wcm_base/modules/contrib/mimemail/theme/mimemail.theme.inc create mode 100644 profiles/wcm_base/modules/custom/ocio_user_config/ocio_user_config.features.features_overrides.inc diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 66c999d1..b4972a60 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,4 +1,8 @@ +Drupal 7.41, 2015-10-21 +----------------------- +- Fixed security issues (open redirect). See SA-CORE-2015-004. + Drupal 7.40, 2015-10-14 ----------------------- - Made Drupal's code for parsing .info files run much faster and use much less diff --git a/includes/bootstrap.inc b/includes/bootstrap.inc index 0cf91aa1..53462f27 100644 --- a/includes/bootstrap.inc +++ b/includes/bootstrap.inc @@ -8,7 +8,7 @@ /** * The current system version. */ -define('VERSION', '7.40'); +define('VERSION', '7.41'); /** * Core API compatibility. diff --git a/modules/overlay/overlay-parent.js b/modules/overlay/overlay-parent.js index 7859821b..efb26370 100644 --- a/modules/overlay/overlay-parent.js +++ b/modules/overlay/overlay-parent.js @@ -350,7 +350,7 @@ Drupal.overlay.setFocusBefore = function ($element, document) { * TRUE if the URL represents an administrative link, FALSE otherwise. */ Drupal.overlay.isAdminLink = function (url) { - if (Drupal.overlay.isExternalLink(url)) { + if (!Drupal.urlIsLocal(url)) { return false; } @@ -378,6 +378,8 @@ Drupal.overlay.isAdminLink = function (url) { /** * Determine whether a link is external to the site. * + * Deprecated. Use Drupal.urlIsLocal() instead. + * * @param url * The URL to be tested. * @@ -385,8 +387,7 @@ Drupal.overlay.isAdminLink = function (url) { * TRUE if the URL is external to the site, FALSE otherwise. */ Drupal.overlay.isExternalLink = function (url) { - var re = RegExp('^((f|ht)tps?:)?//(?!' + window.location.host + ')'); - return re.test(url); + return !Drupal.urlIsLocal(url); }; /** @@ -405,7 +406,7 @@ Drupal.overlay.isExternalLink = function (url) { */ Drupal.overlay.getInternalUrl = function (path) { var url = Drupal.settings.basePath + path; - if (!this.isExternalLink(url)) { + if (Drupal.urlIsLocal(url)) { return url; } }; diff --git a/profiles/wcm_base/CHANGELOG.txt b/profiles/wcm_base/CHANGELOG.txt index 5db10c57..0ef5e909 100644 --- a/profiles/wcm_base/CHANGELOG.txt +++ b/profiles/wcm_base/CHANGELOG.txt @@ -1,3 +1,13 @@ +WCM Base 7.x-1.x, 2015-10-22 +---------------------------- +- OCIO Landing Page: Added create, delete and move permissions for Landing Page panes. +- OCIO User Config: Enabled and customized Admin Views for the user admin page. +- OCIO Omega Base: + - Set Landing Page banner images to 100% of page width. + - Improvements accessibility of Landing Page banner images. +- OCIO Search: Remove image, audio and video files from search index. +- OCIO Web Form: Enable HTML email and set it as the default format. + WCM Base 7.x-1.x, 2015-10-21 ---------------------------- - OCIO Omega Base: diff --git a/profiles/wcm_base/modules/contrib/mailsystem/LICENSE.txt b/profiles/wcm_base/modules/contrib/mailsystem/LICENSE.txt new file mode 100644 index 00000000..d159169d --- /dev/null +++ b/profiles/wcm_base/modules/contrib/mailsystem/LICENSE.txt @@ -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. diff --git a/profiles/wcm_base/modules/contrib/mailsystem/README.html b/profiles/wcm_base/modules/contrib/mailsystem/README.html new file mode 100644 index 00000000..6b778620 --- /dev/null +++ b/profiles/wcm_base/modules/contrib/mailsystem/README.html @@ -0,0 +1,113 @@ +<h2 id="mail-system"><a href="http://drupal.org/project/mailsystem">Mail System</a></h2> +<p>Provides an Administrative UI and Developers API for safely updating the <a href="http://api.drupal.org/api/drupal/includes--mail.inc/function/drupal_mail_system/7">mail_system</a> configuration variable.</p> +<h3 id="administrative-ui">Administrative UI</h3> +<p>The administrative interface is at <code>admin/config/system/mailsystem</code>. A <a href="http://drupal.org/node/1134044">screenshot</a> is available.</p> +<h3 id="used-by">Used by:</h3> +<ul> + <li><a href="http://drupal.org/project/htmlmail">HTML Mail</a></li> + <li><a href="http://drupal.org/project/mimemail">Mime Mail 7.x-1.x-dev</a></li> + <li><a href="http://drupal.org/project/postmark">Postmark 7.x-1.x</a></li> +</ul> +<h3 id="developers-api">Developers API</h3> +<p>A module <code>example</code> with a <a href="http://api.drupal.org/api/drupal/includes--mail.inc/interface/MailSystemInterface/7"><code>MailSystemInterface</code></a> implementation called <code>ExampleMailSystem</code> should add the following in its <code>example.install</code> file:</p> +<pre> +<code>/** + * Implements hook_enable(). + */ +function example_enable() { + mailsystem_set(array('example' => 'ExampleMailSystem')); +} +/** + * Implements hook_disable(). + */ +function example_disable() { + mailsystem_clear(array('example' => 'ExampleMailSystem')); +} +</code> +</pre> +<p>The above settings allow mail sent by <code>example</code> to use <code>ExampleMailSystem</code>. To make <code>ExampleMailSystem</code> the site-wide default for sending mail:</p> +<pre> +<code>mailsystem_set(array(mailsystem_default_id() => 'ExampleMailSystem')); +</code> +</pre> +<p>To restore the default mail system:</p> +<pre> +<code>mailsystem_set(array(mailsystem_default_id() => mailsystem_default_value())); +</code> +</pre> +<p>Or simply:</p> +<pre> +<code>mailsystem_set(mailsystem_defaults()); +</code> +</pre> +<p>If module <code>example</code> relies on dependency <code>foo</code> and its <code>FooMailSystem</code> class, then the <code>example.install</code> code should like like this:</p> +<pre> +<code>/** + * Implements hook_enable(). + */ +function example_enable() { + mailsystem_set(array('example' => 'FooMailSystem')); +} +/** + * Implements hook_disable(). + */ +function example_disable() { + mailsystem_clear(array('example' => '')); +} +</code> +</pre> +<p>If module <code>example</code> only wants to use <code>FooMailSystem</code> when sending emails with a key of <code>examail</code>, then the <code>example.install</code> code should look like this:</p> +<pre> +<code>/** + * Implements hook_enable(). + */ +function example_enable() { + mailsystem_set(array('example_examail' => 'FooMailSystem')); +} +/** + * Implements hook_disable(). + */ +function example_disable() { + mailsystem_clear(array('example_examail' => '')); +} +</code> +</pre> +<h4 id="new-in-2.x-branch"><em>(New in 2.x branch)</em></h4> +<p>To change the site-wide defaults to use the <code>FooMailSystem</code> for formatting messages and the <code>BarMailSystem</code> for sending them:</p> +<pre> +<code>mailsystem_set( + array( + mailsystem_default_id() => array( + 'format' => 'FooMailSystem', + 'mail' => 'BarMailSystem', + ), + ) +); +</code> +</pre> +<p>To change the site-wide defaults to use the <code>FooMailSystem</code> for sending messages, while continuing to use the current system for formatting them:</p> +<pre> +<code>mailsystem_set( + array( + mailsystem_default_id() => array( + 'mail' => 'FooMailsystem', + ), + ) +); +</code> +</pre> +<h3 id="references">References</h3> +<dl> + <dt><strong><a href="http://api.drupal.org/api/drupal/includes--mail.inc/function/drupal_mail_system/7"><code>drupal_mail_system()</code> API documentation</a></strong>:</dt> + <dd> + <p><a href="http://api.drupal.org/api/drupal/includes--mail.inc/function/drupal_mail_system/7">api.drupal.org/api/drupal/includes--mail.inc/function/drupal_mail_system/7</a></p> + </dd> + <dt><strong><a href="http://api.drupal.org/api/drupal/includes--mail.inc/interface/MailSystemInterface/7"><code>MailSystemInterface</code> API documentation</a></strong>:</dt> + <dd> + <p><a href="http://api.drupal.org/api/drupal/includes--mail.inc/interface/MailSystemInterface/7">api.drupal.org/api/drupal/includes--mail.inc/interface/MailSystemInterface/7</a></p> + </dd> + <dt><strong><a href="http://drupal.org/node/900794">Creating HTML formatted mails in Drupal 7</a></strong>:</dt> + <dd> + <p><a href="http://drupal.org/node/900794">drupal.org/node/900794</a></p> + </dd> +</dl> diff --git a/profiles/wcm_base/modules/contrib/mailsystem/README.markdown b/profiles/wcm_base/modules/contrib/mailsystem/README.markdown new file mode 100644 index 00000000..6d549f90 --- /dev/null +++ b/profiles/wcm_base/modules/contrib/mailsystem/README.markdown @@ -0,0 +1,118 @@ +## [Mail System](http://drupal.org/project/mailsystem) + +Provides an Administrative UI and Developers API for safely updating the +[mail_system](http://api.drupal.org/api/drupal/includes--mail.inc/function/drupal_mail_system/7) +configuration variable. + +### Administrative UI + +The administrative interface is at `admin/config/system/mailsystem`. +A [screenshot](http://drupal.org/node/1134044) is available. + +### Used by: + +* [HTML Mail](http://drupal.org/project/htmlmail) +* [Mime Mail 7.x-1.x-dev](http://drupal.org/project/mimemail) +* [Postmark 7.x-1.x](http://drupal.org/project/postmark) + +### Developers API + +A module `example` with a +[`MailSystemInterface`](http://api.drupal.org/api/drupal/includes--mail.inc/interface/MailSystemInterface/7) +implementation called `ExampleMailSystem` should add the following in its +`example.install` file: + + /** + * Implements hook_enable(). + */ + function example_enable() { + mailsystem_set(array('example' =\> 'ExampleMailSystem')); + } + + /** + * Implements hook_disable(). + */ + function example_disable() { + mailsystem_clear(array('example' =\> 'ExampleMailSystem')); + } + +The above settings allow mail sent by `example` to use `ExampleMailSystem`. To make +`ExampleMailSystem` the site-wide default for sending mail: + + mailsystem_set(array(mailsystem_default_id() =\> 'ExampleMailSystem')); + +To restore the default mail system: + + mailsystem_set(array(mailsystem_default_id() =\> mailsystem_default_value())); + +Or simply: + + mailsystem_set(mailsystem_defaults()); + +If module `example` relies on dependency `foo` and its `FooMailSystem` class, then +the `example.install` code should like like this: + + /** + * Implements hook_enable(). + */ + function example_enable() { + mailsystem_set(array('example' =\> 'FooMailSystem')); + } + + /** + * Implements hook_disable(). + */ + function example_disable() { + mailsystem_clear(array('example' =\> '')); + } + +If module `example` only wants to use `FooMailSystem` when sending emails with a key +of `examail`, then the `example.install` code should look like this: + + /** + * Implements hook_enable(). + */ + function example_enable() { + mailsystem_set(array('example_examail' =\> 'FooMailSystem')); + } + + /** + * Implements hook_disable(). + */ + function example_disable() { + mailsystem_clear(array('example_examail' =\> '')); + } + +#### *(New in 2.x branch)* + +To change the site-wide defaults to use the `FooMailSystem` for formatting messages and the `BarMailSystem` for sending them: + + mailsystem_set( + array( + mailsystem_default_id() => array( + 'format' => 'FooMailSystem', + 'mail' => 'BarMailSystem', + ), + ) + ); + +To change the site-wide defaults to use the `FooMailSystem` for sending messages, while continuing to use the current system for formatting them: + + mailsystem_set( + array( + mailsystem_default_id() => array( + 'mail' => 'FooMailsystem', + ), + ) + ); + +### References + +**[`drupal_mail_system()` API documentation](http://api.drupal.org/api/drupal/includes--mail.inc/function/drupal_mail_system/7)**: +: [api.drupal.org/api/drupal/includes--mail.inc/function/drupal_mail_system/7](http://api.drupal.org/api/drupal/includes--mail.inc/function/drupal_mail_system/7) + +**[`MailSystemInterface` API documentation](http://api.drupal.org/api/drupal/includes--mail.inc/interface/MailSystemInterface/7)**: +: [api.drupal.org/api/drupal/includes--mail.inc/interface/MailSystemInterface/7](http://api.drupal.org/api/drupal/includes--mail.inc/interface/MailSystemInterface/7) + +**[Creating HTML formatted mails in Drupal 7](http://drupal.org/node/900794)**: +: [drupal.org/node/900794](http://drupal.org/node/900794) diff --git a/profiles/wcm_base/modules/contrib/mailsystem/README.txt b/profiles/wcm_base/modules/contrib/mailsystem/README.txt new file mode 100644 index 00000000..ca302afc --- /dev/null +++ b/profiles/wcm_base/modules/contrib/mailsystem/README.txt @@ -0,0 +1,134 @@ +[1]Mail System + + Provides an Administrative UI and Developers API for safely updating + the [2]mail_system configuration variable. + + Administrative UI + + The administrative interface is at admin/config/system/mailsystem. A + [3]screenshot is available. + + Used by: + + * [4]HTML Mail + * [5]Mime Mail 7.x-1.x-dev + * [6]Postmark 7.x-1.x + + Developers API + + A module example with a [7]MailSystemInterface implementation called + ExampleMailSystem should add the following in its example.install file: +/** + * Implements hook_enable(). + */ +function example_enable() { + mailsystem_set(array('example' => 'ExampleMailSystem')); +} +/** + * Implements hook_disable(). + */ +function example_disable() { + mailsystem_clear(array('example' => 'ExampleMailSystem')); +} + + + The above settings allow mail sent by example to use ExampleMailSystem. + To make ExampleMailSystem the site-wide default for sending mail: +mailsystem_set(array(mailsystem_default_id() => 'ExampleMailSystem')); + + + To restore the default mail system: +mailsystem_set(array(mailsystem_default_id() => mailsystem_default_value())); + + + Or simply: +mailsystem_set(mailsystem_defaults()); + + + If module example relies on dependency foo and its FooMailSystem class, + then the example.install code should like like this: +/** + * Implements hook_enable(). + */ +function example_enable() { + mailsystem_set(array('example' => 'FooMailSystem')); +} +/** + * Implements hook_disable(). + */ +function example_disable() { + mailsystem_clear(array('example' => '')); +} + + + If module example only wants to use FooMailSystem when sending emails + with a key of examail, then the example.install code should look like + this: +/** + * Implements hook_enable(). + */ +function example_enable() { + mailsystem_set(array('example_examail' => 'FooMailSystem')); +} +/** + * Implements hook_disable(). + */ +function example_disable() { + mailsystem_clear(array('example_examail' => '')); +} + + + (New in 2.x branch) + + To change the site-wide defaults to use the FooMailSystem for + formatting messages and the BarMailSystem for sending them: +mailsystem_set( + array( + mailsystem_default_id() => array( + 'format' => 'FooMailSystem', + 'mail' => 'BarMailSystem', + ), + ) +); + + + To change the site-wide defaults to use the FooMailSystem for sending + messages, while continuing to use the current system for formatting + them: +mailsystem_set( + array( + mailsystem_default_id() => array( + 'mail' => 'FooMailsystem', + ), + ) +); + + + References + + [8]drupal_mail_system() API documentation: + [9]api.drupal.org/api/drupal/includes--mail.inc/function/drupal_ + mail_system/7 + + [10]MailSystemInterface API documentation: + [11]api.drupal.org/api/drupal/includes--mail.inc/interface/MailS + ystemInterface/7 + + [12]Creating HTML formatted mails in Drupal 7: + [13]drupal.org/node/900794 + +References + + 1. http://drupal.org/project/mailsystem + 2. http://api.drupal.org/api/drupal/includes--mail.inc/function/drupal_mail_system/7 + 3. http://drupal.org/node/1134044 + 4. http://drupal.org/project/htmlmail + 5. http://drupal.org/project/mimemail + 6. http://drupal.org/project/postmark + 7. http://api.drupal.org/api/drupal/includes--mail.inc/interface/MailSystemInterface/7 + 8. http://api.drupal.org/api/drupal/includes--mail.inc/function/drupal_mail_system/7 + 9. http://api.drupal.org/api/drupal/includes--mail.inc/function/drupal_mail_system/7 + 10. http://api.drupal.org/api/drupal/includes--mail.inc/interface/MailSystemInterface/7 + 11. http://api.drupal.org/api/drupal/includes--mail.inc/interface/MailSystemInterface/7 + 12. http://drupal.org/node/900794 + 13. http://drupal.org/node/900794 diff --git a/profiles/wcm_base/modules/contrib/mailsystem/html_to_text.inc b/profiles/wcm_base/modules/contrib/mailsystem/html_to_text.inc new file mode 100644 index 00000000..08576220 --- /dev/null +++ b/profiles/wcm_base/modules/contrib/mailsystem/html_to_text.inc @@ -0,0 +1,742 @@ +<?php +/** + * @file + * Copy of drupal_html_to_text improvements from issue #299138. + */ + +/** + * Perform format=flowed soft wrapping for mail (RFC 3676). + * + * We use delsp=yes wrapping, but only break non-spaced languages when + * absolutely necessary to avoid compatibility issues. + * + * We deliberately use variable_get('mail_line_endings', MAIL_LINE_ENDINGS) + * rather than "\r\n". + * + * @param $text + * The plain text to process. + * @param array $options + * (optional) An array containing one or more of the following keys: + * - indent: A string to indent the text with. Only '>' characters are + * repeated on subsequent wrapped lines. Others are replaced by spaces. + * - max: The maximum length at which to wrap each line. Defaults to 80. + * - stuff: Whether to space-stuff special lines. Defaults to TRUE. + * - hard: Whether to enforce the maximum line length even if no convenient + * space character is available. Defaults to FALSE. + * - pad: A string to use for padding short lines to 'max' characters. If + * more than one character, only the last will be repeated. + * - break: The line break sequence to insert. The default is one of the + * following: + * - "\r\n": Windows, when $text does not contain a space character. + * - "\n": Non-Windows, when $text does not contain a space character. + * - " \r\n": On Windows, when $text contains at least one space. + * - " \n": Non-Windows, when $text contains at least one space. + * + * @see drupal_mail() + */ +function mailsystem_wrap_mail($text, array $options = array()) { + static $defaults; + if (!isset($defaults)) { + $defaults = array( + 'indent' => '', + 'pad' => '', + 'pad_repeat' => '', + 'max' => 80, + 'stuff' => TRUE, + 'hard' => FALSE, + 'eol' => variable_get('mail_line_endings', MAIL_LINE_ENDINGS), + ); + } + $options += $defaults; + if (!isset($options['break'])) { + // Allow soft-wrap spaces only when $text contains at least one space. + $options['break'] = (strpos($text, ' ') === FALSE ? '' : ' ') . $defaults['eol']; + } + $options['wrap'] = $options['max'] - drupal_strlen($options['indent']); + if ($options['pad']) { + $options['pad_repeat'] = drupal_substr($options['pad'], -1, 1); + } + // The 'clean' indent is applied to all lines after the first one. + $options['clean'] = _mailsystem_html_to_text_clean($options['indent']); + // Wrap lines according to RFC 3676. + $lines = explode($defaults['eol'], $text); + array_walk($lines, '_mailsystem_wrap_mail_line', $options); + // Expand the lines array on newly-inserted line breaks. + $lines = explode($defaults['eol'], implode($defaults['eol'], $lines)); + // Apply indentation, space-stuffing, and padding. + array_walk($lines, '_mailsystem_indent_mail_line', $options); + return implode($defaults['eol'], $lines); +} + +/** + * Transform an HTML string into plain text, preserving the structure of the + * markup. Useful for preparing the body of a node to be sent by e-mail. + * + * The output will be suitable for use as 'format=flowed; delsp=yes' text + * (RFC 3676) and can be passed directly to drupal_mail() for sending. + * + * We deliberately use variable_get('mail_line_endings', MAIL_LINE_ENDINGS) + * rather than "\r\n". + * + * This function provides suitable alternatives for the following tags: + * + * <a> <address> <b> <blockquote> <br /> <caption> <cite> <dd> <div> <dl> <dt> + * <em> <h1> <h2> <h3> <h4> <h5> <h6> <hr /> <i> <li> <ol> <p> <pre> <strong> + * <table> <tbody> <td> <tfoot> <thead> <tr> <u> <ul> + * + * The following tag attributes are supported: + * - <a href=...>: Hyperlink destination urls. + * - <li value=...>: Ordered list item numbers. + * - <ol start=...>: Ordered list start number. + * + * @param $string + * The string to be transformed. + * @param $allowed_tags + * (optional) If supplied, a list of tags that will be transformed. If + * omitted, all supported tags are transformed. + * + * @return + * The transformed string. + * + * @see drupal_mail() + */ +function mailsystem_html_to_text($string, $allowed_tags = NULL) { + $eol = variable_get('mail_line_endings', MAIL_LINE_ENDINGS); + // Cache list of supported tags. + static $supported_tags; + if (!isset($supported_tags)) { + $supported_tags = array( + 'a', 'address', 'b', 'blockquote', 'br', 'cite', 'dd', 'div', 'dl', + 'dt', 'em', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'li', + 'ol', 'p', 'pre', 'strong', 'table', 'td', 'tr', 'u', 'ul', + ); + } + + // Make sure only supported tags are kept. + $allowed_tags = isset($allowed_tags) ? array_intersect($supported_tags, $allowed_tags) : $supported_tags; + + // Parse $string into a DOM tree. + $dom = filter_dom_load($string); + $notes = array(); + // Recursively convert the DOM tree into plain text. + $text = _mailsystem_html_to_text($dom->documentElement, $allowed_tags, $notes); + // Hard-wrap at 1000 characters (including the line break sequence) + // and space-stuff special lines. + $text = mailsystem_wrap_mail($text, array('max' => 1000 - strlen($eol), 'hard' => TRUE)); + // Change non-breaking spaces back to regular spaces, and trim line breaks. + // chr(160) is the non-breaking space character. + $text = str_replace(chr(160), ' ', trim($text, $eol)); + // Add footnotes; + if ($notes) { + // Add a blank line before the footnote list. + $text .= $eol; + foreach ($notes as $url => $note) { + $text .= $eol . '[' . $note . '] ' . $url; + } + } + return $text; +} + +/** + * Helper function for drupal_html_to_text(). + * + * Recursively converts $node to text, wrapping and indenting as necessary. + * + * @param $node + * The source DOMNode. + * @param $allowed_tags + * A list of tags that will be transformed. + * @param array &$notes + * A writeable array of footnote reference numbers, keyed by their + * respective hyperlink destination urls. + * @param $line_length + * The maximum length of a line, for wrapping. Defaults to 80 characters. + * @param array $parents + * The list of ancestor tags, from nearest to most distant. Defaults to an + * empty array(). + * @param $count + * The number to use for the next list item within an ordered list. Defaults + * to 1. + */ +function _mailsystem_html_to_text(DOMNode $node, array $allowed_tags, array &$notes, $line_length = 80, array $parents = array(), &$count = NULL) { + if (!isset($count)) { + $count = 1; + } + $eol = variable_get('mail_line_endings', MAIL_LINE_ENDINGS); + if ($node->nodeType === XML_TEXT_NODE) { + // For text nodes, we just copy the text content. + $text = $node->textContent; + // Convert line breaks and trim trailing spaces. + $text = preg_replace('/ *\r?\n/', $eol, $text); + if (in_array('pre', $parents)) { + // Within <pre> tags, all spaces become non-breaking. + // chr(160) is the non-breaking space character. + $text = str_replace(' ', chr(160), $text); + } + else { + // Outside <pre> tags, collapse whitespace. + $text = preg_replace('/[[:space:]]+/', ' ', $text); + } + return $text; + } + // Non-text node. + $tag = ''; + $text = ''; + $child_text = ''; + $child_count = 1; + $indent = ''; + $prefix = ''; + $suffix = ''; + $pad = ''; + if (isset($node->tagName) && in_array($node->tagName, $allowed_tags)) { + $tag = $node->tagName; + switch ($tag) { + // Turn links with valid hrefs into footnotes. + case 'a': + $test = !empty($node->attributes); + $test = $test && ($href = $node->attributes->getNamedItem('href')); + $test = $test && ($url = url(preg_replace('|^' . base_path() . '|', '', $href->nodeValue), array('absolute' => TRUE))); + $test = $test && valid_url($url); + if ($test) { + // Only add links that have not already been added. + if (isset($notes[$url])) { + $note = $notes[$url]; + } + else { + $note = count($notes) + 1; + $notes[$url] = $note; + } + $suffix = ' [' . $note . ']'; + } + break; + + // Generic block-level tags. + case 'address': + case 'caption': + case 'div': + case 'p': + case 'pre': + // Start on a new line except as the first child of a list item. + if (!isset($parents[0]) || $parents[0] !== 'li' || !$node->isSameNode($node->parentNode->firstChild)) { + $text = $eol; + } + $suffix = $eol; + break; + + // Forced line break. + case 'br': + $text = $eol; + break; + + // Boldface by wrapping with "*" characters. + case 'b': + case 'strong': + $prefix = '*'; + $suffix = '*'; + break; + + // Italicize by wrapping with "/" characters. + case 'cite': + case 'em': + case 'i': + $prefix = '/'; + $suffix = '/'; + break; + + // Underline by wrapping with "_" characters. + case 'u': + $prefix = '_'; + $suffix = '_'; + break; + + // Blockquotes are indented by "> " at each level. + case 'blockquote': + $text = $eol; + // chr(160) is the non-breaking space character. + $indent = '>' . chr(160); + $suffix = $eol; + break; + + // Dictionary definitions are indented by four spaces. + case 'dd': + // chr(160) is the non-breaking space character. + $indent = chr(160) . chr(160) . chr(160) . chr(160); + $suffix = $eol; + break; + + // Dictionary list. + case 'dl': + // Start on a new line as the first child of a list item. + if (!isset($parents[0]) || $parents[0] !== 'li' || !$node->isSameNode($node->parentNode->firstChild)) { + $text = $eol; + } + $suffix = $eol; + break; + + // Dictionary term. + case 'dt': + $suffix = $eol; + break; + + // Header level 1 is prefixed by eight "=" characters. + case 'h1': + $text = "$eol$eol"; + // chr(160) is the non-breaking space character. + $indent = '========' . chr(160); + $pad = chr(160) . '='; + $suffix = $eol; + break; + + // Header level 2 is prefixed by six "-" characters. + case 'h2': + $text = "$eol$eol"; + // chr(160) is the non-breaking space character. + $indent = '------' . chr(160); + $pad = chr(160) . '-'; + $suffix = $eol; + break; + + // Header level 3 is prefixed by four "." characters and a space. + case 'h3': + $text = "$eol$eol"; + // chr(160) is the non-breaking space character. + $indent = '....' . chr(160); + $suffix = $eol; + break; + + // Header level 4 is prefixed by three "." characters and a space. + case 'h4': + $text = "$eol$eol"; + // chr(160) is the non-breaking space character. + $indent = '...' . chr(160); + $suffix = $eol; + break; + + // Header level 5 is prefixed by two "." character and a space. + case 'h5': + $text = "$eol$eol"; + // chr(160) is the non-breaking space character. + $indent = '..' . chr(160); + $suffix = $eol; + break; + + // Header level 6 is prefixed by one "." character and a space. + case 'h6': + $text = "$eol$eol"; + // chr(160) is the non-breaking space character. + $indent = '.' . chr(160); + $suffix = $eol; + break; + + // Horizontal rulers become a line of "-" characters. + case 'hr': + $text = $eol; + $child_text = '-'; + $pad = '-'; + $suffix = $eol; + break; + + // List items are treated differently depending on the parent tag. + case 'li': + // Ordered list item. + if (reset($parents) === 'ol') { + // Check the value attribute. + $test = !empty($node->attributes); + $test = $test && ($value = $node->attributes->getNamedItem('value')); + if ($test) { + $count = $value->nodeValue; + } + // chr(160) is the non-breaking space character. + $indent = ($count < 10 ? chr(160) : '') . chr(160) . "$count)" . chr(160); + $count++; + } + // Unordered list item. + else { + // chr(160) is the non-breaking space character. + $indent = chr(160) . '*' . chr(160); + } + $suffix = $eol; + break; + + // Ordered lists. + case 'ol': + // Start on a new line as the first child of a list item. + if (!isset($parents[0]) || $parents[0] !== 'li' || !$node->isSameNode($node->parentNode->firstChild)) { + $text = $eol; + } + // Check the start attribute. + $test = !empty($node->attributes); + $test = $test && ($value = $node->attributes->getNamedItem('start')); + if ($test) { + $child_count = $value->nodeValue; + } + break; + + // Tables require special handling. + case 'table': + return _mailsystem_html_to_text_table($node, $allowed_tags, $notes, $line_length); + + // Separate adjacent table cells by two non-breaking spaces. + case 'td': + if (!empty($node->nextSibling)) { + // chr(160) is the non-breaking space character. + $suffix = chr(160) . chr(160); + } + break; + + // End each table row with a newline. + case 'tr': + $suffix = $eol; + break; + + // Unordered lists. + case 'ul': + // Start on a new line as the first child of a list item. + if (!isset($parents[0]) || $parents[0] !== 'li' || !$node->isSameNode($node->parentNode->firstChild)) { + $text = $eol; + } + break; + + default: + // Coder review complains if there is no default case. + break; + } + // Only add allowed tags to the $parents array. + array_unshift($parents, $tag); + } + // Copy each child node to output. + if ($node->hasChildNodes()) { + foreach ($node->childNodes as $child) { + $child_text .= _mailsystem_html_to_text($child, $allowed_tags, $notes, $line_length - drupal_strlen($indent), $parents, $child_count); } + } + // We only add prefix and suffix if the child nodes were non-empty. + if ($child_text > '') { + // We capitalize the contents of h1 and h2 tags. + if ($tag === 'h1' || $tag === 'h2') { + $child_text = drupal_strtoupper($child_text); + } + // Don't add a newline to an existing newline. + if ($suffix === $eol && drupal_substr($child_text, - drupal_strlen($eol)) === $eol) { + $suffix = ''; + } + // Trim spaces around newlines except with <pre> or inline tags. + if (!in_array($tag, array('a', 'b', 'cite', 'em', 'i', 'pre', 'strong', 'u'))) { + $child_text = preg_replace('/ *' . $eol . ' */', $eol, $child_text); + } + // Soft-wrap at effective line length, but don't space-stuff. + $child_text = mailsystem_wrap_mail( + $prefix . $child_text, + array( + // chr(160) is the non-breaking space character. + 'break' => chr(160) . $eol, + 'indent' => $indent, + 'max' => $line_length, + 'pad' => $pad, + 'stuff' => FALSE, + ) + ) . $suffix; + if ($tag === 'pre') { + // Perform RFC-3676 soft-wrapping. + // chr(160) is the non-breaking space character. + $child_text = str_replace(chr(160), ' ', $child_text); + $child_text = mailsystem_wrap_mail( + $child_text, + array('max' => $line_length, 'stuff' => FALSE) + ); + // chr(160) is the non-breaking space character. + $child_text = str_replace(' ', chr(160), $child_text); + } + $text .= $child_text; + } + return $text; +} + +/** + * Helper function for _mailsystem_html_to_text(). + * + * Renders a <table> DOM Node into plain text. Attributes such as rowspan, + * colspan, padding, border, etc. are ignored. + * + * @param DOMNode $node + * The DOMNode corresponding to the <table> tag and its contents. + * @param $allowed_tags + * The list of allowed tags passed to _mailsystem_html_to_text(). + * @param array &$notes + * A writeable array of footnote reference numbers, keyed by their + * respective hyperlink destination urls. + * @param $table_width + * The desired maximum table width, after word-wrapping each table cell. + * + * @return + * A plain text representation of the table. + * + * @see _mailsystem_html_to_text() + */ +function _mailsystem_html_to_text_table(DOMNode $node, $allowed_tags = NULL, array &$notes = array(), $table_width = 80) { + $eol = variable_get('mail_line_endings', MAIL_LINE_ENDINGS); + $header = array(); + $footer = array(); + $body = array(); + $text = $eol; + $current = $node; + while (TRUE) { + if (isset($current->tagName)) { + switch ($current->tagName) { + case 'caption': // The table caption is added first. + $text = _mailsystem_html_to_text($current, $allowed_tags, $notes, $table_width); + break; + + case 'tr': + switch ($current->parentNode->tagName) { + case 'thead': + $header[] = $current; + break; + + case 'tfoot': + $footer[] = $current; + break; + + default: // Either 'tbody' or 'table' + $body[] = $current; + break; + } + break; + + default: + if ($current->hasChildNodes()) { + $current = $current->firstChild; + continue 2; + } + } + } + do { + if ($current->nextSibling) { + $current = $current->nextSibling; + continue 2; + } + $current = $current->parentNode; + } while ($current && !$current->isSameNode($node)); + break; + } + // Merge the thead, tbody, and tfoot sections together. + if ($rows = array_merge($header, $body, $footer)) { + $num_rows = count($rows); + // First just count the number of columns. + $num_cols = 0; + foreach ($rows as $row) { + $row_cols = 0; + foreach ($row->childNodes as $cell) { + if (isset($cell->tagName) && in_array($cell->tagName, array('td', 'th'))) { + $row_cols++; + } + } + $num_cols = max($num_cols, $row_cols); + } + // If any columns were found, calculate each column height and width. + if ($num_cols) { + // Set up a binary search for best wrap width for each column. + $max = max($table_width - $num_cols - 1, 1); + $max_wraps = array_fill(0, $num_cols, $max); + $try = max(intval(($table_width - 1) / $num_cols - 1), 1); + $try_wraps = array_fill(0, $num_cols, $try); + $min_wraps = array_fill(0, $num_cols, 1); + // Start searching... + $change = FALSE; + do { + $change = FALSE; + $widths = array_fill(0, $num_cols, 0); + $heights = array_fill(0, $num_rows, 0); + $table = array_fill(0, $num_rows, array_fill(0, $num_cols, '')); + $breaks = array_fill(0, $num_cols, FALSE); + foreach ($rows as $i => $row) { + $j = 0; + foreach ($row->childNodes as $cell) { + if (!isset($cell->tagName) || !in_array($cell->tagName, array('td', 'th'))) { + // Skip text nodes. + continue; + } + // Render the cell contents. + $cell = _mailsystem_html_to_text($cell, $allowed_tags, $notes, $try_wraps[$j]); + // Trim leading line-breaks and trailing whitespace. + // chr(160) is the non-breaking space character. + $cell = rtrim(ltrim($cell, $eol), ' ' . $eol . chr(160)); + $table[$i][$j] = $cell; + if ($cell > '') { + // Split the cell into lines. + $lines = explode($eol, $cell); + // The row height is the maximum number of lines among all the + // cells in that row. + $heights[$i] = max($heights[$i], count($lines)); + foreach ($lines as $line) { + $this_width = drupal_strlen($line); + // The column width is the maximum line width among all the + // lines in that column. + if ($this_width > $widths[$j]) { + $widths[$j] = $this_width; + // If the longest line in a column contains at least one + // space character, then the table can be made narrower. + $breaks[$j] = strpos(' ', $line) !== FALSE; + } + } + } + $j++; + } + } + // Calculate the total table width; + $this_width = array_sum($widths) + $num_cols + 1; + if ($this_width > $table_width) { + // Wider than desired. + if (!in_array(TRUE, $breaks)) { + // If there are no more break points, then the table is already as + // narrow as it can get, so we're done. + break; + } + foreach ($try_wraps as $i => $wrap) { + $max_wraps[$i] = min($max_wraps[$i], $wrap); + if ($breaks[$i]) { + $new_wrap = intval(($min_wraps[$i] + $max_wraps[$i]) / 2); + $new_wrap = min($new_wrap, $widths[$i] - 1); + $new_wrap = max($new_wrap, $min_wraps[$i]); + } + else { + // There's no point in trying to make the column narrower than + // the widest un-wrappable line in the column. + $min_wraps[$i] = $widths[$i]; + $new_wrap = $widths[$i]; + } + if ($try_wraps[$i] > $new_wrap) { + $try_wraps[$i] = $new_wrap; + $change = TRUE; + } + } + } + elseif ($this_width < $table_width) { + // Narrower than desired. + foreach ($try_wraps as $i => $wrap) { + if ($min_wraps[$i] < $wrap) { + $min_wraps[$i] = $wrap; + } + $new_wrap = intval(($min_wraps[$i] + $max_wraps[$i]) / 2); + $new_wrap = max($new_wrap, $widths[$i] + 1); + $new_wrap = min($new_wrap, $max_wraps[$i]); + if ($try_wraps[$i] < $new_wrap) { + $try_wraps[$i] = $new_wrap; + $change = TRUE; + } + } + } + } while ($change); + // Pad each cell to column width and line height. + for ($i = 0; $i < $num_rows; $i++) { + if ($heights[$i]) { + for ($j = 0; $j < $num_cols; $j++) { + $cell = $table[$i][$j]; + // Pad each cell to the maximum number of lines in that row. + $lines = array_pad(explode($eol, $cell), $heights[$i], ''); + foreach ($lines as $k => $line) { + // Pad each line to the maximum width in that column. + $repeat = $widths[$j] - drupal_strlen($line); + if ($repeat > 0) { + // chr(160) is the non-breaking space character. + $lines[$k] .= str_repeat(chr(160), $repeat); + } + } + $table[$i][$j] = $lines; + } + } + } + // Generate the row separator line. + $separator = '+'; + for($i = 0; $i < $num_cols; $i++) { + $separator .= str_repeat('-', $widths[$i]) . '+'; + } + $separator .= $eol; + for ($i = 0; $i < $num_rows; $i++) { + $text .= $separator; + if (!$heights[$i]) { + continue; + } + $row = $table[$i]; + // For each row, iterate first by lines within the row. + for ($k = 0; $k < $heights[$i]; $k++) { + // Add a vertical-bar at the beginning of each row line. + $row_line = '|'; + $trimmed = ''; + // Within each row line, iterate by cells within that line. + for ($j = 0; $j < $num_cols; $j++) { + // Add a vertical bar at the end of each cell line. + $row_line .= $row[$j][$k] . '|'; + // chr(160) is the non-breaking space character. + $trimmed .= trim($row[$j][$k], ' ' . $eol . chr(160)); + } + if ($trimmed > '') { + // Only print rows that are non-empty. + $text .= $row_line . $eol; + } + } + } + // Final output ends with a row separator. + $text .= $separator; + } + } + // Make sure formatted table content doesn't line-wrap. + // chr(160) is the non-breaking space character. + return str_replace(' ', chr(160), $text); +} + +/** + * Helper function for array_walk in drupal_wrap_mail(). + * + * Inserts $values['break'] sequences to break up $line into parts of no more + * than $values['wrap'] characters. Only breaks at space characters, unless + * $values['hard'] is TRUE. + */ +function _mailsystem_wrap_mail_line(&$line, $key, $values) { + $line = wordwrap($line, $values['wrap'], $values['break'], $values['hard']); +} + +/** + * Helper function for array_walk in drupal_wrap_mail(). + * + * If $values['pad'] is non-empty, $values['indent'] will be added at the start + * of each line, and $values['pad'] at the end, repeating the last character of + * $values['pad'] until the line length equals $values['max']. + * + * If $values['pad'] is empty, $values['indent'] will be added at the start of + * the first line, and $values['clean'] at the start of subsequent lines. + * + * If $values['stuff'] is true, then an extra space character will be added at + * the start of any line beginning with a space, a '>', or the word 'From'. + * + * @see http://www.ietf.org/rfc/rfc3676.txt + */ +function _mailsystem_indent_mail_line(&$line, $key, $values) { + if ($line == '') { + return; + } + if ($values['pad']) { + $line = $values['indent'] . $line; + $count = $values['max'] - drupal_strlen($line) - drupal_strlen($values['pad']); + if ($count >= 0) { + $line .= $values['pad'] . str_repeat($values['pad_repeat'], $count); + } + } + else { + $line = $values[$key === 0 ? 'indent' : 'clean'] . $line; + } + if ($values['stuff']) { + // chr(160) is the non-breaking space character. + $line = preg_replace('/^(' . chr(160) . '| |>|From)/', ' $1', $line); + } +} + +/** + * Helper function for drupal_wrap_mail() and drupal_html_to_text(). + * + * Replace all non-quotation markers from a given piece of indentation with + * non-breaking space characters. + */ +function _mailsystem_html_to_text_clean($indent) { + // chr(160) is the non-breaking space character. + return preg_replace('/[^>]/', chr(160), $indent); +} diff --git a/profiles/wcm_base/modules/contrib/mailsystem/mailsystem.admin.inc b/profiles/wcm_base/modules/contrib/mailsystem/mailsystem.admin.inc new file mode 100644 index 00000000..2dca212c --- /dev/null +++ b/profiles/wcm_base/modules/contrib/mailsystem/mailsystem.admin.inc @@ -0,0 +1,202 @@ +<?php + +/** + * @file + * Administrative form for setting the mail_system variable. + */ +function mailsystem_admin_settings() { + $args = array( + '!interface' => url('http://api.drupal.org/api/drupal/includes--mail.inc/interface/MailSystemInterface/7'), + '@interface' => 'MailSystemInterface', + '!format' => url('http://api.drupal.org/api/drupal/includes--mail.inc/function/MailSystemInterface%3A%3Aformat/7'), + '@format' => 'format()', + '!mail' => url('http://api.drupal.org/api/drupal/includes--mail.inc/function/MailSystemInterface%3A%3Amail/7'), + '@mail' => 'mail()', + '!default_class' => url('http://api.drupal.org/api/drupal/modules--system--system.mail.inc/class/DefaultMailSystem/7'), + '@default_class' => mailsystem_default_value(), + '%module' => 'module', + '%key' => 'key', + ); + $form = array('#submit' => array('mailsystem_admin_settings_submit')); + $mail_system = mailsystem_get(); + $mail_defaults = mailsystem_defaults(); + $mailsystem_classes = mailsystem_get_classes(); + $descriptions = array(); + foreach (system_rebuild_module_data() as $item) { + if ($item->status) { + $descriptions[$item->name] = ( + empty($item->info['package']) + ? '' : $item->info['package'] + ) . ' » ' . t('!module module', array('!module' => $item->info['name'])); + } + } + asort($descriptions); + $form['mailsystem'] = array( + '#type' => 'fieldset', + '#title' => t('Mail System Settings'), + '#description' => t( + 'Drupal provides a default <a href="!interface"><code>@interface</code></a> class called <a href="!default_class"><code>@default_class</code></a>. Modules may provide additional classes. Each <a href="!interface"><code>@interface</code></a> class may be associated with one or more identifiers, composed of a %module and an optional %key. Each email being sent also has a %module and a %key. To decide which class to use, Drupal uses the following search order: <ol><li>The class associated with the %module and %key, if any.</li><li>The class associated with the %module, if any.</li><li>The site-wide default <a href="!interface"><code>@interface</code></a> class.</li></ol>', $args + ), + '#collapsible' => FALSE, + '#tree' => TRUE, + ); + $form['mailsystem'][mailsystem_default_id()] = array( + '#type' => 'select', + '#title' => t( + 'Site-wide default <a href="!interface"><code>@interface</code></a> class', $args + ), + '#options' => $mailsystem_classes, + '#default_value' => $mail_system[mailsystem_default_id()], + ); + $mailsystem_classes = array( + mailsystem_default_id() => t('Remove this setting.') + ) + $mailsystem_classes; + foreach (array_diff_key($mail_system, $mail_defaults) as $id => $class) { + // Separate $id into $module and $key. + $module = $id; + while ($module && empty($descriptions[$module])) { + // Remove a key from the end + $module = implode('_', explode('_', $module, -1)); + } + // If an array key of the $mail_system variable is neither "default-system" + // nor begins with a module name, then it should be unset. + if (empty($module)) { + watchdog('mailsystem', "Removing bogus mail_system key %id.", array('%id' => $id), WATCHDOG_WARNING); + unset($mail_system[$id]); + continue; + } + // Set $title to the human-readable module name. + $title = preg_replace('/^.* » /', '', $descriptions[$module]); + if ($key = substr($id, strlen($module) + 1)) { + $title .= " ($key key)"; + } + $title .= ' class'; + $form['mailsystem'][$id] = array( + '#type' => 'select', + '#title' => $title, + '#options' => $mailsystem_classes, + '#default_value' => $class, + ); + } + // Generate a list of themes which may used to render emails. + $theme_options = array('current' => t('Current'), 'default' => t('Default')); + if (module_exists('domain_theme')) { + $theme_options['domain'] = t('Domain Theme'); + } + // Get a list of all themes. + $themes = list_themes(); + foreach ($themes as $name => $theme) { + if ($theme->status == 1) { + $theme_options[$name] = $theme->info['name']; + } + } + $form['mailsystem']['mailsystem_theme'] = array( + '#type' => 'select', + '#title' => t('Theme to render the emails'), + '#description' => t('Select the theme that will be used to render the emails. This can be either the current theme, the default theme, the domain theme or any active theme.'), + '#options' => $theme_options, + '#default_value' => variable_get('mailsystem_theme', 'current'), + ); + $form['class'] = array( + '#type' => 'fieldset', + '#title' => t('New Class'), + '#description' => t( + 'Create a new <a href="!interface"><code>@interface</code></a> that inherits its methods from other classes. The new class will be named after the other classes it uses.', $args + ), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#tree' => TRUE, + ); + $mailsystem_classes[mailsystem_default_id()] = '--Select--'; + $form['class']['format'] = array( + '#type' => 'select', + '#title' => t( + 'Class to use for the <a href="!format"><code>@format</code></a> method', $args + ), + '#options' => $mailsystem_classes, + ); + $form['class']['mail'] = array( + '#type' => 'select', + '#title' => t( + 'Class to use for the <a href="!mail"><code>@mail</code></a> method', $args + ), + '#options' => $mailsystem_classes, + ); + $form['identifier'] = array( + '#type' => 'fieldset', + '#title' => t('New Setting'), + '#description' => t('Add a new %module and %key to the settings list.', + array( + '%module' => 'module', + '%key' => 'key', + ) + ), + '#collapsible' => TRUE, + '#collapsed' => TRUE, + '#tree' => TRUE, + ); + array_unshift($descriptions, t('-- Select --')); + $form['identifier']['module'] = array( + '#type' => 'select', + '#title' => t('Module'), + '#options' => $descriptions, + ); + $form['identifier']['key'] = array( + '#type' => 'textfield', + '#title' => t('Key'), + '#size' => 80, + ); + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Save settings'), + ); + return $form; +} + +/** + * Processes mailsystem_admin_settings form. + */ +function mailsystem_admin_settings_submit($form, &$form_state) { + variable_set('mailsystem_theme', $form_state['values']['mailsystem']['mailsystem_theme']); + // Rebuild the theme registry to make changes needed by theme rendering. + drupal_theme_rebuild(); + unset($form_state['values']['mailsystem']['mailsystem_theme']); + + $default_id = mailsystem_default_id(); + $mail_system = array( + $default_id => ( + empty($form_state['values'][$default_id]) + ? mailsystem_default_value() + : $form_state['values'][$default_id] + ) + ); + foreach (element_children($form_state['values']['mailsystem']) as $module) { + $class = $form_state['values']['mailsystem'][$module]; + if (!empty($class) && $class != $default_id) { + $mail_system[$module] = $class; + } + } + unset($form_state['values']['mailsystem']); + if ($form_state['values']['class']['format'] === mailsystem_default_id()) { + unset($form_state['values']['class']['format']); + } + if ($form_state['values']['class']['mail'] === mailsystem_default_id()) { + unset($form_state['values']['class']['mail']); + } + if ($form_state['values']['class']) { + $new_class = mailsystem_create_class($form_state['values']['class']); + } + else { + $new_class = $mail_system[mailsystem_default_id()]; + } + unset($form_state['values']['class']); + if ($id = $form_state['values']['identifier']['module']) { + if (!empty($form_state['values']['identifier']['key'])) { + $id .= '_' . $form_state['values']['identifier']['key']; + } + $mail_system[$id] = $new_class; + } + unset($form_state['values']['identifier']); + variable_set('mail_system', $mail_system); + drupal_set_message(t('The configuration options have been saved.')); +} diff --git a/profiles/wcm_base/modules/contrib/mailsystem/mailsystem.info b/profiles/wcm_base/modules/contrib/mailsystem/mailsystem.info new file mode 100644 index 00000000..7d4956fe --- /dev/null +++ b/profiles/wcm_base/modules/contrib/mailsystem/mailsystem.info @@ -0,0 +1,14 @@ +package = Mail +name = Mail System +description = Provides a user interface for per-module and site-wide mail_system selection. +php = 5.0 +core = 7.x +configure = admin/config/system/mailsystem +dependencies[] = filter + +; Information added by drupal.org packaging script on 2012-04-10 +version = "7.x-2.34" +core = "7.x" +project = "mailsystem" +datestamp = "1334082653" + diff --git a/profiles/wcm_base/modules/contrib/mailsystem/mailsystem.module b/profiles/wcm_base/modules/contrib/mailsystem/mailsystem.module new file mode 100644 index 00000000..e414280b --- /dev/null +++ b/profiles/wcm_base/modules/contrib/mailsystem/mailsystem.module @@ -0,0 +1,353 @@ +<?php + +/** + * @file + * Provide UI for controlling the mail_system variable. + */ + +/** + * Implements hook_init(). + * + * Caches the list of MailSystemInterface classes, and removes classes + * from the mail_system variable which are no longer available. + * + * @see mailsystem_get_classes() + */ +function mailsystem_init() { + mailsystem_get_classes(); + // @todo Remove this when issue #299138 gets resolved. + if (!function_exists('mailsystem_html_to_text')) { + module_load_include('inc', 'mailsystem', 'html_to_text'); + } +} + +/** + * Implements hook_permission(). + * + * Defines a permission for managing the mail_system variable. + */ +function mailsystem_permission() { + return array( + 'administer mailsystem' => array( + 'title' => t('Administer Mail System'), + 'description' => t( + 'Select the default, per-module, and per-mailing <a href="!interface"><code>@interface</code></a> to use for formatting and sending email messages.', + array( + '!interface' => url('http://api.drupal.org/api/drupal/includes--mail.inc/interface/MailSystemInterface/7'), + '@interface' => 'MailSystemInterface', + ) + ), + ), + ); +} + +/** + * Implements hook_menu(). + */ +function mailsystem_menu() { + $items['admin/config/system/mailsystem'] = array( + 'title' => 'Mail System', + 'description' => 'Configure per-module Mail System settings.', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('mailsystem_admin_settings'), + 'access arguments' => array('administer mailsystem'), + 'file' => 'mailsystem.admin.inc', + ); + return $items; +} + +/** + * Returns the id for the default mail_system setting. + */ +function mailsystem_default_id() { + // @todo: Is there a way to get this from core? + return 'default-system'; +} + +/** + * Returns the value for the default mail_system setting. + */ +function mailsystem_default_value() { + // @todo: Is there a way to get this from core? + return 'DefaultMailSystem'; +} + +/** + * Returns the default settings for the mail_system variable. + */ +function mailsystem_defaults() { + return array(mailsystem_default_id() => mailsystem_default_value()); +} + +/** + * Returns the current mail_system settings. + * + * @return The contents of the mail_system variable merged with its defaults. + */ +function mailsystem_get() { + return array_merge( + mailsystem_defaults(), + variable_get('mail_system', mailsystem_defaults()) + ); +} + +/** + * Returns the default list of MailSystemInterface methods. + * + * @return + * An array whose keys are the names of the methods defined by + * MailSystemInterface and whose values are the default class used to + * provide that method. + */ +function mailsystem_default_methods() { + $mail_system = mailsystem_get(); + $default_class = $mail_system[mailsystem_default_id()]; + $methods = get_class_methods('MailSystemInterface'); + return array_combine( + $methods, + array_fill(0, count($methods), $default_class) + ); +} + +/** + * Creates and registers a new MailSystemInterface class. + * + * The newly-created class gets its name and each of its class methods from the + * other classes specified by the $class parameter. + * + * @param $class An associative array of ($method_name => $class_name) tuples, + * where each $method_name is the name of a class method to be created, and + * each $class_name is the name of a class to use for that method. + * + * @return + * The name of the newly-created class if successful; otherwise FALSE. + */ +function mailsystem_create_class($classes) { + // Merge in defaults. + $classes += mailsystem_default_methods(); + ksort($classes); + // Do not create a new class whose methods all derive from the same class. + if (count(array_unique($classes)) === 1) { + return FALSE; + } + $class_name = implode('__', $classes); + // Ensure that the mailsystem directory exists. + $class_dir = file_build_uri('mailsystem'); + if (!file_prepare_directory($class_dir, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) { + return FALSE; + } + // Build the class filename. + $class_file = drupal_realpath($class_dir) . DIRECTORY_SEPARATOR . "$class_name.mail.inc"; + // Strip DRUPAL_ROOT. + $drupal_root = drupal_realpath(DRUPAL_ROOT) . DIRECTORY_SEPARATOR; + $class_file = preg_replace('#^' . preg_quote($drupal_root, '#') . '#', '', $class_file); + // Build the class implementation as a string. + $class_contents = '<?php +class ' . $class_name . ' implements MailSystemInterface {'; + // Create a protected variable to hold each method class. + foreach (array_keys($classes) as $method) { + $class_contents .= ' + protected $' . $method . 'Class;'; + } + // Create a class construction function to populate the variables. + $class_contents .= ' + public function __construct() {'; + foreach ($classes as $method => $class) { + $class_contents .= ' + if (drupal_autoload_class(\'' . $class . '\')) { + $this->' . $method . 'Class = new ' . $class . '; + } + else { + $this->' . $method . 'Class = new ' . mailsystem_default_value() . '; + }'; + } + $class_contents .= ' + }'; + // Create each class method. + foreach (array_keys($classes) as $method) { + $class_contents .= ' + public function ' . $method . '(array $message) { + return $this->' . $method . 'Class->' . $method . '($message); + }'; + } + $class_contents .= ' +} +'; + if (file_unmanaged_save_data($class_contents, $class_file, FILE_EXISTS_REPLACE)) { + // Remove any conflicting registry entries to avoid a database error. + $class_condition = db_and() + ->condition('name', $class_name) + ->condition('type', 'class'); + $file_condition = db_and() + ->condition('filename', $class_file); + db_delete('registry_file') + ->condition($file_condition); + db_delete('registry')->condition( + db_or()->condition($class_condition) + ->condition($file_condition) + ); + // Make sure that registry functions are available. + require_once 'includes/registry.inc'; + // Parse the newly-created class file and add it to the registry. + _registry_parse_file($class_file, $class_contents, 'mailsystem'); + // Clear the mailsystem cache so that it will pick up the new class. + drupal_static_reset('mailsystem_get_classes'); + drupal_set_message( + t('Class <code>%class</code> written to <code>%file</code>.', + array('%class' => $class_name, '%file' => $class_file) + ) + ); + } + return $class_name; +} + +/** + * Helps other modules safely set their own key within mail_system. This + * function should be called from hook_enable() implementations. + * + * @param $setting An associative array ($id => $value) where: + * - $id is the machine-readable module name optionally followed by '_' + * and a key. + * - $value is one of + * - (string) The name of a class that implements MailSystemInterface. + * - (array) An associative array whose keys are the names of methods + * defined by MailSystemInterface and whose values are the names of + * the class to use for that method. + * + * @see drupal_mail(), mailsystem_default_methods() + */ +function mailsystem_set(array $setting) { + $mail_system = mailsystem_get(); + foreach ($setting as $key => $class) { + if (is_array($class)) { + unset($setting[$key]); + if ($new_class = mailsystem_create_class($class)) { + $setting[$key] = $new_class; + } + } + } + variable_set('mail_system', array_merge(mailsystem_get(), $setting)); +} + +/** + * Helps other modules safely remove their settings from mail_system. This + * function should be called from the other module's hook_disable() function. + * + * @param $setting An associative array ($module => $classname) describing + * a module and associated MailSystemInterface class that are being disabled. + * - $module is the machine-readable module name. + * - $classname is a class that implements MailSystemInterface. + * + * If $classname is empty, only the $module entry is removed. + * + * @param $class + * The name of the class to be removed, if any. + */ +function mailsystem_clear(array $setting) { + variable_set( + 'mail_system', + array_merge( + mailsystem_defaults(), + array_diff_key(array_diff(mailsystem_get(), $setting), $setting) + ) + ); +} + +/** + * Returns a list of classes which implement MailSystemInterface. + */ +function &mailsystem_get_classes() { + $mailsystem_classes = &drupal_static(__FUNCTION__); + if (!isset($mailsystem_classes)) { + $mailsystem_classes = array(); + // @todo Is there a better way to find all mail-related classes? + $declared_classes = get_declared_classes(); + $all_classes = array_combine( + $declared_classes, + array_fill(0, count($declared_classes), 0) + ); + $mail_classes = db_select('registry', 'registry') + ->distinct() + ->fields('registry', array('name', 'filename')) + ->where("type=:type AND ( filename like :filename OR name like :name )", + // Making the HUGE assumption that all classes which implement + // MailSystemInterface have filenames containing '.mail.' or + // classnames ending in 'MailSystem'. + array( + ':type' => 'class', + ':name' => '%MailSystem', + ':filename' => '%.mail.%', + ) + ) + ->execute() + ->fetchAllKeyed(); + foreach ($mail_classes as $classname => $classfile) { + if ( file_exists($classfile) + && drupal_autoload_class($classname) + ) { + $all_classes[$classname] = 1; + } + } + foreach ($all_classes as $classname => $autoload) { + if ( ($autoload || preg_match('/MailSystem/', $classname)) + && ($object = new $classname) + && ($object instanceof MailSystemInterface) + ) { + $mailsystem_classes[$classname] = $classname; + } + elseif ($autoload) { + // Clear classes that are no longer available. + db_delete('registry') + ->condition('name', $classname) + ->execute(); + } + } + foreach (array_unique(mailsystem_get()) as $classname) { + if (class_exists($classname)) { + $mailsystem_classes[$classname] = $classname; + } + else { + mailsystem_clear(array(mailsystem_default_id() => $classname)); + } + } + ksort($mailsystem_classes); + } + return $mailsystem_classes; +} + +/** +* Implements hook_theme_registry_alter(). +*/ +function mailsystem_theme_registry_alter(&$theme_registry) { + module_load_include('inc', 'mailsystem', 'mailsystem.theme'); + return mailsystem_theme_theme_registry_alter($theme_registry); +} + +/** +* Retrieves the key of the theme used to render the emails. +* +* @todo Add some kind of hook to let other modules alter this behavior. +*/ +function mailsystem_get_mail_theme() { + global $theme_key; + $theme = variable_get('mailsystem_theme', 'current'); + switch ($theme) { + case 'default': + $theme = variable_get('theme_default', NULL); + break; + case 'current': + $theme = $theme_key; + break; + case 'domain': + // Fetch the theme for the current domain. + if (module_exists('domain_theme')) { + // Assign the selected theme, based on the active domain. + global $_domain; + $domain_theme = domain_theme_lookup($_domain['domain_id']); + // The above returns -1 on failure. + $theme = ($domain_theme != -1) ? $domain_theme['theme'] : $theme_key; + } + break; + } + return $theme; +} diff --git a/profiles/wcm_base/modules/contrib/mailsystem/mailsystem.theme.inc b/profiles/wcm_base/modules/contrib/mailsystem/mailsystem.theme.inc new file mode 100644 index 00000000..8a22b912 --- /dev/null +++ b/profiles/wcm_base/modules/contrib/mailsystem/mailsystem.theme.inc @@ -0,0 +1,82 @@ +<?php + +/** +* @file +* The theme system, which controls the output of email messages. +*/ + +/** +* Implements hook_theme_registry_alter(). +*/ +function mailsystem_theme_theme_registry_alter(&$theme_registry) { + global $theme_key; + static $recursion_prevention = FALSE; + + // Prevent recursive execution. + if ($recursion_prevention) { + return; + } + $recursion_prevention = TRUE; + $mailsystem_theme = mailsystem_get_mail_theme(); + + // Only take action if the mailsystem theme is not the current theme. + if ($mailsystem_theme != $theme_key) { + $themes = list_themes(); + // Get the mailsystem theme to be used for rendering emails. + if (isset($themes[$mailsystem_theme])) { + $theme = clone $themes[$mailsystem_theme]; + if (isset($theme)) { + // Establish variables for further processing. + $base_theme = array(); + if (isset($theme->base_themes)) { + foreach (array_keys($theme->base_themes) as $base) { + $base_theme[$base] = clone $themes[$base]; + } + } + if (isset($theme->base_theme) && !isset($base_theme[$theme->base_theme])) { + $base_theme[$theme->base_theme] = clone $themes[$theme->base_theme]; + } + if (isset($theme->engine)) { + $theme_engine = $theme->engine; + } + + // Include template files to let _theme_load_registry add preprocess + // functions. + include_once(drupal_get_path('theme', $theme->name) . '/template.php'); + foreach ($base_theme as $base) { + include_once(drupal_get_path('theme', $base->name) . '/template.php'); + } + + // Get the theme_registry cache. + $cache = _theme_load_registry($theme, $base_theme, $theme_engine); + + // Change the registry for hooks with a 'mail theme' element. + foreach ($theme_registry as $name => $hook) { + if (!empty($hook['mail theme'])) { + if (isset($cache[$name])) { + $cache[$name]['includes'][] = drupal_get_path('theme', $theme->name) . '/template.php'; + foreach ($base_theme as $base) { + $cache[$name]['includes'][] = drupal_get_path('theme', $base->name) . '/template.php'; + } + // Change the current registry for the new record. + $theme_registry[$name] = $cache[$name]; + } + + // Look for template suggestions. + foreach ($cache as $cache_name => $cache_hook) { + if (strpos($cache_name, $name . '__') !== FALSE) { + $cache_hook['includes'][] = drupal_get_path('theme', $theme->name) . '/template.php'; + foreach ($base_theme as $base) { + $cache_hook['includes'][] = drupal_get_path('theme', $base->name) . '/template.php'; + } + // Change the current registry for the new record. + $theme_registry[$cache_name] = $cache_hook; + } + } + } + } + } + } + } + $recursion_prevention = FALSE; +} diff --git a/profiles/wcm_base/modules/contrib/mimemail/CHANGELOG.txt b/profiles/wcm_base/modules/contrib/mimemail/CHANGELOG.txt new file mode 100644 index 00000000..a35cb6e5 --- /dev/null +++ b/profiles/wcm_base/modules/contrib/mimemail/CHANGELOG.txt @@ -0,0 +1,214 @@ +Mime Mail 7.x-1.x, xxxx-xx-xx +----------------------- + +Mime Mail 7.x-1.0-beta4, 2015-08-02 +----------------------- +- #2413495 by sgabe, siggi_kid: Public images not embedded when file default scheme is private +- #2312747 by Lukas von Blarer: Remove 'itok' token from image URL +- #2366659 by david_garcia: Attachments created by content generate warnings +- #2404719 by alexp999: Missing space in RFC headers breaks DKIM +- #1908318 by jvieille, zionduc, bisonbleu | anrkaid: Sender is double encoded. +- #2359771 by PascalAnimateur: Support for OG membership tokens. +- #2218037 by sgabe | pinkonomy: Fixed integrity constraint violation. +- #2219609 by mitrpaka, sgabe: Convert float properties just for images. +- #2057703 by New Zeal, sgabe: Warning: is_file() expects a valid path. +- #2202127 by k.skarlatos: Added List-Unsubscribe header field for bulk mails. +- #2297135 by tobiasb: Language prefix with absolute-path reference. +- #2297091 by cameron1729: TLDs in Return-Path trimmed to 4 characters. +- #2237109 by Dane Powell: Indicate text format for body in Rules. + +Mime Mail 7.x-1.0-beta3, 2014-03-05 +----------------------- +- Public files test incorrect if similar path is not below root. + +Mime Mail 7.x-1.0-beta2, 2014-02-26 +----------------------- +- Stronger authentication for incoming messages. +- #2031143 by sgabe, das-peter: Support @import by using drupal_load_stylesheet(). +- #2087913 by sgabe | alancsilver: Allow spaces in attachment filenames. +- #1852694 by clemens.tolboom | Pixelstyle: Convert float to align for images. +- #2145659 by sgabe, fatherguddha | raincloud: Images with 'itok' token are not embedded. +- #2119613 by sgabe, david_garcia_garcia: Further improve boundary collision avoidance. +- #2185909 by cyrus_bt5, ekidman: Extra space in long header fields. +- #2152705 by gargsuchi: Images with 'itok' token not showing up. +- #2129149 by pokap | satvision83: Undefined offset in mimemail_headers. + +Mime Mail 7.x-1.0-beta1, 2013-10-07 +----------------------- +- #1702868 by sgabe, kid_icarus: Remove tokens if no replacement value can be generated. +- #1719570 by sgabe, oadaeh | greggles: Fix for SA-CONTRIB-2012-124. +- #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 diff --git a/profiles/wcm_base/modules/contrib/mimemail/LICENSE.txt b/profiles/wcm_base/modules/contrib/mimemail/LICENSE.txt new file mode 100644 index 00000000..d159169d --- /dev/null +++ b/profiles/wcm_base/modules/contrib/mimemail/LICENSE.txt @@ -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. diff --git a/profiles/wcm_base/modules/contrib/mimemail/README.txt b/profiles/wcm_base/modules/contrib/mimemail/README.txt new file mode 100644 index 00000000..1bb6aa47 --- /dev/null +++ b/profiles/wcm_base/modules/contrib/mimemail/README.txt @@ -0,0 +1,136 @@ + +-- 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. + + 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. + + 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. Due to security + concerns, only files residing in the public file system (e.g sites/default/files) + can be used by default. + + 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 + or public://mypicture.jpg + + The 'send arbitrary files' permission allows you to attach or embed files located + outside Drupal's public files directory. Note that this has security implications: + arbitrary means even your settings.php! Give to trusted roles only! + + +-- 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 + diff --git a/profiles/wcm_base/modules/contrib/mimemail/includes/mimemail.admin.inc b/profiles/wcm_base/modules/contrib/mimemail/includes/mimemail.admin.inc new file mode 100644 index 00000000..ca71e54e --- /dev/null +++ b/profiles/wcm_base/modules/contrib/mimemail/includes/mimemail.admin.inc @@ -0,0 +1,154 @@ +<?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')), + '#description' => t('The filter set that will be applied to the message body. + If you are using Mime Mail as default mail sytem, make sure to enable + "Convert line breaks into HTML" and "Convert URLs into links" with a long + enough maximum length for e.g. password reset URLs!'), + ); + + $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', drupal_random_key()), + '#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); +} diff --git a/profiles/wcm_base/modules/contrib/mimemail/includes/mimemail.incoming.inc b/profiles/wcm_base/modules/contrib/mimemail/includes/mimemail.incoming.inc new file mode 100644 index 00000000..ff1d43f7 --- /dev/null +++ b/profiles/wcm_base/modules/contrib/mimemail/includes/mimemail.incoming.inc @@ -0,0 +1,211 @@ +<?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() { + if (!isset($_POST['token']) || empty($_POST['token'])) { + return drupal_access_denied(); + } + + if (isset($_POST['message']) && !empty($_POST['message'])) { + $key = variable_get('mimemail_key', drupal_random_key()); + $hash = hash_hmac('sha1', $_POST['message'], $key); + if ($hash != $_POST['token']) { + watchdog('access denied', 'Authentication error for POST e-mail', WATCHDOG_WARNING); + return drupal_access_denied(); + } + else { + return mimemail_incoming($_POST['message']); + } + } + + return drupal_access_denied(); +} + +/** + * 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), + ); +} diff --git a/profiles/wcm_base/modules/contrib/mimemail/includes/mimemail.mail.inc b/profiles/wcm_base/modules/contrib/mimemail/includes/mimemail.mail.inc new file mode 100644 index 00000000..04774faa --- /dev/null +++ b/profiles/wcm_base/modules/contrib/mimemail/includes/mimemail.mail.inc @@ -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); + } +} diff --git a/profiles/wcm_base/modules/contrib/mimemail/mimemail.inc b/profiles/wcm_base/modules/contrib/mimemail/mimemail.inc new file mode 100644 index 00000000..3dbb0052 --- /dev/null +++ b/profiles/wcm_base/modules/contrib/mimemail/mimemail.inc @@ -0,0 +1,577 @@ +<?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. + // A CRLF may be inserted before any WSP. + // @see http://tools.ietf.org/html/rfc2822#section-2.2.3 + 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) { + // According to IANA the current longest TLD is 23 characters. + if (preg_match('/[a-z\d\-\.\+_]+@(?:[a-z\d\-]+\.)+[a-z\d]{2,23}/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) { + // According to RFC 2047 addresses MUST NOT be encoded. + if ($key !== 'From' && $key !== 'Sender') { + $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 = @is_file($file); + + if ($is_file) { + $access = user_access('send arbitrary files'); + $in_public_path = strpos(@drupal_realpath($file), drupal_realpath('public://')) === 0; + if (!$in_public_path && !$access) { + return $url; + } + } + + if (!$name) { + $name = $is_file ? basename($file) : 'attachment.dat'; + } + if (!$type) { + $type = $is_file ? file_get_mimetype($file) : file_get_mimetype($name); + } + + $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) { + // Control variable to avoid boundary collision. + static $part_num = 0; + + $boundary = sha1(uniqid($_SERVER['REQUEST_TIME'], TRUE)) . $part_num++; + $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. + * @param boolean $to_embed + * (optional) Wheter the URL is used to embed the file. Defaults to NULL. + * + * @return string + * A processed URL. + */ +function _mimemail_url($url, $to_embed = NULL) { + global $base_url; + $url = urldecode($url); + + $to_link = variable_get('mimemail_linkonly', 0); + $is_image = preg_match('!\.(png|gif|jpg|jpeg)!i', $url); + $is_absolute = file_uri_scheme($url) != FALSE || preg_match('!(mailto|callto|tel)\:!', $url); + + if (!$to_embed) { + if ($is_absolute) { + return str_replace(' ', '%20', $url); + } + } + else { + $url = preg_replace('!^' . base_path() . '!', '', $url, 1); + if ($is_image) { + // Remove security token from URL, this allows for styled image embedding. + // @see https://drupal.org/drupal-7.20-release-notes + $url = preg_replace('/\\?itok=.*$/', '', $url); + if ($to_link) { + // Exclude images from embedding if needed. + $url = file_create_url($url); + $url = str_replace(' ', '%20', $url); + } + } + return $url; + } + + $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. + $path = trim($path, '/'); + $args = explode('/', $path); + foreach ($languages as $lang) { + if (!empty($args) && $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 (preg_match('!^\?q=*!', $url)) { + $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; +} diff --git a/profiles/wcm_base/modules/contrib/mimemail/mimemail.info b/profiles/wcm_base/modules/contrib/mimemail/mimemail.info new file mode 100644 index 00000000..2d5d028f --- /dev/null +++ b/profiles/wcm_base/modules/contrib/mimemail/mimemail.info @@ -0,0 +1,22 @@ +name = Mime Mail +description = Send MIME-encoded emails with embedded images and attachments. +dependencies[] = mailsystem +dependencies[] = system (>=7.24) +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 2015-08-02 +version = "7.x-1.0-beta4" +core = "7.x" +project = "mimemail" +datestamp = "1438530555" + diff --git a/profiles/wcm_base/modules/contrib/mimemail/mimemail.install b/profiles/wcm_base/modules/contrib/mimemail/mimemail.install new file mode 100644 index 00000000..a09034c6 --- /dev/null +++ b/profiles/wcm_base/modules/contrib/mimemail/mimemail.install @@ -0,0 +1,113 @@ +<?php + +/** + * @file + * Install, update and uninstall functions for Mime Mail module. + */ + +/** + * Implements hook_enable(). + */ +function mimemail_enable() { + module_load_include('module', 'mailsystem'); + mailsystem_set( + array( + mailsystem_default_id() => 'MimeMailSystem', + 'mimemail' => 'MimeMailSystem', + ) + ); + + user_role_grant_permissions(DRUPAL_AUTHENTICATED_RID, array('edit mimemail user settings')); +} + +/** + * 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'); +} + +/** + * Generate new key for authenticating incoming messages. + */ +function mimemail_update_7002() { + variable_set('mimemail_key', drupal_random_key()); + return t('Mime Mail has generated a new key to authenticate incoming messages.'); +} diff --git a/profiles/wcm_base/modules/contrib/mimemail/mimemail.module b/profiles/wcm_base/modules/contrib/mimemail/mimemail.module new file mode 100644 index 00000000..21164b8b --- /dev/null +++ b/profiles/wcm_base/modules/contrib/mimemail/mimemail.module @@ -0,0 +1,392 @@ +<?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.'), + ), + 'send arbitrary files' => array( + 'title' => t('Send arbitrary files'), + 'description' => t('Attach or embed files located outside the public files directory.'), + 'restrict access' => TRUE, + ), + ); +} + +/** + * 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']; + $options = array('clear' => TRUE); + + // 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', + 'list-unsubscribe' => 'List-Unsubscribe', + ); + 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, $options); + } + $message['headers'][$address_header] = implode(',', $params[$param_key]); + } + } + + $message['to'] = token_replace($message['to'], $context, $options); + $message['subject'] = token_replace($context['subject'], $context, $options); + $message['body'][] = token_replace($context['body'], $context, $options); + $message['params']['plaintext'] = token_replace($params['plaintext'], $context, $options); + $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; + } + } + } + + // Removing newline character introduced by _drupal_wrap_mail_line(); + $subject = str_replace(array("\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; +} diff --git a/profiles/wcm_base/modules/contrib/mimemail/mimemail.rules.inc b/profiles/wcm_base/modules/contrib/mimemail/mimemail.rules.inc new file mode 100644 index 00000000..29a6dee5 --- /dev/null +++ b/profiles/wcm_base/modules/contrib/mimemail/mimemail.rules.inc @@ -0,0 +1,360 @@ +<?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, + ), + 'list_unsubscribe' => array( + 'type' => 'text', + 'label' => t('Unsubscription e-mail and/or URL'), + 'description' => t("An e-mail address and/or a URL which can be used for unsubscription. Values must be enclosed by angel brackets and separated by a comma."), + '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 HTML body. Will be formatted using the text format selected on the <a href="@url">settings</a> page.', array('@url' => url('admin/config/system/mimemail'))), + 'sanitize' => TRUE, + 'optional' => TRUE, + 'translatable' => TRUE, + ), + 'plaintext' => array( + 'type' => 'text', + 'label' => t('Plain text body'), + 'description' => t("The mail's 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, $list_unsubscribe = 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, + 'list-unsubscribe' => $list_unsubscribe, + '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))); + } +} + +/** + * @} + */ + diff --git a/profiles/wcm_base/modules/contrib/mimemail/modules/mimemail_action/mimemail_action.info b/profiles/wcm_base/modules/contrib/mimemail/modules/mimemail_action/mimemail_action.info new file mode 100644 index 00000000..e6ede6a8 --- /dev/null +++ b/profiles/wcm_base/modules/contrib/mimemail/modules/mimemail_action/mimemail_action.info @@ -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 2015-08-02 +version = "7.x-1.0-beta4" +core = "7.x" +project = "mimemail" +datestamp = "1438530555" + diff --git a/profiles/wcm_base/modules/contrib/mimemail/modules/mimemail_action/mimemail_action.module b/profiles/wcm_base/modules/contrib/mimemail/modules/mimemail_action/mimemail_action.module new file mode 100644 index 00000000..5ad51d61 --- /dev/null +++ b/profiles/wcm_base/modules/contrib/mimemail/modules/mimemail_action/mimemail_action.module @@ -0,0 +1,197 @@ +<?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'])) { + if (get_class($entity) == 'OgMembership') { + $context['user'] = user_load($entity->etid); + } else { + $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; +} diff --git a/profiles/wcm_base/modules/contrib/mimemail/modules/mimemail_compress/mimemail_compress.inc b/profiles/wcm_base/modules/contrib/mimemail/modules/mimemail_compress/mimemail_compress.inc new file mode 100644 index 00000000..dcf3f0aa --- /dev/null +++ b/profiles/wcm_base/modules/contrib/mimemail/modules/mimemail_compress/mimemail_compress.inc @@ -0,0 +1,267 @@ +<?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); + + // Convert float to align for images. + $float = preg_match ('/float:(left|right)/', $style, $matches); + if ($node->nodeName == 'img' && $float) { + $node->setAttribute('align', $matches[1]); + $node->setAttribute('vspace', 5); + $node->setAttribute('hspace', 5); + } + } + } + + // 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; + } +} diff --git a/profiles/wcm_base/modules/contrib/mimemail/modules/mimemail_compress/mimemail_compress.info b/profiles/wcm_base/modules/contrib/mimemail/modules/mimemail_compress/mimemail_compress.info new file mode 100644 index 00000000..4733b214 --- /dev/null +++ b/profiles/wcm_base/modules/contrib/mimemail/modules/mimemail_compress/mimemail_compress.info @@ -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 2015-08-02 +version = "7.x-1.0-beta4" +core = "7.x" +project = "mimemail" +datestamp = "1438530555" + diff --git a/profiles/wcm_base/modules/contrib/mimemail/modules/mimemail_compress/mimemail_compress.install b/profiles/wcm_base/modules/contrib/mimemail/modules/mimemail_compress/mimemail_compress.install new file mode 100644 index 00000000..1e3bdc8d --- /dev/null +++ b/profiles/wcm_base/modules/contrib/mimemail/modules/mimemail_compress/mimemail_compress.install @@ -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; +} diff --git a/profiles/wcm_base/modules/contrib/mimemail/modules/mimemail_compress/mimemail_compress.module b/profiles/wcm_base/modules/contrib/mimemail/modules/mimemail_compress/mimemail_compress.module new file mode 100644 index 00000000..e538fc43 --- /dev/null +++ b/profiles/wcm_base/modules/contrib/mimemail/modules/mimemail_compress/mimemail_compress.module @@ -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; + } +} diff --git a/profiles/wcm_base/modules/contrib/mimemail/modules/mimemail_example/mimemail_example.info b/profiles/wcm_base/modules/contrib/mimemail/modules/mimemail_example/mimemail_example.info new file mode 100644 index 00000000..649bc3a6 --- /dev/null +++ b/profiles/wcm_base/modules/contrib/mimemail/modules/mimemail_example/mimemail_example.info @@ -0,0 +1,12 @@ +name = Mime Mail Example +description = Example of how to use the Mime Mail module. +dependencies[] = mimemail +package = Example modules +core = 7.x + +; Information added by Drupal.org packaging script on 2015-08-02 +version = "7.x-1.0-beta4" +core = "7.x" +project = "mimemail" +datestamp = "1438530555" + diff --git a/profiles/wcm_base/modules/contrib/mimemail/modules/mimemail_example/mimemail_example.install b/profiles/wcm_base/modules/contrib/mimemail/modules/mimemail_example/mimemail_example.install new file mode 100644 index 00000000..4f062640 --- /dev/null +++ b/profiles/wcm_base/modules/contrib/mimemail/modules/mimemail_example/mimemail_example.install @@ -0,0 +1,20 @@ +<?php + +/** + * @file + * Install, update and uninstall functions for Mime Mail Example module. + */ + +/** + * Implements hook_enable(). + */ +function mimemail_example_enable() { + mailsystem_set(array('mimemail_example' => 'MimeMailSystem')); +} + +/** + * Implements hook_disable(). + */ +function mimemail_example_disable() { + mailsystem_clear(array('mimemail_example' => 'MimeMailSystem')); +} diff --git a/profiles/wcm_base/modules/contrib/mimemail/modules/mimemail_example/mimemail_example.module b/profiles/wcm_base/modules/contrib/mimemail/modules/mimemail_example/mimemail_example.module new file mode 100644 index 00000000..85fe957d --- /dev/null +++ b/profiles/wcm_base/modules/contrib/mimemail/modules/mimemail_example/mimemail_example.module @@ -0,0 +1,170 @@ +<?php + +/** + * @file + * Core and contrib hook implementations for Mime Mail Example module. + */ + +/** + * Implements hook_menu(). + */ +function mimemail_example_menu() { + $items['example/mimemail_example'] = array( + 'title' => 'Mime Mail Example', + 'page callback' => 'drupal_get_form', + 'page arguments' => array('mimemail_example_form'), + 'access arguments' => array('access content'), + ); + + return $items; +} + +/** + * Implements hook_mail(). + */ +function mimemail_example_mail($key, &$message, $params) { + $message['subject'] = $params['subject']; + $message['body'][] = $params['body']; +} + +/** + * The example email contact form. + */ +function mimemail_example_form() { + global $user; + + $form['intro'] = array( + '#markup' => t('Use this form to send a HTML message to an e-mail address. No spamming!'), + ); + + $form['key'] = array( + '#type' => 'textfield', + '#title' => t('Key'), + '#default_value' => 'test', + '#required' => TRUE, + ); + + $form['to'] = array( + '#type' => 'textfield', + '#title' => t('To'), + '#default_value' => $user->mail, + '#required' => TRUE, + ); + + $form['from'] = array( + '#type' => 'textfield', + '#title' => t('Sender name'), + ); + + $form['from_mail'] = array( + '#type' => 'textfield', + '#title' => t('Sender e-mail address'), + ); + + $form['params'] = array( + '#tree' => TRUE, + 'headers' => array( + 'Cc' => array( + '#type' => 'textfield', + '#title' => t('Cc'), + ), + 'Bcc' => array( + '#type' => 'textfield', + '#title' => t('Bcc'), + ), + 'Reply-to' => array( + '#type' => 'textfield', + '#title' => t('Reply to'), + ), + 'List-unsubscribe' => array( + '#type' => 'textfield', + '#title' => t('List-unsubscribe'), + ), + ), + 'subject' => array( + '#type' => 'textfield', + '#title' => t('Subject'), + ), + 'body' => array( + '#type' => 'textarea', + '#title' => t('HTML message'), + ), + 'plain' => array( + '#type' => 'hidden', + '#states' => array( + 'value' => array( + ':input[name="body"]' => array('value' => ''), + ), + ), + ), + 'plaintext' => array( + '#type' => 'textarea', + '#title' => t('Plain text message'), + ), + 'attachments' => array( + '#name' => 'files[attachment]', + '#type' => 'file', + '#title' => t('Choose a file to send as attachment'), + ), + ); + + $form['submit'] = array( + '#type' => 'submit', + '#value' => t('Send message'), + ); + + return $form; +} + +/** + * Form validation logic for the email contact form. + */ +function mimemail_example_form_validate($form, &$form_state) { + $values = &$form_state['values']; + + if (!valid_email_address($values['to'])) { + form_set_error('to', t('That e-mail address is not valid.')); + } + + $file = file_save_upload('attachment'); + if ($file) { + $file = file_move($file, 'public://'); + $values['params']['attachments'][] = array( + 'filepath' => $file->uri, + ); + } +} + +/** + * Form submission logic for the email contact form. + */ +function mimemail_example_form_submit($form, &$form_state) { + $values = $form_state['values']; + + $module = 'mimemail_example'; + $key = $values['key']; + $to = $values['to']; + $language = language_default(); + $params = $values['params']; + + if (!empty($values['from_mail'])) { + module_load_include('inc', 'mimemail'); + $from = mimemail_address(array( + 'name' => $values['from'], + 'mail' => $values['from_mail'], + )); + } + else { + $from = $values['from']; + } + + $send = TRUE; + + $result = drupal_mail($module, $key, $to, $language, $params, $from, $send); + if ($result['result'] == TRUE) { + drupal_set_message(t('Your message has been sent.')); + } + else { + drupal_set_message(t('There was a problem sending your message and it was not sent.'), 'error'); + } +} diff --git a/profiles/wcm_base/modules/contrib/mimemail/tests/mimemail.test b/profiles/wcm_base/modules/contrib/mimemail/tests/mimemail.test new file mode 100644 index 00000000..5896a3c1 --- /dev/null +++ b/profiles/wcm_base/modules/contrib/mimemail/tests/mimemail.test @@ -0,0 +1,99 @@ +<?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.'); + + $url = '/sites/default/files/styles/thumbnail/public/image.jpg?itok=Wrl6Qi9U'; + $result = _mimemail_url($url, TRUE); + $expected = 'sites/default/files/styles/thumbnail/public/image.jpg'; + $this->assertIdentical($result, $expected, 'Security token removed from styled image URL.'); + + $expected = $url = 'public://' . $this->randomName() . ' '. $this->randomName() . '.' . $this->randomName(3); + $result = _mimemail_url($url, TRUE); + $this->assertIdentical($result, $expected, 'Space in the filename of the attachment left intact.'); + } + +} + +/** + * Tests functions from the Mime Mail module. + */ +class MimeMailWebTestCase extends DrupalWebTestCase { + protected $adminUser; + + public static function getInfo() { + return array( + 'name' => 'Mime Mail web tests', + 'description' => 'Test that Mime Mail works properly.', + 'group' => 'Mime Mail', + ); + } + + public function setUp() { + parent::setUp('mailsystem', 'mimemail'); + + $permissions = array( + 'access administration pages', + 'administer site configuration', + ); + + // Check to make sure that the array of permissions are valid. + $this->checkPermissions($permissions, TRUE); + + // Create and login user. + $this->adminUser = $this->drupalCreateUser($permissions); + $this->drupalLogin($this->adminUser); + } + + public function testUrl() { + $this->drupalPost('admin/config/system/mimemail', + array('mimemail_linkonly' => TRUE), + t('Save configuration')); + + $url = 'public://' . $this->randomName() . ' '. $this->randomName() . '.jpg'; + $result = _mimemail_url($url, TRUE); + $expected = str_replace(' ', '%20', file_create_url($url)); + $message = 'Stream wrapper converted to web accessible URL for linked image.'; + $this->assertIdentical($result, $expected, $message); + } + +} diff --git a/profiles/wcm_base/modules/contrib/mimemail/tests/mimemail_compress.test b/profiles/wcm_base/modules/contrib/mimemail/tests/mimemail_compress.test new file mode 100644 index 00000000..2b05613e --- /dev/null +++ b/profiles/wcm_base/modules/contrib/mimemail/tests/mimemail_compress.test @@ -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(); + } + + + +} diff --git a/profiles/wcm_base/modules/contrib/mimemail/tests/mimemail_rules.test b/profiles/wcm_base/modules/contrib/mimemail/tests/mimemail_rules.test new file mode 100644 index 00000000..68dfdcf5 --- /dev/null +++ b/profiles/wcm_base/modules/contrib/mimemail/tests/mimemail_rules.test @@ -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 { + protected $adminUser; + + public static function getInfo() { + return array( + 'name' => 'Rules integration', + 'description' => 'Test the Rules integration.', + 'group' => 'Mime Mail', + ); + } + + public function setUp() { + parent::setUp('mailsystem', 'locale', 'entity', 'entity_token', 'rules', 'mimemail'); + + $permissions = array( + 'access administration pages', + 'edit mimemail user settings', + 'administer languages', + 'administer rules', + 'bypass rules access', + 'access rules debug', + ); + + // Check to make sure that the array of permissions are valid. + $this->checkPermissions($permissions, TRUE); + + // Create and login user. + $this->adminUser = $this->drupalCreateUser($permissions); + $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 rule with "mimemail" action and fire it. + */ + public function testMailToUserAction() { + $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 rule with "mimemail_to_users_of_role" action and fire it. + */ + public function testMailToUsersOfRoleAction() { + $languages = language_list(); + + // Add more users 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); + } + + // 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. + // Enforce German as fallback language if an user doesn't have a language. + $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); + } + + // Rest the collected mails. + variable_set('drupal_test_email_collector', array()); + + // Send mails to all users of a role but use the same language for all. + $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'); + } + + // Rest the collected mails. + variable_set('drupal_test_email_collector', array()); + + // Send mails to all users of a role except deactivated users. + // 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)); + } +} diff --git a/profiles/wcm_base/modules/contrib/mimemail/theme/mimemail-message.tpl.php b/profiles/wcm_base/modules/contrib/mimemail/theme/mimemail-message.tpl.php new file mode 100644 index 00000000..179573d5 --- /dev/null +++ b/profiles/wcm_base/modules/contrib/mimemail/theme/mimemail-message.tpl.php @@ -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> diff --git a/profiles/wcm_base/modules/contrib/mimemail/theme/mimemail.theme.inc b/profiles/wcm_base/modules/contrib/mimemail/theme/mimemail.theme.inc new file mode 100644 index 00000000..2a2df7cf --- /dev/null +++ b/profiles/wcm_base/modules/contrib/mimemail/theme/mimemail.theme.inc @@ -0,0 +1,102 @@ +<?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)) { + $css .= drupal_load_stylesheet($style, TRUE); + } + } + + // 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']); +} diff --git a/profiles/wcm_base/modules/contrib/webform/components/date.inc b/profiles/wcm_base/modules/contrib/webform/components/date.inc index f84c2f23..2450f73a 100644 --- a/profiles/wcm_base/modules/contrib/webform/components/date.inc +++ b/profiles/wcm_base/modules/contrib/webform/components/date.inc @@ -25,6 +25,7 @@ function _webform_defaults_date() { 'datepicker' => 1, 'title_display' => 0, 'description' => '', + 'description_above' => FALSE, 'private' => FALSE, 'analysis' => FALSE, ), @@ -462,7 +463,7 @@ function _webform_submit_date($component, $value) { /** * Implements _webform_display_component(). */ -function _webform_display_date($component, $value, $format = 'html') { +function _webform_display_date($component, $value, $format = 'html', $submission = array()) { $value = webform_date_array(isset($value[0]) ? $value[0] : '', 'date'); return array( diff --git a/profiles/wcm_base/modules/contrib/webform/components/email.inc b/profiles/wcm_base/modules/contrib/webform/components/email.inc index d4077924..8e3e6169 100644 --- a/profiles/wcm_base/modules/contrib/webform/components/email.inc +++ b/profiles/wcm_base/modules/contrib/webform/components/email.inc @@ -24,6 +24,7 @@ function _webform_defaults_email() { 'disabled' => 0, 'title_display' => 0, 'description' => '', + 'description_above' => FALSE, 'placeholder' => '', 'attributes' => array(), 'private' => FALSE, @@ -257,13 +258,14 @@ function _webform_validate_email($form_element, &$form_state) { implode('][', $form_element ['#parents']), TRUE, // Required validation is done elsewhere. $component['extra']['multiple'], + FALSE, // No tokens are allowed in user input. $format); } /** * Implements _webform_display_component(). */ -function _webform_display_email($component, $value, $format = 'html') { +function _webform_display_email($component, $value, $format = 'html', $submission = array()) { return array( '#title' => $component['name'], '#title_display' => $component['extra']['title_display'] ? $component['extra']['title_display'] : 'before', @@ -334,6 +336,13 @@ function _webform_table_email($component, $value) { return check_plain(empty($value[0]) ? '' : $value[0]); } +/** + * Implements _webform_action_set_component(). + */ +function _webform_action_set_email($component, &$element, &$form_state, $value) { + $element['#value'] = $value; + form_set_value($element, $value, $form_state); +} /** * Implements _webform_csv_headers_component(). diff --git a/profiles/wcm_base/modules/contrib/webform/components/fieldset.inc b/profiles/wcm_base/modules/contrib/webform/components/fieldset.inc index 8b43ae33..763e5211 100644 --- a/profiles/wcm_base/modules/contrib/webform/components/fieldset.inc +++ b/profiles/wcm_base/modules/contrib/webform/components/fieldset.inc @@ -19,6 +19,7 @@ function _webform_defaults_fieldset() { 'collapsible' => 0, 'collapsed' => 0, 'description' => '', + 'description_above' => FALSE, 'private' => FALSE, ), ); @@ -82,7 +83,7 @@ function webform_fieldset_prerender($element) { /** * Implements _webform_display_component(). */ -function _webform_display_fieldset($component, $value, $format = 'html') { +function _webform_display_fieldset($component, $value, $format = 'html', $submission = array()) { if ($format == 'text') { $element = array( '#title' => $component['name'], diff --git a/profiles/wcm_base/modules/contrib/webform/components/file.inc b/profiles/wcm_base/modules/contrib/webform/components/file.inc index 6ec752c8..1f7c99ef 100644 --- a/profiles/wcm_base/modules/contrib/webform/components/file.inc +++ b/profiles/wcm_base/modules/contrib/webform/components/file.inc @@ -27,6 +27,7 @@ function _webform_defaults_file() { 'progress_indicator' => 'throbber', 'title_display' => 0, 'description' => '', + 'description_above' => FALSE, 'attributes' => array(), 'private' => FALSE, 'analysis' => FALSE, @@ -47,6 +48,10 @@ function _webform_theme_file() { 'render element' => 'element', 'file' => 'components/file.inc', ), + 'webform_managed_file' => array( + 'render element' => 'element', + 'file' => 'components/file.inc', + ), ); } @@ -161,7 +166,7 @@ function _webform_edit_file($component) { '#type' => 'textfield', '#title' => t('Rename files'), '#default_value' => $component['extra']['rename'], - '#description' => t('You may optionally use tokens to create a pattern used to rename files upon submission. Omit the extension; it will be added automatically.').' '.theme('webform_token_help', array('groups' => array('node', 'submission'))), + '#description' => t('You may optionally use tokens to create a pattern used to rename files upon submission. Omit the extension; it will be added automatically.') . ' ' . theme('webform_token_help', array('groups' => array('node', 'submission'))), '#weight' => 6, '#element_validate' => array('_webform_edit_file_rename_validate'), ); @@ -342,6 +347,7 @@ function _webform_render_file($component, $value = NULL, $filter = TRUE, $submis $element = array( '#type' => 'managed_file', + '#theme' => 'webform_managed_file', '#title' => $filter ? webform_filter_xss($component['name']) : $component['name'], '#title_display' => $component['extra']['title_display'] ? $component['extra']['title_display'] : 'before', '#required' => $component['required'], @@ -369,6 +375,42 @@ function _webform_render_file($component, $value = NULL, $filter = TRUE, $submis return $element; } +/** + * Returns HTML for a webform managed file element. + * + * See #2495821 and #2497909. The core theme_file_managed_file creates a + * wrapper around the element with the element's id, thereby creating 2 elements + * with the same id. + * + * @param $variables + * An associative array containing: + * - element: A render element representing the file. + * + */ +function theme_webform_managed_file($variables) { + $element = $variables['element']; + + $attributes = array(); + + // For webform use, do not add the id to the wrapper. + //if (isset($element['#id'])) { + // $attributes['id'] = $element['#id']; + //} + + if (!empty($element['#attributes']['class'])) { + $attributes['class'] = (array) $element['#attributes']['class']; + } + $attributes['class'][] = 'form-managed-file'; + + // This wrapper is required to apply JS behaviors and CSS styling. + $output = ''; + $output .= '<div' . drupal_attributes($attributes) . '>'; + $output .= drupal_render_children($element); + $output .= '</div>'; + return $output; +} + + /** * Implements _webform_submit_component(). */ @@ -406,7 +448,7 @@ function webform_file_allow_access($element) { /** * Implements _webform_display_component(). */ -function _webform_display_file($component, $value, $format = 'html') { +function _webform_display_file($component, $value, $format = 'html', $submission = array()) { $fid = isset($value[0]) ? $value[0] : NULL; return array( '#title' => $component['name'], diff --git a/profiles/wcm_base/modules/contrib/webform/components/grid.inc b/profiles/wcm_base/modules/contrib/webform/components/grid.inc index 3e5bab89..fbb88272 100644 --- a/profiles/wcm_base/modules/contrib/webform/components/grid.inc +++ b/profiles/wcm_base/modules/contrib/webform/components/grid.inc @@ -30,6 +30,7 @@ function _webform_defaults_grid() { 'custom_question_keys' => 0, 'sticky' => TRUE, 'description' => '', + 'description_above' => FALSE, 'private' => FALSE, 'analysis' => TRUE, ), @@ -104,7 +105,7 @@ function _webform_edit_grid($component) { '#type' => 'textarea', '#title' => t('Options'), '#default_value' => $component['extra']['options'], - '#description' => t('Options to select across the top. One option per line. <strong>Key-value pairs MUST be specified as "safe_key|Some readable option"</strong>. Use of only alphanumeric characters and underscores is recommended in keys.') . ' ' .theme('webform_token_help'), + '#description' => t('Options to select across the top. One option per line. <strong>Key-value pairs MUST be specified as "safe_key|Some readable option"</strong>. Use of only alphanumeric characters and underscores is recommended in keys.') . ' ' . theme('webform_token_help'), '#cols' => 60, '#rows' => 5, '#weight' => -3, @@ -264,7 +265,7 @@ function webform_expand_grid($element) { /** * Implements _webform_display_component(). */ -function _webform_display_grid($component, $value, $format = 'html') { +function _webform_display_grid($component, $value, $format = 'html', $submission = array()) { $node = node_load($component['nid']); $questions = _webform_select_options_from_text($component['extra']['questions'], TRUE); $questions = _webform_select_replace_tokens($questions, $node); @@ -529,9 +530,10 @@ function theme_webform_grid($variables) { // Render each radio button in the row. $radios = form_process_radios($question_element); foreach (element_children($radios) as $key) { - $radios[$key]['#title'] = $question_element['#title'] . ' - ' . $radios[$key]['#title']; + $radio_title = $radios[$key]['#title']; + $radios[$key]['#title'] = $question_element['#title'] . ' - ' . $radio_title; $radios[$key]['#title_display'] = 'invisible'; - $row[] = array('data' => drupal_render($radios[$key]), 'class' => array('checkbox', 'webform-grid-option')); + $row[] = array('data' => drupal_render($radios[$key]), 'class' => array('checkbox', 'webform-grid-option'), 'data-label' => array($radio_title)); } if ($right_titles) { $row[] = array('data' => isset($question_titles[1]) ? webform_filter_xss($question_titles[1]) : '', 'class' => array('webform-grid-question')); diff --git a/profiles/wcm_base/modules/contrib/webform/components/hidden.inc b/profiles/wcm_base/modules/contrib/webform/components/hidden.inc index 0740018f..2e9c07a3 100644 --- a/profiles/wcm_base/modules/contrib/webform/components/hidden.inc +++ b/profiles/wcm_base/modules/contrib/webform/components/hidden.inc @@ -103,7 +103,7 @@ function _webform_render_hidden($component, $value = NULL, $filter = TRUE, $subm /** * Implements _webform_display_component(). */ -function _webform_display_hidden($component, $value, $format = 'html') { +function _webform_display_hidden($component, $value, $format = 'html', $submission = array()) { $element = array( '#title' => $component['name'], '#markup' => isset($value[0]) ? $value[0] : NULL, @@ -170,6 +170,14 @@ function _webform_table_hidden($component, $value) { return check_plain(empty($value[0]) ? '' : $value[0]); } +/** + * Implements _webform_action_set_component(). + */ +function _webform_action_set_hidden($component, &$element, &$form_state, $value) { + $element['#value'] = $value; + form_set_value($element, $value, $form_state); +} + /** * Implements _webform_csv_headers_component(). */ diff --git a/profiles/wcm_base/modules/contrib/webform/components/markup.inc b/profiles/wcm_base/modules/contrib/webform/components/markup.inc index 6ceefb26..6d8b8cf1 100644 --- a/profiles/wcm_base/modules/contrib/webform/components/markup.inc +++ b/profiles/wcm_base/modules/contrib/webform/components/markup.inc @@ -44,7 +44,7 @@ function _webform_edit_markup($component) { '#type' => 'text_format', '#title' => t('Value'), '#default_value' => $component['value'], - '#description' => t('Markup allows you to enter custom HTML or PHP logic into your form.') . theme('webform_token_help'), + '#description' => t('Markup allows you to enter custom HTML into your form.') . ' ' . theme('webform_token_help', array('groups' => array('node', 'submission'))), '#weight' => -1, '#format' => $component['extra']['format'], '#element_validate' => array('_webform_edit_markup_validate'), @@ -80,33 +80,88 @@ function _webform_edit_markup_validate($form, &$form_state) { * Implements _webform_render_component(). */ function _webform_render_markup($component, $value = NULL, $filter = TRUE, $submission = NULL) { - $node = isset($component['nid']) ? node_load($component['nid']) : NULL; $element = array( '#type' => 'markup', '#title' => $filter ? NULL : $component['name'], '#weight' => $component['weight'], - '#markup' => $filter ? webform_replace_tokens($component['value'], $node, NULL, NULL, $component['extra']['format']) : $component['value'], + '#markup' => $component['value'], '#format' => $component['extra']['format'], '#theme_wrappers' => array('webform_element'), '#translatable' => array('title', 'markup'), '#access' => $component['extra']['display_on'] != 'display', + '#webform_nid' => isset($component['nid']) ? $component['nid'] : NULL, + '#webform_submission' => $submission, + '#webform_format' => $component['extra']['format'], ); + if ($filter) { + $element['#after_build'] = array('_webform_render_markup_after_build'); + } + return $element; } + /** + * Helper function to replace tokens in markup component. + * + * Required to have access to current form values from $form_state. + */ +function _webform_render_markup_after_build($form_element, &$form_state) { + global $user; + $node = node_load($form_element['#webform_nid']); + $submission = NULL; + + // Convert existing submission data to an in-progress submission. + $form_state_for_submission = $form_state; + $form_state_for_submission['values']['submitted'] = $form_state['#conditional_values']; + module_load_include('inc', 'webform', 'includes/webform.submissions'); + $submission = webform_submission_create($node, $user, $form_state_for_submission, TRUE, $form_element['#webform_submission']); + + // Replace tokens using the current or generated submission. + $value = webform_replace_tokens($form_element['#markup'], $node, $submission, NULL, $form_element['#webform_format']); + + // If the markup value has been set by a conditional, display that value. + $component = $form_element['#webform_component']; + if ($node) { + $sorter = webform_get_conditional_sorter($node); + // If the form was retrieved from the form cache, the conditionals may not + // have been executed yet. + if (!$sorter->isExecuted()) { + $sorter->executeConditionals(isset($submission) ? $submission->data : array()); + } + $conditional_value = $sorter->componentMarkup($component['cid'], $component['page_num']); + if (isset($conditional_value)) { + // Provide original value, should conditional logic no longer set the value. + $form_element['#wrapper_attributes']['data-webform-markup'] = $value; + if (is_string($conditional_value)) { + $value = check_markup($conditional_value, $component['extra']['format']); + } + } + } + + $form_element['#markup'] = $value; + return $form_element; +} + + /** * Implements _webform_display_component(). */ -function _webform_display_markup($component, $value, $format = 'html') { +function _webform_display_markup($component, $value, $format = 'html', $submission = array()) { $node = isset($component['nid']) ? node_load($component['nid']) : NULL; + $value = webform_replace_tokens($component['value'], $node, $submission, NULL, $component['extra']['format']); + + // If the markup value has been set by a conditional, display that value. + if ($node && is_string($conditional_value = webform_get_conditional_sorter($node)->componentMarkup($component['cid'], $component['page_num']))) { + $value = check_markup($conditional_value, $component['extra']['format']); + } return array( '#weight' => $component['weight'], '#theme' => 'webform_display_markup', '#format' => $format, - '#value' => webform_replace_tokens($component['value'], $node, NULL, NULL, $component['extra']['format']), + '#value' => $value, '#translatable' => array('title'), '#access' => $component['extra']['display_on'] != 'form', ); diff --git a/profiles/wcm_base/modules/contrib/webform/components/number.inc b/profiles/wcm_base/modules/contrib/webform/components/number.inc index 9fe0bb56..9d5830d7 100644 --- a/profiles/wcm_base/modules/contrib/webform/components/number.inc +++ b/profiles/wcm_base/modules/contrib/webform/components/number.inc @@ -24,6 +24,7 @@ function _webform_defaults_number() { 'unique' => 0, 'title_display' => 0, 'description' => '', + 'description_above' => FALSE, 'attributes' => array(), 'private' => FALSE, 'analysis' => FALSE, @@ -377,7 +378,7 @@ function _webform_render_number($component, $value = NULL, $filter = TRUE, $subm /** * Implements _webform_display_component(). */ -function _webform_display_number($component, $value, $format = 'html') { +function _webform_display_number($component, $value, $format = 'html', $submission = array()) { $empty = !isset($value[0]) || $value[0] === ''; return array( '#title' => $component['name'], @@ -477,7 +478,7 @@ function _webform_analysis_number($component, $sids = array(), $single = FALSE, if ($advanced_stats && $population_count && $sum != 0) { // Standard deviation. $stddev = 0; - foreach($population as $value) { + foreach ($population as $value) { // Obtain the total of squared variances. $stddev += pow(($value - $average), 2); } @@ -522,21 +523,21 @@ function _webform_analysis_number($component, $sids = array(), $single = FALSE, $stddev = _webform_number_format($component, $stddev); $low = _webform_number_format($component, $population[0]); $high = _webform_number_format($component, end($population)); - foreach($limit as $key => $value) { + foreach ($limit as $key => $value) { $limit[$key] = _webform_number_format($component, $value); } // Column headings (override potential theme uppercase, e.g. Seven in D7). $header = array( t('Normal Distribution'), - array('data' => '-4' . $sigma, 'style' => 'text-transform: lowercase;',), - array('data' => '-3' . $sigma, 'style' => 'text-transform: lowercase;',), - array('data' => '-2' . $sigma, 'style' => 'text-transform: lowercase;',), - array('data' => '-1' . $sigma, 'style' => 'text-transform: lowercase;',), - array('data' => '+1' . $sigma, 'style' => 'text-transform: lowercase;',), - array('data' => '+2' . $sigma, 'style' => 'text-transform: lowercase;',), - array('data' => '+3' . $sigma, 'style' => 'text-transform: lowercase;',), - array('data' => '+4' . $sigma, 'style' => 'text-transform: lowercase;',), + array('data' => '-4' . $sigma, 'style' => 'text-transform: lowercase;'), + array('data' => '-3' . $sigma, 'style' => 'text-transform: lowercase;'), + array('data' => '-2' . $sigma, 'style' => 'text-transform: lowercase;'), + array('data' => '-1' . $sigma, 'style' => 'text-transform: lowercase;'), + array('data' => '+1' . $sigma, 'style' => 'text-transform: lowercase;'), + array('data' => '+2' . $sigma, 'style' => 'text-transform: lowercase;'), + array('data' => '+3' . $sigma, 'style' => 'text-transform: lowercase;'), + array('data' => '+4' . $sigma, 'style' => 'text-transform: lowercase;'), ); // Insert row labels. @@ -564,6 +565,14 @@ function _webform_table_number($component, $value) { return isset($value[0]) ? _webform_number_format($component, $value[0]) : ''; } +/** + * Implements _webform_action_set_component(). + */ +function _webform_action_set_number($component, &$element, &$form_state, $value) { + $element['#value'] = $value; + form_set_value($element, $value, $form_state); +} + /** * Implements _webform_csv_headers_component(). */ diff --git a/profiles/wcm_base/modules/contrib/webform/components/pagebreak.inc b/profiles/wcm_base/modules/contrib/webform/components/pagebreak.inc index a02367d8..d97c74ff 100644 --- a/profiles/wcm_base/modules/contrib/webform/components/pagebreak.inc +++ b/profiles/wcm_base/modules/contrib/webform/components/pagebreak.inc @@ -78,7 +78,7 @@ function _webform_render_pagebreak($component, $value = NULL, $filter = TRUE, $s /** * Implements _webform_display_component(). */ -function _webform_display_pagebreak($component, $value = NULL, $format = 'html') { +function _webform_display_pagebreak($component, $value = NULL, $format = 'html', $submission = array()) { $element = array( '#theme' => 'webform_display_pagebreak', '#title' => $component['name'], diff --git a/profiles/wcm_base/modules/contrib/webform/components/select.inc b/profiles/wcm_base/modules/contrib/webform/components/select.inc index af6c69e9..942c1d4e 100644 --- a/profiles/wcm_base/modules/contrib/webform/components/select.inc +++ b/profiles/wcm_base/modules/contrib/webform/components/select.inc @@ -26,6 +26,7 @@ function _webform_defaults_select() { 'other_text' => t('Other...'), 'title_display' => 0, 'description' => '', + 'description_above' => FALSE, 'custom_keys' => FALSE, 'options_source' => '', 'private' => FALSE, @@ -328,11 +329,6 @@ function _webform_render_select($component, $value = NULL, $filter = TRUE, $subm '#translatable' => array('title', 'description', 'options'), ); - // Add HTML5 required attribute, if needed and possible (not working on multiple checkboxes). - if ($component['required'] && ($component['extra']['aslist'] || !$component['extra']['multiple'])) { - $element['#attributes']['required'] = 'required'; - } - // Convert the user-entered options list into an array. $default_value = $filter ? webform_replace_tokens($component['value'], $node) : $component['value']; $options = _webform_select_options($component, !$component['extra']['aslist'], $filter); @@ -345,11 +341,19 @@ function _webform_render_select($component, $value = NULL, $filter = TRUE, $subm _webform_shuffle_options($options); } + // Add HTML5 required attribute, if needed and possible (not working on more than one checkboxes). + if ($component['required'] && ($component['extra']['aslist'] || !$component['extra']['multiple'] || count($options) == 1)) { + $element['#attributes']['required'] = 'required'; + } + // Add default options if using a select list with no default. This trigger's // Drupal 7's adding of the option for us. See @form_process_select(). if ($component['extra']['aslist'] && !$component['extra']['multiple'] && $default_value === '') { $element['#empty_value'] = ''; - $element['#empty_option'] = $component['extra']['empty_option']; + if (strlen($component['extra']['empty_option'])) { + $element['#empty_option'] = $component['extra']['empty_option']; + $element['#translatable'][] = 'empty_option'; + } } // Set the component options. @@ -485,7 +489,7 @@ function webform_expand_select_ids($element) { /** * Implements _webform_display_component(). */ -function _webform_display_select($component, $value, $format = 'html') { +function _webform_display_select($component, $value, $format = 'html', $submission = array()) { // Sort values by numeric key. These may be in alphabetic order from the database query, // which is not numeric order for keys '10' and higher. $value = (array) $value; @@ -739,6 +743,24 @@ function _webform_table_select($component, $value) { return implode('<br />', $items); } +/** + * Implements _webform_action_set_component(). + */ +function _webform_action_set_select($component, &$element, &$form_state, $value) { + // Set the value as an array for multiple select or single value otherwise. + if ($element['#type'] == 'checkboxes') { + $checkbox_values = $element['#options']; + array_walk($checkbox_values, function(&$value, $key) use($value) { + $value = (int)(strval($key) === $value); + }); + } + else { + $value = $component['extra']['multiple'] ? array($value) : $value; + } + $element['#value'] = $value; + form_set_value($element, $value, $form_state); +} + /** * Implements _webform_csv_headers_component(). */ @@ -795,7 +817,9 @@ function _webform_csv_data_select($component, $export_options, $value) { if ($component['extra']['multiple']) { foreach ($options as $key => $item) { - $index = array_search($key, (array) $value); + // Strict search is needed to avoid a key of 0 from matching an empty + // value. + $index = array_search((string)$key, (array)$value, TRUE); if ($index !== FALSE) { if ($export_options['select_format'] == 'separate') { $return[] = 'X'; diff --git a/profiles/wcm_base/modules/contrib/webform/components/textarea.inc b/profiles/wcm_base/modules/contrib/webform/components/textarea.inc index 41d0f57d..98adbbbf 100644 --- a/profiles/wcm_base/modules/contrib/webform/components/textarea.inc +++ b/profiles/wcm_base/modules/contrib/webform/components/textarea.inc @@ -23,6 +23,7 @@ function _webform_defaults_textarea() { 'resizable' => 1, 'disabled' => 0, 'description' => '', + 'description_above' => FALSE, 'placeholder' => '', 'attributes' => array(), 'private' => FALSE, @@ -152,7 +153,7 @@ function _webform_render_textarea($component, $value = NULL, $filter = TRUE, $su /** * Implements _webform_display_component(). */ -function _webform_display_textarea($component, $value, $format = 'html') { +function _webform_display_textarea($component, $value, $format = 'html', $submission = array()) { return array( '#title' => $component['name'], '#title_display' => $component['extra']['title_display'] ? $component['extra']['title_display'] : 'before', @@ -225,6 +226,14 @@ function _webform_table_textarea($component, $value) { return empty($value[0]) ? '' : check_plain($value[0]); } +/** + * Implements _webform_action_set_component(). + */ +function _webform_action_set_textarea($component, &$element, &$form_state, $value) { + $element['#value'] = $value; + form_set_value($element, $value, $form_state); +} + /** * Implements _webform_csv_headers_component(). */ diff --git a/profiles/wcm_base/modules/contrib/webform/components/textfield.inc b/profiles/wcm_base/modules/contrib/webform/components/textfield.inc index 7ada5fce..b9693c9d 100644 --- a/profiles/wcm_base/modules/contrib/webform/components/textfield.inc +++ b/profiles/wcm_base/modules/contrib/webform/components/textfield.inc @@ -25,6 +25,7 @@ function _webform_defaults_textfield() { 'unique' => 0, 'title_display' => 0, 'description' => '', + 'description_above' => FALSE, 'placeholder' => '', 'attributes' => array(), 'private' => FALSE, @@ -189,7 +190,7 @@ function _webform_render_textfield($component, $value = NULL, $filter = TRUE, $s /** * Implements _webform_display_component(). */ -function _webform_display_textfield($component, $value, $format = 'html') { +function _webform_display_textfield($component, $value, $format = 'html', $submission = array()) { return array( '#title' => $component['name'], '#title_display' => $component['extra']['title_display'] ? $component['extra']['title_display'] : 'before', @@ -263,6 +264,14 @@ function _webform_table_textfield($component, $value) { return check_plain(empty($value[0]) ? '' : $value[0]); } +/** + * Implements _webform_action_set_component(). + */ +function _webform_action_set_textfield($component, &$element, &$form_state, $value) { + $element['#value'] = $value; + form_set_value($element, $value, $form_state); +} + /** * Implements _webform_csv_headers_component(). */ diff --git a/profiles/wcm_base/modules/contrib/webform/components/time.inc b/profiles/wcm_base/modules/contrib/webform/components/time.inc index 786dae56..59c8fd31 100644 --- a/profiles/wcm_base/modules/contrib/webform/components/time.inc +++ b/profiles/wcm_base/modules/contrib/webform/components/time.inc @@ -27,6 +27,7 @@ function _webform_defaults_time() { 'minuteincrements' => 1, 'title_display' => 0, 'description' => '', + 'description_above' => FALSE, 'private' => FALSE, 'analysis' => FALSE, ), @@ -365,7 +366,7 @@ function _webform_submit_time($component, $value) { /** * Implements _webform_display_component(). */ -function _webform_display_time($component, $value, $format = 'html') { +function _webform_display_time($component, $value, $format = 'html', $submission = array()) { $value = webform_date_array(isset($value[0]) ? $value[0] : '', 'time'); if ($component['extra']['hourformat'] == '12-hour') { $value = webform_time_convert($value, '12-hour'); diff --git a/profiles/wcm_base/modules/contrib/webform/css/webform-admin.css b/profiles/wcm_base/modules/contrib/webform/css/webform-admin.css index 520a86cf..297f5679 100644 --- a/profiles/wcm_base/modules/contrib/webform/css/webform-admin.css +++ b/profiles/wcm_base/modules/contrib/webform/css/webform-admin.css @@ -147,6 +147,7 @@ html.js div.webform-position { tr.webform-add-form .tabledrag-changed { display: none; } +#webform-emails tr.webform-add-form, #webform-components tr.webform-add-form { background-color: inherit; } diff --git a/profiles/wcm_base/modules/contrib/webform/includes/exporters/webform_exporter.inc b/profiles/wcm_base/modules/contrib/webform/includes/exporters/webform_exporter.inc index 3de1396f..de356308 100644 --- a/profiles/wcm_base/modules/contrib/webform/includes/exporters/webform_exporter.inc +++ b/profiles/wcm_base/modules/contrib/webform/includes/exporters/webform_exporter.inc @@ -1,7 +1,10 @@ <?php + /** + * @file * Base class defining the common methods available to exporters. */ + class webform_exporter { public $options = array(); public $export_wordrap; diff --git a/profiles/wcm_base/modules/contrib/webform/includes/exporters/webform_exporter_delimited.inc b/profiles/wcm_base/modules/contrib/webform/includes/exporters/webform_exporter_delimited.inc index 1f523a36..20872b7b 100644 --- a/profiles/wcm_base/modules/contrib/webform/includes/exporters/webform_exporter_delimited.inc +++ b/profiles/wcm_base/modules/contrib/webform/includes/exporters/webform_exporter_delimited.inc @@ -1,11 +1,16 @@ <?php + /** + * @file * Webform exporter for creating CSV/TSV delimited files. */ + class webform_exporter_delimited extends webform_exporter { + public $line_ending; public $delimiter; function __construct($options) { + $this->line_ending = webform_variable_get('webform_csv_line_ending'); $this->delimiter = isset($options['delimiter']) ? $options['delimiter'] : ','; // Convert tabs. if ($this->delimiter == '\t') { @@ -23,7 +28,7 @@ class webform_exporter_delimited extends webform_exporter { // Remove <script> tags, which mysteriously cause Excel not to import. $data[$key] = preg_replace('!<(/?script.*?)>!', '[$1]', $data[$key]); } - $row = implode($this->delimiter, $data) . "\n"; + $row = implode($this->delimiter, $data) . $this->line_ending; @fwrite($file_handle, $row); } diff --git a/profiles/wcm_base/modules/contrib/webform/includes/exporters/webform_exporter_excel_delimited.inc b/profiles/wcm_base/modules/contrib/webform/includes/exporters/webform_exporter_excel_delimited.inc index 963076a1..9f77b891 100644 --- a/profiles/wcm_base/modules/contrib/webform/includes/exporters/webform_exporter_excel_delimited.inc +++ b/profiles/wcm_base/modules/contrib/webform/includes/exporters/webform_exporter_excel_delimited.inc @@ -1,7 +1,10 @@ <?php + /** + * @file * The Excel exporter currently is just a tab-delimited export. */ + class webform_exporter_excel_delimited extends webform_exporter_delimited { function __construct($options) { $options['delimiter'] = '\t'; diff --git a/profiles/wcm_base/modules/contrib/webform/includes/exporters/webform_exporter_excel_xlsx.inc b/profiles/wcm_base/modules/contrib/webform/includes/exporters/webform_exporter_excel_xlsx.inc index dbdcaacc..072a0b3c 100644 --- a/profiles/wcm_base/modules/contrib/webform/includes/exporters/webform_exporter_excel_xlsx.inc +++ b/profiles/wcm_base/modules/contrib/webform/includes/exporters/webform_exporter_excel_xlsx.inc @@ -1,7 +1,10 @@ <?php + /** + * @file * This exporter creates an XLSX file readable by newer versions of Excel. */ + class webform_exporter_excel_xlsx extends webform_exporter { /** * Regular expression that checks for a valid ISO 8601 date/time. diff --git a/profiles/wcm_base/modules/contrib/webform/includes/webform.admin.inc b/profiles/wcm_base/modules/contrib/webform/includes/webform.admin.inc index 8306e87e..e3cf9577 100644 --- a/profiles/wcm_base/modules/contrib/webform/includes/webform.admin.inc +++ b/profiles/wcm_base/modules/contrib/webform/includes/webform.admin.inc @@ -241,7 +241,7 @@ function webform_admin_settings() { '#type' => 'textfield', '#title' => t("Select email mapping limit"), '#default_value' => webform_variable_get('webform_email_select_max'), - '#description' => t('When mapping emails addresses to a select component, limit the choice to components with less than the amount of options indicated. This is to avoid flooding the email settings form. '), + '#description' => t('When mapping emails addresses to a select component, limit the choice to components with less than the amount of options indicated. This is to avoid flooding the email settings form.'), ); $form = system_settings_form($form); diff --git a/profiles/wcm_base/modules/contrib/webform/includes/webform.components.inc b/profiles/wcm_base/modules/contrib/webform/includes/webform.components.inc index c2b9e733..cd0b47d1 100644 --- a/profiles/wcm_base/modules/contrib/webform/includes/webform.components.inc +++ b/profiles/wcm_base/modules/contrib/webform/includes/webform.components.inc @@ -386,7 +386,6 @@ function webform_component_edit_form($form, $form_state, $node, $component, $clo drupal_set_title(t('Edit component: @name', array('@name' => $component['name'])), PASS_THROUGH); $form['#node'] = $node; - $form['#attached']['library'][] = array('webform', 'admin'); $form['#tree'] = TRUE; // Print the correct field type specification. @@ -475,6 +474,17 @@ function webform_component_edit_form($form, $form_state, $node, $component, $clo $form['display']['title_display']['#parents'] = array('extra', 'title_display'); } + if (webform_component_feature($component['type'], 'description')) { + $form['display']['description_above'] = array( + '#type' => 'checkbox', + '#default_value' => !empty($component['extra']['description_above']), + '#title' => t('Description above field'), + '#description' => t('Place the description above — rather than below — the field.'), + '#weight' => 8.5, + '#parents' => array('extra', 'description_above'), + ); + } + if (webform_component_feature($component['type'], 'private')) { // May user mark fields as Private? $form['display']['private'] = array( @@ -590,6 +600,9 @@ function webform_component_edit_form($form, $form_state, $node, $component, $clo unset($form['validation']); } $form = array_merge($form, $additional_form_elements); + // Ensure that the webform admin library is attached, possibly in addition to + // component-specific attachments. + $form['#attached']['library'][] = array('webform', 'admin'); // Add the submit button. $form['actions'] = array( @@ -672,13 +685,12 @@ function webform_component_edit_form_submit($form, &$form_state) { // Since Webform components have been updated but the node itself has not // been saved, it is necessary to explicitly clear the cache to make sure - // the updated webform is visible to anonymous users. + // the updated webform is visible to anonymous users. This resets the page + // and block caches (only); cache_clear_all(); - // Clear the entity cache if Entity Cache module is installed. - if (module_exists('entitycache')) { - entity_get_controller('node')->resetCache(array($node->nid)); - } + // Refresh the entity cache, should it be cached in persistent storage. + entity_get_controller('node')->resetCache(array($node->nid)); $form_state['redirect'] = array('node/' . $node->nid . '/webform/components', isset($cid) ? array('query' => array('cid' => $cid)) : array()); } @@ -727,13 +739,12 @@ function webform_component_delete_form_submit($form, &$form_state) { // Since Webform components have been updated but the node itself has not // been saved, it is necessary to explicitly clear the cache to make sure - // the updated webform is visible to anonymous users. + // the updated webform is visible to anonymous users. This resets the page + // and block caches (only); cache_clear_all(); - // Clear the entity cache if Entity Cache module is installed. - if (module_exists('entitycache')) { - entity_get_controller('node')->resetCache(array($node->nid)); - } + // Refresh the entity cache, should it be cached in persistent storage. + entity_get_controller('node')->resetCache(array($node->nid)); $form_state['redirect'] = 'node/' . $node->nid . '/webform/components'; } diff --git a/profiles/wcm_base/modules/contrib/webform/includes/webform.conditionals.inc b/profiles/wcm_base/modules/contrib/webform/includes/webform.conditionals.inc index 7578a7bb..f6abf7f9 100644 --- a/profiles/wcm_base/modules/contrib/webform/includes/webform.conditionals.inc +++ b/profiles/wcm_base/modules/contrib/webform/includes/webform.conditionals.inc @@ -277,7 +277,7 @@ function theme_webform_conditional_groups($variables) { } $even_odd = ($index + 1) % 2 ? 'odd' : 'even'; $element[$key]['weight']['#attributes']['class'] = array('webform-conditional-weight'); - $data = '<div class="webform-conditional-new">' . $data . t('Add a new condition: ') . drupal_render($element[$key]['new']) . '</div>'; + $data = '<div class="webform-conditional-new">' . $data . t('Add a new condition:') . ' ' . drupal_render($element[$key]['new']) . '</div>'; $output .= '<tr class="webform-conditional-new-row ' . $even_odd . '">'; $output .= '<td>' . $data . '</td>'; $output .= '<td>' . drupal_render($element[$key]['weight']) . '</td>'; @@ -1426,19 +1426,11 @@ function webform_conditional_value_datetime($input_values) { * +N if $a is below (>) $b */ function webform_compare_select($a, $b, $options) { - $options_array = array_keys($options); + // Select keys that are integer-like strings are numberic indices in PHP. + // Convert the array keys to an array of strings. + $options_array = array_map(function($i) {return (string) $i; }, array_keys($options)); $a_position = array_search($a, $options_array, TRUE); $b_position = array_search($b, $options_array, TRUE); - if ($a_position === FALSE && $a_position === FALSE) { - return NULL; - } - elseif ($a_position === FALSE) { - return 1; - } - elseif ($b_position === FALSE) { - return -1; - } - else { - return $a_position - $b_position; - } + return ($a_position === FALSE || $a_position === FALSE) ? NULL : $a_position - $b_position; + } diff --git a/profiles/wcm_base/modules/contrib/webform/includes/webform.emails.inc b/profiles/wcm_base/modules/contrib/webform/includes/webform.emails.inc index 7f24e03e..b6c8d4f3 100644 --- a/profiles/wcm_base/modules/contrib/webform/includes/webform.emails.inc +++ b/profiles/wcm_base/modules/contrib/webform/includes/webform.emails.inc @@ -20,6 +20,10 @@ function webform_emails_form($form, $form_state, $node) { $form['components'] = array(); foreach ($node->webform['emails'] as $eid => $email) { + $form['emails'][$eid]['status'] = array( + '#type' => 'checkbox', + '#default_value' => $email['status'], + ); $form['emails'][$eid]['email'] = array( '#markup' => nl2br(check_plain(implode("\n", webform_format_email_address($email['email'], NULL, $node, NULL, FALSE, FALSE)))), ); @@ -36,6 +40,11 @@ function webform_emails_form($form, $form_state, $node) { '#tree' => FALSE, ); + $form['add']['status'] = array( + '#type' => 'checkbox', + '#default_value' => TRUE, + ); + $form['add']['email_option'] = array( '#type' => 'radios', '#options' => array( @@ -61,17 +70,24 @@ function webform_emails_form($form, $form_state, $node) { $form['add']['email_component']['#disabled'] = TRUE; } + $form['add']['add'] = array( + '#type' => 'submit', + '#value' => t('Add'), + '#validate' => array('webform_email_address_validate'), + '#submit' => array('webform_emails_form_submit'), + ); + $form['actions'] = array( '#type' => 'actions', '#weight' => 45, ); - $form['actions']['add_button'] = array( + $form['actions']['save'] = array( '#type' => 'submit', - '#value' => t('Add'), + '#value' => t('Save'), + '#submit' => array('webform_emails_form_status_save'), + '#access' => count($node->webform['emails']) > 0, ); - $form['#validate'] = array('webform_email_address_validate'); - return $form; } @@ -87,13 +103,14 @@ function theme_webform_emails_form($variables) { $form = $variables['form']; $node = $form['#node']; - $header = array(t('E-mail to'), t('Subject'), t('From'), array('data' => t('Operations'), 'colspan' => 3)); + $header = array(t('Send'), t('E-mail to'), t('Subject'), t('From'), array('data' => t('Operations'), 'colspan' => 3)); $rows = array(); if (!empty($form['emails'])) { foreach (element_children($form['emails']) as $eid) { // Add each component to a table row. $rows[] = array( + array('data' => drupal_render($form['emails'][$eid]['status']), 'class' => array('webform-email-status', 'checkbox')), drupal_render($form['emails'][$eid]['email']), drupal_render($form['emails'][$eid]['subject']), drupal_render($form['emails'][$eid]['from']), @@ -104,13 +121,15 @@ function theme_webform_emails_form($variables) { } } else { - $rows[] = array(array('data' => t('Currently not sending e-mails, add an e-mail recipient below.'), 'colspan' => 6)); + $rows[] = array(array('data' => t('Currently not sending e-mails, add an e-mail recipient below.'), 'colspan' => 7)); } // Add a row containing form elements for a new item. + $add_button = drupal_render($form['add']['add']); $row_data = array( + array('data' => drupal_render($form['add']['status']), 'class' => array('webform-email-status', 'checkbox')), array('colspan' => 3, 'data' => drupal_render($form['add'])), - array('colspan' => 3, 'data' => drupal_render($form['add_button'])), + array('colspan' => 3, 'data' => $add_button), ); $rows[] = array('data' => $row_data, 'class' => array('webform-add-form')); @@ -152,7 +171,21 @@ function webform_emails_form_submit($form, &$form_state) { else { $email = $form_state['values']['email_component']; } - $form_state['redirect'] = array('node/' . $form['#node']->nid . '/webform/emails/new', array('query' => array('option' => $form_state['values']['email_option'], 'email' => trim($email)))); + $form_state['redirect'] = array('node/' . $form['#node']->nid . '/webform/emails/new', array('query' => array('status' => $form_state['values']['status'], 'option' => $form_state['values']['email_option'], 'email' => trim($email)))); +} + +/** + * Submit handler for status update. + */ +function webform_emails_form_status_save($form, &$form_state) { + foreach ($form_state['values']['emails'] as $eid => $status) { + db_update('webform_emails')->fields(array( + 'status' => $status['status'] + )) + ->condition('eid', $eid) + ->condition('nid', $form['#node']->nid) + ->execute(); + } } /** @@ -280,6 +313,14 @@ function webform_email_edit_form($form, $form_state, $node, $email = array(), $c $form['from_name_component']['#access'] = FALSE; } + // Add the checkbox to disable the email for current recipients. + $form['status'] = array( + '#title' => t('Enable sending'), + '#description' => t('Uncheck to disable sending this email.'), + '#type' => 'checkbox', + '#default_value' => $email['status'], + ); + // Add the template fieldset. $form['template'] = array( '#type' => 'fieldset', @@ -392,6 +433,7 @@ function theme_webform_email_edit_form($variables) { $details .= drupal_render($form['from_address_option']); $details .= drupal_render($form['from_address_mapping']); $details .= drupal_render($form['from_name_option']); + $form['details'] = array( '#type' => 'fieldset', '#title' => t('E-mail header details'), @@ -436,10 +478,10 @@ function theme_webform_email_component_mapping($variables) { $table = theme('table', array('header' => $header, 'rows' => $rows, 'sticky' => FALSE, 'empty' => $empty)); $description = t('The selected component %name has multiple options. You may enter an e-mail address for each choice.', array('%name' => $element['#title'])); if ($element['#webform_allow_empty']) { - $description .= ' '. t('When that choice is selected, an e-mail will be sent to the corresponding address. If a field is left blank, no e-mail will be sent for that option.'); + $description .= ' ' . t('When that choice is selected, an e-mail will be sent to the corresponding address. If a field is left blank, no e-mail will be sent for that option.'); } else { - $description .= ' '. t('When that choice is selected, an e-mail will be sent from the corresponding address.'); + $description .= ' ' . t('When that choice is selected, an e-mail will be sent from the corresponding address.'); } $wrapper_element = array( @@ -458,7 +500,7 @@ function theme_webform_email_component_mapping($variables) { */ function webform_email_address_validate($form, &$form_state) { if ($form_state['values']['email_option'] == 'custom') { - webform_email_validate($form_state['values']['email_custom'], 'email_custom', FALSE, TRUE); + webform_email_validate($form_state['values']['email_custom'], 'email_custom', FALSE, TRUE, TRUE); } } @@ -467,7 +509,7 @@ function webform_email_address_validate($form, &$form_state) { */ function webform_email_edit_form_validate($form, &$form_state) { if ($form_state['values']['from_address_option'] == 'custom') { - webform_email_validate($form_state['values']['from_address_custom'], 'from_address_custom', FALSE, FALSE); + webform_email_validate($form_state['values']['from_address_custom'], 'from_address_custom', FALSE, FALSE, TRUE); } // Validate component-based values for the TO and FROM address. @@ -478,7 +520,7 @@ function webform_email_edit_form_validate($form, &$form_state) { $empty_allowed = $field_name === 'email'; $multiple_allowed = $field_name === 'email'; foreach ($form_state['values'][$field_name . '_mapping'][$cid] as $key => &$value) { - webform_email_validate($value, "{$field_name}_mapping][$cid][$key", $empty_allowed, $multiple_allowed); + webform_email_validate($value, "{$field_name}_mapping][$cid][$key", $empty_allowed, $multiple_allowed, TRUE); } } } @@ -514,7 +556,7 @@ function webform_email_edit_form_submit($form, &$form_state) { // Merge the email mapping(s) into single value(s) $cid = $form_state['values'][$field . '_' . $option]; if (is_numeric($cid) && isset($form_state['values'][$field . '_mapping'][$cid])) { - $email['extra'][$field .'_mapping'] = $form_state['values'][$field . '_mapping'][$cid]; + $email['extra'][$field . '_mapping'] = $form_state['values'][$field . '_mapping'][$cid]; } } } @@ -545,6 +587,8 @@ function webform_email_edit_form_submit($form, &$form_state) { $email['exclude_empty'] = empty($form_state['values']['exclude_empty']) ? 0 : 1; + $email['status'] = empty($form_state['values']['status']) ? 0 : 1; + if ($form_state['values']['clone']) { drupal_set_message(t('Email settings cloned.')); $form_state['values']['eid'] = webform_email_clone($email); @@ -558,10 +602,8 @@ function webform_email_edit_form_submit($form, &$form_state) { webform_email_update($email); } - // Clear the entity cache if Entity Cache module is installed. - if (module_exists('entitycache')) { - entity_get_controller('node')->resetCache(array($node->nid)); - } + // Refresh the entity cache, should it be cached in persistent storage. + entity_get_controller('node')->resetCache(array($node->nid)); $form_state['redirect'] = array('node/' . $node->nid . '/webform/emails'); } @@ -606,10 +648,8 @@ function webform_email_delete_form_submit($form, &$form_state) { unset($node->webform['emails'][$email['eid']]); webform_check_record($node); - // Clear the entity cache if Entity Cache module is installed. - if (module_exists('entitycache')) { - entity_get_controller('node')->resetCache(array($node->nid)); - } + // Refresh the entity cache, should it be cached in persistent storage. + entity_get_controller('node')->resetCache(array($node->nid)); $form_state['redirect'] = 'node/' . $node->nid . '/webform/emails'; } @@ -631,11 +671,12 @@ function webform_email_load($eid, $nid) { 'html' => webform_variable_get('webform_default_format'), 'attachments' => 0, 'extra' => '', + 'status' => 1, ); } else { $email = isset($node->webform['emails'][$eid]) ? $node->webform['emails'][$eid] : FALSE; - if (webform_variable_get('webform_format_override')) { + if ($email && webform_variable_get('webform_format_override')) { $email['html'] = webform_variable_get('webform_default_format'); } } diff --git a/profiles/wcm_base/modules/contrib/webform/includes/webform.pages.inc b/profiles/wcm_base/modules/contrib/webform/includes/webform.pages.inc index a4c35ec3..782dfb94 100644 --- a/profiles/wcm_base/modules/contrib/webform/includes/webform.pages.inc +++ b/profiles/wcm_base/modules/contrib/webform/includes/webform.pages.inc @@ -14,6 +14,11 @@ function webform_configure_form($form, &$form_state, $node) { $form['#attached']['library'][] = array('webform', 'admin'); + // Refresh the entity cache to get the lastest submission number. + $nid = $node->nid; + entity_get_controller('node')->resetCache(array($nid)); + $node = node_load($nid); + $form['#node'] = $node; $form['#submit'] = array( @@ -299,7 +304,7 @@ function webform_configure_form($form, &$form_state, $node) { '#title' => t('Preview message'), '#default_value' => $node->webform['preview_message'], '#format' => $node->webform['preview_message_format'], - '#description' => t('A message to be displayed on the preview page. If left blank, the message "!default" will be used. Supports Webform token replacements.', array('!default' => $preview_default_message)) . ' ' . theme('webform_token_help', array('groups' => array('node', 'submission'))), + '#description' => t('A message to be displayed on the preview page. If left blank, the message "!default" will be used. Supports Webform token replacements.', array('!default' => $preview_default_message)) . ' ' . theme('webform_token_help', array('groups' => array('node', 'submission'))), ); $form['preview']['settings']['preview_components'] = array( '#type' => 'select', @@ -414,14 +419,6 @@ function webform_configure_form_validate($form, &$form_state) { } } - // Serial number must be a positive integer greater than any existing serial number. - $next_min = _webform_submission_serial_next_value_used($form['#node']->nid); - if ((int) $form['advanced']['next_serial']['#value'] < $next_min) { - form_error($form['advanced']['next_serial'], - t('The next submission number must be at least %min (greater than any existing serial number).', - array('%min' => $next_min))); - } - // Prohibit the combination of confidential + per-user limit + ip-only // submission tracking for anonymous users as it would not be enforceable. if (webform_variable_get('webform_tracking_mode') == 'ip_address' && @@ -517,8 +514,17 @@ function webform_configure_form_submit($form, &$form_state) { // Set submit button text. $node->webform['submit_text'] = $form_state['values']['submit_text']; - // Set next serial number. - $node->webform['next_serial'] = (int) $form_state['values']['next_serial']; + // Set next serial number. It must be a positive integer greater than any + // existing serial number, which could have increased while this form was + // being edited. + $next_min = _webform_submission_serial_next_value_used($node->nid); + $next_serial = (int)$form_state['values']['next_serial']; + if ($next_serial < $next_min) { + drupal_set_message(t('The next submission number was increased to @min to make it higher than existing submissions.', + array('@min' => $next_min))); + $next_serial = $next_min; + } + $node->webform['next_serial'] = $next_serial; } /** diff --git a/profiles/wcm_base/modules/contrib/webform/includes/webform.report.inc b/profiles/wcm_base/modules/contrib/webform/includes/webform.report.inc index d2c81110..71fb469a 100644 --- a/profiles/wcm_base/modules/contrib/webform/includes/webform.report.inc +++ b/profiles/wcm_base/modules/contrib/webform/includes/webform.report.inc @@ -351,9 +351,8 @@ function webform_results_clear_form_submit($form, &$form_state) { webform_results_clear($form_state['values']['nid']); $node = node_load($form_state['values']['nid']); $title = $node->title; - $message = t('Webform %title entries cleared.', array('%title' => $title)); - drupal_set_message($message); - watchdog('webform', $message); + drupal_set_message(t('Webform %title entries cleared.', array('%title' => $title))); + watchdog('webform', 'Webform %title entries cleared.', array('%title' => $title)); $form_state['redirect'] = 'node/' . $form_state['values']['nid'] . '/webform-results'; } @@ -635,7 +634,7 @@ function webform_results_download_range_validate($element, $form_state) { 'batch_size' => 1, 'batch_number' => 0, ); - if (!webform_download_sids_count($form_state['values']['node']->nid, $range_options)) { + if (!form_get_errors() && !webform_download_sids_count($form_state['values']['node']->nid, $range_options)) { form_error($element['range_type'], t('The specified range will not return any results.')); } @@ -854,8 +853,10 @@ function webform_results_export($node, $format = 'delimited', $options = array() $col_count = count($row) > $col_count ? count($row) : $col_count; } - // Write data from submissions. - $rows = webform_results_download_rows($node, $options); + // Write data from submissions. $last_is is non_NULL to trigger returning it + // by reference. + $last_sid = TRUE; + $rows = webform_results_download_rows($node, $options, 0, $last_sid); foreach ($rows as $row) { $exporter->add_row($handle, $row, $row_count); $row_count++; @@ -872,7 +873,7 @@ function webform_results_export($node, $format = 'delimited', $options = array() $export_info['file_name'] = $file_name; $export_info['row_count'] = $row_count; $export_info['col_count'] = $col_count; - $export_info['last_sid'] = end($submissions) ? key($submissions) : NULL; + $export_info['last_sid'] = $last_sid; $export_info['exporter'] = $exporter; return $export_info; @@ -983,16 +984,18 @@ function webform_results_download_headers($node, $options) { * through from the GUI interface. * @param $serial_start * The starting position for the Serial column in the output. + * @param $last_sid + * If set to a non-NULL value, the last sid will be returned. * * @return $rows * An array of rows built according to the provided $serial_start and * $pager_count variables. Note that the current page number is determined * by the super-global $_GET['page'] variable. */ -function webform_results_download_rows($node, $options, $serial_start = 0) { +function webform_results_download_rows($node, $options, $serial_start = 0, $last_sid = NULL) { // Get all the required submissions for the download. $filters['nid'] = $node->nid; - if (isset($options['sids'])){ + if (isset($options['sids'])) { $filters['sid'] = $options['sids']; } elseif (!empty($options['completion_type']) && $options['completion_type'] !== 'all') { @@ -1001,6 +1004,10 @@ function webform_results_download_rows($node, $options, $serial_start = 0) { $submissions = webform_get_submissions($filters, NULL); + if (isset($last_sid)) { + $last_sid = end($submissions) ? key($submissions) : NULL; + } + return webform_results_download_rows_process($node, $options, $serial_start, $submissions); } @@ -1796,7 +1803,7 @@ function webform_download_sids_query($nid, $range_options, $uid = NULL) { case 'range': // Submissions Start-End. $query->condition('ws.sid', $range_options['start'], '>='); - if ($range_options['end']){ + if ($range_options['end']) { $query->condition('ws.sid', $range_options['end'], '<='); } $query->orderBy('ws.sid', 'ASC'); @@ -1804,7 +1811,7 @@ function webform_download_sids_query($nid, $range_options, $uid = NULL) { case 'range_serial': // Submissions Start-End, using serial numbers. $query->condition('ws.serial', $range_options['start'], '>='); - if ($range_options['end']){ + if ($range_options['end']) { $query->condition('ws.serial', $range_options['end'], '<='); } $query->orderBy('ws.serial', 'ASC'); @@ -1894,5 +1901,5 @@ function webform_download_latest_start_sid($nid, $latest_count, $completion_type } $latest_sids = $query->execute()->fetchCol(); - return min($latest_sids); + return $latest_sids ? min($latest_sids) : 1; } diff --git a/profiles/wcm_base/modules/contrib/webform/includes/webform.submissions.inc b/profiles/wcm_base/modules/contrib/webform/includes/webform.submissions.inc index 3c0fb59c..3d2fcee6 100644 --- a/profiles/wcm_base/modules/contrib/webform/includes/webform.submissions.inc +++ b/profiles/wcm_base/modules/contrib/webform/includes/webform.submissions.inc @@ -232,6 +232,10 @@ function webform_submission_send_mail($node, $submission, $emails = NULL) { // Create a themed message for mailing. $send_count = 0; foreach ($emails as $eid => $email) { + // Continue with next email recipients array if disabled for current. + if (!$email['status']) + continue; + // Set the HTML property based on availablity of MIME Mail. $email['html'] = ($email['html'] && webform_variable_get('webform_email_html_capable')); @@ -316,7 +320,8 @@ function webform_submission_send_mail($node, $submission, $emails = NULL) { // Verify that this submission is not attempting to send any spam hacks. foreach ($addresses_final as $address) { if (_webform_submission_spam_check($address, $email['subject'], $email['from'], $email['headers'])) { - watchdog('webform', 'Possible spam attempt from @remote_addr' . "<br />\n" . nl2br(htmlentities($email['message'])), array('@remote_add' => ip_address())); + watchdog('webform', 'Possible spam attempt from @remote !message', + array('@remote' => ip_address(), '!message'=> "<br />\n" . nl2br(htmlentities($email['message'])))); drupal_set_message(t('Illegal information. Data not submitted.'), 'error'); return FALSE; } @@ -519,8 +524,8 @@ function webform_submission_resend($form, $form_state, $node, $submission) { $form['resend'][$eid] = array( '#type' => 'checkbox', - '#default_value' => $valid_email ? TRUE : FALSE, - '#disabled' => $valid_email ? FALSE : TRUE, + '#default_value' => $valid_email && $email['status'], + '#disabled' => !$valid_email, ); $form['emails'][$eid]['email'] = array( '#markup' => nl2br(check_plain(implode("\n", $addresses))), @@ -591,7 +596,7 @@ function webform_submission_resend_submit($form, &$form_state) { function theme_webform_submission_resend($variables) { $form = $variables['form']; - $header = array('', t('E-mail to'), t('Subject')); + $header = array(t('Send'), t('E-mail to'), t('Subject')); $rows = array(); if (!empty($form['emails'])) { foreach (element_children($form['emails']) as $eid) { @@ -638,7 +643,7 @@ function webform_submission_render($node, $submission, $email, $format, $exclude unset($components[$cid]); } if ($email && $email['exclude_empty']) { - foreach($submission->data as $cid => $data) { + foreach ($submission->data as $cid => $data) { if (!isset($data[0]) || $data[0] == '') { unset($components[$cid]); } diff --git a/profiles/wcm_base/modules/contrib/webform/includes/webform.webformconditionals.inc b/profiles/wcm_base/modules/contrib/webform/includes/webform.webformconditionals.inc index d406838c..92d5dca6 100644 --- a/profiles/wcm_base/modules/contrib/webform/includes/webform.webformconditionals.inc +++ b/profiles/wcm_base/modules/contrib/webform/includes/webform.webformconditionals.inc @@ -28,6 +28,8 @@ class WebformConditionals { protected $childrenMap; protected $visibilityMap; protected $requiredMap; + protected $setMap; + protected $markupMap; public $errors; @@ -64,7 +66,7 @@ class WebformConditionals { $conditionals = $this->node->webform['conditionals']; $errors = array(); - // Generate a component to condional map for conditional targets. + // Generate a component to conditional map for conditional targets. $cid_to_target_rgid = array(); $cid_hidden = array(); foreach ($conditionals as $rgid => $conditional) { @@ -315,14 +317,19 @@ class WebformConditionals { array_walk_recursive($this->visibilityMap, function (&$status) { $status = WebformConditionals::componentShown; }); - // Create an empty required map + // Create empty required, set, and markup maps. $this->requiredMap = array_fill(1, count($this->pageMap), array()); + $this->setMap = $this->requiredMap; + $this->markupMap = $this->requiredMap; - } else { + } + else { array_walk($this->visibilityMap[$page_num], function (&$status) { $status = WebformConditionals::componentShown; }); $this->requiredMap[$page_num] = array(); + $this->setMap[$page_num] = array(); + $this->markupMap[$page_num] = array(); } module_load_include('inc', 'webform', 'includes/webform.conditionals'); @@ -370,6 +377,10 @@ class WebformConditionals { // Perform the comparison callback and build the results for this group. $comparison_callback = $operator_info[$rule['operator']]['comparison callback']; + // Contrib caching, such as entitycache, may have loaded the node + // without building it. It is possible that the component include file + // hasn't been included yet. See #2529246. + webform_component_include($source_component['type']); $conditional_results[] = $comparison_callback($source_values, $rule['value'], $source_component); // Record page number to later determine any intra-page dependency on this source. @@ -398,8 +409,19 @@ class WebformConditionals { $this->requiredMap[$page_num][$target] = $action_result; break; case 'set': - if ($action_result && empty($targetLocked[$target]) && $components[$target]['type'] != 'markup') { - $input_values[$target] = $action['argument']; + if ($components[$target]['type'] == 'markup') { + $this->markupMap[$page_num][$target] = FALSE; + } + if ($action_result && empty($targetLocked[$target])) { + if ($components[$target]['type'] == 'markup') { + $this->markupMap[$page_num][$target] = $action['argument']; + } + else { + $input_values[$target] = isset($input_values[$target]) && is_array($input_values[$target]) + ? array($action['argument']) + : $action['argument']; + $this->setMap[$page_num][$target] = TRUE; + } } break; } @@ -412,6 +434,13 @@ class WebformConditionals { return $input_values; } + /** + * Returns whether the conditionals have been executed yet. + */ + function isExecuted() { + return (boolean)($this->visibilityMap); + } + /** * Returns whether a given component is always hidden, always shown, or might * be shown depending upon other sources on the same page. @@ -429,7 +458,7 @@ class WebformConditionals { if (!$this->visibilityMap) { // The conditionals have not yet been executed on a submission. $this->executeConditionals(array(), 0); - watchdog('webform', 'WebformConditionals::componentVisibility called prior to evaluating a submission.', 'error'); + watchdog('webform', 'WebformConditionals::componentVisibility called prior to evaluating a submission.', array(), WATCHDOG_ERROR); } return isset($this->visibilityMap[$page_num][$cid]) ? $this->visibilityMap[$page_num][$cid] : self::componentShown; } @@ -449,7 +478,8 @@ class WebformConditionals { $result = self::componentHidden; if ($page_num == 1 || empty($this->visibilityMap[$page_num])) { $result = self::componentShown; - } elseif (($page_map = $this->pageMap[$page_num]) && $this->componentVisibility(reset($page_map), $page_num)) { + } + elseif (($page_map = $this->pageMap[$page_num]) && $this->componentVisibility(reset($page_map), $page_num)) { while ($cid = next($page_map)) { if ($this->componentVisibility($cid, $page_num) != self::componentHidden) { $result = self::componentShown; @@ -461,13 +491,13 @@ class WebformConditionals { } /** - * Returns whether a given component is always required, always opption, or + * Returns whether a given component is always required, always optional, or * unchanged by conditional logic. * * Assumes that the conditionals have already been executed on the given page. * * @param integer $cid - * The component id of the component whose required state is being sought + * The component id of the component whose required state is being sought. * @param integer $page_num * The page number that the component is on. * @return boolean @@ -477,9 +507,51 @@ class WebformConditionals { if (!$this->requiredMap) { // The conditionals have not yet been executed on a submission. $this->executeConditionals(array(), 0); - watchdog('webform', 'WebformConditionals::componentRequired called prior to evaluating a submission.', 'error'); + watchdog('webform', 'WebformConditionals::componentRequired called prior to evaluating a submission.', array(), WATCHDOG_ERROR); } return isset($this->requiredMap[$page_num][$cid]) ? $this->requiredMap[$page_num][$cid] : NULL; } + /** + * Returns whether a given component has been set by conditional logic. + * + * Assumes that the conditionals have already been executed on the given page. + * + * @param integer $cid + * The component id of the component whose set state is being sought. + * @param integer $page_num + * The page number that the component is on. + * @return boolean + * Whether the component was set based on conditionals. + */ + function componentSet($cid, $page_num) { + if (!$this->setMap) { + // The conditionals have not yet been executed on a submission. + $this->executeConditionals(array(), 0); + watchdog('webform', 'WebformConditionals::componentSet called prior to evaluating a submission.', array(), WATCHDOG_ERROR); + } + return isset($this->setMap[$page_num][$cid]) ? $this->setMap[$page_num][$cid] : NULL; + } + + /** + * Returns the calculated markup as set by conditional logic. + * + * Assumes that the conditionals have already been executed on the given page. + * + * @param integer $cid + * The component id of the component whose set state is being sought. + * @param integer $page_num + * The page number that the component is on. + * @return string + * The conditional markup, or NULL if none. + */ + function componentMarkup($cid, $page_num) { + if (!$this->markupMap) { + // The conditionals have not yet been executed on a submission. + $this->executeConditionals(array(), 0); + watchdog('webform', 'WebformConditionals::componentMarkup called prior to evaluating a submission.', array(), WATCHDOG_ERROR); + } + return isset($this->markupMap[$page_num][$cid]) ? $this->markupMap[$page_num][$cid] : NULL; + } + } diff --git a/profiles/wcm_base/modules/contrib/webform/js/node-type-form.js b/profiles/wcm_base/modules/contrib/webform/js/node-type-form.js index 9310bb7e..d2547e17 100644 --- a/profiles/wcm_base/modules/contrib/webform/js/node-type-form.js +++ b/profiles/wcm_base/modules/contrib/webform/js/node-type-form.js @@ -1,10 +1,18 @@ +/** + * @file + * Enhancements for webform node type forms. + */ + (function ($) { + + "use strict"; + Drupal.behaviors.webformContentTypes = { attach: function (context) { // Provide the vertical tab summaries. - $('fieldset#edit-webform', context).drupalSetSummary(function(context) { + $('fieldset#edit-webform', context).drupalSetSummary(function (context) { var vals = []; - $('input[type=checkbox]', context).each(function() { + $('input[type=checkbox]', context).each(function () { if (this.checked && this.attributes['data-enabled-description']) { vals.push(this.attributes['data-enabled-description'].value); } diff --git a/profiles/wcm_base/modules/contrib/webform/js/select-admin.js b/profiles/wcm_base/modules/contrib/webform/js/select-admin.js index f6cfcdd4..919b5d17 100644 --- a/profiles/wcm_base/modules/contrib/webform/js/select-admin.js +++ b/profiles/wcm_base/modules/contrib/webform/js/select-admin.js @@ -1,4 +1,3 @@ - /** * @file * Enhancements for select list configuration options. @@ -6,53 +5,63 @@ (function ($) { -Drupal.behaviors.webformSelectLoadOptions = {}; -Drupal.behaviors.webformSelectLoadOptions.attach = function(context) { - settings = Drupal.settings; + "use strict"; + + Drupal.behaviors.webformSelectLoadOptions = {}; + Drupal.behaviors.webformSelectLoadOptions.attach = function (context) { - $('#edit-extra-options-source', context).change(function() { - var url = settings.webform.selectOptionsUrl + '/' + this.value; - $.ajax({ - url: url, - success: Drupal.webform.selectOptionsLoad, - dataType: 'json' + $('#edit-extra-options-source', context).change(function () { + var url = Drupal.settings.webform.selectOptionsUrl + '/' + this.value; + $.ajax({ + url: url, + success: Drupal.webform.selectOptionsLoad, + dataType: 'json' + }); }); - }); -} - -Drupal.webform = Drupal.webform || {}; - -Drupal.webform.selectOptionsOriginal = false; -Drupal.webform.selectOptionsLoad = function(result) { - if (Drupal.optionsElement) { - if (result.options) { - // Save the current select options the first time a new list is chosen. - if (Drupal.webform.selectOptionsOriginal === false) { - Drupal.webform.selectOptionsOriginal = $(Drupal.optionElements[result.elementId].manualOptionsElement).val(); + }; + + Drupal.webform = Drupal.webform || {}; + + Drupal.webform.selectOptionsOriginal = false; + Drupal.webform.selectOptionsLoad = function (result) { + if (Drupal.optionsElement) { + if (result.options) { + // Save the current select options the first time a new list is chosen. + if (Drupal.webform.selectOptionsOriginal === false) { + Drupal.webform.selectOptionsOriginal = $(Drupal.optionElements[result.elementId].manualOptionsElement).val(); + } + $(Drupal.optionElements[result.elementId].manualOptionsElement).val(result.options); + Drupal.optionElements[result.elementId].disable(); + Drupal.optionElements[result.elementId].updateWidgetElements(); + } + else { + Drupal.optionElements[result.elementId].enable(); + if (Drupal.webform.selectOptionsOriginal) { + $(Drupal.optionElements[result.elementId].manualOptionsElement).val(Drupal.webform.selectOptionsOriginal); + Drupal.optionElements[result.elementId].updateWidgetElements(); + Drupal.webform.selectOptionsOriginal = false; + } } - $(Drupal.optionElements[result.elementId].manualOptionsElement).val(result.options); - Drupal.optionElements[result.elementId].disable(); - Drupal.optionElements[result.elementId].updateWidgetElements(); } else { - Drupal.optionElements[result.elementId].enable(); - if (Drupal.webform.selectOptionsOriginal) { - $(Drupal.optionElements[result.elementId].manualOptionsElement).val(Drupal.webform.selectOptionsOriginal); - Drupal.optionElements[result.elementId].updateWidgetElements(); - Drupal.webform.selectOptionsOriginal = false; + var $element = $('#' + result.elementId); + $element.webformProp('readonly', result.options); + if (result.options) { + $element.val(result.options); } } } - else { - var $element = $('#' + result.elementId); - if (result.options) { - $element.val(result.options); - $.fn.prop ? $element.prop('readonly', true) : $element.attr('readonly', 'readonly'); + + /** + * Make a prop shim for jQuery < 1.9. + */ + $.fn.webformProp = $.fn.webformProp || function (name, value) { + if (value) { + return $.fn.prop ? this.prop(name, true) : this.attr(name, true); } else { - $.fn.prop ? $element.prop('readonly', false) : $element.removeAttr('readonly'); + return $.fn.prop ? this.prop(name, false) : this.removeAttr(name); } - } -} + }; })(jQuery); diff --git a/profiles/wcm_base/modules/contrib/webform/js/webform-admin.js b/profiles/wcm_base/modules/contrib/webform/js/webform-admin.js index 2050649a..3fd774c8 100644 --- a/profiles/wcm_base/modules/contrib/webform/js/webform-admin.js +++ b/profiles/wcm_base/modules/contrib/webform/js/webform-admin.js @@ -1,345 +1,354 @@ -(function ($) { - /** + * @file * Webform node form interface enhancments. */ -Drupal.behaviors.webformAdmin = {}; -Drupal.behaviors.webformAdmin.attach = function(context) { - // On click or change, make a parent radio button selected. - Drupal.webform.setActive(context); - Drupal.webform.updateTemplate(context); - // Update the template select list upon changing a template. - // Select all link for file extensions. - Drupal.webform.selectCheckboxesLink(context); - // Enhance the normal tableselect.js file to support indentations. - Drupal.webform.tableSelectIndentation(context); - // Automatically download exports if available. - Drupal.webform.downloadExport(context); - // Enhancements for the conditionals administrative page. - Drupal.webform.conditionalAdmin(context); - // Trigger radio/checkbox change when label click automatically selected by - // browser. - Drupal.webform.radioLabelAutoClick(context); -} - -Drupal.webform = Drupal.webform || {}; - -Drupal.webform.setActive = function(context) { - var setActiveOnChange = function(e) { - if ($(this).val()) { - var $checkbox = $(this).closest('.form-type-radio').find('input[type=radio]'); - $.fn.prop ? $checkbox.prop('checked', true) : $checkbox.attr('checked', true); - } - e.preventDefault(); - }; - var setActiveOnClick = function(e) { - var $checkbox = $(this).closest('.form-type-radio').find('input[type=radio]'); - $.fn.prop ? $checkbox.prop('checked', true) : $checkbox.attr('checked', true); +(function ($) { + + "use strict"; + + Drupal.behaviors.webformAdmin = {}; + Drupal.behaviors.webformAdmin.attach = function (context) { + // On click or change, make a parent radio button selected. + Drupal.webform.setActive(context); + Drupal.webform.updateTemplate(context); + // Update the template select list upon changing a template. + // Select all link for file extensions. + Drupal.webform.selectCheckboxesLink(context); + // Enhance the normal tableselect.js file to support indentations. + Drupal.webform.tableSelectIndentation(context); + // Automatically download exports if available. + Drupal.webform.downloadExport(context); + // Enhancements for the conditionals administrative page. + Drupal.webform.conditionalAdmin(context); + // Trigger radio/checkbox change when label click automatically selected by + // browser. + Drupal.webform.radioLabelAutoClick(context); }; - $('.webform-inline-radio', context).click(setActiveOnClick); - $('.webform-set-active', context).change(setActiveOnChange); - - // Firefox improperly selects the parent radio button when clicking inside - // a label that contains an input field. The only way of preventing this - // currently is to remove the "for" attribute on the label. - // See https://bugzilla.mozilla.org/show_bug.cgi?id=213519. - if (navigator.userAgent.match(/Firefox/)) { - $('.webform-inline-radio', context).removeAttr('for'); - } -}; - -// Upldate e-mail templates between default and custom. -Drupal.webform.updateTemplate = function(context) { - var defaultTemplate = $('#edit-templates-default').val(); - var $templateSelect = $('#webform-template-fieldset select#edit-template-option', context); - var $templateTextarea = $('#webform-template-fieldset textarea:visible', context); - - var updateTemplateSelect = function() { - if ($(this).val() == defaultTemplate) { - $templateSelect.val('default'); - } - else { - $templateSelect.val('custom'); + + Drupal.webform = Drupal.webform || {}; + + Drupal.webform.setActive = function (context) { + $('.webform-inline-radio', context).click(function (e) { + $(this).closest('.form-type-radio').find('input[type=radio]').webformProp('checked', true); + }); + $('.webform-set-active', context).change(function (e) { + if ($(this).val()) { + $(this).closest('.form-type-radio').find('input[type=radio]').webformProp('checked', true); + } + e.preventDefault(); + }); + + // Firefox improperly selects the parent radio button when clicking inside + // a label that contains an input field. The only way of preventing this + // currently is to remove the "for" attribute on the label. + // See https://bugzilla.mozilla.org/show_bug.cgi?id=213519. + if (navigator.userAgent.match(/Firefox/)) { + $('.webform-inline-radio', context).removeAttr('for'); } - } + }; - var updateTemplateText = function() { - if ($(this).val() == 'default' && $templateTextarea.val() != defaultTemplate) { - if (confirm(Drupal.settings.webform.revertConfirm)) { - $templateTextarea.val(defaultTemplate); + // Update e-mail templates between default and custom. + Drupal.webform.updateTemplate = function (context) { + var defaultTemplate = $('#edit-templates-default').val(); + var $templateSelect = $('#webform-template-fieldset select#edit-template-option', context); + var $templateTextarea = $('#webform-template-fieldset textarea:visible', context); + + var updateTemplateSelect = function () { + if ($(this).val() == defaultTemplate) { + $templateSelect.val('default'); } else { - $(this).val('custom'); + $templateSelect.val('custom'); } - } - } - - $templateTextarea.keyup(updateTemplateSelect); - $templateSelect.change(updateTemplateText); -} - -Drupal.webform.selectCheckboxesLink = function(context) { - function selectCheckboxes() { - var group = this.className.replace(/.*?webform-select-link-([^ ]*).*/, '$1'); - var $checkboxes = $('.webform-select-group-' + group + ' input[type=checkbox]'); - var reverseCheck = !$checkboxes[0].checked; - $checkboxes.each(function() { - this.checked = reverseCheck; - }); - $checkboxes.trigger('change'); - return false; - } - $('a.webform-select-link', context).click(selectCheckboxes); -} - -Drupal.webform.tableSelectIndentation = function(context) { - var $tables = $('th.select-all', context).parents('table'); - $tables.find('input.form-checkbox').change(function() { - var $rows = $(this).parents('table:first').find('tr'); - var $checkbox; - var row = $(this).parents('tr:first').get(0); - var rowNumber = $rows.index(row); - var rowTotal = $rows.size(); - var indentLevel = $(row).find('div.indentation').size(); - for (var n = rowNumber + 1; n < rowTotal; n++) { - if ($rows.eq(n).find('div.indentation').size() <= indentLevel) { - break; + }; + + var updateTemplateText = function () { + if ($(this).val() == 'default' && $templateTextarea.val() != defaultTemplate) { + if (confirm(Drupal.settings.webform.revertConfirm)) { + $templateTextarea.val(defaultTemplate); + } + else { + $(this).val('custom'); + } } - $checkbox = $rows.eq(n).find('input.form-checkbox'); - $.fn.prop ? $checkbox.prop('checked', this.checked) : $checkbox.attr('checked', this.checked); + }; + + $templateTextarea.keyup(updateTemplateSelect); + $templateSelect.change(updateTemplateText); + }; + + Drupal.webform.selectCheckboxesLink = function (context) { + function selectCheckboxes() { + var group = this.className.replace(/.*?webform-select-link-([^ ]*).*/, '$1'); + var $checkboxes = $('.webform-select-group-' + group + ' input[type=checkbox]'); + var reverseCheck = !$checkboxes[0].checked; + $checkboxes.each(function () { + this.checked = reverseCheck; + }); + $checkboxes.trigger('change'); + return false; } - }); -} + $('a.webform-select-link', context).click(selectCheckboxes); + }; -/** - * Attach behaviors for Webform results download page. - */ -Drupal.webform.downloadExport = function(context) { - if (context === document && Drupal.settings && Drupal.settings.webformExport && document.cookie.match(/webform_export_info=1/)) { - window.location = Drupal.settings.webformExport; - delete Drupal.settings.webformExport; - } -} + Drupal.webform.tableSelectIndentation = function (context) { + var $tables = $('th.select-all', context).parents('table'); + $tables.find('input.form-checkbox').change(function () { + var $rows = $(this).parents('table:first').find('tr'); + var row = $(this).parents('tr:first').get(0); + var rowNumber = $rows.index(row); + var rowTotal = $rows.size(); + var indentLevel = $(row).find('div.indentation').size(); + for (var n = rowNumber + 1; n < rowTotal; n++) { + if ($rows.eq(n).find('div.indentation').size() <= indentLevel) { + break; + } + $rows.eq(n).find('input.form-checkbox').webformProp('checked', this.checked); + } + }); + }; -/** - * Attach behaviors for Webform conditional administration. - */ -Drupal.webform.conditionalAdmin = function(context) { - var $context = $(context); - // Bind to the entire form and allow events to bubble-up from elements. This - // saves a lot of processing when new conditions are added/removed. - $context.find('#webform-conditionals-ajax:not(.webform-conditional-processed)') + /** + * Attach behaviors for Webform results download page. + */ + Drupal.webform.downloadExport = function (context) { + if (context === document && Drupal.settings && Drupal.settings.webformExport && document.cookie.match(/webform_export_info=1/)) { + window.location = Drupal.settings.webformExport; + delete Drupal.settings.webformExport; + } + }; + + /** + * Attach behaviors for Webform conditional administration. + */ + Drupal.webform.conditionalAdmin = function (context) { + var $context = $(context); + // Bind to the entire form and allow events to bubble-up from elements. This + // saves a lot of processing when new conditions are added/removed. + $context.find('#webform-conditionals-ajax:not(.webform-conditional-processed)') .addClass('webform-conditional-processed') - .bind('change', function(e) { + .bind('change', function (e) { + + var $target = $(e.target); + if ($target.is('.webform-conditional-source select')) { + Drupal.webform.conditionalSourceChange.apply(e.target); + } + + if ($target.is('.webform-conditional-operator select')) { + Drupal.webform.conditionalOperatorChange.apply(e.target); + } + + if ($target.is('.webform-conditional-andor select')) { + Drupal.webform.conditionalAndOrChange.apply(e.target); + } + + if ($target.is('.webform-conditional-action select')) { + Drupal.webform.conditionalActionChange.apply(e.target); + } + }); + + // Add event handlers to delete the entire row if the last rule or action is removed. + $context.find('.webform-conditional-rule-remove:not(.webform-conditional-processed)').bind('click', function () { + this.webformRemoveClass = '.webform-conditional-rule-remove'; + window.setTimeout($.proxy(Drupal.webform.conditionalRemove, this), 100); + }).addClass('webform-conditional-processed'); + $context.find('.webform-conditional-action-remove:not(.webform-conditional-processed)').bind('click', function () { + this.webformRemoveClass = '.webform-conditional-action-remove'; + window.setTimeout($.proxy(Drupal.webform.conditionalRemove, this), 100); + }).addClass('webform-conditional-processed'); + + // Trigger default handlers on the source element, this in turn will trigger + // the operator handlers. + $context.find('.webform-conditional-source select').trigger('change'); + + // Trigger defaults handlers on the action element. + $context.find('.webform-conditional-action select').trigger('change'); + + // When adding a new table row, make it draggable and hide the weight column. + if ($context.is('tr.ajax-new-content') && $context.find('.webform-conditional').length === 1) { + Drupal.tableDrag['webform-conditionals-table'].makeDraggable($context[0]); + $context.find('.webform-conditional-weight').closest('td').addClass('tabledrag-hide'); + if ($.cookie('Drupal.tableDrag.showWeight') !== '1') { + Drupal.tableDrag['webform-conditionals-table'].hideColumns(); + } + $context.removeClass('ajax-new-content'); + } + }; - var $target = $(e.target); - if ($target.is('.webform-conditional-source select')) { - Drupal.webform.conditionalSourceChange.apply(e.target); + /** + * Event callback for the remove button next to an individual rule. + */ + Drupal.webform.conditionalRemove = function () { + // See if there are any remaining rules in this element. + var rowCount = $(this).parents('.webform-conditional:first').find(this.webformRemoveClass).length; + if (rowCount <= 1) { + var $tableRow = $(this).parents('tr:first'); + var $table = $('#webform-conditionals-table'); + if ($tableRow.length && $table.length) { + $tableRow.remove(); + Drupal.webform.restripeTable($table[0]); + } } + }; - if ($target.is('.webform-conditional-operator select')) { - Drupal.webform.conditionalOperatorChange.apply(e.target); + /** + * Event callback to update the list of operators after a source change. + */ + Drupal.webform.conditionalSourceChange = function () { + var source = $(this).val(); + var dataType = Drupal.settings.webform.conditionalValues.sources[source]['data_type']; + var $operator = $(this).parents('.webform-conditional-rule:first').find('.webform-conditional-operator select'); + + // Store a the original list of all operators for all data types in the select + // list DOM element. + if (!$operator[0]['webformConditionalOriginal']) { + $operator[0]['webformConditionalOriginal'] = $operator[0].innerHTML; } - if ($target.is('.webform-conditional-andor select')) { - Drupal.webform.conditionalAndOrChange.apply(e.target); + // Reference the original list to create a new list matching the data type. + var $originalList = $($operator[0]['webformConditionalOriginal']); + var $newList = $originalList.filter('optgroup[label=' + dataType + ']'); + var newHTML = $newList[0].innerHTML; + + // Update the options and fire the change event handler on the list to update + // the value field, only if the options have changed. This avoids resetting + // existing selections. + if (newHTML != $operator.html()) { + $operator.html(newHTML); } + // Trigger the change in case the source component changed from one select + // component to another. + $operator.trigger('change'); - if ($target.is('.webform-conditional-action select')) { - Drupal.webform.conditionalActionChange.apply(e.target); + }; + + /** + * Event callback to update the value field after an operator change. + */ + Drupal.webform.conditionalOperatorChange = function () { + var source = $(this).parents('.webform-conditional-rule:first').find('.webform-conditional-source select').val(); + var dataType = Drupal.settings.webform.conditionalValues.sources[source]['data_type']; + var operator = $(this).val(); + var $value = $(this).parents('.webform-conditional-rule:first').find('.webform-conditional-value'); + var name = $value.find('input, select, textarea').attr('name'); + var originalValue = false; + + // Given the dataType and operator, we can determine the form key. + var formKey = Drupal.settings.webform.conditionalValues.operators[dataType][operator]['form']; + var formSource = typeof Drupal.settings.webform.conditionalValues.forms[formKey] == 'undefined' ? false : source; + + // On initial request, save the default field as printed on the original page. + if (!$value[0]['webformConditionalOriginal']) { + $value[0]['webformConditionalOriginal'] = $value[0].innerHTML; + originalValue = $value.find('input:first').val(); } - }); - - // Add event handlers to delete the entire row if the last rule or action is removed. - $context.find('.webform-conditional-rule-remove:not(.webform-conditional-processed)').bind('click', function() { - this.webformRemoveClass = '.webform-conditional-rule-remove'; - window.setTimeout($.proxy(Drupal.webform.conditionalRemove, this), 100); - }).addClass('webform-conditional-processed'); - $context.find('.webform-conditional-action-remove:not(.webform-conditional-processed)').bind('click', function() { - this.webformRemoveClass = '.webform-conditional-action-remove'; - window.setTimeout($.proxy(Drupal.webform.conditionalRemove, this), 100); - }).addClass('webform-conditional-processed'); - - // Trigger default handlers on the source element, this in turn will trigger - // the operator handlers. - $context.find('.webform-conditional-source select').trigger('change'); - - // Trigger defaults handlers on the action element. - $context.find('.webform-conditional-action select').trigger('change'); - - // When adding a new table row, make it draggable and hide the weight column. - if ($context.is('tr.ajax-new-content') && $context.find('.webform-conditional').length === 1) { - Drupal.tableDrag['webform-conditionals-table'].makeDraggable($context[0]); - $context.find('.webform-conditional-weight').closest('td').addClass('tabledrag-hide'); - if ($.cookie('Drupal.tableDrag.showWeight') !== '1') { - Drupal.tableDrag['webform-conditionals-table'].hideColumns(); + // On changes to an existing operator, check if the form key is different + // (and any per-source form, such as a select option list) before replacing + // the form with an identical version. + else if ($value[0]['webformConditionalFormKey'] == formKey && $value[0]['webformConditionalFormSource'] == formSource) { + return; } - $context.removeClass('ajax-new-content'); - } -} -/** - * Event callback for the remove button next to an individual rule. - */ -Drupal.webform.conditionalRemove = function() { - // See if there are any remaining rules in this element. - var rowCount = $(this).parents('.webform-conditional:first').find(this.webformRemoveClass).length; - if (rowCount <= 1) { - var $tableRow = $(this).parents('tr:first'); - var $table = $('#webform-conditionals-table'); - if ($tableRow.length && $table.length) { - $tableRow.remove(); - Drupal.webform.restripeTable($table[0]); + // Store the current form key for checking the next time the operator changes. + $value[0]['webformConditionalFormKey'] = formKey; + $value[0]['webformConditionalFormSource'] = formSource; + + // If using the default (a textfield), restore the original field. + if (formKey === 'default') { + $value[0].innerHTML = $value[0]['webformConditionalOriginal']; + } + // If the operator does not need a source value (i.e. is empty), hide it. + else if (formKey === false) { + $value[0].innerHTML = ' '; + } + // If there is a per-source form for this operator (e.g. option lists), use + // the specialized value form. + else if (typeof Drupal.settings.webform.conditionalValues.forms[formKey] == 'object') { + $value[0].innerHTML = Drupal.settings.webform.conditionalValues.forms[formKey][source]; + } + // Otherwise all the sources use a generic field (e.g. a text field). + else { + $value[0].innerHTML = Drupal.settings.webform.conditionalValues.forms[formKey]; } - } -} -/** - * Event callback to update the list of operators after a source change. - */ -Drupal.webform.conditionalSourceChange = function() { - var source = $(this).val(); - var dataType = Drupal.settings.webform.conditionalValues.sources[source]['data_type']; - var $operator = $(this).parents('.webform-conditional-rule:first').find('.webform-conditional-operator select'); - - // Store a the original list of all operators for all data types in the select - // list DOM element. - if (!$operator[0]['webformConditionalOriginal']) { - $operator[0]['webformConditionalOriginal'] = $operator[0].innerHTML; - } - - // Reference the original list to create a new list matching the data type. - var $originalList = $($operator[0]['webformConditionalOriginal']); - var $newList = $originalList.filter('optgroup[label=' + dataType + ']'); - var newHTML = $newList[0].innerHTML; - - // Update the options and fire the change event handler on the list to update - // the value field, only if the options have changed. This avoids resetting - // existing selections. - if (newHTML != $operator.html()) { - $operator.html(newHTML); - } - // Trigger the change in case the source component changed from one select - // component to another. - $operator.trigger('change'); - -} + // Set the name attribute to match the original placeholder field. + var $firstElement = $value.find('input, select, textarea').filter(':first'); + $firstElement.attr('name', name); -/** - * Event callback to update the value field after an operator change. - */ -Drupal.webform.conditionalOperatorChange = function() { - var source = $(this).parents('.webform-conditional-rule:first').find('.webform-conditional-source select').val(); - var dataType = Drupal.settings.webform.conditionalValues.sources[source]['data_type']; - var operator = $(this).val(); - var $value = $(this).parents('.webform-conditional-rule:first').find('.webform-conditional-value'); - var name = $value.find('input, select, textarea').attr('name'); - var originalValue = false; - - // Given the dataType and operator, we can determine the form key. - var formKey = Drupal.settings.webform.conditionalValues.operators[dataType][operator]['form']; - var formSource = typeof Drupal.settings.webform.conditionalValues.forms[formKey] == 'undefined' ? false : source; - - // On initial request, save the default field as printed on the original page. - if (!$value[0]['webformConditionalOriginal']) { - $value[0]['webformConditionalOriginal'] = $value[0].innerHTML; - originalValue = $value.find('input:first').val(); - } - // On changes to an existing operator, check if the form key is different - // (and any per-source form, such as a select option list) before replacing - // the form with an identical version. - else if ($value[0]['webformConditionalFormKey'] == formKey && $value[0]['webformConditionalFormSource'] == formSource) { - return; - } - - // Store the current form key for checking the next time the operator changes. - $value[0]['webformConditionalFormKey'] = formKey; - $value[0]['webformConditionalFormSource'] = formSource; - - // If using the default (a textfield), restore the original field. - if (formKey === 'default') { - $value[0].innerHTML = $value[0]['webformConditionalOriginal']; - } - // If the operator does not need a source value (i.e. is empty), hide it. - else if (formKey === false) { - $value[0].innerHTML = ' '; - } - // If there is a per-source form for this operator (e.g. option lists), use - // the specialized value form. - else if (typeof Drupal.settings.webform.conditionalValues.forms[formKey] == 'object') { - $value[0].innerHTML = Drupal.settings.webform.conditionalValues.forms[formKey][source]; - } - // Otherwise all the sources use a generic field (e.g. a text field). - else { - $value[0].innerHTML = Drupal.settings.webform.conditionalValues.forms[formKey]; - } - - // Set the name attribute to match the original placeholder field. - var $firstElement = $value.find('input, select, textarea').filter(':first'); - $firstElement.attr('name', name); - - if (originalValue) { - $firstElement.val(originalValue); - } -} + if (originalValue) { + $firstElement.val(originalValue); + } + }; -/** - * Event callback to make sure all group and/or operators match. - */ -Drupal.webform.conditionalAndOrChange = function() { - $(this).parents('.webform-conditional:first').find('.webform-conditional-andor select').val(this.value); -} + /** + * Event callback to make sure all group and/or operators match. + */ + Drupal.webform.conditionalAndOrChange = function () { + $(this).parents('.webform-conditional:first').find('.webform-conditional-andor select').val(this.value); + }; -/** - * Event callback to show argument only for appropriate actions. - */ -Drupal.webform.conditionalActionChange = function() { - var action = $(this).val(); - var $argument = $(this).parents('.webform-conditional:first').find('.webform-conditional-argument input'); - var isShown = $argument.is(':visible'); - switch (action) { - case 'show': - case 'require': - if (isShown) { - $argument.hide(); - } - break; - case 'set': - if (!isShown) { - $argument.show(); - } - break; - } -} + /** + * Event callback to show argument only for appropriate actions. + */ + Drupal.webform.conditionalActionChange = function () { + var action = $(this).val(); + var $argument = $(this).parents('.webform-conditional:first').find('.webform-conditional-argument input'); + var isShown = $argument.is(':visible'); + switch (action) { + case 'show': + case 'require': + if (isShown) { + $argument.hide(); + } + break; + case 'set': + if (!isShown) { + $argument.show(); + } + break; + } + }; -/** - * Given a table's DOM element, restripe the odd/even classes. - */ -Drupal.webform.restripeTable = function(table) { - // :even and :odd are reversed because jQuery counts from 0 and - // we count from 1, so we're out of sync. - // Match immediate children of the parent element to allow nesting. - $('> tbody > tr, > tr', table) - .filter(':odd').filter('.odd') - .removeClass('odd').addClass('even') - .end().end() - .filter(':even').filter('.even') - .removeClass('even').addClass('odd'); -}; + /** + * Given a table's DOM element, restripe the odd/even classes. + */ + Drupal.webform.restripeTable = function (table) { + // :even and :odd are reversed because jQuery counts from 0 and + // we count from 1, so we're out of sync. + // Match immediate children of the parent element to allow nesting. + $('> tbody > tr, > tr', table) + .filter(':odd').filter('.odd') + .removeClass('odd').addClass('even') + .end().end() + .filter(':even').filter('.even') + .removeClass('even').addClass('odd'); + }; -/** - * Triggers a change event when a label receives a click. - * - * When the browser automatically selects a radio button when it's label is - * clicked, the FAPI states jQuery code doesn't receive an event. This function - * ensures that automatically-selected radio buttons keep in sync with the - * FAPI states. - */ -Drupal.webform.radioLabelAutoClick = function(context) { - $('label').once('webform-label').click(function(){ - $(this).prev('input:radio').change(); - }); -} + /** + * Triggers a change event when a label receives a click. + * + * When the browser automatically selects a radio button when it's label is + * clicked, the FAPI states jQuery code doesn't receive an event. This function + * ensures that automatically-selected radio buttons keep in sync with the + * FAPI states. + */ + Drupal.webform.radioLabelAutoClick = function (context) { + $('label').once('webform-label').click(function () { + $(this).prev('input:radio').change(); + }); + }; + + /** + * Make a prop shim for jQuery < 1.9. + */ + $.fn.webformProp = $.fn.webformProp || function (name, value) { + if (value) { + return $.fn.prop ? this.prop(name, true) : this.attr(name, true); + } + else { + return $.fn.prop ? this.prop(name, false) : this.removeAttr(name); + } + }; })(jQuery); diff --git a/profiles/wcm_base/modules/contrib/webform/js/webform.js b/profiles/wcm_base/modules/contrib/webform/js/webform.js index 887c8213..240c1c50 100644 --- a/profiles/wcm_base/modules/contrib/webform/js/webform.js +++ b/profiles/wcm_base/modules/contrib/webform/js/webform.js @@ -1,630 +1,632 @@ - /** + * @file * JavaScript behaviors for the front-end display of webforms. */ (function ($) { -Drupal.behaviors.webform = Drupal.behaviors.webform || {}; - -Drupal.behaviors.webform.attach = function(context) { - // Calendar datepicker behavior. - Drupal.webform.datepicker(context); + "use strict"; - // Conditional logic. - if (Drupal.settings.webform && Drupal.settings.webform.conditionals) { - Drupal.webform.conditional(context); - } -}; + Drupal.behaviors.webform = Drupal.behaviors.webform || {}; -Drupal.webform = Drupal.webform || {}; + Drupal.behaviors.webform.attach = function (context) { + // Calendar datepicker behavior. + Drupal.webform.datepicker(context); -Drupal.webform.datepicker = function(context) { - $('div.webform-datepicker').each(function() { - var $webformDatepicker = $(this); - var $calendar = $webformDatepicker.find('input.webform-calendar'); - - // Ensure the page we're on actually contains a datepicker. - if ($calendar.length == 0) { - return; + // Conditional logic. + if (Drupal.settings.webform && Drupal.settings.webform.conditionals) { + Drupal.webform.conditional(context); } + }; - var startDate = $calendar[0].className.replace(/.*webform-calendar-start-(\d{4}-\d{2}-\d{2}).*/, '$1').split('-'); - var endDate = $calendar[0].className.replace(/.*webform-calendar-end-(\d{4}-\d{2}-\d{2}).*/, '$1').split('-'); - var firstDay = $calendar[0].className.replace(/.*webform-calendar-day-(\d).*/, '$1'); - // Convert date strings into actual Date objects. - startDate = new Date(startDate[0], startDate[1] - 1, startDate[2]); - endDate = new Date(endDate[0], endDate[1] - 1, endDate[2]); - - // Ensure that start comes before end for datepicker. - if (startDate > endDate) { - var laterDate = startDate; - startDate = endDate; - endDate = laterDate; - } + Drupal.webform = Drupal.webform || {}; - var startYear = startDate.getFullYear(); - var endYear = endDate.getFullYear(); - - // Set up the jQuery datepicker element. - $calendar.datepicker({ - dateFormat: 'yy-mm-dd', - yearRange: startYear + ':' + endYear, - firstDay: parseInt(firstDay), - minDate: startDate, - maxDate: endDate, - onSelect: function(dateText, inst) { - var date = dateText.split('-'); - $webformDatepicker.find('select.year, input.year').val(+date[0]).trigger('change'); - $webformDatepicker.find('select.month').val(+date[1]).trigger('change'); - $webformDatepicker.find('select.day').val(+date[2]).trigger('change'); - }, - beforeShow: function(input, inst) { - // Get the select list values. - var year = $webformDatepicker.find('select.year, input.year').val(); - var month = $webformDatepicker.find('select.month').val(); - var day = $webformDatepicker.find('select.day').val(); - - // If empty, default to the current year/month/day in the popup. - var today = new Date(); - year = year ? year : today.getFullYear(); - month = month ? month : today.getMonth() + 1; - day = day ? day : today.getDate(); - - // Make sure that the default year fits in the available options. - year = (year < startYear || year > endYear) ? startYear : year; - - // jQuery UI Datepicker will read the input field and base its date off - // of that, even though in our case the input field is a button. - $(input).val(year + '-' + month + '-' + day); + Drupal.webform.datepicker = function (context) { + $('div.webform-datepicker').each(function () { + var $webformDatepicker = $(this); + var $calendar = $webformDatepicker.find('input.webform-calendar'); + + // Ensure the page we're on actually contains a datepicker. + if ($calendar.length == 0) { + return; } - }); - // Prevent the calendar button from submitting the form. - $calendar.click(function(event) { - $(this).focus(); - event.preventDefault(); - }); - }); -}; - -Drupal.webform.conditional = function(context) { - // Add the bindings to each webform on the page. - $.each(Drupal.settings.webform.conditionals, function(formKey, settings) { - var $form = $('.' + formKey + ':not(.webform-conditional-processed)'); - $form.each(function(index, currentForm) { - var $currentForm = $(currentForm); - $currentForm.addClass('webform-conditional-processed'); - $currentForm.bind('change', { 'settings': settings }, Drupal.webform.conditionalCheck); - - // Trigger all the elements that cause conditionals on this form. - Drupal.webform.doConditions($form, settings); - }) - }); -}; + var startDate = $calendar[0].className.replace(/.*webform-calendar-start-(\d{4}-\d{2}-\d{2}).*/, '$1').split('-'); + var endDate = $calendar[0].className.replace(/.*webform-calendar-end-(\d{4}-\d{2}-\d{2}).*/, '$1').split('-'); + var firstDay = $calendar[0].className.replace(/.*webform-calendar-day-(\d).*/, '$1'); + // Convert date strings into actual Date objects. + startDate = new Date(startDate[0], startDate[1] - 1, startDate[2]); + endDate = new Date(endDate[0], endDate[1] - 1, endDate[2]); + + // Ensure that start comes before end for datepicker. + if (startDate > endDate) { + var laterDate = startDate; + startDate = endDate; + endDate = laterDate; + } -/** - * Event handler to respond to field changes in a form. - * - * This event is bound to the entire form, not individual fields. - */ -Drupal.webform.conditionalCheck = function(e) { - var $triggerElement = $(e.target).closest('.webform-component'); - var $form = $triggerElement.closest('form'); - var triggerElementKey = $triggerElement.attr('class').match(/webform-component--[^ ]+/)[0]; - var settings = e.data.settings; - if (settings.sourceMap[triggerElementKey]) { - Drupal.webform.doConditions($form, settings); - } -}; + var startYear = startDate.getFullYear(); + var endYear = endDate.getFullYear(); + + // Set up the jQuery datepicker element. + $calendar.datepicker({ + dateFormat: 'yy-mm-dd', + yearRange: startYear + ':' + endYear, + firstDay: parseInt(firstDay), + minDate: startDate, + maxDate: endDate, + onSelect: function (dateText, inst) { + var date = dateText.split('-'); + $webformDatepicker.find('select.year, input.year').val(+date[0]).trigger('change'); + $webformDatepicker.find('select.month').val(+date[1]).trigger('change'); + $webformDatepicker.find('select.day').val(+date[2]).trigger('change'); + }, + beforeShow: function (input, inst) { + // Get the select list values. + var year = $webformDatepicker.find('select.year, input.year').val(); + var month = $webformDatepicker.find('select.month').val(); + var day = $webformDatepicker.find('select.day').val(); + + // If empty, default to the current year/month/day in the popup. + var today = new Date(); + year = year ? year : today.getFullYear(); + month = month ? month : today.getMonth() + 1; + day = day ? day : today.getDate(); + + // Make sure that the default year fits in the available options. + year = (year < startYear || year > endYear) ? startYear : year; + + // jQuery UI Datepicker will read the input field and base its date off + // of that, even though in our case the input field is a button. + $(input).val(year + '-' + month + '-' + day); + } + }); -/** - * Processes all conditional. - */ -Drupal.webform.doConditions = function($form, settings) { - // Track what has be set/shown for each target component. - var targetLocked = []; - - $.each(settings.ruleGroups, function(rgid_key, rule_group) { - var ruleGroup = settings.ruleGroups[rgid_key]; - - // Perform the comparison callback and build the results for this group. - var conditionalResult = true; - var conditionalResults = []; - $.each(ruleGroup['rules'], function(m, rule) { - var elementKey = rule['source']; - var element = $form.find('.' + elementKey)[0]; - var existingValue = settings.values[elementKey] ? settings.values[elementKey] : null; - conditionalResults.push(window['Drupal']['webform'][rule.callback](element, existingValue, rule['value'] )); + // Prevent the calendar button from submitting the form. + $calendar.click(function (event) { + $(this).focus(); + event.preventDefault(); + }); + }); + }; + + Drupal.webform.conditional = function (context) { + // Add the bindings to each webform on the page. + $.each(Drupal.settings.webform.conditionals, function (formKey, settings) { + var $form = $('.' + formKey + ':not(.webform-conditional-processed)'); + $form.each(function (index, currentForm) { + var $currentForm = $(currentForm); + $currentForm.addClass('webform-conditional-processed'); + $currentForm.bind('change', {'settings': settings}, Drupal.webform.conditionalCheck); + + // Trigger all the elements that cause conditionals on this form. + Drupal.webform.doConditions($form, settings); + }); }); + }; + + /** + * Event handler to respond to field changes in a form. + * + * This event is bound to the entire form, not individual fields. + */ + Drupal.webform.conditionalCheck = function (e) { + var $triggerElement = $(e.target).closest('.webform-component'); + var $form = $triggerElement.closest('form'); + var triggerElementKey = $triggerElement.attr('class').match(/webform-component--[^ ]+/)[0]; + var settings = e.data.settings; + if (settings.sourceMap[triggerElementKey]) { + Drupal.webform.doConditions($form, settings); + } + }; + + /** + * Processes all conditional. + */ + Drupal.webform.doConditions = function ($form, settings) { + // Track what has be set/shown for each target component. + var targetLocked = []; + + $.each(settings.ruleGroups, function (rgid_key, rule_group) { + var ruleGroup = settings.ruleGroups[rgid_key]; + + // Perform the comparison callback and build the results for this group. + var conditionalResult = true; + var conditionalResults = []; + $.each(ruleGroup['rules'], function (m, rule) { + var elementKey = rule['source']; + var element = $form.find('.' + elementKey)[0]; + var existingValue = settings.values[elementKey] ? settings.values[elementKey] : null; + conditionalResults.push(window['Drupal']['webform'][rule.callback](element, existingValue, rule['value'])); + }); - // Filter out false values. - var filteredResults = []; - for (var i = 0; i < conditionalResults.length; i++) { - if (conditionalResults[i]) { - filteredResults.push(conditionalResults[i]); + // Filter out false values. + var filteredResults = []; + for (var i = 0; i < conditionalResults.length; i++) { + if (conditionalResults[i]) { + filteredResults.push(conditionalResults[i]); + } } - } - // Calculate the and/or result. - if (ruleGroup['andor'] === 'or') { - conditionalResult = filteredResults.length > 0; - } - else { - conditionalResult = filteredResults.length === conditionalResults.length; - } + // Calculate the and/or result. + if (ruleGroup['andor'] === 'or') { + conditionalResult = filteredResults.length > 0; + } + else { + conditionalResult = filteredResults.length === conditionalResults.length; + } - $.each(ruleGroup['actions'], function(aid, action) { - var $target = $form.find('.' + action['target']); - var actionResult = action['invert'] ? !conditionalResult : conditionalResult; - switch (action['action']) { - case 'show': - if (actionResult != Drupal.webform.isVisible($target)) { - var $targetElements = actionResult - ? $target.find('.webform-conditional-disabled').removeClass('webform-conditional-disabled') - : $target.find(':input').addClass('webform-conditional-disabled'); - $targetElements.webformProp('disabled', !actionResult); - $target.toggleClass('webform-conditional-hidden', !actionResult); + $.each(ruleGroup['actions'], function (aid, action) { + var $target = $form.find('.' + action['target']); + var actionResult = action['invert'] ? !conditionalResult : conditionalResult; + switch (action['action']) { + case 'show': + if (actionResult != Drupal.webform.isVisible($target)) { + var $targetElements = actionResult + ? $target.find('.webform-conditional-disabled').removeClass('webform-conditional-disabled') + : $target.find(':input').addClass('webform-conditional-disabled'); + $targetElements.webformProp('disabled', !actionResult); + $target.toggleClass('webform-conditional-hidden', !actionResult); + if (actionResult) { + $target.show(); + } + else { + $target.hide(); + // Record that the target was hidden. + targetLocked[action['target']] = 'hide'; + } + } + break; + case 'require': + var $requiredSpan = $target.find('.form-required, .form-optional').first(); + if (actionResult != $requiredSpan.hasClass('form-required')) { + var $targetInputElements = $target.find("input:text,textarea,input[type='email'],select,input:radio,input:file"); + // Rather than hide the required tag, remove it so that other jQuery can respond via Drupal behaviors. + Drupal.detachBehaviors($requiredSpan); + $targetInputElements + .webformProp('required', actionResult) + .toggleClass('required', actionResult); + if (actionResult) { + $requiredSpan.replaceWith('<span class="form-required" title="' + Drupal.t('This field is required.') + '">*</span>'); + } + else { + $requiredSpan.replaceWith('<span class="form-optional"></span>'); + } + Drupal.attachBehaviors($requiredSpan); + } + break; + case 'set': + var isLocked = targetLocked[action['target']]; + var $texts = $target.find("input:text,textarea,input[type='email']"); + var $selects = $target.find('select,select option,input:radio,input:checkbox'); + var $markups = $target.filter('.webform-component-markup'); if (actionResult) { - $target.show(); + var multiple = $.map(action['argument'].split(','), $.trim); + $selects.webformVal(multiple); + $texts.val([action['argument']]); + // A special case is made for markup. It is sanitized with filter_xss_admin on the server. + // otherwise text() should be used to avoid an XSS vulnerability. text() however would + // preclude the use of tags like <strong> or <a> + $markups.html(action['argument']); } else { - $target.hide(); - // Record that the target was hidden. - targetLocked[action['target']] = 'hide'; + // Markup not set? Then restore original markup as provided in + // the attribute data-webform-markup. + $markups.each(function() { + var $this = $(this); + var original = $this.data('webform-markup'); + if (original !== undefined) { + $this.html(original); + } + }); } - } - break; - case 'require': - var $requiredSpan = $target.find('.form-required, .form-optional').first(); - if (actionResult != $requiredSpan.hasClass('form-required')) { - var $targetInputElements = $target.find("input:text,textarea,input[type='email'],select,input:radio,input:file"); - // Rather than hide the required tag, remove it so that other jQuery can respond via Drupal behaviors. - Drupal.detachBehaviors($requiredSpan); - $targetInputElements - .webformProp('required', actionResult) - .toggleClass('required', actionResult); - if (actionResult) { - $requiredSpan.replaceWith('<span class="form-required" title="' + Drupal.t('This field is required.') + '">*</span>'); + if (!isLocked) { + // If not previously hidden or set, disable the element readonly or readonly-like behavior. + $selects.webformProp('disabled', actionResult); + $texts.webformProp('readonly', actionResult); + targetLocked[action['target']] = actionResult ? 'set' : false; + } + break; + } + }); // End look on each action for one conditional + }); // End loop on each conditional + }; + + /** + * Event handler to prevent propogation of events, typically click for disabling + * radio and checkboxes. + */ + Drupal.webform.stopEvent = function () { + return false; + }; + + Drupal.webform.conditionalOperatorStringEqual = function (element, existingValue, ruleValue) { + var returnValue = false; + var currentValue = Drupal.webform.stringValue(element, existingValue); + $.each(currentValue, function (n, value) { + if (value.toLowerCase() === ruleValue.toLowerCase()) { + returnValue = true; + return false; // break. + } + }); + return returnValue; + }; + + Drupal.webform.conditionalOperatorStringNotEqual = function (element, existingValue, ruleValue) { + var found = false; + var currentValue = Drupal.webform.stringValue(element, existingValue); + $.each(currentValue, function (n, value) { + if (value.toLowerCase() === ruleValue.toLowerCase()) { + found = true; + } + }); + return !found; + }; + + Drupal.webform.conditionalOperatorStringContains = function (element, existingValue, ruleValue) { + var returnValue = false; + var currentValue = Drupal.webform.stringValue(element, existingValue); + $.each(currentValue, function (n, value) { + if (value.toLowerCase().indexOf(ruleValue.toLowerCase()) > -1) { + returnValue = true; + return false; // break. + } + }); + return returnValue; + }; + + Drupal.webform.conditionalOperatorStringDoesNotContain = function (element, existingValue, ruleValue) { + var found = false; + var currentValue = Drupal.webform.stringValue(element, existingValue); + $.each(currentValue, function (n, value) { + if (value.toLowerCase().indexOf(ruleValue.toLowerCase()) > -1) { + found = true; + } + }); + return !found; + }; + + Drupal.webform.conditionalOperatorStringBeginsWith = function (element, existingValue, ruleValue) { + var returnValue = false; + var currentValue = Drupal.webform.stringValue(element, existingValue); + $.each(currentValue, function (n, value) { + if (value.toLowerCase().indexOf(ruleValue.toLowerCase()) === 0) { + returnValue = true; + return false; // break. + } + }); + return returnValue; + }; + + Drupal.webform.conditionalOperatorStringEndsWith = function (element, existingValue, ruleValue) { + var returnValue = false; + var currentValue = Drupal.webform.stringValue(element, existingValue); + $.each(currentValue, function (n, value) { + if (value.toLowerCase().lastIndexOf(ruleValue.toLowerCase()) === value.length - ruleValue.length) { + returnValue = true; + return false; // break. + } + }); + return returnValue; + }; + + Drupal.webform.conditionalOperatorStringEmpty = function (element, existingValue, ruleValue) { + var currentValue = Drupal.webform.stringValue(element, existingValue); + var returnValue = true; + $.each(currentValue, function (n, value) { + if (value !== '') { + returnValue = false; + return false; // break. + } + }); + return returnValue; + }; + + Drupal.webform.conditionalOperatorStringNotEmpty = function (element, existingValue, ruleValue) { + return !Drupal.webform.conditionalOperatorStringEmpty(element, existingValue, ruleValue); + }; + + Drupal.webform.conditionalOperatorSelectGreaterThan = function (element, existingValue, ruleValue) { + var currentValue = Drupal.webform.stringValue(element, existingValue); + return Drupal.webform.compare_select(currentValue[0], ruleValue, element) > 0; + }; + + Drupal.webform.conditionalOperatorSelectGreaterThanEqual = function (element, existingValue, ruleValue) { + var currentValue = Drupal.webform.stringValue(element, existingValue); + var comparison = Drupal.webform.compare_select(currentValue[0], ruleValue, element); + return comparison > 0 || comparison === 0; + }; + + Drupal.webform.conditionalOperatorSelectLessThan = function (element, existingValue, ruleValue) { + var currentValue = Drupal.webform.stringValue(element, existingValue); + return Drupal.webform.compare_select(currentValue[0], ruleValue, element) < 0; + }; + + Drupal.webform.conditionalOperatorSelectLessThanEqual = function (element, existingValue, ruleValue) { + var currentValue = Drupal.webform.stringValue(element, existingValue); + var comparison = Drupal.webform.compare_select(currentValue[0], ruleValue, element); + return comparison < 0 || comparison === 0; + }; + + Drupal.webform.conditionalOperatorNumericEqual = function (element, existingValue, ruleValue) { + // See float comparison: http://php.net/manual/en/language.types.float.php + var currentValue = Drupal.webform.stringValue(element, existingValue); + var epsilon = 0.000001; + // An empty string does not match any number. + return currentValue[0] === '' ? false : (Math.abs(parseFloat(currentValue[0]) - parseFloat(ruleValue)) < epsilon); + }; + + Drupal.webform.conditionalOperatorNumericNotEqual = function (element, existingValue, ruleValue) { + // See float comparison: http://php.net/manual/en/language.types.float.php + var currentValue = Drupal.webform.stringValue(element, existingValue); + var epsilon = 0.000001; + // An empty string does not match any number. + return currentValue[0] === '' ? true : (Math.abs(parseFloat(currentValue[0]) - parseFloat(ruleValue)) >= epsilon); + }; + + Drupal.webform.conditionalOperatorNumericGreaterThan = function (element, existingValue, ruleValue) { + var currentValue = Drupal.webform.stringValue(element, existingValue); + return parseFloat(currentValue[0]) > parseFloat(ruleValue); + }; + + Drupal.webform.conditionalOperatorNumericGreaterThanEqual = function (element, existingValue, ruleValue) { + return Drupal.webform.conditionalOperatorNumericGreaterThan(element, existingValue, ruleValue) || + Drupal.webform.conditionalOperatorNumericEqual(element, existingValue, ruleValue); + }; + + Drupal.webform.conditionalOperatorNumericLessThan = function (element, existingValue, ruleValue) { + var currentValue = Drupal.webform.stringValue(element, existingValue); + return parseFloat(currentValue[0]) < parseFloat(ruleValue); + }; + + Drupal.webform.conditionalOperatorNumericLessThanEqual = function (element, existingValue, ruleValue) { + return Drupal.webform.conditionalOperatorNumericLessThan(element, existingValue, ruleValue) || + Drupal.webform.conditionalOperatorNumericEqual(element, existingValue, ruleValue); + }; + + Drupal.webform.conditionalOperatorDateEqual = function (element, existingValue, ruleValue) { + var currentValue = Drupal.webform.dateValue(element, existingValue); + return currentValue === ruleValue; + }; + + Drupal.webform.conditionalOperatorDateNotEqual = function (element, existingValue, ruleValue) { + return !Drupal.webform.conditionalOperatorDateEqual(element, existingValue, ruleValue); + }; + + Drupal.webform.conditionalOperatorDateBefore = function (element, existingValue, ruleValue) { + var currentValue = Drupal.webform.dateValue(element, existingValue); + return (currentValue !== false) && currentValue < ruleValue; + }; + + Drupal.webform.conditionalOperatorDateBeforeEqual = function (element, existingValue, ruleValue) { + var currentValue = Drupal.webform.dateValue(element, existingValue); + return (currentValue !== false) && (currentValue < ruleValue || currentValue === ruleValue); + }; + + Drupal.webform.conditionalOperatorDateAfter = function (element, existingValue, ruleValue) { + var currentValue = Drupal.webform.dateValue(element, existingValue); + return (currentValue !== false) && currentValue > ruleValue; + }; + + Drupal.webform.conditionalOperatorDateAfterEqual = function (element, existingValue, ruleValue) { + var currentValue = Drupal.webform.dateValue(element, existingValue); + return (currentValue !== false) && (currentValue > ruleValue || currentValue === ruleValue); + }; + + Drupal.webform.conditionalOperatorTimeEqual = function (element, existingValue, ruleValue) { + var currentValue = Drupal.webform.timeValue(element, existingValue); + return currentValue === ruleValue; + }; + + Drupal.webform.conditionalOperatorTimeNotEqual = function (element, existingValue, ruleValue) { + return !Drupal.webform.conditionalOperatorTimeEqual(element, existingValue, ruleValue); + }; + + Drupal.webform.conditionalOperatorTimeBefore = function (element, existingValue, ruleValue) { + // Date and time operators intentionally exclusive for "before". + var currentValue = Drupal.webform.timeValue(element, existingValue); + return (currentValue !== false) && (currentValue < ruleValue); + }; + + Drupal.webform.conditionalOperatorTimeBeforeEqual = function (element, existingValue, ruleValue) { + // Date and time operators intentionally exclusive for "before". + var currentValue = Drupal.webform.timeValue(element, existingValue); + return (currentValue !== false) && (currentValue < ruleValue || currentValue === ruleValue); + }; + + Drupal.webform.conditionalOperatorTimeAfter = function (element, existingValue, ruleValue) { + // Date and time operators intentionally inclusive for "after". + var currentValue = Drupal.webform.timeValue(element, existingValue); + return (currentValue !== false) && (currentValue > ruleValue); + }; + + Drupal.webform.conditionalOperatorTimeAfterEqual = function (element, existingValue, ruleValue) { + // Date and time operators intentionally inclusive for "after". + var currentValue = Drupal.webform.timeValue(element, existingValue); + return (currentValue !== false) && (currentValue > ruleValue || currentValue === ruleValue); + }; + + /** + * Utility function to compare values of a select component. + * @param string a + * First select option key to compare + * @param string b + * Second select option key to compare + * @param array options + * Associative array where the a and b are within the keys + * @return integer based upon position of $a and $b in $options + * -N if $a above (<) $b + * 0 if $a = $b + * +N if $a is below (>) $b + */ + Drupal.webform.compare_select = function (a, b, element) { + var optionList = []; + $('option,input:radio,input:checkbox', element).each(function () { + optionList.push($(this).val()); + }); + var a_position = optionList.indexOf(a); + var b_position = optionList.indexOf(b); + return (a_position < 0 || b_position < 0) ? null : a_position - b_position; + }; + + /** + * Utility to return current visibility. Uses actual visibility, except for + * hidden components which use the applied disabled class. + */ + Drupal.webform.isVisible = function ($element) { + return $element.hasClass('webform-component-hidden') + ? !$element.find('input').first().hasClass('webform-conditional-disabled') + : $element.closest('.webform-conditional-hidden').length == 0; + }; + + /** + * Utility function to get a string value from a select/radios/text/etc. field. + */ + Drupal.webform.stringValue = function (element, existingValue) { + var value = []; + if (element) { + var $element = $(element); + if (Drupal.webform.isVisible($element)) { + // Checkboxes and radios. + $element.find('input[type=checkbox]:checked,input[type=radio]:checked').each(function () { + value.push(this.value); + }); + // Select lists. + if (!value.length) { + var selectValue = $element.find('select').val(); + if (selectValue) { + if ($.isArray(selectValue)) { + value = selectValue; } else { - $requiredSpan.replaceWith('<span class="form-optional"></span>'); + value.push(selectValue); } - Drupal.attachBehaviors($requiredSpan); } + } + // Simple text fields. This check is done last so that the select list in + // select-or-other fields comes before the "other" text field. + if (!value.length) { + $element.find('input:not([type=checkbox],[type=radio]),textarea').each(function () { + value.push(this.value); + }); + } + } + } + else { + switch ($.type(existingValue)) { + case 'array': + value = existingValue; break; - case 'set': - var isLocked = targetLocked[action['target']]; - var $texts = $target.find("input:text,textarea,input[type='email']"); - var $selects = $target.find('select,select option,input:radio,input:checkbox'); - if (actionResult) { - var multiple = $.map(action['argument'].split(','), $.trim); - $selects.webformVal(multiple); - $texts.val([action['argument']]); - // A special case is made for markup. It is sanitized with filter_xss_admin on the server. - // otherwise text() should be used to avoid an XSS vulnerability. text() however would - // preclude the use of tags like <strong> or <a> - $target.filter('.webform-component-markup').html(action['argument']); - } - if (!isLocked) { - // If not previously hidden or set, disable the element readonly or readonly-like behavior. - $selects.webformProp('disabled', actionResult); - $texts.webformProp('readonly', actionResult); - targetLocked[action['target']] = actionResult ? 'set' : false; - } + case 'string': + value.push(existingValue); break; } - }); // End look on each action for one conditional - }); // End loop on each conditional -} - -/** - * Event handler to prevent propogation of events, typically click for disabling - * radio and checkboxes. - */ -Drupal.webform.stopEvent = function() { - return false; -} - -Drupal.webform.conditionalOperatorStringEqual = function(element, existingValue, ruleValue) { - var returnValue = false; - var currentValue = Drupal.webform.stringValue(element, existingValue); - $.each(currentValue, function(n, value) { - if (value.toLowerCase() === ruleValue.toLowerCase()) { - returnValue = true; - return false; // break. - } - }); - return returnValue; -}; - -Drupal.webform.conditionalOperatorStringNotEqual = function(element, existingValue, ruleValue) { - var found = false; - var currentValue = Drupal.webform.stringValue(element, existingValue); - $.each(currentValue, function(n, value) { - if (value.toLowerCase() === ruleValue.toLowerCase()) { - found = true; } - }); - return !found; -}; - -Drupal.webform.conditionalOperatorStringContains = function(element, existingValue, ruleValue) { - var returnValue = false; - var currentValue = Drupal.webform.stringValue(element, existingValue); - $.each(currentValue, function(n, value) { - if (value.toLowerCase().indexOf(ruleValue.toLowerCase()) > -1) { - returnValue = true; - return false; // break. - } - }); - return returnValue; -}; - -Drupal.webform.conditionalOperatorStringDoesNotContain = function(element, existingValue, ruleValue) { - var found = false; - var currentValue = Drupal.webform.stringValue(element, existingValue); - $.each(currentValue, function(n, value) { - if (value.toLowerCase().indexOf(ruleValue.toLowerCase()) > -1) { - found = true; - } - }); - return !found; -}; - -Drupal.webform.conditionalOperatorStringBeginsWith = function(element, existingValue, ruleValue) { - var returnValue = false; - var currentValue = Drupal.webform.stringValue(element, existingValue); - $.each(currentValue, function(n, value) { - if (value.toLowerCase().indexOf(ruleValue.toLowerCase()) === 0) { - returnValue = true; - return false; // break. - } - }); - return returnValue; -}; - -Drupal.webform.conditionalOperatorStringEndsWith = function(element, existingValue, ruleValue) { - var returnValue = false; - var currentValue = Drupal.webform.stringValue(element, existingValue); - $.each(currentValue, function(n, value) { - if (value.toLowerCase().lastIndexOf(ruleValue.toLowerCase()) === value.length - ruleValue.length) { - returnValue = true; - return false; // break. - } - }); - return returnValue; -}; - -Drupal.webform.conditionalOperatorStringEmpty = function(element, existingValue, ruleValue) { - var currentValue = Drupal.webform.stringValue(element, existingValue); - var returnValue = true; - $.each(currentValue, function(n, value) { - if (value !== '') { - returnValue = false; - return false; // break. - } - }); - return returnValue; -}; - -Drupal.webform.conditionalOperatorStringNotEmpty = function(element, existingValue, ruleValue) { - return !Drupal.webform.conditionalOperatorStringEmpty(element, existingValue, ruleValue); -}; - -Drupal.webform.conditionalOperatorSelectGreaterThan = function(element, existingValue, ruleValue) { - var currentValue = Drupal.webform.stringValue(element, existingValue); - return Drupal.webform.compare_select(currentValue[0], ruleValue, element) > 0; -}; - -Drupal.webform.conditionalOperatorSelectGreaterThanEqual = function(element, existingValue, ruleValue) { - var currentValue = Drupal.webform.stringValue(element, existingValue); - var comparison = Drupal.webform.compare_select(currentValue[0], ruleValue, element); - return comparison > 0 || comparison === 0; -}; - -Drupal.webform.conditionalOperatorSelectLessThan = function(element, existingValue, ruleValue) { - var currentValue = Drupal.webform.stringValue(element, existingValue); - return Drupal.webform.compare_select(currentValue[0], ruleValue, element) < 0; -}; - -Drupal.webform.conditionalOperatorSelectLessThanEqual = function(element, existingValue, ruleValue) { - var currentValue = Drupal.webform.stringValue(element, existingValue); - var comparison = Drupal.webform.compare_select(currentValue[0], ruleValue, element); - return comparison < 0 || comparison === 0; -}; - -Drupal.webform.conditionalOperatorNumericEqual = function(element, existingValue, ruleValue) { - // See float comparison: http://php.net/manual/en/language.types.float.php - var currentValue = Drupal.webform.stringValue(element, existingValue); - var epsilon = 0.000001; - // An empty string does not match any number. - return currentValue[0] === '' ? false : (Math.abs(parseFloat(currentValue[0]) - parseFloat(ruleValue)) < epsilon); -}; - -Drupal.webform.conditionalOperatorNumericNotEqual = function(element, existingValue, ruleValue) { - // See float comparison: http://php.net/manual/en/language.types.float.php - var currentValue = Drupal.webform.stringValue(element, existingValue); - var epsilon = 0.000001; - // An empty string does not match any number. - return currentValue[0] === '' ? true : (Math.abs(parseFloat(currentValue[0]) - parseFloat(ruleValue)) >= epsilon); -}; - -Drupal.webform.conditionalOperatorNumericGreaterThan = function(element, existingValue, ruleValue) { - var currentValue = Drupal.webform.stringValue(element, existingValue); - return parseFloat(currentValue[0]) > parseFloat(ruleValue); -}; - -Drupal.webform.conditionalOperatorNumericGreaterThanEqual = function(element, existingValue, ruleValue) { - return Drupal.webform.conditionalOperatorNumericGreaterThan(element, existingValue, ruleValue) || - Drupal.webform.conditionalOperatorNumericEqual(element, existingValue, ruleValue); -}; - -Drupal.webform.conditionalOperatorNumericLessThan = function(element, existingValue, ruleValue) { - var currentValue = Drupal.webform.stringValue(element, existingValue); - return parseFloat(currentValue[0]) < parseFloat(ruleValue); -}; - -Drupal.webform.conditionalOperatorNumericLessThanEqual = function(element, existingValue, ruleValue) { - return Drupal.webform.conditionalOperatorNumericLessThan(element, existingValue, ruleValue) || - Drupal.webform.conditionalOperatorNumericEqual(element, existingValue, ruleValue); -}; - -Drupal.webform.conditionalOperatorDateEqual = function(element, existingValue, ruleValue) { - var currentValue = Drupal.webform.dateValue(element, existingValue); - return currentValue === ruleValue; -}; - -Drupal.webform.conditionalOperatorDateNotEqual = function(element, existingValue, ruleValue) { - return !Drupal.webform.conditionalOperatorDateEqual(element, existingValue, ruleValue); -}; - -Drupal.webform.conditionalOperatorDateBefore = function(element, existingValue, ruleValue) { - var currentValue = Drupal.webform.dateValue(element, existingValue); - return (currentValue !== false) && currentValue < ruleValue; -}; - -Drupal.webform.conditionalOperatorDateBeforeEqual = function(element, existingValue, ruleValue) { - var currentValue = Drupal.webform.dateValue(element, existingValue); - return (currentValue !== false) && (currentValue < ruleValue || currentValue === ruleValue); -}; - -Drupal.webform.conditionalOperatorDateAfter = function(element, existingValue, ruleValue) { - var currentValue = Drupal.webform.dateValue(element, existingValue); - return (currentValue !== false) && currentValue > ruleValue; -}; - -Drupal.webform.conditionalOperatorDateAfterEqual = function(element, existingValue, ruleValue) { - var currentValue = Drupal.webform.dateValue(element, existingValue); - return (currentValue !== false) && (currentValue > ruleValue || currentValue === ruleValue); -}; - -Drupal.webform.conditionalOperatorTimeEqual = function(element, existingValue, ruleValue) { - var currentValue = Drupal.webform.timeValue(element, existingValue); - return currentValue === ruleValue; -}; - -Drupal.webform.conditionalOperatorTimeNotEqual = function(element, existingValue, ruleValue) { - return !Drupal.webform.conditionalOperatorTimeEqual(element, existingValue, ruleValue); -}; - -Drupal.webform.conditionalOperatorTimeBefore = function(element, existingValue, ruleValue) { - // Date and time operators intentionally exclusive for "before". - var currentValue = Drupal.webform.timeValue(element, existingValue); - return (currentValue !== false) && (currentValue < ruleValue); -}; - -Drupal.webform.conditionalOperatorTimeBeforeEqual = function(element, existingValue, ruleValue) { - // Date and time operators intentionally exclusive for "before". - var currentValue = Drupal.webform.timeValue(element, existingValue); - return (currentValue !== false) && (currentValue < ruleValue || currentValue === ruleValue); -}; - -Drupal.webform.conditionalOperatorTimeAfter = function(element, existingValue, ruleValue) { - // Date and time operators intentionally inclusive for "after". - var currentValue = Drupal.webform.timeValue(element, existingValue); - return (currentValue !== false) && (currentValue > ruleValue); -}; - -Drupal.webform.conditionalOperatorTimeAfterEqual = function(element, existingValue, ruleValue) { - // Date and time operators intentionally inclusive for "after". - var currentValue = Drupal.webform.timeValue(element, existingValue); - return (currentValue !== false) && (currentValue > ruleValue || currentValue === ruleValue); -}; - -/** - * Utility function to compare values of a select component. - * @param string a - * First select option key to compare - * @param string b - * Second select option key to compare - * @param array options - * Associative array where the a and b are within the keys - * @return integer based upon position of $a and $b in $options - * -N if $a above (<) $b - * 0 if $a = $b - * +N if $a is below (>) $b - */ -Drupal.webform.compare_select = function(a, b, element) { - var optionList = []; - $('option,input:radio,input:checkbox', element).each(function() { - optionList.push($(this).val()); - }) - var a_position = optionList.indexOf(a); - var b_position = optionList.indexOf(b); - if (a_position < 0 && b_position < 0) { - return null; - } - else if (a_position < 0) { - return 1; - } - else if (b_position < 0) { - return -1; - } - else { - return a_position - b_position; - } -} - -/** - * Utility to return current visibility. Uses actual visibility, except for - * hidden components which use the applied disabled class. - */ -Drupal.webform.isVisible = function($element) { - return $element.hasClass('webform-component-hidden') - ? !$element.find('input').first().hasClass('webform-conditional-disabled') - : $element.closest('.webform-conditional-hidden').length == 0; -} - -/** - * Utility function to get a string value from a select/radios/text/etc. field. - */ -Drupal.webform.stringValue = function(element, existingValue) { - var value = []; - if (element) { - var $element = $(element); - if (Drupal.webform.isVisible($element)) { - // Checkboxes and radios. - $element.find('input[type=checkbox]:checked,input[type=radio]:checked').each(function() { - value.push(this.value); - }); - // Select lists. - if (!value.length) { - var selectValue = $element.find('select').val(); - if (selectValue) { - if ($.isArray(selectValue)) { - value = selectValue; - } - else { - value.push(selectValue); - } + return value; + }; + + /** + * Utility function to calculate a second-based timestamp from a time field. + */ + Drupal.webform.dateValue = function (element, existingValue) { + var value = false; + if (element) { + var $element = $(element); + if (Drupal.webform.isVisible($element)) { + var day = $element.find('[name*=day]').val(); + var month = $element.find('[name*=month]').val(); + var year = $element.find('[name*=year]').val(); + // Months are 0 indexed in JavaScript. + if (month) { + month--; + } + if (year !== '' && month !== '' && day !== '') { + value = Date.UTC(year, month, day) / 1000; } - } - // Simple text fields. This check is done last so that the select list in - // select-or-other fields comes before the "other" text field. - if (!value.length) { - $element.find('input:not([type=checkbox],[type=radio]),textarea').each(function() { - value.push(this.value); - }); } } - } - else { - switch ($.type(existingValue)) { - case 'array': - value = existingValue; - break; - case 'string': - value.push(existingValue); - break; - } - } - return value; -}; - -/** - * Utility function to calculate a millisecond timestamp from a time field. - */ -Drupal.webform.dateValue = function(element, existingValue) { - var value = false; - if (element) { - var $element = $(element); - if (Drupal.webform.isVisible($element)) { - var day = $element.find('[name*=day]').val(); - var month = $element.find('[name*=month]').val(); - var year = $element.find('[name*=year]').val(); - // Months are 0 indexed in JavaScript. - if (month) { - month--; + else { + if ($.type(existingValue) === 'array' && existingValue.length) { + existingValue = existingValue[0]; } - if (year !== '' && month !== '' && day !== '') { - value = Date.UTC(year, month, day) / 1000; + if ($.type(existingValue) === 'string') { + existingValue = existingValue.split('-'); + } + if (existingValue.length === 3) { + value = Date.UTC(existingValue[0], existingValue[1], existingValue[2]) / 1000; } } - } - else { - if ($.type(existingValue) === 'array' && existingValue.length) { - existingValue = existingValue[0]; - } - if ($.type(existingValue) === 'string') { - existingValue = existingValue.split('-'); - } - if (existingValue.length === 3) { - value = Date.UTC(existingValue[0], existingValue[1], existingValue[2]) / 1000; + return value; + }; + + /** + * Utility function to calculate a millisecond timestamp from a time field. + */ + Drupal.webform.timeValue = function (element, existingValue) { + var value = false; + if (element) { + var $element = $(element); + if (Drupal.webform.isVisible($element)) { + var hour = $element.find('[name*=hour]').val(); + var minute = $element.find('[name*=minute]').val(); + var ampm = $element.find('[name*=ampm]:checked').val(); + + // Convert to integers if set. + hour = (hour === '') ? hour : parseInt(hour); + minute = (minute === '') ? minute : parseInt(minute); + + if (hour !== '') { + hour = (hour < 12 && ampm == 'pm') ? hour + 12 : hour; + hour = (hour === 12 && ampm == 'am') ? 0 : hour; + } + if (hour !== '' && minute !== '') { + value = Date.UTC(1970, 0, 1, hour, minute) / 1000; + } + } } - } - return value; -}; - -/** - * Utility function to calculate a millisecond timestamp from a time field. - */ -Drupal.webform.timeValue = function(element, existingValue) { - var value = false; - if (element) { - var $element = $(element); - if (Drupal.webform.isVisible($element)) { - var hour = $element.find('[name*=hour]').val(); - var minute = $element.find('[name*=minute]').val(); - var ampm = $element.find('[name*=ampm]:checked').val(); - - // Convert to integers if set. - hour = (hour === '') ? hour : parseInt(hour); - minute = (minute === '') ? minute : parseInt(minute); - - if (hour !== '') { - hour = (hour < 12 && ampm == 'pm') ? hour + 12 : hour; - hour = (hour === 12 && ampm == 'am') ? 0 : hour; + else { + if ($.type(existingValue) === 'array' && existingValue.length) { + existingValue = existingValue[0]; } - if (hour !== '' && minute !== '') { - value = Date.UTC(1970, 0, 1, hour, minute) / 1000; + if ($.type(existingValue) === 'string') { + existingValue = existingValue.split(':'); + } + if (existingValue.length >= 2) { + value = Date.UTC(1970, 0, 1, existingValue[0], existingValue[1]) / 1000; } } - } - else { - if ($.type(existingValue) === 'array' && existingValue.length) { - existingValue = existingValue[0]; - } - if ($.type(existingValue) === 'string') { - existingValue = existingValue.split(':'); - } - if (existingValue.length >= 2) { - value = Date.UTC(1970, 0, 1, existingValue[0], existingValue[1]) / 1000; - } - } - return value; -}; - -/** - * Make a prop shim for jQuery < 1.9. - */ -$.fn.webformProp = function(name, value) { - if (value) { - $.fn.prop ? this.prop(name, true) : this.attr(name, true); - } - else { - $.fn.prop ? this.prop(name, false) : this.removeAttr(name); - } - return this; -} - -/** - * Make a multi-valued val() function for setting checkboxes, radios, and select - * elements. - */ -$.fn.webformVal = function(values) { - this.each(function() { - var $this = $(this); - var value = $this.val(); - var on = $.inArray($this.val(), values) != -1; - if (this.nodeName == 'OPTION') { - $this.webformProp('selected', on ? value : false); + return value; + }; + + /** + * Make a prop shim for jQuery < 1.9. + */ + $.fn.webformProp = $.fn.webformProp || function (name, value) { + if (value) { + return $.fn.prop ? this.prop(name, true) : this.attr(name, true); } else { - $this.val(on ? [value] : false); + return $.fn.prop ? this.prop(name, false) : this.removeAttr(name); } - }); - return this; -} + }; + + /** + * Make a multi-valued val() function for setting checkboxes, radios, and select + * elements. + */ + $.fn.webformVal = function (values) { + this.each(function () { + var $this = $(this); + var value = $this.val(); + var on = $.inArray($this.val(), values) != -1; + if (this.nodeName == 'OPTION') { + $this.webformProp('selected', on ? value : false); + } + else { + $this.val(on ? [value] : false); + } + }); + return this; + }; })(jQuery); diff --git a/profiles/wcm_base/modules/contrib/webform/tests/conditionals.test b/profiles/wcm_base/modules/contrib/webform/tests/conditionals.test index 3f16049e..a13b77ae 100644 --- a/profiles/wcm_base/modules/contrib/webform/tests/conditionals.test +++ b/profiles/wcm_base/modules/contrib/webform/tests/conditionals.test @@ -89,7 +89,7 @@ class WebformConditionalsTestCase extends WebformTestCase { $match_string = (is_array($conditional_values) ? print_r($conditional_values, 1) : $conditional_values); $conditional_string = $should_match ? 'should' : 'should not'; $settings = array( - 'title' => 'Test conditional webform: ' . $component['type'] . ' "' . $input_string .'"' . $conditional_string . ' be ' . $operator . ' "' . $match_string . '"', + 'title' => 'Test conditional webform: ' . $component['type'] . ' "' . $input_string . '"' . $conditional_string . ' be ' . $operator . ' "' . $match_string . '"', 'type' => 'webform', 'webform' => webform_node_defaults(), ); diff --git a/profiles/wcm_base/modules/contrib/webform/views/webform.views.inc b/profiles/wcm_base/modules/contrib/webform/views/webform.views.inc index 9590ae54..79ac3968 100644 --- a/profiles/wcm_base/modules/contrib/webform/views/webform.views.inc +++ b/profiles/wcm_base/modules/contrib/webform/views/webform.views.inc @@ -574,7 +574,7 @@ function webform_views_pre_view($view, $display_id, $args) { if (webform_component_implements($component['type'], 'view_field')) { $new_fields = webform_component_invoke($component['type'], 'view_field', $component, $new_fields); } - foreach($new_fields as $sub_id => $new_field) { + foreach ($new_fields as $sub_id => $new_field) { $field_id = $new_id . ($sub_id ? '_' . $sub_id : ''); $fields[$field_id] = $new_field; $new_columns[$field_id] = $field_id; diff --git a/profiles/wcm_base/modules/contrib/webform/views/webform_handler_field_node_link_edit.inc b/profiles/wcm_base/modules/contrib/webform/views/webform_handler_field_node_link_edit.inc index 2528b9a1..92f777b1 100644 --- a/profiles/wcm_base/modules/contrib/webform/views/webform_handler_field_node_link_edit.inc +++ b/profiles/wcm_base/modules/contrib/webform/views/webform_handler_field_node_link_edit.inc @@ -30,8 +30,8 @@ class webform_handler_field_node_link_edit extends views_handler_field_node_link * Renders the link. */ function render_link($node, $values) { - // Ensure user has access to edit this node. - if (!node_access('update', $node)) { + // Ensure node is webform-enabled and user has access to edit this node. + if (!in_array($node->type, webform_node_types()) || !node_access('update', $node)) { return; } diff --git a/profiles/wcm_base/modules/contrib/webform/views/webform_handler_field_node_link_results.inc b/profiles/wcm_base/modules/contrib/webform/views/webform_handler_field_node_link_results.inc index 0102cc5f..c41bdb0f 100644 --- a/profiles/wcm_base/modules/contrib/webform/views/webform_handler_field_node_link_results.inc +++ b/profiles/wcm_base/modules/contrib/webform/views/webform_handler_field_node_link_results.inc @@ -30,8 +30,8 @@ class webform_handler_field_node_link_results extends views_handler_field_node_l * Renders the link. */ function render_link($node, $values) { - // Ensure user has access to edit this node. - if (!webform_results_access($node)) { + // Ensure node is webform-enabled and user has access node's webform results. + if (!in_array($node->type, webform_node_types()) || !webform_results_access($node)) { return; } @@ -41,7 +41,7 @@ class webform_handler_field_node_link_results extends views_handler_field_node_l } $this->options['alter']['make_link'] = TRUE; - $this->options['alter']['path'] = "node/$node->nid/webform-results". + $this->options['alter']['path'] = "node/$node->nid/webform-results" . (strlen($this->options['subpath']) ? '/' . $this->options['subpath'] : ''); $text = !empty($this->options['text']) ? $this->options['text'] : t('results'); diff --git a/profiles/wcm_base/modules/contrib/webform/webform.api.php b/profiles/wcm_base/modules/contrib/webform/webform.api.php index 88e0a634..131f4ed0 100644 --- a/profiles/wcm_base/modules/contrib/webform/webform.api.php +++ b/profiles/wcm_base/modules/contrib/webform/webform.api.php @@ -121,7 +121,7 @@ function hook_webform_submission_load(&$submissions) { * * @see webform_submission_create() */ -function hook_webform_submission_create($submission, $node, $account, $form_state) { +function hook_webform_submission_create_alter(&$submission, &$node, &$account, &$form_state) { $submission->new_property = TRUE; } @@ -835,6 +835,7 @@ function _webform_defaults_component() { 'optrand' => 0, 'qrand' => 0, 'description' => '', + 'description_above' => FALSE, 'private' => FALSE, 'analysis' => TRUE, ), @@ -948,6 +949,8 @@ function hook_webform_component_render_alter(&$element, &$component) { * Either 'html' or 'text'. Defines the format that the content should be * returned as. Make sure that returned content is run through check_plain() * or other filtering functions when returning HTML. + * @param $submission + * The submission. Used to generate tokens. * @return * A renderable element containing at the very least these properties: * - #title @@ -959,7 +962,7 @@ function hook_webform_component_render_alter(&$element, &$component) { * which will properly format the label and content for use within an e-mail * (such as wrapping the text) or as HTML (ensuring consistent output). */ -function _webform_display_component($component, $value, $format = 'html') { +function _webform_display_component($component, $value, $format = 'html', $submission = array()) { return array( '#title' => $component['name'], '#weight' => $component['weight'], @@ -991,6 +994,27 @@ function hook_webform_component_display_alter(&$element, &$component) { } } +/** + * Performs the conditional action set on an implemented component. + * + * Setting the form element allows form validation functions to see the value + * that webform has set for the given component. + * + * @param array $component + * The webform component array whose value is being set for the currently- + * edited submission. + * @param array $element + * The form element currently being set. + * @param array $form_state + * The form's state. + * @param string $value + * The value to be set, as defined in the conditional action. + */ +function _webform_action_set_component($component, &$element, &$form_state, $value) { + $element['#value'] = $value; + form_set_value($element, $value, $form_state); +} + /** * A hook for changing the input values before saving to the database. * @@ -1333,6 +1357,39 @@ function hook_webform_html_capable_mail_systems_alter(&$systems) { } } +/** + * Define a list of webform exporters. + * + * @return array + * A list of the available exporters provided by the module. + * + * @see webform_webform_exporters() + */ +function hook_webform_exporters() { + $exporters = array( + 'webform_exporter_custom' => array( + 'title' => t('Webform exporter name'), + 'description' => t('The description for this exporter.'), + 'handler' => 'webform_exporter_custom', + 'file' => drupal_get_path('module', 'yourmodule') . '/includes/webform_exporter_custom.inc', + 'weight' => 10, + ), + ); + + return $exporters; +} + +/** + * Modify the list of webform exporters definitions. + * + * @param array &$exporters + * A list of all available webform exporters. + */ +function hook_webform_exporters_alter(&$exporters) { + $exporters['excel']['handler'] = 'customized_excel_exporter'; + $exporters['excel']['file'] = drupal_get_path('module', 'yourmodule') . '/includes/customized_excel_exporter.inc'; +} + /** * @} */ diff --git a/profiles/wcm_base/modules/contrib/webform/webform.drush.inc b/profiles/wcm_base/modules/contrib/webform/webform.drush.inc index 23f0ae5b..057ac451 100644 --- a/profiles/wcm_base/modules/contrib/webform/webform.drush.inc +++ b/profiles/wcm_base/modules/contrib/webform/webform.drush.inc @@ -1,8 +1,10 @@ <?php /** + * @file * Implementation of hook_drush_command(). */ + function webform_drush_command() { $items = array(); @@ -15,8 +17,8 @@ function webform_drush_command() { 'file' => 'The file path to export to (defaults to print to stdout)', 'format' => 'The exporter format to use. Out-of-the-box this may be "delimited" or "excel".', 'delimiter' => 'Delimiter between columns (defaults to site-wide setting). This option may need to be wrapped in quotes. i.e. --delimter="\t".', - 'components' => 'Comma-separated list of components IDs to include.' . "\n" . - 'May also include "serial", "sid", "time", "complete_time", "modified_time", "draft", "ip_address", "uid", and "username".', + 'components' => 'Comma-separated list of component IDs or form keys to include.' . "\n" . + 'May also include "webform_serial", "webform_sid", "webform_time", "webform_complete_time", "webform_modified_time", "webform_draft", "webform_ip_address", "webform_uid", and "webform_username".', 'header-keys' => 'Integer -1 for none, 0 for label (default) or 1 for field key.', 'select-keys' => 'Integer 0 or 1 value. Set to 1 to print select list values by their field keys instead of labels.', 'select-format' => 'Set to "separate" (default) or "compact" to determine how select list values are exported.', @@ -63,6 +65,18 @@ function drush_webform_export($nid = FALSE) { } $options['components'] = is_array($options['components']) ? $options['components'] : explode(',', $options['components']); + // Map form keys to cids. + $form_keys = array(); + foreach ($node->webform['components'] as $cid => $component) { + $form_keys[$component['form_key']] = $cid; + } + foreach ($options['components'] as $key => &$component) { + if (isset($form_keys[$component])) { + $component = $form_keys[$component]; + } + } + unset($component); // Drop PHP reference. + // Get the range options. unset($options['range']['range_type']); foreach (drush_get_merged_prefixed_options('range-') as $option_name => $option_value) { diff --git a/profiles/wcm_base/modules/contrib/webform/webform.info b/profiles/wcm_base/modules/contrib/webform/webform.info index 4676e9cd..a0a36e2b 100644 --- a/profiles/wcm_base/modules/contrib/webform/webform.info +++ b/profiles/wcm_base/modules/contrib/webform/webform.info @@ -40,9 +40,9 @@ files[] = tests/permissions.test files[] = tests/submission.test files[] = tests/webform.test -; Information added by Drupal.org packaging script on 2015-04-29 -version = "7.x-4.8" +; Information added by Drupal.org packaging script on 2015-09-22 +version = "7.x-4.11" core = "7.x" project = "webform" -datestamp = "1430334485" +datestamp = "1442953145" diff --git a/profiles/wcm_base/modules/contrib/webform/webform.install b/profiles/wcm_base/modules/contrib/webform/webform.install index 86f07614..27a1c846 100644 --- a/profiles/wcm_base/modules/contrib/webform/webform.install +++ b/profiles/wcm_base/modules/contrib/webform/webform.install @@ -491,6 +491,14 @@ function webform_schema() { 'type' => 'text', 'not null' => TRUE, ), + 'status' => array( + 'description' => 'Whether this email is enabled.', + 'type' => 'int', + 'unsigned' => TRUE, + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 1, + ), ), 'primary key' => array('nid', 'eid'), ); @@ -734,14 +742,21 @@ function webform_install() { ); $webform_type = node_type_set_defaults($webform_type); node_type_save($webform_type); + // Enable webform components by default on Webform nodes. + variable_set('webform_node_webform', TRUE); + // Now that a webform node type has been created, reset the cache of the + // node types that support webforms. This is needed for tests which will + // create nodes in the same execution. + drupal_static_reset('webform_node_types'); if (variable_get('webform_install_add_body_field', FALSE)) { node_add_body_field($webform_type); } - // Enable webform components by default on Webform nodes. - variable_set('webform_node_webform', TRUE); // Disable comments by default on Webform nodes. variable_set('comment_webform', '0'); } + else { + variable_set('webform_node_types_primary', array()); + } // Note: MS SQL Server does not support size-limited indexes and the column // type (text) is too big to fit inside index size limits. @@ -772,6 +787,7 @@ function webform_uninstall() { variable_del('webform_date_type'); variable_del('webform_export_format'); variable_del('webform_csv_delimiter'); + variable_del('webform_csv_line_ending'); variable_del('webform_export_wordwrap'); variable_del('webform_excel_legacy_exporter'); variable_del('webform_progressbar_style'); @@ -804,7 +820,7 @@ function webform_uninstall() { $query = new EntityFieldQuery(); $results = $query->entityCondition('entity_type', 'node') ->entityCondition('bundle', 'webform') - ->range(0,1) + ->range(0, 1) ->execute(); $instances = field_info_instances('node', 'webform'); unset($instances['body']); @@ -1053,7 +1069,7 @@ function webform_update_7314() { */ function webform_update_7315() { if (!db_field_exists('webform_last_download', 'requested')) { - db_add_field('webform_last_download', 'requested', array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0,)); + db_add_field('webform_last_download', 'requested', array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE, 'default' => 0)); } } @@ -1948,7 +1964,7 @@ function webform_update_7416() { ->execute(); } // Commit the transaction - unset ($txn); + unset($txn); // Now that every submission has a serial number, make serial numbers required. $spec['not null'] = TRUE; @@ -2023,7 +2039,7 @@ function webform_update_7421() { FALSE); variable_set('webform_email_html_capable', $capable); return $capable - ? t('An HTML-capable module is installed. The option to send HTML e-mail is enabled. ') + ? t('An HTML-capable module is installed. The option to send HTML e-mail is enabled.') : t('No commonly-known HTML capable module is installed. The option to send HTML e-mail is disabled.'); } @@ -2126,7 +2142,7 @@ function webform_update_7423() { // Rebuild the registry because this point release contains a new class: WebformConditionals. registry_rebuild(); - + return t('Webform database tables were successfully adjusted to allow more than one action for each conditional.'); } @@ -2260,3 +2276,22 @@ function webform_update_7429() { return t('Webforms will now resume draft submissions on the page where the submitter left off.'); } +/** + * Add a column to the emails table to allow disabling. + */ +function webform_update_7430() { + // Add status column to webform_emails. + if (!db_field_exists('webform_emails', 'status')) { + $spec = array( + 'description' => 'Whether this email is enabled.', + 'type' => 'int', + 'unsigned' => TRUE, + 'size' => 'tiny', + 'not null' => TRUE, + 'default' => 1, + ); + db_add_field('webform_emails', 'status', $spec); + } + + return t('Webform emails may now be disabled.'); +} diff --git a/profiles/wcm_base/modules/contrib/webform/webform.module b/profiles/wcm_base/modules/contrib/webform/webform.module index 5936c261..08c6a619 100644 --- a/profiles/wcm_base/modules/contrib/webform/webform.module +++ b/profiles/wcm_base/modules/contrib/webform/webform.module @@ -514,6 +514,9 @@ function webform_menu_email_load($eid, $nid) { $email['email'] = $_GET['email']; } } + if (isset($_GET['status'])) { + $email['status'] = $_GET['status']; + } } return $email; @@ -613,6 +616,12 @@ function webform_submission_access($node, $submission, $op = 'view', $account = $token_access = $submission && isset($_GET['token']) && $_GET['token'] == webform_get_submission_access_token($submission); + // If access is granted via a token, then allow subsequent submission access + // for anonymous users. + if (!$user->uid && $token_access) { + $_SESSION['webform_submission'][$submission->sid] = $node->nid; + } + $general_access = $access_all || $access_own_submission || $access_node_submissions || $token_access; // Disable the page cache for anonymous users in this access callback, @@ -1085,6 +1094,9 @@ function webform_webform_component_info() { ), 'file' => 'components/number.inc', 'conditional_type' => 'numeric', + 'features' => array( + 'conditional_action_set' => TRUE, + ), ), 'pagebreak' => array( 'label' => t('Page break'), @@ -1392,10 +1404,13 @@ function webform_file_download($uri) { * An array of node type names. */ function webform_node_types() { - $types = array(); - foreach (node_type_get_names() as $type => $name) { - if (variable_get('webform_node_' . $type, FALSE)) { - $types[] = $type; + $types = &drupal_static(__FUNCTION__, NULL); + if (!isset($types)) { + $types = array(); + foreach (node_type_get_names() as $type => $name) { + if (variable_get('webform_node_' . $type, FALSE)) { + $types[] = $type; + } } } return $types; @@ -2084,7 +2099,7 @@ function theme_webform_view_messages($variables) { $message = t('Submissions for this form are closed.'); } elseif ($node->webform['confidential'] && user_is_logged_in()) { - $message = t('This form is confidential. You much <a href="!url">Log out</a> to submit it.', + $message = t('This form is confidential. You must <a href="!url">Log out</a> to submit it.', array('!url' => url('/user/logout', array('query' => array('destination' => request_uri()))))); } // If open and not allowed to submit the form, give an explanation. @@ -2250,7 +2265,8 @@ function webform_block_view($delta = '') { '#node' => $node, '#sid' => $_SESSION['webform_confirmation'][$nid]['sid'], ); - } elseif (strlen(trim(strip_tags($node->webform['confirmation'])))) { + } + elseif (strlen(trim(strip_tags($node->webform['confirmation'])))) { // Display confirmation link drupal status messages, but in the block. $message = webform_replace_tokens($node->webform['confirmation'], $node, @@ -2398,6 +2414,12 @@ function webform_client_form($form, &$form_state, $node, $submission = FALSE, $r form_load_include($form_state, 'inc', 'webform', 'includes/webform.components'); form_load_include($form_state, 'inc', 'webform', 'includes/webform.submissions'); + // For ajax requests, $form_state['values']['details'] is missing. Restore + // from storage, if available, for multi-page forms. + if (empty($form_state['values']['details']) && !empty($form_state['storage']['details'])) { + $form_state['values']['details'] = $form_state['storage']['details']; + } + // If in a multi-step form, a submission ID may be specified in form state. // Load this submission. This allows anonymous users to use auto-save. if (empty($submission) && !empty($form_state['values']['details']['sid'])) { @@ -2504,6 +2526,9 @@ function webform_client_form($form, &$form_state, $node, $submission = FALSE, $r // Allow values from other pages to be sent to browser for conditionals. $form['#conditional_values'] = $input_values; + // Allow components access to most up-to-date values. + $form_state['#conditional_values'] = $input_values; + // For resuming a previous draft, find the next page after the last // validated page. if (!isset($form_state['storage']['page_num']) && $submission && $submission->is_draft && $submission->highest_valid_page) { @@ -2520,11 +2545,11 @@ function webform_client_form($form, &$form_state, $node, $submission = FALSE, $r // Force a preview to avert an unintended submission via Next. $form_state['webform']['preview'] = TRUE; $form_state['webform']['page_count']++; - // The form hasn't been submitted (ever) and the preview code will - // expect $form_state['values']['submitted'] to be set from a previous - // submission, so provide these values here. - $form_state['values']['submitted'] = $input_values; } + // The form hasn't been submitted (ever) and the preview code will + // expect $form_state['values']['submitted'] to be set from a previous + // submission, so provide these values here. + $form_state['values']['submitted'] = $input_values; $form_state['storage']['submitted'] = $input_values; } @@ -2739,7 +2764,7 @@ function _webform_client_form_add_component($node, $component, $component_value, if ($format != 'form') { // This component is display only. $data = empty($input_values[$cid]) ? NULL : $input_values[$cid]; - if ($display_element = webform_component_invoke($component['type'], 'display', $component, $data, $format)) { + if ($display_element = webform_component_invoke($component['type'], 'display', $component, $data, $format, $form['#submission'])) { // Set access based on the private property. $display_element += array('#access' => TRUE); $display_element['#access'] = $display_element['#access'] && $component_access; @@ -2923,16 +2948,35 @@ function _webform_client_form_validate(&$elements, &$form_state, $form_id = NULL $sorter = webform_get_conditional_sorter($form_state['complete form']['#node']); $cid = $elements['#webform_component']['cid']; $page_num = $form_state['values']['details']['page_num']; - // Webform-specific enhancement, only validate the field if it was used in this submission. - // This both skips validation on the field and sets the value of the field to NULL, preventing any dangerous input. - // Short-circuit validation for a hidden component (hidden by rules dependent upon component on previous pages), - // or a component this is dependent upon values on the current page, but is hidden based upon their current values. + // Webform-specific enhancements: + // 1) Only validate the field if it was used in this submission. + // This both skips validation on the field and sets the value of the + // field to NULL, preventing any dangerous input. Short-circuit + // validation for a hidden component (hidden by rules dependent upon + // component on previous pages), or a component this is dependent upon + // values on the current page, but is hidden based upon their current + // values. + // 2) Only valididate if the field has not been set by conditionals. + // The user will be unable to fix the validation without surmising the + // logic and changing the trigger for the conditional. Also, it isn't + // possible to set $element['#value'] without component-specific + // knowledge of how the data is stored because $input_values is already + // webform-normalized to contain values in arrays. if ($sorter->componentVisibility($cid, $page_num) != WebformConditionals::componentShown) { form_set_value($elements, NULL, $form_state); return; } + if ($sorter->componentSet($cid, $page_num)) { + $component = $elements['#webform_component']; + $value = $input_values[$cid]; + $value = is_array($value) ? $value[0] : $value; + // webform_component_invoke cannot be called with reference arguments. Call directly. + // webform_component_invoke($component['type'], 'action_set', $component, $elements, $form_state, $value); + $function = '_webform_action_set_' . $component['type']; + $function($component, $elements, $form_state, $value); + } - // Check for changes in requires status made by conditionals. + // Check for changes in required status made by conditionals. $required = $sorter->componentRequired($cid, $page_num); if (isset($required)) { $elements['#required'] = $required; @@ -3210,6 +3254,9 @@ function webform_client_form_submit($form, &$form_state) { // exist, but webform_get_submission() will not find the draft. So, make a new // submission. if ($sid && $submission = webform_get_submission($node->webform['nid'], $sid)) { + // Store original data on object for use in update hook. + $submission->original = clone $submission; + // Merge with new submission data. The + operator maintains numeric keys. // This maintains existing data with just-submitted data when a user resumes // a submission previously saved as a draft. @@ -3251,6 +3298,11 @@ function webform_client_form_submit($form, &$form_state) { $form_state['values']['details']['sid'] = $sid = webform_submission_insert($node, $submission); $form_state['values']['details']['is_new'] = TRUE; + // Save the new details in storage. When ajax calls for file upload/remove, + // $form_state['values']['details'] is missing. This allows the proper + // submission to be retrieved in webform_client_form. See #2562703. + $form_state['storage']['details'] = $form_state['values']['details']; + // Set a cookie including the server's submission time. The cookie expires // in the length of the interval plus a day to compensate for timezones. $tracking_mode = webform_variable_get('webform_tracking_mode'); @@ -3264,7 +3316,7 @@ function webform_client_form_submit($form, &$form_state) { // Save session information about this submission for anonymous users, // allowing them to access or edit their submissions. if (!$user->uid && user_access('access own webform submissions')) { - $_SESSION['webform_submission'][$form_state['values']['details']['sid']] = $node->nid; + $_SESSION['webform_submission'][$sid] = $node->nid; } } else { @@ -3436,7 +3488,7 @@ function template_preprocess_webform_confirmation(&$vars) { // URL back to form (or same page for in-block confirmations). $vars['url'] = empty($node->webform_block) - ? url('node/'. $node->nid) + ? url('node/' . $node->nid) : url(current_path(), array('query' => drupal_get_query_parameters())); // Progress bar. @@ -3519,9 +3571,10 @@ function template_preprocess_webform_element(&$variables) { // Ensure defaults. $element += array( '#title_display' => 'before', - '#wrapper_attributes' => array( - 'class' => array(), - ), + '#wrapper_attributes' => array(), + ); + $element['#wrapper_attributes'] += array( + 'class' => array(), ); // All elements using this for display only are given the "display" type. @@ -3574,37 +3627,37 @@ function theme_webform_element($variables) { $prefix = isset($element['#field_prefix']) ? '<span class="field-prefix">' . webform_filter_xss($element['#field_prefix']) . '</span> ' : ''; $suffix = isset($element['#field_suffix']) ? ' <span class="field-suffix">' . webform_filter_xss($element['#field_suffix']) . '</span>' : ''; - // managed_file uses a different id, make sure the label points to the correct id. - if (isset($element['#type']) && $element['#type'] === 'managed_file') { - if (!empty($variables['element']['#id'])) { - $variables['element']['#id'] .= '-upload'; - } - } + // Generate description for above or below the field. + $above = !empty($element['#webform_component']['extra']['description_above']); + $description = array( + FALSE => '', + TRUE => !empty($element['#description']) ? ' <div class="description">' . $element['#description'] . "</div>\n" : '', + ); switch ($element['#title_display']) { case 'inline': + $output .= $description[$above]; + $description[$above] = ''; + // FALL THRU. case 'before': case 'invisible': $output .= ' ' . theme('form_element_label', $variables); - $output .= ' ' . $prefix . $element['#children'] . $suffix . "\n"; + $output .= ' ' . $prefix . $description[$above] . $element['#children'] . $suffix . "\n"; break; case 'after': - $output .= ' ' . $prefix . $element['#children'] . $suffix; + $output .= ' ' . $prefix . $description[$above] . $element['#children'] . $suffix; $output .= ' ' . theme('form_element_label', $variables) . "\n"; break; case 'none': case 'attribute': // Output no label and no required marker, only the children. - $output .= ' ' . $prefix . $element['#children'] . $suffix . "\n"; + $output .= ' ' . $prefix . $description[$above] . $element['#children'] . $suffix . "\n"; break; } - if (!empty($element['#description'])) { - $output .= ' <div class="description">' . $element['#description'] . "</div>\n"; - } - + $output .= $description[!$above]; $output .= "</div>\n"; return $output; @@ -3869,7 +3922,7 @@ function webform_replace_tokens($string, $node = NULL, $submission = NULL, $emai $clear = is_bool($sanitize); $string = token_replace($string, $token_data, array('clear' => $clear, 'sanitize' => $sanitize === TRUE)); if (!$clear) { - $string = webform_clear_tokens(check_markup($string, $sanitize)); + $string = webform_replace_tokens_clear(check_markup($string, $sanitize)); } return $string; } @@ -3887,11 +3940,14 @@ function webform_replace_tokens($string, $node = NULL, $submission = NULL, $emai * there is no value provided. These tokens i.e. [current-page:query:*] needs to * be removed to not show up in the output. * + * Note: This function was previously named webform_clear_tokens, which + * conflicted with the webform_clear module, being called as hook_tokens. + * * @param string $text * The text to have its tokens removed. * @see token_replace() */ -function webform_clear_tokens($text) { +function webform_replace_tokens_clear($text) { if (empty($text)) { return $text; } @@ -4142,6 +4198,9 @@ function webform_variable_get($variable) { case 'webform_csv_delimiter': $result = variable_get('webform_csv_delimiter', '\t'); break; + case 'webform_csv_line_ending': + $result = variable_get('webform_csv_line_ending', "\n"); + break; case 'webform_export_wordwrap': $result = variable_get('webform_export_wordwrap', 0); break; @@ -4290,7 +4349,7 @@ function webform_format_email_address($address, $name, $node = NULL, $submission if (webform_component_implements($component['type'], 'options')) { $options = webform_component_invoke($component['type'], 'options', $component); foreach ($name as &$one_name) { - $name = isset($options[$one_name]) ? $options[$one_name] : $name; + $one_name = isset($options[$one_name]) ? $options[$one_name] : $one_name; } unset($one_name); // Drop PHP reference. } @@ -4362,14 +4421,16 @@ function webform_format_email_address($address, $name, $node = NULL, $submission * TRUE if optional. FALSE if required. * @param boolean $allow_multiple * TRUE if a list of emails is allowed. FALSE if only one. + * @param boolean $allow_tokens + * TRUE if any token should be assumed to contain a valid e-mail address. * @param string $format * 'short', 'long', or NULL (for default) format. Long format has a name and * the address in angle brackets. * @return integer|boolean * The number of valid addresses found, or FALSE for an invalid email found. */ -function webform_email_validate(&$emails, $form_name, $allow_empty, $allow_multiple, $format = NULL) { - $nr_valid = webform_valid_email_address($emails, $format); +function webform_email_validate(&$emails, $form_name, $allow_empty, $allow_multiple, $allow_tokens, $format = NULL) { + $nr_valid = webform_valid_email_address($emails, $allow_tokens, $format); if ($nr_valid === FALSE) { form_set_error($form_name, t('The entered e-mail address "@email" does not appear valid.', array('@email' => $emails))); } @@ -4389,6 +4450,8 @@ function webform_email_validate(&$emails, $form_name, $allow_empty, $allow_multi * An email address, a list of comma-separated email addresses. If all the * addresses are valid, the list of trimmed, non-empty emails is returned by * reference. + * @param boolean $allow_tokens + * TRUE if any token should be assumed to contain a valid e-mail address. * @param string $format * 'short', 'long', or NULL (for default) format. Long format has a name and * the address in angle brackets. @@ -4396,10 +4459,17 @@ function webform_email_validate(&$emails, $form_name, $allow_empty, $allow_multi * Returns FALSE if an invalid e-mail address was found, 0 if no email * address(es) were found, or the number of valid e-mail addresses found. */ -function webform_valid_email_address(&$emails, $format = NULL) { +function webform_valid_email_address(&$emails, $allow_tokens = FALSE, $format = NULL) { $email_array = array_filter(array_map('trim', explode(',', $emails))); $count = 0; foreach ($email_array as $email) { + if ($allow_tokens) { + foreach (token_scan($email) as $tokens) { + foreach ($tokens as $token) { + $email = str_replace($token, 'admin@example.com', $email); + } + } + } $matches = webform_parse_email_address($email, $format); if (!valid_email_address($matches['address'])) { return FALSE; diff --git a/profiles/wcm_base/modules/contrib/webform/webform.tokens.inc b/profiles/wcm_base/modules/contrib/webform/webform.tokens.inc index 658177b7..5e49bfb8 100644 --- a/profiles/wcm_base/modules/contrib/webform/webform.tokens.inc +++ b/profiles/wcm_base/modules/contrib/webform/webform.tokens.inc @@ -64,13 +64,15 @@ function webform_token_info() { ); $info['tokens']['submission']['values'] = array( 'name' => t('Webform submission values'), - 'description' => t('<div>Webform tokens from submitted data. Replace the "?" with the "field key", including any parent field keys separated by colons. You can append:</div><ul>' . - '<li>the question key for just that one question (grid components).</li>' . - '<li>the option key for just that one option (grid and select components).' . - '<li>":nolabel" for the value without the label (the default)</li>' . - '<li>":label" for just the label.</li>' . - '<li>":withlabel" for both the label and value together.</li>' . - '<li>":key" for just the key in a key|label pair (grid and select components).</li></ul>'), + 'description' => '<div>' . t('Webform tokens from submitted data. Replace the "?" with the "field key", including any parent field keys separated by colons. You can append:') . '</div>' . + '<ul>' . + '<li>' . t('the question key for just that one question (grid components).') . '</li>' . + '<li>' . t('the option key for just that one option (grid and select components).') . '</li>' . + '<li>' . t('<code>@token</code> for the value without the label (the default).', array('@token' => ':nolabel')) . '</li>' . + '<li>' . t('<code>@token</code> for just the label.', array('@token' => ':label')) . '</li>' . + '<li>' . t('<code>@token</code> for both the label and value together.', array('@token' => ':withlabel')) . '</li>' . + '<li>' . t('<code>@token</code> for just the key in a key|label pair (grid and select components).', array('@token' => ':key')) . '</li>' . + '</ul>', 'dynamic' => TRUE, ); @@ -81,192 +83,204 @@ function webform_token_info() { * Implements hook_tokens(). */ function webform_tokens($type, $tokens, array $data = array(), array $options = array()) { - $replacements = array(); + static $recursion_level = 0; - $url_options = array('absolute' => TRUE); - if (isset($options['language'])) { - $url_options['language'] = $options['language']; - $language_code = $options['language']->language; - } - else { - $language_code = NULL; + // Return early unless submission tokens are needed and there is a submission. + if ($type != 'submission' || empty($data['webform-submission'])) { + return array(); } + // Generate Webform tokens. + $replacements = array(); + + // Prepare all the data that we will likely need more than once. + $submission = $data['webform-submission']; + $node = isset($data['node']) ? $data['node'] : node_load($submission->nid); + $email = isset($data['webform-email']) ? $data['webform-email'] : NULL; $sanitize = !empty($options['sanitize']); + $format = $sanitize ? 'html' : 'text'; + $url_options = isset($options['language']) ? $options['language'] : array('absolute' => TRUE); + $language_code = isset($options['language']) ? $options['language']->language : NULL; + $markup_components = array(); - // Webform tokens (caching globally). - if ($type == 'submission' && !empty($data['webform-submission'])) { - // Prepare all the submission data that we will likely need more than once. - $submission = $data['webform-submission']; - $node = isset($data['node']) ? $data['node'] : node_load($submission->nid); - $email = isset($data['webform-email']) ? $data['webform-email'] : NULL; - $excluded_components = isset($email['excluded_components']) ? $email['excluded_components'] : array(); - $format = $sanitize ? 'html' : 'text'; + // Markup components may use tokens when displayed. Displaying the tokens + // requires rendering the components. Rendering a markup component can then + // cause infinite recursion. To prevent this, markup components are omitted + // from the rendered submission if recursion has been detected. + if ($recursion_level) { + $markup_components = array_keys(array_filter($node->webform['components'], + function ($component) { return $component['type'] == 'markup'; })); + } - // Replace individual tokens that have an exact replacement. - foreach ($tokens as $name => $original) { - switch ($name) { - case 'serial': - $replacements[$original] = $submission->serial ? $submission->serial : ''; - break; - case 'sid': - $replacements[$original] = $submission->sid ? $submission->sid : ''; - break; - case 'access-token': - $replacements[$original] = webform_get_submission_access_token($submission); - break; - case 'date': - $replacements[$original] = format_date($submission->submitted, webform_variable_get('webform_date_type'), '', NULL, $language_code); - break; - case 'completed_date': - if ($submission->completed) { - $replacements[$original] = format_date($submission->completed, webform_variable_get('webform_date_type'), '', NULL, $language_code); - } - break; - case 'modified_date': - $replacements[$original] = format_date($submission->modified, webform_variable_get('webform_date_type'), '', NULL, $language_code); - break; - case 'ip-address': - $replacements[$original] = $sanitize ? check_plain($submission->remote_addr) : $submission->remote_addr; - break; - case 'user': - $account = user_load($submission->uid); - $name = format_username($account); - $replacements[$original] = $sanitize ? check_plain($name) : $name; - break; - case 'url': - $replacements[$original] = $submission->sid ? url("node/{$node->nid}/submission/{$submission->sid}", $url_options) : ''; - break; - case 'edit-url': - $replacements[$original] = $submission->sid ? url("node/{$node->nid}/submission/{$submission->sid}/edit", $url_options) : ''; - break; - case 'values': - $submission_renderable = webform_submission_render($node, $submission, $email, $format, $excluded_components); - $replacements[$original] = drupal_render($submission_renderable); - break; - } + $recursion_level++; + + // Replace individual tokens that have an exact replacement. + foreach ($tokens as $name => $original) { + switch ($name) { + case 'serial': + $replacements[$original] = $submission->serial ? $submission->serial : ''; + break; + case 'sid': + $replacements[$original] = $submission->sid ? $submission->sid : ''; + break; + case 'access-token': + $replacements[$original] = webform_get_submission_access_token($submission); + break; + case 'date': + $replacements[$original] = format_date($submission->submitted, webform_variable_get('webform_date_type'), '', NULL, $language_code); + break; + case 'completed_date': + if ($submission->completed) { + $replacements[$original] = format_date($submission->completed, webform_variable_get('webform_date_type'), '', NULL, $language_code); + } + break; + case 'modified_date': + $replacements[$original] = format_date($submission->modified, webform_variable_get('webform_date_type'), '', NULL, $language_code); + break; + case 'ip-address': + $replacements[$original] = $sanitize ? check_plain($submission->remote_addr) : $submission->remote_addr; + break; + case 'user': + $account = user_load($submission->uid); + $name = format_username($account); + $replacements[$original] = $sanitize ? check_plain($name) : $name; + break; + case 'url': + $replacements[$original] = $submission->sid ? url("node/{$node->nid}/submission/{$submission->sid}", $url_options) : ''; + break; + case 'edit-url': + $replacements[$original] = $submission->sid ? url("node/{$node->nid}/submission/{$submission->sid}/edit", $url_options) : ''; + break; + case 'values': + $excluded_components = isset($email['excluded_components']) ? $email['excluded_components'] : array(); + $excluded_components = array_merge($excluded_components, $markup_components); + $submission_renderable = webform_submission_render($node, $submission, $email, $format, $excluded_components); + $replacements[$original] = drupal_render($submission_renderable); + break; } + } - // Webform submission tokens for individual components. - if ($value_tokens = token_find_with_prefix($tokens, 'values')) { - // Get the full submission renderable without $excluded_components so that - // individually referenced values are available. - $submission_renderable = webform_submission_render($node, $submission, $email, $format); - $available_modifiers = array( - 'label', - 'withlabel', - 'nolabel', - 'key', - ); + // Webform submission tokens for individual components. + if ($value_tokens = token_find_with_prefix($tokens, 'values')) { + // Get the full submission renderable without $excluded_components so that + // individually referenced values are available. + $submission_renderable = webform_submission_render($node, $submission, $email, $format, $markup_components); + $available_modifiers = array( + 'label', + 'withlabel', + 'nolabel', + 'key', + ); - foreach ($node->webform['components'] as $cid => $component) { - // Build the list of parents for this component. - $parents = ($component['pid'] == 0) ? array($component['form_key']) : webform_component_parent_keys($node, $component); - $parent_token = implode(':', $parents); - foreach ($value_tokens as $name => $original) { - if (strpos($name, $parent_token) !== 0) { - // Token not found as a prefix or exact match for this component. - continue; // Token loop continue. - } - // Drill down into the renderable to find the element. - $display_element = $submission_renderable; - foreach ($parents as $parent) { - if (!isset($display_element[$parent])) { - // Sometimes an element won't exist in the submission renderable - // due to conditional logic. If not found, skip that element. - continue 2; // Token loop continue. - } - $display_element = $display_element[$parent]; + foreach ($node->webform['components'] as $cid => $component) { + // Build the list of parents for this component. + $parents = ($component['pid'] == 0) ? array($component['form_key']) : webform_component_parent_keys($node, $component); + $parent_token = implode(':', $parents); + foreach ($value_tokens as $name => $original) { + if (strpos($name, $parent_token) !== 0) { + // Token not found as a prefix or exact match for this component. + continue; // Token loop continue. + } + // Drill down into the renderable to find the element. + $display_element = $submission_renderable; + foreach ($parents as $parent) { + if (!isset($display_element[$parent])) { + // Sometimes an element won't exist in the submission renderable + // due to conditional logic. If not found, skip that element. + continue 2; // Token loop continue. } - // Individual tokens always have access granted even if they're - // not displayed when printing the whole renderable. - $display_element['#access'] = TRUE; + $display_element = $display_element[$parent]; + } + // Individual tokens always have access granted even if they're + // not displayed when printing the whole renderable. + $display_element['#access'] = TRUE; - // For grid components, see if optional question key is present. - $matched_token = $parent_token; - if ($display_element['#webform_component']['type'] === 'grid') { - list($question_key) = explode(':', substr($name, strlen($matched_token) + 1)); - if (strlen($question_key) && isset($display_element[$question_key]['#value'])) { - // Generate a faux select component for this grid question. - $select_component = _webform_defaults_select(); - $select_component['type'] = 'select'; - $select_component['nid'] = $display_element['#webform_component']['nid']; - $select_component['name'] = $display_element['#grid_questions'][$question_key]; - $select_component['extra']['items'] = $display_element['#webform_component']['extra']['options']; - $display_element = _webform_display_select($select_component, $display_element[$question_key]['#value'], $format); - $display_element['#webform_component'] = $select_component; - $matched_token .= ':' . $question_key; - } + // For grid components, see if optional question key is present. + $matched_token = $parent_token; + if ($display_element['#webform_component']['type'] === 'grid') { + list($question_key) = explode(':', substr($name, strlen($matched_token) + 1)); + if (strlen($question_key) && isset($display_element[$question_key]['#value'])) { + // Generate a faux select component for this grid question. + $select_component = _webform_defaults_select(); + $select_component['type'] = 'select'; + $select_component['nid'] = $display_element['#webform_component']['nid']; + $select_component['name'] = $display_element['#grid_questions'][$question_key]; + $select_component['extra']['items'] = $display_element['#webform_component']['extra']['options']; + $display_element = _webform_display_select($select_component, $display_element[$question_key]['#value'], $format); + $display_element['#webform_component'] = $select_component; + $matched_token .= ':' . $question_key; } + } - // For select components, see if the optional option key is present. - if ($display_element['#webform_component']['type'] === 'select') { - list($option_key) = explode(':', substr($name, strlen($matched_token) + 1)); - if (strlen($option_key) && strpos("\n" . $display_element['#webform_component']['extra']['items'], "\n" . $option_key . '|') !== FALSE) { - // Return only this specified option and no other values. - $display_element['#value'] = array_intersect($display_element['#value'], array($option_key)); - $matched_token .= ':' . $option_key; - } + // For select components, see if the optional option key is present. + if ($display_element['#webform_component']['type'] === 'select') { + list($option_key) = explode(':', substr($name, strlen($matched_token) + 1)); + if (strlen($option_key) && strpos("\n" . $display_element['#webform_component']['extra']['items'], "\n" . $option_key . '|') !== FALSE) { + // Return only this specified option and no other values. + $display_element['#value'] = array_intersect($display_element['#value'], array($option_key)); + $matched_token .= ':' . $option_key; } + } - // Assume no modifier (implied 'nolabel'). - $modifier = NULL; - if (strcmp($name, $matched_token) !== 0) { - // Check if this matches the key plus a modifier. - $modifier = substr($name, strrpos($name, ':') + 1); - // TODO: Allow components to provide additional modifiers per - // type, i.e. key, day, hour, minute, etc. - if (strcmp($name, $matched_token . ':' . $modifier) !== 0 || !in_array($modifier, $available_modifiers)) { - // No match. - continue; // Token loop continue. - } + // Assume no modifier (implied 'nolabel'). + $modifier = NULL; + if (strcmp($name, $matched_token) !== 0) { + // Check if this matches the key plus a modifier. + $modifier = substr($name, strrpos($name, ':') + 1); + // TODO: Allow components to provide additional modifiers per + // type, i.e. key, day, hour, minute, etc. + if (strcmp($name, $matched_token . ':' . $modifier) !== 0 || !in_array($modifier, $available_modifiers)) { + // No match. + continue; // Token loop continue. } + } - if ($modifier === 'label') { - $replacements[$original] = webform_filter_xss($display_element['#title']); - } - elseif ($modifier === 'key' && $display_element['#webform_component']['type'] === 'select') { - $values = array(); - foreach ($display_element['#value'] as $value) { - $values[] = webform_filter_xss($value); - } - $replacements[$original] = implode(' ', $values); + if ($modifier === 'label') { + $replacements[$original] = webform_filter_xss($display_element['#title']); + } + elseif ($modifier === 'key' && $display_element['#webform_component']['type'] === 'select') { + $values = array(); + foreach ($display_element['#value'] as $value) { + $values[] = webform_filter_xss($value); } - else { - // Remove theme wrappers for the nolabel modifier. - if ($modifier === 'nolabel' || empty($modifier)) { - $display_element['#theme_wrappers'] = array(); - } - $replacements[$original] = render($display_element); + $replacements[$original] = implode(' ', $values); + } + else { + // Remove theme wrappers for the nolabel modifier. + if ($modifier === 'nolabel' || empty($modifier)) { + $display_element['#theme_wrappers'] = array(); } - // Continue processing tokens in case another modifier is used. + $replacements[$original] = render($display_element); } + // Continue processing tokens in case another modifier is used. } - } - // Chained token relationships. - if ($date_tokens = token_find_with_prefix($tokens, 'date')) { - $replacements += token_generate('date', $date_tokens, array('date' => $submission->submitted), $options); - } - if ($submission->completed && ($date_tokens = token_find_with_prefix($tokens, 'completed_date'))) { - $replacements += token_generate('date', $date_tokens, array('date' => $submission->completed), $options); - } - if ($date_tokens = token_find_with_prefix($tokens, 'modified_date')) { - $replacements += token_generate('date', $date_tokens, array('date' => $submission->modified), $options); - } - if (($user_tokens = token_find_with_prefix($tokens, 'user')) && $account = user_load($submission->uid)) { - $replacements += token_generate('user', $user_tokens, array('user' => $account), $options); + } + + // Chained token relationships. + if ($date_tokens = token_find_with_prefix($tokens, 'date')) { + $replacements += token_generate('date', $date_tokens, array('date' => $submission->submitted), $options); + } + if ($submission->completed && ($date_tokens = token_find_with_prefix($tokens, 'completed_date'))) { + $replacements += token_generate('date', $date_tokens, array('date' => $submission->completed), $options); + } + if ($date_tokens = token_find_with_prefix($tokens, 'modified_date')) { + $replacements += token_generate('date', $date_tokens, array('date' => $submission->modified), $options); + } + if (($user_tokens = token_find_with_prefix($tokens, 'user')) && $account = user_load($submission->uid)) { + $replacements += token_generate('user', $user_tokens, array('user' => $account), $options); + } + if ($submission->sid) { + if ($url_tokens = token_find_with_prefix($tokens, 'url')) { + $replacements += token_generate('url', $url_tokens, array('path' => "node/{$node->nid}/submission/{$submission->sid}"), $options); } - if ($submission->sid) { - if ($url_tokens = token_find_with_prefix($tokens, 'url')) { - $replacements += token_generate('url', $url_tokens, array('path' => "node/{$node->nid}/submission/{$submission->sid}"), $options); - } - if ($url_tokens = token_find_with_prefix($tokens, 'edit-url')) { - $replacements += token_generate('url', $url_tokens, array('path' => "node/{$node->nid}/submission/{$submission->sid}/edit"), $options); - } + if ($url_tokens = token_find_with_prefix($tokens, 'edit-url')) { + $replacements += token_generate('url', $url_tokens, array('path' => "node/{$node->nid}/submission/{$submission->sid}/edit"), $options); } } + $recursion_level--; + return $replacements; } diff --git a/profiles/wcm_base/modules/custom/ocio_landing_page/ocio_landing_page.module b/profiles/wcm_base/modules/custom/ocio_landing_page/ocio_landing_page.module index 54838eef..abcf93e3 100644 --- a/profiles/wcm_base/modules/custom/ocio_landing_page/ocio_landing_page.module +++ b/profiles/wcm_base/modules/custom/ocio_landing_page/ocio_landing_page.module @@ -6,6 +6,47 @@ include_once 'ocio_landing_page.features.inc'; +/** + * Implements hook_permission(). + */ +function ocio_landing_page_permission() { + $type_name = 'Landing Page'; + + return array( + 'create landing page pane' => array( + 'title' => t('Create %type_name panes', array('%type_name' => $type_name)), + ), + 'delete landing page pane' => array( + 'title' => t('Delete %type_name panes', array('%type_name' => $type_name)), + ), + 'move landing page pane' => array( + 'title' => t('Move %type_name panes', array('%type_name' => $type_name)), + ), + ); +} + +/** + * Implements template_preprocess_panels_ipe_pane_wrapper(). + */ +function ocio_landing_page_preprocess_panels_ipe_pane_wrapper(&$vars) { + if (!user_access('delete landing page pane')) { + unset($vars['links']['delete']); + } + + if (!user_access('move landing page pane')) { + $vars['pane']->locks = array('type' => 'immovable'); + } +} + +/** + * Implements template_preprocess_panels_ipe_add_pane_button(). + */ +function ocio_landing_page_preprocess_panels_ipe_add_pane_button(&$vars) { + if (!user_access('create landing page pane')) { + unset($vars['links']['add-pane']); + } +} + /** * Implements hook_form_FORM_ID_alter(). */ diff --git a/profiles/wcm_base/modules/custom/ocio_permissions/ocio_permissions.features.user_permission.inc b/profiles/wcm_base/modules/custom/ocio_permissions/ocio_permissions.features.user_permission.inc index c37ed733..6066bc8c 100644 --- a/profiles/wcm_base/modules/custom/ocio_permissions/ocio_permissions.features.user_permission.inc +++ b/profiles/wcm_base/modules/custom/ocio_permissions/ocio_permissions.features.user_permission.inc @@ -469,6 +469,15 @@ function ocio_permissions_user_default_permissions() { 'module' => 'conditional_fields', ); + // Exported permission: 'administer facets'. + $permissions['administer facets'] = array( + 'name' => 'administer facets', + 'roles' => array( + 'administrator' => 'administrator', + ), + 'module' => 'facetapi', + ); + // Exported permission: 'administer features'. $permissions['administer features'] = array( 'name' => 'administer features', @@ -692,6 +701,7 @@ function ocio_permissions_user_default_permissions() { 'name' => 'administer panelizer node ocio_landing_page content', 'roles' => array( 'administrator' => 'administrator', + 'editor' => 'editor', 'site builder' => 'site builder', 'site manager' => 'site manager', ), @@ -1384,6 +1394,16 @@ function ocio_permissions_user_default_permissions() { 'module' => 'file_entity', ); + // Exported permission: 'create landing page pane'. + $permissions['create landing page pane'] = array( + 'name' => 'create landing page pane', + 'roles' => array( + 'administrator' => 'administrator', + 'site builder' => 'site builder', + ), + 'module' => 'ocio_landing_page', + ); + // Exported permission: 'create new books'. $permissions['create new books'] = array( 'name' => 'create new books', @@ -1712,6 +1732,16 @@ function ocio_permissions_user_default_permissions() { 'module' => 'fieldable_panels_panes', ); + // Exported permission: 'delete landing page pane'. + $permissions['delete landing page pane'] = array( + 'name' => 'delete landing page pane', + 'roles' => array( + 'administrator' => 'administrator', + 'site builder' => 'site builder', + ), + 'module' => 'ocio_landing_page', + ); + // Exported permission: 'delete own article content'. $permissions['delete own article content'] = array( 'name' => 'delete own article content', @@ -2727,6 +2757,16 @@ function ocio_permissions_user_default_permissions() { 'module' => 'ocio_omega_settings', ); + // Exported permission: 'move landing page pane'. + $permissions['move landing page pane'] = array( + 'name' => 'move landing page pane', + 'roles' => array( + 'administrator' => 'administrator', + 'site builder' => 'site builder', + ), + 'module' => 'ocio_landing_page', + ); + // Exported permission: 'notify of path changes'. $permissions['notify of path changes'] = array( 'name' => 'notify of path changes', @@ -3386,6 +3426,7 @@ function ocio_permissions_user_default_permissions() { 'name' => 'use panels in place editing', 'roles' => array( 'administrator' => 'administrator', + 'editor' => 'editor', 'site builder' => 'site builder', 'site manager' => 'site manager', ), diff --git a/profiles/wcm_base/modules/custom/ocio_permissions/ocio_permissions.info b/profiles/wcm_base/modules/custom/ocio_permissions/ocio_permissions.info index f93ab808..963638de 100644 --- a/profiles/wcm_base/modules/custom/ocio_permissions/ocio_permissions.info +++ b/profiles/wcm_base/modules/custom/ocio_permissions/ocio_permissions.info @@ -18,6 +18,7 @@ dependencies[] = draggableviews dependencies[] = ds dependencies[] = ds_extras dependencies[] = ds_ui +dependencies[] = facetapi dependencies[] = features dependencies[] = field_group dependencies[] = fieldable_panels_panes @@ -38,6 +39,7 @@ dependencies[] = node dependencies[] = oauth_common dependencies[] = ocio_admin_menu dependencies[] = ocio_field_bases +dependencies[] = ocio_landing_page dependencies[] = ocio_main_menu dependencies[] = ocio_omega_settings dependencies[] = ocio_siteinfo @@ -116,6 +118,7 @@ features[user_permission][] = administer contexts features[user_permission][] = administer custom breadcrumbs features[user_permission][] = administer default config features[user_permission][] = administer dependencies +features[user_permission][] = administer facets features[user_permission][] = administer features features[user_permission][] = administer fieldable panels panes features[user_permission][] = administer fieldgroups @@ -211,6 +214,7 @@ features[user_permission][] = create fieldable tile_pane features[user_permission][] = create fieldable tile_pane_plus_text_area features[user_permission][] = create fieldable video features[user_permission][] = create files +features[user_permission][] = create landing page pane features[user_permission][] = create new books features[user_permission][] = create ocio_landing_page content features[user_permission][] = create url aliases @@ -240,6 +244,7 @@ features[user_permission][] = delete fieldable text features[user_permission][] = delete fieldable tile_pane features[user_permission][] = delete fieldable tile_pane_plus_text_area features[user_permission][] = delete fieldable video +features[user_permission][] = delete landing page pane features[user_permission][] = delete own article content features[user_permission][] = delete own audio files features[user_permission][] = delete own basic_page content @@ -331,6 +336,7 @@ features[user_permission][] = moderate content from needs_review to draft features[user_permission][] = moderate content from needs_review to published features[user_permission][] = moderate content from published to draft features[user_permission][] = modify ocio theme +features[user_permission][] = move landing page pane features[user_permission][] = notify of path changes features[user_permission][] = oauth authorize any consumers features[user_permission][] = oauth register any consumers diff --git a/profiles/wcm_base/modules/custom/ocio_search/ocio_search.apachesolr_environments.inc b/profiles/wcm_base/modules/custom/ocio_search/ocio_search.apachesolr_environments.inc index fedfb721..3f1f6013 100644 --- a/profiles/wcm_base/modules/custom/ocio_search/ocio_search.apachesolr_environments.inc +++ b/profiles/wcm_base/modules/custom/ocio_search/ocio_search.apachesolr_environments.inc @@ -33,10 +33,7 @@ function ocio_search_apachesolr_environments() { 7 => 'web_form', ), 'file' => array( - 0 => 'audio', - 1 => 'document', - 2 => 'image', - 3 => 'video', + 0 => 'document', ), ); $export['solr'] = $environment; diff --git a/profiles/wcm_base/modules/custom/ocio_user_config/ocio_user_config.features.features_overrides.inc b/profiles/wcm_base/modules/custom/ocio_user_config/ocio_user_config.features.features_overrides.inc new file mode 100644 index 00000000..10abd89c --- /dev/null +++ b/profiles/wcm_base/modules/custom/ocio_user_config/ocio_user_config.features.features_overrides.inc @@ -0,0 +1,49 @@ +<?php +/** + * @file + * ocio_user_config.features.features_overrides.inc + */ + +/** + * Implements hook_features_override_default_overrides(). + */ +function ocio_user_config_features_override_default_overrides() { + // This code is only used for UI in features. Exported alters hooks do the magic. + $overrides = array(); + + // Exported overrides for: views_view + $overrides["views_view.admin_views_user.display|default|display_options|fields|cancel_node"]["DELETED"] = TRUE; + $overrides["views_view.admin_views_user.display|default|display_options|fields|views_bulk_operations|vbo_operations|action::role_delegation_delegate_roles_action"] = array( + 'selected' => 1, + 'postpone_processing' => 0, + 'skip_confirmation' => 0, + 'override_label' => 0, + 'label' => '', + ); + $overrides["views_view.admin_views_user.display|default|display_options|fields|views_bulk_operations|vbo_operations|action::system_block_ip_action"]["DELETED"] = TRUE; + $overrides["views_view.admin_views_user.display|default|display_options|fields|views_bulk_operations|vbo_operations|action::system_goto_action"]["DELETED"] = TRUE; + $overrides["views_view.admin_views_user.display|default|display_options|fields|views_bulk_operations|vbo_operations|action::system_message_action"]["DELETED"] = TRUE; + $overrides["views_view.admin_views_user.display|default|display_options|fields|views_bulk_operations|vbo_operations|action::user_block_user_action|selected"] = 1; + $overrides["views_view.admin_views_user.display|default|display_options|fields|views_bulk_operations|vbo_operations|action::user_block_user_action|skip_confirmation"] = 0; + $overrides["views_view.admin_views_user.display|default|display_options|fields|views_bulk_operations|vbo_operations|action::views_bulk_operations_argument_selector_action"]["DELETED"] = TRUE; + $overrides["views_view.admin_views_user.display|default|display_options|fields|views_bulk_operations|vbo_operations|action::views_bulk_operations_delete_item"]["DELETED"] = TRUE; + $overrides["views_view.admin_views_user.display|default|display_options|fields|views_bulk_operations|vbo_operations|action::views_bulk_operations_modify_action|settings|display_values|_all_"]["DELETED"] = TRUE; + $overrides["views_view.admin_views_user.display|default|display_options|fields|views_bulk_operations|vbo_operations|action::views_bulk_operations_modify_action|settings|display_values|status"] = 'status'; + $overrides["views_view.admin_views_user.display|default|display_options|fields|views_bulk_operations|vbo_operations|action::views_bulk_operations_modify_action|settings|display_values|user::field_contact_group"] = 'user::field_contact_group'; + $overrides["views_view.admin_views_user.display|default|display_options|fields|views_bulk_operations|vbo_operations|action::views_bulk_operations_modify_action|settings|display_values|user::field_display_in_leadership_list"] = 'user::field_display_in_leadership_list'; + $overrides["views_view.admin_views_user.display|default|display_options|fields|views_bulk_operations|vbo_operations|action::views_bulk_operations_modify_action|settings|display_values|user::field_display_on_contact_page"] = 'user::field_display_on_contact_page'; + $overrides["views_view.admin_views_user.display|default|display_options|fields|views_bulk_operations|vbo_operations|action::views_bulk_operations_modify_action|settings|display_values|user::field_job_title"] = 'user::field_job_title'; + $overrides["views_view.admin_views_user.display|default|display_options|fields|views_bulk_operations|vbo_operations|action::views_bulk_operations_modify_action|settings|display_values|user::field_leadership_group"] = 'user::field_leadership_group'; + $overrides["views_view.admin_views_user.display|default|display_options|fields|views_bulk_operations|vbo_operations|action::views_bulk_operations_modify_action|settings|display_values|user::field_phone"] = 'user::field_phone'; + $overrides["views_view.admin_views_user.display|default|display_options|fields|views_bulk_operations|vbo_operations|action::views_bulk_operations_modify_action|settings|display_values|user::field_room_and_building"] = 'user::field_room_and_building'; + $overrides["views_view.admin_views_user.display|default|display_options|fields|views_bulk_operations|vbo_operations|action::views_bulk_operations_modify_action|settings|display_values|user::field_street_address"] = 'user::field_street_address'; + $overrides["views_view.admin_views_user.display|default|display_options|fields|views_bulk_operations|vbo_operations|action::views_bulk_operations_script_action"]["DELETED"] = TRUE; + $overrides["views_view.admin_views_user.display|default|display_options|fields|views_bulk_operations|vbo_operations|action::views_bulk_operations_user_cancel_action"]["DELETED"] = TRUE; + $overrides["views_view.admin_views_user.display|default|display_options|fields|views_bulk_operations|vbo_operations|action::views_bulk_operations_user_roles_action"]["DELETED"] = TRUE; + $overrides["views_view.admin_views_user.display|default|display_options|fields|views_bulk_operations|vbo_settings|row_clickable"] = 1; + $overrides["views_view.admin_views_user.display|default|display_options|query|options|query_tags"] = array( + 0 => 'administerusersbyrole_edit_access', + ); + + return $overrides; +} diff --git a/profiles/wcm_base/modules/custom/ocio_user_config/ocio_user_config.features.inc b/profiles/wcm_base/modules/custom/ocio_user_config/ocio_user_config.features.inc index 41504bf5..d3871201 100644 --- a/profiles/wcm_base/modules/custom/ocio_user_config/ocio_user_config.features.inc +++ b/profiles/wcm_base/modules/custom/ocio_user_config/ocio_user_config.features.inc @@ -15,3 +15,43 @@ function ocio_user_config_ctools_plugin_api($module = NULL, $api = NULL) { return array("version" => "1"); } } + +/** + * Implements hook_views_default_views_alter(). + */ +function ocio_user_config_views_default_views_alter(&$data) { + if (isset($data['admin_views_user'])) { + $data['admin_views_user']->display['default']->display_options['fields']['views_bulk_operations']['vbo_operations']['action::role_delegation_delegate_roles_action'] = array( + 'selected' => 1, + 'postpone_processing' => 0, + 'skip_confirmation' => 0, + 'override_label' => 0, + 'label' => '', + ); /* WAS: '' */ + $data['admin_views_user']->display['default']->display_options['fields']['views_bulk_operations']['vbo_operations']['action::user_block_user_action']['selected'] = 1; /* WAS: 0 */ + $data['admin_views_user']->display['default']->display_options['fields']['views_bulk_operations']['vbo_operations']['action::user_block_user_action']['skip_confirmation'] = 0; /* WAS: 1 */ + $data['admin_views_user']->display['default']->display_options['fields']['views_bulk_operations']['vbo_operations']['action::views_bulk_operations_modify_action']['settings']['display_values']['status'] = 'status'; /* WAS: '' */ + $data['admin_views_user']->display['default']->display_options['fields']['views_bulk_operations']['vbo_operations']['action::views_bulk_operations_modify_action']['settings']['display_values']['user::field_contact_group'] = 'user::field_contact_group'; /* WAS: '' */ + $data['admin_views_user']->display['default']->display_options['fields']['views_bulk_operations']['vbo_operations']['action::views_bulk_operations_modify_action']['settings']['display_values']['user::field_display_in_leadership_list'] = 'user::field_display_in_leadership_list'; /* WAS: '' */ + $data['admin_views_user']->display['default']->display_options['fields']['views_bulk_operations']['vbo_operations']['action::views_bulk_operations_modify_action']['settings']['display_values']['user::field_display_on_contact_page'] = 'user::field_display_on_contact_page'; /* WAS: '' */ + $data['admin_views_user']->display['default']->display_options['fields']['views_bulk_operations']['vbo_operations']['action::views_bulk_operations_modify_action']['settings']['display_values']['user::field_job_title'] = 'user::field_job_title'; /* WAS: '' */ + $data['admin_views_user']->display['default']->display_options['fields']['views_bulk_operations']['vbo_operations']['action::views_bulk_operations_modify_action']['settings']['display_values']['user::field_leadership_group'] = 'user::field_leadership_group'; /* WAS: '' */ + $data['admin_views_user']->display['default']->display_options['fields']['views_bulk_operations']['vbo_operations']['action::views_bulk_operations_modify_action']['settings']['display_values']['user::field_phone'] = 'user::field_phone'; /* WAS: '' */ + $data['admin_views_user']->display['default']->display_options['fields']['views_bulk_operations']['vbo_operations']['action::views_bulk_operations_modify_action']['settings']['display_values']['user::field_room_and_building'] = 'user::field_room_and_building'; /* WAS: '' */ + $data['admin_views_user']->display['default']->display_options['fields']['views_bulk_operations']['vbo_operations']['action::views_bulk_operations_modify_action']['settings']['display_values']['user::field_street_address'] = 'user::field_street_address'; /* WAS: '' */ + $data['admin_views_user']->display['default']->display_options['fields']['views_bulk_operations']['vbo_settings']['row_clickable'] = 1; /* WAS: '' */ + $data['admin_views_user']->display['default']->display_options['query']['options']['query_tags'] = array( + 0 => 'administerusersbyrole_edit_access', + ); /* WAS: '' */ + unset($data['admin_views_user']->display['default']->display_options['fields']['cancel_node']); + unset($data['admin_views_user']->display['default']->display_options['fields']['views_bulk_operations']['vbo_operations']['action::system_block_ip_action']); + unset($data['admin_views_user']->display['default']->display_options['fields']['views_bulk_operations']['vbo_operations']['action::system_goto_action']); + unset($data['admin_views_user']->display['default']->display_options['fields']['views_bulk_operations']['vbo_operations']['action::system_message_action']); + unset($data['admin_views_user']->display['default']->display_options['fields']['views_bulk_operations']['vbo_operations']['action::views_bulk_operations_argument_selector_action']); + unset($data['admin_views_user']->display['default']->display_options['fields']['views_bulk_operations']['vbo_operations']['action::views_bulk_operations_delete_item']); + unset($data['admin_views_user']->display['default']->display_options['fields']['views_bulk_operations']['vbo_operations']['action::views_bulk_operations_modify_action']['settings']['display_values']['_all_']); + unset($data['admin_views_user']->display['default']->display_options['fields']['views_bulk_operations']['vbo_operations']['action::views_bulk_operations_script_action']); + unset($data['admin_views_user']->display['default']->display_options['fields']['views_bulk_operations']['vbo_operations']['action::views_bulk_operations_user_cancel_action']); + unset($data['admin_views_user']->display['default']->display_options['fields']['views_bulk_operations']['vbo_operations']['action::views_bulk_operations_user_roles_action']); + } +} diff --git a/profiles/wcm_base/modules/custom/ocio_user_config/ocio_user_config.info b/profiles/wcm_base/modules/custom/ocio_user_config/ocio_user_config.info index 3ff099c5..32cf3e75 100644 --- a/profiles/wcm_base/modules/custom/ocio_user_config/ocio_user_config.info +++ b/profiles/wcm_base/modules/custom/ocio_user_config/ocio_user_config.info @@ -15,6 +15,31 @@ features[ctools][] = ds:ds:1 features[ctools][] = strongarm:strongarm:1 features[ds_layout_settings][] = user|user|default features[features_api][] = api:2 +features[features_override_items][] = views_view.admin_views_user +features[features_overrides][] = views_view.admin_views_user.display|default|display_options|fields|cancel_node +features[features_overrides][] = views_view.admin_views_user.display|default|display_options|fields|views_bulk_operations|vbo_operations|action::role_delegation_delegate_roles_action +features[features_overrides][] = views_view.admin_views_user.display|default|display_options|fields|views_bulk_operations|vbo_operations|action::system_block_ip_action +features[features_overrides][] = views_view.admin_views_user.display|default|display_options|fields|views_bulk_operations|vbo_operations|action::system_goto_action +features[features_overrides][] = views_view.admin_views_user.display|default|display_options|fields|views_bulk_operations|vbo_operations|action::system_message_action +features[features_overrides][] = views_view.admin_views_user.display|default|display_options|fields|views_bulk_operations|vbo_operations|action::user_block_user_action|selected +features[features_overrides][] = views_view.admin_views_user.display|default|display_options|fields|views_bulk_operations|vbo_operations|action::user_block_user_action|skip_confirmation +features[features_overrides][] = views_view.admin_views_user.display|default|display_options|fields|views_bulk_operations|vbo_operations|action::views_bulk_operations_argument_selector_action +features[features_overrides][] = views_view.admin_views_user.display|default|display_options|fields|views_bulk_operations|vbo_operations|action::views_bulk_operations_delete_item +features[features_overrides][] = views_view.admin_views_user.display|default|display_options|fields|views_bulk_operations|vbo_operations|action::views_bulk_operations_modify_action|settings|display_values|_all_ +features[features_overrides][] = views_view.admin_views_user.display|default|display_options|fields|views_bulk_operations|vbo_operations|action::views_bulk_operations_modify_action|settings|display_values|status +features[features_overrides][] = views_view.admin_views_user.display|default|display_options|fields|views_bulk_operations|vbo_operations|action::views_bulk_operations_modify_action|settings|display_values|user::field_contact_group +features[features_overrides][] = views_view.admin_views_user.display|default|display_options|fields|views_bulk_operations|vbo_operations|action::views_bulk_operations_modify_action|settings|display_values|user::field_display_in_leadership_list +features[features_overrides][] = views_view.admin_views_user.display|default|display_options|fields|views_bulk_operations|vbo_operations|action::views_bulk_operations_modify_action|settings|display_values|user::field_display_on_contact_page +features[features_overrides][] = views_view.admin_views_user.display|default|display_options|fields|views_bulk_operations|vbo_operations|action::views_bulk_operations_modify_action|settings|display_values|user::field_job_title +features[features_overrides][] = views_view.admin_views_user.display|default|display_options|fields|views_bulk_operations|vbo_operations|action::views_bulk_operations_modify_action|settings|display_values|user::field_leadership_group +features[features_overrides][] = views_view.admin_views_user.display|default|display_options|fields|views_bulk_operations|vbo_operations|action::views_bulk_operations_modify_action|settings|display_values|user::field_phone +features[features_overrides][] = views_view.admin_views_user.display|default|display_options|fields|views_bulk_operations|vbo_operations|action::views_bulk_operations_modify_action|settings|display_values|user::field_room_and_building +features[features_overrides][] = views_view.admin_views_user.display|default|display_options|fields|views_bulk_operations|vbo_operations|action::views_bulk_operations_modify_action|settings|display_values|user::field_street_address +features[features_overrides][] = views_view.admin_views_user.display|default|display_options|fields|views_bulk_operations|vbo_operations|action::views_bulk_operations_script_action +features[features_overrides][] = views_view.admin_views_user.display|default|display_options|fields|views_bulk_operations|vbo_operations|action::views_bulk_operations_user_cancel_action +features[features_overrides][] = views_view.admin_views_user.display|default|display_options|fields|views_bulk_operations|vbo_operations|action::views_bulk_operations_user_roles_action +features[features_overrides][] = views_view.admin_views_user.display|default|display_options|fields|views_bulk_operations|vbo_settings|row_clickable +features[features_overrides][] = views_view.admin_views_user.display|default|display_options|query|options|query_tags features[user_permission][] = access user profiles features[user_permission][] = access users overview features[user_permission][] = administer all users @@ -50,3 +75,4 @@ features[user_role][] = site builder features[user_role][] = site manager features[variable][] = views_defaults features_exclude[dependencies][ocio_user_config] = ocio_user_config +features_exclude[dependencies][admin_views] = admin_views diff --git a/profiles/wcm_base/modules/custom/ocio_user_config/ocio_user_config.strongarm.inc b/profiles/wcm_base/modules/custom/ocio_user_config/ocio_user_config.strongarm.inc index 7be4b92a..909b3fa9 100644 --- a/profiles/wcm_base/modules/custom/ocio_user_config/ocio_user_config.strongarm.inc +++ b/profiles/wcm_base/modules/custom/ocio_user_config/ocio_user_config.strongarm.inc @@ -15,7 +15,7 @@ function ocio_user_config_strongarm() { $strongarm->api_version = 1; $strongarm->name = 'views_defaults'; $strongarm->value = array( - 'admin_views_user' => TRUE, + 'admin_views_user' => FALSE, 'workbench_files' => TRUE, ); $export['views_defaults'] = $strongarm; diff --git a/profiles/wcm_base/modules/custom/ocio_web_form/ocio_web_form.info b/profiles/wcm_base/modules/custom/ocio_web_form/ocio_web_form.info index 185c1f1e..6cb2a77a 100644 --- a/profiles/wcm_base/modules/custom/ocio_web_form/ocio_web_form.info +++ b/profiles/wcm_base/modules/custom/ocio_web_form/ocio_web_form.info @@ -28,6 +28,9 @@ features[variable][] = node_options_web_form features[variable][] = node_preview_web_form features[variable][] = node_submitted_web_form features[variable][] = private_web_form +features[variable][] = webform_default_format +features[variable][] = webform_email_address_individual +features[variable][] = webform_email_html_capable features[variable][] = webform_node_web_form features[variable][] = workbench_moderation_default_state_web_form features_exclude[field_base][body] = body diff --git a/profiles/wcm_base/modules/custom/ocio_web_form/ocio_web_form.strongarm.inc b/profiles/wcm_base/modules/custom/ocio_web_form/ocio_web_form.strongarm.inc index 64cfb534..fa263d96 100644 --- a/profiles/wcm_base/modules/custom/ocio_web_form/ocio_web_form.strongarm.inc +++ b/profiles/wcm_base/modules/custom/ocio_web_form/ocio_web_form.strongarm.inc @@ -126,6 +126,27 @@ function ocio_web_form_strongarm() { $strongarm->value = '1'; $export['private_web_form'] = $strongarm; + $strongarm = new stdClass(); + $strongarm->disabled = FALSE; /* Edit this to true to make a default strongarm disabled initially */ + $strongarm->api_version = 1; + $strongarm->name = 'webform_default_format'; + $strongarm->value = '1'; + $export['webform_default_format'] = $strongarm; + + $strongarm = new stdClass(); + $strongarm->disabled = FALSE; /* Edit this to true to make a default strongarm disabled initially */ + $strongarm->api_version = 1; + $strongarm->name = 'webform_email_address_individual'; + $strongarm->value = '0'; + $export['webform_email_address_individual'] = $strongarm; + + $strongarm = new stdClass(); + $strongarm->disabled = FALSE; /* Edit this to true to make a default strongarm disabled initially */ + $strongarm->api_version = 1; + $strongarm->name = 'webform_email_html_capable'; + $strongarm->value = 1; + $export['webform_email_html_capable'] = $strongarm; + $strongarm = new stdClass(); $strongarm->disabled = FALSE; /* Edit this to true to make a default strongarm disabled initially */ $strongarm->api_version = 1; diff --git a/profiles/wcm_base/themes/ocio_omega_base/css/ocio-omega-base.no-query.css b/profiles/wcm_base/themes/ocio_omega_base/css/ocio-omega-base.no-query.css index 85d167ab..b208ae34 100644 --- a/profiles/wcm_base/themes/ocio_omega_base/css/ocio-omega-base.no-query.css +++ b/profiles/wcm_base/themes/ocio_omega_base/css/ocio-omega-base.no-query.css @@ -2268,6 +2268,7 @@ div.workbench-info-block #edit-submit { } .l-region--hero-wrapper .field--name-field-banner-image img { display: block; + width: 100%; } /* Styles for main menu */ @@ -3296,6 +3297,7 @@ a#skip:active:hover, a#skip:focus:hover { } .l-region--pre-footer .field--name-field-pre-footer-banner-image img { display: block; + width: 100%; } .front .view-featured-slideshow { diff --git a/profiles/wcm_base/themes/ocio_omega_base/css/ocio-omega-base.styles.css b/profiles/wcm_base/themes/ocio_omega_base/css/ocio-omega-base.styles.css index 12b4e4ef..669f93cf 100644 --- a/profiles/wcm_base/themes/ocio_omega_base/css/ocio-omega-base.styles.css +++ b/profiles/wcm_base/themes/ocio_omega_base/css/ocio-omega-base.styles.css @@ -2310,6 +2310,7 @@ div.workbench-info-block #edit-submit { } .l-region--hero-wrapper .field--name-field-banner-image img { display: block; + width: 100%; } /* Styles for main menu */ @@ -3342,6 +3343,7 @@ a#skip:active:hover, a#skip:focus:hover { } .l-region--pre-footer .field--name-field-pre-footer-banner-image img { display: block; + width: 100%; } .front .view-featured-slideshow { diff --git a/profiles/wcm_base/themes/ocio_omega_base/preprocess/page.preprocess.inc b/profiles/wcm_base/themes/ocio_omega_base/preprocess/page.preprocess.inc index 059ac97f..6da41351 100644 --- a/profiles/wcm_base/themes/ocio_omega_base/preprocess/page.preprocess.inc +++ b/profiles/wcm_base/themes/ocio_omega_base/preprocess/page.preprocess.inc @@ -25,18 +25,26 @@ function ocio_omega_base_preprocess_page(&$vars) { ); $top_banner = field_view_field('node', $vars['node'], 'field_banner_image'); - $top_banner_text = field_view_field('node', $vars['node'], 'field_banner_image_text'); + if (!empty($top_banner)) { + $top_banner['#label_display'] = 'hidden'; + $top_banner[0]['#item']['alt'] = ""; - $top_banner['#label_display'] = 'hidden'; + $vars['page']['hero']['top_banner']['#markup'] = drupal_render($top_banner); - $vars['page']['hero']['top_banner']['#markup'] = drupal_render($top_banner); + $top_banner_text = field_view_field('node', $vars['node'], 'field_banner_image_text'); + if (!empty($top_banner_text) && !empty($vars['node']->field_banner_image_text_color) && !empty($vars['node']->field_banner_image_text_location)) { + $top_banner_text['#label_display'] = 'hidden'; - if (!empty($vars['node']->field_banner_image_text_color) && !empty($vars['node']->field_banner_image_text_location)) { - $top_banner_text['#label_display'] = 'hidden'; - $vars['page']['hero']['top_banner']['#markup'] .= drupal_render($top_banner_text); + $vars['page']['hero']['top_banner']['#markup'] .= drupal_render($top_banner_text); + } } + $bottom_banner = field_view_field('node', $vars['node'], 'field_pre_footer_banner_image'); - $bottom_banner['#label_display'] = 'hidden'; - $vars['page']['pre_footer']['bottom_banner']['#markup'] = drupal_render($bottom_banner); + if (!empty($bottom_banner)) { + $bottom_banner['#label_display'] = 'hidden'; + $bottom_banner[0]['#item']['alt'] = ""; + + $vars['page']['pre_footer']['bottom_banner']['#markup'] = drupal_render($bottom_banner); + } } } diff --git a/profiles/wcm_base/themes/ocio_omega_base/sass/components/regions/_hero.scss b/profiles/wcm_base/themes/ocio_omega_base/sass/components/regions/_hero.scss index 116cac16..297a37e2 100644 --- a/profiles/wcm_base/themes/ocio_omega_base/sass/components/regions/_hero.scss +++ b/profiles/wcm_base/themes/ocio_omega_base/sass/components/regions/_hero.scss @@ -4,6 +4,7 @@ .field--name-field-banner-image { img { display: block; + width: 100%; } } } diff --git a/profiles/wcm_base/themes/ocio_omega_base/sass/components/regions/_pre-footer.scss b/profiles/wcm_base/themes/ocio_omega_base/sass/components/regions/_pre-footer.scss index 0881b56a..6aca1062 100644 --- a/profiles/wcm_base/themes/ocio_omega_base/sass/components/regions/_pre-footer.scss +++ b/profiles/wcm_base/themes/ocio_omega_base/sass/components/regions/_pre-footer.scss @@ -1,3 +1,4 @@ .l-region--pre-footer .field--name-field-pre-footer-banner-image img { display: block; + width: 100%; } diff --git a/profiles/wcm_base/themes/ocio_omega_base/templates/regions/region--masthead.tpl.php b/profiles/wcm_base/themes/ocio_omega_base/templates/regions/region--masthead.tpl.php index 8d83271a..a74c94c4 100644 --- a/profiles/wcm_base/themes/ocio_omega_base/templates/regions/region--masthead.tpl.php +++ b/profiles/wcm_base/themes/ocio_omega_base/templates/regions/region--masthead.tpl.php @@ -48,7 +48,7 @@ <?php endif; ?> </div><!--/site-name--> <div id="site-logo"> - <a href="http://osu.edu" alt="The Ohio State University" target="_blank"> + <a href="http://osu.edu" target="_blank"> <img src="<?php print $logo; ?>" alt=""> </a> </div><!--/site-logo--> diff --git a/profiles/wcm_base/themes/ocio_omega_base/templates/system/html.tpl.php b/profiles/wcm_base/themes/ocio_omega_base/templates/system/html.tpl.php index 4fa4378e..7afed328 100644 --- a/profiles/wcm_base/themes/ocio_omega_base/templates/system/html.tpl.php +++ b/profiles/wcm_base/themes/ocio_omega_base/templates/system/html.tpl.php @@ -28,7 +28,6 @@ <?php print $scripts; ?> </head> <body<?php print $attributes;?>> - <a href="#main-content" class="element-invisible element-focusable"><?php print t('Skip to main content'); ?></a> <?php print $page_top; ?> <?php print $page; ?> <?php print $page_bottom; ?> diff --git a/profiles/wcm_base/wcm_base.info b/profiles/wcm_base/wcm_base.info index 1b051aaf..b449fe58 100644 --- a/profiles/wcm_base/wcm_base.info +++ b/profiles/wcm_base/wcm_base.info @@ -54,6 +54,8 @@ dependencies[] = ds_forms dependencies[] = ds_ui dependencies[] = flexslider dependencies[] = flexslider_views +dependencies[] = mailsystem +dependencies[] = mimemail dependencies[] = override_node_options dependencies[] = panels_accordion dependencies[] = private diff --git a/profiles/wcm_base/wcm_base.make b/profiles/wcm_base/wcm_base.make index a2736457..cdff2583 100644 --- a/profiles/wcm_base/wcm_base.make +++ b/profiles/wcm_base/wcm_base.make @@ -51,6 +51,12 @@ projects[fieldable_panels_panes][subdir] = contrib projects[google_analytics][version] = 2.1 projects[google_analytics][subdir] = contrib +projects[mailsystem][version] = 2.34 +projects[mailsystem][subdir] = contrib + +projects[mimemail][version] = 1.0-beta4 +projects[mimemail][subdir] = contrib + projects[media][version] = 2.0-beta1 projects[media][subdir] = contrib projects[media][patch][2093435] = http://drupal.org/files/issues/media-js-dialog-issues-2093435-21.patch @@ -99,7 +105,7 @@ projects[views_accordion][subdir] = contrib projects[views_bulk_operations][version] = 3.3 projects[views_bulk_operations][subdir] = contrib -projects[webform][version] = 4.8 +projects[webform][version] = 4.11 projects[webform][subdir] = contrib -- GitLab