UsersController.php 40.9 KB
Newer Older
Pastor Mickaël's avatar
CRUD  
Pastor Mickaël committed
1
<?php
2
3
4
5
6
7
8
/**
 * Controller Of Users.
 *
 * @package App\Controller\Api\V1
 * @author  mickael pastor <mickael.pastor@adullact.org>
 * @license https://spdx.org/licenses/AGPL-3.0-or-later.html Affero General Public License
 */
9

10
namespace App\Controller\Api\V1;
Pastor Mickaël's avatar
CRUD  
Pastor Mickaël committed
11
12

use App\Controller\AppController;
13
use App\Controller\Component\RulesComponent;
14
use App\Model\Entity\User;
15
use App\Model\Table\UsersTable;
Matthieu FAURE's avatar
Matthieu FAURE committed
16
use Cake\Core\Configure;
17
use Cake\Event\Event;
18
use Cake\Mailer\MailerAwareTrait;
19
20
use Cake\Network\Exception\NotFoundException;
use Cake\Network\Response;
21
use Cake\ORM\Query;
Pastor Mickaël's avatar
Pastor Mickaël committed
22
use Cake\Utility\Text;
23

Pastor Mickaël's avatar
CRUD  
Pastor Mickaël committed
24
25
26
/**
 * Users Controller
 *
27
 * @property UsersTable $Users
28
29
30
 * @package  App\Controller\Api\V1
 * @author   mickael pastor <mickael.pastor@adullact.org>
 * @license  https://spdx.org/licenses/AGPL-3.0-or-later.html Affero General Public License
Pastor Mickaël's avatar
CRUD  
Pastor Mickaël committed
31
32
33
 */
class UsersController extends AppController
{
34
    use MailerAwareTrait;
Pastor Mickaël's avatar
Pastor Mickaël committed
35

36
37
38
39
40
41
    /**
     * How long the token is valid
     * to change the password (in number of hours)
     */
    const TOKEN_VALIDITY_TIME_TO_CHANGE_PASSWORD = '24'; // 24 hours

Pastor Mickaël's avatar
Pastor Mickaël committed
42
43
    /**
     * Manage all rights for the controllers' actions.
44
     * Returns true if the user can use the current action, FALSE otherwise.
Pastor Mickaël's avatar
Pastor Mickaël committed
45
46
     * Returns true for add a project if the user is connected
     * Returns true for edit and delete action if the user is owner.
47
48
     *
     *
Matthieu FAURE's avatar
Matthieu FAURE committed
49
     * notice: $forceDeny parameter is mandatory to be compatible with parent::isAuthorized()
50
51
52
53
     *                     and it's forced to TRUE
     *
     * @param array $user A user.
     * @param  boolean $forceDeny by default FALSE, set TRUE to force the deny on parent::isAuthorized()
Pastor Mickaël's avatar
Pastor Mickaël committed
54
55
     * @return boolean
     */
56
    public function isAuthorized($user, $forceDeny = false)
Pastor Mickaël's avatar
Pastor Mickaël committed
57
    {
58

Matthieu FAURE's avatar
Matthieu FAURE committed
59
        // Access to edit method only for the current user
60
        $forceDeny = false;
61
62
        if ($this->request->param('action') === 'edit') {
            if ($this->Auth->user('id') == $this->request->params["pass"][0]) {
Pastor Mickaël's avatar
Pastor Mickaël committed
63
64
                return true;
            }
65
66
67
            // GET requests is not allowed, if the user tries to edit another user's record
            // ----> force the deny on parent::isAuthorized()
            $forceDeny = true;
68
        }
Pastor Mickaël's avatar
Pastor Mickaël committed
69

70
71
        // Access to contact method
        if ($this->request->param('action') === 'contact') {
72
            // Allow access to all users except providers
73
74
            $notAllowedUserTypeId = $this->getUserTypeIdByName('Company');
            if ($this->Auth->user('user_type_id') !== $notAllowedUserTypeId) {
75
                return true;
76
            } else { // if a provider (user_type = 'Company') tries to contact another user
77
78
79
                $forceDeny = true; // force the deny on parent::isAuthorized() for GET request
                                   // by default POST request are deny on parent::isAuthorized()
            }
80
81
        }

82
        if ($this->request->param('action') === 'changePassword') {
83
84
            $tokenOrId = $this->request->params["pass"][0];

85
            $user = $this->Users->findById($tokenOrId, ["contain" => []])->first();
Pastor Mickaël's avatar
Pastor Mickaël committed
86

Matthieu FAURE's avatar
Matthieu FAURE committed
87
88
89
90
            if ($this->Auth->user('id') == $user->id || $this->Users->findByToken(
                $tokenOrId,
                ["contain" => []]
            )->first()) {
Pastor Mickaël's avatar
Pastor Mickaël committed
91
92
93
94
                return true;
            }
        }

Pastor Mickaël's avatar
Pastor Mickaël committed
95
        //If user is auth
96
97
        if ($this->request->param('action') === 'register') {
            if ($user) {
Pastor Mickaël's avatar
Pastor Mickaël committed
98
99
100
                return true;
            }
        }
101
        return parent::isAuthorized($user, $forceDeny);
Pastor Mickaël's avatar
Pastor Mickaël committed
102
103
    }

104
105
106
107
108
    /**
     * Initialisation of a user
     *
     * @return void
     */
109
110
    public function initialize()
    {
Pastor Mickaël's avatar
Pastor Mickaël committed
111
112
113
        parent::initialize();
        $this->paginate = [
            'limit' => Configure::read('LIMIT'),
114
            'maxLimit' => Configure::read('LIMIT'),
Pastor Mickaël's avatar
Pastor Mickaël committed
115
116
117
            'order' => [
                'Users.username' => Configure::read('ORDER'),
            ],
Pastor Mickaël's avatar
Pastor Mickaël committed
118
            'fields' => [
Matthieu FAURE's avatar
Matthieu FAURE committed
119
120
121
122
123
124
125
126
127
                'id',
                'username',
                'email',
                'user_type_id',
                'url',
                'description',
                'photo',
                'logo_directory',
                'UserTypes.name'
Pastor Mickaël's avatar
Pastor Mickaël committed
128
            ],
Pastor Mickaël's avatar
Pastor Mickaël committed
129
130
131
132
            'contain' => ['UserTypes']
        ];
    }

Fabrice Gangler's avatar
Fabrice Gangler committed
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174

