aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Alexandre Alapetite <alexandre@alapetite.fr> 2016-10-01 19:35:14 +0200
committerGravatar GitHub <noreply@github.com> 2016-10-01 19:35:14 +0200
commit0ccf100568dc76bd88443fac13c5aef15fed9ff6 (patch)
tree2b72a0afa0fc0fcbd97ed85192fa3737313e7298
parent8a58795e7dfd501c6c8ac84694e695c4f0791cfb (diff)
parent32ac41439a67e2c5777f9da80324fe0bfbf394ff (diff)
Merge pull request #1195 from Alkarex/PostgreSQL
PostgreSQL
-rw-r--r--CHANGELOG.md2
-rw-r--r--README.fr.md23
-rw-r--r--README.md23
-rw-r--r--app/Models/CategoryDAO.php9
-rw-r--r--app/Models/ConfigurationSetter.php1
-rw-r--r--app/Models/DatabaseDAOPGSQL.php43
-rw-r--r--app/Models/EntryDAO.php141
-rw-r--r--app/Models/EntryDAOPGSQL.php31
-rw-r--r--app/Models/EntryDAOSQLite.php8
-rw-r--r--app/Models/Factory.php42
-rw-r--r--app/Models/FeedDAO.php24
-rw-r--r--app/Models/FeedDAOSQLite.php4
-rw-r--r--app/Models/StatsDAO.php45
-rw-r--r--app/Models/StatsDAOPGSQL.php67
-rw-r--r--app/Models/StatsDAOSQLite.php8
-rw-r--r--app/SQL/install.sql.mysql.php2
-rw-r--r--app/SQL/install.sql.pgsql.php59
-rw-r--r--app/i18n/cz/admin.php4
-rw-r--r--app/i18n/cz/install.php4
-rw-r--r--app/i18n/de/admin.php4
-rw-r--r--app/i18n/de/install.php4
-rw-r--r--app/i18n/en/admin.php4
-rw-r--r--app/i18n/en/install.php4
-rw-r--r--app/i18n/fr/admin.php4
-rw-r--r--app/i18n/fr/install.php4
-rw-r--r--app/i18n/it/admin.php4
-rw-r--r--app/i18n/it/install.php4
-rw-r--r--app/i18n/nl/admin.php4
-rw-r--r--app/i18n/nl/install.php4
-rw-r--r--app/i18n/ru/admin.php4
-rw-r--r--app/i18n/ru/install.php4
-rw-r--r--app/i18n/tr/admin.php4
-rw-r--r--app/i18n/tr/install.php4
-rw-r--r--app/install.php59
-rw-r--r--app/views/stats/index.phtml12
-rw-r--r--app/views/stats/repartition.phtml8
-rw-r--r--lib/Minz/ModelPdo.php89
-rw-r--r--p/scripts/install.js6
38 files changed, 539 insertions, 231 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1c2d47586..40b4df1f9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,8 @@
* API
* Support for editing feeds and categories from client applications [#1254](https://github.com/FreshRSS/FreshRSS/issues/1254)
+* Compatibility:
+ * Experimental support for PostgreSQL [#1195](https://github.com/FreshRSS/FreshRSS/pull/1195)
* Features
* Better control of number of entries per page or RSS feed [#1249](https://github.com/FreshRSS/FreshRSS/issues/1249)
* Since X hours: `https://freshrss.example/i/?a=rss&hours=3`
diff --git a/README.fr.md b/README.fr.md
index 99de1f618..65cef544f 100644
--- a/README.fr.md
+++ b/README.fr.md
@@ -33,9 +33,9 @@ Nous sommes une communauté amicale.
* 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.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), [cURL](http://php.net/curl)
+ * 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)
-* MySQL 5.5.3+ (recommandé) ou SQLite 3.7.4+
+* 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
@@ -56,13 +56,20 @@ Nous sommes une communauté amicale.
```sh
# Si vous utilisez le serveur Web Apache (sinon il faut un autre serveur Web)
sudo apt-get install apache2
-sudo a2enmod headers expires rewrite ssl
-# (optionnel) Si vous voulez un serveur de base de données MySQL
-sudo apt-get install mysql-server mysql-client php5-mysql
-# Composants principaux (pour Ubuntu <= 15.10, Debian <= 8 Jessie)
+sudo a2enmod headers expires rewrite ssl #Modules Apache
+
+# Pour Ubuntu <= 15.10, Debian <= 8 Jessie
sudo apt-get install php5 php5-curl php5-gmp php5-intl php5-json php5-sqlite
-# Composants principaux (pour Ubuntu >= 16.04, Debian >= 9 Stretch)
-sudo apt install php libapache2-mod-php php-curl php-gmp php-intl php-mbstring php-sqlite3 php-xml php-zip
+sudo apt-get install libapache2-mod-php5 #Pour Apache
+sudo apt-get install mysql-server mysql-client php5-mysql #Base de données MySQL optionnelle
+sudo apt-get install postgresql php5-pgsql #Base de données PostgreSQL optionnelle
+
+# Pour Ubuntu >= 16.04, Debian >= 9 Stretch
+sudo apt install php php-curl php-gmp php-intl php-mbstring php-sqlite3 php-xml php-zip
+sudo apt install libapache2-mod-php #Pour Apache
+sudo apt install mysql-server mysql-client php-mysql #Base de données MySQL optionnelle
+sudo apt install postgresql php-pgsql #Base de données PostgreSQL optionnelle
+
# Redémarrage du serveur Web
sudo service apache2 restart
diff --git a/README.md b/README.md
index 846652878..505a1d970 100644
--- a/README.md
+++ b/README.md
@@ -33,9 +33,9 @@ We are a friendly community.
* 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.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), [cURL](http://php.net/curl)
+ * 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)
-* MySQL 5.5.3+ (recommended) or SQLite 3.7.4+
+* 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
@@ -56,13 +56,20 @@ We are a friendly community.
```sh
# If you use an Apache Web server (otherwise you need another Web server)
sudo apt-get install apache2
-sudo a2enmod headers expires rewrite ssl
-# (Optional) If you want a MySQL database server
-sudo apt-get install mysql-server mysql-client php5-mysql
-# Main components (for Ubuntu <= 15.10, Debian <= 8 Jessie)
+sudo a2enmod headers expires rewrite ssl #Apache modules
+
+# For Ubuntu <= 15.10, Debian <= 8 Jessie
sudo apt-get install php5 php5-curl php5-gmp php5-intl php5-json php5-sqlite
-# Main components (for Ubuntu >= 16.04, Debian >= 9 Stretch)
-sudo apt install php libapache2-mod-php php-curl php-gmp php-intl php-mbstring php-sqlite3 php-xml php-zip
+sudo apt-get install libapache2-mod-php5 #For Apache
+sudo apt-get install mysql-server mysql-client php5-mysql #Optional MySQL database
+sudo apt-get install postgresql php5-pgsql #Optional PostgreSQL database
+
+# For Ubuntu >= 16.04, Debian >= 9 Stretch
+sudo apt install php php-curl php-gmp php-intl php-mbstring php-sqlite3 php-xml php-zip
+sudo apt install libapache2-mod-php #For Apache
+sudo apt install mysql-server mysql-client php-mysql #Optional MySQL database
+sudo apt install postgresql php-pgsql #Optional PostgreSQL database
+
# Restart Web server
sudo service apache2 restart
diff --git a/app/Models/CategoryDAO.php b/app/Models/CategoryDAO.php
index c103163a1..45fdcb5af 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('"' . $this->prefix . 'category_id_seq"');
} else {
$info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
Minz_Log::error('SQL error addCategory: ' . $info[2]);
@@ -103,7 +103,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 '
@@ -210,12 +210,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;
@@ -231,7 +232,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 79bd0170b..046f54955 100644
--- a/app/Models/ConfigurationSetter.php
+++ b/app/Models/ConfigurationSetter.php
@@ -282,6 +282,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/DatabaseDAOPGSQL.php b/app/Models/DatabaseDAOPGSQL.php
new file mode 100644
index 000000000..a4edaa448
--- /dev/null
+++ b/app/Models/DatabaseDAOPGSQL.php
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * This class is used to test database is well-constructed.
+ */
+class FreshRSS_DatabaseDAOPGSQL extends FreshRSS_DatabaseDAO {
+ public function tablesAreCorrect() {
+ $db = FreshRSS_Context::$system_conf->db;
+ $dbowner = $db['user'];
+ $sql = 'SELECT * FROM pg_catalog.pg_tables where tableowner=?';
+ $stm = $this->bd->prepare($sql);
+ $values = array($dbowner);
+ $stm->execute($values);
+ $res = $stm->fetchAll(PDO::FETCH_ASSOC);
+
+ $tables = array(
+ $this->prefix . 'category' => false,
+ $this->prefix . 'feed' => false,
+ $this->prefix . 'entry' => false,
+ );
+ foreach ($res as $value) {
+ $tables[array_pop($value)] = true;
+ }
+
+ return count(array_keys($tables, true, true)) == count($tables);
+ }
+
+ public function getSchema($table) {
+ $sql = 'select column_name as field, data_type as type, column_default as default, is_nullable as null from INFORMATION_SCHEMA.COLUMNS where table_name = ?';
+ $stm = $this->bd->prepare($sql);
+ $stm->execute(array($this->prefix . $table));
+ return $this->listDaoToSchema($stm->fetchAll(PDO::FETCH_ASSOC));
+ }
+
+ public function daoToSchema($dao) {
+ return array(
+ 'name' => $dao['field'],
+ 'type' => strtolower($dao['type']),
+ 'notnull' => (bool)$dao['null'],
+ 'default' => $dao['default'],
+ );
+ }
+}
diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php
index 8f64098e5..63565e73a 100644
--- a/app/Models/EntryDAO.php
+++ b/app/Models/EntryDAO.php
@@ -3,13 +3,21 @@
class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
public function isCompressed() {
- return parent::$sharedDbType !== 'sqlite';
+ return parent::$sharedDbType === 'mysql';
}
public function hasNativeHex() {
return parent::$sharedDbType !== 'sqlite';
}
+ public function sqlHexDecode($x) {
+ return 'unhex(' . $x . ')';
+ }
+
+ public function sqlHexEncode($x) {
+ return 'hex(' . $x . ')';
+ }
+
protected function addColumn($name) {
Minz_Log::warning('FreshRSS_EntryDAO::addColumn: ' . $name);
$hasTransaction = false;
@@ -20,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()) {
@@ -105,32 +113,43 @@ 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) '
- . 'VALUES(?, ?, ?, ?, '
- . ($this->isCompressed() ? 'COMPRESS(?)' : '?')
- . ', ?, ?, ?, '
- . ($this->hasNativeHex() ? 'X?' : '?')
- . ', ?, ?, ?, ?)';
+ . ', 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, '
+ . $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']); //hex2bin() is PHP5.4+
+ $this->addEntryPrepared->bindParam(':hash', $valuesTmp['hashBin']);
+ }
- $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();
@@ -153,33 +172,41 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
if ($this->updateEntryPrepared === null) {
$sql = 'UPDATE `' . $this->prefix . 'entry` '
- . 'SET title=?, author=?, '
- . ($this->isCompressed() ? 'content_bin=COMPRESS(?)' : 'content=?')
- . ', link=?, date=?, lastSeen=?, hash='
- . ($this->hasNativeHex() ? 'X?' : '?')
- . ', ' . ($valuesTmp['is_read'] === null ? '' : 'is_read=?, ')
- . 'tags=? '
- . 'WHERE id_feed=? AND guid=?';
+ . 'SET title=:title, author=:author, '
+ . ($this->isCompressed() ? 'content_bin=COMPRESS(:content)' : 'content=:content')
+ . ', link=:link, date=:date, `lastSeen`=:last_seen, '
+ . 'hash=' . $this->sqlHexDecode(':hash')
+ . ', ' . ($valuesTmp['is_read'] === null ? '' : 'is_read=:is_read, ')
+ . 'tags=:tags '
+ . 'WHERE id_feed=:id_feed AND guid=:guid';
$this->updateEntryPrepared = $this->bd->prepare($sql);
}
- $values = array(
- 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']),
- );
+ $valuesTmp['guid'] = substr($valuesTmp['guid'], 0, 760);
+ $this->updateEntryPrepared->bindParam(':guid', $valuesTmp['guid']);
+ $valuesTmp['title'] = substr($valuesTmp['title'], 0, 255);
+ $this->updateEntryPrepared->bindParam(':title', $valuesTmp['title']);
+ $valuesTmp['author'] = substr($valuesTmp['author'], 0, 255);
+ $this->updateEntryPrepared->bindParam(':author', $valuesTmp['author']);
+ $this->updateEntryPrepared->bindParam(':content', $valuesTmp['content']);
+ $valuesTmp['link'] = substr($valuesTmp['link'], 0, 1023);
+ $this->updateEntryPrepared->bindParam(':link', $valuesTmp['link']);
+ $this->updateEntryPrepared->bindParam(':date', $valuesTmp['date'], PDO::PARAM_INT);
+ $valuesTmp['lastSeen'] = time();
+ $this->updateEntryPrepared->bindParam(':last_seen', $valuesTmp['lastSeen'], PDO::PARAM_INT);
if ($valuesTmp['is_read'] !== null) {
- $values[] = $valuesTmp['is_read'] ? 1 : 0;
+ $this->updateEntryPrepared->bindParam(':is_read', $valuesTmp['is_read'] ? 1 : 0, PDO::PARAM_INT);
+ }
+ $this->updateEntryPrepared->bindParam(':id_feed', $valuesTmp['id_feed'], PDO::PARAM_INT);
+ $valuesTmp['tags'] = substr($valuesTmp['tags'], 0, 1023);
+ $this->updateEntryPrepared->bindParam(':tags', $valuesTmp['tags']);
+
+ if ($this->hasNativeHex()) {
+ $this->updateEntryPrepared->bindParam(':hash', $valuesTmp['hash']);
+ } else {
+ $valuesTmp['hashBin'] = pack('H*', $valuesTmp['hash']); //hex2bin() is PHP5.4+
+ $this->updateEntryPrepared->bindParam(':hash', $valuesTmp['hashBin']);
}
- $values = array_merge($values, array(
- substr($valuesTmp['tags'], 0, 1023),
- $valuesTmp['id_feed'],
- substr($valuesTmp['guid'], 0, 760),
- ));
if ($this->updateEntryPrepared && $this->updateEntryPrepared->execute($values)) {
return $this->bd->lastInsertId();
@@ -246,7 +273,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) {
@@ -309,7 +336,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);
@@ -430,12 +457,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)))) {
@@ -448,7 +475,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);
@@ -658,7 +685,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 hex_hash 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);
@@ -666,7 +693,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable {
$result = array();
$rows = $stm->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as $row) {
- $result[$row['guid']] = $row['hexHash'];
+ $result[$row['guid']] = $row['hex_hash'];
}
return $result;
} else {
@@ -684,7 +711,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/EntryDAOPGSQL.php b/app/Models/EntryDAOPGSQL.php
new file mode 100644
index 000000000..b96a62ebc
--- /dev/null
+++ b/app/Models/EntryDAOPGSQL.php
@@ -0,0 +1,31 @@
+<?php
+
+class FreshRSS_EntryDAOPGSQL extends FreshRSS_EntryDAOSQLite {
+
+ public function sqlHexDecode($x) {
+ return 'decode(' . $x . ", 'hex')";
+ }
+
+ public function sqlHexEncode($x) {
+ return 'encode(' . $x . ", 'hex')";
+ }
+
+ protected function autoUpdateDb($errorInfo) {
+ return false;
+ }
+
+ protected function addColumn($name) {
+ return false;
+ }
+
+ 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/EntryDAOSQLite.php b/app/Models/EntryDAOSQLite.php
index dad34a93d..fd5d25bf6 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
@@ -24,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';
@@ -82,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/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..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(
@@ -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('"' . $this->prefix . 'feed_id_seq"');
} else {
$info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo();
Minz_Log::error('SQL error addFeed: ' . $info[2]);
@@ -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/app/Models/StatsDAO.php b/app/Models/StatsDAO.php
index 28882baab..2ce4f2944 100644
--- a/app/Models/StatsDAO.php
+++ b/app/Models/StatsDAO.php
@@ -4,6 +4,10 @@ class FreshRSS_StatsDAO extends Minz_ModelPdo {
const ENTRY_COUNT_PERIOD = 30;
+ protected function sqlFloor($s) {
+ return "FLOOR($s)";
+ }
+
/**
* Calculates entry repartition for all feeds and for main stream.
*
@@ -37,12 +41,12 @@ class FreshRSS_StatsDAO extends Minz_ModelPdo {
$filter .= "AND e.id_feed = {$feed}";
}
$sql = <<<SQL
-SELECT COUNT(1) AS `total`,
-COUNT(1) - SUM(e.is_read) AS `unread`,
-SUM(e.is_read) AS `read`,
-SUM(e.is_favorite) AS `favorite`
-FROM {$this->prefix}entry AS e
-, {$this->prefix}feed AS f
+SELECT COUNT(1) AS total,
+COUNT(1) - SUM(e.is_read) AS count_unreads,
+SUM(e.is_read) AS count_reads,
+SUM(e.is_favorite) AS count_favorites
+FROM `{$this->prefix}entry` AS e
+, `{$this->prefix}feed` AS f
WHERE e.id_feed = f.id
{$filter}
SQL;
@@ -65,10 +69,11 @@ SQL;
$oldest = $midnight - (self::ENTRY_COUNT_PERIOD * 86400);
// Get stats per day for the last 30 days
+ $sqlDay = $this->sqlFloor("(date - $midnight) / 86400");
$sql = <<<SQL
-SELECT FLOOR((date - {$midnight}) / 86400) AS day,
+SELECT {$sqlDay} AS day,
COUNT(*) as count
-FROM {$this->prefix}entry
+FROM `{$this->prefix}entry`
WHERE date >= {$oldest} AND date < {$midnight}
GROUP BY day
ORDER BY day ASC
@@ -138,7 +143,7 @@ SQL;
$sql = <<<SQL
SELECT DATE_FORMAT(FROM_UNIXTIME(e.date), '{$period}') AS period
, COUNT(1) AS count
-FROM {$this->prefix}entry AS e
+FROM `{$this->prefix}entry` AS e
{$restrict}
GROUP BY period
ORDER BY period ASC
@@ -202,7 +207,7 @@ SQL;
SELECT COUNT(1) AS count
, MIN(date) AS date_min
, MAX(date) AS date_max
-FROM {$this->prefix}entry AS e
+FROM `{$this->prefix}entry` AS e
{$restrict}
SQL;
$stm = $this->bd->prepare($sql);
@@ -246,8 +251,8 @@ SQL;
$sql = <<<SQL
SELECT c.name AS label
, COUNT(f.id) AS data
-FROM {$this->prefix}category AS c,
-{$this->prefix}feed AS f
+FROM `{$this->prefix}category` AS c,
+`{$this->prefix}feed` AS f
WHERE c.id = f.category
GROUP BY label
ORDER BY data DESC
@@ -269,9 +274,9 @@ SQL;
$sql = <<<SQL
SELECT c.name AS label
, COUNT(e.id) AS data
-FROM {$this->prefix}category AS c,
-{$this->prefix}feed AS f,
-{$this->prefix}entry AS e
+FROM `{$this->prefix}category` AS c,
+`{$this->prefix}feed` AS f,
+`{$this->prefix}entry` AS e
WHERE c.id = f.category
AND f.id = e.id_feed
GROUP BY label
@@ -295,9 +300,9 @@ SELECT f.id AS id
, MAX(f.name) AS name
, MAX(c.name) AS category
, COUNT(e.id) AS count
-FROM {$this->prefix}category AS c,
-{$this->prefix}feed AS f,
-{$this->prefix}entry AS e
+FROM `{$this->prefix}category` AS c,
+`{$this->prefix}feed` AS f,
+`{$this->prefix}entry` AS e
WHERE c.id = f.category
AND f.id = e.id_feed
GROUP BY f.id
@@ -320,8 +325,8 @@ SELECT MAX(f.id) as id
, MAX(f.name) AS name
, MAX(date) AS last_date
, COUNT(*) AS nb_articles
-FROM {$this->prefix}feed AS f,
-{$this->prefix}entry AS e
+FROM `{$this->prefix}feed` AS f,
+`{$this->prefix}entry` AS e
WHERE f.id = e.id_feed
GROUP BY f.id
ORDER BY name
diff --git a/app/Models/StatsDAOPGSQL.php b/app/Models/StatsDAOPGSQL.php
new file mode 100644
index 000000000..1effbb64b
--- /dev/null
+++ b/app/Models/StatsDAOPGSQL.php
@@ -0,0 +1,67 @@
+<?php
+
+class FreshRSS_StatsDAOPGSQL extends FreshRSS_StatsDAO {
+
+ /**
+ * 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 = <<<SQL
+SELECT extract( {$period} from to_timestamp(e.date)) AS period
+, COUNT(1) AS count
+FROM "{$this->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 $repartition;
+ }
+
+}
diff --git a/app/Models/StatsDAOSQLite.php b/app/Models/StatsDAOSQLite.php
index e09d18c77..6cfc20463 100644
--- a/app/Models/StatsDAOSQLite.php
+++ b/app/Models/StatsDAOSQLite.php
@@ -2,6 +2,10 @@
class FreshRSS_StatsDAOSQLite extends FreshRSS_StatsDAO {
+ protected function sqlFloor($s) {
+ return "CAST(($s) AS INT)";
+ }
+
protected function calculateEntryRepartitionPerFeedPerPeriod($period, $feed = null) {
if ($feed) {
$restrict = "WHERE e.id_feed = {$feed}";
@@ -11,7 +15,7 @@ class FreshRSS_StatsDAOSQLite extends FreshRSS_StatsDAO {
$sql = <<<SQL
SELECT strftime('{$period}', e.date, 'unixepoch') AS period
, COUNT(1) AS count
-FROM {$this->prefix}entry AS e
+FROM `{$this->prefix}entry` AS e
{$restrict}
GROUP BY period
ORDER BY period ASC
@@ -26,7 +30,7 @@ SQL;
$repartition[(int) $value['period']] = (int) $value['count'];
}
- return $this->convertToSerie($repartition);
+ return $repartition;
}
}
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 @@
<?php
+define('SQL_CREATE_DB', 'CREATE DATABASE IF NOT EXISTS %1$s DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;');
+
define('SQL_CREATE_TABLES', '
CREATE TABLE IF NOT EXISTS `%1$scategory` (
`id` SMALLINT NOT NULL AUTO_INCREMENT, -- v0.7
diff --git a/app/SQL/install.sql.pgsql.php b/app/SQL/install.sql.pgsql.php
new file mode 100644
index 000000000..e60c5a07b
--- /dev/null
+++ b/app/SQL/install.sql.pgsql.php
@@ -0,0 +1,59 @@
+<?php
+define('SQL_CREATE_DB', 'CREATE DATABASE %1$s ENCODING \'UTF8\';');
+
+global $SQL_CREATE_TABLES;
+$SQL_CREATE_TABLES = array(
+'CREATE TABLE IF NOT EXISTS "%1$scategory" (
+ "id" SERIAL PRIMARY KEY,
+ "name" VARCHAR(255) UNIQUE NOT NULL
+);',
+
+'CREATE TABLE IF NOT EXISTS "%1$sfeed" (
+ "id" SERIAL PRIMARY KEY,
+ "url" varchar(511) UNIQUE NOT NULL,
+ "category" SMALLINT DEFAULT 0,
+ "name" VARCHAR(255) NOT NULL,
+ "website" VARCHAR(255),
+ "description" text,
+ "lastUpdate" INT DEFAULT 0,
+ "priority" SMALLINT NOT NULL DEFAULT 10,
+ "pathEntries" VARCHAR(511) DEFAULT NULL,
+ "httpAuth" VARCHAR(511) DEFAULT NULL,
+ "error" smallint DEFAULT 0,
+ "keep_history" INT NOT NULL DEFAULT -2,
+ "ttl" INT NOT NULL DEFAULT -2,
+ "cache_nbEntries" INT DEFAULT 0,
+ "cache_nbUnreads" INT DEFAULT 0,
+ FOREIGN KEY ("category") REFERENCES "%1$scategory" ("id") ON DELETE SET NULL ON UPDATE CASCADE
+);',
+'CREATE INDEX name_index ON "%1$sfeed" ("name");',
+'CREATE INDEX priority_index ON "%1$sfeed" ("priority");',
+'CREATE INDEX keep_history_index ON "%1$sfeed" ("keep_history");',
+
+'CREATE TABLE IF NOT EXISTS "%1$sentry" (
+ "id" BIGINT NOT NULL PRIMARY KEY,
+ "guid" VARCHAR(760) UNIQUE NOT NULL,
+ "title" VARCHAR(255) NOT NULL,
+ "author" VARCHAR(255),
+ "content" TEXT,
+ "link" VARCHAR(1023) NOT NULL,
+ "date" INT,
+ "lastSeen" INT DEFAULT 0,
+ "hash" BYTEA,
+ "is_read" SMALLINT NOT NULL DEFAULT 0,
+ "is_favorite" SMALLINT NOT NULL DEFAULT 0,
+ "id_feed" SMALLINT,
+ "tags" VARCHAR(1023),
+ FOREIGN KEY ("id_feed") REFERENCES "%1$sfeed" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
+ UNIQUE ("id_feed","guid")
+);',
+'CREATE INDEX is_favorite_index ON "%1$sentry" ("is_favorite");',
+'CREATE INDEX is_read_index ON "%1$sentry" ("is_read");',
+'CREATE INDEX entry_lastSeen_index ON "%1$sentry" ("lastSeen");',
+
+'INSERT INTO "%1$scategory" (id, name) VALUES(1, \'%2$s\');',
+'INSERT INTO "%1$sfeed" (url, category, name, website, description, ttl) VALUES(\'http://freshrss.org/feeds/all.atom.xml\', 1, \'FreshRSS.org\', \'http://freshrss.org/\', \'FreshRSS, a free, self-hostable aggregator…\', 86400);',
+'INSERT INTO "%1$sfeed" (url, category, name, website, description, ttl) VALUES(\'https://github.com/FreshRSS/FreshRSS/releases.atom\', 1, \'FreshRSS @ GitHub\', \'https://github.com/FreshRSS/FreshRSS/\', \'FreshRSS releases @ GitHub\', 86400);',
+);
+
+define('SQL_DROP_TABLES', 'DROP TABLES "%1$sentry", "%1$sfeed", "%1$scategory"');
diff --git a/app/i18n/cz/admin.php b/app/i18n/cz/admin.php
index 881c02fc6..781ca2fe3 100644
--- a/app/i18n/cz/admin.php
+++ b/app/i18n/cz/admin.php
@@ -71,8 +71,8 @@ return array(
'ok' => 'Máte požadovanou knihovnu pro regulární výrazy (PCRE).',
),
'pdo' => array(
- 'nok' => 'Nemáte PDO nebo některý z podporovaných ovladačů (pdo_mysql, pdo_sqlite).',
- 'ok' => 'Máte PDO a alespoň jeden z podporovaných ovladačů (pdo_mysql, pdo_sqlite).',
+ 'nok' => 'Nemáte PDO nebo některý z podporovaných ovladačů (pdo_mysql, pdo_sqlite, pdo_pgsql).',
+ 'ok' => 'Máte PDO a alespoň jeden z podporovaných ovladačů (pdo_mysql, pdo_sqlite, pdo_pgsql).',
),
'php' => array(
'_' => 'PHP instalace',
diff --git a/app/i18n/cz/install.php b/app/i18n/cz/install.php
index 6b94c0d4b..e29a51888 100644
--- a/app/i18n/cz/install.php
+++ b/app/i18n/cz/install.php
@@ -73,8 +73,8 @@ return array(
'ok' => 'Máte požadovanou knihovnu pro regulární výrazy (PCRE).',
),
'pdo' => array(
- 'nok' => 'Nemáte PDO nebo některý z podporovaných ovladačů (pdo_mysql, pdo_sqlite).',
- 'ok' => 'Máte PDO a alespoň jeden z podporovaných ovladačů (pdo_mysql, pdo_sqlite).',
+ 'nok' => 'Nemáte PDO nebo některý z podporovaných ovladačů (pdo_mysql, pdo_sqlite, pdo_pgsql).',
+ 'ok' => 'Máte PDO a alespoň jeden z podporovaných ovladačů (pdo_mysql, pdo_sqlite, pdo_pgsql).',
),
'php' => array(
'nok' => 'Vaše verze PHP je %s, ale FreshRSS vyžaduje alespoň verzi %s.',
diff --git a/app/i18n/de/admin.php b/app/i18n/de/admin.php
index 7b75fe5f4..b1c29b901 100644
--- a/app/i18n/de/admin.php
+++ b/app/i18n/de/admin.php
@@ -71,8 +71,8 @@ return array(
'ok' => 'Sie haben die benötigte Bibliothek für reguläre Ausdrücke (PCRE).',
),
'pdo' => array(
- 'nok' => 'Ihnen fehlt PDO oder einer der unterstützten Treiber (pdo_mysql, pdo_sqlite).',
- 'ok' => 'Sie haben PDO und mindestens einen der unterstützten Treiber (pdo_mysql, pdo_sqlite).',
+ 'nok' => 'Ihnen fehlt PDO oder einer der unterstützten Treiber (pdo_mysql, pdo_sqlite, pdo_pgsql).',
+ 'ok' => 'Sie haben PDO und mindestens einen der unterstützten Treiber (pdo_mysql, pdo_sqlite, pdo_pgsql).',
),
'php' => array(
'_' => 'PHP-Installation',
diff --git a/app/i18n/de/install.php b/app/i18n/de/install.php
index a77822e7b..eb0c2eb89 100644
--- a/app/i18n/de/install.php
+++ b/app/i18n/de/install.php
@@ -73,8 +73,8 @@ return array(
'ok' => 'Sie haben die benötigte Bibliothek für reguläre Ausdrücke (PCRE).',
),
'pdo' => array(
- 'nok' => 'Ihnen fehlt PDO oder einer der unterstützten Treiber (pdo_mysql, pdo_sqlite).',
- 'ok' => 'Sie haben PDO und mindestens einen der unterstützten Treiber (pdo_mysql, pdo_sqlite).',
+ 'nok' => 'Ihnen fehlt PDO oder einer der unterstützten Treiber (pdo_mysql, pdo_sqlite, pdo_pgsql).',
+ 'ok' => 'Sie haben PDO und mindestens einen der unterstützten Treiber (pdo_mysql, pdo_sqlite, pdo_pgsql).',
),
'php' => array(
'nok' => 'Ihre PHP-Version ist %s aber FreshRSS benötigt mindestens Version %s.',
diff --git a/app/i18n/en/admin.php b/app/i18n/en/admin.php
index a88552087..f6b63d44c 100644
--- a/app/i18n/en/admin.php
+++ b/app/i18n/en/admin.php
@@ -71,8 +71,8 @@ return array(
'ok' => 'You have the required library for regular expressions (PCRE).',
),
'pdo' => array(
- 'nok' => 'You lack PDO or one of the supported drivers (pdo_mysql, pdo_sqlite).',
- 'ok' => 'You have PDO and at least one of the supported drivers (pdo_mysql, pdo_sqlite).',
+ 'nok' => 'You lack PDO or one of the supported drivers (pdo_mysql, pdo_sqlite, pdo_pgsql).',
+ 'ok' => 'You have PDO and at least one of the supported drivers (pdo_mysql, pdo_sqlite, pdo_pgsql).',
),
'php' => array(
'_' => 'PHP installation',
diff --git a/app/i18n/en/install.php b/app/i18n/en/install.php
index d1c5f37c8..54ce8edb2 100644
--- a/app/i18n/en/install.php
+++ b/app/i18n/en/install.php
@@ -73,8 +73,8 @@ return array(
'ok' => 'You have the required library for regular expressions (PCRE).',
),
'pdo' => array(
- 'nok' => 'You lack PDO or one of the supported drivers (pdo_mysql, pdo_sqlite).',
- 'ok' => 'You have PDO and at least one of the supported drivers (pdo_mysql, pdo_sqlite).',
+ 'nok' => 'You lack PDO or one of the supported drivers (pdo_mysql, pdo_sqlite, pdo_pgsql).',
+ 'ok' => 'You have PDO and at least one of the supported drivers (pdo_mysql, pdo_sqlite, pdo_pgsql).',
),
'php' => array(
'nok' => 'Your PHP version is %s but FreshRSS requires at least version %s.',
diff --git a/app/i18n/fr/admin.php b/app/i18n/fr/admin.php
index c359e9d24..c796fb539 100644
--- a/app/i18n/fr/admin.php
+++ b/app/i18n/fr/admin.php
@@ -71,8 +71,8 @@ return array(
'ok' => 'Vous disposez du nécessaire pour les expressions régulières (PCRE).',
),
'pdo' => array(
- 'nok' => 'Vous ne disposez pas de PDO ou d’un des drivers supportés (pdo_mysql, pdo_sqlite).',
- 'ok' => 'Vous disposez de PDO et d’au moins un des drivers supportés (pdo_mysql, pdo_sqlite).',
+ 'nok' => 'Vous ne disposez pas de PDO ou d’un des drivers supportés (pdo_mysql, pdo_sqlite, pdo_pgsql).',
+ 'ok' => 'Vous disposez de PDO et d’au moins un des drivers supportés (pdo_mysql, pdo_sqlite, pdo_pgsql).',
),
'php' => array(
'_' => 'Installation de PHP',
diff --git a/app/i18n/fr/install.php b/app/i18n/fr/install.php
index 946a210ee..3b1dfb2f1 100644
--- a/app/i18n/fr/install.php
+++ b/app/i18n/fr/install.php
@@ -73,8 +73,8 @@ return array(
'ok' => 'Vous disposez du nécessaire pour les expressions régulières (PCRE).',
),
'pdo' => array(
- 'nok' => 'Vous ne disposez pas de PDO ou d’un des drivers supportés (pdo_mysql, pdo_sqlite).',
- 'ok' => 'Vous disposez de PDO et d’au moins un des drivers supportés (pdo_mysql, pdo_sqlite).',
+ 'nok' => 'Vous ne disposez pas de PDO ou d’un des drivers supportés (pdo_mysql, pdo_sqlite, pdo_pgsql).',
+ 'ok' => 'Vous disposez de PDO et d’au moins un des drivers supportés (pdo_mysql, pdo_sqlite, pdo_pgsql).',
),
'php' => array(
'nok' => 'Votre version de PHP est la %s mais FreshRSS requiert au moins la version %s.',
diff --git a/app/i18n/it/admin.php b/app/i18n/it/admin.php
index 4eea158f6..475865984 100644
--- a/app/i18n/it/admin.php
+++ b/app/i18n/it/admin.php
@@ -71,8 +71,8 @@ return array(
'ok' => 'Libreria richiesta per le regular expressions presente (PCRE).',
),
'pdo' => array(
- 'nok' => 'Manca PDO o uno degli altri driver supportati (pdo_mysql, pdo_sqlite).',
- 'ok' => 'PDO e altri driver supportati (pdo_mysql, pdo_sqlite).',
+ 'nok' => 'Manca PDO o uno degli altri driver supportati (pdo_mysql, pdo_sqlite, pdo_pgsql).',
+ 'ok' => 'PDO e altri driver supportati (pdo_mysql, pdo_sqlite, pdo_pgsql).',
),
'php' => array(
'_' => 'Installazione PHP',
diff --git a/app/i18n/it/install.php b/app/i18n/it/install.php
index a60dd4523..e7ea61d23 100644
--- a/app/i18n/it/install.php
+++ b/app/i18n/it/install.php
@@ -73,8 +73,8 @@ return array(
'ok' => 'Libreria richiesta per le regular expressions presente (PCRE).',
),
'pdo' => array(
- 'nok' => 'Manca PDO o uno degli altri driver supportati (pdo_mysql, pdo_sqlite).',
- 'ok' => 'PDO e altri driver supportati (pdo_mysql, pdo_sqlite).',
+ 'nok' => 'Manca PDO o uno degli altri driver supportati (pdo_mysql, pdo_sqlite, pdo_pgsql).',
+ 'ok' => 'PDO e altri driver supportati (pdo_mysql, pdo_sqlite, pdo_pgsql).',
),
'php' => array(
'_' => 'Installazione PHP',
diff --git a/app/i18n/nl/admin.php b/app/i18n/nl/admin.php
index 9f05d69b1..f0296b827 100644
--- a/app/i18n/nl/admin.php
+++ b/app/i18n/nl/admin.php
@@ -71,8 +71,8 @@ return array(
'ok' => 'U hebt de benodigde bibliotheek voor regular expressions (PCRE).',
),
'pdo' => array(
- 'nok' => 'U mist PDO of een van de ondersteunde drivers (pdo_mysql, pdo_sqlite).',
- 'ok' => 'U hebt PDO en ten minste één van de ondersteunde drivers (pdo_mysql, pdo_sqlite).',
+ 'nok' => 'U mist PDO of een van de ondersteunde drivers (pdo_mysql, pdo_sqlite, pdo_pgsql).',
+ 'ok' => 'U hebt PDO en ten minste één van de ondersteunde drivers (pdo_mysql, pdo_sqlite, pdo_pgsql).',
),
'php' => array(
'_' => 'PHP installatie',
diff --git a/app/i18n/nl/install.php b/app/i18n/nl/install.php
index 77783cd48..6aac1db20 100644
--- a/app/i18n/nl/install.php
+++ b/app/i18n/nl/install.php
@@ -73,8 +73,8 @@ return array(
'ok' => 'U hebt de benodigde bibliotheek voor regular expressions (PCRE).',
),
'pdo' => array(
- 'nok' => 'U mist PDO of één van de ondersteunde (pdo_mysql, pdo_sqlite).',
- 'ok' => 'U hebt PDO en ten minste één van de ondersteunde drivers (pdo_mysql, pdo_sqlite).',
+ 'nok' => 'U mist PDO of één van de ondersteunde (pdo_mysql, pdo_sqlite, pdo_pgsql).',
+ 'ok' => 'U hebt PDO en ten minste één van de ondersteunde drivers (pdo_mysql, pdo_sqlite, pdo_pgsql).',
),
'php' => array(
'nok' => 'Uw PHP versie is %s maar FreshRSS benodigd tenminste versie %s.',
diff --git a/app/i18n/ru/admin.php b/app/i18n/ru/admin.php
index caea627f3..9bc241f96 100644
--- a/app/i18n/ru/admin.php
+++ b/app/i18n/ru/admin.php
@@ -71,8 +71,8 @@ return array(
'ok' => 'У вас установлена необходимая библиотека для работы с регулярными выражениями (PCRE).',
),
'pdo' => array(
- 'nok' => 'У вас не установлен PDO или один из необходимых драйверов (pdo_mysql, pdo_sqlite).',
- 'ok' => 'У вас установлен PDO и как минимум один из поддерживаемых драйверов (pdo_mysql, pdo_sqlite).',
+ 'nok' => 'У вас не установлен PDO или один из необходимых драйверов (pdo_mysql, pdo_sqlite, pdo_pgsql).',
+ 'ok' => 'У вас установлен PDO и как минимум один из поддерживаемых драйверов (pdo_mysql, pdo_sqlite, pdo_pgsql).',
),
'php' => array(
'_' => 'PHP installation',
diff --git a/app/i18n/ru/install.php b/app/i18n/ru/install.php
index a52e2959b..3a808d544 100644
--- a/app/i18n/ru/install.php
+++ b/app/i18n/ru/install.php
@@ -69,8 +69,8 @@ return array(
'ok' => 'У вас установлена необходимая библиотека для работы с регулярными выражениями (PCRE).',
),
'pdo' => array(
- 'nok' => 'У вас не установлен PDO или один из необходимых драйверов (pdo_mysql, pdo_sqlite).',
- 'ok' => 'У вас установлен PDO и как минимум один из поддерживаемых драйверов (pdo_mysql, pdo_sqlite).',
+ 'nok' => 'У вас не установлен PDO или один из необходимых драйверов (pdo_mysql, pdo_sqlite, pdo_pgsql).',
+ 'ok' => 'У вас установлен PDO и как минимум один из поддерживаемых драйверов (pdo_mysql, pdo_sqlite, pdo_pgsql).',
),
'php' => array(
'nok' => 'У вас установлен PHP версии %s, но FreshRSS необходима версия не ниже %s.',
diff --git a/app/i18n/tr/admin.php b/app/i18n/tr/admin.php
index 43f8e23c5..4e0f755d9 100644
--- a/app/i18n/tr/admin.php
+++ b/app/i18n/tr/admin.php
@@ -71,8 +71,8 @@ return array(
'ok' => 'Düzenli ifadeler kütüphanesi sorunsuz (PCRE).',
),
'pdo' => array(
- 'nok' => 'PDO veya PDO destekli bir sürücü eksik (pdo_mysql, pdo_sqlite).',
- 'ok' => 'PDO sorunsuz (pdo_mysql, pdo_sqlite).',
+ 'nok' => 'PDO veya PDO destekli bir sürücü eksik (pdo_mysql, pdo_sqlite, pdo_pgsql).',
+ 'ok' => 'PDO sorunsuz (pdo_mysql, pdo_sqlite, pdo_pgsql).',
),
'php' => array(
'_' => 'PHP kurulumu',
diff --git a/app/i18n/tr/install.php b/app/i18n/tr/install.php
index 951a7c5fd..8302a95f4 100644
--- a/app/i18n/tr/install.php
+++ b/app/i18n/tr/install.php
@@ -73,8 +73,8 @@ return array(
'ok' => 'Düzenli ifadeler kütüphanesi sorunsuz (PCRE).',
),
'pdo' => array(
- 'nok' => 'PDO veya PDO destekli bir sürücü eksik (pdo_mysql, pdo_sqlite).',
- 'ok' => 'PDO sorunsuz (pdo_mysql, pdo_sqlite).',
+ 'nok' => 'PDO veya PDO destekli bir sürücü eksik (pdo_mysql, pdo_sqlite, pdo_pgsql).',
+ 'ok' => 'PDO sorunsuz (pdo_mysql, pdo_sqlite, pdo_pgsql).',
),
'php' => array(
'nok' => 'PHP versiyonunuz %s fakat FreshRSS için gerekli olan en düşük sürüm %s.',
diff --git a/app/install.php b/app/install.php
index dad3535dc..1972379e5 100644
--- a/app/install.php
+++ b/app/install.php
@@ -19,8 +19,6 @@ if (isset($_GET['step'])) {
define('STEP', 0);
}
-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'];
}
@@ -33,6 +31,9 @@ if (isset($_SESSION['bd_type'])) {
case 'sqlite':
include(APP_PATH . '/SQL/install.sql.sqlite.php');
break;
+ case 'pgsql':
+ include(APP_PATH . '/SQL/install.sql.pgsql.php');
+ break;
}
}
@@ -199,6 +200,9 @@ function saveStep3() {
$_SESSION['bd_prefix'] = substr($_POST['prefix'], 0, 16);
$_SESSION['bd_prefix_user'] = $_SESSION['bd_prefix'] . (empty($_SESSION['default_user']) ? '' : ($_SESSION['default_user'] . '_'));
}
+ if ($_SESSION['bd_type'] === 'pgsql') {
+ $_SESSION['bd_base'] = strtolower($_SESSION['bd_base']);
+ }
// We use dirname to remove the /i part
$base_url = dirname(Minz_Request::guessBaseUrl());
@@ -235,26 +239,6 @@ function saveStep3() {
invalidateHttpCache();
}
-function newPdo() {
- switch ($_SESSION['bd_type']) {
- case 'mysql':
- $str = 'mysql:host=' . $_SESSION['bd_host'] . ';dbname=' . $_SESSION['bd_base'];
- $driver_options = array(
- PDO::MYSQL_ATTR_INIT_COMMAND => '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'));
@@ -301,7 +285,8 @@ function checkStep1() {
$curl = extension_loaded('curl');
$pdo_mysql = extension_loaded('pdo_mysql');
$pdo_sqlite = extension_loaded('pdo_sqlite');
- $pdo = $pdo_mysql || $pdo_sqlite;
+ $pdo_pgsql = extension_loaded('pdo_pgsql');
+ $pdo = $pdo_mysql || $pdo_sqlite || $pdo_pgsql;
$pcre = extension_loaded('pcre');
$ctype = extension_loaded('ctype');
$dom = class_exists('DOMDocument');
@@ -319,6 +304,7 @@ function checkStep1() {
'curl' => $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 +421,23 @@ 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'] . ';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
+ $str = 'pgsql:host=' . $_SESSION['bd_host'] . ';dbname=' . $_SESSION['bd_base'];
+ break;
default:
return false;
}
@@ -690,7 +693,7 @@ function printStep3() {
<p class="alert alert-error"><span class="alert-head"><?php echo _t('gen.short.damn'); ?></span> <?php echo _t('install.bdd.conf.ko'),(empty($_SESSION['bd_error']) ? '' : ' : ' . $_SESSION['bd_error']); ?></p>
<?php } ?>
- <form action="index.php?step=3" method="post">
+ <form action="index.php?step=3" method="post" autocomplete="off">
<legend><?php echo _t('install.bdd.conf'); ?></legend>
<div class="form-group">
<label class="group-name" for="type"><?php echo _t('install.bdd.type'); ?></label>
@@ -708,6 +711,12 @@ function printStep3() {
SQLite
</option>
<?php }?>
+ <?php if (extension_loaded('pdo_pgsql')) {?>
+ <option value="pgsql"
+ <?php echo(isset($_SESSION['bd_type']) && $_SESSION['bd_type'] === 'pgsql') ? 'selected="selected"' : ''; ?>>
+ PostgreSQL (⚠️ experimental)
+ </option>
+ <?php }?>
</select>
</div>
</div>
@@ -730,7 +739,7 @@ function printStep3() {
<div class="form-group">
<label class="group-name" for="pass"><?php echo _t('install.bdd.password'); ?></label>
<div class="group-controls">
- <input type="password" id="pass" name="pass" value="<?php echo isset($_SESSION['bd_password']) ? $_SESSION['bd_password'] : ''; ?>" tabindex="4" />
+ <input type="password" id="pass" name="pass" value="<?php echo isset($_SESSION['bd_password']) ? $_SESSION['bd_password'] : ''; ?>" tabindex="4" autocomplete="off" />
</div>
</div>
diff --git a/app/views/stats/index.phtml b/app/views/stats/index.phtml
index 0a2fbdb10..a36f812a8 100644
--- a/app/views/stats/index.phtml
+++ b/app/views/stats/index.phtml
@@ -23,18 +23,18 @@
</tr>
<tr>
<th><?php echo _t('admin.stats.status_read'); ?></th>
- <td class="numeric"><?php echo format_number($this->repartition['main_stream']['read']); ?></td>
- <td class="numeric"><?php echo format_number($this->repartition['all_feeds']['read']); ?></td>
+ <td class="numeric"><?php echo format_number($this->repartition['main_stream']['count_reads']); ?></td>
+ <td class="numeric"><?php echo format_number($this->repartition['all_feeds']['count_reads']); ?></td>
</tr>
<tr>
<th><?php echo _t('admin.stats.status_unread'); ?></th>
- <td class="numeric"><?php echo format_number($this->repartition['main_stream']['unread']); ?></td>
- <td class="numeric"><?php echo format_number($this->repartition['all_feeds']['unread']); ?></td>
+ <td class="numeric"><?php echo format_number($this->repartition['main_stream']['count_unreads']); ?></td>
+ <td class="numeric"><?php echo format_number($this->repartition['all_feeds']['count_unreads']); ?></td>
</tr>
<tr>
<th><?php echo _t('admin.stats.status_favorites'); ?></th>
- <td class="numeric"><?php echo format_number($this->repartition['main_stream']['favorite']); ?></td>
- <td class="numeric"><?php echo format_number($this->repartition['all_feeds']['favorite']); ?></td>
+ <td class="numeric"><?php echo format_number($this->repartition['main_stream']['count_favorites']); ?></td>
+ <td class="numeric"><?php echo format_number($this->repartition['all_feeds']['count_favorites']); ?></td>
</tr>
</tbody>
</table>
diff --git a/app/views/stats/repartition.phtml b/app/views/stats/repartition.phtml
index ffb2c361e..5ebcdce5a 100644
--- a/app/views/stats/repartition.phtml
+++ b/app/views/stats/repartition.phtml
@@ -12,7 +12,7 @@
if (!empty($feeds)) {
echo '<optgroup label="', $category->name(), '">';
foreach ($feeds as $feed) {
- if ($this->feed && $feed->id() == $this->feed->id()){
+ if ($this->feed && $feed->id() == $this->feed->id()) {
echo '<option value="', $feed->id(), '" selected="selected" data-url="', _url('stats', 'repartition', 'id', $feed->id()), '">', $feed->name(), '</option>';
} else {
echo '<option value="', $feed->id(), '" data-url="', _url('stats', 'repartition', 'id', $feed->id()), '">', $feed->name(), '</option>';
@@ -39,9 +39,9 @@
</tr>
<tr>
<td class="numeric"><?php echo $this->repartition['total']; ?></td>
- <td class="numeric"><?php echo $this->repartition['read']; ?></td>
- <td class="numeric"><?php echo $this->repartition['unread']; ?></td>
- <td class="numeric"><?php echo $this->repartition['favorite']; ?></td>
+ <td class="numeric"><?php echo $this->repartition['count_reads']; ?></td>
+ <td class="numeric"><?php echo $this->repartition['count_unreads']; ?></td>
+ <td class="numeric"><?php echo $this->repartition['count_favorites']; ?></td>
</tr>
</table>
</div>
diff --git a/lib/Minz/ModelPdo.php b/lib/Minz/ModelPdo.php
index f82045df9..da28909df 100644
--- a/lib/Minz/ModelPdo.php
+++ b/lib/Minz/ModelPdo.php
@@ -56,39 +56,41 @@ class Minz_ModelPdo {
$dbServer = parse_url('db://' . $db['host']);
try {
- $type = $db['type'];
- if ($type === 'mysql') {
- $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') {
- $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=' . $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 . '_';
+ $this->bd = new MinzPDOMySql($string, $db['user'], $db['password'], $driver_options);
+ break;
+ case 'sqlite':
+ $string = 'sqlite:' . join_path(DATA_PATH, 'users', $currentUser, 'db.sqlite');
+ $this->prefix = '';
+ $this->bd = new MinzPDOMSQLite($string, $db['user'], $db['password'], $driver_options);
+ $this->bd->exec('PRAGMA foreign_keys = ON;');
+ break;
+ case 'pgsql':
+ $string = 'pgsql:host=' . $dbServer['host'] . ';dbname=' . $db['base'];
+ if (!empty($dbServer['port'])) {
+ $string .= ';port=' . $dbServer['port'];
+ }
+ $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,
@@ -123,18 +125,43 @@ class MinzPDO extends PDO {
}
}
+ protected function compatibility($statement) {
+ return $statement;
+ }
+
public function prepare($statement, $driver_options = array()) {
MinzPDO::check($statement);
+ $statement = $this->compatibility($statement);
return parent::prepare($statement, $driver_options);
}
public function exec($statement) {
MinzPDO::check($statement);
+ $statement = $this->compatibility($statement);
return parent::exec($statement);
}
public function query($statement) {
MinzPDO::check($statement);
+ $statement = $this->compatibility($statement);
return parent::query($statement);
}
}
+
+class MinzPDOMySql extends MinzPDO {
+ public function lastInsertId($name = null) {
+ return parent::lastInsertId(); //We discard the name, only used by PostgreSQL
+ }
+}
+
+class MinzPDOMSQLite extends MinzPDO {
+ 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('`', ' LIKE '), array('"', ' ILIKE '), $statement);
+ }
+}
diff --git a/p/scripts/install.js b/p/scripts/install.js
index d7f4e7b02..b7975fd6e 100644
--- a/p/scripts/install.js
+++ b/p/scripts/install.js
@@ -43,13 +43,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';
}
}
}