diff --git a/.tests/fixtures/config_0_6.php b/.tests/fixtures/config_0_6.php
new file mode 100644
index 0000000..8e0373a
--- /dev/null
+++ b/.tests/fixtures/config_0_6.php
@@ -0,0 +1,40 @@
+.
+ */
+
+$mapapi = "testApi";
+$ol_layers["TestLayer"] = "https://test_tile.png";
+$init_latitude = 12.34;
+$init_longitude = 12.34;
+$gkey = "testGoogleKey";
+$dbdsn = "";
+$dbuser = "";
+$dbpass = "";
+$dbprefix = "";
+$require_authentication = 1;
+$public_tracks = 0;
+$admin_user = "admin";
+$pass_lenmin = 12;
+$pass_strength = 2;
+$interval = 1234;
+$lang = "pl";
+$units = "imperial";
+$strokeWeight = 22;
+$strokeColor = "#abcdef";
+$strokeOpacity = 1;
+?>
\ No newline at end of file
diff --git a/.tests/fixtures/fixture_0_6.xml b/.tests/fixtures/fixture_0_6.xml
new file mode 100644
index 0000000..a1a6b34
--- /dev/null
+++ b/.tests/fixtures/fixture_0_6.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/.tests/fixtures/fixture_non_admin.xml b/.tests/fixtures/fixture_non_admin.xml
new file mode 100644
index 0000000..9ace6e0
--- /dev/null
+++ b/.tests/fixtures/fixture_non_admin.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.tests/fixtures/ulogger_0_6.mysql b/.tests/fixtures/ulogger_0_6.mysql
new file mode 100644
index 0000000..1d55d89
--- /dev/null
+++ b/.tests/fixtures/ulogger_0_6.mysql
@@ -0,0 +1,87 @@
+/*
+ * μlogger
+ *
+ * Copyright(C) 2020 Bartek Fabiszewski (www.fabiszewski.net)
+ *
+ * This is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see .
+ */
+
+--
+-- Database schema for μlogger 0.6
+--
+
+-- --------------------------------------------------------
+
+--
+-- Drop tables if exist
+--
+
+DROP TABLE IF EXISTS `positions`;
+DROP TABLE IF EXISTS `tracks`;
+DROP TABLE IF EXISTS `users`;
+DROP TABLE IF EXISTS `config`;
+DROP TABLE IF EXISTS `ol_layers`;
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `users`
+--
+
+CREATE TABLE `users` (
+ `id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ `login` varchar(15) CHARACTER SET latin1 NOT NULL UNIQUE,
+ `password` varchar(255) CHARACTER SET latin1 NOT NULL DEFAULT ''
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `tracks`
+--
+
+CREATE TABLE `tracks` (
+ `id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ `user_id` int(11) NOT NULL,
+ `name` varchar(255) DEFAULT NULL,
+ `comment` varchar(1024) DEFAULT NULL,
+ INDEX `idx_user_id` (`user_id`),
+ FOREIGN KEY(`user_id`) REFERENCES `users`(`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `positions`
+--
+
+CREATE TABLE `positions` (
+ `id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ `time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ `user_id` int(11) NOT NULL,
+ `track_id` int(11) NOT NULL,
+ `latitude` double NOT NULL,
+ `longitude` double NOT NULL,
+ `altitude` double DEFAULT NULL,
+ `speed` double DEFAULT NULL,
+ `bearing` double DEFAULT NULL,
+ `accuracy` int(11) DEFAULT NULL,
+ `provider` varchar(100) DEFAULT NULL,
+ `comment` varchar(255) DEFAULT NULL,
+ `image_id` int(11) DEFAULT NULL,
+ INDEX `idx_ptrack_id` (`track_id`),
+ INDEX `index_puser_id` (`user_id`),
+ FOREIGN KEY(`user_id`) REFERENCES `users`(`id`),
+ FOREIGN KEY(`track_id`) REFERENCES `tracks`(`id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
diff --git a/.tests/fixtures/ulogger_0_6.pgsql b/.tests/fixtures/ulogger_0_6.pgsql
new file mode 100644
index 0000000..616ff62
--- /dev/null
+++ b/.tests/fixtures/ulogger_0_6.pgsql
@@ -0,0 +1,89 @@
+/*
+ * μlogger
+ *
+ * Copyright(C) 2020 Bartek Fabiszewski (www.fabiszewski.net)
+ *
+ * This is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see .
+ */
+
+--
+-- Database schema for μlogger 0.6
+--
+
+-- --------------------------------------------------------
+
+--
+-- Drop tables if exist
+--
+
+DROP TABLE IF EXISTS positions;
+DROP TABLE IF EXISTS tracks;
+DROP TABLE IF EXISTS users;
+DROP TABLE IF EXISTS config;
+DROP TABLE IF EXISTS ol_layers;
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `users`
+--
+
+CREATE TABLE users (
+ id serial PRIMARY KEY,
+ login varchar(15) NOT NULL UNIQUE,
+ password varchar(255) NOT NULL DEFAULT ''
+);
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `tracks`
+--
+
+CREATE TABLE tracks (
+ id serial PRIMARY KEY,
+ user_id int NOT NULL,
+ name varchar(255) DEFAULT NULL,
+ comment varchar(1024) DEFAULT NULL,
+ FOREIGN KEY(user_id) REFERENCES users(id)
+);
+
+CREATE INDEX idx_user_id ON tracks(user_id);
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `positions`
+--
+
+CREATE TABLE positions (
+ id serial PRIMARY KEY,
+ time timestamp(0) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ user_id int NOT NULL,
+ track_id int NOT NULL,
+ latitude double precision NOT NULL,
+ longitude double precision NOT NULL,
+ altitude double precision DEFAULT NULL,
+ speed double precision DEFAULT NULL,
+ bearing double precision DEFAULT NULL,
+ accuracy int DEFAULT NULL,
+ provider varchar(100) DEFAULT NULL,
+ comment varchar(255) DEFAULT NULL,
+ image_id int DEFAULT NULL,
+ FOREIGN KEY(user_id) REFERENCES users(id),
+ FOREIGN KEY(track_id) REFERENCES tracks(id)
+);
+
+CREATE INDEX idx_ptrack_id ON positions(track_id);
+CREATE INDEX idx_puser_id ON positions(user_id);
diff --git a/.tests/fixtures/ulogger_0_6.sqlite b/.tests/fixtures/ulogger_0_6.sqlite
new file mode 100644
index 0000000..5bff253
--- /dev/null
+++ b/.tests/fixtures/ulogger_0_6.sqlite
@@ -0,0 +1,87 @@
+/*
+ * μlogger
+ *
+ * Copyright(C) 2020 Bartek Fabiszewski (www.fabiszewski.net)
+ *
+ * This is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see .
+ */
+
+--
+-- Database schema for μlogger 0.6
+--
+
+-- --------------------------------------------------------
+
+--
+-- Drop tables if exist
+--
+
+DROP TABLE IF EXISTS `positions`;
+DROP TABLE IF EXISTS `tracks`;
+DROP TABLE IF EXISTS `users`;
+DROP TABLE IF EXISTS `config`;
+DROP TABLE IF EXISTS `ol_layers`;
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `users`
+--
+
+CREATE TABLE `users` (
+ `id` integer PRIMARY KEY AUTOINCREMENT,
+ `login` varchar(15) NOT NULL UNIQUE,
+ `password` varchar(255) NOT NULL DEFAULT ''
+);
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `tracks`
+--
+
+CREATE TABLE `tracks` (
+ `id` integer PRIMARY KEY AUTOINCREMENT,
+ `user_id` integer NOT NULL,
+ `name` varchar(255) DEFAULT NULL,
+ `comment` varchar(1024) DEFAULT NULL,
+ FOREIGN KEY(`user_id`) REFERENCES `users`(`id`)
+);
+CREATE INDEX `idx_user_id` ON `tracks`(`user_id`);
+
+-- --------------------------------------------------------
+
+--
+-- Table structure for table `positions`
+--
+
+CREATE TABLE `positions` (
+ `id` integer PRIMARY KEY AUTOINCREMENT,
+ `time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ `user_id` integer NOT NULL,
+ `track_id` integer NOT NULL,
+ `latitude` double NOT NULL,
+ `longitude` double NOT NULL,
+ `altitude` double DEFAULT NULL,
+ `speed` double DEFAULT NULL,
+ `bearing` double DEFAULT NULL,
+ `accuracy` integer DEFAULT NULL,
+ `provider` varchar(100) DEFAULT NULL,
+ `comment` varchar(255) DEFAULT NULL,
+ `image_id` integer DEFAULT NULL,
+ FOREIGN KEY(`user_id`) REFERENCES `users`(`id`),
+ FOREIGN KEY(`track_id`) REFERENCES `tracks`(`id`)
+);
+CREATE INDEX `idx_ptrack_id` ON `positions`(`track_id`);
+CREATE INDEX `idx_puser_id` ON `positions`(`user_id`);
diff --git a/.tests/lib/BaseDatabaseTestCase.php b/.tests/lib/BaseDatabaseTestCase.php
index b97c519..dbdfd33 100644
--- a/.tests/lib/BaseDatabaseTestCase.php
+++ b/.tests/lib/BaseDatabaseTestCase.php
@@ -97,10 +97,10 @@ abstract class BaseDatabaseTestCase extends PHPUnit_Extensions_Database_TestCase
protected function resetAutoincrement($users = 1, $tracks = 1, $positions = 1, $layers = 1) {
if (self::$driver === "pgsql") {
- self::$pdo->exec("ALTER SEQUENCE users_id_seq RESTART WITH $users");
- self::$pdo->exec("ALTER SEQUENCE tracks_id_seq RESTART WITH $tracks");
- self::$pdo->exec("ALTER SEQUENCE positions_id_seq RESTART WITH $positions");
- self::$pdo->exec("ALTER SEQUENCE ol_layers_id_seq RESTART WITH $layers");
+ self::$pdo->exec("ALTER SEQUENCE IF EXISTS users_id_seq RESTART WITH $users");
+ self::$pdo->exec("ALTER SEQUENCE IF EXISTS tracks_id_seq RESTART WITH $tracks");
+ self::$pdo->exec("ALTER SEQUENCE IF EXISTS positions_id_seq RESTART WITH $positions");
+ self::$pdo->exec("ALTER SEQUENCE IF EXISTS ol_layers_id_seq RESTART WITH $layers");
} else if (self::$driver === "sqlite") {
$retry = 1;
do {
@@ -121,6 +121,17 @@ abstract class BaseDatabaseTestCase extends PHPUnit_Extensions_Database_TestCase
}
}
+ /**
+ * Reset connection
+ * Fixes sqlite error when db schema changes in another connection.
+ */
+ protected function resetConnection() {
+ $this->closeConnection($this->conn);
+ $this->conn = null;
+ self::tearDownAfterClass();
+ self::setUpBeforeClass();
+ }
+
/**
* Insert to database from array
*
diff --git a/.tests/lib/UloggerDatabaseTestCase.php b/.tests/lib/UloggerDatabaseTestCase.php
index bf3ed07..b18895c 100644
--- a/.tests/lib/UloggerDatabaseTestCase.php
+++ b/.tests/lib/UloggerDatabaseTestCase.php
@@ -1,5 +1,4 @@
initConfigValues();
}
+ /**
+ * @throws ReflectionException
+ */
+ protected function tearDown() {
+ parent::tearDown();
+ $configClass = new ReflectionClass("uConfig");
+ $configInstance = $configClass->getProperty('instance');
+ $configInstance->setAccessible(true);
+ $configInstance->setValue(null);
+ }
+
protected function getDataSet() {
$this->initConfigValues();
$this->resetAutoincrement();
diff --git a/.tests/tests/MigrateTest.php b/.tests/tests/MigrateTest.php
new file mode 100644
index 0000000..1d46205
--- /dev/null
+++ b/.tests/tests/MigrateTest.php
@@ -0,0 +1,155 @@
+.
+ */
+
+define("SKIP_RUN", true);
+require_once(dirname(__DIR__) . "/../scripts/migrate_to_1_x.php");
+require_once(dirname(__DIR__) . "/lib/UloggerDatabaseTestCase.php");
+
+class MigrateTest extends UloggerDatabaseTestCase {
+
+ protected function tearDown() {
+ if ($this->getName() === "testUpdateSchemas") {
+ self::runSqlScript(dirname(__DIR__) . "/../scripts/ulogger." . $this->getDbDriverName());
+ }
+ parent::tearDown();
+ }
+
+ public function testUpdateSchemas() {
+ self::runSqlScript(dirname(__DIR__) . "/fixtures/ulogger_0_6." . $this->getDbDriverName());
+ $this->loadDataSet("fixture_0_6.xml");
+ $this->assertEquals(1, $this->getConnection()->getRowCount("users"), "Wrong row count");
+ $this->assertNotContains("admin", $this->getConnection()->getMetaData()->getTableColumns("users"));
+ $this->assertContains("image_id", $this->getConnection()->getMetaData()->getTableColumns("positions"));
+ $this->assertNotContains("ol_layers", $this->getConnection()->getMetaData()->getTableNames());
+ $this->assertNotContains("config", $this->getConnection()->getMetaData()->getTableNames());
+ $this->setOutputCallback(static function() {});
+ $ret = updateSchemas();
+ $this->resetConnection();
+ $this->assertEquals(true, $ret, "Function updateSchemas() failed");
+ $this->assertEquals(1, $this->getConnection()->getRowCount("users"), "Wrong row count");
+ $this->assertEquals(1, $this->getConnection()->getRowCount("tracks"), "Wrong row count");
+ $this->assertEquals(1, $this->getConnection()->getRowCount("positions"), "Wrong row count");
+ $this->assertContains("admin", $this->getConnection()->getMetaData()->getTableColumns("users"), "Missing table column");
+ $this->assertContains("image", $this->getConnection()->getMetaData()->getTableColumns("positions"), "Missing table column");
+ $this->assertContains("ol_layers", $this->getConnection()->getMetaData()->getTableNames(), "Missing table");
+ $this->assertContains("config", $this->getConnection()->getMetaData()->getTableNames(), "Missing table");
+ }
+
+ public function testUpdateConfig() {
+ $this->loadDataSet("fixture_non_admin.xml");
+ $this->setOutputCallback(static function() {});
+ $ret = updateConfig(dirname(__DIR__) . "/fixtures/config_0_6.php");
+ $this->assertEquals(true, $ret, "Function updateConfig() failed");
+ // admin user imported from config file
+ $expected = [ "admin" => 1 ];
+ $actual = $this->getConnection()->createQueryTable(
+ "admin",
+ "SELECT admin FROM users WHERE login = 'admin'"
+ );
+ $this->assertTableContains($expected, $actual, "Wrong actual table data");
+ // settings imported from config file
+ $expected = [ "config" => [
+ ["name" => "color_extra", "value" => "s:7:\"#cccccc\";"], // default
+ ["name" => "color_hilite", "value" => "s:7:\"#feff6a\";"], // default
+ ["name" => "color_normal", "value" => "s:7:\"#ffffff\";"], // default
+ ["name" => "color_start", "value" => "s:7:\"#55b500\";"], // default
+ ["name" => "color_stop", "value" => "s:7:\"#ff6a00\";"], // default
+ ["name" => "google_key", "value" => "s:13:\"testGoogleKey\";"],
+ ["name" => "interval_seconds", "value" => "i:1234;"],
+ ["name" => "lang", "value" => "s:2:\"pl\";"],
+ ["name" => "latitude", "value" => "d:12.34;"],
+ ["name" => "longitude", "value" => "d:12.34;"],
+ ["name" => "map_api", "value" => "s:7:\"testApi\";"],
+ ["name" => "pass_lenmin", "value" => "i:12;"],
+ ["name" => "pass_strength", "value" => "i:2;"],
+ ["name" => "public_tracks", "value" => "b:0;"],
+ ["name" => "require_auth", "value" => "b:1;"],
+ ["name" => "stroke_color", "value" => "s:7:\"#abcdef\";"],
+ ["name" => "stroke_opacity", "value" => "i:1;"],
+ ["name" => "stroke_weight", "value" => "i:22;"],
+ ["name" => "units", "value" => "s:8:\"imperial\";"]
+ ]];
+ $actual = $this->getConnection()->createQueryTable(
+ "config",
+ "SELECT name, " . uDb::getInstance()->from_lob("value") . " FROM config ORDER BY name"
+ );
+ $expected = $this->createArrayDataSet($expected)->getTable("config");
+ self::assertTablesEqual($expected, $actual);
+ // layers imported from config file
+ $this->assertEquals(1, $this->getConnection()->getRowCount("ol_layers"), "Wrong row count");
+ $expected = [ "id" => 1, "name" => "TestLayer", "url" => "https://test_tile.png", "priority" => 0 ];
+ $actual = $this->getConnection()->createQueryTable(
+ "ol_layers",
+ "SELECT * FROM ol_layers"
+ );
+ $this->assertTableContains($expected, $actual, "Wrong actual table data");
+ }
+
+ public function testWaitForUser() {
+ $this->setOutputCallback(static function() {});
+ $yes = tmpfile();
+ fwrite($yes, "yes");
+ $ret = waitForUser(stream_get_meta_data($yes)['uri']);
+ fclose($yes);
+ $this->assertEquals(true, $ret, "Wrong return status");
+
+ $no = tmpfile();
+ fwrite($no, "no");
+ $ret = waitForUser(stream_get_meta_data($no)['uri']);
+ fclose($no);
+ $this->assertEquals(false, $ret, "Wrong return status");
+ }
+
+ /**
+ * Run SQL commands from file.
+ * Basic subset only. Multiple commands must not be on the same line.
+ * @param string $path Script path
+ * @throws PDOException
+ */
+ private static function runSqlScript($path) {
+ $script = file_get_contents($path);
+ $count = preg_match_all('/^(?:(?:DROP|CREATE) (?:TABLE|INDEX)|INSERT|PRAGMA|SET) .*?;\s*$/smi', $script, $queries);
+ if ($count) {
+ try {
+ uDb::getInstance()->beginTransaction();
+ foreach ($queries[0] as $query) {
+ uDb::getInstance()->exec($query);
+ }
+ uDb::getInstance()->commit();
+ } catch (PDOException $e) {
+ uDb::getInstance()->rollBack();
+ throw $e;
+ }
+ }
+ }
+
+ private function getDbDriverName() {
+ return uDb::getInstance()->getAttribute(PDO::ATTR_DRIVER_NAME);
+ }
+
+ private function loadDataSet($name) {
+ $this->resetAutoincrement();
+ $dataSet = $this->createFlatXMLDataSet(dirname(__DIR__) . '/fixtures/' . $name);
+ $this->getDatabaseTester()->setDataSet($dataSet);
+ $this->getDatabaseTester()->onSetUp();
+ }
+}
+
+?>
diff --git a/README.md b/README.md
index 954bbef..18740f4 100644
--- a/README.md
+++ b/README.md
@@ -43,13 +43,20 @@ Together with a dedicated [μlogger mobile client](https://github.com/bfabiszews
- Folders `.docker/` and `.tests/` as well as composer files are needed only for development. May be safely removed.
## Upgrade to version 1.x
-- TODO: convert following notes to migration script
-- Database changes:
- - `ALTER TABLE positions CHANGE image_id image VARCHAR(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL`
- - `ALTER TABLE users ADD admin BOOLEAN NOT NULL DEFAULT FALSE AFTER password`
- - new tables for config values: `config` and `ol_layers`, see SQL files in scripts folder, eg. [mysql](https://github.com/bfabiszewski/ulogger-server/blob/master/scripts/ulogger.mysql)
- - modify admin user entry in `users` table: set `admin` to `true`
-- Config file changes: only database setup is defined in config file, see [config.default.php](https://github.com/bfabiszewski/ulogger-server/blob/master/config.default.php) for valid values
+- Incompatible changes include database and config file changes
+- Upgrading manually:
+ - for database changes, see MySQL example script in `scripts/migrate_to_1_x.mysql`
+ - set `admin` column in `users` table to true for admin users
+ - edit configuration from application settings dialog
+- Upgrading with migration script from version 0.6:
+ - create database backup
+ - replace all project files with new ones, but keep old local config file: `config.php`
+ - change directory to application root folder and run migration script from the console: `php scripts/migrate_to_1_x.php`
+ - the script will update database schema and save old config variables to database
+- Additional tasks after manual or script update:
+ - edit your `config.php` file and remove all variables except database settings, see [config.default.php](https://github.com/bfabiszewski/ulogger-server/blob/master/config.default.php) for valid values
+ - make sure `uploads` folder (for images uploaded from client app) is writable by PHP
+ - clear browser cache and restart web page
## Docker
- Run `docker run --name ulogger -p 8080:80 -d bfabiszewski/ulogger` and access `http://localhost:8080` in your browser. Log in with `admin`:`admin` credentials and change default password.
diff --git a/helpers/db.php b/helpers/db.php
index dffd40a..2547087 100644
--- a/helpers/db.php
+++ b/helpers/db.php
@@ -184,6 +184,24 @@ require_once(ROOT_DIR . "/helpers/utils.php");
}
}
+ /**
+ * Returns construct for getting LOB as string
+ * @param string $column Column name
+ * @return string
+ */
+ public function from_lob($column) {
+ switch (self::$driver) {
+ default:
+ case "mysql":
+ case "sqlite":
+ return $column;
+ break;
+ case "pgsql":
+ return "encode($column, 'escape') AS $column";
+ break;
+ }
+ }
+
/**
* Returns function name for getting date-time column value as 'YYYY-MM-DD hh:mm:ss'
* @param string $column
diff --git a/scripts/migrate_to_1_x.mysql b/scripts/migrate_to_1_x.mysql
new file mode 100644
index 0000000..da8a2a0
--- /dev/null
+++ b/scripts/migrate_to_1_x.mysql
@@ -0,0 +1,87 @@
+/*
+ * μlogger
+ *
+ * Copyright(C) 2020 Bartek Fabiszewski (www.fabiszewski.net)
+ *
+ * This is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see .
+ */
+
+--
+-- Table structure for table `config`
+--
+
+CREATE TABLE `config` (
+ `name` varchar(20) PRIMARY KEY,
+ `value` tinyblob NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+--
+-- Data for table `config`
+--
+
+INSERT INTO `config` (`name`, `value`) VALUES
+('color_extra', 's:7:"#cccccc";'),
+('color_hilite', 's:7:"#feff6a";'),
+('color_normal', 's:7:"#ffffff";'),
+('color_start', 's:7:"#55b500";'),
+('color_stop', 's:7:"#ff6a00";'),
+('google_key', 's:0:"";'),
+('interval_seconds', 'i:10;'),
+('lang', 's:2:"en";'),
+('latitude', 'd:52.229999999999997;'),
+('longitude', 'd:21.010000000000002;'),
+('map_api', 's:10:"openlayers";'),
+('pass_lenmin', 'i:10;'),
+('pass_strength', 'i:2;'),
+('public_tracks', 'b:1;'),
+('require_auth', 'b:1;'),
+('stroke_color', 's:7:"#ff0000";'),
+('stroke_opacity', 'd:1;'),
+('stroke_weight', 'i:2;'),
+('units', 's:6:"metric";');
+
+--
+-- Table structure for table `ol_layers`
+--
+
+CREATE TABLE `ol_layers` (
+ `id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ `name` varchar(50) NOT NULL,
+ `url` varchar(255) NOT NULL,
+ `priority` int(11) NOT NULL DEFAULT '0'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8;
+
+--
+-- Data for table `ol_layers`
+--
+
+INSERT INTO `ol_layers` (`id`, `name`, `url`, `priority`) VALUES
+(1, 'OpenCycleMap', 'https://{a-c}.tile.thunderforest.com/cycle/{z}/{x}/{y}.png', 0),
+(2, 'OpenTopoMap', 'https://{a-c}.tile.opentopomap.org/{z}/{x}/{y}.png', 0),
+(3, 'OpenSeaMap', 'https://tiles.openseamap.org/seamark/{z}/{x}/{y}.png', 0),
+(4, 'ESRI', 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', 0),
+(5, 'UMP', 'http://{1-3}.tiles.ump.waw.pl/ump_tiles/{z}/{x}/{y}.png', 0),
+(6, 'Osmapa.pl', 'http://{a-c}.tile.openstreetmap.pl/osmapa.pl/{z}/{x}/{y}.png', 0);
+
+--
+-- Add admin column to `users` table
+--
+
+ALTER TABLE `users` ADD `admin` BOOLEAN NOT NULL DEFAULT FALSE AFTER `password`;
+
+--
+-- Modify image column in `positions` table
+--
+
+ALTER TABLE `positions` CHANGE `image_id` `image` VARCHAR(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL;
diff --git a/scripts/migrate_to_1_x.php b/scripts/migrate_to_1_x.php
new file mode 100644
index 0000000..96621dc
--- /dev/null
+++ b/scripts/migrate_to_1_x.php
@@ -0,0 +1,376 @@
+.
+ */
+
+/*
+ * CLI script for database migration from μlogger version 0.6 to version 1.x.
+ * Database user defined in config file must have privileges to create and modify tables.
+ *
+ * Backup your database before running this script.
+ */
+
+if (PHP_SAPI !== "cli") {
+ die("This script must be called from CLI console");
+}
+
+if (!defined("ROOT_DIR")) { define("ROOT_DIR", dirname(__DIR__)); }
+
+include_once(ROOT_DIR . "/helpers/config.php");
+include_once(ROOT_DIR . "/helpers/db.php");
+
+if (!defined("SKIP_RUN")) {
+ verifyVersion() || exit(1);
+ waitForUser() || exit(0);
+ updateSchemas() || exit(1);
+ updateConfig() || exit(1);
+ exit(0);
+}
+
+/**
+ * Check μlogger version is valid for migration
+ * @return bool True if valid version
+ */
+function verifyVersion() {
+ if (!class_exists("uConfig") || !class_exists("uDb") ||
+ !method_exists("uConfig", "getOfflineInstance") ||
+ strpos(uConfig::getOfflineInstance()->version, '1.') !== 0) {
+ echo "You need μlogger version 1.x to run this script" . PHP_EOL;
+ return false;
+ }
+ return true;
+}
+
+/**
+ * Waits for user confirmation
+ * @param string $input Optional path to read from (for tests)
+ * @return bool True if confirmed
+ */
+function waitForUser($input = "php://stdin") {
+ echo "This script will update database from version 0.6 to 1.x." . PHP_EOL;
+ echo "Creating database backup is recommended before proceeding" . PHP_EOL;
+ echo "Type 'yes' to continue, anything else to abort" . PHP_EOL;
+ $handle = fopen($input, 'rb');
+ $input = fgets($handle);
+ fclose($handle);
+ if (trim($input) !== "yes") {
+ echo "Cancelled by user" . PHP_EOL;
+ return false;
+ }
+ echo PHP_EOL;
+ echo "Starting migration..." . PHP_EOL;
+ return true;
+}
+
+/**
+ * Updates database schemas
+ * @return bool True on success
+ */
+function updateSchemas() {
+ echo "Migrating database schemas..." . PHP_EOL;
+ try {
+ $queries = getQueries();
+ uDb::getInstance()->beginTransaction();
+ foreach ($queries as $query) {
+ uDb::getInstance()->exec($query);
+ }
+ uDb::getInstance()->commit();
+ } catch (PDOException $e) {
+ echo "Database query failed: {$e->getMessage()}" . PHP_EOL;
+ echo "Reverting changes..." . PHP_EOL;
+ uDb::getInstance()->rollBack();
+ return false;
+ }
+ echo PHP_EOL;
+ echo "Database schemas updated successfully" . PHP_EOL;
+ return true;
+}
+
+/**
+ * Update database settings based on current config file
+ * @param string $path Optional path of config (for tests)
+ * @return bool True on success
+ */
+/** @noinspection IssetArgumentExistenceInspection, PhpIncludeInspection */
+function updateConfig($path = ROOT_DIR . "/config.php") {
+ echo "Migrating config to database..." . PHP_EOL;
+ require_once($path);
+ if (isset($admin_user) && !empty($admin_user)) {
+ try {
+ echo "Setting user $admin_user as admin" . PHP_EOL;
+ $query = "UPDATE " . uDb::getInstance()->table('users') . " SET admin = TRUE WHERE login = ?";
+ $stmt = uDb::getInstance()->prepare($query);
+ $stmt->execute([ $admin_user ]);
+ if ($stmt->rowCount() === 0) {
+ echo "User $admin_user not found in database table" . PHP_EOL;
+ echo "Set your admin user manually in users table" . PHP_EOL;
+ }
+ } catch (PDOException $e) {
+ echo "Setting admin user failed: " . $e->getMessage() . PHP_EOL;
+ echo "Set your admin user manually in users table" . PHP_EOL;
+ }
+ }
+ $config = uConfig::getOfflineInstance();
+ if (isset($mapapi) && !empty($mapapi)) {
+ $config->mapApi = $mapapi;
+ }
+ if (isset($ol_layers) && is_array($ol_layers)) {
+ $id = 1;
+ foreach ($ol_layers as $name => $url) {
+ $config->olLayers[] = new uLayer($id++, $name, $url, 0);
+ }
+ }
+ if (isset($init_latitude) && is_numeric($init_latitude)) {
+ $config->initLatitude = $init_latitude;
+ }
+ if (isset($init_longitude) && is_numeric($init_longitude)) {
+ $config->initLongitude = $init_longitude;
+ }
+ if (isset($gkey) && !empty($gkey)) {
+ $config->googleKey = $gkey;
+ }
+ if (isset($require_authentication) && is_numeric($require_authentication)) {
+ $config->requireAuthentication = (bool) $require_authentication;
+ }
+ if (isset($public_tracks) && is_numeric($public_tracks)) {
+ $config->publicTracks = (bool) $public_tracks;
+ }
+ if (isset($pass_lenmin) && is_numeric($pass_lenmin)) {
+ $config->passLenMin = (int) $pass_lenmin;
+ }
+ if (isset($pass_strength) && is_numeric($pass_strength)) {
+ $config->passStrength = (int) $pass_strength;
+ }
+ if (isset($interval) && is_numeric($interval)) {
+ $config->interval = (int) $interval;
+ }
+ if (isset($lang) && !empty($lang)) {
+ $config->lang = $lang;
+ }
+ if (isset($units) && !empty($units)) {
+ $config->units = $units;
+ }
+ if (isset($strokeWeight) && is_numeric($strokeWeight)) {
+ $config->strokeWeight = (int) $strokeWeight;
+ }
+ if (isset($strokeColor) && !empty($strokeColor)) {
+ $config->strokeColor = $strokeColor;
+ }
+ if (isset($strokeOpacity) && is_numeric($strokeOpacity)) {
+ $config->strokeOpacity = $strokeOpacity;
+ }
+ if ($config->save() !== true) {
+ echo "Configuration migration failed. Please update your settings manually from web interface" . PHP_EOL;
+ return false;
+ }
+ echo "Configuration successfully migrated to database" . PHP_EOL;
+ return true;
+}
+
+/**
+ * Get queries array for current db driver
+ * @return array Queries
+ */
+function getQueries() {
+ $dbDriver = uDb::getInstance()->getAttribute(PDO::ATTR_DRIVER_NAME);
+ $tConfig = uDb::getInstance()->table('config');
+ $tLayers = uDb::getInstance()->table('ol_layers');
+ $tUsers = uDb::getInstance()->table('users');
+ $tTracks = uDb::getInstance()->table('tracks');
+ $tPositions = uDb::getInstance()->table('positions');
+ $tPositionsBackup = "{$tPositions}_backup";
+
+ $queries = [];
+ switch ($dbDriver) {
+ case "mysql":
+ $queries[] = "CREATE TABLE `$tConfig` (
+ `name` varchar(20) PRIMARY KEY,
+ `value` tinyblob NOT NULL
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8";
+
+ $queries[] = "INSERT INTO `$tConfig` (`name`, `value`) VALUES
+ ('color_extra', 's:7:\"#cccccc\";'),
+ ('color_hilite', 's:7:\"#feff6a\";'),
+ ('color_normal', 's:7:\"#ffffff\";'),
+ ('color_start', 's:7:\"#55b500\";'),
+ ('color_stop', 's:7:\"#ff6a00\";'),
+ ('google_key', 's:0:\"\";'),
+ ('interval_seconds', 'i:10;'),
+ ('lang', 's:2:\"en\";'),
+ ('latitude', 'd:52.229999999999997;'),
+ ('longitude', 'd:21.010000000000002;'),
+ ('map_api', 's:10:\"openlayers\";'),
+ ('pass_lenmin', 'i:10;'),
+ ('pass_strength', 'i:2;'),
+ ('public_tracks', 'b:1;'),
+ ('require_auth', 'b:1;'),
+ ('stroke_color', 's:7:\"#ff0000\";'),
+ ('stroke_opacity', 'd:1;'),
+ ('stroke_weight', 'i:2;'),
+ ('units', 's:6:\"metric\";')";
+
+ $queries[] = "CREATE TABLE `$tLayers` (
+ `id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ `name` varchar(50) NOT NULL,
+ `url` varchar(255) NOT NULL,
+ `priority` int(11) NOT NULL DEFAULT '0'
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8";
+
+ $queries[] = "INSERT INTO `$tLayers` (`id`, `name`, `url`, `priority`) VALUES
+ (1, 'OpenCycleMap', 'https://{a-c}.tile.thunderforest.com/cycle/{z}/{x}/{y}.png', 0),
+ (2, 'OpenTopoMap', 'https://{a-c}.tile.opentopomap.org/{z}/{x}/{y}.png', 0),
+ (3, 'OpenSeaMap', 'https://tiles.openseamap.org/seamark/{z}/{x}/{y}.png', 0),
+ (4, 'ESRI', 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', 0),
+ (5, 'UMP', 'http://{1-3}.tiles.ump.waw.pl/ump_tiles/{z}/{x}/{y}.png', 0),
+ (6, 'Osmapa.pl', 'http://{a-c}.tile.openstreetmap.pl/osmapa.pl/{z}/{x}/{y}.png', 0);
+ ";
+
+ $queries[] = "ALTER TABLE `$tUsers` ADD `admin` BOOLEAN NOT NULL DEFAULT FALSE AFTER `password`";
+
+ $queries[] = "ALTER TABLE `$tPositions` CHANGE `image_id` `image` VARCHAR(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL";
+
+ break;
+
+ case "pgsql":
+ $queries[] = "CREATE TABLE $tConfig (
+ name varchar(20) PRIMARY KEY,
+ value bytea NOT NULL
+ )";
+
+ $queries[] = "INSERT INTO $tConfig (name, value) VALUES
+ ('color_extra', 's:7:\"#cccccc\";'),
+ ('color_hilite', 's:7:\"#feff6a\";'),
+ ('color_normal', 's:7:\"#ffffff\";'),
+ ('color_start', 's:7:\"#55b500\";'),
+ ('color_stop', 's:7:\"#ff6a00\";'),
+ ('google_key', 's:0:\"\";'),
+ ('interval_seconds', 'i:10;'),
+ ('lang', 's:2:\"en\";'),
+ ('latitude', 'd:52.229999999999997;'),
+ ('longitude', 'd:21.010000000000002;'),
+ ('map_api', 's:10:\"openlayers\";'),
+ ('pass_lenmin', 'i:10;'),
+ ('pass_strength', 'i:2;'),
+ ('public_tracks', 'b:1;'),
+ ('require_auth', 'b:1;'),
+ ('stroke_color', 's:7:\"#ff0000\";'),
+ ('stroke_opacity', 'd:1;'),
+ ('stroke_weight', 'i:2;'),
+ ('units', 's:6:\"metric\";')";
+
+ $queries[] = "CREATE TABLE $tLayers (
+ id serial PRIMARY KEY,
+ name varchar(50) NOT NULL,
+ url varchar(255) NOT NULL,
+ priority int NOT NULL DEFAULT '0'
+ )";
+
+ $queries[] = "INSERT INTO $tLayers (id, name, url, priority) VALUES
+ (1, 'OpenCycleMap', 'https://{a-c}.tile.thunderforest.com/cycle/{z}/{x}/{y}.png', 0),
+ (2, 'OpenTopoMap', 'https://{a-c}.tile.opentopomap.org/{z}/{x}/{y}.png', 0),
+ (3, 'OpenSeaMap', 'https://tiles.openseamap.org/seamark/{z}/{x}/{y}.png', 0),
+ (4, 'ESRI', 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', 0),
+ (5, 'UMP', 'http://{1-3}.tiles.ump.waw.pl/ump_tiles/{z}/{x}/{y}.png', 0),
+ (6, 'Osmapa.pl', 'http://{a-c}.tile.openstreetmap.pl/osmapa.pl/{z}/{x}/{y}.png', 0);
+ ";
+
+ $queries[] = "ALTER TABLE $tUsers ADD COLUMN admin boolean NOT NULL DEFAULT FALSE";
+ $queries[] = "ALTER TABLE $tPositions DROP COLUMN image_id";
+ $queries[] = "ALTER TABLE $tPositions ADD COLUMN image varchar(100) NULL DEFAULT NULL";
+
+ break;
+
+ case "sqlite":
+ $queries[] = "CREATE TABLE `$tConfig` (
+ `name` varchar(20) PRIMARY KEY,
+ `value` tinyblob NOT NULL
+ )";
+
+ $queries[] = "INSERT INTO `$tConfig` (`name`, `value`) VALUES
+ ('color_extra', 's:7:\"#cccccc\";'),
+ ('color_hilite', 's:7:\"#feff6a\";'),
+ ('color_normal', 's:7:\"#ffffff\";'),
+ ('color_start', 's:7:\"#55b500\";'),
+ ('color_stop', 's:7:\"#ff6a00\";'),
+ ('google_key', 's:0:\"\";'),
+ ('interval_seconds', 'i:10;'),
+ ('lang', 's:2:\"en\";'),
+ ('latitude', 'd:52.229999999999997;'),
+ ('longitude', 'd:21.010000000000002;'),
+ ('map_api', 's:10:\"openlayers\";'),
+ ('pass_lenmin', 'i:10;'),
+ ('pass_strength', 'i:2;'),
+ ('public_tracks', 'b:1;'),
+ ('require_auth', 'b:1;'),
+ ('stroke_color', 's:7:\"#ff0000\";'),
+ ('stroke_opacity', 'd:1;'),
+ ('stroke_weight', 'i:2;'),
+ ('units', 's:6:\"metric\";')";
+
+ $queries[] = "CREATE TABLE `$tLayers` (
+ `id` integer PRIMARY KEY AUTOINCREMENT,
+ `name` varchar(50) NOT NULL,
+ `url` varchar(255) NOT NULL,
+ `priority` integer NOT NULL DEFAULT '0'
+ )";
+
+ $queries[] = "INSERT INTO `$tLayers` (`id`, `name`, `url`, `priority`) VALUES
+ (1, 'OpenCycleMap', 'https://{a-c}.tile.thunderforest.com/cycle/{z}/{x}/{y}.png', 0),
+ (2, 'OpenTopoMap', 'https://{a-c}.tile.opentopomap.org/{z}/{x}/{y}.png', 0),
+ (3, 'OpenSeaMap', 'https://tiles.openseamap.org/seamark/{z}/{x}/{y}.png', 0),
+ (4, 'ESRI', 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', 0),
+ (5, 'UMP', 'http://{1-3}.tiles.ump.waw.pl/ump_tiles/{z}/{x}/{y}.png', 0),
+ (6, 'Osmapa.pl', 'http://{a-c}.tile.openstreetmap.pl/osmapa.pl/{z}/{x}/{y}.png', 0);
+ ";
+
+ $queries[] = "ALTER TABLE `$tUsers` ADD `admin` boolean NOT NULL DEFAULT FALSE";
+
+ $queries[] = "PRAGMA foreign_keys=OFF";
+ $queries[] = "CREATE TABLE `$tPositionsBackup` (
+ `id` integer PRIMARY KEY AUTOINCREMENT,
+ `time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ `user_id` integer NOT NULL,
+ `track_id` integer NOT NULL,
+ `latitude` double NOT NULL,
+ `longitude` double NOT NULL,
+ `altitude` double DEFAULT NULL,
+ `speed` double DEFAULT NULL,
+ `bearing` double DEFAULT NULL,
+ `accuracy` integer DEFAULT NULL,
+ `provider` varchar(100) DEFAULT NULL,
+ `comment` varchar(255) DEFAULT NULL,
+ `image` varchar(100) DEFAULT NULL,
+ FOREIGN KEY(`user_id`) REFERENCES `$tUsers`(`id`),
+ FOREIGN KEY(`track_id`) REFERENCES `$tTracks`(`id`)
+ )";
+ $queries[] = "INSERT INTO `$tPositionsBackup` SELECT id, time, user_id, track_id, latitude, longitude, altitude, speed, bearing, accuracy, provider, comment, NULL FROM positions";
+ $queries[] = "DROP TABLE `$tPositions`";
+ $queries[] = "ALTER TABLE `$tPositionsBackup` RENAME TO `$tPositions`";
+ $queries[] = "CREATE INDEX `idx_ptrack_id` ON `$tPositions`(`track_id`)";
+ $queries[] = "CREATE INDEX `idx_puser_id` ON `$tPositions`(`user_id`)";
+ $queries[] = "PRAGMA foreign_keys=ON";
+
+ break;
+
+ default:
+ throw new InvalidArgumentException("Driver not supported");
+ }
+ return $queries;
+}
+
+?>
diff --git a/scripts/setup.php b/scripts/setup.php
index 91a0572..2f2eafd 100644
--- a/scripts/setup.php
+++ b/scripts/setup.php
@@ -29,7 +29,7 @@ if (version_compare(PHP_VERSION, "5.5.0", "<")) {
die("Sorry, ulogger will not work with PHP version lower than 5.5 (you have " . PHP_VERSION . ")");
}
-define("ROOT_DIR", dirname(__DIR__));
+if (!defined("ROOT_DIR")) { define("ROOT_DIR", dirname(__DIR__)); }
require_once(ROOT_DIR . "/helpers/db.php");
require_once(ROOT_DIR . "/helpers/config.php");