Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
21.05% covered (danger)
21.05%
12 / 57
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
Settings_Page
21.05% covered (danger)
21.05%
12 / 57
0.00% covered (danger)
0.00%
0 / 5
260.15
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 add_settings_page
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 display_plugin_admin_page
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
20
 get_wp_mail_info
42.86% covered (danger)
42.86%
12 / 28
0.00% covered (danger)
0.00%
0 / 1
33.58
 get_plugin_from_path
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
30
1<?php
2/**
3 * The wp-admin settings page to configure the ARNs to listen to.
4 *
5 * @link
6 * @since      1.0.0
7 *
8 * @package brianhenryie/bh-wp-aws-ses-bounce-handler
9 */
10
11namespace BrianHenryIE\AWS_SES_Bounce_Handler\Admin;
12
13use BrianHenryIE\AWS_SES_Bounce_Handler\API_Interface;
14use BrianHenryIE\AWS_SES_Bounce_Handler\Settings_Interface;
15use Psr\Log\LogLevel;
16
17
18/**
19 * Adds a wp-admin Settings submenu. Adds a page with input for bounces ARN and complaints ARN.
20 */
21class Settings_Page {
22
23    /**
24     * The settings, to pass to the individual fields for populating.
25     *
26     * @var Settings_Interface $settings The previously saved settings for the plugin.
27     */
28    protected Settings_Interface $settings;
29
30    /**
31     * Needed to display what integrations are available or enabled.
32     *
33     * @uses API_Interface::get_integrations()
34     *
35     * @var API_Interface
36     */
37    protected API_Interface $api;
38
39    /**
40     * Constructor.
41     *
42     * @param API_Interface      $api The main plugin functions.
43     * @param Settings_Interface $settings The plugin settings.
44     */
45    public function __construct( API_Interface $api, Settings_Interface $settings ) {
46        $this->settings = $settings;
47        $this->api      = $api;
48    }
49
50    /**
51     * Add the AWS SES Bounce Handler settings menu-item/page as a submenu-item of the Settings menu.
52     *
53     * /wp-admin/options-general.php?page=bh-wp-aws-ses-bounce-handler
54     *
55     * @hooked admin_menu
56     */
57    public function add_settings_page(): void {
58
59        add_options_page(
60            'AWS SES Bounce Handler',
61            'AWS SES Bounce Handler',
62            'manage_options',
63            $this->settings->get_plugin_slug(),
64            array( $this, 'display_plugin_admin_page' )
65        );
66    }
67
68    /**
69     * Registered above, called by WordPress to display the admin settings page.
70     *
71     * @see API::set_log_level() for valid levels
72     */
73    public function display_plugin_admin_page(): void {
74
75        $settings = $this->settings;
76        $api      = $this->api;
77
78        $current_log_level = $this->settings->get_log_level();
79        $logs_url          = admin_url( 'admin.php?page=bh-wp-aws-ses-bounce-handler-logs' );
80
81        $allowed_log_levels = array(
82            'none'            => 'None',
83            LogLevel::ERROR   => 'Error',
84            LogLevel::WARNING => 'Warning',
85            LogLevel::NOTICE  => 'Notice',
86            LogLevel::INFO    => 'Info',
87            LogLevel::DEBUG   => 'Debug',
88        );
89
90        $template = 'admin/settings-page.php';
91
92        $template_admin_settings_page = WP_PLUGIN_DIR . '/' . plugin_dir_path( $this->settings->get_plugin_basename() ) . 'templates/' . $template;
93
94        // Check the child theme for template overrides.
95        if ( file_exists( get_stylesheet_directory() . $template ) ) {
96            $template_admin_settings_page = get_stylesheet_directory() . $template;
97        } elseif ( file_exists( get_stylesheet_directory() . 'templates/' . $template ) ) {
98            $template_admin_settings_page = get_stylesheet_directory() . 'templates/' . $template;
99        }
100
101        /**
102         * Allow overriding the admin settings template.
103         */
104        $filtered_template_admin_settings_page = apply_filters( 'bh_wp_aws_ses_bounce_handler_admin_settings_page_template', $template_admin_settings_page );
105
106        if ( file_exists( $filtered_template_admin_settings_page ) ) {
107            include $filtered_template_admin_settings_page;
108        } else {
109            include $template_admin_settings_page;
110        }
111    }
112
113    /**
114     * Figure out if WP_Mail is being overridden.
115     *
116     * @return string Admin notice showing how wp_mail is operating.
117     */
118    public function get_wp_mail_info(): string {
119
120        $wp_mail_reflector = new \ReflectionFunction( 'wp_mail' );
121        $wp_mail_filename  = $wp_mail_reflector->getFileName();
122
123        $built_in_wp_mail_filename = 'wp-includes/pluggable.php';
124
125        // If wp_mail has been overridden.
126        if ( substr( $wp_mail_filename, - 1 * strlen( $built_in_wp_mail_filename ) ) !== $built_in_wp_mail_filename ) {
127
128            $plugin = $this->get_plugin_from_path( $wp_mail_filename );
129
130            if ( null === $plugin ) {
131                return '<div class="notice inline notice-warning"><p>WordPress is sending mail using <em>' . $wp_mail_filename . '</em>.</p></div>';
132            }
133
134            $notice_type = 'warning';
135            if ( stristr( $plugin['Name'], ' ses' )
136                || stristr( $plugin['Description'], ' ses' ) ) {
137                $notice_type = 'success';
138            }
139
140            return '<div class="notice inline notice-' . $notice_type . '"><p>WordPress is sending mail using <em>' . $plugin['Name'] . '</em> plugin.</p></div>';
141
142        }
143
144        // If phpmailer has been set, check is it the built-in WordPress class.
145        global $phpmailer;
146        if ( ! empty( $phpmailer ) ) {
147            try {
148                $phpmailer_reflector = new \ReflectionClass( get_class( $phpmailer ) );
149
150            } catch ( \ReflectionException $e ) {
151                return '<div class="notice inline notice-error"><p>Error checking PHPMailer class: ' . $e->getMessage() . ' – ' . get_class( $phpmailer ) . '</p></div>';
152
153            }
154            $phpmailer_filename = $phpmailer_reflector->getFileName();
155
156            $built_in_phpmailer_filename = 'wp-includes/class-phpmailer.php';
157
158            // If phpMailer has been overridden (this happens in tests too).
159            if ( substr( $phpmailer_filename, - 1 * strlen( $built_in_phpmailer_filename ) ) !== $built_in_phpmailer_filename ) {
160
161                $plugin = $this->get_plugin_from_path( $phpmailer_filename );
162                if ( null === $plugin ) {
163                    return '<div class="notice inline notice-warning"><p>WordPress is sending mail using <em>' . $phpmailer_filename . '</em>.</p></div>';
164                }
165
166                $notice_type = 'warning';
167                if ( stristr( $plugin['Name'], ' ses' )
168                    || stristr( $plugin['Description'], ' ses' ) ) {
169                    $notice_type = 'success';
170                }
171
172                return '<div class="notice inline notice-' . $notice_type . '"><p>WordPress is sending mail using <em>' . $plugin['Name'] . '</em> plugin.</p></div>';
173            }
174        }
175
176        return '<div class="notice inline notice-error"><p>Email is being sent using WordPress\'s built in <code>wp_mail()</code> function. It is probably not being sent using AWS SES.</p></div>';
177
178    }
179
180    /**
181     * Given a filename, figure out what plugin it is from.
182     *
183     * I.e. given the file that has the phpmailer, determine what plugin it is part of.
184     *
185     * TODO: See `global $wp_plugin_paths` if there are problems with this method.
186     *
187     * @see get_plugins()
188     *
189     * @param string $filename The file path we're trying to determine the plugin for.
190     *
191     * @return ?array<string, mixed> The plugin entry from get_plugins().
192     */
193    private function get_plugin_from_path( string $filename ): ?array {
194
195        // If the file is outside the plugins' dir, what's up? MU plugins?
196        if ( ! stristr( $filename, WP_PLUGIN_DIR ) ) {
197            return null;
198        }
199
200        $plugin_file = trim( substr( $filename, strlen( realpath( WP_PLUGIN_DIR ) ) ), DIRECTORY_SEPARATOR );
201
202        $plugins = get_plugins();
203
204        if ( array_key_exists( $plugin_file, $plugins ) ) {
205
206            return $plugins[ $plugin_file ];
207        }
208
209        $plugin_slug = substr( $plugin_file, 0, strpos( $plugin_file, DIRECTORY_SEPARATOR ) );
210
211        foreach ( $plugins as $file => $plugin ) {
212
213            if ( stristr( $file, $plugin_slug ) ) {
214                return $plugin;
215            }
216        }
217
218        return null;
219    }
220}