    /**
     * $links = [ 0 => [ 'name' => '…', 'url' => '/dir/file', 'title' => '…'],
     *            1 => [ 'name' => '…', 'url' => '/dir/file', 'title' => '…'], ]
     *
     * @param array $links
     */
    protected function setBreadcrumbs(array $links = [])
    {
        $firstLink = [
            'name' => __d('Breadcrumbs', 'User.ListOfUsers'),
            'url' => 'users'
        ];
        array_unshift($links, $firstLink);
        parent::setBreadcrumbs($links);
    }

    /**
     * $links = [ 0 => [ 'name' => '…', 'url' => '/dir/file', 'title' => '…'],
     *            1 => [ 'name' => '…', 'url' => '/dir/file', 'title' => '…'], ]
     *
     * @param array $links
     */
    protected function setBreadcrumbsUser(array $links = [], $user = null)
    {

        if (is_object($user) and $user->user_type->name === 'Company') {
            $firstLink = [
                'name' => __d('Breadcrumbs', 'Users.Providers'),
                'url' => "users/providers"
            ];
        } else {
            $firstLink = [
                'name' => __d('Breadcrumbs', 'User.ListOfUsers'),
                'url' => 'users'
            ];
        }
        array_unshift($links, $firstLink);
        parent::setBreadcrumbs($links);
    }


Pastor Mickaël's avatar
Pastor Mickaël committed
175
    /**
176
     * Allow = actions allow without being connected
177
178
     * Before filter manage right access of users
     *
179
     * @param Event $event Events
180
181
     *
     * @return void
Pastor Mickaël's avatar
Pastor Mickaël committed
182
     */
183
    public function beforeFilter(Event $event)
Pastor Mickaël's avatar
Pastor Mickaël committed
184
    {
185
186
        $this->Auth->allow(
            [
Matthieu FAURE's avatar
Matthieu FAURE committed
187
188
189
190
191
                'add',
                'providers',
                'administrations',
                'nonProfitUsers',
                'providerforSoftwares',
192
                'reviewsforSoftware',
Matthieu FAURE's avatar
Matthieu FAURE committed
193
194
195
                'usedSoftwares',
                'forgotPassword',
                'changePassword',
196
197
198
                'resetPassword',
            ]
        );
199
        $this->Auth->deny(['delete', 'edit', 'register', 'contact']);
200

Pastor Mickaël's avatar
Pastor Mickaël committed
201
202
203
        parent::beforeFilter($event);
    }

Pastor Mickaël's avatar
CRUD  
Pastor Mickaël committed
204
    /**
Julie gauthier's avatar
Julie gauthier committed
205
206
207
208
209
     * Returns a list of users.
     *
     * Note: whereas service providers are part of the User entity in the database, they are not considered as a user
     *   from the application point of view. They have their dedicated "identity". Maybe one day, they'll be extracted
     *   as a separated entity in the DB schema.
Pastor Mickaël's avatar
CRUD  
Pastor Mickaël committed
210
211
212
213
214
     *
     * @return void
     */
    public function index()
    {
Julie gauthier's avatar
Julie gauthier committed
215
216
217
        if (!empty($this->request->query)
            && isset($this->request->query["order"])
        ) {
218
            $sort = explode(".", $this->request->query["order"]);
Fabrice Gangler's avatar
Fabrice Gangler committed
219
220
            // Note JGauthier 2019-07-16: just discovered this horror. This is not the correct way to do this.
            // TODO This should be fixed
221
222
223
224
            $this->request->query["sort"] = $sort[0];
            $this->request->query["direction"] = $sort[1];
        }

225
        $users = $this->Users
226
227
            ->find(
                'search',
228
                [
229
230
231
232
233
234
                    'sortWhitelist' => [
                        'username',
                        'average_review',
                        'created',
                        'modified',
                    ],
235
236
                    'search' => $this->request->query,
                    'contain' => ['UserTypes'],
Julie gauthier's avatar
Julie gauthier committed
237
                    'order' =>
238
                        isset($this->request->query["sort"])
Julie gauthier's avatar
Julie gauthier committed
239
240
241
242
243
                        && isset($this->request->query["direction"])
                            ?
                            ['Users.' . $this->request->query["sort"] => $this->request->query["direction"]]
                            :
                            ['Users.username' => Configure::read('ORDER')]
244
                    ,
Julie gauthier's avatar
Julie gauthier committed
245
                ]
246
247
            )
            ->where(["UserTypes.cd <> " => "Company"]);
248

249
        //For filters
250
        $hadReview = [
251
252
            "false" => __d("Forms", "Search.Filter.Users.NoFilterOnReview"),
            "true" => __d("Forms", "Search.Filter.Users.WithReview"),
253
254
255
        ];

        $userType = [
256
257
258
259
            "all" => __d("Forms", "Search.Filter.Users.NoFilterOnUserType"),
            "Administration" => __d("Forms", "Search.Filter.Users.userTpye.Administration"),
            "Person" => __d("Forms", "Search.Filter.Users.userTpye.Person"),
            "Association" => __d("Forms", "Search.Filter.Users.userTpye.Association"),
260
261
262
        ];

        $isUserOf = [
263
264
            "false" => __d("Forms", "Search.Filter.Users.NoFilterOnIsUserOf"),
            "true" => __d("Forms", "Search.Filter.Users.IsUserOf"),
265
266
        ];

Matthieu FAURE's avatar
Matthieu FAURE committed
267
        $isServiceProvider = [
268
269
            "false" => __d("Forms", "Search.Filter.Users.NoFilterOnIsServicesProvider"),
            "true" => __d("Forms", "Search.Filter.Users.IsServicesProvider"),
270
271
272
        ];

        $order = [
273
274
275
276
277
278
            "username.asc" => __d("Forms", "Search.Sort.usernameAsc"),
            "username.desc" => __d("Forms", "Search.Sort.usernameDesc"),
            "created.asc" => __d("Forms", "Search.Sort.createdAsc"),
            "created.desc" => __d("Forms", "Search.Sort.createdDesc"),
            "modified.asc" => __d("Forms", "Search.Sort.modifiedAsc"),
            "modified.desc" => __d("Forms", "Search.Sort.modifiedDesc"),
279
        ];
280
        //End For filters
281
282
283
284
285

        $this->set('hadReview', $hadReview);
        $this->set('userType', $userType);
        $this->set('isUserOf', $isUserOf);
        $this->set('order', $order);
Matthieu FAURE's avatar
Matthieu FAURE committed
286
        $this->set('isServiceProvider', $isServiceProvider);
287

288
289
        $this->set(
            [
Matthieu FAURE's avatar
Matthieu FAURE committed
290
291
                'users' => $this->paginate($users),
                '_serialize' => ['users']
Julie gauthier's avatar
Julie gauthier committed
292
293
            ]
        );
Fabrice Gangler's avatar
Fabrice Gangler committed
294
        $this->setBreadcrumbs();
295
296
    }

Pastor Mickaël's avatar
CRUD  
Pastor Mickaël committed
297
298
299
300
    /**
     * View method
     *
     * @param string|null $id User id.
301
302
     *
     * @return void
303
304
     * @throws NotFoundException When record not found.
     *
Pastor Mickaël's avatar
CRUD  
Pastor Mickaël committed
305
306
307
     */
    public function view($id = null)
    {
308
        $user = $this->Users->get(
309
310
            $id,
            [
Matthieu FAURE's avatar
Matthieu FAURE committed
311
312
313
314
315
316
317
318
319
320
321
322
323
324
                'contain' => [
                    'UserTypes',
                    'Reviews' => [
                        'strategy' => 'select',
                        'queryBuilder' => function ($q) {
                            return $q->order(['Reviews.created' => 'DESC']);
                        },
                        'Softwares' => [
                            'fields' => [
                                'id',
                                'softwarename',
                                'logo_directory',
                                'photo',
                            ]
325
                        ]
Matthieu FAURE's avatar
Matthieu FAURE committed
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
                    ],
                    'Usedsoftwares' => [
                        'strategy' => 'select',
                        'queryBuilder' => function (Query $q) {
                            return $q->order(['Softwares.softwarename' => 'ASC']);
                        },
                        'Softwares' => [
                            'Reviews' => [
                            ],
                            'fields' => [
                                'id',
                                'softwarename',
                                'logo_directory',
                                'photo',
                                'description',
                            ]
342
                        ]
Matthieu FAURE's avatar
Matthieu FAURE committed
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
                    ],
                    "Providerforsoftwares" => [
                        'strategy' => 'select',
                        'queryBuilder' => function ($q) {
                            return $q->order(['Softwares.softwarename' => 'ASC']);
                        },
                        'Softwares' => [
                            'Reviews' => [
                            ],
                            'fields' => [
                                'id',
                                'softwarename',
                                'logo_directory',
                                'photo',
                                'description'
                            ]
359
                        ],
360
                    ],
Matthieu FAURE's avatar
Matthieu FAURE committed
361
                ]
362
363
            ]
        );
364

365
366
367
368
369
370
371
        // Check that the current URL is correct
        $lang = $this->selectedLanguage;
        $allowedUrl = "/$lang/users/". (int) $id;
        if ($allowedUrl !== $this->request->here(false) && !$this->request->is('json')) {
            return $this->redirect("$allowedUrl", 301);
        }

372
        // Load mapping data if available for current user and populate view data
373
        $this->commonMappingForUser($user);
374

375
376
        //For Social MEDIAS => OPENGRAPH
        $openGraph = [
377
            "title" => $user->username,
378
            "description" => $user->description,
379
            "image" => Configure::read('App.fullBaseUrl') . DS . $user->logo_directory . DS . $user->photo,
380
381
382
383
            "imageSize" => ['width' => '200', 'height' => '300']
        ];

        $card = [
384
            "title" => $user->username,
385
            "description" => $user->description,
386
            "image" => Configure::read('App.fullBaseUrl') . DS . $user->logo_directory . DS . $user->photo,
387
388
389
390
391
392
            "imageSize" => ['width' => '200', 'height' => '300']
        ];

        $this->set('openGraph', $openGraph);
        $this->set('card', $card);

393
        $this->set(compact('user'));
394
        $this->set('_serialize', ['user']);
Fabrice Gangler's avatar
Fabrice Gangler committed
395
396
397
398
399
400
401
402

        // Breadcrumbs
        $links = array();
        $links[] = [
            'name' => $user->username,
            'url' => "users/".$user->id
        ];
        $this->setBreadcrumbsUser($links, $user);
Pastor Mickaël's avatar
CRUD  
Pastor Mickaël committed
403
404
    }

405
406
407
408
    /**
     * Load mapping data if available for given user ID and populate view data
     * @param int $userId
     */
409
    protected function commonMappingForUser(User $user)
410
    {
411
412
        $taxonomiesSoftware = [];
        $usedSoftwareKeys = [];
413
414
        $mappingFirstLevels = [];
        $mappingTaxons = [];
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450

        $userId = $user->id;
        if ($user->isAdministrationType() && count($user->usedsoftwares) > 0) {
            // Get taxonomy records grouped by taxon ID for this user
            $this->loadModel("TaxonomysSoftwares");
            $taxonomiesSoftware = $this->TaxonomysSoftwares->getListByUserId($userId);

            // Load mapping data if necessary
            $mappingFirstLevels = [];
            $mappingTaxons = [];
            if (count($taxonomiesSoftware) > 0) {
                // Load mapping data
                $taxonIds = array_keys($taxonomiesSoftware);
                $mappingFirstLevels = $this->getMappingFirstLevels($this->selectedLanguage);
                $mappingTaxons = $this->getMappingTaxonsWithTaxonIdsFilter($taxonIds, $this->selectedLanguage);

                // Extract used software keys and software names
                $softwareNameList = [];
                foreach ($user->usedsoftwares as $usedSoftwareKey => $usedSoftware) {
                    $softwareId = $usedSoftware->software->id;
                    $softwareNameList[$softwareId] = $usedSoftware->software->softwarename;
                    $usedSoftwareKeys[$softwareId] = $usedSoftwareKey;
                }

                // Sort records by software name
                foreach ($taxonomiesSoftware as $taxonId => $taxonData) {
                    $taxonDataSorted = [];
                    foreach ($taxonData as $item) {
                        $softwareId = $item->software_id;
                        $softwareName = $softwareNameList[$softwareId];
                        $taxonDataSorted[$softwareName] = $item;
                    }
                    ksort($taxonDataSorted);
                    $taxonomiesSoftware[$taxonId] = $taxonDataSorted;
                }
            }
451
452
453
454
455
        }

        // Populate view data
        $this->set(compact(['mappingFirstLevels']));
        $this->set(compact(['mappingTaxons']));
456
        $this->set(compact(['usedSoftwareKeys']));
457
        $this->set(compact(['taxonomiesSoftware']));
458
459
460
461
462
463
        $this->set('_serialize', [
            'taxonomiesSoftware',
            'usedSoftwareKeys',
            'mappingTaxons',
            'mappingFirstLevels'
        ]);
464
465
466
    }


Pastor Mickaël's avatar
CRUD  
Pastor Mickaël committed
467
468
469
470
471
472
473
    /**
     * Add method
     *
     * @return void Redirects on successful add, renders view otherwise.
     */
    public function add()
    {
474
475
476
477
478
        if (!empty($this->request->data)) {
            $user = $this->Users->newEntity($this->request->data);
        } else {
            $user = $this->Users->newEntity();
        }
479

480
        $message = "";
481
        // Get the avatar before unset it to save the user.
482
        // The Upload plugin need an existing entity to attach a file to it.
483
484
485
        // Note JGauthier 2019-08-28: This way of doing things does not seem to be correct.
        // The addition of a user should be done in one step. I checked up the plugin doc but it's not accurate enough.
        // Here's the doc: https://cakephp-upload.readthedocs.io/en/latest/examples.html
486
        if ($this->request->is('post')) {
487
            if (isset($this->request->data['photo']) && !$user->errors()) {
488
489
490
491
                $avatar = $this->request->data['photo'];
                $this->request->data['photo'] = "";
            }

492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
           // the "role" field needs a different treatment in production
           // to avoid that a user can be added with an "admin" role
           // via the form or the json API.
            if (Configure::read('debug') !== true) {
                // in production:
                // the role of a new user must always
                // be "User" (via the form or the json API)
                $this->request->data['role'] = 'User';
            } else {
                // If debug mode is enabled,
                // we must allow the addition of a user via the form
                // by adding the missing field "role" with the value "user".
                if (isset($this->request->data['submit_signUpForm'])) {
                    $this->request->data['role'] = 'User';
                }
                // The "else" part of this "if"
                // concerns the JSON interrogation (used only by unit test), and thus is not relevant
            }

Pastor Mickaël's avatar
CRUD  
Pastor Mickaël committed
511
            $user = $this->Users->patchEntity($user, $this->request->data);
512

513
            if ($this->Users->save($user)) {
514
                $user = $this->Users->get($user->id, ['contain' => []]);
515

516
                isset($avatar) ? $this->request->data['photo'] = $avatar : null;
517

518
                $user = $this->Users->patchEntity($user, $this->request->data);
519
520
                if ($this->Users->save($user)) {
                    $message = "Success";
521
                    $this->Flash->success(__d("Forms", "Your are registred on the Comptoir du Libre, welcome !"));
522
                    if (!$this->request->is('json')) {
523
524
525
526
527
528
                        $currentUser = $this->Auth->identify();
                        $currentUser["user_type"] = $this->Users
                            ->UserTypes
                            ->get($currentUser["user_type_id"])->get("name");
                        $this->Auth->setUser($currentUser);

529
530
531
                        // REDIRECTS TO ---> /<lang>/users/<idUser>
                        $lang = $this->selectedLanguage;
                        return $this->redirect("/$lang/users/" . $user->get('id'));
532
533
                        // REDIRECTS TO ---> /api/v1/users/view/<idUser>
                        // return $this->redirect(['action' => 'view', $user->get('id')]);
534
                    }
535
536
                } else {
                    $message = "Error";
537
                }
538
            } else {
539
                $message = "Error";
540
                $this->Flash->error(__d("Forms", "Your registration failed, please follow rules in red."));
541
            }
542
            $message == "Error" ? $this->set('errors', $user->errors()) : null;
543
        }
544
545
546
547
        $this->ValidationRules->config('tableRegistry', "Users");
        $rules = $this->ValidationRules->get();
        $userTypes = $this->Users->UserTypes->find('list', ['limit' => 200]);
        $this->set(compact('user', 'userTypes', 'rules', 'message'));
548
        $this->set('_serialize', ['user', 'userTypes', 'rules', 'message', 'errors']);
Fabrice Gangler's avatar
Fabrice Gangler committed
549
550
551
552
553
554
555

        // Breadcrumbs
        $links[] = [
            'name' => __d('Breadcrumbs', 'Users.SignUp'),
            'url' => "users/add"
        ];
        parent::setBreadcrumbs($links);
Pastor Mickaël's avatar
CRUD  
Pastor Mickaël committed
556
557
    }

Pastor Mickaël's avatar
Pastor Mickaël committed
558
    /**
559
560
561
562
563
     * Allow a user to request a password reset. Sends an email to the user containing a link to change his password.
     *
     * @param string $token Token generated.
     *
     * @return void Redirects on login page or , plus successful message.
Pastor Mickaël's avatar
Pastor Mickaël committed
564
     */
Matthieu FAURE's avatar
Matthieu FAURE committed
565
    public function forgotPassword($token = null)
566
    {
Pastor Mickaël's avatar
Pastor Mickaël committed
567
        $user = $this->Users->newEntity();
568
        $message = "";
569
        if (!$token) {
Pastor Mickaël's avatar
Pastor Mickaël committed
570
571
            if (!empty($this->request->data)) {
                $user = $this->Users->find('byEmail', ['user' => $this->request->data["email"]])->first();
572
573

                if (!$user) {
574
575
                    $this->Flash->error(__d("Forms", 'Sorry, the email entered was not found.'));
                    $message = __d("Forms", 'Sorry, the email entered was not found.');
Pastor Mickaël's avatar
Pastor Mickaël committed
576
                } else {
Matthieu FAURE's avatar
Matthieu FAURE committed
577
                    $user = $this->generatePasswordToken($user);
Pastor Mickaël's avatar
Pastor Mickaël committed
578

Matthieu FAURE's avatar
Matthieu FAURE committed
579
580
581
                    if ($this->Users->save($user)
                        && $this->getMailer("User")->send("resetPassword", ["user" => $user])
                    ) {
582
                        $this->Flash->success(__d("Forms", 'password.reset.instructions.have.been.sent'));
583
                        $this->redirect(["prefix" => false, 'controller' => "users", "action" => "login"]);
Pastor Mickaël's avatar
Pastor Mickaël committed
584
585
586
587
                    }
                }
            }
        }
588
589
        $this->set(compact('message', 'user'));
        $this->set('_serialize', ['message', 'user']);
Fabrice Gangler's avatar
Fabrice Gangler committed
590
591
592
593
594
595
596
597
598
599
600
        // Breadcrumbs
        $links = array();
        $links[] = [
            'name' => __d('Breadcrumbs', 'Users.Login'),
            'url' => "users/login"
        ];
        $links[] = [
            'name' => __d('Breadcrumbs', 'Users.ForgotPassword'),
            'url' => "users/forgot-password"
        ];
        parent::setBreadcrumbs($links);
Pastor Mickaël's avatar
Pastor Mickaël committed
601
602
603
604
    }

