wp/wp-includes/class-wp-recovery-mode-email-service.php
changeset 16 a86126ab1dd4
parent 9 177826044cd9
child 18 be944660c56a
equal deleted inserted replaced
15:3d4e9c994f10 16:a86126ab1dd4
     1 <?php
     1 <?php
     2 /**
     2 /**
     3  * Error Protection API: WP_Recovery_Mode_Email_Link class
     3  * Error Protection API: WP_Recovery_Mode_Email_Link class
     4  *
     4  *
     5  * @package WordPress
     5  * @package WordPress
     6  * @since   5.2.0
     6  * @since 5.2.0
     7  */
     7  */
     8 
     8 
     9 /**
     9 /**
    10  * Core class used to send an email with a link to begin Recovery Mode.
    10  * Core class used to send an email with a link to begin Recovery Mode.
    11  *
    11  *
    39 	 *
    39 	 *
    40 	 * @since 5.2.0
    40 	 * @since 5.2.0
    41 	 *
    41 	 *
    42 	 * @param int   $rate_limit Number of seconds before another email can be sent.
    42 	 * @param int   $rate_limit Number of seconds before another email can be sent.
    43 	 * @param array $error      Error details from {@see error_get_last()}
    43 	 * @param array $error      Error details from {@see error_get_last()}
    44 	 * @param array $extension  The extension that caused the error. {
    44 	 * @param array $extension {
    45 	 *      @type string $slug The extension slug. The plugin or theme's directory.
    45 	 *     The extension that caused the error.
    46 	 *      @type string $type The extension type. Either 'plugin' or 'theme'.
    46 	 *
       
    47 	 *     @type string $slug The extension slug. The plugin or theme's directory.
       
    48 	 *     @type string $type The extension type. Either 'plugin' or 'theme'.
    47 	 * }
    49 	 * }
    48 	 * @return true|WP_Error True if email sent, WP_Error otherwise.
    50 	 * @return true|WP_Error True if email sent, WP_Error otherwise.
    49 	 */
    51 	 */
    50 	public function maybe_send_recovery_mode_email( $rate_limit, $error, $extension ) {
    52 	public function maybe_send_recovery_mode_email( $rate_limit, $error, $extension ) {
    51 
    53 
    60 
    62 
    61 			if ( $sent ) {
    63 			if ( $sent ) {
    62 				return true;
    64 				return true;
    63 			}
    65 			}
    64 
    66 
    65 			return new WP_Error( 'email_failed', __( 'The email could not be sent. Possible reason: your host may have disabled the mail() function.' ) );
    67 			return new WP_Error(
       
    68 				'email_failed',
       
    69 				sprintf(
       
    70 					/* translators: %s: mail() */
       
    71 					__( 'The email could not be sent. Possible reason: your host may have disabled the %s function.' ),
       
    72 					'mail()'
       
    73 				)
       
    74 			);
    66 		}
    75 		}
    67 
    76 
    68 		$err_message = sprintf(
    77 		$err_message = sprintf(
    69 			/* translators: 1. Last sent as a human time diff 2. Wait time as a human time diff. */
    78 			/* translators: 1. Last sent as a human time diff, 2. Wait time as a human time diff. */
    70 			__( 'A recovery link was already sent %1$s ago. Please wait another %2$s before requesting a new email.' ),
    79 			__( 'A recovery link was already sent %1$s ago. Please wait another %2$s before requesting a new email.' ),
    71 			human_time_diff( $last_sent ),
    80 			human_time_diff( $last_sent ),
    72 			human_time_diff( $last_sent + $rate_limit )
    81 			human_time_diff( $last_sent + $rate_limit )
    73 		);
    82 		);
    74 
    83 
    92 	 * @since 5.2.0
   101 	 * @since 5.2.0
    93 	 *
   102 	 *
    94 	 * @param int   $rate_limit Number of seconds before another email can be sent.
   103 	 * @param int   $rate_limit Number of seconds before another email can be sent.
    95 	 * @param array $error      Error details from {@see error_get_last()}
   104 	 * @param array $error      Error details from {@see error_get_last()}
    96 	 * @param array $extension  Extension that caused the error.
   105 	 * @param array $extension  Extension that caused the error.
    97 	 *
       
    98 	 * @return bool Whether the email was sent successfully.
   106 	 * @return bool Whether the email was sent successfully.
    99 	 */
   107 	 */
   100 	private function send_recovery_mode_email( $rate_limit, $error, $extension ) {
   108 	private function send_recovery_mode_email( $rate_limit, $error, $extension ) {
   101 
   109 
   102 		$url      = $this->link_service->generate_url();
   110 		$url      = $this->link_service->generate_url();
   125 		/**
   133 		/**
   126 		 * Filters the support message sent with the the fatal error protection email.
   134 		 * Filters the support message sent with the the fatal error protection email.
   127 		 *
   135 		 *
   128 		 * @since 5.2.0
   136 		 * @since 5.2.0
   129 		 *
   137 		 *
   130 		 * @param $message string The Message to include in the email.
   138 		 * @param string $message The Message to include in the email.
   131 		 */
   139 		 */
   132 		$support = apply_filters( 'recovery_email_support_info', __( 'Please contact your host for assistance with investigating this issue further.' ) );
   140 		$support = apply_filters( 'recovery_email_support_info', __( 'Please contact your host for assistance with investigating this issue further.' ) );
   133 
   141 
   134 		/* translators: Do not translate LINK, EXPIRES, CAUSE, DETAILS, SITEURL, PAGEURL, SUPPORT: those are placeholders. */
   142 		/**
       
   143 		 * Filters the debug information included in the fatal error protection email.
       
   144 		 *
       
   145 		 * @since 5.3.0
       
   146 		 *
       
   147 		 * @param array $message An associative array of debug information.
       
   148 		 */
       
   149 		$debug = apply_filters( 'recovery_email_debug_info', $this->get_debug( $extension ) );
       
   150 
       
   151 		/* translators: Do not translate LINK, EXPIRES, CAUSE, DETAILS, SITEURL, PAGEURL, SUPPORT. DEBUG: those are placeholders. */
   135 		$message = __(
   152 		$message = __(
   136 			'Howdy!
   153 			'Howdy!
   137 
   154 
   138 Since WordPress 5.2 there is a built-in feature that detects when a plugin or theme causes a fatal error on your site, and notifies you with this automated email.
   155 Since WordPress 5.2 there is a built-in feature that detects when a plugin or theme causes a fatal error on your site, and notifies you with this automated email.
   139 ###CAUSE###
   156 ###CAUSE###
   144 If your site appears broken and you can\'t access your dashboard normally, WordPress now has a special "recovery mode". This lets you safely login to your dashboard and investigate further.
   161 If your site appears broken and you can\'t access your dashboard normally, WordPress now has a special "recovery mode". This lets you safely login to your dashboard and investigate further.
   145 
   162 
   146 ###LINK###
   163 ###LINK###
   147 
   164 
   148 To keep your site safe, this link will expire in ###EXPIRES###. Don\'t worry about that, though: a new link will be emailed to you if the error occurs again after it expires.
   165 To keep your site safe, this link will expire in ###EXPIRES###. Don\'t worry about that, though: a new link will be emailed to you if the error occurs again after it expires.
       
   166 
       
   167 When seeking help with this issue, you may be asked for some of the following information:
       
   168 ###DEBUG###
   149 
   169 
   150 ###DETAILS###'
   170 ###DETAILS###'
   151 		);
   171 		);
   152 		$message = str_replace(
   172 		$message = str_replace(
   153 			array(
   173 			array(
   156 				'###CAUSE###',
   176 				'###CAUSE###',
   157 				'###DETAILS###',
   177 				'###DETAILS###',
   158 				'###SITEURL###',
   178 				'###SITEURL###',
   159 				'###PAGEURL###',
   179 				'###PAGEURL###',
   160 				'###SUPPORT###',
   180 				'###SUPPORT###',
       
   181 				'###DEBUG###',
   161 			),
   182 			),
   162 			array(
   183 			array(
   163 				$url,
   184 				$url,
   164 				human_time_diff( time() + $rate_limit ),
   185 				human_time_diff( time() + $rate_limit ),
   165 				$cause ? "\n{$cause}\n" : "\n",
   186 				$cause ? "\n{$cause}\n" : "\n",
   166 				$details,
   187 				$details,
   167 				home_url( '/' ),
   188 				home_url( '/' ),
   168 				home_url( $_SERVER['REQUEST_URI'] ),
   189 				home_url( $_SERVER['REQUEST_URI'] ),
   169 				$support,
   190 				$support,
       
   191 				implode( "\r\n", $debug ),
   170 			),
   192 			),
   171 			$message
   193 			$message
   172 		);
   194 		);
   173 
   195 
   174 		$email = array(
   196 		$email = array(
   175 			'to'      => $this->get_recovery_mode_email_address(),
   197 			'to'      => $this->get_recovery_mode_email_address(),
   176 			/* translators: %s: site name */
   198 			/* translators: %s: Site title. */
   177 			'subject' => __( '[%s] Your Site is Experiencing a Technical Issue' ),
   199 			'subject' => __( '[%s] Your Site is Experiencing a Technical Issue' ),
   178 			'message' => $message,
   200 			'message' => $message,
   179 			'headers' => '',
   201 			'headers' => '',
   180 		);
   202 		);
   181 
   203 
   227 	 * @return string Message about which extension caused the error.
   249 	 * @return string Message about which extension caused the error.
   228 	 */
   250 	 */
   229 	private function get_cause( $extension ) {
   251 	private function get_cause( $extension ) {
   230 
   252 
   231 		if ( 'plugin' === $extension['type'] ) {
   253 		if ( 'plugin' === $extension['type'] ) {
   232 			if ( ! function_exists( 'get_plugins' ) ) {
   254 			$plugin = $this->get_plugin( $extension );
   233 				require_once ABSPATH . 'wp-admin/includes/plugin.php';
   255 
   234 			}
   256 			if ( false === $plugin ) {
   235 
   257 				$name = $extension['slug'];
   236 			$plugins = get_plugins();
       
   237 
       
   238 			$name = '';
       
   239 
       
   240 			// Assume plugin main file name first since it is a common convention.
       
   241 			if ( isset( $plugins[ "{$extension['slug']}/{$extension['slug']}.php" ] ) ) {
       
   242 				$name = $plugins[ "{$extension['slug']}/{$extension['slug']}.php" ]['Name'];
       
   243 			} else {
   258 			} else {
   244 				foreach ( $plugins as $file => $plugin_data ) {
   259 				$name = $plugin['Name'];
   245 					if ( 0 === strpos( $file, "{$extension['slug']}/" ) || $file === $extension['slug'] ) {
   260 			}
   246 						$name = $plugin_data['Name'];
   261 
   247 						break;
   262 			/* translators: %s: Plugin name. */
   248 					}
       
   249 				}
       
   250 			}
       
   251 
       
   252 			if ( empty( $name ) ) {
       
   253 				$name = $extension['slug'];
       
   254 			}
       
   255 
       
   256 			/* translators: %s: plugin name */
       
   257 			$cause = sprintf( __( 'In this case, WordPress caught an error with one of your plugins, %s.' ), $name );
   263 			$cause = sprintf( __( 'In this case, WordPress caught an error with one of your plugins, %s.' ), $name );
   258 		} else {
   264 		} else {
   259 			$theme = wp_get_theme( $extension['slug'] );
   265 			$theme = wp_get_theme( $extension['slug'] );
   260 			$name  = $theme->exists() ? $theme->display( 'Name' ) : $extension['slug'];
   266 			$name  = $theme->exists() ? $theme->display( 'Name' ) : $extension['slug'];
   261 
   267 
   262 			/* translators: %s: theme name */
   268 			/* translators: %s: Theme name. */
   263 			$cause = sprintf( __( 'In this case, WordPress caught an error with your theme, %s.' ), $name );
   269 			$cause = sprintf( __( 'In this case, WordPress caught an error with your theme, %s.' ), $name );
   264 		}
   270 		}
   265 
   271 
   266 		return $cause;
   272 		return $cause;
   267 	}
   273 	}
       
   274 
       
   275 	/**
       
   276 	 * Return the details for a single plugin based on the extension data from an error.
       
   277 	 *
       
   278 	 * @since 5.3.0
       
   279 	 *
       
   280 	 * @param array $extension The extension that caused the error.
       
   281 	 * @return bool|array A plugin array {@see get_plugins()} or `false` if no plugin was found.
       
   282 	 */
       
   283 	private function get_plugin( $extension ) {
       
   284 		if ( ! function_exists( 'get_plugins' ) ) {
       
   285 			require_once ABSPATH . 'wp-admin/includes/plugin.php';
       
   286 		}
       
   287 
       
   288 		$plugins = get_plugins();
       
   289 
       
   290 		// Assume plugin main file name first since it is a common convention.
       
   291 		if ( isset( $plugins[ "{$extension['slug']}/{$extension['slug']}.php" ] ) ) {
       
   292 			return $plugins[ "{$extension['slug']}/{$extension['slug']}.php" ];
       
   293 		} else {
       
   294 			foreach ( $plugins as $file => $plugin_data ) {
       
   295 				if ( 0 === strpos( $file, "{$extension['slug']}/" ) || $file === $extension['slug'] ) {
       
   296 					return $plugin_data;
       
   297 				}
       
   298 			}
       
   299 		}
       
   300 
       
   301 		return false;
       
   302 	}
       
   303 
       
   304 	/**
       
   305 	 * Return debug information in an easy to manipulate format.
       
   306 	 *
       
   307 	 * @since 5.3.0
       
   308 	 *
       
   309 	 * @param array $extension The extension that caused the error.
       
   310 	 * @return array An associative array of debug information.
       
   311 	 */
       
   312 	private function get_debug( $extension ) {
       
   313 		$theme      = wp_get_theme();
       
   314 		$wp_version = get_bloginfo( 'version' );
       
   315 
       
   316 		if ( $extension ) {
       
   317 			$plugin = $this->get_plugin( $extension );
       
   318 		} else {
       
   319 			$plugin = null;
       
   320 		}
       
   321 
       
   322 		$debug = array(
       
   323 			/* translators: %s: Current WordPress version number. */
       
   324 			'wp'    => sprintf(
       
   325 				__( 'WordPress version %s' ),
       
   326 				$wp_version
       
   327 			),
       
   328 			'theme' => sprintf(
       
   329 				/* translators: 1: Current active theme name. 2: Current active theme version. */
       
   330 				__( 'Current theme: %1$s (version %2$s)' ),
       
   331 				$theme->get( 'Name' ),
       
   332 				$theme->get( 'Version' )
       
   333 			),
       
   334 		);
       
   335 
       
   336 		if ( null !== $plugin ) {
       
   337 			$debug['plugin'] = sprintf(
       
   338 				/* translators: 1: The failing plugins name. 2: The failing plugins version. */
       
   339 				__( 'Current plugin: %1$s (version %2$s)' ),
       
   340 				$plugin['Name'],
       
   341 				$plugin['Version']
       
   342 			);
       
   343 		}
       
   344 
       
   345 		$debug['php'] = sprintf(
       
   346 			/* translators: %s: The currently used PHP version. */
       
   347 			__( 'PHP version %s' ),
       
   348 			PHP_VERSION
       
   349 		);
       
   350 
       
   351 		return $debug;
       
   352 	}
   268 }
   353 }