public.php 37 KB

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