Completed
Push — develop ( fed724...263ba5 )
by Schlaefer
03:00 queued 11s
created

UserOnlineTable::setOnline()   A

Complexity

Conditions 5
Paths 7

Size

Total Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
nc 7
nop 2
dl 0
loc 35
rs 9.0488
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * Saito - The Threaded Web Forum
7
 *
8
 * @copyright Copyright (c) the Saito Project Developers
9
 * @link https://github.com/Schlaefer/Saito
10
 * @license http://opensource.org/licenses/MIT
11
 */
12
13
namespace App\Model\Table;
14
15
use Cake\ORM\Query;
16
use Cake\ORM\Table;
17
use Cake\Validation\Validator;
18
use Stopwatch\Lib\Stopwatch;
19
20
/**
21
 * Stores which users are online
22
 *
23
 * Storage can be nopersistent as it is constantly rebuild with live-data.
24
 *
25
 * Field notes:
26
 * - `time` - Timestamp as int unix-epoch instead regular DATETIME. Makes it
27
 *   cheap to clear out-timed users by comparing int values.
28
 */
29
class UserOnlineTable extends Table
30
{
31
    /**
32
     * Time in seconds until a user is considered offline
33
     *
34
     * @var int
35
     */
36
    public $timeUntilOffline = 1200;
37
38
    /**
39
     * {@inheritDoc}
40
     */
41
    public function initialize(array $config)
42
    {
43
        $this->setTable('useronline');
44
45
        $this->addBehavior('Timestamp');
46
47
        $this->addBehavior(
48
            'Cron.Cron',
49
            [
50
                'gc' => [
51
                    'id' => 'UserOnline.deleteGone',
52
                    'due' => '+1 minutes',
53
                ],
54
            ]
55
        );
56
57
        $this->belongsTo(
58
            'Users',
59
            [
60
                'foreignKey' => 'user_id'
61
            ]
62
        );
63
    }
64
65
    /**
66
     * {@inheritDoc}
67
     */
68
    public function validationDefault(Validator $validator)
69
    {
70
        $validator
0 ignored issues
show
Deprecated Code introduced by
The method Cake\Validation\Validator::notEmpty() has been deprecated with message: 3.7.0 Use allowEmptyString(), allowEmptyArray(), allowEmptyFile(), allowEmptyDate(), allowEmptyTime() or allowEmptyDateTime() with reversed conditions instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
71
            //= uuid
72
            ->notEmpty('uuid')
73
            ->requirePresence('uuid')
74
            ->add(
75
                'uuid',
76
                [
77
                    'isUnique' => [
78
                        'rule' => 'validateUnique',
79
                        'provider' => 'table'
80
                    ],
81
                ]
82
            );
83
84
        return $validator;
85
    }
86
87
    /**
88
     * Sets user with `$id` online
89
     *
90
     * @param int|string $id user-ID
91
     * @param bool $loggedIn user is logged-in
92
     * @return void
93
     * @throws \InvalidArgumentException
94
     */
95
    public function setOnline($id, bool $loggedIn): void
96
    {
97
        if (empty($id)) {
98
            throw new \InvalidArgumentException(
99
                sprintf('Invalid Argument $id in setOnline(): %s', $id)
100
            );
101
        }
102
103
        $now = time();
104
        $id = $this->getShortendedId((string)$id);
105
        $data = [
106
            'uuid' => $id,
107
            'logged_in' => $loggedIn,
108
            'time' => $now
109
        ];
110
111
        if ($loggedIn) {
112
            $data['user_id'] = $id;
113
        }
114
115
        $user = $this->find()
116
            ->where(['uuid' => $id])
117
            ->first();
118
119
        if ($user) {
120
            // only hit database if timestamp is outdated
121
            if ($user->get('time') < ($now - $this->timeUntilOffline)) {
122
                $user->set('time', $now);
123
                $this->save($user);
0 ignored issues
show
Bug introduced by
It seems like $user defined by $this->find()->where(arr...uuid' => $id))->first() on line 115 can also be of type array; however, Cake\ORM\Table::save() does only seem to accept object<Cake\Datasource\EntityInterface>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
124
            }
125
        } else {
126
            $user = $this->newEntity($data);
127
            $this->save($user);
128
        }
129
    }
130
131
    /**
132
     * Removes user with uuid `$id` from UserOnline
133
     *
134
     * @param int|string $id id
135
     * @return void
136
     */
137
    public function setOffline($id): void
138
    {
139
        $id = $this->getShortendedId((string)$id);
140
        $this->deleteAll(['UserOnline.uuid' => $id]);
141
    }
142
143
    /**
144
     * Get all logged-in users
145
     *
146
     * Don't use directly but use \Saito\App\Stats
147
     *
148
     * @td @sm make finder
149
     *
150
     * @return Query
151
     */
152
    public function getLoggedIn(): Query
153
    {
154
        Stopwatch::start('UserOnline->getLoggedIn()');
155
        $loggedInUsers = $this->find(
156
            'all',
157
            [
158
                'contain' => [
159
                    'Users' => [
160
                        'fields' => ['id', 'user_type', 'username']
161
                    ]
162
                ],
163
                'conditions' => ['UserOnline.logged_in' => true],
164
                'fields' => ['id'],
165
                'order' => ['LOWER(Users.username)' => 'ASC']
166
            ]
167
        );
168
        Stopwatch::stop('UserOnline->getLoggedIn()');
169
170
        return $loggedInUsers;
171
    }
172
173
    /**
174
     * Removes users which weren't online $timeDiff seconds
175
     *
176
     * @return void
177
     */
178
    public function gc(): void
179
    {
180
        $this->deleteAll(['time <' => time() - ($this->timeUntilOffline)]);
181
    }
182
183
    /**
184
     * shorten string
185
     *
186
     * @param string $id string
187
     * @return string
188
     */
189
    protected function getShortendedId(string $id)
190
    {
191
        return substr($id, 0, 32);
192
    }
193
}
194