Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 254
0.00% covered (danger)
0.00%
0 / 25
CRAP
0.00% covered (danger)
0.00%
0 / 1
MySqlUser
0.00% covered (danger)
0.00%
0 / 254
0.00% covered (danger)
0.00%
0 / 25
7482
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 isGuest
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
12
 isLoggedIn
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 fresh
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 __isset
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
30
 __get
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
30
 getGroups
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getStoreGroups
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 addGroup
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 removeGroup
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 fetchGroups
0.00% covered (danger)
0.00%
0 / 16
0.00% covered (danger)
0.00%
0 / 1
6
 getTheme
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
20
 getPrimaryGroup
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getDailyReport
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
6
 fetchPrimaryGroup
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
12
 store
0.00% covered (danger)
0.00%
0 / 34
0.00% covered (danger)
0.00%
0 / 1
72
 delete
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
6
 checkAccess
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
72
 checkStoreGroup
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
 verifyPassword
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
30
 verifyAPIKey
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
20
 login
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
 getStores
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 fetchStores
0.00% covered (danger)
0.00%
0 / 22
0.00% covered (danger)
0.00%
0 / 1
12
 UpdateAsBuyerInEmployeeTable
0.00% covered (danger)
0.00%
0 / 20
0.00% covered (danger)
0.00%
0 / 1
42
1<?php
2
3namespace UserFrosting;
4
5class MySqlUser extends MySqlDatabaseObject implements UserObjectInterface {
6
7    protected $_groups;         // An undefined value means that the user's groups have not been loaded yet
8    protected $_stores;
9    protected $_primary_group;  // The primary group for the user.  TODO: simply fetch it from the _groups array?
10
11    /**
12     * Columns that are computed in the uf_user VIEW and cannot be updated directly.
13     * These are excluded from UPDATE statements.
14     */
15    protected $_nonUpdatableColumns = ['primary_group_id'];
16
17    public function __construct($properties, $id = null) {
18        $this->_table = static::getTable('user');
19
20        // Set default locale, if not specified
21        if (!isset($properties['locale']))
22            $properties['locale'] = static::$app->site->default_locale;
23        parent::__construct($properties, $id);
24    }
25
26    // Determine whether this User is a guest (id set to user_id_guest) or a live, logged-in user
27    public function isGuest(){
28        if (!isset($this->_id) || $this->_id === static::$app->config('user_id_guest'))
29            return true;
30        else
31            return false;
32    }
33
34    /* Determine if this user is currently logged in. */
35    public static function isLoggedIn(){
36        // TODO.  Not sure how to implement this right now.  Flag in DB?  Or, check sessions?
37    }
38
39    /* Refresh the User and their associated Groups from the DB.
40     *
41     */
42    public function fresh(){
43        // Update table and column info, in case it has changed
44        $this->_table = static::$tables['user'];
45        $user = new User(parent::fresh(), $this->_id);
46        $user->_groups = $this->fetchGroups();
47        $user->_primary_group = $this->fetchPrimaryGroup();
48        return $user;
49    }
50
51    // Must be implemented for compatibility with Twig
52    public function __isset($name) {
53        if ($name == "primary_group" || $name == "theme" || $name == "icon" || $name == "landing_page")
54            return isset($this->_primary_group);
55        else
56            return parent::__isset($name);
57    }
58
59    // Getter
60    public function __get($name){
61        if ($name == "primary_group")
62            return $this->getPrimaryGroup();
63        else if ($name == "theme")
64            return $this->getPrimaryGroup()->theme;
65        else if ($name == "icon")
66            return $this->getPrimaryGroup()->icon;
67        else if ($name == "landing_page")
68            return $this->getPrimaryGroup()->landing_page;
69        else
70            return parent::__get($name);
71    }
72
73    // Get a list of groups to which this user belongs, lazily loading them if not already set
74    public function getGroups(){
75        if (!isset($this->_groups))
76            $this->_groups = $this->fetchGroups();
77
78        return $this->_groups;
79    }
80    public function getStoreGroups(){
81        $groups = $this->getGroups();
82        $groupsArray = array();
83        foreach ($groups as $group) {
84            if(preg_match("/[a-z][a-z][0-9]{2,}/", $group->name, $match)) {
85                $groupsArray[] = array("TypeNum" => $group->name);
86            }
87        }
88        return $groupsArray;
89    }
90
91    // Add this user to a specified group.  Won't be stored in DB until store() is called.
92    public function addGroup($group_id){
93        // First, load current groups for user
94        $this->getGroups();
95        // Return if user already in group
96        if (isset($this->_groups[$group_id]))
97            return $this;
98
99        // Next, check that the requested group actually exists
100        if (!GroupLoader::exists($group_id))
101            throw new \Exception("The specified group_id ($group_id) does not exist.");
102
103        // Ok, add to the list of groups
104        $this->_groups[$group_id] = GroupLoader::fetch($group_id);
105
106        return $this;
107    }
108
109    // Remove this user from a specified group.  Won't be stored in DB until store() is called.
110    public function removeGroup($group_id){
111        // First, load current groups for user
112        $this->getGroups();
113        // Return if user not in group
114        if (!isset($this->_groups[$group_id]))
115            return $this;
116
117        // Ok, remove from the list of groups
118        unset($this->_groups[$group_id]);
119
120        return $this;
121
122    }
123
124    // Fetch an array of Groups that this User belongs to from the database
125    private function fetchGroups(){
126        $db = static::connection();
127
128        $link_table = static::$tables['group_user']->name;
129        $group_table = static::$tables['group']->name;
130
131        $query = "
132            SELECT `$group_table`.*
133            FROM `$link_table`, `$group_table`
134            WHERE `$link_table`.user_id = :id
135            AND `$link_table`.group_id = `$group_table`.id";
136
137        $stmt = $db->prepare($query);
138
139        $sqlVars[':id'] = $this->_id;
140
141        $stmt->execute($sqlVars);
142
143        // For now just create an array of Group objects.  Later we can implement GroupCollection for faster access.
144        $results = [];
145        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
146            $id = $row['id'];
147            $results[$id] = new Group($row, $row['id']);
148        }
149        return $results;
150    }
151
152    // Get the theme for this user, based on their primary group.  Guest/undefined user returns the default theme.  Master user returns the root theme.  Lazy load.
153    public function getTheme(){
154        if (!isset($this->_id) || $this->_id == static::$app->config('user_id_guest'))
155            return "default";
156        else if ($this->_id == static::$app->config('user_id_master'))
157            return "root";
158        else
159            return $this->getPrimaryGroup()->theme;
160    }
161
162    // Get the primary group to which this user belongs.  Lazy load into object.
163    public function getPrimaryGroup(){
164        if (!isset($this->_primary_group))
165            $this->_primary_group = $this->fetchPrimaryGroup();
166
167        return $this->_primary_group;
168    }
169    public function getDailyReport() {
170        $db = static::connection();
171        $users_table = static::$tables['user']->name;
172
173        $query = "
174            SELECT `$users_table`.dailyReport
175            FROM `$users_table`
176            WHERE `$users_table`.id = :user_id LIMIT 1";
177
178        $stmt = $db->prepare($query);
179
180        $stmt->bindValue(":user_id", $this->_id);
181
182        $stmt->execute();
183
184        $result = $stmt->fetch(\PDO::FETCH_ASSOC);
185
186        if($result) {
187            return (int)$result['dailyReport'];
188        } else {
189            return null;
190        }
191    }
192
193    private function fetchPrimaryGroup() {
194        if (!isset($this->primary_group_id)){
195            throw new \Exception("This user does not appear to have a primary group id set.");
196        }
197        $db = static::connection();
198        $group_table = static::$tables['group']->name;
199
200        $query = "
201            SELECT `$group_table`.*
202            FROM `$group_table`
203            WHERE `$group_table`.id = :primary_group_id LIMIT 1";
204
205        $stmt = $db->prepare($query);
206
207        $sqlVars[':primary_group_id'] = $this->primary_group_id;
208
209        $stmt->execute($sqlVars);
210
211        $results = $stmt->fetch(\PDO::FETCH_ASSOC);
212
213        if ($results)
214            return new Group($results, $results['id']);
215        else
216            return false;
217    }
218
219    public function store($force_create = false){
220        // Initialize timestamps for new Users.  Should this be done here, or somewhere else?
221        if (!isset($this->_id) || $force_create){
222            $this->sign_up_stamp = date("Y-m-d H:i:s");
223            $this->activation_token = UserLoader::generateActivationToken();
224            $this->last_activation_request = date("Y-m-d H:i:s");
225        }
226
227        // Update the user record itself
228        parent::store();
229
230        // Get the User object's current groups
231        $this->getGroups();
232
233        // Get the User's groups as stored in the DB
234        $db_groups = $this->fetchGroups();
235
236        $link_table = static::$tables['group_user']->name;
237
238        // Add any groups in object that are not in DB yet
239        $db = static::connection();
240        $query = "
241            INSERT INTO `$link_table` (user_id, group_id)
242            VALUES (:user_id, :group_id);";
243        foreach ($this->_groups as $group_id => $group){
244            $stmt = $db->prepare($query);
245            if (!isset($db_groups[$group_id])){
246                $sqlVars = [
247                    ':group_id' => $group_id,
248                    ':user_id' => $this->_id
249                ];
250                $stmt->execute($sqlVars);
251            }
252        }
253
254        // Remove any group links in DB that are no longer modeled in this object
255        if ($db_groups){
256            $db = static::connection();
257            $query = "
258                DELETE FROM `$link_table`
259                WHERE group_id = :group_id
260                AND user_id = :user_id LIMIT 1";
261
262            $stmt = $db->prepare($query);
263            foreach ($db_groups as $group_id => $group){
264                if (!isset($this->_groups[$group_id])){
265                    $sqlVars = [
266                        ':group_id' => $group_id,
267                        ':user_id' => $this->_id
268                    ];
269                    $stmt->execute($sqlVars);
270                }
271            }
272        }
273
274        // Store function should always return the id of the object
275        return $this->_id;
276    }
277
278    /*** Delete this user from the database, along with any linked groups and authorization rules
279    ***/
280    public function delete(){
281        // Can only delete an object where `id` is set
282        if (!$this->_id) {
283            return false;
284        }
285
286        $result = parent::delete();
287
288        // Get connection
289        $db = static::connection();
290        $link_table = static::$tables['group_user']->name;
291        $auth_table = static::$tables['authorize_user']->name;
292
293        $sqlVars[":id"] = $this->_id;
294
295        $query = "
296            DELETE FROM `$link_table`
297            WHERE user_id = :id";
298
299        $stmt = $db->prepare($query);
300        $stmt->execute($sqlVars);
301
302        $query = "
303            DELETE FROM `$auth_table`
304            WHERE user_id = :id";
305
306        $stmt = $db->prepare($query);
307        $stmt->execute($sqlVars);
308
309        return $result;
310    }
311
312    // Determine if this user has access to the given $hook under the given $params
313    public function checkAccess($hook, $params = []){
314        if ($this->isGuest()){   // TODO: do we sometimes want to allow access to protected resources for guests?  Should we model a "guest" group?
315            return false;
316        }
317
318        // The master (root) account has access to everything.
319        if ($this->_id == static::$app->config('user_id_master'))
320            return true;
321
322        // Try to find an authorization rule for $hook that matches the currently logged-in user, or one of their groups.
323        $rule = AuthLoader::fetchUserAuthHook($this->_id, $hook);
324
325        if (empty($rule))
326            $pass = false;
327        else {
328            $ace = new AccessConditionExpression(static::$app); // TODO: should we have to pass the app in, or just make it available statically?
329            $pass = $ace->evaluateCondition($rule['conditions'], $params);
330        }
331
332        // If no user-specific rule is passed, look for a group-level rule
333        if (!$pass){
334            $ace = new AccessConditionExpression(static::$app);
335            $groups = $this->getGroups();
336            foreach ($groups as $group){
337                // Try to find an authorization rule for $hook that matches this group
338                $rule = AuthLoader::fetchGroupAuthHook($group->id, $hook);
339                if (!$rule)
340                    continue;
341                $pass = $ace->evaluateCondition($rule['conditions'], $params);
342                if ($pass)
343                    break;
344            }
345        }
346        return $pass;
347    }
348    public function checkStoreGroup($typeNum) {
349        if($this->checkAccess('all_stores')) {
350            return true;
351        }
352        $storeGroup = GroupLoader::fetch($typeNum, 'name');
353        $pass = false;
354        $groups = $this->getGroups();
355        if($storeGroup && array_key_exists($storeGroup->id, $groups)) {
356            $pass = true;
357        }
358        return $pass;
359
360    }
361
362    // Check that the specified password (unhashed) matches this user's password (hashed).
363    public function verifyPassword($password){
364        if (Authentication::getPasswordHashType($this->password) == "sha1"){
365            $salt = substr($this->password, 0, 25);        // Extract the salt from the hash
366            $hash_input = $salt . sha1($salt . $password);
367            if ($hash_input == $this->password){
368                return true;
369            } else {
370                return false;
371            }
372        }
373        // Homegrown implementation (assuming that current install has been using a cost parameter of 12)
374        else if (Authentication::getPasswordHashType($this->password) == "homegrown"){
375            /*used for manual implementation of bcrypt*/
376            $cost = '12';
377            if (substr($this->password, 0, 60) == crypt($password, "$2y$".$cost."$".substr($this->password, 60))){
378                return true;
379            } else {
380                return false;
381            }
382        // Modern implementation
383        } else {
384            return password_verify($password, $this->password);
385        }
386    }
387    public function verifyAPIKey($apiKey, \Klogger $log){
388        $db = static::connection();
389
390        $stmt = $db->prepare("SELECT `key`, active FROM uf_apiKey_user JOIN uf_apiKey ON uf_apiKey_user.key_id = uf_apiKey.id WHERE uf_apiKey_user.user_id = :user_id");
391        $stmt->bindValue(":user_id", $this->_id);
392        if($stmt->execute()) {
393            $row = $stmt->fetch(\PDO::FETCH_ASSOC);
394            $log->LogDebug("API Compare: ".$row['key']." - ".$apiKey);
395            $log->LogDebug("Active is: ".$row['active']);
396            if(hash_equals($row['key'], $apiKey) && (int)$row['active'] == 1) {
397                return true;
398            }
399        }
400        return false;
401    }
402
403    public function login($password = null){
404        //Update last sign in
405        $this->last_sign_in_stamp = date("Y-m-d H:i:s");
406
407        // Update password if we had encountered an outdated hash and plaintext password is provided
408        if ($password !== null && Authentication::getPasswordHashType($this->password) != "modern"){
409            // Hash the user's password and update
410            $password_hash = Authentication::hashPassword($password);
411            if ($password_hash === null){
412                error_log("Notice: outdated password hash could not be updated because the new hashing algorithm is not supported.  Are you running PHP >= 5.3.7?");
413            } else {
414                $this->password = $password_hash;
415                error_log("Notice: outdated password hash has been automatically updated to modern hashing.");
416            }
417        }
418
419        // Store changes
420        $this->store();
421
422        return $this;
423    }
424    public function getStores(){
425        if (!isset($this->_stores))
426            $this->_stores = $this->fetchStores();
427
428        return $this->_stores;
429    }
430
431    public function fetchStores() {
432        $db = static::connection();
433        global $db_name;
434
435        $link_table = static::$tables['store_user']->name;
436        //$db = dbConnectByName($db_name);
437
438        $query = "SELECT storeID FROM ".$link_table." WHERE ".$link_table.".userID = ?";
439
440        $stmt = $db->prepare($query);
441        $sqlVars = array($this->_id);
442        $stmt->execute($sqlVars);
443
444        $results = [];
445        while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
446            $id = $row['storeID'];
447            $results[] = $id;
448        }
449        $typeNums = [];
450
451        global $db_name;
452        $db = dbConnectByName($db_name);
453
454        foreach ($results as $store) {
455            $query = "SELECT typeNum FROM stores WHERE stores.id = :id";
456            $stmt = $db->prepare($query);
457            $vars[':id'] = $store;
458            $stmt->execute($vars);
459
460            $result = $stmt->fetch(\PDO::FETCH_ASSOC);
461            $typeNums[] = $result['typeNum'];
462        }
463        return $typeNums;
464    }
465    public function UpdateAsBuyerInEmployeeTable($active, $typeNum = null) {
466        $typeNumArray = [];
467        if($typeNum !== null) {
468            $typeNumArray[] = $typeNum;
469        } else {
470            $groups = $this->getGroups();
471            foreach ($groups as $group) {
472                if(preg_match("/[a-z][a-z][0-9]{2,}/", $group->name, $match)) {
473                    $typeNumArray[] = $group->name;
474                }
475            }
476        }
477
478        foreach($typeNumArray as $typeNum) {
479            //error_log($typeNum);
480            $store = new \Store();
481            $store->createStore($typeNum);
482
483            $storeDB = dbConnectByName($store->getDbName());
484
485            $name = explode(" ", $this->display_name);
486            try {
487                $stmt = $storeDB->prepare("UPDATE employees SET active = :active WHERE (employeeFirstName = :firstName AND employeeLastName = :lastName) OR login = :username");
488                $stmt->bindValue(":firstName", $name[0]);
489                $stmt->bindValue(":lastName", $name[1]);
490                $stmt->bindValue(":username", $this->user_name);
491                $stmt->bindValue(":active", $active);
492                $stmt->execute();
493            } catch (\PDOException $e) {
494                error_log($e->getMessage());
495            }
496        }
497
498
499    }
500
501}