FeedDAO.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631
  1. <?php
  2. class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
  3. protected function addColumn(string $name) {
  4. Minz_Log::warning(__method__ . ': ' . $name);
  5. try {
  6. if ($name === 'kind') { //v1.20.0
  7. return $this->pdo->exec('ALTER TABLE `_feed` ADD COLUMN kind SMALLINT DEFAULT 0') !== false;
  8. } elseif ($name === 'attributes') { //v1.11.0
  9. return $this->pdo->exec('ALTER TABLE `_feed` ADD COLUMN attributes TEXT') !== false;
  10. }
  11. } catch (Exception $e) {
  12. Minz_Log::error(__method__ . ' error: ' . $e->getMessage());
  13. }
  14. return false;
  15. }
  16. protected function autoUpdateDb(array $errorInfo) {
  17. if (isset($errorInfo[0])) {
  18. if ($errorInfo[0] === FreshRSS_DatabaseDAO::ER_BAD_FIELD_ERROR || $errorInfo[0] === FreshRSS_DatabaseDAOPGSQL::UNDEFINED_COLUMN) {
  19. $errorLines = explode("\n", $errorInfo[2], 2); // The relevant column name is on the first line, other lines are noise
  20. foreach (['attributes', 'kind'] as $column) {
  21. if (stripos($errorLines[0], $column) !== false) {
  22. return $this->addColumn($column);
  23. }
  24. }
  25. }
  26. }
  27. return false;
  28. }
  29. /** @return int|false */
  30. public function addFeed(array $valuesTmp) {
  31. $sql = 'INSERT INTO `_feed` (url, kind, category, name, website, description, `lastUpdate`, priority, `pathEntries`, `httpAuth`, error, ttl, attributes)
  32. VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
  33. $stm = $this->pdo->prepare($sql);
  34. $valuesTmp['url'] = safe_ascii($valuesTmp['url']);
  35. $valuesTmp['website'] = safe_ascii($valuesTmp['website']);
  36. if (!isset($valuesTmp['pathEntries'])) {
  37. $valuesTmp['pathEntries'] = '';
  38. }
  39. if (!isset($valuesTmp['attributes'])) {
  40. $valuesTmp['attributes'] = [];
  41. }
  42. $values = array(
  43. substr($valuesTmp['url'], 0, 511),
  44. $valuesTmp['kind'] ?? FreshRSS_Feed::KIND_RSS,
  45. $valuesTmp['category'],
  46. mb_strcut(trim($valuesTmp['name']), 0, FreshRSS_DatabaseDAO::LENGTH_INDEX_UNICODE, 'UTF-8'),
  47. substr($valuesTmp['website'], 0, 255),
  48. sanitizeHTML($valuesTmp['description'], '', 1023),
  49. $valuesTmp['lastUpdate'],
  50. isset($valuesTmp['priority']) ? intval($valuesTmp['priority']) : FreshRSS_Feed::PRIORITY_MAIN_STREAM,
  51. mb_strcut($valuesTmp['pathEntries'], 0, 511, 'UTF-8'),
  52. base64_encode($valuesTmp['httpAuth']),
  53. isset($valuesTmp['error']) ? intval($valuesTmp['error']) : 0,
  54. isset($valuesTmp['ttl']) ? intval($valuesTmp['ttl']) : FreshRSS_Feed::TTL_DEFAULT,
  55. is_string($valuesTmp['attributes']) ? $valuesTmp['attributes'] : json_encode($valuesTmp['attributes'], JSON_UNESCAPED_SLASHES),
  56. );
  57. if ($stm && $stm->execute($values)) {
  58. return $this->pdo->lastInsertId('`_feed_id_seq`');
  59. } else {
  60. $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
  61. if ($this->autoUpdateDb($info)) {
  62. return $this->addFeed($valuesTmp);
  63. }
  64. Minz_Log::error('SQL error addFeed: ' . $info[2]);
  65. return false;
  66. }
  67. }
  68. /** @return int|false */
  69. public function addFeedObject(FreshRSS_Feed $feed) {
  70. // Add feed only if we don’t find it in DB
  71. $feed_search = $this->searchByUrl($feed->url());
  72. if (!$feed_search) {
  73. $values = array(
  74. 'id' => $feed->id(),
  75. 'url' => $feed->url(),
  76. 'kind' => $feed->kind(),
  77. 'category' => $feed->categoryId(),
  78. 'name' => $feed->name(true),
  79. 'website' => $feed->website(),
  80. 'description' => $feed->description(),
  81. 'lastUpdate' => 0,
  82. 'pathEntries' => $feed->pathEntries(),
  83. 'httpAuth' => $feed->httpAuth(),
  84. 'ttl' => $feed->ttl(true),
  85. 'attributes' => $feed->attributes(),
  86. );
  87. $id = $this->addFeed($values);
  88. if ($id) {
  89. $feed->_id($id);
  90. $feed->faviconPrepare();
  91. }
  92. return $id;
  93. } else {
  94. // The feed already exists so make sure it is not muted
  95. $feed->_ttl($feed_search->ttl());
  96. $feed->_mute(false);
  97. // Merge existing and import attributes
  98. $existingAttributes = $feed_search->attributes();
  99. $importAttributes = $feed->attributes();
  100. $feed->_attributes('', array_merge_recursive($existingAttributes, $importAttributes));
  101. // Update some values of the existing feed using the import
  102. $values = [
  103. 'kind' => $feed->kind(),
  104. 'name' => $feed->name(true),
  105. 'website' => $feed->website(),
  106. 'description' => $feed->description(),
  107. 'pathEntries' => $feed->pathEntries(),
  108. 'ttl' => $feed->ttl(true),
  109. 'attributes' => $feed->attributes(),
  110. ];
  111. if (!$this->updateFeed($feed_search->id(), $values)) {
  112. return false;
  113. }
  114. return $feed_search->id();
  115. }
  116. }
  117. /** @return int|false */
  118. public function updateFeed(int $id, array $valuesTmp) {
  119. if (isset($valuesTmp['name'])) {
  120. $valuesTmp['name'] = mb_strcut(trim($valuesTmp['name']), 0, FreshRSS_DatabaseDAO::LENGTH_INDEX_UNICODE, 'UTF-8');
  121. }
  122. if (isset($valuesTmp['url'])) {
  123. $valuesTmp['url'] = safe_ascii($valuesTmp['url']);
  124. }
  125. if (isset($valuesTmp['website'])) {
  126. $valuesTmp['website'] = safe_ascii($valuesTmp['website']);
  127. }
  128. $set = '';
  129. foreach ($valuesTmp as $key => $v) {
  130. $set .= '`' . $key . '`=?, ';
  131. if ($key === 'httpAuth') {
  132. $valuesTmp[$key] = base64_encode($v);
  133. } elseif ($key === 'attributes') {
  134. $valuesTmp[$key] = is_string($valuesTmp[$key]) ? $valuesTmp[$key] : json_encode($valuesTmp[$key], JSON_UNESCAPED_SLASHES);
  135. }
  136. }
  137. $set = substr($set, 0, -2);
  138. $sql = 'UPDATE `_feed` SET ' . $set . ' WHERE id=?';
  139. $stm = $this->pdo->prepare($sql);
  140. foreach ($valuesTmp as $v) {
  141. $values[] = $v;
  142. }
  143. $values[] = $id;
  144. if ($stm && $stm->execute($values)) {
  145. return $stm->rowCount();
  146. } else {
  147. $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
  148. if ($this->autoUpdateDb($info)) {
  149. return $this->updateFeed($id, $valuesTmp);
  150. }
  151. Minz_Log::error('SQL error updateFeed: ' . $info[2] . ' for feed ' . $id);
  152. return false;
  153. }
  154. }
  155. public function updateFeedAttribute(FreshRSS_Feed $feed, string $key, $value) {
  156. $feed->_attributes($key, $value);
  157. return $this->updateFeed(
  158. $feed->id(),
  159. array('attributes' => $feed->attributes())
  160. );
  161. }
  162. /**
  163. * @see updateCachedValue()
  164. */
  165. public function updateLastUpdate(int $id, bool $inError = false, int $mtime = 0) {
  166. $sql = 'UPDATE `_feed` SET `lastUpdate`=?, error=? WHERE id=?';
  167. $values = array(
  168. $mtime <= 0 ? time() : $mtime,
  169. $inError ? 1 : 0,
  170. $id,
  171. );
  172. $stm = $this->pdo->prepare($sql);
  173. if ($stm && $stm->execute($values)) {
  174. return $stm->rowCount();
  175. } else {
  176. $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
  177. Minz_Log::warning(__METHOD__ . ' error: ' . $sql . ' : ' . json_encode($info));
  178. return false;
  179. }
  180. }
  181. public function mute(int $id, bool $value = true) {
  182. $sql = 'UPDATE `_feed` SET ttl=' . ($value ? '-' : '') . 'ABS(ttl) WHERE id=' . intval($id);
  183. return $this->pdo->exec($sql);
  184. }
  185. public function changeCategory(int $idOldCat, int $idNewCat) {
  186. $catDAO = FreshRSS_Factory::createCategoryDao();
  187. $newCat = $catDAO->searchById($idNewCat);
  188. if (!$newCat) {
  189. $newCat = $catDAO->getDefault();
  190. }
  191. $sql = 'UPDATE `_feed` SET category=? WHERE category=?';
  192. $stm = $this->pdo->prepare($sql);
  193. $values = array(
  194. $newCat->id(),
  195. $idOldCat
  196. );
  197. if ($stm && $stm->execute($values)) {
  198. return $stm->rowCount();
  199. } else {
  200. $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
  201. Minz_Log::error('SQL error changeCategory: ' . $info[2]);
  202. return false;
  203. }
  204. }
  205. /** @return int|false */
  206. public function deleteFeed(int $id) {
  207. $sql = 'DELETE FROM `_feed` WHERE id=?';
  208. $stm = $this->pdo->prepare($sql);
  209. $values = array($id);
  210. if ($stm && $stm->execute($values)) {
  211. return $stm->rowCount();
  212. } else {
  213. $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
  214. Minz_Log::error('SQL error deleteFeed: ' . $info[2]);
  215. return false;
  216. }
  217. }
  218. /**
  219. * @param bool|null $muted to include only muted feeds
  220. * @return int|false
  221. */
  222. public function deleteFeedByCategory(int $id, $muted = null) {
  223. $sql = 'DELETE FROM `_feed` WHERE category=?';
  224. if ($muted) {
  225. $sql .= ' AND ttl < 0';
  226. }
  227. $stm = $this->pdo->prepare($sql);
  228. $values = array($id);
  229. if ($stm && $stm->execute($values)) {
  230. return $stm->rowCount();
  231. } else {
  232. $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
  233. Minz_Log::error('SQL error deleteFeedByCategory: ' . $info[2]);
  234. return false;
  235. }
  236. }
  237. public function selectAll() {
  238. $sql = <<<'SQL'
  239. SELECT id, url, kind, category, name, website, description, `lastUpdate`,
  240. priority, `pathEntries`, `httpAuth`, error, ttl, attributes
  241. FROM `_feed`
  242. SQL;
  243. $stm = $this->pdo->query($sql);
  244. while ($row = $stm->fetch(PDO::FETCH_ASSOC)) {
  245. yield $row;
  246. }
  247. }
  248. /**
  249. * @return FreshRSS_Feed|null
  250. */
  251. public function searchById($id) {
  252. $sql = 'SELECT * FROM `_feed` WHERE id=:id';
  253. $stm = $this->pdo->prepare($sql);
  254. $stm->bindParam(':id', $id, PDO::PARAM_INT);
  255. $stm->execute();
  256. $res = $stm->fetchAll(PDO::FETCH_ASSOC);
  257. $feed = self::daoToFeed($res);
  258. return $feed[$id] ?? null;
  259. }
  260. /**
  261. * @return FreshRSS_Feed|null
  262. */
  263. public function searchByUrl(string $url) {
  264. $sql = 'SELECT * FROM `_feed` WHERE url=?';
  265. $stm = $this->pdo->prepare($sql);
  266. $values = array($url);
  267. $stm->execute($values);
  268. $res = $stm->fetchAll(PDO::FETCH_ASSOC);
  269. $feed = current(self::daoToFeed($res));
  270. return $feed == false ? null : $feed;
  271. }
  272. public function listFeedsIds(): array {
  273. $sql = 'SELECT id FROM `_feed`';
  274. $stm = $this->pdo->query($sql);
  275. return $stm->fetchAll(PDO::FETCH_COLUMN, 0);
  276. }
  277. /**
  278. * @return array<FreshRSS_Feed>
  279. */
  280. public function listFeeds(): array {
  281. $sql = 'SELECT * FROM `_feed` ORDER BY name';
  282. $stm = $this->pdo->query($sql);
  283. return self::daoToFeed($stm->fetchAll(PDO::FETCH_ASSOC));
  284. }
  285. public function listFeedsNewestItemUsec($id_feed = null) {
  286. $sql = 'SELECT id_feed, MAX(id) as newest_item_us FROM `_entry` ';
  287. if ($id_feed === null) {
  288. $sql .= 'GROUP BY id_feed';
  289. } else {
  290. $sql .= 'WHERE id_feed=' . intval($id_feed);
  291. }
  292. $stm = $this->pdo->query($sql);
  293. $res = $stm->fetchAll(PDO::FETCH_ASSOC);
  294. $newestItemUsec = [];
  295. foreach ($res as $line) {
  296. $newestItemUsec['f_' . $line['id_feed']] = $line['newest_item_us'];
  297. }
  298. return $newestItemUsec;
  299. }
  300. /**
  301. * Use $defaultCacheDuration == -1 to return all feeds, without filtering them by TTL.
  302. * @return array<FreshRSS_Feed>
  303. */
  304. public function listFeedsOrderUpdate(int $defaultCacheDuration = 3600, int $limit = 0) {
  305. $this->updateTTL();
  306. $sql = 'SELECT id, url, kind, name, website, `lastUpdate`, `pathEntries`, `httpAuth`, ttl, attributes '
  307. . 'FROM `_feed` '
  308. . ($defaultCacheDuration < 0 ? '' : 'WHERE ttl >= ' . FreshRSS_Feed::TTL_DEFAULT
  309. . ' AND `lastUpdate` < (' . (time() + 60)
  310. . '-(CASE WHEN ttl=' . FreshRSS_Feed::TTL_DEFAULT . ' THEN ' . intval($defaultCacheDuration) . ' ELSE ttl END)) ')
  311. . 'ORDER BY `lastUpdate` '
  312. . ($limit < 1 ? '' : 'LIMIT ' . intval($limit));
  313. $stm = $this->pdo->query($sql);
  314. if ($stm !== false) {
  315. return self::daoToFeed($stm->fetchAll(PDO::FETCH_ASSOC));
  316. } else {
  317. $info = $this->pdo->errorInfo();
  318. if ($this->autoUpdateDb($info)) {
  319. return $this->listFeedsOrderUpdate($defaultCacheDuration, $limit);
  320. }
  321. Minz_Log::error('SQL error listFeedsOrderUpdate: ' . $info[2]);
  322. return array();
  323. }
  324. }
  325. public function listTitles(int $id, int $limit = 0) {
  326. $sql = 'SELECT title FROM `_entry` WHERE id_feed=:id_feed ORDER BY id DESC'
  327. . ($limit < 1 ? '' : ' LIMIT ' . intval($limit));
  328. $stm = $this->pdo->prepare($sql);
  329. $stm->bindParam(':id_feed', $id, PDO::PARAM_INT);
  330. if ($stm && $stm->execute()) {
  331. return $stm->fetchAll(PDO::FETCH_COLUMN, 0);
  332. }
  333. return false;
  334. }
  335. /**
  336. * @param bool|null $muted to include only muted feeds
  337. * @return array<FreshRSS_Feed>
  338. */
  339. public function listByCategory(int $cat, $muted = null): array {
  340. $sql = 'SELECT * FROM `_feed` WHERE category=?';
  341. if ($muted) {
  342. $sql .= ' AND ttl < 0';
  343. }
  344. $stm = $this->pdo->prepare($sql);
  345. $stm->execute(array($cat));
  346. $feeds = self::daoToFeed($stm->fetchAll(PDO::FETCH_ASSOC));
  347. usort($feeds, function ($a, $b) {
  348. return strnatcasecmp($a->name(), $b->name());
  349. });
  350. return $feeds;
  351. }
  352. public function countEntries(int $id) {
  353. $sql = 'SELECT COUNT(*) AS count FROM `_entry` WHERE id_feed=?';
  354. $stm = $this->pdo->prepare($sql);
  355. $values = array($id);
  356. $stm->execute($values);
  357. $res = $stm->fetchAll(PDO::FETCH_ASSOC);
  358. return $res[0]['count'];
  359. }
  360. public function countNotRead(int $id) {
  361. $sql = 'SELECT COUNT(*) AS count FROM `_entry` WHERE id_feed=? AND is_read=0';
  362. $stm = $this->pdo->prepare($sql);
  363. $values = array($id);
  364. $stm->execute($values);
  365. $res = $stm->fetchAll(PDO::FETCH_ASSOC);
  366. return $res[0]['count'];
  367. }
  368. /**
  369. * @return int|false
  370. */
  371. public function updateCachedValues(int $id = 0) {
  372. //2 sub-requests with FOREIGN KEY(e.id_feed), INDEX(e.is_read) faster than 1 request with GROUP BY or CASE
  373. $sql = 'UPDATE `_feed` '
  374. . 'SET `cache_nbEntries`=(SELECT COUNT(e1.id) FROM `_entry` e1 WHERE e1.id_feed=`_feed`.id),'
  375. . '`cache_nbUnreads`=(SELECT COUNT(e2.id) FROM `_entry` e2 WHERE e2.id_feed=`_feed`.id AND e2.is_read=0)'
  376. . ($id != 0 ? ' WHERE id=:id' : '');
  377. $stm = $this->pdo->prepare($sql);
  378. if ($id != 0) {
  379. $stm->bindParam(':id', $id, PDO::PARAM_INT);
  380. }
  381. if ($stm && $stm->execute()) {
  382. return $stm->rowCount();
  383. } else {
  384. $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
  385. Minz_Log::error('SQL error updateCachedValue: ' . $info[2]);
  386. return false;
  387. }
  388. }
  389. /**
  390. * Remember to call updateCachedValues() after calling this function
  391. * @return int|false number of lines affected or false in case of error
  392. */
  393. public function keepMaxUnread(int $id, int $n) {
  394. //Double SELECT for MySQL workaround ERROR 1093 (HY000)
  395. $sql = <<<'SQL'
  396. UPDATE `_entry` SET is_read=1
  397. WHERE id_feed=:id_feed1 AND is_read=0 AND id <= (SELECT e3.id FROM (
  398. SELECT e2.id FROM `_entry` e2
  399. WHERE e2.id_feed=:id_feed2 AND e2.is_read=0
  400. ORDER BY e2.id DESC
  401. LIMIT 1
  402. OFFSET :limit) e3)
  403. SQL;
  404. if (($stm = $this->pdo->prepare($sql)) &&
  405. $stm->bindParam(':id_feed1', $id, PDO::PARAM_INT) &&
  406. $stm->bindParam(':id_feed2', $id, PDO::PARAM_INT) &&
  407. $stm->bindParam(':limit', $n, PDO::PARAM_INT) &&
  408. $stm->execute()) {
  409. return $stm->rowCount();
  410. } else {
  411. $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
  412. Minz_Log::error('SQL error keepMaxUnread: ' . json_encode($info));
  413. return false;
  414. }
  415. }
  416. /**
  417. * Remember to call updateCachedValues() after calling this function
  418. * @return int|false number of lines affected or false in case of error
  419. */
  420. public function markAsReadUponGone(int $id) {
  421. //Double SELECT for MySQL workaround ERROR 1093 (HY000)
  422. $sql = <<<'SQL'
  423. UPDATE `_entry` SET is_read=1
  424. WHERE id_feed=:id_feed1 AND is_read=0 AND `lastSeen` < (SELECT e3.maxlastseen FROM (
  425. SELECT MAX(e2.`lastSeen`) AS maxlastseen FROM `_entry` e2 WHERE e2.id_feed = :id_feed2) e3)
  426. SQL;
  427. if (($stm = $this->pdo->prepare($sql)) &&
  428. $stm->bindParam(':id_feed1', $id, PDO::PARAM_INT) &&
  429. $stm->bindParam(':id_feed2', $id, PDO::PARAM_INT) &&
  430. $stm->execute()) {
  431. return $stm->rowCount();
  432. } else {
  433. $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
  434. Minz_Log::error('SQL error markAsReadUponGone: ' . json_encode($info));
  435. return false;
  436. }
  437. }
  438. /**
  439. * @return int|false
  440. */
  441. public function truncate(int $id) {
  442. $sql = 'DELETE FROM `_entry` WHERE id_feed=:id';
  443. $stm = $this->pdo->prepare($sql);
  444. $stm->bindParam(':id', $id, PDO::PARAM_INT);
  445. $this->pdo->beginTransaction();
  446. if (!($stm && $stm->execute())) {
  447. $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
  448. Minz_Log::error('SQL error truncate: ' . $info[2]);
  449. $this->pdo->rollBack();
  450. return false;
  451. }
  452. $affected = $stm->rowCount();
  453. $sql = 'UPDATE `_feed` '
  454. . 'SET `cache_nbEntries`=0, `cache_nbUnreads`=0, `lastUpdate`=0 WHERE id=:id';
  455. $stm = $this->pdo->prepare($sql);
  456. $stm->bindParam(':id', $id, PDO::PARAM_INT);
  457. if (!($stm && $stm->execute())) {
  458. $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
  459. Minz_Log::error('SQL error truncate: ' . $info[2]);
  460. $this->pdo->rollBack();
  461. return false;
  462. }
  463. $this->pdo->commit();
  464. return $affected;
  465. }
  466. public function purge() {
  467. $sql = 'DELETE FROM `_entry`';
  468. $stm = $this->pdo->prepare($sql);
  469. $this->pdo->beginTransaction();
  470. if (!($stm && $stm->execute())) {
  471. $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
  472. Minz_Log::error('SQL error truncate: ' . $info[2]);
  473. $this->pdo->rollBack();
  474. return false;
  475. }
  476. $sql = 'UPDATE `_feed` SET `cache_nbEntries` = 0, `cache_nbUnreads` = 0';
  477. $stm = $this->pdo->prepare($sql);
  478. if (!($stm && $stm->execute())) {
  479. $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
  480. Minz_Log::error('SQL error truncate: ' . $info[2]);
  481. $this->pdo->rollBack();
  482. return false;
  483. }
  484. $this->pdo->commit();
  485. }
  486. /**
  487. * @return array<FreshRSS_Feed>
  488. */
  489. public static function daoToFeed($listDAO, $catID = null): array {
  490. $list = array();
  491. if (!is_array($listDAO)) {
  492. $listDAO = array($listDAO);
  493. }
  494. foreach ($listDAO as $key => $dao) {
  495. if (!isset($dao['name'])) {
  496. continue;
  497. }
  498. if (isset($dao['id'])) {
  499. $key = $dao['id'];
  500. }
  501. if ($catID === null) {
  502. $category = isset($dao['category']) ? $dao['category'] : 0;
  503. } else {
  504. $category = $catID;
  505. }
  506. $myFeed = new FreshRSS_Feed($dao['url'] ?? '', false);
  507. $myFeed->_kind($dao['kind'] ?? FreshRSS_Feed::KIND_RSS);
  508. $myFeed->_categoryId($category);
  509. $myFeed->_name($dao['name']);
  510. $myFeed->_website($dao['website'] ?? '', false);
  511. $myFeed->_description($dao['description'] ?? '');
  512. $myFeed->_lastUpdate($dao['lastUpdate'] ?? 0);
  513. $myFeed->_priority($dao['priority'] ?? 10);
  514. $myFeed->_pathEntries($dao['pathEntries'] ?? '');
  515. $myFeed->_httpAuth(base64_decode($dao['httpAuth'] ?? ''));
  516. $myFeed->_error($dao['error'] ?? 0);
  517. $myFeed->_ttl($dao['ttl'] ?? FreshRSS_Feed::TTL_DEFAULT);
  518. $myFeed->_attributes('', $dao['attributes'] ?? '');
  519. $myFeed->_nbNotRead($dao['cache_nbUnreads'] ?? 0);
  520. $myFeed->_nbEntries($dao['cache_nbEntries'] ?? 0);
  521. if (isset($dao['id'])) {
  522. $myFeed->_id($dao['id']);
  523. }
  524. $list[$key] = $myFeed;
  525. }
  526. return $list;
  527. }
  528. public function updateTTL() {
  529. $sql = 'UPDATE `_feed` SET ttl=:new_value WHERE ttl=:old_value';
  530. $stm = $this->pdo->prepare($sql);
  531. if (!($stm && $stm->execute(array(':new_value' => FreshRSS_Feed::TTL_DEFAULT, ':old_value' => -2)))) {
  532. $info = $stm == null ? $this->pdo->errorInfo() : $stm->errorInfo();
  533. Minz_Log::error('SQL warning updateTTL 1: ' . $info[2] . ' ' . $sql);
  534. $sql2 = 'ALTER TABLE `_feed` ADD COLUMN ttl INT NOT NULL DEFAULT ' . FreshRSS_Feed::TTL_DEFAULT; //v0.7.3
  535. $stm = $this->pdo->query($sql2);
  536. if ($stm === false) {
  537. $info = $this->pdo->errorInfo();
  538. Minz_Log::error('SQL error updateTTL 2: ' . $info[2] . ' ' . $sql2);
  539. }
  540. } else {
  541. $stm->execute(array(':new_value' => -3600, ':old_value' => -1));
  542. }
  543. }
  544. /**
  545. * @return int|false
  546. */
  547. public function count() {
  548. $sql = 'SELECT COUNT(e.id) AS count FROM `_feed` e';
  549. $stm = $this->pdo->query($sql);
  550. if ($stm == false) {
  551. return false;
  552. }
  553. $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0);
  554. return isset($res[0]) ? $res[0] : 0;
  555. }
  556. }