Passed
Push — develop ( 19a517...90693d )
by Nikolay
05:54 queued 10s
created

ModelsBase   F

Complexity

Total Complexity 157

Size/Duplication

Total Lines 832
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 157
eloc 473
dl 0
loc 832
rs 2
c 3
b 0
f 0

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