Completed
Push — master ( f8882e...156190 )
by bader
03:45
created

Visits::newExpiration()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 20
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 15
nc 5
nop 1
dl 0
loc 20
rs 9.4555
c 0
b 0
f 0
1
<?php
2
3
namespace awssat\Visits;
4
5
use Carbon\Carbon;
6
use Illuminate\Support\Facades\Redis;
7
use Spatie\Referer\Referer;
8
9
class Visits
10
{
11
    /**
12
     * @var mixed
13
     */
14
    protected $ipSeconds;
15
    /**
16
     * @var null
17
     */
18
    protected $subject;
19
    /**
20
     * @var bool|mixed
21
     */
22
    protected $fresh = false;
23
    /**
24
     * @var null
25
     */
26
    protected $country = null;
27
    /**
28
     * @var null
29
     */
30
    protected $referer = null;
31
    /**
32
     * @var mixed
33
     */
34
    protected $periods;
35
    /**
36
     * @var Keys
37
     */
38
    protected $keys;
39
    /**
40
     * @var Redis
41
     */
42
    public $redis;
43
44
    /**
45
     * Visits constructor.
46
     * @param $subject
47
     * @param string $tag|null
48
     */
49
    public function __construct($subject = null, $tag = 'visits')
50
    {
51
        $config = config('visits');
52
        $this->redis = Redis::connection($config['connection']);
53
        $this->periods = $config['periods'];
54
        $this->ipSeconds = $config['remember_ip'];
55
        $this->fresh = $config['always_fresh'];
56
        $this->subject = $subject;
57
        $this->keys = new Keys($subject, $tag);
58
59
        $this->periodsSync();
60
    }
61
62
    /**
63
     * Return fresh cache from database
64
     * @return $this
65
     */
66
    public function fresh()
67
    {
68
        $this->fresh = true;
69
70
        return $this;
71
    }
72
73
    /**
74
     * set x seconds for ip expiration
75
     *
76
     * @param $seconds
77
     * @return $this
78
     */
79
    public function seconds($seconds)
80
    {
81
        $this->ipSeconds = $seconds;
82
83
        return $this;
84
    }
85
86
87
    /**
88
     * @param $country
89
     * @return $this
90
     */
91
    public function country($country)
92
    {
93
        $this->country = $country;
94
95
        return $this;
96
    }
97
98
99
    /**
100
     * @param $referer
101
     * @return $this
102
     */
103
    public function referer($referer)
104
    {
105
        $this->referer = $referer;
106
107
        return $this;
108
    }
109
110
    /**
111
     * Change period
112
     *
113
     * @param $period
114
     * @return $this
115
     */
116
    public function period($period)
117
    {
118
        if (in_array($period, array_keys($this->periods))) {
119
            $this->keys->visits = $this->keys->period($period);
120
        }
121
122
        return $this;
123
    }
124
125
    /**
126
     * Sync periods times
127
     */
128
    protected function periodsSync()
129
    {
130
        foreach ($this->periods as $period) {
131
            $periodKey = $this->keys->period($period);
132
133
            if ($this->noExpiration($periodKey)) {
134
                $expireInSeconds = $this->newExpiration($period);
135
                $this->redis->incrby($periodKey . '_total', 0);
136
                $this->redis->zincrby($periodKey, 0, 0);
137
                $this->redis->expire($periodKey, $expireInSeconds);
138
                $this->redis->expire($periodKey . '_total', $expireInSeconds);
139
            }
140
        }
141
    }
142
143
    /**
144
     * @param $periodKey
145
     * @return bool
146
     */
147
    protected function noExpiration($periodKey)
148
    {
149
        return $this->redis->ttl($periodKey) == -1 || !$this->redis->exists($periodKey);
150
    }
151
152
    /**
153
     * @param $period
154
     * @return int
155
     */
156
    protected function newExpiration($period)
157
    {
158
        $expireInSeconds = 0;
159
160
        switch ($period) {
161
            case 'day':
162
                $expireInSeconds = Carbon::now()->endOfDay()->timestamp - Carbon::now()->timestamp;
163
                break;
164
            case 'week':
165
                $expireInSeconds = Carbon::now()->endOfWeek()->timestamp - Carbon::now()->timestamp;
166
                break;
167
            case 'month':
168
                $expireInSeconds = Carbon::now()->endOfMonth()->timestamp - Carbon::now()->timestamp;
169
                break;
170
            case 'year':
171
                $expireInSeconds = Carbon::now()->endOfYear()->timestamp - Carbon::now()->timestamp;
172
                break;
173
        }
174
175
        return $expireInSeconds + 1;
176
    }
177
178
    /**
179
     * Reset methods
180
     *
181
     * @param $method
182
     * @param string $args
183
     * @return Reset
184
     */
185
    public function reset($method = 'visits', $args = '')
186
    {
187
        return new Reset($this, $method, $args);
188
    }
189
190
    /**
191
     * Fetch all time trending subjects.
192
     *
193
     * @param int $limit
194
     * @param bool $isLow
195
     * @return \Illuminate\Support\Collection|array
196
     */
197
    public function top($limit = 5, $isLow = false)
198
    {
199
        $visitsIds = $this->getVisits($limit, $this->keys->visits, $isLow);
200
        $cacheKey = $this->keys->cache($limit, $isLow);
201
        $cachedList = $this->cachedList($limit, $cacheKey);
202
        $cachedIds = $cachedList->pluck('id')->toArray();
203
204
        return ($visitsIds === $cachedIds && !$this->fresh) ? $cachedList : $this->freshList($cacheKey, $visitsIds);
205
    }
206
207
208
    /**
209
     * Top/low countries
210
     *
211
     * @param int $limit
212
     * @param bool $isLow
213
     * @return mixed
214
     */
215
    public function countries($limit = -1, $isLow = false)
216
    {
217
        $range = $isLow ? 'zrange' : 'zrevrange';
218
219
        return $this->redis->$range($this->keys->visits . "_countries:{$this->keys->id}", 0, $limit, 'WITHSCORES');
220
    }
221
222
    /**
223
     * top/lows refs
224
     *
225
     * @param int $limit
226
     * @param bool $isLow
227
     * @return mixed
228
     */
229
    public function refs($limit = -1, $isLow = false)
230
    {
231
        $range = $isLow ? 'zrange' : 'zrevrange';
232
233
        return $this->redis->$range($this->keys->visits . "_referers:{$this->keys->id}", 0, $limit, 'WITHSCORES');
234
    }
235
236
    /**
237
     * Fetch lowest subjects.
238
     *
239
     * @param int $limit
240
     * @return \Illuminate\Support\Collection|array
241
     */
242
    public function low($limit = 5)
243
    {
244
        return $this->top($limit, true);
245
    }
246
247
    /**
248
     * Check for the ip is has been recorded before
249
     *
250
     * @return bool
251
     * @internal param $subject
252
     */
253
    public function recordedIp()
254
    {
255
        return !$this->redis->set($this->keys->ip(request()->ip()), true, 'EX', $this->ipSeconds, 'NX');
256
    }
257
258
    /**
259
     * Get visits of model instance.
260
     *
261
     * @return mixed
262
     * @internal param $subject
263
     */
264
    public function count()
265
    {
266
        if ($this->country) {
267
            return $this->redis->zscore($this->keys->visits . "_countries:{$this->keys->id}", $this->country);
268
        } else if ($this->referer) {
269
            return $this->redis->zscore($this->keys->visits . "_referers:{$this->keys->id}", $this->referer);
270
        }
271
272
        return intval(
273
            (!$this->keys->instanceOfModel) ?
274
                $this->redis->get($this->keys->visits . '_total') :
275
                $this->redis->zscore($this->keys->visits, $this->keys->id)
276
        );
277
    }
278
279
    /**
280
     * use diffForHumans to show diff
281
     * @param $period
282
     * @return Carbon
283
     */
284
    public function timeLeft($period = false)
285
    {
286
        return Carbon::now()->addSeconds($this->redis->ttl(
287
            $period ? $this->keys->period($period) : $this->keys->ip(request()->ip())
288
        ));
289
    }
290
291
292
    /**
293
     * Increment a new/old subject to the cache cache.
294
     *
295
     * @param int $inc
296
     * @param bool $force
297
     * @param bool $periods
298
     * @param bool $country
299
     * @param bool $refer
300
     */
301
    public function increment($inc = 1, $force = false, $periods = true, $country = true, $ip = null, $refer = true)
302
    {
303
        if ($force || !$this->recordedIp()) {
304
            $this->redis->zincrby($this->keys->visits, $inc, $this->keys->id);
305
            $this->redis->incrby($this->keys->visits . '_total', $inc);
306
307
308
            if ($country) {
309
                $zz = $this->getCountry($ip);
310
                $this->redis->zincrby($this->keys->visits . "_countries:{$this->keys->id}", $inc, $zz);
311
            }
312
313
314
            $referer = app(Referer::class)->get();
315
316
            if ($refer && !empty($referer)) {
317
                $this->redis->zincrby($this->keys->visits . "_referers:{$this->keys->id}", $inc, $referer);
318
            }
319
320
            if ($periods) {
321
                foreach ($this->periods as $period) {
322
                    $periodKey = $this->keys->period($period);
323
324
                    $this->redis->zincrby($periodKey, $inc, $this->keys->id);
325
                    $this->redis->incrby($periodKey . '_total', $inc);
326
                }
327
            }
328
        }
329
    }
330
331
332
    /**
333
     * @param int $inc
334
     * @param bool $periods
335
     */
336
    public function forceIncrement($inc = 1, $periods = true)
337
    {
338
        $this->increment($inc, true, $periods);
339
    }
340
341
    /**
342
     * Decrement a new/old subject to the cache cache.
343
     *
344
     * @param int $dec
345
     * @param bool $force
346
     */
347
    public function decrement($dec = 1, $force = false)
348
    {
349
        $this->increment(-$dec, $force);
350
    }
351
352
    /**
353
     * @param int $dec
354
     * @param bool $periods
355
     */
356
    public function forceDecrement($dec = 1, $periods = true)
357
    {
358
        $this->increment(-$dec, true, $periods);
359
    }
360
361
    /**
362
     * @param $limit
363
     * @param $visitsKey
364
     * @param bool $isLow
365
     * @return mixed
366
     */
367
    protected function getVisits($limit, $visitsKey, $isLow = false)
368
    {
369
        $range = $isLow ? 'zrange' : 'zrevrange';
370
371
        return array_map('intval', $this->redis->$range($visitsKey, 0, $limit - 1));
372
    }
373
374
    /**
375
     * @param $cacheKey
376
     * @param $visitsIds
377
     * @return mixed
378
     */
379
    protected function freshList($cacheKey, $visitsIds)
380
    {
381
        if (count($visitsIds)) {
382
            $this->redis->del($cacheKey);
383
384
            return ($this->subject)::whereIn($this->keys->primary, $visitsIds)
385
                ->get()
386
                ->sortBy(function ($subject) use ($visitsIds) {
387
                    return array_search($subject->{$this->keys->primary}, $visitsIds);
388
                })
389
                ->each(function ($subject) use ($cacheKey) {
390
                    $this->redis->rpush($cacheKey, serialize($subject));
391
                });
392
        }
393
394
        return [];
395
    }
396
397
    /**
398
     * @param $limit
399
     * @param $cacheKey
400
     * @return \Illuminate\Support\Collection|array
401
     */
402
    protected function cachedList($limit, $cacheKey)
403
    {
404
        return collect(array_map('unserialize', $this->redis->lrange($cacheKey, 0, $limit - 1)));
405
    }
406
407
408
    /**
409
     *  Gets visitor country code
410
     * @return mixed|string
411
     */
412
    public function getCountry($ip = null)
413
    {
414
        if (session('country_code')) {
415
            return session('country_code');
416
        }
417
418
        $country_code = 'zz';
419
420
        if (isset($_SERVER["HTTP_CF_IPCOUNTRY"])) {
421
            $country_code = strtolower($_SERVER["HTTP_CF_IPCOUNTRY"]);
422
        }
423
424
        if ($country_code === 'zz' && app()->has('geoip')) {
425
426
            $geo_info = geoip()->getLocation($ip);
427
428
            if (!empty($geo_info) && isset($geo_info['iso_code'])) {
429
                $country_code = strtolower($geo_info['iso_code']);
430
            }
431
432
        }
433
434
        session(['country_code', $country_code]);
435
        return $country_code;
436
    }
437
438
    /**
439
     * @param $period
440
     * @param int $time
441
     * @return bool
442
     */
443
    public function expireAt($period, $time)
444
    {
445
        $periodKey = $this->keys->period($period);
446
        return $this->redis->expire($periodKey, $time);
447
    }
448
}
449