Completed
Push — master ( 225222...7d085e )
by Schlaefer
05:18 queued 02:52
created

ReadPostingsDatabase::getUserId()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 4
rs 10
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 Saito\User\ReadPostings;
14
15
use App\Model\Table\EntriesTable;
16
use App\Model\Table\UserReadsTable;
17
use Cake\Core\Configure;
18
use Saito\App\Registry;
19
use Saito\User\CurrentUser\CurrentUserInterface;
20
use Stopwatch\Lib\Stopwatch;
21
22
/**
23
 * Handles read postings by a server table. Used for logged-in users.
24
 */
25
class ReadPostingsDatabase extends ReadPostingsAbstract
26
{
27
    /**
28
     * @var UserReadsTable
29
     */
30
    protected $storage;
31
32
    /**
33
     * @var EntriesTable
34
     */
35
    protected $entriesTable;
36
37
    /**
38
     * {@inheritDoc}
39
     */
40
    public function __construct(
41
        CurrentUserInterface $CurrentUser,
42
        UserReadsTable $storage,
43
        EntriesTable $entriesTable
44
    ) {
45
        $this->entriesTable = $entriesTable;
46
47
        parent::__construct($CurrentUser, $storage);
48
49
        $Cron = Registry::get('Cron');
50
        $userId = $this->getUserId();
51
        $Cron->addCronJob("ReadUserDb.$userId", '+12 hours', [$this, 'garbageCollection']);
52
    }
53
54
    /**
55
     * {@inheritDoc}
56
     */
57
    public function set($entries)
58
    {
59
        Stopwatch::start('ReadPostingsDatabase::set()');
60
        $entries = $this->_prepareForSave($entries);
61
        if (empty($entries)) {
62
            return;
63
        }
64
        $this->storage->setEntriesForUser($entries, $this->getUserId());
65
        Stopwatch::stop('ReadPostingsDatabase::set()');
66
    }
67
68
    /**
69
     * {@inheritDoc}
70
     */
71
    public function delete()
72
    {
73
        $this->storage->deleteAllFromUser($this->getUserId());
74
    }
75
76
    /**
77
     * Calculates the max number of postings to remember (limits DB storage).
78
     *
79
     * @return int
80
     * @throws \UnexpectedValueException
81
     */
82
    protected function postingsPerUser() :int
83
    {
84
        $threadsOnPage = Configure::read('Saito.Settings.topics_per_page');
85
        $postingsPerThread = Configure::read('Saito.Globals.postingsPerThread');
86
        $pagesToCache = 1.5;
87
        $minPostingsToKeep = intval($postingsPerThread * $threadsOnPage * $pagesToCache);
88
        if (empty($minPostingsToKeep)) {
89
            throw new \UnexpectedValueException();
90
        }
91
92
        return $minPostingsToKeep;
93
    }
94
95
    /**
96
     * Removes old read-posting data from a single user.
97
     *
98
     * Prevent growing of DB if user never clicks the MAR-button.
99
     *
100
     * @return void
101
     */
102
    public function garbageCollection()
103
    {
104
        $readPostings = $this->_get();
105
        $numberOfRp = count($readPostings);
106
        if ($numberOfRp === 0) {
107
            return;
108
        }
109
110
        $maxRpToKeep = $this->postingsPerUser();
111
        $numberOfRpToDelete = $numberOfRp - $maxRpToKeep;
112
        if ($numberOfRpToDelete <= 0) {
113
            // Number under GC threshold or user has no data at all.
114
            return;
115
        }
116
117
        // assign dummy var to prevent Strict notice on reference passing
118
        $dummy = array_slice($readPostings, $numberOfRpToDelete, 1);
119
        $idOfOldestPostingToKeepInRp = array_shift($dummy);
120
121
        /// Update last refresh
122
        // All entries older than (and including) the deleted entries become
123
        // old entries by updating the MAR-timestamp.
124
        $oldestPostingToKeepInRp = $this->entriesTable->find()
125
            ->where(['id' => $idOfOldestPostingToKeepInRp])
126
            ->first();
127
128
        if (empty($oldestPostingToKeepInRp)) {
129
            // Posting was deleted for whatever reason: Skip this gc run. Next
130
            // time a later posting will be the oldest one to keep.
131
            return;
132
        }
133
134
        // Can't use  $this->_CU->LastRefresh->set(): that would not only delete
135
        // old but *all* of the user's individually read postings.
136
        $this->storage->Users
137
            ->setLastRefresh(
138
                $this->getUserId(),
139
                $oldestPostingToKeepInRp->get('time')
140
            );
141
142
        /// Now delete the old entries
143
        $this->storage->deleteUserEntriesBefore($this->getUserId(), $idOfOldestPostingToKeepInRp);
144
    }
145
146
    /**
147
     * {@inheritDoc}
148
     */
149
    protected function _get()
150
    {
151
        if ($this->readPostings !== null) {
152
            return $this->readPostings;
153
        }
154
        $this->readPostings = $this->storage->getUser($this->getUserId());
155
156
        return $this->readPostings;
157
    }
158
159
    /**
160
     * Get current-user-id
161
     *
162
     * @return int
163
     */
164
    protected function getUserId()
165
    {
166
        return $this->CurrentUser->getId();
167
    }
168
}
169