PageRenderTime 62ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

/classes/abstracts/abstract-wc-email.php

https://github.com/alexcsandru/woocommerce
PHP | 667 lines | 602 code | 20 blank | 45 comment | 1 complexity | c01ef918e3448bab81d576fddb4d980e MD5 | raw file
Possible License(s): GPL-3.0
  1. <?php
  2. if ( ! defined( 'ABSPATH' ) ) exit; // Exit if accessed directly
  3. /**
  4. * Abstract Email Class
  5. *
  6. * WooCommerce Email Class which is extended by specific email template classes to add emails to WooCommerce
  7. *
  8. * @class WC_Email
  9. * @version 2.0.0
  10. * @package WooCommerce/Abstracts
  11. * @author WooThemes
  12. * @category Abstract Class
  13. * @extends WC_Settings_API
  14. */
  15. abstract class WC_Email extends WC_Settings_API {
  16. /** @var string Payment method ID. */
  17. var $id;
  18. /** @var string Payment method title. */
  19. var $title;
  20. /** @var bool True if the method is enabled. */
  21. var $enabled;
  22. /** @var string Description for the gateway. */
  23. var $description;
  24. /** @var string plain text template path */
  25. var $template_plain;
  26. /** @var string html template path */
  27. var $template_html;
  28. /** @var string template path */
  29. var $template_base;
  30. /** @var string recipients for the email */
  31. var $recipient;
  32. /** @var string heading for the email content */
  33. var $heading;
  34. /** @var string subject for the email */
  35. var $subject;
  36. /** @var object this email is for, for example a customer, product, or email */
  37. var $object;
  38. /** @var array strings to find in subjects/headings */
  39. var $find;
  40. /** @var array strings to replace in subjects/headings */
  41. var $replace;
  42. /** @var string For multipart emails */
  43. var $mime_boundary;
  44. /** @var string For multipart emails */
  45. var $mime_boundary_header;
  46. /** @var bool true when email is being sent */
  47. var $sending;
  48. /**
  49. * List of preg* regular expression patterns to search for,
  50. * used in conjunction with $replace.
  51. * https://raw.github.com/ushahidi/wp-silcc/master/class.html2text.inc
  52. *
  53. * @var array $search
  54. * @access public
  55. * @see $replace
  56. */
  57. var $plain_search = array(
  58. "/\r/", // Non-legal carriage return
  59. '/&(nbsp|#160);/i', // Non-breaking space
  60. '/&(quot|rdquo|ldquo|#8220|#8221|#147|#148);/i',
  61. // Double quotes
  62. '/&(apos|rsquo|lsquo|#8216|#8217);/i', // Single quotes
  63. '/&gt;/i', // Greater-than
  64. '/&lt;/i', // Less-than
  65. '/&(amp|#38);/i', // Ampersand
  66. '/&(copy|#169);/i', // Copyright
  67. '/&(trade|#8482|#153);/i', // Trademark
  68. '/&(reg|#174);/i', // Registered
  69. '/&(mdash|#151|#8212);/i', // mdash
  70. '/&(ndash|minus|#8211|#8722);/i', // ndash
  71. '/&(bull|#149|#8226);/i', // Bullet
  72. '/&(pound|#163);/i', // Pound sign
  73. '/&(euro|#8364);/i', // Euro sign
  74. '/&[^&;]+;/i', // Unknown/unhandled entities
  75. '/[ ]{2,}/' // Runs of spaces, post-handling
  76. );
  77. /**
  78. * List of pattern replacements corresponding to patterns searched.
  79. *
  80. * @var array $replace
  81. * @access public
  82. * @see $search
  83. */
  84. var $plain_replace = array(
  85. '', // Non-legal carriage return
  86. ' ', // Non-breaking space
  87. '"', // Double quotes
  88. "'", // Single quotes
  89. '>',
  90. '<',
  91. '&',
  92. '(c)',
  93. '(tm)',
  94. '(R)',
  95. '--',
  96. '-',
  97. '*',
  98. 'ÂŁ',
  99. 'EUR', // Euro sign. € ?
  100. '', // Unknown/unhandled entities
  101. ' ' // Runs of spaces, post-handling
  102. );
  103. /**
  104. * Constructor
  105. *
  106. * @access public
  107. * @return void
  108. */
  109. function __construct() {
  110. global $woocommerce;
  111. // Init settings
  112. $this->init_form_fields();
  113. $this->init_settings();
  114. // Save settings hook
  115. add_action( 'woocommerce_update_options_email_' . $this->id, array( $this, 'process_admin_options' ) );
  116. // Default template base if not declared in child constructor
  117. if ( is_null( $this->template_base ) )
  118. $this->template_base = $woocommerce->plugin_path() . '/templates/';
  119. // Settings
  120. $this->heading = $this->get_option( 'heading', $this->heading );
  121. $this->subject = $this->get_option( 'subject', $this->subject );
  122. $this->email_type = $this->get_option( 'email_type' );
  123. $this->enabled = $this->get_option( 'enabled' );
  124. // Find/replace
  125. $this->find = array( '{blogname}' );
  126. $this->replace = array( $this->get_blogname() );
  127. // For multipart messages
  128. add_filter( 'phpmailer_init', array( $this, 'handle_multipart' ) );
  129. }
  130. /**
  131. * handle_multipart function.
  132. *
  133. * @access public
  134. * @param mixed $mailer
  135. * @return void
  136. */
  137. function handle_multipart( $mailer ) {
  138. if ( $this->sending && $this->get_email_type() == 'multipart' ) {
  139. $mailer->AltBody = wordwrap( preg_replace( $this->plain_search, $this->plain_replace, strip_tags( $this->get_content_plain() ) ) );
  140. //$mailer->AltBody = wordwrap( html_entity_decode( strip_tags( $this->get_content_plain() ) ), 70 );
  141. $this->sending = false;
  142. }
  143. return $mailer;
  144. }
  145. /**
  146. * format_string function.
  147. *
  148. * @access public
  149. * @param mixed $string
  150. * @return string
  151. */
  152. function format_string( $string ) {
  153. return str_replace( $this->find, $this->replace, $string );
  154. }
  155. /**
  156. * get_subject function.
  157. *
  158. * @access public
  159. * @return string
  160. */
  161. function get_subject() {
  162. return apply_filters( 'woocommerce_email_subject_' . $this->id, $this->format_string( $this->subject ), $this->object );
  163. }
  164. /**
  165. * get_heading function.
  166. *
  167. * @access public
  168. * @return string
  169. */
  170. function get_heading() {
  171. return apply_filters( 'woocommerce_email_heading_' . $this->id, $this->format_string( $this->heading ), $this->object );
  172. }
  173. /**
  174. * get_recipient function.
  175. *
  176. * @access public
  177. * @return string
  178. */
  179. function get_recipient() {
  180. return apply_filters( 'woocommerce_email_recipient_' . $this->id, $this->recipient, $this->object );
  181. }
  182. /**
  183. * get_headers function.
  184. *
  185. * @access public
  186. * @return string
  187. */
  188. function get_headers() {
  189. return apply_filters( 'woocommerce_email_headers', "Content-Type: " . $this->get_content_type() . "\r\n", $this->id, $this->object );
  190. }
  191. /**
  192. * get_attachments function.
  193. *
  194. * @access public
  195. * @return string
  196. */
  197. function get_attachments() {
  198. return apply_filters( 'woocommerce_email_attachments', '', $this->id, $this->object );
  199. }
  200. /**
  201. * get_type function.
  202. *
  203. * @access public
  204. * @return string
  205. */
  206. function get_email_type() {
  207. return $this->email_type ? $this->email_type : 'plain';
  208. }
  209. /**
  210. * get_content_type function.
  211. *
  212. * @access public
  213. * @return void
  214. */
  215. function get_content_type() {
  216. switch ( $this->get_email_type() ) {
  217. case "html" :
  218. return 'text/html';
  219. case "multipart" :
  220. return 'multipart/alternative';
  221. default :
  222. return 'text/plain';
  223. }
  224. }
  225. /**
  226. * Checks if this email is enabled and will be sent.
  227. *
  228. * @access public
  229. * @return bool
  230. */
  231. function is_enabled() {
  232. if ( $this->enabled == "yes" )
  233. return true;
  234. }
  235. /**
  236. * get_blogname function.
  237. *
  238. * @access public
  239. * @return void
  240. */
  241. function get_blogname() {
  242. return wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES );
  243. }
  244. /**
  245. * get_content function.
  246. *
  247. * @access public
  248. * @return string
  249. */
  250. function get_content() {
  251. $this->sending = true;
  252. if ( $this->get_email_type() == 'plain' ) {
  253. $email_content = preg_replace( $this->plain_search, $this->plain_replace, strip_tags( $this->get_content_plain() ) );
  254. } else {
  255. $email_content = $this->style_inline( $this->get_content_html() );
  256. }
  257. return $email_content;
  258. }
  259. /**
  260. * Apply inline styles to dynamic content.
  261. *
  262. * @access public
  263. * @param mixed $content
  264. * @return void
  265. */
  266. function style_inline( $content ) {
  267. if ( ! class_exists( 'DOMDocument' ) )
  268. return $content;
  269. $dom = new DOMDocument();
  270. @$dom->loadHTML( $content );
  271. $nodes = $dom->getElementsByTagName('img');
  272. foreach( $nodes as $node )
  273. if ( ! $node->hasAttribute( 'style' ) )
  274. $node->setAttribute( "style", "display:inline; border:none; font-size:14px; font-weight:bold; height:auto; line-height:100%; outline:none; text-decoration:none; text-transform:capitalize;" );
  275. $nodes_h1 = $dom->getElementsByTagName('h1');
  276. $nodes_h2 = $dom->getElementsByTagName('h2');
  277. $nodes_h3 = $dom->getElementsByTagName('h3');
  278. foreach( $nodes_h1 as $node )
  279. if ( ! $node->hasAttribute( 'style' ) )
  280. $node->setAttribute( "style", "color: " . get_option( 'woocommerce_email_text_color' ) . "; display:block; font-family:Arial; font-size:34px; font-weight:bold; margin-top: 10px; margin-right:0; margin-bottom:10px; margin-left:0; text-align:left; line-height: 150%;" );
  281. foreach( $nodes_h2 as $node )
  282. if ( ! $node->hasAttribute( 'style' ) )
  283. $node->setAttribute( "style", "color: " . get_option( 'woocommerce_email_text_color' ) . "; display:block; font-family:Arial; font-size:30px; font-weight:bold; margin-top: 10px; margin-right:0; margin-bottom:10px; margin-left:0; text-align:left; line-height: 150%;" );
  284. foreach( $nodes_h3 as $node )
  285. if ( ! $node->hasAttribute( 'style' ) )
  286. $node->setAttribute( "style", "color: " . get_option( 'woocommerce_email_text_color' ) . "; display:block; font-family:Arial; font-size:26px; font-weight:bold; margin-top: 10px; margin-right:0; margin-bottom:10px; margin-left:0; text-align:left; line-height: 150%;" );
  287. $nodes = $dom->getElementsByTagName('a');
  288. foreach( $nodes as $node )
  289. if ( ! $node->hasAttribute( 'style' ) )
  290. $node->setAttribute( "style", "color: " . get_option( 'woocommerce_email_text_color' ) . "; font-weight:normal; text-decoration:underline;" );
  291. $content = $dom->saveHTML();
  292. return $content;
  293. }
  294. /**
  295. * get_content_plain function.
  296. *
  297. * @access public
  298. * @return void
  299. */
  300. function get_content_plain() {}
  301. /**
  302. * get_content_html function.
  303. *
  304. * @access public
  305. * @return void
  306. */
  307. function get_content_html() {}
  308. /**
  309. * Get from name for email.
  310. *
  311. * @access public
  312. * @return string
  313. */
  314. function get_from_name() {
  315. return esc_html( get_option( 'woocommerce_email_from_name' ) );
  316. }
  317. /**
  318. * Get from email address.
  319. *
  320. * @access public
  321. * @return string
  322. */
  323. function get_from_address() {
  324. return sanitize_email( get_option( 'woocommerce_email_from_address' ) );
  325. }
  326. /**
  327. * Send the email.
  328. *
  329. * @access public
  330. * @param mixed $to
  331. * @param mixed $subject
  332. * @param mixed $message
  333. * @param string $headers
  334. * @param string $attachments
  335. * @return void
  336. */
  337. function send( $to, $subject, $message, $headers, $attachments ) {
  338. add_filter( 'wp_mail_from', array( $this, 'get_from_address' ) );
  339. add_filter( 'wp_mail_from_name', array( $this, 'get_from_name' ) );
  340. add_filter( 'wp_mail_content_type', array( $this, 'get_content_type' ) );
  341. wp_mail( $to, $subject, $message, $headers, $attachments );
  342. remove_filter( 'wp_mail_from', array( $this, 'get_from_address' ) );
  343. remove_filter( 'wp_mail_from_name', array( $this, 'get_from_name' ) );
  344. remove_filter( 'wp_mail_content_type', array( $this, 'get_content_type' ) );
  345. }
  346. /**
  347. * Initialise Settings Form Fields - these are generic email options most will use.
  348. *
  349. * @access public
  350. * @return void
  351. */
  352. function init_form_fields() {
  353. $this->form_fields = array(
  354. 'enabled' => array(
  355. 'title' => __( 'Enable/Disable', 'woocommerce' ),
  356. 'type' => 'checkbox',
  357. 'label' => __( 'Enable this email notification', 'woocommerce' ),
  358. 'default' => 'yes'
  359. ),
  360. 'subject' => array(
  361. 'title' => __( 'Email subject', 'woocommerce' ),
  362. 'type' => 'text',
  363. 'description' => sprintf( __( 'Defaults to <code>%s</code>', 'woocommerce' ), $this->subject ),
  364. 'placeholder' => '',
  365. 'default' => ''
  366. ),
  367. 'heading' => array(
  368. 'title' => __( 'Email heading', 'woocommerce' ),
  369. 'type' => 'text',
  370. 'description' => sprintf( __( 'Defaults to <code>%s</code>', 'woocommerce' ), $this->heading ),
  371. 'placeholder' => '',
  372. 'default' => ''
  373. ),
  374. 'email_type' => array(
  375. 'title' => __( 'Email type', 'woocommerce' ),
  376. 'type' => 'select',
  377. 'description' => __( 'Choose which format of email to send.', 'woocommerce' ),
  378. 'default' => 'html',
  379. 'class' => 'email_type',
  380. 'options' => array(
  381. 'plain' => __( 'Plain text', 'woocommerce' ),
  382. 'html' => __( 'HTML', 'woocommerce' ),
  383. 'multipart' => __( 'Multipart', 'woocommerce' ),
  384. )
  385. )
  386. );
  387. }
  388. /**
  389. * Admin Panel Options Processing
  390. * - Saves the options to the DB
  391. *
  392. * @since 1.0.0
  393. * @access public
  394. * @return bool
  395. */
  396. public function process_admin_options() {
  397. // Save regular options
  398. parent::process_admin_options();
  399. // Save templates
  400. if ( ! empty( $_POST['template_html_code'] ) && ! empty( $this->template_html ) ) {
  401. $saved = false;
  402. $file = get_stylesheet_directory() . '/woocommerce/' . $this->template_html;
  403. $code = stripslashes( $_POST['template_html_code'] );
  404. if ( is_writeable( $file ) ) {
  405. $f = fopen( $file, 'w+' );
  406. if ( $f !== FALSE ) {
  407. fwrite( $f, $code );
  408. fclose( $f );
  409. $saved = true;
  410. }
  411. }
  412. if ( ! $saved ) {
  413. $redirect = add_query_arg( 'wc_error', urlencode( __( 'Could not write to template file.', 'woocommerce' ) ) );
  414. wp_redirect( $redirect );
  415. exit;
  416. }
  417. }
  418. if ( ! empty( $_POST['template_plain_code'] ) && ! empty( $this->template_plain ) ) {
  419. $saved = false;
  420. $file = get_stylesheet_directory() . '/woocommerce/' . $this->template_plain;
  421. $code = stripslashes( $_POST['template_plain_code'] );
  422. if ( is_writeable( $file ) ) {
  423. $f = fopen( $file, 'w+' );
  424. if ( $f !== FALSE ) {
  425. fwrite( $f, $code );
  426. fclose( $f );
  427. $saved = true;
  428. }
  429. }
  430. if ( ! $saved ) {
  431. $redirect = add_query_arg( 'wc_error', __( 'Could not write to template file.', 'woocommerce' ) );
  432. wp_redirect( $redirect );
  433. exit;
  434. }
  435. }
  436. }
  437. /**
  438. * Admin Options
  439. *
  440. * Setup the gateway settings screen.
  441. * Override this in your gateway.
  442. *
  443. * @since 1.0.0
  444. * @access public
  445. * @return void
  446. */
  447. function admin_options() {
  448. global $woocommerce;
  449. // Handle any actions
  450. if ( ! empty( $this->template_html ) || ! empty( $this->template_plain ) ) {
  451. if ( ! empty( $_GET['move_template'] ) && ( $template = esc_attr( basename( $_GET['move_template'] ) ) ) ) {
  452. if ( ! empty( $this->$template ) ) {
  453. if ( wp_mkdir_p( dirname( get_stylesheet_directory() . '/woocommerce/' . $this->$template ) ) && ! file_exists( get_stylesheet_directory() . '/woocommerce/' . $this->$template ) ) {
  454. copy( $this->template_base . $this->$template, get_stylesheet_directory() . '/woocommerce/' . $this->$template );
  455. echo '<div class="updated fade"><p>' . __( 'Template file copied to theme.', 'woocommerce' ) . '</p></div>';
  456. }
  457. }
  458. }
  459. if ( ! empty( $_GET['delete_template'] ) && ( $template = esc_attr( basename( $_GET['delete_template'] ) ) ) ) {
  460. if ( ! empty( $this->$template ) ) {
  461. if ( file_exists( get_stylesheet_directory() . '/woocommerce/' . $this->$template ) ) {
  462. unlink( get_stylesheet_directory() . '/woocommerce/' . $this->$template );
  463. echo '<div class="updated fade"><p>' . __( 'Template file deleted from theme.', 'woocommerce' ) . '</p></div>';
  464. }
  465. }
  466. }
  467. }
  468. ?>
  469. <h3><?php echo ( ! empty( $this->title ) ) ? $this->title : __( 'Settings','woocommerce' ) ; ?></h3>
  470. <?php echo ( ! empty( $this->description ) ) ? wpautop( $this->description ) : ''; ?>
  471. <table class="form-table">
  472. <?php $this->generate_settings_html(); ?>
  473. </table>
  474. <?php if ( ! empty( $this->template_html ) || ! empty( $this->template_plain ) ) { ?>
  475. <div id="template">
  476. <?php
  477. $templates = array(
  478. 'template_html' => __( 'HTML template', 'woocommerce' ),
  479. 'template_plain' => __( 'Plain text template', 'woocommerce' )
  480. );
  481. foreach ( $templates as $template => $title ) :
  482. if ( empty( $this->$template ) )
  483. continue;
  484. $local_file = get_stylesheet_directory() . '/woocommerce/' . $this->$template;
  485. $core_file = $this->template_base . $this->$template;
  486. ?>
  487. <div class="template <?php echo $template; ?>">
  488. <h4><?php echo wp_kses_post( $title ); ?></h4>
  489. <?php if ( file_exists( $local_file ) ) : ?>
  490. <p>
  491. <a href="#" class="button toggle_editor"></a>
  492. <?php if ( is_writable( $local_file ) ) : ?>
  493. <a href="<?php echo remove_query_arg( array( 'move_template', 'saved' ), add_query_arg( 'delete_template', $template ) ); ?>" class="delete_template button"><?php _e( 'Delete template file', 'woocommerce' ); ?></a>
  494. <?php endif; ?>
  495. <?php printf( __( 'This template has been overridden by your theme and can be found in: <code>%s</code>.', 'woocommerce' ), 'yourtheme/woocommerce/' . $this->$template ); ?>
  496. </p>
  497. <div class="editor" style="display:none">
  498. <textarea class="code" cols="25" rows="20" <?php if ( ! is_writable( $local_file ) ) : ?>readonly="readonly" disabled="disabled"<?php else : ?>data-name="<?php echo $template . '_code'; ?>"<?php endif; ?>><?php echo file_get_contents( $local_file ); ?></textarea>
  499. </div>
  500. <?php elseif ( file_exists( $core_file ) ) : ?>
  501. <p>
  502. <a href="#" class="button toggle_editor"></a>
  503. <?php if ( ( is_dir( get_stylesheet_directory() . '/woocommerce/emails/' ) && is_writable( get_stylesheet_directory() . '/woocommerce/emails/' ) ) || is_writable( get_stylesheet_directory() ) ) : ?>
  504. <a href="<?php echo remove_query_arg( array( 'delete_template', 'saved' ), add_query_arg( 'move_template', $template ) ); ?>" class="button"><?php _e( 'Copy file to theme', 'woocommerce' ); ?></a>
  505. <?php endif; ?>
  506. <?php printf( __( 'To override and edit this email template copy <code>%s</code> to your theme folder: <code>%s</code>.', 'woocommerce' ), plugin_basename( $core_file ) , 'yourtheme/woocommerce/' . $this->$template ); ?>
  507. </p>
  508. <div class="editor" style="display:none">
  509. <textarea class="code" readonly="readonly" disabled="disabled" cols="25" rows="20"><?php echo file_get_contents( $core_file ); ?></textarea>
  510. </div>
  511. <?php else : ?>
  512. <p><?php _e( 'File was not found.', 'woocommerce' ); ?></p>
  513. <?php endif; ?>
  514. </div>
  515. <?php
  516. endforeach;
  517. ?>
  518. </div>
  519. <?php
  520. $woocommerce->add_inline_js("
  521. jQuery('select.email_type').change(function(){
  522. var val = jQuery( this ).val();
  523. jQuery('.template_plain, .template_html').show();
  524. if ( val != 'multipart' && val != 'html' )
  525. jQuery('.template_html').hide();
  526. if ( val != 'multipart' && val != 'plain' )
  527. jQuery('.template_plain').hide();
  528. }).change();
  529. var view = '" . __( 'View template', 'woocommerce' ) . "';
  530. var hide = '" . __( 'Hide template', 'woocommerce' ) . "';
  531. jQuery('a.toggle_editor').text( view ).toggle( function() {
  532. jQuery( this ).text( hide ).closest('.template').find('.editor').slideToggle();
  533. return false;
  534. }, function() {
  535. jQuery( this ).text( view ).closest('.template').find('.editor').slideToggle();
  536. return false;
  537. } );
  538. jQuery('a.delete_template').click(function(){
  539. var answer = confirm('" . __( 'Are you sure you want to delete this template file?', 'woocommerce' ) . "');
  540. if (answer)
  541. return true;
  542. return false;
  543. });
  544. jQuery('.editor textarea').change(function(){
  545. var name = jQuery(this).attr( 'data-name' );
  546. if ( name )
  547. jQuery(this).attr( 'name', name );
  548. });
  549. ");
  550. }
  551. }
  552. }