PageRenderTime 44ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 1ms

/applications/dashboard/library/class.emailtemplate.php

http://github.com/vanillaforums/Garden
PHP | 559 lines | 220 code | 63 blank | 276 comment | 18 complexity | 249f00c9cd543275ddc5e1b8f5bb1051 MD5 | raw file
Possible License(s): LGPL-2.1, GPL-2.0, AGPL-1.0, BSD-3-Clause, MIT
  1. <?php
  2. /**
  3. * @copyright 2009-2019 Vanilla Forums Inc.
  4. * @license GPL-2.0-only
  5. */
  6. use Vanilla\Formatting\Formats\WysiwygFormat;
  7. use Vanilla\Web\Asset\LegacyAssetModel;
  8. /**
  9. * Class EmailTemplate
  10. *
  11. * Compiles the data for an email, applies appropriate content filters and renders the email.
  12. *
  13. * @author Becky Van Bussel <becky@vanillaforums.com>
  14. * @package Core
  15. * @since 2.2
  16. */
  17. class EmailTemplate extends Gdn_Pluggable implements Gdn_IEmailTemplate {
  18. /** Delimiter for plaintext email. */
  19. const PLAINTEXT_START = '<!-- //TEXT VERSION FOLLOWS//';
  20. /** Default email colors. */
  21. const DEFAULT_TEXT_COLOR = '#333333';
  22. const DEFAULT_BACKGROUND_COLOR = '#eeeeee';
  23. const DEFAULT_CONTAINER_BACKGROUND_COLOR = '#ffffff';
  24. const DEFAULT_BUTTON_BACKGROUND_COLOR = '#38abe3'; // Vanilla blue
  25. const DEFAULT_BUTTON_TEXT_COLOR = '#ffffff';
  26. // Component properties
  27. /** @var string The HTML formatted email title. */
  28. protected $title;
  29. /** @var string The HTML formatted email lead (sub-title, appears under title). */
  30. protected $lead;
  31. /** @var string The HTML formatted email message (the body of the email). */
  32. protected $message;
  33. /**
  34. * @var array An array representing a footer with the following keys:
  35. * 'text' => The HTML-formatted footer text.
  36. * 'textColor' => The hex color code of the footer text, must include the leading '#'.
  37. * 'backgroundColor' => The hex color code of the footer background, must include the leading '#'.
  38. */
  39. protected $footer;
  40. /**
  41. * @var array An array representing a button with the following keys:
  42. * 'url' => The href value of the button.
  43. * 'text' => The button text.
  44. * 'textColor' => The hex color code of the button text, must include the leading '#'.
  45. * 'backgroundColor' => The hex color code of the button background, must include the leading '#'.
  46. */
  47. protected $button;
  48. /**
  49. * @var array An array representing an image with the following keys:
  50. * 'source' => The image source url.
  51. * 'link' => The href value of the image wrapper.
  52. * 'alt' => The alt value of the image tag.
  53. */
  54. protected $image;
  55. /** @var string The path to the email view. */
  56. protected $view;
  57. /** @var bool Whether to render in plaintext. */
  58. protected $plaintext = false;
  59. // Color properties
  60. /** @var string The hex color code of the text, must include the leading '#'.*/
  61. protected $textColor = self::DEFAULT_TEXT_COLOR;
  62. /** @var string The hex color code of the background, must include the leading '#'. */
  63. protected $backgroundColor = self::DEFAULT_BACKGROUND_COLOR;
  64. /** @var string The hex color code of the container background, must include the leading '#'. */
  65. protected $containerBackgroundColor = self::DEFAULT_CONTAINER_BACKGROUND_COLOR;
  66. /**@var string The default hex color code of the button text, must include the leading '#'. */
  67. protected $defaultButtonTextColor = self::DEFAULT_BUTTON_TEXT_COLOR;
  68. /** @var string The default hex color code of the button background, must include the leading '#'. */
  69. protected $defaultButtonBackgroundColor = self::DEFAULT_BUTTON_BACKGROUND_COLOR;
  70. /**
  71. * Template initial setup.
  72. *
  73. * @param string $message HTML formatted email message (the body of the email).
  74. * @param string $title HTML formatted email title.
  75. * @param string $lead HTML formatted email lead (sub-title, appears under title).
  76. * @param string $view
  77. *
  78. * @throws Exception
  79. */
  80. public function __construct($message = '', $title = '', $lead = '', $view = 'email-basic') {
  81. parent::__construct();
  82. $this->setMessage($message);
  83. $this->setTitle($title);
  84. $this->setLead($lead);
  85. // Set templating defaults
  86. $this->setTextColor(c('Garden.EmailTemplate.TextColor', self::DEFAULT_TEXT_COLOR));
  87. $this->setBackgroundColor(c('Garden.EmailTemplate.BackgroundColor', self::DEFAULT_BACKGROUND_COLOR));
  88. $this->setContainerBackgroundColor(c('Garden.EmailTemplate.ContainerBackgroundColor', self::DEFAULT_CONTAINER_BACKGROUND_COLOR));
  89. $this->setDefaultButtonBackgroundColor(c('Garden.EmailTemplate.ButtonBackgroundColor', self::DEFAULT_BUTTON_BACKGROUND_COLOR));
  90. $this->setDefaultButtonTextColor(c('Garden.EmailTemplate.ButtonTextColor', self::DEFAULT_BUTTON_TEXT_COLOR));
  91. $this->setDefaultEmailImage();
  92. // Set default view
  93. $this->view = LegacyAssetModel::viewLocation($view, 'email', 'dashboard');
  94. }
  95. /**
  96. * Sets the default image for the email template.
  97. */
  98. protected function setDefaultEmailImage() {
  99. if (!$this->getImage()) {
  100. $image = $this->getDefaultEmailImage();
  101. $this->setImageArray($image);
  102. }
  103. }
  104. /**
  105. * Retrieves default values for the email image.
  106. *
  107. * @return array An array representing an image.
  108. */
  109. public function getDefaultEmailImage() {
  110. $image = [];
  111. if (c('Garden.EmailTemplate.Image', '')) {
  112. $image['source'] = Gdn_UploadImage::url(c('Garden.EmailTemplate.Image'));
  113. }
  114. $image['link'] = url('/', true);
  115. $image['alt'] = c('Garden.LogoTitle', c('Garden.Title', ''));
  116. return $image;
  117. }
  118. /**
  119. * Filters an unsafe HTML string and returns it.
  120. *
  121. * @param string $html The HTML to filter.
  122. * @param bool $convertNewlines Whether to convert new lines to html br tags.
  123. * @param bool $filter Whether to escape HTML or not.
  124. *
  125. * @return string The filtered HTML string.
  126. */
  127. protected function formatContent($html, $convertNewlines = false, $filter = false) {
  128. $str = $html;
  129. if ($filter) {
  130. $str = Gdn_Format::htmlFilter($str);
  131. }
  132. if ($convertNewlines) {
  133. $str = preg_replace('/(\015\012)|(\015)|(\012)/', '<br>', $str);
  134. }
  135. // $str = strip_tags($str, ['b', 'i', 'p', 'strong', 'em', 'br']);
  136. return $str;
  137. }
  138. /**
  139. * Set which application view to use.
  140. *
  141. * @param string $view The view name.
  142. * @param string $controllerName The controller name for the view.
  143. * @param string $applicationFolder The application folder for the view.
  144. *
  145. * @return EmailTemplate $this The calling object.
  146. * @throws Exception
  147. */
  148. public function setView($view, $controllerName = 'email', $applicationFolder = 'dashboard') {
  149. $this->view = LegacyAssetModel::viewLocation($view, $controllerName, $applicationFolder);
  150. return $this;
  151. }
  152. /**
  153. * Get the current email title.
  154. *
  155. * @return string The HTML formatted email title.
  156. */
  157. public function getTitle() {
  158. return $this->title;
  159. }
  160. /**
  161. * Set the current email title.
  162. *
  163. * @param string $title The HTML formatted email title.
  164. *
  165. * @return EmailTemplate $this The calling object.
  166. */
  167. public function setTitle($title) {
  168. $this->title = $this->formatContent($title);
  169. return $this;
  170. }
  171. /**
  172. * Get the current email sub-title.
  173. *
  174. * @return string The HTML formatted email lead (sub-title, appears under title).
  175. */
  176. public function getLead() {
  177. return $this->lead;
  178. }
  179. /**
  180. * Set the current email sub-title.
  181. *
  182. * @param string $lead The HTML formatted email lead (sub-title, appears under title).
  183. *
  184. * @return EmailTemplate $this The calling object.
  185. */
  186. public function setLead($lead) {
  187. $this->lead = $this->formatContent($lead);
  188. return $this;
  189. }
  190. /**
  191. * Get the main body of the email.
  192. *
  193. * @return string The HTML formatted email message (the body of the email).
  194. */
  195. public function getMessage() {
  196. return $this->message;
  197. }
  198. /**
  199. * Set the main body of the email.
  200. *
  201. * @param string $message The HTML formatted email message (the body of the email).
  202. * @param bool $convertNewlines Whether to convert new lines to html br tags.
  203. * @param bool $filter Whether to filter HTML or not.
  204. *
  205. * @return EmailTemplate $this The calling object.
  206. */
  207. public function setMessage($message, $convertNewlines = false, $filter = false) {
  208. $this->message = $this->formatContent($message, $convertNewlines, $filter);
  209. return $this;
  210. }
  211. /**
  212. * Get the HTML footer.
  213. *
  214. * @return string The HTML formatted email footer.
  215. */
  216. public function getFooter() {
  217. return $this->footer;
  218. }
  219. /**
  220. * Sets the HTML footer.
  221. *
  222. * The footer background and text colors default to the button background and text colors.
  223. *
  224. * @param string $text The HTML formatted email footer text.
  225. * @param string $textColor The hex color code of the footer text, must include the leading '#'.
  226. * @param string $backgroundColor The hex color code of the footer background, must include the leading '#'.
  227. * @return EmailTemplate $this The calling object.
  228. */
  229. public function setFooter($text, $textColor = '', $backgroundColor = '') {
  230. if (!$textColor) {
  231. $textColor = $this->defaultButtonTextColor;
  232. }
  233. if (!$backgroundColor) {
  234. $backgroundColor = $this->defaultButtonBackgroundColor;
  235. }
  236. $this->footer = ['text' => htmlspecialchars($this->formatContent($text)),
  237. 'textColor' => htmlspecialchars($textColor),
  238. 'backgroundColor' => htmlspecialchars($backgroundColor)];
  239. return $this;
  240. }
  241. /**
  242. * Get the main text color.
  243. *
  244. * @return string The hex color code of the text.
  245. */
  246. public function getTextColor() {
  247. return $this->textColor;
  248. }
  249. /**
  250. * Set the main text color. Chainable.
  251. *
  252. * @param string $color The hex color code of the text, must include the leading '#'.
  253. *
  254. * @return EmailTemplate $this The calling object.
  255. */
  256. public function setTextColor($color) {
  257. $this->textColor = htmlspecialchars($color);
  258. return $this;
  259. }
  260. /**
  261. *
  262. *
  263. * @return string The hex color code of the background.
  264. */
  265. public function getBackgroundColor() {
  266. return $this->backgroundColor;
  267. }
  268. /**
  269. *
  270. *
  271. * @param string $color The hex color code of the background, must include the leading '#'.
  272. *
  273. * @return EmailTemplate $this The calling object.
  274. */
  275. public function setBackgroundColor($color) {
  276. $this->backgroundColor = htmlspecialchars($color);
  277. return $this;
  278. }
  279. /**
  280. *
  281. *
  282. * @return string The hex color code of the container background.
  283. */
  284. public function getContainerBackgroundColor() {
  285. return $this->containerBackgroundColor;
  286. }
  287. /**
  288. *
  289. *
  290. * @param string $color The hex color code of the container background, must include the leading '#'.
  291. *
  292. * @return EmailTemplate $this The calling object.
  293. */
  294. public function setContainerBackgroundColor($color) {
  295. $this->containerBackgroundColor = htmlspecialchars($color);
  296. return $this;
  297. }
  298. /**
  299. *
  300. *
  301. * @return string The default hex color code of the button text, must include the leading '#'.
  302. */
  303. public function getDefaultButtonTextColor() {
  304. return $this->defaultButtonTextColor;
  305. }
  306. /**
  307. * Sets the default color for the button text.
  308. *
  309. * The text color of the EmailTemplate's button property can be overridden by setting $button['textColor']
  310. *
  311. * @param string $color The default hex color code of the button text, must include the leading '#'.
  312. * @return EmailTemplate $this The calling object.
  313. */
  314. public function setDefaultButtonTextColor($color) {
  315. $this->defaultButtonTextColor = $color;
  316. return $this;
  317. }
  318. /**
  319. *
  320. *
  321. * @return string The default hex color code of the button background, must include the leading '#'.
  322. */
  323. public function getDefaultButtonBackgroundColor() {
  324. return $this->defaultButtonBackgroundColor;
  325. }
  326. /**
  327. * Sets the default color for the button background.
  328. *
  329. * The background color of the EmailTemplate's button property can be overridden by setting $button['backgroundColor']
  330. *
  331. * @param string $color The default hex color code of the button background, must include the leading '#'.
  332. * @return EmailTemplate $this The calling object.
  333. */
  334. public function setDefaultButtonBackgroundColor($color) {
  335. $this->defaultButtonBackgroundColor = $color;
  336. return $this;
  337. }
  338. /**
  339. * Get the `plaintext` property.
  340. *
  341. * @return bool Whether to render in plaintext.
  342. */
  343. public function isPlaintext() {
  344. return $this->plaintext;
  345. }
  346. /**
  347. * Set the `plaintext` property.
  348. *
  349. * @param bool $plainText Whether to render in plaintext.
  350. */
  351. public function setPlaintext($plainText) {
  352. $this->plaintext = $plainText;
  353. }
  354. /**
  355. * Set the button property.
  356. *
  357. * @param string $url The href value of the button.
  358. * @param string $text The button text.
  359. * @param string $textColor The hex color code of the button text, must include the leading '#'.
  360. * @param string $backgroundColor The hex color code of the button background, must include the leading '#'.
  361. * @return EmailTemplate $this The calling object.
  362. */
  363. public function setButton($url, $text, $textColor = '', $backgroundColor = '') {
  364. if (!$textColor) {
  365. $textColor = $this->defaultButtonTextColor;
  366. }
  367. if (!$backgroundColor) {
  368. $backgroundColor = $this->defaultButtonBackgroundColor;
  369. }
  370. $this->button = ['url' => htmlspecialchars($url),
  371. 'text' => htmlspecialchars($this->formatContent($text)),
  372. 'textColor' => htmlspecialchars($textColor),
  373. 'backgroundColor' => htmlspecialchars($backgroundColor)];
  374. return $this;
  375. }
  376. /**
  377. * Remove the button.
  378. *
  379. * @return EmailTemplate $this The calling object.
  380. */
  381. public function removeButton() {
  382. $this->button = [];
  383. return $this;
  384. }
  385. /**
  386. *
  387. *
  388. * @return array An array representing an image with the following keys:
  389. * 'source' => The image source url.
  390. * 'link' => The href value of the image wrapper.
  391. * 'alt' => The alt value of the image tag.
  392. */
  393. public function getImage() {
  394. return $this->image;
  395. }
  396. /**
  397. * Set the image property.
  398. *
  399. * @param string $sourceUrl The image source url.
  400. * @param string $linkUrl The href value of the image wrapper.
  401. * @param string $alt The alt value of the img tag.
  402. *
  403. * @return EmailTemplate $this The calling object.
  404. */
  405. public function setImage($sourceUrl = '', $linkUrl = '', $alt = '') {
  406. // We need either a source image or an alt to have an img tag.
  407. if ($sourceUrl || $alt) {
  408. $this->image = ['source' => htmlspecialchars($sourceUrl),
  409. 'link' => htmlspecialchars($linkUrl),
  410. 'alt' => $alt];
  411. }
  412. return $this;
  413. }
  414. /**
  415. * Set the image properties.
  416. *
  417. * @param array $image Uses the following keys:
  418. * 'source' => The image source url.
  419. * 'link' => The href value of the image wrapper.
  420. * 'alt' => The alt value of the img tag.
  421. *
  422. * @return EmailTemplate $this The calling object.
  423. */
  424. public function setImageArray($image) {
  425. $this->setImage(val('source', $image), val('link', $image), val('alt', $image));
  426. return $this;
  427. }
  428. /**
  429. * Copies the email object to an array.
  430. *
  431. * A simple (array) typecast won't work since the properties are protected
  432. * and, as such, add unwanted information to the array keys.
  433. *
  434. * @param EmailTemplate $email The email object.
  435. *
  436. * @return array Copy of email object in an array format for output.
  437. */
  438. protected function objectToArray($email) {
  439. if (is_array($email) || is_object($email)) {
  440. $result = [];
  441. foreach ($email as $key => $value) {
  442. $result[$key] = $this->objectToArray($value);
  443. }
  444. return $result;
  445. }
  446. return $email;
  447. }
  448. /**
  449. * Renders a plaintext email.
  450. *
  451. * @return string A plaintext email.
  452. */
  453. protected function plainTextEmail() {
  454. $formatService = Gdn::formatService();
  455. $email = [
  456. 'banner' => val('alt', $this->image).' '.val('link', $this->image),
  457. 'title' => $this->getTitle(),
  458. 'lead' => $this->getLead(),
  459. 'message' => $this->getMessage(),
  460. 'button' => sprintf(t('%s: %s'), val('text', $this->button), val('url', $this->button)),
  461. 'footer' => $this->getFooter()
  462. ];
  463. foreach ($email as $key => $val) {
  464. if (!$val) {
  465. unset($email[$key]);
  466. } else {
  467. // Add some additional padding, where necessary, to improve the appearance of plain-text messages.
  468. if ($key == 'message') {
  469. $email[$key] = "<br>$val<br>";
  470. } elseif ($key == "banner") {
  471. $email["banner"] = "{$val}<br>";
  472. }
  473. }
  474. }
  475. $body = implode("<br>", $email);
  476. // Treat the HTML as WYSIWYG for the format's preferred handling of breaks/newlines.
  477. $result = $formatService->renderPlainText($body, WysiwygFormat::FORMAT_KEY);
  478. return $result;
  479. }
  480. /**
  481. * Render the email.
  482. *
  483. * @return string The rendered email.
  484. */
  485. public function toString() {
  486. if ($this->isPlaintext()) {
  487. return $this->plainTextEmail();
  488. }
  489. $controller = new Gdn_Controller();
  490. $controller->setData('email', $this->objectToArray($this));
  491. $email = $controller->fetchView($this->view);
  492. // Append plaintext version
  493. $email .= self::PLAINTEXT_START.$this->plainTextEmail();
  494. return $email;
  495. }
  496. }