Completed
Push — develop ( d8eab8...c1cebe )
by Paul
01:57
created

Email::normalize()   B

Complexity

Conditions 5
Paths 12

Size

Total Lines 22
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

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