Completed
Pull Request — master (#895)
by
unknown
03:26
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 34
    public function __construct(IDBConnection $db){
22 34
        parent::__construct($db, 'news_items', Item::class);
23 34
    }
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
    public function find($id, $userId){
75 1
        $sql = $this->makeSelectQuery('AND `items`.`id` = ? ');
76 1
        return $this->findEntity($sql, [$userId, $id]);
77
    }
78
79 1
    public function starredCount($userId){
80
        $sql = 'SELECT COUNT(*) AS size FROM `*PREFIX*news_items` `items` '.
81 1
            'JOIN `*PREFIX*news_feeds` `feeds` ' .
82 1
                'ON `feeds`.`id` = `items`.`feed_id` '.
83 1
                'AND `feeds`.`deleted_at` = 0 ' .
84 1
                'AND `feeds`.`user_id` = ? ' .
85 1
                'AND ((`items`.`status` & ' . StatusFlag::STARRED . ') = ' .
86 1
                StatusFlag::STARRED . ')' .
87 1
            'LEFT OUTER JOIN `*PREFIX*news_folders` `folders` ' .
88 1
                'ON `folders`.`id` = `feeds`.`folder_id` ' .
89 1
            'WHERE `feeds`.`folder_id` = 0 ' .
90 1
                'OR `folders`.`deleted_at` = 0';
91
92 1
        $params = [$userId];
93
94 1
        $result = $this->execute($sql, $params)->fetch();
95
96 1
        return (int) $result['size'];
97
    }
98
99
100 1
    public function readAll($highestItemId, $time, $userId) {
101
        $sql = 'UPDATE `*PREFIX*news_items` ' .
102 1
            'SET `status` = `status` & ? ' .
103 1
            ', `last_modified` = ? ' .
104 1
            'WHERE `feed_id` IN (' .
105 1
                'SELECT `id` FROM `*PREFIX*news_feeds` ' .
106 1
                    'WHERE `user_id` = ? ' .
107 1
                ') '.
108 1
            'AND `id` <= ?';
109 1
        $params = [~StatusFlag::UNREAD, $time, $userId, $highestItemId];
110 1
        $this->execute($sql, $params);
111 1
    }
112
113
114 1
    public function readFolder($folderId, $highestItemId, $time, $userId) {
115
        $sql = 'UPDATE `*PREFIX*news_items` ' .
116 1
            'SET `status` = `status` & ? ' .
117 1
            ', `last_modified` = ? ' .
118 1
            'WHERE `feed_id` IN (' .
119 1
                'SELECT `id` FROM `*PREFIX*news_feeds` ' .
120 1
                    'WHERE `folder_id` = ? ' .
121 1
                    'AND `user_id` = ? ' .
122 1
                ') '.
123 1
            'AND `id` <= ?';
124 1
        $params = [~StatusFlag::UNREAD, $time, $folderId, $userId,
125 1
            $highestItemId];
126 1
        $this->execute($sql, $params);
127 1
    }
128
129
130
    public function readFeed($feedId, $highestItemId, $time, $userId){
131
        $sql = 'UPDATE `*PREFIX*news_items` ' .
132
            'SET `status` = `status` & ? ' .
133
            ', `last_modified` = ? ' .
134
                'WHERE `feed_id` = ? ' .
135
                'AND `id` <= ? ' .
136
                'AND EXISTS (' .
137
                    'SELECT * FROM `*PREFIX*news_feeds` ' .
138
                    'WHERE `user_id` = ? ' .
139
                    'AND `id` = ? ) ';
140
        $params = [~StatusFlag::UNREAD, $time, $feedId, $highestItemId,
141
            $userId, $feedId];
142
143
        $this->execute($sql, $params);
144
    }
145
146
    public function read($itemId, $time, $userId) {
147
        $sql = 'UPDATE `*PREFIX*news_items`
148
            SET `status` = `status` & ?,
149
                `last_modified` = ?
150
            WHERE `id` IN (
151
                SELECT `b`.`id` FROM `*PREFIX*news_items` `a`,
152
                                     `*PREFIX*news_items` `b`
153
                WHERE  `a`.`id` = ?
154
                    AND `a`.`fingerprint` = `b`.`fingerprint`
155
                    AND `b`.`feed_id` IN (
156
                        SELECT `id` FROM `*PREFIX*news_feeds`
157
                        WHERE `user_id` = ?
158
                    )
159
            )';
160
        $params = [~StatusFlag::UNREAD, $time, $itemId, $userId];
161
        $this->execute($sql, $params);
162
    }
163
164
165
    private function getOperator($oldestFirst) {
166
        if($oldestFirst) {
167
            return '>';
168
        } else {
169
            return '<';
170 12
        }
171 12
    }
172 3
173
174 9
    public function findAllNew($updatedSince, $status, $userId){
175
        $sql = $this->makeSelectQueryStatus(
176
            'AND `items`.`last_modified` >= ? ', $status);
177
        $params = [$userId, $updatedSince];
178
        return $this->findEntities($sql, $params);
179 1
    }
180 1
181 1
182 1
    public function findAllNewFolder($id, $updatedSince, $status, $userId){
183 1
        $sql = 'AND `feeds`.`folder_id` = ? ' .
184
                'AND `items`.`last_modified` >= ? ';
185
        $sql = $this->makeSelectQueryStatus($sql, $status);
186
        $params = [$userId, $id, $updatedSince];
187 1
        return $this->findEntities($sql, $params);
188
    }
189 1
190 1
191 1
    public function findAllNewFeed($id, $updatedSince, $status, $userId){
192 1
        $sql = 'AND `items`.`feed_id` = ? ' .
193
                'AND `items`.`last_modified` >= ? ';
194
        $sql = $this->makeSelectQueryStatus($sql, $status);
195
        $params = [$userId, $id, $updatedSince];
196 1
        return $this->findEntities($sql, $params);
197
    }
198 1
199 1
200 1
    private function findEntitiesIgnoringNegativeLimit($sql, $params, $limit) {
201 1
        // ignore limit if negative to offer a way to return all feeds
202
        if ($limit >= 0) {
203
            return $this->findEntities($sql, $params, $limit);
204
        } else {
205 15
            return $this->findEntities($sql, $params);
206
        }
207 15
    }
208 12
209
210 3
    public function findAllFeed($id, $limit, $offset, $status, $oldestFirst,
211
                                $userId, $search=[]){
212
        $params = [$userId];
213
        $params = array_merge($params, $this->buildLikeParameters($search));
214
        $params[] = $id;
215 5
216
        $sql = 'AND `items`.`feed_id` = ? ';
217 5
        if($offset !== 0){
218 5
            $sql .= 'AND `items`.`id` ' .
219 5
                $this->getOperator($oldestFirst) . ' ? ';
220
            $params[] = $offset;
221 5
        }
222 5
        $sql = $this->makeSelectQueryStatus($sql, $status, $oldestFirst,
223
                                            $search);
224 4
        return $this->findEntitiesIgnoringNegativeLimit($sql, $params, $limit);
225 4
    }
226 4
227 5
228 5
    public function findAllFolder($id, $limit, $offset, $status, $oldestFirst,
229 5
                                  $userId, $search=[]){
230
        $params = [$userId];
231
        $params = array_merge($params, $this->buildLikeParameters($search));
232
        $params[] = $id;
233 5
234
        $sql = 'AND `feeds`.`folder_id` = ? ';
235 5
        if($offset !== 0){
236 5
            $sql .= 'AND `items`.`id` ' .
237 5
                $this->getOperator($oldestFirst) . ' ? ';
238
            $params[] = $offset;
239 5
        }
240 5
        $sql = $this->makeSelectQueryStatus($sql, $status, $oldestFirst,
241
                                            $search);
242 4
        return $this->findEntitiesIgnoringNegativeLimit($sql, $params, $limit);
243 4
    }
244 4
245 5
246 5
    public function findAll($limit, $offset, $status, $oldestFirst, $userId,
247 5
                            $search=[]){
248
        $params = [$userId];
249
        $params = array_merge($params, $this->buildLikeParameters($search));
250
        $sql = '';
251 5
        if($offset !== 0){
252
            $sql .= 'AND `items`.`id` ' .
253 5
                $this->getOperator($oldestFirst) . ' ? ';
254 5
            $params[] = $offset;
255 5
        }
256 5
        $sql = $this->makeSelectQueryStatus($sql, $status, $oldestFirst,
257
                                            $search);
258 4
259 4
        return $this->findEntitiesIgnoringNegativeLimit($sql, $params, $limit);
260 4
    }
261 5
262 5
263
    public function findAllUnreadOrStarred($userId) {
264 5
        $params = [$userId];
265
        $status = StatusFlag::UNREAD | StatusFlag::STARRED;
266
        $sql = 'AND ((`items`.`status` & ' . $status . ') > 0) ';
267
        $sql = $this->makeSelectQuery($sql);
268 1
        return $this->findEntities($sql, $params);
269 1
    }
270 1
271 1
272 1
    public function findByGuidHash($guidHash, $feedId, $userId){
273 1
        $sql = $this->makeSelectQuery(
274
            'AND `items`.`guid_hash` = ? ' .
275
            'AND `feeds`.`id` = ? ');
276
277 1
        return $this->findEntity($sql, [$userId, $guidHash, $feedId]);
278 1
    }
279
280 1
281
    /**
282 1
     * Delete all items for feeds that have over $threshold unread and not
283
     * starred items
284
	 * @param int $threshold the number of items that should be deleted
285
     */
286
    public function deleteReadOlderThanThreshold($threshold){
287
        $status = StatusFlag::STARRED | StatusFlag::UNREAD;
288
        $params = [$status, $threshold];
289
290
        $sql = 'SELECT (COUNT(*) - `feeds`.`articles_per_update`) AS `size`, ' .
291 2
        '`feeds`.`id` AS `feed_id`, `feeds`.`articles_per_update` ' .
292 2
            'FROM `*PREFIX*news_items` `items` ' .
293 2
            'JOIN `*PREFIX*news_feeds` `feeds` ' .
294
                'ON `feeds`.`id` = `items`.`feed_id` ' .
295
                'AND NOT ((`items`.`status` & ?) > 0) ' .
296 2
            'GROUP BY `feeds`.`id`, `feeds`.`articles_per_update` ' .
297 2
            'HAVING COUNT(*) > ?';
298 2
299 2
        $result = $this->execute($sql, $params);
300 2
301 2
        while($row = $result->fetch()) {
302 2
303
            $size = (int) $row['size'];
304 2
            $limit = $size - $threshold;
305
306 2
            if($limit > 0) {
307
                $params = [$status, $row['feed_id'], $limit];
308 2
309 2
                $sql = 'DELETE FROM `*PREFIX*news_items` ' .
310
                'WHERE `id` IN (' .
311 2
                    'SELECT `id` FROM `*PREFIX*news_items` ' .
312 1
                    'WHERE NOT ((`status` & ?) > 0) ' .
313
                    'AND `feed_id` = ? ' .
314
                    'ORDER BY `id` ASC ' .
315 1
                    'LIMIT ?' .
316 1
                ')';
317 1
318 1
                $this->execute($sql, $params);
319 1
            }
320 1
        }
321 1
322
    }
323 1
324 1
325 2
    public function getNewestItemId($userId) {
326
        $sql = 'SELECT MAX(`items`.`id`) AS `max_id` ' .
327 2
            'FROM `*PREFIX*news_items` `items` '.
328
            'JOIN `*PREFIX*news_feeds` `feeds` ' .
329
                'ON `feeds`.`id` = `items`.`feed_id` '.
330 2
                'AND `feeds`.`user_id` = ?';
331
        $params = [$userId];
332 2
333 2
        $result = $this->findOneQuery($sql, $params);
334 2
335 2
        return (int) $result['max_id'];
336 2
    }
337
338 2
339
    /**
340 1
     * Deletes all items of a user
341
     * @param string $userId the name of the user
342
     */
343
    public function deleteUser($userId) {
344
        $sql = 'DELETE FROM `*PREFIX*news_items` ' .
345
            'WHERE `feed_id` IN (' .
346
                'SELECT `feeds`.`id` FROM `*PREFIX*news_feeds` `feeds` ' .
347
                    'WHERE `feeds`.`user_id` = ?' .
348 1
                ')';
349
350 1
        $this->execute($sql, [$userId]);
351 1
    }
352 1
353 1
354
    /**
355 1
     * Returns a list of ids and userid of all items
356 1
     */
357
    public function findAllItemIdsAndUsers() {
358
        $sql = 'SELECT `items`.`id`, `feeds`.`user_id` ' .
359
                'FROM `*PREFIX*news_items` `items` ' .
360
                'JOIN `*PREFIX*news_feeds` `feeds` ' .
361
                    'ON `items`.`feed_id` = `feeds`.`id`';
362
        return $this->execute($sql)->fetchAll();
363
    }
364
365
366
}
367