    /**
     * Generate a unique hash / token.
605
606
607
     *
     * @param Object $user User
     *
Pastor Mickaël's avatar
Pastor Mickaël committed
608
609
     * @return Object User
     */
Matthieu FAURE's avatar
Matthieu FAURE committed
610
    private function generatePasswordToken($user)
611
    {
Pastor Mickaël's avatar
Pastor Mickaël committed
612
        // Generate an 'token'
613
614
        $user->token_plain
            = Text::uuid();
615
        $user->token = $user->token_plain;
Pastor Mickaël's avatar
Pastor Mickaël committed
616
617
        return $user;
    }
618

Pastor Mickaël's avatar
Pastor Mickaël committed
619
    /**
Matthieu FAURE's avatar
Matthieu FAURE committed
620
     * Unregistered user can change their password if they forgot it thanks to their email.
621
     *
Matthieu FAURE's avatar
Matthieu FAURE committed
622
     * @param null $token Token value.
623
     *
624
     * @return void Redirects on successful, renders view otherwise.
Pastor Mickaël's avatar
Pastor Mickaël committed
625
     */
Matthieu FAURE's avatar
Matthieu FAURE committed
626
    public function resetPassword($token = null)
627
    {
628
        $lang = $this->selectedLanguage;
629
        $message = "";
Matthieu FAURE's avatar
Matthieu FAURE committed
630
631
        $result = $this->changePasswordByToken($token);
        $result ? $message = $result["message"] : $message = "Error";
632
        $user = $result["user"];
Matthieu FAURE's avatar
Matthieu FAURE committed
633

634
635
636
637
638
639
640
641
642
643
644
645
646
        if ($message === 'Error_notValidToken') {
            $this->Flash->error(__d("Forms", "forgotPassord.tokenNotValid"));
            return $this->redirect("/$lang/users/forgot-password", 302);
        } elseif ($message === 'Success') {
            $this->Flash->success(__d("Forms", "Your password has been changed."));
            return $this->redirect("/$lang/users/login", 302);
        } else {
            $this->ValidationRules->config('tableRegistry', "Users");
            $rules = $this->ValidationRules->get();
            $userTypes = $this->Users->UserTypes->find('list', ['limit' => 200]);
            $this->set(compact('user', 'userTypes', 'rules', 'message'));
            $this->set('_serialize', ['user', 'userTypes', 'rules', 'message']);
        }
647
648
649
    }

