Completed
Push — develop ( dfa7d2...0cc879 )
by Paul
02:12
created

Email::send()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 16
rs 9.2
c 0
b 0
f 0
cc 4
eloc 10
nc 2
nop 0
1
<?php
2
3
namespace GeminiLabs\Castor\Services;
4
5
use GeminiLabs\Castor\Helpers\Template;
6
7
class Email
8
{
9
	/**
10
	 * @var Template
11
	 */
12
	public $template;
13
14
	/**
15
	 * @var array
16
	 */
17
	protected $attachments;
18
19
	/**
20
	 * @var array
21
	 */
22
	protected $headers;
23
24
	/**
25
	 * @var string
26
	 */
27
	protected $message;
28
29
	/**
30
	 * @var string
31
	 */
32
	protected $subject;
33
34
	/**
35
	 * @var string
36
	 */
37
	protected $to;
38
39
	public function __construct( Template $template )
40
	{
41
		$this->template = $template;
42
	}
43
44
	/**
45
	 * @return Email
46
	 */
47
	public function compose( array $email )
48
	{
49
		$email = $this->normalize( $email );
50
51
		$this->attachments = $email['attachments'];
52
		$this->headers     = $this->buildHeaders( $email );
53
		$this->message     = $this->buildHtmlMessage( $email );
54
		$this->subject     = $email['subject'];
55
		$this->to          = $email['to'];
56
57
		add_action( 'phpmailer_init', function( PHPMailer $phpmailer ) {
58
			if( $phpmailer->ContentType === 'text/plain' || !empty( $phpmailer->AltBody ))return;
59
			$phpmailer->AltBody = $this->buildPlainTextMessage( $phpmailer->Body );
60
		});
61
62
		return $this;
63
	}
64
65
	/**
66
	 * @param bool $plaintext
67
	 *
68
	 * @return string|null
69
	 */
70
	public function read( $plaintext = false )
71
	{
72
		return $plaintext
73
			? $this->buildPlainTextMessage( $this->message )
74
			: $this->message;
75
	}
76
77
	/**
78
	 * @return bool|null
79
	 */
80
	public function send()
81
	{
82
		if( !$this->message || !$this->subject || !$this->to )return;
83
84
		$sent = wp_mail(
85
			$this->to,
86
			$this->subject,
87
			$this->message,
88
			$this->headers,
89
			$this->attachments
90
		);
91
92
		$this->reset();
93
94
		return $sent;
95
	}
96
97
	/**
98
	 * @return array
99
	 */
100
	protected function buildHeaders( array $email )
101
	{
102
		$allowed = [
103
			'bcc',
104
			'cc',
105
			'from',
106
			'reply-to',
107
		];
108
109
		$headers = array_intersect_key( $email, array_flip( $allowed ));
110
		$headers = array_filter( $headers );
111
112
		foreach( $headers as $key => $value ) {
113
			unset( $headers[ $key ] );
114
			$headers[] = sprintf( '%s: %s', $key, $value );
115
		}
116
117
		$headers[] = 'Content-Type: text/html';
118
119
		return apply_filters( 'castor/email/headers', $headers, $this );
120
	}
121
122
	/**
123
	 * @return string
124
	 */
125
	protected function buildHtmlMessage( array $email )
126
	{
127
		$body = $this->renderTemplate( 'email' );
128
129
		$message = !empty( $email['template'] )
130
			? $this->renderTemplate( $email['template'], $email['template-tags'] )
131
			: $email['message'];
132
133
		$message = $this->filterHtml( $email['before'] . $message . $email['after'] );
134
		$message = str_replace( '{message}', $message, $body );
135
136
		return apply_filters( 'castor/email/message', $message, 'html', $this );
137
	}
138
139
	/**
140
	 * @param string $message
141
	 *
142
	 * @return string
143
	 */
144
	protected function buildPlainTextMessage( $message )
145
	{
146
		return apply_filters( 'castor/email/message', $this->stripHtmlTags( $message ), 'text', $this );
147
	}
148
149
	/**
150
	 * @param string $message
151
	 *
152
	 * @return string
153
	 */
154
	protected function filterHtml( $message )
155
	{
156
		$message = strip_shortcodes( $message );
157
		$message = wptexturize( $message );
158
		$message = wpautop( $message );
159
		$message = str_replace( ['&lt;&gt; ', ']]>'], ['', ']]&gt;'], $message );
160
		$message = stripslashes( $message );
161
		return $message;
162
	}
163
164
	/**
165
	 * @return string
166
	 */
167
	protected function getDefaultFrom()
168
	{
169
		return sprintf( '%s <%s>',
170
			wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ),
171
			get_option( 'admin_email' )
172
		);
173
	}
