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 --- lib/Minz/ModelPdo.php | 77 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 49 insertions(+), 28 deletions(-) (limited to 'lib/Minz/ModelPdo.php') 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); + } } -- 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