MonsterInsights_WP_Emails::get_from_address()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 2
eloc 5
c 2
b 0
f 0
nc 2
nop 0
dl 0
loc 9
rs 10
1
<?php
2
3
/**
4
 * Emails.
5
 *
6
 * This class handles all (notification) emails sent by MonsterInsights.
7
 *
8
 * Heavily influenced by the AffiliateWP plugin by Pippin Williamson.
9
 * https://github.com/AffiliateWP/AffiliateWP/blob/master/includes/emails/class-affwp-emails.php
10
 *
11
 * @since 7.10.5
12
 */
13
class MonsterInsights_WP_Emails {
14
15
	/**
16
	 * Holds the from address.
17
	 *
18
	 * @since 7.10.5
19
	 *
20
	 * @var string
21
	 */
22
	private $from_address;
23
24
	/**
25
	 * Holds the from name.
26
	 *
27
	 * @since 7.10.5
28
	 *
29
	 * @var string
30
	 */
31
	private $from_name;
32
33
	/**
34
	 * Holds the reply-to address.
35
	 *
36
	 * @since 7.10.5
37
	 *
38
	 * @var string
39
	 */
40
	private $reply_to = false;
41
42
	/**
43
	 * Holds the carbon copy addresses.
44
	 *
45
	 * @since 7.10.5
46
	 *
47
	 * @var string
48
	 */
49
	private $cc = false;
50
51
	/**
52
	 * Holds the email content type.
53
	 *
54
	 * @since 7.10.5
55
	 *
56
	 * @var string
57
	 */
58
	private $content_type;
59
60
	/**
61
	 * Holds the email headers.
62
	 *
63
	 * @since 7.10.5
64
	 *
65
	 * @var string
66
	 */
67
	private $headers;
68
69
	/**
70
	 * Whether to send email in HTML.
71
	 *
72
	 * @since 7.10.5
73
	 *
74
	 * @var bool
75
	 */
76
	private $html = true;
77
78
	/**
79
	 * The email template to use.
80
	 *
81
	 * @since 7.10.5
82
	 *
83
	 * @var string
84
	 */
85
	private $template;
86
87
	/**
88
	 * Header/footer/body arguments.
89
	 *
90
	 * @since 7.10.5
91
	 *
92
	 * @var array
93
	 */
94
	protected $args;
95
96
	/**
97
	 * Get things going.
98
	 *
99
	 * @since 7.10.5
100
	 */
101
	public function __construct( $template ) {
102
		$this->template = $template;
103
104
		$this->set_initial_args();
105
106
		add_action( 'monsterinsights_email_send_before', array( $this, 'send_before' ) );
107
		add_action( 'monsterinsights_email_send_after', array( $this, 'send_after' ) );
108
	}
109
110
	/**
111
	 * Set a property.
112
	 *
113
	 * @param string $key Object property key.
114
	 * @param mixed $value Object property value.
115
	 *
116
	 * @since 7.10.5
117
	 *
118
	 */
119
	public function __set( $key, $value ) {
120
		$this->$key = $value;
121
	}
122
123
	/**
124
	 * Get the email from name.
125
	 *
126
	 * @return string The email from name
127
	 * @since 7.10.5
128
	 *
129
	 */
130
	public function get_from_name() {
131
132
		if ( ! empty( $this->from_name ) ) {
133
			$this->from_name = $this->process_tag( $this->from_name );
134
		} else {
135
			$this->from_name = get_bloginfo( 'name' );
136
		}
137
138
		return apply_filters( 'monsterinsights_email_from_name', monsterinsights_decode_string( $this->from_name ), $this );
139
	}
140
141
	/**
142
	 * Get the email from address.
143
	 *
144
	 * @return string The email from address.
145
	 * @since 7.10.5
146
	 *
147
	 */
148
	public function get_from_address() {
149
150
		if ( ! empty( $this->from_address ) ) {
151
			$this->from_address = $this->process_tag( $this->from_address );
152
		} else {
153
			$this->from_address = get_option( 'admin_email' );
0 ignored issues
show
Documentation Bug introduced by
It seems like get_option('admin_email') can also be of type false. However, the property $from_address is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
154
		}
155
156
		return apply_filters( 'monsterinsights_email_from_address', $this->from_address, $this );
157
	}
158
159
	/**
160
	 * Get the email reply-to.
161
	 *
162
	 * @return string The email reply-to address.
163
	 * @since 7.10.5
164
	 *
165
	 */
166
	public function get_reply_to() {
167
168
		if ( ! empty( $this->reply_to ) ) {
169
170
			$this->reply_to = $this->process_tag( $this->reply_to );
171
172
			if ( ! is_email( $this->reply_to ) ) {
173
				$this->reply_to = false;
0 ignored issues
show
Documentation Bug introduced by
The property $reply_to was declared of type string, but false is of type false. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
174
			}
175
		}
176
177
		return apply_filters( 'monsterinsights_email_reply_to', $this->reply_to, $this );
178
	}
179
180
	/**
181
	 * Get the email carbon copy addresses.
182
	 *
183
	 * @return string The email reply-to address.
184
	 * @since 7.10.5
185
	 *
186
	 */
187
	public function get_cc() {
188
189
		if ( ! empty( $this->cc ) ) {
190
191
			$this->cc = $this->process_tag( $this->cc );
192
193
			$addresses = array_map( 'trim', explode( ',', $this->cc ) );
194
195
			foreach ( $addresses as $key => $address ) {
196
				if ( ! is_email( $address ) ) {
197
					unset( $addresses[ $key ] );
198
				}
199
			}
200
201
			$this->cc = implode( ',', $addresses );
202
		}
203
204
		return apply_filters( 'monsterinsights_email_cc', $this->cc, $this );
205
	}
206
207
	/**
208
	 * Get the email content type.
209
	 *
210
	 * @return string The email content type.
211
	 * @since 7.10.5
212
	 *
213
	 */
214
	public function get_content_type() {
215
216
		if ( ! $this->content_type && $this->html ) {
217
			$this->content_type = apply_filters( 'monsterinsights_email_default_content_type', 'text/html', $this );
218
		} elseif ( ! $this->html ) {
219
			$this->content_type = 'text/plain';
220
		}
221
222
		return apply_filters( 'monsterinsights_email_content_type', $this->content_type, $this );
223
	}
224
225
	/**
226
	 * Get the email headers.
227
	 *
228
	 * @return string The email headers.
229
	 * @since 7.10.5
230
	 *
231
	 */
232
	public function get_headers() {
233
234
		if ( ! $this->headers ) {
235
			$this->headers = "From: {$this->get_from_name()} <{$this->get_from_address()}>\r\n";
236
			if ( $this->get_reply_to() ) {
237
				$this->headers .= "Reply-To: {$this->get_reply_to()}\r\n";
238
			}
239
			if ( $this->get_cc() ) {
240
				$this->headers .= "Cc: {$this->get_cc()}\r\n";
241
			}
242
			$this->headers .= "Content-Type: {$this->get_content_type()}; charset=utf-8\r\n";
243
		}
244
245
		return apply_filters( 'monsterinsights_email_headers', $this->headers, $this );
246
	}
247
248
	/**
249
	 * Set initial arguments to use in a template.
250
	 *
251
	 * @since 7.10.5
252
	 */
253
	public function set_initial_args() {
254
		$header_args = array(
0 ignored issues
show
Unused Code introduced by
The assignment to $header_args is dead and can be removed.
Loading history...
255
			'title' => esc_html__( 'MonsterInsights', 'google-analytics-for-wordpress' ),
256
		);
257
258
		$args = array(
259
			'header' => array(),
260
			'body'   => array(),
261
			'footer' => array(),
262
		);
263
264
		$from_address = $this->get_from_address();
265
		if ( ! empty( $from_address ) ) {
266
			$args['footer']['from_address'] = $from_address;
267
		}
268
269
		$args = apply_filters( 'monsterinsights_emails_templates_set_initial_args', $args, $this );
270
271
		$this->set_args( $args );
272
	}
273
274
	/**
275
	 * Set header/footer/body/style arguments to use in a template.
276
	 *
277
	 * @param array $args Arguments to set.
278
	 * @param bool $merge Merge the arguments with existing once or replace.
279
	 *
280
	 * @return MI_WP_Emails
0 ignored issues
show
Bug introduced by
The type MI_WP_Emails was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
281
	 * @since 7.10.5
282
	 *
283
	 */
284
	public function set_args( $args, $merge = true ) {
285
286
		$args = apply_filters( 'monsterinsights_emails_templates_set_args', $args, $this );
287
288
		if ( empty( $args ) || ! is_array( $args ) ) {
289
			return $this;
290
		}
291
292
		foreach ( $args as $type => $value ) {
293
294
			if ( ! is_array( $value ) ) {
295
				continue;
296
			}
297
298
			if ( ! isset( $this->args[ $type ] ) || ! is_array( $this->args[ $type ] ) ) {
299
				$this->args[ $type ] = array();
300
			}
301
302
			$this->args[ $type ] = $merge ? array_merge( $this->args[ $type ], $value ) : $value;
303
		}
304
305
		return $this;
306
	}
307
308
	/**
309
	 * Get header/footer/body arguments
310
	 *
311
	 * @param string $type Header/footer/body.
312
	 *
313
	 * @return array
314
	 * @since 7.10.5
315
	 *
316
	 */
317
	public function get_args( $type ) {
318
		if ( ! empty( $type ) ) {
319
			return isset( $this->args[ $type ] ) ? apply_filters( 'monsterinsights_emails_templates_get_args_' . $type, $this->args[ $type ], $this ) : array();
320
		}
321
322
		return apply_filters( 'monsterinsights_emails_templates_get_args', $this->args, $this );
323
	}
324
325
	/**
326
	 * Build the email.
327
	 *
328
	 * @param string $message The email message.
329
	 *
330
	 * @return string
331
	 * @since 7.10.5
332
	 *
333
	 */
334
	public function build_email( $message = null ) {
335
		// process plain text email
336
		if ( false === $this->html ) {
337
			$body    = $this->get_template_part( 'body', $this->get_template(), true );
338
			$body    = wp_strip_all_tags( $body );
339
			$message = str_replace( '{email}', $message, $body );
340
341
			return apply_filters( 'monsterinsights_email_message', $message, $this );
342
		}
343
344
		// process html email template
345
		$email_parts           = array();
346
		$email_parts['header'] = $this->get_template_part( 'header', $this->get_template(), true );
347
348
		// Hooks into the email header.
349
		do_action( 'monsterinsights_email_header', $email_parts['header'] );
350
351
		$email_parts['body'] = $this->get_template_part( 'body', $this->get_template(), true );
352
353
		// Hooks into the email body.
354
		do_action( 'monsterinsights_email_body', $email_parts['body'] );
355
356
		$email_parts['footer'] = $this->get_template_part( 'footer', $this->get_template(), true );
357
358
		// Hooks into the email footer.
359
		do_action( 'monsterinsights_email_footer', $email_parts['footer'] );
360
361
		$body    = implode( $email_parts );
362
		$message = $this->process_tag( $message, false );
363
		$message = $message ? nl2br( $message ) : '';
364
		$message = str_replace( '{email}', $message, $body );
365
366
		//$message = make_clickable( $message );
367
368
		return apply_filters( 'monsterinsights_email_message', $message, $this );
369
	}
370
371
	/**
372
	 * Send the email.
373
	 *
374
	 * @param string $to The To address.
375
	 * @param string $subject The subject line of the email.
376
	 * @param string $message The body of the email.
377
	 * @param array $attachments Attachments to the email.
378
	 *
379
	 * @return bool
380
	 * @since 7.10.5
381
	 *
382
	 */
383
	public function send( $to, $subject, $message = null, $attachments = array() ) {
384
385
		if ( ! did_action( 'init' ) && ! did_action( 'admin_init' ) ) {
386
			_doing_it_wrong( __FUNCTION__, esc_html__( 'You cannot send emails with MI_WP_Emails() until init/admin_init has been reached.', 'google-analytics-for-wordpress' ), null );
387
388
			return false;
389
		}
390
391
		// Don't send anything if emails have been disabled.
392
		if ( $this->is_email_disabled() ) {
393
			return false;
394
		}
395
396
		// Don't send if email address is invalid.
397
		if ( is_string( $to ) && ! is_email( $to ) ) {
398
			return false;
399
		}
400
401
		// Hooks before email is sent.
402
		do_action( 'monsterinsights_email_send_before', $this );
403
404
		/*
405
		 * Allow to filter data on per-email basis,
406
		 * useful for localizations based on recipient email address, form settings,
407
		 * or for specific notifications - whatever available in MI_WP_Emails class.
408
		 */
409
		$data = apply_filters(
410
			'monsterinsights_emails_send_email_data',
411
			array(
412
				'to'          => $to,
413
				'subject'     => $subject,
414
				'message'     => $message,
415
				'headers'     => $this->get_headers(),
416
				'attachments' => $attachments,
417
			),
418
			$this
419
		);
420
421
		// Let's do this.
422
		$sent = wp_mail(
423
			$data['to'],
424
			monsterinsights_decode_string( $this->process_tag( $data['subject'] ) ),
425
			$this->build_email( $data['message'] ),
426
			$data['headers'],
427
			$data['attachments']
428
		);
429
430
		// Hooks after the email is sent.
431
		do_action( 'monsterinsights_email_send_after', $this );
432
433
		return $sent;
434
	}
435
436
	/**
437
	 * Add filters/actions before the email is sent.
438
	 *
439
	 * @since 7.10.5
440
	 */
441
	public function send_before() {
442
443
		add_filter( 'wp_mail_from', array( $this, 'get_from_address' ) );
444
		add_filter( 'wp_mail_from_name', array( $this, 'get_from_name' ) );
445
		add_filter( 'wp_mail_content_type', array( $this, 'get_content_type' ) );
446
	}
447
448
	/**
449
	 * Remove filters/actions after the email is sent.
450
	 *
451
	 * @since 7.10.5
452
	 */
453
	public function send_after() {
454
455
		remove_filter( 'wp_mail_from', array( $this, 'get_from_address' ) );
456
		remove_filter( 'wp_mail_from_name', array( $this, 'get_from_name' ) );
457
		remove_filter( 'wp_mail_content_type', array( $this, 'get_content_type' ) );
458
	}
459
460
	/**
461
	 * Process a smart tag.
462
	 *
463
	 * @param string $string String that may contain tags.
464
	 * @param bool $sanitize Toggle to maybe sanitize.
465
	 * @param bool $linebreaks Toggle to process linebreaks.
466
	 *
467
	 * @return string
468
	 * @since 7.10.5
469
	 *
470
	 */
471
	public function process_tag( $string = '', $sanitize = true, $linebreaks = false ) {
472
473
		$tag = apply_filters( 'monsterinsights_process_smart_tags', $string );
474
475
		$tag = monsterinsights_decode_string( $tag );
476
477
		if ( $sanitize ) {
478
			if ( $linebreaks ) {
479
				$tag = monsterinsights_sanitize_textarea_field( $tag );
480
			} else {
481
				$tag = sanitize_text_field( $tag );
482
			}
483
		}
484
485
		return $tag;
486
	}
487
488
	/**
489
	 * Email kill switch if needed.
490
	 *
491
	 * @return bool
492
	 * @since 7.10.5
493
	 *
494
	 */
495
	public function is_email_disabled() {
496
		return (bool) apply_filters( 'monsterinsights_disable_all_emails', false, $this );
497
	}
498
499
	/**
500
	 * Get the enabled email template.
501
	 *
502
	 * @return string When filtering return 'default' to switch to text/plain email.
503
	 * @since 7.10.5
504
	 *
505
	 */
506
	public function get_template() {
507
508
		if ( ! empty( $this->template ) ) {
509
			$this->template = $this->process_tag( $this->template );
510
		} else {
511
			$this->template = 'default';
512
		}
513
514
		return apply_filters( 'monsterinsights_email_template', $this->template );
515
	}
516
517
	/**
518
	 * Retrieves a template content.
519
	 *
520
	 * @param string $slug Template file slug.
521
	 * @param string $name Optional. Default null.
522
	 * @param bool $load Maybe load.
523
	 *
524
	 * @return string
525
	 * @since 7.10.5
526
	 *
527
	 */
528
	public function get_template_part( $slug, $name = null, $load = true ) {
0 ignored issues
show
Unused Code introduced by
The parameter $load is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

528
	public function get_template_part( $slug, $name = null, /** @scrutinizer ignore-unused */ $load = true ) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
529
530
		if ( false === $this->html ) {
531
			$name .= '-plain';
532
		}
533
534
		if ( isset( $name ) ) {
535
			$template = $slug . '-' . $name;
536
537
			$html = $this->get_html(
538
				$template,
539
				$this->get_args( $slug ),
540
				true
541
			);
542
543
			return apply_filters( 'monsterinsights_emails_templates_get_content_part', $html, $template, $this );
544
		}
545
546
	}
547
548
	/**
549
	 * Like $this->include_html, but returns the HTML instead of including.
550
	 *
551
	 * @param string $template_name Template name.
552
	 * @param array $args Arguments.
553
	 * @param bool $extract Extract arguments.
554
	 *
555
	 * @return string
556
	 * @since 7.10.5
557
	 *
558
	 */
559
	public static function get_html( $template_name, $args = array(), $extract = false ) {
560
		ob_start();
561
		self::include_html( $template_name, $args, $extract );
562
563
		return ob_get_clean();
564
	}
565
566
	/**
567
	 * Include a template.
568
	 * Uses 'require' if $args are passed or 'load_template' if not.
569
	 *
570
	 * @param string $template_name Template name.
571
	 * @param array $args Arguments.
572
	 * @param bool $extract Extract arguments.
573
	 *
574
	 * @throws \RuntimeException If extract() tries to modify the scope.
575
	 * @since 7.10.5
576
	 *
577
	 */
578
	public static function include_html( $template_name, $args = array(), $extract = false ) {
579
580
		$template_name .= '.php';
581
582
		// Allow 3rd party plugins to filter template file from their plugin.
583
		$located = apply_filters( 'monsterinsights_helpers_templates_include_html_located', self::locate_template( $template_name ), $template_name, $args, $extract );
584
		$args    = apply_filters( 'monsterinsights_helpers_templates_include_html_args', $args, $template_name, $extract );
585
586
		if ( empty( $located ) || ! is_readable( $located ) ) {
587
			return;
588
		}
589
590
		// Load template WP way if no arguments were passed.
591
		if ( empty( $args ) ) {
592
			load_template( $located, false );
593
594
			return;
595
		}
596
597
		$extract = apply_filters( 'monsterinsights_helpers_templates_include_html_extract_args', $extract, $template_name, $args );
598
599
		if ( $extract && is_array( $args ) ) {
600
601
			$created_vars_count = extract( $args, EXTR_SKIP ); // phpcs:ignore WordPress.PHP.DontExtract
602
603
			// Protecting existing scope from modification.
604
			if ( count( $args ) !== $created_vars_count ) {
605
				throw new RuntimeException( 'Extraction failed: variable names are clashing with the existing ones.' );
606
			}
607
		}
608
609
		require $located;
610
	}
611
612
	/**
613
	 * Locate a template and return the path for inclusion.
614
	 *
615
	 * @param string $template_name Template name.
616
	 *
617
	 * @return string
618
	 * @since 7.10.5
619
	 *
620
	 */
621
	public static function locate_template( $template_name ) {
622
623
		// Trim off any slashes from the template name.
624
		$template_name = ltrim( $template_name, '/' );
625
626
		if ( empty( $template_name ) ) {
627
			return apply_filters( 'monsterinsights_helpers_templates_locate', '', $template_name );
628
		}
629
630
		$located = '';
631
632
		// Try locating this template file by looping through the template paths.
633
		foreach ( self::get_theme_template_paths() as $template_path ) {
634
			if ( file_exists( $template_path . $template_name ) ) {
635
				$located = $template_path . $template_name;
636
				break;
637
			}
638
		}
639
640
		return apply_filters( 'monsterinsights_helpers_templates_locate', $located, $template_name );
641
	}
642
643
	/**
644
	 * Return a list of paths to check for template locations
645
	 *
646
	 * @return array
647
	 * @since 7.10.5
648
	 *
649
	 */
650
	public static function get_theme_template_paths() {
651
652
		$template_dir = 'monsterinsights-email';
653
654
		$file_paths = array(
655
			1   => trailingslashit( get_stylesheet_directory() ) . $template_dir,
656
			10  => trailingslashit( get_template_directory() ) . $template_dir,
657
			100 => trailingslashit( MONSTERINSIGHTS_PLUGIN_DIR ) . 'includes/emails/templates',
658
		);
659
660
		$file_paths = apply_filters( 'monsterinsights_email_template_paths', $file_paths );
661
662
		// Sort the file paths based on priority.
663
		ksort( $file_paths, SORT_NUMERIC );
664
665
		return array_map( 'trailingslashit', $file_paths );
666
	}
667
}
668