Passed
Push — master ( 02a4a5...734a7e )
by Mihail
05:36
created

User   C

Complexity

Total Complexity 40

Size/Duplication

Total Lines 301
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 19

Importance

Changes 23
Bugs 10 Features 1
Metric Value
c 23
b 10
f 1
dl 0
loc 301
rs 5.9206
wmc 40
lcom 1
cbo 19

6 Methods

Rating   Name   Duplication   Size   Complexity  
A actionLogout() 0 12 2
B actionApprove() 0 25 5
B actionLogin() 0 32 5
C actionSocialauth() 0 48 9
C actionSignup() 0 72 11
B actionRecovery() 0 55 8

How to fix   Complexity   

Complex Class

Complex classes like User 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 User, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Apps\Controller\Front;
4
5
use Apps\ActiveRecord\Invite;
6
use Apps\ActiveRecord\UserRecovery;
7
use Apps\Model\Front\User\FormRecovery;
8
use Apps\Model\Front\User\FormRegister;
9
use Apps\Model\Front\User\FormSocialAuth;
10
use Extend\Core\Arch\FrontAppController;
11
use Ffcms\Core\App;
12
use Apps\Model\Front\User\FormLogin;
13
use Ffcms\Core\Arch\View;
14
use Ffcms\Core\Exception\ForbiddenException;
15
use Ffcms\Core\Exception\NativeException;
16
use Ffcms\Core\Exception\NotFoundException;
17
use Ffcms\Core\Exception\SyntaxException;
18
use Ffcms\Core\Helper\Type\Obj;
19
use Ffcms\Core\Helper\Type\Str;
20
use Apps\ActiveRecord\UserLog;
21
22
/**
23
 * Class User - standard user controller: login/signup/logout/etc
24
 * @package Apps\Controller\Front
25
 */
