Passed
Push — master ( f59959...13fcd7 )
by Shahrad
12:47
created

CrashPad::enableCrashHandler()   A

Complexity

Conditions 3
Paths 1

Size

Total Lines 18
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 9
c 0
b 0
f 0
dl 0
loc 18
rs 9.9666
cc 3
nc 1
nop 0
1
<?php
2
declare(strict_types=1);
3
4
namespace TelegramBot;
5
6
use Exception;
7
use Symfony\Component\Dotenv\Dotenv;
8
use Throwable;
9
10
/**
11
 * CrashPad class
12
 *
13
 * @link    https://github.com/telegram-bot-php/core
14
 * @author  Shahrad Elahi (https://github.com/shahradelahi)
15
 * @license https://github.com/telegram-bot-php/core/blob/master/LICENSE (MIT License)
16
 */
17
class CrashPad
18
{
19
20
    /**
21
     * Enable the crash handler
22
     *
23
     * @return void
24
     */
25
    public static function enableCrashHandler(): void
26
    {
27
        $handler = function (Throwable $throwable) {
28
            if (Telegram::getAdminId() !== -1) {
29
                $input = getenv('TG_CURRENT_UPDATE') ?? Telegram::getInput();
30
                CrashPad::sendCrash(Telegram::getAdminId(), $throwable, $input);
31
            }
32
33
            if (!defined('DEBUG_MODE')) {
34
                throw new \RuntimeException(
35
                    'Something went wrong, Unfortunately, we can not handle this error.', 0, $throwable
36
                );
37
            }
38
39
            CrashPad::print($throwable);
40
        };
41
42
        set_exception_handler($handler);
43
    }
44
45
    /**
46
     * Debug mode. Catch the crash reports.
47
     *
48
     * @param int $admin_id (optional) The admin chat id.
49
     * @return void
50
     */
51
    public static function setDebugMode(int $admin_id = -1): void
52
    {
53
        error_reporting(E_ALL);
54
        ini_set('display_errors', '1');
55
56
        defined('DEBUG_MODE') or define('DEBUG_MODE', true);
57
        if ($admin_id !== -1) {
58
            Telegram::setAdminId($admin_id);
59
        }
60
61
        self::enableCrashHandler();
62
    }
63
64
    /**
65
     * Send crash message and log
66
     *
67
     * @param int $chat_id The chat id of the group to send the message to.
68
     * @param Exception|Throwable $exception The exception to report.
69
     * @param string|null $update (Optional) The update that caused the exception.
70
     *
71
     * @retrun bool
72
     */
73
    public static function sendCrash(int $chat_id, Exception|Throwable $exception, string|null $update = null): bool
74
    {
75
        if ($chat_id === -1) {
76
            throw new \RuntimeException(sprintf(
77
                'The given `chat_id` is not valid. given: %s',
78
                $chat_id
79
            ));
80
        }
81
82
        if (!Telegram::validateToken($_ENV['TELEGRAM_BOT_TOKEN'] ?? '')) {
83
            (new Dotenv())->load(Telegram::getEnvFilePath());
84
            Telegram::setToken($_ENV['TELEGRAM_BOT_TOKEN']);
85
        }
86
87
        if (($token = self::loadToken()) === null) {
88
            throw new \RuntimeException(
89
                'The token is not set. Please set the token using `Telegram::setToken()` method.'
90
            );
91
        }
92
93
        $text = Request::sendMessage([
94
            'bot_token' => $token,
95
            'chat_id' => $chat_id,
96
            'parse_mode' => 'HTML',
97
            'text' => sprintf(
98
                "<b>Message</b>: %s\n\n<b>File</b>: %s(%d)\n\n<b>Trace</b>: \n%s",
99
                $exception->getMessage(),
100
                $exception->getFile(),
101
                $exception->getLine(),
102
                $exception->getTraceAsString()
103
            ),
104
        ]);
105
106
        $document = Request::sendDocument([
107
            'bot_token' => $token,
108
            'chat_id' => $chat_id,
109
            'document' => self::createCrashFile(sprintf(
110
                "Message: %s\n\nFile: %s(%d)\n\nTrace: \n%s\n\nUpdate: \n%s",
111
                $exception->getMessage(),
112
                $exception->getFile(),
113
                $exception->getLine(),
114
                $exception->getTraceAsString(),
115
                $update ?? 'Did not receive update.'
116
            )),
117
        ]);
118
119
        return $text->isOk() && $document->isOk();
120
    }
121
122
    /**
123
     * Create a log file for the error.
124
     *
125
     * @param string $content The content of the log file.
126
     * @retrun string The path of the log file.
127
     */
128
    private static function createCrashFile(string $content): string
129
    {
130
        $base_path = $_SERVER['DOCUMENT_ROOT'] . '.telegram-bot/';
131
        if (!file_exists($base_path)) {
132
            mkdir($base_path, 0777, true);
133
        }
134
135
        file_put_contents(($file = $base_path . uniqid('error_') . '.log'), $content);
136
        return $file;
137
    }
138
139
    /**
140
     * Report the error to the developers from the Telegram Bot API.
141
     *
142
     * @param Exception|Throwable $exception The exception to report.
143
     * @retrun void
144
     */
145
    public static function print(Exception|Throwable $exception): void
146
    {
147
        TelegramLog::error(($message = sprintf(
148
            "%s(%d): %s\n%s",
149
            $exception->getFile(),
150
            $exception->getLine(),
151
            $exception->getMessage(),
152
            $exception->getTraceAsString()
153
        )));
154
        echo '<b>TelegramError:</b> ' . $message;
155
    }
156
157
    /**
158
     * Clear the crash logs.
159
     *
160
     * @return void
161
     */
162
    public static function clearCrashLogs(): void
163
    {
164
        $base_path = $_SERVER['DOCUMENT_ROOT'] . '.telegram-bot/';
165
        if (!file_exists($base_path)) {
166
            return;
167
        }
168
169
        $files = glob($base_path . '*');
170
        foreach ($files as $file) {
171
            if (is_file($file)) {
172
                unlink($file);
173
            }
174
        }
175
    }
176
177
    /**
178
     * Check is there any loaded token or any token in the environment file.
179
     *
180
     * @return string|null
181
     */
182
    private static function loadToken(): string|null
183
    {
184
        if (($token = Telegram::getApiToken()) !== false) {
185
            return $token;
186
        }
187
188
        if (file_exists(Telegram::getEnvFilePath())) {
189
            (new Dotenv())->load(Telegram::getEnvFilePath());
190
            return $_ENV['TELEGRAM_BOT_TOKEN'] ?? null;
191
        }
192
193
        return null;
194
    }
195
196
}