174
175
	/**
176
	 * @return array
177
	 */
178
	protected function normalize( $email )
179
	{
180
		$defaults = array_flip([
181
			'after', 'attachments', 'bcc', 'before', 'cc', 'from', 'message', 'reply-to', 'subject',
182
			'template', 'template-tags', 'to',
183
		]);
184
		$defaults['from'] = $this->getDefaultFrom();
185
186
		$email = shortcode_atts( $defaults, $email );
187
188
		foreach( ['attachments', 'template-tags'] as $key ) {
189
			$email[$key] = array_filter( (array) $email[$key] );
190
		}
191
		if( empty( $email['reply-to'] )) {
192
			$email['reply-to'] = $email['from'];
193
		}
194
195
		return apply_filters( 'castor/email/compose', $email, $this );
196
	}
197
198
	/**
199
	 * Override by adding the custom template to "templates/castor/" in your theme
200
	 *
201
	 * @param string $templatePath
202
	 *
203
	 * @return void|string
204
	 */
205
	protected function renderTemplate( $templatePath, array $args = [] )
206
	{
207
		$file = $this->template->get( sprintf( 'castor/%s', $templatePath ));
208
209
		if( !file_exists( $file )) {
210
			$file = sprintf( '%s/templates/%s.php', dirname( __DIR__ ), $templatePath );
211
		}
212
		if( !file_exists( $file ))return;
213
214
		ob_start();
215
		include $file;
216
		$template = ob_get_clean();
217
218
		return $this->renderTemplateString( $template, $args );
219
	}
220
221
	/**
222
	 * @param string $template
223
	 *
224
	 * @return string
225
	 */
226
	protected function renderTemplateString( $template, array $args = [] )
227
	{
228
		foreach( $args as $key => $value ) {
229
			$template = str_replace( sprintf( '{%s}', $key ), $value, $template );
230
		}
231
		return trim( $template );
232
	}
233
234
	/**
235
	 * @return void
236
	 */
237
	protected function reset()
238
	{
239
		$this->attachments = [];
240
		$this->headers = [];
241
		$this->message = null;
242
		$this->subject = null;
243
		$this->to = null;
244
	}
245
246
	/**
247
	 * - remove invisible elements
248
	 * - replace certain elements with a line-break
249
	 * - replace certain table elements with a space
250
	 * - add a placeholder for plain-text bullets to list elements
251
	 * - strip all remaining HTML tags
252
	 * @return string
253
	 */
254
	protected function stripHtmlTags( $string )
255
	{
256
		$string = preg_replace( '@<(embed|head|noembed|noscript|object|script|style)[^>]*?>.*?</\\1>@siu', '', $string );
257
		$string = preg_replace( '@</(div|h[1-9]|p|pre|tr)@iu', "\r\n\$0", $string );
258
		$string = preg_replace( '@</(td|th)@iu', " \$0", $string );
259
		$string = preg_replace( '@<(li)[^>]*?>@siu', "\$0-o-^-o-", $string );
260
		$string = wp_strip_all_tags( $string );
261
		$string = wp_specialchars_decode( $string, ENT_QUOTES );
262
		$string = preg_replace( '/\v(?:[\v\h]+){2,}/', "\r\n\r\n", $string );
263
		$string = str_replace( '-o-^-o-', ' - ', $string );
264
		return html_entity_decode( $string, ENT_QUOTES, 'UTF-8' );
265
	}
266
}
267