GeneralSettingsController::modifyAction()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 21
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 10
dl 0
loc 21
rs 9.9332
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
3
/*
4
 * MikoPBX - free phone system for small business
5
 * Copyright © 2017-2025 Alexey Portnov and Nikolay Beketov
6
 *
7
 * This program is free software: you can redistribute it and/or modify
8
 * it under the terms of the GNU General Public License as published by
9
 * the Free Software Foundation; either version 3 of the License, or
10
 * (at your option) any later version.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU General Public License along with this program.
18
 * If not, see <https://www.gnu.org/licenses/>.
19
 */
20
21
namespace MikoPBX\AdminCabinet\Controllers;
22
23
use MikoPBX\AdminCabinet\Forms\GeneralSettingsEditForm;
24
use MikoPBX\Common\Models\Codecs;
25
use MikoPBX\Common\Models\Extensions;
26
use MikoPBX\Common\Models\PbxSettings;
27
use MikoPBX\Core\System\Util;
28
29
/**
30
 * Class GeneralSettingsController
31
 *
32
 * This class handles general settings for the application.
33
 */
34
class GeneralSettingsController extends BaseController
35
{
36
    /**
37
     * Builds the general settings form.
38
     *
39
     * This action is responsible for preparing the data required to populate the general settings form.
40
     * It retrieves the audio and video codecs from the database, sorts them by priority, and assigns them to the view.
41
     * It also retrieves all PBX settings and creates an instance of the GeneralSettingsEditForm.
42
     *
43
     * @return void
44
     */
45
    public function modifyAction(): void
46
    {
47
        // Retrieve and sort audio codecs from database
48
        $audioCodecs = Codecs::find(['conditions' => 'type="audio"'])->toArray();
49
        usort($audioCodecs, [__CLASS__, 'sortArrayByPriority']);
50
        $this->view->audioCodecs = $audioCodecs;
51
52
        // Retrieve and sort video codecs from database
53
        $videoCodecs = Codecs::find(['conditions' => 'type="video"'])->toArray();
54
        usort($videoCodecs, [__CLASS__, 'sortArrayByPriority']);
55
        $this->view->videoCodecs = $videoCodecs;
56
57
        // Fetch all PBX settings
58
        $pbxSettings = PbxSettings::getAllPbxSettings(false);
59
60
        // Fetch and assign simple passwords for the view
61
        $this->view->simplePasswords = $this->getSimplePasswords($pbxSettings);
62
63
        // Create an instance of the GeneralSettingsEditForm and assign it to the view
64
        $this->view->form = new GeneralSettingsEditForm(null, $pbxSettings);
65
        $this->view->submitMode = null;
66
    }
67
68
    /**
69
     * Retrieves a list of simple passwords from the given data.
70
     *
71
     * This function checks if the SSHPassword and WebAdminPassword in the data array are simple passwords.
72
     * It also checks if the CloudInstanceId matches any of these passwords.
73
     * If a simple password or a matching CloudInstanceId is found, the corresponding password key is added to the list.
74
     *
75
     * @param array $data The data array containing the passwords and CloudInstanceId.
76
     * @return array The list of password keys that failed the simple password check.
77
     */
78
    private function getSimplePasswords(array $data): array
79
    {
80
        // Initialize an array to keep track of passwords that fail the check
81
        $passwordCheckFail = [];
82
83
        $cloudInstanceId = $data[PbxSettings::CLOUD_INSTANCE_ID] ?? '';
84
        $checkPasswordFields = [PbxSettings::SSH_PASSWORD, PbxSettings::WEB_ADMIN_PASSWORD];
85
86
        // If SSH is disabled, remove the SSH_PASSWORD key
87
        if ($data[PbxSettings::SSH_DISABLE_SSH_PASSWORD] === 'on') {
88
            unset($checkPasswordFields[PbxSettings::SSH_PASSWORD]);
89
        }
90
91
        // Loop through and check passwords
92
        foreach ($checkPasswordFields as $value) {
93
            if (!isset($data[$value]) || $data[$value] === GeneralSettingsEditForm::HIDDEN_PASSWORD) {
94
                continue;
95
            }
96
            if ($cloudInstanceId === $data[$value] || Util::isSimplePassword($data[$value])) {
97
                $passwordCheckFail[] = $value;
98
            }
99
        }
100
        return $passwordCheckFail;
101
    }
102
103
    /**
104
     * Saves the general settings form data.
105
     *
106
     */
107
    public function saveAction(): void
108
    {
109
        if (!$this->request->isPost()) {
110
            return;
111
        }
112
        $postData = self::sanitizeData($this->request->getPost(), $this->filter);
113
        
114
        // No need to sanitize these fields
115
        $postData[PbxSettings::WEB_ADMIN_PASSWORD] = $this->request->getPost(PbxSettings::WEB_ADMIN_PASSWORD);
116
        $postData[PbxSettings::SSH_PASSWORD] = $this->request->getPost(PbxSettings::SSH_PASSWORD);
117
118
        $passwordCheckFail = $this->getSimplePasswords($postData);
119
        if (!empty($passwordCheckFail)) {
120
            foreach ($passwordCheckFail as $settingsKey) {
121
                $this->flash->error($this->translation->_('gs_SetPasswordError', ['password' => $postData[$settingsKey]]));
122
            }
123
            $this->view->success = false;
124
            $this->view->passwordCheckFail = $passwordCheckFail;
125
            return;
126
        }
127
128
        $this->db->begin();
129
130
        list($result, $messages) = $this->updatePBXSettings($postData);
131
        if (!$result) {
132
            $this->view->success = false;
133
            $this->view->messages = $messages;
134
            $this->db->rollback();
135
            return;
136
        }
137
138
        list($result, $messages) = $this->updateCodecs($postData['codecs']);
139
        if (!$result) {
140
            $this->view->success = false;
141
            $this->view->messages = $messages;
142
            $this->db->rollback();
143
            return;
144
        }
145
146
        list($result, $messages) = $this->createParkingExtensions(
147
            $postData[PbxSettings::PBX_CALL_PARKING_START_SLOT],
148
            $postData[PbxSettings::PBX_CALL_PARKING_END_SLOT],
149
            $postData[PbxSettings::PBX_CALL_PARKING_EXT],
150
        );
151
152
        if (!$result) {
153
            $this->view->success = false;
154
            $this->view->messages = $messages;
155
            $this->db->rollback();
156
            return;
157
        }
158
159
        $this->flash->success($this->translation->_('ms_SuccessfulSaved'));
160
        $this->view->success = true;
161
        $this->db->commit();
162
    }
163
164
165
    /**
166
     * Create or update parking extensions by ensuring only necessary slots are modified.
167
     * This method first fetches the existing parking slots and determines which slots
168
     * need to be created or deleted based on the desired range and reserved slot.
169
     * It aims to minimize database operations by only deleting slots that are no longer needed
170
     * and creating new slots that do not exist yet, preserving all others.
171
     *
172
     * @param int $startSlot The starting number of the parking slot range.
173
     * @param int $endSlot The ending number of the parking slot range.
174
     * @param int $reservedSlot The number of the reserved slot to be included outside the range.
175
     *
176
     * @return array Returns an array with two elements:
177
     *               - bool: true if the operation was successful without any errors, false otherwise.
178
     *               - array: an array of messages, primarily errors encountered during operations.
179
     */
180
    private function createParkingExtensions(int $startSlot, int $endSlot, int $reservedSlot): array
181
    {
182
        $messages = [];
183
184
        // Retrieve all current parking slots.
185
        $currentSlots = Extensions::findByType(Extensions::TYPE_PARKING);
186
187
        // Create an array of desired numbers.
188
        $desiredNumbers = range($startSlot, $endSlot);
189
        $desiredNumbers[] = $reservedSlot;
190
191
        // Determine slots to delete.
192
        $currentNumbers = [];
193
        foreach ($currentSlots as $slot) {
194
            if (!in_array($slot->number, $desiredNumbers)) {
195
                if (!$slot->delete()) {
196
                    $messages['error'][] = $slot->getMessages();
197
                }
198
            } else {
199
                $currentNumbers[] = $slot->number;
200
            }
201
        }
202
203
        // Determine slots to create.
204
        $numbersToCreate = array_diff($desiredNumbers, $currentNumbers);
205
        foreach ($numbersToCreate as $number) {
206
            $record = new Extensions();
207
            $record->type = Extensions::TYPE_PARKING;
208
            $record->number = $number;
209
            $record->show_in_phonebook = '0';
210
            if (!$record->create()) {
211
                $messages['error'][] = $record->getMessages();
212
            }
213
        }
214
215
        // Determine the overall result.
216
        $result = count($messages['error'] ?? []) === 0;
217
        return [$result, $messages];
218
    }
219
220
    /**
221
     * Update codecs based on the provided data.
222
     *
223
     * @param string $codecsData The JSON-encoded data for codecs.
224
     *
225
     * @return array
226
     */
227
    private function updateCodecs(string $codecsData): array
228
    {
229
        $messages = [];
230
        $codecs = json_decode($codecsData, true);
231
        foreach ($codecs as $codec) {
232
            $record = Codecs::findFirstById($codec['codecId']);
233
            $newPriority = $codec['priority'];
234
            $newStatus = $codec['disabled'] === true ? '1' : '0';
235
            if (intval($record->priority) !== intval($newPriority) || $record->disabled !== $newStatus) {
236
                $record->priority = $newPriority;
237
                $record->disabled = $newStatus;
238
                if (!$record->update()) {
239
                    $messages['error'][] = $record->getMessages();
240
                }
241
            }
242
        }
243
        $result = count($messages) === 0;
244
        return [$result, $messages];
245
    }
246
247
    /**
248
     * Update PBX settings based on the provided data.
249
     *
250
     * @param array $data The data containing PBX settings.
251
     *
252
     * @return array
253
     */
254
    private function updatePBXSettings(array $data): array
255
    {
256
        $messages = ['error' => []];
257
        $defaultPbxSettings = PbxSettings::getDefaultArrayValues();
258
259
        // Update PBX settings
260
        foreach ($defaultPbxSettings as $key => $defaultValue) {
261
            switch ($key) {
262
                case PbxSettings::SSH_ID_RSA_PUB:
263
                    continue 2;
264
                case PbxSettings::PBX_RECORD_CALLS:
265
                case PbxSettings::PBX_RECORD_CALLS_INNER:
266
                case PbxSettings::AJAM_ENABLED:
267
                case PbxSettings::AMI_ENABLED:
268
                case PbxSettings::RESTART_EVERY_NIGHT:
269
                case PbxSettings::REDIRECT_TO_HTTPS:
270
                case PbxSettings::PBX_SPLIT_AUDIO_THREAD:
271
                case PbxSettings::USE_WEB_RTC:
272
                case PbxSettings::SSH_DISABLE_SSH_PASSWORD:
273
                case PbxSettings::PBX_ALLOW_GUEST_CALLS:
274
                case PbxSettings::DISABLE_ALL_MODULES:
275
                case '***ALL CHECK BOXES ABOVE***':
276
                    $newValue = ($data[$key] === 'on') ? '1' : '0';
277
                    break;
278
                case PbxSettings::SSH_PASSWORD:
279
                    if ($data[$key] !== GeneralSettingsEditForm::HIDDEN_PASSWORD) {
280
                        // User changed SSH password
281
                        $newValue = $data[$key];
282
                        PbxSettings::setValueByKey(PbxSettings::SSH_PASSWORD_HASH_STRING, md5($newValue), $messages['error']);
283
                    } elseif(
284
                        $data[PbxSettings::WEB_ADMIN_PASSWORD] !== GeneralSettingsEditForm::HIDDEN_PASSWORD
285
                        && PbxSettings::getValueByKey(PbxSettings::SSH_PASSWORD) === $defaultPbxSettings[PbxSettings::SSH_PASSWORD]
286
                    ) {
287
                        // Пользователь изменил Web пароль И текущий SSH пароль равен дефолтному
288
                        $newValue = $data[PbxSettings::WEB_ADMIN_PASSWORD];
289
                        PbxSettings::setValueByKey(PbxSettings::SSH_PASSWORD_HASH_STRING, md5($newValue), $messages['error']);
290
                    } else {
291
                        // User did not change SSH password, continue processing other settings
292
                        continue 2; // Use continue 2 to skip to the next iteration of the outer loop
293
                    }
294
                    break;
295
                case PbxSettings::SEND_METRICS:
296
                    $newValue = ($data[$key] === 'on') ? '1' : '0';
297
                    $this->session->set(PbxSettings::SEND_METRICS, $newValue);
298
                    break;
299
                case PbxSettings::PBX_FEATURE_TRANSFER_DIGIT_TIMEOUT:
300
                    $newValue = ceil((int)$data[PbxSettings::PBX_FEATURE_DIGIT_TIMEOUT] / 1000);
301
                    break;
302
                case PbxSettings::SIP_AUTH_PREFIX:
303
                    $newValue = trim($data[$key]);
304
                    break;
305
                case PbxSettings::WEB_ADMIN_PASSWORD:
306
                    if ($data[$key] !== GeneralSettingsEditForm::HIDDEN_PASSWORD) {
307
                        // User changed Web password
308
                        $newValue = $this->security->hash($data[$key]);
309
                    } elseif ($data[PbxSettings::SSH_PASSWORD] !== GeneralSettingsEditForm::HIDDEN_PASSWORD 
310
                        && PbxSettings::getValueByKey(PbxSettings::WEB_ADMIN_PASSWORD) === $defaultPbxSettings[PbxSettings::WEB_ADMIN_PASSWORD]) {
311
                        // User changed SSH password AND current Web password equals default
312
                        $newValue = $this->security->hash($data[PbxSettings::SSH_PASSWORD]);
313
                    } else {
314
                        // User did not change Web password, continue processing other settings
315
                        continue 2; // Use continue 2 to skip to the next iteration of the outer loop
316
                    }
317
                    break;
318
                default:
319
                    $newValue = $data[$key];
320
            }
321
322
            if (array_key_exists($key, $data)) {
323
                PbxSettings::setValueByKey($key, $newValue, $messages['error']);
324
            }
325
        }
326
327
        // Reset a cloud provision flag
328
        PbxSettings::setValueByKey(PbxSettings::CLOUD_PROVISIONING, '1', $messages['error']);
329
330
        $result = count($messages['error']) === 0;
331
        return [$result, $messages];
332
    }
333
}
334