Completed
Push — master ( 4831e4...32ef47 )
by
unknown
09:35
created

FeedService::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 21
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 21
rs 9.3143
c 1
b 0
f 0
ccs 13
cts 13
cp 1
cc 1
eloc 20
nc 1
nop 9
crap 1

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