From d6f414108667f32fe2b480adeb7ec9c218db2f4a Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Thu, 3 Jul 2014 21:26:30 +0200 Subject: Preparation for SQLite https://github.com/marienfressinaud/FreshRSS/issues/100 --- lib/Minz/ModelPdo.php | 52 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 20 deletions(-) (limited to 'lib/Minz/ModelPdo.php') diff --git a/lib/Minz/ModelPdo.php b/lib/Minz/ModelPdo.php index 831df13a2..cbbc0bebc 100644 --- a/lib/Minz/ModelPdo.php +++ b/lib/Minz/ModelPdo.php @@ -16,6 +16,7 @@ class Minz_ModelPdo { public static $useSharedBd = true; private static $sharedBd = null; private static $sharedPrefix; + protected static $sharedDbType; /** * $bd variable représentant la base de données @@ -24,46 +25,57 @@ class Minz_ModelPdo { protected $prefix; + public function dbType() { + return self::$sharedDbType; + } + /** * Créé la connexion à la base de données à l'aide des variables * HOST, BASE, USER et PASS définies dans le fichier de configuration */ - public function __construct () { + public function __construct() { if (self::$useSharedBd && self::$sharedBd != null) { $this->bd = self::$sharedBd; $this->prefix = self::$sharedPrefix; return; } - $db = Minz_Configuration::dataBase (); - $driver_options = null; + $db = Minz_Configuration::dataBase(); try { $type = $db['type']; - if($type == 'mysql') { - $string = $type - . ':host=' . $db['host'] + if ($type === 'mysql') { + $string = 'mysql:host=' . $db['host'] . ';dbname=' . $db['base'] . ';charset=utf8'; $driver_options = array( - PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8' + PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8', + ); + $this->prefix = $db['prefix'] . Minz_Session::param('currentUser', '_') . '_'; + } elseif ($type === 'sqlite') { + $string = 'sqlite:' . DATA_PATH . '/' . Minz_Session::param('currentUser', '_') . '.sqlite'; + $driver_options = array( + //PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + ); + $this->prefix = ''; + } else { + throw new Minz_PDOConnectionException( + 'Invalid database type!', + $db['user'], Minz_Exception::ERROR ); - } elseif($type == 'sqlite') { - $string = $type . ':/' . DATA_PATH . $db['base'] . '.sqlite'; //TODO: DEBUG UTF-8 http://www.siteduzero.com/forum/sujet/sqlite-connexion-utf-8-18797 } + self::$sharedDbType = $type; + self::$sharedPrefix = $this->prefix; - $this->bd = new FreshPDO ( + $this->bd = new FreshPDO( $string, $db['user'], $db['password'], $driver_options ); self::$sharedBd = $this->bd; - - $this->prefix = $db['prefix'] . Minz_Session::param('currentUser', '_') . '_'; - self::$sharedPrefix = $this->prefix; } catch (Exception $e) { - throw new Minz_PDOConnectionException ( + throw new Minz_PDOConnectionException( $string, $db['user'], Minz_Exception::ERROR ); @@ -81,15 +93,15 @@ class Minz_ModelPdo { } public function size($all = false) { - $db = Minz_Configuration::dataBase (); + $db = Minz_Configuration::dataBase(); $sql = 'SELECT SUM(data_length + index_length) FROM information_schema.TABLES WHERE table_schema = ?'; - $values = array ($db['base']); + $values = array($db['base']); if (!$all) { $sql .= ' AND table_name LIKE ?'; $values[] = $this->prefix . '%'; } - $stm = $this->bd->prepare ($sql); - $stm->execute ($values); + $stm = $this->bd->prepare($sql); + $stm->execute($values); $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0); return $res[0]; } @@ -107,12 +119,12 @@ class FreshPDO extends PDO { } } - public function prepare ($statement, $driver_options = array()) { + public function prepare($statement, $driver_options = array()) { FreshPDO::check($statement); return parent::prepare($statement, $driver_options); } - public function exec ($statement) { + public function exec($statement) { FreshPDO::check($statement); return parent::exec($statement); } -- cgit v1.2.3 From 805c91da98c2f582e279f3c853fba9e43f572419 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sat, 5 Jul 2014 01:52:41 +0200 Subject: Add support for SQLite https://github.com/marienfressinaud/FreshRSS/issues/100 Warning: MySQL has been changed too, so bugs may have been introduced --- CHANGELOG | 2 + README.md | 2 +- app/Models/CategoryDAO.php | 4 +- app/Models/EntryDAO.php | 526 +++++++++++++++++------------------------- app/Models/EntryDAOSQLite.php | 111 ++++++++- app/Models/FeedDAO.php | 218 ++++++++--------- app/Models/FeedDAOSQLite.php | 14 ++ lib/Minz/ModelPdo.php | 14 -- p/api/greader.php | 12 +- 9 files changed, 441 insertions(+), 462 deletions(-) (limited to 'lib/Minz/ModelPdo.php') diff --git a/CHANGELOG b/CHANGELOG index 9325ee724..4ca0199e1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,8 @@ ## 2014-xx-xx FreshRSS 0.7.x +* SQL + * Add support for SQLite (beta) in addition to MySQL * SimplePie * Complies with HTTP "301 Moved Permanently" responses by automatically updating the URL of feeds that have changed address. diff --git a/README.md b/README.md index ce3c8ef32..fff08472b 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Privilégiez pour cela des demandes sur GitHub * PHP 5.2.1+ (PHP 5.3.7+ recommandé) * Requis : [PDO_MySQL](http://php.net/pdo-mysql), [cURL](http://php.net/curl), [LibXML](http://php.net/xml), [PCRE](http://php.net/pcre), [ctype](http://php.net/ctype) * Recommandés : [JSON](http://php.net/json), [zlib](http://php.net/zlib), [mbstring](http://php.net/mbstring), [iconv](http://php.net/iconv), [Zip](http://php.net/zip) -* MySQL 5.0.3+ (ou SQLite 3.7.4+ à venir) +* MySQL 5.0.3+ (recommandé) ou SQLite 3.7.4+ (en bêta) * Un navigateur Web récent tel Firefox 4+, Chrome, Opera, Safari, Internet Explorer 9+ * Fonctionne aussi sur mobile diff --git a/app/Models/CategoryDAO.php b/app/Models/CategoryDAO.php index 3003dea0d..f11f87f47 100644 --- a/app/Models/CategoryDAO.php +++ b/app/Models/CategoryDAO.php @@ -102,7 +102,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo { $sql = 'SELECT c.id AS c_id, c.name AS c_name, ' . ($details ? 'f.* ' : 'f.id, f.name, f.url, f.website, f.priority, f.error, f.cache_nbEntries, f.cache_nbUnreads ') . 'FROM `' . $this->prefix . 'category` c ' - . 'LEFT OUTER JOIN `' . $this->prefix . 'feed` f ON f.category = c.id ' + . 'LEFT OUTER JOIN `' . $this->prefix . 'feed` f ON f.category=c.id ' . 'GROUP BY f.id ' . 'ORDER BY c.name, f.name'; $stm = $this->bd->prepare ($sql); @@ -166,7 +166,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo { } public function countNotRead ($id) { - $sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id WHERE category=? AND e.is_read=0'; + $sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id WHERE category=? AND e.is_read=0'; $stm = $this->bd->prepare ($sql); $values = array ($id); $stm->execute ($values); diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index 9ba0d2128..1775af63c 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -6,16 +6,16 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { return parent::$sharedDbType !== 'sqlite'; } - public function addEntry ($valuesTmp) { + public function addEntry($valuesTmp) { $sql = 'INSERT INTO `' . $this->prefix . 'entry`(id, guid, title, author, ' . ($this->isCompressed() ? 'content_bin' : 'content') . ', link, date, is_read, is_favorite, id_feed, tags) ' . 'VALUES(?, ?, ?, ?, ' . ($this->isCompressed() ? 'COMPRESS(?)' : '?') . ', ?, ?, ?, ?, ?, ?)'; - $stm = $this->bd->prepare ($sql); + $stm = $this->bd->prepare($sql); - $values = array ( + $values = array( $valuesTmp['id'], substr($valuesTmp['guid'], 0, 760), substr($valuesTmp['title'], 0, 255), @@ -29,7 +29,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { substr($valuesTmp['tags'], 0, 1023), ); - if ($stm && $stm->execute ($values)) { + if ($stm && $stm->execute($values)) { return $this->bd->lastInsertId(); } else { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); @@ -78,13 +78,13 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { if (!is_array($ids)) { $ids = array($ids); } - $sql = 'UPDATE `' . $this->prefix . 'entry` e ' - . 'SET e.is_favorite = ? ' - . 'WHERE e.id IN (' . str_repeat('?,', count($ids) - 1). '?)'; - $values = array ($is_favorite ? 1 : 0); + $sql = 'UPDATE `' . $this->prefix . 'entry` ' + . 'SET is_favorite=? ' + . 'WHERE id IN (' . str_repeat('?,', count($ids) - 1). '?)'; + $values = array($is_favorite ? 1 : 0); $values = array_merge($values, $ids); - $stm = $this->bd->prepare ($sql); - if ($stm && $stm->execute ($values)) { + $stm = $this->bd->prepare($sql); + if ($stm && $stm->execute($values)) { return $stm->rowCount(); } else { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); @@ -93,8 +93,38 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { } } + protected function updateCacheUnreads($catId = false, $feedId = false) { + $sql = 'UPDATE `' . $this->prefix . 'feed` f ' + . 'LEFT OUTER JOIN (' + . 'SELECT e.id_feed, ' + . 'COUNT(*) AS nbUnreads ' + . 'FROM `' . $this->prefix . 'entry` e ' + . 'WHERE e.is_read=0 ' + . 'GROUP BY e.id_feed' + . ') x ON x.id_feed=f.id ' + . 'SET f.cache_nbUnreads=COALESCE(x.nbUnreads, 0) ' + . 'WHERE 1'; + $values = array(); + if ($feedId !== false) { + $sql .= ' AND f.id=?'; + $values[] = $id; + } + if ($catId !== false) { + $sql .= ' AND f.category=?'; + $values[] = $catId; + } + $stm = $this->bd->prepare($sql); + if ($stm && $stm->execute($values)) { + return true; + } else { + $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + Minz_Log::record('SQL error updateCacheUnreads: ' . $info[2], Minz_Log::ERROR); + return false; + } + } + public function markRead($ids, $is_read = true) { - if (is_array($ids)) { //Many IDs at once + if (is_array($ids)) { //Many IDs at once (used by API) if (count($ids) < 6) { //Speed heuristics $affected = 0; foreach ($ids as $id) { @@ -104,9 +134,9 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { } $this->bd->beginTransaction(); - $sql = 'UPDATE `' . $this->prefix . 'entry` e ' - . 'SET e.is_read = ? ' - . 'WHERE e.id IN (' . str_repeat('?,', count($ids) - 1). '?)'; + $sql = 'UPDATE `' . $this->prefix . 'entry` ' + . 'SET is_read=? ' + . 'WHERE id IN (' . str_repeat('?,', count($ids) - 1). '?)'; $values = array($is_read ? 1 : 0); $values = array_merge($values, $ids); $stm = $this->bd->prepare($sql); @@ -117,34 +147,18 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { return false; } $affected = $stm->rowCount(); - - if ($affected > 0) { - $sql = 'UPDATE `' . $this->prefix . 'feed` f ' - . 'INNER JOIN (' - . 'SELECT e.id_feed, ' - . 'COUNT(CASE WHEN e.is_read = 0 THEN 1 END) AS nbUnreads, ' - . 'COUNT(e.id) AS nbEntries ' - . 'FROM `' . $this->prefix . 'entry` e ' - . 'GROUP BY e.id_feed' - . ') x ON x.id_feed=f.id ' - . 'SET f.cache_nbEntries=x.nbEntries, f.cache_nbUnreads=x.nbUnreads'; - $stm = $this->bd->prepare($sql); - if (!($stm && $stm->execute())) { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::record('SQL error markRead: ' . $info[2], Minz_Log::ERROR); - $this->bd->rollBack(); - return false; - } + if (($affected > 0) && (!$this->updateCacheUnreads(false, false))) { + $this->bd->rollBack(); + return false; } - $this->bd->commit(); return $affected; } else { - $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id ' - . 'SET e.is_read = ?,' + $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id ' + . 'SET e.is_read=?,' . 'f.cache_nbUnreads=f.cache_nbUnreads' . ($is_read ? '-' : '+') . '1 ' - . 'WHERE e.id=? AND e.is_read<>?'; - $values = array($is_read ? 1 : 0, $ids, $is_read ? 1 : 0); + . 'WHERE e.id=? AND e.is_read=?'; + $values = array($is_read ? 1 : 0, $ids, $is_read ? 0 : 1); $stm = $this->bd->prepare($sql); if ($stm && $stm->execute($values)) { return $stm->rowCount(); @@ -156,267 +170,139 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { } } - public function markReadEntries ($idMax = 0, $onlyFavorites = false, $priorityMin = 0) { + public function markReadEntries($idMax = 0, $onlyFavorites = false, $priorityMin = 0) { if ($idMax == 0) { - $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id ' - . 'SET e.is_read = 1, f.cache_nbUnreads=0 ' - . 'WHERE e.is_read = 0'; - if ($onlyFavorites) { - $sql .= ' AND e.is_favorite = 1'; - } elseif ($priorityMin >= 0) { - $sql .= ' AND f.priority > ' . intval($priorityMin); - } - $stm = $this->bd->prepare ($sql); - if ($stm && $stm->execute ()) { - return $stm->rowCount(); - } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::record('SQL error markReadEntries: ' . $info[2], Minz_Log::ERROR); - return false; - } - } else { - $this->bd->beginTransaction (); - - $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id ' - . 'SET e.is_read = 1 ' - . 'WHERE e.is_read = 0 AND e.id <= ?'; - if ($onlyFavorites) { - $sql .= ' AND e.is_favorite = 1'; - } elseif ($priorityMin >= 0) { - $sql .= ' AND f.priority > ' . intval($priorityMin); - } - $values = array ($idMax); - $stm = $this->bd->prepare ($sql); - if (!($stm && $stm->execute ($values))) { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::record('SQL error markReadEntries: ' . $info[2], Minz_Log::ERROR); - $this->bd->rollBack (); - return false; - } - $affected = $stm->rowCount(); - - if ($affected > 0) { - $sql = 'UPDATE `' . $this->prefix . 'feed` f ' - . 'LEFT OUTER JOIN (' - . 'SELECT e.id_feed, ' - . 'COUNT(*) AS nbUnreads ' - . 'FROM `' . $this->prefix . 'entry` e ' - . 'WHERE e.is_read = 0 ' - . 'GROUP BY e.id_feed' - . ') x ON x.id_feed=f.id ' - . 'SET f.cache_nbUnreads=COALESCE(x.nbUnreads, 0)'; - $stm = $this->bd->prepare ($sql); - if (!($stm && $stm->execute ())) { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::record('SQL error markReadEntries: ' . $info[2], Minz_Log::ERROR); - $this->bd->rollBack (); - return false; - } - } - - $this->bd->commit (); - return $affected; + $idMax = time() . '000000'; + Minz_Log::record($nb . 'Calling markReadEntries(0) is deprecated!', Minz_Log::DEBUG); + } + $this->bd->beginTransaction(); + + $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id ' + . 'SET e.is_read=1 ' + . 'WHERE e.is_read=0 AND e.id <= ?'; + if ($onlyFavorites) { + $sql .= ' AND e.is_favorite=1'; + } elseif ($priorityMin >= 0) { + $sql .= ' AND f.priority > ' . intval($priorityMin); + } + $values = array($idMax); + $stm = $this->bd->prepare($sql); + if (!($stm && $stm->execute($values))) { + $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + Minz_Log::record('SQL error markReadEntries: ' . $info[2], Minz_Log::ERROR); + $this->bd->rollBack(); + return false; + } + $affected = $stm->rowCount(); + if (($affected > 0) && (!$this->updateCacheUnreads(false, false))) { + $this->bd->rollBack(); + return false; } + $this->bd->commit(); + return $affected; } - public function markReadCat ($id, $idMax = 0) { + public function markReadCat($id, $idMax = 0) { if ($idMax == 0) { - $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id ' - . 'SET e.is_read = 1, f.cache_nbUnreads=0 ' - . 'WHERE f.category = ? AND e.is_read = 0'; - $values = array ($id); - $stm = $this->bd->prepare ($sql); - if ($stm && $stm->execute ($values)) { - return $stm->rowCount(); - } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::record('SQL error markReadCat: ' . $info[2], Minz_Log::ERROR); - return false; - } - } else { - $this->bd->beginTransaction (); - - $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id ' - . 'SET e.is_read = 1 ' - . 'WHERE f.category = ? AND e.is_read = 0 AND e.id <= ?'; - $values = array ($id, $idMax); - $stm = $this->bd->prepare ($sql); - if (!($stm && $stm->execute ($values))) { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::record('SQL error markReadCat: ' . $info[2], Minz_Log::ERROR); - $this->bd->rollBack (); - return false; - } - $affected = $stm->rowCount(); - - if ($affected > 0) { - $sql = 'UPDATE `' . $this->prefix . 'feed` f ' - . 'LEFT OUTER JOIN (' - . 'SELECT e.id_feed, ' - . 'COUNT(*) AS nbUnreads ' - . 'FROM `' . $this->prefix . 'entry` e ' - . 'WHERE e.is_read = 0 ' - . 'GROUP BY e.id_feed' - . ') x ON x.id_feed=f.id ' - . 'SET f.cache_nbUnreads=COALESCE(x.nbUnreads, 0) ' - . 'WHERE f.category = ?'; - $values = array ($id); - $stm = $this->bd->prepare ($sql); - if (!($stm && $stm->execute ($values))) { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::record('SQL error markReadCat: ' . $info[2], Minz_Log::ERROR); - $this->bd->rollBack (); - return false; - } - } + $idMax = time() . '000000'; + Minz_Log::record($nb . 'Calling markReadCat(0) is deprecated!', Minz_Log::DEBUG); + } + $this->bd->beginTransaction(); - $this->bd->commit (); - return $affected; + $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id ' + . 'SET e.is_read=1 ' + . 'WHERE f.category=? AND e.is_read=0 AND e.id <= ?'; + $values = array($id, $idMax); + $stm = $this->bd->prepare($sql); + if (!($stm && $stm->execute($values))) { + $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + Minz_Log::record('SQL error markReadCat: ' . $info[2], Minz_Log::ERROR); + $this->bd->rollBack(); + return false; } + $affected = $stm->rowCount(); + if (($affected > 0) && (!$this->updateCacheUnreads($id, false))) { + $this->bd->rollBack(); + return false; + } + $this->bd->commit(); + return $affected; } - public function markReadCatName($name, $idMax = 0) { + public function markReadFeed($id, $idMax = 0) { if ($idMax == 0) { - $sql = 'UPDATE `' . $this->prefix . 'entry` e ' - . 'INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id ' - . 'INNER JOIN `' . $this->prefix . 'category` c ON c.id = f.category ' - . 'SET e.is_read = 1, f.cache_nbUnreads=0 ' - . 'WHERE c.name = ?'; - $values = array($name); - $stm = $this->bd->prepare($sql); - if ($stm && $stm->execute($values)) { - return $stm->rowCount(); - } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::record('SQL error markReadCatName: ' . $info[2], Minz_Log::ERROR); - return false; - } - } else { - $this->bd->beginTransaction(); + $idMax = time() . '000000'; + Minz_Log::record($nb . 'Calling markReadFeed(0) is deprecated!', Minz_Log::DEBUG); + } + $this->bd->beginTransaction(); - $sql = 'UPDATE `' . $this->prefix . 'entry` e ' - . 'INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id ' - . 'INNER JOIN `' . $this->prefix . 'category` c ON c.id = f.category ' - . 'SET e.is_read = 1 ' - . 'WHERE c.name = ? AND e.id <= ?'; - $values = array($name, $idMax); + $sql = 'UPDATE `' . $this->prefix . 'entry` ' + . 'SET is_read=1 ' + . 'WHERE id_feed=? AND is_read=0 AND id <= ?'; + $values = array($id, $idMax); + $stm = $this->bd->prepare($sql); + if (!($stm && $stm->execute($values))) { + $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + Minz_Log::record('SQL error markReadFeed: ' . $info[2], Minz_Log::ERROR); + $this->bd->rollBack(); + return false; + } + $affected = $stm->rowCount(); + + if ($affected > 0) { + $sql = 'UPDATE `' . $this->prefix . 'feed` ' + . 'SET cache_nbUnreads=cache_nbUnreads-' . $affected + . ' WHERE id=?'; + $values = array($id); $stm = $this->bd->prepare($sql); if (!($stm && $stm->execute($values))) { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::record('SQL error markReadCatName: ' . $info[2], Minz_Log::ERROR); + Minz_Log::record('SQL error markReadFeed: ' . $info[2], Minz_Log::ERROR); $this->bd->rollBack(); return false; } - $affected = $stm->rowCount(); - - if ($affected > 0) { - $sql = 'UPDATE `' . $this->prefix . 'feed` f ' - . 'LEFT OUTER JOIN (' - . 'SELECT e.id_feed, ' - . 'COUNT(*) AS nbUnreads ' - . 'FROM `' . $this->prefix . 'entry` e ' - . 'WHERE e.is_read = 0 ' - . 'GROUP BY e.id_feed' - . ') x ON x.id_feed=f.id ' - . 'INNER JOIN `' . $this->prefix . 'category` c ON c.id = f.category ' - . 'SET f.cache_nbUnreads=COALESCE(x.nbUnreads, 0) ' - . 'WHERE c.name = ?'; - $values = array($name); - $stm = $this->bd->prepare($sql); - if (!($stm && $stm->execute($values))) { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::record('SQL error markReadCatName: ' . $info[2], Minz_Log::ERROR); - $this->bd->rollBack(); - return false; - } - } - - $this->bd->commit(); - return $affected; } - } - public function markReadFeed ($id, $idMax = 0) { - if ($idMax == 0) { - $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id ' - . 'SET e.is_read = 1, f.cache_nbUnreads=0 ' - . 'WHERE f.id=? AND e.is_read = 0'; - $values = array ($id); - $stm = $this->bd->prepare ($sql); - if ($stm && $stm->execute ($values)) { - return $stm->rowCount(); - } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::record('SQL error markReadFeed: ' . $info[2], Minz_Log::ERROR); - return false; - } - } else { - $this->bd->beginTransaction (); - - $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id ' - . 'SET e.is_read = 1 ' - . 'WHERE f.id=? AND e.is_read = 0 AND e.id <= ?'; - $values = array ($id, $idMax); - $stm = $this->bd->prepare ($sql); - if (!($stm && $stm->execute ($values))) { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::record('SQL error markReadFeed: ' . $info[2], Minz_Log::ERROR); - $this->bd->rollBack (); - return false; - } - $affected = $stm->rowCount(); - - if ($affected > 0) { - $sql = 'UPDATE `' . $this->prefix . 'feed` f ' - . 'SET f.cache_nbUnreads=f.cache_nbUnreads-' . $affected - . ' WHERE f.id=?'; - $values = array ($id); - $stm = $this->bd->prepare ($sql); - if (!($stm && $stm->execute ($values))) { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::record('SQL error markReadFeed: ' . $info[2], Minz_Log::ERROR); - $this->bd->rollBack (); - return false; - } - } - - $this->bd->commit (); - return $affected; - } + $this->bd->commit(); + return $affected; } - public function searchByGuid ($feed_id, $id) { + public function searchByGuid($feed_id, $id) { // un guid est unique pour un flux donné $sql = 'SELECT id, guid, title, author, ' . ($this->isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content') . ', link, date, is_read, is_favorite, id_feed, tags ' . 'FROM `' . $this->prefix . 'entry` WHERE id_feed=? AND guid=?'; - $stm = $this->bd->prepare ($sql); + $stm = $this->bd->prepare($sql); - $values = array ( + $values = array( $feed_id, $id ); - $stm->execute ($values); - $res = $stm->fetchAll (PDO::FETCH_ASSOC); - $entries = self::daoToEntry ($res); - return isset ($entries[0]) ? $entries[0] : null; + $stm->execute($values); + $res = $stm->fetchAll(PDO::FETCH_ASSOC); + $entries = self::daoToEntry($res); + return isset($entries[0]) ? $entries[0] : null; } - public function searchById ($id) { + public function searchById($id) { $sql = 'SELECT id, guid, title, author, ' . ($this->isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content') . ', link, date, is_read, is_favorite, id_feed, tags ' . 'FROM `' . $this->prefix . 'entry` WHERE id=?'; - $stm = $this->bd->prepare ($sql); + $stm = $this->bd->prepare($sql); - $values = array ($id); + $values = array($id); - $stm->execute ($values); - $res = $stm->fetchAll (PDO::FETCH_ASSOC); - $entries = self::daoToEntry ($res); - return isset ($entries[0]) ? $entries[0] : null; + $stm->execute($values); + $res = $stm->fetchAll(PDO::FETCH_ASSOC); + $entries = self::daoToEntry($res); + return isset($entries[0]) ? $entries[0] : null; + } + + protected function sqlConcat($s1, $s2) { + return 'CONCAT(' . $s1 . ',' . $s2 . ')'; //MySQL } private function sqlListWhere($type = 'a', $id = '', $state = FreshRSS_Entry::STATE_ALL, $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $showOlderUnreadsorFavorites = false, $keepHistoryDefault = 0) { @@ -432,39 +318,39 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { $joinFeed = true; break; case 's': //Deprecated: use $state instead - $where .= 'e1.is_favorite = 1 '; + $where .= 'e1.is_favorite=1 '; break; case 'c': - $where .= 'f.category = ? '; + $where .= 'f.category=? '; $values[] = intval($id); $joinFeed = true; break; case 'f': - $where .= 'e1.id_feed = ? '; + $where .= 'e1.id_feed=? '; $values[] = intval($id); break; case 'A': $where .= '1 '; break; default: - throw new FreshRSS_EntriesGetter_Exception ('Bad type in Entry->listByType: [' . $type . ']!'); + throw new FreshRSS_EntriesGetter_Exception('Bad type in Entry->listByType: [' . $type . ']!'); } if ($state & FreshRSS_Entry::STATE_NOT_READ) { if (!($state & FreshRSS_Entry::STATE_READ)) { - $where .= 'AND e1.is_read = 0 '; + $where .= 'AND e1.is_read=0 '; } } elseif ($state & FreshRSS_Entry::STATE_READ) { - $where .= 'AND e1.is_read = 1 '; + $where .= 'AND e1.is_read=1 '; } if ($state & FreshRSS_Entry::STATE_FAVORITE) { if (!($state & FreshRSS_Entry::STATE_NOT_FAVORITE)) { - $where .= 'AND e1.is_favorite = 1 '; + $where .= 'AND e1.is_favorite=1 '; } } elseif ($state & FreshRSS_Entry::STATE_NOT_FAVORITE) { - $where .= 'AND e1.is_favorite = 0 '; + $where .= 'AND e1.is_favorite=0 '; } switch ($order) { @@ -472,7 +358,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { case 'ASC': break; default: - throw new FreshRSS_EntriesGetter_Exception ('Bad order in Entry->listByType: [' . $order . ']!'); + throw new FreshRSS_EntriesGetter_Exception('Bad order in Entry->listByType: [' . $order . ']!'); } if ($firstId !== '') { $where .= 'AND e1.id ' . ($order === 'DESC' ? '<=' : '>=') . $firstId . ' '; @@ -480,7 +366,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { if (($date_min > 0) && ($type !== 's')) { $where .= 'AND (e1.id >= ' . $date_min . '000000'; if ($showOlderUnreadsorFavorites) { //Lax date constraint - $where .= ' OR e1.is_read = 0 OR e1.is_favorite = 1 OR (f.keep_history <> 0'; + $where .= ' OR e1.is_read=0 OR e1.is_favorite=1 OR (f.keep_history <> 0'; if (intval($keepHistoryDefault) === 0) { $where .= ' AND f.keep_history <> -2'; //default } @@ -533,7 +419,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { $search .= 'AND e1.tags LIKE ? '; $values[] = '%' . $word .'%'; } else { - $search .= 'AND CONCAT(e1.title, ' . ($this->isCompressed() ? 'UNCOMPRESS(content_bin)' : 'content') . ') LIKE ? '; + $search .= 'AND ' . $this->sqlconcat('e1.title', $this->isCompressed() ? 'UNCOMPRESS(content_bin)' : 'content') . ' LIKE ? '; $values[] = '%' . $word .'%'; } } @@ -542,7 +428,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { return array($values, 'SELECT e1.id FROM `' . $this->prefix . 'entry` e1 ' - . ($joinFeed ? 'INNER JOIN `' . $this->prefix . 'feed` f ON e1.id_feed = f.id ' : '') + . ($joinFeed ? 'INNER JOIN `' . $this->prefix . 'feed` f ON e1.id_feed=f.id ' : '') . 'WHERE ' . $where . $search . 'ORDER BY e1.id ' . $order @@ -558,13 +444,13 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { . 'FROM `' . $this->prefix . 'entry` e ' . 'INNER JOIN (' . $sql - . ') e2 ON e2.id = e.id ' + . ') e2 ON e2.id=e.id ' . 'ORDER BY e.id ' . $order; - $stm = $this->bd->prepare ($sql); - $stm->execute ($values); + $stm = $this->bd->prepare($sql); + $stm->execute($values); - return self::daoToEntry ($stm->fetchAll (PDO::FETCH_ASSOC)); + return self::daoToEntry($stm->fetchAll(PDO::FETCH_ASSOC)); } public function listIdsWhere($type = 'a', $id = '', $state = FreshRSS_Entry::STATE_ALL, $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $showOlderUnreadsorFavorites = false, $keepHistoryDefault = 0) { //For API @@ -578,69 +464,85 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { public function listLastGuidsByFeed($id, $n) { $sql = 'SELECT guid FROM `' . $this->prefix . 'entry` WHERE id_feed=? ORDER BY id DESC LIMIT ' . intval($n); - $stm = $this->bd->prepare ($sql); - $values = array ($id); - $stm->execute ($values); - return $stm->fetchAll (PDO::FETCH_COLUMN, 0); + $stm = $this->bd->prepare($sql); + $values = array($id); + $stm->execute($values); + return $stm->fetchAll(PDO::FETCH_COLUMN, 0); } - public function countUnreadRead () { - $sql = 'SELECT COUNT(e.id) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id WHERE priority > 0' - . ' UNION SELECT COUNT(e.id) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id WHERE priority > 0 AND is_read = 0'; - $stm = $this->bd->prepare ($sql); - $stm->execute (); - $res = $stm->fetchAll (PDO::FETCH_COLUMN, 0); + public function countUnreadRead() { + $sql = 'SELECT COUNT(e.id) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id WHERE priority > 0' + . ' UNION SELECT COUNT(e.id) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id WHERE priority > 0 AND is_read=0'; + $stm = $this->bd->prepare($sql); + $stm->execute(); + $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0); $all = empty($res[0]) ? 0 : $res[0]; $unread = empty($res[1]) ? 0 : $res[1]; return array('all' => $all, 'unread' => $unread, 'read' => $all - $unread); } - public function count ($minPriority = null) { - $sql = 'SELECT COUNT(e.id) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id'; + public function count($minPriority = null) { + $sql = 'SELECT COUNT(e.id) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id'; if ($minPriority !== null) { $sql = ' WHERE priority > ' . intval($minPriority); } - $stm = $this->bd->prepare ($sql); - $stm->execute (); - $res = $stm->fetchAll (PDO::FETCH_COLUMN, 0); + $stm = $this->bd->prepare($sql); + $stm->execute(); + $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0); return $res[0]; } - public function countNotRead ($minPriority = null) { - $sql = 'SELECT COUNT(e.id) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id WHERE is_read = 0'; + public function countNotRead($minPriority = null) { + $sql = 'SELECT COUNT(e.id) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id WHERE is_read=0'; if ($minPriority !== null) { $sql = ' AND priority > ' . intval($minPriority); } - $stm = $this->bd->prepare ($sql); - $stm->execute (); - $res = $stm->fetchAll (PDO::FETCH_COLUMN, 0); + $stm = $this->bd->prepare($sql); + $stm->execute(); + $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0); return $res[0]; } - public function countUnreadReadFavorites () { - $sql = 'SELECT COUNT(id) FROM `' . $this->prefix . 'entry` WHERE is_favorite=1' - . ' UNION SELECT COUNT(id) FROM `' . $this->prefix . 'entry` WHERE is_favorite=1 AND is_read = 0'; - $stm = $this->bd->prepare ($sql); - $stm->execute (); - $res = $stm->fetchAll (PDO::FETCH_COLUMN, 0); + public function countUnreadReadFavorites() { + $sql = 'SELECT c FROM (' + . 'SELECT COUNT(id) AS c, 1 as o FROM `' . $this->prefix . 'entry` WHERE is_favorite=1 ' + . 'UNION SELECT COUNT(id) AS c, 2 AS o FROM `' . $this->prefix . 'entry` WHERE is_favorite=1 AND is_read=0' + . ') u ORDER BY o'; + $stm = $this->bd->prepare($sql); + $stm->execute(); + $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0); $all = empty($res[0]) ? 0 : $res[0]; $unread = empty($res[1]) ? 0 : $res[1]; return array('all' => $all, 'unread' => $unread, 'read' => $all - $unread); } public function optimizeTable() { - $sql = 'OPTIMIZE TABLE `' . $this->prefix . 'entry`'; - $stm = $this->bd->prepare ($sql); - $stm->execute (); + $sql = 'OPTIMIZE TABLE `' . $this->prefix . 'entry`'; //MySQL + $stm = $this->bd->prepare($sql); + $stm->execute(); + } + + public function size($all = false) { + $db = Minz_Configuration::dataBase(); + $sql = 'SELECT SUM(data_length + index_length) FROM information_schema.TABLES WHERE table_schema=?'; //MySQL + $values = array($db['base']); + if (!$all) { + $sql .= ' AND table_name LIKE ?'; + $values[] = $this->prefix . '%'; + } + $stm = $this->bd->prepare($sql); + $stm->execute($values); + $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0); + return $res[0]; } - public static function daoToEntry ($listDAO) { - $list = array (); + public static function daoToEntry($listDAO) { + $list = array(); - if (!is_array ($listDAO)) { - $listDAO = array ($listDAO); + if (!is_array($listDAO)) { + $listDAO = array($listDAO); } foreach ($listDAO as $key => $dao) { - $entry = new FreshRSS_Entry ( + $entry = new FreshRSS_Entry( $dao['id_feed'], $dao['guid'], $dao['title'], @@ -652,13 +554,13 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { $dao['is_favorite'], $dao['tags'] ); - if (isset ($dao['id'])) { - $entry->_id ($dao['id']); + if (isset($dao['id'])) { + $entry->_id($dao['id']); } $list[] = $entry; } - unset ($listDAO); + unset($listDAO); return $list; } diff --git a/app/Models/EntryDAOSQLite.php b/app/Models/EntryDAOSQLite.php index 0a837b3aa..4680c3eaa 100644 --- a/app/Models/EntryDAOSQLite.php +++ b/app/Models/EntryDAOSQLite.php @@ -2,9 +2,38 @@ class FreshRSS_EntryDAOSQLite extends FreshRSS_EntryDAO { + protected function sqlConcat($s1, $s2) { + return $s1 . '||' . $s2; + } + + protected function updateCacheUnreads($catId = false, $feedId = false) { + $sql = 'UPDATE `' . $this->prefix . 'feed` ' + . 'SET cache_nbUnreads=(' + . 'SELECT COUNT(*) AS nbUnreads FROM `' . $this->prefix . 'entry` e ' + . 'WHERE e.id_feed=feed.id AND e.is_read=0) ' + . 'WHERE 1'; + $values = array(); + if ($feedId !== false) { + $sql .= ' AND id=?'; + $values[] = $feedId; + } + if ($catId !== false) { + $sql .= ' AND category=?'; + $values[] = $catId; + } + $stm = $this->bd->prepare($sql); + if ($stm && $stm->execute($values)) { + return true; + } else { + $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + Minz_Log::record('SQL error updateCacheUnreads: ' . $info[2], Minz_Log::ERROR); + return false; + } + } + public function markRead($ids, $is_read = true) { - if (is_array($ids)) { //Many IDs at once - if (true) { //Not supported yet in SQLite, so always call IDs one by one + if (is_array($ids)) { //Many IDs at once (used by API) + if (true) { //Speed heuristics //TODO: Not implemented yet for SQLite (so always call IDs one by one) $affected = 0; foreach ($ids as $id) { $affected += $this->markRead($id, $is_read); @@ -13,13 +42,13 @@ class FreshRSS_EntryDAOSQLite extends FreshRSS_EntryDAO { } } else { $this->bd->beginTransaction(); - $sql = 'UPDATE `' . $this->prefix . 'entry` SET is_read=? WHERE id=? AND is_read<>?'; - $values = array($is_read ? 1 : 0, $ids, $is_read ? 1 : 0); + $sql = 'UPDATE `' . $this->prefix . 'entry` SET is_read=? WHERE id=? AND is_read=?'; + $values = array($is_read ? 1 : 0, $ids, $is_read ? 0 : 1); $stm = $this->bd->prepare($sql); - if (!($stm && $stm->execute ($values))) { + if (!($stm && $stm->execute($values))) { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); Minz_Log::record('SQL error markRead 1: ' . $info[2], Minz_Log::ERROR); - $this->bd->rollBack (); + $this->bd->rollBack(); return false; } $affected = $stm->rowCount(); @@ -28,10 +57,10 @@ class FreshRSS_EntryDAOSQLite extends FreshRSS_EntryDAO { . 'WHERE id=(SELECT e.id_feed FROM `' . $this->prefix . 'entry` e WHERE e.id=?)'; $values = array($ids); $stm = $this->bd->prepare($sql); - if (!($stm && $stm->execute ($values))) { + if (!($stm && $stm->execute($values))) { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); Minz_Log::record('SQL error markRead 2: ' . $info[2], Minz_Log::ERROR); - $this->bd->rollBack (); + $this->bd->rollBack(); return false; } } @@ -39,4 +68,70 @@ class FreshRSS_EntryDAOSQLite extends FreshRSS_EntryDAO { return $affected; } } + + public function markReadEntries($idMax = 0, $onlyFavorites = false, $priorityMin = 0) { + if ($idMax == 0) { + $idMax = time() . '000000'; + Minz_Log::record($nb . 'Calling markReadEntries(0) is deprecated!', Minz_Log::DEBUG); + } + + $this->bd->beginTransaction(); + $sql = 'UPDATE `' . $this->prefix . 'entry` SET is_read=1 WHERE is_read=0 AND id <= ?'; + if ($onlyFavorites) { + $sql .= ' AND is_favorite=1'; + } elseif ($priorityMin >= 0) { + $sql .= ' AND id_feed IN (SELECT f.id FROM `' . $this->prefix . 'feed` f WHERE f.priority > ' . intval($priorityMin) . ')'; + } + $values = array($idMax); + $stm = $this->bd->prepare($sql); + if (!($stm && $stm->execute($values))) { + $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + Minz_Log::record('SQL error markReadEntries 1: ' . $info[2], Minz_Log::ERROR); + $this->bd->rollBack(); + return false; + } + $affected = $stm->rowCount(); + if (($affected > 0) && (!$this->updateCacheUnreads(false, false))) { + $this->bd->rollBack(); + return false; + } + $this->bd->commit(); + return $affected; + } + + public function markReadCat($id, $idMax = 0) { + if ($idMax == 0) { + $idMax = time() . '000000'; + Minz_Log::record($nb . 'Calling markReadCat(0) is deprecated!', Minz_Log::DEBUG); + } + $this->bd->beginTransaction(); + + $sql = 'UPDATE `' . $this->prefix . 'entry` ' + . 'SET is_read=1 ' + . 'WHERE is_read=0 AND id <= ? AND ' + . 'id_feed IN (SELECT f.id FROM `' . $this->prefix . 'feed` f WHERE f.category=?)'; + $values = array($idMax, $id); + $stm = $this->bd->prepare($sql); + if (!($stm && $stm->execute($values))) { + $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + Minz_Log::record('SQL error markReadCat: ' . $info[2], Minz_Log::ERROR); + $this->bd->rollBack(); + return false; + } + $affected = $stm->rowCount(); + if (($affected > 0) && (!$this->updateCacheUnreads($id, false))) { + $this->bd->rollBack(); + return false; + } + $this->bd->commit(); + return $affected; + } + + public function optimizeTable() { + //TODO: Search for an equivalent in SQLite + } + + public function size($all = false) { + return @filesize(DATA_PATH . '/' . Minz_Session::param('currentUser', '_') . '.sqlite'); + } } diff --git a/app/Models/FeedDAO.php b/app/Models/FeedDAO.php index 9534d61bd..5281b371d 100644 --- a/app/Models/FeedDAO.php +++ b/app/Models/FeedDAO.php @@ -1,21 +1,21 @@ prefix . 'feed` (url, category, name, website, description, lastUpdate, priority, httpAuth, error, keep_history) VALUES(?, ?, ?, ?, ?, ?, 10, ?, 0, -2)'; - $stm = $this->bd->prepare ($sql); + $stm = $this->bd->prepare($sql); - $values = array ( + $values = array( substr($valuesTmp['url'], 0, 511), $valuesTmp['category'], substr($valuesTmp['name'], 0, 255), substr($valuesTmp['website'], 0, 255), substr($valuesTmp['description'], 0, 1023), $valuesTmp['lastUpdate'], - base64_encode ($valuesTmp['httpAuth']), + base64_encode($valuesTmp['httpAuth']), ); - if ($stm && $stm->execute ($values)) { + if ($stm && $stm->execute($values)) { return $this->bd->lastInsertId(); } else { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); @@ -54,26 +54,26 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { return $feed_search->id(); } - public function updateFeed ($id, $valuesTmp) { + public function updateFeed($id, $valuesTmp) { $set = ''; foreach ($valuesTmp as $key => $v) { $set .= $key . '=?, '; if ($key == 'httpAuth') { - $valuesTmp[$key] = base64_encode ($v); + $valuesTmp[$key] = base64_encode($v); } } - $set = substr ($set, 0, -2); + $set = substr($set, 0, -2); $sql = 'UPDATE `' . $this->prefix . 'feed` SET ' . $set . ' WHERE id=?'; - $stm = $this->bd->prepare ($sql); + $stm = $this->bd->prepare($sql); foreach ($valuesTmp as $v) { $values[] = $v; } $values[] = $id; - if ($stm && $stm->execute ($values)) { + if ($stm && $stm->execute($values)) { return $stm->rowCount(); } else { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); @@ -82,11 +82,11 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { } } - public function updateLastUpdate ($id, $inError = 0, $updateCache = true) { + public function updateLastUpdate($id, $inError = 0, $updateCache = true) { if ($updateCache) { $sql = 'UPDATE `' . $this->prefix . 'feed` ' //2 sub-requests with FOREIGN KEY(e.id_feed), INDEX(e.is_read) faster than 1 request with GROUP BY or CASE - . 'SET cache_nbEntries=(SELECT COUNT(e1.id) FROM `' . $this->prefix . 'entry` e1 WHERE e1.id_feed=feed.id),' - . 'cache_nbUnreads=(SELECT COUNT(e2.id) FROM `' . $this->prefix . 'entry` e2 WHERE e2.id_feed=feed.id AND e2.is_read=0),' + . 'SET cache_nbEntries=(SELECT COUNT(e1.id) FROM `' . $this->prefix . 'entry` e1 WHERE e1.id_feed=`' . $this->prefix . 'feed`.id),' + . 'cache_nbUnreads=(SELECT COUNT(e2.id) FROM `' . $this->prefix . 'entry` e2 WHERE e2.id_feed=`' . $this->prefix . 'feed`.id AND e2.is_read=0),' . 'lastUpdate=?, error=? ' . 'WHERE id=?'; } else { @@ -95,15 +95,15 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { . 'WHERE id=?'; } - $values = array ( + $values = array( time(), $inError, $id, ); - $stm = $this->bd->prepare ($sql); + $stm = $this->bd->prepare($sql); - if ($stm && $stm->execute ($values)) { + if ($stm && $stm->execute($values)) { return $stm->rowCount(); } else { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); @@ -112,22 +112,22 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { } } - public function changeCategory ($idOldCat, $idNewCat) { - $catDAO = new FreshRSS_CategoryDAO (); - $newCat = $catDAO->searchById ($idNewCat); + public function changeCategory($idOldCat, $idNewCat) { + $catDAO = new FreshRSS_CategoryDAO(); + $newCat = $catDAO->searchById($idNewCat); if (!$newCat) { - $newCat = $catDAO->getDefault (); + $newCat = $catDAO->getDefault(); } $sql = 'UPDATE `' . $this->prefix . 'feed` SET category=? WHERE category=?'; - $stm = $this->bd->prepare ($sql); + $stm = $this->bd->prepare($sql); - $values = array ( - $newCat->id (), + $values = array( + $newCat->id(), $idOldCat ); - if ($stm && $stm->execute ($values)) { + if ($stm && $stm->execute($values)) { return $stm->rowCount(); } else { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); @@ -136,23 +136,13 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { } } - public function deleteFeed ($id) { - /*//For MYISAM (MySQL 5.5-) without FOREIGN KEY - $sql = 'DELETE FROM `' . $this->prefix . 'entry` WHERE id_feed=?'; - $stm = $this->bd->prepare ($sql); - $values = array ($id); - if (!($stm && $stm->execute ($values))) { - $info = $stm->errorInfo(); - Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR); - return false; - }*/ - + public function deleteFeed($id) { $sql = 'DELETE FROM `' . $this->prefix . 'feed` WHERE id=?'; - $stm = $this->bd->prepare ($sql); + $stm = $this->bd->prepare($sql); - $values = array ($id); + $values = array($id); - if ($stm && $stm->execute ($values)) { + if ($stm && $stm->execute($values)) { return $stm->rowCount(); } else { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); @@ -160,25 +150,13 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { return false; } } - public function deleteFeedByCategory ($id) { - /*//For MYISAM (MySQL 5.5-) without FOREIGN KEY - $sql = 'DELETE FROM `' . $this->prefix . 'entry` e ' - . 'INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id ' - . 'WHERE f.category=?'; - $stm = $this->bd->prepare ($sql); - $values = array ($id); - if (!($stm && $stm->execute ($values))) { - $info = $stm->errorInfo(); - Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR); - return false; - }*/ - + public function deleteFeedByCategory($id) { $sql = 'DELETE FROM `' . $this->prefix . 'feed` WHERE category=?'; - $stm = $this->bd->prepare ($sql); + $stm = $this->bd->prepare($sql); - $values = array ($id); + $values = array($id); - if ($stm && $stm->execute ($values)) { + if ($stm && $stm->execute($values)) { return $stm->rowCount(); } else { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); @@ -187,52 +165,52 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { } } - public function searchById ($id) { + public function searchById($id) { $sql = 'SELECT * FROM `' . $this->prefix . 'feed` WHERE id=?'; - $stm = $this->bd->prepare ($sql); + $stm = $this->bd->prepare($sql); - $values = array ($id); + $values = array($id); - $stm->execute ($values); - $res = $stm->fetchAll (PDO::FETCH_ASSOC); - $feed = self::daoToFeed ($res); + $stm->execute($values); + $res = $stm->fetchAll(PDO::FETCH_ASSOC); + $feed = self::daoToFeed($res); - if (isset ($feed[$id])) { + if (isset($feed[$id])) { return $feed[$id]; } else { return null; } } - public function searchByUrl ($url) { + public function searchByUrl($url) { $sql = 'SELECT * FROM `' . $this->prefix . 'feed` WHERE url=?'; - $stm = $this->bd->prepare ($sql); + $stm = $this->bd->prepare($sql); - $values = array ($url); + $values = array($url); - $stm->execute ($values); - $res = $stm->fetchAll (PDO::FETCH_ASSOC); - $feed = current (self::daoToFeed ($res)); + $stm->execute($values); + $res = $stm->fetchAll(PDO::FETCH_ASSOC); + $feed = current(self::daoToFeed($res)); - if (isset ($feed)) { + if (isset($feed)) { return $feed; } else { return null; } } - public function listFeeds () { + public function listFeeds() { $sql = 'SELECT * FROM `' . $this->prefix . 'feed` ORDER BY name'; - $stm = $this->bd->prepare ($sql); - $stm->execute (); + $stm = $this->bd->prepare($sql); + $stm->execute(); - return self::daoToFeed ($stm->fetchAll (PDO::FETCH_ASSOC)); + return self::daoToFeed($stm->fetchAll(PDO::FETCH_ASSOC)); } public function arrayFeedCategoryNames() { //For API $sql = 'SELECT f.id, f.name, c.name as c_name FROM `' . $this->prefix . 'feed` f ' . 'INNER JOIN `' . $this->prefix . 'category` c ON c.id = f.category'; - $stm = $this->bd->prepare ($sql); - $stm->execute (); + $stm = $this->bd->prepare($sql); + $stm->execute(); $res = $stm->fetchAll(PDO::FETCH_ASSOC); $feedCategoryNames = array(); foreach ($res as $line) { @@ -244,49 +222,49 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { return $feedCategoryNames; } - public function listFeedsOrderUpdate ($cacheDuration = 1500) { + public function listFeedsOrderUpdate($cacheDuration = 1500) { $sql = 'SELECT id, url, name, website, lastUpdate, pathEntries, httpAuth, keep_history ' . 'FROM `' . $this->prefix . 'feed` ' . 'WHERE lastUpdate < ' . (time() - intval($cacheDuration)) . ' ORDER BY lastUpdate'; - $stm = $this->bd->prepare ($sql); - $stm->execute (); + $stm = $this->bd->prepare($sql); + $stm->execute(); - return self::daoToFeed ($stm->fetchAll (PDO::FETCH_ASSOC)); + return self::daoToFeed($stm->fetchAll(PDO::FETCH_ASSOC)); } - public function listByCategory ($cat) { + public function listByCategory($cat) { $sql = 'SELECT * FROM `' . $this->prefix . 'feed` WHERE category=? ORDER BY name'; - $stm = $this->bd->prepare ($sql); + $stm = $this->bd->prepare($sql); - $values = array ($cat); + $values = array($cat); - $stm->execute ($values); + $stm->execute($values); - return self::daoToFeed ($stm->fetchAll (PDO::FETCH_ASSOC)); + return self::daoToFeed($stm->fetchAll(PDO::FETCH_ASSOC)); } - public function countEntries ($id) { + public function countEntries($id) { $sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'entry` WHERE id_feed=?'; - $stm = $this->bd->prepare ($sql); - $values = array ($id); - $stm->execute ($values); - $res = $stm->fetchAll (PDO::FETCH_ASSOC); + $stm = $this->bd->prepare($sql); + $values = array($id); + $stm->execute($values); + $res = $stm->fetchAll(PDO::FETCH_ASSOC); return $res[0]['count']; } - public function countNotRead ($id) { + public function countNotRead($id) { $sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'entry` WHERE id_feed=? AND is_read=0'; - $stm = $this->bd->prepare ($sql); - $values = array ($id); - $stm->execute ($values); - $res = $stm->fetchAll (PDO::FETCH_ASSOC); + $stm = $this->bd->prepare($sql); + $values = array($id); + $stm->execute($values); + $res = $stm->fetchAll(PDO::FETCH_ASSOC); return $res[0]['count']; } - public function updateCachedValues () { //For one single feed, call updateLastUpdate($id) + public function updateCachedValues() { //For one single feed, call updateLastUpdate($id) $sql = 'UPDATE `' . $this->prefix . 'feed` f ' . 'INNER JOIN (' . 'SELECT e.id_feed, ' @@ -296,7 +274,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { . 'GROUP BY e.id_feed' . ') x ON x.id_feed=f.id ' . 'SET f.cache_nbEntries=x.nbEntries, f.cache_nbUnreads=x.nbUnreads'; - $stm = $this->bd->prepare ($sql); + $stm = $this->bd->prepare($sql); if ($stm && $stm->execute()) { return $stm->rowCount(); @@ -307,39 +285,39 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { } } - public function truncate ($id) { - $sql = 'DELETE e.* FROM `' . $this->prefix . 'entry` e WHERE e.id_feed=?'; + public function truncate($id) { + $sql = 'DELETE FROM `' . $this->prefix . 'entry` WHERE id_feed=?'; $stm = $this->bd->prepare($sql); $values = array($id); - $this->bd->beginTransaction (); - if (!($stm && $stm->execute ($values))) { + $this->bd->beginTransaction(); + if (!($stm && $stm->execute($values))) { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); Minz_Log::record('SQL error truncate: ' . $info[2], Minz_Log::ERROR); - $this->bd->rollBack (); + $this->bd->rollBack(); return false; } $affected = $stm->rowCount(); $sql = 'UPDATE `' . $this->prefix . 'feed` ' . 'SET cache_nbEntries=0, cache_nbUnreads=0 WHERE id=?'; - $values = array ($id); - $stm = $this->bd->prepare ($sql); - if (!($stm && $stm->execute ($values))) { + $values = array($id); + $stm = $this->bd->prepare($sql); + if (!($stm && $stm->execute($values))) { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); Minz_Log::record('SQL error truncate: ' . $info[2], Minz_Log::ERROR); - $this->bd->rollBack (); + $this->bd->rollBack(); return false; } - $this->bd->commit (); + $this->bd->commit(); return $affected; } - public function cleanOldEntries ($id, $date_min, $keep = 15) { //Remember to call updateLastUpdate($id) just after - $sql = 'DELETE e.* FROM `' . $this->prefix . 'entry` e ' - . 'WHERE e.id_feed = :id_feed AND e.id <= :id_max AND e.is_favorite = 0 AND e.id NOT IN ' - . '(SELECT id FROM (SELECT e2.id FROM `' . $this->prefix . 'entry` e2 WHERE e2.id_feed = :id_feed ORDER BY id DESC LIMIT :keep) keep)'; //Double select because of: MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery' - $stm = $this->bd->prepare ($sql); + public function cleanOldEntries($id, $date_min, $keep = 15) { //Remember to call updateLastUpdate($id) just after + $sql = 'DELETE FROM `' . $this->prefix . 'entry` ' + . 'WHERE id_feed = :id_feed AND id <= :id_max AND is_favorite=0 AND id NOT IN ' + . '(SELECT id FROM (SELECT e2.id FROM `' . $this->prefix . 'entry` e2 WHERE e2.id_feed = :id_feed ORDER BY id DESC LIMIT :keep) keep)'; //Double select MySQL doesn't yet support 'LIMIT & IN/ALL/ANY/SOME subquery' + $stm = $this->bd->prepare($sql); $id_max = intval($date_min) . '000000'; @@ -347,7 +325,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { $stm->bindParam(':id_max', $id_max, PDO::PARAM_INT); $stm->bindParam(':keep', $keep, PDO::PARAM_INT); - if ($stm && $stm->execute ()) { + if ($stm && $stm->execute()) { return $stm->rowCount(); } else { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); @@ -356,18 +334,18 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { } } - public static function daoToFeed ($listDAO, $catID = null) { - $list = array (); + public static function daoToFeed($listDAO, $catID = null) { + $list = array(); - if (!is_array ($listDAO)) { - $listDAO = array ($listDAO); + if (!is_array($listDAO)) { + $listDAO = array($listDAO); } foreach ($listDAO as $key => $dao) { - if (!isset ($dao['name'])) { + if (!isset($dao['name'])) { continue; } - if (isset ($dao['id'])) { + if (isset($dao['id'])) { $key = $dao['id']; } if ($catID === null) { @@ -384,13 +362,13 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { $myFeed->_lastUpdate(isset($dao['lastUpdate']) ? $dao['lastUpdate'] : 0); $myFeed->_priority(isset($dao['priority']) ? $dao['priority'] : 10); $myFeed->_pathEntries(isset($dao['pathEntries']) ? $dao['pathEntries'] : ''); - $myFeed->_httpAuth(isset($dao['httpAuth']) ? base64_decode ($dao['httpAuth']) : ''); + $myFeed->_httpAuth(isset($dao['httpAuth']) ? base64_decode($dao['httpAuth']) : ''); $myFeed->_error(isset($dao['error']) ? $dao['error'] : 0); $myFeed->_keepHistory(isset($dao['keep_history']) ? $dao['keep_history'] : -2); $myFeed->_nbNotRead(isset($dao['cache_nbUnreads']) ? $dao['cache_nbUnreads'] : 0); $myFeed->_nbEntries(isset($dao['cache_nbEntries']) ? $dao['cache_nbEntries'] : 0); - if (isset ($dao['id'])) { - $myFeed->_id ($dao['id']); + if (isset($dao['id'])) { + $myFeed->_id($dao['id']); } $list[$key] = $myFeed; } diff --git a/app/Models/FeedDAOSQLite.php b/app/Models/FeedDAOSQLite.php index f3c92149e..bb75c860b 100644 --- a/app/Models/FeedDAOSQLite.php +++ b/app/Models/FeedDAOSQLite.php @@ -2,4 +2,18 @@ class FreshRSS_FeedDAOSQLite extends FreshRSS_FeedDAO { + public function updateCachedValues() { //For one single feed, call updateLastUpdate($id) + $sql = 'UPDATE `' . $this->prefix . 'feed` ' + . 'SET cache_nbEntries=(SELECT COUNT(e1.id) FROM `' . $this->prefix . 'entry` e1 WHERE e1.id_feed=feed.id),' + . 'cache_nbUnreads=(SELECT COUNT(e2.id) FROM `' . $this->prefix . 'entry` e2 WHERE e2.id_feed=feed.id AND e2.is_read=0)'; + $stm = $this->bd->prepare($sql); + if ($stm && $stm->execute()) { + return $stm->rowCount(); + } else { + $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + Minz_Log::record('SQL error updateCachedValues: ' . $info[2], Minz_Log::ERROR); + return false; + } + } + } diff --git a/lib/Minz/ModelPdo.php b/lib/Minz/ModelPdo.php index cbbc0bebc..1f56f09c2 100644 --- a/lib/Minz/ModelPdo.php +++ b/lib/Minz/ModelPdo.php @@ -92,20 +92,6 @@ class Minz_ModelPdo { $this->bd->rollBack(); } - public function size($all = false) { - $db = Minz_Configuration::dataBase(); - $sql = 'SELECT SUM(data_length + index_length) FROM information_schema.TABLES WHERE table_schema = ?'; - $values = array($db['base']); - if (!$all) { - $sql .= ' AND table_name LIKE ?'; - $values[] = $this->prefix . '%'; - } - $stm = $this->bd->prepare($sql); - $stm->execute($values); - $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0); - return $res[0]; - } - public static function clean() { self::$sharedBd = null; self::$sharedPrefix = ''; diff --git a/p/api/greader.php b/p/api/greader.php index 843cd93c4..7a961225f 100644 --- a/p/api/greader.php +++ b/p/api/greader.php @@ -364,7 +364,7 @@ function streamContents($path, $include_target, $start_time, $count, $order, $ex $count++; //Shift by one element } - $entryDAO = new FreshRSS_EntryDAO(); + $entryDAO = FreshRSS_Factory::createEntryDao(); $entries = $entryDAO->listWhere($type, $include_target, $state, $order === 'o' ? 'ASC' : 'DESC', $count, $continuation, '', $start_time); $items = array(); @@ -458,7 +458,7 @@ function streamContentsItemsIds($streamId, $start_time, $count, $order, $exclude break; } - $entryDAO = new FreshRSS_EntryDAO(); + $entryDAO = FreshRSS_Factory::createEntryDao(); $ids = $entryDAO->listIdsWhere($type, $id, $state, $order === 'o' ? 'ASC' : 'DESC', $count, '', '', $start_time); $itemRefs = array(); @@ -481,7 +481,7 @@ function editTag($e_ids, $a, $r) { $e_ids[$i] = hex2dec(basename($e_id)); //Strip prefix 'tag:google.com,2005:reader/item/' } - $entryDAO = new FreshRSS_EntryDAO(); + $entryDAO = FreshRSS_Factory::createEntryDao(); switch ($a) { case 'user/-/state/com.google/read': @@ -512,13 +512,15 @@ function editTag($e_ids, $a, $r) { function markAllAsRead($streamId, $olderThanId) { logMe("markAllAsRead($streamId, $olderThanId)\n"); - $entryDAO = new FreshRSS_EntryDAO(); + $entryDAO = FreshRSS_Factory::createEntryDao(); if (strpos($streamId, 'feed/') === 0) { $f_id = basename($streamId); $entryDAO->markReadFeed($f_id, $olderThanId); } elseif (strpos($streamId, 'user/-/label/') === 0) { $c_name = basename($streamId); - $entryDAO->markReadCatName($c_name, $olderThanId); + $categoryDAO = new FreshRSS_CategoryDAO(); + $cat = $categoryDAO->searchByName($c_name); + $entryDAO->markReadCat($cat === null ? -1 : $cat->id(), $olderThanId); } elseif ($streamId === 'user/-/state/com.google/reading-list') { $entryDAO->markReadEntries($olderThanId, false, -1); } -- cgit v1.2.3 From d477373ef2879bdeeaa3c157287c0fab98afefdc Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sat, 9 Aug 2014 19:58:39 +0200 Subject: SQLite: Bug creation new users Not tested much yet. Some MySQL parts changed a bit too to double-check. https://github.com/marienfressinaud/FreshRSS/issues/574 --- app/Models/UserDAO.php | 35 ++++++++++++++++++++++------------- app/SQL/install.sql.sqlite.php | 1 + lib/Minz/ModelPdo.php | 20 ++++++++++++-------- 3 files changed, 35 insertions(+), 21 deletions(-) (limited to 'lib/Minz/ModelPdo.php') diff --git a/app/Models/UserDAO.php b/app/Models/UserDAO.php index 1763fac67..9f64fb4a7 100644 --- a/app/Models/UserDAO.php +++ b/app/Models/UserDAO.php @@ -4,18 +4,21 @@ class FreshRSS_UserDAO extends Minz_ModelPdo { public function createUser($username) { $db = Minz_Configuration::dataBase(); require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php'); - - if (defined('SQL_CREATE_TABLES')) { + + $userPDO = new Minz_ModelPdo($username); + + $ok = false; + if (defined('SQL_CREATE_TABLES')) { //E.g. MySQL $sql = sprintf(SQL_CREATE_TABLES, $db['prefix'] . $username . '_', Minz_Translate::t('default_category')); - $stm = $this->bd->prepare($sql); + $stm = $userPDO->bd->prepare($sql); $ok = $stm && $stm->execute(); - } else { + } else { //E.g. SQLite global $SQL_CREATE_TABLES; if (is_array($SQL_CREATE_TABLES)) { $ok = true; foreach ($SQL_CREATE_TABLES as $instruction) { $sql = sprintf($instruction, '', Minz_Translate::t('default_category')); - $stm = $c->prepare($sql); + $stm = $userPDO->bd->prepare($sql); $ok &= ($stm && $stm->execute()); } } @@ -24,7 +27,7 @@ class FreshRSS_UserDAO extends Minz_ModelPdo { if ($ok) { return true; } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + $info = empty($stm) ? array(2 => 'syntax error') : $stm->errorInfo(); Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR); return false; } @@ -34,14 +37,20 @@ class FreshRSS_UserDAO extends Minz_ModelPdo { $db = Minz_Configuration::dataBase(); require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php'); - $sql = sprintf(SQL_DROP_TABLES, $db['prefix'] . $username . '_'); - $stm = $this->bd->prepare($sql); - if ($stm && $stm->execute()) { - return true; + if ($db['type'] === 'sqlite') { + return unlink(DATA_PATH . '/' . $username . '.sqlite'); } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR); - return false; + $userPDO = new Minz_ModelPdo($username); + + $sql = sprintf(SQL_DROP_TABLES, $db['prefix'] . $username . '_'); + $stm = $userPDO->bd->prepare($sql); + if ($stm && $stm->execute()) { + return true; + } else { + $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR); + return false; + } } } } diff --git a/app/SQL/install.sql.sqlite.php b/app/SQL/install.sql.sqlite.php index b90a5ef5e..7988ada04 100644 --- a/app/SQL/install.sql.sqlite.php +++ b/app/SQL/install.sql.sqlite.php @@ -1,4 +1,5 @@ bd = self::$sharedBd; $this->prefix = self::$sharedPrefix; return; @@ -42,6 +42,10 @@ class Minz_ModelPdo { $db = Minz_Configuration::dataBase(); + if ($currentUser === null) { + $currentUser = Minz_Session::param('currentUser', '_'); + } + try { $type = $db['type']; if ($type === 'mysql') { @@ -51,9 +55,9 @@ class Minz_ModelPdo { $driver_options = array( PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8', ); - $this->prefix = $db['prefix'] . Minz_Session::param('currentUser', '_') . '_'; + $this->prefix = $db['prefix'] . $currentUser . '_'; } elseif ($type === 'sqlite') { - $string = 'sqlite:' . DATA_PATH . '/' . Minz_Session::param('currentUser', '_') . '.sqlite'; + $string = 'sqlite:' . DATA_PATH . '/' . $currentUser . '.sqlite'; $driver_options = array( //PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, ); @@ -67,7 +71,7 @@ class Minz_ModelPdo { self::$sharedDbType = $type; self::$sharedPrefix = $this->prefix; - $this->bd = new FreshPDO( + $this->bd = new MinzPDO( $string, $db['user'], $db['password'], @@ -98,7 +102,7 @@ class Minz_ModelPdo { } } -class FreshPDO extends PDO { +class MinzPDO extends PDO { private static function check($statement) { if (preg_match('/^(?:UPDATE|INSERT|DELETE)/i', $statement)) { invalidateHttpCache(); @@ -106,12 +110,12 @@ class FreshPDO extends PDO { } public function prepare($statement, $driver_options = array()) { - FreshPDO::check($statement); + MinzPDO::check($statement); return parent::prepare($statement, $driver_options); } public function exec($statement) { - FreshPDO::check($statement); + MinzPDO::check($statement); return parent::exec($statement); } } -- cgit v1.2.3 From 17d1e67e822dd7b903328195b5957eaf38a6f47b Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Tue, 16 Sep 2014 19:01:24 +0200 Subject: Fix sqlite bug ON DELETE CASCADE Foreign key constraints are not enabled by default. See https://github.com/marienfressinaud/FreshRSS/issues/579 See http://stackoverflow.com/questions/13534040/sqlite3-foreign-keys-on-pdo --- lib/Minz/ModelPdo.php | 3 +++ 1 file changed, 3 insertions(+) (limited to 'lib/Minz/ModelPdo.php') diff --git a/lib/Minz/ModelPdo.php b/lib/Minz/ModelPdo.php index 45a1e9451..b4bfca746 100644 --- a/lib/Minz/ModelPdo.php +++ b/lib/Minz/ModelPdo.php @@ -77,6 +77,9 @@ class Minz_ModelPdo { $db['password'], $driver_options ); + if ($type === 'sqlite') { + $this->bd->exec('PRAGMA foreign_keys = ON;'); + } self::$sharedBd = $this->bd; } catch (Exception $e) { throw new Minz_PDOConnectionException( -- cgit v1.2.3 From e2da6e6e6b871dc3dbc289cdd40ba401a21d8e91 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Sun, 5 Oct 2014 23:20:16 +0200 Subject: Refactor feedController See https://github.com/marienfressinaud/FreshRSS/issues/655 --- app/Controllers/feedController.php | 611 ++++++++++++++++++++----------------- lib/Minz/ModelPdo.php | 7 + 2 files changed, 330 insertions(+), 288 deletions(-) (limited to 'lib/Minz/ModelPdo.php') diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php index 70d5c4e22..0f52917e4 100755 --- a/app/Controllers/feedController.php +++ b/app/Controllers/feedController.php @@ -1,6 +1,14 @@ view->loginOk) { // Token is useful in the case that anonymous refresh is forbidden @@ -10,9 +18,8 @@ class FreshRSS_feed_Controller extends Minz_ActionController { $token_param = Minz_Request::param('token', ''); $token_is_ok = ($token != '' && $token == $token_param); $action = Minz_Request::actionName(); - if (!(($token_is_ok || Minz_Configuration::allowAnonymousRefresh()) && - $action === 'actualize') - ) { + if ($action !== 'actualize' || + !(Minz_Configuration::allowAnonymousRefresh() || $token_is_ok)) { Minz_Error::error( 403, array('error' => array(_t('access_denied'))) @@ -21,10 +28,32 @@ class FreshRSS_feed_Controller extends Minz_ActionController { } } + /** + * This action subscribes to a feed. + * + * It can be reached by both GET and POST requests. + * + * GET request displays a form to add and configure a feed. + * Request parameter is: + * - url_rss (default: false) + * + * POST request adds a feed in database. + * Parameters are: + * - url_rss (default: false) + * - category (default: false) + * - new_category (required if category == 'nc') + * - http_user (default: false) + * - http_pass (default: false) + * It tries to get website information from RSS feed. + * If no category is given, feed is added to the default one. + * + * If url_rss is false, nothing happened. + */ public function addAction() { - $url = Minz_Request::param('url_rss', false); + $url = Minz_Request::param('url_rss'); if ($url === false) { + // No url, do nothing Minz_Request::forward(array( 'c' => 'subscription', 'a' => 'index' @@ -33,14 +62,19 @@ class FreshRSS_feed_Controller extends Minz_ActionController { $feedDAO = FreshRSS_Factory::createFeedDao(); $this->catDAO = new FreshRSS_CategoryDAO(); - $this->catDAO->checkDefault(); + $url_redirect = array( + 'c' => 'subscription', + 'a' => 'index', + 'params' => array(), + ); if (Minz_Request::isPost()) { @set_time_limit(300); - - $cat = Minz_Request::param('category', false); + $cat = Minz_Request::param('category'); if ($cat === 'nc') { + // User want to create a new category, new_category parameter + // must exist $new_cat = Minz_Request::param('new_category'); if (empty($new_cat['name'])) { $cat = false; @@ -48,139 +82,114 @@ class FreshRSS_feed_Controller extends Minz_ActionController { $cat = $this->catDAO->addCategory($new_cat); } } + if ($cat === false) { + // If category was not given or if creating new category failed, + // get the default category + $this->catDAO->checkDefault(); $def_cat = $this->catDAO->getDefault(); $cat = $def_cat->id(); } + // HTTP information are useful if feed is protected behind a + // HTTP authentication $user = Minz_Request::param('http_user'); $pass = Minz_Request::param('http_pass'); - $params = array(); + $http_auth = ''; + if ($user != '' || $pass != '') { + $http_auth = $user . ':' . $pass; + } - $transactionStarted = false; + $transaction_started = false; try { $feed = new FreshRSS_Feed($url); - $feed->_category($cat); - - $httpAuth = ''; - if ($user != '' || $pass != '') { - $httpAuth = $user . ':' . $pass; - } - $feed->_httpAuth($httpAuth); - - $feed->load(true); - - $values = array( - 'url' => $feed->url(), - 'category' => $feed->category(), - 'name' => $feed->name(), - 'website' => $feed->website(), - 'description' => $feed->description(), - 'lastUpdate' => time(), - 'httpAuth' => $feed->httpAuth(), - ); - - if ($feedDAO->searchByUrl($values['url'])) { - // on est déjà abonné à ce flux - $notif = array( - 'type' => 'bad', - 'content' => _t('already_subscribed', $feed->name()) - ); - Minz_Session::_param('notification', $notif); - } else { - $id = $feedDAO->addFeed($values); - if (!$id) { - // problème au niveau de la base de données - $notif = array( - 'type' => 'bad', - 'content' => _t('feed_not_added', $feed->name()) - ); - Minz_Session::_param('notification', $notif); - } else { - $feed->_id($id); - $feed->faviconPrepare(); - - $is_read = $this->view->conf->mark_when['reception'] ? 1 : 0; - - $entryDAO = FreshRSS_Factory::createEntryDao(); - $entries = array_reverse($feed->entries()); //We want chronological order and SimplePie uses reverse order - - // on calcule la date des articles les plus anciens qu'on accepte - $nb_month_old = $this->view->conf->old_entries; - $date_min = time() - (3600 * 24 * 30 * $nb_month_old); - - //MySQL: http://docs.oracle.com/cd/E17952_01/refman-5.5-en/optimizing-innodb-transaction-management.html - //SQLite: http://stackoverflow.com/questions/1711631/how-do-i-improve-the-performance-of-sqlite - $preparedStatement = $entryDAO->addEntryPrepare(); - $transactionStarted = true; - $feedDAO->beginTransaction(); - // on ajoute les articles en masse sans vérification - foreach ($entries as $entry) { - $values = $entry->toArray(); - $values['id_feed'] = $feed->id(); - $values['id'] = min(time(), $entry->date(true)) . uSecString(); - $values['is_read'] = $is_read; - $entryDAO->addEntry($values, $preparedStatement); - } - $feedDAO->updateLastUpdate($feed->id()); - if ($transactionStarted) { - $feedDAO->commit(); - } - $transactionStarted = false; - - // ok, ajout terminé - $notif = array( - 'type' => 'good', - 'content' => _t('feed_added', $feed->name()) - ); - Minz_Session::_param('notification', $notif); - - // permet de rediriger vers la page de conf du flux - $params['id'] = $feed->id(); - } - } } catch (FreshRSS_BadUrl_Exception $e) { + // Given url was not a valid url! Minz_Log::warning($e->getMessage()); - $notif = array( - 'type' => 'bad', - 'content' => _t('invalid_url', $url) - ); - Minz_Session::_param('notification', $notif); + Minz_Request::bad(_t('invalid_url', $url), $url_redirect); + } + + try { + $feed->load(true); } catch (FreshRSS_Feed_Exception $e) { + // Something went bad (timeout, server not found, etc.) Minz_Log::warning($e->getMessage()); - $notif = array( - 'type' => 'bad', - 'content' => _t('internal_problem_feed', - Minz_Url::display(array('a' => 'logs'))) + Minz_Request::bad( + _t('internal_problem_feed', _url('index', 'logs')), + $url_redirect ); - Minz_Session::_param('notification', $notif); } catch (Minz_FileNotExistException $e) { - // Répertoire de cache n'existe pas + // Cache directory doesn't exist! Minz_Log::error($e->getMessage()); - $notif = array( - 'type' => 'bad', - 'content' => _t('internal_problem_feed', - Minz_Url::display(array('a' => 'logs'))) + Minz_Request::bad( + _t('internal_problem_feed', _url('index', 'logs')), + $url_redirect ); - Minz_Session::_param('notification', $notif); } - if ($transactionStarted) { - $feedDAO->rollBack(); + + if ($feedDAO->searchByUrl($feed->url())) { + Minz_Request::bad(_t('already_subscribed', $feed->name()), $url_redirect); } - Minz_Request::forward(array( - 'c' => 'subscription', - 'a' => 'index', - 'params' => $params - ), true); - } else { + $feed->_category($cat); + $feed->_httpAuth($http_auth); + + $values = array( + 'url' => $feed->url(), + 'category' => $feed->category(), + 'name' => $feed->name(), + 'website' => $feed->website(), + 'description' => $feed->description(), + 'lastUpdate' => time(), + 'httpAuth' => $feed->httpAuth(), + ); + + $id = $feedDAO->addFeed($values); + if (!$id) { + // There was an error in database... we cannot say what here. + Minz_Request::bad(_t('feed_not_added', $feed->name()), $url_redirect); + } + + // Ok, feed has been added in database. Now we have to refresh entries. + $feed->_id($id); + $feed->faviconPrepare(); + + $is_read = $this->view->conf->mark_when['reception'] ? 1 : 0; + + $entryDAO = FreshRSS_Factory::createEntryDao(); + // We want chronological order and SimplePie uses reverse order. + $entries = array_reverse($feed->entries()); + + // Calculate date of oldest entries we accept in DB. + $nb_month_old = $this->view->conf->old_entries; + $date_min = time() - (3600 * 24 * 30 * $nb_month_old); + + // Use a shared statement and a transaction to improve a LOT the + // performances. + $prepared_statement = $entryDAO->addEntryPrepare(); + $feedDAO->beginTransaction(); + foreach ($entries as $entry) { + // Entries are added without any verification. + $values = $entry->toArray(); + $values['id_feed'] = $feed->id(); + $values['id'] = min(time(), $entry->date(true)) . uSecString(); + $values['is_read'] = $is_read; + $entryDAO->addEntry($values, $prepared_statement); + } + $feedDAO->updateLastUpdate($feed->id()); + $feedDAO->commit(); - // GET request so we must ask confirmation to user + // Entries are in DB, we redirect to feed configuration page. + $url_redirect['params']['id'] = $feed->id(); + Minz_Request::good(_t('feed_added', $feed->name()), $url_redirect); + } else { + // GET request: we must ask confirmation to user before adding feed. Minz_View::prependTitle(_t('add_rss_feed') . ' · '); + $this->view->categories = $this->catDAO->listCategories(false); $this->view->feed = new FreshRSS_Feed($url); try { - // We try to get some more information about the feed + // We try to get more information about the feed. $this->view->feed->load(true); $this->view->load_ok = true; } catch (Exception $e) { @@ -189,43 +198,53 @@ class FreshRSS_feed_Controller extends Minz_ActionController { $feed = $feedDAO->searchByUrl($this->view->feed->url()); if ($feed) { - // Already subscribe so we redirect to the feed configuration page - $notif = array( - 'type' => 'bad', - 'content' => _t('already_subscribed', $feed->name()) - ); - Minz_Session::_param('notification', $notif); - - Minz_Request::forward(array( - 'c' => 'subscription', - 'a' => 'index', - 'params' => array( - 'id' => $feed->id() - ) - ), true); + // Already subscribe so we redirect to the feed configuration page. + $url_redirect['params']['id'] = $feed->id(); + Minz_Request::good(_t('already_subscribed', $feed->name()), $url_redirect); } } } + /** + * This action remove entries from a given feed. + * + * It should be reached by a POST action. + * + * Parameter is: + * - id (default: false) + */ public function truncateAction() { - if (Minz_Request::isPost()) { - $id = Minz_Request::param('id'); - $feedDAO = FreshRSS_Factory::createFeedDao(); - $n = $feedDAO->truncate($id); - $notif = array( - 'type' => $n === false ? 'bad' : 'good', - 'content' => _t('n_entries_deleted', $n) - ); - Minz_Session::_param('notification', $notif); - invalidateHttpCache(); - Minz_Request::forward(array( - 'c' => 'subscription', - 'a' => 'index', - 'params' => array('id' => $id) - ), true); + $id = Minz_Request::param('id'); + $url_redirect = array( + 'c' => 'subscription', + 'a' => 'index', + 'params' => array('id' => $id) + ); + + if (!Minz_Request::isPost()) { + Minz_Request::forward($url_redirect, true); + } + + $feedDAO = FreshRSS_Factory::createFeedDao(); + $n = $feedDAO->truncate($id); + + invalidateHttpCache(); + if ($n === false) { + Minz_Request::bad(_t('error_occurred'), $url_redirect); + } else { + Minz_Request::good(_t('n_entries_deleted', $n), $url_redirect); } } + /** + * This action actualizes entries from one or several feeds. + * + * Parameters are: + * - id (default: false) + * - force (default: false) + * If id is not specified, all the feeds are actualized. But if force is + * false, process stops at 10 feeds to avoid time execution problem. + */ public function actualizeAction() { @set_time_limit(300); @@ -234,213 +253,229 @@ class FreshRSS_feed_Controller extends Minz_ActionController { Minz_Session::_param('actualize_feeds', false); $id = Minz_Request::param('id'); - $force = Minz_Request::param('force', false); + $force = Minz_Request::param('force'); - // on créé la liste des flux à mettre à actualiser - // si on veut mettre un flux à jour spécifiquement, on le met - // dans la liste, mais seul (permet d'automatiser le traitement) + // Create a list of feeds to actualize. + // If id is set and valid, corresponding feed is added to the list but + // alone in order to automatize further process. $feeds = array(); if ($id) { $feed = $feedDAO->searchById($id); if ($feed) { - $feeds = array($feed); + $feeds[] = $feed; } } else { $feeds = $feedDAO->listFeedsOrderUpdate($this->view->conf->ttl_default); } - // on calcule la date des articles les plus anciens qu'on accepte + // Calculate date of oldest entries we accept in DB. $nb_month_old = max($this->view->conf->old_entries, 1); $date_min = time() - (3600 * 24 * 30 * $nb_month_old); - $i = 0; - $flux_update = 0; + $updated_feeds = 0; $is_read = $this->view->conf->mark_when['reception'] ? 1 : 0; foreach ($feeds as $feed) { if (!$feed->lock()) { Minz_Log::notice('Feed already being actualized: ' . $feed->url()); continue; } - try { - $url = $feed->url(); - $feedHistory = $feed->keepHistory(); + try { + // Load entries $feed->load(false); - $entries = array_reverse($feed->entries()); //We want chronological order and SimplePie uses reverse order - $hasTransaction = false; - - if (count($entries) > 0) { - //For this feed, check last n entry GUIDs already in database - $existingGuids = array_fill_keys( - $entryDAO->listLastGuidsByFeed($feed->id(), - count($entries) + 10), 1); - $useDeclaredDate = empty($existingGuids); - - if ($feedHistory == -2) { //default - $feedHistory = $this->view->conf->keep_history_default; - } + } catch (FreshRSS_Feed_Exception $e) { + Minz_Log::notice($e->getMessage()); + $feedDAO->updateLastUpdate($feed->id(), 1); + continue; + } - $preparedStatement = $entryDAO->addEntryPrepare(); - $hasTransaction = true; - $feedDAO->beginTransaction(); + $url = $feed->url(); + $feed_history = $feed->keepHistory(); + if ($feed_history == -2) { + // TODO: -2 must be a constant! + // -2 means we take the default value from configuration + $feed_history = $this->view->conf->keep_history_default; + } - // On ne vérifie pas strictement que l'article n'est pas déjà en BDD - // La BDD refusera l'ajout car (id_feed, guid) doit être unique - foreach ($entries as $entry) { - $eDate = $entry->date(true); - if ((!isset($existingGuids[$entry->guid()])) && - (($feedHistory != 0) || ($eDate >= $date_min))) { - $values = $entry->toArray(); - //Use declared date at first import, otherwise use discovery date - $values['id'] = ($useDeclaredDate || $eDate < $date_min) ? - min(time(), $eDate) . uSecString() : - uTimeString(); - $values['is_read'] = $is_read; - $entryDAO->addEntry($values, $preparedStatement); - } + // We want chronological order and SimplePie uses reverse order. + $entries = array_reverse($feed->entries()); + if (count($entries) > 0) { + // For this feed, check last n entry GUIDs already in database. + $existing_guids = array_fill_keys($entryDAO->listLastGuidsByFeed( + $feed->id(), count($entries) + 10 + ), 1); + $use_declared_date = empty($existing_guids); + + // Add entries in database if possible. + $prepared_statement = $entryDAO->addEntryPrepare(); + $feedDAO->beginTransaction(); + foreach ($entries as $entry) { + $entry_date = $entry->date(true); + if (isset($existing_guids[$entry->guid()]) || + ($feed_history == 0 && $entry_date < $date_min)) { + // This entry already exists in DB or should not be added + // considering configuration and date. + continue; } - } - if (($feedHistory >= 0) && (rand(0, 30) === 1)) { - if (!$hasTransaction) { - $feedDAO->beginTransaction(); - } - $nb = $feedDAO->cleanOldEntries($feed->id(), $date_min, max($feedHistory, count($entries) + 10)); - if ($nb > 0) { - Minz_Log::debug($nb . ' old entries cleaned in feed [' . $feed->url() . ']'); + $id = uTimeString(); + if ($use_declared_date || $entry_date < $date_min) { + // Use declared date at first import. + $id = min(time(), $entry_date) . uSecString(); } + + $values = $entry->toArray(); + $values['id'] = $id; + $values['is_read'] = $is_read; + $entryDAO->addEntry($values, $prepared_statement); } + } - // on indique que le flux vient d'être mis à jour en BDD - $feedDAO->updateLastUpdate($feed->id(), 0, $hasTransaction); - if ($hasTransaction) { - $feedDAO->commit(); + if ($feed_history >= 0 && rand(0, 30) === 1) { + // TODO: move this function in web cron when available + // Remove old entries once in 30. + if (!$feedDAO->hasTransaction()) { + $feedDAO->beginTransaction(); } - $flux_update++; - if (($feed->url() !== $url)) { //HTTP 301 Moved Permanently - Minz_Log::notice('Feed ' . $url . ' moved permanently to ' . $feed->url()); - $feedDAO->updateFeed($feed->id(), array('url' => $feed->url())); + + $nb = $feedDAO->cleanOldEntries($feed->id(), + $date_min, + max($feed_history, count($entries) + 10)); + if ($nb > 0) { + Minz_Log::debug($nb . ' old entries cleaned in feed [' . + $feed->url() . ']'); } - } catch (FreshRSS_Feed_Exception $e) { - Minz_Log::notice($e->getMessage()); - $feedDAO->updateLastUpdate($feed->id(), 1); + } + + $feedDAO->updateLastUpdate($feed->id(), 0, $feedDAO->hasTransaction()); + if ($feedDAO->hasTransaction()) { + $feedDAO->commit(); + } + + if ($feed->url() !== $url) { + // HTTP 301 Moved Permanently + Minz_Log::notice('Feed ' . $url . ' moved permanently to ' . $feed->url()); + $feedDAO->updateFeed($feed->id(), array('url' => $feed->url())); } $feed->faviconPrepare(); $feed->unlock(); + $updated_feeds++; unset($feed); - // On arrête à 10 flux pour ne pas surcharger le serveur - // sauf si le paramètre $force est à vrai - $i++; - if ($i >= 10 && !$force) { + // No more than 10 feeds unless $force is true to avoid overloading + // the server. + if ($updated_feeds >= 10 && !$force) { break; } } - $url = array(); - if ($flux_update === 1) { - // on a mis un seul flux à jour - $feed = reset($feeds); + if (Minz_Request::param('ajax')) { + // Most of the time, ajax request is for only one feed. But since + // there are several parallel requests, we should return that there + // are several updated feeds. $notif = array( 'type' => 'good', - 'content' => _t('feed_actualized', $feed->name()) - ); - } elseif ($flux_update > 1) { - // plusieurs flux on été mis à jour - $notif = array( - 'type' => 'good', - 'content' => _t('n_feeds_actualized', $flux_update) - ); - } else { - // aucun flux n'a été mis à jour, oups - $notif = array( - 'type' => 'good', - 'content' => _t('no_feed_to_refresh') + 'content' => _t('feeds_actualized') ); + Minz_Session::_param('notification', $notif); + // No layout in ajax request. + $this->view->_useLayout(false); + return; } - if ($i === 1) { - // Si on a voulu mettre à jour qu'un flux - // on filtre l'affichage par ce flux + // Redirect to the main page with correct notification. + if ($updated_feeds === 1) { $feed = reset($feeds); - $url['params'] = array('get' => 'f_' . $feed->id()); - } - - if (Minz_Request::param('ajax', 0) === 0) { - Minz_Session::_param('notification', $notif); - Minz_Request::forward($url, true); + Minz_Request::good(_t('feed_actualized', $feed->name()), + array('get' => 'f_' . $feed->id())); + } elseif ($updated_feeds > 1) { + Minz_Request::good(_t('n_feeds_actualized', $updated_feeds), array()); } else { - // Une requête Ajax met un seul flux à jour. - // Comme en principe plusieurs requêtes ont lieu, - // on indique que "plusieurs flux ont été mis à jour". - // Cela permet d'avoir une notification plus proche du - // ressenti utilisateur - $notif = array( - 'type' => 'good', - 'content' => _t('feeds_actualized') - ); - Minz_Session::_param('notification', $notif); - // et on désactive le layout car ne sert à rien - $this->view->_useLayout(false); + Minz_Request::good(_t('no_feed_to_refresh'), array()); } } + /** + * This action changes the category of a feed. + * + * This page must be reached by a POST request. + * + * Parameters are: + * - f_id (default: false) + * - c_id (default: false) + * If c_id is false, default category is used. + * + * @todo should handle order of the feed inside the category. + */ public function moveAction() { - if (Minz_Request::isPost()) { - $feed_id = Minz_Request::param('f_id'); - $cat_id = Minz_Request::param('c_id'); + if (!Minz_Request::isPost()) { + Minz_Request::forward(array('c' => 'subscription'), true); + } - $feedDAO = FreshRSS_Factory::createFeedDao(); - $values = array( - 'category' => $cat_id, - ); + $feed_id = Minz_Request::param('f_id'); + $cat_id = Minz_Request::param('c_id'); - $feed = $feedDAO->searchById($feed_id); + if ($cat_id === false) { + // If category was not given get the default one. + $catDAO = new FreshRSS_CategoryDAO(); + $catDAO->checkDefault(); + $def_cat = $catDAO->getDefault(); + $cat_id = $def_cat->id(); + } - if ($feed && ($feed->category() == $cat_id || - $feedDAO->updateFeed($feed_id, $values))) { - // TODO: return something useful - } else { - Minz_Error::error( - 404, - array('error' => array(_t('error_occurred'))) - ); - } + $feedDAO = FreshRSS_Factory::createFeedDao(); + $values = array('category' => $cat_id); + + $feed = $feedDAO->searchById($feed_id); + if ($feed && ($feed->category() == $cat_id || + $feedDAO->updateFeed($feed_id, $values))) { + // TODO: return something useful + } else { + Minz_Log::warning('Cannot move feed `' . $feed_id . '` ' . + 'in the category `' . $cat_id . '`'); + Minz_Error::error( + 404, + array('error' => array(_t('error_occurred'))) + ); } } + /** + * This action deletes a feed. + * + * This page must be reached by a POST request. + * If there are related queries, they are deleted too. + * + * Parameters are: + * - id (default: false) + * - r (default: false) + * r permits to redirect to a given page at the end of this action. + * + * @todo handle "r" redirection in Minz_Request::forward()? + */ public function deleteAction() { - if (Minz_Request::isPost()) { - $id = Minz_Request::param('id'); - $feedDAO = FreshRSS_Factory::createFeedDao(); - - if ($feedDAO->deleteFeed($id)) { - // TODO: Delete old favicon + $redirect_url = Minz_Request::param('r', false, true); + if (!$redirect_url) { + $redirect_url = array('c' => 'subscription', 'a' => 'index'); + } - // Remove related queries - $this->view->conf->remove_query_by_get('f_' . $id); - $this->view->conf->save(); + if (!Minz_Request::isPost()) { + Minz_Request::forward($redirect_url, true); + } - $notif = array( - 'type' => 'good', - 'content' => _t('feed_deleted') - ); - } else { - $notif = array( - 'type' => 'bad', - 'content' => _t('error_occured') - ); - } + $id = Minz_Request::param('id'); + $feedDAO = FreshRSS_Factory::createFeedDao(); + if ($feedDAO->deleteFeed($id)) { + // TODO: Delete old favicon - Minz_Session::_param('notification', $notif); + // Remove related queries + $this->view->conf->remove_query_by_get('f_' . $id); + $this->view->conf->save(); - $redirect_url = Minz_Request::param('r', false, true); - if ($redirect_url) { - Minz_Request::forward($redirect_url); - } else { - Minz_Request::forward(array('c' => 'subscription', 'a' => 'index'), true); - } + Minz_Request::good(_t('feed_deleted'), $redirect_url); + } else { + Minz_Request::bad(_t('error_occurred'), $redirect_url); } } } diff --git a/lib/Minz/ModelPdo.php b/lib/Minz/ModelPdo.php index b4bfca746..69e46fb39 100644 --- a/lib/Minz/ModelPdo.php +++ b/lib/Minz/ModelPdo.php @@ -16,6 +16,7 @@ class Minz_ModelPdo { public static $useSharedBd = true; private static $sharedBd = null; private static $sharedPrefix; + private static $has_transaction = false; protected static $sharedDbType; /** @@ -91,12 +92,18 @@ class Minz_ModelPdo { public function beginTransaction() { $this->bd->beginTransaction(); + $this->has_transaction = true; + } + public function hasTransaction() { + return $this->has_transaction; } public function commit() { $this->bd->commit(); + $this->has_transaction = false; } public function rollBack() { $this->bd->rollBack(); + $this->has_transaction = false; } public static function clean() { -- cgit v1.2.3 From 031c1d802d74b94d5ef5245f3678138107621179 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Sun, 5 Oct 2014 23:57:54 +0200 Subject: Fix a bug (has_transaction) Introduced by the last commit. --- lib/Minz/ModelPdo.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'lib/Minz/ModelPdo.php') diff --git a/lib/Minz/ModelPdo.php b/lib/Minz/ModelPdo.php index 69e46fb39..66127ea22 100644 --- a/lib/Minz/ModelPdo.php +++ b/lib/Minz/ModelPdo.php @@ -92,18 +92,18 @@ class Minz_ModelPdo { public function beginTransaction() { $this->bd->beginTransaction(); - $this->has_transaction = true; + self::$has_transaction = true; } public function hasTransaction() { - return $this->has_transaction; + return self::$has_transaction; } public function commit() { $this->bd->commit(); - $this->has_transaction = false; + self::$has_transaction = false; } public function rollBack() { $this->bd->rollBack(); - $this->has_transaction = false; + self::$has_transaction = false; } public static function clean() { -- cgit v1.2.3 From 5797344aff9ceebbdeb6e49305f3984a5c89f82c Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Thu, 16 Oct 2014 17:15:51 +0200 Subject: Fix a bug to get size of user (SQLite) --- app/Controllers/userController.php | 3 ++- app/Models/EntryDAOSQLite.php | 2 +- lib/Minz/ModelPdo.php | 2 ++ 3 files changed, 5 insertions(+), 2 deletions(-) (limited to 'lib/Minz/ModelPdo.php') diff --git a/app/Controllers/userController.php b/app/Controllers/userController.php index 4a04737f2..d5c90a382 100644 --- a/app/Controllers/userController.php +++ b/app/Controllers/userController.php @@ -94,14 +94,15 @@ class FreshRSS_user_Controller extends Minz_ActionController { Minz_View::prependTitle(_t('users.manage') . ' · '); + // Get the correct current user. $userDAO = new FreshRSS_UserDAO(); - $username = Minz_Request::param('u', Minz_Session::param('currentUser')); if (!$userDAO->exist($username)) { $username = Minz_Session::param('currentUser'); } $this->view->current_user = $username; + // Get information about the current user. $entryDAO = FreshRSS_Factory::createEntryDao($this->view->current_user); $this->view->nb_articles = $entryDAO->count(); $this->view->size_user = $entryDAO->size(); diff --git a/app/Models/EntryDAOSQLite.php b/app/Models/EntryDAOSQLite.php index 66078aca9..4a3fe24a2 100644 --- a/app/Models/EntryDAOSQLite.php +++ b/app/Models/EntryDAOSQLite.php @@ -124,6 +124,6 @@ class FreshRSS_EntryDAOSQLite extends FreshRSS_EntryDAO { } public function size($all = false) { - return @filesize(DATA_PATH . '/' . Minz_Session::param('currentUser', '_') . '.sqlite'); + return @filesize(DATA_PATH . '/' . $this->current_user . '.sqlite'); } } diff --git a/lib/Minz/ModelPdo.php b/lib/Minz/ModelPdo.php index 66127ea22..827c89c69 100644 --- a/lib/Minz/ModelPdo.php +++ b/lib/Minz/ModelPdo.php @@ -24,6 +24,7 @@ class Minz_ModelPdo { */ protected $bd; + protected $current_user; protected $prefix; public function dbType() { @@ -46,6 +47,7 @@ class Minz_ModelPdo { if ($currentUser === null) { $currentUser = Minz_Session::param('currentUser', '_'); } + $this->current_user = $currentUser; try { $type = $db['type']; -- cgit v1.2.3 From ee5bbe48269a2fd5bc9c175fdb1e5a92a2c04502 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Thu, 30 Oct 2014 19:19:09 +0100 Subject: Fix bug size with SQLite --- app/Controllers/configureController.php | 2 +- lib/Minz/ModelPdo.php | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) (limited to 'lib/Minz/ModelPdo.php') diff --git a/app/Controllers/configureController.php b/app/Controllers/configureController.php index cafd0e8a8..deb8cc849 100755 --- a/app/Controllers/configureController.php +++ b/app/Controllers/configureController.php @@ -225,7 +225,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController { Minz_View::prependTitle(_t('archiving_configuration') . ' · '); - $entryDAO = FreshRSS_Factory::createEntryDao(); + $entryDAO = FreshRSS_Factory::createEntryDao('freshrss'); $this->view->nb_total = $entryDAO->count(); $this->view->size_user = $entryDAO->size(); diff --git a/lib/Minz/ModelPdo.php b/lib/Minz/ModelPdo.php index 827c89c69..6198cd85c 100644 --- a/lib/Minz/ModelPdo.php +++ b/lib/Minz/ModelPdo.php @@ -17,6 +17,7 @@ class Minz_ModelPdo { private static $sharedBd = null; private static $sharedPrefix; private static $has_transaction = false; + private static $sharedCurrentUser; protected static $sharedDbType; /** @@ -39,6 +40,7 @@ class Minz_ModelPdo { if (self::$useSharedBd && self::$sharedBd != null && $currentUser === null) { $this->bd = self::$sharedBd; $this->prefix = self::$sharedPrefix; + $this->current_user = self::$sharedCurrentUser; return; } @@ -48,6 +50,7 @@ class Minz_ModelPdo { $currentUser = Minz_Session::param('currentUser', '_'); } $this->current_user = $currentUser; + self::$sharedCurrentUser = $currentUser; try { $type = $db['type']; -- cgit v1.2.3 From b5bee8560345e4123432a8bd3bcd63b938549ef9 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Sun, 21 Dec 2014 13:10:02 +0100 Subject: BREAKING FEATURE: move user data - Create ./data/users/ folder - Move user configuration to ./data/users/username/config.php - Move sqlite db to ./data/users/username/db.sqlite - Move user logs to ./data/users/username/log.txt See https://github.com/FreshRSS/FreshRSS/issues/729 --- app/Controllers/userController.php | 15 ++++++++------- app/Models/Configuration.php | 4 ++-- app/Models/LogDAO.php | 4 ++-- app/Models/UserDAO.php | 8 ++++---- data/.gitignore | 1 - data/log/.gitignore | 1 - data/log/index.html | 13 ------------- data/users/.gitignore | 4 ++++ data/users/index.html | 13 +++++++++++++ lib/Minz/Log.php | 2 +- lib/Minz/ModelPdo.php | 2 +- lib/lib_rss.php | 24 +++++++++++++++--------- 12 files changed, 50 insertions(+), 41 deletions(-) delete mode 100644 data/log/.gitignore delete mode 100644 data/log/index.html create mode 100644 data/users/.gitignore create mode 100644 data/users/index.html (limited to 'lib/Minz/ModelPdo.php') diff --git a/app/Controllers/userController.php b/app/Controllers/userController.php index 3b40e42dc..1b1ccaac9 100644 --- a/app/Controllers/userController.php +++ b/app/Controllers/userController.php @@ -109,7 +109,8 @@ class FreshRSS_user_Controller extends Minz_ActionController { require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php'); $new_user_language = Minz_Request::param('new_user_language', FreshRSS_Context::$conf->language); - if (!in_array($new_user_language, FreshRSS_Context::$conf->availableLanguages())) { + $languages = FreshRSS_Context::$conf->availableLanguages(); + if (!isset($languages[$new_user_language])) { $new_user_language = FreshRSS_Context::$conf->language; } @@ -121,11 +122,10 @@ class FreshRSS_user_Controller extends Minz_ActionController { $ok &= !in_array(strtoupper($new_user_name), array_map('strtoupper', listUsers())); //Not an existing user, case-insensitive - $configPath = DATA_PATH . '/' . $new_user_name . '_user.php'; + $configPath = join_path(DATA_PATH, 'users', $new_user_name, 'config.php'); $ok &= !file_exists($configPath); } if ($ok) { - $passwordPlain = Minz_Request::param('new_user_passwordPlain', '', true); $passwordHash = ''; if ($passwordPlain != '') { @@ -147,12 +147,13 @@ class FreshRSS_user_Controller extends Minz_ActionController { if (empty($new_user_email)) { $new_user_email = ''; } else { - $personaFile = DATA_PATH . '/persona/' . $new_user_email . '.txt'; + $personaFile = join_path(DATA_PATH, 'persona', $new_user_email . '.txt'); @unlink($personaFile); $ok &= (file_put_contents($personaFile, $new_user_name) !== false); } } if ($ok) { + mkdir(join_path(DATA_PATH, 'users', $new_user_name)); $config_array = array( 'language' => $new_user_language, 'passwordHash' => $passwordHash, @@ -183,18 +184,18 @@ class FreshRSS_user_Controller extends Minz_ActionController { $username = Minz_Request::param('username'); $ok = ctype_alnum($username); + $user_data = join_path(DATA_PATH, 'users', $username); if ($ok) { $ok &= (strcasecmp($username, Minz_Configuration::defaultUser()) !== 0); //It is forbidden to delete the default user } if ($ok) { - $configPath = DATA_PATH . '/' . $username . '_user.php'; - $ok &= file_exists($configPath); + $ok &= is_dir($user_data); } if ($ok) { $userDAO = new FreshRSS_UserDAO(); $ok &= $userDAO->deleteUser($username); - $ok &= unlink($configPath); + $ok &= recursive_unlink($user_data); //TODO: delete Persona file } invalidateHttpCache(); diff --git a/app/Models/Configuration.php b/app/Models/Configuration.php index 8668470b0..8bba8f777 100644 --- a/app/Models/Configuration.php +++ b/app/Models/Configuration.php @@ -74,7 +74,7 @@ class FreshRSS_Configuration { private $shares; public function __construct($user) { - $this->filename = DATA_PATH . DIRECTORY_SEPARATOR . $user . '_user.php'; + $this->filename = join_path(DATA_PATH, 'users', $user, 'config.php'); $data = @include($this->filename); if (!is_array($data)) { @@ -89,7 +89,7 @@ class FreshRSS_Configuration { } $this->data['user'] = $user; - $this->shares = DATA_PATH . DIRECTORY_SEPARATOR . 'shares.php'; + $this->shares = join_path(DATA_PATH, 'shares.php'); $shares = @include($this->shares); if (!is_array($shares)) { diff --git a/app/Models/LogDAO.php b/app/Models/LogDAO.php index 21593435d..4c56e3150 100644 --- a/app/Models/LogDAO.php +++ b/app/Models/LogDAO.php @@ -3,7 +3,7 @@ class FreshRSS_LogDAO { public static function lines() { $logs = array(); - $handle = @fopen(LOG_PATH . '/' . Minz_Session::param('currentUser', '_') . '.log', 'r'); + $handle = @fopen(join_path(DATA_PATH, 'users', Minz_Session::param('currentUser', '_'), 'log.txt'), 'r'); if ($handle) { while (($line = fgets($handle)) !== false) { if (preg_match('/^\[([^\[]+)\] \[([^\[]+)\] --- (.*)$/', $line, $matches)) { @@ -20,6 +20,6 @@ class FreshRSS_LogDAO { } public static function truncate() { - file_put_contents(LOG_PATH . '/' . Minz_Session::param('currentUser', '_') . '.log', ''); + file_put_contents(join_path(DATA_PATH, 'users', Minz_Session::param('currentUser', '_'), 'log.txt'), ''); } } diff --git a/app/Models/UserDAO.php b/app/Models/UserDAO.php index f04ae26bf..6514080bc 100644 --- a/app/Models/UserDAO.php +++ b/app/Models/UserDAO.php @@ -38,7 +38,7 @@ class FreshRSS_UserDAO extends Minz_ModelPdo { require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php'); if ($db['type'] === 'sqlite') { - return unlink(DATA_PATH . '/' . $username . '.sqlite'); + return unlink(join_path(DATA_PATH, 'users', $username, 'db.sqlite')); } else { $userPDO = new Minz_ModelPdo($username); @@ -55,14 +55,14 @@ class FreshRSS_UserDAO extends Minz_ModelPdo { } public static function exist($username) { - return file_exists(DATA_PATH . '/' . $username . '_user.php'); + return is_dir(join_path(DATA_PATH , 'users', $username)); } public static function touch($username) { - return touch(DATA_PATH . '/' . $username . '_user.php'); + return touch(join_path(DATA_PATH , 'users', $username, 'config.php')); } public static function mtime($username) { - return @filemtime(DATA_PATH . '/' . $username . '_user.php'); + return @filemtime(join_path(DATA_PATH , 'users', $username, 'config.php')); } } diff --git a/data/.gitignore b/data/.gitignore index 325fa75c6..20364e266 100644 --- a/data/.gitignore +++ b/data/.gitignore @@ -1,6 +1,5 @@ application.ini config.php -*_user.php *.sqlite touch.txt no-cache.txt diff --git a/data/log/.gitignore b/data/log/.gitignore deleted file mode 100644 index bf0824e59..000000000 --- a/data/log/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.log \ No newline at end of file diff --git a/data/log/index.html b/data/log/index.html deleted file mode 100644 index 85faaa37e..000000000 --- a/data/log/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - -Redirection - - - - -

