Feature: image upload (beta)

This commit is contained in:
Bartek Fabiszewski 2019-07-12 21:50:21 +02:00
parent 9f885f6068
commit 460d608095
16 changed files with 334 additions and 109 deletions

View File

@ -36,7 +36,7 @@ abstract class BaseDatabaseTestCase extends PHPUnit_Extensions_Database_TestCase
protected $testAccuracy = 10; protected $testAccuracy = 10;
protected $testProvider = "gps"; protected $testProvider = "gps";
protected $testComment = "test comment"; protected $testComment = "test comment";
protected $testImageId = 1; protected $testImage = "1234_1502974402_5d1a1960335cf.jpg";
// Fixes PostgreSQL: "cannot truncate a table referenced in a foreign key constraint" // Fixes PostgreSQL: "cannot truncate a table referenced in a foreign key constraint"
protected function getSetUpOperation() { protected function getSetUpOperation() {

View File

@ -225,7 +225,7 @@ class ClientAPITest extends UloggerAPITestCase {
'accuracy' => $this->testAccuracy, 'accuracy' => $this->testAccuracy,
'provider' => $this->testProvider, 'provider' => $this->testProvider,
'comment' => $this->testComment, 'comment' => $this->testComment,
'imageid' => $this->testImageId 'imageid' => $this->testImage
], ],
]; ];
$response = $this->http->post('/client/index.php', $options); $response = $this->http->post('/client/index.php', $options);
@ -246,11 +246,11 @@ class ClientAPITest extends UloggerAPITestCase {
"accuracy" => $this->testAccuracy, "accuracy" => $this->testAccuracy,
"provider" => $this->testProvider, "provider" => $this->testProvider,
"comment" => $this->testComment, "comment" => $this->testComment,
"image_id" => $this->testImageId "image" => $this->testImage
]; ];
$actual = $this->getConnection()->createQueryTable( $actual = $this->getConnection()->createQueryTable(
"positions", "positions",
"SELECT id, user_id, track_id, " . $this->unix_timestamp('time') . " AS time, latitude, longitude, altitude, speed, bearing, accuracy, provider, comment, image_id FROM positions" "SELECT id, user_id, track_id, " . $this->unix_timestamp('time') . " AS time, latitude, longitude, altitude, speed, bearing, accuracy, provider, comment, image FROM positions"
); );
$this->assertTableContains($expected, $actual, "Wrong actual table data"); $this->assertTableContains($expected, $actual, "Wrong actual table data");
} }
@ -275,7 +275,7 @@ class ClientAPITest extends UloggerAPITestCase {
'accuracy' => $this->testAccuracy, 'accuracy' => $this->testAccuracy,
'provider' => $this->testProvider, 'provider' => $this->testProvider,
'comment' => $this->testComment, 'comment' => $this->testComment,
'imageid' => $this->testImageId 'imageid' => $this->testImage
], ],
]; ];
$response = $this->http->post('/client/index.php', $options); $response = $this->http->post('/client/index.php', $options);
@ -306,7 +306,7 @@ class ClientAPITest extends UloggerAPITestCase {
'accuracy' => $this->testAccuracy, 'accuracy' => $this->testAccuracy,
'provider' => $this->testProvider, 'provider' => $this->testProvider,
'comment' => $this->testComment, 'comment' => $this->testComment,
'imageid' => $this->testImageId 'imageid' => $this->testImage
], ],
]; ];
@ -343,7 +343,7 @@ class ClientAPITest extends UloggerAPITestCase {
'accuracy' => $this->testAccuracy, 'accuracy' => $this->testAccuracy,
'provider' => $this->testProvider, 'provider' => $this->testProvider,
'comment' => $this->testComment, 'comment' => $this->testComment,
'imageid' => $this->testImageId 'imageid' => $this->testImage
], ],
]; ];

View File