    /**
650
651
652
653
     * Allow a user to change his password using a token
     *
     * @param string $token A unique token given via email.
     *
654
655
     * @return array
     */
Matthieu FAURE's avatar
Matthieu FAURE committed
656
    private function changePasswordByToken($token)
657
    {
658
        $message = '';
659
        $user = $this->Users->newEntity();
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
        $userByToken = $this->Users->findByToken($token, ["contain" => []])->first();
        $tokenValidityInHours = self::TOKEN_VALIDITY_TIME_TO_CHANGE_PASSWORD;
        if (is_null($userByToken)) {
            // This token does not exist in the database.
            $message = "Error_notValidToken";
        } elseif (false === $userByToken->modified->wasWithinLast("$tokenValidityInHours hours")) {
            // This token has expired (based on the user's modification date in the database)
            $user = $this->Users->patchEntity($userByToken, ['token' => null]);
            $this->Users->save($user);
            $message = "Error_notValidToken";
        } else {
            $this->viewBuilder()->template("reset_password");
            if ($this->request->is('POST') && $userByToken) {
                $user = $this->Users->patchEntity(
                    $userByToken,
                    [
                        'password' => $this->request->data['new_password'],
                        'confirm_password' => $this->request->data['confirm_password'],
                        'token' => null,
                    ]
                );
                if ($this->Users->save($user)) {
                    $message = "Success";
                } else {
                    $message = "Error";
                    $this->Flash->error(__d("Forms", "Your password can not be changed, please follow rules in red."));
                }
687
            }
688
689
        }
        return ["message" => $message, "user" => $user];
690
691
692
    }