Redirection

- - diff --git a/data/users/.gitignore b/data/users/.gitignore new file mode 100644 index 000000000..53ed0587b --- /dev/null +++ b/data/users/.gitignore @@ -0,0 +1,4 @@ +db.sqlite +config.php +log.txt + diff --git a/data/users/index.html b/data/users/index.html new file mode 100644 index 000000000..85faaa37e --- /dev/null +++ b/data/users/index.html @@ -0,0 +1,13 @@ + + + + + +Redirection + + + + +

Redirection

+ + diff --git a/lib/Minz/Log.php b/lib/Minz/Log.php index d3eaec2ae..26412c547 100644 --- a/lib/Minz/Log.php +++ b/lib/Minz/Log.php @@ -37,7 +37,7 @@ class Minz_Log { || ($env === Minz_Configuration::PRODUCTION && ($level >= Minz_Log::NOTICE)))) { if ($file_name === null) { - $file_name = LOG_PATH . '/' . Minz_Session::param('currentUser', '_') . '.log'; + $file_name = join_path(DATA_PATH, 'users', Minz_Session::param('currentUser', '_'), 'log.txt'); } switch ($level) { diff --git a/lib/Minz/ModelPdo.php b/lib/Minz/ModelPdo.php index 6198cd85c..118d89ad2 100644 --- a/lib/Minz/ModelPdo.php +++ b/lib/Minz/ModelPdo.php @@ -63,7 +63,7 @@ class Minz_ModelPdo { ); $this->prefix = $db['prefix'] . $currentUser . '_'; } elseif ($type === 'sqlite') { - $string = 'sqlite:' . DATA_PATH . '/' . $currentUser . '.sqlite'; + $string = 'sqlite:' . join_path(DATA_PATH, 'users', $currentUser, 'db.sqlite'); $driver_options = array( //PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, ); diff --git a/lib/lib_rss.php b/lib/lib_rss.php index 264c69d58..cfd31b2c8 100644 --- a/lib/lib_rss.php +++ b/lib/lib_rss.php @@ -15,6 +15,17 @@ if (!function_exists('json_encode')) { } } +/** + * Build a directory path by concatenating a list of directory names. + * + * @param $path_parts a list of directory names + * @return a string corresponding to the final pathname + */ +function join_path() { + $path_parts = func_get_args(); + return join(DIRECTORY_SEPARATOR, $path_parts); +} + // function classAutoloader($class) { if (strpos($class, 'FreshRSS') === 0) { @@ -208,16 +219,11 @@ function invalidateHttpCache() { return touch(LOG_PATH . '/' . Minz_Session::param('currentUser', '_') . '.log'); } -function usernameFromPath($userPath) { - if (preg_match('%/([A-Za-z0-9]{1,16})_user\.php$%', $userPath, $matches)) { - return $matches[1]; - } else { - return ''; - } -} - function listUsers() { - return array_map('usernameFromPath', glob(DATA_PATH . '/*_user.php')); + return array_values(array_diff( + scandir(join_path(DATA_PATH, 'users')), + array('..', '.') + )); } function httpAuthUser() { -- cgit v1.2.3 From 51a71ec4b9d62528054be8faee1576a8fd6d37f6 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Mon, 5 Jan 2015 16:54:16 +0100 Subject: New configuration system (not working yet) - Use only Minz_Configuration - register() method to load a new configuration file - get() to get a configuration - new exceptions related to configuration - fix a list configuration calls to have FRSS working Current problems to resolve: - How to handle configuration param verifications (i.e. check auth_type is a value from none, http_auth, persona or form) - We must use $conf = Minz_Configuration::get('system'); $general_conf = $conf->general; to access global system configuration which is quite annoying. How to change that? See https://github.com/FreshRSS/FreshRSS/issues/730 --- app/Controllers/authController.php | 36 +- app/FreshRSS.php | 8 +- app/Models/Auth.php | 40 ++- app/Models/Context.php | 4 +- app/Models/Factory.php | 16 +- app/Models/Feed.php | 3 +- app/layout/header.phtml | 13 +- app/layout/layout.phtml | 3 +- app/views/helpers/javascript_vars.phtml | 3 +- data/config.default.php | 32 ++ data/users/_/config.default.php | 66 ++++ lib/Minz/BadConfigurationException.php | 9 - lib/Minz/Configuration.php | 517 ++++++++------------------- lib/Minz/ConfigurationException.php | 8 + lib/Minz/ConfigurationNamespaceException.php | 4 + lib/Minz/ConfigurationParamException.php | 4 + lib/Minz/Error.php | 7 +- lib/Minz/FrontController.php | 26 +- lib/Minz/Log.php | 11 +- lib/Minz/ModelPdo.php | 3 +- lib/Minz/Request.php | 3 +- lib/Minz/Translate.php | 3 +- lib/Minz/View.php | 4 +- p/i/index.php | 2 +- 24 files changed, 384 insertions(+), 441 deletions(-) create mode 100644 data/config.default.php create mode 100644 data/users/_/config.default.php delete mode 100644 lib/Minz/BadConfigurationException.php create mode 100644 lib/Minz/ConfigurationException.php create mode 100644 lib/Minz/ConfigurationNamespaceException.php create mode 100644 lib/Minz/ConfigurationParamException.php (limited to 'lib/Minz/ModelPdo.php') diff --git a/app/Controllers/authController.php b/app/Controllers/authController.php index ccc32ec0d..f68ea8da5 100644 --- a/app/Controllers/authController.php +++ b/app/Controllers/authController.php @@ -27,6 +27,8 @@ class FreshRSS_auth_Controller extends Minz_ActionController { if (Minz_Request::isPost()) { $ok = true; + $system_conf = Minz_Configuration::get('system'); + $general = $system_conf->general; $current_token = FreshRSS_Context::$conf->token; $token = Minz_Request::param('token', $current_token); FreshRSS_Context::$conf->_token($token); @@ -39,18 +41,21 @@ class FreshRSS_auth_Controller extends Minz_ActionController { $auth_type = Minz_Request::param('auth_type', 'none'); $unsafe_autologin = Minz_Request::param('unsafe_autologin', false); $api_enabled = Minz_Request::param('api_enabled', false); - if ($anon != Minz_Configuration::allowAnonymous() || - $auth_type != Minz_Configuration::authType() || - $anon_refresh != Minz_Configuration::allowAnonymousRefresh() || - $unsafe_autologin != Minz_Configuration::unsafeAutologinEnabled() || - $api_enabled != Minz_Configuration::apiEnabled()) { - - Minz_Configuration::_authType($auth_type); - Minz_Configuration::_allowAnonymous($anon); - Minz_Configuration::_allowAnonymousRefresh($anon_refresh); - Minz_Configuration::_enableAutologin($unsafe_autologin); - Minz_Configuration::_enableApi($api_enabled); - $ok &= Minz_Configuration::writeFile(); + if ($anon != $general['allow_anonymous'] || + $auth_type != $general['auth_type'] || + $anon_refresh != $general['allow_anonymous_refresh'] || + $unsafe_autologin != $general['unsafe_autologin_enabled'] || + $api_enabled != $general['api_enabled']) { + + // TODO: test values from form + $general['auth_type'] = $auth_type; + $general['allow_anonymous'] = $anon; + $general['allow_anonymous_refresh'] = $anon_refresh; + $general['unsafe_autologin_enabled'] = $unsafe_autologin; + $general['api_enabled'] = $api_enabled; + + $system_conf->general = $general; + $ok &= $system_conf->save(); } invalidateHttpCache(); @@ -76,7 +81,8 @@ class FreshRSS_auth_Controller extends Minz_ActionController { Minz_Request::forward(array('c' => 'index', 'a' => 'index'), true); } - $auth_type = Minz_Configuration::authType(); + $conf = Minz_Configuration::get('system'); + $auth_type = $conf->general['auth_type']; switch ($auth_type) { case 'form': Minz_Request::forward(array('c' => 'auth', 'a' => 'formLogin')); @@ -114,6 +120,8 @@ class FreshRSS_auth_Controller extends Minz_ActionController { $file_mtime = @filemtime(PUBLIC_PATH . '/scripts/bcrypt.min.js'); Minz_View::appendScript(Minz_Url::display('/scripts/bcrypt.min.js?' . $file_mtime)); + $conf = Minz_Configuration::get('system'); + if (Minz_Request::isPost()) { $nonce = Minz_Session::param('nonce'); $username = Minz_Request::param('username', ''); @@ -154,7 +162,7 @@ class FreshRSS_auth_Controller extends Minz_ActionController { Minz_Request::bad(_t('feedback.auth.login.invalid'), array('c' => 'auth', 'a' => 'login')); } - } elseif (Minz_Configuration::unsafeAutologinEnabled()) { + } elseif ($conf->general['unsafe_autologin_enabled']) { $username = Minz_Request::param('u', ''); $password = Minz_Request::param('p', ''); Minz_Request::_param('p'); diff --git a/app/FreshRSS.php b/app/FreshRSS.php index 6114a5d1a..455f2fefd 100644 --- a/app/FreshRSS.php +++ b/app/FreshRSS.php @@ -6,6 +6,11 @@ class FreshRSS extends Minz_FrontController { Minz_Session::init('FreshRSS'); } + $current_user = Minz_Session::param('currentUser', '_'); + Minz_Configuration::register('user', + join_path(USERS_PATH, $current_user, 'config.php'), + join_path(USERS_PATH, '_', 'config.default.php')); + // Need to be called just after session init because it initializes // current user. FreshRSS_Auth::init(); @@ -57,7 +62,8 @@ class FreshRSS extends Minz_FrontController { Minz_View::appendScript(Minz_Url::display('/scripts/shortcut.js?' . @filemtime(PUBLIC_PATH . '/scripts/shortcut.js'))); Minz_View::appendScript(Minz_Url::display('/scripts/main.js?' . @filemtime(PUBLIC_PATH . '/scripts/main.js'))); - if (Minz_Configuration::authType() === 'persona') { + $conf = Minz_Configuration::get('system'); + if ($conf->general['auth_type'] === 'persona') { // TODO move it in a plugin // Needed for login AND logout with Persona. Minz_View::appendScript('https://login.persona.org/include.js'); diff --git a/app/Models/Auth.php b/app/Models/Auth.php index 2971d65c8..84b4e3721 100644 --- a/app/Models/Auth.php +++ b/app/Models/Auth.php @@ -16,7 +16,8 @@ class FreshRSS_Auth { self::$login_ok = Minz_Session::param('loginOk', false); $current_user = Minz_Session::param('currentUser', ''); if ($current_user === '') { - $current_user = Minz_Configuration::defaultUser(); + $conf = Minz_Configuration::get('system'); + $current_user = $conf->general['default_user']; Minz_Session::_param('currentUser', $current_user); } @@ -40,7 +41,9 @@ class FreshRSS_Auth { * @return boolean true if user can be connected, false else. */ private static function accessControl() { - switch (Minz_Configuration::authType()) { + $conf = Minz_Configuration::get('system'); + $auth_type = $conf->general['auth_type']; + switch ($auth_type) { case 'form': $credentials = FreshRSS_FormAuth::getCredentialsFromCookie(); $current_user = ''; @@ -79,22 +82,19 @@ class FreshRSS_Auth { * Gives access to the current user. */ public static function giveAccess() { - $current_user = Minz_Session::param('currentUser'); - try { - $conf = new FreshRSS_Configuration($current_user); - } catch(Minz_Exception $e) { - die($e->getMessage()); - } + $user_conf = Minz_Configuration::get('user'); + $system_conf = Minz_Configuration::get('system'); + $auth_type = $system_conf->general['auth_type']; - switch (Minz_Configuration::authType()) { + switch ($auth_type) { case 'form': - self::$login_ok = Minz_Session::param('passwordHash') === $conf->passwordHash; + self::$login_ok = Minz_Session::param('passwordHash') === $user_conf->passwordHash; break; case 'http_auth': self::$login_ok = strcasecmp($current_user, httpAuthUser()) === 0; break; case 'persona': - self::$login_ok = strcasecmp(Minz_Session::param('mail'), $conf->mail_login) === 0; + self::$login_ok = strcasecmp(Minz_Session::param('mail'), $user_conf->mail_login) === 0; break; case 'none': self::$login_ok = true; @@ -114,12 +114,14 @@ class FreshRSS_Auth { * @return boolean true if user has corresponding access, false else. */ public static function hasAccess($scope = 'general') { + $conf = Minz_Configuration::get('system'); + $default_user = $conf->general['default_user']; $ok = self::$login_ok; switch ($scope) { case 'general': break; case 'admin': - $ok &= Minz_Session::param('currentUser') === Minz_Configuration::defaultUser(); + $ok &= Minz_Session::param('currentUser') === $default_user; break; default: $ok = false; @@ -133,9 +135,10 @@ class FreshRSS_Auth { public static function removeAccess() { Minz_Session::_param('loginOk'); self::$login_ok = false; - Minz_Session::_param('currentUser', Minz_Configuration::defaultUser()); + $conf = Minz_Configuration::get('system'); + Minz_Session::_param('currentUser', $conf->general['default_user']); - switch (Minz_Configuration::authType()) { + switch ($conf->general['auth_type']) { case 'form': Minz_Session::_param('passwordHash'); FreshRSS_FormAuth::deleteCookie(); @@ -151,6 +154,15 @@ class FreshRSS_Auth { // TODO: extensions } } + + /** + * Return if authentication is enabled on this instance of FRSS. + */ + public static function accessNeedLogin() { + $conf = Minz_Configuration::get('system'); + $auth_type = $conf->general['auth_type']; + return $auth_type === 'form' || $auth_type === 'persona'; + } } diff --git a/app/Models/Context.php b/app/Models/Context.php index c8a65063a..9bbad9857 100644 --- a/app/Models/Context.php +++ b/app/Models/Context.php @@ -41,10 +41,10 @@ class FreshRSS_Context { */ public static function init() { // Init configuration. - $current_user = Minz_Session::param('currentUser'); try { - self::$conf = new FreshRSS_Configuration($current_user); + self::$conf = Minz_Configuration::get('user'); } catch(Minz_Exception $e) { + $current_user = Minz_Session::param('currentUser', '_'); Minz_Log::error('Cannot load configuration file of user `' . $current_user . '`'); die($e->getMessage()); } diff --git a/app/Models/Factory.php b/app/Models/Factory.php index 91cb84998..db09d155d 100644 --- a/app/Models/Factory.php +++ b/app/Models/Factory.php @@ -3,8 +3,8 @@ class FreshRSS_Factory { public static function createFeedDao($username = null) { - $db = Minz_Configuration::dataBase(); - if ($db['type'] === 'sqlite') { + $conf = Minz_Configuration::get('system'); + if ($conf->db['type'] === 'sqlite') { return new FreshRSS_FeedDAOSQLite($username); } else { return new FreshRSS_FeedDAO($username); @@ -12,8 +12,8 @@ class FreshRSS_Factory { } public static function createEntryDao($username = null) { - $db = Minz_Configuration::dataBase(); - if ($db['type'] === 'sqlite') { + $conf = Minz_Configuration::get('system'); + if ($conf->db['type'] === 'sqlite') { return new FreshRSS_EntryDAOSQLite($username); } else { return new FreshRSS_EntryDAO($username); @@ -21,8 +21,8 @@ class FreshRSS_Factory { } public static function createStatsDAO($username = null) { - $db = Minz_Configuration::dataBase(); - if ($db['type'] === 'sqlite') { + $conf = Minz_Configuration::get('system'); + if ($conf->db['type'] === 'sqlite') { return new FreshRSS_StatsDAOSQLite($username); } else { return new FreshRSS_StatsDAO($username); @@ -30,8 +30,8 @@ class FreshRSS_Factory { } public static function createDatabaseDAO($username = null) { - $db = Minz_Configuration::dataBase(); - if ($db['type'] === 'sqlite') { + $conf = Minz_Configuration::get('system'); + if ($conf->db['type'] === 'sqlite') { return new FreshRSS_DatabaseDAOSQLite($username); } else { return new FreshRSS_DatabaseDAO($username); diff --git a/app/Models/Feed.php b/app/Models/Feed.php index 8f4b60097..071eafdf6 100644 --- a/app/Models/Feed.php +++ b/app/Models/Feed.php @@ -39,8 +39,9 @@ class FreshRSS_Feed extends Minz_Model { } public function hash() { + $conf = Minz_Configuration::get('system'); if ($this->hash === null) { - $this->hash = hash('crc32b', Minz_Configuration::salt() . $this->url); + $this->hash = hash('crc32b', $conf->general['salt'] . $this->url); } return $this->hash; } diff --git a/app/layout/header.phtml b/app/layout/header.phtml index ba13c2a45..2f16b5f63 100644 --- a/app/layout/header.phtml +++ b/app/layout/header.phtml @@ -1,5 +1,8 @@ - +
diff --git a/app/layout/layout.phtml b/app/layout/layout.phtml index 1827d6c26..656fbe9bc 100644 --- a/app/layout/layout.phtml +++ b/app/layout/layout.phtml @@ -10,6 +10,7 @@ renderHelper('javascript_vars'); ?> //]]> "> - + diff --git a/app/views/helpers/javascript_vars.phtml b/app/views/helpers/javascript_vars.phtml index 6577e0109..02f2014ee 100644 --- a/app/views/helpers/javascript_vars.phtml +++ b/app/views/helpers/javascript_vars.phtml @@ -1,6 +1,7 @@ "use strict"; mark_when; $mail = Minz_Session::param('mail', false); $auto_actualize = Minz_Session::param('actualize_feeds', false); @@ -29,7 +30,7 @@ echo 'var context={', 'does_lazyload:', FreshRSS_Context::$conf->lazyload ? 'true' : 'false', ',', 'sticky_post:', FreshRSS_Context::isStickyPostEnabled() ? 'true' : 'false', ',', 'html5_notif_timeout:', FreshRSS_Context::$conf->html5_notif_timeout, ',', - 'auth_type:"', Minz_Configuration::authType(), '",', + 'auth_type:"', $conf->general['auth_type'], '",', 'current_user_mail:', $mail ? ('"' . $mail . '"') : 'null', ',', 'current_view:"', Minz_Request::param('output', 'normal'), '"', "},\n"; diff --git a/data/config.default.php b/data/config.default.php new file mode 100644 index 000000000..a69d8050b --- /dev/null +++ b/data/config.default.php @@ -0,0 +1,32 @@ + array( + 'environment' => 'production', + 'salt' => '', + 'base_url' => '', + 'language' => 'en', + 'title' => 'FreshRSS', + 'default_user' => '_', + 'allow_anonymous' => false, + 'allow_anonymous_refresh' => false, + 'auth_type' => 'none', + 'api_enabled' => false, + 'unsafe_autologin_enabled' => false, + ), + 'limits' => array( + 'cache_duration' => 800, + 'timeout' => 10, + 'max_inactivity' => PHP_INT_MAX, + 'max_feeds' => 16384, + 'max_categories' => 16384, + ), + 'db' => array( + 'type' => 'sqlite', + 'host' => '', + 'user' => '', + 'password' => '', + 'base' => '', + 'prefix' => '', + ), +); diff --git a/data/users/_/config.default.php b/data/users/_/config.default.php new file mode 100644 index 000000000..56d54b293 --- /dev/null +++ b/data/users/_/config.default.php @@ -0,0 +1,66 @@ + 'en', + 'old_entries' => 3, + 'keep_history_default' => 0, + 'ttl_default' => 3600, + 'mail_login' => '', + 'token' => '', + 'passwordHash' => '', + 'apiPasswordHash' => '', + 'posts_per_page' => 20, + 'view_mode' => 'normal', + 'default_view' => 'adaptive', + 'default_state' => FreshRSS_Entry::STATE_NOT_READ, + 'auto_load_more' => true, + 'display_posts' => false, + 'display_categories' => false, + 'hide_read_feeds' => true, + 'onread_jump_next' => true, + 'lazyload' => true, + 'sticky_post' => true, + 'reading_confirm' => false, + 'auto_remove_article' => false, + 'sort_order' => 'DESC', + 'anon_access' => false, + 'mark_when' => array ( + 'article' => true, + 'site' => true, + 'scroll' => false, + 'reception' => false, + ), + 'theme' => 'Origine', + 'content_width' => 'thin', + 'shortcuts' => array ( + 'mark_read' => 'r', + 'mark_favorite' => 'f', + 'go_website' => 'space', + 'next_entry' => 'j', + 'prev_entry' => 'k', + 'first_entry' => 'home', + 'last_entry' => 'end', + 'collapse_entry' => 'c', + 'load_more' => 'm', + 'auto_share' => 's', + 'focus_search' => 'a', + 'user_filter' => 'u', + 'help' => 'f1', + 'close_dropdown' => 'escape', + ), + 'topline_read' => true, + 'topline_favorite' => true, + 'topline_date' => true, + 'topline_link' => true, + 'bottomline_read' => true, + 'bottomline_favorite' => true, + 'bottomline_sharing' => true, + 'bottomline_tags' => true, + 'bottomline_date' => true, + 'bottomline_link' => true, + 'sharing' => array ( + ), + 'queries' => array ( + ), + 'html5_notif_timeout' => 0, +); diff --git a/lib/Minz/BadConfigurationException.php b/lib/Minz/BadConfigurationException.php deleted file mode 100644 index a7b77d687..000000000 --- a/lib/Minz/BadConfigurationException.php +++ /dev/null @@ -1,9 +0,0 @@ - -*/ /** - * La classe Configuration permet de gérer la configuration de l'application + * Manage configuration for the application. */ class Minz_Configuration { - const CONF_PATH_NAME = '/config.php'; - /** - * VERSION est la version actuelle de MINZ + * The list of configurations. */ - const VERSION = '1.3.1.freshrss'; // version spéciale FreshRSS + private static $config_list = array(); /** - * valeurs possibles pour l'"environment" - * SILENT rend l'application muette (pas de log) - * PRODUCTION est recommandée pour une appli en production - * (log les erreurs critiques) - * DEVELOPMENT log toutes les erreurs + * Add a new configuration to the list of configuration. + * + * @param $namespace the name of the current configuration + * @param $config_filename the filename of the configuration + * @param $default_filename a filename containing default values for the configuration + * @throws Minz_ConfigurationNamespaceException if the namespace already exists. */ - const SILENT = 0; - const PRODUCTION = 1; - const DEVELOPMENT = 2; + public static function register($namespace, $config_filename, $default_filename = null) { + if (isset(self::$config_list[$namespace])) { + throw new Minz_ConfigurationNamespaceException( + $namespace . ' namespace already exists' + ); + } + + self::$config_list[$namespace] = new Minz_Configuration( + $namespace, $config_filename, $default_filename + ); + } /** - * définition des variables de configuration - * $salt une chaîne de caractères aléatoires (obligatoire) - * $environment gère le niveau d'affichage pour log et erreurs - * $base_url le chemin de base pour accéder à l'application - * $title le nom de l'application - * $language la langue par défaut de l'application - * $db paramètres pour la base de données (tableau) - * - host le serveur de la base - * - user nom d'utilisateur - * - password mot de passe de l'utilisateur - * - base le nom de la base de données + * Parse a file and return its data. + * + * If the file does not contain a valid PHP code returning an array, an + * empty array is returned anyway. + * + * @param $filename the name of the file to parse. + * @return an array of values + * @throws Minz_FileNotExistException if the file does not exist. */ - private static $salt = ''; - private static $environment = Minz_Configuration::PRODUCTION; - private static $base_url = ''; - private static $title = ''; - private static $language = 'en'; - private static $default_user = ''; - private static $allow_anonymous = false; - private static $allow_anonymous_refresh = false; - private static $auth_type = 'none'; - private static $api_enabled = false; - private static $unsafe_autologin_enabled = false; - - private static $db = array ( - 'type' => 'mysql', - 'host' => '', - 'user' => '', - 'password' => '', - 'base' => '', - 'prefix' => '', - ); - - const MAX_SMALL_INT = 16384; - private static $limits = array( - 'cache_duration' => 800, //SimplePie cache duration in seconds - 'timeout' => 10, //SimplePie timeout in seconds - 'max_inactivity' => PHP_INT_MAX, //Time in seconds after which a user who has not used the account is considered inactive (no auto-refresh of feeds). - 'max_feeds' => Minz_Configuration::MAX_SMALL_INT, - 'max_categories' => Minz_Configuration::MAX_SMALL_INT, - ); + public static function parseFile($filename) { + if (!file_exists($filename)) { + throw new Minz_FileNotExistException($filename); + } - /* - * Getteurs - */ - public static function salt () { - return self::$salt; + $data = @include($filename); + if (is_array($data)) { + return $data; + } else { + return array(); + } } - public static function environment ($str = false) { - $env = self::$environment; - if ($str) { - switch (self::$environment) { - case self::SILENT: - $env = 'silent'; - break; - case self::DEVELOPMENT: - $env = 'development'; - break; - case self::PRODUCTION: - default: - $env = 'production'; - } + /** + * Return the configuration related to a given namespace. + * + * @param $namespace the name of the configuration to get. + * @return a Minz_Configuration object + * @throws Minz_ConfigurationNamespaceException if the namespace does not exist. + */ + public static function get($namespace) { + if (!isset(self::$config_list[$namespace])) { + throw new Minz_ConfigurationNamespaceException( + $namespace . ' namespace does not exist' + ); } - return $env; - } - public static function baseUrl () { - return self::$base_url; - } - public static function title () { - return self::$title; - } - public static function language () { - return self::$language; - } - public static function dataBase () { - return self::$db; - } - public static function limits() { - return self::$limits; - } - public static function defaultUser () { - return self::$default_user; - } - public static function allowAnonymous() { - return self::$allow_anonymous; - } - public static function allowAnonymousRefresh() { - return self::$allow_anonymous_refresh; - } - public static function authType() { - return self::$auth_type; - } - public static function needsLogin() { - return self::$auth_type !== 'none'; - } - public static function canLogIn() { - return self::$auth_type === 'form' || self::$auth_type === 'persona'; - } - public static function apiEnabled() { - return self::$api_enabled; - } - public static function unsafeAutologinEnabled() { - return self::$unsafe_autologin_enabled; + return self::$config_list[$namespace]; } - public static function _allowAnonymous($allow = false) { - self::$allow_anonymous = ((bool)$allow) && self::canLogIn(); - } - public static function _allowAnonymousRefresh($allow = false) { - self::$allow_anonymous_refresh = ((bool)$allow) && self::allowAnonymous(); - } - public static function _authType($value) { - $value = strtolower($value); - switch ($value) { - case 'form': - case 'http_auth': - case 'persona': - case 'none': - self::$auth_type = $value; - break; - } - self::_allowAnonymous(self::$allow_anonymous); - } + /** + * The namespace of the current configuration. + */ + private $namespace = ''; - public static function _enableApi($value = false) { - self::$api_enabled = (bool)$value; - } - public static function _enableAutologin($value = false) { - self::$unsafe_autologin_enabled = (bool)$value; - } + /** + * The filename for the current configuration. + */ + private $config_filename = ''; + + /** + * The filename for the current default values, null by default. + */ + private $default_filename = null; + + /** + * The configuration values, an empty array by default. + */ + private $data = array(); + + /** + * The default values, an empty array by default. + */ + private $data_default = array(); /** - * Initialise les variables de configuration - * @exception Minz_FileNotExistException si le CONF_PATH_NAME n'existe pas - * @exception Minz_BadConfigurationException si CONF_PATH_NAME mal formaté + * Create a new Minz_Configuration object. + * + * @param $namespace the name of the current configuration. + * @param $config_filename the file containing configuration values. + * @param $default_filename the file containing default values, null by default. */ - public static function init () { + private function __construct($namespace, $config_filename, $default_filename = null) { + $this->namespace = $namespace; + $this->config_filename = $config_filename; + try { - self::parseFile (); - self::setReporting (); + $this->data = self::parseFile($this->config_filename); } catch (Minz_FileNotExistException $e) { - throw $e; - } catch (Minz_BadConfigurationException $e) { - throw $e; + if (is_null($default_filename)) { + throw $e; + } } - } - public static function writeFile() { - $ini_array = array( - 'general' => array( - 'environment' => self::environment(true), - 'salt' => self::$salt, - 'base_url' => self::$base_url, - 'title' => self::$title, - 'default_user' => self::$default_user, - 'allow_anonymous' => self::$allow_anonymous, - 'allow_anonymous_refresh' => self::$allow_anonymous_refresh, - 'auth_type' => self::$auth_type, - 'api_enabled' => self::$api_enabled, - 'unsafe_autologin_enabled' => self::$unsafe_autologin_enabled, - ), - 'limits' => self::$limits, - 'db' => self::$db, - ); - @rename(DATA_PATH . self::CONF_PATH_NAME, DATA_PATH . self::CONF_PATH_NAME . '.bak.php'); - $result = file_put_contents(DATA_PATH . self::CONF_PATH_NAME, "default_filename = $default_filename; + if (!is_null($this->default_filename)) { + $this->data_default = self::parseFile($this->default_filename); } - return (bool)$result; } /** - * Parse un fichier de configuration - * @exception Minz_PermissionDeniedException si le CONF_PATH_NAME n'est pas accessible - * @exception Minz_BadConfigurationException si CONF_PATH_NAME mal formaté + * Return the value of the given param. + * + * @param $key the name of the param. + * @param $default default value to return if key does not exist. + * @return the value corresponding to the key. + * @throws Minz_ConfigurationParamException if the param does not exist */ - private static function parseFile () { - $ini_array = include(DATA_PATH . self::CONF_PATH_NAME); - - if (!is_array($ini_array)) { - throw new Minz_PermissionDeniedException ( - DATA_PATH . self::CONF_PATH_NAME, - Minz_Exception::ERROR + public function param($key, $default = null) { + if (isset($this->data[$key])) { + return $this->data[$key]; + } elseif (!is_null($default)) { + return $default; + } elseif (isset($this->data_default[$key])) { + return $this->data_default[$key]; + } else { + throw new Minz_ConfigurationParamException( + $key . ' param does not exist' ); } + } - // [general] est obligatoire - if (!isset ($ini_array['general'])) { - throw new Minz_BadConfigurationException ( - '[general]', - Minz_Exception::ERROR - ); - } - $general = $ini_array['general']; + /** + * A wrapper for param(). + */ + public function __get($key) { + return $this->param($key); + } - // salt est obligatoire - if (!isset ($general['salt'])) { - if (isset($general['sel_application'])) { //v0.6 - $general['salt'] = $general['sel_application']; - } else { - throw new Minz_BadConfigurationException ( - 'salt', - Minz_Exception::ERROR - ); - } + /** + * Set or remove a param. + * + * @param $key the param name to set. + * @param $value the value to set. If null, the key is removed from the configuration. + */ + public function _param($key, $value = null) { + if (isset($this->data[$key]) && is_null($value)) { + unset($this->data[$key]); + } else { + $this->data[$key] = $value; } - self::$salt = $general['salt']; - - if (isset ($general['environment'])) { - switch ($general['environment']) { - case 'silent': - self::$environment = Minz_Configuration::SILENT; - break; - case 'development': - self::$environment = Minz_Configuration::DEVELOPMENT; - break; - case 'production': - self::$environment = Minz_Configuration::PRODUCTION; - break; - default: - if ($general['environment'] >= 0 && - $general['environment'] <= 2) { - // fallback 0.7-beta - self::$environment = $general['environment']; - } else { - throw new Minz_BadConfigurationException ( - 'environment', - Minz_Exception::ERROR - ); - } - } + } - } - if (isset ($general['base_url'])) { - self::$base_url = $general['base_url']; - } + /** + * A wrapper for _param(). + */ + public function __set($key, $value) { + $this->_param($key, $value); + } - if (isset ($general['title'])) { - self::$title = $general['title']; - } - if (isset ($general['language'])) { - self::$language = $general['language']; - } - if (isset ($general['default_user'])) { - self::$default_user = $general['default_user']; - } - if (isset ($general['auth_type'])) { - self::_authType($general['auth_type']); - } - if (isset ($general['allow_anonymous'])) { - self::$allow_anonymous = ( - ((bool)($general['allow_anonymous'])) && - ($general['allow_anonymous'] !== 'no') - ); - } - if (isset ($general['allow_anonymous_refresh'])) { - self::$allow_anonymous_refresh = ( - ((bool)($general['allow_anonymous_refresh'])) && - ($general['allow_anonymous_refresh'] !== 'no') - ); - } - if (isset ($general['api_enabled'])) { - self::$api_enabled = ( - ((bool)($general['api_enabled'])) && - ($general['api_enabled'] !== 'no') - ); - } - if (isset ($general['unsafe_autologin_enabled'])) { - self::$unsafe_autologin_enabled = ( - ((bool)($general['unsafe_autologin_enabled'])) && - ($general['unsafe_autologin_enabled'] !== 'no') - ); - } + /** + * Save the current configuration in the configuration file. + */ + public function save() { + $back_filename = $this->config_filename . '.bak.php'; + @rename($this->config_filename, $back_filename); - if (isset($ini_array['limits'])) { - $limits = $ini_array['limits']; - if (isset($limits['cache_duration'])) { - $v = intval($limits['cache_duration']); - if ($v > 0) { - self::$limits['cache_duration'] = $v; - } - } - if (isset($limits['timeout'])) { - $v = intval($limits['timeout']); - if ($v > 0) { - self::$limits['timeout'] = $v; - } - } - if (isset($limits['max_inactivity'])) { - $v = intval($limits['max_inactivity']); - if ($v > 0) { - self::$limits['max_inactivity'] = $v; - } - } - if (isset($limits['max_feeds'])) { - $v = intval($limits['max_feeds']); - if ($v > 0 && $v < Minz_Configuration::MAX_SMALL_INT) { - self::$limits['max_feeds'] = $v; - } - } - if (isset($limits['max_categories'])) { - $v = intval($limits['max_categories']); - if ($v > 0 && $v < Minz_Configuration::MAX_SMALL_INT) { - self::$limits['max_categories'] = $v; - } - } + if (file_put_contents($this->config_filename, + "data, true) . ';', + LOCK_EX) === false) { + return false; } - // Base de données - if (isset ($ini_array['db'])) { - $db = $ini_array['db']; - if (empty($db['type'])) { - throw new Minz_BadConfigurationException ( - 'type', - Minz_Exception::ERROR - ); - } - switch ($db['type']) { - case 'mysql': - if (empty($db['host'])) { - throw new Minz_BadConfigurationException ( - 'host', - Minz_Exception::ERROR - ); - } - if (empty($db['user'])) { - throw new Minz_BadConfigurationException ( - 'user', - Minz_Exception::ERROR - ); - } - if (!isset($db['password'])) { - throw new Minz_BadConfigurationException ( - 'password', - Minz_Exception::ERROR - ); - } - if (empty($db['base'])) { - throw new Minz_BadConfigurationException ( - 'base', - Minz_Exception::ERROR - ); - } - self::$db['host'] = $db['host']; - self::$db['user'] = $db['user']; - self::$db['password'] = $db['password']; - self::$db['base'] = $db['base']; - if (isset($db['prefix'])) { - self::$db['prefix'] = $db['prefix']; - } - break; - case 'sqlite': - self::$db['host'] = ''; - self::$db['user'] = ''; - self::$db['password'] = ''; - self::$db['base'] = ''; - self::$db['prefix'] = ''; - break; - default: - throw new Minz_BadConfigurationException ( - 'type', - Minz_Exception::ERROR - ); - break; - } - self::$db['type'] = $db['type']; + // Clear PHP 5.5+ cache for include + if (function_exists('opcache_invalidate')) { + opcache_invalidate($this->config_filename); } - } - private static function setReporting() { - switch (self::$environment) { - case self::PRODUCTION: - error_reporting(E_ALL); - ini_set('display_errors','Off'); - ini_set('log_errors', 'On'); - break; - case self::DEVELOPMENT: - error_reporting(E_ALL); - ini_set('display_errors','On'); - ini_set('log_errors', 'On'); - break; - case self::SILENT: - error_reporting(0); - break; - } + return true; } } diff --git a/lib/Minz/ConfigurationException.php b/lib/Minz/ConfigurationException.php new file mode 100644 index 000000000..f294c3341 --- /dev/null +++ b/lib/Minz/ConfigurationException.php @@ -0,0 +1,8 @@ + en fonction de l'environment */ private static function processLogs ($logs) { - $env = Minz_Configuration::environment (); + $conf = Minz_Configuration::get('system'); + $env = $conf->general['environment']; $logs_ok = array (); $error = array (); $warning = array (); @@ -98,10 +99,10 @@ class Minz_Error { $notice = $logs['notice']; } - if ($env == Minz_Configuration::PRODUCTION) { + if ($env == 'production') { $logs_ok = $error; } - if ($env == Minz_Configuration::DEVELOPMENT) { + if ($env == 'development') { $logs_ok = array_merge ($error, $warning, $notice); } diff --git a/lib/Minz/FrontController.php b/lib/Minz/FrontController.php index 3dac1e438..974cf4260 100644 --- a/lib/Minz/FrontController.php +++ b/lib/Minz/FrontController.php @@ -31,9 +31,12 @@ class Minz_FrontController { */ public function __construct () { try { - Minz_Configuration::init (); + Minz_Configuration::register('system', + DATA_PATH . '/config.php', + DATA_PATH . '/config.default.php'); + $this->setReporting(); - Minz_Request::init (); + Minz_Request::init(); $url = $this->buildUrl(); $url['params'] = array_merge ( @@ -110,4 +113,23 @@ class Minz_FrontController { } exit ('### Application problem ###
'."\n".$txt); } + + private function setReporting() { + $conf = Minz_Configuration::get('system'); + switch($conf->general['environment']) { + case 'production': + error_reporting(E_ALL); + ini_set('display_errors','Off'); + ini_set('log_errors', 'On'); + break; + case 'development': + error_reporting(E_ALL); + ini_set('display_errors','On'); + ini_set('log_errors', 'On'); + break; + case 'silent': + error_reporting(0); + break; + } + } } diff --git a/lib/Minz/Log.php b/lib/Minz/Log.php index d19edc1dc..2063efe7e 100644 --- a/lib/Minz/Log.php +++ b/lib/Minz/Log.php @@ -31,10 +31,15 @@ class Minz_Log { * @param $file_name fichier de log */ public static function record ($information, $level, $file_name = null) { - $env = Minz_Configuration::environment (); + try { + $conf = Minz_Configuration::get('system'); + $env = $conf->general['environment']; + } catch (Minz_ConfigurationException $e) { + $env = 'production'; + } - if (! ($env === Minz_Configuration::SILENT - || ($env === Minz_Configuration::PRODUCTION + if (! ($env === 'silent' + || ($env === 'production' && ($level >= Minz_Log::NOTICE)))) { if ($file_name === null) { $file_name = join_path(USERS_PATH, Minz_Session::param('currentUser', '_'), 'log.txt'); diff --git a/lib/Minz/ModelPdo.php b/lib/Minz/ModelPdo.php index 118d89ad2..ac7a1bed7 100644 --- a/lib/Minz/ModelPdo.php +++ b/lib/Minz/ModelPdo.php @@ -44,7 +44,8 @@ class Minz_ModelPdo { return; } - $db = Minz_Configuration::dataBase(); + $conf = Minz_Configuration::get('system'); + $db = $conf->db; if ($currentUser === null) { $currentUser = Minz_Session::param('currentUser', '_'); diff --git a/lib/Minz/Request.php b/lib/Minz/Request.php index 4b97a3caf..5f2f6a858 100644 --- a/lib/Minz/Request.php +++ b/lib/Minz/Request.php @@ -96,7 +96,8 @@ class Minz_Request { * @return la base de l'url */ public static function getBaseUrl() { - $defaultBaseUrl = Minz_Configuration::baseUrl(); + $conf = Minz_Configuration::get('system'); + $defaultBaseUrl = $conf->general['base_url']; if (!empty($defaultBaseUrl)) { return $defaultBaseUrl; } elseif (isset($_SERVER['REQUEST_URI'])) { diff --git a/lib/Minz/Translate.php b/lib/Minz/Translate.php index e7efb8665..7525e95cc 100644 --- a/lib/Minz/Translate.php +++ b/lib/Minz/Translate.php @@ -28,7 +28,8 @@ class Minz_Translate { * Load $lang_name and $lang_path based on configuration and selected language. */ public static function init() { - $l = Minz_Configuration::language(); + $conf = Minz_Configuration::get('system'); + $l = $conf->general['language']; self::$lang_name = Minz_Session::param('language', $l); self::$lang_path = APP_PATH . '/i18n/' . self::$lang_name . '/'; } diff --git a/lib/Minz/View.php b/lib/Minz/View.php index b40448491..24ad630d0 100644 --- a/lib/Minz/View.php +++ b/lib/Minz/View.php @@ -28,7 +28,9 @@ class Minz_View { public function __construct () { $this->change_view(Minz_Request::controllerName(), Minz_Request::actionName()); - self::$title = Minz_Configuration::title (); + + $conf = Minz_Configuration::get('system'); + self::$title = $conf->general['title']; } /** diff --git a/p/i/index.php b/p/i/index.php index 009d56bc3..d3fc0b37c 100755 --- a/p/i/index.php +++ b/p/i/index.php @@ -33,7 +33,7 @@ if (file_exists(DATA_PATH . '/do-install.txt')) { $currentUser = Minz_Session::param('currentUser', ''); $dateLastModification = $currentUser === '' ? time() : max( @filemtime(join_path(USERS_PATH, $currentUser, 'log.txt')), - @filemtime(join_path(DATA_PATH . 'config.php')) + @filemtime(join_path(DATA_PATH, 'config.php')) ); if (httpConditional($dateLastModification, 0, 0, false, PHP_COMPRESSION, true)) { exit(); //No need to send anything -- cgit v1.2.3 From 217c191a1ba3ac03b847d261a32e19975380fcad Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Mon, 11 May 2015 22:42:41 +0200 Subject: More SQLite compatibility Additional changes to add compatibility with SQLite for the new hash/lastSeen mode of updating articles. --- app/Models/EntryDAO.php | 71 ++++++++++++++++++++++++------------------ app/Models/EntryDAOSQLite.php | 15 +++++++++ app/Models/FeedDAO.php | 11 ++++--- app/SQL/install.sql.mysql.php | 2 +- app/SQL/install.sql.sqlite.php | 2 +- app/install.php | 2 ++ lib/Minz/ModelPdo.php | 5 +++ 7 files changed, 70 insertions(+), 38 deletions(-) (limited to 'lib/Minz/ModelPdo.php') diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index 172eac897..eae9683ad 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -6,38 +6,48 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { return parent::$sharedDbType !== 'sqlite'; } - protected function autoAddColumn($errorInfo) { - if (isset($errorInfo[0])) { - if ($errorInfo[0] == '42S22') { //ER_BAD_FIELD_ERROR - $hasTransaction = false; - try { - $stm = null; - if (stripos($errorInfo[2], 'lastSeen') !== false) { //v1.2 - if (!$this->bd->inTransaction()) { - $this->bd->beginTransaction(); - $hasTransaction = true; - } - $stm = $this->bd->prepare('ALTER TABLE `' . $this->prefix . 'entry` ADD COLUMN lastSeen INT(11) NOT NULL'); - if ($stm && $stm->execute()) { - $stm = $this->bd->prepare('CREATE INDEX entry_lastSeen_index ON `' . $this->prefix . 'entry`(`lastSeen`);'); //"IF NOT EXISTS" does not exist in MySQL 5.7 - if ($stm && $stm->execute()) { - if ($hasTransaction) { - $this->bd->commit(); - } - return true; - } - } + protected function addColumn($name) { + Minz_Log::debug('FreshRSS_EntryDAO::autoAddColumn: ' . $name); + $hasTransaction = false; + try { + $stm = null; + if ($name === 'lastSeen') { //v1.2 + if (!$this->bd->inTransaction()) { + $this->bd->beginTransaction(); + $hasTransaction = true; + } + $stm = $this->bd->prepare('ALTER TABLE `' . $this->prefix . 'entry` ADD COLUMN lastSeen INT(11) DEFAULT 0'); + if ($stm && $stm->execute()) { + $stm = $this->bd->prepare('CREATE INDEX entry_lastSeen_index ON `' . $this->prefix . 'entry`(`lastSeen`);'); //"IF NOT EXISTS" does not exist in MySQL 5.7 + if ($stm && $stm->execute()) { if ($hasTransaction) { - $this->bd->rollBack(); + $this->bd->commit(); } - } elseif (stripos($errorInfo[2], 'hash') !== false) { //v1.2 - $stm = $this->bd->prepare('ALTER TABLE `' . $this->prefix . 'entry` ADD COLUMN hash BINARY(16) NOT NULL'); - return $stm && $stm->execute(); + return true; } - } catch (Exception $e) { - Minz_Log::debug('FreshRSS_EntryDAO::autoAddColumn error: ' . $e->getMessage()); - if ($hasTransaction) { - $this->bd->rollBack(); + } + if ($hasTransaction) { + $this->bd->rollBack(); + } + } elseif ($name === 'hash') { //v1.2 + $stm = $this->bd->prepare('ALTER TABLE `' . $this->prefix . 'entry` ADD COLUMN hash BINARY(16)'); + return $stm && $stm->execute(); + } + } catch (Exception $e) { + Minz_Log::debug('FreshRSS_EntryDAO::autoAddColumn error: ' . $e->getMessage()); + if ($hasTransaction) { + $this->bd->rollBack(); + } + } + return false; + } + + protected function autoAddColumn($errorInfo) { + if (isset($errorInfo[0])) { + if ($errorInfo[0] == '42S22') { //ER_BAD_FIELD_ERROR + foreach (array('lastSeen', 'hash') as $column) { + if (stripos($errorInfo[2], $column) !== false) { + return $this->addColumn($column); } } } @@ -82,7 +92,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { return $this->addEntry($valuesTmp); } elseif ((int)($info[0] / 1000) !== 23) { //Filter out "SQLSTATE Class code 23: Constraint Violation" because of expected duplicate entries Minz_Log::error('SQL error addEntry: ' . $info[0] . ': ' . $info[1] . ' ' . $info[2] - . ' while adding entry in feed ' . $valuesTmp['id_feed'] . ' with title: ' . $valuesTmp['title']); + . ' while adding entry in feed ' . $valuesTmp['id_feed'] . ' with title: ' . $valuesTmp['title']. ' ' . $this->addEntryPrepared); } return false; } @@ -597,7 +607,6 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } return $result; } else { - $info = $stm == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $stm->errorInfo(); if ($this->autoAddColumn($info)) { return $this->listHashForFeedGuids($id_feed, $guids); diff --git a/app/Models/EntryDAOSQLite.php b/app/Models/EntryDAOSQLite.php index ffe0f037c..ff049d813 100644 --- a/app/Models/EntryDAOSQLite.php +++ b/app/Models/EntryDAOSQLite.php @@ -2,6 +2,21 @@ class FreshRSS_EntryDAOSQLite extends FreshRSS_EntryDAO { + protected function autoAddColumn($errorInfo) { + if (empty($errorInfo[0]) || $errorInfo[0] == '42S22') { //ER_BAD_FIELD_ERROR + if ($tableInfo = $this->bd->query("SELECT sql FROM sqlite_master where name='entry'")) { + $showCreate = $tableInfo->fetchColumn(); + Minz_Log::debug('FreshRSS_EntryDAOSQLite::autoAddColumn: ' . $showCreate); + foreach (array('lastSeen', 'hash') as $column) { + if (stripos($showCreate, $column) === false) { + return $this->addColumn($column); + } + } + } + } + return false; + } + protected function sqlConcat($s1, $s2) { return $s1 . '||' . $s2; } diff --git a/app/Models/FeedDAO.php b/app/Models/FeedDAO.php index 76025ff53..475d39286 100644 --- a/app/Models/FeedDAO.php +++ b/app/Models/FeedDAO.php @@ -330,11 +330,12 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { . 'AND id NOT IN (SELECT id FROM (SELECT e2.id FROM `' . $this->prefix . 'entry` e2 WHERE e2.id_feed=:id_feed ORDER BY id DESC LIMIT :keep) keep)'; //Double select: MySQL doesn't support 'LIMIT & IN/ALL/ANY/SOME subquery' $stm = $this->bd->prepare($sql); - $id_max = intval($date_min) . '000000'; - - $stm->bindParam(':id_feed', $id, PDO::PARAM_INT); - $stm->bindParam(':id_max', $id_max, PDO::PARAM_STR); - $stm->bindParam(':keep', $keep, PDO::PARAM_INT); + if ($stm) { + $id_max = intval($date_min) . '000000'; + $stm->bindParam(':id_feed', $id, PDO::PARAM_INT); + $stm->bindParam(':id_max', $id_max, PDO::PARAM_STR); + $stm->bindParam(':keep', $keep, PDO::PARAM_INT); + } if ($stm && $stm->execute()) { return $stm->rowCount(); diff --git a/app/SQL/install.sql.mysql.php b/app/SQL/install.sql.mysql.php index afdd821b2..9c6af405d 100644 --- a/app/SQL/install.sql.mysql.php +++ b/app/SQL/install.sql.mysql.php @@ -41,7 +41,7 @@ CREATE TABLE IF NOT EXISTS `%1$sentry` ( `content_bin` blob, -- v0.7 `link` varchar(1023) CHARACTER SET latin1 NOT NULL, `date` int(11), -- Until year 2038 - `lastSeen` INT(11) NOT NULL, -- v1.2, Until year 2038 + `lastSeen` INT(11) DEFAULT 0, -- v1.2, Until year 2038 `hash` BINARY(16), -- v1.2 `is_read` boolean NOT NULL DEFAULT 0, `is_favorite` boolean NOT NULL DEFAULT 0, diff --git a/app/SQL/install.sql.sqlite.php b/app/SQL/install.sql.sqlite.php index 7517ead45..77e8e094c 100644 --- a/app/SQL/install.sql.sqlite.php +++ b/app/SQL/install.sql.sqlite.php @@ -39,7 +39,7 @@ $SQL_CREATE_TABLES = array( `content` text, `link` varchar(1023) NOT NULL, `date` int(11), -- Until year 2038 - `lastSeen` INT(11) NOT NULL, -- v1.2, Until year 2038 + `lastSeen` INT(11) DEFAULT 0, -- v1.2, Until year 2038 `hash` BINARY(16), -- v1.2 `is_read` boolean NOT NULL DEFAULT 0, `is_favorite` boolean NOT NULL DEFAULT 0, diff --git a/app/install.php b/app/install.php index 177173fdb..86afb9318 100644 --- a/app/install.php +++ b/app/install.php @@ -168,8 +168,10 @@ function saveStep3() { $_SESSION['bd_prefix_user'] = $_SESSION['bd_prefix'] .(empty($_SESSION['default_user']) ? '' :($_SESSION['default_user'] . '_')); } + //TODO: load `config.default.php` as default $config_array = array( 'environment' => 'production', + 'simplepie_syslog_enabled' => true, 'salt' => $_SESSION['salt'], 'title' => $_SESSION['title'], 'default_user' => $_SESSION['default_user'], diff --git a/lib/Minz/ModelPdo.php b/lib/Minz/ModelPdo.php index ac7a1bed7..3e8ec1f43 100644 --- a/lib/Minz/ModelPdo.php +++ b/lib/Minz/ModelPdo.php @@ -134,4 +134,9 @@ class MinzPDO extends PDO { MinzPDO::check($statement); return parent::exec($statement); } + + public function query($statement) { + MinzPDO::check($statement); + return parent::query($statement); + } } -- cgit v1.2.3 From 568ab2313d5a922e06d7f0c33b3e39dd152d43db Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Wed, 3 Jun 2015 22:07:25 +0200 Subject: PDO options for e.g. SSL https://github.com/FreshRSS/FreshRSS/issues/868 --- data/config.default.php | 3 +++ lib/Minz/ModelPdo.php | 10 ++++------ 2 files changed, 7 insertions(+), 6 deletions(-) (limited to 'lib/Minz/ModelPdo.php') diff --git a/data/config.default.php b/data/config.default.php index 80d331df7..7c179c8a0 100644 --- a/data/config.default.php +++ b/data/config.default.php @@ -99,6 +99,9 @@ return array( # MySQL table prefix. 'prefix' => '', + 'pdo_options' => array( + ), + ), # List of enabled FreshRSS extensions. diff --git a/lib/Minz/ModelPdo.php b/lib/Minz/ModelPdo.php index 3e8ec1f43..e82c1f30a 100644 --- a/lib/Minz/ModelPdo.php +++ b/lib/Minz/ModelPdo.php @@ -53,21 +53,19 @@ class Minz_ModelPdo { $this->current_user = $currentUser; self::$sharedCurrentUser = $currentUser; + $driver_options = is_array($conf->db['pdo_options']) ? $conf->db['pdo_options'] : array(); + try { $type = $db['type']; if ($type === 'mysql') { $string = 'mysql:host=' . $db['host'] . ';dbname=' . $db['base'] . ';charset=utf8'; - $driver_options = array( - PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8', - ); + $driver_options[PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET NAMES utf8'; $this->prefix = $db['prefix'] . $currentUser . '_'; } elseif ($type === 'sqlite') { $string = 'sqlite:' . join_path(DATA_PATH, 'users', $currentUser, 'db.sqlite'); - $driver_options = array( - //PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, - ); + //$driver_options[PDO::ATTR_ERRMODE] = PDO::ERRMODE_EXCEPTION; $this->prefix = ''; } else { throw new Minz_PDOConnectionException( -- cgit v1.2.3 From 9b512b82e2e65c6ca18199ed328de2645a7bc84e Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Wed, 3 Jun 2015 22:17:40 +0200 Subject: PDO config isset https://github.com/FreshRSS/FreshRSS/issues/868 --- lib/Minz/ModelPdo.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/Minz/ModelPdo.php') diff --git a/lib/Minz/ModelPdo.php b/lib/Minz/ModelPdo.php index e82c1f30a..25999f02b 100644 --- a/lib/Minz/ModelPdo.php +++ b/lib/Minz/ModelPdo.php @@ -53,7 +53,7 @@ class Minz_ModelPdo { $this->current_user = $currentUser; self::$sharedCurrentUser = $currentUser; - $driver_options = is_array($conf->db['pdo_options']) ? $conf->db['pdo_options'] : array(); + $driver_options = isset($conf->db['pdo_options']) && is_array($conf->db['pdo_options']) ? $conf->db['pdo_options'] : array(); try { $type = $db['type']; -- cgit v1.2.3 From fe18d12551f626c56257f825caf8f97a4a5461ce Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sat, 30 Jul 2016 18:45:34 +0200 Subject: Update MySQL to utf8mb4 (full unicode) 🔥 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Requires MySQL 5.5.3+ (drop support for MySQL 5.0) * Requires PHP 5.3.3+ (drop support for PHP 5.3.0) https://github.com/FreshRSS/FreshRSS/issues/789#issuecomment-73878076 --- README.fr.md | 4 +-- README.md | 4 +-- app/Controllers/feedController.php | 14 +++++---- app/Models/EntryDAO.php | 60 +++++++++++++++++++++++++++++++------ app/SQL/install.sql.mysql.php | 31 +++++++++++++++---- app/install.php | 10 +++---- app/views/update/checkInstall.phtml | 2 +- lib/Minz/ModelPdo.php | 12 +++----- 8 files changed, 99 insertions(+), 38 deletions(-) (limited to 'lib/Minz/ModelPdo.php') diff --git a/README.fr.md b/README.fr.md index fd01a507b..9da60408f 100644 --- a/README.fr.md +++ b/README.fr.md @@ -32,11 +32,11 @@ Nous sommes une communauté amicale. * Serveur modeste, par exemple sous Linux ou Windows * Fonctionne même sur un Raspberry Pi 1 avec des temps de réponse < 1s (testé sur 150 flux, 22k articles) * Serveur Web Apache2 (recommandé), ou nginx, lighttpd (non testé sur les autres) -* PHP 5.3+ (PHP 5.3.7+ recommandé, et PHP 5.5+ pour les performances, et PHP 7+ pour d’encore meilleures performances) +* PHP 5.3.3+ (PHP 5.3.7+ recommandé, et PHP 5.5+ pour les performances, et PHP 7+ pour d’encore meilleures performances) * Requis : [PDO_MySQL](http://php.net/pdo-mysql) ou [PDO_SQLite](http://php.net/pdo-sqlite), [cURL](http://php.net/curl), [GMP](http://php.net/gmp) (pour accès API sur plateformes < 64 bits), [IDN](http://php.net/intl.idn) (pour les noms de domaines internationalisés) * Recommandés : [iconv](http://php.net/iconv), [JSON](http://php.net/json), [mbstring](http://php.net/mbstring), [Zip](http://php.net/zip), [zlib](http://php.net/zlib) * Inclus par défaut : [DOM](http://php.net/dom), [XML](http://php.net/xml)… -* MySQL 5.0.3+ (recommandé) ou SQLite 3.7.4+ +* MySQL 5.5.3+ (recommandé) ou SQLite 3.7.4+ * Un navigateur Web récent tel Firefox, Chrome, Opera, Safari. [Internet Explorer ne fonctionne plus, mais ce sera corrigé](https://github.com/FreshRSS/FreshRSS/issues/772). * Fonctionne aussi sur mobile * L’entête HTTP `Referer` ne doit pas être désactivé pour pouvoir utiliser le formulaire de connexion diff --git a/README.md b/README.md index a0d22a75c..db5e8c02d 100644 --- a/README.md +++ b/README.md @@ -32,11 +32,11 @@ We are a friendly community. * Light server running Linux or Windows * It even works on Raspberry Pi 1 with response time under a second (tested with 150 feeds, 22k articles) * A web server: Apache2 (recommended), nginx, lighttpd (not tested on others) -* PHP 5.3+ (PHP 5.3.7+ recommended, and PHP 5.5+ for performance, and PHP 7 for even higher performance) +* PHP 5.3.3+ (PHP 5.3.7+ recommended, and PHP 5.5+ for performance, and PHP 7 for even higher performance) * Required extensions: [PDO_MySQL](http://php.net/pdo-mysql) or [PDO_SQLite](http://php.net/pdo-sqlite), [cURL](http://php.net/curl), [GMP](http://php.net/gmp) (for API access on platforms < 64 bits), [IDN](http://php.net/intl.idn) (for Internationalized Domain Names) * Recommended extensions: [iconv](http://php.net/iconv), [JSON](http://php.net/json), [mbstring](http://php.net/mbstring), [Zip](http://php.net/zip), [zlib](http://php.net/zlib) * Enabled by default: [DOM](http://php.net/dom), [XML](http://php.net/xml)… -* MySQL 5.0.3+ (recommended) or SQLite 3.7.4+ +* MySQL 5.5.3+ (recommended) or SQLite 3.7.4+ * A recent browser like Firefox, Chrome, Opera, Safari. [Internet Explorer currently not supported, but support will come back](https://github.com/FreshRSS/FreshRSS/issues/772). * Works on mobile * The browser HTTP `Referer` header must not be disabled when using the form login method diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php index 6a8aa01cf..ffda1450d 100755 --- a/app/Controllers/feedController.php +++ b/app/Controllers/feedController.php @@ -200,7 +200,9 @@ class FreshRSS_feed_Controller extends Minz_ActionController { $entryDAO->addEntry($values); } $feedDAO->updateLastUpdate($feed->id()); - $feedDAO->commit(); + if ($feedDAO->inTransaction()) { + $feedDAO->commit(); + } // Entries are in DB, we redirect to feed configuration page. $url_redirect['params']['id'] = $feed->id(); @@ -364,7 +366,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController { //', old hash ' . $existingHash . ', new hash ' . $entry->hash()); //TODO: Make an updated/is_read policy by feed, in addition to the global one. $entry->_isRead(FreshRSS_Context::$user_conf->mark_updated_article_unread ? false : null); //Change is_read according to policy. - if (!$entryDAO->hasTransaction()) { + if (!$entryDAO->inTransaction()) { $entryDAO->beginTransaction(); } $entryDAO->updateEntry($entry->toArray()); @@ -396,7 +398,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController { $feed->pubSubHubbubError(true); } - if (!$entryDAO->hasTransaction()) { + if (!$entryDAO->inTransaction()) { $entryDAO->beginTransaction(); } $entryDAO->addEntry($entry->toArray()); @@ -408,7 +410,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController { if ($feed_history >= 0 && rand(0, 30) === 1) { // TODO: move this function in web cron when available (see entry::purge) // Remove old entries once in 30. - if (!$entryDAO->hasTransaction()) { + if (!$entryDAO->inTransaction()) { $entryDAO->beginTransaction(); } @@ -421,8 +423,8 @@ class FreshRSS_feed_Controller extends Minz_ActionController { } } - $feedDAO->updateLastUpdate($feed->id(), 0, $entryDAO->hasTransaction()); - if ($entryDAO->hasTransaction()) { + $feedDAO->updateLastUpdate($feed->id(), 0, $entryDAO->inTransaction()); + if ($entryDAO->inTransaction()) { $entryDAO->commit(); } diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index f74055835..fa5d87b55 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -11,7 +11,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } protected function addColumn($name) { - Minz_Log::debug('FreshRSS_EntryDAO::autoAddColumn: ' . $name); + Minz_Log::warning('FreshRSS_EntryDAO::addColumn: ' . $name); $hasTransaction = false; try { $stm = null; @@ -38,7 +38,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { return $stm && $stm->execute(); } } catch (Exception $e) { - Minz_Log::debug('FreshRSS_EntryDAO::autoAddColumn error: ' . $e->getMessage()); + Minz_Log::error('FreshRSS_EntryDAO::addColumn error: ' . $e->getMessage()); if ($hasTransaction) { $this->bd->rollBack(); } @@ -46,9 +46,44 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { return false; } - protected function autoAddColumn($errorInfo) { + private $triedUpdateToUtf8mb4 = false; + + protected function updateToUtf8mb4() { + if ($this->triedUpdateToUtf8mb4) { + return false; + } + $this->triedUpdateToUtf8mb4 = true; + Minz_Log::warning('Updating MySQL to UTF8MB4...'); + $db = FreshRSS_Context::$system_conf->db; + if ($db['type'] === 'mysql') { + include_once(APP_PATH . '/SQL/install.sql.mysql.php'); + if (defined('SQL_UPDATE_UTF8MB4')) { + $hadTransaction = $this->bd->inTransaction(); + if ($hadTransaction) { + $this->bd->commit(); + } + $ok = false; + try { + $sql = sprintf(SQL_UPDATE_UTF8MB4, $this->prefix, $db['base']); + $stm = $this->bd->prepare($sql); + $ok = $stm->execute(); + } catch (Exception $e) { + Minz_Log::error('FreshRSS_EntryDAO::updateToUtf8mb4 error: ' . $e->getMessage()); + } + if ($hadTransaction) { + $this->bd->beginTransaction(); + //NB: Transaction not starting. Why? (tested on PHP 7.0.8-0ubuntu and MySQL 5.7.13-0ubuntu) + } + return $ok; + } + } + return false; + } + + protected function autoUpdateDb($errorInfo) { if (isset($errorInfo[0])) { - if ($errorInfo[0] == '42S22') { //ER_BAD_FIELD_ERROR + if ($errorInfo[0] === '42S22') { //ER_BAD_FIELD_ERROR + //autoAddColumn foreach (array('lastSeen', 'hash') as $column) { if (stripos($errorInfo[2], $column) !== false) { return $this->addColumn($column); @@ -56,6 +91,11 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } } } + if (isset($errorInfo[1])) { + if ($errorInfo[1] == '1366') { //ER_TRUNCATED_WRONG_VALUE_FOR_FIELD + return $this->updateToUtf8mb4(); + } + } return false; } @@ -94,7 +134,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { return $this->bd->lastInsertId(); } else { $info = $this->addEntryPrepared == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $this->addEntryPrepared->errorInfo(); - if ($this->autoAddColumn($info)) { + if ($this->autoUpdateDb($info)) { return $this->addEntry($valuesTmp); } elseif ((int)($info[0] / 1000) !== 23) { //Filter out "SQLSTATE Class code 23: Constraint Violation" because of expected duplicate entries Minz_Log::error('SQL error addEntry: ' . $info[0] . ': ' . $info[1] . ' ' . $info[2] @@ -145,7 +185,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { return $this->bd->lastInsertId(); } else { $info = $this->updateEntryPrepared == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $this->updateEntryPrepared->errorInfo(); - if ($this->autoAddColumn($info)) { + if ($this->autoUpdateDb($info)) { return $this->updateEntry($valuesTmp); } Minz_Log::error('SQL error updateEntry: ' . $info[0] . ': ' . $info[1] . ' ' . $info[2] @@ -615,7 +655,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { return $result; } else { $info = $stm == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $stm->errorInfo(); - if ($this->autoAddColumn($info)) { + if ($this->autoUpdateDb($info)) { return $this->listHashForFeedGuids($id_feed, $guids); } Minz_Log::error('SQL error listHashForFeedGuids: ' . $info[0] . ': ' . $info[1] . ' ' . $info[2] @@ -636,7 +676,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { return $stm->rowCount(); } else { $info = $stm == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $stm->errorInfo(); - if ($this->autoAddColumn($info)) { + if ($this->autoUpdateDb($info)) { return $this->updateLastSeen($id_feed, $guids); } Minz_Log::error('SQL error updateLastSeen: ' . $info[0] . ': ' . $info[1] . ' ' . $info[2] @@ -692,7 +732,9 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { public function optimizeTable() { $sql = 'OPTIMIZE TABLE `' . $this->prefix . 'entry`'; //MySQL $stm = $this->bd->prepare($sql); - $stm->execute(); + if ($stm) { + return $stm->execute(); + } } public function size($all = false) { diff --git a/app/SQL/install.sql.mysql.php b/app/SQL/install.sql.mysql.php index 0f4e04620..c8755c1ad 100644 --- a/app/SQL/install.sql.mysql.php +++ b/app/SQL/install.sql.mysql.php @@ -2,17 +2,17 @@ define('SQL_CREATE_TABLES', ' CREATE TABLE IF NOT EXISTS `%1$scategory` ( `id` SMALLINT NOT NULL AUTO_INCREMENT, -- v0.7 - `name` varchar(255) NOT NULL, + `name` varchar(191) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY (`name`) -- v0.7 -) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = INNODB; CREATE TABLE IF NOT EXISTS `%1$sfeed` ( `id` SMALLINT NOT NULL AUTO_INCREMENT, -- v0.7 `url` varchar(511) CHARACTER SET latin1 NOT NULL, `category` SMALLINT DEFAULT 0, -- v0.7 - `name` varchar(255) NOT NULL, + `name` varchar(191) NOT NULL, `website` varchar(255) CHARACTER SET latin1, `description` text, `lastUpdate` int(11) DEFAULT 0, -- Until year 2038 @@ -30,7 +30,7 @@ CREATE TABLE IF NOT EXISTS `%1$sfeed` ( INDEX (`name`), -- v0.7 INDEX (`priority`), -- v0.7 INDEX (`keep_history`) -- v0.7 -) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = INNODB; CREATE TABLE IF NOT EXISTS `%1$sentry` ( @@ -53,7 +53,7 @@ CREATE TABLE IF NOT EXISTS `%1$sentry` ( INDEX (`is_favorite`), -- v0.7 INDEX (`is_read`), -- v0.7 INDEX `entry_lastSeen_index` (`lastSeen`) -- v1.1.1 -) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci +) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = INNODB; INSERT IGNORE INTO `%1$scategory` (id, name) VALUES(1, "%2$s"); @@ -62,3 +62,24 @@ INSERT IGNORE INTO `%1$sfeed` (url, category, name, website, description, ttl) V '); define('SQL_DROP_TABLES', 'DROP TABLES %1$sentry, %1$sfeed, %1$scategory'); + +define('SQL_UPDATE_UTF8MB4', ' +ALTER DATABASE `%2$s` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; + +ALTER TABLE `%1$scategory` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +UPDATE `%1$scategory` SET name=SUBSTRING(name,1,190) where LENGTH(name) > 191; +ALTER TABLE `%1$scategory` MODIFY `name` VARCHAR(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL; +OPTIMIZE TABLE `%1$scategory`; + +ALTER TABLE `%1$sfeed` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +UPDATE `%1$sfeed` SET name=SUBSTRING(name,1,190) where LENGTH(name) > 191; +ALTER TABLE `%1$sfeed` MODIFY `name` VARCHAR(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL; +ALTER TABLE `%1$sfeed` MODIFY `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +OPTIMIZE TABLE `%1$sfeed`; + +ALTER TABLE `%1$sentry` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +ALTER TABLE `%1$sentry` MODIFY `title` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL; +ALTER TABLE `%1$sentry` MODIFY `author` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +ALTER TABLE `%1$sentry` MODIFY `tags` VARCHAR(1023) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; +OPTIMIZE TABLE `%1$sentry`; +'); diff --git a/app/install.php b/app/install.php index 062f66814..df6e1aeec 100644 --- a/app/install.php +++ b/app/install.php @@ -19,7 +19,7 @@ if (isset($_GET['step'])) { define('STEP', 0); } -define('SQL_CREATE_DB', 'CREATE DATABASE IF NOT EXISTS %1$s DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci;'); +define('SQL_CREATE_DB', 'CREATE DATABASE IF NOT EXISTS %1$s DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;'); if (STEP === 3 && isset($_POST['type'])) { $_SESSION['bd_type'] = $_POST['type']; @@ -253,7 +253,7 @@ function newPdo() { case 'mysql': $str = 'mysql:host=' . $_SESSION['bd_host'] . ';dbname=' . $_SESSION['bd_base']; $driver_options = array( - PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8', + PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4', ); break; case 'sqlite': @@ -309,7 +309,7 @@ function checkStep0() { } function checkStep1() { - $php = version_compare(PHP_VERSION, '5.3.0') >= 0; + $php = version_compare(PHP_VERSION, '5.3.3') >= 0; $minz = file_exists(join_path(LIB_PATH, 'Minz')); $curl = extension_loaded('curl'); $pdo_mysql = extension_loaded('pdo_mysql'); @@ -437,7 +437,7 @@ function checkBD() { switch ($_SESSION['bd_type']) { case 'mysql': $driver_options = array( - PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8' + PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4' ); try { // on ouvre une connexion juste pour créer la base si elle n'existe pas @@ -536,7 +536,7 @@ function printStep1() {

-

+

diff --git a/app/views/update/checkInstall.phtml b/app/views/update/checkInstall.phtml index ed3858b56..543ab43de 100644 --- a/app/views/update/checkInstall.phtml +++ b/app/views/update/checkInstall.phtml @@ -9,7 +9,7 @@

prefix = $db['prefix'] . $currentUser . '_'; } elseif ($type === 'sqlite') { $string = 'sqlite:' . join_path(DATA_PATH, 'users', $currentUser, 'db.sqlite'); @@ -96,18 +95,15 @@ class Minz_ModelPdo { public function beginTransaction() { $this->bd->beginTransaction(); - self::$has_transaction = true; } - public function hasTransaction() { - return self::$has_transaction; + public function inTransaction() { + return $this->bd->inTransaction(); //requires PHP >= 5.3.3 } public function commit() { $this->bd->commit(); - self::$has_transaction = false; } public function rollBack() { $this->bd->rollBack(); - self::$has_transaction = false; } public static function clean() { -- cgit v1.2.3 From 7c1b5e322cca0134f57b3a436129985ba9170b9f Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Tue, 2 Aug 2016 22:49:35 +0200 Subject: PostgreSQL draft https://github.com/FreshRSS/FreshRSS/issues/416 Based on @Damstre work https://github.com/FreshRSS/FreshRSS/pull/1071 Not tested --- app/Models/CategoryDAO.php | 7 +- app/Models/ConfigurationSetter.php | 1 + app/Models/EntryDAO.php | 2 +- app/Models/EntryDAOPGSQL.php | 92 ++++++++++++++++++ app/Models/Factory.php | 42 ++++---- app/Models/FeedDAO.php | 2 +- app/Models/StatsDAO.php | 8 +- app/Models/StatsDAOPGSQL.php | 192 +++++++++++++++++++++++++++++++++++++ app/SQL/install.sql.mysql.php | 2 + app/SQL/install.sql.pgsql.php | 91 ++++++++++++++++++ app/install.php | 31 +++++- lib/Minz/ModelPdo.php | 77 +++++++++------ p/scripts/install.js | 6 +- 13 files changed, 495 insertions(+), 58 deletions(-) create mode 100644 app/Models/EntryDAOPGSQL.php create mode 100644 app/Models/StatsDAOPGSQL.php create mode 100644 app/SQL/install.sql.pgsql.php (limited to 'lib/Minz/ModelPdo.php') diff --git a/app/Models/CategoryDAO.php b/app/Models/CategoryDAO.php index fc431553e..a44edb0f6 100644 --- a/app/Models/CategoryDAO.php +++ b/app/Models/CategoryDAO.php @@ -10,7 +10,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable ); if ($stm && $stm->execute($values)) { - return $this->bd->lastInsertId(); + return $this->bd->lastInsertId('"' . parent::prefix . 'category_id_seq"'); } else { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); Minz_Log::error('SQL error addCategory: ' . $info[2]); @@ -207,12 +207,13 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable $previousLine = null; $feedsDao = array(); + $feedDao = FreshRSS_Factory::createFeedDAO(); foreach ($listDAO as $line) { if ($previousLine['c_id'] != null && $line['c_id'] !== $previousLine['c_id']) { // End of the current category, we add it to the $list $cat = new FreshRSS_Category( $previousLine['c_name'], - FreshRSS_FeedDAO::daoToFeed($feedsDao, $previousLine['c_id']) + $feedDao->daoToFeed($feedsDao, $previousLine['c_id']) ); $cat->_id($previousLine['c_id']); $list[$previousLine['c_id']] = $cat; @@ -228,7 +229,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable if ($previousLine != null) { $cat = new FreshRSS_Category( $previousLine['c_name'], - FreshRSS_FeedDAO::daoToFeed($feedsDao, $previousLine['c_id']) + $feedDao->daoToFeed($feedsDao, $previousLine['c_id']) ); $cat->_id($previousLine['c_id']); $list[$previousLine['c_id']] = $cat; diff --git a/app/Models/ConfigurationSetter.php b/app/Models/ConfigurationSetter.php index e472b1e7f..988e83356 100644 --- a/app/Models/ConfigurationSetter.php +++ b/app/Models/ConfigurationSetter.php @@ -287,6 +287,7 @@ class FreshRSS_ConfigurationSetter { switch ($value['type']) { case 'mysql': + case 'pgsql': if (empty($value['host']) || empty($value['user']) || empty($value['base']) || diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index c9e6f9742..ba52d3f15 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -3,7 +3,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { public function isCompressed() { - return parent::$sharedDbType !== 'sqlite'; + return parent::$sharedDbType === 'mysql'; } public function hasNativeHex() { diff --git a/app/Models/EntryDAOPGSQL.php b/app/Models/EntryDAOPGSQL.php new file mode 100644 index 000000000..95c12ff5d --- /dev/null +++ b/app/Models/EntryDAOPGSQL.php @@ -0,0 +1,92 @@ +bd->beginTransaction(); + + $sql = 'UPDATE "' . $this->prefix . 'entry" ' + . 'SET is_read=:is_read ' + . 'WHERE id_feed=:id_feed AND NOT is_read AND id <= :idmax'; + $values = array($id_feed, $idMax); + $stm = $this->bd->prepare($sql); + $stm->bindValue(':is_read', true, PDO::PARAM_BOOL); + $stm->bindValue(':id_feed', $id_feed); + $stm->bindValue(':idmax', $idMax); + + if (!($stm && $stm->execute())) { + $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); + Minz_Log::error('SQL error markReadFeed: ' . $info[2]); + $this->bd->rollBack(); + return false; + } + $affected = $stm->rowCount(); + + $this->bd->commit(); + return $affected; + } + + public function listHashForFeedGuids($id_feed, $guids) { + if (count($guids) < 1) { + return array(); + } + $sql = 'SELECT guid, hash AS hexHash FROM "' . $this->prefix . 'entry" WHERE id_feed=? AND guid IN (' . str_repeat('?,', count($guids) - 1). '?)'; + $stm = $this->bd->prepare($sql); + $values = array($id_feed); + $values = array_merge($values, $guids); + if ($stm && $stm->execute($values)) { + $result = array(); + $rows = $stm->fetchAll(PDO::FETCH_ASSOC); + foreach ($rows as $row) { + $result[$row['guid']] = $row['hexHash']; + } + return $result; + } else { + $info = $stm == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $stm->errorInfo(); + if ($this->autoAddColumn($info)) { + return $this->listHashForFeedGuids($id_feed, $guids); + } + Minz_Log::error('SQL error listHashForFeedGuids: ' . $info[0] . ': ' . $info[1] . ' ' . $info[2] + . ' while querying feed ' . $id_feed); + return false; + } + } + + public function optimizeTable() { + return null; + } + + public function size($all = true) { + $db = FreshRSS_Context::$system_conf->db; + $sql = 'SELECT pg_size_pretty(pg_database_size(?))'; + $values = array($db['base']); + $stm = $this->bd->prepare($sql); + $stm->execute($values); + $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0); + return $res[0]; + } + +} diff --git a/app/Models/Factory.php b/app/Models/Factory.php index db09d155d..764987c46 100644 --- a/app/Models/Factory.php +++ b/app/Models/Factory.php @@ -4,37 +4,47 @@ class FreshRSS_Factory { public static function createFeedDao($username = null) { $conf = Minz_Configuration::get('system'); - if ($conf->db['type'] === 'sqlite') { - return new FreshRSS_FeedDAOSQLite($username); - } else { - return new FreshRSS_FeedDAO($username); + switch ($conf->db['type']) { + case 'sqlite': + return new FreshRSS_FeedDAOSQLite($username); + default: + return new FreshRSS_FeedDAO($username); } } public static function createEntryDao($username = null) { $conf = Minz_Configuration::get('system'); - if ($conf->db['type'] === 'sqlite') { - return new FreshRSS_EntryDAOSQLite($username); - } else { - return new FreshRSS_EntryDAO($username); + switch ($conf->db['type']) { + case 'sqlite': + return new FreshRSS_EntryDAOSQLite($username); + case 'pgsql': + return new FreshRSS_EntryDAOPGSQL($username); + default: + return new FreshRSS_EntryDAO($username); } } public static function createStatsDAO($username = null) { $conf = Minz_Configuration::get('system'); - if ($conf->db['type'] === 'sqlite') { - return new FreshRSS_StatsDAOSQLite($username); - } else { - return new FreshRSS_StatsDAO($username); + switch ($conf->db['type']) { + case 'sqlite': + return new FreshRSS_StatsDAOSQLite($username); + case 'pgsql': + return new FreshRSS_StatsDAOPGSQL($username); + default: + return new FreshRSS_StatsDAO($username); } } public static function createDatabaseDAO($username = null) { $conf = Minz_Configuration::get('system'); - if ($conf->db['type'] === 'sqlite') { - return new FreshRSS_DatabaseDAOSQLite($username); - } else { - return new FreshRSS_DatabaseDAO($username); + switch ($conf->db['type']) { + case 'sqlite': + return new FreshRSS_DatabaseDAOSQLite($username); + case 'pgsql': + return new FreshRSS_DatabaseDAOPGSQL($username); + default: + return new FreshRSS_DatabaseDAO($username); } } diff --git a/app/Models/FeedDAO.php b/app/Models/FeedDAO.php index 475d39286..f29dac9c0 100644 --- a/app/Models/FeedDAO.php +++ b/app/Models/FeedDAO.php @@ -16,7 +16,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { ); if ($stm && $stm->execute($values)) { - return $this->bd->lastInsertId(); + return $this->bd->lastInsertId('"' . parent::prefix . 'feed_id_seq"'); } else { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); Minz_Log::error('SQL error addFeed: ' . $info[2]); diff --git a/app/Models/StatsDAO.php b/app/Models/StatsDAO.php index 5ca333396..e18ba4748 100644 --- a/app/Models/StatsDAO.php +++ b/app/Models/StatsDAO.php @@ -37,10 +37,10 @@ class FreshRSS_StatsDAO extends Minz_ModelPdo { $filter .= "AND e.id_feed = {$feed}"; } $sql = <<prefix}entry AS e , {$this->prefix}feed AS f WHERE e.id_feed = f.id diff --git a/app/Models/StatsDAOPGSQL.php b/app/Models/StatsDAOPGSQL.php new file mode 100644 index 000000000..0bde72e58 --- /dev/null +++ b/app/Models/StatsDAOPGSQL.php @@ -0,0 +1,192 @@ +prefix}entry" AS e +, "{$this->prefix}feed" AS f +WHERE e.id_feed = f.id +{$filter} +SQL; + $stm = $this->bd->prepare($sql); + $stm->execute(); + $res = $stm->fetchAll(PDO::FETCH_ASSOC); + + return $res[0]; + } + + /** + * Calculates entry count per day on a 30 days period. + * Returns the result as a JSON string. + * + * @return string + */ + public function calculateEntryCount() { + $count = $this->initEntryCountArray(); + $period = self::ENTRY_COUNT_PERIOD; + + // Get stats per day for the last 30 days + $sql = <<prefix}entry" AS e +WHERE to_timestamp(e.date) BETWEEN NOW() - INTERVAL '{$period} DAYS' AND NOW() - INTERVAL '1 DAY' +GROUP BY day +ORDER BY day ASC +SQL; + $stm = $this->bd->prepare($sql); + $stm->execute(); + $res = $stm->fetchAll(PDO::FETCH_ASSOC); + + foreach ($res as $value) { + $count[$value['day']] = (int) $value['count']; + } + + return $this->convertToSerie($count); + } + + /** + * Calculates entry average per day on a 30 days period. + * + * @return integer + */ + public function calculateEntryAverage() { + $period = self::ENTRY_COUNT_PERIOD; + + // Get stats per day for the last 30 days + $sql = <<prefix}entry" AS e +WHERE to_timestamp(e.date) BETWEEN NOW() - INTERVAL '{$period} DAYS' AND NOW() - INTERVAL '1 DAY' +SQL; + $stm = $this->bd->prepare($sql); + $stm->execute(); + $res = $stm->fetch(PDO::FETCH_NAMED); + + return round($res['average'], 2); + } + + /** + * Calculates the number of article per hour of the day per feed + * + * @param integer $feed id + * @return string + */ + public function calculateEntryRepartitionPerFeedPerHour($feed = null) { + return $this->calculateEntryRepartitionPerFeedPerPeriod('hour', $feed); + } + + /** + * Calculates the number of article per day of week per feed + * + * @param integer $feed id + * @return string + */ + public function calculateEntryRepartitionPerFeedPerDayOfWeek($feed = null) { + return $this->calculateEntryRepartitionPerFeedPerPeriod('day', $feed); + } + + /** + * Calculates the number of article per month per feed + * + * @param integer $feed + * @return string + */ + public function calculateEntryRepartitionPerFeedPerMonth($feed = null) { + return $this->calculateEntryRepartitionPerFeedPerPeriod('month', $feed); + } + + /** + * Calculates the number of article per period per feed + * + * @param string $period format string to use for grouping + * @param integer $feed id + * @return string + */ + protected function calculateEntryRepartitionPerFeedPerPeriod($period, $feed = null) { + $restrict = ''; + if ($feed) { + $restrict = "WHERE e.id_feed = {$feed}"; + } + $sql = <<prefix}entry" AS e +{$restrict} +GROUP BY period +ORDER BY period ASC +SQL; + + $stm = $this->bd->prepare($sql); + $stm->execute(); + $res = $stm->fetchAll(PDO::FETCH_NAMED); + + foreach ($res as $value) { + $repartition[(int) $value['period']] = (int) $value['count']; + } + + return $this->convertToSerie($repartition); + } + + /** + * Calculates the average number of article per feed + * + * @param float $period number used to divide the number of day in the period + * @param integer $feed id + * @return integer + */ + protected function calculateEntryAveragePerFeedPerPeriod($period, $feed = null) { + $restrict = ''; + if ($feed) { + $restrict = "WHERE e.id_feed = {$feed}"; + } + $sql = <<prefix}entry" AS e +{$restrict} +SQL; + $stm = $this->bd->prepare($sql); + $stm->execute(); + $res = $stm->fetch(PDO::FETCH_NAMED); + $date_min = new \DateTime(); + $date_min->setTimestamp($res['date_min']); + $date_max = new \DateTime(); + $date_max->setTimestamp($res['date_max']); + $interval = $date_max->diff($date_min, true); + $interval_in_days = $interval->format('%a'); + if ($interval_in_days <= 0) { + // Surely only one article. + // We will return count / (period/period) == count. + $interval_in_days = $period; + } + + return $res['count'] / ($interval_in_days / $period); + } + +} diff --git a/app/SQL/install.sql.mysql.php b/app/SQL/install.sql.mysql.php index c78839ef7..92a00aecc 100644 --- a/app/SQL/install.sql.mysql.php +++ b/app/SQL/install.sql.mysql.php @@ -1,4 +1,6 @@ $curl ? 'ok' : 'ko', 'pdo-mysql' => $pdo_mysql ? 'ok' : 'ko', 'pdo-sqlite' => $pdo_sqlite ? 'ok' : 'ko', + 'pdo-pgsql' => $pdo_pgsql ? 'ok' : 'ko', 'pdo' => $pdo ? 'ok' : 'ko', 'pcre' => $pcre ? 'ok' : 'ko', 'ctype' => $ctype ? 'ok' : 'ko', @@ -435,6 +438,22 @@ function checkBD() { PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, ); break; + case 'pgsql': + $driver_options = array( + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + ); + + try { // on ouvre une connexion juste pour créer la base si elle n'existe pas + $str = 'pgsql:host=' . $_SESSION['bd_host'] . ';'; + $c = new PDO($str, $_SESSION['bd_user'], $_SESSION['bd_password'], $driver_options); + $sql = sprintf(SQL_CREATE_DB, $_SESSION['bd_base']); + $res = $c->query($sql); + } catch (PDOException $e) { + } + + // on écrase la précédente connexion en sélectionnant la nouvelle BDD + $str = 'pgsql:host=' . $_SESSION['bd_host'] . ';dbname=' . $_SESSION['bd_base']; + break; default: return false; } @@ -708,6 +727,12 @@ function printStep3() { SQLite + + + diff --git a/lib/Minz/ModelPdo.php b/lib/Minz/ModelPdo.php index 845aecaae..b98a26d06 100644 --- a/lib/Minz/ModelPdo.php +++ b/lib/Minz/ModelPdo.php @@ -55,36 +55,36 @@ class Minz_ModelPdo { $driver_options = isset($conf->db['pdo_options']) && is_array($conf->db['pdo_options']) ? $conf->db['pdo_options'] : array(); try { - $type = $db['type']; - if ($type === 'mysql') { - $string = 'mysql:host=' . $db['host'] - . ';dbname=' . $db['base'] - . ';charset=utf8mb4'; - $driver_options[PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET NAMES utf8mb4'; - $this->prefix = $db['prefix'] . $currentUser . '_'; - } elseif ($type === 'sqlite') { - $string = 'sqlite:' . join_path(DATA_PATH, 'users', $currentUser, 'db.sqlite'); - //$driver_options[PDO::ATTR_ERRMODE] = PDO::ERRMODE_EXCEPTION; - $this->prefix = ''; - } else { - throw new Minz_PDOConnectionException( - 'Invalid database type!', - $db['user'], Minz_Exception::ERROR - ); - } - self::$sharedDbType = $type; - self::$sharedPrefix = $this->prefix; - - $this->bd = new MinzPDO( - $string, - $db['user'], - $db['password'], - $driver_options - ); - if ($type === 'sqlite') { - $this->bd->exec('PRAGMA foreign_keys = ON;'); + switch ($db['type']) { + case 'mysql': + $string = 'mysql:host=' . $db['host'] . ';dbname=' . $db['base'] . ';charset=utf8mb4'; + $driver_options[PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET NAMES utf8mb4'; + $this->prefix = $db['prefix'] . $currentUser . '_'; + $this->bd = new MinzPDO($string, $db['user'], $db['password'], $driver_options); + //TODO Consider: $this->bd->exec("SET SESSION sql_mode = 'ANSI_QUOTES';"); + break; + case 'sqlite': + $string = 'sqlite:' . join_path(DATA_PATH, 'users', $currentUser, 'db.sqlite'); + $this->prefix = ''; + $this->bd = new MinzPDO($string, $db['user'], $db['password'], $driver_options); + $this->bd->exec('PRAGMA foreign_keys = ON;'); + break; + case 'pgsql': + $string = 'pgsql:host=' . $db['host'] . ';dbname=' . $db['base'] . ';charset=utf8'; + $this->prefix = $db['prefix'] . $currentUser . '_'; + $this->bd = new MinzPDOPGSQL($string, $db['user'], $db['password'], $driver_options); + $this->bd->exec("SET NAMES 'UTF8';"); + break; + default: + throw new Minz_PDOConnectionException( + 'Invalid database type!', + $db['user'], Minz_Exception::ERROR + ); + break; } self::$sharedBd = $this->bd; + self::$sharedDbType = $db['type']; + self::$sharedPrefix = $this->prefix; } catch (Exception $e) { throw new Minz_PDOConnectionException( $string, @@ -119,18 +119,39 @@ class MinzPDO extends PDO { } } + protected function compatibility($statement) { + return $statement; + } + public function prepare($statement, $driver_options = array()) { MinzPDO::check($statement); + $statement = MinzPDO::compatibility($statement); return parent::prepare($statement, $driver_options); } public function exec($statement) { MinzPDO::check($statement); + $statement = MinzPDO::compatibility($statement); return parent::exec($statement); } public function query($statement) { MinzPDO::check($statement); + $statement = MinzPDO::compatibility($statement); return parent::query($statement); } + + public function lastInsertId($name = null) { + return parent::lastInsertId(); //We discard the name, only used by PostgreSQL + } +} + +class MinzPDOPGSQL extends MinzPDO { + protected function compatibility($statement) { + return str_replace(array('`', " X'"), array('"', " E'\\x"), $statement); + } + + public function lastInsertId($name = null) { + return parent::lastInsertId($name); + } } diff --git a/p/scripts/install.js b/p/scripts/install.js index 57fc2450a..3e1db57b5 100644 --- a/p/scripts/install.js +++ b/p/scripts/install.js @@ -42,13 +42,15 @@ if (auth_type) { function mySqlShowHide() { var mysql = document.getElementById('mysql'); if (mysql) { - mysql.style.display = document.getElementById('type').value === 'mysql' ? 'block' : 'none'; - if (document.getElementById('type').value !== 'mysql') { + if (document.getElementById('type').value === 'sqlite') { document.getElementById('host').value = ''; document.getElementById('user').value = ''; document.getElementById('pass').value = ''; document.getElementById('base').value = ''; document.getElementById('prefix').value = ''; + mysql.style.display = 'none'; + } else { + mysql.style.display = 'block'; } } } -- cgit v1.2.3 From c25fdbcc0990b637e305665a456e52e1aa3dec0a Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Tue, 23 Aug 2016 00:02:54 +0200 Subject: More PostgreSQL --- app/Models/EntryDAO.php | 75 ++++++++++++++--------- app/Models/EntryDAOPGSQL.php | 79 +++--------------------- app/Models/EntryDAOSQLite.php | 4 ++ app/SQL/install.sql.pgsql.php | 136 +++++++++++++++++------------------------- app/install.php | 26 ++------ lib/Minz/ModelPdo.php | 29 +++++---- 6 files changed, 138 insertions(+), 211 deletions(-) (limited to 'lib/Minz/ModelPdo.php') diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index 3a1dc1ba5..8d136cd6c 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -10,6 +10,14 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { return parent::$sharedDbType !== 'sqlite'; } + public function sqlHexDecode($x) { + return 'X' . $x; + } + + public function sqlHexEncode($x) { + return 'hex(' . $x . ')'; + } + protected function addColumn($name) { Minz_Log::warning('FreshRSS_EntryDAO::addColumn: ' . $name); $hasTransaction = false; @@ -106,31 +114,42 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { $sql = 'INSERT INTO `' . $this->prefix . 'entry` (id, guid, title, author, ' . ($this->isCompressed() ? 'content_bin' : 'content') . ', link, date, lastSeen, hash, is_read, is_favorite, id_feed, tags) ' - . 'VALUES(?, ?, ?, ?, ' - . ($this->isCompressed() ? 'COMPRESS(?)' : '?') - . ', ?, ?, ?, ' - . ($this->hasNativeHex() ? 'X?' : '?') - . ', ?, ?, ?, ?)'; + . 'VALUES(:id, :guid, :title, :author, ' + . ($this->isCompressed() ? 'COMPRESS(:content)' : ':content') + . ', :link, :date, :last_seen, ' + . $this->sqlHexDecode(':hash') + . ', :is_read, :is_favorite, :id_feed, :tags)'; $this->addEntryPrepared = $this->bd->prepare($sql); } + $this->addEntryPrepared->bindParam(':id', $valuesTmp['id']); + $valuesTmp['guid'] = substr($valuesTmp['guid'], 0, 760); + $this->addEntryPrepared->bindParam(':guid', $valuesTmp['guid']); + $valuesTmp['title'] = substr($valuesTmp['title'], 0, 255); + $this->addEntryPrepared->bindParam(':title', $valuesTmp['title']); + $valuesTmp['author'] = substr($valuesTmp['author'], 0, 255); + $this->addEntryPrepared->bindParam(':author', $valuesTmp['author']); + $this->addEntryPrepared->bindParam(':content', $valuesTmp['content']); + $valuesTmp['link'] = substr($valuesTmp['link'], 0, 1023); + $this->addEntryPrepared->bindParam(':link', $valuesTmp['link']); + $this->addEntryPrepared->bindParam(':date', $valuesTmp['date'], PDO::PARAM_INT); + $valuesTmp['lastSeen'] = time(); + $this->addEntryPrepared->bindParam(':last_seen', $valuesTmp['lastSeen'], PDO::PARAM_INT); + $valuesTmp['is_read'] = $valuesTmp['is_read'] ? 1 : 0; + $this->addEntryPrepared->bindParam(':is_read', $valuesTmp['is_read'], PDO::PARAM_INT); + $valuesTmp['is_favorite'] = $valuesTmp['is_favorite'] ? 1 : 0; + $this->addEntryPrepared->bindParam(':is_favorite', $valuesTmp['is_favorite'], PDO::PARAM_INT); + $this->addEntryPrepared->bindParam(':id_feed', $valuesTmp['id_feed'], PDO::PARAM_INT); + $valuesTmp['tags'] = substr($valuesTmp['tags'], 0, 1023); + $this->addEntryPrepared->bindParam(':tags', $valuesTmp['tags']); + + if ($this->hasNativeHex()) { + $this->addEntryPrepared->bindParam(':hash', $valuesTmp['hash']); + } else { + $valuesTmp['hashBin'] = pack('H*', $valuesTmp['hash']); + $this->addEntryPrepared->bindParam(':hash', $valuesTmp['hashBin']); // X'09AF' hexadecimal literals do not work with SQLite/PDO //hex2bin() is PHP5.4+ + } - $values = array( - $valuesTmp['id'], - substr($valuesTmp['guid'], 0, 760), - substr($valuesTmp['title'], 0, 255), - substr($valuesTmp['author'], 0, 255), - $valuesTmp['content'], - substr($valuesTmp['link'], 0, 1023), - $valuesTmp['date'], - time(), - $this->hasNativeHex() ? $valuesTmp['hash'] : pack('H*', $valuesTmp['hash']), // X'09AF' hexadecimal literals do not work with SQLite/PDO //hex2bin() is PHP5.4+ - $valuesTmp['is_read'] ? 1 : 0, - $valuesTmp['is_favorite'] ? 1 : 0, - $valuesTmp['id_feed'], - substr($valuesTmp['tags'], 0, 1023), - ); - - if ($this->addEntryPrepared && $this->addEntryPrepared->execute($values)) { + if ($this->addEntryPrepared && $this->addEntryPrepared->execute()) { return $this->bd->lastInsertId(); } else { $info = $this->addEntryPrepared == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $this->addEntryPrepared->errorInfo(); @@ -156,7 +175,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { . 'SET title=?, author=?, ' . ($this->isCompressed() ? 'content_bin=COMPRESS(?)' : 'content=?') . ', link=?, date=?, lastSeen=?, hash=' - . ($this->hasNativeHex() ? 'X?' : '?') + . ($this->hasNativeHex() ? 'X?' : '?') //TODO PostgreSQL . ', ' . ($valuesTmp['is_read'] === null ? '' : 'is_read=?, ') . 'tags=? ' . 'WHERE id_feed=? AND guid=?'; @@ -430,12 +449,12 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } $this->bd->beginTransaction(); - $sql = 'UPDATE `' . $this->prefix . 'entry` e ' - . 'SET e.is_read=1 ' - . 'WHERE e.id_feed=? AND e.is_read=0 AND e.id <= ?'; + $sql = 'UPDATE `' . $this->prefix . 'entry` ' + . 'SET is_read=1 ' + . 'WHERE id_feed=? AND is_read=0 AND id <= ?'; $values = array($id_feed, $idMax); - list($searchValues, $search) = $this->sqlListEntriesWhere('e.', $filter, $state); + list($searchValues, $search) = $this->sqlListEntriesWhere('', $filter, $state); $stm = $this->bd->prepare($sql . $search); if (!($stm && $stm->execute(array_merge($values, $searchValues)))) { @@ -658,7 +677,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { if (count($guids) < 1) { return array(); } - $sql = 'SELECT guid, hex(hash) AS hexHash FROM `' . $this->prefix . 'entry` WHERE id_feed=? AND guid IN (' . str_repeat('?,', count($guids) - 1). '?)'; + $sql = 'SELECT guid, ' . $this->sqlHexEncode('hash') . ' AS hexHash FROM `' . $this->prefix . 'entry` WHERE id_feed=? AND guid IN (' . str_repeat('?,', count($guids) - 1). '?)'; $stm = $this->bd->prepare($sql); $values = array($id_feed); $values = array_merge($values, $guids); diff --git a/app/Models/EntryDAOPGSQL.php b/app/Models/EntryDAOPGSQL.php index 95c12ff5d..b96a62ebc 100644 --- a/app/Models/EntryDAOPGSQL.php +++ b/app/Models/EntryDAOPGSQL.php @@ -1,82 +1,21 @@ bd->beginTransaction(); - - $sql = 'UPDATE "' . $this->prefix . 'entry" ' - . 'SET is_read=:is_read ' - . 'WHERE id_feed=:id_feed AND NOT is_read AND id <= :idmax'; - $values = array($id_feed, $idMax); - $stm = $this->bd->prepare($sql); - $stm->bindValue(':is_read', true, PDO::PARAM_BOOL); - $stm->bindValue(':id_feed', $id_feed); - $stm->bindValue(':idmax', $idMax); - - if (!($stm && $stm->execute())) { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::error('SQL error markReadFeed: ' . $info[2]); - $this->bd->rollBack(); - return false; - } - $affected = $stm->rowCount(); - - $this->bd->commit(); - return $affected; + public function sqlHexEncode($x) { + return 'encode(' . $x . ", 'hex')"; } - public function listHashForFeedGuids($id_feed, $guids) { - if (count($guids) < 1) { - return array(); - } - $sql = 'SELECT guid, hash AS hexHash FROM "' . $this->prefix . 'entry" WHERE id_feed=? AND guid IN (' . str_repeat('?,', count($guids) - 1). '?)'; - $stm = $this->bd->prepare($sql); - $values = array($id_feed); - $values = array_merge($values, $guids); - if ($stm && $stm->execute($values)) { - $result = array(); - $rows = $stm->fetchAll(PDO::FETCH_ASSOC); - foreach ($rows as $row) { - $result[$row['guid']] = $row['hexHash']; - } - return $result; - } else { - $info = $stm == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $stm->errorInfo(); - if ($this->autoAddColumn($info)) { - return $this->listHashForFeedGuids($id_feed, $guids); - } - Minz_Log::error('SQL error listHashForFeedGuids: ' . $info[0] . ': ' . $info[1] . ' ' . $info[2] - . ' while querying feed ' . $id_feed); - return false; - } + protected function autoUpdateDb($errorInfo) { + return false; } - public function optimizeTable() { - return null; + protected function addColumn($name) { + return false; } public function size($all = true) { diff --git a/app/Models/EntryDAOSQLite.php b/app/Models/EntryDAOSQLite.php index dad34a93d..80dbcca6b 100644 --- a/app/Models/EntryDAOSQLite.php +++ b/app/Models/EntryDAOSQLite.php @@ -2,6 +2,10 @@ class FreshRSS_EntryDAOSQLite extends FreshRSS_EntryDAO { + public function sqlHexDecode($x) { + return $x; + } + protected function autoUpdateDb($errorInfo) { if (empty($errorInfo[0]) || $errorInfo[0] == '42S22') { //ER_BAD_FIELD_ERROR //autoAddColumn diff --git a/app/SQL/install.sql.pgsql.php b/app/SQL/install.sql.pgsql.php index 0f243bdb5..04e35af68 100644 --- a/app/SQL/install.sql.pgsql.php +++ b/app/SQL/install.sql.pgsql.php @@ -1,91 +1,65 @@ 'SET NAMES utf8mb4', - ); - break; - case 'sqlite': - $str = 'sqlite:' . join_path(USERS_PATH, $_SESSION['default_user'], 'db.sqlite'); - $driver_options = array( - PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, - ); - break; - default: - return false; - } - return new PDO($str, $_SESSION['bd_user'], $_SESSION['bd_password'], $driver_options); -} - function deleteInstall() { $res = unlink(join_path(DATA_PATH, 'do-install.txt')); @@ -444,11 +427,12 @@ function checkBD() { ); try { // on ouvre une connexion juste pour créer la base si elle n'existe pas - $str = 'pgsql:host=' . $_SESSION['bd_host'] . ';'; + $str = 'pgsql:host=' . $_SESSION['bd_host'] . ';dbname=postgres'; $c = new PDO($str, $_SESSION['bd_user'], $_SESSION['bd_password'], $driver_options); $sql = sprintf(SQL_CREATE_DB, $_SESSION['bd_base']); $res = $c->query($sql); } catch (PDOException $e) { + syslog(LOG_DEBUG, 'pgsql ' . $e->getMessage()); } // on écrase la précédente connexion en sélectionnant la nouvelle BDD diff --git a/lib/Minz/ModelPdo.php b/lib/Minz/ModelPdo.php index b98a26d06..41a9f60bf 100644 --- a/lib/Minz/ModelPdo.php +++ b/lib/Minz/ModelPdo.php @@ -60,17 +60,17 @@ class Minz_ModelPdo { $string = 'mysql:host=' . $db['host'] . ';dbname=' . $db['base'] . ';charset=utf8mb4'; $driver_options[PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET NAMES utf8mb4'; $this->prefix = $db['prefix'] . $currentUser . '_'; - $this->bd = new MinzPDO($string, $db['user'], $db['password'], $driver_options); + $this->bd = new MinzPDOMySql($string, $db['user'], $db['password'], $driver_options); //TODO Consider: $this->bd->exec("SET SESSION sql_mode = 'ANSI_QUOTES';"); break; case 'sqlite': $string = 'sqlite:' . join_path(DATA_PATH, 'users', $currentUser, 'db.sqlite'); $this->prefix = ''; - $this->bd = new MinzPDO($string, $db['user'], $db['password'], $driver_options); + $this->bd = new MinzPDOMSQLite($string, $db['user'], $db['password'], $driver_options); $this->bd->exec('PRAGMA foreign_keys = ON;'); break; case 'pgsql': - $string = 'pgsql:host=' . $db['host'] . ';dbname=' . $db['base'] . ';charset=utf8'; + $string = 'pgsql:host=' . $db['host'] . ';dbname=' . $db['base']; $this->prefix = $db['prefix'] . $currentUser . '_'; $this->bd = new MinzPDOPGSQL($string, $db['user'], $db['password'], $driver_options); $this->bd->exec("SET NAMES 'UTF8';"); @@ -125,33 +125,40 @@ class MinzPDO extends PDO { public function prepare($statement, $driver_options = array()) { MinzPDO::check($statement); - $statement = MinzPDO::compatibility($statement); + $statement = $this->compatibility($statement); return parent::prepare($statement, $driver_options); } public function exec($statement) { MinzPDO::check($statement); - $statement = MinzPDO::compatibility($statement); + $statement = $this->compatibility($statement); return parent::exec($statement); } public function query($statement) { MinzPDO::check($statement); - $statement = MinzPDO::compatibility($statement); + $statement = $this->compatibility($statement); return parent::query($statement); } +} +class MinzPDOMySql extends PDO { public function lastInsertId($name = null) { return parent::lastInsertId(); //We discard the name, only used by PostgreSQL } } -class MinzPDOPGSQL extends MinzPDO { - protected function compatibility($statement) { - return str_replace(array('`', " X'"), array('"', " E'\\x"), $statement); +class MinzPDOMSQLite extends PDO { + public function lastInsertId($name = null) { + return parent::lastInsertId(); //We discard the name, only used by PostgreSQL } +} - public function lastInsertId($name = null) { - return parent::lastInsertId($name); +class MinzPDOPGSQL extends MinzPDO { + protected function compatibility($statement) { + return str_replace( + array('`', 'lastUpdate', 'pathEntries', 'httpAuth', 'cache_nbEntries', 'cache_nbUnreads', 'lastSeen'), + array('"', '"lastUpdate"', '"pathEntries"', '"httpAuth"', '"cache_nbEntries"', '"cache_nbUnreads"', '"lastSeen"'), + $statement); } } -- cgit v1.2.3 From 0f1bc956d4f301e61618cff2dd5bfcac1532067f Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Wed, 31 Aug 2016 21:23:09 +0200 Subject: MinzPDO inheritance --- lib/Minz/ModelPdo.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/Minz/ModelPdo.php') diff --git a/lib/Minz/ModelPdo.php b/lib/Minz/ModelPdo.php index 41a9f60bf..93a22fc3d 100644 --- a/lib/Minz/ModelPdo.php +++ b/lib/Minz/ModelPdo.php @@ -142,13 +142,13 @@ class MinzPDO extends PDO { } } -class MinzPDOMySql extends PDO { +class MinzPDOMySql extends MinzPDO { public function lastInsertId($name = null) { return parent::lastInsertId(); //We discard the name, only used by PostgreSQL } } -class MinzPDOMSQLite extends PDO { +class MinzPDOMSQLite extends MinzPDO { public function lastInsertId($name = null) { return parent::lastInsertId(); //We discard the name, only used by PostgreSQL } -- cgit v1.2.3 From f66be86e41d89214688a28243b412ffa43ce500d Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Wed, 31 Aug 2016 21:47:12 +0200 Subject: Quoted upper-cases instead of string replace --- app/Models/CategoryDAO.php | 2 +- app/Models/EntryDAO.php | 14 +++++++------- app/Models/EntryDAOSQLite.php | 4 ++-- app/Models/FeedDAO.php | 22 +++++++++++----------- app/Models/FeedDAOSQLite.php | 4 ++-- lib/Minz/ModelPdo.php | 5 +---- 6 files changed, 24 insertions(+), 27 deletions(-) (limited to 'lib/Minz/ModelPdo.php') diff --git a/app/Models/CategoryDAO.php b/app/Models/CategoryDAO.php index 3b1519c20..1dab60ea9 100644 --- a/app/Models/CategoryDAO.php +++ b/app/Models/CategoryDAO.php @@ -100,7 +100,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable public function listCategories($prePopulateFeeds = true, $details = false) { if ($prePopulateFeeds) { $sql = 'SELECT c.id AS c_id, c.name AS c_name, ' - . ($details ? 'f.* ' : 'f.id, f.name, f.url, f.website, f.priority, f.error, f.cache_nbEntries, f.cache_nbUnreads ') + . ($details ? 'f.* ' : 'f.id, f.name, f.url, f.website, f.priority, f.error, f.`cache_nbEntries`, f.`cache_nbUnreads` ') . 'FROM `' . $this->prefix . 'category` c ' . 'LEFT OUTER JOIN `' . $this->prefix . 'feed` f ON f.category=c.id ' . 'GROUP BY f.id, c_id ' diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index 8d136cd6c..d39f0237c 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -28,7 +28,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { $this->bd->beginTransaction(); $hasTransaction = true; } - $stm = $this->bd->prepare('ALTER TABLE `' . $this->prefix . 'entry` ADD COLUMN lastSeen INT(11) DEFAULT 0'); + $stm = $this->bd->prepare('ALTER TABLE `' . $this->prefix . 'entry` ADD COLUMN `lastSeen` INT(11) DEFAULT 0'); if ($stm && $stm->execute()) { $stm = $this->bd->prepare('CREATE INDEX entry_lastSeen_index ON `' . $this->prefix . 'entry`(`lastSeen`);'); //"IF NOT EXISTS" does not exist in MySQL 5.7 if ($stm && $stm->execute()) { @@ -113,7 +113,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { if ($this->addEntryPrepared === null) { $sql = 'INSERT INTO `' . $this->prefix . 'entry` (id, guid, title, author, ' . ($this->isCompressed() ? 'content_bin' : 'content') - . ', link, date, lastSeen, hash, is_read, is_favorite, id_feed, tags) ' + . ', link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags) ' . 'VALUES(:id, :guid, :title, :author, ' . ($this->isCompressed() ? 'COMPRESS(:content)' : ':content') . ', :link, :date, :last_seen, ' @@ -174,7 +174,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { $sql = 'UPDATE `' . $this->prefix . 'entry` ' . 'SET title=?, author=?, ' . ($this->isCompressed() ? 'content_bin=COMPRESS(?)' : 'content=?') - . ', link=?, date=?, lastSeen=?, hash=' + . ', link=?, date=?, `lastSeen`=?, hash=' . ($this->hasNativeHex() ? 'X?' : '?') //TODO PostgreSQL . ', ' . ($valuesTmp['is_read'] === null ? '' : 'is_read=?, ') . 'tags=? ' @@ -265,7 +265,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { . 'WHERE e.is_read=0 ' . 'GROUP BY e.id_feed' . ') x ON x.id_feed=f.id ' - . 'SET f.cache_nbUnreads=COALESCE(x.nbUnreads, 0) ' + . 'SET f.`cache_nbUnreads`=COALESCE(x.nbUnreads, 0) ' . 'WHERE 1'; $values = array(); if ($feedId !== false) { @@ -328,7 +328,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { } else { $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id ' . 'SET e.is_read=?,' - . 'f.cache_nbUnreads=f.cache_nbUnreads' . ($is_read ? '-' : '+') . '1 ' + . 'f.`cache_nbUnreads`=f.`cache_nbUnreads`' . ($is_read ? '-' : '+') . '1 ' . 'WHERE e.id=? AND e.is_read=?'; $values = array($is_read ? 1 : 0, $ids, $is_read ? 0 : 1); $stm = $this->bd->prepare($sql); @@ -467,7 +467,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { if ($affected > 0) { $sql = 'UPDATE `' . $this->prefix . 'feed` ' - . 'SET cache_nbUnreads=cache_nbUnreads-' . $affected + . 'SET `cache_nbUnreads`=`cache_nbUnreads`-' . $affected . ' WHERE id=?'; $values = array($id_feed); $stm = $this->bd->prepare($sql); @@ -703,7 +703,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { if (count($guids) < 1) { return 0; } - $sql = 'UPDATE `' . $this->prefix . 'entry` SET lastSeen=? WHERE id_feed=? AND guid IN (' . str_repeat('?,', count($guids) - 1). '?)'; + $sql = 'UPDATE `' . $this->prefix . 'entry` SET `lastSeen`=? WHERE id_feed=? AND guid IN (' . str_repeat('?,', count($guids) - 1). '?)'; $stm = $this->bd->prepare($sql); $values = array(time(), $id_feed); $values = array_merge($values, $guids); diff --git a/app/Models/EntryDAOSQLite.php b/app/Models/EntryDAOSQLite.php index 80dbcca6b..fd5d25bf6 100644 --- a/app/Models/EntryDAOSQLite.php +++ b/app/Models/EntryDAOSQLite.php @@ -28,7 +28,7 @@ class FreshRSS_EntryDAOSQLite extends FreshRSS_EntryDAO { protected function updateCacheUnreads($catId = false, $feedId = false) { $sql = 'UPDATE `' . $this->prefix . 'feed` ' - . 'SET cache_nbUnreads=(' + . 'SET `cache_nbUnreads`=(' . 'SELECT COUNT(*) AS nbUnreads FROM `' . $this->prefix . 'entry` e ' . 'WHERE e.id_feed=`' . $this->prefix . 'feed`.id AND e.is_read=0) ' . 'WHERE 1'; @@ -86,7 +86,7 @@ class FreshRSS_EntryDAOSQLite extends FreshRSS_EntryDAO { } $affected = $stm->rowCount(); if ($affected > 0) { - $sql = 'UPDATE `' . $this->prefix . 'feed` SET cache_nbUnreads=cache_nbUnreads' . ($is_read ? '-' : '+') . '1 ' + $sql = 'UPDATE `' . $this->prefix . 'feed` SET `cache_nbUnreads`=`cache_nbUnreads`' . ($is_read ? '-' : '+') . '1 ' . 'WHERE id=(SELECT e.id_feed FROM `' . $this->prefix . 'entry` e WHERE e.id=?)'; $values = array($ids); $stm = $this->bd->prepare($sql); diff --git a/app/Models/FeedDAO.php b/app/Models/FeedDAO.php index ce65ed8b1..6e6d8857b 100644 --- a/app/Models/FeedDAO.php +++ b/app/Models/FeedDAO.php @@ -2,7 +2,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { public function addFeed($valuesTmp) { - $sql = 'INSERT INTO `' . $this->prefix . 'feed` (url, category, name, website, description, lastUpdate, priority, httpAuth, error, keep_history, ttl) VALUES(?, ?, ?, ?, ?, ?, 10, ?, 0, -2, -2)'; + $sql = 'INSERT INTO `' . $this->prefix . 'feed` (url, category, name, website, description, `lastUpdate`, priority, `httpAuth`, error, keep_history, ttl) VALUES(?, ?, ?, ?, ?, ?, 10, ?, 0, -2, -2)'; $stm = $this->bd->prepare($sql); $values = array( @@ -85,13 +85,13 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { public function updateLastUpdate($id, $inError = 0, $updateCache = true) { if ($updateCache) { $sql = 'UPDATE `' . $this->prefix . 'feed` ' //2 sub-requests with FOREIGN KEY(e.id_feed), INDEX(e.is_read) faster than 1 request with GROUP BY or CASE - . 'SET cache_nbEntries=(SELECT COUNT(e1.id) FROM `' . $this->prefix . 'entry` e1 WHERE e1.id_feed=`' . $this->prefix . 'feed`.id),' - . 'cache_nbUnreads=(SELECT COUNT(e2.id) FROM `' . $this->prefix . 'entry` e2 WHERE e2.id_feed=`' . $this->prefix . 'feed`.id AND e2.is_read=0),' - . 'lastUpdate=?, error=? ' + . 'SET `cache_nbEntries`=(SELECT COUNT(e1.id) FROM `' . $this->prefix . 'entry` e1 WHERE e1.id_feed=`' . $this->prefix . 'feed`.id),' + . '`cache_nbUnreads`=(SELECT COUNT(e2.id) FROM `' . $this->prefix . 'entry` e2 WHERE e2.id_feed=`' . $this->prefix . 'feed`.id AND e2.is_read=0),' + . '`lastUpdate`=?, error=? ' . 'WHERE id=?'; } else { $sql = 'UPDATE `' . $this->prefix . 'feed` ' - . 'SET lastUpdate=?, error=? ' + . 'SET `lastUpdate`=?, error=? ' . 'WHERE id=?'; } @@ -226,10 +226,10 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { if ($defaultCacheDuration < 0) { $defaultCacheDuration = 2147483647; } - $sql = 'SELECT id, url, name, website, lastUpdate, pathEntries, httpAuth, keep_history, ttl ' + $sql = 'SELECT id, url, name, website, `lastUpdate`, `pathEntries`, `httpAuth`, keep_history, ttl ' . 'FROM `' . $this->prefix . 'feed` ' - . 'WHERE ttl <> -1 AND lastUpdate < (' . (time() + 60) . '-(CASE WHEN ttl=-2 THEN ' . intval($defaultCacheDuration) . ' ELSE ttl END)) ' - . 'ORDER BY lastUpdate'; + . 'WHERE ttl <> -1 AND `lastUpdate` < (' . (time() + 60) . '-(CASE WHEN ttl=-2 THEN ' . intval($defaultCacheDuration) . ' ELSE ttl END)) ' + . 'ORDER BY `lastUpdate`'; $stm = $this->bd->prepare($sql); if (!($stm && $stm->execute())) { $sql2 = 'ALTER TABLE `' . $this->prefix . 'feed` ADD COLUMN ttl INT NOT NULL DEFAULT -2'; //v0.7.3 @@ -282,7 +282,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { . 'FROM `' . $this->prefix . 'entry` e ' . 'GROUP BY e.id_feed' . ') x ON x.id_feed=f.id ' - . 'SET f.cache_nbEntries=x.nbEntries, f.cache_nbUnreads=x.nbUnreads'; + . 'SET f.`cache_nbEntries`=x.nbEntries, f.`cache_nbUnreads`=x.nbUnreads'; $stm = $this->bd->prepare($sql); if ($stm && $stm->execute()) { @@ -308,7 +308,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { $affected = $stm->rowCount(); $sql = 'UPDATE `' . $this->prefix . 'feed` ' - . 'SET cache_nbEntries=0, cache_nbUnreads=0 WHERE id=?'; + . 'SET `cache_nbEntries`=0, `cache_nbUnreads`=0 WHERE id=?'; $values = array($id); $stm = $this->bd->prepare($sql); if (!($stm && $stm->execute($values))) { @@ -326,7 +326,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { $sql = 'DELETE FROM `' . $this->prefix . 'entry` ' . 'WHERE id_feed=:id_feed AND id<=:id_max ' . 'AND is_favorite=0 ' //Do not remove favourites - . 'AND lastSeen < (SELECT maxLastSeen FROM (SELECT (MAX(e3.lastSeen)-99) AS maxLastSeen FROM `' . $this->prefix . 'entry` e3 WHERE e3.id_feed=:id_feed) recent) ' //Do not remove the most newly seen articles, plus a few seconds of tolerance + . 'AND `lastSeen` < (SELECT maxLastSeen FROM (SELECT (MAX(e3.`lastSeen`)-99) AS maxLastSeen FROM `' . $this->prefix . 'entry` e3 WHERE e3.id_feed=:id_feed) recent) ' //Do not remove the most newly seen articles, plus a few seconds of tolerance . 'AND id NOT IN (SELECT id FROM (SELECT e2.id FROM `' . $this->prefix . 'entry` e2 WHERE e2.id_feed=:id_feed ORDER BY id DESC LIMIT :keep) keep)'; //Double select: MySQL doesn't support 'LIMIT & IN/ALL/ANY/SOME subquery' $stm = $this->bd->prepare($sql); diff --git a/app/Models/FeedDAOSQLite.php b/app/Models/FeedDAOSQLite.php index 7599fda53..440ae74da 100644 --- a/app/Models/FeedDAOSQLite.php +++ b/app/Models/FeedDAOSQLite.php @@ -4,8 +4,8 @@ class FreshRSS_FeedDAOSQLite extends FreshRSS_FeedDAO { public function updateCachedValues() { //For one single feed, call updateLastUpdate($id) $sql = 'UPDATE `' . $this->prefix . 'feed` ' - . 'SET cache_nbEntries=(SELECT COUNT(e1.id) FROM `' . $this->prefix . 'entry` e1 WHERE e1.id_feed=`' . $this->prefix . 'feed`.id),' - . 'cache_nbUnreads=(SELECT COUNT(e2.id) FROM `' . $this->prefix . 'entry` e2 WHERE e2.id_feed=`' . $this->prefix . 'feed`.id AND e2.is_read=0)'; + . 'SET `cache_nbEntries`=(SELECT COUNT(e1.id) FROM `' . $this->prefix . 'entry` e1 WHERE e1.id_feed=`' . $this->prefix . 'feed`.id),' + . '`cache_nbUnreads`=(SELECT COUNT(e2.id) FROM `' . $this->prefix . 'entry` e2 WHERE e2.id_feed=`' . $this->prefix . 'feed`.id AND e2.is_read=0)'; $stm = $this->bd->prepare($sql); if ($stm && $stm->execute()) { return $stm->rowCount(); diff --git a/lib/Minz/ModelPdo.php b/lib/Minz/ModelPdo.php index 93a22fc3d..78b44ea7f 100644 --- a/lib/Minz/ModelPdo.php +++ b/lib/Minz/ModelPdo.php @@ -156,9 +156,6 @@ class MinzPDOMSQLite extends MinzPDO { class MinzPDOPGSQL extends MinzPDO { protected function compatibility($statement) { - return str_replace( - array('`', 'lastUpdate', 'pathEntries', 'httpAuth', 'cache_nbEntries', 'cache_nbUnreads', 'lastSeen'), - array('"', '"lastUpdate"', '"pathEntries"', '"httpAuth"', '"cache_nbEntries"', '"cache_nbUnreads"', '"lastSeen"'), - $statement); + return str_replace('`', '"', $statement); } } -- cgit v1.2.3 From 0564f5e1c4d85d34ec14a493dfa529fb724878d1 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Mon, 5 Sep 2016 21:09:15 +0200 Subject: Support custom MySQL ports https://github.com/FreshRSS/FreshRSS/issues/1241 --- app/install.php | 2 +- lib/Minz/ModelPdo.php | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) (limited to 'lib/Minz/ModelPdo.php') diff --git a/app/install.php b/app/install.php index 62695ceb6..dad3535dc 100644 --- a/app/install.php +++ b/app/install.php @@ -716,7 +716,7 @@ function printStep3() {

- +
diff --git a/lib/Minz/ModelPdo.php b/lib/Minz/ModelPdo.php index 845aecaae..f82045df9 100644 --- a/lib/Minz/ModelPdo.php +++ b/lib/Minz/ModelPdo.php @@ -53,13 +53,17 @@ class Minz_ModelPdo { self::$sharedCurrentUser = $currentUser; $driver_options = isset($conf->db['pdo_options']) && is_array($conf->db['pdo_options']) ? $conf->db['pdo_options'] : array(); + $dbServer = parse_url('db://' . $db['host']); try { $type = $db['type']; if ($type === 'mysql') { - $string = 'mysql:host=' . $db['host'] + $string = 'mysql:host=' . $dbServer['host'] . ';dbname=' . $db['base'] . ';charset=utf8mb4'; + if (!empty($dbServer['port'])) { + $string .= ';port=' . $dbServer['port']; + } $driver_options[PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET NAMES utf8mb4'; $this->prefix = $db['prefix'] . $currentUser . '_'; } elseif ($type === 'sqlite') { -- cgit v1.2.3 From 272afc0b7eef5f1873fcc3763e114a24bab9bef6 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sat, 1 Oct 2016 09:42:23 +0200 Subject: PostgreSQL case-insensitive SQL LIKE Compatibility with MySQL and SQLite --- lib/Minz/ModelPdo.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/Minz/ModelPdo.php') diff --git a/lib/Minz/ModelPdo.php b/lib/Minz/ModelPdo.php index 5bc18bf1f..da28909df 100644 --- a/lib/Minz/ModelPdo.php +++ b/lib/Minz/ModelPdo.php @@ -162,6 +162,6 @@ class MinzPDOMSQLite extends MinzPDO { class MinzPDOPGSQL extends MinzPDO { protected function compatibility($statement) { - return str_replace('`', '"', $statement); + return str_replace(array('`', ' LIKE '), array('"', ' ILIKE '), $statement); } } -- cgit v1.2.3 From 1b8eb6c7e732f1eda4fc8f22e847b363b016f857 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sun, 23 Oct 2016 01:46:14 +0200 Subject: CLI import ZIP/OPML/JSON for user https://github.com/FreshRSS/FreshRSS/issues/1095 https://github.com/FreshRSS/FreshRSS/issues/851 --- CHANGELOG.md | 2 +- README.fr.md | 2 +- README.md | 2 +- app/Controllers/categoryController.php | 5 +- app/Controllers/feedController.php | 6 +- app/Controllers/importExportController.php | 273 ++++++++++++++++++----------- app/Exceptions/ZipException.php | 14 ++ app/Exceptions/ZipMissingException.php | 4 + app/Models/CategoryDAO.php | 11 +- app/i18n/cz/feedback.php | 6 +- app/i18n/cz/sub.php | 4 +- app/i18n/de/feedback.php | 6 +- app/i18n/de/sub.php | 2 +- app/i18n/en/feedback.php | 6 +- app/i18n/en/sub.php | 4 +- app/i18n/fr/feedback.php | 6 +- app/i18n/fr/sub.php | 4 +- app/i18n/it/feedback.php | 6 +- app/i18n/it/sub.php | 4 +- app/i18n/nl/feedback.php | 6 +- app/i18n/nl/sub.php | 4 +- app/i18n/ru/feedback.php | 6 +- app/i18n/ru/sub.php | 4 +- app/i18n/tr/feedback.php | 6 +- app/i18n/tr/sub.php | 4 +- cli/_cli.php | 5 + cli/create-user.php | 17 +- cli/delete-user.php | 3 +- cli/import-for-user.php | 36 ++++ cli/list-users.php | 4 +- lib/Minz/ModelPdo.php | 13 +- 31 files changed, 296 insertions(+), 179 deletions(-) create mode 100644 app/Exceptions/ZipException.php create mode 100644 app/Exceptions/ZipMissingException.php create mode 100644 cli/import-for-user.php (limited to 'lib/Minz/ModelPdo.php') diff --git a/CHANGELOG.md b/CHANGELOG.md index c35b3b578..d24ec0739 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -390,7 +390,7 @@ * Possibility to combine search filters, e.g. `date:2014-05 intitle:FreshRSS intitle:Open great reader #Internet` * Change nav menu with more buttons instead of dropdown menus and add some filters * New system of import / export - * Support OPML, Json (like Google Reader) and Zip archives + * Support OPML, Json (like Google Reader) and ZIP archives * Can export and import articles (specific option for favorites) * Refactor "Origine" theme * Some improvements diff --git a/README.fr.md b/README.fr.md index 3fcedd337..2a2da256d 100644 --- a/README.fr.md +++ b/README.fr.md @@ -34,7 +34,7 @@ Nous sommes une communauté amicale. * Serveur Web Apache2 (recommandé), ou nginx, lighttpd (non testé sur les autres) * PHP 5.3.3+ (PHP 5.4+ recommandé, et PHP 5.5+ pour les performances, et PHP 7+ pour d’encore meilleures performances) * Requis : [DOM](http://php.net/dom), [XML](http://php.net/xml), [PDO_MySQL](http://php.net/pdo-mysql) ou [PDO_SQLite](http://php.net/pdo-sqlite) ou [PDO_PGSQL](http://php.net/pdo-pgsql), [cURL](http://php.net/curl) - * Recommandés : [JSON](http://php.net/json), [GMP](http://php.net/gmp) (pour accès API sur plateformes < 64 bits), [IDN](http://php.net/intl.idn) (pour les noms de domaines internationalisés), [mbstring](http://php.net/mbstring) et/ou [iconv](http://php.net/iconv) (pour conversion d’encodages), [Zip](http://php.net/zip) (pour import/export), [zlib](http://php.net/zlib) (pour les flux compressés) + * Recommandés : [JSON](http://php.net/json), [GMP](http://php.net/gmp) (pour accès API sur plateformes < 64 bits), [IDN](http://php.net/intl.idn) (pour les noms de domaines internationalisés), [mbstring](http://php.net/mbstring) et/ou [iconv](http://php.net/iconv) (pour conversion d’encodages), [ZIP](http://php.net/zip) (pour import/export), [zlib](http://php.net/zlib) (pour les flux compressés) * MySQL 5.5.3+ (recommandé), ou SQLite 3.7.4+, ou PostgreSQL (experimental) * Un navigateur Web récent tel Firefox, Internet Explorer 11 / Edge, Chrome, Opera, Safari. * Fonctionne aussi sur mobile diff --git a/README.md b/README.md index 5c8f586fa..a541d6dea 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ We are a friendly community. * A web server: Apache2 (recommended), nginx, lighttpd (not tested on others) * PHP 5.3.3+ (PHP 5.4+ recommended, and PHP 5.5+ for performance, and PHP 7 for even higher performance) * Required extensions: [DOM](http://php.net/dom), [XML](http://php.net/xml), [PDO_MySQL](http://php.net/pdo-mysql) or [PDO_SQLite](http://php.net/pdo-sqlite) or [PDO_PGSQL](http://php.net/pdo-pgsql), [cURL](http://php.net/curl) - * Recommended extensions: [JSON](http://php.net/json), [GMP](http://php.net/gmp) (for API access on platforms < 64 bits), [IDN](http://php.net/intl.idn) (for Internationalized Domain Names), [mbstring](http://php.net/mbstring) and/or [iconv](http://php.net/iconv) (for charset conversion), [Zip](http://php.net/zip) (for import/export), [zlib](http://php.net/zlib) (for compressed feeds) + * Recommended extensions: [JSON](http://php.net/json), [GMP](http://php.net/gmp) (for API access on platforms < 64 bits), [IDN](http://php.net/intl.idn) (for Internationalized Domain Names), [mbstring](http://php.net/mbstring) and/or [iconv](http://php.net/iconv) (for charset conversion), [ZIP](http://php.net/zip) (for import/export), [zlib](http://php.net/zlib) (for compressed feeds) * MySQL 5.5.3+ (recommended), or SQLite 3.7.4+, or PostgreSQL (experimental) * A recent browser like Firefox, Internet Explorer 11 / Edge, Chrome, Opera, Safari. * Works on mobile diff --git a/app/Controllers/categoryController.php b/app/Controllers/categoryController.php index e65c146de..922f92844 100644 --- a/app/Controllers/categoryController.php +++ b/app/Controllers/categoryController.php @@ -117,7 +117,6 @@ class FreshRSS_category_Controller extends Minz_ActionController { public function deleteAction() { $feedDAO = FreshRSS_Factory::createFeedDao(); $catDAO = new FreshRSS_CategoryDAO(); - $default_category = $catDAO->getDefault(); $url_redirect = array('c' => 'subscription', 'a' => 'index'); if (Minz_Request::isPost()) { @@ -128,11 +127,11 @@ class FreshRSS_category_Controller extends Minz_ActionController { Minz_Request::bad(_t('feedback.sub.category.no_id'), $url_redirect); } - if ($id === $default_category->id()) { + if ($id === FreshRSS_CategoryDAO::defaultCategoryId) { Minz_Request::bad(_t('feedback.sub.category.not_delete_default'), $url_redirect); } - if ($feedDAO->changeCategory($id, $default_category->id()) === false) { + if ($feedDAO->changeCategory($id, FreshRSS_CategoryDAO::defaultCategoryId) === false) { Minz_Request::bad(_t('feedback.sub.category.error'), $url_redirect); } diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php index ed3229687..c4115584a 100755 --- a/app/Controllers/feedController.php +++ b/app/Controllers/feedController.php @@ -40,9 +40,8 @@ class FreshRSS_feed_Controller extends Minz_ActionController { } if ($cat == null) { $catDAO->checkDefault(); - $cat = $catDAO->getDefault(); } - $cat_id = $cat->id(); + $cat_id = $cat == null ? FreshRSS_CategoryDAO::defaultCategoryId : $cat->id(); $feed = new FreshRSS_Feed($url); //Throws FreshRSS_BadUrl_Exception $feed->_httpAuth($http_auth); @@ -504,8 +503,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController { } if ($cat_id <= 1) { $catDAO->checkDefault(); - $cat = $catDAO->getDefault(); - $cat_id = $cat->id(); + $cat_id = FreshRSS_CategoryDAO::defaultCategoryId; } $feedDAO = FreshRSS_Factory::createFeedDao(); diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php index a1f789805..fbebb2a78 100644 --- a/app/Controllers/importExportController.php +++ b/app/Controllers/importExportController.php @@ -29,32 +29,14 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { Minz_View::prependTitle(_t('sub.import_export.title') . ' · '); } - /** - * This action handles import action. - * - * It must be reached by a POST request. - * - * Parameter is: - * - file (default: nothing!) - * Available file types are: zip, json or xml. - */ - public function importAction() { - if (!Minz_Request::isPost()) { - Minz_Request::forward(array('c' => 'importExport', 'a' => 'index'), true); - } - - $file = $_FILES['file']; - $status_file = $file['error']; - - if ($status_file !== 0) { - Minz_Log::warning('File cannot be uploaded. Error code: ' . $status_file); - Minz_Request::bad(_t('feedback.import_export.file_cannot_be_uploaded'), - array('c' => 'importExport', 'a' => 'index')); - } + public function importFile($name, $path, $username = null) { + require_once(LIB_PATH . '/lib_opml.php'); - @set_time_limit(300); + $this->catDAO = new FreshRSS_CategoryDAO($username); + $this->entryDAO = FreshRSS_Factory::createEntryDao($username); + $this->feedDAO = FreshRSS_Factory::createFeedDao($username); - $type_file = $this->guessFileType($file['name']); + $type_file = self::guessFileType($name); $list_files = array( 'opml' => array(), @@ -65,21 +47,17 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { // We try to list all files according to their type $list = array(); if ($type_file === 'zip' && extension_loaded('zip')) { - $zip = zip_open($file['tmp_name']); - + $zip = zip_open($path); if (!is_resource($zip)) { // zip_open cannot open file: something is wrong - Minz_Log::warning('Zip archive cannot be imported. Error code: ' . $zip); - Minz_Request::bad(_t('feedback.import_export.zip_error'), - array('c' => 'importExport', 'a' => 'index')); + throw new FreshRSS_Zip_Exception($zip); } - while (($zipfile = zip_read($zip)) !== false) { if (!is_resource($zipfile)) { // zip_entry() can also return an error code! - Minz_Log::warning('Zip file cannot be imported. Error code: ' . $zipfile); + throw new FreshRSS_Zip_Exception($zipfile); } else { - $type_zipfile = $this->guessFileType(zip_entry_name($zipfile)); + $type_zipfile = self::guessFileType(zip_entry_name($zipfile)); if ($type_file !== 'unknown') { $list_files[$type_zipfile][] = zip_entry_read( $zipfile, @@ -88,29 +66,82 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { } } } - zip_close($zip); } elseif ($type_file === 'zip') { - // Zip extension is not loaded - Minz_Request::bad(_t('feedback.import_export.no_zip_extension'), - array('c' => 'importExport', 'a' => 'index')); + // ZIP extension is not loaded + throw new FreshRSS_ZipMissing_Exception(); } elseif ($type_file !== 'unknown') { - $list_files[$type_file][] = file_get_contents($file['tmp_name']); + $list_files[$type_file][] = file_get_contents($path); } // Import file contents. // OPML first(so categories and feeds are imported) // Starred articles then so the "favourite" status is already set // And finally all other files. - $error = false; + $ok = true; foreach ($list_files['opml'] as $opml_file) { - $error = $this->importOpml($opml_file); + if (!$this->importOpml($opml_file)) { + $ok = false; + if (FreshRSS_Context::$isCli) { + fwrite(STDERR, 'FreshRSS error during OPML import' . "\n"); + } + } } foreach ($list_files['json_starred'] as $article_file) { - $error = $this->importJson($article_file, true); + if (!$this->importJson($article_file, true)) { + $ok = false; + if (FreshRSS_Context::$isCli) { + fwrite(STDERR, 'FreshRSS error during JSON stars import' . "\n"); + } + } } foreach ($list_files['json_feed'] as $article_file) { - $error = $this->importJson($article_file); + if (!$this->importJson($article_file)) { + $ok = false; + if (FreshRSS_Context::$isCli) { + fwrite(STDERR, 'FreshRSS error during JSON feeds import' . "\n"); + } + } + } + + return $ok; + } + + /** + * This action handles import action. + * + * It must be reached by a POST request. + * + * Parameter is: + * - file (default: nothing!) + * Available file types are: zip, json or xml. + */ + public function importAction() { + if (!Minz_Request::isPost()) { + Minz_Request::forward(array('c' => 'importExport', 'a' => 'index'), true); + } + + $file = $_FILES['file']; + $status_file = $file['error']; + + if ($status_file !== 0) { + Minz_Log::warning('File cannot be uploaded. Error code: ' . $status_file); + Minz_Request::bad(_t('feedback.import_export.file_cannot_be_uploaded'), + array('c' => 'importExport', 'a' => 'index')); + } + + @set_time_limit(300); + + $error = false; + try { + $error = !$this->importFile($file['name'], $file['tmp_name']); + } catch (FreshRSS_ZipMissing_Exception $zme) { + Minz_Request::bad(_t('feedback.import_export.no_zip_extension'), + array('c' => 'importExport', 'a' => 'index')); + } catch (FreshRSS_Zip_Exception $ze) { + Minz_Log::warning('ZIP archive cannot be imported. Error code: ' . $ze->zipErrorCode()); + Minz_Request::bad(_t('feedback.import_export.zip_error'), + array('c' => 'importExport', 'a' => 'index')); } // And finally, we get import status and redirect to the home page @@ -126,7 +157,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { * Itis a *very* basic guess file type function. Only based on filename. * That's could be improved but should be enough for what we have to do. */ - private function guessFileType($filename) { + private static function guessFileType($filename) { if (substr_compare($filename, '.zip', -4) === 0) { return 'zip'; } elseif (substr_compare($filename, '.opml', -5) === 0 || @@ -146,15 +177,19 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { * This method parses and imports an OPML file. * * @param string $opml_file the OPML file content. - * @return boolean true if an error occured, false else. + * @return boolean false if an error occured, true otherwise. */ private function importOpml($opml_file) { $opml_array = array(); try { $opml_array = libopml_parse_string($opml_file, false); } catch (LibOPML_Exception $e) { - Minz_Log::warning($e->getMessage()); - return true; + if (FreshRSS_Context::$isCli) { + fwrite(STDERR, 'FreshRSS error during OPML parsing: ' . $e->getMessage() . "\n"); + } else { + Minz_Log::warning($e->getMessage()); + } + return false; } $this->catDAO->checkDefault(); @@ -167,51 +202,53 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { * * @param array $opml_elements an OPML element (body or outline). * @param string $parent_cat the name of the parent category. - * @return boolean true if an error occured, false else. + * @return boolean false if an error occured, true otherwise. */ private function addOpmlElements($opml_elements, $parent_cat = null) { - $error = false; + $ok = true; $nb_feeds = count($this->feedDAO->listFeeds()); $nb_cats = count($this->catDAO->listCategories(false)); $limits = FreshRSS_Context::$system_conf->limits; foreach ($opml_elements as $elt) { - $is_error = false; if (isset($elt['xmlUrl'])) { // If xmlUrl exists, it means it is a feed - if ($nb_feeds >= $limits['max_feeds']) { - Minz_Log::warning(_t('feedback.sub.feed.over_max', - $limits['max_feeds'])); - $is_error = true; - continue; + if (!FreshRSS_Context::$isCli) { + if ($nb_feeds >= $limits['max_feeds']) { + Minz_Log::warning(_t('feedback.sub.feed.over_max', + $limits['max_feeds'])); + $ok = false; + continue; + } } - $is_error = $this->addFeedOpml($elt, $parent_cat); - if (!$is_error) { - $nb_feeds += 1; + if ($this->addFeedOpml($elt, $parent_cat)) { + $nb_feeds++; + } else { + $ok = false; } } else { // No xmlUrl? It should be a category! $limit_reached = ($nb_cats >= $limits['max_categories']); - if ($limit_reached) { - Minz_Log::warning(_t('feedback.sub.category.over_max', - $limits['max_categories'])); + if (!FreshRSS_Context::$isCli) { + if ($limit_reached) { + Minz_Log::warning(_t('feedback.sub.category.over_max', + $limits['max_categories'])); + } + $ok = false; + continue; } - $is_error = $this->addCategoryOpml($elt, $parent_cat, $limit_reached); - if (!$is_error) { - $nb_cats += 1; + if ($this->addCategoryOpml($elt, $parent_cat, $limit_reached)) { + $nb_cats++; + } else { + $ok = false; } } - - if (!$error && $is_error) { - // oops: there is at least one error! - $error = $is_error; - } } - return $error; + return $ok; } /** @@ -219,21 +256,23 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { * * @param array $feed_elt an OPML element (must be a feed element). * @param string $parent_cat the name of the parent category. - * @return boolean true if an error occured, false else. + * @return boolean false if an error occured, true otherwise. */ private function addFeedOpml($feed_elt, $parent_cat) { - $default_cat = $this->catDAO->getDefault(); - if (is_null($parent_cat)) { + if ($parent_cat == null) { // This feed has no parent category so we get the default one + $this->catDAO->checkDefault(); + $default_cat = $this->catDAO->getDefault(); $parent_cat = $default_cat->name(); } $cat = $this->catDAO->searchByName($parent_cat); - if (is_null($cat)) { + if ($cat == null) { // If there is not $cat, it means parent category does not exist in // database. // If it happens, take the default category. - $cat = $default_cat; + $this->catDAO->checkDefault(); + $cat = $this->catDAO->getDefault(); } // We get different useful information @@ -259,7 +298,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { // Call the extension hook $feed = Minz_ExtensionManager::callHook('feed_before_insert', $feed); - if (!is_null($feed)) { + if ($feed != null) { // addFeedObject checks if feed is already in DB so nothing else to // check here $id = $this->feedDAO->addFeedObject($feed); @@ -268,11 +307,23 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $error = true; } } catch (FreshRSS_Feed_Exception $e) { - Minz_Log::warning($e->getMessage()); + if (FreshRSS_Context::$isCli) { + fwrite(STDERR, 'FreshRSS error during OPML feed import: ' . $e->getMessage() . "\n"); + } else { + Minz_Log::warning($e->getMessage()); + } $error = true; } - return $error; + if ($error) { + if (FreshRSS_Context::$isCli) { + fwrite(STDERR, 'FreshRSS error during OPML feed import from URL: ' . $url . ' in category ' . $cat->id() . "\n"); + } else { + Minz_Log::warning('Error during OPML feed import from URL: ' . $url . ' in category ' . $cat->id()); + } + } + + return !$error; } /** @@ -282,29 +333,34 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { * @param string $parent_cat the name of the parent category. * @param boolean $cat_limit_reached indicates if category limit has been reached. * if yes, category is not added (but we try for feeds!) - * @return boolean true if an error occured, false else. + * @return boolean false if an error occured, true otherwise. */ private function addCategoryOpml($cat_elt, $parent_cat, $cat_limit_reached) { // Create a new Category object - $cat = new FreshRSS_Category(Minz_Helper::htmlspecialchars_utf8($cat_elt['text'])); + $catName = Minz_Helper::htmlspecialchars_utf8($cat_elt['text']); + $cat = new FreshRSS_Category($catName); $error = true; - if (!$cat_limit_reached) { + if (FreshRSS_Context::$isCli || !$cat_limit_reached) { $id = $this->catDAO->addCategoryObject($cat); $error = ($id === false); } + if ($error) { + if (FreshRSS_Context::$isCli) { + fwrite(STDERR, 'FreshRSS error during OPML category import from URL: ' . $catName . "\n"); + } else { + Minz_Log::warning('Error during OPML category import from URL: ' . $catName); + } + } if (isset($cat_elt['@outlines'])) { // Our cat_elt contains more categories or more feeds, so we // add them recursively. // Note: FreshRSS does not support yet category arborescence - $res = $this->addOpmlElements($cat_elt['@outlines'], $cat->name()); - if (!$error && $res) { - $error = true; - } + $error &= !$this->addOpmlElements($cat_elt['@outlines'], $catName); } - return $error; + return !$error; } /** @@ -312,13 +368,17 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { * * @param string $article_file the JSON file content. * @param boolean $starred true if articles from the file must be starred. - * @return boolean true if an error occured, false else. + * @return boolean false if an error occured, true otherwise. */ private function importJson($article_file, $starred = false) { $article_object = json_decode($article_file, true); - if (is_null($article_object)) { - Minz_Log::warning('Try to import a non-JSON file'); - return true; + if ($article_object == null) { + if (FreshRSS_Context::$isCli) { + fwrite(STDERR, 'FreshRSS error trying to import a non-JSON file' . "\n"); + } else { + Minz_Log::warning('Try to import a non-JSON file'); + } + return false; } $is_read = FreshRSS_Context::$user_conf->mark_when['reception'] ? 1 : 0; @@ -337,25 +397,24 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $feed = new FreshRSS_Feed($item['origin'][$key]); $feed = $this->feedDAO->searchByUrl($feed->url()); - if (is_null($feed)) { + if ($feed == null) { // Feed does not exist in DB,we should to try to add it. - if ($nb_feeds >= $limits['max_feeds']) { + if ((!FreshRSS_Context::$isCli) && ($nb_feeds >= $limits['max_feeds'])) { // Oops, no more place! Minz_Log::warning(_t('feedback.sub.feed.over_max', $limits['max_feeds'])); } else { $feed = $this->addFeedJson($item['origin'], $google_compliant); } - if (is_null($feed)) { + if ($feed == null) { // Still null? It means something went wrong. $error = true; } else { - // Nice! Increase the counter. - $nb_feeds += 1; + $nb_feeds++; } } - if (!is_null($feed)) { + if ($feed != null) { $article_to_feed[$item['id']] = $feed->id(); } } @@ -384,7 +443,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { if ($google_compliant) { // Remove tags containing "/state/com.google" which are useless. $tags = array_filter($tags, function($var) { - return strpos($var, '/state/com.google') === false; + return strpos($var, '/state/com.google') !== false; }); } @@ -397,7 +456,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $entry->_tags($tags); $entry = Minz_ExtensionManager::callHook('entry_before_insert', $entry); - if (is_null($entry)) { + if ($entry == null) { // An extension has returned a null value, there is nothing to insert. continue; } @@ -415,7 +474,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { } $this->entryDAO->commit(); - return $error; + return !$error; } /** @@ -427,8 +486,6 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { * else null. */ private function addFeedJson($origin, $google_compliant) { - $default_cat = $this->catDAO->getDefault(); - $return = null; $key = $google_compliant ? 'htmlUrl' : 'feedUrl'; $url = $origin[$key]; @@ -438,13 +495,13 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { try { // Create a Feed object and add it in database. $feed = new FreshRSS_Feed($url); - $feed->_category($default_cat->id()); + $feed->_category(FreshRSS_CategoryDAO::defaultCategoryId); $feed->_name($name); $feed->_website($website); // Call the extension hook $feed = Minz_ExtensionManager::callHook('feed_before_insert', $feed); - if (!is_null($feed)) { + if ($feed != null) { // addFeedObject checks if feed is already in DB so nothing else to // check here. $id = $this->feedDAO->addFeedObject($feed); @@ -455,7 +512,11 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { } } } catch (FreshRSS_Feed_Exception $e) { - Minz_Log::warning($e->getMessage()); + if (FreshRSS_Context::$isCli) { + fwrite(STDERR, 'FreshRSS error during JSON feed import: ' . $e->getMessage() . "\n"); + } else { + Minz_Log::warning($e->getMessage()); + } } return $return; @@ -503,18 +564,18 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $nb_files = count($export_files); if ($nb_files > 1) { - // If there are more than 1 file to export, we need a zip archive. + // If there are more than 1 file to export, we need a ZIP archive. try { $this->exportZip($export_files); } catch (Exception $e) { - # Oops, there is no Zip extension! + # Oops, there is no ZIP extension! Minz_Request::bad(_t('feedback.import_export.export_no_zip_extension'), array('c' => 'importExport', 'a' => 'index')); } } elseif ($nb_files === 1) { // Only one file? Guess its type and export it. $filename = key($export_files); - $type = $this->guessFileType($filename); + $type = self::guessFileType($filename); $this->exportFile('freshrss_' . $filename, $export_files[$filename], $type); } else { // Nothing to do... @@ -555,7 +616,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $this->view->entries = $this->entryDAO->listWhere( 's', '', FreshRSS_Entry::STATE_ALL, 'ASC', $unread_fav['all'] ); - } elseif ($type == 'feed' && !is_null($feed)) { + } elseif ($type === 'feed' && $feed != null) { $this->view->list_title = _t('sub.import_export.feed_list', $feed->name()); $this->view->type = 'feed/' . $feed->id(); $this->view->entries = $this->entryDAO->listWhere( diff --git a/app/Exceptions/ZipException.php b/app/Exceptions/ZipException.php new file mode 100644 index 000000000..8441daedf --- /dev/null +++ b/app/Exceptions/ZipException.php @@ -0,0 +1,14 @@ +zipErrorCode = $zipErrorCode; + } + + public function zipErrorCode() { + return $this->zipErrorCode; + } +} diff --git a/app/Exceptions/ZipMissingException.php b/app/Exceptions/ZipMissingException.php new file mode 100644 index 000000000..864cc3991 --- /dev/null +++ b/app/Exceptions/ZipMissingException.php @@ -0,0 +1,4 @@ +prefix . 'category`(name) VALUES(?)'; $stm = $this->bd->prepare($sql); @@ -50,7 +53,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable } public function deleteCategory($id) { - if ($id <= 1) { + if ($id <= self::defaultCategoryId) { return false; } $sql = 'DELETE FROM `' . $this->prefix . 'category` WHERE id=?'; @@ -120,7 +123,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable } public function getDefault() { - $sql = 'SELECT * FROM `' . $this->prefix . 'category` WHERE id=1'; + $sql = 'SELECT * FROM `' . $this->prefix . 'category` WHERE id=' . self::defaultCategoryId; $stm = $this->bd->prepare($sql); $stm->execute(); @@ -134,11 +137,11 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable } } public function checkDefault() { - $def_cat = $this->searchById(1); + $def_cat = $this->searchById(self::defaultCategoryId); if ($def_cat == null) { $cat = new FreshRSS_Category(_t('gen.short.default_category')); - $cat->_id(1); + $cat->_id(self::defaultCategoryId); $values = array( 'id' => $cat->id(), diff --git a/app/i18n/cz/feedback.php b/app/i18n/cz/feedback.php index 81302afca..f2bd87c77 100644 --- a/app/i18n/cz/feedback.php +++ b/app/i18n/cz/feedback.php @@ -43,12 +43,12 @@ return array( 'not_found' => '%s neexistuje', ), 'import_export' => array( - 'export_no_zip_extension' => 'Na serveru není naistalována podpora zip. Zkuste prosím exportovat soubory jeden po druhém.', + 'export_no_zip_extension' => 'Na serveru není naistalována podpora ZIP. Zkuste prosím exportovat soubory jeden po druhém.', 'feeds_imported' => 'Vaše kanály byly naimportovány a nyní budou aktualizovány', 'feeds_imported_with_errors' => 'Vaše kanály byly naimportovány, došlo ale k nějakým chybám', 'file_cannot_be_uploaded' => 'Soubor nelze nahrát!', - 'no_zip_extension' => 'Na serveru není naistalována podpora zip.', - 'zip_error' => 'Během importu zip souboru došlo k chybě.', + 'no_zip_extension' => 'Na serveru není naistalována podpora ZIP.', + 'zip_error' => 'Během importu ZIP souboru došlo k chybě.', ), 'sub' => array( 'actualize' => 'Aktualizovat', diff --git a/app/i18n/cz/sub.php b/app/i18n/cz/sub.php index cea0541e3..274cf16e1 100644 --- a/app/i18n/cz/sub.php +++ b/app/i18n/cz/sub.php @@ -44,8 +44,8 @@ return array( 'export_opml' => 'Exportovat seznam kanálů (OPML)', 'export_starred' => 'Exportovat oblíbené', 'feed_list' => 'Seznam %s článků', - 'file_to_import' => 'Soubor k importu
(OPML, Json nebo Zip)', - 'file_to_import_no_zip' => 'Soubor k importu
(OPML nebo Json)', + 'file_to_import' => 'Soubor k importu
(OPML, JSON nebo ZIP)', + 'file_to_import_no_zip' => 'Soubor k importu
(OPML nebo JSON)', 'import' => 'Import', 'starred_list' => 'Seznam oblíbených článků', 'title' => 'Import / export', diff --git a/app/i18n/de/feedback.php b/app/i18n/de/feedback.php index f93992982..195083b36 100644 --- a/app/i18n/de/feedback.php +++ b/app/i18n/de/feedback.php @@ -43,12 +43,12 @@ return array( 'not_found' => '%s existiert nicht', ), 'import_export' => array( - 'export_no_zip_extension' => 'Die Zip-Erweiterung fehlt auf Ihrem Server. Bitte versuchen Sie die Dateien eine nach der anderen zu exportieren.', + 'export_no_zip_extension' => 'Die ZIP-Erweiterung fehlt auf Ihrem Server. Bitte versuchen Sie die Dateien eine nach der anderen zu exportieren.', 'feeds_imported' => 'Ihre Feeds sind importiert worden und werden jetzt aktualisiert', 'feeds_imported_with_errors' => 'Ihre Feeds sind importiert worden, aber es traten einige Fehler auf', 'file_cannot_be_uploaded' => 'Die Datei kann nicht hochgeladen werden!', - 'no_zip_extension' => 'Die Zip-Erweiterung ist auf Ihrem Server nicht vorhanden.', - 'zip_error' => 'Ein Fehler trat während des Zip-Imports auf.', + 'no_zip_extension' => 'Die ZIP-Erweiterung ist auf Ihrem Server nicht vorhanden.', + 'zip_error' => 'Ein Fehler trat während des ZIP-Imports auf.', ), 'sub' => array( 'actualize' => 'Aktualisieren', diff --git a/app/i18n/de/sub.php b/app/i18n/de/sub.php index 0f05a5635..cc98fd25c 100644 --- a/app/i18n/de/sub.php +++ b/app/i18n/de/sub.php @@ -44,7 +44,7 @@ return array( 'export_opml' => 'Liste der Feeds exportieren (OPML)', 'export_starred' => 'Ihre Favoriten exportieren', 'feed_list' => 'Liste von %s Artikeln', - 'file_to_import' => 'Zu importierende Datei
(OPML, JSON oder Zip)', + 'file_to_import' => 'Zu importierende Datei
(OPML, JSON oder ZIP)', 'file_to_import_no_zip' => 'Zu importierende Datei
(OPML oder JSON)', 'import' => 'Importieren', 'starred_list' => 'Liste der Lieblingsartikel', diff --git a/app/i18n/en/feedback.php b/app/i18n/en/feedback.php index 7ce2ae9cf..e7f6b9f85 100644 --- a/app/i18n/en/feedback.php +++ b/app/i18n/en/feedback.php @@ -43,12 +43,12 @@ return array( 'not_found' => '%s does not exist', ), 'import_export' => array( - 'export_no_zip_extension' => 'Zip extension is not present on your server. Please try to export files one by one.', + 'export_no_zip_extension' => 'ZIP extension is not present on your server. Please try to export files one by one.', 'feeds_imported' => 'Your feeds have been imported and will now be updated', 'feeds_imported_with_errors' => 'Your feeds have been imported but some errors occurred', 'file_cannot_be_uploaded' => 'File cannot be uploaded!', - 'no_zip_extension' => 'Zip extension is not present on your server.', - 'zip_error' => 'An error occured during Zip import.', + 'no_zip_extension' => 'ZIP extension is not present on your server.', + 'zip_error' => 'An error occured during ZIP import.', ), 'sub' => array( 'actualize' => 'Actualise', diff --git a/app/i18n/en/sub.php b/app/i18n/en/sub.php index aaaa02827..789433ee6 100644 --- a/app/i18n/en/sub.php +++ b/app/i18n/en/sub.php @@ -44,8 +44,8 @@ return array( 'export_opml' => 'Export list of feeds (OPML)', 'export_starred' => 'Export your favourites', 'feed_list' => 'List of %s articles', - 'file_to_import' => 'File to import
(OPML, Json or Zip)', - 'file_to_import_no_zip' => 'File to import
(OPML or Json)', + 'file_to_import' => 'File to import
(OPML, JSON or ZIP)', + 'file_to_import_no_zip' => 'File to import
(OPML or JSON)', 'import' => 'Import', 'starred_list' => 'List of favourite articles', 'title' => 'Import / export', diff --git a/app/i18n/fr/feedback.php b/app/i18n/fr/feedback.php index 15f3ab859..5966fc3a7 100644 --- a/app/i18n/fr/feedback.php +++ b/app/i18n/fr/feedback.php @@ -43,12 +43,12 @@ return array( 'not_found' => '%s n’existe pas', ), 'import_export' => array( - 'export_no_zip_extension' => 'L’extension Zip n’est pas présente sur votre serveur. Veuillez essayer d’exporter les fichiers un par un.', + 'export_no_zip_extension' => 'L’extension ZIP n’est pas présente sur votre serveur. Veuillez essayer d’exporter les fichiers un par un.', 'feeds_imported' => 'Vos flux ont été importés et vont maintenant être actualisés.', 'feeds_imported_with_errors' => 'Vos flux ont été importés mais des erreurs sont survenues.', 'file_cannot_be_uploaded' => 'Le fichier ne peut pas être téléchargé !', - 'no_zip_extension' => 'L’extension Zip n’est pas présente sur votre serveur.', - 'zip_error' => 'Une erreur est survenue durant l’import du fichier Zip.', + 'no_zip_extension' => 'L’extension ZIP n’est pas présente sur votre serveur.', + 'zip_error' => 'Une erreur est survenue durant l’import du fichier ZIP.', ), 'sub' => array( 'actualize' => 'Actualiser', diff --git a/app/i18n/fr/sub.php b/app/i18n/fr/sub.php index e3631eb8b..bb3f2fefc 100644 --- a/app/i18n/fr/sub.php +++ b/app/i18n/fr/sub.php @@ -44,8 +44,8 @@ return array( 'export_opml' => 'Exporter la liste des flux (OPML)', 'export_starred' => 'Exporter les favoris', 'feed_list' => 'Liste des articles de %s', - 'file_to_import' => 'Fichier à importer
(OPML, Json ou Zip)', - 'file_to_import_no_zip' => 'Fichier à importer
(OPML ou Json)', + 'file_to_import' => 'Fichier à importer
(OPML, JSON ou ZIP)', + 'file_to_import_no_zip' => 'Fichier à importer
(OPML ou JSON)', 'import' => 'Importer', 'starred_list' => 'Liste des articles favoris', 'title' => 'Importer / exporter', diff --git a/app/i18n/it/feedback.php b/app/i18n/it/feedback.php index f217586b0..5851cb2e6 100644 --- a/app/i18n/it/feedback.php +++ b/app/i18n/it/feedback.php @@ -43,12 +43,12 @@ return array( 'not_found' => '%s non disponibile', ), 'import_export' => array( - 'export_no_zip_extension' => 'Estensione Zip non presente sul server. Per favore esporta i files singolarmente.', + 'export_no_zip_extension' => 'Estensione ZIP non presente sul server. Per favore esporta i files singolarmente.', 'feeds_imported' => 'I tuoi feed sono stati importati e saranno aggiornati', 'feeds_imported_with_errors' => 'I tuoi feeds sono stati importati ma si sono verificati alcuni errori', 'file_cannot_be_uploaded' => 'Il file non può essere caricato!', - 'no_zip_extension' => 'Estensione Zip non presente sul server.', - 'zip_error' => 'Si è verificato un errore importando il file Zip', + 'no_zip_extension' => 'Estensione ZIP non presente sul server.', + 'zip_error' => 'Si è verificato un errore importando il file ZIP', ), 'sub' => array( 'actualize' => 'Aggiorna', diff --git a/app/i18n/it/sub.php b/app/i18n/it/sub.php index dfcee2ce3..758e322c5 100644 --- a/app/i18n/it/sub.php +++ b/app/i18n/it/sub.php @@ -44,8 +44,8 @@ return array( 'export_opml' => 'Esporta tutta la lista dei feed (OPML)', 'export_starred' => 'Esporta i tuoi preferiti', 'feed_list' => 'Elenco di %s articoli', - 'file_to_import' => 'File da importare
(OPML, Json o Zip)', - 'file_to_import_no_zip' => 'File da importare
(OPML o Json)', + 'file_to_import' => 'File da importare
(OPML, JSON o ZIP)', + 'file_to_import_no_zip' => 'File da importare
(OPML o JSON)', 'import' => 'Importa', 'starred_list' => 'Elenco articoli preferiti', 'title' => 'Importa / esporta', diff --git a/app/i18n/nl/feedback.php b/app/i18n/nl/feedback.php index b703c43cf..386b8d415 100644 --- a/app/i18n/nl/feedback.php +++ b/app/i18n/nl/feedback.php @@ -43,12 +43,12 @@ return array( 'not_found' => '%s bestaat niet', ), 'import_export' => array( - 'export_no_zip_extension' => 'Zip uitbreiding is niet aanwezig op uw server. Exporteer a.u.b. uw bestanden één voor één.', + 'export_no_zip_extension' => 'ZIP uitbreiding is niet aanwezig op uw server. Exporteer a.u.b. uw bestanden één voor één.', 'feeds_imported' => 'Uw feeds zijn geimporteerd en worden nu vernieuwd', 'feeds_imported_with_errors' => 'Uw feeds zijn geimporteerd maar er zijn enige fouten opgetreden', 'file_cannot_be_uploaded' => 'Bestand kan niet worden verzonden!', - 'no_zip_extension' => 'Zip uitbreiding is niet aanwezig op uw server.', - 'zip_error' => 'Er is een fout opgetreden tijdens het imporeren van het Zip bestand.', + 'no_zip_extension' => 'ZIP uitbreiding is niet aanwezig op uw server.', + 'zip_error' => 'Er is een fout opgetreden tijdens het imporeren van het ZIP bestand.', ), 'sub' => array( 'actualize' => 'Actualiseren', diff --git a/app/i18n/nl/sub.php b/app/i18n/nl/sub.php index 159a58b27..a1ba3f03d 100644 --- a/app/i18n/nl/sub.php +++ b/app/i18n/nl/sub.php @@ -44,8 +44,8 @@ return array( 'export_opml' => 'Exporteer lijst van feeds (OPML)', 'export_starred' => 'Exporteer je fovorieten', 'feed_list' => 'Lijst van %s artikelen', - 'file_to_import' => 'Bestand om te importeren
(OPML, Json of Zip)', - 'file_to_import_no_zip' => 'Bestand om te importeren
(OPML of Json)', + 'file_to_import' => 'Bestand om te importeren
(OPML, JSON of ZIP)', + 'file_to_import_no_zip' => 'Bestand om te importeren
(OPML of JSON)', 'import' => 'Importeer', 'starred_list' => 'Lijst van favoriete artikelen', 'title' => 'Importeren / exporteren', diff --git a/app/i18n/ru/feedback.php b/app/i18n/ru/feedback.php index 7ce2ae9cf..e7f6b9f85 100644 --- a/app/i18n/ru/feedback.php +++ b/app/i18n/ru/feedback.php @@ -43,12 +43,12 @@ return array( 'not_found' => '%s does not exist', ), 'import_export' => array( - 'export_no_zip_extension' => 'Zip extension is not present on your server. Please try to export files one by one.', + 'export_no_zip_extension' => 'ZIP extension is not present on your server. Please try to export files one by one.', 'feeds_imported' => 'Your feeds have been imported and will now be updated', 'feeds_imported_with_errors' => 'Your feeds have been imported but some errors occurred', 'file_cannot_be_uploaded' => 'File cannot be uploaded!', - 'no_zip_extension' => 'Zip extension is not present on your server.', - 'zip_error' => 'An error occured during Zip import.', + 'no_zip_extension' => 'ZIP extension is not present on your server.', + 'zip_error' => 'An error occured during ZIP import.', ), 'sub' => array( 'actualize' => 'Actualise', diff --git a/app/i18n/ru/sub.php b/app/i18n/ru/sub.php index aaaa02827..789433ee6 100644 --- a/app/i18n/ru/sub.php +++ b/app/i18n/ru/sub.php @@ -44,8 +44,8 @@ return array( 'export_opml' => 'Export list of feeds (OPML)', 'export_starred' => 'Export your favourites', 'feed_list' => 'List of %s articles', - 'file_to_import' => 'File to import
(OPML, Json or Zip)', - 'file_to_import_no_zip' => 'File to import
(OPML or Json)', + 'file_to_import' => 'File to import
(OPML, JSON or ZIP)', + 'file_to_import_no_zip' => 'File to import
(OPML or JSON)', 'import' => 'Import', 'starred_list' => 'List of favourite articles', 'title' => 'Import / export', diff --git a/app/i18n/tr/feedback.php b/app/i18n/tr/feedback.php index a53316206..87361ff51 100644 --- a/app/i18n/tr/feedback.php +++ b/app/i18n/tr/feedback.php @@ -43,12 +43,12 @@ return array( 'not_found' => '%s bulunmamaktadır', ), 'import_export' => array( - 'export_no_zip_extension' => 'Zip eklentisi mevcut sunucunuzda yer almıyor. Lütfen başka dosya formatında dışarı aktarmayı deneyin.', + 'export_no_zip_extension' => 'ZIP eklentisi mevcut sunucunuzda yer almıyor. Lütfen başka dosya formatında dışarı aktarmayı deneyin.', 'feeds_imported' => 'Akışlarınız içe aktarıldı ve şimdi güncellenecek', 'feeds_imported_with_errors' => 'Akışlarınız içeri aktarıldı ama bazı hatalar meydana geldi', 'file_cannot_be_uploaded' => 'Dosya yüklenemedi!', - 'no_zip_extension' => 'Zip eklentisi mevcut sunucunuzda yer almıyor.', - 'zip_error' => 'Zip içe aktarımı sırasında hata meydana geldi.', + 'no_zip_extension' => 'ZIP eklentisi mevcut sunucunuzda yer almıyor.', + 'zip_error' => 'ZIP içe aktarımı sırasında hata meydana geldi.', ), 'sub' => array( 'actualize' => 'Güncelleme', diff --git a/app/i18n/tr/sub.php b/app/i18n/tr/sub.php index 5ab367ebb..7592096d9 100644 --- a/app/i18n/tr/sub.php +++ b/app/i18n/tr/sub.php @@ -44,8 +44,8 @@ return array( 'export_opml' => 'Akış listesini dışarı aktar (OPML)', 'export_starred' => 'Favorileri dışarı aktar', 'feed_list' => '%s makalenin listesi', - 'file_to_import' => 'Dosyadan içe aktar
(OPML, Json or Zip)', - 'file_to_import_no_zip' => 'Dosyadan içe aktar
(OPML or Json)', + 'file_to_import' => 'Dosyadan içe aktar
(OPML, JSON or ZIP)', + 'file_to_import_no_zip' => 'Dosyadan içe aktar
(OPML or JSON)', 'import' => 'İçe aktar', 'starred_list' => 'Favori makaleleirn listesi', 'title' => 'İçe / dışa aktar', diff --git a/cli/_cli.php b/cli/_cli.php index cb6d8ec32..d81d83d66 100644 --- a/cli/_cli.php +++ b/cli/_cli.php @@ -37,3 +37,8 @@ function cliInitUser($username) { return $username; } + +function done($ok) { + echo 'Result: ', ($ok ? 'success' : 'fail'), ".\n"; + exit($ok ? 0 : 1); +} diff --git a/cli/create-user.php b/cli/create-user.php index 08c057af8..387b503b6 100755 --- a/cli/create-user.php +++ b/cli/create-user.php @@ -15,19 +15,19 @@ if (empty($options['user'])) { fail('Usage: ' . basename(__FILE__) . " --user=username --password='password' --api-password='api_password'" . " --language=en --email=user@example.net --token='longRandomString'"); } -$new_user_name = $options['user']; -if (!ctype_alnum($new_user_name)) { - fail('FreshRSS error: invalid username “' . $new_user_name . '”'); +$username = $options['user']; +if (!ctype_alnum($username)) { + fail('FreshRSS error: invalid username “' . $username . '”'); } $usernames = listUsers(); -if (preg_grep("/^$new_user_name$/i", $usernames)) { - fail('FreshRSS error: username already taken “' . $new_user_name . '”'); +if (preg_grep("/^$username$/i", $usernames)) { + fail('FreshRSS error: username already taken “' . $username . '”'); } -echo 'FreshRSS creating user “', $new_user_name, "”…\n"; +echo 'FreshRSS creating user “', $username, "”…\n"; -$ok = FreshRSS_user_Controller::createUser($new_user_name, +$ok = FreshRSS_user_Controller::createUser($username, empty($options['password']) ? '' : $options['password'], empty($options['api-password']) ? '' : $options['api-password'], array( @@ -37,5 +37,4 @@ $ok = FreshRSS_user_Controller::createUser($new_user_name, invalidateHttpCache(FreshRSS_Context::$system_conf->default_user); -echo 'Result: ', ($ok ? 'success' : 'fail'), ".\n"; -exit($ok ? 0 : 1); +done($ok); diff --git a/cli/delete-user.php b/cli/delete-user.php index 46332fe34..da48103f7 100755 --- a/cli/delete-user.php +++ b/cli/delete-user.php @@ -29,5 +29,4 @@ $ok = FreshRSS_user_Controller::deleteUser($username); invalidateHttpCache(FreshRSS_Context::$system_conf->default_user); -echo 'Result: ', ($ok ? 'success' : 'fail'), ".\n"; -exit($ok ? 0 : 1); +done($ok); diff --git a/cli/import-for-user.php b/cli/import-for-user.php new file mode 100644 index 000000000..308786599 --- /dev/null +++ b/cli/import-for-user.php @@ -0,0 +1,36 @@ +#!/usr/bin/php +importFile($filename, $filename, $username); +} catch (FreshRSS_ZipMissing_Exception $zme) { + fail('FreshRSS error: Lacking php-zip extension!'); +} catch (FreshRSS_Zip_Exception $ze) { + fail('FreshRSS error: ZIP archive cannot be imported! Error code: ' . $ze->zipErrorCode()); +} +invalidateHttpCache($username); + +done($ok); diff --git a/cli/list-users.php b/cli/list-users.php index cc1cf5269..e690ff451 100755 --- a/cli/list-users.php +++ b/cli/list-users.php @@ -4,8 +4,8 @@ require('_cli.php'); $users = listUsers(); sort($users); -if ($system_conf->default_user !== '') { - array_unshift($users, $system_conf->default_user); +if (FreshRSS_Context::$system_conf->default_user !== '') { + array_unshift($users, FreshRSS_Context::$system_conf->default_user); $users = array_unique($users); } diff --git a/lib/Minz/ModelPdo.php b/lib/Minz/ModelPdo.php index da28909df..45139b0d6 100644 --- a/lib/Minz/ModelPdo.php +++ b/lib/Minz/ModelPdo.php @@ -36,22 +36,21 @@ class Minz_ModelPdo { * HOST, BASE, USER et PASS définies dans le fichier de configuration */ public function __construct($currentUser = null) { - if (self::$useSharedBd && self::$sharedBd != null && $currentUser === null) { + if ($currentUser === null) { + $currentUser = Minz_Session::param('currentUser', '_'); + } + if (self::$useSharedBd && self::$sharedBd != null && $currentUser === self::$sharedCurrentUser) { $this->bd = self::$sharedBd; $this->prefix = self::$sharedPrefix; $this->current_user = self::$sharedCurrentUser; return; } + $this->current_user = $currentUser; + self::$sharedCurrentUser = $currentUser; $conf = Minz_Configuration::get('system'); $db = $conf->db; - if ($currentUser === null) { - $currentUser = Minz_Session::param('currentUser', '_'); - } - $this->current_user = $currentUser; - self::$sharedCurrentUser = $currentUser; - $driver_options = isset($conf->db['pdo_options']) && is_array($conf->db['pdo_options']) ? $conf->db['pdo_options'] : array(); $dbServer = parse_url('db://' . $db['host']); -- cgit v1.2.3 From a6db495494130da044a95b57c65d944be45746ee Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Mon, 24 Oct 2016 02:25:16 +0200 Subject: CLI actualize-user https://github.com/FreshRSS/FreshRSS/issues/1095 --- cli/actualize-user.php | 23 +++++++++++++++++++++++ lib/Minz/ModelPdo.php | 5 +++-- 2 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 cli/actualize-user.php (limited to 'lib/Minz/ModelPdo.php') diff --git a/cli/actualize-user.php b/cli/actualize-user.php new file mode 100644 index 000000000..29d51753a --- /dev/null +++ b/cli/actualize-user.php @@ -0,0 +1,23 @@ +#!/usr/bin/php + 0); diff --git a/lib/Minz/ModelPdo.php b/lib/Minz/ModelPdo.php index 45139b0d6..6e8d60bc9 100644 --- a/lib/Minz/ModelPdo.php +++ b/lib/Minz/ModelPdo.php @@ -37,9 +37,10 @@ class Minz_ModelPdo { */ public function __construct($currentUser = null) { if ($currentUser === null) { - $currentUser = Minz_Session::param('currentUser', '_'); + $currentUser = Minz_Session::param('currentUser'); } - if (self::$useSharedBd && self::$sharedBd != null && $currentUser === self::$sharedCurrentUser) { + if (self::$useSharedBd && self::$sharedBd != null && + ($currentUser == null || $currentUser === self::$sharedCurrentUser)) { $this->bd = self::$sharedBd; $this->prefix = self::$sharedPrefix; $this->current_user = self::$sharedCurrentUser; -- cgit v1.2.3 From 568b737b6c62ace5aaa6b3f6c968e2595ea70f55 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Tue, 15 Nov 2016 21:38:18 +0100 Subject: Function to disable MySQL buffering for large exports --- app/Controllers/importExportController.php | 2 ++ lib/Minz/ModelPdo.php | 6 ++++++ 2 files changed, 8 insertions(+) (limited to 'lib/Minz/ModelPdo.php') diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php index d86587ea9..6ae89defb 100644 --- a/app/Controllers/importExportController.php +++ b/app/Controllers/importExportController.php @@ -531,6 +531,8 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $this->entryDAO = FreshRSS_Factory::createEntryDao($username); $this->feedDAO = FreshRSS_Factory::createFeedDao($username); + $this->entryDAO->disableBuffering(); + if ($export_feeds === true) { //All feeds $export_feeds = $this->feedDAO->listFeedsIds(); diff --git a/lib/Minz/ModelPdo.php b/lib/Minz/ModelPdo.php index 6e8d60bc9..caab1d114 100644 --- a/lib/Minz/ModelPdo.php +++ b/lib/Minz/ModelPdo.php @@ -116,6 +116,12 @@ class Minz_ModelPdo { self::$sharedBd = null; self::$sharedPrefix = ''; } + + public function disableBuffering() { + if ((self::$sharedDbType === 'mysql') && defined('PDO::MYSQL_ATTR_USE_BUFFERED_QUERY')) { + $this->bd->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false); + } + } } class MinzPDO extends PDO { -- cgit v1.2.3