EMailTemplate.php 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * @copyright 2017, Morris Jobke <hey@morrisjobke.de>
  5. * @copyright 2017, Lukas Reschke <lukas@statuscode.ch>
  6. *
  7. * @author Bjoern Schiessle <bjoern@schiessle.org>
  8. * @author brad2014 <brad2014@users.noreply.github.com>
  9. * @author Brad Rubenstein <brad@wbr.tech>
  10. * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  11. * @author Jan-Christoph Borchardt <hey@jancborchardt.net>
  12. * @author Joas Schilling <coding@schilljs.com>
  13. * @author Julius Härtl <jus@bitgrid.net>
  14. * @author Liam JACK <liamjack@users.noreply.github.com>
  15. * @author Lukas Reschke <lukas@statuscode.ch>
  16. * @author medcloud <42641918+medcloud@users.noreply.github.com>
  17. * @author Morris Jobke <hey@morrisjobke.de>
  18. * @author Roeland Jago Douma <roeland@famdouma.nl>
  19. * @author Tomasz Paluszkiewicz <tomasz.paluszkiewicz@gmail.com>
  20. *
  21. * @license GNU AGPL version 3 or any later version
  22. *
  23. * This program is free software: you can redistribute it and/or modify
  24. * it under the terms of the GNU Affero General Public License as
  25. * published by the Free Software Foundation, either version 3 of the
  26. * License, or (at your option) any later version.
  27. *
  28. * This program is distributed in the hope that it will be useful,
  29. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  30. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  31. * GNU Affero General Public License for more details.
  32. *
  33. * You should have received a copy of the GNU Affero General Public License
  34. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  35. *
  36. */
  37. namespace OC\Mail;
  38. use OCP\Defaults;
  39. use OCP\IURLGenerator;
  40. use OCP\L10N\IFactory;
  41. use OCP\Mail\IEMailTemplate;
  42. /**
  43. * Class EMailTemplate
  44. *
  45. * addBodyText and addBodyButtonGroup automatically opens the body
  46. * addFooter, renderHtml, renderText automatically closes the body and the HTML if opened
  47. *
  48. * @package OC\Mail
  49. */
  50. class EMailTemplate implements IEMailTemplate {
  51. protected string $subject = '';
  52. protected string $htmlBody = '';
  53. protected string $plainBody = '';
  54. /** indicated if the header is added */
  55. protected bool $headerAdded = false;
  56. /** indicated if the body is already opened */
  57. protected bool $bodyOpened = false;
  58. /** indicated if there is a list open in the body */
  59. protected bool $bodyListOpened = false;
  60. /** indicated if the footer is added */
  61. protected bool $footerAdded = false;
  62. protected string $head = <<<EOF
  63. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  64. <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en" style="-webkit-font-smoothing:antialiased;background:#fff!important">
  65. <head>
  66. <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  67. <meta name="viewport" content="width=device-width">
  68. <title></title>
  69. <style type="text/css">@media only screen{html{min-height:100%;background:#fff}}@media only screen and (max-width:610px){table.body img{width:auto;height:auto}table.body center{min-width:0!important}table.body .container{width:95%!important}table.body .columns{height:auto!important;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;padding-left:30px!important;padding-right:30px!important}th.small-12{display:inline-block!important;width:100%!important}table.menu{width:100%!important}table.menu td,table.menu th{width:auto!important;display:inline-block!important}table.menu.vertical td,table.menu.vertical th{display:block!important}table.menu[align=center]{width:auto!important}}</style>
  70. </head>
  71. <body style="-moz-box-sizing:border-box;-ms-text-size-adjust:100%;-webkit-box-sizing:border-box;-webkit-font-smoothing:antialiased;-webkit-text-size-adjust:100%;margin:0;background:#fff!important;box-sizing:border-box;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0;min-width:100%;padding:0;text-align:left;width:100%!important">
  72. <span class="preheader" style="color:#F5F5F5;display:none!important;font-size:1px;line-height:1px;max-height:0;max-width:0;mso-hide:all!important;opacity:0;overflow:hidden;visibility:hidden">
  73. </span>
  74. <table class="body" style="-webkit-font-smoothing:antialiased;margin:0;background:#fff;border-collapse:collapse;border-spacing:0;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0;padding:0;text-align:left;vertical-align:top;width:100%">
  75. <tr style="padding:0;text-align:left;vertical-align:top">
  76. <td class="center" align="center" valign="top" style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:1.3;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word">
  77. <center data-parsed="" style="min-width:580px;width:100%">
  78. EOF;
  79. protected string $tail = <<<EOF
  80. </center>
  81. </td>
  82. </tr>
  83. </table>
  84. <!-- prevent Gmail on iOS font size manipulation -->
  85. <div style="display:none;white-space:nowrap;font:15px courier;line-height:0">&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;</div>
  86. </body>
  87. </html>
  88. EOF;
  89. protected string $header = <<<EOF
  90. <table align="center" class="wrapper header float-center" style="Margin:0 auto;background:#fff;border-collapse:collapse;border-spacing:0;float:none;margin:0 auto;padding:0;text-align:center;vertical-align:top;width:100%%">
  91. <tr style="padding:0;text-align:left;vertical-align:top">
  92. <td class="wrapper-inner" style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:1.3;margin:0;padding:20px;text-align:left;vertical-align:top;word-wrap:break-word">
  93. <table align="center" class="container" style="Margin:0 auto;background:0 0;border-collapse:collapse;border-spacing:0;margin:0 auto;padding:0;text-align:inherit;vertical-align:top;width:150px">
  94. <tbody>
  95. <tr style="padding:0;text-align:left;vertical-align:top">
  96. <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:1.3;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word">
  97. <table class="row collapse" style="border-collapse:collapse;border-spacing:0;display:table;padding:0;position:relative;text-align:left;vertical-align:top;width:100%%">
  98. <tbody>
  99. <tr style="padding:0;text-align:left;vertical-align:top">
  100. <center data-parsed="" style="background-color:%s;min-width:175px;max-height:175px; padding:35px 0px;border-radius:200px">
  101. <img class="logo float-center" src="%s" alt="%s" align="center" style="-ms-interpolation-mode:bicubic;clear:both;display:block;float:none;margin:0 auto;outline:0;text-align:center;text-decoration:none;max-height:105px;max-width:105px;width:auto;height:auto">
  102. </center>
  103. </tr>
  104. </tbody>
  105. </table>
  106. </td>
  107. </tr>
  108. </tbody>
  109. </table>
  110. </td>
  111. </tr>
  112. </table>
  113. <table class="spacer float-center" style="Margin:0 auto;border-collapse:collapse;border-spacing:0;float:none;margin:0 auto;padding:0;text-align:center;vertical-align:top;width:100%%">
  114. <tbody>
  115. <tr style="padding:0;text-align:left;vertical-align:top">
  116. <td height="40px" style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#0a0a0a;font-size:80px;font-weight:400;hyphens:auto;line-height:80px;margin:0;mso-line-height-rule:exactly;padding:0;text-align:left;vertical-align:top;word-wrap:break-word">&#xA0;</td>
  117. </tr>
  118. </tbody>
  119. </table>
  120. EOF;
  121. protected string $heading = <<<EOF
  122. <table align="center" class="container main-heading float-center" style="Margin:0 auto;background:0 0!important;border-collapse:collapse;border-spacing:0;float:none;margin:0 auto;padding:0;text-align:center;vertical-align:top;width:580px">
  123. <tbody>
  124. <tr style="padding:0;text-align:left;vertical-align:top">
  125. <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:1.3;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word">
  126. <h1 class="text-center" style="Margin:0;Margin-bottom:10px;color:inherit;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:24px;font-weight:400;line-height:1.3;margin:0;padding:0;text-align:center;word-wrap:normal">%s</h1>
  127. </td>
  128. </tr>
  129. </tbody>
  130. </table>
  131. <table class="spacer float-center" style="Margin:0 auto;border-collapse:collapse;border-spacing:0;float:none;margin:0 auto;padding:0;text-align:center;vertical-align:top;width:100%%">
  132. <tbody>
  133. <tr style="padding:0;text-align:left;vertical-align:top">
  134. <td height="36px" style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#0a0a0a;font-size:40px;font-weight:400;hyphens:auto;line-height:36px;margin:0;mso-line-height-rule:exactly;padding:0;text-align:left;vertical-align:top;word-wrap:break-word">&#xA0;</td>
  135. </tr>
  136. </tbody>
  137. </table>
  138. EOF;
  139. protected string $bodyBegin = <<<EOF
  140. <table align="center" class="wrapper content float-center" style="Margin:0 auto;border-collapse:collapse;border-spacing:0;float:none;margin:0 auto;padding:0;text-align:center;vertical-align:top;width:100%">
  141. <tr style="padding:0;text-align:left;vertical-align:top">
  142. <td class="wrapper-inner" style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:1.3;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word">
  143. <table align="center" class="container" style="Margin:0 auto;background:#fff;border-collapse:collapse;border-spacing:0;margin:0 auto;padding:0;text-align:inherit;vertical-align:top;width:580px">
  144. <tbody>
  145. <tr style="padding:0;text-align:left;vertical-align:top">
  146. <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:1.3;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word">
  147. EOF;
  148. protected string $bodyText = <<<EOF
  149. <table class="row description" style="border-collapse:collapse;border-spacing:0;display:table;padding:0;position:relative;text-align:left;vertical-align:top;width:100%%">
  150. <tbody>
  151. <tr style="padding:0;text-align:left;vertical-align:top">
  152. <th class="small-12 large-12 columns first last" style="Margin:0 auto;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0 auto;padding:0;padding-bottom:30px;padding-left:30px;padding-right:30px;text-align:left;width:550px">
  153. <table style="border-collapse:collapse;border-spacing:0;padding:0;text-align:left;vertical-align:top;width:100%%">
  154. <tr style="padding:0;text-align:left;vertical-align:top">
  155. <th style="Margin:0;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0;padding:0;text-align:left">
  156. <p style="Margin:0;Margin-bottom:10px;color:#777;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0;margin-bottom:10px;padding:0;text-align:center">%s</p>
  157. </th>
  158. <th class="expander" style="Margin:0;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0;padding:0!important;text-align:left;visibility:hidden;width:0"></th>
  159. </tr>
  160. </table>
  161. </th>
  162. </tr>
  163. </tbody>
  164. </table>
  165. EOF;
  166. // note: listBegin (like bodyBegin) is not processed through sprintf, so "%" is not escaped as "%%". (bug #12151)
  167. protected string $listBegin = <<<EOF
  168. <table class="row description" style="border-collapse:collapse;border-spacing:0;display:table;padding:0;position:relative;text-align:left;vertical-align:top;width:100%">
  169. <tbody>
  170. <tr style="padding:0;text-align:left;vertical-align:top">
  171. <th class="small-12 large-12 columns first last" style="Margin:0 auto;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0 auto;padding:0;padding-bottom:30px;padding-left:30px;padding-right:30px;text-align:left;width:550px">
  172. <table style="border-collapse:collapse;border-spacing:0;padding:0;text-align:left;vertical-align:top;width:100%">
  173. EOF;
  174. protected string $listItem = <<<EOF
  175. <tr style="padding:0;text-align:left;vertical-align:top">
  176. <td style="Margin:0;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0;padding:0;text-align:left;width:15px;">
  177. <p class="text-left" style="Margin:0;Margin-bottom:10px;color:#777;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0;margin-bottom:10px;padding:0;padding-left:10px;text-align:left">%s</p>
  178. </td>
  179. <td style="Margin:0;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0;padding:0;text-align:left">
  180. <p class="text-left" style="Margin:0;Margin-bottom:10px;color:#555;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0;margin-bottom:10px;padding:0;padding-left:10px;text-align:left">%s</p>
  181. </td>
  182. <td class="expander" style="Margin:0;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0;padding:0!important;text-align:left;visibility:hidden;width:0"></td>
  183. </tr>
  184. EOF;
  185. protected string $listEnd = <<<EOF
  186. </table>
  187. </th>
  188. </tr>
  189. </tbody>
  190. </table>
  191. EOF;
  192. protected string $buttonGroup = <<<EOF
  193. <table class="spacer" style="border-collapse:collapse;border-spacing:0;padding:0;text-align:left;vertical-align:top;width:100%%">
  194. <tbody>
  195. <tr style="padding:0;text-align:left;vertical-align:top">
  196. <td height="50px" style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:50px;font-weight:400;hyphens:auto;line-height:50px;margin:0;mso-line-height-rule:exactly;padding:0;text-align:left;vertical-align:top;word-wrap:break-word">&#xA0;</td>
  197. </tr>
  198. </tbody>
  199. </table>
  200. <table align="center" class="row btn-group" style="border-collapse:collapse;border-spacing:0;display:table;padding:0;position:relative;text-align:left;vertical-align:top;width:100%%">
  201. <tbody>
  202. <tr style="padding:0;text-align:left;vertical-align:top">
  203. <th class="small-12 large-12 columns first last" style="Margin:0 auto;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0 auto;padding:0;padding-bottom:30px;padding-left:30px;padding-right:30px;text-align:left;width:550px">
  204. <table style="border-collapse:collapse;border-spacing:0;padding:0;text-align:left;vertical-align:top;width:100%%">
  205. <tr style="padding:0;text-align:left;vertical-align:top">
  206. <th style="Margin:0;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0;padding:0;text-align:left">
  207. <center data-parsed="" style="min-width:490px;width:100%%">
  208. <table class="button btn default primary float-center" style="Margin:0 0 30px 0;border-collapse:collapse;border-spacing:0;display:inline-block;float:none;margin:0 0 30px 0;margin-right:15px;max-height:60px;max-width:300px;padding:0;text-align:center;vertical-align:top;width:auto;background:%1\$s;background-color:%1\$s;color:#fefefe;">
  209. <tr style="padding:0;text-align:left;vertical-align:top">
  210. <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:1.3;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word">
  211. <table style="border-collapse:collapse;border-spacing:0;padding:0;text-align:left;vertical-align:top;width:100%%">
  212. <tr style="padding:0;text-align:left;vertical-align:top">
  213. <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border:0 solid %2\$s;border-collapse:collapse!important;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:1.3;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word">
  214. <a href="%3\$s" style="Margin:0;border:0 solid %4\$s;border-radius:2px;color:%5\$s;display:inline-block;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:regular;line-height:1.3;margin:0;padding:10px 25px 10px 25px;text-align:left;outline:1px solid %6\$s;text-decoration:none">%7\$s</a>
  215. </td>
  216. </tr>
  217. </table>
  218. </td>
  219. </tr>
  220. </table>
  221. <table class="button btn default secondary float-center" style="Margin:0 0 30px 0;border-collapse:collapse;border-spacing:0;display:inline-block;float:none;margin:0 0 30px 0;max-height:40px;max-width:300px;padding:0;text-align:center;vertical-align:top;width:auto">
  222. <tr style="padding:0;text-align:left;vertical-align:top">
  223. <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:1.3;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word">
  224. <table style="border-collapse:collapse;border-spacing:0;padding:0;text-align:left;vertical-align:top;width:100%%">
  225. <tr style="padding:0;text-align:left;vertical-align:top">
  226. <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;background:#777;border:0 solid #777;border-collapse:collapse!important;color:#fefefe;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:1.3;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word">
  227. <a href="%8\$s" style="Margin:0;background-color:#fff;border:0 solid #777;border-radius:2px;color:#6C6C6C!important;display:inline-block;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:regular;line-height:1.3;margin:0;outline:1px solid #CBCBCB;padding:10px 25px 10px 25px;text-align:left;text-decoration:none">%9\$s</a>
  228. </td>
  229. </tr>
  230. </table>
  231. </td>
  232. </tr>
  233. </table>
  234. </center>
  235. </th>
  236. <th class="expander" style="Margin:0;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0;padding:0!important;text-align:left;visibility:hidden;width:0"></th>
  237. </tr>
  238. </table>
  239. </th>
  240. </tr>
  241. </tbody>
  242. </table>
  243. EOF;
  244. protected string $button = <<<EOF
  245. <table class="spacer" style="border-collapse:collapse;border-spacing:0;padding:0;text-align:left;vertical-align:top;width:100%%">
  246. <tbody>
  247. <tr style="padding:0;text-align:left;vertical-align:top">
  248. <td height="50px" style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:50px;font-weight:400;hyphens:auto;line-height:50px;margin:0;mso-line-height-rule:exactly;padding:0;text-align:left;vertical-align:top;word-wrap:break-word">&#xA0;</td>
  249. </tr>
  250. </tbody>
  251. </table>
  252. <table align="center" class="row btn-group" style="border-collapse:collapse;border-spacing:0;display:table;padding:0;position:relative;text-align:left;vertical-align:top;width:100%%">
  253. <tbody>
  254. <tr style="padding:0;text-align:left;vertical-align:top">
  255. <th class="small-12 large-12 columns first last" style="Margin:0 auto;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0 auto;padding:0;padding-bottom:30px;padding-left:30px;padding-right:30px;text-align:left;width:550px">
  256. <table style="border-collapse:collapse;border-spacing:0;padding:0;text-align:left;vertical-align:top;width:100%%">
  257. <tr style="padding:0;text-align:left;vertical-align:top">
  258. <th style="Margin:0;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0;padding:0;text-align:left">
  259. <center data-parsed="" style="min-width:490px;width:100%%">
  260. <table class="button btn default primary float-center" style="Margin:0;border-collapse:collapse;border-spacing:0;display:inline-block;float:none;margin:0;max-height:60px;padding:0;text-align:center;vertical-align:top;width:auto;background:%1\$s;color:#fefefe;background-color:%1\$s;">
  261. <tr style="padding:0;text-align:left;vertical-align:top">
  262. <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:1.3;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word">
  263. <table style="border-collapse:collapse;border-spacing:0;padding:0;text-align:left;vertical-align:top;width:100%%">
  264. <tr style="padding:0;text-align:left;vertical-align:top">
  265. <td style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border:0 solid %2\$s;border-collapse:collapse!important;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:1.3;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word">
  266. <a href="%3\$s" style="Margin:0;border:0 solid %4\$s;border-radius:2px;color:%5\$s;display:inline-block;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:regular;line-height:1.3;margin:0;padding:10px 25px 10px 25px;text-align:left;outline:1px solid %5\$s;text-decoration:none">%7\$s</a>
  267. </td>
  268. </tr>
  269. </table>
  270. </td>
  271. </tr>
  272. </table>
  273. </center>
  274. </th>
  275. <th class="expander" style="Margin:0;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;line-height:1.3;margin:0;padding:0!important;text-align:left;visibility:hidden;width:0"></th>
  276. </tr>
  277. </table>
  278. </th>
  279. </tr>
  280. </tbody>
  281. </table>
  282. EOF;
  283. protected string $bodyEnd = <<<EOF
  284. </td>
  285. </tr>
  286. </tbody>
  287. </table>
  288. </td>
  289. </tr>
  290. </table>
  291. EOF;
  292. protected string $footer = <<<EOF
  293. <table class="spacer float-center" style="Margin:0 auto;border-collapse:collapse;border-spacing:0;float:none;margin:0 auto;padding:0;text-align:center;vertical-align:top;width:100%%">
  294. <tbody>
  295. <tr style="padding:0;text-align:left;vertical-align:top">
  296. <td height="60px" style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:60px;font-weight:400;hyphens:auto;line-height:60px;margin:0;mso-line-height-rule:exactly;padding:0;text-align:left;vertical-align:top;word-wrap:break-word">&#xA0;</td>
  297. </tr>
  298. </tbody>
  299. </table>
  300. <table align="center" class="wrapper footer float-center" style="Margin:0 auto;border-collapse:collapse;border-spacing:0;float:none;margin:0 auto;padding:0;text-align:center;vertical-align:top;width:100%%">
  301. <tr style="padding:0;text-align:left;vertical-align:top">
  302. <td class="wrapper-inner" style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:16px;font-weight:400;hyphens:auto;line-height:1.3;margin:0;padding:0;text-align:left;vertical-align:top;word-wrap:break-word">
  303. <center data-parsed="" style="min-width:580px;width:100%%">
  304. <table class="spacer float-center" style="Margin:0 auto;border-collapse:collapse;border-spacing:0;float:none;margin:0 auto;padding:0;text-align:center;vertical-align:top;width:100%%">
  305. <tbody>
  306. <tr style="padding:0;text-align:left;vertical-align:top">
  307. <td height="15px" style="-moz-hyphens:auto;-webkit-hyphens:auto;Margin:0;border-collapse:collapse!important;color:#0a0a0a;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:15px;font-weight:400;hyphens:auto;line-height:15px;margin:0;mso-line-height-rule:exactly;padding:0;text-align:left;vertical-align:top;word-wrap:break-word">&#xA0;</td>
  308. </tr>
  309. </tbody>
  310. </table>
  311. <p class="text-center float-center" align="center" style="Margin:0;Margin-bottom:10px;color:#C8C8C8;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen-Sans,Ubuntu,Cantarell,'Helvetica Neue',Arial,sans-serif;font-size:12px;font-weight:400;line-height:16px;margin:0;margin-bottom:10px;padding:0;text-align:center">%s</p>
  312. </center>
  313. </td>
  314. </tr>
  315. </table>
  316. EOF;
  317. public function __construct(
  318. protected Defaults $themingDefaults,
  319. protected IURLGenerator $urlGenerator,
  320. protected IFactory $l10nFactory,
  321. protected string $emailId,
  322. protected array $data,
  323. ) {
  324. $this->htmlBody .= $this->head;
  325. }
  326. /**
  327. * Sets the subject of the email
  328. */
  329. public function setSubject(string $subject): void {
  330. $this->subject = $subject;
  331. }
  332. /**
  333. * Adds a header to the email
  334. */
  335. public function addHeader(): void {
  336. if ($this->headerAdded) {
  337. return;
  338. }
  339. $this->headerAdded = true;
  340. $logoUrl = $this->urlGenerator->getAbsoluteURL($this->themingDefaults->getLogo(false));
  341. $this->htmlBody .= vsprintf($this->header, [$this->themingDefaults->getDefaultColorPrimary(), $logoUrl, $this->themingDefaults->getName()]);
  342. }
  343. /**
  344. * Adds a heading to the email
  345. *
  346. * @param string|bool $plainTitle Title that is used in the plain text email
  347. * if empty the $title is used, if false none will be used
  348. */
  349. public function addHeading(string $title, $plainTitle = ''): void {
  350. if ($this->footerAdded) {
  351. return;
  352. }
  353. if ($plainTitle === '') {
  354. $plainTitle = $title;
  355. }
  356. $this->htmlBody .= vsprintf($this->heading, [htmlspecialchars($title)]);
  357. if ($plainTitle !== false) {
  358. $this->plainBody .= $plainTitle . PHP_EOL . PHP_EOL;
  359. }
  360. }
  361. /**
  362. * Open the HTML body when it is not already
  363. */
  364. protected function ensureBodyIsOpened(): void {
  365. if ($this->bodyOpened) {
  366. return;
  367. }
  368. $this->htmlBody .= $this->bodyBegin;
  369. $this->bodyOpened = true;
  370. }
  371. /**
  372. * Adds a paragraph to the body of the email
  373. *
  374. * @param string $text Note: When $plainText falls back to this, HTML is automatically escaped in the HTML email
  375. * @param string|bool $plainText Text that is used in the plain text email
  376. * if empty the $text is used, if false none will be used
  377. */
  378. public function addBodyText(string $text, $plainText = ''): void {
  379. if ($this->footerAdded) {
  380. return;
  381. }
  382. if ($plainText === '') {
  383. $plainText = $text;
  384. $text = htmlspecialchars($text);
  385. }
  386. $this->ensureBodyListClosed();
  387. $this->ensureBodyIsOpened();
  388. $this->htmlBody .= vsprintf($this->bodyText, [$text]);
  389. if ($plainText !== false) {
  390. $this->plainBody .= $plainText . PHP_EOL . PHP_EOL;
  391. }
  392. }
  393. /**
  394. * Adds a list item to the body of the email
  395. *
  396. * @param string $text Note: When $plainText falls back to this, HTML is automatically escaped in the HTML email
  397. * @param string $metaInfo Note: When $plainMetaInfo falls back to this, HTML is automatically escaped in the HTML email
  398. * @param string $icon Absolute path, must be 16*16 pixels
  399. * @param string|bool $plainText Text that is used in the plain text email
  400. * if empty or true the $text is used, if false none will be used
  401. * @param string|bool $plainMetaInfo Meta info that is used in the plain text email
  402. * if empty or true the $metaInfo is used, if false none will be used
  403. * @param integer $plainIndent plainIndent If > 0, Indent plainText by this amount.
  404. * @since 12.0.0
  405. */
  406. public function addBodyListItem(
  407. string $text,
  408. string $metaInfo = '',
  409. string $icon = '',
  410. $plainText = '',
  411. $plainMetaInfo = '',
  412. $plainIndent = 0,
  413. ): void {
  414. $this->ensureBodyListOpened();
  415. if ($plainText === '' || $plainText === true) {
  416. $plainText = $text;
  417. $text = htmlspecialchars($text);
  418. $text = str_replace("\n", "<br/>", $text); // convert newlines to HTML breaks
  419. }
  420. if ($plainMetaInfo === '' || $plainMetaInfo === true) {
  421. $plainMetaInfo = $metaInfo;
  422. $metaInfo = htmlspecialchars($metaInfo);
  423. }
  424. $htmlText = $text;
  425. if ($metaInfo) {
  426. $htmlText = '<em style="color:#777;">' . $metaInfo . '</em><br>' . $htmlText;
  427. }
  428. if ($icon !== '') {
  429. $icon = '<img src="' . htmlspecialchars($icon) . '" alt="&bull;">';
  430. } else {
  431. $icon = '&bull;';
  432. }
  433. $this->htmlBody .= vsprintf($this->listItem, [$icon, $htmlText]);
  434. if ($plainText !== false) {
  435. if ($plainIndent === 0) {
  436. /*
  437. * If plainIndent is not set by caller, this is the old NC17 layout code.
  438. */
  439. $this->plainBody .= ' * ' . $plainText;
  440. if ($plainMetaInfo !== false) {
  441. $this->plainBody .= ' (' . $plainMetaInfo . ')';
  442. }
  443. $this->plainBody .= PHP_EOL;
  444. } else {
  445. /*
  446. * Caller can set plainIndent > 0 to format plainText in tabular fashion.
  447. * with plainMetaInfo in column 1, and plainText in column 2.
  448. * The plainMetaInfo label is right justified in a field of width
  449. * "plainIndent". Multilines after the first are indented plainIndent+1
  450. * (to account for space after label). Fixes: #12391
  451. */
  452. /** @var string $label */
  453. $label = ($plainMetaInfo !== false)? $plainMetaInfo : '';
  454. $this->plainBody .= sprintf("%{$plainIndent}s %s\n",
  455. $label,
  456. str_replace("\n", "\n" . str_repeat(' ', $plainIndent + 1), $plainText));
  457. }
  458. }
  459. }
  460. protected function ensureBodyListOpened(): void {
  461. if ($this->bodyListOpened) {
  462. return;
  463. }
  464. $this->ensureBodyIsOpened();
  465. $this->bodyListOpened = true;
  466. $this->htmlBody .= $this->listBegin;
  467. }
  468. protected function ensureBodyListClosed(): void {
  469. if (!$this->bodyListOpened) {
  470. return;
  471. }
  472. $this->bodyListOpened = false;
  473. $this->htmlBody .= $this->listEnd;
  474. }
  475. /**
  476. * Adds a button group of two buttons to the body of the email
  477. *
  478. * @param string $textLeft Text of left button; Note: When $plainTextLeft falls back to this, HTML is automatically escaped in the HTML email
  479. * @param string $urlLeft URL of left button
  480. * @param string $textRight Text of right button; Note: When $plainTextRight falls back to this, HTML is automatically escaped in the HTML email
  481. * @param string $urlRight URL of right button
  482. * @param string $plainTextLeft Text of left button that is used in the plain text version - if unset the $textLeft is used
  483. * @param string $plainTextRight Text of right button that is used in the plain text version - if unset the $textRight is used
  484. */
  485. public function addBodyButtonGroup(
  486. string $textLeft,
  487. string $urlLeft,
  488. string $textRight,
  489. string $urlRight,
  490. string $plainTextLeft = '',
  491. string $plainTextRight = '',
  492. ): void {
  493. if ($this->footerAdded) {
  494. return;
  495. }
  496. if ($plainTextLeft === '') {
  497. $plainTextLeft = $textLeft;
  498. $textLeft = htmlspecialchars($textLeft);
  499. }
  500. if ($plainTextRight === '') {
  501. $plainTextRight = $textRight;
  502. $textRight = htmlspecialchars($textRight);
  503. }
  504. $this->ensureBodyIsOpened();
  505. $this->ensureBodyListClosed();
  506. $color = $this->themingDefaults->getDefaultColorPrimary();
  507. $textColor = $this->themingDefaults->getTextColorPrimary();
  508. $this->htmlBody .= vsprintf($this->buttonGroup, [$color, $color, $urlLeft, $color, $textColor, $textColor, $textLeft, $urlRight, $textRight]);
  509. $this->plainBody .= PHP_EOL . $plainTextLeft . ': ' . $urlLeft . PHP_EOL;
  510. $this->plainBody .= $plainTextRight . ': ' . $urlRight . PHP_EOL . PHP_EOL;
  511. }
  512. /**
  513. * Adds a button to the body of the email
  514. *
  515. * @param string $text Text of button; Note: When $plainText falls back to this, HTML is automatically escaped in the HTML email
  516. * @param string $url URL of button
  517. * @param string|false $plainText Text of button in plain text version
  518. * if empty the $text is used, if false none will be used
  519. *
  520. * @since 12.0.0
  521. */
  522. public function addBodyButton(string $text, string $url, $plainText = ''): void {
  523. if ($this->footerAdded) {
  524. return;
  525. }
  526. $this->ensureBodyIsOpened();
  527. $this->ensureBodyListClosed();
  528. if ($plainText === '') {
  529. $plainText = $text;
  530. $text = htmlspecialchars($text);
  531. }
  532. $color = $this->themingDefaults->getDefaultColorPrimary();
  533. $textColor = $this->themingDefaults->getTextColorPrimary();
  534. $this->htmlBody .= vsprintf($this->button, [$color, $color, $url, $color, $textColor, $textColor, $text]);
  535. if ($plainText !== false) {
  536. $this->plainBody .= $plainText . ': ';
  537. }
  538. $this->plainBody .= $url . PHP_EOL;
  539. }
  540. /**
  541. * Close the HTML body when it is open
  542. */
  543. protected function ensureBodyIsClosed(): void {
  544. if (!$this->bodyOpened) {
  545. return;
  546. }
  547. $this->ensureBodyListClosed();
  548. $this->htmlBody .= $this->bodyEnd;
  549. $this->bodyOpened = false;
  550. }
  551. /**
  552. * Adds a logo and a text to the footer. <br> in the text will be replaced by new lines in the plain text email
  553. *
  554. * @param string $text If the text is empty the default "Name - Slogan<br>This is an automatically sent email" will be used
  555. */
  556. public function addFooter(string $text = '', ?string $lang = null): void {
  557. if ($text === '') {
  558. $l10n = $this->l10nFactory->get('lib', $lang);
  559. $slogan = $this->themingDefaults->getSlogan($lang);
  560. if ($slogan !== '') {
  561. $slogan = ' - ' . $slogan;
  562. }
  563. $text = $this->themingDefaults->getName() . $slogan . '<br>' . $l10n->t('This is an automatically sent email, please do not reply.');
  564. }
  565. if ($this->footerAdded) {
  566. return;
  567. }
  568. $this->footerAdded = true;
  569. $this->ensureBodyIsClosed();
  570. $this->htmlBody .= vsprintf($this->footer, [$text]);
  571. $this->htmlBody .= $this->tail;
  572. $this->plainBody .= PHP_EOL . '-- ' . PHP_EOL;
  573. $this->plainBody .= str_replace('<br>', PHP_EOL, $text);
  574. }
  575. /**
  576. * Returns the rendered email subject as string
  577. */
  578. public function renderSubject(): string {
  579. return $this->subject;
  580. }
  581. /**
  582. * Returns the rendered HTML email as string
  583. */
  584. public function renderHtml(): string {
  585. if (!$this->footerAdded) {
  586. $this->footerAdded = true;
  587. $this->ensureBodyIsClosed();
  588. $this->htmlBody .= $this->tail;
  589. }
  590. return $this->htmlBody;
  591. }
  592. /**
  593. * Returns the rendered plain text email as string
  594. */
  595. public function renderText(): string {
  596. if (!$this->footerAdded) {
  597. $this->footerAdded = true;
  598. $this->ensureBodyIsClosed();
  599. $this->htmlBody .= $this->tail;
  600. }
  601. return $this->plainBody;
  602. }
  603. }