Passed
Push — develop ( dadf5b...439163 )
by Nikolay
04:25
created

ModelsBase   F

Complexity

Total Complexity 156

Size/Duplication

Total Lines 761
Duplicated Lines 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
wmc 156
eloc 490
dl 0
loc 761
rs 2
c 4
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A makeCacheKey() 0 4 1
A t() 0 3 1
A beforeValidationOnCreate() 0 7 3
F getRepresent() 0 235 63
A beforeDelete() 0 3 1
A processSettingsChanges() 0 20 5
B clearCache() 0 31 6
A initialize() 0 27 1
A sendChangesToBackend() 0 23 3
F getWebInterfaceLink() 0 122 33
A getIdentityFieldName() 0 5 1
B onValidationFails() 0 31 9
F checkRelationsSatisfaction() 0 81 20
B addExtensionModulesRelations() 0 26 7
A trimName() 0 10 2

How to fix   Complexity   

Complex Class

Complex classes like ModelsBase often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ModelsBase, and based on these observations, apply Extract Interface, too.

1
<?php
2
/*
3
 * MikoPBX - free phone system for small business
4
 * Copyright (C) 2017-2020 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\Common\Models;
21
22
use MikoPBX\Common\Providers\BeanstalkConnectionModelsProvider;
23
use MikoPBX\Common\Providers\CDRDatabaseProvider;
24
use MikoPBX\Common\Providers\ManagedCacheProvider;
25
use MikoPBX\Common\Providers\ModelsCacheProvider;
26
use MikoPBX\Common\Providers\ModelsMetadataProvider;
27
use MikoPBX\Common\Providers\TranslationProvider;
28
use MikoPBX\Core\Providers\EventsLogDatabaseProvider;
29
use MikoPBX\Core\System\Util;
30
use MikoPBX\Modules\PbxExtensionUtils;
31
use Phalcon\Db\Adapter\AdapterInterface;
32
use Phalcon\Di;
33
use Phalcon\Events\Event;
34
use Phalcon\Events\Manager;
35
use Phalcon\Messages\Message;
36
use Phalcon\Messages\MessageInterface;
37
use Phalcon\Mvc\Model;
38
use Phalcon\Mvc\Model\Relation;
39
use Phalcon\Mvc\Model\Resultset;
40
use Phalcon\Mvc\Model\Resultset\Simple;
41
use Phalcon\Mvc\Model\ResultsetInterface;
42
use Phalcon\Text;
43
use Phalcon\Url;
44
45
/**
46
 * Class ModelsBase
47
 *
48
 * @method static mixed findFirstById(array|string|int $parameters = null)
49
 * @method static mixed findFirstByKey(string|null $parameters)
50
 * @method static mixed findFirstByUniqid(array|string|int $parameters = null)
51
 * @method static mixed findFirst(array|string|int $parameters = null)
52
 * @method static ResultsetInterface find(array|string|int $parameters = null)
53
 * @method static mixed count(array $parameters = null)
54
 * @method  bool create()
55
 * @method  bool delete()
56
 * @method  bool save()
57
 * @method  bool update()
58
 * @method  array|MessageInterface[] getMessages(mixed $filter = null)
59
 * @method static AdapterInterface getReadConnection()
60
 * @method  Simple|false getRelated(string $alias, $arguments = null)
61
 *
62
 * @property \Phalcon\Mvc\Model\Manager _modelsManager
63
 * @property \Phalcon\Di                di
64
 *
65
 * @package MikoPBX\Common\Models
66
 */
