API::api_get_headlines()   D
last analyzed

Complexity

Conditions 26
Paths 12

Size

Total Lines 162
Code Lines 99

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 26
eloc 99
c 0
b 0
f 0
nc 12
nop 18
dl 0
loc 162
rs 4.1666

How to fix   Long Method    Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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
class API extends Handler {
3
4
    const API_LEVEL  = 14;
5
6
    const STATUS_OK  = 0;
7
    const STATUS_ERR = 1;
8
9
    private $seq;
10
11
    public static function param_to_bool($p) {
12
        return $p && ($p !== "f" && $p !== "false");
13
    }
14
15
    public function before($method) {
16
        if (parent::before($method)) {
17
            header("Content-Type: text/json");
18
19
            if (!$_SESSION["uid"] && $method != "login" && $method != "isloggedin") {
20
                $this->wrap(self::STATUS_ERR, array("error" => 'NOT_LOGGED_IN'));
21
                return false;
22
            }
23
24
            if ($_SESSION["uid"] && $method != "logout" && !get_pref('ENABLE_API_ACCESS')) {
25
                $this->wrap(self::STATUS_ERR, array("error" => 'API_DISABLED'));
26
                return false;
27
            }
28
29
            $this->seq = (int) clean($_REQUEST['seq']);
30
31
            return true;
32
        }
33
        return false;
34
    }
35
36
    public function wrap($status, $reply) {
37
        print json_encode(array("seq" => $this->seq,
38
            "status" => $status,
39
            "content" => $reply));
40
    }
41
42
    public function getVersion() {
43
        $rv = array("version" => VERSION);
0 ignored issues
show
Bug introduced by
The constant VERSION was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
44
        $this->wrap(self::STATUS_OK, $rv);
45
    }
46
47
    public function getApiLevel() {
48
        $rv = array("level" => self::API_LEVEL);
49
        $this->wrap(self::STATUS_OK, $rv);
50
    }
51
52
    public function login() {
53
        @session_destroy();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for session_destroy(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

53
        /** @scrutinizer ignore-unhandled */ @session_destroy();

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
54
        @session_start();
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for session_start(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

54
        /** @scrutinizer ignore-unhandled */ @session_start();

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
55
56
        $login = clean($_REQUEST["user"]);
57
        $password = clean($_REQUEST["password"]);
58
        $password_base64 = base64_decode(clean($_REQUEST["password"]));
0 ignored issues
show
Bug introduced by
It seems like clean($_REQUEST['password']) can also be of type array; however, parameter $data of base64_decode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

58
        $password_base64 = base64_decode(/** @scrutinizer ignore-type */ clean($_REQUEST["password"]));
Loading history...
59
60
        if (SINGLE_USER_MODE) {
0 ignored issues
show
Bug introduced by
The constant SINGLE_USER_MODE was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
61
            $login = "admin";
62
        }
63
64
        $sth = $this->pdo->prepare("SELECT id FROM ttrss_users WHERE login = ?");
65
        $sth->execute([$login]);
66
67
        if ($row = $sth->fetch()) {
68
            $uid = $row["id"];
69
        } else {
70
            $uid = 0;
71
        }
72
73
        if (!$uid) {
74
            $this->wrap(self::STATUS_ERR, array("error" => "LOGIN_ERROR"));
75
            return;
76
        }
77
78
        if (get_pref("ENABLE_API_ACCESS", $uid)) {
79
            if (authenticate_user($login, $password, false, Auth_Base::AUTH_SERVICE_API)) {               // try login with normal password
80
                $this->wrap(self::STATUS_OK, array("session_id" => session_id(),
81
                    "api_level" => self::API_LEVEL));
82
            } else if (authenticate_user($login, $password_base64, false, Auth_Base::AUTH_SERVICE_API)) { // else try with base64_decoded password
83
                $this->wrap(self::STATUS_OK, array("session_id" => session_id(),
84
                    "api_level" => self::API_LEVEL));
85
            } else {                                                         // else we are not logged in
86
                user_error("Failed login attempt for $login from {$_SERVER['REMOTE_ADDR']}", E_USER_WARNING);
87
                $this->wrap(self::STATUS_ERR, array("error" => "LOGIN_ERROR"));
88
            }
89
        } else {
90
            $this->wrap(self::STATUS_ERR, array("error" => "API_DISABLED"));
91
        }
92
93
    }
94
95
    public function logout() {
96
        logout_user();
97
        $this->wrap(self::STATUS_OK, array("status" => "OK"));
98
    }
99
100
    public function isLoggedIn() {
101
        $this->wrap(self::STATUS_OK, array("status" => $_SESSION["uid"] != ''));
102
    }
103
104
    public function getUnread() {
105
        $feed_id = clean($_REQUEST["feed_id"]);
106
        $is_cat = clean($_REQUEST["is_cat"]);
107
108
        if ($feed_id) {
109
            $this->wrap(self::STATUS_OK, array("unread" => getFeedUnread($feed_id, $is_cat)));
110
        } else {
111
            $this->wrap(self::STATUS_OK, array("unread" => Feeds::getGlobalUnread()));
112
        }
113
    }
114
115
    /* Method added for ttrss-reader for Android */
116
    public function getCounters() {
117
        $this->wrap(self::STATUS_OK, Counters::getAllCounters());
118
    }
119
120
    public function getFeeds() {
121
        $cat_id = clean($_REQUEST["cat_id"]);
122
        $unread_only = API::param_to_bool(clean($_REQUEST["unread_only"]));
123
        $limit = (int) clean($_REQUEST["limit"]);
124
        $offset = (int) clean($_REQUEST["offset"]);
125
        $include_nested = API::param_to_bool(clean($_REQUEST["include_nested"]));
126
127
        $feeds = $this->api_get_feeds($cat_id, $unread_only, $limit, $offset, $include_nested);
128
129
        $this->wrap(self::STATUS_OK, $feeds);
130
    }
131
132
    public function getCategories() {
133
        $unread_only = API::param_to_bool(clean($_REQUEST["unread_only"]));
134
        $enable_nested = API::param_to_bool(clean($_REQUEST["enable_nested"]));
135
        $include_empty = API::param_to_bool(clean($_REQUEST['include_empty']));
136
137
        // TODO do not return empty categories, return Uncategorized and standard virtual cats
138
139
        if ($enable_nested) {
140
                    $nested_qpart = "parent_cat IS NULL";
141
        } else {
142
                    $nested_qpart = "true";
143
        }
144
145
        $sth = $this->pdo->prepare("SELECT
146
				id, title, order_id, (SELECT COUNT(id) FROM
147
				ttrss_feeds WHERE
148
				ttrss_feed_categories.id IS NOT NULL AND cat_id = ttrss_feed_categories.id) AS num_feeds,
149
			(SELECT COUNT(id) FROM
150
				ttrss_feed_categories AS c2 WHERE
151
				c2.parent_cat = ttrss_feed_categories.id) AS num_cats
152
			FROM ttrss_feed_categories
153
			WHERE $nested_qpart AND owner_uid = ?");
154
        $sth->execute([$_SESSION['uid']]);
155
156
        $cats = array();
157
158
        while ($line = $sth->fetch()) {
159
            if ($include_empty || $line["num_feeds"] > 0 || $line["num_cats"] > 0) {
160
                $unread = getFeedUnread($line["id"], true);
161
162
                if ($enable_nested) {
163
                                    $unread += Feeds::getCategoryChildrenUnread($line["id"]);
164
                }
165
166
                if ($unread || !$unread_only) {
167
                    array_push($cats, array("id" => $line["id"],
168
                        "title" => $line["title"],
169
                        "unread" => $unread,
170
                        "order_id" => (int) $line["order_id"],
171
                    ));
172
                }
173
            }
174
        }
175
176
        foreach (array(-2, -1, 0) as $cat_id) {
177
            if ($include_empty || !$this->isCategoryEmpty($cat_id)) {
178
                $unread = getFeedUnread($cat_id, true);
179
180
                if ($unread || !$unread_only) {
181
                    array_push($cats, array("id" => $cat_id,
182
                        "title" => Feeds::getCategoryTitle($cat_id),
183
                        "unread" => $unread));
184
                }
185
            }
186
        }
187
188
        $this->wrap(self::STATUS_OK, $cats);
189
    }
190
191
    public function getHeadlines() {
192
        $feed_id = clean($_REQUEST["feed_id"]);
193
        if ($feed_id !== "") {
194
195
            if (is_numeric($feed_id)) {
196
                $feed_id = (int) $feed_id;
197
            }
198
199
            $limit = (int) clean($_REQUEST["limit"]);
200
201
            if (!$limit || $limit >= 200) {
202
                $limit = 200;
203
            }
204
205
            $offset = (int) clean($_REQUEST["skip"]);
206
            $filter = clean($_REQUEST["filter"]);
207
            $is_cat = API::param_to_bool(clean($_REQUEST["is_cat"]));
208
            $show_excerpt = API::param_to_bool(clean($_REQUEST["show_excerpt"]));
209
            $show_content = API::param_to_bool(clean($_REQUEST["show_content"]));
210
            /* all_articles, unread, adaptive, marked, updated */
211
            $view_mode = clean($_REQUEST["view_mode"]);
212
            $include_attachments = API::param_to_bool(clean($_REQUEST["include_attachments"]));
213
            $since_id = (int) clean($_REQUEST["since_id"]);
214
            $include_nested = API::param_to_bool(clean($_REQUEST["include_nested"]));
215
            $sanitize_content = !isset($_REQUEST["sanitize"]) ||
216
                API::param_to_bool($_REQUEST["sanitize"]);
217
            $force_update = API::param_to_bool(clean($_REQUEST["force_update"]));
218
            $has_sandbox = API::param_to_bool(clean($_REQUEST["has_sandbox"]));
219
            $excerpt_length = (int) clean($_REQUEST["excerpt_length"]);
220
            $check_first_id = (int) clean($_REQUEST["check_first_id"]);
221
            $include_header = API::param_to_bool(clean($_REQUEST["include_header"]));
222
223
            $_SESSION['hasSandbox'] = $has_sandbox;
224
225
            $skip_first_id_check = false;
226
227
            $override_order = false;
228
            switch (clean($_REQUEST["order_by"])) {
229
            case "title":
230
                $override_order = "ttrss_entries.title, date_entered, updated";
231
                break;
232
            case "date_reverse":
233
                $override_order = "score DESC, date_entered, updated";
234
                $skip_first_id_check = true;
235
                break;
236
            case "feed_dates":
237
                $override_order = "updated DESC";
238
                break;
239
            }
240
241
            /* do not rely on params below */
242
243
            $search = clean($_REQUEST["search"]);
244
245
            list($headlines, $headlines_header) = $this->api_get_headlines($feed_id, $limit, $offset,
246
                $filter, $is_cat, $show_excerpt, $show_content, $view_mode, $override_order,
247
                $include_attachments, $since_id, $search,
248
                $include_nested, $sanitize_content, $force_update, $excerpt_length, $check_first_id, $skip_first_id_check);
249
250
            if ($include_header) {
251
                $this->wrap(self::STATUS_OK, array($headlines_header, $headlines));
252
            } else {
253
                $this->wrap(self::STATUS_OK, $headlines);
254
            }
255
        } else {
256
            $this->wrap(self::STATUS_ERR, array("error" => 'INCORRECT_USAGE'));
257
        }
258
    }
259
260
    public function updateArticle() {
261
        $article_ids = explode(",", clean($_REQUEST["article_ids"]));
0 ignored issues
show
Bug introduced by
It seems like clean($_REQUEST['article_ids']) can also be of type array; however, parameter $string of explode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

261
        $article_ids = explode(",", /** @scrutinizer ignore-type */ clean($_REQUEST["article_ids"]));
Loading history...
262
        $mode = (int) clean($_REQUEST["mode"]);
263
        $data = clean($_REQUEST["data"]);
264
        $field_raw = (int) clean($_REQUEST["field"]);
265
266
        $field = "";
267
        $set_to = "";
268
269
        switch ($field_raw) {
270
        case 0:
271
            $field = "marked";
272
            $additional_fields = ",last_marked = NOW()";
273
            break;
274
        case 1:
275
            $field = "published";
276
            $additional_fields = ",last_published = NOW()";
277
            break;
278
        case 2:
279
            $field = "unread";
280
            $additional_fields = ",last_read = NOW()";
281
            break;
282
        case 3:
283
            $field = "note";
284
        };
285
286
        switch ($mode) {
287
        case 1:
288
            $set_to = "true";
289
            break;
290
        case 0:
291
            $set_to = "false";
292
            break;
293
        case 2:
294
            $set_to = "not $field";
295
            break;
296
        }
297
298
        if ($field == "note") {
299
            $set_to = $this->pdo->quote($data);
0 ignored issues
show
Bug introduced by
It seems like $data can also be of type array; however, parameter $string of PDO::quote() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

299
            $set_to = $this->pdo->quote(/** @scrutinizer ignore-type */ $data);
Loading history...
300
        }
301
302
        if ($field && $set_to && count($article_ids) > 0) {
303
304
            $article_qmarks = arr_qmarks($article_ids);
305
306
            $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET
307
				$field = $set_to $additional_fields
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $additional_fields does not seem to be defined for all execution paths leading up to this point.
Loading history...
308
				WHERE ref_id IN ($article_qmarks) AND owner_uid = ?");
309
            $sth->execute(array_merge($article_ids, [$_SESSION['uid']]));
310
311
            $num_updated = $sth->rowCount();
312
313
            if ($num_updated > 0 && $field == "unread") {
314
                $sth = $this->pdo->prepare("SELECT DISTINCT feed_id FROM ttrss_user_entries
315
					WHERE ref_id IN ($article_qmarks)");
316
                $sth->execute($article_ids);
317
318
                while ($line = $sth->fetch()) {
319
                    CCache::update($line["feed_id"], $_SESSION["uid"]);
320
                }
321
            }
322
323
            $this->wrap(self::STATUS_OK, array("status" => "OK",
324
                "updated" => $num_updated));
325
326
        } else {
327
            $this->wrap(self::STATUS_ERR, array("error" => 'INCORRECT_USAGE'));
328
        }
329
330
    }
331
332
    public function getArticle() {
333
334
        $article_ids = explode(",", clean($_REQUEST["article_id"]));
0 ignored issues
show
Bug introduced by
It seems like clean($_REQUEST['article_id']) can also be of type array; however, parameter $string of explode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

334
        $article_ids = explode(",", /** @scrutinizer ignore-type */ clean($_REQUEST["article_id"]));
Loading history...
335
        $sanitize_content = !isset($_REQUEST["sanitize"]) ||
336
            API::param_to_bool($_REQUEST["sanitize"]);
337
338
        if ($article_ids) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $article_ids of type string[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
339
340
            $article_qmarks = arr_qmarks($article_ids);
341
342
            $sth = $this->pdo->prepare("SELECT id,guid,title,link,content,feed_id,comments,int_id,
343
				marked,unread,published,score,note,lang,
344
				".SUBSTRING_FOR_DATE."(updated,1,16) as updated,
345
				author,(SELECT title FROM ttrss_feeds WHERE id = feed_id) AS feed_title,
346
				(SELECT site_url FROM ttrss_feeds WHERE id = feed_id) AS site_url,
347
				(SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) AS hide_images
348
				FROM ttrss_entries,ttrss_user_entries
349
				WHERE id IN ($article_qmarks) AND ref_id = id AND owner_uid = ?");
350
351
            $sth->execute(array_merge($article_ids, [$_SESSION['uid']]));
352
353
            $articles = array();
354
355
            while ($line = $sth->fetch()) {
356
357
                $attachments = Article::get_article_enclosures($line['id']);
358
359
                $article = array(
360
                    "id" => $line["id"],
361
                    "guid" => $line["guid"],
362
                    "title" => $line["title"],
363
                    "link" => $line["link"],
364
                    "labels" => Article::get_article_labels($line['id']),
365
                    "unread" => API::param_to_bool($line["unread"]),
366
                    "marked" => API::param_to_bool($line["marked"]),
367
                    "published" => API::param_to_bool($line["published"]),
368
                    "comments" => $line["comments"],
369
                    "author" => $line["author"],
370
                    "updated" => (int) strtotime($line["updated"]),
371
                    "feed_id" => $line["feed_id"],
372
                    "attachments" => $attachments,
373
                    "score" => (int) $line["score"],
374
                    "feed_title" => $line["feed_title"],
375
                    "note" => $line["note"],
376
                    "lang" => $line["lang"]
377
                );
378
379
                if ($sanitize_content) {
380
                    $article["content"] = sanitize(
381
                        $line["content"],
382
                        API::param_to_bool($line['hide_images']),
383
                        false, $line["site_url"], false, $line["id"]);
384
                } else {
385
                    $article["content"] = $line["content"];
386
                }
387
388
                foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ARTICLE_API) as $p) {
389
                    $article = $p->hook_render_article_api(array("article" => $article));
390
                }
391
392
                $article['content'] = DiskCache::rewriteUrls($article['content']);
393
394
                array_push($articles, $article);
395
396
            }
397
398
            $this->wrap(self::STATUS_OK, $articles);
399
        } else {
400
            $this->wrap(self::STATUS_ERR, array("error" => 'INCORRECT_USAGE'));
401
        }
402
    }
403
404
    public function getConfig() {
405
        $config = array(
406
            "icons_dir" => ICONS_DIR,
0 ignored issues
show
Bug introduced by
The constant ICONS_DIR was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
407
            "icons_url" => ICONS_URL);
0 ignored issues
show
Bug introduced by
The constant ICONS_URL was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
408
409
        $config["daemon_is_running"] = file_is_locked("update_daemon.lock");
410
411
        $sth = $this->pdo->prepare("SELECT COUNT(*) AS cf FROM
412
			ttrss_feeds WHERE owner_uid = ?");
413
        $sth->execute([$_SESSION['uid']]);
414
        $row = $sth->fetch();
415
416
        $config["num_feeds"] = $row["cf"];
417
418
        $this->wrap(self::STATUS_OK, $config);
419
    }
420
421
    public function updateFeed() {
422
        $feed_id = (int) clean($_REQUEST["feed_id"]);
423
424
        if (!ini_get("open_basedir")) {
425
            RSSUtils::update_rss_feed($feed_id);
426
        }
427
428
        $this->wrap(self::STATUS_OK, array("status" => "OK"));
429
    }
430
431
    public function catchupFeed() {
432
        $feed_id = clean($_REQUEST["feed_id"]);
433
        $is_cat = clean($_REQUEST["is_cat"]);
434
435
        Feeds::catchup_feed($feed_id, $is_cat);
436
437
        $this->wrap(self::STATUS_OK, array("status" => "OK"));
438
    }
439
440
    public function getPref() {
441
        $pref_name = clean($_REQUEST["pref_name"]);
442
443
        $this->wrap(self::STATUS_OK, array("value" => get_pref($pref_name)));
444
    }
445
446
    public function getLabels() {
447
        $article_id = (int) clean($_REQUEST['article_id']);
448
449
        $rv = array();
450
451
        $sth = $this->pdo->prepare("SELECT id, caption, fg_color, bg_color
452
			FROM ttrss_labels2
453
			WHERE owner_uid = ? ORDER BY caption");
454
        $sth->execute([$_SESSION['uid']]);
455
456
        if ($article_id) {
457
                    $article_labels = Article::get_article_labels($article_id);
458
        } else {
459
                    $article_labels = array();
460
        }
461
462
        while ($line = $sth->fetch()) {
463
464
            $checked = false;
465
            foreach ($article_labels as $al) {
466
                if (Labels::feed_to_label_id($al[0]) == $line['id']) {
467
                    $checked = true;
468
                    break;
469
                }
470
            }
471
472
            array_push($rv, array(
473
                "id" => (int) Labels::label_to_feed_id($line['id']),
474
                "caption" => $line['caption'],
475
                "fg_color" => $line['fg_color'],
476
                "bg_color" => $line['bg_color'],
477
                "checked" => $checked));
478
        }
479
480
        $this->wrap(self::STATUS_OK, $rv);
481
    }
482
483
    public function setArticleLabel() {
484
485
        $article_ids = explode(",", clean($_REQUEST["article_ids"]));
0 ignored issues
show
Bug introduced by
It seems like clean($_REQUEST['article_ids']) can also be of type array; however, parameter $string of explode() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

485
        $article_ids = explode(",", /** @scrutinizer ignore-type */ clean($_REQUEST["article_ids"]));
Loading history...
486
        $label_id = (int) clean($_REQUEST['label_id']);
487
        $assign = API::param_to_bool(clean($_REQUEST['assign']));
488
489
        $label = Labels::find_caption(Labels::feed_to_label_id($label_id), $_SESSION["uid"]);
490
491
        $num_updated = 0;
492
493
        if ($label) {
494
495
            foreach ($article_ids as $id) {
496
497
                if ($assign) {
498
                                    Labels::add_article($id, $label, $_SESSION["uid"]);
499
                } else {
500
                                    Labels::remove_article($id, $label, $_SESSION["uid"]);
501
                }
502
503
                ++$num_updated;
504
505
            }
506
        }
507
508
        $this->wrap(self::STATUS_OK, array("status" => "OK",
509
            "updated" => $num_updated));
510
511
    }
512
513
    public function index($method) {
514
        $plugin = PluginHost::getInstance()->get_api_method(strtolower($method));
515
516
        if ($plugin && method_exists($plugin, $method)) {
517
            $reply = $plugin->$method();
518
519
            $this->wrap($reply[0], $reply[1]);
520
521
        } else {
522
            $this->wrap(self::STATUS_ERR, array("error" => 'UNKNOWN_METHOD', "method" => $method));
523
        }
524
    }
525
526
    public function shareToPublished() {
527
        $title = strip_tags(clean($_REQUEST["title"]));
0 ignored issues
show
Bug introduced by
It seems like clean($_REQUEST['title']) can also be of type array; however, parameter $str of strip_tags() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

527
        $title = strip_tags(/** @scrutinizer ignore-type */ clean($_REQUEST["title"]));
Loading history...
528
        $url = strip_tags(clean($_REQUEST["url"]));
529
        $content = strip_tags(clean($_REQUEST["content"]));
530
531
        if (Article::create_published_article($title, $url, $content, "", $_SESSION["uid"])) {
532
            $this->wrap(self::STATUS_OK, array("status" => 'OK'));
533
        } else {
534
            $this->wrap(self::STATUS_ERR, array("error" => 'Publishing failed'));
535
        }
536
    }
537
538
    public static function api_get_feeds($cat_id, $unread_only, $limit, $offset, $include_nested = false) {
539
540
            $feeds = array();
541
542
            $pdo = Db::pdo();
543
544
            $limit = (int) $limit;
545
            $offset = (int) $offset;
546
            $cat_id = (int) $cat_id;
547
548
            /* Labels */
549
550
            /* API only: -4 All feeds, including virtual feeds */
551
            if ($cat_id == -4 || $cat_id == -2) {
552
                $counters = Counters::getLabelCounters(true);
553
554
                foreach (array_values($counters) as $cv) {
555
556
                    $unread = $cv["counter"];
557
558
                    if ($unread || !$unread_only) {
559
560
                        $row = array(
561
                                "id" => (int) $cv["id"],
562
                                "title" => $cv["description"],
563
                                "unread" => $cv["counter"],
564
                                "cat_id" => -2,
565
                            );
566
567
                        array_push($feeds, $row);
568
                    }
569
                }
570
            }
571
572
            /* Virtual feeds */
573
574
            if ($cat_id == -4 || $cat_id == -1) {
575
                foreach (array(-1, -2, -3, -4, -6, 0) as $i) {
576
                    $unread = getFeedUnread($i);
577
578
                    if ($unread || !$unread_only) {
579
                        $title = Feeds::getFeedTitle($i);
580
581
                        $row = array(
582
                                "id" => $i,
583
                                "title" => $title,
584
                                "unread" => $unread,
585
                                "cat_id" => -1,
586
                            );
587
                        array_push($feeds, $row);
588
                    }
589
590
                }
591
            }
592
593
            /* Child cats */
594
595
            if ($include_nested && $cat_id) {
596
                $sth = $pdo->prepare("SELECT
597
					id, title, order_id FROM ttrss_feed_categories
598
					WHERE parent_cat = ? AND owner_uid = ? ORDER BY order_id, title");
599
600
                $sth->execute([$cat_id, $_SESSION['uid']]);
601
602
                while ($line = $sth->fetch()) {
603
                    $unread = getFeedUnread($line["id"], true) +
604
                        Feeds::getCategoryChildrenUnread($line["id"]);
605
606
                    if ($unread || !$unread_only) {
607
                        $row = array(
608
                                "id" => (int) $line["id"],
609
                                "title" => $line["title"],
610
                                "unread" => $unread,
611
                                "is_cat" => true,
612
                                "order_id" => (int) $line["order_id"]
613
                            );
614
                        array_push($feeds, $row);
615
                    }
616
                }
617
            }
618
619
            /* Real feeds */
620
621
            if ($limit) {
622
                $limit_qpart = "limit $limit OFFSET $offset";
623
            } else {
624
                $limit_qpart = "";
625
            }
626
627
            /* API only: -3 All feeds, excluding virtual feeds (e.g. Labels and such) */
628
            if ($cat_id == -4 || $cat_id == -3) {
629
                $sth = $pdo->prepare("SELECT
630
					id, feed_url, cat_id, title, order_id, ".
631
                        SUBSTRING_FOR_DATE."(last_updated,1,19) AS last_updated
632
						FROM ttrss_feeds WHERE owner_uid = ?
633
						ORDER BY order_id, title " . $limit_qpart);
634
                $sth->execute([$_SESSION['uid']]);
635
636
            } else {
637
638
                $sth = $pdo->prepare("SELECT
639
					id, feed_url, cat_id, title, order_id, ".
640
                        SUBSTRING_FOR_DATE."(last_updated,1,19) AS last_updated
641
						FROM ttrss_feeds WHERE
642
						(cat_id = :cat OR (:cat = 0 AND cat_id IS NULL))
643
						AND owner_uid = :uid
644
						ORDER BY order_id, title " . $limit_qpart);
645
                $sth->execute([":uid" => $_SESSION['uid'], ":cat" => $cat_id]);
646
            }
647
648
            while ($line = $sth->fetch()) {
649
650
                $unread = getFeedUnread($line["id"]);
651
652
                $has_icon = Feeds::feedHasIcon($line['id']);
653
654
                if ($unread || !$unread_only) {
655
656
                    $row = array(
657
                            "feed_url" => $line["feed_url"],
658
                            "title" => $line["title"],
659
                            "id" => (int) $line["id"],
660
                            "unread" => (int) $unread,
661
                            "has_icon" => $has_icon,
662
                            "cat_id" => (int) $line["cat_id"],
663
                            "last_updated" => (int) strtotime($line["last_updated"]),
664
                            "order_id" => (int) $line["order_id"],
665
                        );
666
667
                    array_push($feeds, $row);
668
                }
669
            }
670
671
        return $feeds;
672
    }
673
674
    /**
675
     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
676
     */
677
    public static function api_get_headlines($feed_id, $limit, $offset,
678
                $filter, $is_cat, $show_excerpt, $show_content, $view_mode, $order,
679
                $include_attachments, $since_id,
680
                $search = "", $include_nested = false, $sanitize_content = true,
681
                $force_update = false, $excerpt_length = 100, $check_first_id = false, $skip_first_id_check = false) {
682
683
            $pdo = Db::pdo();
684
685
            if ($force_update && $feed_id > 0 && is_numeric($feed_id)) {
686
                // Update the feed if required with some basic flood control
687
688
                $sth = $pdo->prepare(
689
                    "SELECT cache_images,".SUBSTRING_FOR_DATE."(last_updated,1,19) AS last_updated
690
						FROM ttrss_feeds WHERE id = ?");
691
                $sth->execute([$feed_id]);
692
693
                if ($row = $sth->fetch()) {
694
                    $last_updated = strtotime($row["last_updated"]);
695
                    $cache_images = API::param_to_bool($row["cache_images"]);
696
697
                    if (!$cache_images && time() - $last_updated > 120) {
698
                        RSSUtils::update_rss_feed($feed_id, true);
699
                    } else {
700
                        $sth = $pdo->prepare("UPDATE ttrss_feeds SET last_updated = '1970-01-01', last_update_started = '1970-01-01'
701
							WHERE id = ?");
702
                        $sth->execute([$feed_id]);
703
                    }
704
                }
705
            }
706
707
            $params = array(
708
                "feed" => $feed_id,
709
                "limit" => $limit,
710
                "view_mode" => $view_mode,
711
                "cat_view" => $is_cat,
712
                "search" => $search,
713
                "override_order" => $order,
714
                "offset" => $offset,
715
                "since_id" => $since_id,
716
                "include_children" => $include_nested,
717
                "check_first_id" => $check_first_id,
718
                "skip_first_id_check" => $skip_first_id_check
719
            );
720
721
            $qfh_ret = Feeds::queryFeedHeadlines($params);
722
723
            $result = $qfh_ret[0];
724
            $feed_title = $qfh_ret[1];
725
            $first_id = $qfh_ret[6];
726
727
            $headlines = array();
728
729
            $headlines_header = array(
730
                'id' => $feed_id,
731
                'first_id' => $first_id,
732
                'is_cat' => $is_cat);
733
734
            if (!is_numeric($result)) {
735
                while ($line = $result->fetch()) {
736
                    $line["content_preview"] = truncate_string(strip_tags($line["content"]), $excerpt_length);
737
                    foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_QUERY_HEADLINES) as $p) {
738
                        $line = $p->hook_query_headlines($line, $excerpt_length, true);
739
                    }
740
741
                    $is_updated = ($line["last_read"] == "" &&
742
                        ($line["unread"] != "t" && $line["unread"] != "1"));
743
744
                    $tags = explode(",", $line["tag_cache"]);
745
746
                    $label_cache = $line["label_cache"];
747
                    $labels = false;
748
749
                    if ($label_cache) {
750
                        $label_cache = json_decode($label_cache, true);
751
752
                        if ($label_cache) {
753
                            if ($label_cache["no-labels"] == 1) {
754
                                                            $labels = array();
755
                            } else {
756
                                                            $labels = $label_cache;
757
                            }
758
                        }
759
                    }
760
761
                    if (!is_array($labels)) {
762
                        $labels = Article::get_article_labels($line["id"]);
763
                    }
764
765
                    $headline_row = array(
766
                        "id" => (int) $line["id"],
767
                        "guid" => $line["guid"],
768
                        "unread" => API::param_to_bool($line["unread"]),
769
                        "marked" => API::param_to_bool($line["marked"]),
770
                        "published" => API::param_to_bool($line["published"]),
771
                        "updated" => (int) strtotime($line["updated"]),
772
                        "is_updated" => $is_updated,
773
                        "title" => $line["title"],
774
                        "link" => $line["link"],
775
                        "feed_id" => $line["feed_id"] ? $line['feed_id'] : 0,
776
                        "tags" => $tags,
777
                    );
778
779
                    $enclosures = Article::get_article_enclosures($line['id']);
780
781
                    if ($include_attachments) {
782
                                            $headline_row['attachments'] = $enclosures;
783
                    }
784
785
                    if ($show_excerpt) {
786
                                            $headline_row["excerpt"] = $line["content_preview"];
787
                    }
788
789
                    if ($show_content) {
790
791
                        if ($sanitize_content) {
792
                            $headline_row["content"] = sanitize(
793
                                $line["content"],
794
                                API::param_to_bool($line['hide_images']),
795
                                false, $line["site_url"], false, $line["id"]);
796
                        } else {
797
                            $headline_row["content"] = $line["content"];
798
                        }
799
                    }
800
801
                    // unify label output to ease parsing
802
                    if ($labels["no-labels"] == 1) {
803
                        $labels = array();
804
                    }
805
806
                    $headline_row["labels"] = $labels;
807
808
                    $headline_row["feed_title"] = $line["feed_title"] ? $line["feed_title"] : $feed_title;
809
810
                    $headline_row["comments_count"] = (int) $line["num_comments"];
811
                    $headline_row["comments_link"] = $line["comments"];
812
813
                    $headline_row["always_display_attachments"] = API::param_to_bool($line["always_display_enclosures"]);
814
815
                    $headline_row["author"] = $line["author"];
816
817
                    $headline_row["score"] = (int) $line["score"];
818
                    $headline_row["note"] = $line["note"];
819
                    $headline_row["lang"] = $line["lang"];
820
821
                    foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ARTICLE_API) as $p) {
822
                        $headline_row = $p->hook_render_article_api(array("headline" => $headline_row));
823
                    }
824
825
                    $headline_row["content"] = DiskCache::rewriteUrls($headline_row['content']);
826
827
                    list ($flavor_image, $flavor_stream) = Article::get_article_image($enclosures, $line["content"], $line["site_url"]);
828
829
                    $headline_row["flavor_image"] = $flavor_image;
830
                    $headline_row["flavor_stream"] = $flavor_stream;
831
832
                    array_push($headlines, $headline_row);
833
                }
834
            } else if (is_numeric($result) && $result == -1) {
835
                $headlines_header['first_id_changed'] = true;
836
            }
837
838
            return array($headlines, $headlines_header);
839
    }
840
841
    public function unsubscribeFeed() {
842
        $feed_id = (int) clean($_REQUEST["feed_id"]);
843
844
        $sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds WHERE
845
			id = ? AND owner_uid = ?");
846
        $sth->execute([$feed_id, $_SESSION['uid']]);
847
848
        if ($sth->fetch()) {
849
            Pref_Feeds::remove_feed($feed_id, $_SESSION["uid"]);
850
            $this->wrap(self::STATUS_OK, array("status" => "OK"));
851
        } else {
852
            $this->wrap(self::STATUS_ERR, array("error" => "FEED_NOT_FOUND"));
853
        }
854
    }
855
856
    public function subscribeToFeed() {
857
        $feed_url = clean($_REQUEST["feed_url"]);
858
        $category_id = (int) clean($_REQUEST["category_id"]);
859
        $login = clean($_REQUEST["login"]);
860
        $password = clean($_REQUEST["password"]);
861
862
        if ($feed_url) {
863
            $rc = Feeds::subscribe_to_feed($feed_url, $category_id, $login, $password);
864
865
            $this->wrap(self::STATUS_OK, array("status" => $rc));
866
        } else {
867
            $this->wrap(self::STATUS_ERR, array("error" => 'INCORRECT_USAGE'));
868
        }
869
    }
870
871
    public function getFeedTree() {
872
        $include_empty = API::param_to_bool(clean($_REQUEST['include_empty']));
873
874
        $pf = new Pref_Feeds($_REQUEST);
875
876
        $_REQUEST['mode'] = 2;
877
        $_REQUEST['force_show_empty'] = $include_empty;
878
879
        if ($pf) {
0 ignored issues
show
introduced by
$pf is of type Pref_Feeds, thus it always evaluated to true.
Loading history...
880
            $data = $pf->makefeedtree();
881
            $this->wrap(self::STATUS_OK, array("categories" => $data));
882
        } else {
883
            $this->wrap(self::STATUS_ERR, array("error" =>
884
                'UNABLE_TO_INSTANTIATE_OBJECT'));
885
        }
886
887
    }
888
889
    // only works for labels or uncategorized for the time being
890
    private function isCategoryEmpty($id) {
891
892
        if ($id == -2) {
893
            $sth = $this->pdo->prepare("SELECT COUNT(id) AS count FROM ttrss_labels2
894
				WHERE owner_uid = ?");
895
            $sth->execute([$_SESSION['uid']]);
896
            $row = $sth->fetch();
897
898
            return $row["count"] == 0;
899
900
        } else if ($id == 0) {
901
            $sth = $this->pdo->prepare("SELECT COUNT(id) AS count FROM ttrss_feeds
902
				WHERE cat_id IS NULL AND owner_uid = ?");
903
            $sth->execute([$_SESSION['uid']]);
904
            $row = $sth->fetch();
905
906
            return $row["count"] == 0;
907
908
        }
909
910
        return false;
911
    }
912
913
914
}
915