@ -88,12 +88,12 @@ class ImportTest extends UloggerAPITestCase {
"accuracy" => null, "accuracy" => null,
"provider" => "gps", "provider" => "gps",
"comment" => null, "comment" => null,
"image_id" => null "image" => null
]; ];
$actual = $this->getConnection()->createQueryTable( $actual = $this->getConnection()->createQueryTable(
"positions", "positions",
"SELECT id, " . $this->unix_timestamp('time') . " AS time, user_id, track_id, latitude, longitude, "SELECT id, " . $this->unix_timestamp('time') . " AS time, user_id, track_id, latitude, longitude,
altitude, speed, bearing, accuracy, provider, comment, image_id FROM positions" altitude, speed, bearing, accuracy, provider, comment, image FROM positions"
); );
$this->assertTableContains($expected, $actual, "Wrong actual table data"); $this->assertTableContains($expected, $actual, "Wrong actual table data");
@ -110,7 +110,7 @@ class ImportTest extends UloggerAPITestCase {
"accuracy" => null, "accuracy" => null,
"provider" => "gps", "provider" => "gps",
"comment" => null, "comment" => null,
"image_id" => null "image" => null
]; ];
$this->assertTableContains($expected, $actual, "Wrong actual table data"); $this->assertTableContains($expected, $actual, "Wrong actual table data");
} }
@ -204,12 +204,12 @@ class ImportTest extends UloggerAPITestCase {
"accuracy" => null, "accuracy" => null,
"provider" => "gps", "provider" => "gps",
"comment" => null, "comment" => null,
"image_id" => null "image" => null
]; ];
$actual = $this->getConnection()->createQueryTable( $actual = $this->getConnection()->createQueryTable(
"positions", "positions",
"SELECT id, " . $this->unix_timestamp('time') . " AS time, user_id, track_id, latitude, longitude, "SELECT id, " . $this->unix_timestamp('time') . " AS time, user_id, track_id, latitude, longitude,
altitude, speed, bearing, accuracy, provider, comment, image_id FROM positions" altitude, speed, bearing, accuracy, provider, comment, image FROM positions"
); );
$this->assertTableContains($expected, $actual, "Wrong actual table data"); $this->assertTableContains($expected, $actual, "Wrong actual table data");
} }
@ -304,12 +304,12 @@ class ImportTest extends UloggerAPITestCase {
"accuracy" => $this->testAccuracy, "accuracy" => $this->testAccuracy,
"provider" => $this->testProvider, "provider" => $this->testProvider,
"comment" => null, "comment" => null,
"image_id" => null "image" => null
]; ];
$actual = $this->getConnection()->createQueryTable( $actual = $this->getConnection()->createQueryTable(
"positions", "positions",
"SELECT id, " . $this->unix_timestamp('time') . " AS time, user_id, track_id, latitude, longitude, "SELECT id, " . $this->unix_timestamp('time') . " AS time, user_id, track_id, latitude, longitude,
altitude, speed, bearing, accuracy, provider, comment, image_id FROM positions" altitude, speed, bearing, accuracy, provider, comment, image FROM positions"
); );
$this->assertTableContains($expected, $actual, "Wrong actual table data"); $this->assertTableContains($expected, $actual, "Wrong actual table data");
} }
@ -385,12 +385,12 @@ class ImportTest extends UloggerAPITestCase {
"accuracy" => null, "accuracy" => null,
"provider" => "gps", "provider" => "gps",
"comment" => null, "comment" => null,
"image_id" => null "image" => null
]; ];
$actual = $this->getConnection()->createQueryTable( $actual = $this->getConnection()->createQueryTable(
"positions", "positions",
"SELECT id, " . $this->unix_timestamp('time') . " AS time, user_id, track_id, latitude, longitude, "SELECT id, " . $this->unix_timestamp('time') . " AS time, user_id, track_id, latitude, longitude,
altitude, speed, bearing, accuracy, provider, comment, image_id FROM positions" altitude, speed, bearing, accuracy, provider, comment, image FROM positions"
); );
$this->assertTableContains($expected, $actual, "Wrong actual table data"); $this->assertTableContains($expected, $actual, "Wrong actual table data");
} }
@ -472,12 +472,12 @@ class ImportTest extends UloggerAPITestCase {
"accuracy" => null, "accuracy" => null,
"provider" => "gps", "provider" => "gps",
"comment" => null, "comment" => null,
"image_id" => null "image" => null
]; ];
$actual = $this->getConnection()->createQueryTable( $actual = $this->getConnection()->createQueryTable(
"positions", "positions",
"SELECT id, " . $this->unix_timestamp('time') . " AS time, user_id, track_id, latitude, longitude, "SELECT id, " . $this->unix_timestamp('time') . " AS time, user_id, track_id, latitude, longitude,
altitude, speed, bearing, accuracy, provider, comment, image_id FROM positions" altitude, speed, bearing, accuracy, provider, comment, image FROM positions"
); );
$this->assertTableContains($expected, $actual, "Wrong actual table data"); $this->assertTableContains($expected, $actual, "Wrong actual table data");
$expected = [ $expected = [
@ -493,7 +493,7 @@ class ImportTest extends UloggerAPITestCase {
"accuracy" => null, "accuracy" => null,
"provider" => "gps", "provider" => "gps",
"comment" => null, "comment" => null,
"image_id" => null "image" => null
]; ];
$this->assertTableContains($expected, $actual, "Wrong actual table data"); $this->assertTableContains($expected, $actual, "Wrong actual table data");
} }
@ -584,12 +584,12 @@ class ImportTest extends UloggerAPITestCase {
"accuracy" => null, "accuracy" => null,
"provider" => "gps", "provider" => "gps",
"comment" => null, "comment" => null,
"image_id" => null "image" => null
]; ];
$actual = $this->getConnection()->createQueryTable( $actual = $this->getConnection()->createQueryTable(
"positions", "positions",
"SELECT id, " . $this->unix_timestamp('time') . " AS time, user_id, track_id, latitude, longitude, "SELECT id, " . $this->unix_timestamp('time') . " AS time, user_id, track_id, latitude, longitude,
altitude, speed, bearing, accuracy, provider, comment, image_id FROM positions" altitude, speed, bearing, accuracy, provider, comment, image FROM positions"
); );
$this->assertTableContains($expected, $actual, "Wrong actual table data"); $this->assertTableContains($expected, $actual, "Wrong actual table data");
$expected = [ $expected = [
@ -605,7 +605,7 @@ class ImportTest extends UloggerAPITestCase {
"accuracy" => null, "accuracy" => null,
"provider" => "gps", "provider" => "gps",
"comment" => null, "comment" => null,
"image_id" => null "image" => null
]; ];
$this->assertTableContains($expected, $actual, "Wrong actual table data"); $this->assertTableContains($expected, $actual, "Wrong actual table data");
} }

View File

