wpo365-msgraphmailer.php
| 1 | <?php |
| 2 | /** |
| 3 | * Plugin Name: WPO365 | MICROSOFT 365 GRAPH MAILER |
| 4 | * Plugin URI: https://wordpress.org/plugins/wpo365-msgraphmailer |
| 5 | * Description: WPO365 | MS GRAPH MAILER re-configures your WordPress website to send transactional emails from one of your Microsoft 365 Exchange Online / Mail enabled accounts using Microsoft Graph instead of - for example - using SMTP. |
| 6 | * Version: 6.1 |
| 7 | * Author: marco@wpo365.com |
| 8 | * Author URI: https://www.wpo365.com |
| 9 | * License: GPL2+ |
| 10 | */ |
| 11 | |
| 12 | namespace Wpo; |
| 13 | |
| 14 | require __DIR__ . '/vendor/autoload.php'; |
| 15 | |
| 16 | use Wpo\Core\Globals; |
| 17 | use Wpo\Core\Permissions_Helpers; |
| 18 | use Wpo\Services\Dependency_Service; |
| 19 | use Wpo\Services\Options_Service; |
| 20 | use Wpo\Services\Request_Service; |
| 21 | use Wpo\Services\Router_Service; |
| 22 | |
| 23 | // Prevent public access to this script |
| 24 | defined( 'ABSPATH' ) || die(); |
| 25 | |
| 26 | if ( ! class_exists( '\Wpo\MsGraphMailer' ) ) { |
| 27 | |
| 28 | class MsGraphMailer { |
| 29 | |
| 30 | |
| 31 | /** |
| 32 | * |
| 33 | * @var \Wpo\Services\Dependency_Service |
| 34 | */ |
| 35 | private $dependencies; |
| 36 | |
| 37 | private $no_conflict = true; |
| 38 | |
| 39 | public function __construct() { |
| 40 | $this->deactivation_hooks(); |
| 41 | add_action( 'plugins_loaded', array( $this, 'load' ), 1 ); |
| 42 | add_filter( 'cron_schedules', '\Wpo\Core\Cron_Helpers::add_cron_schedules', 10, 1 ); // phpcs:ignore |
| 43 | } |
| 44 | |
| 45 | public function get_plugin_url() { |
| 46 | return plugin_dir_url( __FILE__ ); |
| 47 | } |
| 48 | |
| 49 | public function load() { |
| 50 | // If WPO365 | LOGIN exists and is active then do not initialize this plugin. |
| 51 | if ( \file_exists( dirname( __DIR__ ) . '/wpo365-login' ) ) { |
| 52 | include_once ABSPATH . 'wp-admin/includes/plugin.php'; |
| 53 | |
| 54 | if ( is_plugin_active( 'wpo365-login/wpo365-login.php' ) ) { |
| 55 | add_action( 'admin_notices', array( $this, 'ensure_wpo365_login' ), 10, 0 ); |
| 56 | return; |
| 57 | } |
| 58 | } |
| 59 | |
| 60 | Globals::set_global_vars( __FILE__, __DIR__ ); |
| 61 | $this->cache_dependencies(); |
| 62 | Options_Service::ensure_options_cache(); |
| 63 | $this->update_request_log(); |
| 64 | $this->add_wp_hooks(); |
| 65 | Router_Service::has_route(); |
| 66 | } |
| 67 | |
| 68 | public function ensure_wpo365_login() { |
| 69 | echo '<div class="notice notice-error" style="margin-left: 2px;"><p>' |
| 70 | . 'The <strong>WPO365 | MS GRAPH MAILER</strong> plugin has detected that you have also installed the <strong>WPO365 | LOGIN</strong> plugin. ' |
| 71 | . 'Since that plugin offers the same functionality <a href="https://www.wpo365.com/features/" target="_blank">plus a lot more</a> and uses the ' |
| 72 | . 'same configuration-source, the <em>Microsoft Graph Mailer</em> of the <strong>WPO365 | LOGIN</strong> plugin will be used instead. ' |
| 73 | . '<br><br>' |
| 74 | . 'See the <a href="https://docs.wpo365.com/article/141-send-email-using-microsoft-graph-mailer" target="_blank">online documentation</a> for details.' |
| 75 | . '</p></div>'; |
| 76 | } |
| 77 | |
| 78 | private function cache_dependencies() { |
| 79 | $this->dependencies = Dependency_Service::get_instance(); |
| 80 | $this->dependencies->add( 'Request_Service', Request_Service::get_instance( true ) ); |
| 81 | } |
| 82 | |
| 83 | private function add_wp_hooks() { |
| 84 | // Plugin updater and license checker |
| 85 | add_filter( 'pre_set_site_transient_update_plugins', '\Wpo\Core\Plugin_Helpers::check_for_updates', 10, 1 ); |
| 86 | |
| 87 | // Do super admin stuff |
| 88 | if ( ( is_admin() || is_network_admin() ) && Permissions_Helpers::user_is_admin( \wp_get_current_user() ) ) { |
| 89 | |
| 90 | // Add and hide wizard (page) |
| 91 | add_action( 'admin_menu', '\Wpo\Pages\Wizard_Page::add_management_page' ); |
| 92 | add_action( 'network_admin_menu', '\Wpo\Pages\Wizard_Page::add_management_page' ); |
| 93 | |
| 94 | new \Wpo\Pages\License_Page(); |
| 95 | |
| 96 | // Show admin notification when WPO365 not properly configured |
| 97 | add_action( 'admin_notices', '\Wpo\Services\Notifications_Service::show_admin_notices', 10, 0 ); |
| 98 | add_action( 'network_admin_notices', '\Wpo\Services\Notifications_Service::show_admin_notices', 10, 0 ); |
| 99 | add_action( 'admin_init', '\Wpo\Services\Notifications_Service::dismiss_admin_notices', 10, 0 ); |
| 100 | |
| 101 | // Wire up AJAX backend services |
| 102 | add_action( 'wp_ajax_wpo365_delete_settings', '\Wpo\Services\Ajax_Service::delete_settings' ); |
| 103 | add_action( 'wp_ajax_wpo365_delete_tokens', '\Wpo\Services\Ajax_Service::delete_tokens' ); |
| 104 | add_action( 'wp_ajax_wpo365_get_settings', '\Wpo\Services\Ajax_Service::get_settings' ); |
| 105 | add_action( 'wp_ajax_wpo365_update_settings', '\Wpo\Services\Ajax_Service::update_settings' ); |
| 106 | add_action( 'wp_ajax_wpo365_get_log', '\Wpo\Services\Ajax_Service::get_log' ); |
| 107 | add_action( 'wp_ajax_wpo365_dismiss_wpo_health_messages', '\Wpo\Services\Ajax_Service::dismiss_wpo_health_messages' ); |
| 108 | add_action( 'wp_ajax_wpo365_get_wpo_health_messages', '\Wpo\Services\Ajax_Service::get_wpo_health_messages' ); |
| 109 | add_action( 'wp_ajax_wpo365_get_parseable_options', '\Wpo\Services\Ajax_Service::get_parseable_options' ); |
| 110 | add_action( 'wp_ajax_wpo365_get_insights_summary', '\Wpo\Services\Ajax_Service::get_insights_summary' ); |
| 111 | add_action( 'wp_ajax_wpo365_get_insights', '\Wpo\Services\Ajax_Service::get_insights' ); |
| 112 | add_action( 'wp_ajax_wpo365_truncate_insights_data', '\Wpo\Services\Ajax_Service::truncate_insights_data' ); |
| 113 | add_action( 'wp_ajax_wpo365_send_test_alert', '\Wpo\Services\Ajax_Service::send_test_alert' ); |
| 114 | add_action( 'wp_ajax_wpo365_obfuscate_sensitive_options', '\Wpo\Services\Ajax_Service::obfuscate_sensitive_options' ); |
| 115 | |
| 116 | // Graph mailer |
| 117 | add_action( 'wp_ajax_wpo365_send_test_mail', '\Wpo\Services\Ajax_Service::send_test_mail' ); |
| 118 | add_action( 'wp_ajax_wpo365_get_mail_authorization_url', '\Wpo\Services\Ajax_Service::get_mail_authorization_url' ); |
| 119 | add_action( 'wp_ajax_wpo365_get_mail_auth_configuration', '\Wpo\Services\Ajax_Service::get_mail_auth_configuration' ); |
| 120 | add_action( 'wp_ajax_wpo365_try_migrate_mail_app_principal_info', '\Wpo\Services\Ajax_Service::try_migrate_mail_app_principal_info' ); |
| 121 | |
| 122 | // Graph mailer auditing |
| 123 | if ( class_exists( '\Wpo\Mail\Mail_Db' ) ) { |
| 124 | // Wire up AJAX backend services |
| 125 | add_action( 'wp_ajax_wpo365_get_mail_log', '\Wpo\Mail\Mail_Ajax_Service::get_mail_log' ); |
| 126 | add_action( 'wp_ajax_wpo365_send_mail_again', '\Wpo\Mail\Mail_Ajax_Service::send_mail_again' ); |
| 127 | add_action( 'wp_ajax_wpo365_truncate_mail_log', '\Wpo\Mail\Mail_Ajax_Service::truncate_mail_log' ); |
| 128 | |
| 129 | if ( method_exists( '\Wpo\Mail\Mail_Ajax_Service', 'mail_auto_retry' ) ) { |
| 130 | add_action( 'wp_ajax_wpo365_mail_auto_retry', '\Wpo\Mail\Mail_Ajax_Service::mail_auto_retry' ); |
| 131 | } |
| 132 | } |
| 133 | |
| 134 | // Show settings link |
| 135 | add_filter( ( is_network_admin() ? 'network_admin_' : '' ) . 'plugin_action_links_' . $GLOBALS['WPO_CONFIG']['plugin'], '\Wpo\Core\Plugin_Helpers::get_configuration_action_link', 10, 1 ); |
| 136 | |
| 137 | // Add license related messages to WP Admin |
| 138 | \Wpo\Core\Plugin_Helpers::show_license_notices(); |
| 139 | |
| 140 | // Ensure WP Cron job to check for each registered application whether its secret will epxire soon is added. |
| 141 | |
| 142 | if ( class_exists( '\Wpo\Services\Password_Credentials_Service' ) ) { |
| 143 | \Wpo\Services\Password_Credentials_Service::ensure_check_password_credentials_expiration(); |
| 144 | } |
| 145 | |
| 146 | // Ensure WP Cron job to obtain a new mail refresh token once a day is added. |
| 147 | \Wpo\Mail\Mail_Authorization_Helpers::ensure_get_mail_refresh_token(); |
| 148 | |
| 149 | // To force WordPress to check for plugin updates if requested by an administrator |
| 150 | add_action( 'admin_post_wpo365_force_check_for_plugin_updates', '\Wpo\Core\Plugin_Helpers::force_check_for_plugin_updates' ); |
| 151 | add_filter( 'plugin_row_meta', '\Wpo\Core\Plugin_Helpers::show_old_version_warning', 10, 2 ); |
| 152 | add_filter( 'plugins_api', '\Wpo\Core\Plugin_Helpers::plugin_info', 20, 3 ); |
| 153 | |
| 154 | // Set up the dashboard widget. |
| 155 | add_action( 'wp_dashboard_setup', '\Wpo\Insights\Dashboard_Service::insights_widget' ); |
| 156 | } // End of admin stuff |
| 157 | |
| 158 | if ( Options_Service::get_global_boolean_var( 'insights_alerts_enabled' ) && class_exists( '\Wpo\Insights\Event_Notify_Service' ) ) { |
| 159 | add_action( |
| 160 | 'wpo365/insights/notify', |
| 161 | function ( $message, $category = 'N/A', $recipient = '' ) { |
| 162 | $nofications = new \Wpo\Insights\Event_Notify_Service(); |
| 163 | $nofications->notify( $message, $category, $recipient ); |
| 164 | }, |
| 165 | 10, |
| 166 | 3 |
| 167 | ); |
| 168 | |
| 169 | add_action( |
| 170 | 'wpo365_insights_check_failed_notifications', |
| 171 | function () { |
| 172 | $nofications = new \Wpo\Insights\Event_Notify_Service(); |
| 173 | $nofications->check_failed_notifications(); |
| 174 | } |
| 175 | ); |
| 176 | } |
| 177 | |
| 178 | // WP Cron job triggered action to check for each registered application whether its secret will epxire soon. |
| 179 | add_action( 'wpo_check_password_credentials_expiration', '\Wpo\Services\Password_Credentials_Service::check_password_credentials_expiration' ); |
| 180 | |
| 181 | // WP Cron job triggered action to obtain a new mail refresh token once a day. |
| 182 | add_action( 'wpo365_get_mail_refresh_token', '\Wpo\Mail\Mail_Authorization_Helpers::get_mail_refresh_token' ); |
| 183 | |
| 184 | // Clean up on shutdown |
| 185 | add_action( 'shutdown', '\Wpo\Services\Request_Service::shutdown', PHP_INT_MAX ); |
| 186 | |
| 187 | add_action( 'admin_enqueue_scripts', '\Wpo\Core\Script_Helpers::enqueue_wizard', 10, 0 ); |
| 188 | |
| 189 | // Replace phpmailer with Graph Mailer (but only if configured) |
| 190 | // According to https://developer.wordpress.org/reference/functions/wp_mail/ wp_mail is available after plugins_loaded |
| 191 | add_action( 'phpmailer_init', '\Wpo\Mail\Mailer::init', PHP_INT_MAX ); |
| 192 | add_filter( 'wp_mail_from', '\Wpo\Mail\Mailer::mail_from', 10, 1 ); |
| 193 | |
| 194 | if ( Options_Service::get_global_boolean_var( 'mail_log', false ) && method_exists( '\Wpo\Mail\Mail_Db', 'add_mail_log' ) ) { |
| 195 | // Log each wp_mail in the wpo365_table |
| 196 | add_filter( 'wp_mail', '\Wpo\Mail\Mail_Db::add_mail_log', 10, 1 ); |
| 197 | } |
| 198 | |
| 199 | if ( Options_Service::get_global_boolean_var( 'mail_throttling_enabled' ) && method_exists( '\Wpo\Mail\Mail_Db', 'check_message_rate_limit' ) ) { |
| 200 | add_filter( 'wpo365/mail/before', '\Wpo\Mail\Mail_Db::check_message_rate_limit' ); |
| 201 | } |
| 202 | |
| 203 | if ( Options_Service::get_global_boolean_var( 'mail_auto_retry' ) && method_exists( '\Wpo\Mail\Mail_Db', 'process_unsent_messages' ) ) { |
| 204 | add_action( 'wpo_process_unsent_messages', '\Wpo\Mail\Mail_Db::process_unsent_messages' ); |
| 205 | add_action( |
| 206 | 'admin_init', |
| 207 | function () { |
| 208 | \Wpo\Mail\Mail_Db::ensure_unsent_messages( false ); |
| 209 | } |
| 210 | ); |
| 211 | } |
| 212 | |
| 213 | // Add safe style css |
| 214 | add_filter( 'safe_style_css', '\Wpo\Core\WordPress_Helpers::safe_css', 10, 1 ); |
| 215 | |
| 216 | // Admin menu bar notifications |
| 217 | if ( Options_Service::get_global_boolean_var( 'mail_staging_mode', false ) && class_exists( '\Wpo\Mail\Mail_Notifications' ) ) { |
| 218 | add_action( 'admin_bar_menu', '\Wpo\Mail\Mail_Notifications::staging_mode_active', 100 ); |
| 219 | add_action( 'wp_enqueue_scripts', '\Wpo\Core\Script_Helpers::add_admin_bar_styles' ); |
| 220 | add_action( 'admin_enqueue_scripts', '\Wpo\Core\Script_Helpers::add_admin_bar_styles' ); |
| 221 | } |
| 222 | |
| 223 | // Update WPO365 extensions cache whenever a plugin is added or deleted |
| 224 | add_action( 'activated_plugin', '\Wpo\Core\Extensions_Helpers::plugin_activated', 10, 2 ); |
| 225 | add_action( 'deactivated_plugin', '\Wpo\Core\Extensions_Helpers::plugin_deactivated', 10, 2 ); |
| 226 | |
| 227 | // Check again for updates after upgrader process completes to fix "Update available notice" |
| 228 | add_action( 'upgrader_process_complete', '\Wpo\Core\Extensions_Helpers::plugin_updated', 10, 2 ); |
| 229 | |
| 230 | // To collect Insights |
| 231 | if ( Options_Service::get_global_boolean_var( 'insights_enabled', false ) ) { |
| 232 | add_action( 'wpo365/mail/sent', '\Wpo\Insights\Event_Service::mail_sent__handler', 10, 1 ); |
| 233 | add_action( 'wpo365/mail/sent/fail', '\Wpo\Insights\Event_Service::mail_sent_fail__handler', 10, 2 ); |
| 234 | add_action( 'wpo365/alert/submitted', '\Wpo\Insights\Event_Service::notification_sent__handler', 10, 3 ); |
| 235 | add_action( 'wpo365/alert/submitted/fail', '\Wpo\Insights\Event_Service::notification_sent_fail__handler', 10, 3 ); |
| 236 | } |
| 237 | |
| 238 | // To collect cURL logging |
| 239 | if ( Options_Service::get_global_boolean_var( 'curl_logging_enabled' ) ) { |
| 240 | add_action( 'http_api_curl', '\Wpo\Services\Log_Service::enable_curl_logging', 10, 3 ); |
| 241 | } |
| 242 | |
| 243 | // To disable ALPN for requests to Microsoft's login / Graph endpoints |
| 244 | // (option check happens inside the callback so a runtime option flip below takes effect immediately) |
| 245 | add_action( 'http_api_curl', '\Wpo\Core\Url_Helpers::disable_alpn_for_microsoft_hosts', 10, 3 ); |
| 246 | |
| 247 | // To auto-detect and self-heal the ALPN/PQ-TLS issue against Microsoft's SSO/token endpoints |
| 248 | add_filter( 'http_response', '\Wpo\Core\Url_Helpers::maybe_recover_from_microsoft_tls_404', 10, 3 ); |
| 249 | |
| 250 | // Customize script tag. |
| 251 | add_filter( 'script_loader_tag', '\Wpo\Core\Script_Helpers::custom_script_loading', 10, 3 ); |
| 252 | } |
| 253 | |
| 254 | private function update_request_log() { |
| 255 | $request_service = Request_Service::get_instance(); |
| 256 | $request = $request_service->get_request( $GLOBALS['WPO_CONFIG']['request_id'] ); |
| 257 | $request_log = $request->get_item( 'request_log' ); |
| 258 | $request_log['debug_log'] = Options_Service::get_global_boolean_var( 'debug_log', false ); |
| 259 | $request->set_item( 'request_log', $request_log ); |
| 260 | } |
| 261 | |
| 262 | private function deactivation_hooks() { |
| 263 | if ( \class_exists( '\Wpo\Mail\Mail_Db' ) ) { |
| 264 | // Delete possible cron jobs |
| 265 | register_deactivation_hook( |
| 266 | __FILE__, |
| 267 | function () { |
| 268 | wp_clear_scheduled_hook( 'wpo_process_unsent_messages' ); |
| 269 | Options_Service::add_update_option( 'mail_auto_retry', false ); |
| 270 | } |
| 271 | ); |
| 272 | } |
| 273 | |
| 274 | // Delete the mail refresh token cron job |
| 275 | register_deactivation_hook( |
| 276 | __FILE__, |
| 277 | function () { |
| 278 | wp_clear_scheduled_hook( 'wpo365_get_mail_refresh_token' ); |
| 279 | delete_site_transient( 'wpo365_get_mail_refresh_token_hook_ensured' ); |
| 280 | } |
| 281 | ); |
| 282 | |
| 283 | register_deactivation_hook( |
| 284 | __FILE__, |
| 285 | function () { |
| 286 | global $wpdb; |
| 287 | |
| 288 | $wpdb->query( // phpcs:ignore |
| 289 | $wpdb->prepare( |
| 290 | 'DELETE FROM %i WHERE `option_name` LIKE %s AND `option_name` != %s AND `option_name` != %s', |
| 291 | $wpdb->options, |
| 292 | '%wpo365%', |
| 293 | 'wpo365_options', |
| 294 | 'wpo365_mail_authorization' |
| 295 | ) |
| 296 | ); |
| 297 | |
| 298 | $wpdb->query( // phpcs:ignore |
| 299 | $wpdb->prepare( |
| 300 | 'DELETE FROM %i WHERE `option_name` LIKE %s', |
| 301 | $wpdb->options, |
| 302 | 'wpo_app_only_access_tokens' |
| 303 | ) |
| 304 | ); |
| 305 | } |
| 306 | ); |
| 307 | } |
| 308 | } |
| 309 | } |
| 310 | |
| 311 | $wpo365_msgraphmailer = new MsGraphMailer(); |
| 312 |