Passed
Push — develop ( 0ab024...c90030 )
by Nikolay
07:31 queued 02:05
created

BaseController::initialize()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 7
dl 0
loc 11
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 0
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\AdminCabinet\Controllers;
21
22
use MikoPBX\Common\Providers\PBXConfModulesProvider;
23
use MikoPBX\Common\Providers\SentryErrorHandlerProvider;
24
use MikoPBX\Modules\Config\WebUIConfigInterface;
25
use MikoPBX\Common\Models\{PbxExtensionModules, PbxSettings};
26
use Phalcon\Http\ResponseInterface;
27
use Phalcon\Mvc\{Controller, Dispatcher, View};
28
use Phalcon\Tag;
29
use Phalcon\Text;
30
31
/**
32
 * @property \Phalcon\Session\Manager session
33
 * @property \MikoPBX\Common\Providers\TranslationProvider translation
34
 * @property string language
35
 * @property bool showModuleStatusToggle if false it hides status toggle on current UI page
36
 * @property \MikoPBX\AdminCabinet\Library\Elements elements
37
 * @property \Phalcon\Flash\Session flash
38
 * @property \Phalcon\Tag tag
39
 * @property \Phalcon\Config\Adapter\Json config
40
 * @property \Phalcon\Logger loggerAuth
41
 */
