rpc.php 16 KB

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