├── changelog.md ├── disable-emails.php ├── includes ├── bootstrap.php ├── class.EmailAddress.php ├── class.PHPMailerMock.php ├── class.Plugin.php ├── class.Requires.php ├── functions-global.php └── functions.php ├── languages ├── disable-emails-cs_CZ.mo ├── disable-emails-de_DE.mo ├── disable-emails-nb_NO.mo ├── disable-emails-nn_NO.mo └── disable-emails-zh_CN.mo ├── mu-plugin └── disable-emails-mu.php ├── readme.txt ├── static ├── css │ └── admin.css └── js │ ├── settings.js │ └── settings.min.js └── views ├── indicator-notice.php ├── requires-admin-notice.php ├── requires-plugin-notice.php └── settings-form.php /changelog.md: -------------------------------------------------------------------------------- 1 | # Disable Emails 2 | 3 | ## Changelog 4 | 5 | ### 1.8.2, 2023-11-22 6 | 7 | * fixed: deprecation warnings in PHP 8.1+ 8 | 9 | ### 1.8.1, 2022-05-26 10 | 11 | * fixed: fatal exception when an email address was invalid 12 | 13 | ### 1.8.0, 2022-02-16 14 | 15 | * fixed: mock PHPMailer object did not have recipient addresses for developers to inspect (thanks, [SpartakusMd](https://wordpress.org/support/users/spartakusmd/)!) 16 | 17 | ### 1.7.0, 2020-08-11 18 | 19 | * fixed: WordPress 5.5 compatibility 20 | 21 | ### 1.6.3, 2020-03-19 22 | 23 | * fixed: unhandled `phpmailerException` exceptions (thanks, [y0uri](https://wordpress.org/support/users/y0uri/)!) 24 | 25 | ### 1.6.2, 2020-03-10 26 | 27 | * fixed: activating the must-use plugin throws an error if the mu-plugins folder is missing 28 | * changed: can now enable both a Toolbar indicator and a site-wide admin notice when emails are disabled 29 | * changed: filter hook `disable_emails_indicator` also accepts 'notice_toolbar' to enable both notice and Toolbar indicator 30 | 31 | ### 1.6.1, 2019-12-21 32 | 33 | * fixed: Toolbar indicator has no visible slash in Sunrise admin theme 34 | * fixed: undefined function `is_plugin_active_for_network()` (thanks [isabelc](https://github.com/isabelc)!) 35 | 36 | ### 1.6.0, 2019-12-15 37 | 38 | * fixed: undefined index for `$_SERVER['SERVER_NAME']` when emails sent during wp-cli 39 | * added: indicator setting to show either a Toolbar indicator or a site-wide admin notice when emails are disabled 40 | * added: filter hook `disable_emails_indicator` for setting the indicator from code; accepts 'none', 'toolbar', 'notice' 41 | 42 | ### 1.5.0, 2019-11-11 43 | 44 | * fixed: PHP notice -- Trying to get property 'ErrorInfo' of non-object 45 | * changed: requires minimum PHP 5.6; recommend PHP 7.3+ 46 | * added: support for running the plugin as a must-use plugin (mu-plugin) 47 | 48 | ### 1.4.0, 2018-11-21 49 | 50 | * added: setting to force Events Manager to use `wp_mail()` so that its emails can be disabled 51 | * tested: WordPress 5.0 52 | 53 | ### 1.3.0, 2016-11-21 54 | 55 | * added: setting to force BuddyPress to use `wp_mail()` so that its emails can be disabled 56 | 57 | ### 1.2.5, 2015-12-02 58 | 59 | * added: Chinese translation (thanks, [Cai_Miao](https://profiles.wordpress.org/cai_miao)!) 60 | * added: Japanese translation (thanks, [Cai_Miao](https://profiles.wordpress.org/cai_miao)!) 61 | * added: status message on At A Glance dashboard metabox when emails are disabled 62 | 63 | ### 1.2.4, 2015-02-28 64 | 65 | * added: German translation (thanks, [Peter Harlacher](http://helvetian.io/)!) 66 | 67 | ### 1.2.3, 2014-11-03 68 | 69 | * added: Czech translation (thanks, [Rudolf Klusal](http://www.klusik.cz/)!) 70 | 71 | ### 1.2.2, 2014-08-31 72 | 73 | * added: Norwegian translations (thanks, [neonnero](http://www.neonnero.com/)!) 74 | 75 | ### 1.2.1, 2014-06-22 76 | 77 | * added: warn when wp_mail() can't be replaced, so admin knows that emails cannot be disabled 78 | 79 | ### 1.2.0, 2014-04-19 80 | 81 | * changed: refactored to fully support filter and action hooks that other plugins might use to listen to and modify emails, e.g. so that email loggers can properly record what would have been sent 82 | 83 | ### 1.1.0, 2014-02-25 84 | 85 | * fixed: `wp_mail()` now returns true, simulating a successful email attempt 86 | * added: support filter hook `wp_mail` so that listeners can act, e.g. log emails (even though they will not be sent); can be turned off in settings 87 | 88 | ### 1.0.0, 2014-02-18 89 | 90 | * initial release 91 | -------------------------------------------------------------------------------- /disable-emails.php: -------------------------------------------------------------------------------- 1 | wpmail($to, $subject, $message, $headers, $attachments); 24 | } 25 | 26 | } 27 | 28 | /** 29 | * kick start the plugin 30 | */ 31 | require DISABLE_EMAILS_PLUGIN_ROOT . 'includes/class.Plugin.php'; 32 | Plugin::getInstance()->pluginStart(); 33 | -------------------------------------------------------------------------------- /includes/class.EmailAddress.php: -------------------------------------------------------------------------------- 1 | /', $email_address, $matches) && count($matches) === 3) { 19 | $this->name = rtrim($matches[1], ' '); 20 | $this->address = $matches[2]; 21 | } 22 | else { 23 | $this->name = ''; 24 | $this->address = $email_address; 25 | } 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /includes/class.PHPMailerMock.php: -------------------------------------------------------------------------------- 1 | phpmailer = new PHPMailer( true ); 29 | 30 | // build map of allowed function calls 31 | $this->allowed_calls = array_flip([ 32 | 'isHTML', 33 | 'addAddress', 34 | 'addCC', 35 | 'addBCC', 36 | 'addReplyTo', 37 | 'setFrom', 38 | 'addrAppend', 39 | 'addrFormat', 40 | 'wrapText', 41 | 'utf8CharBoundary', 42 | 'setWordWrap', 43 | 'createHeader', 44 | 'getMailMIME', 45 | 'getSentMIMEMessage', 46 | 'createBody', 47 | 'headerLine', 48 | 'textLine', 49 | 'addAttachment', 50 | 'getAttachments', 51 | 'encodeString', 52 | 'encodeHeader', 53 | 'hasMultiBytes', 54 | 'base64EncodeWrapMB', 55 | 'encodeQP', 56 | 'encodeQ', 57 | 'addStringAttachment', 58 | 'addEmbeddedImage', 59 | 'addStringEmbeddedImage', 60 | 'inlineImageExists', 61 | 'attachmentExists', 62 | 'alternativeExists', 63 | 'clearAddresses', 64 | 'clearCCs', 65 | 'clearBCCs', 66 | 'clearReplyTos', 67 | 'clearAllRecipients', 68 | 'clearAttachments', 69 | 'clearCustomHeaders', 70 | 'setError', 71 | 'rfcDate', 72 | 'isError', 73 | 'fixEOL', 74 | 'addCustomHeader', 75 | 'msgHTML', 76 | 'html2text', 77 | 'filenameToType', 78 | 'mb_pathinfo', 79 | 'set', 80 | 'secureHeader', 81 | 'normalizeBreaks', 82 | 'sign', 83 | 'DKIM_QP', 84 | 'DKIM_Sign', 85 | 'DKIM_HeaderC', 86 | 'DKIM_BodyC', 87 | 'DKIM_Add', 88 | 'validateAddress', 89 | ]); 90 | 91 | } 92 | 93 | /** 94 | * simulate WordPress call to PHPMailer 95 | * @param string|array $to Array or comma-separated list of email addresses to send message. 96 | * @param string $subject Email subject 97 | * @param string $message Message contents 98 | * @param string|array $headers Optional. Additional headers. 99 | * @param string|array $attachments Optional. Files to attach. 100 | * @return bool 101 | */ 102 | public function wpmail($to, $subject, $message, $headers, $attachments) { 103 | $settings = get_plugin_settings(); 104 | 105 | // get the site domain and get rid of www. 106 | if (isset($_SERVER['SERVER_NAME'])) { 107 | $sitename = strtolower( $_SERVER['SERVER_NAME'] ); 108 | if ( substr( $sitename, 0, 4 ) === 'www.' ) { 109 | $sitename = substr( $sitename, 4 ); 110 | } 111 | } 112 | else { 113 | // likely running from WP-CLI with no hostname set, so fake it 114 | $sitename = 'server-name.invalid'; 115 | } 116 | 117 | // set default From name and address 118 | $this->phpmailer->FromName = 'WordPress'; 119 | $this->phpmailer->From = 'wordpress@' . $sitename; 120 | 121 | // let hookers change the function arguments if settings allow 122 | if ($settings['wp_mail']) { 123 | extract( apply_filters( 'wp_mail', compact( 'to', 'subject', 'message', 'headers', 'attachments' ) ), EXTR_IF_EXISTS ); 124 | } 125 | 126 | // allow hookers to see recipient addresses on mock PHPMailer object 127 | try { 128 | $this->_addTo($to); 129 | } 130 | catch (phpmailerException $e) { 131 | // NOP 132 | } 133 | 134 | // set mail's subject and body 135 | $this->phpmailer->Subject = $subject; 136 | $this->phpmailer->Body = $message; 137 | 138 | // headers 139 | if ( !empty( $headers ) ) { 140 | if ( !is_array( $headers ) ) { 141 | // Explode the headers out, so this function can take both 142 | // string headers and an array of headers. 143 | $headers = explode( "\n", str_replace( "\r\n", "\n", $headers ) ); 144 | } 145 | 146 | foreach ( $headers as $header ) { 147 | // check for pseudo-headers 148 | if ( strpos($header, ':') === false ) { 149 | // TODO: handle multipart boundaries 150 | continue; 151 | } 152 | 153 | list( $name, $content ) = explode( ':', trim( $header ), 2 ); 154 | $name = trim( $name ); 155 | $content = trim( $content ); 156 | 157 | try { 158 | switch ( strtolower( $name ) ) { 159 | // Mainly for legacy -- process a From: header if it's there 160 | case 'from': 161 | $this->_setFrom( $content ); 162 | break; 163 | 164 | case 'cc': 165 | $this->_addCC( explode( ',', $content ) ); 166 | break; 167 | 168 | case 'bcc': 169 | $this->_addBCC( explode( ',', $content ) ); 170 | break; 171 | 172 | case 'content-type': 173 | $this->_setContentType( $content ); 174 | break; 175 | 176 | default: 177 | $this->phpmailer->AddCustomHeader( "$name: $content" ); 178 | break; 179 | } 180 | } 181 | catch (phpmailerException $e) { 182 | continue; 183 | } 184 | } 185 | } 186 | 187 | // attachments 188 | if ( !empty( $attachments ) ) { 189 | foreach ( $attachments as $attachment ) { 190 | try { 191 | $this->phpmailer->AddAttachment($attachment); 192 | } 193 | catch ( phpmailerException $e ) { 194 | continue; 195 | } 196 | } 197 | } 198 | 199 | if ($settings['wp_mail_from']) { 200 | $this->phpmailer->From = apply_filters( 'wp_mail_from', $this->phpmailer->From ); 201 | } 202 | if ($settings['wp_mail_from_name']) { 203 | $this->phpmailer->FromName = apply_filters( 'wp_mail_from_name', $this->phpmailer->FromName ); 204 | } 205 | if ($settings['wp_mail_content_type']) { 206 | $this->phpmailer->ContentType = apply_filters( 'wp_mail_content_type', $this->phpmailer->ContentType ); 207 | } 208 | if ($settings['wp_mail_charset']) { 209 | $this->phpmailer->CharSet = apply_filters( 'wp_mail_charset', $this->phpmailer->CharSet ); 210 | } 211 | if ($settings['phpmailer_init']) { 212 | do_action('phpmailer_init', $this->phpmailer); 213 | } 214 | 215 | return true; 216 | } 217 | 218 | /** 219 | * set a different From address and potentially, name 220 | * @param string $from 221 | */ 222 | protected function _setFrom($from) { 223 | $recipient = new EmailAddress($from); 224 | $this->phpmailer->FromName = trim($recipient->name); 225 | $this->phpmailer->From = $recipient->address; 226 | } 227 | 228 | /** 229 | * add To address(es) 230 | * @param string|array $to 231 | */ 232 | protected function _addTo($to) { 233 | $recipients = is_array($to) ? $to : explode(',', $to); 234 | 235 | foreach ($recipients as $address) { 236 | $recipient = new EmailAddress($address); 237 | $this->phpmailer->addAddress($recipient->address, $recipient->name); 238 | } 239 | } 240 | 241 | /** 242 | * add CC address(es) 243 | * @param array $addresses 244 | */ 245 | protected function _addCC($addresses) { 246 | foreach ($addresses as $address) { 247 | try { 248 | $recipient = new EmailAddress($address); 249 | $this->phpmailer->addCC($recipient->address, $recipient->name); 250 | } 251 | catch ( phpmailerException $e ) { 252 | continue; 253 | } 254 | } 255 | } 256 | 257 | /** 258 | * add BCC addresses 259 | * @param array $addresses 260 | */ 261 | protected function _addBCC($addresses) { 262 | foreach ($addresses as $address) { 263 | try { 264 | $recipient = new EmailAddress($address); 265 | $this->phpmailer->addBCC($recipient->address, $recipient->name); 266 | } 267 | catch ( phpmailerException $e ) { 268 | continue; 269 | } 270 | } 271 | } 272 | 273 | /** 274 | * set content type 275 | * @param string $content_type 276 | */ 277 | protected function _setContentType($content_type) { 278 | if ( strpos( $content_type, ';' ) !== false ) { 279 | list( $type, $charset ) = explode( ';', $content_type ); 280 | 281 | $this->phpmailer->ContentType = trim( $type ); 282 | 283 | if ( false !== stripos( $charset, 'charset=' ) ) { 284 | $this->phpmailer->CharSet = trim( str_ireplace( ['charset=', '"'], '', $charset ) ); 285 | } 286 | } 287 | else { 288 | $this->phpmailer->ContentType = trim( $content_type ); 289 | } 290 | } 291 | 292 | /** 293 | * passthrough for setting PHPMailer properties 294 | * @param string $name 295 | * @param mixed $value 296 | */ 297 | public function __set($name, $value) { 298 | $this->phpmailer->$name = $value; 299 | } 300 | 301 | /** 302 | * passthrough for getting PHPMailer properties 303 | * @param string $name 304 | * @return mixed 305 | */ 306 | public function __get($name) { 307 | return $this->phpmailer->$name; 308 | } 309 | 310 | /** 311 | * catchall for methods we just want to ignore 312 | */ 313 | public function __call($name, $args) { 314 | if (isset($this->allowed_calls[$name])) { 315 | 316 | switch (count($args)) { 317 | case 1: 318 | return $this->phpmailer->$name($args[0]); 319 | 320 | case 2: 321 | return $this->phpmailer->$name($args[0], $args[1]); 322 | 323 | case 3: 324 | return $this->phpmailer->$name($args[0], $args[1], $args[2]); 325 | 326 | case 4: 327 | return $this->phpmailer->$name($args[0], $args[1], $args[2], $args[3]); 328 | 329 | case 5: 330 | return $this->phpmailer->$name($args[0], $args[1], $args[2], $args[3], $args[4]); 331 | 332 | case 6: 333 | return $this->phpmailer->$name($args[0], $args[1], $args[2], $args[3], $args[4], $args[5]); 334 | 335 | default: 336 | return $this->phpmailer->$name(); 337 | } 338 | 339 | } 340 | 341 | return false; 342 | } 343 | 344 | /** 345 | * catchall for methods we just want to ignore 346 | */ 347 | public static function __callStatic($name, $args) { 348 | return false; 349 | } 350 | 351 | } 352 | -------------------------------------------------------------------------------- /includes/class.Plugin.php: -------------------------------------------------------------------------------- 1 | wpmailReplaced = class_exists(__NAMESPACE__ . '\\PHPMailerMock', false); 44 | 45 | // add hooks 46 | add_action('init', 'disable_emails_load_text_domain'); 47 | add_action('admin_init', [$this, 'adminInit']); 48 | add_action('admin_menu', [$this, 'adminMenu']); 49 | add_action('admin_enqueue_scripts', [$this, 'settingsScripts']); 50 | add_action('admin_print_styles-' . self::SETTINGS_HOOK_SUFFIX, [$this, 'adminStyles']); 51 | add_action('plugin_action_links_' . DISABLE_EMAILS_PLUGIN_NAME, [$this, 'pluginActionLinks']); 52 | add_filter('dashboard_glance_items', [$this, 'dashboardStatus'], 99); 53 | add_filter('plugin_row_meta', [$this, 'addPluginDetailsLinks'], 10, 2); 54 | 55 | $settings = get_plugin_settings(); 56 | 57 | // maybe add an indicator that emails are disabled 58 | if ($this->wpmailReplaced) { 59 | switch (apply_filters('disable_emails_indicator', $settings['indicator'])) { 60 | 61 | case INDICATOR_TOOLBAR: 62 | add_action('admin_bar_menu', [$this, 'showIndicatorToolbar'], 500); 63 | add_action('admin_print_styles', [$this, 'adminStyles']); 64 | break; 65 | 66 | case INDICATOR_NOTICE: 67 | add_action('admin_notices', [$this, 'showIndicatorNotice']); 68 | break; 69 | 70 | case INDICATOR_NOTICE_AND_TB: 71 | add_action('admin_notices', [$this, 'showIndicatorNotice']); 72 | add_action('admin_bar_menu', [$this, 'showIndicatorToolbar'], 500); 73 | add_action('admin_print_styles', [$this, 'adminStyles']); 74 | break; 75 | 76 | } 77 | } 78 | else { 79 | $this->showWarningAlreadyDefined(); 80 | } 81 | 82 | // maybe stop BuddyPress emails too 83 | if (!empty($settings['buddypress'])) { 84 | add_filter('bp_email_use_wp_mail', '__return_true'); 85 | } 86 | 87 | // maybe stop Events Manager emails too 88 | if (!empty($settings['events_manager'])) { 89 | add_filter('pre_option_dbem_rsvp_mail_send_method', [$this, 'forceEventsManagerDisable']); 90 | add_action('load-event_page_events-manager-options', [$this, 'cancelEventsManagerDisable']); 91 | } 92 | } 93 | 94 | /** 95 | * admin_init action 96 | */ 97 | public function adminInit() { 98 | add_settings_section(OPT_SETTINGS, false, false, OPT_SETTINGS); 99 | register_setting(OPT_SETTINGS, OPT_SETTINGS, [$this, 'settingsValidate']); 100 | } 101 | 102 | /** 103 | * admin menu items 104 | */ 105 | public function adminMenu() { 106 | add_options_page('Disable Emails', 'Disable Emails', 'manage_options', 'disable-emails', [$this, 'settingsPage']); 107 | } 108 | 109 | /** 110 | * enqueue styles for admin 111 | */ 112 | public function adminStyles() { 113 | $ver = SCRIPT_DEBUG ? time() : DISABLE_EMAILS_VERSION; 114 | wp_enqueue_style('disable-emails', plugins_url('static/css/admin.css', DISABLE_EMAILS_PLUGIN_FILE), [], $ver); 115 | } 116 | 117 | /** 118 | * settings admin scripts 119 | * @param string $hook_suffix 120 | */ 121 | public function settingsScripts($hook_suffix) { 122 | if ($hook_suffix === self::SETTINGS_HOOK_SUFFIX) { 123 | $min = SCRIPT_DEBUG ? '' : '.min'; 124 | $ver = SCRIPT_DEBUG ? time() : DISABLE_EMAILS_VERSION; 125 | 126 | wp_enqueue_script('disable-emails-settings', plugins_url("static/js/settings$min.js", DISABLE_EMAILS_PLUGIN_FILE), [], $ver, true); 127 | wp_localize_script('disable-emails-settings', 'disable_emails_settings', [ 128 | 'mu_url' => wp_nonce_url(admin_url('options-general.php?page=disable-emails'), 'disable-emails-mu'), 129 | 'msg' => [ 130 | 'mu_activate' => _x('Activate the must-use plugin?', 'settings', 'disable-emails'), 131 | 'mu_deactivate' => _x('Deactivate the must-use plugin?', 'settings', 'disable-emails'), 132 | ], 133 | ]); 134 | } 135 | } 136 | 137 | /** 138 | * settings admin 139 | */ 140 | public function settingsPage() { 141 | $settings = get_plugin_settings(); 142 | $has_mu_plugin = defined('DISABLE_EMAILS_MU_PLUGIN'); 143 | 144 | // check for enable/disable mu-plugin 145 | if (isset($_GET['action'])) { 146 | check_admin_referer('disable-emails-mu'); 147 | $has_mu_plugin = mu_plugin_manage($_GET['action']); 148 | } 149 | 150 | $indicators = [ 151 | INDICATOR_TOOLBAR => _x('Toolbar indicator', 'admin indicator setting', 'disable-emails'), 152 | INDICATOR_NOTICE => _x('notice on all admin pages', 'admin indicator setting', 'disable-emails'), 153 | INDICATOR_NOTICE_AND_TB => _x('notice and Toolbar indicator', 'admin indicator setting', 'disable-emails'), 154 | INDICATOR_NONE => _x('no indicator', 'admin indicator setting', 'disable-emails'), 155 | ]; 156 | 157 | require DISABLE_EMAILS_PLUGIN_ROOT . 'views/settings-form.php'; 158 | } 159 | 160 | /** 161 | * validate settings on save 162 | * @param array $input 163 | * @return array 164 | */ 165 | public function settingsValidate($input) { 166 | $output = []; 167 | 168 | $output['indicator'] = isset($input['indicator']) ? $input['indicator'] : INDICATOR_TOOLBAR; 169 | if (!in_array($output['indicator'], [INDICATOR_NONE, INDICATOR_TOOLBAR, INDICATOR_NOTICE, INDICATOR_NOTICE_AND_TB])) { 170 | add_settings_error(OPT_SETTINGS, 'indicator', _x('Indicator is invalid', 'settings error', 'disable-emails')); 171 | } 172 | 173 | $checkboxes = [ 174 | 'wp_mail', 175 | 'wp_mail_from', 176 | 'wp_mail_from_name', 177 | 'wp_mail_content_type', 178 | 'wp_mail_charset', 179 | 'phpmailer_init', 180 | 'buddypress', 181 | 'events_manager', 182 | ]; 183 | foreach ($checkboxes as $name) { 184 | $output[$name] = empty($input[$name]) ? 0 : 1; 185 | } 186 | 187 | return $output; 188 | } 189 | 190 | /** 191 | * add plugin action links on plugins page 192 | * @param array $links 193 | * @return array 194 | */ 195 | public function pluginActionLinks($links) { 196 | if (current_user_can('manage_options')) { 197 | // add settings link 198 | $url = admin_url('options-general.php?page=disable-emails'); 199 | $settings_link = sprintf('%s', esc_url($url), esc_html_x('Settings', 'plugin details links', 'disable-emails')); 200 | array_unshift($links, $settings_link); 201 | } 202 | 203 | return $links; 204 | } 205 | 206 | /** 207 | * action hook for adding plugin details links 208 | */ 209 | public function addPluginDetailsLinks($links, $file) { 210 | if ($file === DISABLE_EMAILS_PLUGIN_NAME) { 211 | $links[] = sprintf('%s', _x('Get help', 'plugin details links', 'disable-emails')); 212 | $links[] = sprintf('%s', _x('Rating', 'plugin details links', 'disable-emails')); 213 | $links[] = sprintf('%s', _x('Translate', 'plugin details links', 'disable-emails')); 214 | $links[] = sprintf('%s', _x('Donate', 'plugin details links', 'disable-emails')); 215 | } 216 | 217 | return $links; 218 | } 219 | 220 | /** 221 | * warn that wp_mail() is defined so emails cannot be disabled! 222 | */ 223 | public function showWarningAlreadyDefined() { 224 | $requires = new Requires(); 225 | 226 | $requires->addNotice( 227 | esc_html__('Emails are not disabled! Something else has already declared wp_mail(), so Disable Emails cannot stop emails being sent!', 'disable-emails') 228 | ); 229 | 230 | if (!defined('DISABLE_EMAILS_MU_PLUGIN')) { 231 | $requires->addNotice( 232 | esc_html__('Try enabling the must-use plugin from the settings page.', 'disable-emails') 233 | ); 234 | } 235 | } 236 | 237 | /** 238 | * admin notice for indicator of disabled emails status 239 | */ 240 | public function showIndicatorNotice() { 241 | if (current_user_can('activate_plugins') && current_user_can('manage_options')) { 242 | include DISABLE_EMAILS_PLUGIN_ROOT . 'views/indicator-notice.php'; 243 | } 244 | } 245 | 246 | /** 247 | * Toolbar indicator of disabled emails status 248 | * @param WP_Admin_Bar $admin_bar 249 | */ 250 | public function showIndicatorToolbar($admin_bar) { 251 | if (current_user_can('activate_plugins') && current_user_can('manage_options')) { 252 | $admin_bar->add_node([ 253 | 'id' => 'disable-emails-indicator', 254 | 'title' => sprintf('%s', __('Disable Emails', 'disable-emails')), 255 | 'href' => admin_url('options-general.php?page=disable-emails'), 256 | 'meta' => [ 257 | 'title' => get_status_message(), 258 | ], 259 | ]); 260 | } 261 | } 262 | 263 | /** 264 | * show on admin dashboard that emails have been disabled 265 | * @param array $glances 266 | */ 267 | public function dashboardStatus($glances) { 268 | if ($this->wpmailReplaced) { 269 | $dash_msg = get_status_message(); 270 | $glances[] = sprintf('
  • %s
  • ', esc_html($dash_msg)); 271 | } 272 | 273 | return $glances; 274 | } 275 | 276 | /** 277 | * force Events Manager to use wp_mail(), so that we can disable it 278 | * @param string|bool $return 279 | * @return string 280 | */ 281 | public function forceEventsManagerDisable($return_value) { 282 | return 'wp_mail'; 283 | } 284 | 285 | /** 286 | * cancel Events Manager hook forcing wp_mail() because we're on its settings page 287 | */ 288 | public function cancelEventsManagerDisable() { 289 | remove_filter('pre_option_dbem_rsvp_mail_send_method', [$this, 'forceEventsManagerDisable']); 290 | } 291 | 292 | } 293 | -------------------------------------------------------------------------------- /includes/class.Requires.php: -------------------------------------------------------------------------------- 1 | notices)) { 20 | $this->notices = array(); 21 | 22 | // hook admin_notices, again! so need to hook later than 10 23 | add_action('admin_notices', array($this, 'maybeShowAdminNotices'), 20); 24 | 25 | // show Requires notices before update information, so hook earlier than 10 26 | add_action('after_plugin_row_' . DISABLE_EMAILS_PLUGIN_NAME, array($this, 'showPluginRowNotices'), 9, 2); 27 | } 28 | } 29 | 30 | /** 31 | * add a Requires notices 32 | * @param string $notice 33 | */ 34 | public function addNotice($notice) { 35 | $this->init(); 36 | $this->notices[] = $notice; 37 | } 38 | 39 | /** 40 | * maybe show admin notices, if on an appropriate admin page with admin or similar logged in 41 | */ 42 | public function maybeShowAdminNotices() { 43 | if ($this->canShowAdminNotices()) { 44 | $notices = $this->notices; 45 | require DISABLE_EMAILS_PLUGIN_ROOT . 'views/requires-admin-notice.php'; 46 | } 47 | } 48 | 49 | /** 50 | * show plugin page row with requires notices 51 | */ 52 | public function showPluginRowNotices() { 53 | global $wp_list_table; 54 | 55 | if (empty($wp_list_table)) { 56 | return; 57 | } 58 | 59 | $notices = $this->notices; 60 | require DISABLE_EMAILS_PLUGIN_ROOT . 'views/requires-plugin-notice.php'; 61 | } 62 | 63 | /** 64 | * test whether we can show admin-related notices 65 | * @return bool 66 | */ 67 | protected function canShowAdminNotices() { 68 | $current_screen = get_current_screen(); 69 | 70 | // only on specific pages 71 | if (!$current_screen || $current_screen->id !== 'dashboard') { 72 | return false; 73 | } 74 | 75 | // only bother admins / plugin installers / option setters with this stuff 76 | if (!current_user_can('activate_plugins') && !current_user_can('manage_options')) { 77 | return false; 78 | } 79 | 80 | return true; 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /includes/functions-global.php: -------------------------------------------------------------------------------- 1 | addNotice( 17 | disable_emails_external_link( 18 | /* translators: %1$s: minimum required version number, %2$s: installed version number */ 19 | sprintf(esc_html__('It requires PHP %1$s or higher; your website has PHP %2$s which is {{a}}old, obsolete, and unsupported{{/a}}.', 'disable-emails'), 20 | esc_html(DISABLE_EMAILS_MIN_PHP), esc_html(PHP_VERSION)), 21 | 'https://www.php.net/supported-versions.php' 22 | ) 23 | ); 24 | $requires->addNotice( 25 | /* translators: %s: minimum recommended version number */ 26 | sprintf(esc_html__('Please upgrade your website hosting. At least PHP %s is recommended.', 'disable-emails'), '7.3') 27 | ); 28 | } 29 | 30 | /** 31 | * load text translations 32 | */ 33 | function disable_emails_load_text_domain() { 34 | load_plugin_textdomain('disable-emails'); 35 | } 36 | 37 | /** 38 | * replace link placeholders with an external link 39 | * @param string $template 40 | * @param string $url 41 | * @return string 42 | */ 43 | function disable_emails_external_link($template, $url) { 44 | $search = array( 45 | '{{a}}', 46 | '{{/a}}', 47 | ); 48 | $replace = array( 49 | sprintf('', esc_url($url)), 50 | '', 51 | ); 52 | return str_replace($search, $replace, $template); 53 | } 54 | -------------------------------------------------------------------------------- /includes/functions.php: -------------------------------------------------------------------------------- 1 | 1, 25 | 'wp_mail_from' => 1, 26 | 'wp_mail_from_name' => 1, 27 | 'wp_mail_content_type' => 1, 28 | 'wp_mail_charset' => 1, 29 | 'phpmailer_init' => 1, 30 | 'buddypress' => 1, 31 | 'events_manager' => 1, 32 | 'indicator' => INDICATOR_TOOLBAR, 33 | ]; 34 | 35 | return wp_parse_args(get_option(OPT_SETTINGS, []), $defaults); 36 | } 37 | 38 | /** 39 | * can current user activate/deactivate the must-use plugin? 40 | * @return bool 41 | */ 42 | function has_mu_plugin_permission() { 43 | return current_user_can(is_multisite() ? 'manage_network_plugins' : 'activate_plugins'); 44 | } 45 | 46 | /** 47 | * install, update, or remove the must-use plugin 48 | * @param string $action 49 | * @return bool 50 | * @throws Exception 51 | */ 52 | function mu_plugin_manage($action) { 53 | if (!has_mu_plugin_permission()) { 54 | throw new Exception(__('No permission to manage Disable Emails must-use plugin.', 'disable-emails')); 55 | } 56 | 57 | $has_mu_plugin = defined('DISABLE_EMAILS_MU_PLUGIN'); 58 | 59 | $wpmu_plugin_dir = rtrim(wp_normalize_path(WPMU_PLUGIN_DIR), '/'); 60 | if (!is_dir($wpmu_plugin_dir)) { 61 | // folder does not exist, create it now 62 | wp_mkdir_p($wpmu_plugin_dir); 63 | if (!is_dir($wpmu_plugin_dir)) { 64 | throw new Exception(__('Unable to create folder for Disable Emails must-use plugin.', 'disable-emails')); 65 | } 66 | } 67 | 68 | $source = wp_normalize_path(DISABLE_EMAILS_PLUGIN_ROOT . 'mu-plugin/disable-emails-mu.php'); 69 | $target = "$wpmu_plugin_dir/disable-emails-mu.php"; 70 | 71 | switch ($action) { 72 | 73 | case 'activate': 74 | case 'update': 75 | if (!copy($source, $target)) { 76 | throw new Exception(__('Unable to install Disable Emails must-use plugin.', 'disable-emails')); 77 | } 78 | $has_mu_plugin = true; 79 | break; 80 | 81 | case 'deactivate': 82 | if (!unlink($target)) { 83 | throw new Exception(__('Unable to uninstall Disable Emails must-use plugin.', 'disable-emails')); 84 | } 85 | $has_mu_plugin = false; 86 | break; 87 | 88 | default: 89 | throw new Exception(__('Invalid action for Disable Emails must-use plugin.', 'disable-emails')); 90 | 91 | } 92 | 93 | return $has_mu_plugin; 94 | } 95 | 96 | /** 97 | * get message for current active status 98 | * @return string 99 | */ 100 | function get_status_message() { 101 | require_once ABSPATH . 'wp-admin/includes/plugin.php'; 102 | 103 | if (defined('DISABLE_EMAILS_MU_PLUGIN') && is_multisite()) { 104 | /* translators: shown when emails are disabled for all sites in all networks in a multisite, with the must-use plugin */ 105 | $msg = __('Emails are disabled for all sites.', 'disable-emails'); 106 | } 107 | elseif (is_plugin_active_for_network(DISABLE_EMAILS_PLUGIN_NAME)) { 108 | /* translators: shown when emails are disabled for all sites in a multisite network, by network-activating the plugin */ 109 | $msg = __('Emails are disabled on this network.', 'disable-emails'); 110 | } 111 | else { 112 | /* translators: shown when emails are disabled for the current site */ 113 | $msg = __('Emails are disabled.', 'disable-emails'); 114 | } 115 | 116 | return $msg; 117 | } 118 | -------------------------------------------------------------------------------- /languages/disable-emails-cs_CZ.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webaware/disable-emails/03d8816ece71c0557e69eed5dcaa05b50f63662a/languages/disable-emails-cs_CZ.mo -------------------------------------------------------------------------------- /languages/disable-emails-de_DE.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webaware/disable-emails/03d8816ece71c0557e69eed5dcaa05b50f63662a/languages/disable-emails-de_DE.mo -------------------------------------------------------------------------------- /languages/disable-emails-nb_NO.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webaware/disable-emails/03d8816ece71c0557e69eed5dcaa05b50f63662a/languages/disable-emails-nb_NO.mo -------------------------------------------------------------------------------- /languages/disable-emails-nn_NO.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webaware/disable-emails/03d8816ece71c0557e69eed5dcaa05b50f63662a/languages/disable-emails-nn_NO.mo -------------------------------------------------------------------------------- /languages/disable-emails-zh_CN.mo: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webaware/disable-emails/03d8816ece71c0557e69eed5dcaa05b50f63662a/languages/disable-emails-zh_CN.mo -------------------------------------------------------------------------------- /mu-plugin/disable-emails-mu.php: -------------------------------------------------------------------------------- 1 | NB: if you need to run this plugin on WordPress 5.4 or earlier, and must install manually from a .zip file, please install version 1.6.3 which you can [download from the Advanced page for the plugin](https://wordpress.org/plugins/disable-emails/advanced/). Since version 1.7.0, WordPress 5.5 or later is required. 29 | 30 | ### Translations 31 | 32 | Many thanks to the generous efforts of our translators: 33 | 34 | * Chinese (zh-CN) -- [Cai_Miao](https://profiles.wordpress.org/cai_miao) and [the Chinese translation team](https://translate.wordpress.org/locale/zh-cn/default/wp-plugins/disable-emails/) 35 | * Chinese (zh-TW) -- [the Chinese (Taiwan) translation team](https://translate.wordpress.org/locale/zh-tw/default/wp-plugins/disable-emails/) 36 | * Czech (cs-CZ) -- [Rudolf Klusal](http://www.klusik.cz/) 37 | * Dutch (nl_NL) -- [the Dutch translation team](https://translate.wordpress.org/locale/nl/default/wp-plugins/disable-emails/) 38 | * English (en_CA) -- [the English (Canadian) translation team](https://translate.wordpress.org/locale/en-ca/default/wp-plugins/disable-emails/) 39 | * English (en_GB) -- [the English (UK) translation team](https://translate.wordpress.org/locale/en-gb/default/wp-plugins/disable-emails/) 40 | * French (fr_FR) -- [the French translation team](https://translate.wordpress.org/locale/fr/default/wp-plugins/disable-emails/) 41 | * Korean (ko_KR) -- [the Korean translation team](https://translate.wordpress.org/locale/ko/default/wp-plugins/disable-emails/) 42 | * Japanese (ja) -- [Cai_Miao](https://profiles.wordpress.org/cai_miao) and [the Japanese translation team](https://translate.wordpress.org/locale/ja/default/wp-plugins/disable-emails/) 43 | * German (de-DE) -- [Peter Harlacher](http://helvetian.io/) 44 | * Norwegian: Bokmål (nb-NO) -- [neonnero](http://www.neonnero.com/) 45 | * Norwegian: Nynorsk (nn-NO) -- [neonnero](http://www.neonnero.com/) 46 | * Russian (ru_RU) -- [the Russian translation team](https://translate.wordpress.org/locale/ru/default/wp-plugins/disable-emails/) 47 | * Swedish (sv_SE) -- [the Swedish translation team](https://translate.wordpress.org/locale/sv/default/wp-plugins/disable-emails/) 48 | 49 | If you'd like to help out by translating this plugin, please [sign up for an account and dig in](https://translate.wordpress.org/projects/wp-plugins/disable-emails). 50 | 51 | ## Installation 52 | 53 | 1. Either install automatically through the WordPress admin, or download the .zip file, unzip to a folder, and upload the folder to your /wp-content/plugins/ directory. Read [Installing Plugins](https://codex.wordpress.org/Managing_Plugins#Installing_Plugins) in the WordPress Codex for details. 54 | 2. Activate the plugin through the 'Plugins' menu in WordPress. 55 | 3. Optional: from the WordPress admin, navigate to Settings > Disable Emails and click the "Activate must-use plugin" if you want the plugin to always be activated, no matter what. 56 | 57 | ## Frequently Asked Questions 58 | 59 | ### Why am I still getting standard WordPress emails? 60 | 61 | You probably have another plugin that adds its own implementation of the `wp_mail()` function. Try disabling some plugins. 62 | 63 | In some circumstances, enabling the must-use plugin from settings will fix this, because must-use plugins load before other plugins. 64 | 65 | ### Standard WordPress emails have stopped, but some others still get sent 66 | 67 | You probably have a plugin that is sending emails via some other method, like directly using the PHP `mail()` function, or directly implementing an SMTP client. Not much I can do about that... 68 | 69 | ### How does it work? 70 | 71 | The plugin replaces the standard WordPress `wp_mail()` function with a function that sends no emails. Nada. Zip. Silence. 72 | 73 | Behind the scenes, it creates a private copy of PHPMailer and allows the system to interact with it, but silently suppresses the functions that send emails. The standard WordPress filter and action hooks are supported, so plugins that register hooks for those will still function as normal. It just doesn't actually send any emails. 74 | 75 | ### Can I make it a must-use plugin? 76 | 77 | Yes. Once you have activated the plugin, navigate to Settings > Disable Emails and click the "Activate must-use plugin". This will create a must-use plugin (mu-plugin) that ensures that Disable Emails is always loaded. This can be especially useful on development websites where the database is frequently refreshed from a live site which _does not_ have Disable Emails activated. 78 | 79 | NB: if you activate the must-use plugin on a multisite, it will stop emails on all sites on the multisite! If you have multiple networks on your multisite, the must-use plugin will stop emails on all networks. 80 | 81 | ### Contributions 82 | 83 | * [Translate into your preferred language](https://translate.wordpress.org/projects/wp-plugins/disable-emails) 84 | * [Fork me on GitHub](https://github.com/webaware/disable-emails) 85 | 86 | ## Upgrade Notice 87 | 88 | ### 1.8.2 89 | 90 | fixed deprecation warnings in PHP 8.1+ 91 | 92 | ## Changelog 93 | 94 | The full changelog can be found [on GitHub](https://github.com/webaware/disable-emails/blob/master/changelog.md). Recent entries: 95 | 96 | ### 1.8.2 97 | 98 | Released 2023-11-22 99 | 100 | * fixed: deprecation warnings in PHP 8.1+ 101 | -------------------------------------------------------------------------------- /static/css/admin.css: -------------------------------------------------------------------------------- 1 | #wpadminbar #wp-admin-bar-disable-emails-indicator .ab-icon::before { 2 | content: "\f465"; /* dashicons-email */ 3 | top: 3px; 4 | background: linear-gradient(to left top, transparent 45%, #dc3232 45%, #dc3232 55%, transparent 60%); 5 | } 6 | 7 | body.admin-color-sunrise #wpadminbar #wp-admin-bar-disable-emails-indicator .ab-icon::before { 8 | background: linear-gradient(to left top, transparent 45%, #000 45%, #000 55%, transparent 60%); 9 | } 10 | 11 | .no-js .disable-emails-mu-buttons { 12 | display: none; 13 | } 14 | -------------------------------------------------------------------------------- /static/js/settings.js: -------------------------------------------------------------------------------- 1 | (function (settings) { 2 | addHandler("disable-emails-mu-enable", "click", function () { 3 | if (window.confirm(settings.msg.mu_activate)) { 4 | mustUse('activate'); 5 | } 6 | }); 7 | addHandler("disable-emails-mu-disable", "click", function () { 8 | if (window.confirm(settings.msg.mu_deactivate)) { 9 | mustUse('deactivate'); 10 | } 11 | }); 12 | 13 | /** 14 | * add an event handler to element, if element is found 15 | * @param {String} selector 16 | * @param {String} event 17 | * @param {Function} handler 18 | */ 19 | function addHandler(selector, event, handler) { 20 | const element = document.getElementById(selector); 21 | if (element) { 22 | element.addEventListener(event, handler, false); 23 | } 24 | } 25 | 26 | /** 27 | * reload page with request to enable / disable the must-use plugin 28 | * @param {String} action 29 | */ 30 | function mustUse(action) { 31 | document.location = settings.mu_url + "&action=" + action; 32 | } 33 | })(disable_emails_settings); 34 | -------------------------------------------------------------------------------- /static/js/settings.min.js: -------------------------------------------------------------------------------- 1 | !function(e){function i(i,e,n){i=document.getElementById(i);i&&i.addEventListener(e,n,!1)}function n(i){document.location=e.mu_url+"&action="+i}i("disable-emails-mu-enable","click",function(){window.confirm(e.msg.mu_activate)&&n("activate")}),i("disable-emails-mu-disable","click",function(){window.confirm(e.msg.mu_deactivate)&&n("deactivate")})}(disable_emails_settings); -------------------------------------------------------------------------------- /views/indicator-notice.php: -------------------------------------------------------------------------------- 1 | 6 | 7 |
    8 |

    9 |
    10 | -------------------------------------------------------------------------------- /views/requires-admin-notice.php: -------------------------------------------------------------------------------- 1 | 6 | 7 |
    8 |

    9 | 14 |
    15 | -------------------------------------------------------------------------------- /views/requires-plugin-notice.php: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /views/settings-form.php: -------------------------------------------------------------------------------- 1 | 11 | 12 |
    13 |

    14 | 15 |
    16 | 17 | 18 | 19 | 20 | 21 | 22 | 30 | 31 | 32 | 33 | 34 | 43 | 44 | 45 | 46 | 47 | 51 | 52 | 53 | 54 | 55 | 59 | 60 | 61 |
    23 | 28 |

    29 |
    35 | 36 |
    37 |
    38 |
    39 |
    40 |
    41 |

    42 |
    48 | /> 49 | 50 |
    56 | /> 57 | 58 |
    62 | 63 | 64 | 65 | 66 | 67 |

    68 | 69 |

    70 | 71 |

    72 | 73 | 74 |

    75 | 76 | 77 |

    78 | 79 | 80 | 81 | 82 | 83 | 84 |

    85 | 86 | 87 |

    88 | 89 | 90 | 91 | 92 | 93 |
    94 | 95 | 103 | 104 | 105 | 106 |
    107 |
    108 | --------------------------------------------------------------------------------