<?php
/**
 * @package   admintoolswp
 * @copyright Copyright (c)2017-2026 Nicholas K. Dionysopoulos / Akeeba Ltd
 * @license   GNU GPL version 3 or later
 */

defined('ADMINTOOLSINC') or die;

class AtsystemFeaturePhpexceptionemail extends AtsystemFeatureAbstract
{
	protected $loadOrder = 1;

	private string $emailAddress;

	/**
	 * Is this feature enabled?
	 *
	 * @return bool
	 */
	public function isEnabled()
	{
		$this->emailAddress = trim($this->cparams->getValue('emailphpexceptions', '') ?: '');

		// If user wants to apply the system default one, simply disable this feature
		return ($this->emailAddress != '');
	}

	public function onCustomHooks()
	{
		if (empty($this->emailAddress))
		{
			return;
		}

		if (!class_exists('AdminToolsFatalErrorHandler'))
		{
			require_once ADMINTOOLSWP_PATH . '/helpers/errorhandler.php';
		}

		// Set our exceptions handler
		set_exception_handler([$this, 'errorHandler']);
	}

	public function redactPath(string $text): string
	{
		$absPath = rtrim(ABSPATH, '/' . DIRECTORY_SEPARATOR);

		if (empty($absPath))
		{
			return $text;
		}

		return str_replace($absPath, '<SITE ROOT>', $text);
	}

	public function errorHandler(Throwable $error): void
	{
		if (empty($this->emailAddress))
		{
			return;
		}

		$code = $error->getCode();

		// Do not handle "Not found" and "Forbidden" exceptions
		if ($code === 403 || $code === 404)
		{
			return;
		}

		\AdminToolsFatalErrorHandler::setException($error);

		$type    = get_class($error);
		$subject = 'Unhandled exception - ' . $type;

		// Now let's htmlencode the dump of all superglobals
		$get     = print_r($_GET, true);
		$post    = print_r($_POST, true);
		$cookie  = print_r($_COOKIE, true);
		$request = print_r($_REQUEST, true);
		$server  = print_r($_SERVER, true);

		try
		{
			// Try to increase verbosity.
			ini_set('zend.exception_string_param_max_len', 100);
		}
		catch (Throwable $e)
		{
			// No sweat.
		}

		$body = <<<HTML
A PHP Exception occurred on your site.

Exception Identity
======================================================================

Exception Type:
$type

Message:
{$error->getMessage()} 

File and line:
{$error->getFile()}:{$error->getLine()}

Stack trace
----------------------------------------------------------------------
{$error->getTraceAsString()}

Request information
======================================================================

GET variables
----------------------------------------------------------------------
$get

POST variables
----------------------------------------------------------------------
$post

COOKIE variables
----------------------------------------------------------------------
$cookie

REQUEST variables
----------------------------------------------------------------------
$request

SERVER variables
----------------------------------------------------------------------
$server

IMPORTANT
======================================================================

Why am I receiving this email?
----------------------------------------------------------------------

This is an automated informational email from Admin Tools. Please do
not reply to this email. 

This email does not necessarily indicate a problem with your site. 
Please read the entirety of this message before deciding whether you
need to act.

Not all exceptions indicate an issue with your site.
----------------------------------------------------------------------

Not all exceptions are bad or unwanted. WordPress and its plugins
may raise exceptions to stop execution when there are obvious
problems with the request data (e.g. the post no longer exists),
responding to it would violate security policies (e.g. accessing an
unpublished post), or would otherwise cause undesirable or unexpected
behavior. 

Exceptions are only a problem when they are the result of an error in
the code or your server configuration, not the request itself. Even
so, they might be the result of a configuration error on your part,
or a temporary issue such as a network error, the database server
restarting, or your site being temporarily unavailable due to core
WordPress, plugin, or theme updates. Always check the exception and
evaluate the conditions it occurred under before deciding whether to
act on it.

Do note that in some cases WordPress may decide to disable the plugin
or theme that caused the exception. If this happens, your site may
indeed not function properly. This is the whole idea behind receiving
these emails: to notify you of exceptions that might have unintended
consequences.

Who to contact, and when.
----------------------------------------------------------------------

If you are not sure why this exception occurred, please contact the
developer of the plugin or theme that caused it. If the exception is
raised by WordPress itself, and you don't understand why, please
request assistance in your local WordPress support forum.

When requesting assistance for an exception, you should include all
information in this email above the "IMPORTANT" header. Do keep in
mind that some of the information may contain sensitive data, such as
paths to your site's various directories, session identifiers, and
usernames / passwords. Please be sure to redact any such information
before sharing it, especially if you are sharing it on a public
forum.

Kindly note that the developers of Admin Tools cannot help you with
exceptions raised by WordPress itself, third-party plugins, or
themes. We can only help you with exceptions raised by our own
plugins. Thank you for your understanding.
HTML;

		$body = $this->redactPath($body);

		$recipients = is_array($this->emailAddress) ? $this->emailAddress : explode(',', $this->emailAddress);
		$recipients = array_map('trim', $recipients);

		foreach ($recipients as $recipient)
		{
			try
			{
				if (!function_exists('wp_mail'))
				{
					require_once ABSPATH . WPINC . '/pluggable.php';
				}

				wp_mail($recipient, $subject, $body);
			}
			catch (Throwable $e)
			{
				// Do nothing
			}
		}

		try
		{
			$handler = $this->getErrorHandler();

			if (!is_object($handler) || !is_callable([$handler, 'handle']))
			{
				die('An unhandled error occurred on your site.');
			}

			$handler->handle();
		}
		catch (Throwable $e)
		{
			die('An unhandled error occurred on your site.');
		}
	}

	private function getErrorHandler(): ?object
	{
		if (!wp_is_fatal_error_handler_enabled())
		{
			return null;
		}

		$handler = null;

		if (
			defined('WP_CONTENT_DIR')
			&& is_readable(WP_CONTENT_DIR . '/fatal-error-handler.php'))
		{
			try
			{
				$handler = $this->getHandlerFromDropIn();
			}
			catch (Throwable $e)
			{
				$handler = null;
			}
		}

		if (!is_object($handler) || !is_callable([$handler, 'handle']))
		{
			$handler = new AdminToolsFatalErrorHandler();
		}

		return $handler;
	}

	private function getHandlerFromDropIn()
	{
		require_once __DIR__ . '/../../helpers/buffer.php';
		$tempFile    = 'admintoolstmp://fatal-error-handler.php';
		$fileContent = @file_get_contents(WP_CONTENT_DIR . '/fatal-error-handler.php');

		if ($fileContent === false)
		{
			return null;
		}

		$fileContent = str_replace(
			[
				'WP_Fatal_Error_Handler',
				'error_get_last()'
			],
			[
				'AdminToolsFatalErrorHandler',
				'\\AdminToolsFatalErrorHandler::error_get_last()'
			],
			$fileContent
		);

		if (file_put_contents($tempFile, $fileContent) === false)
		{
			return null;
		}

		return include_once $tempFile;
	}
}