rpc.php 16 KB

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