@ -11,15 +11,15 @@ class PositionTest extends UloggerDatabaseTestCase {
$trackId = $this->addTestTrack($userId); $trackId = $this->addTestTrack($userId);
$this->assertEquals(1, $this->getConnection()->getRowCount('tracks'), "Wrong row count"); $this->assertEquals(1, $this->getConnection()->getRowCount('tracks'), "Wrong row count");
$posId = uPosition::add($userId, $trackId + 1, $this->testTimestamp, $this->testLat, $this->testLon, $this->testAltitude, $this->testSpeed, $this->testBearing, $this->testAccuracy, $this->testProvider, $this->testComment, $this->testImageId); $posId = uPosition::add($userId, $trackId + 1, $this->testTimestamp, $this->testLat, $this->testLon, $this->testAltitude, $this->testSpeed, $this->testBearing, $this->testAccuracy, $this->testProvider, $this->testComment, $this->testImage);
$this->assertEquals(0, $this->getConnection()->getRowCount('positions'), "Wrong row count"); $this->assertEquals(0, $this->getConnection()->getRowCount('positions'), "Wrong row count");
$this->assertFalse($posId, "Adding position with nonexistant track should fail"); $this->assertFalse($posId, "Adding position with nonexistant track should fail");
$posId = uPosition::add($userId + 1, $trackId, $this->testTimestamp, $this->testLat, $this->testLon, $this->testAltitude, $this->testSpeed, $this->testBearing, $this->testAccuracy, $this->testProvider, $this->testComment, $this->testImageId); $posId = uPosition::add($userId + 1, $trackId, $this->testTimestamp, $this->testLat, $this->testLon, $this->testAltitude, $this->testSpeed, $this->testBearing, $this->testAccuracy, $this->testProvider, $this->testComment, $this->testImage);
$this->assertEquals(0, $this->getConnection()->getRowCount('positions'), "Wrong row count"); $this->assertEquals(0, $this->getConnection()->getRowCount('positions'), "Wrong row count");
$this->assertFalse($posId, "Adding position with wrong user should fail"); $this->assertFalse($posId, "Adding position with wrong user should fail");
$posId = uPosition::add($userId, $trackId, $this->testTimestamp, $this->testLat, $this->testLon, $this->testAltitude, $this->testSpeed, $this->testBearing, $this->testAccuracy, $this->testProvider, $this->testComment, $this->testImageId); $posId = uPosition::add($userId, $trackId, $this->testTimestamp, $this->testLat, $this->testLon, $this->testAltitude, $this->testSpeed, $this->testBearing, $this->testAccuracy, $this->testProvider, $this->testComment, $this->testImage);
$this->assertEquals(1, $this->getConnection()->getRowCount('positions'), "Wrong row count"); $this->assertEquals(1, $this->getConnection()->getRowCount('positions'), "Wrong row count");
$expected = [ $expected = [
"id" => $posId, "id" => $posId,
@ -34,11 +34,11 @@ class PositionTest extends UloggerDatabaseTestCase {
"accuracy" => $this->testAccuracy, "accuracy" => $this->testAccuracy,
"provider" => $this->testProvider, "provider" => $this->testProvider,
"comment" => $this->testComment, "comment" => $this->testComment,
"image_id" => $this->testImageId "image" => $this->testImage
]; ];
$actual = $this->getConnection()->createQueryTable( $actual = $this->getConnection()->createQueryTable(
"positions", "positions",
"SELECT id, user_id, track_id, " . $this->unix_timestamp('time') . " AS time, latitude, longitude, altitude, speed, bearing, accuracy, provider, comment, image_id FROM positions" "SELECT id, user_id, track_id, " . $this->unix_timestamp('time') . " AS time, latitude, longitude, altitude, speed, bearing, accuracy, provider, comment, image FROM positions"
); );
$this->assertTableContains($expected, $actual, "Wrong actual table data"); $this->assertTableContains($expected, $actual, "Wrong actual table data");

View File

@ -41,16 +41,16 @@ class TrackTest extends UloggerDatabaseTestCase {
$this->assertEquals(1, $this->getConnection()->getRowCount('tracks'), "Wrong row count"); $this->assertEquals(1, $this->getConnection()->getRowCount('tracks'), "Wrong row count");
$track = new uTrack($trackId + 1); $track = new uTrack($trackId + 1);
$posId = $track->addPosition($userId, $this->testTimestamp, $this->testLat, $this->testLon, $this->testAltitude, $this->testSpeed, $this->testBearing, $this->testAccuracy, $this->testProvider, $this->testComment, $this->testImageId); $posId = $track->addPosition($userId, $this->testTimestamp, $this->testLat, $this->testLon, $this->testAltitude, $this->testSpeed, $this->testBearing, $this->testAccuracy, $this->testProvider, $this->testComment, $this->testImage);
$this->assertEquals(0, $this->getConnection()->getRowCount('positions'), "Wrong row count"); $this->assertEquals(0, $this->getConnection()->getRowCount('positions'), "Wrong row count");
$this->assertFalse($posId, "Adding position with nonexistant track should fail"); $this->assertFalse($posId, "Adding position with nonexistant track should fail");
$track = new uTrack($trackId); $track = new uTrack($trackId);
$posId = $track->addPosition($userId2, $this->testTimestamp, $this->testLat, $this->testLon, $this->testAltitude, $this->testSpeed, $this->testBearing, $this->testAccuracy, $this->testProvider, $this->testComment, $this->testImageId); $posId = $track->addPosition($userId2, $this->testTimestamp, $this->testLat, $this->testLon, $this->testAltitude, $this->testSpeed, $this->testBearing, $this->testAccuracy, $this->testProvider, $this->testComment, $this->testImage);
$this->assertEquals(0, $this->getConnection()->getRowCount('positions'), "Wrong row count"); $this->assertEquals(0, $this->getConnection()->getRowCount('positions'), "Wrong row count");
$this->assertFalse($posId, "Adding position with wrong user should fail"); $this->assertFalse($posId, "Adding position with wrong user should fail");
$posId = $track->addPosition($userId, $this->testTimestamp, $this->testLat, $this->testLon, $this->testAltitude, $this->testSpeed, $this->testBearing, $this->testAccuracy, $this->testProvider, $this->testComment, $this->testImageId); $posId = $track->addPosition($userId, $this->testTimestamp, $this->testLat, $this->testLon, $this->testAltitude, $this->testSpeed, $this->testBearing, $this->testAccuracy, $this->testProvider, $this->testComment, $this->testImage);
$this->assertEquals(1, $this->getConnection()->getRowCount('positions'), "Wrong row count"); $this->assertEquals(1, $this->getConnection()->getRowCount('positions'), "Wrong row count");
$expected = [ $expected = [
"id" => $posId, "id" => $posId,
@ -65,11 +65,11 @@ class TrackTest extends UloggerDatabaseTestCase {
"accuracy" => $this->testAccuracy, "accuracy" => $this->testAccuracy,
"provider" => $this->testProvider, "provider" => $this->testProvider,
"comment" => $this->testComment, "comment" => $this->testComment,
"image_id" => $this->testImageId "image" => $this->testImage
]; ];
$actual = $this->getConnection()->createQueryTable( $actual = $this->getConnection()->createQueryTable(
"positions", "positions",
"SELECT id, user_id, track_id, " . $this->unix_timestamp('time') . " AS time, latitude, longitude, altitude, speed, bearing, accuracy, provider, comment, image_id FROM positions" "SELECT id, user_id, track_id, " . $this->unix_timestamp('time') . " AS time, latitude, longitude, altitude, speed, bearing, accuracy, provider, comment, image FROM positions"
); );
$this->assertTableContains($expected, $actual, "Wrong actual table data"); $this->assertTableContains($expected, $actual, "Wrong actual table data");

View File

@ -55,7 +55,7 @@
exitWithError("Unauthorized"); exitWithError("Unauthorized");
} }
switch ($action) { switch ($action) {
// action: authorize // action: authorize
case "auth": case "auth":
$login = uUtils::postString('user'); $login = uUtils::postString('user');
@ -111,16 +111,21 @@
$accuracy = uUtils::postInt('accuracy'); $accuracy = uUtils::postInt('accuracy');
$provider = uUtils::postString('provider'); $provider = uUtils::postString('provider');
$comment = uUtils::postString('comment'); $comment = uUtils::postString('comment');
$imageId = uUtils::postInt('imageid'); $imageMeta = uUtils::requestFile('image');
$trackId = uUtils::postInt('trackid'); $trackId = uUtils::postInt('trackid');
if (!is_float($lat) || !is_float($lon) || !is_int($timestamp) || !is_int($trackId)) { if (!is_float($lat) || !is_float($lon) || !is_int($timestamp) || !is_int($trackId)) {
exitWithError("Missing required parameter"); exitWithError("Missing required parameter");
} }
$image = null;
if (!empty($imageMeta)) {
$image = uUpload::add($imageMeta, $trackId);
}
require_once(ROOT_DIR . "/helpers/position.php"); require_once(ROOT_DIR . "/helpers/position.php");
$positionId = uPosition::add($auth->user->id, $trackId, $positionId = uPosition::add($auth->user->id, $trackId,
$timestamp, $lat, $lon, $altitude, $speed, $bearing, $accuracy, $provider, $comment, $imageId); $timestamp, $lat, $lon, $altitude, $speed, $bearing, $accuracy, $provider, $comment, $image);
if ($positionId === false) { if ($positionId === false) {
exitWithError("Server error"); exitWithError("Server error");

View File

@ -18,7 +18,8 @@
*/ */
require_once(ROOT_DIR . "/helpers/db.php"); require_once(ROOT_DIR . "/helpers/db.php");
require_once(ROOT_DIR . "/helpers/track.php"); require_once(ROOT_DIR . "/helpers/track.php");
require_once(ROOT_DIR . "/helpers/upload.php");
/** /**
* Positions handling * Positions handling
@ -51,9 +52,9 @@
/** @param String Provider */ /** @param String Provider */
public $provider; public $provider;
/** @param String Comment */ /** @param String Comment */
public $comment; // not used yet public $comment;
/** @param int Image id */ /** @param String Image path */
public $imageId; // not used yet public $image;
public $isValid = false; public $isValid = false;
@ -66,7 +67,7 @@
if (!empty($positionId)) { if (!empty($positionId)) {
$query = "SELECT p.id, " . self::db()->unix_timestamp('p.time') . " AS tstamp, p.user_id, p.track_id, $query = "SELECT p.id, " . self::db()->unix_timestamp('p.time') . " AS tstamp, p.user_id, p.track_id,
p.latitude, p.longitude, p.altitude, p.speed, p.bearing, p.accuracy, p.provider, p.latitude, p.longitude, p.altitude, p.speed, p.bearing, p.accuracy, p.provider,
p.comment, p.image_id, u.login, t.name p.comment, p.image, u.login, t.name
FROM " . self::db()->table('positions') . " p FROM " . self::db()->table('positions') . " p
LEFT JOIN " . self::db()->table('users') . " u ON (p.user_id = u.id) LEFT JOIN " . self::db()->table('users') . " u ON (p.user_id = u.id)
LEFT JOIN " . self::db()->table('tracks') . " t ON (p.track_id = t.id) LEFT JOIN " . self::db()->table('tracks') . " t ON (p.track_id = t.id)
@ -104,12 +105,12 @@
* @param int $accuracy Optional * @param int $accuracy Optional
* @param string $provider Optional * @param string $provider Optional
* @param string $comment Optional * @param string $comment Optional
* @param int $imageId Optional * @param int $image Optional
* @return int|bool New position id in database, false on error * @return int|bool New position id in database, false on error
*/ */
public static function add($userId, $trackId, $timestamp, $lat, $lon, public static function add($userId, $trackId, $timestamp, $lat, $lon,
$altitude = NULL, $speed = NULL, $bearing = NULL, $accuracy = NULL, $altitude = NULL, $speed = NULL, $bearing = NULL, $accuracy = NULL,
$provider = NULL, $comment = NULL, $imageId = NULL) { $provider = NULL, $comment = NULL, $image = NULL) {
$positionId = false; $positionId = false;
if (is_numeric($lat) && is_numeric($lon) && is_numeric($timestamp) && is_numeric($userId) && is_numeric($trackId)) { if (is_numeric($lat) && is_numeric($lon) && is_numeric($timestamp) && is_numeric($userId) && is_numeric($trackId)) {
$track = new uTrack($trackId); $track = new uTrack($trackId);
@ -118,11 +119,11 @@
$table = self::db()->table('positions'); $table = self::db()->table('positions');
$query = "INSERT INTO $table $query = "INSERT INTO $table
(user_id, track_id, (user_id, track_id,
time, latitude, longitude, altitude, speed, bearing, accuracy, provider, comment, image_id) time, latitude, longitude, altitude, speed, bearing, accuracy, provider, comment, image)
VALUES (?, ?, " . self::db()->from_unixtime('?') . ", ?, ?, ?, ?, ?, ?, ?, ?, ?)"; VALUES (?, ?, " . self::db()->from_unixtime('?') . ", ?, ?, ?, ?, ?, ?, ?, ?, ?)";
$stmt = self::db()->prepare($query); $stmt = self::db()->prepare($query);
$params = [ $userId, $trackId, $params = [ $userId, $trackId,
$timestamp, $lat, $lon, $altitude, $speed, $bearing, $accuracy, $provider, $comment, $imageId ]; $timestamp, $lat, $lon, $altitude, $speed, $bearing, $accuracy, $provider, $comment, $image ];
$stmt->execute($params); $stmt->execute($params);
$positionId = self::db()->lastInsertId("${table}_id_seq"); $positionId = self::db()->lastInsertId("${table}_id_seq");
} catch (PDOException $e) { } catch (PDOException $e) {
@ -151,6 +152,7 @@
$where .= " AND track_id = ?"; $where .= " AND track_id = ?";
$args[] = $trackId; $args[] = $trackId;
} }
self::removeImages($userId, $trackId);
try { try {
$query = "DELETE FROM " . self::db()->table('positions') . " $where"; $query = "DELETE FROM " . self::db()->table('positions') . " $where";
$stmt = self::db()->prepare($query); $stmt = self::db()->prepare($query);
@ -181,7 +183,7 @@
} }
$query = "SELECT p.id, " . self::db()->unix_timestamp('p.time') . " AS tstamp, p.user_id, p.track_id, $query = "SELECT p.id, " . self::db()->unix_timestamp('p.time') . " AS tstamp, p.user_id, p.track_id,
p.latitude, p.longitude, p.altitude, p.speed, p.bearing, p.accuracy, p.provider, p.latitude, p.longitude, p.altitude, p.speed, p.bearing, p.accuracy, p.provider,
p.comment, p.image_id, u.login, t.name p.comment, p.image, u.login, t.name
FROM " . self::db()->table('positions') . " p FROM " . self::db()->table('positions') . " p
LEFT JOIN " . self::db()->table('users') . " u ON (p.user_id = u.id) LEFT JOIN " . self::db()->table('users') . " u ON (p.user_id = u.id)
LEFT JOIN " . self::db()->table('tracks') . " t ON (p.track_id = t.id) LEFT JOIN " . self::db()->table('tracks') . " t ON (p.track_id = t.id)
@ -205,7 +207,7 @@
public static function getLastAllUsers() { public static function getLastAllUsers() {
$query = "SELECT p.id, " . self::db()->unix_timestamp('p.time') . " AS tstamp, p.user_id, p.track_id, $query = "SELECT p.id, " . self::db()->unix_timestamp('p.time') . " AS tstamp, p.user_id, p.track_id,
p.latitude, p.longitude, p.altitude, p.speed, p.bearing, p.accuracy, p.provider, p.latitude, p.longitude, p.altitude, p.speed, p.bearing, p.accuracy, p.provider,
p.comment, p.image_id, u.login, t.name p.comment, p.image, u.login, t.name
FROM " . self::db()->table('positions') . " p FROM " . self::db()->table('positions') . " p
LEFT JOIN " . self::db()->table('users') . " u ON (p.user_id = u.id) LEFT JOIN " . self::db()->table('users') . " u ON (p.user_id = u.id)
LEFT JOIN " . self::db()->table('tracks') . " t ON (p.track_id = t.id) LEFT JOIN " . self::db()->table('tracks') . " t ON (p.track_id = t.id)
@ -224,6 +226,7 @@
} catch (PDOException $e) { } catch (PDOException $e) {
// TODO: handle exception // TODO: handle exception
syslog(LOG_ERR, $e->getMessage()); syslog(LOG_ERR, $e->getMessage());
$positionsArr = false;
} }
return $positionsArr; return $positionsArr;
} }
@ -234,17 +237,17 @@
* @param int $userId Optional limit to given user id * @param int $userId Optional limit to given user id
* @param int $trackId Optional limit to given track id * @param int $trackId Optional limit to given track id
* @param int $afterId Optional limit to positions with id greater then given id * @param int $afterId Optional limit to positions with id greater then given id
* @return array|bool Array of uPosition positions, false on error * @param array $rules Optional rules
* @return uPosition[]|bool Array of uPosition positions, false on error
*/ */
public static function getAll($userId = NULL, $trackId = NULL, $afterId = NULL) { public static function getAll($userId = NULL, $trackId = NULL, $afterId = NULL, $rules = []) {
$rules = [];
if (!empty($userId)) { if (!empty($userId)) {
$rules[] = "p.user_id = " . self::db()->quote($userId); $rules[] = "p.user_id = " . self::db()->quote($userId);
} }
if (!empty($trackId)) { if (!empty($trackId)) {
$rules[] = "p.track_id = " . self::db()->quote($trackId); $rules[] = "p.track_id = " . self::db()->quote($trackId);
} }
if (!empty($trackId)) { if (!empty($afterId)) {
$rules[] = "p.id > " . self::db()->quote($afterId); $rules[] = "p.id > " . self::db()->quote($afterId);
} }
if (!empty($rules)) { if (!empty($rules)) {
@ -254,7 +257,7 @@
} }
$query = "SELECT p.id, " . self::db()->unix_timestamp('p.time') . " AS tstamp, p.user_id, p.track_id, $query = "SELECT p.id, " . self::db()->unix_timestamp('p.time') . " AS tstamp, p.user_id, p.track_id,
p.latitude, p.longitude, p.altitude, p.speed, p.bearing, p.accuracy, p.provider, p.latitude, p.longitude, p.altitude, p.speed, p.bearing, p.accuracy, p.provider,
p.comment, p.image_id, u.login, t.name p.comment, p.image, u.login, t.name
FROM " . self::db()->table('positions') . " p FROM " . self::db()->table('positions') . " p
LEFT JOIN " . self::db()->table('users') . " u ON (p.user_id = u.id) LEFT JOIN " . self::db()->table('users') . " u ON (p.user_id = u.id)
LEFT JOIN " . self::db()->table('tracks') . " t ON (p.track_id = t.id) LEFT JOIN " . self::db()->table('tracks') . " t ON (p.track_id = t.id)
@ -269,10 +272,53 @@
} catch (PDOException $e) { } catch (PDOException $e) {
// TODO: handle exception // TODO: handle exception
syslog(LOG_ERR, $e->getMessage()); syslog(LOG_ERR, $e->getMessage());
$positionsArr = false;
} }
return $positionsArr; return $positionsArr;
} }
/**
* Get array of all positions with image
*
* @param int $userId Optional limit to given user id
* @param int $trackId Optional limit to given track id
* @param int $afterId Optional limit to positions with id greater then given id
* @param array $rules Optional rules
* @return uPosition[]|bool Array of uPosition positions, false on error
*/
public static function getAllWithImage($userId = NULL, $trackId = NULL, $afterId = NULL, $rules = []) {
$rules[] = "p.image IS NOT NULL";
return self::getAll($userId, $trackId, $afterId, $rules);
}
/**
* Delete all user's uploads, optionally limit to given track
*
* @param int $userId User id
* @param int $trackId Optional track id
* @return bool True if success, false otherwise
*/
public static function removeImages($userId, $trackId = NULL) {
if (($positions = uPosition::getAllWithImage($userId, $trackId)) !== false) {
/** @var uUpload $position */
foreach ($positions as $position) {
try {
$query = "UPDATE " . self::db()->table('positions') . "
SET image = NULL WHERE id = ?";
$stmt = self::db()->prepare($query);
$stmt->execute([ $position->id ]);
// ignore unlink errors
uUpload::delete($position->image);
} catch (PDOException $e) {
// TODO: handle exception
syslog(LOG_ERR, $e->getMessage());
return false;
}
}
}
return true;
}
/** /**
* Calculate distance to target point using haversine formula * Calculate distance to target point using haversine formula
* *
@ -322,7 +368,7 @@
$position->accuracy = $row['accuracy']; $position->accuracy = $row['accuracy'];
$position->provider = $row['provider']; $position->provider = $row['provider'];
$position->comment = $row['comment']; $position->comment = $row['comment'];
$position->imageId = $row['image_id']; $position->image = $row['image'];
$position->isValid = true; $position->isValid = true;
return $position; return $position;
} }
@ -350,7 +396,7 @@
$stmt->bindColumn('accuracy', $this->accuracy, PDO::PARAM_INT); $stmt->bindColumn('accuracy', $this->accuracy, PDO::PARAM_INT);
$stmt->bindColumn('provider', $this->provider); $stmt->bindColumn('provider', $this->provider);
$stmt->bindColumn('comment', $this->comment); $stmt->bindColumn('comment', $this->comment);
$stmt->bindColumn('image_id', $this->imageId, PDO::PARAM_INT); $stmt->bindColumn('image', $this->image);
$stmt->bindColumn('login', $this->userLogin); $stmt->bindColumn('login', $this->userLogin);
$stmt->bindColumn('name', $this->trackName); $stmt->bindColumn('name', $this->trackName);
if ($stmt->fetch(PDO::FETCH_BOUND)) { if ($stmt->fetch(PDO::FETCH_BOUND)) {

166
helpers/upload.php Normal file
View File

@ -0,0 +1,166 @@
<?php
/**
* μlogger
*
* Copyright(C) 2019 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 <http://www.gnu.org/licenses/>.
*/
require_once(ROOT_DIR . "/helpers/db.php");
require_once(ROOT_DIR . "/helpers/utils.php");
/**
* Uploaded files
*/
class uUpload {
const META_TYPE = "type";
const META_NAME = "name";
const META_TMP_NAME = "tmp_name";
const META_ERROR = "error";
const META_SIZE = "size";
public static $uploadDir = ROOT_DIR . "/uploads/";
private static $filePattern = "[a-z0-9_.]{20,}";
private static $mimeMap = [];
/**
* @return string[] Mime to extension mapping
*/
private static function getMimeMap() {
if (empty(self::$mimeMap)) {
self::$mimeMap["image/jpeg"] = "jpg";
self::$mimeMap["image/x-ms-bmp"] = "bmp";
self::$mimeMap["image/gif"] = "gif";
self::$mimeMap["image/png"] = "png";
}
return self::$mimeMap;
}
/**
* Is mime accepted type
* @param string $mime Mime type
* @return bool True if known
*/
private static function isKnownMime($mime) {
return array_key_exists($mime, self::getMimeMap());
}
/**
* Get file extension for given mime
* @param $mime
* @return string|null Extension or NULL if not found
*/
private static function getExtension($mime) {
if (self::isKnownMime($mime)) {
return self::getMimeMap()[$mime];
}
return NULL;
}
/**
* Save file to uploads, basic sanitizing
* @param array $uploaded File meta array from $_FILES[]
* @param int $trackId
* @return string|NULL Unique file name, null on error
*/
public static function add($uploaded, $trackId) {
try {
$fileMeta = self::sanitizeUpload($uploaded);
} catch (Exception $e) {
syslog(LOG_ERR, $e->getMessage());
// save exception to txt file as image replacement?
return NULL;
}
$extension = self::getExtension($fileMeta[self::META_TYPE]);
do {
$fileName = uniqid("{$trackId}_") . ".$extension";
} while (file_exists(self::$uploadDir . $fileName));
if (move_uploaded_file($fileMeta[self::META_TMP_NAME], self::$uploadDir . $fileName)) {
return $fileName;
}
return NULL;
}
/**
* Delete upload from database and filesystem
* @param String $path File relative path
* @return bool False if file exists but can't be unlinked
*/
public static function delete($path) {
$ret = true;
if (preg_match(self::$filePattern, $path)) {
$path = self::$uploadDir . $path;
if (file_exists($path)) {
$ret = unlink($path);
}
}
return $ret;
}
/**
* @param array $fileMeta File meta array from $_FILES[]
* @param boolean $checkMime Check with known mime types
* @return array File metadata array
* @throws ErrorException Internal server exception
* @throws Exception File upload exception
*/
public static function sanitizeUpload($fileMeta, $checkMime = true) {
if (!isset($fileMeta) ||
!isset($fileMeta[self::META_NAME]) || !isset($fileMeta[self::META_TYPE]) ||
!isset($fileMeta[self::META_SIZE]) || !isset($fileMeta[self::META_TMP_NAME])) {
$message = "no uploaded file";
$lastErr = error_get_last();
if (!empty($lastErr)) {
$message = $lastErr["message"];
}
throw new ErrorException($message);
}
$uploadErrors = [];
$uploadErrors[UPLOAD_ERR_INI_SIZE] = "Uploaded file exceeds the upload_max_filesize directive in php.ini";
$uploadErrors[UPLOAD_ERR_FORM_SIZE] = "Uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form";
$uploadErrors[UPLOAD_ERR_PARTIAL] = "File was only partially uploaded";
$uploadErrors[UPLOAD_ERR_NO_FILE] = "No file was uploaded";
$uploadErrors[UPLOAD_ERR_NO_TMP_DIR] = "Missing a temporary folder";
$uploadErrors[UPLOAD_ERR_CANT_WRITE] = "Failed to write file to disk";
$uploadErrors[UPLOAD_ERR_EXTENSION] = "A PHP extension stopped file upload";
$file = NULL;
$fileError = isset($fileMeta[self::META_ERROR]) ? $fileMeta[self::META_ERROR] : UPLOAD_ERR_OK;
if ($fileMeta[self::META_SIZE] > uUtils::getUploadMaxSize() && $fileError == UPLOAD_ERR_OK) {
$fileError = UPLOAD_ERR_FORM_SIZE;
}
if ($fileError == UPLOAD_ERR_OK) {
$file = $fileMeta[self::META_TMP_NAME];
} else {
$message = "Unknown error";
if (isset($uploadErrors[$fileError])) {
$message = $uploadErrors[$fileError];
}
$message .= " ($fileError)";
throw new Exception($message);
}
if (!$file || !file_exists($file)) {
throw new ErrorException("File not found");
}
if ($checkMime && !self::isKnownMime($fileMeta[self::META_TYPE])) {
throw new Exception("Unsupported mime type");
}
return $fileMeta;
}
}

View File

@ -165,6 +165,27 @@
return self::requestInt($name, $default, INPUT_GET); return self::requestInt($name, $default, INPUT_GET);
} }
public static function requestFile($name, $default = NULL) {
if (isset($_FILES[$name])) {
$files = $_FILES[$name];
if (isset($files["name"]) && isset($files["type"]) && isset($files["size"]) && isset($files["tmp_name"])) {
return $_FILES[$name];
}
}
return $default;
}
/**
* @param string $name Input name
* @param boolean $checkMime Optionally check mime with known types
* @return array File metadata array
* @throws Exception Upload exception
* @throws ErrorException Internal server exception
*/
public static function requireFile($name, $checkMime = false) {
return uUpload::sanitizeUpload($_FILES[$name], $checkMime);
}
private static function requestString($name, $default, $type) { private static function requestString($name, $default, $type) {
if (is_string(($val = self::requestValue($name, $default, $type)))) { if (is_string(($val = self::requestValue($name, $default, $type)))) {
return trim($val); return trim($val);

View File

@ -208,7 +208,7 @@ function getQueries($dbDriver) {
`accuracy` int(11) DEFAULT NULL, `accuracy` int(11) DEFAULT NULL,
`provider` varchar(100) DEFAULT NULL, `provider` varchar(100) DEFAULT NULL,
`comment` varchar(255) DEFAULT NULL, `comment` varchar(255) DEFAULT NULL,
`image_id` int(11) DEFAULT NULL, `image` varchar(100) DEFAULT NULL,
INDEX `idx_track_id` (`track_id`), INDEX `idx_track_id` (`track_id`),
INDEX `idx_user_id` (`user_id`), INDEX `idx_user_id` (`user_id`),
FOREIGN KEY(`user_id`) REFERENCES `$tUsers`(`id`), FOREIGN KEY(`user_id`) REFERENCES `$tUsers`(`id`),
@ -249,7 +249,7 @@ function getQueries($dbDriver) {
accuracy INT DEFAULT NULL, accuracy INT DEFAULT NULL,
provider VARCHAR(100) DEFAULT NULL, provider VARCHAR(100) DEFAULT NULL,
comment VARCHAR(255) DEFAULT NULL, comment VARCHAR(255) DEFAULT NULL,
image_id INT DEFAULT NULL, image VARCHAR(100) DEFAULT NULL,
FOREIGN KEY(user_id) REFERENCES $tUsers(id), FOREIGN KEY(user_id) REFERENCES $tUsers(id),
FOREIGN KEY(track_id) REFERENCES $tTracks(id) FOREIGN KEY(track_id) REFERENCES $tTracks(id)
)"; )";
@ -289,7 +289,7 @@ function getQueries($dbDriver) {
`accuracy` integer DEFAULT NULL, `accuracy` integer DEFAULT NULL,
`provider` varchar(100) DEFAULT NULL, `provider` varchar(100) DEFAULT NULL,
`comment` varchar(255) DEFAULT NULL, `comment` varchar(255) DEFAULT NULL,
`image_id` integer DEFAULT NULL, `image` varchar(100) DEFAULT NULL,
FOREIGN KEY(`user_id`) REFERENCES `$tUsers`(`id`), FOREIGN KEY(`user_id`) REFERENCES `$tUsers`(`id`),
FOREIGN KEY(`track_id`) REFERENCES `$tTracks`(`id`) FOREIGN KEY(`track_id`) REFERENCES `$tTracks`(`id`)
)"; )";

View File

@ -56,7 +56,7 @@ CREATE TABLE positions (
accuracy int DEFAULT NULL, accuracy int DEFAULT NULL,
provider varchar(100) DEFAULT NULL, provider varchar(100) DEFAULT NULL,
comment varchar(255) DEFAULT NULL, comment varchar(255) DEFAULT NULL,
image_id int DEFAULT NULL, image varchar(100) DEFAULT NULL,
FOREIGN KEY(user_id) REFERENCES users(id), FOREIGN KEY(user_id) REFERENCES users(id),
FOREIGN KEY(track_id) REFERENCES tracks(id) FOREIGN KEY(track_id) REFERENCES tracks(id)
); );

View File

@ -55,7 +55,7 @@ CREATE TABLE `positions` (
`accuracy` int(11) DEFAULT NULL, `accuracy` int(11) DEFAULT NULL,
`provider` varchar(100) DEFAULT NULL, `provider` varchar(100) DEFAULT NULL,
`comment` varchar(255) DEFAULT NULL, `comment` varchar(255) DEFAULT NULL,
`image_id` int(11) DEFAULT NULL, `image` varchar(100) DEFAULT NULL,
INDEX `idx_ptrack_id` (`track_id`), INDEX `idx_ptrack_id` (`track_id`),
INDEX `index_puser_id` (`user_id`), INDEX `index_puser_id` (`user_id`),
FOREIGN KEY(`user_id`) REFERENCES `users`(`id`), FOREIGN KEY(`user_id`) REFERENCES `users`(`id`),

View File

@ -52,7 +52,7 @@ CREATE TABLE `positions` (
`accuracy` integer DEFAULT NULL, `accuracy` integer DEFAULT NULL,
`provider` varchar(100) DEFAULT NULL, `provider` varchar(100) DEFAULT NULL,
`comment` varchar(255) DEFAULT NULL, `comment` varchar(255) DEFAULT NULL,
`image_id` integer DEFAULT NULL, `image` varchar(100) DEFAULT NULL,
FOREIGN KEY(`user_id`) REFERENCES `users`(`id`), FOREIGN KEY(`user_id`) REFERENCES `users`(`id`),
FOREIGN KEY(`track_id`) REFERENCES `tracks`(`id`) FOREIGN KEY(`track_id`) REFERENCES `tracks`(`id`)
); );

1
uploads/README Normal file
View File

@ -0,0 +1 @@
This folder is for uploaded images. It should be writable by PHP.

View File

@ -56,28 +56,30 @@ $xml->startDocument("1.0");
$xml->setIndent(true); $xml->setIndent(true);
$xml->startElement('root'); $xml->startElement('root');
foreach ($positionsArr as $position) { if (!empty($positionsArr)) {
/** @var uPosition $prevPosition */ foreach ($positionsArr as $position) {
$xml->startElement("position"); /** @var uPosition $prevPosition */
$xml->writeAttribute("id", $position->id); $xml->startElement("position");
$xml->writeElement("latitude", $position->latitude); $xml->writeAttribute("id", $position->id);
$xml->writeElement("longitude", $position->longitude); $xml->writeElement("latitude", $position->latitude);
$xml->writeElement("altitude", ($position->altitude) ? round($position->altitude) : $position->altitude); $xml->writeElement("longitude", $position->longitude);
$xml->writeElement("speed", $position->speed); $xml->writeElement("altitude", ($position->altitude) ? round($position->altitude) : $position->altitude);
$xml->writeElement("bearing", $position->bearing); $xml->writeElement("speed", $position->speed);
$xml->writeElement("timestamp", $position->timestamp); $xml->writeElement("bearing", $position->bearing);
$xml->writeElement("accuracy", $position->accuracy); $xml->writeElement("timestamp", $position->timestamp);
$xml->writeElement("provider", $position->provider); $xml->writeElement("accuracy", $position->accuracy);
$xml->writeElement("comments", $position->comment); $xml->writeElement("provider", $position->provider);
$xml->writeElement("username", $position->userLogin); $xml->writeElement("comments", $position->comment);
$xml->writeElement("trackid", $position->trackId); $xml->writeElement("username", $position->userLogin);
$xml->writeElement("trackname", $position->trackName); $xml->writeElement("trackid", $position->trackId);
$distance = !$last && isset($prevPosition) ? $position->distanceTo($prevPosition) : 0; $xml->writeElement("trackname", $position->trackName);
$xml->writeElement("distance", round($distance)); $distance = !$last && isset($prevPosition) ? $position->distanceTo($prevPosition) : 0;
$seconds = !$last && isset($prevPosition) ? $position->secondsTo($prevPosition) : 0; $xml->writeElement("distance", round($distance));
$xml->writeElement("seconds", $seconds); $seconds = !$last && isset($prevPosition) ? $position->secondsTo($prevPosition) : 0;
$xml->endElement(); $xml->writeElement("seconds", $seconds);
$prevPosition = $position; $xml->endElement();
$prevPosition = $position;
}
} }
$xml->endElement(); $xml->endElement();

View File

@ -41,40 +41,23 @@ if (!$auth->isAuthenticated()) {
uUtils::exitWithError($lang["private"]); uUtils::exitWithError($lang["private"]);
} }
if (!isset($_FILES["gpx"])) { try {
$fileMeta = uUtils::requireFile("gpx");
} catch (ErrorException $ee) {
$message = $lang["servererror"]; $message = $lang["servererror"];
$lastErr = error_get_last(); $message .= ": {$ee->getMessage()}";
if (!empty($lastErr)) {
$message .= ": " . $lastErr["message"];
} else {
$message .= ": no uploaded file";
}
uUtils::exitWithError($message); uUtils::exitWithError($message);
} } catch (Exception $e) {
$gpxFile = NULL;
$gpxUpload = $_FILES["gpx"];
$uploadErr = $gpxUpload["error"];
if ($gpxUpload["size"] > uUtils::getUploadMaxSize() && $uploadErr == UPLOAD_ERR_OK) {
$uploadErr = UPLOAD_ERR_FORM_SIZE;
}
if ($uploadErr == UPLOAD_ERR_OK) {
$gpxFile = $gpxUpload["tmp_name"];
$gpxName = basename($gpxUpload["name"]);
} else {
$message = $lang["iuploadfailure"]; $message = $lang["iuploadfailure"];
if (isset($uploadErrors[$uploadErr])) { $message .= ": {$ee->getMessage()}";
$message .= ": " . $uploadErrors[$uploadErr];
}
$message .= " ($uploadErr)";
uUtils::exitWithError($message); uUtils::exitWithError($message);
} }
$gpx = false; $gpxFile = $fileMeta[uUpload::META_TMP_NAME];
$gpxName = basename($fileMeta[uUpload::META_NAME]);
libxml_use_internal_errors(true); libxml_use_internal_errors(true);
if ($gpxFile && file_exists($gpxFile)) { $gpx = simplexml_load_file($gpxFile);
$gpx = simplexml_load_file($gpxFile); unlink($gpxFile);
}
if ($gpx === false) { if ($gpx === false) {
$message = $lang["iparsefailure"]; $message = $lang["iparsefailure"];
@ -115,6 +98,7 @@ foreach ($gpx->trk as $trk) {
} }
$time = isset($point->time) ? strtotime($point->time) : 1; $time = isset($point->time) ? strtotime($point->time) : 1;
$altitude = isset($point->ele) ? (double) $point->ele : NULL; $altitude = isset($point->ele) ? (double) $point->ele : NULL;
$comment = isset($point->desc) && !empty($point->desc) ? (string) $point->desc : NULL;
$speed = NULL; $speed = NULL;
$bearing = NULL; $bearing = NULL;
$accuracy = NULL; $accuracy = NULL;
@ -129,7 +113,7 @@ foreach ($gpx->trk as $trk) {
} }
$ret = $track->addPosition($auth->user->id, $ret = $track->addPosition($auth->user->id,
$time, (double) $point["lat"], (double) $point["lon"], $altitude, $time, (double) $point["lat"], (double) $point["lon"], $altitude,
$speed, $bearing, $accuracy, $provider, NULL, NULL); $speed, $bearing, $accuracy, $provider, $comment, NULL);
if ($ret === false) { if ($ret === false) {
$track->delete(); $track->delete();
uUtils::exitWithError($lang["servererror"]); uUtils::exitWithError($lang["servererror"]);