FeedService::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 21
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 21
rs 9.3142
c 0
b 0
f 0
cc 1
eloc 20
nc 1
nop 9

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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\Service;
15
16
use HTMLPurifier;
17
18
use OCP\ILogger;
19
use OCP\IL10N;
20
use OCP\AppFramework\Db\DoesNotExistException;
21
use OCP\AppFramework\Utility\ITimeFactory;
22
23
use OCA\News\Db\Feed;
24
use OCA\News\Db\Item;
25
use OCA\News\Db\FeedMapper;
26
use OCA\News\Db\ItemMapper;
27
use OCA\News\Fetcher\Fetcher;
28
use OCA\News\Fetcher\FetcherException;
29
use OCA\News\Config\Config;
30
31
32
class FeedService extends Service {
33
34
    private $feedFetcher;
35
    private $itemMapper;
36
    private $feedMapper;
37
    private $logger;
38
    private $l10n;
39
    private $timeFactory;
40
    private $autoPurgeMinimumInterval;
41
    private $purifier;
42
    private $loggerParams;
43
44
    public function __construct(FeedMapper $feedMapper,
45
                                Fetcher $feedFetcher,
46
                                ItemMapper $itemMapper,
47
                                ILogger $logger,
48
                                IL10N $l10n,
49
                                ITimeFactory $timeFactory,
50
                                Config $config,
51
                                HTMLPurifier $purifier,
52
                                $LoggerParameters){
53
        parent::__construct($feedMapper);
54
        $this->feedFetcher = $feedFetcher;
55
        $this->itemMapper = $itemMapper;
56
        $this->logger = $logger;
57
        $this->l10n = $l10n;
58
        $this->timeFactory = $timeFactory;
59
        $this->autoPurgeMinimumInterval =
60
            $config->getAutoPurgeMinimumInterval();
61
        $this->purifier = $purifier;
62
        $this->feedMapper = $feedMapper;
63
        $this->loggerParams = $LoggerParameters;
64
    }
65
66
    /**
67
     * Finds all feeds of a user
68
     * @param string $userId the name of the user
69
     * @return Feed[]
70
     */
71
    public function findAll($userId){
72
        return $this->feedMapper->findAllFromUser($userId);
73
    }
74
75
76
    /**
77
     * Finds all feeds from all users
78
     * @return array of feeds
79
     */
80
    public function findAllFromAllUsers() {
81
        return $this->feedMapper->findAll();
82
    }
83
84
85
    /**
86
     * Creates a new feed
87
     * @param string $feedUrl the url to the feed
88
     * @param int $folderId the folder where it should be put into, 0 for root
89
     * folder
90
     * @param string $userId for which user the feed should be created
91
     * @param string $title if given, this is used for the opml feed title
92
     * @param string $basicAuthUser if given, basic auth is set for this feed
93
     * @param string $basicAuthPassword if given, basic auth is set for this
94
     * feed. Ignored if user is null or an empty string
95
     * @throws ServiceConflictException if the feed exists already
96
     * @throws ServiceNotFoundException if the url points to an invalid feed
97
     * @return Feed the newly created feed
98
     */
99
    public function create($feedUrl, $folderId, $userId, $title=null,
100
                           $basicAuthUser=null, $basicAuthPassword=null){
101
        // first try if the feed exists already
102
        try {
103
            list($feed, $items) = $this->feedFetcher->fetch($feedUrl, true,
104
                                  null, null, false, $basicAuthUser,
105
                                  $basicAuthPassword);
106
107
            // try again if feed exists depending on the reported link
108
            try {
109
                $this->feedMapper->findByUrlHash($feed->getUrlHash(), $userId);
110
                throw new ServiceConflictException(
111
                    $this->l10n->t('Can not add feed: Exists already'));
112
113
            // If no matching feed was found everything was ok
114
            } catch(DoesNotExistException $ex){}
115
116
            // insert feed
117
            $itemCount = count($items);
118
            $feed->setBasicAuthUser($basicAuthUser);
119
            $feed->setBasicAuthPassword($basicAuthPassword);
120
            $feed->setFolderId($folderId);
121
            $feed->setUserId($userId);
122
            $feed->setArticlesPerUpdate($itemCount);
123
124
            if ($title !== null && $title !== '') {
125
                $feed->setTitle($title);
126
            }
127
128
            $feed = $this->feedMapper->insert($feed);
129
130
            // insert items in reverse order because the first one is usually
131
            // the newest item
132
            $unreadCount = 0;
133
            for($i=$itemCount-1; $i>=0; $i--){
134
                $item = $items[$i];
135
                $item->setFeedId($feed->getId());
136
137
                // check if item exists (guidhash is the same)
138
                // and ignore it if it does
139
                try {
140
                    $this->itemMapper->findByGuidHash(
141
                        $item->getGuidHash(), $item->getFeedId(), $userId);
142
                    continue;
143
                } catch(DoesNotExistException $ex){
144
                    $unreadCount += 1;
145
                    $item->setBody($this->purifier->purify($item->getBody()));
146
                    $this->itemMapper->insert($item);
147
                }
148
            }
149
150
            // set unread count
151
            $feed->setUnreadCount($unreadCount);
152
153
            return $feed;
154
        } catch(FetcherException $ex){
155
            $this->logger->debug($ex->getMessage(), $this->loggerParams);
156
            throw new ServiceNotFoundException($ex->getMessage());
157
        }
158
    }
159
160
161
    /**
162
     * Runs all the feed updates
163
     */
164
    public function updateAll(){
165
        // TODO: this method is not covered by any tests
166
        $feeds = $this->feedMapper->findAll();
167
        foreach($feeds as $feed){
168
            try {
169
                $this->update($feed->getId(), $feed->getUserId());
170
            } catch(\Exception $ex){
171
                // something is really wrong here, log it
172
                $this->logger->error(
173
                    'Unexpected error when updating feed ' . $ex->getMessage(),
174
                    $this->loggerParams
175
                );
176
            }
177
        }
178
    }
179
180
181
    /**
182
     * Updates a single feed
183
     * @param int $feedId the id of the feed that should be updated
184
     * @param string $userId the id of the user
185
     * @param bool $forceUpdate update even if the article exists already
186
     * @throws ServiceNotFoundException if the feed does not exist
187
     * @return Feed the updated feed entity
188
     */
189
    public function update($feedId, $userId, $forceUpdate=false){
190
        $existingFeed = $this->find($feedId, $userId);
191
192
        if($existingFeed->getPreventUpdate() === true) {
193
            return $existingFeed;
194
        }
195
196
        // for backwards compability it can be that the location is not set
197
        // yet, if so use the url
198
        $location = $existingFeed->getLocation();
199
        if (!$location) {
200
            $location = $existingFeed->getUrl();
201
        }
202
203
        try {
204
            list($fetchedFeed, $items) = $this->feedFetcher->fetch(
205
                $location,
206
                false,
207
                $existingFeed->getHttpLastModified(),
208
                $existingFeed->getHttpEtag(),
209
                $existingFeed->getFullTextEnabled(),
210
                $existingFeed->getBasicAuthUser(),
211
                $existingFeed->getBasicAuthPassword()
212
            );
213
214
            // if there is no feed it means that no update took place
215
            if (!$fetchedFeed) {
216
                return $existingFeed;
217
            }
218
219
            // update number of articles on every feed update
220
            $itemCount = count($items);
221
222
            // this is needed to adjust to updates that add more items
223
            // than when the feed was created. You can't update the count
224
            // if it's lower because it may be due to the caching headers
225
            // that were sent as the request and it might cause unwanted
226
            // deletion and reappearing of feeds
227
            if ($itemCount > $existingFeed->getArticlesPerUpdate()) {
228
                $existingFeed->setArticlesPerUpdate($itemCount);
229
            }
230
231
            $existingFeed->setHttpLastModified(
232
                $fetchedFeed->getHttpLastModified());
233
            $existingFeed->setHttpEtag($fetchedFeed->getHttpEtag());
234
            $existingFeed->setLocation($fetchedFeed->getLocation());
235
236
            // insert items in reverse order because the first one is
237
            // usually the newest item
238
            for($i=$itemCount-1; $i>=0; $i--){
239
                $item = $items[$i];
240
                $item->setFeedId($existingFeed->getId());
241
242
                try {
243
                    $dbItem = $this->itemMapper->findByGuidHash(
244
                        $item->getGuidHash(), $feedId, $userId
245
                    );
246
247
                    // in case of update
248
                    if ($forceUpdate ||
249
                        $item->getPubDate() > $dbItem->getPubDate()) {
250
251
                        $dbItem->setTitle($item->getTitle());
252
                        $dbItem->setUrl($item->getUrl());
253
                        $dbItem->setAuthor($item->getAuthor());
254
                        $dbItem->setSearchIndex($item->getSearchIndex());
255
                        $dbItem->setRtl($item->getRtl());
256
                        $dbItem->setLastModified($item->getLastModified());
257
                        $dbItem->setPubDate($item->getPubDate());
258
                        $dbItem->setEnclosureMime($item->getEnclosureMime());
259
                        $dbItem->setEnclosureLink($item->getEnclosureLink());
260
                        $dbItem->setBody(
261
                            $this->purifier->purify($item->getBody())
262
                        );
263
264
                        // update modes: 0 nothing, 1 set unread
265
                        if ($existingFeed->getUpdateMode() === 1) {
266
                            $dbItem->setUnread();
267
                        }
268
269
                        $this->itemMapper->update($dbItem);
270
                    }
271
                } catch(DoesNotExistException $ex){
272
                    $item->setBody(
273
                        $this->purifier->purify($item->getBody())
274
                    );
275
                    $this->itemMapper->insert($item);
276
                }
277
            }
278
279
            // mark feed as successfully updated
280
            $existingFeed->setUpdateErrorCount(0);
281
            $existingFeed->setLastUpdateError('');
282
283
        } catch(FetcherException $ex){
284
            $existingFeed->setUpdateErrorCount(
285
                $existingFeed->getUpdateErrorCount()+1
286
            );
287
            $existingFeed->setLastUpdateError($ex->getMessage());
288
        }
289
290
        $this->feedMapper->update($existingFeed);
291
292
        return $this->find($feedId, $userId);
293
    }
294
295
    /**
296
     * Import articles
297
     * @param array $json the array with json
298
     * @param string $userId the username
299
     * @return Feed if one had to be created for nonexistent feeds
300
     */
301
    public function importArticles($json, $userId) {
302
        $url = 'http://owncloud/nofeed';
303
        $urlHash = md5($url);
304
305
        // build assoc array for fast access
306
        $feeds = $this->findAll($userId);
307
        $feedsDict = [];
308
        foreach($feeds as $feed) {
309
            $feedsDict[$feed->getLink()] = $feed;
310
        }
311
312
        $createdFeed = false;
313
314
        // loop over all items and get the corresponding feed
315
        // if the feed does not exist, create a separate feed for them
316
        foreach ($json as $entry) {
317
            $item = Item::fromImport($entry);
318
            $item->setLastModified($this->timeFactory->getTime());
319
            $feedLink = $entry['feedLink'];  // this is not set on the item yet
320
321
            if(array_key_exists($feedLink, $feedsDict)) {
322
                $feed = $feedsDict[$feedLink];
323
                $item->setFeedId($feed->getId());
324
            } elseif(array_key_exists($url, $feedsDict)) {
325
                $feed = $feedsDict[$url];
326
                $item->setFeedId($feed->getId());
327
            } else {
328
                $createdFeed = true;
329
                $feed = new Feed();
330
                $feed->setUserId($userId);
331
                $feed->setLink($url);
332
                $feed->setUrl($url);
333
                $feed->setTitle($this->l10n->t('Articles without feed'));
334
                $feed->setAdded($this->timeFactory->getTime());
335
                $feed->setFolderId(0);
336
                $feed->setPreventUpdate(true);
337
                $feed = $this->feedMapper->insert($feed);
338
339
                $item->setFeedId($feed->getId());
340
                $feedsDict[$feed->getLink()] = $feed;
341
            }
342
343
            try {
344
                // if item exists, copy the status
345
                $existingItem = $this->itemMapper->findByGuidHash(
346
                    $item->getGuidHash(), $feed->getId(), $userId);
347
                $existingItem->setStatus($item->getStatus());
348
                $this->itemMapper->update($existingItem);
349
            } catch(DoesNotExistException $ex){
350
                $item->setBody($this->purifier->purify($item->getBody()));
351
                $item->generateSearchIndex();
352
                $this->itemMapper->insert($item);
353
            }
354
        }
355
356
        if($createdFeed) {
357
            return $this->feedMapper->findByUrlHash($urlHash, $userId);
358
        }
359
360
        return null;
361
    }
362
363
364
    /**
365
     * Use this to mark a feed as deleted. That way it can be un-deleted
366
     * @param int $feedId the id of the feed that should be deleted
367
     * @param string $userId the name of the user for security reasons
368
     * @throws ServiceNotFoundException when feed does not exist
369
     */
370
    public function markDeleted($feedId, $userId) {
371
        $feed = $this->find($feedId, $userId);
372
        $feed->setDeletedAt($this->timeFactory->getTime());
373
        $this->feedMapper->update($feed);
374
    }
375
376
377
    /**
378
     * Use this to undo a feed deletion
379
     * @param int $feedId the id of the feed that should be restored
380
     * @param string $userId the name of the user for security reasons
381
     * @throws ServiceNotFoundException when feed does not exist
382
     */
383
    public function unmarkDeleted($feedId, $userId) {
384
        $feed = $this->find($feedId, $userId);
385
        $feed->setDeletedAt(0);
386
        $this->feedMapper->update($feed);
387
    }
388
389
390
    /**
391
     * Deletes all deleted feeds
392
     * @param string $userId if given it purges only feeds of that user
393
     * @param boolean $useInterval defaults to true, if true it only purges
394
     * entries in a given interval to give the user a chance to undo the
395
     * deletion
396
     */
397
    public function purgeDeleted($userId=null, $useInterval=true) {
398
        $deleteOlderThan = null;
399
400
        if ($useInterval) {
401
            $now = $this->timeFactory->getTime();
402
            $deleteOlderThan = $now - $this->autoPurgeMinimumInterval;
403
        }
404
405
        $toDelete = $this->feedMapper->getToDelete($deleteOlderThan, $userId);
406
407
        foreach ($toDelete as $feed) {
408
            $this->feedMapper->delete($feed);
409
        }
410
    }
411
412
413
    /**
414
     * Deletes all feeds of a user, delete items first since the user_id
415
     * is not defined in there
416
     * @param string $userId the name of the user
417
     */
418
    public function deleteUser($userId) {
419
        $this->feedMapper->deleteUser($userId);
420
    }
421
422
    /**
423
     * @param $feedId
424
     * @param $userId
425
     * @param $diff an array containing the fields to update, e.g.:
426
     *  [
427
     *      'ordering' => 1,
428
     *      'fullTextEnabled' => true,
429
     *      'pinned' => true,
430
     *      'updateMode' => 0,
431
     *      'title' => 'title'
432
     * ]
433
     * @throws ServiceNotFoundException if feed does not exist
434
     */
435
    public function patch($feedId, $userId, $diff=[]) {
436
        $feed = $this->find($feedId, $userId);
437
438
        foreach ($diff as $attribute => $value) {
439
            $method = 'set' . ucfirst($attribute);
440
            $feed->$method($value);
441
        }
442
443
        // special feed updates
444
        if (array_key_exists('fullTextEnabled', $diff)) {
445
            // disable caching for the next update
446
            $feed->setHttpEtag('');
447
            $feed->setHttpLastModified(0);
448
            $this->feedMapper->update($feed);
449
            return $this->update($feedId, $userId, true);
450
        }
451
452
        return $this->feedMapper->update($feed);
453
    }
454
455
}
456