API::isLoggedIn()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 2
rs 10
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