    /**
Matthieu FAURE's avatar
Matthieu FAURE committed
693
     * Change the password for a current user logged.
694
     *
Matthieu FAURE's avatar
Matthieu FAURE committed
695
     * @param null $id Id of current user.
696
697
     *
     * @return void Redirects on successful edit, renders view otherwise.
698
     */
Matthieu FAURE's avatar
Matthieu FAURE committed
699
    public function changePassword($id = null)
700
    {
701
        $message = "";
Matthieu FAURE's avatar
Matthieu FAURE committed
702
703
        $user = $this->Users->get($id);
        $result = $this->changePasswordById($user);
704
705
706
707
708
709
710
711
712
713
714
715
716
        $user = $result["user"];
        $result ? $message = $result["message"] : $message = "Error";

        $message == "Success" ? $this->redirect(
            [
                "prefix" => false,
                "controller" => "Users",
                "action" => "edit",
                "language" => $this->request->param("language"),
                $user->id
            ]
        ) : null;

Matthieu FAURE's avatar
Matthieu FAURE committed
717

718
719
720
721
722
723
724
725
        $this->ValidationRules->config('tableRegistry', "Users");
        $rules = $this->ValidationRules->get();
        $userTypes = $this->Users->UserTypes->find('list', ['limit' => 200]);
        $this->set(compact('user', 'userTypes', 'rules', 'message'));
        $this->set('_serialize', ['user', 'userTypes', 'rules', 'message']);
    }

