Issues (994)

src/User/user.php (9 issues)

1
<?php
2
3
namespace User;
4
5
use Crypto\crypt;
6
use DB\pdo;
7
use JSON\json;
8
9
$GLOBALS['user_instance'] = null;
10
11
/**
12
 * Universal Framework User Management.
13
 *
14
 * @author Dimas Lanjaka <[email protected]>
15
 */
16
class user
17
{
18
  public $admin_pattern = '/^superadmin$/s';
19
  private static $_instance = null;
20
  public $dbname = 'userdata';
21
  private $id;
22
  private $username;
23
  private $password;
24
  private $role;
25
  private $user;
26
  /**
27
   * PDO instance.
28
   *
29
   * @var \DB\pdo
30
   */
31
  private $pdo = null;
32
  private $db = ['user', 'pass', 'dbname', 'host', 'charset'];
33
34
  public function __construct($user = 'root', $pass = '', $db, $host = 'localhost', $charset = 'utf8mb4')
35
  {
36
    if (!empty($user) && !empty($db)) {
37
      $this->db = [
38
        'user' => $user,
39
        'pass' => $pass,
40
        'dbname' => $db,
41
        'host' => $host,
42
        'charset' => $charset,
43
      ];
44
      $this->pdo = new \DB\pdo($user, $pass, $db, $host, $charset);
45
      $this->pdo->connect($user, $pass, $db, $host, $charset);
46
    }
47
    if (!$GLOBALS['user_instance']) {
48
      $GLOBALS['user_instance'] = $this;
49
    }
50
    user::$_instance = $this;
51
  }
52
53
  /**
54
   * Current instance.
55
   *
56
   * @return user
57
   */
58
  public static function instance()
59
  {
60
    return $GLOBALS['user_instance'];
61
  }
62
63
  public function db($data)
64
  {
65
    if (isset($this->db[$data])) {
66
      return $this->db[$data];
67
    }
68
  }
69
70
  /**
71
   * Access Management
72
   *
73
   * @return access
74
   */
75
  public function access()
76
  {
77
    return new access($this);
78
  }
79
80
  /**
81
   * Get All Users.
82
   *
83
   * @return void
84
   */
85
  public function getUsers()
86
  {
87
    if (!$this->pdo) {
88
      throw new \MVC\Exception('Database not properly configured', 1);
89
    }
90
    $result = [];
91
    $users = $this->pdo->select($this->dbname)->row_array();
92
    if (!\ArrayHelper\helper::isSequent($users)) {
0 ignored issues
show
It seems like $users can also be of type null; however, parameter $source of ArrayHelper\helper::isSequent() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

92
    if (!\ArrayHelper\helper::isSequent(/** @scrutinizer ignore-type */ $users)) {
Loading history...
93
      $result[] = $users;
94
    } else {
95
      $result = $users;
96
    }
97
98
    return $result;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $result also could return the type array|array<mixed,array|null> which is incompatible with the documented return type void.
Loading history...
99
  }
100
101
  /**
102
   * Get PDO Instance.
103
   *
104
   * @param \DB\pdo $pdo set new instance
105
   *
106
   * @return \DB\pdo
107
   */
108
  public function pdo_instance(\DB\pdo $pdo = null)
109
  {
110
    if ($pdo) {
111
      $this->pdo = $pdo;
112
      self::$_instance = $this;
113
    }
114
115
    return $this->pdo;
116
  }
117
118
  /**
119
   * Static Chain.
120
   *
121
   * @return $this
122
   */
123
  public static function getInstance()
124
  {
125
    if (null === self::$_instance) {
126
      if (defined('CONFIG')) {
127
        self::$_instance = new self(CONFIG['database']['user'], CONFIG['database']['pass'], CONFIG['database']['dbname'], CONFIG['database']['host']);
128
      } else {
129
        self::$_instance = new self(null, null, null, null, null);
130
      }
131
    }
132
133
    return self::$_instance;
134
  }
135
136
  public static function setInstance(\User\user $user)
137
  {
138
    self::$_instance = $user;
139
  }
140
141
  /**
142
   * Meta instance.
143
   *
144
   * @var \User\meta
145
   */
146
  private $meta_instance;
147
148
  /**
149
   * Get \User\meta instance.
150
   *
151
   * @return meta
152
   */
153
  public function meta()
154
  {
155
    $this->pdo_required();
156
    if (!$this->meta_instance) {
157
      $this->meta_instance = new meta($this->pdo);
158
    }
159
160
    return $this->meta_instance;
161
  }
162
163
  /**
164
   * Check user can do something.
165
   *
166
   * @return bool
167
   */
168
  public function can(string $what)
0 ignored issues
show
The parameter $what is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

168
  public function can(/** @scrutinizer ignore-unused */ string $what)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
169
  {
170
    if ($this->is_login()) {
171
      $check = $this->meta()->get($this->userdata('id'), 'can');
0 ignored issues
show
Are you sure the assignment to $check is correct as $this->meta()->get($this->userdata('id'), 'can') targeting User\meta::get() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
172
      if (!empty($check)) {
173
        return $check;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $check returns the type void which is incompatible with the documented return type boolean.
Loading history...
174
      }
175
    }
176
  }
177
178
  /**
179
   * Check pdo active.
180
   *
181
   * @throws \MVC\Exception
182
   *
183
   * @return void
184
   */
185
  public function pdo_required()
186
  {
187
    if (!$this->pdo) {
188
      throw new \MVC\Exception('Active PDO Required');
189
    }
190
  }
191
192
  public static function get_admin_pattern()
193
  {
194
    return self::getInstance()->admin_pattern;
195
  }
196
197
  /**
198
   * Check current user is superadmin.
199
   *
200
   * @return bool
201
   */
202
  public function is_admin()
203
  {
204
    if ($this->is_login()) {
205
      return preg_match($this->admin_pattern, $this->get_role());
0 ignored issues
show
Bug Best Practice introduced by
The expression return preg_match($this-...ern, $this->get_role()) returns the type integer which is incompatible with the documented return type boolean.
Loading history...
206
    }
207
  }
208
209
  /**
210
   * If not admin auto redirect.
211
   *
212
   * @return void
213
   */
214
  public function admin_required(string $redirect = '/signin')
215
  {
216
    if (!$this->is_admin()) {
217
      \MVC\router::safe_redirect($redirect);
218
      exit;
0 ignored issues
show
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
219
    }
220
  }
221
222
  /**
223
   * Get Current user role.
224
   *
225
   * @return string
226
   */
227
  public function get_role()
228
  {
229
    return isset($_SESSION['login']['role']) ? $_SESSION['login']['role'] : 'UNAUTHORIZED';
230
  }
231
232
  /**
233
   * Get All Available Roles.
234
   *
235
   * @return array
236
   */
237
  public function get_roles()
238
  {
239
    if (!$this->pdo) {
240
      throw new \MVC\Exception('PDO instance required', 1);
241
    }
242
    $list = \DB\schema::get_enumset_values($this->pdo, $this->dbname, 'role');
243
244
    return $list;
245
  }
246
247
  /**
248
   * Get user data.
249
   *
250
   * @return array|int|string|null if empty not logged in
251
   */
252
  public function userdata(string $what)
253
  {
254
    if ($this->is_login()) {
255
      if (class_exists('\Indosat\api')) {
256
        $class = '\Indosat\api';
257
        $m3 = new $class();
258
        $m3->setdata('login', \ArrayHelper\helper::unset($_SESSION['login'], ['password']));
259
      }
260
      if ('all' == $what) {
261
        return \ArrayHelper\helper::unset($_SESSION['login'], ['password']);
262
      }
263
      if (isset($_SESSION['login'][$what])) {
264
        return $_SESSION['login'][$what];
265
      }
266
    }
267
268
    return null;
269
  }
270
271
  /**
272
   * Get user data.
273
   *
274
   * @see \User\user::userdata
275
   */
276
  public function data(string $name)
277
  {
278
    return $this->userdata($name);
279
  }
280
281
  public function update_last_seen()
282
  {
283
    if ($this->is_login()) {
284
      $currentUsername = $this->userdata('username');
285
      $run = $this
286
        ->pdo
287
        ->query("UPDATE `userdata` SET `last_seen` = NOW() WHERE `username` = '{$currentUsername}';")
288
        ->exec();
289
290
      return $run;
291
    }
292
  }
293
294
  /**
295
   * Change password.
296
   *
297
   * @param int    $id   user id want to update
298
   * @param string $pass new password
299
   */
300
  public function update_password($id, $pass)
301
  {
302
    if (!$this->pdo) {
303
      throw new \MVC\Exception('Database not properly configured', 1);
304
    }
305
    if (!is_numeric($id)) {
0 ignored issues
show
The condition is_numeric($id) is always true.
Loading history...
306
      throw new \MVC\Exception('User ID must be instance of integer', 1);
307
    }
308
    $db = $this->dbname;
309
    $crypt = new crypt();
310
    $pass = $crypt->encrypt('dimaslanjaka', $pass);
311
    if (!$this->is_admin()) {
312
      $q = "UPDATE `$db` SET `password` = :pass WHERE `$db`.`id` = :id AND `$db`.`role` <> 'superadmin' AND `$db`.`role` <> 'admin';";
313
    } else {
314
      $q = "UPDATE `$db` SET `password` = :pass WHERE `$db`.`id` = :id;";
315
    }
316
    $this->pdo->SQL_Exec($q, ['id' => $id, 'pass' => $pass]);
317
    $result = [];
318
    $chk = $this->pdo->SQL_Fetch("SELECT * FROM `$db` WHERE `$db`.`id` = :id AND `$db`.`password` = :pass", ['id' => $id, 'pass' => $pass]);
319
    if (!empty($chk) && isset($chk['id'])) {
320
      $result['error'] = false;
321
      $result['message'] = 'Password changed successfully';
322
      $result['title'] = 'User information';
323
    } else {
324
      $result['error'] = true;
325
    }
326
    $result = $this->hide_additional(array_merge($result, $chk));
327
328
    return $result;
329
  }
330
331
  public function delete_user_by_id($id)
332
  {
333
    $db = $this->dbname;
334
    $q = "DELETE FROM `$db` WHERE `$db`.`id` = :id";
335
    $this->pdo->SQL_Exec($q, ['id' => $id]);
336
  }
337
338
  /**
339
   * Check user is logged in or redirect them
340
   *
341
   * @param string $redirect
342
   * @return $this
343
   */
344
  public function login_required(string $redirect = '/signin')
345
  {
346
    if (!$this->is_login()) {
347
      \MVC\router::safe_redirect($redirect);
348
    }
349
    return $this;
350
  }
351
352
  /**
353
   * Get current database name.
354
   *
355
   * @return array
356
   */
357
  public function get_dbname()
358
  {
359
    return $this->pdo->query('select database()')->row_array();
360
  }
361
362
  public function generate_password($password)
363
  {
364
    $crypt = new crypt();
365
    return $crypt->encrypt('dimaslanjaka', $password);
366
  }
367
368
  public function login($username, $password)
369
  {
370
    if (!$this->pdo) {
371
      throw new \MVC\Exception('Database not properly configured', 1);
372
    }
373
    if ('GET' == $_SERVER['REQUEST_METHOD']) {
374
      $username = urldecode($username);
375
      $password = urldecode($password);
376
    }
377
    $username = addslashes($username);
378
    $db = $this->dbname;
379
    $password = addslashes($password);
380
    $crypt = new crypt();
381
    $password = $crypt->encrypt('dimaslanjaka', $password);
382
383
    $query = "SELECT * FROM `$db` WHERE username=:username AND password=:password";
384
    //var_dump($username, $password, $query);
385
    $exec = $this->pdo->SQL_Fetch($query, ['username' => $username, 'password' => $password]);
386
    //var_dump($this->pdo, $exec);
387
    $result = [];
388
    if (!empty($exec)) {
389
      if (isset($exec['id'])) {
390
        $result['error'] = false;
391
        $result['success'] = true;
392
        $result['message'] = 'login successful';
393
        $result = array_merge($exec, $result);
394
        $id = $exec['id'];
395
        $query = "UPDATE `$db` SET `last_login` = now() WHERE `$db`.`id` = $id;";
396
        foreach ($exec as $key => $value) {
397
          $_SESSION['login'][$key] = $value;
398
        }
399
        $this->pdo->SQL_Exec($query);
400
      } else {
401
        //var_dump($exec);
402
        $result['error'] = true;
403
      }
404
    } else {
405
      $result['error'] = true;
406
      $result['message'] = 'Empty response from database';
407
    }
408
    $result = $this->hide_additional($result);
409
410
    $this->user = $result;
411
    $this->pdo->resetQuery();
412
    $result['title'] = 'login information';
413
414
    return $result;
415
  }
416
417
  /**
418
   * Hidding additional information into backend response.
419
   *
420
   * @param array $result
421
   *
422
   * @author DimasLanjaka <[email protected]>
423
   *
424
   * @return array
425
   */
426
  public function hide_additional($result)
427
  {
428
    if (isset($result['role'])) {
429
      unset($result['role']);
430
    }
431
    if (isset($result['number'])) {
432
      unset($result['number']);
433
    }
434
    if (isset($result['password'])) {
435
      unset($result['password']);
436
    }
437
    if (isset($result['id'])) {
438
      unset($result['id']);
439
    }
440
441
    return $result;
442
  }
443
444
  /**
445
   * Check Login.
446
   *
447
   * @author Dimaslanjaka <[email protected]>
448
   *
449
   * @param Function $callback
0 ignored issues
show
The type User\Function was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
450
   */
451
  public function check_login($callback)
452
  {
453
    if (isset($_SESSION['login'])) {
454
      if (isset($_SESSION['login']['id']) && is_numeric($_SESSION['login']['id'])) {
455
        if (is_callable($callback)) {
456
          call_user_func($callback, \ArrayHelper\helper::unset($_SESSION['login'], ['password']));
457
        }
458
      }
459
    } else {
460
      if (is_callable($callback)) {
461
        call_user_func($callback, [
462
          'error' => true,
463
          'message' => 'User Login required. Please relogin from login page',
464
          'unauthorized' => true,
465
        ]);
466
      }
467
    }
468
  }
469
470
  /**
471
   * Check user is login.
472
   *
473
   * @return bool
474
   */
475
  public function is_login()
476
  {
477
    $isLogin = isset($_SESSION['login']['username']) && !empty(trim($_SESSION['login']['username'])) ? true : false;
478
    if ($isLogin) {
479
      $this->username = $_SESSION['login']['username'];
480
      $check = $this->pdo->select('userdata')->where(['username' => $this->username])->row_array();
481
      if (!empty($check)) {
482
        foreach ($check as $key => $value) {
483
          $this->{$key} = $value;
484
        }
485
      }
486
    }
487
488
    return $isLogin;
489
  }
490
491
  /**
492
   * Update Display Name.
493
   *
494
   * @param Number $id
495
   * @param string $display
496
   */
497
  public function update_display_name($id, $display)
498
  {
499
    if (!$this->pdo) {
500
      throw new \MVC\Exception('Database not properly configured', 1);
501
    }
502
    $db = $this->dbname;
503
    $q = "UPDATE `$db` SET `display_name` = :name WHERE `$db`.`id` = :id;";
504
    $this->pdo->SQL_Exec($q, ['name' => $display, 'id' => $id]);
505
    $chk = $this->pdo->SQL_Fetch("SELECT `$db` WHERE `$db`.`id` = :id AND `display_name` = :name;", ['id' => $id, 'name' => $display]);
506
    $this->json($chk);
507
  }
508
509
  public function update_role($id, $role, $rgx = '/^(admin|client|superadmin)$/s')
510
  {
511
    if (!$this->pdo) {
512
      throw new \MVC\Exception('Database not properly configured', 1);
513
    }
514
    if (!preg_match($rgx, $role)) {
515
      throw new \MVC\Exception('Error Processing Change Role Request', 1);
516
    }
517
    $chk = [
518
      'error' => true,
519
      'message' => 'insufficient privileges',
520
    ];
521
    if ($this->is_admin()) {
522
      $db = $this->dbname;
523
      $q = "UPDATE `$db` SET `role` = :role WHERE `$db`.`id` = :id;";
524
      $this->pdo->SQL_Exec($q, ['role' => $role, 'id' => $id]);
525
      $chk = $this->pdo->SQL_Fetch("SELECT * FROM `$db` WHERE `$db`.`id` = :id AND `role` = :role;", ['id' => $id, 'role' => $role]);
526
      if (isset($chk['id'])) {
527
        $chk['error'] = false;
528
      }
529
    }
530
531
    return $chk;
532
  }
533
534
  public function register($username, $password, $name = 'user', $role = 'client')
535
  {
536
    if ('GET' == $_SERVER['REQUEST_METHOD']) {
537
      $username = urldecode($username);
538
      $password = urldecode($password);
539
    }
540
    $username = addslashes($username);
541
    $password = addslashes($password);
542
    $crypt = new crypt();
543
    $password = $crypt->encrypt('dimaslanjaka', $password);
544
    $db = $this->dbname;
545
    $q = "INSERT INTO `$db` (`display_name`, `username`, `password`, `role`) VALUES (:name, :username, :password, :role);";
546
    $exec = $this->pdo->SQL_Exec($q, ['name' => $name, 'username' => $username, 'password' => $password, 'role' => $role]);
547
    $result = [];
548
    if (isset($exec['id'])) {
549
      $chk = $this->pdo->SQL_Fetch("SELECT * FROM `$db` WHERE `$db`.`username` = :username AND `$db`.`password` = :password", ['username' => $username, 'password' => $password]);
550
      $result['success'] = true;
551
      $result = $this->hide_additional(array_merge($result, $chk));
552
    }
553
554
    return $result;
555
  }
556
557
  /**
558
   * JSON formatter.
559
   *
560
   * @param array $data
561
   * @param bool  $header
562
   * @param bool  $print
563
   */
564
  public function json($data = [], $header = true, $print = true)
565
  {
566
    return json::json($data, $header, $print);
567
  }
568
}
569