26
class User extends FrontAppController
27
{
28
    const EVENT_USER_LOGIN_SUCCESS = 'user.login.success';
29
    const EVENT_USER_LOGIN_FAIL = 'user.login.fail';
30
    const EVENT_USER_REGISTER_SUCCESS = 'user.signup.success';
31
    const EVENT_USER_REGISTER_FAIL = 'user.signup.fail';
32
33
    /**
34
     * View login form and process submit action
35
     * @throws ForbiddenException
36
     * @throws NativeException
37
     * @throws SyntaxException
38
     */
39
    public function actionLogin()
40
    {
41
        if (App::$User->isAuth()) { // always auth? get the f*ck out
42
            throw new ForbiddenException();
43
        }
44
45
        $configs = $this->getConfigs();
46
        // load login model
47
        $loginForm = new FormLogin($configs['captchaOnLogin'] === 1);
48
49
        // check if data is send and valid
50
        if ($loginForm->send() && $loginForm->validate()) {
51
            if ($loginForm->tryAuth()) {
52
                // initialize success event
53
                App::$Event->run(static::EVENT_USER_LOGIN_SUCCESS, [
54
                    'model' => $loginForm
55
                ]);
56
                $this->response->redirect('/'); // void header change & exit()
0 ignored issues
show
Bug introduced by
The method redirect cannot be called on $this->response (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
57
            }
58
            App::$Session->getFlashBag()->add('error', __('User is never exist or password is incorrect!'));
59
            // initialize fail event
60
            App::$Event->run(static::EVENT_USER_LOGIN_FAIL, [
61
               'model' => $loginForm
62
            ]);
63
        }
64
65
        // render view
66
        return $this->view->render('login', [
0 ignored issues
show
Documentation introduced by
The property view does not exist on object<Apps\Controller\Front\User>. 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...
67
            'model' => $loginForm->filter(),
68
            'useCaptcha' => $configs['captchaOnLogin'] === 1
69
        ]);
70
    }
71
72
    /**
73
     * Authorization in social networks over hybridauth layer. How its work:
74
     *  1. User visit actionSocialauth and initialize openid instance
75
     *  2. 3rd party software generate redirect to @api -> User::actionEndpoint() (as endpoint) where create hash's, tokens and other shit
76
     *  3. After successful auth on service user redirect back to actionSocialauth and we can work with $userIdentity if no exceptions catched.
77
     * Don't aks me "why did you do this sh@t"? I want to make container in User class, but this shit work only on direct call on endpoint.
78
     * @param string $provider
79
     * @return string
80
     * @throws \Ffcms\Core\Exception\NativeException
81
     * @throws ForbiddenException
82
     * @throws SyntaxException
83
     */
84
    public function actionSocialauth($provider)
85
    {
86
        // get hybridauth instance
87
        /** @var \Hybrid_Auth $instance */
88
        $instance = App::$User->getOpenidInstance();
89
        if ($instance === null) {
90
            throw new ForbiddenException(__('OpenID auth is disabled'));
91
        }
92
93
        // try to get user identity data from remove service
94
        $userIdentity = null;
95
        try {
96
            $adapter = $instance->authenticate($provider);
97
            $userIdentity = $adapter->getUserProfile();
98
        } catch (\Exception $e) {
99
            throw new SyntaxException(__('Authorization failed: %e%', ['e' => $e->getMessage()]));
100
        }
101
102
        // check if openid data provided
103
        if ($userIdentity === null || Str::likeEmpty($userIdentity->identifier)) {
104
            throw new ForbiddenException(__('User data not provided!'));
105
        }
106
107
        // initialize model and pass user identity
108
        $model = new FormSocialAuth($provider, $userIdentity);
0 ignored issues
show
Documentation introduced by
$provider is of type string, but the function expects a boolean.

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...
109
        // check if user is always registered
110
        if ($model->identityExists()) {
111
            $model->makeAuth();
112
            $this->response->redirect('/');
0 ignored issues
show
Bug introduced by
The method redirect cannot be called on $this->response (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
113
            return null;
114
        }
115
        // its a new identify, check if finish register form is submited
116
        if ($model->send() && $model->validate()) {
117
            if ($model->tryRegister()) {
118
                // registration is completed, lets open new session
119
                $loginModel = new FormLogin();
120
                $loginModel->openSession($model->_userObject);
0 ignored issues
show
Bug introduced by
It seems like $model->_userObject can be null; however, openSession() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
121
                $this->response->redirect('/'); // session is opened, refresh page
0 ignored issues
show
Bug introduced by
The method redirect cannot be called on $this->response (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
122
            } else { // something gonna wrong, lets notify user
123
                App::$Session->getFlashBag()->add('error', __('Login or email is always used on website'));
124
            }
125
        }
126
127
        // render output view
128
        return $this->view->render('social_signup', [
0 ignored issues
show
Documentation introduced by
The property view does not exist on object<Apps\Controller\Front\User>. 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...
129
            'model' => $model
130
        ]);
131
    }
132
133
    /**
134
     * View register form and process submit action
135
     * @throws ForbiddenException
136
     * @throws \Ffcms\Core\Exception\NativeException
137
     * @throws \Ffcms\Core\Exception\SyntaxException
138
     */
139
    public function actionSignup()
140
    {
141
        if (App::$User->isAuth()) { // always auth? prevent any actions
142
            throw new ForbiddenException();
143
        }
144
145
        // load configs
146
        $configs = $this->getConfigs();
147
148
        // init register model
149
        $registerForm = new FormRegister($configs['captchaOnRegister'] === 1);
150
151
        // registration based on invite. Check conditions.
152
        if ($configs['registrationType'] === 0) {
153
            // get token and email
154
            $inviteToken = $this->request->query->get('token');
0 ignored issues
show
Documentation introduced by
The property request does not exist on object<Apps\Controller\Front\User>. 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...
155
            $inviteEmail = $this->request->query->get('email');
0 ignored issues
show
Documentation introduced by
The property request does not exist on object<Apps\Controller\Front\User>. 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...
156
            // data sounds like a invalid?
157
            if (Str::length($inviteToken) < 32 || !Str::isEmail($inviteEmail)) {
158
                throw new ForbiddenException(__('Registration allowed only if you have invite!'));
159
            }
160
            // remove oldest data
161
            Invite::clean();
162
            // try to find token
163
            $find = Invite::where('token', '=', $inviteToken)
164
                ->where('email', '=', $inviteEmail)->count();
165
166
            // token not foud? invalid invite key
167
            if ($find !== 1) {
168
                throw new ForbiddenException(__('Your invite token is invalid! Contact with administrator'));
169
            }
170
            // notify the invite token is accepted
171
            if (!$registerForm->send()) {
172
                App::$Session->getFlashBag()->add('success', __('Invite was accepted! Continue registration'));
173
            }
174
175
            // set email from token data
176
            $registerForm->email = $inviteEmail;
177
        }
178
179
        // if register data is send and valid
180
        if ($registerForm->send() && $registerForm->validate()) {
181
            $activation = $configs['registrationType'] === 1;
182
            if ($registerForm->tryRegister($activation)) {
183
                // initialize succes signup event
184
                App::$Event->run(static::EVENT_USER_REGISTER_SUCCESS, [
185
                   'model' => $registerForm
186
                ]);
187
                // if no activation is required - just open session and redirect user to main page
188
                if (!$activation) {
189
                    $loginModel = new FormLogin();
190
                    $loginModel->openSession($registerForm->_userObject);
0 ignored issues
show
Bug introduced by
It seems like $registerForm->_userObject can be null; however, openSession() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
191
                    $this->response->redirect('/'); // session is opened, refresh page
0 ignored issues
show
Bug introduced by
The method redirect cannot be called on $this->response (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
192
                }
193
                // send notification of successful registering
194
                App::$Session->getFlashBag()->add('success', __('Your account is registered. You must confirm account via email'));
195
            } else {
196
                // init fail signup event
197
                App::$Event->run(static::EVENT_USER_REGISTER_FAIL, [
198
                   'model' => $registerForm
199
                ]);
200
                App::$Session->getFlashBag()->add('error', __('Login or email is always used on website'));
201
            }
202
        }
203
204
        // render view
205
        return $this->view->render('signup', [
0 ignored issues
show
Documentation introduced by
The property view does not exist on object<Apps\Controller\Front\User>. 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...
206
            'model' => $registerForm->filter(),
207
            'config' => $configs,
208
            'useCaptcha' => $configs['captchaOnRegister'] === 1
209
        ]);
210
    }
211
212
    /**
213
     * Recovery form and recovery submit action
214
     * @param int|null $id
215
     * @param string|null $token
216
     * @return string
217
     * @throws \Ffcms\Core\Exception\NativeException
218
     * @throws ForbiddenException
219
     * @throws NotFoundException
220
     * @throws \Ffcms\Core\Exception\SyntaxException
221
     */
222
    public function actionRecovery($id = null, $token = null)
223
    {
224
        if (App::$User->isAuth()) { // always auth? prevent any actions
225
            throw new ForbiddenException();
226
        }
227
228
        // is recovery submit?
229
        if (Obj::isLikeInt($id) && Str::length($token) >= 64) {
230
            $rObject = UserRecovery::where('id', '=', $id)
231
                ->where('token', '=', $token)
232
                ->where('archive', '=', false);
233
            // check if recovery row exist
234
            if ($rObject->count() !== 1) {
235
                throw new NotFoundException('This recovery data is not found');
236
            }
237
238
            $rData = $rObject->first();
239
            // check if user with this "user_id" in recovery row exist
240
            $rUser = App::$User->identity($rData->user_id);
241
            if ($rUser === null) {
242
                throw new NotFoundException('User is not found');
243
            }
244
245
            // all is ok, lets set new pwd
246
            $rUser->password = $rData->password;
0 ignored issues
show
Bug introduced by
Accessing password on the interface Ffcms\Core\Interfaces\iUser suggest that you code against a concrete implementation. How about adding an instanceof check?

If you access a property on an interface, you most likely code against a concrete implementation of the interface.

Available Fixes

  1. Adding an additional type check:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeInterface $object) {
        if ($object instanceof SomeClass) {
            $a = $object->a;
        }
    }
    
  2. Changing the type hint:

    interface SomeInterface { }
    class SomeClass implements SomeInterface {
        public $a;
    }
    
    function someFunction(SomeClass $object) {
        $a = $object->a;
    }
    
Loading history...
247
            $rUser->save();
248
249
            $rData->archive = true;
250
            $rData->save();
251
252
            // add notification
253
            App::$Session->getFlashBag()->add('success', __('Your account are successful recovered. We recommend you change password'));
254
255
            // lets open user session with recovered data
256
            $loginModel = new FormLogin();
257
            $loginModel->openSession($rUser);
258
            $this->response->redirect('/'); // session is opened, refresh page
0 ignored issues
show
Bug introduced by
The method redirect cannot be called on $this->response (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
259
        }
260
261
        // lets work with recovery form data
262
        $model = new FormRecovery(true);
0 ignored issues
show
Unused Code introduced by
The call to FormRecovery::__construct() has too many arguments starting with true.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
263
        if ($model->send()) {
264
            if ($model->validate()) {
265
                $model->make();
266
                App::$Session->getFlashBag()->add('success', __('We send to you email with instruction to recovery your account'));
267
            } else {
268
                App::$Session->getFlashBag()->add('error', __('Form validation is failed'));
269
            }
270
        }
271
272
        // render visual form content
273
        return $this->view->render('recovery', [
0 ignored issues
show
Documentation introduced by
The property view does not exist on object<Apps\Controller\Front\User>. 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...
274
            'model' => $model->filter()
275
        ]);
276
    }
277
278
    /**
279
     * Make logout if user is signIn
280
     * @throws ForbiddenException
281
     */
282
    public function actionLogout()
283
    {
284
        if (!App::$User->isAuth()) { // not auth? what you wanna?
285
            throw new ForbiddenException();
286
        }
287
288
        // unset session data
289
        App::$Session->invalidate();
290
291
        // redirect to main
292
        $this->response->redirect('/');
0 ignored issues
show
Bug introduced by
The method redirect cannot be called on $this->response (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
293
    }
294
295
    /**
296
     * Approve user profile via $email and $token params
297
     * @param string $email
298
     * @param string $token
299
     * @throws ForbiddenException
300
     */
301
    public function actionApprove($email, $token)
302
    {
303
        // sounds like a not valid token
304
        if (App::$User->isAuth() || Str::length($token) < 32 || !Str::isEmail($email)) {
305
            throw new ForbiddenException();
306
        }
307
        // lets find token&email
308
        $find = App::$User->where('approve_token', '=', $token)
309
            ->where('email', '=', $email);
310
311
        // not found? exit
312
        if ($find->count() !== 1) {
313
            throw new ForbiddenException();
314
        }
315
316
        // get row and update approve information
317
        $user = $find->first();
318
        $user->approve_token = '0';
319
        $user->save();
320
321
        // open session and redirect to main
322
        $loginModel = new FormLogin();
323
        $loginModel->openSession($user);
324
        $this->response->redirect('/'); // session is opened, refresh page
0 ignored issues
show
Bug introduced by
The method redirect cannot be called on $this->response (of type string).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
325
    }
326
}