2020-07-20 11:17:25 +02:00
|
|
|
<?PHP
|
|
|
|
/**
|
|
|
|
* Debugging and error handling helper for MD.
|
|
|
|
*/
|
|
|
|
declare(strict_types = 1);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Debugging and error handling class.
|
|
|
|
*/
|
2020-08-29 12:57:45 +02:00
|
|
|
final class MDErrorReporter {
|
2020-07-20 11:17:25 +02:00
|
|
|
|
2020-09-03 20:00:08 +02:00
|
|
|
private string $_context;
|
|
|
|
private string $_contact_mail_addr;
|
2020-07-20 11:17:25 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Categorizes an error to better decide how to act on it.
|
|
|
|
*
|
|
|
|
* @param Throwable $exception Exception to be categorized.
|
|
|
|
*
|
2024-06-11 22:30:04 +02:00
|
|
|
* @return MDErrorCategory
|
2020-07-20 11:17:25 +02:00
|
|
|
*/
|
2024-06-11 22:30:04 +02:00
|
|
|
public static function categorizeError(Throwable $exception):MDErrorCategory {
|
2020-07-20 11:17:25 +02:00
|
|
|
|
|
|
|
if ($exception instanceof MDpageParameterMissingException
|
|
|
|
|| $exception instanceof MDpageParameterNotNumericException
|
|
|
|
|| $exception instanceof MDpageParameterNotFromListException
|
|
|
|
|| $exception instanceof MDmainEntityNotExistentException
|
|
|
|
|| $exception instanceof MDmainEntityNotPublicException
|
2020-07-20 13:47:53 +02:00
|
|
|
|| $exception instanceof MDwriteAccessDeniedException
|
|
|
|
|| $exception instanceof MDNoUpdateVarSetException
|
2020-07-20 11:17:25 +02:00
|
|
|
|| $exception instanceof MDDBConnectionImpossible
|
2020-07-20 13:47:53 +02:00
|
|
|
|| $exception instanceof MDPageNotInAggregatedException
|
|
|
|
|| $exception instanceof MDWrongFileType
|
|
|
|
|| $exception instanceof MDTooManyFilesUploadException
|
2020-08-06 11:34:33 +02:00
|
|
|
|| $exception instanceof MDgenericInvalidInputsException
|
2020-08-06 10:33:20 +02:00
|
|
|
|| $exception instanceof MDExpectedException
|
2020-07-20 11:17:25 +02:00
|
|
|
|| $exception instanceof MDMysqliExpectedError
|
2020-09-21 10:42:26 +02:00
|
|
|
|| $exception instanceof MDcouldNotSetPublic
|
2023-05-24 22:03:12 +02:00
|
|
|
|| $exception instanceof MDInvalidNodaLink
|
2020-07-20 11:17:25 +02:00
|
|
|
) {
|
2024-06-11 22:30:04 +02:00
|
|
|
return MDErrorCategory::known;
|
2020-07-20 11:17:25 +02:00
|
|
|
}
|
|
|
|
|
2024-06-11 22:30:04 +02:00
|
|
|
return MDErrorCategory::unknown;
|
2020-07-20 11:17:25 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Function human_filesize translates byte-level filesizes to human readable ones.
|
|
|
|
* Thanks to Jeffrey Sambells http://jeffreysambells.com/2012/10/25/human-readable-filesize-php
|
|
|
|
*
|
2022-04-03 14:43:52 +02:00
|
|
|
* @param integer $bytes A file size, e.g. returned from filesize().
|
2020-07-20 11:17:25 +02:00
|
|
|
* @param integer $decimals Number of decimal digits to allow.
|
|
|
|
*
|
|
|
|
* @return string
|
|
|
|
*/
|
2022-04-03 14:43:52 +02:00
|
|
|
public function human_filesize(int $bytes, int $decimals = 2):string {
|
2020-07-20 11:17:25 +02:00
|
|
|
|
2021-10-01 16:25:27 +02:00
|
|
|
$size = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
2022-04-03 14:43:52 +02:00
|
|
|
$factor = \floor((strlen((string)$bytes) - 1) / 3);
|
2020-10-23 15:07:06 +02:00
|
|
|
return \sprintf("%.{$decimals}f", $bytes / \pow(1024, $factor)) . $size[$factor];
|
2020-07-20 11:17:25 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Function for formatting a section in an error mail.
|
|
|
|
*
|
|
|
|
* @param string $headline Headline of the section.
|
|
|
|
* @param array<mixed> $toFormat Associative array containing information to
|
|
|
|
* be formatted.
|
|
|
|
* @param integer $level Indentation level. Defaults to 0.
|
|
|
|
*
|
|
|
|
* @return string
|
|
|
|
*/
|
2021-05-14 00:08:11 +02:00
|
|
|
private function _formulateDebugMailSection(string $headline, array $toFormat, int $level = 0):string {
|
2020-07-20 11:17:25 +02:00
|
|
|
|
|
|
|
$msg = "";
|
2020-08-27 16:15:54 +02:00
|
|
|
if ($headline !== "") {
|
2020-07-20 11:17:25 +02:00
|
|
|
if ($level === 0) {
|
|
|
|
$msg .= PHP_EOL . "## " . $headline;
|
|
|
|
$msg .= PHP_EOL . "----------------------" . PHP_EOL . PHP_EOL;
|
|
|
|
}
|
2021-02-06 20:19:55 +01:00
|
|
|
else {
|
|
|
|
$msg .= "$headline" . PHP_EOL;
|
|
|
|
}
|
2020-07-20 11:17:25 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
$longestKey = 0;
|
|
|
|
foreach ($toFormat as $key => $value) {
|
2021-02-06 20:19:55 +01:00
|
|
|
if (\strlen((string)$key) + (4 * $level) > $longestKey) {
|
|
|
|
$longestKey = \strlen((string)$key) + (4 * $level);
|
|
|
|
}
|
2020-07-20 11:17:25 +02:00
|
|
|
}
|
|
|
|
foreach ($toFormat as $key => $value) {
|
2020-10-23 15:07:06 +02:00
|
|
|
if (\is_array($value)) {
|
2020-07-20 11:17:25 +02:00
|
|
|
$msg .= $this->_formulateDebugMailSection((string)$key, $value, $level + 1);
|
|
|
|
}
|
2020-10-23 15:07:06 +02:00
|
|
|
else if (\is_object($value)) {
|
|
|
|
$msg .= \sprintf("%-{$longestKey}s :: %s", $key, \var_export($value, true)) . PHP_EOL;
|
2020-07-20 11:17:25 +02:00
|
|
|
}
|
|
|
|
else {
|
2020-10-23 15:07:06 +02:00
|
|
|
$msg .= \sprintf("%-{$longestKey}s :: %s", $key, (string)$value) . PHP_EOL;
|
2020-07-20 11:17:25 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-15 21:06:32 +02:00
|
|
|
return $msg . PHP_EOL;
|
2020-07-20 11:17:25 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Gets additional debugging information, e.g. RAM usage.
|
|
|
|
*
|
2020-07-26 05:41:06 +02:00
|
|
|
* @return array<string|array<string>>
|
2020-07-20 11:17:25 +02:00
|
|
|
*/
|
2021-05-14 00:08:11 +02:00
|
|
|
public function getAdditionalDebuggingInfo():array {
|
2020-07-20 11:17:25 +02:00
|
|
|
|
2022-09-15 21:06:32 +02:00
|
|
|
return [
|
2022-04-03 14:43:52 +02:00
|
|
|
"Current memory usage" => $this->human_filesize(\memory_get_usage()),
|
|
|
|
"Peak memory usage" => $this->human_filesize(\memory_get_peak_usage()),
|
2020-10-23 15:07:06 +02:00
|
|
|
"Included / required files" => \get_included_files(),
|
2022-04-03 14:43:52 +02:00
|
|
|
# "Allowed memory usage" => $this->human_filesize((int)ini_get("memory_limit")),
|
2020-07-20 11:17:25 +02:00
|
|
|
];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This function sends a mail to the specified address, containing relevant debug information.
|
|
|
|
* .
|
|
|
|
*
|
|
|
|
* @param string $to Recipient mail address.
|
|
|
|
* @param string $subject Optional subject of the notification. Defaults to "".
|
|
|
|
* @param string $description Optional text of the notification. Defaults to : "".
|
|
|
|
* @param array<mixed> $contextInfo Context information that will be displayed in a
|
|
|
|
* key-value format at the start of the mail.
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
2021-05-14 00:08:11 +02:00
|
|
|
public function sendDebugMail(string $to, string $subject = "", string $description = "", array $contextInfo = []):void {
|
2020-07-20 11:17:25 +02:00
|
|
|
|
|
|
|
$subject = "[{$this->_context}]{$subject}[Controlled error notification]";
|
|
|
|
|
|
|
|
$msg = "
|
|
|
|
# Automated error message
|
|
|
|
=========================
|
|
|
|
";
|
|
|
|
|
2020-08-27 16:15:54 +02:00
|
|
|
if ($contextInfo !== []) {
|
2020-07-20 11:17:25 +02:00
|
|
|
$msg .= $this->_formulateDebugMailSection("Context information", $contextInfo);
|
|
|
|
}
|
|
|
|
|
2021-07-24 23:16:59 +02:00
|
|
|
if ($description !== "") {
|
2020-07-20 11:17:25 +02:00
|
|
|
$msg .= "## Description" . PHP_EOL;
|
|
|
|
$msg .= "--------------" . PHP_EOL . PHP_EOL;
|
|
|
|
$msg .= "$description" . PHP_EOL;
|
|
|
|
}
|
|
|
|
|
2020-10-23 15:07:06 +02:00
|
|
|
$msg .= $this->_formulateDebugMailSection("Debug backtrace", \debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT));
|
2020-07-20 11:17:25 +02:00
|
|
|
|
2020-07-21 15:51:07 +02:00
|
|
|
if (PHP_SAPI === 'cli') {
|
|
|
|
echo $msg;
|
|
|
|
exit;
|
|
|
|
}
|
|
|
|
|
2020-07-20 11:17:25 +02:00
|
|
|
// Append the contents of $_SERVER
|
|
|
|
if (!empty($_SERVER)) {
|
|
|
|
$msg .= $this->_formulateDebugMailSection("The current content of \$_SERVER", $_SERVER);
|
|
|
|
}
|
|
|
|
|
2020-07-20 14:19:12 +02:00
|
|
|
// Append the contents of $_SERVER
|
|
|
|
if (!empty($_GET)) {
|
|
|
|
$msg .= $this->_formulateDebugMailSection("The current content of \$_GET", $_GET);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Append the contents of $_SERVER
|
|
|
|
if (!empty($_POST)) {
|
|
|
|
$msg .= $this->_formulateDebugMailSection("The current content of \$_POST", $_POST);
|
|
|
|
}
|
|
|
|
|
2020-07-20 11:17:25 +02:00
|
|
|
// Append the contents of $_SESSION
|
2021-01-30 21:01:05 +01:00
|
|
|
if (session_status() === PHP_SESSION_ACTIVE) {
|
2020-07-20 11:17:25 +02:00
|
|
|
$msg .= $this->_formulateDebugMailSection("The current content of \$_SESSION", $_SESSION);
|
|
|
|
}
|
2021-02-06 20:19:55 +01:00
|
|
|
else {
|
|
|
|
$msg .= "No active session";
|
|
|
|
}
|
2020-07-20 11:17:25 +02:00
|
|
|
|
|
|
|
$msg .= $this->_formulateDebugMailSection("Additional debugging information",
|
|
|
|
$this->getAdditionalDebuggingInfo());
|
|
|
|
|
|
|
|
$msg .= PHP_EOL . PHP_EOL;
|
|
|
|
|
2020-10-21 12:06:42 +02:00
|
|
|
$msg = MDMailerHelper::pgp_encrypt($to, $msg);
|
2020-07-20 11:17:25 +02:00
|
|
|
|
2020-10-21 12:06:42 +02:00
|
|
|
$mail = MDMailerHelper::setup_PHPMailer();
|
|
|
|
$mail->setFrom($this->_contact_mail_addr, $this->_contact_mail_addr);
|
|
|
|
$mail->addAddress($to);
|
|
|
|
$mail->addReplyTo(MD_CONF_EMAIL::SMTP_REPLY_TO_ERROR);
|
|
|
|
$mail->isHTML(false);
|
|
|
|
$mail->Subject = $subject;
|
|
|
|
$mail->Body = "$msg";
|
2020-07-20 11:17:25 +02:00
|
|
|
|
2020-10-21 12:06:42 +02:00
|
|
|
$mail->send();
|
2020-07-20 11:17:25 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Wrapper around MDErrorReporter::sendDebugMail that can handle a throwable.
|
|
|
|
*
|
|
|
|
* @param Throwable $exception Exception to report on.
|
|
|
|
* @param string $recipient Recipient mail address.
|
|
|
|
* @param string $addDesc Additional descriptive information.
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
2021-05-14 00:08:11 +02:00
|
|
|
public function sendErrorReport(Throwable $exception, string $recipient, string $addDesc = ""):void {
|
2020-07-20 11:17:25 +02:00
|
|
|
|
2021-09-15 10:46:26 +02:00
|
|
|
// First, write the error to the general error log. This should happen first,
|
|
|
|
// so that it will also work if there is a problem retrieving GPG keys.
|
|
|
|
|
|
|
|
\error_log((string)$exception);
|
|
|
|
|
|
|
|
// Second, a mail is generated and sent.
|
|
|
|
|
2020-10-23 15:07:06 +02:00
|
|
|
$subject = \get_class($exception) . ": " . $exception->getCode();
|
2020-07-20 11:17:25 +02:00
|
|
|
|
|
|
|
$description = $addDesc;
|
|
|
|
|
|
|
|
$additionalContextInfo = [
|
|
|
|
"Message" => $exception->getMessage(),
|
|
|
|
"File" => $exception->getFile(),
|
|
|
|
"Line" => $exception->getLine()
|
|
|
|
];
|
|
|
|
|
|
|
|
$this->sendDebugMail($recipient,
|
|
|
|
$subject,
|
|
|
|
$description,
|
|
|
|
$additionalContextInfo
|
|
|
|
);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Constructor.
|
|
|
|
*
|
|
|
|
* @param string $toolName Name of the current tool.
|
|
|
|
* @param string $contact_mail_addr Mail address for mail's from header.
|
|
|
|
*
|
|
|
|
* @return void
|
|
|
|
*/
|
2020-07-21 12:14:46 +02:00
|
|
|
public function __construct(string $toolName = "museum-digital (unspecified)", string $contact_mail_addr = "bugs-md@museum-digital.de") {
|
2020-07-20 11:17:25 +02:00
|
|
|
|
|
|
|
$this->_context = $toolName;
|
|
|
|
$this->_contact_mail_addr = $contact_mail_addr;
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|