feeds.php 54 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809
  1. <?php
  2. class Pref_Feeds extends Handler_Protected {
  3. public static $feed_languages = array("English", "Danish", "Dutch", "Finnish", "French", "German", "Hungarian", "Italian", "Norwegian",
  4. "Portuguese", "Russian", "Spanish", "Swedish", "Turkish", "Simple");
  5. function csrf_ignore($method) {
  6. $csrf_ignored = array("index", "getfeedtree", "add", "editcats", "editfeed",
  7. "savefeedorder", "uploadicon", "feedswitherrors", "inactivefeeds",
  8. "batchsubscribe");
  9. return array_search($method, $csrf_ignored) !== false;
  10. }
  11. function batch_edit_cbox($elem, $label = false) {
  12. print "<input type=\"checkbox\" title=\"".__("Check to enable field")."\"
  13. onchange=\"dijit.byId('feedEditDlg').toggleField(this, '$elem', '$label')\">";
  14. }
  15. function renamecat() {
  16. $title = clean($_REQUEST['title']);
  17. $id = clean($_REQUEST['id']);
  18. if ($title) {
  19. $sth = $this->pdo->prepare("UPDATE ttrss_feed_categories SET
  20. title = ? WHERE id = ? AND owner_uid = ?");
  21. $sth->execute([$title, $id, $_SESSION['uid']]);
  22. }
  23. }
  24. private function get_category_items($cat_id) {
  25. if (clean($_REQUEST['mode']) != 2)
  26. $search = $_SESSION["prefs_feed_search"];
  27. else
  28. $search = "";
  29. // first one is set by API
  30. $show_empty_cats = clean($_REQUEST['force_show_empty']) ||
  31. (clean($_REQUEST['mode']) != 2 && !$search);
  32. $items = array();
  33. $sth = $this->pdo->prepare("SELECT id, title FROM ttrss_feed_categories
  34. WHERE owner_uid = ? AND parent_cat = ? ORDER BY order_id, title");
  35. $sth->execute([$_SESSION['uid'], $cat_id]);
  36. while ($line = $sth->fetch()) {
  37. $cat = array();
  38. $cat['id'] = 'CAT:' . $line['id'];
  39. $cat['bare_id'] = (int)$line['id'];
  40. $cat['name'] = $line['title'];
  41. $cat['items'] = array();
  42. $cat['checkbox'] = false;
  43. $cat['type'] = 'category';
  44. $cat['unread'] = 0;
  45. $cat['child_unread'] = 0;
  46. $cat['auxcounter'] = 0;
  47. $cat['parent_id'] = $cat_id;
  48. $cat['items'] = $this->get_category_items($line['id']);
  49. $num_children = $this->calculate_children_count($cat);
  50. $cat['param'] = vsprintf(_ngettext('(%d feed)', '(%d feeds)', (int) $num_children), $num_children);
  51. if ($num_children > 0 || $show_empty_cats)
  52. array_push($items, $cat);
  53. }
  54. $fsth = $this->pdo->prepare("SELECT id, title, last_error,
  55. ".SUBSTRING_FOR_DATE."(last_updated,1,19) AS last_updated, update_interval
  56. FROM ttrss_feeds
  57. WHERE cat_id = :cat AND
  58. owner_uid = :uid AND
  59. (:search = '' OR (LOWER(title) LIKE :search OR LOWER(feed_url) LIKE :search))
  60. ORDER BY order_id, title");
  61. $fsth->execute([":cat" => $cat_id, ":uid" => $_SESSION['uid'], ":search" => $search ? "%$search%" : ""]);
  62. while ($feed_line = $fsth->fetch()) {
  63. $feed = array();
  64. $feed['id'] = 'FEED:' . $feed_line['id'];
  65. $feed['bare_id'] = (int)$feed_line['id'];
  66. $feed['auxcounter'] = 0;
  67. $feed['name'] = $feed_line['title'];
  68. $feed['checkbox'] = false;
  69. $feed['unread'] = 0;
  70. $feed['error'] = $feed_line['last_error'];
  71. $feed['icon'] = Feeds::getFeedIcon($feed_line['id']);
  72. $feed['param'] = make_local_datetime(
  73. $feed_line['last_updated'], true);
  74. $feed['updates_disabled'] = (int)($feed_line['update_interval'] < 0);
  75. array_push($items, $feed);
  76. }
  77. return $items;
  78. }
  79. function getfeedtree() {
  80. print json_encode($this->makefeedtree());
  81. }
  82. function makefeedtree() {
  83. if (clean($_REQUEST['mode']) != 2)
  84. $search = $_SESSION["prefs_feed_search"];
  85. else
  86. $search = "";
  87. $root = array();
  88. $root['id'] = 'root';
  89. $root['name'] = __('Feeds');
  90. $root['items'] = array();
  91. $root['type'] = 'category';
  92. $enable_cats = get_pref('ENABLE_FEED_CATS');
  93. if (clean($_REQUEST['mode']) == 2) {
  94. if ($enable_cats) {
  95. $cat = $this->feedlist_init_cat(-1);
  96. } else {
  97. $cat['items'] = array();
  98. }
  99. foreach (array(-4, -3, -1, -2, 0, -6) as $i) {
  100. array_push($cat['items'], $this->feedlist_init_feed($i));
  101. }
  102. /* Plugin feeds for -1 */
  103. $feeds = PluginHost::getInstance()->get_feeds(-1);
  104. if ($feeds) {
  105. foreach ($feeds as $feed) {
  106. $feed_id = PluginHost::pfeed_to_feed_id($feed['id']);
  107. $item = array();
  108. $item['id'] = 'FEED:' . $feed_id;
  109. $item['bare_id'] = (int)$feed_id;
  110. $item['auxcounter'] = 0;
  111. $item['name'] = $feed['title'];
  112. $item['checkbox'] = false;
  113. $item['error'] = '';
  114. $item['icon'] = $feed['icon'];
  115. $item['param'] = '';
  116. $item['unread'] = 0; //$feed['sender']->get_unread($feed['id']);
  117. $item['type'] = 'feed';
  118. array_push($cat['items'], $item);
  119. }
  120. }
  121. if ($enable_cats) {
  122. array_push($root['items'], $cat);
  123. } else {
  124. $root['items'] = array_merge($root['items'], $cat['items']);
  125. }
  126. $sth = $this->pdo->prepare("SELECT * FROM
  127. ttrss_labels2 WHERE owner_uid = ? ORDER by caption");
  128. $sth->execute([$_SESSION['uid']]);
  129. if (get_pref('ENABLE_FEED_CATS')) {
  130. $cat = $this->feedlist_init_cat(-2);
  131. } else {
  132. $cat['items'] = array();
  133. }
  134. $num_labels = 0;
  135. while ($line = $sth->fetch()) {
  136. ++$num_labels;
  137. $label_id = Labels::label_to_feed_id($line['id']);
  138. $feed = $this->feedlist_init_feed($label_id, false, 0);
  139. $feed['fg_color'] = $line['fg_color'];
  140. $feed['bg_color'] = $line['bg_color'];
  141. array_push($cat['items'], $feed);
  142. }
  143. if ($num_labels) {
  144. if ($enable_cats) {
  145. array_push($root['items'], $cat);
  146. } else {
  147. $root['items'] = array_merge($root['items'], $cat['items']);
  148. }
  149. }
  150. }
  151. if ($enable_cats) {
  152. $show_empty_cats = clean($_REQUEST['force_show_empty']) ||
  153. (clean($_REQUEST['mode']) != 2 && !$search);
  154. $sth = $this->pdo->prepare("SELECT id, title FROM ttrss_feed_categories
  155. WHERE owner_uid = ? AND parent_cat IS NULL ORDER BY order_id, title");
  156. $sth->execute([$_SESSION['uid']]);
  157. while ($line = $sth->fetch()) {
  158. $cat = array();
  159. $cat['id'] = 'CAT:' . $line['id'];
  160. $cat['bare_id'] = (int)$line['id'];
  161. $cat['auxcounter'] = 0;
  162. $cat['name'] = $line['title'];
  163. $cat['items'] = array();
  164. $cat['checkbox'] = false;
  165. $cat['type'] = 'category';
  166. $cat['unread'] = 0;
  167. $cat['child_unread'] = 0;
  168. $cat['items'] = $this->get_category_items($line['id']);
  169. $num_children = $this->calculate_children_count($cat);
  170. $cat['param'] = vsprintf(_ngettext('(%d feed)', '(%d feeds)', (int) $num_children), $num_children);
  171. if ($num_children > 0 || $show_empty_cats)
  172. array_push($root['items'], $cat);
  173. $root['param'] += count($cat['items']);
  174. }
  175. /* Uncategorized is a special case */
  176. $cat = array();
  177. $cat['id'] = 'CAT:0';
  178. $cat['bare_id'] = 0;
  179. $cat['auxcounter'] = 0;
  180. $cat['name'] = __("Uncategorized");
  181. $cat['items'] = array();
  182. $cat['type'] = 'category';
  183. $cat['checkbox'] = false;
  184. $cat['unread'] = 0;
  185. $cat['child_unread'] = 0;
  186. $fsth = $this->pdo->prepare("SELECT id, title,last_error,
  187. ".SUBSTRING_FOR_DATE."(last_updated,1,19) AS last_updated, update_interval
  188. FROM ttrss_feeds
  189. WHERE cat_id IS NULL AND
  190. owner_uid = :uid AND
  191. (:search = '' OR (LOWER(title) LIKE :search OR LOWER(feed_url) LIKE :search))
  192. ORDER BY order_id, title");
  193. $fsth->execute([":uid" => $_SESSION['uid'], ":search" => $search ? "%$search%" : ""]);
  194. while ($feed_line = $fsth->fetch()) {
  195. $feed = array();
  196. $feed['id'] = 'FEED:' . $feed_line['id'];
  197. $feed['bare_id'] = (int)$feed_line['id'];
  198. $feed['auxcounter'] = 0;
  199. $feed['name'] = $feed_line['title'];
  200. $feed['checkbox'] = false;
  201. $feed['error'] = $feed_line['last_error'];
  202. $feed['icon'] = Feeds::getFeedIcon($feed_line['id']);
  203. $feed['param'] = make_local_datetime(
  204. $feed_line['last_updated'], true);
  205. $feed['unread'] = 0;
  206. $feed['type'] = 'feed';
  207. $feed['updates_disabled'] = (int)($feed_line['update_interval'] < 0);
  208. array_push($cat['items'], $feed);
  209. }
  210. $cat['param'] = vsprintf(_ngettext('(%d feed)', '(%d feeds)', count($cat['items'])), count($cat['items']));
  211. if (count($cat['items']) > 0 || $show_empty_cats)
  212. array_push($root['items'], $cat);
  213. $num_children = $this->calculate_children_count($root);
  214. $root['param'] = vsprintf(_ngettext('(%d feed)', '(%d feeds)', (int) $num_children), $num_children);
  215. } else {
  216. $fsth = $this->pdo->prepare("SELECT id, title, last_error,
  217. ".SUBSTRING_FOR_DATE."(last_updated,1,19) AS last_updated, update_interval
  218. FROM ttrss_feeds
  219. WHERE owner_uid = :uid AND
  220. (:search = '' OR (LOWER(title) LIKE :search OR LOWER(feed_url) LIKE :search))
  221. ORDER BY order_id, title");
  222. $fsth->execute([":uid" => $_SESSION['uid'], ":search" => $search ? "%$search%" : ""]);
  223. while ($feed_line = $fsth->fetch()) {
  224. $feed = array();
  225. $feed['id'] = 'FEED:' . $feed_line['id'];
  226. $feed['bare_id'] = (int)$feed_line['id'];
  227. $feed['auxcounter'] = 0;
  228. $feed['name'] = $feed_line['title'];
  229. $feed['checkbox'] = false;
  230. $feed['error'] = $feed_line['last_error'];
  231. $feed['icon'] = Feeds::getFeedIcon($feed_line['id']);
  232. $feed['param'] = make_local_datetime(
  233. $feed_line['last_updated'], true);
  234. $feed['unread'] = 0;
  235. $feed['type'] = 'feed';
  236. $feed['updates_disabled'] = (int)($feed_line['update_interval'] < 0);
  237. array_push($root['items'], $feed);
  238. }
  239. $root['param'] = vsprintf(_ngettext('(%d feed)', '(%d feeds)', count($cat['items'])), count($cat['items']));
  240. }
  241. $fl = array();
  242. $fl['identifier'] = 'id';
  243. $fl['label'] = 'name';
  244. if (clean($_REQUEST['mode']) != 2) {
  245. $fl['items'] = array($root);
  246. } else {
  247. $fl['items'] = $root['items'];
  248. }
  249. return $fl;
  250. }
  251. function catsortreset() {
  252. $sth = $this->pdo->prepare("UPDATE ttrss_feed_categories
  253. SET order_id = 0 WHERE owner_uid = ?");
  254. $sth->execute([$_SESSION['uid']]);
  255. }
  256. function feedsortreset() {
  257. $sth = $this->pdo->prepare("UPDATE ttrss_feeds
  258. SET order_id = 0 WHERE owner_uid = ?");
  259. $sth->execute([$_SESSION['uid']]);
  260. }
  261. private function process_category_order(&$data_map, $item_id, $parent_id = false, $nest_level = 0) {
  262. $debug = isset($_REQUEST["debug"]);
  263. $prefix = "";
  264. for ($i = 0; $i < $nest_level; $i++)
  265. $prefix .= " ";
  266. if ($debug) _debug("$prefix C: $item_id P: $parent_id");
  267. $bare_item_id = substr($item_id, strpos($item_id, ':')+1);
  268. if ($item_id != 'root') {
  269. if ($parent_id && $parent_id != 'root') {
  270. $parent_bare_id = substr($parent_id, strpos($parent_id, ':')+1);
  271. $parent_qpart = $parent_bare_id;
  272. } else {
  273. $parent_qpart = null;
  274. }
  275. $sth = $this->pdo->prepare("UPDATE ttrss_feed_categories
  276. SET parent_cat = ? WHERE id = ? AND
  277. owner_uid = ?");
  278. $sth->execute([$parent_qpart, $bare_item_id, $_SESSION['uid']]);
  279. }
  280. $order_id = 1;
  281. $cat = $data_map[$item_id];
  282. if ($cat && is_array($cat)) {
  283. foreach ($cat as $item) {
  284. $id = $item['_reference'];
  285. $bare_id = substr($id, strpos($id, ':')+1);
  286. if ($debug) _debug("$prefix [$order_id] $id/$bare_id");
  287. if ($item['_reference']) {
  288. if (strpos($id, "FEED") === 0) {
  289. $cat_id = ($item_id != "root") ? $bare_item_id : null;
  290. $sth = $this->pdo->prepare("UPDATE ttrss_feeds
  291. SET order_id = ?, cat_id = ?
  292. WHERE id = ? AND owner_uid = ?");
  293. $sth->execute([$order_id, $cat_id ? $cat_id : null, $bare_id, $_SESSION['uid']]);
  294. } else if (strpos($id, "CAT:") === 0) {
  295. $this->process_category_order($data_map, $item['_reference'], $item_id,
  296. $nest_level+1);
  297. $sth = $this->pdo->prepare("UPDATE ttrss_feed_categories
  298. SET order_id = ? WHERE id = ? AND
  299. owner_uid = ?");
  300. $sth->execute([$order_id, $bare_id, $_SESSION['uid']]);
  301. }
  302. }
  303. ++$order_id;
  304. }
  305. }
  306. }
  307. function savefeedorder() {
  308. $data = json_decode($_POST['payload'], true);
  309. #file_put_contents("/tmp/saveorder.json", clean($_POST['payload']));
  310. #$data = json_decode(file_get_contents("/tmp/saveorder.json"), true);
  311. if (!is_array($data['items']))
  312. $data['items'] = json_decode($data['items'], true);
  313. # print_r($data['items']);
  314. if (is_array($data) && is_array($data['items'])) {
  315. # $cat_order_id = 0;
  316. $data_map = array();
  317. $root_item = false;
  318. foreach ($data['items'] as $item) {
  319. # if ($item['id'] != 'root') {
  320. if (is_array($item['items'])) {
  321. if (isset($item['items']['_reference'])) {
  322. $data_map[$item['id']] = array($item['items']);
  323. } else {
  324. $data_map[$item['id']] = $item['items'];
  325. }
  326. }
  327. if ($item['id'] == 'root') {
  328. $root_item = $item['id'];
  329. }
  330. }
  331. $this->process_category_order($data_map, $root_item);
  332. }
  333. }
  334. function removeicon() {
  335. $feed_id = clean($_REQUEST["feed_id"]);
  336. $sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds
  337. WHERE id = ? AND owner_uid = ?");
  338. $sth->execute([$feed_id, $_SESSION['uid']]);
  339. if ($row = $sth->fetch()) {
  340. @unlink(ICONS_DIR . "/$feed_id.ico");
  341. $sth = $this->pdo->prepare("UPDATE ttrss_feeds SET favicon_avg_color = NULL
  342. where id = ?");
  343. $sth->execute([$feed_id]);
  344. }
  345. }
  346. function uploadicon() {
  347. header("Content-type: text/html");
  348. if (is_uploaded_file($_FILES['icon_file']['tmp_name'])) {
  349. $tmp_file = tempnam(CACHE_DIR . '/upload', 'icon');
  350. $result = move_uploaded_file($_FILES['icon_file']['tmp_name'],
  351. $tmp_file);
  352. if (!$result) {
  353. return;
  354. }
  355. } else {
  356. return;
  357. }
  358. $icon_file = $tmp_file;
  359. $feed_id = clean($_REQUEST["feed_id"]);
  360. if (is_file($icon_file) && $feed_id) {
  361. if (filesize($icon_file) < 65535) {
  362. $sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds
  363. WHERE id = ? AND owner_uid = ?");
  364. $sth->execute([$feed_id, $_SESSION['uid']]);
  365. if ($row = $sth->fetch()) {
  366. @unlink(ICONS_DIR . "/$feed_id.ico");
  367. if (rename($icon_file, ICONS_DIR . "/$feed_id.ico")) {
  368. $sth = $this->pdo->prepare("UPDATE ttrss_feeds SET
  369. favicon_avg_color = ''
  370. WHERE id = ?");
  371. $sth->execute([$feed_id]);
  372. $rc = 0;
  373. }
  374. } else {
  375. $rc = 2;
  376. }
  377. } else {
  378. $rc = 1;
  379. }
  380. } else {
  381. $rc = 2;
  382. }
  383. @unlink($icon_file);
  384. print "<script type=\"text/javascript\">";
  385. print "parent.uploadIconHandler($rc);";
  386. print "</script>";
  387. return;
  388. }
  389. function editfeed() {
  390. global $purge_intervals;
  391. global $update_intervals;
  392. $feed_id = clean($_REQUEST["id"]);
  393. $sth = $this->pdo->prepare("SELECT * FROM ttrss_feeds WHERE id = ? AND
  394. owner_uid = ?");
  395. $sth->execute([$feed_id, $_SESSION['uid']]);
  396. if ($row = $sth->fetch()) {
  397. print '<div dojoType="dijit.layout.TabContainer" style="height : 450px">
  398. <div dojoType="dijit.layout.ContentPane" title="'.__('General').'">';
  399. $auth_pass_encrypted = $row["auth_pass_encrypted"];
  400. $title = htmlspecialchars($row["title"]);
  401. print_hidden("id", "$feed_id");
  402. print_hidden("op", "pref-feeds");
  403. print_hidden("method", "editSave");
  404. print "<div class=\"dlgSec\">".__("Feed")."</div>";
  405. print "<div class=\"dlgSecCont\">";
  406. /* Title */
  407. print "<input dojoType=\"dijit.form.ValidationTextBox\" required=\"1\"
  408. placeHolder=\"".__("Feed Title")."\"
  409. style=\"font-size : 16px; width: 20em\" name=\"title\" value=\"$title\">";
  410. /* Feed URL */
  411. $feed_url = htmlspecialchars($row["feed_url"]);
  412. print "<hr/>";
  413. print __('URL:') . " ";
  414. print "<input dojoType=\"dijit.form.ValidationTextBox\" required=\"1\"
  415. placeHolder=\"".__("Feed URL")."\"
  416. regExp='^(http|https)://.*' style=\"width : 20em\"
  417. name=\"feed_url\" value=\"$feed_url\">";
  418. $last_error = $row["last_error"];
  419. if ($last_error) {
  420. print "&nbsp;<img src=\"images/error.png\" alt=\"(error)\"
  421. style=\"vertical-align : middle\"
  422. title=\"".htmlspecialchars($last_error)."\">";
  423. }
  424. /* Category */
  425. if (get_pref('ENABLE_FEED_CATS')) {
  426. $cat_id = $row["cat_id"];
  427. print "<hr/>";
  428. print __('Place in category:') . " ";
  429. print_feed_cat_select("cat_id", $cat_id,
  430. 'dojoType="dijit.form.Select"');
  431. }
  432. /* Site URL */
  433. $site_url = htmlspecialchars($row["site_url"]);
  434. print "<hr/>";
  435. print __('Site URL:') . " ";
  436. print "<input dojoType=\"dijit.form.ValidationTextBox\" required=\"1\"
  437. placeHolder=\"".__("Site URL")."\"
  438. regExp='^(http|https)://.*' style=\"width : 15em\"
  439. name=\"site_url\" value=\"$site_url\">";
  440. /* FTS Stemming Language */
  441. if (DB_TYPE == "pgsql") {
  442. $feed_language = $row["feed_language"];
  443. print "<hr/>";
  444. print __('Language:') . " ";
  445. print_select("feed_language", $feed_language, $this::$feed_languages,
  446. 'dojoType="dijit.form.Select"');
  447. }
  448. print "</div>";
  449. print "<div class=\"dlgSec\">".__("Update")."</div>";
  450. print "<div class=\"dlgSecCont\">";
  451. /* Update Interval */
  452. $update_interval = $row["update_interval"];
  453. print_select_hash("update_interval", $update_interval, $update_intervals,
  454. 'dojoType="dijit.form.Select"');
  455. /* Purge intl */
  456. $purge_interval = $row["purge_interval"];
  457. print "<hr/>";
  458. print __('Article purging:') . " ";
  459. print_select_hash("purge_interval", $purge_interval, $purge_intervals,
  460. 'dojoType="dijit.form.Select" ' .
  461. ((FORCE_ARTICLE_PURGE == 0) ? "" : 'disabled="1"'));
  462. print "</div>";
  463. $auth_login = htmlspecialchars($row["auth_login"]);
  464. $auth_pass = $row["auth_pass"];
  465. if ($auth_pass_encrypted && function_exists("mcrypt_decrypt")) {
  466. require_once "crypt.php";
  467. $auth_pass = decrypt_string($auth_pass);
  468. }
  469. $auth_pass = htmlspecialchars($auth_pass);
  470. $auth_enabled = $auth_login !== '' || $auth_pass !== '';
  471. $auth_style = $auth_enabled ? '' : 'display: none';
  472. print "<div id='feedEditDlg_loginContainer' style='$auth_style'>";
  473. print "<div class=\"dlgSec\">".__("Authentication")."</div>";
  474. print "<div class=\"dlgSecCont\">";
  475. print "<input dojoType=\"dijit.form.TextBox\" id=\"feedEditDlg_login\"
  476. placeHolder=\"".__("Login")."\"
  477. autocomplete=\"new-password\"
  478. name=\"auth_login\" value=\"$auth_login\"><hr/>";
  479. print "<input dojoType=\"dijit.form.TextBox\" type=\"password\" name=\"auth_pass\"
  480. autocomplete=\"new-password\"
  481. placeHolder=\"".__("Password")."\"
  482. value=\"$auth_pass\">";
  483. print "<div dojoType=\"dijit.Tooltip\" connectId=\"feedEditDlg_login\" position=\"below\">
  484. ".__('<b>Hint:</b> you need to fill in your login information if your feed requires authentication, except for Twitter feeds.')."
  485. </div>";
  486. print "</div></div>";
  487. $auth_checked = $auth_enabled ? 'checked' : '';
  488. print "<div style=\"clear : both\">
  489. <input type=\"checkbox\" $auth_checked name=\"need_auth\" dojoType=\"dijit.form.CheckBox\" id=\"feedEditDlg_loginCheck\"
  490. onclick='checkboxToggleElement(this, \"feedEditDlg_loginContainer\")'>
  491. <label for=\"feedEditDlg_loginCheck\">".
  492. __('This feed requires authentication.')."</div>";
  493. print '</div><div dojoType="dijit.layout.ContentPane" title="'.__('Options').'">';
  494. //print "<div class=\"dlgSec\">".__("Options")."</div>";
  495. print "<div class=\"dlgSecSimple\">";
  496. $private = $row["private"];
  497. if ($private) {
  498. $checked = "checked=\"1\"";
  499. } else {
  500. $checked = "";
  501. }
  502. print "<input dojoType=\"dijit.form.CheckBox\" type=\"checkbox\" name=\"private\" id=\"private\"
  503. $checked>&nbsp;<label for=\"private\">".__('Hide from Popular feeds')."</label>";
  504. $include_in_digest = $row["include_in_digest"];
  505. if ($include_in_digest) {
  506. $checked = "checked=\"1\"";
  507. } else {
  508. $checked = "";
  509. }
  510. print "<hr/><input dojoType=\"dijit.form.CheckBox\" type=\"checkbox\" id=\"include_in_digest\"
  511. name=\"include_in_digest\"
  512. $checked>&nbsp;<label for=\"include_in_digest\">".__('Include in e-mail digest')."</label>";
  513. $always_display_enclosures = $row["always_display_enclosures"];
  514. if ($always_display_enclosures) {
  515. $checked = "checked";
  516. } else {
  517. $checked = "";
  518. }
  519. print "<hr/><input dojoType=\"dijit.form.CheckBox\" type=\"checkbox\" id=\"always_display_enclosures\"
  520. name=\"always_display_enclosures\"
  521. $checked>&nbsp;<label for=\"always_display_enclosures\">".__('Always display image attachments')."</label>";
  522. $hide_images = $row["hide_images"];
  523. if ($hide_images) {
  524. $checked = "checked=\"1\"";
  525. } else {
  526. $checked = "";
  527. }
  528. print "<hr/><input dojoType=\"dijit.form.CheckBox\" type=\"checkbox\" id=\"hide_images\"
  529. name=\"hide_images\"
  530. $checked>&nbsp;<label for=\"hide_images\">".
  531. __('Do not embed images')."</label>";
  532. $cache_images = $row["cache_images"];
  533. if ($cache_images) {
  534. $checked = "checked=\"1\"";
  535. } else {
  536. $checked = "";
  537. }
  538. print "<hr/><input dojoType=\"dijit.form.CheckBox\" type=\"checkbox\" id=\"cache_images\"
  539. name=\"cache_images\"
  540. $checked>&nbsp;<label for=\"cache_images\">".
  541. __('Cache media')."</label>";
  542. $mark_unread_on_update = $row["mark_unread_on_update"];
  543. if ($mark_unread_on_update) {
  544. $checked = "checked";
  545. } else {
  546. $checked = "";
  547. }
  548. print "<hr/><input dojoType=\"dijit.form.CheckBox\" type=\"checkbox\" id=\"mark_unread_on_update\"
  549. name=\"mark_unread_on_update\"
  550. $checked>&nbsp;<label for=\"mark_unread_on_update\">".__('Mark updated articles as unread')."</label>";
  551. print "</div>";
  552. print '</div><div dojoType="dijit.layout.ContentPane" title="'.__('Icon').'">';
  553. /* Icon */
  554. print "<div class=\"dlgSecSimple\">";
  555. print "<img class=\"feedIcon\" src=\"".Feeds::getFeedIcon($feed_id)."\">";
  556. print "<iframe name=\"icon_upload_iframe\"
  557. style=\"width: 400px; height: 100px; display: none;\"></iframe>";
  558. print "<form style='display : block' target=\"icon_upload_iframe\"
  559. enctype=\"multipart/form-data\" method=\"POST\"
  560. action=\"backend.php\">
  561. <label class=\"dijitButton\">".__("Choose file...")."
  562. <input style=\"display: none\" id=\"icon_file\" size=\"10\" name=\"icon_file\" type=\"file\">
  563. </label>
  564. <input type=\"hidden\" name=\"op\" value=\"pref-feeds\">
  565. <input type=\"hidden\" name=\"feed_id\" value=\"$feed_id\">
  566. <input type=\"hidden\" name=\"method\" value=\"uploadicon\">
  567. <button class=\"\" dojoType=\"dijit.form.Button\" onclick=\"return uploadFeedIcon();\"
  568. type=\"submit\">".__('Replace')."</button>
  569. <button class=\"btn-danger\" dojoType=\"dijit.form.Button\" onclick=\"return removeFeedIcon($feed_id);\"
  570. type=\"submit\">".__('Remove')."</button>
  571. </form>";
  572. print "</div>";
  573. print '</div><div dojoType="dijit.layout.ContentPane" title="'.__('Plugins').'">';
  574. PluginHost::getInstance()->run_hooks(PluginHost::HOOK_PREFS_EDIT_FEED,
  575. "hook_prefs_edit_feed", $feed_id);
  576. print "</div></div>";
  577. $title = htmlspecialchars($title, ENT_QUOTES);
  578. print "<div class='dlgButtons'>
  579. <div style=\"float : left\">
  580. <button class=\"btn-danger\" dojoType=\"dijit.form.Button\" onclick='return unsubscribeFeed($feed_id, \"$title\")'>".
  581. __('Unsubscribe')."</button>";
  582. print "</div>";
  583. print "<button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('feedEditDlg').execute()\">".__('Save')."</button>
  584. <button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('feedEditDlg').hide()\">".__('Cancel')."</button>
  585. </div>";
  586. }
  587. }
  588. function editfeeds() {
  589. global $purge_intervals;
  590. global $update_intervals;
  591. $feed_ids = clean($_REQUEST["ids"]);
  592. print_notice("Enable the options you wish to apply using checkboxes on the right:");
  593. print "<p>";
  594. print_hidden("ids", "$feed_ids");
  595. print_hidden("op", "pref-feeds");
  596. print_hidden("method", "batchEditSave");
  597. print "<div class=\"dlgSec\">".__("Feed")."</div>";
  598. print "<div class=\"dlgSecCont\">";
  599. /* Category */
  600. if (get_pref('ENABLE_FEED_CATS')) {
  601. print __('Place in category:') . " ";
  602. print_feed_cat_select("cat_id", false,
  603. 'disabled="1" dojoType="dijit.form.Select"');
  604. $this->batch_edit_cbox("cat_id");
  605. }
  606. /* FTS Stemming Language */
  607. if (DB_TYPE == "pgsql") {
  608. print "<hr/>";
  609. print __('Language:') . " ";
  610. print_select("feed_language", "", $this::$feed_languages,
  611. 'disabled="1" dojoType="dijit.form.Select"');
  612. $this->batch_edit_cbox("feed_language");
  613. }
  614. print "</div>";
  615. print "<div class=\"dlgSec\">".__("Update")."</div>";
  616. print "<div class=\"dlgSecCont\">";
  617. /* Update Interval */
  618. print_select_hash("update_interval", "", $update_intervals,
  619. 'disabled="1" dojoType="dijit.form.Select"');
  620. $this->batch_edit_cbox("update_interval");
  621. /* Purge intl */
  622. if (FORCE_ARTICLE_PURGE == 0) {
  623. print "<br/>";
  624. print __('Article purging:') . " ";
  625. print_select_hash("purge_interval", "", $purge_intervals,
  626. 'disabled="1" dojoType="dijit.form.Select"');
  627. $this->batch_edit_cbox("purge_interval");
  628. }
  629. print "</div>";
  630. print "<div class=\"dlgSec\">".__("Authentication")."</div>";
  631. print "<div class=\"dlgSecCont\">";
  632. print "<input dojoType=\"dijit.form.TextBox\"
  633. placeHolder=\"".__("Login")."\" disabled=\"1\"
  634. autocomplete=\"new-password\"
  635. name=\"auth_login\" value=\"\">";
  636. $this->batch_edit_cbox("auth_login");
  637. print "<hr/> <input dojoType=\"dijit.form.TextBox\" type=\"password\" name=\"auth_pass\"
  638. autocomplete=\"new-password\"
  639. placeHolder=\"".__("Password")."\" disabled=\"1\"
  640. value=\"\">";
  641. $this->batch_edit_cbox("auth_pass");
  642. print "</div>";
  643. print "<div class=\"dlgSec\">".__("Options")."</div>";
  644. print "<div class=\"dlgSecCont\">";
  645. print "<input disabled=\"1\" type=\"checkbox\" name=\"private\" id=\"private\"
  646. dojoType=\"dijit.form.CheckBox\">&nbsp;<label id=\"private_l\" class='insensitive' for=\"private\">".__('Hide from Popular feeds')."</label>";
  647. print "&nbsp;"; $this->batch_edit_cbox("private", "private_l");
  648. print "<br/><input disabled=\"1\" type=\"checkbox\" id=\"include_in_digest\"
  649. name=\"include_in_digest\"
  650. dojoType=\"dijit.form.CheckBox\">&nbsp;<label id=\"include_in_digest_l\" class='insensitive' for=\"include_in_digest\">".__('Include in e-mail digest')."</label>";
  651. print "&nbsp;"; $this->batch_edit_cbox("include_in_digest", "include_in_digest_l");
  652. print "<br/><input disabled=\"1\" type=\"checkbox\" id=\"always_display_enclosures\"
  653. name=\"always_display_enclosures\"
  654. dojoType=\"dijit.form.CheckBox\">&nbsp;<label id=\"always_display_enclosures_l\" class='insensitive' for=\"always_display_enclosures\">".__('Always display image attachments')."</label>";
  655. print "&nbsp;"; $this->batch_edit_cbox("always_display_enclosures", "always_display_enclosures_l");
  656. print "<br/><input disabled=\"1\" type=\"checkbox\" id=\"hide_images\"
  657. name=\"hide_images\"
  658. dojoType=\"dijit.form.CheckBox\">&nbsp;<label class='insensitive' id=\"hide_images_l\"
  659. for=\"hide_images\">".
  660. __('Do not embed images')."</label>";
  661. print "&nbsp;"; $this->batch_edit_cbox("hide_images", "hide_images_l");
  662. print "<br/><input disabled=\"1\" type=\"checkbox\" id=\"cache_images\"
  663. name=\"cache_images\"
  664. dojoType=\"dijit.form.CheckBox\">&nbsp;<label class='insensitive' id=\"cache_images_l\"
  665. for=\"cache_images\">".
  666. __('Cache media')."</label>";
  667. print "&nbsp;"; $this->batch_edit_cbox("cache_images", "cache_images_l");
  668. print "<br/><input disabled=\"1\" type=\"checkbox\" id=\"mark_unread_on_update\"
  669. name=\"mark_unread_on_update\"
  670. dojoType=\"dijit.form.CheckBox\">&nbsp;<label id=\"mark_unread_on_update_l\" class='insensitive' for=\"mark_unread_on_update\">".__('Mark updated articles as unread')."</label>";
  671. print "&nbsp;"; $this->batch_edit_cbox("mark_unread_on_update", "mark_unread_on_update_l");
  672. print "</div>";
  673. print "<div class='dlgButtons'>
  674. <button dojoType=\"dijit.form.Button\"
  675. onclick=\"return dijit.byId('feedEditDlg').execute()\">".
  676. __('Save')."</button>
  677. <button dojoType=\"dijit.form.Button\"
  678. onclick=\"return dijit.byId('feedEditDlg').hide()\">".
  679. __('Cancel')."</button>
  680. </div>";
  681. return;
  682. }
  683. function batchEditSave() {
  684. return $this->editsaveops(true);
  685. }
  686. function editSave() {
  687. return $this->editsaveops(false);
  688. }
  689. function editsaveops($batch) {
  690. $feed_title = trim(clean($_POST["title"]));
  691. $feed_url = trim(clean($_POST["feed_url"]));
  692. $site_url = trim(clean($_POST["site_url"]));
  693. $upd_intl = (int) clean($_POST["update_interval"]);
  694. $purge_intl = (int) clean($_POST["purge_interval"]);
  695. $feed_id = (int) clean($_POST["id"]); /* editSave */
  696. $feed_ids = explode(",", clean($_POST["ids"])); /* batchEditSave */
  697. $cat_id = (int) clean($_POST["cat_id"]);
  698. $auth_login = trim(clean($_POST["auth_login"]));
  699. $auth_pass = trim(clean($_POST["auth_pass"]));
  700. $private = checkbox_to_sql_bool(clean($_POST["private"]));
  701. $include_in_digest = checkbox_to_sql_bool(
  702. clean($_POST["include_in_digest"]));
  703. $cache_images = checkbox_to_sql_bool(
  704. clean($_POST["cache_images"]));
  705. $hide_images = checkbox_to_sql_bool(
  706. clean($_POST["hide_images"]));
  707. $always_display_enclosures = checkbox_to_sql_bool(
  708. clean($_POST["always_display_enclosures"]));
  709. $mark_unread_on_update = checkbox_to_sql_bool(
  710. clean($_POST["mark_unread_on_update"]));
  711. $feed_language = trim(clean($_POST["feed_language"]));
  712. if (!$batch) {
  713. if (clean($_POST["need_auth"]) !== 'on') {
  714. $auth_login = '';
  715. $auth_pass = '';
  716. }
  717. /* $sth = $this->pdo->prepare("SELECT feed_url FROM ttrss_feeds WHERE id = ?");
  718. $sth->execute([$feed_id]);
  719. $row = $sth->fetch();$orig_feed_url = $row["feed_url"];
  720. $reset_basic_info = $orig_feed_url != $feed_url; */
  721. $sth = $this->pdo->prepare("UPDATE ttrss_feeds SET
  722. cat_id = :cat_id,
  723. title = :title,
  724. feed_url = :feed_url,
  725. site_url = :site_url,
  726. update_interval = :upd_intl,
  727. purge_interval = :purge_intl,
  728. auth_login = :auth_login,
  729. auth_pass = :auth_pass,
  730. auth_pass_encrypted = false,
  731. private = :private,
  732. cache_images = :cache_images,
  733. hide_images = :hide_images,
  734. include_in_digest = :include_in_digest,
  735. always_display_enclosures = :always_display_enclosures,
  736. mark_unread_on_update = :mark_unread_on_update,
  737. feed_language = :feed_language
  738. WHERE id = :id AND owner_uid = :uid");
  739. $sth->execute([":title" => $feed_title,
  740. ":cat_id" => $cat_id ? $cat_id : null,
  741. ":feed_url" => $feed_url,
  742. ":site_url" => $site_url,
  743. ":upd_intl" => $upd_intl,
  744. ":purge_intl" => $purge_intl,
  745. ":auth_login" => $auth_login,
  746. ":auth_pass" => $auth_pass,
  747. ":private" => (int)$private,
  748. ":cache_images" => (int)$cache_images,
  749. ":hide_images" => (int)$hide_images,
  750. ":include_in_digest" => (int)$include_in_digest,
  751. ":always_display_enclosures" => (int)$always_display_enclosures,
  752. ":mark_unread_on_update" => (int)$mark_unread_on_update,
  753. ":feed_language" => $feed_language,
  754. ":id" => $feed_id,
  755. ":uid" => $_SESSION['uid']]);
  756. /* if ($reset_basic_info) {
  757. RSSUtils::set_basic_feed_info($feed_id);
  758. } */
  759. PluginHost::getInstance()->run_hooks(PluginHost::HOOK_PREFS_SAVE_FEED,
  760. "hook_prefs_save_feed", $feed_id);
  761. } else {
  762. $feed_data = array();
  763. foreach (array_keys($_POST) as $k) {
  764. if ($k != "op" && $k != "method" && $k != "ids") {
  765. $feed_data[$k] = clean($_POST[$k]);
  766. }
  767. }
  768. $this->pdo->beginTransaction();
  769. $feed_ids_qmarks = arr_qmarks($feed_ids);
  770. foreach (array_keys($feed_data) as $k) {
  771. $qpart = "";
  772. switch ($k) {
  773. case "title":
  774. $qpart = "title = " . $this->pdo->quote($feed_title);
  775. break;
  776. case "feed_url":
  777. $qpart = "feed_url = " . $this->pdo->quote($feed_url);
  778. break;
  779. case "update_interval":
  780. $qpart = "update_interval = " . $this->pdo->quote($upd_intl);
  781. break;
  782. case "purge_interval":
  783. $qpart = "purge_interval =" . $this->pdo->quote($purge_intl);
  784. break;
  785. case "auth_login":
  786. $qpart = "auth_login = " . $this->pdo->quote($auth_login);
  787. break;
  788. case "auth_pass":
  789. $qpart = "auth_pass =" . $this->pdo->quote($auth_pass). ", auth_pass_encrypted = false";
  790. break;
  791. case "private":
  792. $qpart = "private = " . $this->pdo->quote($private);
  793. break;
  794. case "include_in_digest":
  795. $qpart = "include_in_digest = " . $this->pdo->quote($include_in_digest);
  796. break;
  797. case "always_display_enclosures":
  798. $qpart = "always_display_enclosures = " . $this->pdo->quote($always_display_enclosures);
  799. break;
  800. case "mark_unread_on_update":
  801. $qpart = "mark_unread_on_update = " . $this->pdo->quote($mark_unread_on_update);
  802. break;
  803. case "cache_images":
  804. $qpart = "cache_images = " . $this->pdo->quote($cache_images);
  805. break;
  806. case "hide_images":
  807. $qpart = "hide_images = " . $this->pdo->quote($hide_images);
  808. break;
  809. case "cat_id":
  810. if (get_pref('ENABLE_FEED_CATS')) {
  811. if ($cat_id) {
  812. $qpart = "cat_id = " . $this->pdo->quote($cat_id);
  813. } else {
  814. $qpart = 'cat_id = NULL';
  815. }
  816. } else {
  817. $qpart = "";
  818. }
  819. break;
  820. case "feed_language":
  821. $qpart = "feed_language = " . $this->pdo->quote($feed_language);
  822. break;
  823. }
  824. if ($qpart) {
  825. $sth = $this->pdo->prepare("UPDATE ttrss_feeds SET $qpart WHERE id IN ($feed_ids_qmarks)
  826. AND owner_uid = ?");
  827. $sth->execute(array_merge($feed_ids, [$_SESSION['uid']]));
  828. }
  829. }
  830. $this->pdo->commit();
  831. }
  832. return;
  833. }
  834. function remove() {
  835. $ids = explode(",", clean($_REQUEST["ids"]));
  836. foreach ($ids as $id) {
  837. Pref_Feeds::remove_feed($id, $_SESSION["uid"]);
  838. }
  839. return;
  840. }
  841. function removeCat() {
  842. $ids = explode(",", clean($_REQUEST["ids"]));
  843. foreach ($ids as $id) {
  844. $this->remove_feed_category($id, $_SESSION["uid"]);
  845. }
  846. }
  847. function addCat() {
  848. $feed_cat = trim(clean($_REQUEST["cat"]));
  849. add_feed_category($feed_cat);
  850. }
  851. function index() {
  852. print "<div dojoType=\"dijit.layout.AccordionContainer\" region=\"center\">";
  853. print "<div id=\"pref-feeds-feeds\" dojoType=\"dijit.layout.AccordionPane\" title=\"".__('Feeds')."\">";
  854. $sth = $this->pdo->prepare("SELECT COUNT(id) AS num_errors
  855. FROM ttrss_feeds WHERE last_error != '' AND owner_uid = ?");
  856. $sth->execute([$_SESSION['uid']]);
  857. if ($row = $sth->fetch()) {
  858. $num_errors = $row["num_errors"];
  859. } else {
  860. $num_errors = 0;
  861. }
  862. if ($num_errors > 0) {
  863. $error_button = "<button dojoType=\"dijit.form.Button\"
  864. onclick=\"showFeedsWithErrors()\" id=\"errorButton\">" .
  865. __("Feeds with errors") . "</button>";
  866. }
  867. $inactive_button = "<button dojoType=\"dijit.form.Button\"
  868. id=\"pref_feeds_inactive_btn\"
  869. style=\"display : none\"
  870. onclick=\"showInactiveFeeds()\">" .
  871. __("Inactive feeds") . "</button>";
  872. $feed_search = clean($_REQUEST["search"]);
  873. if (array_key_exists("search", $_REQUEST)) {
  874. $_SESSION["prefs_feed_search"] = $feed_search;
  875. } else {
  876. $feed_search = $_SESSION["prefs_feed_search"];
  877. }
  878. print '<div dojoType="dijit.layout.BorderContainer" gutters="false">';
  879. print "<div region='top' dojoType=\"dijit.Toolbar\">"; #toolbar
  880. print "<div style='float : right; padding-right : 4px;'>
  881. <input dojoType=\"dijit.form.TextBox\" id=\"feed_search\" size=\"20\" type=\"search\"
  882. value=\"$feed_search\">
  883. <button dojoType=\"dijit.form.Button\" onclick=\"updateFeedList()\">".
  884. __('Search')."</button>
  885. </div>";
  886. print "<div dojoType=\"dijit.form.DropDownButton\">".
  887. "<span>" . __('Select')."</span>";
  888. print "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
  889. print "<div onclick=\"dijit.byId('feedTree').model.setAllChecked(true)\"
  890. dojoType=\"dijit.MenuItem\">".__('All')."</div>";
  891. print "<div onclick=\"dijit.byId('feedTree').model.setAllChecked(false)\"
  892. dojoType=\"dijit.MenuItem\">".__('None')."</div>";
  893. print "</div></div>";
  894. print "<div dojoType=\"dijit.form.DropDownButton\">".
  895. "<span>" . __('Feeds')."</span>";
  896. print "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
  897. print "<div onclick=\"quickAddFeed()\"
  898. dojoType=\"dijit.MenuItem\">".__('Subscribe to feed')."</div>";
  899. print "<div onclick=\"editSelectedFeed()\"
  900. dojoType=\"dijit.MenuItem\">".__('Edit selected feeds')."</div>";
  901. print "<div onclick=\"resetFeedOrder()\"
  902. dojoType=\"dijit.MenuItem\">".__('Reset sort order')."</div>";
  903. print "<div onclick=\"batchSubscribe()\"
  904. dojoType=\"dijit.MenuItem\">".__('Batch subscribe')."</div>";
  905. print "<div dojoType=\"dijit.MenuItem\" onclick=\"removeSelectedFeeds()\">"
  906. .__('Unsubscribe')."</div> ";
  907. print "</div></div>";
  908. if (get_pref('ENABLE_FEED_CATS')) {
  909. print "<div dojoType=\"dijit.form.DropDownButton\">".
  910. "<span>" . __('Categories')."</span>";
  911. print "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
  912. print "<div onclick=\"createCategory()\"
  913. dojoType=\"dijit.MenuItem\">".__('Add category')."</div>";
  914. print "<div onclick=\"resetCatOrder()\"
  915. dojoType=\"dijit.MenuItem\">".__('Reset sort order')."</div>";
  916. print "<div onclick=\"removeSelectedCategories()\"
  917. dojoType=\"dijit.MenuItem\">".__('Remove selected')."</div>";
  918. print "</div></div>";
  919. }
  920. print $error_button;
  921. print $inactive_button;
  922. print "</div>"; # toolbar
  923. //print '</div>';
  924. print '<div dojoType="dijit.layout.ContentPane" region="center">';
  925. print "<div id=\"feedlistLoading\">
  926. <img src='images/indicator_tiny.gif'>".
  927. __("Loading, please wait...")."</div>";
  928. $auto_expand = $feed_search != "" ? "true" : "false";
  929. print "<div dojoType=\"fox.PrefFeedStore\" jsId=\"feedStore\"
  930. url=\"backend.php?op=pref-feeds&method=getfeedtree\">
  931. </div>
  932. <div dojoType=\"lib.CheckBoxStoreModel\" jsId=\"feedModel\" store=\"feedStore\"
  933. query=\"{id:'root'}\" rootId=\"root\" rootLabel=\"Feeds\"
  934. childrenAttrs=\"items\" checkboxStrict=\"false\" checkboxAll=\"false\">
  935. </div>
  936. <div dojoType=\"fox.PrefFeedTree\" id=\"feedTree\"
  937. dndController=\"dijit.tree.dndSource\"
  938. betweenThreshold=\"5\"
  939. autoExpand='$auto_expand'
  940. model=\"feedModel\" openOnClick=\"false\">
  941. <script type=\"dojo/method\" event=\"onClick\" args=\"item\">
  942. var id = String(item.id);
  943. var bare_id = id.substr(id.indexOf(':')+1);
  944. if (id.match('FEED:')) {
  945. editFeed(bare_id);
  946. } else if (id.match('CAT:')) {
  947. editCat(bare_id, item);
  948. }
  949. </script>
  950. <script type=\"dojo/method\" event=\"onLoad\" args=\"item\">
  951. Element.hide(\"feedlistLoading\");
  952. checkInactiveFeeds();
  953. </script>
  954. </div>";
  955. # print "<div dojoType=\"dijit.Tooltip\" connectId=\"feedTree\" position=\"below\">
  956. # ".__('<b>Hint:</b> you can drag feeds and categories around.')."
  957. # </div>";
  958. print '</div>';
  959. print '</div>';
  960. print "</div>"; # feeds pane
  961. print "<div dojoType=\"dijit.layout.AccordionPane\" title=\"".__('OPML')."\">";
  962. print "<p>" . __("Using OPML you can export and import your feeds, filters, labels and Tiny Tiny RSS settings.") .
  963. __("Only main settings profile can be migrated using OPML.") . "</p>";
  964. print "<iframe id=\"upload_iframe\"
  965. name=\"upload_iframe\" onload=\"opmlImportComplete(this)\"
  966. style=\"width: 400px; height: 100px; display: none;\"></iframe>";
  967. print "<form name=\"opml_form\" style='display : block' target=\"upload_iframe\"
  968. enctype=\"multipart/form-data\" method=\"POST\"
  969. action=\"backend.php\">
  970. <label class=\"dijitButton\">".__("Choose file...")."
  971. <input style=\"display : none\" id=\"opml_file\" name=\"opml_file\" type=\"file\">&nbsp;
  972. </label>
  973. <input type=\"hidden\" name=\"op\" value=\"dlg\">
  974. <input type=\"hidden\" name=\"method\" value=\"importOpml\">
  975. <button dojoType=\"dijit.form.Button\" onclick=\"return opmlImport();\" type=\"submit\">" .
  976. __('Import my OPML') . "</button>";
  977. print "<hr>";
  978. $opml_export_filename = "TinyTinyRSS_".date("Y-m-d").".opml";
  979. print "<p>" . __('Filename:') .
  980. " <input class=\"input input-text\" type=\"text\" id=\"filename\" value=\"$opml_export_filename\" />&nbsp;" .
  981. __('Include settings') . "<input type=\"checkbox\" id=\"settings\" checked=\"1\"/>";
  982. print "</p><button dojoType=\"dijit.form.Button\"
  983. onclick=\"gotoExportOpml(document.opml_form.filename.value, document.opml_form.settings.checked)\" >" .
  984. __('Export OPML') . "</button></p></form>";
  985. print "<hr>";
  986. print "<p>" . __('Your OPML can be published publicly and can be subscribed by anyone who knows the URL below.') . "</p>";
  987. print_warning("Published OPML does not include your Tiny Tiny RSS settings, feeds that require authentication or feeds hidden from Popular feeds.");
  988. print "<button dojoType=\"dijit.form.Button\" onclick=\"return displayDlg('".__("Public OPML URL")."','pubOPMLUrl')\">".
  989. __('Display published OPML URL')."</button> ";
  990. PluginHost::getInstance()->run_hooks(PluginHost::HOOK_PREFS_TAB_SECTION,
  991. "hook_prefs_tab_section", "prefFeedsOPML");
  992. print "</div>"; # pane
  993. if (strpos($_SERVER['HTTP_USER_AGENT'], "Firefox") !== false) {
  994. print "<div dojoType=\"dijit.layout.AccordionPane\" title=\"".__('Firefox integration')."\">";
  995. print_notice(__('This Tiny Tiny RSS site can be used as a Firefox Feed Reader by clicking the link below.'));
  996. print "<p>";
  997. print "<button onclick='window.navigator.registerContentHandler(" .
  998. "\"application/vnd.mozilla.maybe.feed\", " .
  999. "\"" . $this->subscribe_to_feed_url() . "\", " . " \"Tiny Tiny RSS\")'>" .
  1000. __('Click here to register this site as a feed reader.') .
  1001. "</button>";
  1002. print "</p>";
  1003. print "</div>"; # pane
  1004. }
  1005. print "<div dojoType=\"dijit.layout.AccordionPane\" title=\"".__('Published & shared articles / Generated feeds')."\">";
  1006. print "<p>" . __('Published articles are exported as a public RSS feed and can be subscribed by anyone who knows the URL specified below.') . "</p>";
  1007. $rss_url = '-2::' . htmlspecialchars(get_self_url_prefix() .
  1008. "/public.php?op=rss&id=-2&view-mode=all_articles");;
  1009. print "<p>";
  1010. print "<button dojoType=\"dijit.form.Button\" onclick=\"return displayDlg('".__("View as RSS")."','generatedFeed', '$rss_url')\">".
  1011. __('Display URL')."</button> ";
  1012. print "<button class=\"warning\" dojoType=\"dijit.form.Button\" onclick=\"return clearFeedAccessKeys()\">".
  1013. __('Clear all generated URLs')."</button> ";
  1014. print "</p>";
  1015. PluginHost::getInstance()->run_hooks(PluginHost::HOOK_PREFS_TAB_SECTION,
  1016. "hook_prefs_tab_section", "prefFeedsPublishedGenerated");
  1017. print "</div>"; #pane
  1018. PluginHost::getInstance()->run_hooks(PluginHost::HOOK_PREFS_TAB,
  1019. "hook_prefs_tab", "prefFeeds");
  1020. print "</div>"; #container
  1021. }
  1022. private function feedlist_init_cat($cat_id) {
  1023. $obj = array();
  1024. $cat_id = (int) $cat_id;
  1025. if ($cat_id > 0) {
  1026. $cat_unread = CCache::find($cat_id, $_SESSION["uid"], true);
  1027. } else if ($cat_id == 0 || $cat_id == -2) {
  1028. $cat_unread = Feeds::getCategoryUnread($cat_id);
  1029. }
  1030. $obj['id'] = 'CAT:' . $cat_id;
  1031. $obj['items'] = array();
  1032. $obj['name'] = Feeds::getCategoryTitle($cat_id);
  1033. $obj['type'] = 'category';
  1034. $obj['unread'] = (int) $cat_unread;
  1035. $obj['bare_id'] = $cat_id;
  1036. return $obj;
  1037. }
  1038. private function feedlist_init_feed($feed_id, $title = false, $unread = false, $error = '', $updated = '') {
  1039. $obj = array();
  1040. $feed_id = (int) $feed_id;
  1041. if (!$title)
  1042. $title = Feeds::getFeedTitle($feed_id, false);
  1043. if ($unread === false)
  1044. $unread = getFeedUnread($feed_id, false);
  1045. $obj['id'] = 'FEED:' . $feed_id;
  1046. $obj['name'] = $title;
  1047. $obj['unread'] = (int) $unread;
  1048. $obj['type'] = 'feed';
  1049. $obj['error'] = $error;
  1050. $obj['updated'] = $updated;
  1051. $obj['icon'] = Feeds::getFeedIcon($feed_id);
  1052. $obj['bare_id'] = $feed_id;
  1053. $obj['auxcounter'] = 0;
  1054. return $obj;
  1055. }
  1056. function inactiveFeeds() {
  1057. if (DB_TYPE == "pgsql") {
  1058. $interval_qpart = "NOW() - INTERVAL '3 months'";
  1059. } else {
  1060. $interval_qpart = "DATE_SUB(NOW(), INTERVAL 3 MONTH)";
  1061. }
  1062. $sth = $this->pdo->prepare("SELECT ttrss_feeds.title, ttrss_feeds.site_url,
  1063. ttrss_feeds.feed_url, ttrss_feeds.id, MAX(updated) AS last_article
  1064. FROM ttrss_feeds, ttrss_entries, ttrss_user_entries WHERE
  1065. (SELECT MAX(updated) FROM ttrss_entries, ttrss_user_entries WHERE
  1066. ttrss_entries.id = ref_id AND
  1067. ttrss_user_entries.feed_id = ttrss_feeds.id) < $interval_qpart
  1068. AND ttrss_feeds.owner_uid = ? AND
  1069. ttrss_user_entries.feed_id = ttrss_feeds.id AND
  1070. ttrss_entries.id = ref_id
  1071. GROUP BY ttrss_feeds.title, ttrss_feeds.id, ttrss_feeds.site_url, ttrss_feeds.feed_url
  1072. ORDER BY last_article");
  1073. $sth->execute([$_SESSION['uid']]);
  1074. print "<p" .__("These feeds have not been updated with new content for 3 months (oldest first):") . "</p>";
  1075. print "<div dojoType=\"dijit.Toolbar\">";
  1076. print "<div dojoType=\"dijit.form.DropDownButton\">".
  1077. "<span>" . __('Select')."</span>";
  1078. print "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
  1079. print "<div onclick=\"selectTableRows('prefInactiveFeedList', 'all')\"
  1080. dojoType=\"dijit.MenuItem\">".__('All')."</div>";
  1081. print "<div onclick=\"selectTableRows('prefInactiveFeedList', 'none')\"
  1082. dojoType=\"dijit.MenuItem\">".__('None')."</div>";
  1083. print "</div></div>";
  1084. print "</div>"; #toolbar
  1085. print "<div class=\"inactiveFeedHolder\">";
  1086. print "<table width=\"100%\" cellspacing=\"0\" id=\"prefInactiveFeedList\">";
  1087. $lnum = 1;
  1088. while ($line = $sth->fetch()) {
  1089. $feed_id = $line["id"];
  1090. $this_row_id = "id=\"FUPDD-$feed_id\"";
  1091. # class needed for selectTableRows()
  1092. print "<tr class=\"placeholder\" $this_row_id>";
  1093. # id needed for selectTableRows()
  1094. print "<td width='5%' align='center'><input
  1095. onclick='toggleSelectRow2(this);' dojoType=\"dijit.form.CheckBox\"
  1096. type=\"checkbox\" id=\"FUPDC-$feed_id\"></td>";
  1097. print "<td>";
  1098. print "<a class=\"visibleLink\" href=\"#\" ".
  1099. "title=\"".__("Click to edit feed")."\" ".
  1100. "onclick=\"editFeed(".$line["id"].")\">".
  1101. htmlspecialchars($line["title"])."</a>";
  1102. print "</td><td class=\"insensitive\" align='right'>";
  1103. print make_local_datetime($line['last_article'], false);
  1104. print "</td>";
  1105. print "</tr>";
  1106. ++$lnum;
  1107. }
  1108. print "</table>";
  1109. print "</div>";
  1110. print "<div class='dlgButtons'>";
  1111. print "<div style='float : left'>";
  1112. print "<button class=\"btn-danger\" dojoType=\"dijit.form.Button\" onclick=\"dijit.byId('inactiveFeedsDlg').removeSelected()\">"
  1113. .__('Unsubscribe from selected feeds')."</button> ";
  1114. print "</div>";
  1115. print "<button dojoType=\"dijit.form.Button\" onclick=\"dijit.byId('inactiveFeedsDlg').hide()\">".
  1116. __('Close this window')."</button>";
  1117. print "</div>";
  1118. }
  1119. function feedsWithErrors() {
  1120. $sth = $this->pdo->prepare("SELECT id,title,feed_url,last_error,site_url
  1121. FROM ttrss_feeds WHERE last_error != '' AND owner_uid = ?");
  1122. $sth->execute([$_SESSION['uid']]);
  1123. print "<div dojoType=\"dijit.Toolbar\">";
  1124. print "<div dojoType=\"dijit.form.DropDownButton\">".
  1125. "<span>" . __('Select')."</span>";
  1126. print "<div dojoType=\"dijit.Menu\" style=\"display: none;\">";
  1127. print "<div onclick=\"selectTableRows('prefErrorFeedList', 'all')\"
  1128. dojoType=\"dijit.MenuItem\">".__('All')."</div>";
  1129. print "<div onclick=\"selectTableRows('prefErrorFeedList', 'none')\"
  1130. dojoType=\"dijit.MenuItem\">".__('None')."</div>";
  1131. print "</div></div>";
  1132. print "</div>"; #toolbar
  1133. print "<div class=\"inactiveFeedHolder\">";
  1134. print "<table width=\"100%\" cellspacing=\"0\" id=\"prefErrorFeedList\">";
  1135. $lnum = 1;
  1136. while ($line = $sth->fetch()) {
  1137. $feed_id = $line["id"];
  1138. $this_row_id = "id=\"FERDD-$feed_id\"";
  1139. # class needed for selectTableRows()
  1140. print "<tr class=\"placeholder\" $this_row_id>";
  1141. # id needed for selectTableRows()
  1142. print "<td width='5%' align='center'><input
  1143. onclick='toggleSelectRow2(this);' dojoType=\"dijit.form.CheckBox\"
  1144. type=\"checkbox\" id=\"FERDC-$feed_id\"></td>";
  1145. print "<td>";
  1146. print "<a class=\"visibleLink\" href=\"#\" ".
  1147. "title=\"".__("Click to edit feed")."\" ".
  1148. "onclick=\"editFeed(".$line["id"].")\">".
  1149. htmlspecialchars($line["title"])."</a>: ";
  1150. print "<span class=\"insensitive\">";
  1151. print htmlspecialchars($line["last_error"]);
  1152. print "</span>";
  1153. print "</td>";
  1154. print "</tr>";
  1155. ++$lnum;
  1156. }
  1157. print "</table>";
  1158. print "</div>";
  1159. print "<div class='dlgButtons'>";
  1160. print "<div style='float : left'>";
  1161. print "<button class=\"btn-danger\" dojoType=\"dijit.form.Button\" onclick=\"dijit.byId('errorFeedsDlg').removeSelected()\">"
  1162. .__('Unsubscribe from selected feeds')."</button> ";
  1163. print "</div>";
  1164. print "<button dojoType=\"dijit.form.Button\" onclick=\"dijit.byId('errorFeedsDlg').hide()\">".
  1165. __('Close this window')."</button>";
  1166. print "</div>";
  1167. }
  1168. private function remove_feed_category($id, $owner_uid) {
  1169. $sth = $this->pdo->prepare("DELETE FROM ttrss_feed_categories
  1170. WHERE id = ? AND owner_uid = ?");
  1171. $sth->execute([$id, $owner_uid]);
  1172. CCache::remove($id, $owner_uid, true);
  1173. }
  1174. static function remove_feed($id, $owner_uid) {
  1175. $debug = isset($_REQUEST["debug"]);
  1176. foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_UNSUBSCRIBE_FEED) as $p) {
  1177. if( ! $p->hook_unsubscribe_feed($id, $owner_uid)){
  1178. if($debug) _debug("Feed not removed due to Error in Plugin. (HOOK_UNSUBSCRIBE_FEED)");
  1179. return;
  1180. }
  1181. }
  1182. $pdo = Db::pdo();
  1183. if ($id > 0) {
  1184. $pdo->beginTransaction();
  1185. /* save starred articles in Archived feed */
  1186. /* prepare feed if necessary */
  1187. $sth = $pdo->prepare("SELECT feed_url FROM ttrss_feeds WHERE id = ?
  1188. AND owner_uid = ?");
  1189. $sth->execute([$id, $owner_uid]);
  1190. if ($row = $sth->fetch()) {
  1191. $feed_url = $row["feed_url"];
  1192. $sth = $pdo->prepare("SELECT id FROM ttrss_archived_feeds
  1193. WHERE feed_url = ? AND owner_uid = ?");
  1194. $sth->execute([$feed_url, $owner_uid]);
  1195. if ($row = $sth->fetch()) {
  1196. $archive_id = $row["id"];
  1197. } else {
  1198. $res = $pdo->query("SELECT MAX(id) AS id FROM ttrss_archived_feeds");
  1199. $row = $res->fetch();
  1200. $new_feed_id = (int)$row['id'] + 1;
  1201. $sth = $pdo->prepare("INSERT INTO ttrss_archived_feeds
  1202. (id, owner_uid, title, feed_url, site_url)
  1203. SELECT ?, owner_uid, title, feed_url, site_url from ttrss_feeds
  1204. WHERE id = ?");
  1205. $sth->execute([$new_feed_id, $id]);
  1206. $archive_id = $new_feed_id;
  1207. }
  1208. $sth = $pdo->prepare("UPDATE ttrss_user_entries SET feed_id = NULL,
  1209. orig_feed_id = ? WHERE feed_id = ? AND
  1210. marked = true AND owner_uid = ?");
  1211. $sth->execute([$archive_id, $id, $owner_uid]);
  1212. /* Remove access key for the feed */
  1213. $sth = $pdo->prepare("DELETE FROM ttrss_access_keys WHERE
  1214. feed_id = ? AND owner_uid = ?");
  1215. $sth->execute([$id, $owner_uid]);
  1216. /* remove the feed */
  1217. $sth = $pdo->prepare("DELETE FROM ttrss_feeds
  1218. WHERE id = ? AND owner_uid = ?");
  1219. $sth->execute([$id, $owner_uid]);
  1220. }
  1221. $pdo->commit();
  1222. if (file_exists(ICONS_DIR . "/$id.ico")) {
  1223. unlink(ICONS_DIR . "/$id.ico");
  1224. }
  1225. CCache::remove($id, $owner_uid);
  1226. } else {
  1227. Labels::remove(Labels::feed_to_label_id($id), $owner_uid);
  1228. //CCache::remove($id, $owner_uid); don't think labels are cached
  1229. }
  1230. }
  1231. function batchSubscribe() {
  1232. print_hidden("op", "pref-feeds");
  1233. print_hidden("method", "batchaddfeeds");
  1234. print "<table width='100%'><tr><td>
  1235. ".__("Add one valid RSS feed per line (no feed detection is done)")."
  1236. </td><td align='right'>";
  1237. if (get_pref('ENABLE_FEED_CATS')) {
  1238. print __('Place in category:') . " ";
  1239. print_feed_cat_select("cat", false, 'dojoType="dijit.form.Select"');
  1240. }
  1241. print "</td></tr><tr><td colspan='2'>";
  1242. print "<textarea
  1243. style='font-size : 12px; width : 98%; height: 200px;'
  1244. placeHolder=\"".__("Feeds to subscribe, One per line")."\"
  1245. dojoType=\"dijit.form.SimpleTextarea\" required=\"1\" name=\"feeds\"></textarea>";
  1246. print "</td></tr><tr><td colspan='2'>";
  1247. print "<div id='feedDlg_loginContainer' style='display : none'>
  1248. " .
  1249. " <input dojoType=\"dijit.form.TextBox\" name='login'\"
  1250. placeHolder=\"".__("Login")."\"
  1251. style=\"width : 10em;\"> ".
  1252. " <input
  1253. placeHolder=\"".__("Password")."\"
  1254. dojoType=\"dijit.form.TextBox\" type='password'
  1255. autocomplete=\"new-password\"
  1256. style=\"width : 10em;\" name='pass'\">".
  1257. "</div>";
  1258. print "</td></tr><tr><td colspan='2'>";
  1259. print "<div style=\"clear : both\">
  1260. <input type=\"checkbox\" name=\"need_auth\" dojoType=\"dijit.form.CheckBox\" id=\"feedDlg_loginCheck\"
  1261. onclick='checkboxToggleElement(this, \"feedDlg_loginContainer\")'>
  1262. <label for=\"feedDlg_loginCheck\">".
  1263. __('Feeds require authentication.')."</div>";
  1264. print "</form>";
  1265. print "</td></tr></table>";
  1266. print "<div class=\"dlgButtons\">
  1267. <button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('batchSubDlg').execute()\">".__('Subscribe')."</button>
  1268. <button dojoType=\"dijit.form.Button\" onclick=\"return dijit.byId('batchSubDlg').hide()\">".__('Cancel')."</button>
  1269. </div>";
  1270. }
  1271. function batchAddFeeds() {
  1272. $cat_id = clean($_REQUEST['cat']);
  1273. $feeds = explode("\n", clean($_REQUEST['feeds']));
  1274. $login = clean($_REQUEST['login']);
  1275. $pass = trim(clean($_REQUEST['pass']));
  1276. foreach ($feeds as $feed) {
  1277. $feed = trim($feed);
  1278. if (validate_feed_url($feed)) {
  1279. $this->pdo->beginTransaction();
  1280. $sth = $this->pdo->prepare("SELECT id FROM ttrss_feeds
  1281. WHERE feed_url = ? AND owner_uid = ?");
  1282. $sth->execute([$feed, $_SESSION['uid']]);
  1283. if (!$sth->fetch()) {
  1284. $sth = $this->pdo->prepare("INSERT INTO ttrss_feeds
  1285. (owner_uid,feed_url,title,cat_id,auth_login,auth_pass,update_method,auth_pass_encrypted)
  1286. VALUES (?, ?, '[Unknown]', ?, ?, ?, 0, false)");
  1287. $sth->execute([$_SESSION['uid'], $feed, $cat_id ? $cat_id : null, $login, $pass]);
  1288. }
  1289. $this->pdo->commit();
  1290. }
  1291. }
  1292. }
  1293. function regenOPMLKey() {
  1294. $this->update_feed_access_key('OPML:Publish',
  1295. false, $_SESSION["uid"]);
  1296. $new_link = Opml::opml_publish_url();
  1297. print json_encode(array("link" => $new_link));
  1298. }
  1299. function regenFeedKey() {
  1300. $feed_id = clean($_REQUEST['id']);
  1301. $is_cat = clean($_REQUEST['is_cat']) == "true";
  1302. $new_key = $this->update_feed_access_key($feed_id, $is_cat);
  1303. print json_encode(array("link" => $new_key));
  1304. }
  1305. private function update_feed_access_key($feed_id, $is_cat, $owner_uid = false) {
  1306. if (!$owner_uid) $owner_uid = $_SESSION["uid"];
  1307. // clear old value and generate new one
  1308. $sth = $this->pdo->prepare("DELETE FROM ttrss_access_keys
  1309. WHERE feed_id = ? AND is_cat = ? AND owner_uid = ?");
  1310. $sth->execute([$feed_id, $is_cat, $owner_uid]);
  1311. return get_feed_access_key($feed_id, $is_cat, $owner_uid);
  1312. }
  1313. // Silent
  1314. function clearKeys() {
  1315. $sth = $this->pdo->prepare("DELETE FROM ttrss_access_keys WHERE
  1316. owner_uid = ?");
  1317. $sth->execute([$_SESSION['uid']]);
  1318. }
  1319. private function calculate_children_count($cat) {
  1320. $c = 0;
  1321. foreach ($cat['items'] as $child) {
  1322. if ($child['type'] == 'category') {
  1323. $c += $this->calculate_children_count($child);
  1324. } else {
  1325. $c += 1;
  1326. }
  1327. }
  1328. return $c;
  1329. }
  1330. function getinactivefeeds() {
  1331. if (DB_TYPE == "pgsql") {
  1332. $interval_qpart = "NOW() - INTERVAL '3 months'";
  1333. } else {
  1334. $interval_qpart = "DATE_SUB(NOW(), INTERVAL 3 MONTH)";
  1335. }
  1336. $sth = $this->pdo->prepare("SELECT COUNT(id) AS num_inactive FROM ttrss_feeds WHERE
  1337. (SELECT MAX(updated) FROM ttrss_entries, ttrss_user_entries WHERE
  1338. ttrss_entries.id = ref_id AND
  1339. ttrss_user_entries.feed_id = ttrss_feeds.id) < $interval_qpart AND
  1340. ttrss_feeds.owner_uid = ?");
  1341. $sth->execute([$_SESSION['uid']]);
  1342. if ($row = $sth->fetch()) {
  1343. print (int)$row["num_inactive"];
  1344. }
  1345. }
  1346. static function subscribe_to_feed_url() {
  1347. $url_path = get_self_url_prefix() .
  1348. "/public.php?op=subscribe&feed_url=%s";
  1349. return $url_path;
  1350. }
  1351. }