PluginProbe ʕ •ᴥ•ʔ
WP Mail SMTP by WPForms – The Most Popular SMTP and Email Log Plugin / 4.9.0
WP Mail SMTP by WPForms – The Most Popular SMTP and Email Log Plugin v4.9.0
4.9.0 0.9.6 1.0.0 1.0.1 1.0.2 1.1.0 1.2.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.3.0 1.3.1 1.3.2 1.3.3 1.4.0 1.4.1 1.4.2 1.5.0 1.5.1 1.5.2 1.6.0 1.6.2 1.7.0 1.7.1 1.8.0 1.8.1 1.9.0 2.0.0 2.0.1 2.1.1 2.2.1 2.3.1 2.4.0 2.5.0 2.5.1 2.6.0 2.7.0 2.8.0 2.9.0 3.0.1 3.0.2 3.0.3 3.1.0 3.10.0 3.11.0 3.11.1 3.2.0 3.2.1 3.3.0 3.4.0 3.5.0 3.5.1 3.5.2 3.6.1 3.7.0 3.8.0 3.8.2 3.9.0 4.0.1 4.1.0 4.1.1 4.2.0 4.3.0 4.4.0 4.5.0 4.6.0 4.7.0 4.7.1 4.8.0 trunk 0.10.0 0.10.1 0.11.1 0.11.2 0.3.1 0.3.2 0.4 0.4.1 0.4.2 0.5.0 0.5.1 0.5.2 0.6 0.7 0.8 0.8.2 0.8.3 0.8.4 0.8.5 0.8.6 0.8.7 0.9.0 0.9.1 0.9.2 0.9.3 0.9.4 0.9.5
wp-mail-smtp / src / WP.php
wp-mail-smtp / src Last commit date
Abilities 5 days ago Admin 5 days ago Compatibility 5 days ago Helpers 5 days ago Integrations 5 days ago Providers 5 days ago Queue 5 days ago Reports 5 days ago Tasks 5 days ago TestEmail 5 days ago UsageTracking 5 days ago WPCLI 5 days ago AbstractConnection.php 5 days ago Conflicts.php 5 days ago Connect.php 5 days ago Connection.php 5 days ago ConnectionInterface.php 5 days ago ConnectionsManager.php 5 days ago Core.php 5 days ago DBRepair.php 5 days ago Debug.php 5 days ago EmailSendingDebug.php 5 days ago Geo.php 5 days ago MailCatcher.php 5 days ago MailCatcherInterface.php 5 days ago MailCatcherTrait.php 5 days ago MailCatcherV6.php 5 days ago Migration.php 5 days ago MigrationAbstract.php 5 days ago Migrations.php 5 days ago OptimizedEmailSending.php 5 days ago Options.php 5 days ago Processor.php 5 days ago SiteHealth.php 5 days ago Upgrade.php 5 days ago Uploads.php 5 days ago WP.php 5 days ago WPMailArgs.php 5 days ago WPMailInitiator.php 5 days ago
WP.php
975 lines
1 <?php
2
3 namespace WPMailSMTP;
4
5 use WPMailSMTP\Admin\DebugEvents\DebugEvents;
6 use WPMailSMTP\Helpers\Helpers;
7
8 /**
9 * Class WP provides WordPress shortcuts.
10 *
11 * @since 1.0.0
12 */
13 class WP {
14
15 /**
16 * The "queue" of notices.
17 *
18 * @since 1.0.0
19 *
20 * @var array
21 */
22 protected static $admin_notices = [];
23
24 /**
25 * CSS class for a success notice.
26 *
27 * @since 1.0.0
28 *
29 * @var string
30 */
31 const ADMIN_NOTICE_SUCCESS = 'notice-success';
32
33 /**
34 * CSS class for an error notice.
35 *
36 * @since 1.0.0
37 *
38 * @var string
39 */
40 const ADMIN_NOTICE_ERROR = 'notice-error';
41
42 /**
43 * CSS class for an info notice.
44 *
45 * @since 1.0.0
46 *
47 * @var string
48 */
49 const ADMIN_NOTICE_INFO = 'notice-info';
50
51 /**
52 * CSS class for a warning notice.
53 *
54 * @since 1.0.0
55 *
56 * @var string
57 */
58 const ADMIN_NOTICE_WARNING = 'notice-warning';
59
60 /**
61 * Cross-platform line break.
62 *
63 * @since 3.4.0
64 *
65 * @var string
66 */
67 const EOL = "\r\n";
68
69 /**
70 * True if WP is processing an AJAX call.
71 *
72 * @since 1.0.0
73 *
74 * @return bool
75 */
76 public static function is_doing_ajax() {
77
78 if ( function_exists( 'wp_doing_ajax' ) ) {
79 return wp_doing_ajax();
80 }
81
82 return ( defined( 'DOING_AJAX' ) && DOING_AJAX );
83 }
84
85 /**
86 * True if I am in the Admin Panel, not doing AJAX.
87 *
88 * @since 1.0.0
89 *
90 * @return bool
91 */
92 public static function in_wp_admin() {
93
94 return ( is_admin() && ! self::is_doing_ajax() );
95 }
96
97 /**
98 * Add a notice to the "queue of notices".
99 *
100 * @since 1.0.0
101 * @since 1.5.0 Added `$is_dismissible` param.
102 *
103 * @param string $message Message text (HTML is OK).
104 * @param string $class Display class (severity).
105 * @param bool $is_dismissible Whether the message should be dismissible.
106 * @param string $key Unique key for the notice. If defined, dismissible notice will be dismissed permanently.
107 */
108 public static function add_admin_notice( $message, $class = self::ADMIN_NOTICE_INFO, $is_dismissible = true, $key = '', $error_code = '' ) {
109
110 self::$admin_notices[] = [
111 'message' => $message,
112 'class' => $class,
113 'is_dismissible' => (bool) $is_dismissible,
114 'key' => sanitize_key( $key ),
115 'error_code' => $error_code,
116 ];
117 }
118
119 /**
120 * Add an admin notice and append the DebugEvent referenced by `?debug_event_id=` in
121 * the current request URL (if any) on a new line below the notice copy.
122 *
123 * Use this from redirect-based flows where the upstream request constructed a URL
124 * like `?error=foo&debug_event_id=42` to carry the underlying technical detail to
125 * the landing page. The opt-in form (rather than auto-detection in add_admin_notice)
126 * prevents the detail from attaching to unrelated notices that happen to render on
127 * the same page.
128 *
129 * Signature mirrors add_admin_notice.
130 *
131 * @since 4.9.0
132 *
133 * @param string $message Message text (HTML is OK).
134 * @param string $class Display class (severity).
135 * @param bool $is_dismissible Whether the message should be dismissible.
136 * @param string $key Unique key for the notice.
137 * @param string $error_code Optional error code displayed next to the notice.
138 */
139 public static function add_admin_notice_with_debug( $message, $class = self::ADMIN_NOTICE_INFO, $is_dismissible = true, $key = '', $error_code = '' ) {
140
141 self::add_admin_notice(
142 $message . self::get_debug_event_detail_html(),
143 $class,
144 $is_dismissible,
145 $key,
146 $error_code
147 );
148 }
149
150 /**
151 * Render the inline HTML for a debug event referenced by the current request URL.
152 *
153 * Cached per request — the DebugEvents lookup runs at most once. Returns an empty
154 * string when no `debug_event_id` is present in the URL or the event cannot be
155 * resolved.
156 *
157 * @since 4.9.0
158 *
159 * @return string
160 */
161 private static function get_debug_event_detail_html() {
162
163 static $cache = [];
164
165 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
166 $event_id = isset( $_GET['debug_event_id'] ) ? absint( wp_unslash( $_GET['debug_event_id'] ) ) : 0;
167
168 if ( isset( $cache[ $event_id ] ) ) {
169 return $cache[ $event_id ];
170 }
171
172 $cache[ $event_id ] = '';
173
174 if ( $event_id <= 0 ) {
175 return $cache[ $event_id ];
176 }
177
178 $details = DebugEvents::get_debug_messages( $event_id );
179 $detail = is_array( $details ) ? reset( $details ) : '';
180
181 if ( ! empty( $detail ) ) {
182 $cache[ $event_id ] = '<code class="wp-mail-smtp-notice__debug-detail">' . esc_html( $detail ) . '</code>';
183 }
184
185 return $cache[ $event_id ];
186 }
187
188 /**
189 * Display all notices.
190 *
191 * @since 1.0.0
192 * @since 1.5.0 Allow the notice to be dismissible, remove the id attribute, which is not unique.
193 */
194 public static function display_admin_notices() {
195
196 $has_notices = false;
197
198 foreach ( (array) self::$admin_notices as $notice ) :
199 $is_dismissible = $notice['is_dismissible'];
200 $dismissible = $is_dismissible ? 'is-dismissible' : '';
201
202 if (
203 $is_dismissible &&
204 ! empty( $notice['key'] ) &&
205 (bool) get_user_meta( get_current_user_id(), "wp_mail_smtp_notice_{$notice['key']}_dismissed", true )
206 ) {
207 continue;
208 }
209
210 $has_notices = true;
211 ?>
212
213 <div class="notice wp-mail-smtp-notice <?php echo esc_attr( $notice['class'] ); ?> <?php echo esc_attr( $dismissible ); ?>" <?php echo ! empty( $notice['key'] ) ? 'data-notice="' . esc_attr( $notice['key'] ) . '"' : ''; ?>>
214 <p>
215 <?php echo wp_kses_post( $notice['message'] ); ?>
216 </p>
217 <?php if ( ! empty( $notice['error_code'] ) ) : ?>
218 <div class="wp-mail-smtp-notice__error-code">
219 <code><?php echo esc_html( $notice['error_code'] ); ?></code>
220 <button type="button" class="wp-mail-smtp-notice__copy-btn" title="<?php esc_attr_e( 'Copy error code', 'wp-mail-smtp' ); ?>">
221 <svg class="wp-mail-smtp-notice__icon-copy" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z"/></svg>
222 <svg class="wp-mail-smtp-notice__icon-check" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" style="display:none;"><path fill="#00A32A" d="M256 512c141.4 0 256-114.6 256-256S397.4 0 256 0S0 114.6 0 256S114.6 512 256 512zM369 209L241 337c-9.4 9.4-24.6 9.4-33.9 0l-64-64c-9.4-9.4-9.4-24.6 0-33.9s24.6-9.4 33.9 0l47 47L335 175c9.4-9.4 24.6-9.4 33.9 0s9.4 24.6 0 33.9z"/></svg>
223 </button>
224 </div>
225 <?php endif; ?>
226 </div>
227
228 <?php
229 endforeach;
230
231 if ( $has_notices ) {
232 wp_enqueue_script(
233 'wp-mail-smtp-admin-notices',
234 wp_mail_smtp()->assets_url . '/js/smtp-admin-notices' . self::asset_min() . '.js',
235 [ 'jquery' ],
236 WPMS_PLUGIN_VER,
237 true
238 );
239
240 wp_localize_script(
241 'wp-mail-smtp-admin-notices',
242 'wp_mail_smtp_admin_notices',
243 [
244 'nonce' => wp_create_nonce( 'wp-mail-smtp-admin' ),
245 ]
246 );
247 }
248 }
249
250 /**
251 * Check whether WP_DEBUG is active.
252 *
253 * @since 1.0.0
254 *
255 * @return bool
256 */
257 public static function is_debug() {
258
259 return defined( 'WP_DEBUG' ) && WP_DEBUG;
260 }
261
262 /**
263 * Shortcut to global $wpdb.
264 *
265 * @since 1.0.0
266 *
267 * @return \wpdb
268 */
269 public static function wpdb() {
270 global $wpdb;
271
272 return $wpdb;
273 }
274
275 /**
276 * Get the postfix for assets files - ".min" or empty.
277 * ".min" if in production mode.
278 *
279 * @since 1.0.0
280 *
281 * @return string
282 */
283 public static function asset_min() {
284
285 $min = '.min';
286
287 if ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) {
288 $min = '';
289 }
290
291 return $min;
292 }
293
294 /**
295 * Check whether the string is a JSON or not.
296 *
297 * @since 1.5.0
298 *
299 * @param string $string String we want to test if it's json.
300 *
301 * @return bool
302 */
303 public static function is_json( $string ) {
304
305 return is_string( $string ) && is_array( json_decode( $string, true ) ) && ( json_last_error() === JSON_ERROR_NONE ) ? true : false;
306 }
307
308 /**
309 * Get the full date format as per WP options.
310 *
311 * @since 1.5.0
312 *
313 * @return string
314 */
315 public static function datetime_format() {
316
317 return sprintf( /* translators: %1$s - date, \a\t - specially escaped "at", %2$s - time. */
318 esc_html__( '%1$s \a\t %2$s', 'wp-mail-smtp' ),
319 get_option( 'date_format' ),
320 get_option( 'time_format' )
321 );
322 }
323
324 /**
325 * Get the full date form as per MySQL format.
326 *
327 * @since 1.5.0
328 *
329 * @return string
330 */
331 public static function datetime_mysql_format() {
332
333 return 'Y-m-d H:i:s';
334 }
335
336 /**
337 * Sanitize the value, similar to `sanitize_text_field()`, but a bit differently.
338 * It preserves `<` and `>` for non-HTML tags.
339 *
340 * @since 1.5.0
341 *
342 * @param string $value String we want to sanitize.
343 *
344 * @return string
345 */
346 public static function sanitize_value( $value ) {
347
348 // Remove HTML tags.
349 $filtered = wp_strip_all_tags( $value, false );
350 // Remove multi-lines/tabs.
351 $filtered = preg_replace( '/[\r\n\t ]+/', ' ', $filtered );
352 // Remove whitespaces.
353 $filtered = trim( $filtered );
354
355 // Remove octets.
356 $found = false;
357 while ( preg_match( '/%[a-f0-9]{2}/i', $filtered, $match ) ) {
358 $filtered = str_replace( $match[0], '', $filtered );
359 $found = true;
360 }
361
362 if ( $found ) {
363 // Strip out the whitespace that may now exist after removing the octets.
364 $filtered = trim( preg_replace( '/ +/', ' ', $filtered ) );
365 }
366
367 return $filtered;
368 }
369
370 /**
371 * Get default email address.
372 *
373 * This is the same code as used in WP core for getting the default email address.
374 *
375 * @see https://github.com/WordPress/WordPress/blob/master/wp-includes/pluggable.php#L332
376 *
377 * @since 2.2.0
378 * @since 2.3.0 In WP 5.5 the core code changed and is now using `network_home_url`.
379 *
380 * @return string
381 */
382 public static function get_default_email() {
383
384 if ( version_compare( get_bloginfo( 'version' ), '5.5-alpha', '<' ) ) {
385 $sitename = ! empty( $_SERVER['SERVER_NAME'] ) ?
386 strtolower( sanitize_text_field( wp_unslash( $_SERVER['SERVER_NAME'] ) ) ) :
387 wp_parse_url( get_home_url( get_current_blog_id() ), PHP_URL_HOST );
388 } else {
389 $sitename = wp_parse_url( network_home_url(), PHP_URL_HOST );
390 }
391
392 if ( 'www.' === substr( $sitename, 0, 4 ) ) {
393 $sitename = substr( $sitename, 4 );
394 }
395
396 return 'wordpress@' . $sitename;
397 }
398
399 /**
400 * Wrapper for the WP `admin_url` method that should be used in the plugin.
401 *
402 * We can filter into it, to maybe call `network_admin_url` for multisite support.
403 *
404 * @since 2.2.0
405 *
406 * @param string $path Optional path relative to the admin URL.
407 * @param string $scheme The scheme to use. Default is 'admin', which obeys force_ssl_admin() and is_ssl().
408 * 'http' or 'https' can be passed to force those schemes.
409 *
410 * @return string Admin URL link with optional path appended.
411 */
412 public static function admin_url( $path = '', $scheme = 'admin' ) {
413
414 return apply_filters( 'wp_mail_smtp_admin_url', \admin_url( $path, $scheme ), $path, $scheme );
415 }
416
417 /**
418 * Check if the global plugin option in a multisite should be used.
419 * If the global plugin option "multisite" is set and true.
420 *
421 * @since 2.2.0
422 *
423 * @return bool
424 */
425 public static function use_global_plugin_settings() {
426
427 if ( ! is_multisite() ) {
428 return false;
429 }
430
431 $main_site_options = get_blog_option( get_main_site_id(), Options::META_KEY, [] );
432
433 return ! empty( $main_site_options['general']['network_wide'] );
434 }
435
436 /**
437 * Returns Jed-formatted localization data.
438 * This code was taken from a function removed from WP core: `wp_get_jed_locale_data`.
439 *
440 * @since 2.6.0
441 *
442 * @param string $domain Translation domain.
443 *
444 * @return array
445 */
446 public static function get_jed_locale_data( $domain ) {
447
448 $translations = get_translations_for_domain( $domain );
449
450 $locale = array(
451 '' => array(
452 'domain' => $domain,
453 'lang' => is_admin() && function_exists( 'get_user_locale' ) ? get_user_locale() : get_locale(),
454 ),
455 );
456
457 if ( ! empty( $translations->headers['Plural-Forms'] ) ) {
458 $locale['']['plural_forms'] = $translations->headers['Plural-Forms'];
459 }
460
461 foreach ( $translations->entries as $entry ) {
462 $locale[ $entry->singular ] = $entry->translations;
463 }
464
465 return $locale;
466 }
467
468 /**
469 * Check if plugins is activated.
470 * Replacement for is_plugin_active function as it works only in admin area
471 *
472 * @since 2.8.0
473 *
474 * @param string $plugin_slug Plugin slug.
475 *
476 * @return bool
477 */
478 public static function is_plugin_activated( $plugin_slug ) {
479
480 static $active_plugins;
481
482 if ( ! isset( $active_plugins ) ) {
483 $active_plugins = (array) get_option( 'active_plugins', [] );
484
485 if ( is_multisite() ) {
486 $active_plugins = array_merge( $active_plugins, get_site_option( 'active_sitewide_plugins', [] ) );
487 }
488 }
489
490 return ( in_array( $plugin_slug, $active_plugins, true ) || array_key_exists( $plugin_slug, $active_plugins ) );
491 }
492
493 /**
494 * Get the ISO 639-2 Language Code from user/site locale.
495 *
496 * @see http://www.loc.gov/standards/iso639-2/php/code_list.php
497 *
498 * @since 2.8.0
499 *
500 * @return string
501 */
502 public static function get_language_code() {
503
504 $default_lang = 'en';
505 $locale = get_user_locale();
506
507 if ( ! empty( $locale ) ) {
508 $lang = explode( '_', $locale );
509 if ( ! empty( $lang ) && is_array( $lang ) ) {
510 $default_lang = strtolower( $lang[0] );
511 }
512 }
513
514 return $default_lang;
515 }
516
517 /**
518 * Get the certain date of a specified day in a specified format.
519 *
520 * @since 2.8.0
521 *
522 * @param string $period Supported values: start, end.
523 * @param string $timestamp Default is the current timestamp, if left empty.
524 * @param string $format Default is a MySQL format.
525 * @param bool $use_gmt_offset Use GTM offset.
526 *
527 * @return string
528 */
529 public static function get_day_period_date( $period, $timestamp = '', $format = 'Y-m-d H:i:s', $use_gmt_offset = false ) {
530
531 $date = '';
532
533 if ( empty( $timestamp ) ) {
534 $timestamp = time();
535 }
536
537 $offset_sec = $use_gmt_offset ? get_option( 'gmt_offset' ) * 3600 : 0;
538
539 switch ( $period ) {
540 case 'start_of_day':
541 $date = gmdate( $format, strtotime( 'today', $timestamp ) - $offset_sec );
542 break;
543
544 case 'end_of_day':
545 $date = gmdate( $format, strtotime( 'tomorrow', $timestamp ) - 1 - $offset_sec );
546 break;
547 }
548
549 return $date;
550 }
551
552 /**
553 * Returns extracted domain from email address.
554 *
555 * @since 2.8.0
556 *
557 * @param string $email Email address.
558 *
559 * @return string
560 */
561 public static function get_email_domain( $email ) {
562
563 return substr( strrchr( $email, '@' ), 1 );
564 }
565
566 /**
567 * Wrapper for set_time_limit to see if it is enabled.
568 *
569 * @since 2.8.0
570 *
571 * @param int $limit Time limit.
572 */
573 public static function set_time_limit( $limit = 0 ) {
574
575 if ( function_exists( 'set_time_limit' ) && false === strpos( ini_get( 'disable_functions' ), 'set_time_limit' ) && ! ini_get( 'safe_mode' ) ) { // phpcs:ignore PHPCompatibility.IniDirectives.RemovedIniDirectives.safe_modeDeprecatedRemoved
576 @set_time_limit( $limit ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
577 }
578 }
579
580 /**
581 * Recursive arguments parsing.
582 *
583 * @since 2.8.0
584 *
585 * @param array $args Arguments.
586 * @param array $defaults Defaults.
587 *
588 * @return array
589 */
590 public static function parse_args_r( &$args, $defaults ) {
591
592 $args = (array) $args;
593 $defaults = (array) $defaults;
594 $r = $defaults;
595
596 foreach ( $args as $k => &$v ) {
597 if ( is_array( $v ) && isset( $r[ $k ] ) ) {
598 $r[ $k ] = self::parse_args_r( $v, $r[ $k ] );
599 } else {
600 $r[ $k ] = $v;
601 }
602 }
603
604 return $r;
605 }
606
607 /**
608 * True if WP is processing plugin related AJAX call.
609 *
610 * @since 3.0.0
611 *
612 * @return bool
613 */
614 public static function is_doing_self_ajax() {
615
616 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
617 $action = isset( $_REQUEST['action'] ) ? sanitize_key( $_REQUEST['action'] ) : false;
618
619 return self::is_doing_ajax() && $action && substr( $action, 0, 12 ) === 'wp_mail_smtp';
620 }
621
622 /**
623 * Get the name of the plugin/theme/wp-core that initiated the desired function call.
624 *
625 * @since 3.0.0
626 *
627 * @param string $file_path The absolute path of a file that that called the desired function.
628 *
629 * @return string
630 */
631 public static function get_initiator_name( $file_path ) {
632
633 return self::get_initiator( $file_path )['name'];
634 }
635
636 /**
637 * Get the info of the plugin/theme/wp-core function.
638 *
639 * @since 3.5.0
640 *
641 * @param string $file_path The absolute path of the function location.
642 *
643 * @return array
644 */
645 public static function get_initiator( $file_path ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh
646
647 $cache_key = 'wp_mail_smtp_initiators_data';
648
649 // Mainly we have several initiators and we can cache them for better performance.
650 $initiators_cache = get_transient( $cache_key );
651 $initiators_cache = is_array( $initiators_cache ) ? $initiators_cache : [];
652
653 if ( isset( $initiators_cache[ $file_path ] ) ) {
654 return $initiators_cache[ $file_path ];
655 }
656
657 $initiator = self::get_initiator_plugin( $file_path );
658
659 // Change the initiator name if the email was sent from the reloaded method in the email controls.
660 if (
661 ! empty( $initiator ) &&
662 strpos( str_replace( '\\', '/', $file_path ), 'src/Pro/Emails/Control/Reload.php' )
663 ) {
664 $initiator['name'] = sprintf( /* translators: %s - plugin name. */
665 esc_html__( 'WP Core (%s)', 'wp-mail-smtp' ),
666 $initiator['name']
667 );
668 }
669
670 if ( empty( $initiator ) ) {
671 $initiator = self::get_initiator_plugin( $file_path, true );
672 }
673
674 if ( empty( $initiator ) ) {
675 $initiator = self::get_initiator_theme( $file_path );
676 }
677
678 if ( empty( $initiator ) ) {
679 $initiator = self::get_initiator_wp_core( $file_path );
680 }
681
682 if ( empty( $initiator ) ) {
683 $initiator = [];
684 $initiator['name'] = esc_html__( 'N/A', 'wp-mail-smtp' );
685 $initiator['slug'] = '';
686 $initiator['type'] = 'unknown';
687 }
688
689 $initiators_cache[ $file_path ] = $initiator;
690
691 set_transient( $cache_key, $initiators_cache, HOUR_IN_SECONDS );
692
693 return $initiator;
694 }
695
696 /**
697 * Get the initiator's data, if it's a plugin (or mu plugin).
698 *
699 * @since 3.0.0
700 *
701 * @param string $file_path The absolute path of a file.
702 * @param bool $check_mu_plugin Whether to check for mu plugins or not.
703 *
704 * @return false|array
705 */
706 private static function get_initiator_plugin( $file_path, $check_mu_plugin = false ) { // phpcs:ignore Generic.Metrics.CyclomaticComplexity.TooHigh, Generic.Metrics.CyclomaticComplexity.MaxExceeded
707
708 $constant = empty( $check_mu_plugin ) ? 'WP_PLUGIN_DIR' : 'WPMU_PLUGIN_DIR';
709
710 if ( ! defined( $constant ) ) {
711 return false;
712 }
713
714 $root = basename( constant( $constant ) );
715 $separator = defined( 'DIRECTORY_SEPARATOR' ) ? '\\' . DIRECTORY_SEPARATOR : '\/';
716
717 preg_match( "/$separator$root$separator(.[^$separator]+)($separator|\.php)/", $file_path, $result );
718
719 if ( ! empty( $result[1] ) ) {
720 if ( ! function_exists( 'get_plugins' ) ) {
721 include ABSPATH . '/wp-admin/includes/plugin.php';
722 }
723
724 $all_plugins = empty( $check_mu_plugin ) ? get_plugins() : get_mu_plugins();
725 $plugin_slug = $result[1];
726
727 foreach ( $all_plugins as $plugin => $plugin_data ) {
728 if (
729 1 === preg_match( "/^$plugin_slug(\/|\.php)/", $plugin ) &&
730 isset( $plugin_data['Name'] )
731 ) {
732 return [
733 'name' => $plugin_data['Name'],
734 'slug' => $plugin,
735 'type' => $check_mu_plugin ? 'mu-plugin' : 'plugin',
736 ];
737 }
738 }
739
740 return [
741 'name' => $result[1],
742 'slug' => '',
743 'type' => $check_mu_plugin ? 'mu-plugin' : 'plugin',
744 ];
745 }
746
747 return false;
748 }
749
750 /**
751 * Get the initiator's data, if it's a theme.
752 *
753 * @since 3.0.0
754 *
755 * @param string $file_path The absolute path of a file.
756 *
757 * @return false|array
758 */
759 private static function get_initiator_theme( $file_path ) {
760
761 if ( ! defined( 'WP_CONTENT_DIR' ) ) {
762 return false;
763 }
764
765 $root = basename( WP_CONTENT_DIR );
766 $separator = defined( 'DIRECTORY_SEPARATOR' ) ? '\\' . DIRECTORY_SEPARATOR : '\/';
767
768 preg_match( "/$separator$root{$separator}themes{$separator}(.[^$separator]+)/", $file_path, $result );
769
770 if ( ! empty( $result[1] ) ) {
771 $theme = wp_get_theme( $result[1] );
772
773 return [
774 'name' => method_exists( $theme, 'get' ) ? $theme->get( 'Name' ) : $result[1],
775 'slug' => $result[1],
776 'type' => 'theme',
777 ];
778 }
779
780 return false;
781 }
782
783 /**
784 * Return WP Core if the file path is from WP Core (wp-admin or wp-includes folders).
785 *
786 * @since 3.1.0
787 *
788 * @param string $file_path The absolute path of a file.
789 *
790 * @return false|array
791 */
792 private static function get_initiator_wp_core( $file_path ) {
793
794 if ( ! defined( 'ABSPATH' ) ) {
795 return false;
796 }
797
798 $wp_includes = defined( 'WPINC' ) ? trailingslashit( ABSPATH . WPINC ) : false;
799 $wp_admin = trailingslashit( ABSPATH . 'wp-admin' );
800
801 if (
802 strpos( $file_path, $wp_includes ) === 0 ||
803 strpos( $file_path, $wp_admin ) === 0
804 ) {
805 return [
806 'name' => esc_html__( 'WP Core', 'wp-mail-smtp' ),
807 'slug' => 'wp-core',
808 'type' => 'wp-core',
809 ];
810 }
811
812 return false;
813 }
814
815 /**
816 * Retrieves the timezone from site settings as a `DateTimeZone` object.
817 *
818 * Timezone can be based on a PHP timezone string or a ±HH:MM offset.
819 *
820 * We use `wp_timezone()` when it's available (WP 5.3+),
821 * otherwise fallback to the same code, copy-pasted.
822 *
823 * @since 3.0.2
824 *
825 * @return \DateTimeZone Timezone object.
826 */
827 public static function wp_timezone() {
828
829 if ( function_exists( 'wp_timezone' ) ) {
830 return wp_timezone();
831 }
832
833 return new \DateTimeZone( self::wp_timezone_string() );
834 }
835
836 /**
837 * Retrieves the timezone from site settings as a string.
838 *
839 * Uses the `timezone_string` option to get a proper timezone if available,
840 * otherwise falls back to an offset.
841 *
842 * We use `wp_timezone_string()` when it's available (WP 5.3+),
843 * otherwise fallback to the same code, copy-pasted.
844 *
845 * @since 3.0.2
846 *
847 * @return string PHP timezone string or a ±HH:MM offset.
848 */
849 public static function wp_timezone_string() {
850
851 if ( function_exists( 'wp_timezone_string' ) ) {
852 return wp_timezone_string();
853 }
854
855 $timezone_string = get_option( 'timezone_string' );
856
857 if ( $timezone_string ) {
858 return $timezone_string;
859 }
860
861 $offset = (float) get_option( 'gmt_offset' );
862 $hours = (int) $offset;
863 $minutes = ( $offset - $hours );
864
865 $sign = ( $offset < 0 ) ? '-' : '+';
866 $abs_hour = abs( $hours );
867 $abs_mins = abs( $minutes * 60 );
868 $tz_offset = sprintf( '%s%02d:%02d', $sign, $abs_hour, $abs_mins );
869
870 return $tz_offset;
871 }
872
873 /**
874 * Get wp remote response error message.
875 *
876 * @since 3.4.0
877 *
878 * @param array $response Response array.
879 */
880 public static function wp_remote_get_response_error_message( $response ) {
881
882 if ( is_wp_error( $response ) ) {
883 return '';
884 }
885
886 $body = wp_remote_retrieve_body( $response );
887 $message = wp_remote_retrieve_response_message( $response );
888 $code = wp_remote_retrieve_response_code( $response );
889 $description = '';
890
891 if ( ! empty( $body ) ) {
892 $description = is_string( $body ) ? $body : wp_json_encode( $body );
893 }
894
895 return Helpers::format_error_message( $message, $code, $description );
896 }
897
898 /**
899 * Clean variables using sanitize_text_field. Arrays are cleaned recursively.
900 * Non-string values are ignored.
901 *
902 * @since 3.7.0
903 *
904 * @param string|array $var Data to sanitize.
905 *
906 * @return string|array
907 */
908 public static function sanitize_text( $var ) {
909
910 if ( is_array( $var ) ) {
911 return array_map( [ __CLASS__, 'sanitize_text' ], $var );
912 } else {
913 return is_string( $var ) ? sanitize_text_field( $var ) : $var;
914 }
915 }
916
917 /**
918 * Get the current site URL,
919 * or the network URL if using network-wide settings.
920 *
921 * @since 4.4.0
922 *
923 * @return string
924 */
925 public static function get_site_url() {
926
927 $site_id = null;
928
929 if ( self::use_global_plugin_settings() ) {
930 $site_id = get_main_site_id();
931 }
932
933 /**
934 * Whether to return the unfiltered site URL.
935 *
936 * @since 4.6.0
937 *
938 * @param bool $unfiltered Whether to return the unfiltered site URL.
939 *
940 * @return bool
941 */
942 if ( apply_filters( 'wp_mail_smtp_wp_get_site_url_unfiltered', false ) ) {
943 return self::get_raw_site_url( $site_id );
944 }
945
946 return get_site_url( $site_id );
947 }
948
949 /**
950 * Get the raw/unfiltered site URL.
951 *
952 * @since 4.6.0
953 *
954 * @param int $site_id The site ID.
955 *
956 * @return string
957 */
958 private static function get_raw_site_url( $site_id ) {
959
960 if ( empty( $site_id ) || ! is_multisite() ) {
961 $url = get_option( 'siteurl' );
962 } else {
963 switch_to_blog( $site_id );
964
965 $url = get_option( 'siteurl' );
966
967 restore_current_blog();
968 }
969
970 $url = set_url_scheme( $url );
971
972 return $url;
973 }
974 }
975