Passed
Push — master ( 52ff6a...d7ff82 )
by bader
02:46
created

Visits::connection()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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