Passed
Push — develop ( 31ebc6...f6a1e0 )
by Nikolay
04:27
created

Notifications::sendAdminNotification()   A

Complexity

Conditions 6
Paths 6

Size

Total Lines 34
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 18
c 0
b 0
f 0
dl 0
loc 34
rs 9.0444
cc 6
nc 6
nop 3
1
<?php
2
/*
3
 * MikoPBX - free phone system for small business
4
 * Copyright © 2017-2023 Alexey Portnov and Nikolay Beketov
5
 *
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation; either version 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License along with this program.
17
 * If not, see <https://www.gnu.org/licenses/>.
18
 */
19
20
namespace MikoPBX\Core\System;
21
22
use MikoPBX\Common\Models\PbxSettings;
23
use MikoPBX\Common\Providers\ManagedCacheProvider;
24
use Phalcon\Di;
25
use PHPMailer\PHPMailer\PHPMailer;
26
use Throwable;
27
28
/**
29
 * Class Notifications
30
 *
31
 * @package MikoPBX\Core\System
32
 */
33
class Notifications
34
{
35
    public const TYPE_PHP_MAILER = 'PHP_MAILER';
36
    private array $settings;
37
    private bool $enableNotifications;
38
    private string $fromAddres;
39
    private string $fromName;
40
41
    /**
42
     * Notifications constructor.
43
     */
44
    public function __construct()
45
    {
46
        $mikoPBXConfig = new MikoPBXConfig();
47
        $this->settings = $mikoPBXConfig->getGeneralSettings();
48
        $this->enableNotifications = $this->settings['MailEnableNotifications'] === '1';
49
50
        $mailSMTPSenderAddress = $this->settings['MailSMTPSenderAddress'] ?? '';
51
        if (!empty($mailSMTPSenderAddress)) {
52
            $this->fromAddres = $mailSMTPSenderAddress;
53
        } else {
54
            $this->fromAddres = $this->settings['MailSMTPUsername'];
55
        }
56
57
        if (empty($this->settings['MailSMTPFromUsername'])) {
58
            $this->fromName = 'MikoPBX Notification';
59
        } else {
60
            $this->fromName = $this->settings['MailSMTPFromUsername'];
61
        }
62
    }
63
64
    /**
65
     * Tests the connection to the msmtp mail server.
66
     * @return bool True if the connection is successful, false otherwise.
67
     */
68
    public static function testConnectionMSMTP(): bool
69
    {
70
        $path = Util::which('msmtp');
71
        $result = Processes::mwExec("$path --file=/etc/msmtp.conf -S --timeout 1", $out);
72
        return ($result === 0);
73
    }
74
75
    /**
76
     * Send an admin notification.
77
     *
78
     * @param string $subject The subject of the notification.
79
     * @param array $messages An array of messages to be included in the notification.
80
     * @param bool $urgent (optional) Set to true for urgent notifications to bypass caching.
81
     *
82
     * @return void
83
     */
84
    public static function sendAdminNotification(string $subject, array $messages, bool $urgent=false): void
85
    {
86
        // Prevent sending the same message twice.
87
        $di = Di::getDefault();
88
        $managedCache = $di->getShared(ManagedCacheProvider::SERVICE_NAME);
89
        $cacheKey = 'sendAdminNotification:' . md5($subject . implode('', $messages));
90
        $cacheTime = 3600 * 24; // 1 day
91
92
        // Check if the message is not urgent and has been sent recently from cache.
93
        if (!$urgent &&  $managedCache->has($cacheKey)) {
94
            return;
95
        }
96
97
        // Check if the notification system is available (e.g., PHP Mailer is configured and working).
98
        if (!Notifications::checkConnection(Notifications::TYPE_PHP_MAILER)) {
99
            return;
100
        }
101
102
        // Translate the subject and messages to the desired language.
103
        $subject = Util::translate($subject, false);
104
        $text = '';
105
        foreach ($messages as $message) {
106
            $text .= PHP_EOL . Util::translate($message, false);
107
        }
108
        $text = $text . PHP_EOL . PHP_EOL . Network::getInfoMessage();
109
110
        // Get the admin email address from PbxSettings.
111
        $adminMail = PbxSettings::getValueByKey('SystemNotificationsEmail');
112
        $notify = new Notifications();
113
        $result = $notify->sendMail($adminMail, $subject, trim($text));
114
115
        // If the notification was sent successfully, cache it to prevent duplicates.
116
        if ($result) {
117
            $managedCache->set($cacheKey, true, $cacheTime);
118
        }
119
120
    }
121
122
    /**
123
     * Sends a test email.
124
     * @return bool True if the test email is sent successfully, false otherwise.
125
     */
126
    public function sendTestMail(): bool
127
    {
128
        if (!self::checkConnection(self::TYPE_PHP_MAILER)) {
129
            return false;
130
        }
131
        $systemNotificationsEmail = $this->settings['SystemNotificationsEmail'];
132
        $result = $this->sendMail($systemNotificationsEmail, 'Test mail from MIKO PBX', '<b>Test message</b><hr>');
133
        return ($result === true);
134
    }
135
136
    /**
137
     * Checks the connection to the specified type of mail server.
138
     * @param string $type The type of mail server.
139
     * @return bool True if the connection is successful, false otherwise.
140
     */
141
    public static function checkConnection($type): bool
142
    {
143
        $timeoutPath = Util::which('timeout');
144
        $phpPath = Util::which('php');
145
        $result = Processes::mwExec("$timeoutPath 5 $phpPath -f /etc/rc/emailTestConnection.php " . $type);
146
        if ($result !== 0) {
147
            Util::sysLogMsg('PHPMailer', 'Error connect to SMTP server... (' . $type . ')', LOG_ERR);
148
        }
149
150
        return ($result === 0);
151
    }
152
153
    /**
154
     * Sends an email using PHPMailer.
155
     *
156
     * @param string|array $to The recipient(s) of the email.
157
     * @param string $subject The subject of the email.
158
     * @param string $message The body of the email.
159
     * @param string $filename The path to the file to be attached (optional).
160
     * @return bool True if the email is sent successfully, false otherwise.
161
     */
162
    public function sendMail($to, $subject, $message, string $filename = ''): bool
163
    {
164
        if (!$this->enableNotifications) {
165
            return false;
166
        }
167
        $messages = [];
168
        try {
169
            $mail = $this->getMailSender();
170
            $mail->setFrom($this->fromAddres, $this->fromName);
171
            $to = explode(',', $to);
0 ignored issues
show
Bug introduced by
It seems like $to can also be of type array; however, parameter $string of explode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

171
            $to = explode(',', /** @scrutinizer ignore-type */ $to);
Loading history...
172
            foreach ($to as $email) {
173
                $mail->addAddress($email);
174
            }
175
            if (file_exists($filename)) {
176
                $mail->addAttachment($filename);
177
            }
178
            $mail->isHTML(true);
179
            $mail->Subject = $subject;
180
            $mail->Body = $message;
181
182
            if (!self::checkConnection(self::TYPE_PHP_MAILER)) {
183
                return false;
184
            }
185
            if (!$mail->send()) {
186
                $messages[] = $mail->ErrorInfo;
187
            }
188
        } catch (Throwable $e) {
189
            $messages[] = $e->getMessage();
190
        }
191
        if (!empty($messages)) {
192
            Util::sysLogMsg('PHPMailer', implode(' ', $messages), LOG_ERR);
193
        }
194
        return true;
195
    }
196
197
    /**
198
     * Returns an initialized PHPMailer object.
199
     * @return PHPMailer
200
     */
201
    public function getMailSender(): PHPMailer
202
    {
203
        $mail = new PHPMailer();
204
        $mail->isSMTP();
205
        $mail->SMTPDebug = 0;
206
        $mail->Timeout = 5;
207
        $mail->Host = $this->settings['MailSMTPHost'];
208
        if ($this->settings["MailSMTPUseTLS"] === "1") {
209
            $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
210
            // $mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;
211
        } else {
212
            $mail->SMTPSecure = '';
213
        }
214
        if (empty($this->settings['MailSMTPUsername']) && empty($this->settings['MailSMTPPassword'])) {
215
            $mail->SMTPAuth = false;
216
        } else {
217
            $mail->SMTPAuth = true;
218
            $mail->Username = $this->settings['MailSMTPUsername'];
219
            $mail->Password = $this->settings['MailSMTPPassword'];
220
            if ($this->settings["MailSMTPCertCheck"] !== '1') {
221
                $mail->SMTPOptions = [
222
                    'ssl' => [
223
                        'verify_peer' => false,
224
                        'verify_peer_name' => false,
225
                        'allow_self_signed' => true,
226
                    ],
227
                ];
228
            }
229
        }
230
        $mail->Port = (integer)$this->settings['MailSMTPPort'];
231
        $mail->CharSet = 'UTF-8';
232
233
        return $mail;
234
    }
235
236
    /**
237
     * Tests the connection to the PHPMailer mail server.
238
     * @return bool True if the connection is successful, false otherwise.
239
     */
240
    public function testConnectionPHPMailer(): bool
241
    {
242
        $mail = $this->getMailSender();
243
        try {
244
            $result = $mail->smtpConnect();
245
        } catch (\Exception $e) {
246
            $result = false;
247
        }
248
        return $result;
249
    }
250
}