Passed
Pull Request — master (#16)
by Nikolay
13:10 queued 02:12
created

ModelsBase   F

Complexity

Total Complexity 153

Size/Duplication

Total Lines 810
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 153
eloc 465
c 3
b 0
f 0
dl 0
loc 810
rs 2

15 Methods

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