    /**
Matthieu FAURE's avatar
Matthieu FAURE committed
726
     * Change the password of a current user logged.
727
     *
Matthieu FAURE's avatar
Matthieu FAURE committed
728
     * @param array $user Current user.
729
     *
Matthieu FAURE's avatar
Matthieu FAURE committed
730
     * @return array Returns an user entity or errors regarding the user entity, plus a message (Success or Error )
731
     */
Matthieu FAURE's avatar
Matthieu FAURE committed
732
    private function changePasswordById($user)
733
734
    {
        $message = "";
Matthieu FAURE's avatar
Matthieu FAURE committed
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
        if ($this->request->is(['put', 'post'])) {
            if (!empty($this->request->data) && $user) {
                $user = $this->Users->patchEntity(
                    $user,
                    [
                        'old_password' => $this->request->data['old_password'],
                        'password' => $this->request->data['new_password'],
                        'confirm_password' => $this->request->data['confirm_password']
                    ],
                    ['validate' => 'password']
                );
                if ($this->Users->save($user)) {
                    $message = "Success";
                    $this->Flash->success(__d("Forms", "Your password has been changed."));
                } else {
                    $message = "Error";
                    $this->Flash->error(__d("Forms", "Your password can not be changed, please follow rules in red."));
                }
            }
        }
        return ["message" => $message, "user" => $user];
Pastor Mickaël's avatar
Pastor Mickaël committed
756
757
    }

Pastor Mickaël's avatar
CRUD  
Pastor Mickaël committed
758
759
760
761
    /**
     * Edit method
     *
     * @param string|null $id User id.
762
763
     *
     * @return void Redirects on successful edit, renders view otherwise.
Matthieu FAURE's avatar
Matthieu FAURE committed
764
     * @throws NotFoundException When record not found.
765
     *
Pastor Mickaël's avatar
CRUD  
Pastor Mickaël committed
766
767
768
     */
    public function edit($id = null)
    {
769
        $user = $this->Users->get(
770
771
            $id,
            [
Fabrice Gangler's avatar
Fabrice Gangler committed
772
                'contain' => ['UserTypes']
773
774
            ]
        );
775

776
777
778
779
        if ($this->request->is(['patch', 'post', 'put'])) {
            $data = $this->request->data;
            if (isset($data['role'])) {
                unset($data['role']);
Pastor Mickaël's avatar
CRUD  
Pastor Mickaël committed
780
            }
781
782
783
784
785
786
787
788
789
790
            $user = $this->Users->patchEntity($user, $data);
            if ($this->request->is('json')) {
                if ($this->Users->save($user)) {
                    $message = "Success";
                } else {
                    $message = "Error";
                    $this->set('errors', $user->errors());
                }
            } else {
                if ($this->Users->save($user)) {
791
792
793
                    // update username in session
                    $session = $this->request->session();
                    $session->write('Auth.User.username', $user->username);
794

795
                    $this->Flash->success(__d("Forms", "Edit.User.profile.success"));
Matthieu FAURE's avatar
Matthieu FAURE committed
796
797
798
799
800
801
802
803
804

                    // We would have written:
                    //   return $this->redirect(['action' => 'view', $user->get('id')]);
                    // but this generates the following URL
                    //   /api/v1/users/view/<idUser>
                    // and we want to have the following one:
                    //   /users/<idUser>
                    // so to write thie following statement:
                    return $this->redirect("users/" . $user->get('id'));
805
806
                } else {
                    $message = "Error";
807

808
809
                    $this->Flash->error(__d("Forms", "Edit.User.profile.error"));
                }
810
811
            }
        }
812
813
814
815

        $this->ValidationRules->config('tableRegistry', "Users");
        $rules = $this->ValidationRules->get();
        $userTypes = $this->Users->UserTypes->find('list', ['limit' => 200]);
816
817
        $this->set(compact('user', 'userTypes', 'rules', 'message'));
        $this->set('_serialize', ['user', 'userTypes', 'rules', 'message', 'errors']);
Fabrice Gangler's avatar
Fabrice Gangler committed
818
819
820
821
822
823
824
825
826
827
828
829

        // Breadcrumbs
        $links = array();
        $links[] = [
            'name' => $user->username,
            'url' => "users/".$user->id
        ];
        $links[] = [
            'name' => __d('Breadcrumbs', 'User.edit'),
            'url' => "users/edit/".$user->id
        ];
        $this->setBreadcrumbsUser($links, $user);
Pastor Mickaël's avatar
Co/deco    
Pastor Mickaël committed
830
    }
831

832
833
834
835
836
    /**
     * Display a list of user who are only service providers.
     *
     * @return void Redirects on successful edit, renders view otherwise.
     */
837
    public function providers()
838
    {
839
840
841
        $this->viewBuilder()->template("users_list");
        $this->set("title", "List of service providers");

Pastor Mickaël's avatar
Pastor Mickaël committed
842
843
        try {
            $users = $this->Users->find("all")->contain(["UserTypes"])
844
                ->where(["UserTypes.cd = " => "Company"]);
845
846
            $this->set(
                [
Matthieu FAURE's avatar
Matthieu FAURE committed
847
848
849
                    'message' => "Success",
                    'users' => $this->paginate($users),
                    '_serialize' => ['message', 'users']
850
851
                ]
            );
852
        } catch (Exception $e) {
853
        }
Fabrice Gangler's avatar
Fabrice Gangler committed
854
855
856
857
858
859
860
861

        // Breadcrumbs
        $links = array();
        $links[] = [
            'name' => __d('Breadcrumbs', 'Users.Providers'),
            'url' => "users/providers"
        ];
        parent::setBreadcrumbs($links);
862
863
    }

Pastor Mickaël's avatar
Pastor Mickaël committed
864
865
    /**
     * Return an user with all used softwares
866
867
868
869
     *
     * @param string|null $id User id.
     *
     * @return void
Pastor Mickaël's avatar
Pastor Mickaël committed
870
     */
871
    public function usedSoftwares($id = null)
Pastor Mickaël's avatar
Pastor Mickaël committed
872
    {
873
874
        $user = $this->Users->get(
            $id,
Matthieu FAURE's avatar
Matthieu FAURE committed
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
            [
                "contain" =>
                    [
                        'Usedsoftwares' =>
                            [
                                'strategy' => 'select',
                                'queryBuilder' => function ($q) {
                                    return $q->order(['Softwares.softwarename' => 'ASC']);
                                },
                                'Softwares' =>
                                    [
                                        'Reviews' => [
                                        ],
                                        'fields' => [
                                            'id',
                                            'softwarename',
                                            'logo_directory',
                                            'photo',
                                            'description'
                                        ]
895
                                    ]
Matthieu FAURE's avatar
Matthieu FAURE committed
896
897
898
                            ],
                        'UserTypes',
                    ]
899
900
            ]
        );
Pastor Mickaël's avatar
Pastor Mickaël committed
901

902
903
904
905
906
907
908
        // Check that the current URL is correct
        $lang = $this->selectedLanguage;
        $allowedUrl = "/$lang/users/usedSoftwares/". (int) $id;
        if ($allowedUrl !== $this->request->here(false)) {
            return $this->redirect("$allowedUrl", 301);
        }

Pastor Mickaël's avatar
Pastor Mickaël committed
909
        $this->set(compact('user'));
910
        $this->set('_serialize', ['user']);
Fabrice Gangler's avatar
Fabrice Gangler committed
911
912
913
914
915
916
917
918
919
920
921
922

        // Breadcrumbs
        $links = array();
        $links[] = [
            'name' => $user->username,
            'url' => "users/".$user->id
        ];
        $links[] = [
            'name' => __d('Breadcrumbs', 'Users.UsedSoftwares'),
            'url' => "users/usedSoftwares/".$user->id
        ];
        $this->setBreadcrumbs($links);
Pastor Mickaël's avatar
Pastor Mickaël committed
923
924
925
926
927
    }


