Completed
Pull Request — master (#320)
by
unknown
06:57
created

Bootstrap::getRoute()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

Changes 0
Metric Value
dl 0
loc 9
ccs 0
cts 5
cp 0
rs 9.9666
c 0
b 0
f 0
cc 3
nc 3
nop 2
crap 12
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\Event\FormEvent;
21
use Da\User\Event\UserEvent;
22
use Da\User\Event\ResetPasswordEvent;
23
use Da\User\Event\SocialNetworkAuthEvent;
24
use Da\User\Helper\ClassMapHelper;
25
use Da\User\Model\User;
26
use Yii;
27
use yii\authclient\Collection;
28
use yii\base\Application;
29
use yii\base\BootstrapInterface;
30
use yii\base\Event as YiiEvent;
31
use yii\base\Exception;
32
use yii\base\InvalidConfigException;
33
use yii\console\Application as ConsoleApplication;
34
use yii\i18n\PhpMessageSource;
35
use yii\web\Application as WebApplication;
36
37
/**
38
 * Bootstrap class of the yii2-usuario extension. Configures container services, initializes translations,
39
 * builds class map, and does the other setup actions participating in the application bootstrap process.
40
 */
41
class Bootstrap implements BootstrapInterface
42
{
43
    /**
44
     * {@inheritdoc}
45
     *
46
     * @throws InvalidConfigException
47
     */
48
    public function bootstrap($app)
49
    {
50
        if ($app->hasModule('user') && $app->getModule('user') instanceof Module) {
51
            $map = $this->buildClassMap($app->getModule('user')->classMap);
52
            $this->initContainer($app, $map);
53
            $this->initTranslations($app);
54
            $this->initMailServiceConfiguration($app, $app->getModule('user'));
55
56
            if ($app instanceof WebApplication) {
57
                $this->initControllerNamespace($app);
58
                $this->initUrlRoutes($app);
59
                $this->initAuthCollection($app);
60
                $this->initAuthManager($app);
61
            } else {
62
                /* @var $app ConsoleApplication */
63
                $this->initConsoleCommands($app);
64
                $this->initAuthManager($app);
65
            }
66
        }
67
    }
68
69
    /**
70
     * Initialize container with module classes.
71
     *
72
     * @param \yii\base\Application $app
73
     * @param array                 $map the previously built class map list
74
     */
75 15
    protected function initContainer($app, $map)
76
    {
77
        $di = Yii::$container;
78
        try {
79
            // events
80
            $di->set(Event\FormEvent::class);
81
            $di->set(Event\ProfileEvent::class);
82
            $di->set(Event\ResetPasswordEvent::class);
83
            $di->set(Event\SocialNetworkAuthEvent::class);
84
            $di->set(Event\SocialNetworkConnectEvent::class);
85
            $di->set(Event\UserEvent::class);
86
            $di->set(Event\GdprEvent::class);
87
88
            // forms
89
            $di->set(Form\LoginForm::class);
90
            $di->set(Form\RecoveryForm::class);
91
            $di->set(Form\RegistrationForm::class);
92
            $di->set(Form\ResendForm::class);
93
            $di->set(Form\SettingsForm::class);
94
            $di->set(Form\GdprDeleteForm::class);
95
96
            // helpers
97
            $di->set(Helper\AuthHelper::class);
98
            $di->set(Helper\GravatarHelper::class);
99
            $di->set(Helper\SecurityHelper::class);
100
            $di->set(Helper\TimezoneHelper::class);
101
102
            // services
103
            $di->set(Service\AccountConfirmationService::class);
104
            $di->set(Service\EmailChangeService::class);
105
            $di->set(Service\PasswordExpireService::class);
106
            $di->set(Service\PasswordRecoveryService::class);
107
            $di->set(Service\ResendConfirmationService::class);
108
            $di->set(Service\ResetPasswordService::class);
109
            $di->set(Service\SocialNetworkAccountConnectService::class);
110
            $di->set(Service\SocialNetworkAuthenticateService::class);
111
            $di->set(Service\UserBlockService::class);
112
            $di->set(Service\UserCreateService::class);
113
            $di->set(Service\UserRegisterService::class);
114
            $di->set(Service\UserConfirmationService::class);
115
            $di->set(Service\AuthItemEditionService::class);
116
            $di->set(Service\UpdateAuthAssignmentsService::class);
117
            $di->set(Service\SwitchIdentityService::class);
118
            $di->set(Service\TwoFactorQrCodeUriGeneratorService::class);
119
120
            // email change strategy
121
            $di->set(Strategy\DefaultEmailChangeStrategy::class);
122
            $di->set(Strategy\InsecureEmailChangeStrategy::class);
123
            $di->set(Strategy\SecureEmailChangeStrategy::class);
124
125
            // validators
126
            $di->set(Validator\AjaxRequestModelValidator::class);
127
            $di->set(Validator\TimeZoneValidator::class);
128
            $di->set(Validator\TwoFactorCodeValidator::class);
129
130
            // class map models + query classes
131
            $modelClassMap = [];
132
            foreach ($map as $class => $definition) {
133
                $di->set($class, $definition);
134
                $model = is_array($definition) ? $definition['class'] : $definition;
135
                $name = substr($class, strrpos($class, '\\') + 1);
136
                $modelClassMap[$class] = $model;
137
                if (in_array($name, ['User', 'Profile', 'Token', 'SocialNetworkAccount'])) {
138
                    $di->set(
139
                        "Da\\User\\Query\\{$name}Query",
140
                        function () use ($model) {
141 15
                            return $model::find();
142
                        }
143
                    );
144
                }
145
            }
146
            $di->setSingleton(ClassMapHelper::class, ClassMapHelper::class, [$modelClassMap]);
147
148
            // search classes
149
            if (!$di->has(Search\UserSearch::class)) {
150
                $di->set(Search\UserSearch::class, [$di->get(Query\UserQuery::class)]);
151
            }
152
            if (!$di->has(Search\PermissionSearch::class)) {
153
                $di->set(Search\PermissionSearch::class);
154
            }
155
            if (!$di->has(Search\RoleSearch::class)) {
156
                $di->set(Search\RoleSearch::class);
157
            }
158
159
            // Attach an event to check if the password has expired
160
            if (null !== Yii::$app->getModule('user')->maxPasswordAge) {
161
                YiiEvent::on(SecurityController::class, FormEvent::EVENT_AFTER_LOGIN, function (FormEvent $event) {
162
                    $user = $event->form->user;
163
                    if ($user->password_age >= Yii::$app->getModule('user')->maxPasswordAge) {
164
                        // Force password change
165
                        Yii::$app->session->setFlash('warning', Yii::t('usuario', 'Your password has expired, you must change it now'));
166
                        Yii::$app->response->redirect(['/user/settings/account'])->send();
167
                    }
168
                });
169
            }
170
171
            // Attach events for logging
172
            $this->attachAuditLogging();
173
174
            if ($app instanceof WebApplication) {
175
                // override Yii
176
                $di->set(
177
                    'yii\web\User',
178
                    [
179
                        'enableAutoLogin' => $app->getModule('user')->enableAutoLogin,
180
                        'loginUrl' => ['/user/security/login'],
181
                        'identityClass' => $di->get(ClassMapHelper::class)->get(User::class),
182
                    ]
183
                );
184
            }
185
        } catch (Exception $e) {
186
            die($e);
187
        }
188
    }
189
190
    /**
191
     * Registers module translation messages.
192
     *
193
     * @param Application $app
194
     *
195
     * @throws InvalidConfigException
196
     */
197
    protected function initTranslations(Application $app)
198
    {
199
        if (!isset($app->get('i18n')->translations['usuario*'])) {
200
            $app->get('i18n')->translations['usuario*'] = [
201
                'class' => PhpMessageSource::class,
202
                'basePath' => __DIR__ . '/resources/i18n',
203
                'sourceLanguage' => 'en-US',
204
            ];
205
        }
206
    }
207
208
    /**
209
     * Ensures the auth manager is the one provided by the library.
210
     *
211
     * @param Application $app
212
     *
213
     * @throws InvalidConfigException
214
     */
215
    protected function initAuthManager(Application $app)
216
    {
217
        if (!($app->getAuthManager() instanceof AuthManagerInterface)) {
218
            $app->set(
219
                'authManager',
220
                [
221
                    'class' => AuthDbManagerComponent::class,
222
                ]
223
            );
224
        }
225
    }
226
227
    /**
228
     * Initializes web url routes (rules in Yii2).
229
     *
230
     * @param WebApplication $app
231
     *
232
     * @throws InvalidConfigException
233
     */
234
    protected function initUrlRoutes(WebApplication $app)
235
    {
236
        /** @var $module Module */
237
        $module = $app->getModule('user');
238
        $config = [
239
            'class' => 'yii\web\GroupUrlRule',
240
            'prefix' => $module->prefix,
241
            'rules' => $module->routes,
242
        ];
243
244
        if ($module->prefix !== 'user') {
245
            $config['routePrefix'] = 'user';
246
        }
247
248
        $rule = Yii::createObject($config);
249
        $app->getUrlManager()->addRules([$rule], false);
250
    }
251
252
    /**
253
     * Ensures required mail parameters needed for the mail service.
254
     *
255
     * @param Application             $app
256
     * @param Module|\yii\base\Module $module
257
     */
258
    protected function initMailServiceConfiguration(Application $app, Module $module)
259
    {
260
        $defaults = [
261
            'fromEmail' => '[email protected]',
262
            '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...
263
            '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...
264
            '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...
265
            '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...
266
        ];
267
268
        $module->mailParams = array_merge($defaults, $module->mailParams);
269
    }
270
271
    /**
272
     * Ensures the authCollection component is configured.
273
     *
274
     * @param WebApplication $app
275
     *
276
     * @throws InvalidConfigException
277
     */
278
    protected function initAuthCollection(WebApplication $app)
279
    {
280
        if (!$app->has('authClientCollection')) {
281
            $app->set('authClientCollection', Collection::class);
282
        }
283
    }
284
285
    /**
286
     * Registers console commands to main app.
287
     *
288
     * @param ConsoleApplication $app
289
     */
290
    protected function initConsoleCommands(ConsoleApplication $app)
291
    {
292
        $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...
293
    }
294
295
    /**
296
     * Registers controllers.
297
     *
298
     * @param WebApplication $app
299
     */
300
    protected function initControllerNamespace(WebApplication $app)
301
    {
302
        $app->getModule('user')->controllerNamespace = $app->getModule('user')->controllerNamespace;
303
        $app->getModule('user')->setViewPath('@Da/User/resources/views');
304
    }
305
306
    /**
307
     * Builds class map according to user configuration.
308
     *
309
     * @param array $userClassMap user configuration on the module
310
     *
311
     * @throws Exception
312
     * @return array
313
     */
314
    protected function buildClassMap(array $userClassMap)
315
    {
316
        $map = [];
317
318
        $defaults = [
319
            // --- models
320
            'User' => 'Da\User\Model\User',
321
            'SocialNetworkAccount' => 'Da\User\Model\SocialNetworkAccount',
322
            'Profile' => 'Da\User\Model\Profile',
323
            'Token' => 'Da\User\Model\Token',
324
            'Assignment' => 'Da\User\Model\Assignment',
325
            'Permission' => 'Da\User\Model\Permission',
326
            'Role' => 'Da\User\Model\Role',
327
            // --- search
328
            'UserSearch' => 'Da\User\Search\UserSearch',
329
            'PermissionSearch' => 'Da\User\Search\PermissionSearch',
330
            'RoleSearch' => 'Da\User\Search\RoleSearch',
331
            // --- forms
332
            'RegistrationForm' => 'Da\User\Form\RegistrationForm',
333
            'ResendForm' => 'Da\User\Form\ResendForm',
334
            'LoginForm' => 'Da\User\Form\LoginForm',
335
            'SettingsForm' => 'Da\User\Form\SettingsForm',
336
            'RecoveryForm' => 'Da\User\Form\RecoveryForm',
337
        ];
338
339
        $routes = [
340
            'Da\User\Model' => [
341
                'User',
342
                'SocialNetworkAccount',
343
                'Profile',
344
                'Token',
345
                'Assignment',
346
                'Permission',
347
                'Role',
348
            ],
349
            'Da\User\Search' => [
350
                'UserSearch',
351
                'PermissionSearch',
352
                'RoleSearch',
353
            ],
354
            'Da\User\Form' => [
355
                'RegistrationForm',
356
                'ResendForm',
357
                'LoginForm',
358
                'SettingsForm',
359
                'RecoveryForm',
360
            ],
361
        ];
362
363
        $mapping = array_merge($defaults, $userClassMap);
364
365
        foreach ($mapping as $name => $definition) {
366
            $map[$this->getRoute($routes, $name) . "\\$name"] = $definition;
367
        }
368
369
        return $map;
370
    }
371
372
    /**
373
     * Returns the parent class name route of a short class name.
374
     *
375
     * @param array  $routes class name routes
376
     * @param string $name
377
     *
378
     * @throws Exception
379
     * @return int|string
380
     *
381
     */
382
    protected function getRoute(array $routes, $name)
383
    {
384
        foreach ($routes as $route => $names) {
385
            if (in_array($name, $names, false)) {
386
                return $route;
387
            }
388
        }
389
        throw new Exception("Unknown configuration class name '{$name}'");
390
    }
391
392
    /**
393
     * Attach events for audit logging in usuario
394
     */
395
    private function attachAuditLogging() 
396
    {
397
        if (Yii::$app->getModule('user')->enableAuditLogging != true) {
398
            return;
399
        }
400
401
        YiiEvent::on(SecurityController::class, FormEvent::EVENT_AFTER_LOGIN, function (FormEvent $event) {
402
            Yii::info(Yii::t('usuario', "User {user} logged in from {ip}", [
403
                'user' => $event->form->user->username,
404
                'ip' => Yii::$app->request->remoteIP,
405
            ]), "usuario.audit");
406
        });
407
        YiiEvent::on(SecurityController::class, UserEvent::EVENT_AFTER_LOGOUT, function (UserEvent $event) {
408
            Yii::info(Yii::t('usuario', "User {user} logged out from {ip}", [
409
                'user' => $event->user->username,
410
                'ip' => Yii::$app->request->remoteIP,
411
            ]), "usuario.audit");
412
        });
413
        YiiEvent::on(RegistrationController::class, FormEvent::EVENT_AFTER_REGISTER, function (FormEvent $event) {
414
            Yii::info(Yii::t('usuario', "User {user} registered from {ip}", [
415
                'user' => $event->form->username,
416
                'ip' => Yii::$app->request->remoteIP,
417
            ]), "usuario.audit");
418
        });
419
        YiiEvent::on(RegistrationController::class, UserEvent::EVENT_AFTER_CONFIRMATION, function (UserEvent $event) {
420
            Yii::info(Yii::t('usuario', "User {user} confirmed from {ip}", [
421
                'user' => $event->user->username,
422
                'ip' => Yii::$app->request->remoteIP,
423
            ]), "usuario.audit");
424
        });
425
        YiiEvent::on(AdminController::class, UserEvent::EVENT_AFTER_CREATE, function (UserEvent $event) {
426
            Yii::info(Yii::t('usuario', "User {user} created from {ip}", [
427
                'user' => $event->user->username,
428
                'ip' => Yii::$app->request->remoteIP,
429
            ]), "usuario.audit");
430
        });
431
        YiiEvent::on(User::class, UserEvent::EVENT_AFTER_REGISTER, function (UserEvent $event) {
432
            Yii::info(Yii::t('usuario', "User {user} registered from {ip}", [
433
                'user' => $event->user->username,
434
                'ip' => Yii::$app->request->remoteIP,
435
            ]), "usuario.audit");
436
        });
437
        YiiEvent::on(SecurityController::class, SocialNetworkAuthEvent::EVENT_AFTER_CONNECT, function (SocialNetworkAuthEvent $event) {
438
            Yii::info(Yii::t('usuario', "User {user} connnected to social network {sn} from {ip}", [
439
                '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...
440
                'ip' => Yii::$app->request->remoteIP,
441
                'sn' => $event->account->provider,
442
            ]), "usuario.audit");
443
        });
444
        YiiEvent::on(SecurityController::class, SocialNetworkAuthEvent::EVENT_AFTER_AUTHENTICATE, function (SocialNetworkAuthEvent $event) {
445
            Yii::info(Yii::t('usuario', "User {user} logged in via social network {sn} from {ip}", [
446
                '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...
447
                'ip' => Yii::$app->request->remoteIP,
448
                'sn' => $event->account->provider,
449
            ]), "usuario.audit");
450
        });
451
        YiiEvent::on(RecoveryController::class, ResetPasswordEvent::EVENT_AFTER_RESET, function (ResetPasswordEvent $event) {
452
            Yii::info(Yii::t('usuario', "User {user} has reset password from {ip}", [
453
                'user' => $event->token->user->username,
454
                'ip' => Yii::$app->request->remoteIP,
455
            ]), "usuario.audit");
456
        });
457
    }
458
}
459