rpc.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612
  1. <?php
  2. class RPC extends Handler_Protected {
  3. function csrf_ignore($method) {
  4. $csrf_ignored = array("sanitycheck", "completelabels", "saveprofile");
  5. return array_search($method, $csrf_ignored) !== false;
  6. }
  7. function setprofile() {
  8. $_SESSION["profile"] = (int) clean($_REQUEST["id"]);
  9. // default value
  10. if (!$_SESSION["profile"]) $_SESSION["profile"] = null;
  11. }
  12. function remprofiles() {
  13. $ids = explode(",", trim(clean($_REQUEST["ids"])));
  14. foreach ($ids as $id) {
  15. if ($_SESSION["profile"] != $id) {
  16. $sth = $this->pdo->prepare("DELETE FROM ttrss_settings_profiles WHERE id = ? AND
  17. owner_uid = ?");
  18. $sth->execute([$id, $_SESSION['uid']]);
  19. }
  20. }
  21. }
  22. // Silent
  23. function addprofile() {
  24. $title = trim(clean($_REQUEST["title"]));
  25. if ($title) {
  26. $this->pdo->beginTransaction();
  27. $sth = $this->pdo->prepare("SELECT id FROM ttrss_settings_profiles
  28. WHERE title = ? AND owner_uid = ?");
  29. $sth->execute([$title, $_SESSION['uid']]);
  30. if (!$sth->fetch()) {
  31. $sth = $this->pdo->prepare("INSERT INTO ttrss_settings_profiles (title, owner_uid)
  32. VALUES (?, ?)");
  33. $sth->execute([$title, $_SESSION['uid']]);
  34. $sth = $this->pdo->prepare("SELECT id FROM ttrss_settings_profiles WHERE
  35. title = ? AND owner_uid = ?");
  36. $sth->execute([$title, $_SESSION['uid']]);
  37. if ($row = $sth->fetch()) {
  38. $profile_id = $row['id'];
  39. if ($profile_id) {
  40. initialize_user_prefs($_SESSION["uid"], $profile_id);
  41. }
  42. }
  43. }
  44. $this->pdo->commit();
  45. }
  46. }
  47. function saveprofile() {
  48. $id = clean($_REQUEST["id"]);
  49. $title = trim(clean($_REQUEST["value"]));
  50. if ($id == 0) {
  51. print __("Default profile");
  52. return;
  53. }
  54. if ($title) {
  55. $sth = $this->pdo->prepare("UPDATE ttrss_settings_profiles
  56. SET title = ? WHERE id = ? AND
  57. owner_uid = ?");
  58. $sth->execute([$title, $id, $_SESSION['uid']]);
  59. print $title;
  60. }
  61. }
  62. // Silent
  63. function remarchive() {
  64. $ids = explode(",", clean($_REQUEST["ids"]));
  65. $sth = $this->pdo->prepare("DELETE FROM ttrss_archived_feeds WHERE
  66. (SELECT COUNT(*) FROM ttrss_user_entries
  67. WHERE orig_feed_id = :id) = 0 AND
  68. id = :id AND owner_uid = :uid");
  69. foreach ($ids as $id) {
  70. $sth->execute([":id" => $id, ":uid" => $_SESSION['uid']]);
  71. }
  72. }
  73. function addfeed() {
  74. $feed = clean($_REQUEST['feed']);
  75. $cat = clean($_REQUEST['cat']);
  76. $need_auth = isset($_REQUEST['need_auth']);
  77. $login = $need_auth ? clean($_REQUEST['login']) : '';
  78. $pass = $need_auth ? trim(clean($_REQUEST['pass'])) : '';
  79. $rc = Feeds::subscribe_to_feed($feed, $cat, $login, $pass);
  80. print json_encode(array("result" => $rc));
  81. }
  82. function togglepref() {
  83. $key = clean($_REQUEST["key"]);
  84. set_pref($key, !get_pref($key));
  85. $value = get_pref($key);
  86. print json_encode(array("param" =>$key, "value" => $value));
  87. }
  88. function setpref() {
  89. // set_pref escapes input, so no need to double escape it here
  90. $key = clean($_REQUEST['key']);
  91. $value = $_REQUEST['value'];
  92. set_pref($key, $value, false, $key != 'USER_STYLESHEET');
  93. print json_encode(array("param" =>$key, "value" => $value));
  94. }
  95. function mark() {
  96. $mark = clean($_REQUEST["mark"]);
  97. $id = clean($_REQUEST["id"]);
  98. $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET marked = ?,
  99. last_marked = NOW()
  100. WHERE ref_id = ? AND owner_uid = ?");
  101. $sth->execute([$mark, $id, $_SESSION['uid']]);
  102. print json_encode(array("message" => "UPDATE_COUNTERS"));
  103. }
  104. function delete() {
  105. $ids = explode(",", clean($_REQUEST["ids"]));
  106. $ids_qmarks = arr_qmarks($ids);
  107. $sth = $this->pdo->prepare("DELETE FROM ttrss_user_entries
  108. WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?");
  109. $sth->execute(array_merge($ids, [$_SESSION['uid']]));
  110. Article::purge_orphans();
  111. print json_encode(array("message" => "UPDATE_COUNTERS"));
  112. }
  113. function unarchive() {
  114. $ids = explode(",", clean($_REQUEST["ids"]));
  115. foreach ($ids as $id) {
  116. $this->pdo->beginTransaction();
  117. $sth = $this->pdo->prepare("SELECT feed_url,site_url,title FROM ttrss_archived_feeds
  118. WHERE id = (SELECT orig_feed_id FROM ttrss_user_entries WHERE ref_id = :id
  119. AND owner_uid = :uid) AND owner_uid = :uid");
  120. $sth->execute([":uid" => $_SESSION['uid'], ":id" => $id]);
  121. if ($row = $sth->fetch()) {
  122. $feed_url = $row['feed_url'];
  123. $site_url = $row['site_url'];
  124. $title = $row['title'];
  125. $sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds WHERE feed_url = ?
  126. AND owner_uid = ?");
  127. $sth->execute([$feed_url, $_SESSION['uid']]);
  128. if ($row = $sth->fetch()) {
  129. $feed_id = $row["id"];
  130. } else {
  131. if (!$title) $title = '[Unknown]';
  132. $sth = $this->pdo->prepare("INSERT INTO ttrss_feeds
  133. (owner_uid,feed_url,site_url,title,cat_id,auth_login,auth_pass,update_method)
  134. VALUES (?, ?, ?, ?, NULL, '', '', 0)");
  135. $sth->execute([$_SESSION['uid'], $feed_url, $site_url, $title]);
  136. $sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds WHERE feed_url = ?
  137. AND owner_uid = ?");
  138. $sth->execute([$feed_url, $_SESSION['uid']]);
  139. if ($row = $sth->fetch()) {
  140. $feed_id = $row['id'];
  141. }
  142. }
  143. if ($feed_id) {
  144. $sth = $this->pdo->prepare("UPDATE ttrss_user_entries
  145. SET feed_id = ?, orig_feed_id = NULL
  146. WHERE ref_id = ? AND owner_uid = ?");
  147. $sth->execute([$feed_id, $id, $_SESSION['uid']]);
  148. }
  149. }
  150. $this->pdo->commit();
  151. }
  152. print json_encode(array("message" => "UPDATE_COUNTERS"));
  153. }
  154. function archive() {
  155. $ids = explode(",", clean($_REQUEST["ids"]));
  156. foreach ($ids as $id) {
  157. $this->archive_article($id, $_SESSION["uid"]);
  158. }
  159. print json_encode(array("message" => "UPDATE_COUNTERS"));
  160. }
  161. private function archive_article($id, $owner_uid) {
  162. $this->pdo->beginTransaction();
  163. if (!$owner_uid) $owner_uid = $_SESSION['uid'];
  164. $sth = $this->pdo->prepare("SELECT feed_id FROM ttrss_user_entries
  165. WHERE ref_id = ? AND owner_uid = ?");
  166. $sth->execute([$id, $owner_uid]);
  167. if ($row = $sth->fetch()) {
  168. /* prepare the archived table */
  169. $feed_id = (int) $row['feed_id'];
  170. if ($feed_id) {
  171. $sth = $this->pdo->prepare("SELECT id FROM ttrss_archived_feeds
  172. WHERE id = ? AND owner_uid = ?");
  173. $sth->execute([$feed_id, $owner_uid]);
  174. if ($row = $sth->fetch()) {
  175. $new_feed_id = $row['id'];
  176. } else {
  177. $row = $this->pdo->query("SELECT MAX(id) AS id FROM ttrss_archived_feeds")->fetch();
  178. $new_feed_id = (int)$row['id'] + 1;
  179. $sth = $this->pdo->prepare("INSERT INTO ttrss_archived_feeds
  180. (id, owner_uid, title, feed_url, site_url, created)
  181. SELECT ?, owner_uid, title, feed_url, site_url, NOW() from ttrss_feeds
  182. WHERE id = ?");
  183. $sth->execute([$new_feed_id, $feed_id]);
  184. }
  185. $sth = $this->pdo->prepare("UPDATE ttrss_user_entries
  186. SET orig_feed_id = ?, feed_id = NULL
  187. WHERE ref_id = ? AND owner_uid = ?");
  188. $sth->execute([$new_feed_id, $id, $owner_uid]);
  189. }
  190. }
  191. $this->pdo->commit();
  192. }
  193. function publ() {
  194. $pub = clean($_REQUEST["pub"]);
  195. $id = clean($_REQUEST["id"]);
  196. $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET
  197. published = ?, last_published = NOW()
  198. WHERE ref_id = ? AND owner_uid = ?");
  199. $sth->execute([$pub, $id, $_SESSION['uid']]);
  200. print json_encode(array("message" => "UPDATE_COUNTERS"));
  201. }
  202. function getAllCounters() {
  203. @$seq = (int) $_REQUEST['seq'];
  204. $reply = [
  205. 'counters' => Counters::getAllCounters(),
  206. 'seq' => $seq
  207. ];
  208. if ($seq % 2 == 0)
  209. $reply['runtime-info'] = make_runtime_info();
  210. print json_encode($reply);
  211. }
  212. /* GET["cmode"] = 0 - mark as read, 1 - as unread, 2 - toggle */
  213. function catchupSelected() {
  214. $ids = explode(",", clean($_REQUEST["ids"]));
  215. $cmode = (int)clean($_REQUEST["cmode"]);
  216. Article::catchupArticlesById($ids, $cmode);
  217. print json_encode(array("message" => "UPDATE_COUNTERS", "ids" => $ids));
  218. }
  219. function markSelected() {
  220. $ids = explode(",", clean($_REQUEST["ids"]));
  221. $cmode = (int)clean($_REQUEST["cmode"]);
  222. $this->markArticlesById($ids, $cmode);
  223. print json_encode(array("message" => "UPDATE_COUNTERS"));
  224. }
  225. function publishSelected() {
  226. $ids = explode(",", clean($_REQUEST["ids"]));
  227. $cmode = (int)clean($_REQUEST["cmode"]);
  228. $this->publishArticlesById($ids, $cmode);
  229. print json_encode(array("message" => "UPDATE_COUNTERS"));
  230. }
  231. function sanityCheck() {
  232. $_SESSION["hasAudio"] = clean($_REQUEST["hasAudio"]) === "true";
  233. $_SESSION["hasSandbox"] = clean($_REQUEST["hasSandbox"]) === "true";
  234. $_SESSION["hasMp3"] = clean($_REQUEST["hasMp3"]) === "true";
  235. $_SESSION["clientTzOffset"] = clean($_REQUEST["clientTzOffset"]);
  236. $reply = array();
  237. $reply['error'] = sanity_check();
  238. if ($reply['error']['code'] == 0) {
  239. $reply['init-params'] = make_init_params();
  240. $reply['runtime-info'] = make_runtime_info();
  241. }
  242. print json_encode($reply);
  243. }
  244. function completeLabels() {
  245. $search = clean($_REQUEST["search"]);
  246. $sth = $this->pdo->prepare("SELECT DISTINCT caption FROM
  247. ttrss_labels2
  248. WHERE owner_uid = ? AND
  249. LOWER(caption) LIKE LOWER(?) ORDER BY caption
  250. LIMIT 5");
  251. $sth->execute([$_SESSION['uid'], "%$search%"]);
  252. print "<ul>";
  253. while ($line = $sth->fetch()) {
  254. print "<li>" . $line["caption"] . "</li>";
  255. }
  256. print "</ul>";
  257. }
  258. // Silent
  259. function massSubscribe() {
  260. $payload = json_decode(clean($_REQUEST["payload"]), false);
  261. $mode = clean($_REQUEST["mode"]);
  262. if (!$payload || !is_array($payload)) return;
  263. if ($mode == 1) {
  264. foreach ($payload as $feed) {
  265. $title = $feed[0];
  266. $feed_url = $feed[1];
  267. $sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds WHERE
  268. feed_url = ? AND owner_uid = ?");
  269. $sth->execute([$feed_url, $_SESSION['uid']]);
  270. if (!$sth->fetch()) {
  271. $sth = $this->pdo->prepare("INSERT INTO ttrss_feeds
  272. (owner_uid,feed_url,title,cat_id,site_url)
  273. VALUES (?, ?, ?, NULL, '')");
  274. $sth->execute([$_SESSION['uid'], $feed_url, $title]);
  275. }
  276. }
  277. } else if ($mode == 2) {
  278. // feed archive
  279. foreach ($payload as $id) {
  280. $sth = $this->pdo->prepare("SELECT * FROM ttrss_archived_feeds
  281. WHERE id = ? AND owner_uid = ?");
  282. $sth->execute([$id, $_SESSION['uid']]);
  283. if ($row = $sth->fetch()) {
  284. $site_url = $row['site_url'];
  285. $feed_url = $row['feed_url'];
  286. $title = $row['title'];
  287. $sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds WHERE
  288. feed_url = ? AND owner_uid = ?");
  289. $sth->execute([$feed_url, $_SESSION['uid']]);
  290. if (!$sth->fetch()) {
  291. $sth = $this->pdo->prepare("INSERT INTO ttrss_feeds
  292. (owner_uid,feed_url,title,cat_id,site_url)
  293. VALUES (?, ?, ?, NULL, ?)");
  294. $sth->execute([$_SESSION['uid'], $feed_url, $title, $site_url]);
  295. }
  296. }
  297. }
  298. }
  299. }
  300. function catchupFeed() {
  301. $feed_id = clean($_REQUEST['feed_id']);
  302. $is_cat = clean($_REQUEST['is_cat']) == "true";
  303. $mode = clean($_REQUEST['mode']);
  304. $search_query = clean($_REQUEST['search_query']);
  305. $search_lang = clean($_REQUEST['search_lang']);
  306. Feeds::catchup_feed($feed_id, $is_cat, false, $mode, [$search_query, $search_lang]);
  307. // return counters here synchronously so that frontend can figure out next unread feed properly
  308. print json_encode(['counters' => Counters::getAllCounters()]);
  309. //print json_encode(array("message" => "UPDATE_COUNTERS"));
  310. }
  311. function setpanelmode() {
  312. $wide = (int) clean($_REQUEST["wide"]);
  313. setcookie("ttrss_widescreen", $wide,
  314. time() + COOKIE_LIFETIME_LONG);
  315. print json_encode(array("wide" => $wide));
  316. }
  317. static function updaterandomfeed_real() {
  318. // Test if the feed need a update (update interval exceded).
  319. if (DB_TYPE == "pgsql") {
  320. $update_limit_qpart = "AND ((
  321. ttrss_feeds.update_interval = 0
  322. AND ttrss_feeds.last_updated < NOW() - CAST((ttrss_user_prefs.value || ' minutes') AS INTERVAL)
  323. ) OR (
  324. ttrss_feeds.update_interval > 0
  325. AND ttrss_feeds.last_updated < NOW() - CAST((ttrss_feeds.update_interval || ' minutes') AS INTERVAL)
  326. ) OR ttrss_feeds.last_updated IS NULL
  327. OR last_updated = '1970-01-01 00:00:00')";
  328. } else {
  329. $update_limit_qpart = "AND ((
  330. ttrss_feeds.update_interval = 0
  331. AND ttrss_feeds.last_updated < DATE_SUB(NOW(), INTERVAL CONVERT(ttrss_user_prefs.value, SIGNED INTEGER) MINUTE)
  332. ) OR (
  333. ttrss_feeds.update_interval > 0
  334. AND ttrss_feeds.last_updated < DATE_SUB(NOW(), INTERVAL ttrss_feeds.update_interval MINUTE)
  335. ) OR ttrss_feeds.last_updated IS NULL
  336. OR last_updated = '1970-01-01 00:00:00')";
  337. }
  338. // Test if feed is currently being updated by another process.
  339. if (DB_TYPE == "pgsql") {
  340. $updstart_thresh_qpart = "AND (ttrss_feeds.last_update_started IS NULL OR ttrss_feeds.last_update_started < NOW() - INTERVAL '5 minutes')";
  341. } else {
  342. $updstart_thresh_qpart = "AND (ttrss_feeds.last_update_started IS NULL OR ttrss_feeds.last_update_started < DATE_SUB(NOW(), INTERVAL 5 MINUTE))";
  343. }
  344. $random_qpart = sql_random_function();
  345. $pdo = Db::pdo();
  346. // we could be invoked from public.php with no active session
  347. if ($_SESSION["uid"]) {
  348. $owner_check_qpart = "AND ttrss_feeds.owner_uid = ".$pdo->quote($_SESSION["uid"]);
  349. } else {
  350. $owner_check_qpart = "";
  351. }
  352. // We search for feed needing update.
  353. $res = $pdo->query("SELECT ttrss_feeds.feed_url,ttrss_feeds.id
  354. FROM
  355. ttrss_feeds, ttrss_users, ttrss_user_prefs
  356. WHERE
  357. ttrss_feeds.owner_uid = ttrss_users.id
  358. AND ttrss_users.id = ttrss_user_prefs.owner_uid
  359. AND ttrss_user_prefs.pref_name = 'DEFAULT_UPDATE_INTERVAL'
  360. $owner_check_qpart
  361. $update_limit_qpart
  362. $updstart_thresh_qpart
  363. ORDER BY $random_qpart LIMIT 30");
  364. $num_updated = 0;
  365. $tstart = time();
  366. while ($line = $res->fetch()) {
  367. $feed_id = $line["id"];
  368. if (time() - $tstart < ini_get("max_execution_time") * 0.7) {
  369. RSSUtils::update_rss_feed($feed_id, true);
  370. ++$num_updated;
  371. } else {
  372. break;
  373. }
  374. }
  375. // Purge orphans and cleanup tags
  376. Article::purge_orphans();
  377. //cleanup_tags(14, 50000);
  378. if ($num_updated > 0) {
  379. print json_encode(array("message" => "UPDATE_COUNTERS",
  380. "num_updated" => $num_updated));
  381. } else {
  382. print json_encode(array("message" => "NOTHING_TO_UPDATE"));
  383. }
  384. }
  385. function updaterandomfeed() {
  386. RPC::updaterandomfeed_real();
  387. }
  388. private function markArticlesById($ids, $cmode) {
  389. $ids_qmarks = arr_qmarks($ids);
  390. if ($cmode == 0) {
  391. $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET
  392. marked = false, last_marked = NOW()
  393. WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?");
  394. } else if ($cmode == 1) {
  395. $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET
  396. marked = true, last_marked = NOW()
  397. WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?");
  398. } else {
  399. $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET
  400. marked = NOT marked,last_marked = NOW()
  401. WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?");
  402. }
  403. $sth->execute(array_merge($ids, [$_SESSION['uid']]));
  404. }
  405. private function publishArticlesById($ids, $cmode) {
  406. $ids_qmarks = arr_qmarks($ids);
  407. if ($cmode == 0) {
  408. $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET
  409. published = false, last_published = NOW()
  410. WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?");
  411. } else if ($cmode == 1) {
  412. $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET
  413. published = true, last_published = NOW()
  414. WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?");
  415. } else {
  416. $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET
  417. published = NOT published,last_published = NOW()
  418. WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?");
  419. }
  420. $sth->execute(array_merge($ids, [$_SESSION['uid']]));
  421. }
  422. function getlinktitlebyid() {
  423. $id = clean($_REQUEST['id']);
  424. $sth = $this->pdo->prepare("SELECT link, title FROM ttrss_entries, ttrss_user_entries
  425. WHERE ref_id = ? AND ref_id = id AND owner_uid = ?");
  426. $sth->execute([$id, $_SESSION['uid']]);
  427. if ($row = $sth->fetch()) {
  428. $link = $row['link'];
  429. $title = $row['title'];
  430. echo json_encode(array("link" => $link, "title" => $title));
  431. } else {
  432. echo json_encode(array("error" => "ARTICLE_NOT_FOUND"));
  433. }
  434. }
  435. function log() {
  436. $msg = clean($_REQUEST['msg']);
  437. $file = basename(clean($_REQUEST['file']));
  438. $line = (int) clean($_REQUEST['line']);
  439. $context = clean($_REQUEST['context']);
  440. if ($msg) {
  441. Logger::get()->log_error(E_USER_WARNING,
  442. $msg, 'client-js:' . $file, $line, $context);
  443. echo json_encode(array("message" => "HOST_ERROR_LOGGED"));
  444. } else {
  445. echo json_encode(array("error" => "MESSAGE_NOT_FOUND"));
  446. }
  447. }
  448. function checkforupdates() {
  449. $rv = [];
  450. if (CHECK_FOR_UPDATES && $_SESSION["access_level"] >= 10 && defined("GIT_VERSION_TIMESTAMP")) {
  451. $content = @fetch_file_contents(["url" => "https://tt-rss.org/version.json"]);
  452. if ($content) {
  453. $content = json_decode($content, true);
  454. if ($content && isset($content["changeset"])) {
  455. if ((int)GIT_VERSION_TIMESTAMP < (int)$content["changeset"]["timestamp"] &&
  456. GIT_VERSION_HEAD != $content["changeset"]["id"]) {
  457. $rv = $content["changeset"];
  458. }
  459. }
  460. }
  461. }
  462. print json_encode($rv);
  463. }
  464. }