Completed
Push — master ( 9ae1c6...469563 )
by
unknown
07:25
created

ItemMapper::makeSelectQueryStatus()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 13
ccs 7
cts 7
cp 1
rs 9.4286
cc 1
eloc 9
nc 1
nop 5
crap 1
1
<?php
2
/**
3
 * ownCloud - News
4
 *
5
 * This file is licensed under the Affero General Public License version 3 or
6
 * later. See the COPYING file.
7
 *
8
 * @author Alessandro Cosentino <[email protected]>
9
 * @author Bernhard Posselt <[email protected]>
10
 * @copyright Alessandro Cosentino 2012
11
 * @copyright Bernhard Posselt 2012, 2014
12
 */
13
14
namespace OCA\News\Db;
15
16
use OCP\IDBConnection;
17
18
19
class ItemMapper extends NewsMapper {
20
21 35
    public function __construct(IDBConnection $db){
22 35
        parent::__construct($db, 'news_items', Item::class);
23 35
    }
24
25
26 21
    private function makeSelectQuery($prependTo='', $oldestFirst=false,
27
                                     $distinctFingerprint=false){
28 21
        if($oldestFirst) {
29 3
            $ordering = 'ASC';
30 3
        } else {
31 18
            $ordering = 'DESC';
32
        }
33
34
        return 'SELECT `items`.* FROM `*PREFIX*news_items` `items` '.
35 21
            'JOIN `*PREFIX*news_feeds` `feeds` ' .
36 21
                'ON `feeds`.`id` = `items`.`feed_id` '.
37 21
                'AND `feeds`.`deleted_at` = 0 ' .
38 21
                'AND `feeds`.`user_id` = ? ' .
39 21
                $prependTo .
40 21
            'LEFT OUTER JOIN `*PREFIX*news_folders` `folders` ' .
41 21
                'ON `folders`.`id` = `feeds`.`folder_id` ' .
42 21
            'WHERE `feeds`.`folder_id` = 0 ' .
43 21
                'OR `folders`.`deleted_at` = 0 ' .
44 21
            'ORDER BY `items`.`id` ' . $ordering;
45
    }
46
47 18
    private function makeSelectQueryStatus($prependTo, $status,
48
                                           $oldestFirst=false, $search=[],
49
                                           $distinctFingerprint=false) {
50 18
        $status = (int) $status;
51 18
        $count = count($search);
52
53
        // WARNING: Potential SQL injection if you change this carelessly
54 18
        $sql = 'AND ((`items`.`status` & ' . $status . ') = ' . $status . ') ';
55 18
        $sql .= str_repeat('AND `items`.`search_index` LIKE ? ', $count);
56 18
        $sql .= $prependTo;
57
58 18
        return $this->makeSelectQuery($sql, $oldestFirst, $distinctFingerprint);
59
    }
60
61
	/**
62
	 * wrap and escape search parameters in a like statement
63
	 *
64
	 * @param string[] $search an array of strings that should be searched
65
	 * @return array with like parameters
66
	 */
67
    private function buildLikeParameters($search=[]) {
68 15
        return array_map(function ($param) {
69 3
            $param = addcslashes($param, '\\_%');
70 3
            return '%' . strtolower($param) . '%';
71 15
        }, $search);
72
    }
73
74 1
    /**
75 1
     * @param int $id
76 1
     * @param string $userId
77
     * @return \OCA\News\Db\Item
78
     */
79
    public function find($id, $userId){
80 1
        $sql = $this->makeSelectQuery('AND `items`.`id` = ? ');
81
        return $this->findEntity($sql, [$userId, $id]);
82 1
    }
83 1
84 1
    public function starredCount($userId){
85 1
        $sql = 'SELECT COUNT(*) AS size FROM `*PREFIX*news_items` `items` '.
86 1
            'JOIN `*PREFIX*news_feeds` `feeds` ' .
87 1
                'ON `feeds`.`id` = `items`.`feed_id` '.
88 1
                'AND `feeds`.`deleted_at` = 0 ' .
89 1
                'AND `feeds`.`user_id` = ? ' .
90 1
                'AND ((`items`.`status` & ' . StatusFlag::STARRED . ') = ' .
91 1
                StatusFlag::STARRED . ')' .
92
            'LEFT OUTER JOIN `*PREFIX*news_folders` `folders` ' .
93 1
                'ON `folders`.`id` = `feeds`.`folder_id` ' .
94
            'WHERE `feeds`.`folder_id` = 0 ' .
95 1
                'OR `folders`.`deleted_at` = 0';
96
97 1
        $params = [$userId];
98
99
        $result = $this->execute($sql, $params)->fetch();
100
101 1
        return (int) $result['size'];
102
    }
103 1
104 1
105 1
    public function readAll($highestItemId, $time, $userId) {
106 1
        $sql = 'UPDATE `*PREFIX*news_items` ' .
107 1
            'SET `status` = `status` & ? ' .
108 1
            ', `last_modified` = ? ' .
109 1
            'WHERE `feed_id` IN (' .
110 1
                'SELECT `id` FROM `*PREFIX*news_feeds` ' .
111 1
                    'WHERE `user_id` = ? ' .
112 1
                ') '.
113
            'AND `id` <= ?';
114
        $params = [~StatusFlag::UNREAD, $time, $userId, $highestItemId];
115 1
        $this->execute($sql, $params);
116
    }
117 1
118 1
119 1
    public function readFolder($folderId, $highestItemId, $time, $userId) {
120 1
        $sql = 'UPDATE `*PREFIX*news_items` ' .
121 1
            'SET `status` = `status` & ? ' .
122 1
            ', `last_modified` = ? ' .
123 1
            'WHERE `feed_id` IN (' .
124 1
                'SELECT `id` FROM `*PREFIX*news_feeds` ' .
125 1
                    'WHERE `folder_id` = ? ' .
126 1
                    'AND `user_id` = ? ' .
127 1
                ') '.
128 1
            'AND `id` <= ?';
129
        $params = [~StatusFlag::UNREAD, $time, $folderId, $userId,
130
            $highestItemId];
131 1
        $this->execute($sql, $params);
132
    }
133 1
134 1
135 1
    public function readFeed($feedId, $highestItemId, $time, $userId){
136 1
        $sql = 'UPDATE `*PREFIX*news_items` ' .
137 1
            'SET `status` = `status` & ? ' .
138 1
            ', `last_modified` = ? ' .
139 1
                'WHERE `feed_id` = ? ' .
140 1
                'AND `id` <= ? ' .
141 1
                'AND EXISTS (' .
142 1
                    'SELECT * FROM `*PREFIX*news_feeds` ' .
143
                    'WHERE `user_id` = ? ' .
144 1
                    'AND `id` = ? ) ';
145 1
        $params = [~StatusFlag::UNREAD, $time, $feedId, $highestItemId,
146
            $userId, $feedId];
147
148 12
        $this->execute($sql, $params);
149 12
    }
150 3
151
152 9
    private function getOperator($oldestFirst) {
153
        if($oldestFirst) {
154
            return '>';
155
        } else {
156
            return '<';
157 1
        }
158 1
    }
159 1
160 1
161 1
    public function findAllNew($updatedSince, $status, $userId){
162
        $sql = $this->makeSelectQueryStatus(
163
            'AND `items`.`last_modified` >= ? ', $status);
164
        $params = [$userId, $updatedSince];
165 1
        return $this->findEntities($sql, $params);
166
    }
167 1
168 1
169 1
    public function findAllNewFolder($id, $updatedSince, $status, $userId){
170 1
        $sql = 'AND `feeds`.`folder_id` = ? ' .
171
                'AND `items`.`last_modified` >= ? ';
172
        $sql = $this->makeSelectQueryStatus($sql, $status);
173
        $params = [$userId, $id, $updatedSince];
174 1
        return $this->findEntities($sql, $params);
175
    }
176 1
177 1
178 1
    public function findAllNewFeed($id, $updatedSince, $status, $userId){
179 1
        $sql = 'AND `items`.`feed_id` = ? ' .
180
                'AND `items`.`last_modified` >= ? ';
181
        $sql = $this->makeSelectQueryStatus($sql, $status);
182
        $params = [$userId, $id, $updatedSince];
183 15
        return $this->findEntities($sql, $params);
184
    }
185 15
186 12
187
    private function findEntitiesIgnoringNegativeLimit($sql, $params, $limit) {
188 3
        // ignore limit if negative to offer a way to return all feeds
189
        if ($limit >= 0) {
190
            return $this->findEntities($sql, $params, $limit);
191
        } else {
192
            return $this->findEntities($sql, $params);
193 5
        }
194
    }
195 5
196 5
197 5
    public function findAllFeed($id, $limit, $offset, $status, $oldestFirst,
198
                                $userId, $search=[]){
199 5
        $params = [$userId];
200 5
        $params = array_merge($params, $this->buildLikeParameters($search));
201
        $params[] = $id;
202 4
203 4
        $sql = 'AND `items`.`feed_id` = ? ';
204 4
        if($offset !== 0){
205 5
            $sql .= 'AND `items`.`id` ' .
206 5
                $this->getOperator($oldestFirst) . ' ? ';
207 5
            $params[] = $offset;
208
        }
209
        $sql = $this->makeSelectQueryStatus($sql, $status, $oldestFirst,
210
                                            $search);
211 5
        return $this->findEntitiesIgnoringNegativeLimit($sql, $params, $limit);
212
    }
213 5
214 5
215 5
    public function findAllFolder($id, $limit, $offset, $status, $oldestFirst,
216
                                  $userId, $search=[]){
217 5
        $params = [$userId];
218 5
        $params = array_merge($params, $this->buildLikeParameters($search));
219
        $params[] = $id;
220 4
221 4
        $sql = 'AND `feeds`.`folder_id` = ? ';
222 4
        if($offset !== 0){
223 5
            $sql .= 'AND `items`.`id` ' .
224 5
                $this->getOperator($oldestFirst) . ' ? ';
225 5
            $params[] = $offset;
226
        }
227
        $sql = $this->makeSelectQueryStatus($sql, $status, $oldestFirst,
228
                                            $search);
229 5
        return $this->findEntitiesIgnoringNegativeLimit($sql, $params, $limit);
230
    }
231 5
232 5
233 5
    public function findAll($limit, $offset, $status, $oldestFirst, $userId,
234 5
                            $search=[]){
235
        $params = [$userId];
236 4
        $params = array_merge($params, $this->buildLikeParameters($search));
237 4
        $sql = '';
238 4
        if($offset !== 0){
239 5
            $sql .= 'AND `items`.`id` ' .
240 5
                $this->getOperator($oldestFirst) . ' ? ';
241
            $params[] = $offset;
242 5
        }
243
        $sql = $this->makeSelectQueryStatus($sql, $status, $oldestFirst,
244
                                            $search);
245
246 1
        return $this->findEntitiesIgnoringNegativeLimit($sql, $params, $limit);
247 1
    }
248 1
249 1
250 1
    public function findAllUnreadOrStarred($userId) {
251 1
        $params = [$userId];
252
        $status = StatusFlag::UNREAD | StatusFlag::STARRED;
253
        $sql = 'AND ((`items`.`status` & ' . $status . ') > 0) ';
254
        $sql = $this->makeSelectQuery($sql);
255 1
        return $this->findEntities($sql, $params);
256 1
    }
257
258 1
259
    public function findByGuidHash($guidHash, $feedId, $userId){
260 1
        $sql = $this->makeSelectQuery(
261
            'AND `items`.`guid_hash` = ? ' .
262
            'AND `feeds`.`id` = ? ');
263
264
        return $this->findEntity($sql, [$userId, $guidHash, $feedId]);
265
    }
266
267
268
    /**
269 2
     * Delete all items for feeds that have over $threshold unread and not
270 2
     * starred items
271 2
	 * @param int $threshold the number of items that should be deleted
272
     */
273
    public function deleteReadOlderThanThreshold($threshold){
274 2
        $status = StatusFlag::STARRED | StatusFlag::UNREAD;
275 2
        $params = [$status, $threshold];
276 2
277 2
        $sql = 'SELECT (COUNT(*) - `feeds`.`articles_per_update`) AS `size`, ' .
278 2
        '`feeds`.`id` AS `feed_id`, `feeds`.`articles_per_update` ' .
279 2
            'FROM `*PREFIX*news_items` `items` ' .
280 2
            'JOIN `*PREFIX*news_feeds` `feeds` ' .
281
                'ON `feeds`.`id` = `items`.`feed_id` ' .
282 2
                'AND NOT ((`items`.`status` & ?) > 0) ' .
283
            'GROUP BY `feeds`.`id`, `feeds`.`articles_per_update` ' .
284 2
            'HAVING COUNT(*) > ?';
285
286 2
        $result = $this->execute($sql, $params);
287 2
288
        while($row = $result->fetch()) {
289 2
290 1
            $size = (int) $row['size'];
291
            $limit = $size - $threshold;
292
293 1
            if($limit > 0) {
294 1
                $params = [$status, $row['feed_id'], $limit];
295 1
296 1
                $sql = 'DELETE FROM `*PREFIX*news_items` ' .
297 1
                'WHERE `id` IN (' .
298 1
                    'SELECT `id` FROM `*PREFIX*news_items` ' .
299 1
                    'WHERE NOT ((`status` & ?) > 0) ' .
300
                    'AND `feed_id` = ? ' .
301 1
                    'ORDER BY `id` ASC ' .
302 1
                    'LIMIT ?' .
303 2
                ')';
304
305 2
                $this->execute($sql, $params);
306
            }
307
        }
308 2
309
    }
310 2
311 2
312 2
    public function getNewestItemId($userId) {
313 2
        $sql = 'SELECT MAX(`items`.`id`) AS `max_id` ' .
314 2
            'FROM `*PREFIX*news_items` `items` '.
315
            'JOIN `*PREFIX*news_feeds` `feeds` ' .
316 2
                'ON `feeds`.`id` = `items`.`feed_id` '.
317
                'AND `feeds`.`user_id` = ?';
318 1
        $params = [$userId];
319
320
        $result = $this->findOneQuery($sql, $params);
321
322
        return (int) $result['max_id'];
323
    }
324
325
326 1
    /**
327
     * Deletes all items of a user
328 1
     * @param string $userId the name of the user
329 1
     */
330 1
    public function deleteUser($userId) {
331 1
        $sql = 'DELETE FROM `*PREFIX*news_items` ' .
332
            'WHERE `feed_id` IN (' .
333 1
                'SELECT `feeds`.`id` FROM `*PREFIX*news_feeds` `feeds` ' .
334 1
                    'WHERE `feeds`.`user_id` = ?' .
335
                ')';
336
337
        $this->execute($sql, [$userId]);
338
    }
339
340
341
    /**
342
     * Returns a list of ids and userid of all items
343
     */
344
    public function findAllItemIdsAndUsers($limit=null, $offset=null) {
345
        $sql = 'SELECT `items`.`id`, `feeds`.`user_id` ' .
346
                'FROM `*PREFIX*news_items` `items` ' .
347
                'JOIN `*PREFIX*news_feeds` `feeds` ' .
348
                    'ON `items`.`feed_id` = `feeds`.`id`';
349
        return $this->execute($sql, [], $limit, $offset)->fetchAll();
350
    }
351
352
    /**
353
     * Update search indices of all items
354
     */
355
    public function updateSearchIndices() {
356
        // update indices in steps to prevent memory issues on larger systems
357
        $step = 1000;  // update 1000 items at a time
358
        $itemCount = 1;
359
        $offset = 0;
360
361
        // stop condition if there are no previously fetched items
362
        while ($itemCount > 0) {
363
            $items = $this->findAllItemIdsAndUsers($step, $offset);
364
            $itemCount = count($items);
365
            $this->updateSearchIndex($items);
366
            $offset += $step;
367
        }
368
    }
369
370
    private function updateSearchIndex(array $items=[]) {
371
        foreach ($items as $row) {
372
            $item = $this->find($row['id'], $row['user_id']);
373
            $item->generateSearchIndex();
374
            $this->update($item);
375
        }
376
    }
377
378
379
}
380