public.php 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263
  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. Feeds::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('<?xml encoding="UTF-8">' . 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(html_entity_decode($line["title"], ENT_NOQUOTES | ENT_HTML401))."\"/>\n";
  294. $rv .= "<meta property='og:description' content=\"".
  295. htmlspecialchars(
  296. truncate_string(
  297. preg_replace("/[\r\n\t]/", "",
  298. preg_replace("/ {1,}/", " ",
  299. strip_tags(html_entity_decode($line["content"], ENT_NOQUOTES | ENT_HTML401))
  300. )
  301. ), 500, "...")
  302. )."\"/>\n";
  303. $rv .= "</head>";
  304. $og_image = $this->get_article_image($enclosures, $line['content'], $line["site_url"]);
  305. if ($og_image) {
  306. $rv .= "<meta property='og:image' content=\"" . htmlspecialchars($og_image) . "\"/>";
  307. }
  308. $rv .= "<body class='flat ttrss_utility ttrss_zoom'>";
  309. $rv .= "<div class='container'>";
  310. if ($line["link"]) {
  311. $rv .= "<h1><a target='_blank' rel='noopener noreferrer'
  312. title=\"".htmlspecialchars($line['title'])."\"
  313. href=\"" .htmlspecialchars($line["link"]) . "\">" . $line["title"] . "</a></h1>";
  314. } else {
  315. $rv .= "<h1>" . $line["title"] . "</h1>";
  316. }
  317. $rv .= "<div class='content post'>";
  318. /* header */
  319. $rv .= "<div class='header'>";
  320. $rv .= "<div class='row'>"; # row
  321. //$entry_author = $line["author"] ? " - " . $line["author"] : "";
  322. $parsed_updated = make_local_datetime($line["updated"], true,
  323. $owner_uid, true);
  324. $rv .= "<div>".$line['author']."</div>";
  325. $rv .= "<div>$parsed_updated</div>";
  326. $rv .= "</div>"; # row
  327. $rv .= "</div>"; # header
  328. /* content */
  329. $lang = $line['lang'] ? $line['lang'] : "en";
  330. $rv .= "<div class='content' lang='$lang'>";
  331. /* content body */
  332. $rv .= $line["content"];
  333. $rv .= Article::format_article_enclosures($id,
  334. $line["always_display_enclosures"],
  335. $line["content"],
  336. $line["hide_images"]);
  337. $rv .= "</div>"; # content
  338. $rv .= "</div>"; # post
  339. }
  340. foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_FORMAT_ARTICLE) as $p) {
  341. $rv = $p->hook_format_article($rv, $line, true);
  342. }
  343. return $rv;
  344. }
  345. function rss() {
  346. $feed = clean($_REQUEST["id"]);
  347. $key = clean($_REQUEST["key"]);
  348. $is_cat = clean($_REQUEST["is_cat"]);
  349. $limit = (int)clean($_REQUEST["limit"]);
  350. $offset = (int)clean($_REQUEST["offset"]);
  351. $search = clean($_REQUEST["q"]);
  352. $view_mode = clean($_REQUEST["view-mode"]);
  353. $order = clean($_REQUEST["order"]);
  354. $start_ts = clean($_REQUEST["ts"]);
  355. $format = clean($_REQUEST['format']);
  356. $orig_guid = clean($_REQUEST["orig_guid"]);
  357. if (!$format) $format = 'atom';
  358. if (SINGLE_USER_MODE) {
  359. authenticate_user("admin", null);
  360. }
  361. $owner_id = false;
  362. if ($key) {
  363. $sth = $this->pdo->prepare("SELECT owner_uid FROM
  364. ttrss_access_keys WHERE access_key = ? AND feed_id = ?");
  365. $sth->execute([$key, $feed]);
  366. if ($row = $sth->fetch())
  367. $owner_id = $row["owner_uid"];
  368. }
  369. if ($owner_id) {
  370. $this->generate_syndicated_feed($owner_id, $feed, $is_cat, $limit,
  371. $offset, $search, $view_mode, $format, $order, $orig_guid, $start_ts);
  372. } else {
  373. header('HTTP/1.1 403 Forbidden');
  374. }
  375. }
  376. function updateTask() {
  377. PluginHost::getInstance()->run_hooks(PluginHost::HOOK_UPDATE_TASK, "hook_update_task", false);
  378. }
  379. function housekeepingTask() {
  380. PluginHost::getInstance()->run_hooks(PluginHost::HOOK_HOUSE_KEEPING, "hook_house_keeping", false);
  381. }
  382. function globalUpdateFeeds() {
  383. RPC::updaterandomfeed_real();
  384. PluginHost::getInstance()->run_hooks(PluginHost::HOOK_UPDATE_TASK, "hook_update_task", false);
  385. }
  386. function sharepopup() {
  387. if (SINGLE_USER_MODE) {
  388. login_sequence();
  389. }
  390. header('Content-Type: text/html; charset=utf-8');
  391. ?>
  392. <!DOCTYPE html>
  393. <html>
  394. <head>
  395. <title><?php echo __("Share with Tiny Tiny RSS") ?> ?></title>
  396. <?php
  397. echo stylesheet_tag("css/default.css");
  398. echo javascript_tag("lib/prototype.js");
  399. echo javascript_tag("lib/dojo/dojo.js");
  400. echo javascript_tag("lib/dojo/tt-rss-layer.js");
  401. echo javascript_tag("lib/scriptaculous/scriptaculous.js?load=effects,controls")
  402. ?>
  403. <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
  404. <link rel="shortcut icon" type="image/png" href="images/favicon.png">
  405. <link rel="icon" type="image/png" sizes="72x72" href="images/favicon-72px.png">
  406. </head>
  407. <body class='flat ttrss_utility share_popup'>
  408. <script type="text/javascript">
  409. require(['dojo/parser', "dojo/ready", 'dijit/form/Button','dijit/form/CheckBox', 'dijit/form/Form',
  410. 'dijit/form/Select','dijit/form/TextBox','dijit/form/ValidationTextBox'],function(parser, ready){
  411. ready(function() {
  412. parser.parse();
  413. new Ajax.Autocompleter('labels_value', 'labels_choices',
  414. "backend.php?op=rpc&method=completeLabels",
  415. { tokens: ',', paramName: "search" });
  416. });
  417. });
  418. </script>
  419. <div class="content">
  420. <?php
  421. $action = clean($_REQUEST["action"]);
  422. if ($_SESSION["uid"]) {
  423. if ($action == 'share') {
  424. $title = strip_tags(clean($_REQUEST["title"]));
  425. $url = strip_tags(clean($_REQUEST["url"]));
  426. $content = strip_tags(clean($_REQUEST["content"]));
  427. $labels = strip_tags(clean($_REQUEST["labels"]));
  428. Article::create_published_article($title, $url, $content, $labels,
  429. $_SESSION["uid"]);
  430. print "<script type='text/javascript'>";
  431. print "window.close();";
  432. print "</script>";
  433. } else {
  434. $title = htmlspecialchars(clean($_REQUEST["title"]));
  435. $url = htmlspecialchars(clean($_REQUEST["url"]));
  436. ?>
  437. <form id='share_form' name='share_form'>
  438. <input type="hidden" name="op" value="sharepopup">
  439. <input type="hidden" name="action" value="share">
  440. <fieldset>
  441. <label><?php echo __("Title:") ?></label>
  442. <input style='width : 270px' dojoType='dijit.form.TextBox' name='title' value="<?php echo $title ?>">
  443. </fieldset>
  444. <fieldset>
  445. <label><?php echo __("URL:") ?></label>
  446. <input style='width : 270px' name='url' dojoType='dijit.form.TextBox' value="<?php echo $url ?>">
  447. </fieldset>
  448. <fieldset>
  449. <label><?php echo __("Content:") ?></label>
  450. <input style='width : 270px' name='content' dojoType='dijit.form.TextBox' value="">
  451. </fieldset>
  452. <fieldset>
  453. <label><?php echo __("Labels:") ?></label>
  454. <input style='width : 270px' name='labels' dojoType='dijit.form.TextBox' id="labels_value"
  455. placeholder='Alpha, Beta, Gamma' value="">
  456. <div class="autocomplete" id="labels_choices"
  457. style="display : block"></div>
  458. </fieldset>
  459. <hr/>
  460. <fieldset>
  461. <button dojoType='dijit.form.Button' class="alt-primary" type="submit"><?php echo __('Share') ?></button>
  462. <button dojoType='dijit.form.Button' onclick="return window.close()"><?php echo __('Cancel') ?></button>
  463. <span class="text-muted small"><?php echo __("Shared article will appear in the Published feed.") ?></span>
  464. </fieldset>
  465. </form>
  466. <?php
  467. }
  468. } else {
  469. $return = urlencode(make_self_url());
  470. ?>
  471. <?php print_error("Not logged in"); ?>
  472. <form action="public.php?return=<?php echo $return ?>" method="post">
  473. <input type="hidden" name="op" value="login">
  474. <fieldset>
  475. <label><?php echo __("Login:") ?></label>
  476. <input name="login" id="login" dojoType="dijit.form.TextBox" type="text"
  477. onchange="fetchProfiles()" onfocus="fetchProfiles()" onblur="fetchProfiles()"
  478. required="1" value="<?php echo $_SESSION["fake_login"] ?>" />
  479. </fieldset>
  480. <fieldset>
  481. <label><?php echo __("Password:") ?></label>
  482. <input type="password" name="password" required="1"
  483. dojoType="dijit.form.TextBox"
  484. class="input input-text"
  485. value="<?php echo $_SESSION["fake_password"] ?>"/>
  486. </fieldset>
  487. <hr/>
  488. <fieldset>
  489. <label> </label>
  490. <button dojoType="dijit.form.Button" type="submit" class="alt-primary"><?php echo __('Log in') ?></button>
  491. </fieldset>
  492. </form>
  493. <?php
  494. }
  495. print "</div></body></html>";
  496. }
  497. function login() {
  498. if (!SINGLE_USER_MODE) {
  499. $login = clean($_POST["login"]);
  500. $password = clean($_POST["password"]);
  501. $remember_me = clean($_POST["remember_me"]);
  502. if ($remember_me) {
  503. session_set_cookie_params(SESSION_COOKIE_LIFETIME);
  504. } else {
  505. session_set_cookie_params(0);
  506. }
  507. if (authenticate_user($login, $password)) {
  508. $_POST["password"] = "";
  509. if (get_schema_version() >= 120) {
  510. $_SESSION["language"] = get_pref("USER_LANGUAGE", $_SESSION["uid"]);
  511. }
  512. $_SESSION["ref_schema_version"] = get_schema_version(true);
  513. $_SESSION["bw_limit"] = !!clean($_POST["bw_limit"]);
  514. if (clean($_POST["profile"])) {
  515. $profile = (int) clean($_POST["profile"]);
  516. $sth = $this->pdo->prepare("SELECT id FROM ttrss_settings_profiles
  517. WHERE id = ? AND owner_uid = ?");
  518. $sth->execute([$profile, $_SESSION['uid']]);
  519. if ($sth->fetch()) {
  520. $_SESSION["profile"] = $profile;
  521. } else {
  522. $_SESSION["profile"] = null;
  523. }
  524. }
  525. } else {
  526. // start an empty session to deliver login error message
  527. @session_start();
  528. if (!isset($_SESSION["login_error_msg"]))
  529. $_SESSION["login_error_msg"] = __("Incorrect username or password");
  530. user_error("Failed login attempt for $login from {$_SERVER['REMOTE_ADDR']}", E_USER_WARNING);
  531. }
  532. $return = clean($_REQUEST['return']);
  533. if ($_REQUEST['return'] && mb_strpos($return, SELF_URL_PATH) === 0) {
  534. header("Location: " . clean($_REQUEST['return']));
  535. } else {
  536. header("Location: " . get_self_url_prefix());
  537. }
  538. }
  539. }
  540. function subscribe() {
  541. if (SINGLE_USER_MODE) {
  542. login_sequence();
  543. }
  544. if ($_SESSION["uid"]) {
  545. $feed_url = trim(clean($_REQUEST["feed_url"]));
  546. header('Content-Type: text/html; charset=utf-8');
  547. ?>
  548. <!DOCTYPE html>
  549. <html>
  550. <head>
  551. <title>Tiny Tiny RSS</title>
  552. <?php
  553. echo stylesheet_tag("css/default.css");
  554. echo javascript_tag("lib/prototype.js");
  555. echo javascript_tag("lib/dojo/dojo.js");
  556. echo javascript_tag("lib/dojo/tt-rss-layer.js");
  557. ?>
  558. <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
  559. <link rel="shortcut icon" type="image/png" href="images/favicon.png">
  560. <link rel="icon" type="image/png" sizes="72x72" href="images/favicon-72px.png">
  561. </head>
  562. <body class='flat ttrss_utility'>
  563. <script type="text/javascript">
  564. require(['dojo/parser', "dojo/ready", 'dijit/form/Button','dijit/form/CheckBox', 'dijit/form/Form',
  565. 'dijit/form/Select','dijit/form/TextBox','dijit/form/ValidationTextBox'],function(parser, ready){
  566. ready(function() {
  567. parser.parse();
  568. });
  569. });
  570. </script>
  571. <div class="container">
  572. <h1><?php echo __("Subscribe to feed...") ?></h1>
  573. <div class='content'>
  574. <?php
  575. if (!$feed_url) {
  576. ?>
  577. <form method="post">
  578. <input type="hidden" name="op" value="subscribe">
  579. <fieldset>
  580. <label>Feed or site URL:</label>
  581. <input style="width: 300px" dojoType="dijit.form.ValidationTextBox" required="1" name="feed_url">
  582. </fieldset>
  583. <button class="alt-primary" dojoType="dijit.form.Button" type="submit">
  584. <?php echo __("Subscribe") ?>
  585. </button>
  586. <a href="index.php"><?php echo __("Return to Tiny Tiny RSS") ?></a>
  587. </form>
  588. <?php
  589. } else {
  590. $rc = Feeds::subscribe_to_feed($feed_url);
  591. $feed_urls = false;
  592. switch ($rc['code']) {
  593. case 0:
  594. print_warning(T_sprintf("Already subscribed to <b>%s</b>.", $feed_url));
  595. break;
  596. case 1:
  597. print_notice(T_sprintf("Subscribed to <b>%s</b>.", $feed_url));
  598. break;
  599. case 2:
  600. print_error(T_sprintf("Could not subscribe to <b>%s</b>.", $feed_url));
  601. break;
  602. case 3:
  603. print_error(T_sprintf("No feeds found in <b>%s</b>.", $feed_url));
  604. break;
  605. case 4:
  606. $feed_urls = $rc["feeds"];
  607. break;
  608. case 5:
  609. print_error(T_sprintf("Could not subscribe to <b>%s</b>.<br>Can't download the Feed URL.", $feed_url));
  610. break;
  611. }
  612. if ($feed_urls) {
  613. print "<form action='public.php'>";
  614. print "<input type='hidden' name='op' value='subscribe'>";
  615. print "<fieldset>";
  616. print "<label style='display : inline'>" . __("Multiple feed URLs found:") . "</label>";
  617. print "<select name='feed_url' dojoType='dijit.form.Select'>";
  618. foreach ($feed_urls as $url => $name) {
  619. $url = htmlspecialchars($url);
  620. $name = htmlspecialchars($name);
  621. print "<option value=\"$url\">$name</option>";
  622. }
  623. print "</select>";
  624. print "</fieldset>";
  625. print "<button class='alt-primary' dojoType='dijit.form.Button' type='submit'>".__("Subscribe to selected feed")."</button>";
  626. print "<a href='index.php'>".__("Return to Tiny Tiny RSS")."</a>";
  627. print "</form>";
  628. }
  629. $tp_uri = get_self_url_prefix() . "/prefs.php";
  630. if ($rc['code'] <= 2){
  631. $sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds WHERE
  632. feed_url = ? AND owner_uid = ?");
  633. $sth->execute([$feed_url, $_SESSION['uid']]);
  634. $row = $sth->fetch();
  635. $feed_id = $row["id"];
  636. } else {
  637. $feed_id = 0;
  638. }
  639. if ($feed_id) {
  640. print "<form method='GET' action=\"$tp_uri\">
  641. <input type='hidden' name='tab' value='feedConfig'>
  642. <input type='hidden' name='method' value='editfeed'>
  643. <input type='hidden' name='methodparam' value='$feed_id'>
  644. <button dojoType='dijit.form.Button' class='alt-info' type='submit'>".__("Edit subscription options")."</button>
  645. <a href='index.php'>".__("Return to Tiny Tiny RSS")."</a>
  646. </form>";
  647. }
  648. }
  649. print "</div></div></body></html>";
  650. } else {
  651. render_login_form();
  652. }
  653. }
  654. function index() {
  655. header("Content-Type: text/plain");
  656. print error_json(13);
  657. }
  658. function forgotpass() {
  659. startup_gettext();
  660. session_start();
  661. @$hash = clean($_REQUEST["hash"]);
  662. header('Content-Type: text/html; charset=utf-8');
  663. ?>
  664. <!DOCTYPE html>
  665. <html>
  666. <head>
  667. <title>Tiny Tiny RSS</title>
  668. <link rel="shortcut icon" type="image/png" href="images/favicon.png">
  669. <link rel="icon" type="image/png" sizes="72x72" href="images/favicon-72px.png">
  670. <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
  671. <?php
  672. echo stylesheet_tag("css/default.css");
  673. echo javascript_tag("lib/prototype.js");
  674. echo javascript_tag("lib/dojo/dojo.js");
  675. echo javascript_tag("lib/dojo/tt-rss-layer.js");
  676. ?>
  677. </head>
  678. <body class='flat ttrss_utility'>
  679. <div class='container'>
  680. <script type="text/javascript">
  681. require(['dojo/parser', "dojo/ready", 'dijit/form/Button','dijit/form/CheckBox', 'dijit/form/Form',
  682. 'dijit/form/Select','dijit/form/TextBox','dijit/form/ValidationTextBox'],function(parser, ready){
  683. ready(function() {
  684. parser.parse();
  685. });
  686. });
  687. </script>
  688. <?php
  689. print "<h1>".__("Password recovery")."</h1>";
  690. print "<div class='content'>";
  691. @$method = clean($_POST['method']);
  692. if ($hash) {
  693. $login = clean($_REQUEST["login"]);
  694. if ($login) {
  695. $sth = $this->pdo->prepare("SELECT id, resetpass_token FROM ttrss_users
  696. WHERE login = ?");
  697. $sth->execute([$login]);
  698. if ($row = $sth->fetch()) {
  699. $id = $row["id"];
  700. $resetpass_token_full = $row["resetpass_token"];
  701. list($timestamp, $resetpass_token) = explode(":", $resetpass_token_full);
  702. if ($timestamp && $resetpass_token &&
  703. $timestamp >= time() - 15*60*60 &&
  704. $resetpass_token == $hash) {
  705. $sth = $this->pdo->prepare("UPDATE ttrss_users SET resetpass_token = NULL
  706. WHERE id = ?");
  707. $sth->execute([$id]);
  708. Pref_Users::resetUserPassword($id, true);
  709. print "<p>"."Completed."."</p>";
  710. } else {
  711. print_error("Some of the information provided is missing or incorrect.");
  712. }
  713. } else {
  714. print_error("Some of the information provided is missing or incorrect.");
  715. }
  716. } else {
  717. print_error("Some of the information provided is missing or incorrect.");
  718. }
  719. print "<a href='index.php'>".__("Return to Tiny Tiny RSS")."</a>";
  720. } else if (!$method) {
  721. print_notice(__("You will need to provide valid account name and email. Password reset link will be sent to your email address."));
  722. print "<form method='POST' action='public.php'>
  723. <input type='hidden' name='method' value='do'>
  724. <input type='hidden' name='op' value='forgotpass'>
  725. <fieldset>
  726. <label>".__("Login:")."</label>
  727. <input dojoType='dijit.form.TextBox' type='text' name='login' value='' required>
  728. </fieldset>
  729. <fieldset>
  730. <label>".__("Email:")."</label>
  731. <input dojoType='dijit.form.TextBox' type='email' name='email' value='' required>
  732. </fieldset>";
  733. $_SESSION["pwdreset:testvalue1"] = rand(1,10);
  734. $_SESSION["pwdreset:testvalue2"] = rand(1,10);
  735. print "<fieldset>
  736. <label>".T_sprintf("How much is %d + %d:", $_SESSION["pwdreset:testvalue1"], $_SESSION["pwdreset:testvalue2"])."</label>
  737. <input dojoType='dijit.form.TextBox' type='text' name='test' value='' required>
  738. </fieldset>
  739. <hr/>
  740. <fieldset>
  741. <button dojoType='dijit.form.Button' type='submit' class='alt-danger'>".__("Reset password")."</button>
  742. <a href='index.php'>".__("Return to Tiny Tiny RSS")."</a>
  743. </fieldset>
  744. </form>";
  745. } else if ($method == 'do') {
  746. $login = clean($_POST["login"]);
  747. $email = clean($_POST["email"]);
  748. $test = clean($_POST["test"]);
  749. if ($test != ($_SESSION["pwdreset:testvalue1"] + $_SESSION["pwdreset:testvalue2"]) || !$email || !$login) {
  750. print_error(__('Some of the required form parameters are missing or incorrect.'));
  751. print "<form method='GET' action='public.php'>
  752. <input type='hidden' name='op' value='forgotpass'>
  753. <button dojoType='dijit.form.Button' type='submit' class='alt-primary'>".__("Go back")."</button>
  754. </form>";
  755. } else {
  756. // prevent submitting this form multiple times
  757. $_SESSION["pwdreset:testvalue1"] = rand(1, 1000);
  758. $_SESSION["pwdreset:testvalue2"] = rand(1, 1000);
  759. $sth = $this->pdo->prepare("SELECT id FROM ttrss_users
  760. WHERE login = ? AND email = ?");
  761. $sth->execute([$login, $email]);
  762. if ($row = $sth->fetch()) {
  763. print_notice("Password reset instructions are being sent to your email address.");
  764. $id = $row["id"];
  765. if ($id) {
  766. $resetpass_token = sha1(get_random_bytes(128));
  767. $resetpass_link = get_self_url_prefix() . "/public.php?op=forgotpass&hash=" . $resetpass_token .
  768. "&login=" . urlencode($login);
  769. require_once "lib/MiniTemplator.class.php";
  770. $tpl = new MiniTemplator;
  771. $tpl->readTemplateFromFile("templates/resetpass_link_template.txt");
  772. $tpl->setVariable('LOGIN', $login);
  773. $tpl->setVariable('RESETPASS_LINK', $resetpass_link);
  774. $tpl->addBlock('message');
  775. $message = "";
  776. $tpl->generateOutputToString($message);
  777. $mailer = new Mailer();
  778. $rc = $mailer->mail(["to_name" => $login,
  779. "to_address" => $email,
  780. "subject" => __("[tt-rss] Password reset request"),
  781. "message" => $message]);
  782. if (!$rc) print_error($mailer->error());
  783. $resetpass_token_full = time() . ":" . $resetpass_token;
  784. $sth = $this->pdo->prepare("UPDATE ttrss_users
  785. SET resetpass_token = ?
  786. WHERE login = ? AND email = ?");
  787. $sth->execute([$resetpass_token_full, $login, $email]);
  788. } else {
  789. print_error("User ID not found.");
  790. }
  791. print "<a href='index.php'>".__("Return to Tiny Tiny RSS")."</a>";
  792. } else {
  793. print_error(__("Sorry, login and email combination not found."));
  794. print "<form method='GET' action='public.php'>
  795. <input type='hidden' name='op' value='forgotpass'>
  796. <button dojoType='dijit.form.Button' type='submit'>".__("Go back")."</button>
  797. </form>";
  798. }
  799. }
  800. }
  801. print "</div>";
  802. print "</div>";
  803. print "</body>";
  804. print "</html>";
  805. }
  806. function dbupdate() {
  807. startup_gettext();
  808. if (!SINGLE_USER_MODE && $_SESSION["access_level"] < 10) {
  809. $_SESSION["login_error_msg"] = __("Your access level is insufficient to run this script.");
  810. render_login_form();
  811. exit;
  812. }
  813. ?>
  814. <!DOCTYPE html>
  815. <html>
  816. <head>
  817. <title>Database Updater</title>
  818. <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
  819. <?php echo stylesheet_tag("css/default.css") ?>
  820. <link rel="shortcut icon" type="image/png" href="images/favicon.png">
  821. <link rel="icon" type="image/png" sizes="72x72" href="images/favicon-72px.png">
  822. <?php
  823. echo stylesheet_tag("css/default.css");
  824. echo javascript_tag("lib/prototype.js");
  825. echo javascript_tag("lib/dojo/dojo.js");
  826. echo javascript_tag("lib/dojo/tt-rss-layer.js");
  827. ?>
  828. <style type="text/css">
  829. span.ok { color : #009000; font-weight : bold; }
  830. span.err { color : #ff0000; font-weight : bold; }
  831. </style>
  832. </head>
  833. <body class="flat ttrss_utility">
  834. <script type="text/javascript">
  835. require(['dojo/parser', "dojo/ready", 'dijit/form/Button','dijit/form/CheckBox', 'dijit/form/Form',
  836. 'dijit/form/Select','dijit/form/TextBox','dijit/form/ValidationTextBox'],function(parser, ready){
  837. ready(function() {
  838. parser.parse();
  839. });
  840. });
  841. function confirmOP() {
  842. return confirm("Update the database?");
  843. }
  844. </script>
  845. <div class="container">
  846. <h1><?php echo __("Database Updater") ?></h1>
  847. <div class="content">
  848. <?php
  849. @$op = clean($_REQUEST["subop"]);
  850. $updater = new DbUpdater(Db::pdo(), DB_TYPE, SCHEMA_VERSION);
  851. if ($op == "performupdate") {
  852. if ($updater->isUpdateRequired()) {
  853. print "<h2>" . T_sprintf("Performing updates to version %d", SCHEMA_VERSION) . "</h2>";
  854. for ($i = $updater->getSchemaVersion() + 1; $i <= SCHEMA_VERSION; $i++) {
  855. print "<ul>";
  856. print "<li class='text-info'>" . T_sprintf("Updating to version %d", $i) . "</li>";
  857. print "<li>";
  858. $result = $updater->performUpdateTo($i, true);
  859. print "</li>";
  860. if (!$result) {
  861. print "</ul>";
  862. print_error("One of the updates failed. Either retry the process or perform updates manually.");
  863. print "<form method='POST'>
  864. <input type='hidden' name='subop' value='performupdate'>
  865. <button type='submit' dojoType='dijit.form.Button' class='alt-danger' onclick='return confirmOP()'>".__("Try again")."</button>
  866. <a href='index.php'>".__("Return to Tiny Tiny RSS")."</a>
  867. </form>";
  868. return;
  869. } else {
  870. print "<li class='text-success'>" . __("Completed.") . "</li>";
  871. print "</ul>";
  872. }
  873. }
  874. print_notice("Your Tiny Tiny RSS database is now updated to the latest version.");
  875. print "<a href='index.php'>".__("Return to Tiny Tiny RSS")."</a>";
  876. } else {
  877. print_notice("Tiny Tiny RSS database is up to date.");
  878. print "<a href='index.php'>".__("Return to Tiny Tiny RSS")."</a>";
  879. }
  880. } else {
  881. if ($updater->isUpdateRequired()) {
  882. print "<h2>".T_sprintf("Tiny Tiny RSS database needs update to the latest version (%d to %d).",
  883. $updater->getSchemaVersion(), SCHEMA_VERSION)."</h2>";
  884. if (DB_TYPE == "mysql") {
  885. print_error("<strong>READ THIS:</strong> Due to MySQL limitations, your database is not completely protected while updating. ".
  886. "Errors may put it in an inconsistent state requiring manual rollback. <strong>BACKUP YOUR DATABASE BEFORE CONTINUING.</strong>");
  887. } else {
  888. print_warning("Please backup your database before proceeding.");
  889. }
  890. print "<form method='POST'>
  891. <input type='hidden' name='subop' value='performupdate'>
  892. <button type='submit' dojoType='dijit.form.Button' class='alt-danger' onclick='return confirmOP()'>".__("Perform updates")."</button>
  893. </form>";
  894. } else {
  895. print_notice("Tiny Tiny RSS database is up to date.");
  896. print "<a href='index.php'>".__("Return to Tiny Tiny RSS")."</a>";
  897. }
  898. }
  899. ?>
  900. </div>
  901. </div>
  902. </body>
  903. </html>
  904. <?php
  905. }
  906. function cached_url() {
  907. @$req_filename = basename($_GET['hash']);
  908. // we don't need an extension to find the file, hash is a complete URL
  909. $hash = preg_replace("/\.[^\.]*$/", "", $req_filename);
  910. if ($hash) {
  911. $filename = CACHE_DIR . '/images/' . $hash;
  912. if (file_exists($filename)) {
  913. header("Content-Disposition: inline; filename=\"$req_filename\"");
  914. send_local_file($filename);
  915. } else {
  916. header($_SERVER["SERVER_PROTOCOL"]." 404 Not Found");
  917. echo "File not found.";
  918. }
  919. }
  920. }
  921. private function make_article_tag_uri($id, $timestamp) {
  922. $timestamp = date("Y-m-d", strtotime($timestamp));
  923. return "tag:" . parse_url(get_self_url_prefix(), PHP_URL_HOST) . ",$timestamp:/$id";
  924. }
  925. // this should be used very carefully because this endpoint is exposed to unauthenticated users
  926. // plugin data is not loaded because there's no user context and owner_uid/session may or may not be available
  927. // in general, don't do anything user-related in here and do not modify $_SESSION
  928. public function pluginhandler() {
  929. $host = new PluginHost();
  930. $plugin = basename(clean($_REQUEST["plugin"]));
  931. $method = clean($_REQUEST["pmethod"]);
  932. $host->load($plugin, PluginHost::KIND_USER, 0);
  933. $host->load_data();
  934. $pclass = $host->get_plugin($plugin);
  935. if ($pclass) {
  936. if (method_exists($pclass, $method)) {
  937. if ($pclass->is_public_method($method)) {
  938. $pclass->$method();
  939. } else {
  940. header("Content-Type: text/json");
  941. print error_json(6);
  942. }
  943. } else {
  944. header("Content-Type: text/json");
  945. print error_json(13);
  946. }
  947. } else {
  948. header("Content-Type: text/json");
  949. print error_json(14);
  950. }
  951. }
  952. }
  953. ?>