public.php 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251
  1. <?php
  2. class Handler_Public extends Handler {
  3. private function generate_syndicated_feed($owner_uid, $feed, $is_cat,
  4. $limit, $offset, $search,
  5. $view_mode = false, $format = 'atom', $order = false, $orig_guid = false, $start_ts = false) {
  6. require_once "lib/MiniTemplator.class.php";
  7. $note_style = "background-color : #fff7d5;
  8. border-width : 1px; ".
  9. "padding : 5px; border-style : dashed; border-color : #e7d796;".
  10. "margin-bottom : 1em; color : #9a8c59;";
  11. if (!$limit) $limit = 60;
  12. $date_sort_field = "date_entered DESC, updated DESC";
  13. if ($feed == -2 && !$is_cat) {
  14. $date_sort_field = "last_published DESC";
  15. } else if ($feed == -1 && !$is_cat) {
  16. $date_sort_field = "last_marked DESC";
  17. }
  18. switch ($order) {
  19. case "title":
  20. $date_sort_field = "ttrss_entries.title, date_entered, updated";
  21. break;
  22. case "date_reverse":
  23. $date_sort_field = "date_entered, updated";
  24. break;
  25. case "feed_dates":
  26. $date_sort_field = "updated DESC";
  27. break;
  28. }
  29. $params = array(
  30. "owner_uid" => $owner_uid,
  31. "feed" => $feed,
  32. "limit" => $limit,
  33. "view_mode" => $view_mode,
  34. "cat_view" => $is_cat,
  35. "search" => $search,
  36. "override_order" => $date_sort_field,
  37. "include_children" => true,
  38. "ignore_vfeed_group" => true,
  39. "offset" => $offset,
  40. "start_ts" => $start_ts
  41. );
  42. if (!$is_cat && is_numeric($feed) && $feed < PLUGIN_FEED_BASE_INDEX && $feed > LABEL_BASE_INDEX) {
  43. $user_plugins = get_pref("_ENABLED_PLUGINS", $owner_uid);
  44. $tmppluginhost = new PluginHost();
  45. $tmppluginhost->load(PLUGINS, PluginHost::KIND_ALL);
  46. $tmppluginhost->load($user_plugins, PluginHost::KIND_USER, $owner_uid);
  47. $tmppluginhost->load_data();
  48. $handler = $tmppluginhost->get_feed_handler(
  49. PluginHost::feed_to_pfeed_id($feed));
  50. if ($handler) {
  51. $qfh_ret = $handler->get_headlines(PluginHost::feed_to_pfeed_id($feed), $params);
  52. }
  53. } else {
  54. $qfh_ret = Feeds::queryFeedHeadlines($params);
  55. }
  56. $result = $qfh_ret[0];
  57. $feed_title = htmlspecialchars($qfh_ret[1]);
  58. $feed_site_url = $qfh_ret[2];
  59. /* $last_error = $qfh_ret[3]; */
  60. $feed_self_url = get_self_url_prefix() .
  61. "/public.php?op=rss&id=$feed&key=" .
  62. get_feed_access_key($feed, false, $owner_uid);
  63. if (!$feed_site_url) $feed_site_url = get_self_url_prefix();
  64. if ($format == 'atom') {
  65. $tpl = new MiniTemplator;
  66. $tpl->readTemplateFromFile("templates/generated_feed.txt");
  67. $tpl->setVariable('FEED_TITLE', $feed_title, true);
  68. $tpl->setVariable('VERSION', VERSION, true);
  69. $tpl->setVariable('FEED_URL', htmlspecialchars($feed_self_url), true);
  70. $tpl->setVariable('SELF_URL', htmlspecialchars(get_self_url_prefix()), true);
  71. while ($line = $result->fetch()) {
  72. $line["content_preview"] = sanitize(truncate_string(strip_tags($line["content"]), 100, '...'));
  73. foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_QUERY_HEADLINES) as $p) {
  74. $line = $p->hook_query_headlines($line);
  75. }
  76. foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_EXPORT_FEED) as $p) {
  77. $line = $p->hook_article_export_feed($line, $feed, $is_cat);
  78. }
  79. $tpl->setVariable('ARTICLE_ID',
  80. htmlspecialchars($orig_guid ? $line['link'] :
  81. $this->make_article_tag_uri($line['id'], $line['date_entered'])), true);
  82. $tpl->setVariable('ARTICLE_LINK', htmlspecialchars($line['link']), true);
  83. $tpl->setVariable('ARTICLE_TITLE', htmlspecialchars($line['title']), true);
  84. $tpl->setVariable('ARTICLE_EXCERPT', $line["content_preview"], true);
  85. $content = sanitize($line["content"], false, $owner_uid,
  86. $feed_site_url, false, $line["id"]);
  87. if ($line['note']) {
  88. $content = "<div style=\"$note_style\">Article note: " . $line['note'] . "</div>" .
  89. $content;
  90. $tpl->setVariable('ARTICLE_NOTE', htmlspecialchars($line['note']), true);
  91. }
  92. $tpl->setVariable('ARTICLE_CONTENT', $content, true);
  93. $tpl->setVariable('ARTICLE_UPDATED_ATOM',
  94. date('c', strtotime($line["updated"])), true);
  95. $tpl->setVariable('ARTICLE_UPDATED_RFC822',
  96. date(DATE_RFC822, strtotime($line["updated"])), true);
  97. $tpl->setVariable('ARTICLE_AUTHOR', htmlspecialchars($line['author']), true);
  98. $tpl->setVariable('ARTICLE_SOURCE_LINK', htmlspecialchars($line['site_url'] ? $line["site_url"] : get_self_url_prefix()), true);
  99. $tpl->setVariable('ARTICLE_SOURCE_TITLE', htmlspecialchars($line['feed_title'] ? $line['feed_title'] : $feed_title), true);
  100. $tags = Article::get_article_tags($line["id"], $owner_uid);
  101. foreach ($tags as $tag) {
  102. $tpl->setVariable('ARTICLE_CATEGORY', htmlspecialchars($tag), true);
  103. $tpl->addBlock('category');
  104. }
  105. $enclosures = Article::get_article_enclosures($line["id"]);
  106. if (count($enclosures) > 0) {
  107. foreach ($enclosures as $e) {
  108. $type = htmlspecialchars($e['content_type']);
  109. $url = htmlspecialchars($e['content_url']);
  110. $length = $e['duration'] ? $e['duration'] : 1;
  111. $tpl->setVariable('ARTICLE_ENCLOSURE_URL', $url, true);
  112. $tpl->setVariable('ARTICLE_ENCLOSURE_TYPE', $type, true);
  113. $tpl->setVariable('ARTICLE_ENCLOSURE_LENGTH', $length, true);
  114. $tpl->addBlock('enclosure');
  115. }
  116. } else {
  117. $tpl->setVariable('ARTICLE_ENCLOSURE_URL', null, true);
  118. $tpl->setVariable('ARTICLE_ENCLOSURE_TYPE', null, true);
  119. $tpl->setVariable('ARTICLE_ENCLOSURE_LENGTH', null, true);
  120. }
  121. $tpl->setVariable('ARTICLE_OG_IMAGE',
  122. $this->get_article_image($enclosures, $line['content'], $feed_site_url), true);
  123. $tpl->addBlock('entry');
  124. }
  125. $tmp = "";
  126. $tpl->addBlock('feed');
  127. $tpl->generateOutputToString($tmp);
  128. if (@!clean($_REQUEST["noxml"])) {
  129. header("Content-Type: text/xml; charset=utf-8");
  130. } else {
  131. header("Content-Type: text/plain; charset=utf-8");
  132. }
  133. print $tmp;
  134. } else if ($format == 'json') {
  135. $feed = array();
  136. $feed['title'] = $feed_title;
  137. $feed['version'] = VERSION;
  138. $feed['feed_url'] = $feed_self_url;
  139. $feed['self_url'] = get_self_url_prefix();
  140. $feed['articles'] = array();
  141. while ($line = $result->fetch()) {
  142. $line["content_preview"] = sanitize(truncate_string(strip_tags($line["content_preview"]), 100, '...'));
  143. foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_QUERY_HEADLINES) as $p) {
  144. $line = $p->hook_query_headlines($line, 100);
  145. }
  146. foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_ARTICLE_EXPORT_FEED) as $p) {
  147. $line = $p->hook_article_export_feed($line, $feed, $is_cat);
  148. }
  149. $article = array();
  150. $article['id'] = $line['link'];
  151. $article['link'] = $line['link'];
  152. $article['title'] = $line['title'];
  153. $article['excerpt'] = $line["content_preview"];
  154. $article['content'] = sanitize($line["content"], false, $owner_uid, $feed_site_url, false, $line["id"]);
  155. $article['updated'] = date('c', strtotime($line["updated"]));
  156. if ($line['note']) $article['note'] = $line['note'];
  157. if ($article['author']) $article['author'] = $line['author'];
  158. $tags = Article::get_article_tags($line["id"], $owner_uid);
  159. if (count($tags) > 0) {
  160. $article['tags'] = array();
  161. foreach ($tags as $tag) {
  162. array_push($article['tags'], $tag);
  163. }
  164. }
  165. $enclosures = Article::get_article_enclosures($line["id"]);
  166. if (count($enclosures) > 0) {
  167. $article['enclosures'] = array();
  168. foreach ($enclosures as $e) {
  169. $type = $e['content_type'];
  170. $url = $e['content_url'];
  171. $length = $e['duration'];
  172. array_push($article['enclosures'], array("url" => $url, "type" => $type, "length" => $length));
  173. }
  174. }
  175. array_push($feed['articles'], $article);
  176. }
  177. header("Content-Type: text/json; charset=utf-8");
  178. print json_encode($feed);
  179. } else {
  180. header("Content-Type: text/plain; charset=utf-8");
  181. print json_encode(array("error" => array("message" => "Unknown format")));
  182. }
  183. }
  184. function getUnread() {
  185. $login = clean($_REQUEST["login"]);
  186. $fresh = clean($_REQUEST["fresh"]) == "1";
  187. $sth = $this->pdo->prepare("SELECT id FROM ttrss_users WHERE login = ?");
  188. $sth->execute([$login]);
  189. if ($row = $sth->fetch()) {
  190. $uid = $row["id"];
  191. print Feeds::getGlobalUnread($uid);
  192. if ($fresh) {
  193. print ";";
  194. print Feeds::getFeedArticles(-3, false, true, $uid);
  195. }
  196. } else {
  197. print "-1;User not found";
  198. }
  199. }
  200. function getProfiles() {
  201. $login = clean($_REQUEST["login"]);
  202. $rv = [];
  203. if ($login) {
  204. $sth = $this->pdo->prepare("SELECT ttrss_settings_profiles.* FROM ttrss_settings_profiles,ttrss_users
  205. WHERE ttrss_users.id = ttrss_settings_profiles.owner_uid AND login = ? ORDER BY title");
  206. $sth->execute([$login]);
  207. $rv = [ [ "value" => 0, "label" => __("Default profile") ] ];
  208. while ($line = $sth->fetch()) {
  209. $id = $line["id"];
  210. $title = $line["title"];
  211. array_push($rv, [ "label" => $title, "value" => $id ]);
  212. }
  213. }
  214. print json_encode($rv);
  215. }
  216. function logout() {
  217. logout_user();
  218. header("Location: index.php");
  219. }
  220. function share() {
  221. $uuid = clean($_REQUEST["key"]);
  222. $sth = $this->pdo->prepare("SELECT ref_id, owner_uid FROM ttrss_user_entries WHERE
  223. uuid = ?");
  224. $sth->execute([$uuid]);
  225. if ($row = $sth->fetch()) {
  226. header("Content-Type: text/html");
  227. $id = $row["ref_id"];
  228. $owner_uid = $row["owner_uid"];
  229. print $this->format_article($id, $owner_uid);
  230. } else {
  231. header($_SERVER["SERVER_PROTOCOL"]." 404 Not Found");
  232. print "Article not found.";
  233. }
  234. }
  235. private function get_article_image($enclosures, $content, $site_url) {
  236. $og_image = false;
  237. foreach ($enclosures as $enc) {
  238. if (strpos($enc["content_type"], "image/") !== FALSE) {
  239. return rewrite_relative_url($site_url, $enc["content_url"]);
  240. }
  241. }
  242. if (!$og_image) {
  243. $tmpdoc = new DOMDocument();
  244. if (@$tmpdoc->loadHTML(mb_substr($content, 0, 131070))) {
  245. $tmpxpath = new DOMXPath($tmpdoc);
  246. $imgs = $tmpxpath->query("//img");
  247. foreach ($imgs as $img) {
  248. $src = $img->getAttribute("src");
  249. if (mb_strpos($src, "data:") !== 0)
  250. return rewrite_relative_url($site_url, $src);
  251. }
  252. }
  253. }
  254. return false;
  255. }
  256. private function format_article($id, $owner_uid) {
  257. $pdo = Db::pdo();
  258. $sth = $pdo->prepare("SELECT id,title,link,content,feed_id,comments,int_id,lang,
  259. ".SUBSTRING_FOR_DATE."(updated,1,16) as updated,
  260. (SELECT site_url FROM ttrss_feeds WHERE id = feed_id) as site_url,
  261. (SELECT title FROM ttrss_feeds WHERE id = feed_id) as feed_title,
  262. (SELECT hide_images FROM ttrss_feeds WHERE id = feed_id) as hide_images,
  263. (SELECT always_display_enclosures FROM ttrss_feeds WHERE id = feed_id) as always_display_enclosures,
  264. num_comments,
  265. tag_cache,
  266. author,
  267. guid,
  268. orig_feed_id,
  269. note
  270. FROM ttrss_entries,ttrss_user_entries
  271. WHERE id = ? AND ref_id = id AND owner_uid = ?");
  272. $sth->execute([$id, $owner_uid]);
  273. $rv = '';
  274. if ($line = $sth->fetch()) {
  275. $line["tags"] = Article::get_article_tags($id, $owner_uid, $line["tag_cache"]);
  276. unset($line["tag_cache"]);
  277. $line["content"] = sanitize($line["content"],
  278. $line['hide_images'],
  279. $owner_uid, $line["site_url"], false, $line["id"]);
  280. foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_RENDER_ARTICLE) as $p) {
  281. $line = $p->hook_render_article($line);
  282. }
  283. $line['content'] = rewrite_cached_urls($line['content']);
  284. $enclosures = Article::get_article_enclosures($line["id"]);
  285. header("Content-Type: text/html");
  286. $rv .= "<!DOCTYPE html>
  287. <html><head>
  288. <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>
  289. <title>".$line["title"]."</title>".
  290. stylesheet_tag("css/default.css")."
  291. <link rel=\"shortcut icon\" type=\"image/png\" href=\"images/favicon.png\">
  292. <link rel=\"icon\" type=\"image/png\" sizes=\"72x72\" href=\"images/favicon-72px.png\">";
  293. $rv .= "<meta property=\"og:title\" content=\"".htmlspecialchars($line["title"])."\"/>\n";
  294. $rv .= "<meta property=\"og:site_name\" content=\"".htmlspecialchars($line["feed_title"])."\"/>\n";
  295. $rv .= "<meta property=\"og:description\" content=\"".
  296. htmlspecialchars(truncate_string(strip_tags($line["content"]), 500, "..."))."\"/>\n";
  297. $rv .= "</head>";
  298. $og_image = $this->get_article_image($enclosures, $line['content'], $line["site_url"]);
  299. if ($og_image) {
  300. $rv .= "<meta property=\"og:image\" content=\"" . htmlspecialchars($og_image) . "\"/>";
  301. }
  302. $rv .= "<body class='flat ttrss_utility ttrss_zoom'>";
  303. $rv .= "<div class='container'>";
  304. if ($line["link"]) {
  305. $rv .= "<h1><a target='_blank' rel='noopener noreferrer'
  306. title=\"".htmlspecialchars($line['title'])."\"
  307. href=\"" .htmlspecialchars($line["link"]) . "\">" . $line["title"] . "</a></h1>";
  308. } else {
  309. $rv .= "<h1>" . $line["title"] . "</h1>";
  310. }
  311. $rv .= "<div class='content post'>";
  312. /* header */
  313. $rv .= "<div class='header'>";
  314. $rv .= "<div class='row'>"; # row
  315. //$entry_author = $line["author"] ? " - " . $line["author"] : "";
  316. $parsed_updated = make_local_datetime($line["updated"], true,
  317. $owner_uid, true);
  318. $rv .= "<div>".$line['author']."</div>";
  319. $rv .= "<div>$parsed_updated</div>";
  320. $rv .= "</div>"; # row
  321. $rv .= "</div>"; # header
  322. /* content */
  323. $lang = $line['lang'] ? $line['lang'] : "en";
  324. $rv .= "<div class=\"content\" lang=\"$lang\">";
  325. /* content body */
  326. $rv .= $line["content"];
  327. $rv .= Article::format_article_enclosures($id,
  328. $line["always_display_enclosures"],
  329. $line["content"],
  330. $line["hide_images"]);
  331. $rv .= "</div>"; # content
  332. $rv .= "</div>"; # post
  333. }
  334. foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_FORMAT_ARTICLE) as $p) {
  335. $rv = $p->hook_format_article($rv, $line, true);
  336. }
  337. return $rv;
  338. }
  339. function rss() {
  340. $feed = clean($_REQUEST["id"]);
  341. $key = clean($_REQUEST["key"]);
  342. $is_cat = clean($_REQUEST["is_cat"]);
  343. $limit = (int)clean($_REQUEST["limit"]);
  344. $offset = (int)clean($_REQUEST["offset"]);
  345. $search = clean($_REQUEST["q"]);
  346. $view_mode = clean($_REQUEST["view-mode"]);
  347. $order = clean($_REQUEST["order"]);
  348. $start_ts = clean($_REQUEST["ts"]);
  349. $format = clean($_REQUEST['format']);
  350. $orig_guid = clean($_REQUEST["orig_guid"]);
  351. if (!$format) $format = 'atom';
  352. if (SINGLE_USER_MODE) {
  353. authenticate_user("admin", null);
  354. }
  355. $owner_id = false;
  356. if ($key) {
  357. $sth = $this->pdo->prepare("SELECT owner_uid FROM
  358. ttrss_access_keys WHERE access_key = ? AND feed_id = ?");
  359. $sth->execute([$key, $feed]);
  360. if ($row = $sth->fetch())
  361. $owner_id = $row["owner_uid"];
  362. }
  363. if ($owner_id) {
  364. $this->generate_syndicated_feed($owner_id, $feed, $is_cat, $limit,
  365. $offset, $search, $view_mode, $format, $order, $orig_guid, $start_ts);
  366. } else {
  367. header('HTTP/1.1 403 Forbidden');
  368. }
  369. }
  370. function updateTask() {
  371. PluginHost::getInstance()->run_hooks(PluginHost::HOOK_UPDATE_TASK, "hook_update_task", false);
  372. }
  373. function housekeepingTask() {
  374. PluginHost::getInstance()->run_hooks(PluginHost::HOOK_HOUSE_KEEPING, "hook_house_keeping", false);
  375. }
  376. function globalUpdateFeeds() {
  377. RPC::updaterandomfeed_real();
  378. PluginHost::getInstance()->run_hooks(PluginHost::HOOK_UPDATE_TASK, "hook_update_task", false);
  379. }
  380. function sharepopup() {
  381. if (SINGLE_USER_MODE) {
  382. login_sequence();
  383. }
  384. header('Content-Type: text/html; charset=utf-8');
  385. ?>
  386. <!DOCTYPE html>
  387. <html>
  388. <head>
  389. <title><?php echo __("Share with Tiny Tiny RSS") ?> ?></title>
  390. <?php
  391. echo stylesheet_tag("css/default.css");
  392. echo javascript_tag("lib/prototype.js");
  393. echo javascript_tag("lib/dojo/dojo.js");
  394. echo javascript_tag("lib/dojo/tt-rss-layer.js");
  395. echo javascript_tag("lib/scriptaculous/scriptaculous.js?load=effects,controls")
  396. ?>
  397. <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
  398. <link rel="shortcut icon" type="image/png" href="images/favicon.png">
  399. <link rel="icon" type="image/png" sizes="72x72" href="images/favicon-72px.png">
  400. </head>
  401. <body class='flat ttrss_utility share_popup'>
  402. <script type="text/javascript">
  403. require(['dojo/parser', "dojo/ready", 'dijit/form/Button','dijit/form/CheckBox', 'dijit/form/Form',
  404. 'dijit/form/Select','dijit/form/TextBox','dijit/form/ValidationTextBox'],function(parser, ready){
  405. ready(function() {
  406. parser.parse();
  407. new Ajax.Autocompleter('labels_value', 'labels_choices',
  408. "backend.php?op=rpc&method=completeLabels",
  409. { tokens: ',', paramName: "search" });
  410. });
  411. });
  412. </script>
  413. <div class="content">
  414. <?php
  415. $action = clean($_REQUEST["action"]);
  416. if ($_SESSION["uid"]) {
  417. if ($action == 'share') {
  418. $title = strip_tags(clean($_REQUEST["title"]));
  419. $url = strip_tags(clean($_REQUEST["url"]));
  420. $content = strip_tags(clean($_REQUEST["content"]));
  421. $labels = strip_tags(clean($_REQUEST["labels"]));
  422. Article::create_published_article($title, $url, $content, $labels,
  423. $_SESSION["uid"]);
  424. print "<script type='text/javascript'>";
  425. print "window.close();";
  426. print "</script>";
  427. } else {
  428. $title = htmlspecialchars(clean($_REQUEST["title"]));
  429. $url = htmlspecialchars(clean($_REQUEST["url"]));
  430. ?>
  431. <form id='share_form' name='share_form'>
  432. <input type="hidden" name="op" value="sharepopup">
  433. <input type="hidden" name="action" value="share">
  434. <fieldset>
  435. <label><?php echo __("Title:") ?></label>
  436. <input style='width : 270px' dojoType='dijit.form.TextBox' name='title' value="<?php echo $title ?>">
  437. </fieldset>
  438. <fieldset>
  439. <label><?php echo __("URL:") ?></label>
  440. <input style='width : 270px' name='url' dojoType='dijit.form.TextBox' value="<?php echo $url ?>">
  441. </fieldset>
  442. <fieldset>
  443. <label><?php echo __("Content:") ?></label>
  444. <input style='width : 270px' name='content' dojoType='dijit.form.TextBox' value="">
  445. </fieldset>
  446. <fieldset>
  447. <label><?php echo __("Labels:") ?></label>
  448. <input style='width : 270px' name='labels' dojoType='dijit.form.TextBox' id="labels_value"
  449. placeholder='Alpha, Beta, Gamma' value="">
  450. <div class="autocomplete" id="labels_choices"
  451. style="display : block"></div>
  452. </fieldset>
  453. <hr/>
  454. <fieldset>
  455. <button dojoType='dijit.form.Button' class="alt-primary" type="submit"><?php echo __('Share') ?></button>
  456. <button dojoType='dijit.form.Button' onclick="return window.close()"><?php echo __('Cancel') ?></button>
  457. <span class="insensitive small"><?php echo __("Shared article will appear in the Published feed.") ?></span>
  458. </fieldset>
  459. </form>
  460. <?php
  461. }
  462. } else {
  463. $return = urlencode($_SERVER["REQUEST_URI"])
  464. ?>
  465. <?php print_error("Not logged in"); ?>
  466. <form action="public.php?return=<?php echo $return ?>" method="post">
  467. <input type="hidden" name="op" value="login">
  468. <fieldset>
  469. <label><?php echo __("Login:") ?></label>
  470. <input name="login" id="login" dojoType="dijit.form.TextBox" type="text"
  471. onchange="fetchProfiles()" onfocus="fetchProfiles()" onblur="fetchProfiles()"
  472. required="1" value="<?php echo $_SESSION["fake_login"] ?>" />
  473. </fieldset>
  474. <fieldset>
  475. <label><?php echo __("Password:") ?></label>
  476. <input type="password" name="password" required="1"
  477. dojoType="dijit.form.TextBox"
  478. class="input input-text"
  479. value="<?php echo $_SESSION["fake_password"] ?>"/>
  480. </fieldset>
  481. <hr/>
  482. <fieldset>
  483. <label> </label>
  484. <button dojoType="dijit.form.Button" type="submit" class="alt-primary"><?php echo __('Log in') ?></button>
  485. </fieldset>
  486. </form>
  487. <?php
  488. }
  489. print "</div></body></html>";
  490. }
  491. function login() {
  492. if (!SINGLE_USER_MODE) {
  493. $login = clean($_POST["login"]);
  494. $password = clean($_POST["password"]);
  495. $remember_me = clean($_POST["remember_me"]);
  496. if ($remember_me) {
  497. session_set_cookie_params(SESSION_COOKIE_LIFETIME);
  498. } else {
  499. session_set_cookie_params(0);
  500. }
  501. if (authenticate_user($login, $password)) {
  502. $_POST["password"] = "";
  503. if (get_schema_version() >= 120) {
  504. $_SESSION["language"] = get_pref("USER_LANGUAGE", $_SESSION["uid"]);
  505. }
  506. $_SESSION["ref_schema_version"] = get_schema_version(true);
  507. $_SESSION["bw_limit"] = !!clean($_POST["bw_limit"]);
  508. if (clean($_POST["profile"])) {
  509. $profile = (int) clean($_POST["profile"]);
  510. $sth = $this->pdo->prepare("SELECT id FROM ttrss_settings_profiles
  511. WHERE id = ? AND owner_uid = ?");
  512. $sth->execute([$profile, $_SESSION['uid']]);
  513. if ($sth->fetch()) {
  514. $_SESSION["profile"] = $profile;
  515. } else {
  516. $_SESSION["profile"] = null;
  517. }
  518. }
  519. } else {
  520. // start an empty session to deliver login error message
  521. @session_start();
  522. if (!isset($_SESSION["login_error_msg"]))
  523. $_SESSION["login_error_msg"] = __("Incorrect username or password");
  524. user_error("Failed login attempt for $login from {$_SERVER['REMOTE_ADDR']}", E_USER_WARNING);
  525. }
  526. if (clean($_REQUEST['return'])) {
  527. header("Location: " . clean($_REQUEST['return']));
  528. } else {
  529. header("Location: " . get_self_url_prefix());
  530. }
  531. }
  532. }
  533. /* function subtest() {
  534. header("Content-type: text/plain; charset=utf-8");
  535. $url = clean($_REQUEST["url"]);
  536. print "$url\n\n";
  537. print_r(get_feeds_from_html($url, fetch_file_contents($url)));
  538. } */
  539. function subscribe() {
  540. if (SINGLE_USER_MODE) {
  541. login_sequence();
  542. }
  543. if ($_SESSION["uid"]) {
  544. $feed_url = trim(clean($_REQUEST["feed_url"]));
  545. header('Content-Type: text/html; charset=utf-8');
  546. ?>
  547. <!DOCTYPE html>
  548. <html>
  549. <head>
  550. <title>Tiny Tiny RSS</title>
  551. <?php
  552. echo stylesheet_tag("css/default.css");
  553. echo javascript_tag("lib/prototype.js");
  554. echo javascript_tag("lib/dojo/dojo.js");
  555. echo javascript_tag("lib/dojo/tt-rss-layer.js");
  556. ?>
  557. <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
  558. <link rel="shortcut icon" type="image/png" href="images/favicon.png">
  559. <link rel="icon" type="image/png" sizes="72x72" href="images/favicon-72px.png">
  560. </head>
  561. <body class='flat ttrss_utility'>
  562. <script type="text/javascript">
  563. require(['dojo/parser', "dojo/ready", 'dijit/form/Button','dijit/form/CheckBox', 'dijit/form/Form',
  564. 'dijit/form/Select','dijit/form/TextBox','dijit/form/ValidationTextBox'],function(parser, ready){
  565. ready(function() {
  566. parser.parse();
  567. });
  568. });
  569. </script>
  570. <div class="container">
  571. <h1><?php echo __("Subscribe to feed...") ?></h1>
  572. <div class='content'>
  573. <?php
  574. if (!$feed_url) {
  575. print_error("No feed to subscribe to.");
  576. } else {
  577. $rc = Feeds::subscribe_to_feed($feed_url);
  578. $feed_urls = false;
  579. switch ($rc['code']) {
  580. case 0:
  581. print_warning(T_sprintf("Already subscribed to <b>%s</b>.", $feed_url));
  582. break;
  583. case 1:
  584. print_notice(T_sprintf("Subscribed to <b>%s</b>.", $feed_url));
  585. break;
  586. case 2:
  587. print_error(T_sprintf("Could not subscribe to <b>%s</b>.", $feed_url));
  588. break;
  589. case 3:
  590. print_error(T_sprintf("No feeds found in <b>%s</b>.", $feed_url));
  591. break;
  592. case 4:
  593. $feed_urls = $rc["feeds"];
  594. break;
  595. case 5:
  596. print_error(T_sprintf("Could not subscribe to <b>%s</b>.<br>Can't download the Feed URL.", $feed_url));
  597. break;
  598. }
  599. if ($feed_urls) {
  600. print "<form action='public.php'>";
  601. print "<input type='hidden' name='op' value='subscribe'>";
  602. print "<fieldset>";
  603. print "<label style='display : inline'>" . __("Multiple feed URLs found:") . "</label>";
  604. print "<select name='feed_url' dojoType='dijit.form.Select'>";
  605. foreach ($feed_urls as $url => $name) {
  606. $url = htmlspecialchars($url);
  607. $name = htmlspecialchars($name);
  608. print "<option value=\"$url\">$name</option>";
  609. }
  610. print "</select>";
  611. print "<button class='alt-primary' dojoType='dijit.form.Button' type='submit'>".__("Subscribe to selected feed")."</button>";
  612. print "</fieldset>";
  613. print "</form>";
  614. }
  615. $tp_uri = get_self_url_prefix() . "/prefs.php";
  616. if ($rc['code'] <= 2){
  617. $sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds WHERE
  618. feed_url = ? AND owner_uid = ?");
  619. $sth->execute([$feed_url, $_SESSION['uid']]);
  620. $row = $sth->fetch();
  621. $feed_id = $row["id"];
  622. } else {
  623. $feed_id = 0;
  624. }
  625. print "<p>";
  626. if ($feed_id) {
  627. print "<form method='GET' style='float : left' action=\"$tp_uri\">
  628. <input type='hidden' name='tab' value='feedConfig'>
  629. <input type='hidden' name='method' value='editfeed'>
  630. <input type='hidden' name='methodparam' value='$feed_id'>
  631. <button dojoType='dijit.form.Button' class='alt-info' type='submit'>".__("Edit subscription options")."</button>
  632. </form>";
  633. }
  634. }
  635. print "<a href='index.php'>".__("Return to Tiny Tiny RSS")."</a>";
  636. print "</div></div></body></html>";
  637. } else {
  638. render_login_form();
  639. }
  640. }
  641. function index() {
  642. header("Content-Type: text/plain");
  643. print error_json(13);
  644. }
  645. function forgotpass() {
  646. startup_gettext();
  647. session_start();
  648. @$hash = clean($_REQUEST["hash"]);
  649. header('Content-Type: text/html; charset=utf-8');
  650. ?>
  651. <!DOCTYPE html>
  652. <html>
  653. <head>
  654. <title>Tiny Tiny RSS</title>
  655. <link rel="shortcut icon" type="image/png" href="images/favicon.png">
  656. <link rel="icon" type="image/png" sizes="72x72" href="images/favicon-72px.png">
  657. <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
  658. <?php
  659. echo stylesheet_tag("css/default.css");
  660. echo javascript_tag("lib/prototype.js");
  661. echo javascript_tag("lib/dojo/dojo.js");
  662. echo javascript_tag("lib/dojo/tt-rss-layer.js");
  663. ?>
  664. </head>
  665. <body class='flat ttrss_utility'>
  666. <div class='container'>
  667. <script type="text/javascript">
  668. require(['dojo/parser', "dojo/ready", 'dijit/form/Button','dijit/form/CheckBox', 'dijit/form/Form',
  669. 'dijit/form/Select','dijit/form/TextBox','dijit/form/ValidationTextBox'],function(parser, ready){
  670. ready(function() {
  671. parser.parse();
  672. });
  673. });
  674. </script>
  675. <?php
  676. print "<h1>".__("Password recovery")."</h1>";
  677. print "<div class='content'>";
  678. @$method = clean($_POST['method']);
  679. if ($hash) {
  680. $login = clean($_REQUEST["login"]);
  681. if ($login) {
  682. $sth = $this->pdo->prepare("SELECT id, resetpass_token FROM ttrss_users
  683. WHERE login = ?");
  684. $sth->execute([$login]);
  685. if ($row = $sth->fetch()) {
  686. $id = $row["id"];
  687. $resetpass_token_full = $row["resetpass_token"];
  688. list($timestamp, $resetpass_token) = explode(":", $resetpass_token_full);
  689. if ($timestamp && $resetpass_token &&
  690. $timestamp >= time() - 15*60*60 &&
  691. $resetpass_token == $hash) {
  692. $sth = $this->pdo->prepare("UPDATE ttrss_users SET resetpass_token = NULL
  693. WHERE id = ?");
  694. $sth->execute([$id]);
  695. Pref_Users::resetUserPassword($id, true);
  696. print "<p>"."Completed."."</p>";
  697. } else {
  698. print_error("Some of the information provided is missing or incorrect.");
  699. }
  700. } else {
  701. print_error("Some of the information provided is missing or incorrect.");
  702. }
  703. } else {
  704. print_error("Some of the information provided is missing or incorrect.");
  705. }
  706. print "<a href='index.php'>".__("Return to Tiny Tiny RSS")."</a>";
  707. } else if (!$method) {
  708. print_notice(__("You will need to provide valid account name and email. Password reset link will be sent to your email address."));
  709. print "<form method='POST' action='public.php'>
  710. <input type='hidden' name='method' value='do'>
  711. <input type='hidden' name='op' value='forgotpass'>
  712. <fieldset>
  713. <label>".__("Login:")."</label>
  714. <input dojoType='dijit.form.TextBox' type='text' name='login' value='' required>
  715. </fieldset>
  716. <fieldset>
  717. <label>".__("Email:")."</label>
  718. <input dojoType='dijit.form.TextBox' type='email' name='email' value='' required>
  719. </fieldset>";
  720. $_SESSION["pwdreset:testvalue1"] = rand(1,10);
  721. $_SESSION["pwdreset:testvalue2"] = rand(1,10);
  722. print "<fieldset>
  723. <label>".T_sprintf("How much is %d + %d:", $_SESSION["pwdreset:testvalue1"], $_SESSION["pwdreset:testvalue2"])."</label>
  724. <input dojoType='dijit.form.TextBox' type='text' name='test' value='' required>
  725. </fieldset>
  726. <hr/>
  727. <fieldset>
  728. <button dojoType='dijit.form.Button' type='submit' class='alt-danger'>".__("Reset password")."</button>
  729. <a href='index.php'>".__("Return to Tiny Tiny RSS")."</a>
  730. </fieldset>
  731. </form>";
  732. } else if ($method == 'do') {
  733. $login = clean($_POST["login"]);
  734. $email = clean($_POST["email"]);
  735. $test = clean($_POST["test"]);
  736. if ($test != ($_SESSION["pwdreset:testvalue1"] + $_SESSION["pwdreset:testvalue2"]) || !$email || !$login) {
  737. print_error(__('Some of the required form parameters are missing or incorrect.'));
  738. print "<form method='GET' action='public.php'>
  739. <input type='hidden' name='op' value='forgotpass'>
  740. <button dojoType='dijit.form.Button' type='submit' class='alt-primary'>".__("Go back")."</button>
  741. </form>";
  742. } else {
  743. // prevent submitting this form multiple times
  744. $_SESSION["pwdreset:testvalue1"] = rand(1, 1000);
  745. $_SESSION["pwdreset:testvalue2"] = rand(1, 1000);
  746. $sth = $this->pdo->prepare("SELECT id FROM ttrss_users
  747. WHERE login = ? AND email = ?");
  748. $sth->execute([$login, $email]);
  749. if ($row = $sth->fetch()) {
  750. print_notice("Password reset instructions are being sent to your email address.");
  751. $id = $row["id"];
  752. if ($id) {
  753. $resetpass_token = sha1(get_random_bytes(128));
  754. $resetpass_link = get_self_url_prefix() . "/public.php?op=forgotpass&hash=" . $resetpass_token .
  755. "&login=" . urlencode($login);
  756. require_once "lib/MiniTemplator.class.php";
  757. $tpl = new MiniTemplator;
  758. $tpl->readTemplateFromFile("templates/resetpass_link_template.txt");
  759. $tpl->setVariable('LOGIN', $login);
  760. $tpl->setVariable('RESETPASS_LINK', $resetpass_link);
  761. $tpl->addBlock('message');
  762. $message = "";
  763. $tpl->generateOutputToString($message);
  764. $mailer = new Mailer();
  765. $rc = $mailer->mail(["to_name" => $login,
  766. "to_address" => $email,
  767. "subject" => __("[tt-rss] Password reset request"),
  768. "message" => $message]);
  769. if (!$rc) print_error($mailer->error());
  770. $resetpass_token_full = time() . ":" . $resetpass_token;
  771. $sth = $this->pdo->prepare("UPDATE ttrss_users
  772. SET resetpass_token = ?
  773. WHERE login = ? AND email = ?");
  774. $sth->execute([$resetpass_token_full, $login, $email]);
  775. //Pref_Users::resetUserPassword($id, false);
  776. } else {
  777. print_error("User ID not found.");
  778. }
  779. print "<a href='index.php'>".__("Return to Tiny Tiny RSS")."</a>";
  780. } else {
  781. print_error(__("Sorry, login and email combination not found."));
  782. print "<form method='GET' action='public.php'>
  783. <input type='hidden' name='op' value='forgotpass'>
  784. <button dojoType='dijit.form.Button' type='submit'>".__("Go back")."</button>
  785. </form>";
  786. }
  787. }
  788. }
  789. print "</div>";
  790. print "</div>";
  791. print "</body>";
  792. print "</html>";
  793. }
  794. function dbupdate() {
  795. startup_gettext();
  796. if (!SINGLE_USER_MODE && $_SESSION["access_level"] < 10) {
  797. $_SESSION["login_error_msg"] = __("Your access level is insufficient to run this script.");
  798. render_login_form();
  799. exit;
  800. }
  801. ?>
  802. <!DOCTYPE html>
  803. <html>
  804. <head>
  805. <title>Database Updater</title>
  806. <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
  807. <?php echo stylesheet_tag("css/default.css") ?>
  808. <link rel="shortcut icon" type="image/png" href="images/favicon.png">
  809. <link rel="icon" type="image/png" sizes="72x72" href="images/favicon-72px.png">
  810. <?php
  811. echo stylesheet_tag("css/default.css");
  812. echo javascript_tag("lib/prototype.js");
  813. echo javascript_tag("lib/dojo/dojo.js");
  814. echo javascript_tag("lib/dojo/tt-rss-layer.js");
  815. ?>
  816. <style type="text/css">
  817. span.ok { color : #009000; font-weight : bold; }
  818. span.err { color : #ff0000; font-weight : bold; }
  819. </style>
  820. </head>
  821. <body class="flat ttrss_utility">
  822. <script type="text/javascript">
  823. require(['dojo/parser', "dojo/ready", 'dijit/form/Button','dijit/form/CheckBox', 'dijit/form/Form',
  824. 'dijit/form/Select','dijit/form/TextBox','dijit/form/ValidationTextBox'],function(parser, ready){
  825. ready(function() {
  826. parser.parse();
  827. });
  828. });
  829. function confirmOP() {
  830. return confirm("Update the database?");
  831. }
  832. </script>
  833. <div class="container">
  834. <h1><?php echo __("Database Updater") ?></h1>
  835. <div class="content">
  836. <?php
  837. @$op = clean($_REQUEST["subop"]);
  838. $updater = new DbUpdater(Db::pdo(), DB_TYPE, SCHEMA_VERSION);
  839. if ($op == "performupdate") {
  840. if ($updater->isUpdateRequired()) {
  841. print "<h2>" . __("Performing updates") . "</h2>";
  842. print "<h3>" . T_sprintf("Updating to schema version %d", SCHEMA_VERSION) . "</h3>";
  843. print "<ul>";
  844. for ($i = $updater->getSchemaVersion() + 1; $i <= SCHEMA_VERSION; $i++) {
  845. print "<li>" . T_sprintf("Performing update up to version %d...", $i);
  846. $result = $updater->performUpdateTo($i, true);
  847. if (!$result) {
  848. print "<span class='err'>".__("FAILED!")."</span></li></ul>";
  849. print_warning("One of the updates failed. Either retry the process or perform updates manually.");
  850. print "<a href='index.php'>".__("Return to Tiny Tiny RSS")."</a>";
  851. return;
  852. } else {
  853. print "<span class='ok'>".__("OK!")."</span></li>";
  854. }
  855. }
  856. print "</ul>";
  857. print_notice("Your Tiny Tiny RSS database is now updated to the latest version.");
  858. print "<a href='index.php'>".__("Return to Tiny Tiny RSS")."</a>";
  859. } else {
  860. print_notice("Tiny Tiny RSS database is up to date.");
  861. print "<a href='index.php'>".__("Return to Tiny Tiny RSS")."</a>";
  862. }
  863. } else {
  864. if ($updater->isUpdateRequired()) {
  865. print "<h2>" . __("Database update required") . "</h2>";
  866. print_notice("<h4>".
  867. sprintf("Your Tiny Tiny RSS database needs update to the latest version: %d to %d.",
  868. $updater->getSchemaVersion(), SCHEMA_VERSION).
  869. "</h4>");
  870. print_warning("Please backup your database before proceeding.");
  871. print "<form method='POST'>
  872. <input type='hidden' name='subop' value='performupdate'>
  873. <button type='submit' dojoType='dijit.form.Button' class='alt-danger' onclick='return confirmOP()'>".__("Perform updates")."</button>
  874. </form>";
  875. } else {
  876. print_notice("Tiny Tiny RSS database is up to date.");
  877. print "<a href='index.php'>".__("Return to Tiny Tiny RSS")."</a>";
  878. }
  879. }
  880. ?>
  881. </div>
  882. </div>
  883. </body>
  884. </html>
  885. <?php
  886. }
  887. function cached_url() {
  888. @$req_filename = basename($_GET['hash']);
  889. // we don't need an extension to find the file, hash is a complete URL
  890. $hash = preg_replace("/\.[^\.]*$/", "", $req_filename);
  891. if ($hash) {
  892. $filename = CACHE_DIR . '/images/' . $hash;
  893. if (file_exists($filename)) {
  894. header("Content-Disposition: inline; filename=\"$req_filename\"");
  895. send_local_file($filename);
  896. } else {
  897. header($_SERVER["SERVER_PROTOCOL"]." 404 Not Found");
  898. echo "File not found.";
  899. }
  900. }
  901. }
  902. private function make_article_tag_uri($id, $timestamp) {
  903. $timestamp = date("Y-m-d", strtotime($timestamp));
  904. return "tag:" . parse_url(get_self_url_prefix(), PHP_URL_HOST) . ",$timestamp:/$id";
  905. }
  906. // this should be used very carefully because this endpoint is exposed to unauthenticated users
  907. // plugin data is not loaded because there's no user context and owner_uid/session may or may not be available
  908. // in general, don't do anything user-related in here and do not modify $_SESSION
  909. public function pluginhandler() {
  910. $host = new PluginHost();
  911. $plugin = basename(clean($_REQUEST["plugin"]));
  912. $method = clean($_REQUEST["pmethod"]);
  913. $host->load($plugin, PluginHost::KIND_USER, 0);
  914. $host->load_data();
  915. $pclass = $host->get_plugin($plugin);
  916. if ($pclass) {
  917. if (method_exists($pclass, $method)) {
  918. if ($pclass->is_public_method($method)) {
  919. $pclass->$method();
  920. } else {
  921. header("Content-Type: text/json");
  922. print error_json(6);
  923. }
  924. } else {
  925. header("Content-Type: text/json");
  926. print error_json(13);
  927. }
  928. } else {
  929. header("Content-Type: text/json");
  930. print error_json(14);
  931. }
  932. }
  933. }
  934. ?>