diff --git a/docs/NEWS b/docs/NEWS
index 43981f6a..2dde39dd 100644
--- a/docs/NEWS
+++ b/docs/NEWS
@@ -9,6 +9,8 @@ Version 2.0-beta4/5/6 / RC? ()
being used in NON-UTF-8 environments. Now we utilize a
serendipity_specialchars() wrapper call.
+ * Added SQLite3 OO database layer for PHP 5.4+
+
* New personal preference to choose CKEditor toolbar presets.
Presets can be overwritte through a
templates/xxx/admin/ckeditor_custom_config.js if needed.
diff --git a/include/admin/category.inc.php b/include/admin/category.inc.php
index 116be101..828d6a89 100644
--- a/include/admin/category.inc.php
+++ b/include/admin/category.inc.php
@@ -72,7 +72,7 @@ if ($serendipity['GET']['adminAction'] == 'doDelete' && serendipity_checkFormTok
$remaining_cat = (int)$serendipity['POST']['cat']['remaining_catid'];
$category_ranges = serendipity_fetchCategoryRange((int)$serendipity['GET']['cid']);
$category_range = implode(' AND ', $category_ranges);
- if ($serendipity['dbType'] == 'postgres' || $serendipity['dbType'] == 'sqlite' || $serendipity['dbType'] == 'sqlite3' || $serendipity['dbType'] == 'pdo-sqlite') {
+ if ($serendipity['dbType'] == 'postgres' || $serendipity['dbType'] == 'sqlite' || $serendipity['dbType'] == 'sqlite3' || $serendipity['dbType'] == 'sqlite3oo' || $serendipity['dbType'] == 'pdo-sqlite') {
$query = "UPDATE {$serendipity['dbPrefix']}entrycat
SET categoryid={$remaining_cat} WHERE entryid IN
(
diff --git a/include/db/db.inc.php b/include/db/db.inc.php
index b4c966fe..b99d0f86 100644
--- a/include/db/db.inc.php
+++ b/include/db/db.inc.php
@@ -127,6 +127,7 @@ function serendipity_db_get_interval($val, $ival = 900) {
switch($serendipity['dbType']) {
case 'sqlite':
case 'sqlite3':
+ case 'sqlite3oo':
case 'pdo-sqlite':
$interval = $ival;
$ts = time();
diff --git a/include/db/generic.inc.php b/include/db/generic.inc.php
index 1d2bde24..fc9e52df 100644
--- a/include/db/generic.inc.php
+++ b/include/db/generic.inc.php
@@ -213,7 +213,7 @@ function serendipity_db_connect() {
}
$dbName = $serendipity['dbName'];
- if ($serendipity['dbType'] == "pdo-sqlite" || $serendipity['dbType'] == "sqlite3" || $serendipity['dbType'] == "sqlite") {
+ if ($serendipity['dbType'] == "pdo-sqlite" || $serendipity['dbType'] == "sqlite3" || $serendipity['dbType'] == 'sqlite3oo' || $serendipity['dbType'] == "sqlite") {
$dbName .= ".db"; # the old sqlite-wrapper appended this .db to the dbName, keep this for bc
}
@@ -275,6 +275,7 @@ function serendipity_db_schema_import($query) {
break;
case 'sqlite3':
+ case 'sqlite3oo':
case 'pdo-sqlite':
case "sqlite":
static $search = array('{AUTOINCREMENT}', '{PRIMARY}', '{UNSIGNED}', '{FULLTEXT}', '{FULLTEXT_MYSQL}', '{BOOLEAN}', '{UTF_8}', '{TEXT}');
@@ -296,7 +297,7 @@ function serendipity_db_schema_import($query) {
*/
function serendipity_db_probe($hash, &$errs) {
$dbName = $hash['dbName'];
- if ($hash['dbType'] == "pdo-sqlite" || $hash['dbType'] == "sqlite3" || $hash['dbType'] == "sqlite") {
+ if ($hash['dbType'] == "pdo-sqlite" || $hash['dbType'] == "sqlite3" || $hash['dbType'] == "sqlite" || $hash['dbType'] == 'sqlite3oo') {
$dbName .= ".db"; # the old sqlite-wrapper appended this .db to the dbName, keep this for bc
}
diff --git a/include/db/sqlite3oo.inc.php b/include/db/sqlite3oo.inc.php
new file mode 100644
index 00000000..50c9238b
--- /dev/null
+++ b/include/db/sqlite3oo.inc.php
@@ -0,0 +1,411 @@
+changes();
+}
+
+/**
+ * Returns the number of updated rows in a SQL query
+ *
+ * @access public
+ * @return int Number of updated rows
+ */
+function serendipity_db_updated_rows()
+{
+ global $serendipity;
+ // It is unknown whether sqllite returns rows MATCHED or rows UPDATED
+ return $serendipity['dbConn']->changes();
+}
+
+/**
+ * Returns the number of matched rows in a SQL query
+ *
+ * @access public
+ * @return int Number of matched rows
+ */
+function serendipity_db_matched_rows()
+{
+ global $serendipity;
+ // It is unknown whether sqllite returns rows MATCHED or rows UPDATED
+ return $serendipity['dbConn']->changes($serendipity['dbConn']);
+}
+
+/**
+ * Returns the latest INSERT_ID of an SQL INSERT INTO command, for auto-increment columns
+ *
+ * @access public
+ * @return int Value of the auto-increment column
+ */
+function serendipity_db_insert_id()
+{
+ global $serendipity;
+
+ return $serendipity['dbConn']->lastInsertRowID();
+}
+
+/**
+ * Parse result arrays into expected format for further operations
+ *
+ * SQLite does not support to return "e.entryid" within a $row['entryid'] return.
+ * So this function manually iteratse through all result rows and rewrites 'X.yyyy' to 'yyyy'.
+ * Yeah. This sucks. Don't tell me!
+ *
+ * @access private
+ * @param ressource The row ressource handle
+ * @param int Bitmask to tell whether to fetch numerical/associative arrays
+ * @return array Propper array containing the ressource results
+ */
+function serendipity_db_sqlite_fetch_array($res, $type = SQLITE3_BOTH)
+{
+ static $search = array('%00', '%25');
+ static $replace = array("\x00", '%');
+
+ try {
+ $row = $res->fetchArray();
+ } catch (Exception $e) {
+ $row = false;
+ echo "SQLITE-EXCEPTION: " . $e->getMessage() . "\n";
+ }
+
+ if (!is_array($row)) {
+ return $row;
+ }
+
+ /* strip any slashes, correct fieldname */
+ foreach ($row AS $i => $v) {
+ // TODO: If a query of the format 'SELECT a.id, b.text FROM table' is used,
+ // the sqlite extension will give us key indizes 'a.id' and 'b.text'
+ // instead of just 'id' and 'text' like in mysql/postgresql extension.
+ // To fix that, we use a preg-regex; but that is quite performance costy.
+ // Either we always need to use 'SELECT a.id AS id, b.text AS text' in query,
+ // or the sqlite extension may get fixed. :-)
+ $row[preg_replace('@^.+\.(.*)@', '\1', $i)] = str_replace($search, $replace, $v);
+ }
+
+ if ($type == SQLITE3_NUM)
+ $frow = array();
+ else
+ $frow = $row;
+
+ if ($type != SQLITE3_ASSOC) {
+ $i = 0;
+ foreach($row AS $k => $v) {
+ $frow[$i] = $v;
+ $i++;
+ }
+ }
+
+ return $frow;
+}
+
+/**
+ * Assemble and return SQL condition for a "IN (...)" clause
+ *
+ * @access public
+ * @param string table column name
+ * @param array referenced array of values to search for in the "IN (...)" clause
+ * @param string condition of how to associate the different input values of the $search_ids parameter
+ * @return string resulting SQL string
+ */
+function serendipity_db_in_sql($col, &$search_ids, $type = ' OR ') {
+ $sql = array();
+ if (!is_array($search_ids)) {
+ return false;
+ }
+
+ foreach($search_ids AS $id) {
+ $sql[] = $col . ' = ' . $id;
+ }
+
+ $cond = '(' . implode($type, $sql) . ')';
+ return $cond;
+}
+
+/**
+ * Perform a DB Layer SQL query.
+ *
+ * This function returns values dependin on the input parameters and the result of the query.
+ * It can return:
+ * false or a string if there was an error (depends on $expectError),
+ * true if the query succeeded but did not generate any rows
+ * array of field values if it returned a single row and $single is true
+ * array of array of field values if it returned row(s) [stacked array]
+ *
+ * @access public
+ * @param string SQL query to execute
+ * @param boolean Toggle whether the expected result is a single row (TRUE) or multiple rows (FALSE). This affects whether the returned array is 1 or 2 dimensional!
+ * @param string Result type of the array indexing. Can be one of "assoc" (associative), "num" (numerical), "both" (numerical and associative, default)
+ * @param boolean If true, errors will be reported. If false, errors will be ignored.
+ * @param string A possible array key name, so that you can control the multi-dimensional mapping of an array by the key column
+ * @param string A possible array field name, so that you can control the multi-dimensional mapping of an array by the key column and the field value.
+ * @param boolean If true, the executed SQL error is known to fail, and should be disregarded (errors can be ignroed on DUPLICATE INDEX queries and the likes)
+ * @return mixed Returns the result of the SQL query, depending on the input parameters
+ */
+function &serendipity_db_query($sql, $single = false, $result_type = "both", $reportErr = true, $assocKey = false, $assocVal = false, $expectError = false)
+{
+ global $serendipity;
+ $type_map = array(
+ 'assoc' => SQLITE3_ASSOC,
+ 'num' => SQLITE3_NUM,
+ 'both' => SQLITE3_BOTH,
+ 'true' => true,
+ 'false' => false
+ );
+
+ static $debug = false;
+
+ if ($debug) {
+ // Open file and write directly. In case of crashes, the pointer needs to be killed.
+ $fp = @fopen('sqlite.log', 'a');
+ fwrite($fp, '[' . date('d.m.Y H:i') . '] SQLITE QUERY: ' . $sql . "\n\n");
+ fclose($fp);
+ }
+
+ if ($reportErr && !$expectError) {
+ $res = $serendipity['dbConn']->query($sql);
+ } else {
+ $res = @$serendipity['dbConn']->query($sql);
+ }
+
+ if (!$res) {
+ if (!$expectError && !$serendipity['production']) {
+ var_dump($res);
+ var_dump($sql);
+ $msg = "problem with query";
+ return $msg;
+ }
+ if ($debug) {
+ $fp = @fopen('sqlite.log', 'a');
+ fwrite($fp, '[' . date('d.m.Y H:i') . '] [ERROR] ' . "\n\n");
+ fclose($fp);
+ }
+
+ return $type_map['false'];
+ }
+
+
+ if (!preg_match('@^SELECT@imsU', trim($sql))) {
+ // Everything that is not SELECT will not return rows.
+ // SQLite3 OO will always return an object though.
+ if ($serendipity['dbConn']->lastErrorCode() > 0) {
+ echo "SQLITE-ERROR: " . $serendipity['dbConn']->lastErrorMsg() . "
\n";
+ return $type_map['false'];
+ } else {
+ return $type_map['true'];
+ }
+ }
+
+ $rows = array();
+
+ while (($row = serendipity_db_sqlite_fetch_array($res, $type_map[$result_type]))) {
+ if (!empty($assocKey)) {
+ // You can fetch a key-associated array via the two function parameters assocKey and assocVal
+ if (empty($assocVal)) {
+ $rows[$row[$assocKey]] = $row;
+ } else {
+ $rows[$row[$assocKey]] = $row[$assocVal];
+ }
+ } else {
+ $rows[] = $row;
+ }
+ }
+
+ if ($debug) {
+ $fp = @fopen('sqlite.log', 'a');
+ fwrite($fp, '[' . date('d.m.Y H:i') . '] SQLITE RESULT: ' . print_r($rows, true). "\n\n");
+ fclose($fp);
+ }
+
+ if ($single && count($rows) == 1) {
+ return $rows[0];
+ }
+
+ if (count($rows) == 0) {
+ if ($single)
+ return $type_map['false'];
+ return $type_map['true'];
+ }
+
+ return $rows;
+}
+
+/**
+ * Try to connect to the configured Database (during installation)
+ *
+ * @access public
+ * @param array input configuration array, holding the connection info
+ * @param array referenced array which holds the errors that might be encountered
+ * @return boolean return true on success, false on error
+ */
+function serendipity_db_probe($hash, &$errs)
+{
+ global $serendipity;
+
+ $dbName = (isset($hash['sqlitedbName']) ? $hash['sqlitedbName'] : $hash['dbName']);
+
+ if (!class_exists('SQLite3')) {
+ $errs[] = 'SQLite extension not installed. Available on PHP 5.4+.';
+ return false;
+ }
+
+ if (defined('S9Y_DATA_PATH')) {
+ // Shared installations!
+ $dbfile = S9Y_DATA_PATH . $dbName . '.db';
+ } else {
+ $dbfile = $serendipity['serendipityPath'] . $dbName . '.db';
+ }
+
+
+ $serendipity['dbConn'] = new SQLite3($dbfile);
+
+ if ($serendipity['dbConn']) {
+ return true;
+ }
+
+ $errs[] = "Unable to open \"$dbfile\" - check permissions (directory needs to be writeable for webserver)!";
+ return false;
+}
+
+/**
+ * Prepares a Serendipty query input to fully valid SQL. Replaces certain "template" variables.
+ *
+ * @access public
+ * @param string SQL query with template variables to convert
+ * @return ressource SQL ressource handle of the executed query
+ */
+function serendipity_db_schema_import($query)
+{
+ static $search = array('{AUTOINCREMENT}', '{PRIMARY}', '{UNSIGNED}', '{FULLTEXT}', '{BOOLEAN}', '{UTF_8}', '{TEXT}');
+ static $replace = array('INTEGER AUTOINCREMENT', 'PRIMARY KEY', '', '', 'BOOLEAN NOT NULL', '', 'LONGTEXT');
+
+ if (stristr($query, '{FULLTEXT_MYSQL}')) {
+ return true;
+ }
+
+ $query = trim(str_replace($search, $replace, $query));
+ $query = str_replace('INTEGER AUTOINCREMENT PRIMARY KEY', 'INTEGER PRIMARY KEY AUTOINCREMENT', $query);
+ if ($query[0] == '@') {
+ // Errors are expected to happen (like duplicate index creation)
+ return serendipity_db_query(substr($query, 1), false, 'both', false, false, false, true);
+ } else {
+ return serendipity_db_query($query);
+ }
+}
+
+/**
+ * Returns the option to a LIMIT SQL statement, because it varies accross DB systems
+ *
+ * @access public
+ * @param int Number of the first row to return data from
+ * @param int Number of rows to return
+ * @return string SQL string to pass to a LIMIT statement
+ */
+function serendipity_db_limit($start, $offset) {
+ return $start . ', ' . $offset;
+}
+
+/**
+ * Return a LIMIT SQL option to the DB Layer as a full LIMIT statement
+ *
+ * @access public
+ * @param SQL string of a LIMIT option
+ * @return SQL string containing a full LIMIT statement
+ */
+function serendipity_db_limit_sql($limitstring) {
+ return ' LIMIT ' . $limitstring;
+}
+
+/**
+ * Returns the SQL code used for concatenating strings
+ *
+ * @access public
+ * @param string Input string/column to concatenate
+ * @return string SQL parameter
+ */
+function serendipity_db_concat($string) {
+ return 'concat(' . $string . ')';
+}
+
+/* vim: set sts=4 ts=4 expandtab : */
diff --git a/include/functions_config.inc.php b/include/functions_config.inc.php
index 40ca1851..45fd4f38 100644
--- a/include/functions_config.inc.php
+++ b/include/functions_config.inc.php
@@ -898,6 +898,9 @@ function serendipity_probeInstallation($item) {
if (extension_loaded('PDO') &&
in_array('sqlite', PDO::getAvailableDrivers())) {
$res['pdo-sqlite'] = 'PDO::SQLite';
+ $has_pdo = true;
+ } else {
+ $has_pdo = false;
}
if (extension_loaded('pgsql')) {
@@ -912,6 +915,13 @@ function serendipity_probeInstallation($item) {
if (extension_loaded('SQLITE3') && function_exists('sqlite3_open')) {
$res['sqlite3'] = 'SQLite3';
}
+ if (class_exists('SQLite3')) {
+ if ($has_pdo) {
+ $res['sqlite3oo'] = 'SQLite3 (OO) (Preferrably use PDO-SQlite!)';
+ } else {
+ $res['sqlite3oo'] = 'SQLite3 (OO)';
+ }
+ }
if (function_exists('sqlrcon_alloc')) {
$res['sqlrelay'] = 'SQLRelay';
}
diff --git a/include/functions_entries.inc.php b/include/functions_entries.inc.php
index b690f8d1..44fb4a20 100644
--- a/include/functions_entries.inc.php
+++ b/include/functions_entries.inc.php
@@ -782,7 +782,7 @@ function &serendipity_searchEntries($term, $limit = '', $searchresults = '') {
} else {
$cond['find_part'] = "(title ILIKE '%$term%' OR body ILIKE '%$term%' OR extended ILIKE '%$term%')";
}
- } elseif ($serendipity['dbType'] == 'sqlite' || $serendipity['dbType'] == 'sqlite3' || $serendipity['dbType'] == 'pdo-sqlite') {
+ } elseif ($serendipity['dbType'] == 'sqlite' || $serendipity['dbType'] == 'sqlite3' || $serendipity['dbType'] == 'pdo-sqlite' || $serendipity['dbType'] == 'sqlite3oo') {
// Very extensive SQLite search. There currently seems no other way to perform fulltext search in SQLite
// But it's better than no search at all :-D
$term = str_replace('*', '%', $term);
@@ -879,7 +879,7 @@ function &serendipity_searchEntries($term, $limit = '', $searchresults = '') {
//if * wasn't already appended and if there are none or not enough
//results, search again for entries containing the searchterm as a part
- if ($p == 1 && strpos($term, '*') === false && $serendipity['dbType'] != 'sqlite' && $serendipity['dbType'] != 'sqlite3' && $serendipity['dbType'] != 'pdo-sqlite') {
+ if ($p == 1 && strpos($term, '*') === false && $serendipity['dbType'] != 'sqlite' && $serendipity['dbType'] != 'sqlite3' && $serendipity['dbType'] != 'pdo-sqlite' && $serendipity['dbType'] != 'sqlite3oo') {
if (! is_array($search)) {
return serendipity_searchEntries($term.'*', $orig_limit);
} else {
@@ -982,7 +982,7 @@ function serendipity_getTotalEntries() {
global $serendipity;
// The unique query condition was built previously in serendipity_fetchEntries()
- if ($serendipity['dbType'] == 'sqlite' || $serendipity['dbType'] == 'sqlite3' || $serendipity['dbType'] == 'pdo-sqlite') {
+ if ($serendipity['dbType'] == 'sqlite' || $serendipity['dbType'] == 'sqlite3' || $serendipity['dbType'] == 'pdo-sqlite' || $serendipity['dbType'] == 'sqlite3oo') {
$querystring = "SELECT count(e.id) {$serendipity['fullCountQuery']} GROUP BY e.id";
} else {
$querystring = "SELECT count(distinct e.id) {$serendipity['fullCountQuery']}";
@@ -991,7 +991,7 @@ function serendipity_getTotalEntries() {
$query =& serendipity_db_query($querystring);
if (is_array($query) && isset($query[0])) {
- if ($serendipity['dbType'] == 'sqlite' || $serendipity['dbType'] == 'sqlite3' || $serendipity['dbType'] == 'pdo-sqlite') {
+ if ($serendipity['dbType'] == 'sqlite' || $serendipity['dbType'] == 'sqlite3' || $serendipity['dbType'] == 'pdo-sqlite' || $serendipity['dbType'] == 'sqlite3oo') {
return count($query);
} else {
return $query[0][0];
diff --git a/plugins/serendipity_plugin_archives/serendipity_plugin_archives.php b/plugins/serendipity_plugin_archives/serendipity_plugin_archives.php
index 814fe0f2..330491be 100644
--- a/plugins/serendipity_plugin_archives/serendipity_plugin_archives.php
+++ b/plugins/serendipity_plugin_archives/serendipity_plugin_archives.php
@@ -82,7 +82,7 @@ class serendipity_plugin_archives extends serendipity_plugin {
echo '