    /**
     * Returns a user with a list of software as know as services provider
928
929
930
931
     *
     * @param string|null $id User id.
     *
     * @return void
Pastor Mickaël's avatar
Pastor Mickaël committed
932
     */
933
    public function providerforSoftwares($id = null)
Pastor Mickaël's avatar
Pastor Mickaël committed
934
    {
935
936
        $user = $this->Users->get(
            $id,
Matthieu FAURE's avatar
Matthieu FAURE committed
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
            [
                "contain" =>
                    [
                        'UserTypes',
                        'Providerforsoftwares' =>
                            [
                                'strategy' => 'select',
                                'queryBuilder' => function ($q) {
                                    return $q->order(['Softwares.softwarename' => 'ASC']);
                                },
                                'Softwares' => [
                                    'Reviews' => [
                                    ],
                                    'fields' => [
                                        'id',
                                        'softwarename',
                                        'logo_directory',
                                        'photo',
                                        'description'
                                    ]
Pastor Mickaël's avatar
Pastor Mickaël committed
957
958
                                ]
                            ]
Matthieu FAURE's avatar
Matthieu FAURE committed
959
                    ]
960
961
            ]
        );
Pastor Mickaël's avatar
Pastor Mickaël committed
962

963
964
965
966
967
968
969
        // Check that the current URL is correct
        $lang = $this->selectedLanguage;
        $allowedUrl = "/$lang/users/providerforSoftwares/". (int) $id;
        if ($allowedUrl !== $this->request->here(false)) {
            return $this->redirect("$allowedUrl", 301);
        }

Pastor Mickaël's avatar
Pastor Mickaël committed
970
        $this->set(compact('user'));
971
        $this->set('_serialize', ['user']);
Fabrice Gangler's avatar
Fabrice Gangler committed
972
973
974
975
976
977
978
979
980
981
982
983

        // Breadcrumbs
        $links = array();
        $links[] = [
            'name' => $user->username,
            'url' => "users/".$user->id
        ];
        $links[] = [
            'name' => __d('Breadcrumbs', 'Users.providerforSoftwares'),
            'url' => "users/providerforSoftwares/".$user->id
        ];
        $this->setBreadcrumbsUser($links, $user);
Pastor Mickaël's avatar
Pastor Mickaël committed
984
985
    }

986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000


    /**
     * load a user with a list of his/her reviews
     *
     * @param string|null $id User id.     *
     * @return void
     */
    public function reviewsforSoftware($id = null)
    {
        $user = $this->Users->get(
            $id,
            [
                "contain" =>
                    [