67
class ModelsBase extends Model
68
{
69
    /**
70
     * All models with lover than this version in module.json won't be attached as children
71
     */
72
    public const MIN_MODULE_MODEL_VER = '2020.2.468';
73
74
    public function initialize(): void
75
    {
76
        self::setup(['orm.events' => true]);
77
        $this->keepSnapshots(true);
78
        $this->addExtensionModulesRelations();
79
80
        $eventsManager = new Manager();
81
82
        $eventsManager->attach(
83
            'model:afterSave',
84
            function (Event $event, $record){
85
                $record->processSettingsChanges($event->getType());
86
                self::clearCache(get_class($record));
87
                return true;
88
            }
89
        );
90
91
        $eventsManager->attach(
92
            'model:afterDelete',
93
            function (Event $event, $record){
94
                $record->processSettingsChanges($event->getType());
95
                self::clearCache(get_class($record));
96
                return true;
97
            }
98
        );
99
100
        $this->setEventsManager($eventsManager);
101
102
    }
103
104
    /**
105
     * Attaches model's relationships from modules models classes
106
     */
107
    private function addExtensionModulesRelations()
108
    {
109
        $modules = PbxExtensionModules::getEnabledModulesArray();
110
        foreach ($modules as $module) {
111
            $moduleDir = PbxExtensionUtils::getModuleDir($module['uniqid']);
112
113
            $moduleJson = "{$moduleDir}/module.json";
114
            if ( ! file_exists($moduleJson)) {
115
                continue;
116
            }
117
            $jsonString            = file_get_contents($moduleJson);
118
            $jsonModuleDescription = json_decode($jsonString, true);
119
            $minPBXVersion         = $jsonModuleDescription['min_pbx_version'] ?? '1.0.0';
120
            if (version_compare($minPBXVersion, self::MIN_MODULE_MODEL_VER, '<')) {
121
                continue;
122
            }
123
124
            $moduleModelsDir = "{$moduleDir}/Models";
125
            $results         = glob($moduleModelsDir . '/*.php', GLOB_NOSORT);
126
            foreach ($results as $file) {
127
                $className        = pathinfo($file)['filename'];
128
                $moduleModelClass = "\\Modules\\{$module['uniqid']}\\Models\\{$className}";
129
130
                if (class_exists($moduleModelClass)
131
                    && method_exists($moduleModelClass, 'getDynamicRelations')) {
132
                    $moduleModelClass::getDynamicRelations($this);
133
                }
134
            }
135
        }
136
    }
137
138
139
    /**
140
     * Обработчик ошибок валидации, обычно сюда попадаем если неправильно
141
     * сохраняются или удаляютмя модели или неправильно настроены зависимости между ними.
142
     * Эта функция формирует список ссылок на объект который мы пытаемся удалить
143
     *
144
     */
145
    public function onValidationFails(): void
146
    {
147
        $errorMessages = $this->getMessages();
148
        foreach ($errorMessages as $errorMessage) {
149
            if ($errorMessage->getType() === 'ConstraintViolation') {
150
                $arrMessageParts = explode('Common\\Models\\', $errorMessage->getMessage());
151
                if (count($arrMessageParts) === 2) {
152
                    $relatedModel = $arrMessageParts[1];
153
                } else {
154
                    $relatedModel = $errorMessage->getMessage();
155
                }
156
                $relatedRecords  = $this->getRelated($relatedModel);
157
                $newErrorMessage = $this->t('ConstraintViolation');
158
                $newErrorMessage .= "<ul class='list'>";
159
                if ($relatedRecords === false) {
160
                    throw new Model\Exception('Error on models relationship ' . $errorMessage);
161
                }
162
                if ($relatedRecords instanceof Resultset) {
163
                    foreach ($relatedRecords as $item) {
164
                        if ($item instanceof ModelsBase) {
165
                            $newErrorMessage .= '<li>' . $item->getRepresent(true) . '</li>';
166
                        }
167
                    }
168
                } elseif ($relatedRecords instanceof ModelsBase) {
169
                    $newErrorMessage .= '<li>' . $relatedRecords->getRepresent(true) . '</li>';
170
                } else {
171
                    $newErrorMessage .= '<li>Unknown object</li>';
172
                }
173
                $newErrorMessage .= '</ul>';
174
                $errorMessage->setMessage($newErrorMessage);
175
                break;
176
            }
177
        }
178
    }
179
180
    /**
181
     * Функция для доступа к массиву переводов из моделей, используется для
182
     * сообщений на понятном пользователю языке
183
     *
184
     * @param       $message
185
     * @param array $parameters
186
     *
187
     * @return mixed
188
     */
189
    public function t($message, $parameters = [])
190
    {
191
        return $this->getDI()->getShared(TranslationProvider::SERVICE_NAME)->t($message, $parameters);
192
    }
193
194
    /**
195
     * Returns a model's element representation
196
     *
197
     * @param bool $needLink add link to element
198
     *
199
     * @return string
200
     */
201
    public function getRepresent($needLink = false): string
202
    {
203
        switch (static::class) {
204
            case AsteriskManagerUsers::class:
205
                $name = '<i class="asterisk icon"></i> ';
206
                if (empty($this->id)) {
207
                    $name .= $this->t('mo_NewElementAsteriskManagerUsers');
208
                } else {
209
                    $name .= $this->t('repAsteriskManagerUsers', ['represent' => $this->username]);
210
                }
211
                break;
212
            case CallQueueMembers::class:
213
                $name = $this->Extensions->getRepresent();
214
                break;
215
            case CallQueues::class:
216
                $name = '<i class="users icon"></i> ';
217
                if (empty($this->id)) {
218
                    $name .= $this->t('mo_NewElementCallQueues');
219
                } else {
220
                    $name .= $this->t('mo_CallQueueShort4Dropdown') . ': ' . $this->name;
221
                }
222
                break;
223
            case ConferenceRooms::class:
224
                $name = '<i class="phone volume icon"></i> ';
225
                if (empty($this->id)) {
226
                    $name .= $this->t('mo_NewElementConferenceRooms');
227
                } else {
228
                    $name .= $this->t('mo_ConferenceRoomsShort4Dropdown') . ': ' . $this->name;
229
                }
230
                break;
231
            case CustomFiles::class:
232
                $name = "<i class='file icon'></i> {$this->filepath}";
233
                break;
234
            case DialplanApplications::class:
235
                $name = '<i class="php icon"></i> ';
236
                if (empty($this->id)) {
237
                    $name .= $this->t('mo_NewElementDialplanApplications');
238
                } else {
239
                    $name .= $this->t('mo_ApplicationShort4Dropdown') . ': ' . $this->name;
240
                }
241
                break;
242
            case ExtensionForwardingRights::class:
243
                $name = $this->Extensions->getRepresent();
244
                break;
245
            case Extensions::class:
246
                // Для внутреннего номера бывают разные представления
247
                if ($this->type === Extensions::TYPE_EXTERNAL) {
248
                    $icon = '<i class="icons"><i class="user outline icon"></i><i class="top right corner alternate mobile icon"></i></i>';
249
                } else {
250
                    $icon = '<i class="icons"><i class="user outline icon"></i></i>';
251
                }
252
                if (empty($this->id)) {
253
                    $name = "{$icon} {$this->t('mo_NewElementExtensions')}";
254
                } elseif ($this->userid > 0) {
255
                    $name = '';
256
                    if (isset($this->Users->username)) {
257
                        $name = $this->trimName($this->Users->username);
258
                    }
259
                    $name = "{$icon} {$name} <{$this->number}>";
260
                } else {
261
                    switch (strtoupper($this->type)) {
262
                        case Extensions::TYPE_CONFERENCE:
263
                            $name = $this->ConferenceRooms->getRepresent();
264
                            break;
265
                        case Extensions::TYPE_QUEUE:
266
                            $name = $this->CallQueues->getRepresent();
267
                            break;
268
                        case Extensions::TYPE_DIALPLAN_APPLICATION:
269
                            $name = $this->DialplanApplications->getRepresent();
270
                            break;
271
                        case Extensions::TYPE_IVR_MENU:
272
                            $name = $this->IvrMenu->getRepresent();
273
                            break;
274
                        case Extensions::TYPE_MODULES:
275
                            $name = '<i class="puzzle piece icon"></i> '
276
                                . $this->t('mo_ModuleShort4Dropdown')
277
                                . ': '
278
                                . $this->callerid;
279
                            break;
280
                        case Extensions::TYPE_EXTERNAL:
281
                        case Extensions::TYPE_SIP:
282
                        default:
283
                            $name = "{$this->callerid} <{$this->number}>";
284
                    }
285
                }
286
                break;
287
            case ExternalPhones::class:
288
                $name = $this->Extensions->getRepresent();
289
                break;
290
            case Fail2BanRules::class:
291
                $name = '';
292
                break;
293
            case FirewallRules::class:
294
                $name = $this->category;
295
                break;
296
            case Iax::class:
297
                $name = '<i class="server icon"></i> ';
298
                if (empty($this->id)) {
299
                    $name .= $this->t('mo_NewElementIax');
300
                } elseif ($this->disabled === '1') {
301
                    $name .= "{$this->description} ({$this->t( 'mo_Disabled' )})";
302
                } else {
303
                    $name .= $this->description;
304
                }
305
                break;
306
            case IvrMenu::class:
307
                $name = '<i class="sitemap icon"></i> ';
308
                if (empty($this->id)) {
309
                    $name .= $this->t('mo_NewElementIvrMenu');
310
                } else {
311
                    $name .= $this->t('mo_IVRMenuShort4Dropdown') . ': ' . $this->name;
312
                }
313
                break;
314
            case IvrMenuActions::class:
315
                $name = $this->IvrMenu->name;
316
                break;
317
            case Codecs::class:
318
                $name = $this->name;
319
                break;
320
            case IncomingRoutingTable::class:
321
                $name = '<i class="map signs icon"></i> ';
322
                if (empty($this->id)) {
323
                    $name .= $this->t('mo_NewElementIncomingRoutingTable');
324
                } elseif ( ! empty($this->note)) {
325
                    $name .= $this->t('repIncomingRoutingTable', ['represent' => $this->note]);
326
                } else {
327
                    $name .= $this->t('repIncomingRoutingTableNumber', ['represent' => $this->id]);
328
                }
329
                break;
330
            case LanInterfaces::class:
331
                // LanInterfaces
332
                $name = $this->name;
333
                break;
334
            case NetworkFilters::class:
335
                $name = '<i class="globe icon"></i> ';
336
                if (empty($this->id)) {
337
                    $name .= $this->t('mo_NewElementNetworkFilters');
338
                } else {
339
                    $name .= $this->description . '('
340
                        . $this->t('fw_PermitNetwork') . ': ' . $this->permit
341
                        . ')';
342
                }
343
                break;
344
            case OutgoingRoutingTable::class:
345
                $name = '<i class="random icon"></i> ';
346
                if (empty($this->id)) {
347
                    $name .= $this->t('mo_NewElementOutgoingRoutingTable');
348
                } elseif ( ! empty($this->rulename)) {
349
                    $name .= $this->t('repOutgoingRoutingTable', ['represent' => $this->rulename]);
350
                } else {
351
                    $name .= $this->t('repOutgoingRoutingTableNumber', ['represent' => $this->id]);
352
                }
353
                break;
354
            case OutWorkTimes::class:
355
                $name = '<i class="time icon"></i> ';
356
                if (empty($this->id)) {
357
                    $name .= $this->t('mo_NewElementOutWorkTimes');
358
                } elseif ( ! empty($this->description)) {
359
                    $name .= $this->t('repOutWorkTimes', ['represent' => $this->description]);
360
                } else {
361
                    $represent = '';
362
                    if (is_numeric($this->date_from)) {
363
                        $represent .= date("d/m/Y", $this->date_from) . '-';
364
                    }
365
                    if (is_numeric($this->date_to)) {
366
                        $represent .= date("d/m/Y", $this->date_to) . ' ';
367
                    }
368
                    if (isset($this->weekday_from)) {
369
                        $represent .= $this->t(date('D', strtotime("Sunday +{$this->weekday_from} days"))) . '-';
370
                    }
371
                    if (isset($this->weekday_to)) {
372
                        $represent .= $this->t(date('D', strtotime("Sunday +{$this->weekday_to} days"))) . ' ';
373
                    }
374
                    if (isset($this->time_from) || isset($this->time_to)) {
375
                        $represent .= $this->time_from . ' - ' . $this->time_to . ' ';
376
                    }
377
                    $name .= $this->t('repOutWorkTimes', ['represent' => $represent]);
378
                }
379
                break;
380
            case Providers::class:
381
                if ($this->type === "IAX") {
382
                    $name = $this->Iax->getRepresent();
383
                } else {
384
                    $name = $this->Sip->getRepresent();
385
                }
386
                break;
387
            case PbxSettings::class:
388
                $name = $this->key;
389
                break;
390
            case PbxExtensionModules::class:
391
                $name = '<i class="puzzle piece icon"></i> '
392
                    . $this->t('mo_ModuleShort4Dropdown') . ': '
393
                    . $this->name;
394
                break;
395
            case Sip::class:
396
                $name = '<i class="server icon"></i> ';
397
                if (empty($this->id)) {
398
                    $name .= $this->t('mo_NewElementSip');
399
                } elseif ($this->disabled === '1') {
400
                    $name .= "{$this->description} ({$this->t( 'mo_Disabled' )})";
401
                } else {
402
                    $name .= $this->description;
403
                }
404
405
                break;
406
            case Users::class:
407
                $name = '<i class="user outline icon"></i> ' . $this->username;
408
                break;
409
            case SoundFiles::class:
410
                $name = '<i class="file audio outline icon"></i> ';
411
                if (empty($this->id)) {
412
                    $name .= $this->t('mo_NewElementSoundFiles');
413
                } else {
414
                    $name .= $this->t('repSoundFiles', ['represent' => $this->name]);
415
                }
416
417
                break;
418
            default:
419
                $name = 'Unknown';
420
        }
421
422
        if ($needLink) {
423
            $link     = $this->getWebInterfaceLink();
424
            $category = explode('\\', static::class)[3];
425
            $result   = $this->t(
426
                'rep' . $category,
427
                [
428
                    'represent' => "<a href='{$link}'>{$name}</a>",
429
                ]
430
            );
431
        } else {
432
            $result = $name;
433
        }
434
435
        return $result;
436
    }
437
438
    /**
439
     * Укорачивает длинные имена
440
     *
441
     * @param $s
442
     *
443
     * @return string
444
     */
445
    private function trimName($s): string
446
    {
447
        $max_length = 64;
448
449
        if (strlen($s) > $max_length) {
450
            $offset = ($max_length - 3) - strlen($s);
451
            $s      = substr($s, 0, strrpos($s, ' ', $offset)) . '...';
452
        }
453
454
        return $s;
455
    }
456
457
    /**
458
     * Return link on database record in web interface
459
     *
460
     * @return string
461
     */
462
    public function getWebInterfaceLink(): string
463
    {
464
        $url = new Url();
465
466
        $baseUri = $this->di->getShared('config')->path('adminApplication.baseUri');
467
        $link    = '#';
468
        switch (static::class) {
469
            case AsteriskManagerUsers::class:
470
                $link = $url->get('asterisk-managers/modify/' . $this->id, null, null, $baseUri);
471
                break;
472
            case CallQueueMembers::class:
473
                $link = $url->get('call-queues/modify/' . $this->CallQueues->uniqid, null, null, $baseUri);
474
                break;
475
            case CallQueues::class:
476
                $link = $url->get('call-queues/modify/' . $this->uniqid, null, null, $baseUri);
477
                break;
478
            case ConferenceRooms::class:
479
                $link = $url->get('conference-rooms/modify/' . $this->uniqid, null, null, $baseUri);
480
                break;
481
            case CustomFiles::class:
482
                $link = $url->get('custom-files/modify/' . $this->id, null, null, $baseUri);
483
                break;
484
            case DialplanApplications::class:
485
                $link = $url->get('dialplan-applications/modify/' . $this->uniqid, null, null, $baseUri);
486
                break;
487
            case ExtensionForwardingRights::class:
488
489
                break;
490
            case Extensions::class:
491
                $link = $url->get('extensions/modify/' . $this->id, null, null, $baseUri);
492
                break;
493
            case ExternalPhones::class:
494
                if ($this->Extensions->is_general_user_number === "1") {
495
                    $parameters    = [
496
                        'conditions' => 'is_general_user_number="1" AND type="' . Extensions::TYPE_EXTERNAL . '" AND userid=:userid:',
497
                        'bind'       => [
498
                            'userid' => $this->Extensions->userid,
499
                        ],
500
                    ];
501
                    $needExtension = Extensions::findFirst($parameters);
502
                    $link          = $url->get('extensions/modify/' . $needExtension->id, null, null, $baseUri);
503
                } else {
504
                    $link = '#';//TODO сделать если будет раздел для допоплнинельных номеров пользователя
505
                }
506
                break;
507
            case Fail2BanRules::class:
508
                $link = '#';//TODO сделать если будет fail2ban
509
                break;
510
            case FirewallRules::class:
511
                $link = $url->get('firewall/modify/' . $this->NetworkFilters->id, null, null, $baseUri);
512
                break;
513
            case Iax::class:
514
                $link = $url->get('providers/modifyiax/' . $this->Providers->id, null, null, $baseUri);
515
                break;
516
            case IvrMenu::class:
517
                $link = $url->get('ivr-menu/modify/' . $this->uniqid, null, null, $baseUri);
518
                break;
519
            case IvrMenuActions::class:
520
                $link = $url->get('ivr-menu/modify/' . $this->IvrMenu->uniqid, null, null, $baseUri);
521
                break;
522
            case Codecs::class:
523
                break;
524
            case IncomingRoutingTable::class:
525
                $link = $url->get('incoming-routes/modify/' . $this->id, null, null, $baseUri);
526
                break;
527
            case LanInterfaces::class:
528
                $link = $url->get('network/index/', null, null, $baseUri);
529
                break;
530
            case NetworkFilters::class:
531
                $link = $url->get('firewall/modify/' . $this->id, null, null, $baseUri);
532
                break;
533
            case OutgoingRoutingTable::class:
534
                $link = $url->get('outbound-routes/modify/' . $this->id, null, null, $baseUri);
535
                break;
536
            case OutWorkTimes::class:
537
                $link = $url->get('out-off-work-time/modify/' . $this->id, null, null, $baseUri);
538
                break;
539
            case Providers::class:
540
                if ($this->type === "IAX") {
541
                    $link = $url->get('providers/modifyiax/' . $this->uniqid, null, null, $baseUri);
542
                } else {
543
                    $link = $url->get('providers/modifysip/' . $this->uniqid, null, null, $baseUri);
544
                }
545
                break;
546
            case PbxSettings::class:
547
                $link = $url->get('general-settings/index');
548
                break;
549
            case PbxExtensionModules::class:
550
                $link = $url->get(Text::uncamelize($this->uniqid), null, null, $baseUri);
551
                break;
552
            case Sip::class:
553
                if ($this->Extensions) { // Это внутренний номер?
554
                    if ($this->Extensions->is_general_user_number === "1") {
555
                        $link = $url->get('extensions/modify/' . $this->Extensions->id, null, null, $baseUri);
556
                    } else {
557
                        $link = '#';//TODO сделать если будет раздел для допоплнинельных номеров пользователя
558
                    }
559
                } elseif ($this->Providers) { // Это провайдер
560
                    $link = $url->get('providers/modifysip/' . $this->Providers->id, null, null, $baseUri);
561
                }
562
                break;
563
            case Users::class:
564
                $parameters    = [
565
                    'conditions' => 'userid=:userid:',
566
                    'bind'       => [
567
                        'userid' => $this->id,
568
                    ],
569
                ];
570
                $needExtension = Extensions::findFirst($parameters);
571
                if ($needExtension === null) {
572
                    $link = $url->get('extensions/index/', null, null, $baseUri);
573
                } else {
574
                    $link = $url->get('extensions/modify/' . $needExtension->id, null, null, $baseUri);
575
                }
576
                break;
577
            case SoundFiles::class:
578
                $link = $url->get('sound-files/modify/' . $this->id, null, null, $baseUri);
579
                break;
580
            default:
581
        }
582
583
        return $link;
584
    }
585
586
    /**
587
     * Fill default values from annotations
588
     */
589
    public function beforeValidationOnCreate(): void
590
    {
591
        $metaData      = $this->di->get(ModelsMetadataProvider::SERVICE_NAME);
592
        $defaultValues = $metaData->getDefaultValues($this);
593
        foreach ($defaultValues as $field => $value) {
594
            if ( ! isset($this->{$field})) {
595
                $this->{$field} = $value;
596
            }
597
        }
598
    }
599
600
    /**
601
     * Функция позволяет вывести список зависимостей с сылками,
602
     * которые мешают удалению текущей сущности
603
     *
604
     * @return bool
605
     */
606
    public function beforeDelete(): bool
607
    {
608
        return $this->checkRelationsSatisfaction($this, $this);
609
    }
610
611
    /**
612
     *  Check whether this object has unsatisfied relations or not
613
     *
614
     * @param $theFirstDeleteRecord
615
     * @param $currentDeleteRecord
616
     *
617
     * @return bool
618
     */
619
    private function checkRelationsSatisfaction($theFirstDeleteRecord, $currentDeleteRecord): bool
620
    {
621
        $result = true;
622
        $relations
623
                = $currentDeleteRecord->_modelsManager->getRelations(get_class($currentDeleteRecord));
624
        foreach ($relations as $relation) {
625
            $foreignKey = $relation->getOption('foreignKey');
626
            if ( ! array_key_exists('action', $foreignKey)) {
627
                continue;
628
            }
629
            // Check if there are some record which restrict delete current record
630
            $relatedModel             = $relation->getReferencedModel();
631
            $mappedFields             = $relation->getFields();
632
            $mappedFields             = is_array($mappedFields)
633
                ? $mappedFields : [$mappedFields];
634
            $referencedFields         = $relation->getReferencedFields();
635
            $referencedFields         = is_array($referencedFields)
636
                ? $referencedFields : [$referencedFields];
637
            $parameters['conditions'] = '';
638
            $parameters['bind']       = [];
639
            foreach ($referencedFields as $index => $referencedField) {
640
                $parameters['conditions']             .= $index > 0
641
                    ? ' OR ' : '';
642
                $parameters['conditions']             .= $referencedField
643
                    . '= :field'
644
                    . $index . ':';
645
                $bindField
646
                                                      = $mappedFields[$index];
647
                $parameters['bind']['field' . $index] = $currentDeleteRecord->$bindField;
648
            }
649
            $relatedRecords = $relatedModel::find($parameters);
650
            switch ($foreignKey['action']) {
651
                case Relation::ACTION_RESTRICT: // Restrict deletion and add message about unsatisfied undeleted links
652
                    foreach ($relatedRecords as $relatedRecord) {
653
                        if (serialize($relatedRecord) === serialize($theFirstDeleteRecord)
654
                            || serialize($relatedRecord) === serialize($currentDeleteRecord)
655
                        ) {
656
                            continue; // It is checked object
657
                        }
658
                        $message = new Message(
659
                            $theFirstDeleteRecord->t(
660
                                'mo_BeforeDeleteFirst',
661
                                [
662
                                    'represent' => $relatedRecord->getRepresent(true),
663
                                ]
664
                            )
665
                        );
666
                        $theFirstDeleteRecord->appendMessage($message);
667
                        $result = false;
668
                    }
669
                    break;
670
                case Relation::ACTION_CASCADE: // Удалим все зависимые записи
671
                    foreach ($relatedRecords as $relatedRecord) {
672
                        if (serialize($relatedRecord) === serialize($theFirstDeleteRecord)
673
                            || serialize($relatedRecord) === serialize($currentDeleteRecord)
674
                        ) {
675
                            continue; // It is checked object
676
                        }
677
                        $result = $result && $relatedRecord->checkRelationsSatisfaction(
678
                                $theFirstDeleteRecord,
679
                                $relatedRecord
680
                            );
681
                        if ($result) {
682
                            $result = $relatedRecord->delete();
683
                        }
684
                        if ($result === false) {
685
                            $messages = $relatedRecord->getMessages();
686
                            foreach ($messages as $message) {
687
                                $theFirstDeleteRecord->appendMessage($message);
688
                            }
689
                        }
690
                    }
691
                    break;
692
                case Relation::NO_ACTION: // Clear all refs
693
                    break;
694
                default:
695
                    break;
696
            }
697
        }
698
699
        return $result;
700
    }
701
702
703
704
    /**
705
     * Sends changed fields and settings to backend worker WorkerModelsEvents
706
     *
707
     * @param $action string may be model:afterSave or model:afterDelete
708
     */
709
    private function processSettingsChanges(string $action): void
710
    {
711
        $doNotTrackThisDB = [
712
            CDRDatabaseProvider::SERVICE_NAME,
713
            EventsLogDatabaseProvider::SERVICE_NAME
714
        ];
715
716
        if (in_array($this->getReadConnectionService(), $doNotTrackThisDB)) {
717
            return;
718
        }
719
720
        if ( ! $this->hasSnapshotData()) {
721
            return;
722
        } // nothing changed
723
724
        $changedFields = $this->getUpdatedFields();
725
        if (empty($changedFields) && $action === 'model:afterSave') {
726
            return;
727
        }
728
        $this->sendChangesToBackend($action, $changedFields);
729
    }
730
731
    /**
732
     * Sends changed fields and class to WorkerModelsEvents
733
     *
734
     * @param $action
735
     * @param $changedFields
736
     */
737
    private function sendChangesToBackend($action, $changedFields): void
738
    {
739
        // Add changed fields set to Beanstalkd queue
740
        $queue = $this->di->getShared(BeanstalkConnectionModelsProvider::SERVICE_NAME);
741
        if ($queue === null) {
742
            return;
743
        }
744
        if ($this instanceof PbxSettings) {
745
            $idProperty = 'key';
746
        } else {
747
            $idProperty = 'id';
748
        }
749
        $id      = $this->$idProperty;
750
        $jobData = json_encode(
751
            [
752
                'source'        => BeanstalkConnectionModelsProvider::SOURCE_MODELS_CHANGED,
753
                'model'         => get_class($this),
754
                'recordId'      => $id,
755
                'action'        => $action,
756
                'changedFields' => $changedFields,
757
            ]
758
        );
759
        $queue->publish($jobData);
760
    }
761
762
    /**
763
     * Invalidates cached records contains model name in cache key value
764
     *
765
     * @param      $calledClass string full model class name
766
     *
767
     */
768
    public static function clearCache(string $calledClass): void
769
    {
770
        Util::sysLogMsg(__METHOD__, "Clear acahe for ".$calledClass, LOG_DEBUG);
771
772
        $di = Di::getDefault();
773
        if ($di === null) {
774
            return;
775
        }
776
777
        if ($di->has(ManagedCacheProvider::SERVICE_NAME)) {
778
            $managedCache = $di->get(ManagedCacheProvider::SERVICE_NAME);
779
            $category     = explode('\\', $calledClass)[3];
780
            $keys         = $managedCache->getKeys($category);
781
            $prefix      = $managedCache->getPrefix();
782
            // Delete all items from the managed cache
783
            foreach ($keys as $key)
784
            {
785
                $cacheKey = str_ireplace($prefix, '', $key);
786
                $managedCache->delete($cacheKey);
787
            }
788
        }
789
        if ($di->has(ModelsCacheProvider::SERVICE_NAME)) {
790
            $modelsCache = $di->getShared(ModelsCacheProvider::SERVICE_NAME);
791
            $category    = explode('\\', $calledClass)[3];
792
            $keys        = $modelsCache->getKeys($category);
793
            $prefix      = $modelsCache->getPrefix();
794
            // Delete all items from the models cache
795
            foreach ($keys as $key)
796
            {
797
                $cacheKey = str_ireplace($prefix, '', $key);
798
                $modelsCache->delete($cacheKey);
799
            }
800
        }
801
802
    }
803
804
    /**
805
     * Returns Identity field name for current model
806
     *
807
     * @return string
808
     */
809
    public function getIdentityFieldName(): string
810
    {
811
        $metaData = $this->di->get(ModelsMetadataProvider::SERVICE_NAME);
812
813
        return $metaData->getIdentityField($this);
814
    }
815
816
    /**
817
     * Returns Cache key for the models cache service
818
     *
819
     * @param string $modelClass
820
     * @param string $keyName
821
     *
822
     * @return string
823
     */
824
    public static function makeCacheKey(string $modelClass, string $keyName):string
825
    {
826
        $category    = explode('\\', $modelClass)[3];
827
        return "{$category}:{$keyName}";
828
    }
829
}