feeds.php 53 KB

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