feedController.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  1. <?php
  2. class FreshRSS_feed_Controller extends Minz_ActionController {
  3. public function firstAction() {
  4. if (!$this->view->loginOk) {
  5. // Token is useful in the case that anonymous refresh is forbidden
  6. // and CRON task cannot be used with php command so the user can
  7. // set a CRON task to refresh his feeds by using token inside url
  8. $token = $this->view->conf->token;
  9. $token_param = Minz_Request::param('token', '');
  10. $token_is_ok = ($token != '' && $token == $token_param);
  11. $action = Minz_Request::actionName();
  12. if (!(($token_is_ok || Minz_Configuration::allowAnonymousRefresh()) &&
  13. $action === 'actualize')
  14. ) {
  15. Minz_Error::error(
  16. 403,
  17. array('error' => array(_t('access_denied')))
  18. );
  19. }
  20. }
  21. }
  22. public function addAction() {
  23. $url = Minz_Request::param('url_rss', false);
  24. if ($url === false) {
  25. Minz_Request::forward(array(
  26. 'c' => 'subscription',
  27. 'a' => 'index'
  28. ), true);
  29. }
  30. $feedDAO = FreshRSS_Factory::createFeedDao();
  31. $this->catDAO = new FreshRSS_CategoryDAO();
  32. $this->catDAO->checkDefault();
  33. if (Minz_Request::isPost()) {
  34. @set_time_limit(300);
  35. $cat = Minz_Request::param('category', false);
  36. if ($cat === 'nc') {
  37. $new_cat = Minz_Request::param('new_category');
  38. if (empty($new_cat['name'])) {
  39. $cat = false;
  40. } else {
  41. $cat = $this->catDAO->addCategory($new_cat);
  42. }
  43. }
  44. if ($cat === false) {
  45. $def_cat = $this->catDAO->getDefault();
  46. $cat = $def_cat->id();
  47. }
  48. $user = Minz_Request::param('http_user');
  49. $pass = Minz_Request::param('http_pass');
  50. $params = array();
  51. $transactionStarted = false;
  52. try {
  53. $feed = new FreshRSS_Feed($url);
  54. $feed->_category($cat);
  55. $httpAuth = '';
  56. if ($user != '' || $pass != '') {
  57. $httpAuth = $user . ':' . $pass;
  58. }
  59. $feed->_httpAuth($httpAuth);
  60. $feed->load(true);
  61. $values = array(
  62. 'url' => $feed->url(),
  63. 'category' => $feed->category(),
  64. 'name' => $feed->name(),
  65. 'website' => $feed->website(),
  66. 'description' => $feed->description(),
  67. 'lastUpdate' => time(),
  68. 'httpAuth' => $feed->httpAuth(),
  69. );
  70. if ($feedDAO->searchByUrl($values['url'])) {
  71. // on est déjà abonné à ce flux
  72. $notif = array(
  73. 'type' => 'bad',
  74. 'content' => _t('already_subscribed', $feed->name())
  75. );
  76. Minz_Session::_param('notification', $notif);
  77. } else {
  78. $id = $feedDAO->addFeed($values);
  79. if (!$id) {
  80. // problème au niveau de la base de données
  81. $notif = array(
  82. 'type' => 'bad',
  83. 'content' => _t('feed_not_added', $feed->name())
  84. );
  85. Minz_Session::_param('notification', $notif);
  86. } else {
  87. $feed->_id($id);
  88. $feed->faviconPrepare();
  89. $is_read = $this->view->conf->mark_when['reception'] ? 1 : 0;
  90. $entryDAO = FreshRSS_Factory::createEntryDao();
  91. $entries = array_reverse($feed->entries()); //We want chronological order and SimplePie uses reverse order
  92. // on calcule la date des articles les plus anciens qu'on accepte
  93. $nb_month_old = $this->view->conf->old_entries;
  94. $date_min = time() - (3600 * 24 * 30 * $nb_month_old);
  95. //MySQL: http://docs.oracle.com/cd/E17952_01/refman-5.5-en/optimizing-innodb-transaction-management.html
  96. //SQLite: http://stackoverflow.com/questions/1711631/how-do-i-improve-the-performance-of-sqlite
  97. $preparedStatement = $entryDAO->addEntryPrepare();
  98. $transactionStarted = true;
  99. $feedDAO->beginTransaction();
  100. // on ajoute les articles en masse sans vérification
  101. foreach ($entries as $entry) {
  102. $values = $entry->toArray();
  103. $values['id_feed'] = $feed->id();
  104. $values['id'] = min(time(), $entry->date(true)) . uSecString();
  105. $values['is_read'] = $is_read;
  106. $entryDAO->addEntry($values, $preparedStatement);
  107. }
  108. $feedDAO->updateLastUpdate($feed->id());
  109. if ($transactionStarted) {
  110. $feedDAO->commit();
  111. }
  112. $transactionStarted = false;
  113. // ok, ajout terminé
  114. $notif = array(
  115. 'type' => 'good',
  116. 'content' => _t('feed_added', $feed->name())
  117. );
  118. Minz_Session::_param('notification', $notif);
  119. // permet de rediriger vers la page de conf du flux
  120. $params['id'] = $feed->id();
  121. }
  122. }
  123. } catch (FreshRSS_BadUrl_Exception $e) {
  124. Minz_Log::warning($e->getMessage());
  125. $notif = array(
  126. 'type' => 'bad',
  127. 'content' => _t('invalid_url', $url)
  128. );
  129. Minz_Session::_param('notification', $notif);
  130. } catch (FreshRSS_Feed_Exception $e) {
  131. Minz_Log::warning($e->getMessage());
  132. $notif = array(
  133. 'type' => 'bad',
  134. 'content' => _t('internal_problem_feed',
  135. Minz_Url::display(array('a' => 'logs')))
  136. );
  137. Minz_Session::_param('notification', $notif);
  138. } catch (Minz_FileNotExistException $e) {
  139. // Répertoire de cache n'existe pas
  140. Minz_Log::error($e->getMessage());
  141. $notif = array(
  142. 'type' => 'bad',
  143. 'content' => _t('internal_problem_feed',
  144. Minz_Url::display(array('a' => 'logs')))
  145. );
  146. Minz_Session::_param('notification', $notif);
  147. }
  148. if ($transactionStarted) {
  149. $feedDAO->rollBack();
  150. }
  151. Minz_Request::forward(array(
  152. 'c' => 'subscription',
  153. 'a' => 'index',
  154. 'params' => $params
  155. ), true);
  156. } else {
  157. // GET request so we must ask confirmation to user
  158. Minz_View::prependTitle(_t('add_rss_feed') . ' · ');
  159. $this->view->categories = $this->catDAO->listCategories(false);
  160. $this->view->feed = new FreshRSS_Feed($url);
  161. try {
  162. // We try to get some more information about the feed
  163. $this->view->feed->load(true);
  164. $this->view->load_ok = true;
  165. } catch (Exception $e) {
  166. $this->view->load_ok = false;
  167. }
  168. $feed = $feedDAO->searchByUrl($this->view->feed->url());
  169. if ($feed) {
  170. // Already subscribe so we redirect to the feed configuration page
  171. $notif = array(
  172. 'type' => 'bad',
  173. 'content' => _t('already_subscribed', $feed->name())
  174. );
  175. Minz_Session::_param('notification', $notif);
  176. Minz_Request::forward(array(
  177. 'c' => 'subscription',
  178. 'a' => 'index',
  179. 'params' => array(
  180. 'id' => $feed->id()
  181. )
  182. ), true);
  183. }
  184. }
  185. }
  186. public function truncateAction() {
  187. if (Minz_Request::isPost()) {
  188. $id = Minz_Request::param('id');
  189. $feedDAO = FreshRSS_Factory::createFeedDao();
  190. $n = $feedDAO->truncate($id);
  191. $notif = array(
  192. 'type' => $n === false ? 'bad' : 'good',
  193. 'content' => _t('n_entries_deleted', $n)
  194. );
  195. Minz_Session::_param('notification', $notif);
  196. invalidateHttpCache();
  197. Minz_Request::forward(array(
  198. 'c' => 'subscription',
  199. 'a' => 'index',
  200. 'params' => array('id' => $id)
  201. ), true);
  202. }
  203. }
  204. public function actualizeAction() {
  205. @set_time_limit(300);
  206. $feedDAO = FreshRSS_Factory::createFeedDao();
  207. $entryDAO = FreshRSS_Factory::createEntryDao();
  208. Minz_Session::_param('actualize_feeds', false);
  209. $id = Minz_Request::param('id');
  210. $force = Minz_Request::param('force', false);
  211. // on créé la liste des flux à mettre à actualiser
  212. // si on veut mettre un flux à jour spécifiquement, on le met
  213. // dans la liste, mais seul (permet d'automatiser le traitement)
  214. $feeds = array();
  215. if ($id) {
  216. $feed = $feedDAO->searchById($id);
  217. if ($feed) {
  218. $feeds = array($feed);
  219. }
  220. } else {
  221. $feeds = $feedDAO->listFeedsOrderUpdate($this->view->conf->ttl_default);
  222. }
  223. // on calcule la date des articles les plus anciens qu'on accepte
  224. $nb_month_old = max($this->view->conf->old_entries, 1);
  225. $date_min = time() - (3600 * 24 * 30 * $nb_month_old);
  226. $i = 0;
  227. $flux_update = 0;
  228. $is_read = $this->view->conf->mark_when['reception'] ? 1 : 0;
  229. foreach ($feeds as $feed) {
  230. if (!$feed->lock()) {
  231. Minz_Log::notice('Feed already being actualized: ' . $feed->url());
  232. continue;
  233. }
  234. try {
  235. $url = $feed->url();
  236. $feedHistory = $feed->keepHistory();
  237. $feed->load(false);
  238. $entries = array_reverse($feed->entries()); //We want chronological order and SimplePie uses reverse order
  239. $hasTransaction = false;
  240. if (count($entries) > 0) {
  241. //For this feed, check last n entry GUIDs already in database
  242. $existingGuids = array_fill_keys(
  243. $entryDAO->listLastGuidsByFeed($feed->id(),
  244. count($entries) + 10), 1);
  245. $useDeclaredDate = empty($existingGuids);
  246. if ($feedHistory == -2) { //default
  247. $feedHistory = $this->view->conf->keep_history_default;
  248. }
  249. $preparedStatement = $entryDAO->addEntryPrepare();
  250. $hasTransaction = true;
  251. $feedDAO->beginTransaction();
  252. // On ne vérifie pas strictement que l'article n'est pas déjà en BDD
  253. // La BDD refusera l'ajout car (id_feed, guid) doit être unique
  254. foreach ($entries as $entry) {
  255. $eDate = $entry->date(true);
  256. if ((!isset($existingGuids[$entry->guid()])) &&
  257. (($feedHistory != 0) || ($eDate >= $date_min))) {
  258. $values = $entry->toArray();
  259. //Use declared date at first import, otherwise use discovery date
  260. $values['id'] = ($useDeclaredDate || $eDate < $date_min) ?
  261. min(time(), $eDate) . uSecString() :
  262. uTimeString();
  263. $values['is_read'] = $is_read;
  264. $entryDAO->addEntry($values, $preparedStatement);
  265. }
  266. }
  267. }
  268. if (($feedHistory >= 0) && (rand(0, 30) === 1)) {
  269. if (!$hasTransaction) {
  270. $feedDAO->beginTransaction();
  271. }
  272. $nb = $feedDAO->cleanOldEntries($feed->id(), $date_min, max($feedHistory, count($entries) + 10));
  273. if ($nb > 0) {
  274. Minz_Log::debug($nb . ' old entries cleaned in feed [' . $feed->url() . ']');
  275. }
  276. }
  277. // on indique que le flux vient d'être mis à jour en BDD
  278. $feedDAO->updateLastUpdate($feed->id(), 0, $hasTransaction);
  279. if ($hasTransaction) {
  280. $feedDAO->commit();
  281. }
  282. $flux_update++;
  283. if (($feed->url() !== $url)) { //HTTP 301 Moved Permanently
  284. Minz_Log::notice('Feed ' . $url . ' moved permanently to ' . $feed->url());
  285. $feedDAO->updateFeed($feed->id(), array('url' => $feed->url()));
  286. }
  287. } catch (FreshRSS_Feed_Exception $e) {
  288. Minz_Log::notice($e->getMessage());
  289. $feedDAO->updateLastUpdate($feed->id(), 1);
  290. }
  291. $feed->faviconPrepare();
  292. $feed->unlock();
  293. unset($feed);
  294. // On arrête à 10 flux pour ne pas surcharger le serveur
  295. // sauf si le paramètre $force est à vrai
  296. $i++;
  297. if ($i >= 10 && !$force) {
  298. break;
  299. }
  300. }
  301. $url = array();
  302. if ($flux_update === 1) {
  303. // on a mis un seul flux à jour
  304. $feed = reset($feeds);
  305. $notif = array(
  306. 'type' => 'good',
  307. 'content' => _t('feed_actualized', $feed->name())
  308. );
  309. } elseif ($flux_update > 1) {
  310. // plusieurs flux on été mis à jour
  311. $notif = array(
  312. 'type' => 'good',
  313. 'content' => _t('n_feeds_actualized', $flux_update)
  314. );
  315. } else {
  316. // aucun flux n'a été mis à jour, oups
  317. $notif = array(
  318. 'type' => 'good',
  319. 'content' => _t('no_feed_to_refresh')
  320. );
  321. }
  322. if ($i === 1) {
  323. // Si on a voulu mettre à jour qu'un flux
  324. // on filtre l'affichage par ce flux
  325. $feed = reset($feeds);
  326. $url['params'] = array('get' => 'f_' . $feed->id());
  327. }
  328. if (Minz_Request::param('ajax', 0) === 0) {
  329. Minz_Session::_param('notification', $notif);
  330. Minz_Request::forward($url, true);
  331. } else {
  332. // Une requête Ajax met un seul flux à jour.
  333. // Comme en principe plusieurs requêtes ont lieu,
  334. // on indique que "plusieurs flux ont été mis à jour".
  335. // Cela permet d'avoir une notification plus proche du
  336. // ressenti utilisateur
  337. $notif = array(
  338. 'type' => 'good',
  339. 'content' => _t('feeds_actualized')
  340. );
  341. Minz_Session::_param('notification', $notif);
  342. // et on désactive le layout car ne sert à rien
  343. $this->view->_useLayout(false);
  344. }
  345. }
  346. public function moveAction() {
  347. if (Minz_Request::isPost()) {
  348. $feed_id = Minz_Request::param('f_id');
  349. $cat_id = Minz_Request::param('c_id');
  350. $feedDAO = FreshRSS_Factory::createFeedDao();
  351. $values = array(
  352. 'category' => $cat_id,
  353. );
  354. $feed = $feedDAO->searchById($feed_id);
  355. if ($feed && ($feed->category() == $cat_id ||
  356. $feedDAO->updateFeed($feed_id, $values))) {
  357. // TODO: return something useful
  358. } else {
  359. Minz_Error::error(
  360. 404,
  361. array('error' => array(_t('error_occurred')))
  362. );
  363. }
  364. }
  365. }
  366. public function deleteAction() {
  367. if (Minz_Request::isPost()) {
  368. $id = Minz_Request::param('id');
  369. $feedDAO = FreshRSS_Factory::createFeedDao();
  370. if ($feedDAO->deleteFeed($id)) {
  371. // TODO: Delete old favicon
  372. // Remove related queries
  373. $this->view->conf->remove_query_by_get('f_' . $id);
  374. $this->view->conf->save();
  375. $notif = array(
  376. 'type' => 'good',
  377. 'content' => _t('feed_deleted')
  378. );
  379. } else {
  380. $notif = array(
  381. 'type' => 'bad',
  382. 'content' => _t('error_occured')
  383. );
  384. }
  385. Minz_Session::_param('notification', $notif);
  386. $redirect_url = Minz_Request::param('r', false, true);
  387. if ($redirect_url) {
  388. Minz_Request::forward($redirect_url);
  389. } else {
  390. Minz_Request::forward(array('c' => 'subscription', 'a' => 'index'), true);
  391. }
  392. }
  393. }
  394. }