Completed
Pull Request — master (#320)
by
unknown
03:22
created

Bootstrap   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 448
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 16

Test Coverage

Coverage 0.92%

Importance

Changes 0
Metric Value
wmc 33
lcom 1
cbo 16
dl 0
loc 448
ccs 2
cts 216
cp 0.0092
rs 9.76
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
A bootstrap() 0 20 4
F initContainer() 0 114 11
A initTranslations() 0 10 2
A initAuthManager() 0 11 2
A initUrlRoutes() 0 17 2
A initMailServiceConfiguration() 0 12 1
A initAuthCollection() 0 6 2
A initConsoleCommands() 0 4 1
A initControllerNamespace() 0 5 1
B buildClassMap() 0 57 2
A getRoute() 0 9 3
B attachAuditLogging() 0 93 2
1
<?php
2
3
/*
4
 * This file is part of the 2amigos/yii2-usuario project.
5
 *
6
 * (c) 2amigOS! <http://2amigos.us/>
7
 *
8
 * For the full copyright and license information, please view
9
 * the LICENSE file that was distributed with this source code.
10
 */
11
12
namespace Da\User;
13
14
use Da\User\Component\AuthDbManagerComponent;
15
use Da\User\Contracts\AuthManagerInterface;
16
use Da\User\Controller\AdminController;
17
use Da\User\Controller\RecoveryController;
18
use Da\User\Controller\RegistrationController;
19
use Da\User\Controller\SecurityController;
20
use Da\User\Controller\SettingsController;
21
use Da\User\Event\FormEvent;
22
use Da\User\Event\UserEvent;
23
use Da\User\Event\ResetPasswordEvent;
24
use Da\User\Event\SocialNetworkAuthEvent;
25
use Da\User\Helper\ClassMapHelper;
26
use Da\User\Model\User;
27
use Yii;
28
use yii\authclient\Collection;
29
use yii\base\Application;
30
use yii\base\BootstrapInterface;
31
use yii\base\Event as YiiEvent;
32
use yii\base\Exception;
33
use yii\base\InvalidConfigException;
34
use yii\console\Application as ConsoleApplication;
35
use yii\i18n\PhpMessageSource;
36
use yii\web\Application as WebApplication;
37
38
/**
39
 * Bootstrap class of the yii2-usuario extension. Configures container services, initializes translations,
40
 * builds class map, and does the other setup actions participating in the application bootstrap process.
41
 */
42
class Bootstrap implements BootstrapInterface
43
{
44
    /**
45
     * {@inheritdoc}
46
     *
47
     * @throws InvalidConfigException
48
     */
49
    public function bootstrap($app)
50
    {
51
        if ($app->hasModule('user') && $app->getModule('user') instanceof Module) {
52
            $map = $this->buildClassMap($app->getModule('user')->classMap);
53
            $this->initContainer($app, $map);
54
            $this->initTranslations($app);
55
            $this->initMailServiceConfiguration($app, $app->getModule('user'));
56
57
            if ($app instanceof WebApplication) {
58
                $this->initControllerNamespace($app);
59
                $this->initUrlRoutes($app);
60
                $this->initAuthCollection($app);
61
                $this->initAuthManager($app);
62
            } else {
63
                /* @var $app ConsoleApplication */
64
                $this->initConsoleCommands($app);
65
                $this->initAuthManager($app);
66
            }
67
        }
68
    }
69
70
    /**
71
     * Initialize container with module classes.
72
     *
73
     * @param \yii\base\Application $app
74
     * @param array                 $map the previously built class map list
75
     */
76 15
    protected function initContainer($app, $map)
77
    {
78
        $di = Yii::$container;
79
        try {
80
            // events
81
            $di->set(Event\FormEvent::class);
82
            $di->set(Event\ProfileEvent::class);
83
            $di->set(Event\ResetPasswordEvent::class);
84
            $di->set(Event\SocialNetworkAuthEvent::class);
85
            $di->set(Event\SocialNetworkConnectEvent::class);
86
            $di->set(Event\UserEvent::class);
87
            $di->set(Event\GdprEvent::class);
88
89
            // forms
90
            $di->set(Form\LoginForm::class);
91
            $di->set(Form\RecoveryForm::class);
92
            $di->set(Form\RegistrationForm::class);
93
            $di->set(Form\ResendForm::class);
94
            $di->set(Form\SettingsForm::class);
95
            $di->set(Form\GdprDeleteForm::class);
96
97
            // helpers
98
            $di->set(Helper\AuthHelper::class);
99
            $di->set(Helper\GravatarHelper::class);
100
            $di->set(Helper\SecurityHelper::class);
101
            $di->set(Helper\TimezoneHelper::class);
102
103
            // services
104
            $di->set(Service\AccountConfirmationService::class);
105
            $di->set(Service\EmailChangeService::class);
106
            $di->set(Service\PasswordExpireService::class);
107
            $di->set(Service\PasswordRecoveryService::class);
108
            $di->set(Service\ResendConfirmationService::class);
109
            $di->set(Service\ResetPasswordService::class);
110
            $di->set(Service\SocialNetworkAccountConnectService::class);
111
            $di->set(Service\SocialNetworkAuthenticateService::class);
112
            $di->set(Service\UserBlockService::class);
113
            $di->set(Service\UserCreateService::class);
114
            $di->set(Service\UserRegisterService::class);
115
            $di->set(Service\UserConfirmationService::class);
116
            $di->set(Service\AuthItemEditionService::class);
117
            $di->set(Service\UpdateAuthAssignmentsService::class);
118
            $di->set(Service\SwitchIdentityService::class);
119
            $di->set(Service\TwoFactorQrCodeUriGeneratorService::class);
120
121
            // email change strategy
122
            $di->set(Strategy\DefaultEmailChangeStrategy::class);
123
            $di->set(Strategy\InsecureEmailChangeStrategy::class);
124
            $di->set(Strategy\SecureEmailChangeStrategy::class);
125
126
            // validators
127
            $di->set(Validator\AjaxRequestModelValidator::class);
128
            $di->set(Validator\TimeZoneValidator::class);
129
            $di->set(Validator\TwoFactorCodeValidator::class);
130
131
            // class map models + query classes
132
            $modelClassMap = [];
133
            foreach ($map as $class => $definition) {
134
                $di->set($class, $definition);
135
                $model = is_array($definition) ? $definition['class'] : $definition;
136
                $name = substr($class, strrpos($class, '\\') + 1);
137
                $modelClassMap[$class] = $model;
138
                if (in_array($name, ['User', 'Profile', 'Token', 'SocialNetworkAccount'])) {
139
                    $di->set(
140
                        "Da\\User\\Query\\{$name}Query",
141
                        function () use ($model) {
142 15
                            return $model::find();
143
                        }
144
                    );
145
                }
146
            }
147
            $di->setSingleton(ClassMapHelper::class, ClassMapHelper::class, [$modelClassMap]);
148
149
            // search classes
150
            if (!$di->has(Search\UserSearch::class)) {
151
                $di->set(Search\UserSearch::class, [$di->get(Query\UserQuery::class)]);
152
            }
153
            if (!$di->has(Search\PermissionSearch::class)) {
154
                $di->set(Search\PermissionSearch::class);
155
            }
156
            if (!$di->has(Search\RoleSearch::class)) {
157
                $di->set(Search\RoleSearch::class);
158
            }
159
160
            // Attach an event to check if the password has expired
161
            if (null !== Yii::$app->getModule('user')->maxPasswordAge) {
162
                YiiEvent::on(SecurityController::class, FormEvent::EVENT_AFTER_LOGIN, function (FormEvent $event) {
163
                    $user = $event->form->user;
164
                    if ($user->password_age >= Yii::$app->getModule('user')->maxPasswordAge) {
165
                        // Force password change
166
                        Yii::$app->session->setFlash('warning', Yii::t('usuario', 'Your password has expired, you must change it now'));
167
                        Yii::$app->response->redirect(['/user/settings/account'])->send();
168
                    }
169
                });
170
            }
171
172
            // Attach events for logging
173
            $this->attachAuditLogging();
174
175
            if ($app instanceof WebApplication) {
176
                // override Yii
177
                $di->set(
178
                    'yii\web\User',
179
                    [
180
                        'enableAutoLogin' => $app->getModule('user')->enableAutoLogin,
181
                        'loginUrl' => ['/user/security/login'],
182
                        'identityClass' => $di->get(ClassMapHelper::class)->get(User::class),
183
                    ]
184
                );
185
            }
186
        } catch (Exception $e) {
187
            die($e);
188
        }
189
    }
190
191
    /**
192
     * Registers module translation messages.
193
     *
194
     * @param Application $app
195
     *
196
     * @throws InvalidConfigException
197
     */
198
    protected function initTranslations(Application $app)
199
    {
200
        if (!isset($app->get('i18n')->translations['usuario*'])) {
201
            $app->get('i18n')->translations['usuario*'] = [
202
                'class' => PhpMessageSource::class,
203
                'basePath' => __DIR__ . '/resources/i18n',
204
                'sourceLanguage' => 'en-US',
205
            ];
206
        }
207
    }
208
209
    /**
210
     * Ensures the auth manager is the one provided by the library.
211
     *
212
     * @param Application $app
213
     *
214
     * @throws InvalidConfigException
215
     */
216
    protected function initAuthManager(Application $app)
217
    {
218
        if (!($app->getAuthManager() instanceof AuthManagerInterface)) {
219
            $app->set(
220
                'authManager',
221
                [
222
                    'class' => AuthDbManagerComponent::class,
223
                ]
224
            );
225
        }
226
    }
227
228
    /**
229
     * Initializes web url routes (rules in Yii2).
230
     *
231
     * @param WebApplication $app
232
     *
233
     * @throws InvalidConfigException
234
     */
235
    protected function initUrlRoutes(WebApplication $app)
236
    {
237
        /** @var $module Module */
238
        $module = $app->getModule('user');
239
        $config = [
240
            'class' => 'yii\web\GroupUrlRule',
241
            'prefix' => $module->prefix,
242
            'rules' => $module->routes,
243
        ];
244
245
        if ($module->prefix !== 'user') {
246
            $config['routePrefix'] = 'user';
247
        }
248
249
        $rule = Yii::createObject($config);
250
        $app->getUrlManager()->addRules([$rule], false);
251
    }
252
253
    /**
254
     * Ensures required mail parameters needed for the mail service.
255
     *
256
     * @param Application             $app
257
     * @param Module|\yii\base\Module $module
258
     */
259
    protected function initMailServiceConfiguration(Application $app, Module $module)
260
    {
261
        $defaults = [
262
            'fromEmail' => '[email protected]',
263
            'welcomeMailSubject' => Yii::t('usuario', 'Welcome to {0}', $app->name),
0 ignored issues
show
Documentation introduced by
$app->name is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
264
            'confirmationMailSubject' => Yii::t('usuario', 'Confirm account on {0}', $app->name),
0 ignored issues
show
Documentation introduced by
$app->name is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
265
            'reconfirmationMailSubject' => Yii::t('usuario', 'Confirm email change on {0}', $app->name),
0 ignored issues
show
Documentation introduced by
$app->name is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
266
            'recoveryMailSubject' => Yii::t('usuario', 'Complete password reset on {0}', $app->name),
0 ignored issues
show
Documentation introduced by
$app->name is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
267
        ];
268
269
        $module->mailParams = array_merge($defaults, $module->mailParams);
270
    }
271
272
    /**
273
     * Ensures the authCollection component is configured.
274
     *
275
     * @param WebApplication $app
276
     *
277
     * @throws InvalidConfigException
278
     */
279
    protected function initAuthCollection(WebApplication $app)
280
    {
281
        if (!$app->has('authClientCollection')) {
282
            $app->set('authClientCollection', Collection::class);
283
        }
284
    }
285
286
    /**
287
     * Registers console commands to main app.
288
     *
289
     * @param ConsoleApplication $app
290
     */
291
    protected function initConsoleCommands(ConsoleApplication $app)
292
    {
293
        $app->getModule('user')->controllerNamespace = $app->getModule('user')->consoleControllerNamespace;
0 ignored issues
show
Bug introduced by
The property consoleControllerNamespace does not seem to exist. Did you mean controllerNamespace?

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
294
    }
295
296
    /**
297
     * Registers controllers.
298
     *
299
     * @param WebApplication $app
300
     */
301
    protected function initControllerNamespace(WebApplication $app)
302
    {
303
        $app->getModule('user')->controllerNamespace = $app->getModule('user')->controllerNamespace;
304
        $app->getModule('user')->setViewPath('@Da/User/resources/views');
305
    }
306
307
    /**
308
     * Builds class map according to user configuration.
309
     *
310
     * @param array $userClassMap user configuration on the module
311
     *
312
     * @throws Exception
313
     * @return array
314
     */
315
    protected function buildClassMap(array $userClassMap)
316
    {
317
        $map = [];
318
319
        $defaults = [
320
            // --- models
321
            'User' => 'Da\User\Model\User',
322
            'SocialNetworkAccount' => 'Da\User\Model\SocialNetworkAccount',
323
            'Profile' => 'Da\User\Model\Profile',
324
            'Token' => 'Da\User\Model\Token',
325
            'Assignment' => 'Da\User\Model\Assignment',
326
            'Permission' => 'Da\User\Model\Permission',
327
            'Role' => 'Da\User\Model\Role',
328
            // --- search
329
            'UserSearch' => 'Da\User\Search\UserSearch',
330
            'PermissionSearch' => 'Da\User\Search\PermissionSearch',
331
            'RoleSearch' => 'Da\User\Search\RoleSearch',
332
            // --- forms
333
            'RegistrationForm' => 'Da\User\Form\RegistrationForm',
334
            'ResendForm' => 'Da\User\Form\ResendForm',
335
            'LoginForm' => 'Da\User\Form\LoginForm',
336
            'SettingsForm' => 'Da\User\Form\SettingsForm',
337
            'RecoveryForm' => 'Da\User\Form\RecoveryForm',
338
        ];
339
340
        $routes = [
341
            'Da\User\Model' => [
342
                'User',
343
                'SocialNetworkAccount',
344
                'Profile',
345
                'Token',
346
                'Assignment',
347
                'Permission',
348
                'Role',
349
            ],
350
            'Da\User\Search' => [
351
                'UserSearch',
352
                'PermissionSearch',
353
                'RoleSearch',
354
            ],
355
            'Da\User\Form' => [
356
                'RegistrationForm',
357
                'ResendForm',
358
                'LoginForm',
359
                'SettingsForm',
360
                'RecoveryForm',
361
            ],
362
        ];
363
364
        $mapping = array_merge($defaults, $userClassMap);
365
366
        foreach ($mapping as $name => $definition) {
367
            $map[$this->getRoute($routes, $name) . "\\$name"] = $definition;
368
        }
369
370
        return $map;
371
    }
372
373
    /**
374
     * Returns the parent class name route of a short class name.
375
     *
376
     * @param array  $routes class name routes
377
     * @param string $name
378
     *
379
     * @throws Exception
380
     * @return int|string
381
     *
382
     */
383
    protected function getRoute(array $routes, $name)
384
    {
385
        foreach ($routes as $route => $names) {
386
            if (in_array($name, $names, false)) {
387
                return $route;
388
            }
389
        }
390
        throw new Exception("Unknown configuration class name '{$name}'");
391
    }
392
393
    /**
394
     * Attach events for audit logging in usuario
395
     */
396
    private function attachAuditLogging() 
397
    {
398
        if (Yii::$app->getModule('user')->enableAuditLogging != true) {
399
            return;
400
        }
401
402
        YiiEvent::on(SecurityController::class, FormEvent::EVENT_AFTER_LOGIN, function (FormEvent $event) {
403
            Yii::info(Yii::t('usuario', "User '{user}' logged in from {ip}", [
404
                'user' => $event->form->user->username,
405
                'ip' => Yii::$app->request->remoteIP,
406
            ]), "usuario.audit");
407
        });
408
        YiiEvent::on(SecurityController::class, UserEvent::EVENT_AFTER_LOGOUT, function (UserEvent $event) {
409
            Yii::info(Yii::t('usuario', "User '{user}' logged out from {ip}", [
410
                'user' => $event->user->username,
411
                'ip' => Yii::$app->request->remoteIP,
412
            ]), "usuario.audit");
413
        });
414
        YiiEvent::on(RegistrationController::class, FormEvent::EVENT_AFTER_REGISTER, function (FormEvent $event) {
415
            Yii::info(Yii::t('usuario', "User '{user}' registered from {ip}", [
416
                'user' => $event->form->username,
417
                'ip' => Yii::$app->request->remoteIP,
418
            ]), "usuario.audit");
419
        });
420
        YiiEvent::on(RegistrationController::class, UserEvent::EVENT_AFTER_CONFIRMATION, function (UserEvent $event) {
421
            Yii::info(Yii::t('usuario', "User '{user}' confirmed from {ip}", [
422
                'user' => $event->user->username,
423
                'ip' => Yii::$app->request->remoteIP,
424
            ]), "usuario.audit");
425
        });
426
        YiiEvent::on(AdminController::class, UserEvent::EVENT_AFTER_CREATE, function (UserEvent $event) {
427
            Yii::info(Yii::t('usuario', "User '{user}' created from {ip}", [
428
                'user' => $event->user->username,
429
                'ip' => Yii::$app->request->remoteIP,
430
            ]), "usuario.audit");
431
        });
432
        YiiEvent::on(AdminController::class, UserEvent::EVENT_AFTER_BLOCK, function (UserEvent $event) {
433
            Yii::info(Yii::t('usuario', "User '{user}' has been blocked from {ip}", [
434
                'user' => $event->user->username,
435
                'ip' => Yii::$app->request->remoteIP,
436
            ]), "usuario.audit");
437
        });
438
        YiiEvent::on(AdminController::class, UserEvent::EVENT_AFTER_UNBLOCK, function (UserEvent $event) {
439
            Yii::info(Yii::t('usuario', "User '{user}' has been unblocked from {ip}", [
440
                'user' => $event->user->username,
441
                'ip' => Yii::$app->request->remoteIP,
442
            ]), "usuario.audit");
443
        });
444
        YiiEvent::on(User::class, UserEvent::EVENT_AFTER_REGISTER, function (UserEvent $event) {
445
            Yii::info(Yii::t('usuario', "User '{user}' registered from {ip}", [
446
                'user' => $event->user->username,
447
                'ip' => Yii::$app->request->remoteIP,
448
            ]), "usuario.audit");
449
        });
450
        YiiEvent::on(RecoveryController::class, ResetPasswordEvent::EVENT_AFTER_RESET, function (ResetPasswordEvent $event) {
451
            Yii::info(Yii::t('usuario', "User '{user}' has reset password from {ip}", [
452
                'user' => $event->token->user->username,
453
                'ip' => Yii::$app->request->remoteIP,
454
            ]), "usuario.audit");
455
        });
456
        YiiEvent::on(SecurityController::class, SocialNetworkAuthEvent::EVENT_AFTER_CONNECT, function (SocialNetworkAuthEvent $event) {
457
            Yii::info(Yii::t('usuario', "User '{user}' connnected to social network {sn} from {ip}", [
458
                'user' => $event->form->user->username,
0 ignored issues
show
Documentation introduced by
The property form does not exist on object<Da\User\Event\SocialNetworkAuthEvent>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
459
                'ip' => Yii::$app->request->remoteIP,
460
                'sn' => $event->account->provider,
461
            ]), "usuario.audit");
462
        });
463
        YiiEvent::on(SecurityController::class, SocialNetworkAuthEvent::EVENT_AFTER_AUTHENTICATE, function (SocialNetworkAuthEvent $event) {
464
            Yii::info(Yii::t('usuario', "User '{user}' logged in via social network {sn} from {ip}", [
465
                'user' => $event->form->user->username,
0 ignored issues
show
Documentation introduced by
The property form does not exist on object<Da\User\Event\SocialNetworkAuthEvent>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
466
                'ip' => Yii::$app->request->remoteIP,
467
                'sn' => $event->account->provider,
468
            ]), "usuario.audit");
469
        });
470
        YiiEvent::on(SettingsController::class, UserEvent::EVENT_AFTER_DELETE, function (UserEvent $event) {
471
            Yii::info(Yii::t('usuario', "User '{user}' deleted from {ip}", [
472
                'user' => $event->user->username,
473
                'ip' => Yii::$app->request->remoteIP,
474
            ]), "usuario.audit");
475
        });
476
        YiiEvent::on(SettingsController::class, UserEvent::EVENT_AFTER_ACCOUNT_UPDATE, function (UserEvent $event) {
477
            Yii::info(Yii::t('usuario', "User '{user}' account updated from {ip}", [
478
                'user' => $event->user->username,
479
                'ip' => Yii::$app->request->remoteIP,
480
            ]), "usuario.audit");
481
        });
482
        YiiEvent::on(SettingsController::class, UserEvent::EVENT_AFTER_PROFILE_UPDATE, function (UserEvent $event) {
483
            Yii::info(Yii::t('usuario', "User '{user}' profile updated from {ip}", [
484
                'user' => $event->user->username,
485
                'ip' => Yii::$app->request->remoteIP,
486
            ]), "usuario.audit");
487
        });
488
    }
489
}
490