42
class BaseController extends Controller
43
{
44
    protected string $actionName;
45
    protected string $controllerName;
46
    protected string $controllerClass;
47
    protected string $controllerNameUnCamelized;
48
    protected bool $isExternalModuleController;
49
50
    /**
51
     * Initializes base class
52
     */
53
    public function initialize(): void
54
    {
55
        $this->actionName = $this->dispatcher->getActionName();
56
        /** @scrutinizer ignore-call */
57
        $this->controllerClass = $this->dispatcher->getHandlerClass();
58
        $this->controllerName = Text::camelize($this->dispatcher->getControllerName(), '_');
59
        $this->controllerNameUnCamelized = Text::uncamelize($this->controllerName, '-');
60
        $this->isExternalModuleController = str_starts_with($this->dispatcher->getNamespaceName(), 'Modules');
61
62
        if ($this->request->isAjax() === false) {
63
            $this->prepareView();
64
        }
65
    }
66
67
    /**
68
     * Prepares the view by setting necessary variables and configurations.
69
     *
70
     * @return void
71
     */
72
    protected function prepareView(): void
73
    {
74
        // Set the default timezone based on PBX settings
75
        date_default_timezone_set(PbxSettings::getValueByKey('PBXTimezone'));
76
77
        // Set PBXLicense view variable if session exists
78
        if ($this->session->has(SessionController::SESSION_ID)) {
79
            $this->view->PBXLicense = PbxSettings::getValueByKey('PBXLicense');
80
        } else {
81
            $this->view->PBXLicense = '';
82
        }
83
84
        // Set URLs for Wiki and Support based on language
85
        $this->view->urlToWiki = "https://wiki.mikopbx.com/{$this->controllerNameUnCamelized}";
86
        if ($this->language === 'ru') {
87
            $this->view->urlToSupport = 'https://www.mikopbx.ru/support/?fromPBX=true';
88
        } else {
89
            $this->view->urlToSupport = 'https://www.mikopbx.com/support/?fromPBX=true';
90
        }
91
92
        // Set the title based on the current action
93
        $title = 'MikoPBX';
94
        switch ($this->actionName) {
95
            case'index':
96
            case'delete':
97
            case'save':
98
            case'modify':
99
            case'*** WITHOUT ACTION ***':
100
                $title .= '|' . $this->translation->_("Breadcrumb{$this->controllerName}");
101
                break;
102
            default:
103
                $title .= '|' . $this->translation->_("Breadcrumb{$this->controllerName}{$this->actionName}");
104
        }
105
        Tag::setTitle($title);
106
107
        // Set other view variables
108
        $this->view->t = $this->translation;
109
        $this->view->debugMode = $this->config->path('adminApplication.debugMode');
110
        $this->view->urlToLogo = $this->url->get('assets/img/logo-mikopbx.svg');
111
        $this->view->urlToController = $this->url->get($this->controllerNameUnCamelized);
112
        $this->view->represent = '';
113
        $this->view->WebAdminLanguage = $this->session->get(LanguageController::WEB_ADMIN_LANGUAGE)??PbxSettings::getValueByKey('WebAdminLanguage');
114
        $this->view->AvailableLanguages = json_encode(LanguageController::getAvailableWebAdminLanguages());
115
        $this->view->submitMode = $this->session->get('SubmitMode') ?? 'SaveSettings';
116
        $this->view->lastSentryEventId = $this->setLastSentryEventId();
117
        $this->view->PBXVersion = PbxSettings::getValueByKey('PBXVersion');
118
        $this->view->MetaTegHeadDescription = $this->translation->_('MetaTegHeadDescription');
119
        $this->view->isExternalModuleController = $this->isExternalModuleController;
120
121
        if ($this->controllerClass!==SessionController::class){
122
            $this->view->setTemplateAfter('main');
123
        }
124
125
        $this->view->globalModuleUniqueId = '';
126
        $this->view->actionName = $this->actionName;
127
        $this->view->controllerName = $this->controllerName;
128
        $this->view->controllerClass = $this->controllerClass;
129
130
        // Add module variables into view if it is an external module controller
131
        if ($this->isExternalModuleController) {
132
            /** @var PbxExtensionModules $module */
133
            $module = PbxExtensionModules::findFirstByUniqid($this->getModuleUniqueId());
134
            if ($module === null) {
135
                $module = new PbxExtensionModules();
136
                $module->disabled = '1';
137
                $module->name = 'Unknown module';
138
            }
139
            $this->view->module = $module->toArray();
140
            $this->view->globalModuleUniqueId = $module->uniqid;
141
        }
142
    }
143
144
    /**
145
     * Performs actions before executing the route.
146
     *
147
     * @return void
148
     */
149
    public function beforeExecuteRoute(Dispatcher $dispatcher): void
150
    {
151
        // Check if the request method is POST
152
        if ($this->request->isPost()) {
153
            // Retrieve the 'submitMode' data from the request
154
            $data = $this->request->getPost('submitMode');
155
            if (!empty($data)) {
156
                // Set the 'SubmitMode' session variable to the retrieved data
157
                $this->session->set('SubmitMode', $data);
158
            }
159
        }
160
161
        $this->actionName = $this->dispatcher->getActionName();
162
        $this->controllerName = Text::camelize($this->dispatcher->getControllerName(), '_');
163
        // Add module variables into view if it is an external module controller
164
        if (str_starts_with($this->dispatcher->getNamespaceName(), 'Modules')) {
165
            $this->view->pick("Modules/{$this->getModuleUniqueId()}/{$this->controllerName}/{$this->actionName}");
166
        } else  {
167
            $this->view->pick("{$this->controllerName}/{$this->actionName}");
168
        }
169
170
        PBXConfModulesProvider::hookModulesMethod(WebUIConfigInterface::ON_BEFORE_EXECUTE_ROUTE,[$dispatcher]);
171
    }
172
173
    /**
174
     * Performs actions after executing the route and returns the response.
175
     *
176
     * @return \Phalcon\Http\ResponseInterface
177
     */
178
    public function afterExecuteRoute(Dispatcher $dispatcher): ResponseInterface
179
    {
180
181
        if ($this->request->isAjax() === true) {
182
            $this->view->setRenderLevel(View::LEVEL_NO_RENDER);
183
            $this->response->setContentType('application/json', 'UTF-8');
184
            $data = $this->view->getParamsToView();
185
186
            /* Set global params if is not set in controller/action */
187
            if (isset($data['raw_response'])) {
188
                $result = $data['raw_response'];
189
            } else {
190
                $data['success'] = $data['success'] ?? true;
191
                $data['reload'] = $data['reload'] ?? false;
192
                $data['message'] = $data['message'] ?? $this->flash->getMessages();
193
194
                // Let's add information about the last error to display a dialog window for the user.
195
                $sentry =  $this->di->get(SentryErrorHandlerProvider::SERVICE_NAME,['admin-cabinet']);
196
                if ($sentry){
197
                    $data['lastSentryEventId'] = $sentry->getLastEventId();
198
                }
199
200
                $result = json_encode($data);
201
            }
202
            $this->response->setContent($result);
203
        }
204
205
        PBXConfModulesProvider::hookModulesMethod(WebUIConfigInterface::ON_AFTER_EXECUTE_ROUTE,[$dispatcher]);
206
207
        return $this->response->send();
208
    }
209
210
    /**
211
     * Forwards the request to a different controller and action based on the provided URI.
212
     *
213
     * @param string $uri The URI to forward to.
214
     * @return void
215
     */
216
    protected function forward(string $uri): void
217
    {
218
        $uriParts = explode('/', $uri);
219
        if ($this->isExternalModuleController and count($uriParts)>2){
220
            $params = array_slice($uriParts, 3);
221
            $moduleUniqueID = $this->getModuleUniqueId();
222
            $this->dispatcher->forward(
223
                [
224
                    'namespace'=>"Modules\\{$moduleUniqueID}\\App\\Controllers",
225
                    'controller' => $uriParts[1],
226
                    'action' => $uriParts[2],
227
                    'params' => $params,
228
                ]
229
            );
230
        } else {
231
            $params = array_slice($uriParts, 2);
232
233
            $this->dispatcher->forward(
234
                [
235
                    'namespace'=>"MikoPBX\AdminCabinet\Controllers",
236
                    'controller' => $uriParts[0],
237
                    'action' => $uriParts[1],
238
                    'params' => $params,
239
                ]
240
            );
241
        }
242
    }
243
244
    /**
245
     * Sanitizes the caller ID by removing any characters that are not alphanumeric or spaces.
246
     *
247
     * @param string $callerId The caller ID to sanitize.
248
     * @return string The sanitized caller ID.
249
     */
250
    protected function sanitizeCallerId(string $callerId): string
251
    {
252
        return preg_replace('/[^a-zA-Zа-яА-Я0-9 ]/ui', '', $callerId);
253
    }
254
255
    /**
256
     * Sorts array by priority field
257
     *
258
     * @param $a
259
     * @param $b
260
     *
261
     * @return int|null
262
     */
263
    protected function sortArrayByPriority($a, $b): ?int
264
    {
265
        if (is_array($a)) {
266
            $a = (int)$a['priority'];
267
        } else {
268
            $a = (int)$a->priority;
269
        }
270
271
        if (is_array($b)) {
272
            $b = (int)$b['priority'];
273
        } else {
274
            $b = (int)$b->priority;
275
        }
276
277
        if ($a === $b) {
278
            return 0;
279
        } else {
280
            return ($a < $b) ? -1 : 1;
281
        }
282
    }
283
284
    /**
285
     * Sets the last Sentry event ID.
286
     *
287
     * @return \Sentry\EventId|null The last Sentry event ID, or null if metrics sending is disabled.
288
     */
289
    private function setLastSentryEventId(): ?\Sentry\EventId
290
    {
291
        $result = null;
292
        // Allow anonymous statistics collection for JS code
293
        $sentry =  $this->di->get(SentryErrorHandlerProvider::SERVICE_NAME);
294
        if ($sentry){
295
            $result = $sentry->getLastEventId();
296
        }
297
        return $result;
298
    }
299
300
    /**
301
     *  Returns the unique ID of the module parsing controller namespace;
302
     * @return string
303
     */
304
    private function getModuleUniqueId():string
305
    {
306
        // Split the namespace into an array using the backslash as a separator
307
        $parts = explode('\\', get_class($this));
308
309
        // Get the second part of the namespace
310
        return $parts[1];
311
    }
312
313
    /**
314
     * Save an entity and handle success or error messages.
315
     *
316
     * @param mixed $entity The entity to be saved.
317
     * @return bool True if the entity was successfully saved, false otherwise.
318
     */
319
    protected function saveEntity($entity, string $reloadPath=''): bool
320
    {
321
        $success = $entity->save();
322
323
        if (!$success) {
324
            $errors = $entity->getMessages();
325
            $this->flash->error(implode('<br>', $errors));
326
        } elseif (!$this->request->isAjax()) {
327
            $this->flash->success($this->translation->_('ms_SuccessfulSaved'));
328
            if ($reloadPath!==''){
329
                $this->forward($reloadPath);
330
            }
331
        }
332
333
        if ($this->request->isAjax()) {
334
            $this->view->success = $success;
335
            if ($reloadPath!=='' && $success){
336
                $this->view->reload = str_replace('{id}', $entity->id, $reloadPath);
337
            }
338
        }
339
340
        return $success;
341
    }
342
343
344
    /**
345
     * Delete an entity and handle success or error messages.
346
     *
347
     * @param mixed $entity The entity to be deleted.
348
     * @return bool True if the entity was successfully deleted, false otherwise.
349
     */
350
    protected function deleteEntity($entity, string $reloadPath=''): bool
351
    {
352
        $success = $entity->delete();
353
354
        if (!$success) {
355
            $errors = $entity->getMessages();
356
            $this->flash->error(implode('<br>', $errors));
357
        } elseif (!$this->request->isAjax()) {
358
            // $this->flash->success($this->translation->_('ms_SuccessfulSaved'));
359
            if ($reloadPath!==''){
360
                $this->forward($reloadPath);
361
            }
362
        }
363
364
        if ($this->request->isAjax()) {
365
            $this->view->success = $success;
366
            if ($reloadPath!=='' && $success){
367
                $this->view->reload = $reloadPath;
368
            }
369
        }
370
371
        return $success;
372
    }
373
374
    /**
375
     * Creates a JPEG file from the provided image.
376
     *
377
     * @param string $base64_string The base64 encoded image string.
378
     * @param string $output_file The output file path to save the JPEG file.
379
     *
380
     * @return void
381
     */
382
    protected function base64ToJpegFile(string $base64_string, string $output_file): void
383
    {
384
        // Open the output file for writing
385
        $ifp = fopen($output_file, 'wb');
386
387
        if ($ifp === false) {
388
            return;
389
        }
390
        // Split the string on commas
391
        // $data[0] == "data:image/png;base64"
392
        // $data[1] == <actual base64 string>
393
        $data = explode(',', $base64_string);
394
395
        if (count($data) > 1) {
396
            // Write the base64 decoded data to the file
397
            fwrite($ifp, base64_decode($data[1]));
398
399
            // Close the file resource
400
            fclose($ifp);
401
        }
402
    }
403
}
404