rpc.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602
  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"] = 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)
  181. SELECT ?, owner_uid, title, feed_url, site_url 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. $last_article_id = (int) clean($_REQUEST["last_article_id"]);
  204. $reply = array();
  205. if (!empty($_REQUEST['seq'])) $reply['seq'] = (int) $_REQUEST['seq'];
  206. if ($last_article_id != Article::getLastArticleId()) {
  207. $reply['counters'] = Counters::getAllCounters();
  208. }
  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 = sprintf("%d", 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(true);
  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. function updateFeedBrowser() {
  259. if (defined('_DISABLE_FEED_BROWSER') && _DISABLE_FEED_BROWSER) return;
  260. $search = clean($_REQUEST["search"]);
  261. $limit = clean($_REQUEST["limit"]);
  262. $mode = (int) clean($_REQUEST["mode"]);
  263. require_once "feedbrowser.php";
  264. print json_encode(array("content" =>
  265. make_feed_browser($search, $limit, $mode),
  266. "mode" => $mode));
  267. }
  268. // Silent
  269. function massSubscribe() {
  270. $payload = json_decode(clean($_REQUEST["payload"]), false);
  271. $mode = clean($_REQUEST["mode"]);
  272. if (!$payload || !is_array($payload)) return;
  273. if ($mode == 1) {
  274. foreach ($payload as $feed) {
  275. $title = $feed[0];
  276. $feed_url = $feed[1];
  277. $sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds WHERE
  278. feed_url = ? AND owner_uid = ?");
  279. $sth->execute([$feed_url, $_SESSION['uid']]);
  280. if (!$sth->fetch()) {
  281. $sth = $this->pdo->prepare("INSERT INTO ttrss_feeds
  282. (owner_uid,feed_url,title,cat_id,site_url)
  283. VALUES (?, ?, ?, NULL, '')");
  284. $sth->execute([$_SESSION['uid'], $feed_url, $title]);
  285. }
  286. }
  287. } else if ($mode == 2) {
  288. // feed archive
  289. foreach ($payload as $id) {
  290. $sth = $this->pdo->prepare("SELECT * FROM ttrss_archived_feeds
  291. WHERE id = ? AND owner_uid = ?");
  292. $sth->execute([$id, $_SESSION['uid']]);
  293. if ($row = $sth->fetch()) {
  294. $site_url = $row['site_url'];
  295. $feed_url = $row['feed_url'];
  296. $title = $row['title'];
  297. $sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds WHERE
  298. feed_url = ? AND owner_uid = ?");
  299. $sth->execute([$feed_url, $_SESSION['uid']]);
  300. if (!$sth->fetch()) {
  301. $sth = $this->pdo->prepare("INSERT INTO ttrss_feeds
  302. (owner_uid,feed_url,title,cat_id,site_url)
  303. VALUES (?, ?, ?, NULL, ?)");
  304. $sth->execute([$_SESSION['uid'], $feed_url, $title, $site_url]);
  305. }
  306. }
  307. }
  308. }
  309. }
  310. function catchupFeed() {
  311. $feed_id = clean($_REQUEST['feed_id']);
  312. $is_cat = clean($_REQUEST['is_cat']) == "true";
  313. $mode = clean($_REQUEST['mode']);
  314. $search_query = clean($_REQUEST['search_query']);
  315. $search_lang = clean($_REQUEST['search_lang']);
  316. Feeds::catchup_feed($feed_id, $is_cat, false, $mode, [$search_query, $search_lang]);
  317. print json_encode(array("message" => "UPDATE_COUNTERS"));
  318. }
  319. function setpanelmode() {
  320. $wide = (int) clean($_REQUEST["wide"]);
  321. setcookie("ttrss_widescreen", $wide,
  322. time() + COOKIE_LIFETIME_LONG);
  323. print json_encode(array("wide" => $wide));
  324. }
  325. static function updaterandomfeed_real() {
  326. // Test if the feed need a update (update interval exceded).
  327. if (DB_TYPE == "pgsql") {
  328. $update_limit_qpart = "AND ((
  329. ttrss_feeds.update_interval = 0
  330. AND ttrss_feeds.last_updated < NOW() - CAST((ttrss_user_prefs.value || ' minutes') AS INTERVAL)
  331. ) OR (
  332. ttrss_feeds.update_interval > 0
  333. AND ttrss_feeds.last_updated < NOW() - CAST((ttrss_feeds.update_interval || ' minutes') AS INTERVAL)
  334. ) OR ttrss_feeds.last_updated IS NULL
  335. OR last_updated = '1970-01-01 00:00:00')";
  336. } else {
  337. $update_limit_qpart = "AND ((
  338. ttrss_feeds.update_interval = 0
  339. AND ttrss_feeds.last_updated < DATE_SUB(NOW(), INTERVAL CONVERT(ttrss_user_prefs.value, SIGNED INTEGER) MINUTE)
  340. ) OR (
  341. ttrss_feeds.update_interval > 0
  342. AND ttrss_feeds.last_updated < DATE_SUB(NOW(), INTERVAL ttrss_feeds.update_interval MINUTE)
  343. ) OR ttrss_feeds.last_updated IS NULL
  344. OR last_updated = '1970-01-01 00:00:00')";
  345. }
  346. // Test if feed is currently being updated by another process.
  347. if (DB_TYPE == "pgsql") {
  348. $updstart_thresh_qpart = "AND (ttrss_feeds.last_update_started IS NULL OR ttrss_feeds.last_update_started < NOW() - INTERVAL '5 minutes')";
  349. } else {
  350. $updstart_thresh_qpart = "AND (ttrss_feeds.last_update_started IS NULL OR ttrss_feeds.last_update_started < DATE_SUB(NOW(), INTERVAL 5 MINUTE))";
  351. }
  352. $random_qpart = sql_random_function();
  353. $pdo = Db::pdo();
  354. // we could be invoked from public.php with no active session
  355. if ($_SESSION["uid"]) {
  356. $owner_check_qpart = "AND ttrss_feeds.owner_uid = ".$pdo->quote($_SESSION["uid"]);
  357. } else {
  358. $owner_check_qpart = "";
  359. }
  360. // We search for feed needing update.
  361. $res = $pdo->query("SELECT ttrss_feeds.feed_url,ttrss_feeds.id
  362. FROM
  363. ttrss_feeds, ttrss_users, ttrss_user_prefs
  364. WHERE
  365. ttrss_feeds.owner_uid = ttrss_users.id
  366. AND ttrss_users.id = ttrss_user_prefs.owner_uid
  367. AND ttrss_user_prefs.pref_name = 'DEFAULT_UPDATE_INTERVAL'
  368. $owner_check_qpart
  369. $update_limit_qpart
  370. $updstart_thresh_qpart
  371. ORDER BY $random_qpart LIMIT 30");
  372. $num_updated = 0;
  373. $tstart = time();
  374. while ($line = $res->fetch()) {
  375. $feed_id = $line["id"];
  376. if (time() - $tstart < ini_get("max_execution_time") * 0.7) {
  377. RSSUtils::update_rss_feed($feed_id, true);
  378. ++$num_updated;
  379. } else {
  380. break;
  381. }
  382. }
  383. // Purge orphans and cleanup tags
  384. Article::purge_orphans();
  385. //cleanup_tags(14, 50000);
  386. if ($num_updated > 0) {
  387. print json_encode(array("message" => "UPDATE_COUNTERS",
  388. "num_updated" => $num_updated));
  389. } else {
  390. print json_encode(array("message" => "NOTHING_TO_UPDATE"));
  391. }
  392. }
  393. function updaterandomfeed() {
  394. RPC::updaterandomfeed_real();
  395. }
  396. private function markArticlesById($ids, $cmode) {
  397. $ids_qmarks = arr_qmarks($ids);
  398. if ($cmode == 0) {
  399. $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET
  400. marked = false, last_marked = NOW()
  401. WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?");
  402. } else if ($cmode == 1) {
  403. $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET
  404. marked = true, last_marked = NOW()
  405. WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?");
  406. } else {
  407. $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET
  408. marked = NOT marked,last_marked = NOW()
  409. WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?");
  410. }
  411. $sth->execute(array_merge($ids, [$_SESSION['uid']]));
  412. }
  413. private function publishArticlesById($ids, $cmode) {
  414. $ids_qmarks = arr_qmarks($ids);
  415. if ($cmode == 0) {
  416. $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET
  417. published = false, last_published = NOW()
  418. WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?");
  419. } else if ($cmode == 1) {
  420. $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET
  421. published = true, last_published = NOW()
  422. WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?");
  423. } else {
  424. $sth = $this->pdo->prepare("UPDATE ttrss_user_entries SET
  425. published = NOT published,last_published = NOW()
  426. WHERE ref_id IN ($ids_qmarks) AND owner_uid = ?");
  427. }
  428. $sth->execute(array_merge($ids, [$_SESSION['uid']]));
  429. }
  430. function getlinktitlebyid() {
  431. $id = clean($_REQUEST['id']);
  432. $sth = $this->pdo->prepare("SELECT link, title FROM ttrss_entries, ttrss_user_entries
  433. WHERE ref_id = ? AND ref_id = id AND owner_uid = ?");
  434. $sth->execute([$id, $_SESSION['uid']]);
  435. if ($row = $sth->fetch()) {
  436. $link = $row['link'];
  437. $title = $row['title'];
  438. echo json_encode(array("link" => $link, "title" => $title));
  439. } else {
  440. echo json_encode(array("error" => "ARTICLE_NOT_FOUND"));
  441. }
  442. }
  443. function log() {
  444. $msg = clean($_REQUEST['msg']);
  445. $file = basename(clean($_REQUEST['file']));
  446. $line = (int) clean($_REQUEST['line']);
  447. $context = clean($_REQUEST['context']);
  448. if ($msg) {
  449. Logger::get()->log_error(E_USER_WARNING,
  450. $msg, 'client-js:' . $file, $line, $context);
  451. echo json_encode(array("message" => "HOST_ERROR_LOGGED"));
  452. } else {
  453. echo json_encode(array("error" => "MESSAGE_NOT_FOUND"));
  454. }
  455. }
  456. }