feedController.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436
  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::record ($e->getMessage (), Minz_Log::WARNING);
  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::record ($e->getMessage (), Minz_Log::WARNING);
  132. $notif = array (
  133. 'type' => 'bad',
  134. 'content' => _t('internal_problem_feed', Minz_Url::display(array('a' => 'logs')))
  135. );
  136. Minz_Session::_param ('notification', $notif);
  137. } catch (Minz_FileNotExistException $e) {
  138. // Répertoire de cache n'existe pas
  139. Minz_Log::record ($e->getMessage (), Minz_Log::ERROR);
  140. $notif = array (
  141. 'type' => 'bad',
  142. 'content' => _t('internal_problem_feed', Minz_Url::display(array('a' => 'logs')))
  143. );
  144. Minz_Session::_param ('notification', $notif);
  145. }
  146. if ($transactionStarted) {
  147. $feedDAO->rollBack ();
  148. }
  149. Minz_Request::forward (array ('c' => 'subscription', 'a' => 'index', 'params' => $params), true);
  150. } else {
  151. // GET request so we must ask confirmation to user
  152. Minz_View::prependTitle(_t('add_rss_feed') . ' · ');
  153. $this->view->categories = $this->catDAO->listCategories(false);
  154. $this->view->feed = new FreshRSS_Feed($url);
  155. try {
  156. // We try to get some more information about the feed
  157. $this->view->feed->load(true);
  158. $this->view->load_ok = true;
  159. } catch (Exception $e) {
  160. $this->view->load_ok = false;
  161. }
  162. $feed = $feedDAO->searchByUrl($this->view->feed->url());
  163. if ($feed) {
  164. // Already subscribe so we redirect to the feed configuration page
  165. $notif = array(
  166. 'type' => 'bad',
  167. 'content' => _t('already_subscribed', $feed->name())
  168. );
  169. Minz_Session::_param('notification', $notif);
  170. Minz_Request::forward(array(
  171. 'c' => 'subscription',
  172. 'a' => 'index',
  173. 'params' => array(
  174. 'id' => $feed->id()
  175. )
  176. ), true);
  177. }
  178. }
  179. }
  180. public function truncateAction () {
  181. if (Minz_Request::isPost ()) {
  182. $id = Minz_Request::param ('id');
  183. $feedDAO = FreshRSS_Factory::createFeedDao();
  184. $n = $feedDAO->truncate($id);
  185. $notif = array(
  186. 'type' => $n === false ? 'bad' : 'good',
  187. 'content' => _t('n_entries_deleted', $n)
  188. );
  189. Minz_Session::_param ('notification', $notif);
  190. invalidateHttpCache();
  191. Minz_Request::forward (array ('c' => 'subscription',
  192. 'a' => 'index',
  193. 'params' => array('id' => $id)), true);
  194. }
  195. }
  196. public function actualizeAction () {
  197. @set_time_limit(300);
  198. $feedDAO = FreshRSS_Factory::createFeedDao();
  199. $entryDAO = FreshRSS_Factory::createEntryDao();
  200. Minz_Session::_param('actualize_feeds', false);
  201. $id = Minz_Request::param ('id');
  202. $force = Minz_Request::param ('force', false);
  203. // on créé la liste des flux à mettre à actualiser
  204. // si on veut mettre un flux à jour spécifiquement, on le met
  205. // dans la liste, mais seul (permet d'automatiser le traitement)
  206. $feeds = array ();
  207. if ($id) {
  208. $feed = $feedDAO->searchById ($id);
  209. if ($feed) {
  210. $feeds = array ($feed);
  211. }
  212. } else {
  213. $feeds = $feedDAO->listFeedsOrderUpdate($this->view->conf->ttl_default);
  214. }
  215. // on calcule la date des articles les plus anciens qu'on accepte
  216. $nb_month_old = max($this->view->conf->old_entries, 1);
  217. $date_min = time () - (3600 * 24 * 30 * $nb_month_old);
  218. $i = 0;
  219. $flux_update = 0;
  220. $is_read = $this->view->conf->mark_when['reception'] ? 1 : 0;
  221. foreach ($feeds as $feed) {
  222. if (!$feed->lock()) {
  223. Minz_Log::record('Feed already being actualized: ' . $feed->url(), Minz_Log::NOTICE);
  224. continue;
  225. }
  226. try {
  227. $url = $feed->url();
  228. $feedHistory = $feed->keepHistory();
  229. $feed->load(false);
  230. $entries = array_reverse($feed->entries()); //We want chronological order and SimplePie uses reverse order
  231. $hasTransaction = false;
  232. if (count($entries) > 0) {
  233. //For this feed, check last n entry GUIDs already in database
  234. $existingGuids = array_fill_keys ($entryDAO->listLastGuidsByFeed ($feed->id (), count($entries) + 10), 1);
  235. $useDeclaredDate = empty($existingGuids);
  236. if ($feedHistory == -2) { //default
  237. $feedHistory = $this->view->conf->keep_history_default;
  238. }
  239. $preparedStatement = $entryDAO->addEntryPrepare();
  240. $hasTransaction = true;
  241. $feedDAO->beginTransaction();
  242. // On ne vérifie pas strictement que l'article n'est pas déjà en BDD
  243. // La BDD refusera l'ajout car (id_feed, guid) doit être unique
  244. foreach ($entries as $entry) {
  245. $eDate = $entry->date(true);
  246. if ((!isset($existingGuids[$entry->guid()])) &&
  247. (($feedHistory != 0) || ($eDate >= $date_min))) {
  248. $values = $entry->toArray();
  249. //Use declared date at first import, otherwise use discovery date
  250. $values['id'] = ($useDeclaredDate || $eDate < $date_min) ?
  251. min(time(), $eDate) . uSecString() :
  252. uTimeString();
  253. $values['is_read'] = $is_read;
  254. $entryDAO->addEntry($values, $preparedStatement);
  255. }
  256. }
  257. }
  258. if (($feedHistory >= 0) && (rand(0, 30) === 1)) {
  259. if (!$hasTransaction) {
  260. $feedDAO->beginTransaction();
  261. }
  262. $nb = $feedDAO->cleanOldEntries ($feed->id (), $date_min, max($feedHistory, count($entries) + 10));
  263. if ($nb > 0) {
  264. Minz_Log::record ($nb . ' old entries cleaned in feed [' . $feed->url() . ']', Minz_Log::DEBUG);
  265. }
  266. }
  267. // on indique que le flux vient d'être mis à jour en BDD
  268. $feedDAO->updateLastUpdate ($feed->id (), 0, $hasTransaction);
  269. if ($hasTransaction) {
  270. $feedDAO->commit();
  271. }
  272. $flux_update++;
  273. if (($feed->url() !== $url)) { //HTTP 301 Moved Permanently
  274. Minz_Log::record('Feed ' . $url . ' moved permanently to ' . $feed->url(), Minz_Log::NOTICE);
  275. $feedDAO->updateFeed($feed->id(), array('url' => $feed->url()));
  276. }
  277. } catch (FreshRSS_Feed_Exception $e) {
  278. Minz_Log::record ($e->getMessage (), Minz_Log::NOTICE);
  279. $feedDAO->updateLastUpdate ($feed->id (), 1);
  280. }
  281. $feed->faviconPrepare();
  282. $feed->unlock();
  283. unset($feed);
  284. // On arrête à 10 flux pour ne pas surcharger le serveur
  285. // sauf si le paramètre $force est à vrai
  286. $i++;
  287. if ($i >= 10 && !$force) {
  288. break;
  289. }
  290. }
  291. $url = array ();
  292. if ($flux_update === 1) {
  293. // on a mis un seul flux à jour
  294. $feed = reset ($feeds);
  295. $notif = array (
  296. 'type' => 'good',
  297. 'content' => _t('feed_actualized', $feed->name ())
  298. );
  299. } elseif ($flux_update > 1) {
  300. // plusieurs flux on été mis à jour
  301. $notif = array (
  302. 'type' => 'good',
  303. 'content' => _t('n_feeds_actualized', $flux_update)
  304. );
  305. } else {
  306. // aucun flux n'a été mis à jour, oups
  307. $notif = array (
  308. 'type' => 'good',
  309. 'content' => _t('no_feed_to_refresh')
  310. );
  311. }
  312. if ($i === 1) {
  313. // Si on a voulu mettre à jour qu'un flux
  314. // on filtre l'affichage par ce flux
  315. $feed = reset ($feeds);
  316. $url['params'] = array ('get' => 'f_' . $feed->id ());
  317. }
  318. if (Minz_Request::param ('ajax', 0) === 0) {
  319. Minz_Session::_param ('notification', $notif);
  320. Minz_Request::forward ($url, true);
  321. } else {
  322. // Une requête Ajax met un seul flux à jour.
  323. // Comme en principe plusieurs requêtes ont lieu,
  324. // on indique que "plusieurs flux ont été mis à jour".
  325. // Cela permet d'avoir une notification plus proche du
  326. // ressenti utilisateur
  327. $notif = array (
  328. 'type' => 'good',
  329. 'content' => _t('feeds_actualized')
  330. );
  331. Minz_Session::_param ('notification', $notif);
  332. // et on désactive le layout car ne sert à rien
  333. $this->view->_useLayout (false);
  334. }
  335. }
  336. public function moveAction() {
  337. if (Minz_Request::isPost()) {
  338. $feed_id = Minz_Request::param('f_id');
  339. $cat_id = Minz_Request::param('c_id');
  340. $feedDAO = FreshRSS_Factory::createFeedDao();
  341. $values = array(
  342. 'category' => $cat_id,
  343. );
  344. $feed = $feedDAO->searchById($feed_id);
  345. if ($feed && ($feed->category() == $cat_id ||
  346. $feedDAO->updateFeed($feed_id, $values))) {
  347. // TODO: return something useful
  348. } else {
  349. Minz_Error::error(
  350. 404,
  351. array('error' => array(_t('error_occurred')))
  352. );
  353. }
  354. }
  355. }
  356. public function deleteAction() {
  357. if (Minz_Request::isPost()) {
  358. $id = Minz_Request::param('id');
  359. $feedDAO = FreshRSS_Factory::createFeedDao();
  360. if ($feedDAO->deleteFeed($id)) {
  361. // TODO: Delete old favicon
  362. // Remove related queries
  363. $this->view->conf->remove_query_by_get('f_' . $id);
  364. $this->view->conf->save();
  365. $notif = array(
  366. 'type' => 'good',
  367. 'content' => _t('feed_deleted')
  368. );
  369. } else {
  370. $notif = array(
  371. 'type' => 'bad',
  372. 'content' => _t('error_occured')
  373. );
  374. }
  375. Minz_Session::_param('notification', $notif);
  376. $redirect_url = Minz_Request::param('r', false, true);
  377. if ($redirect_url) {
  378. Minz_Request::forward($redirect_url);
  379. } else {
  380. Minz_Request::forward(array('c' => 'subscription', 'a' => 'index'), true);
  381. }
  382. }
  383. }
  384. }