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 $testProvider = "gps";
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"
protected function getSetUpOperation() {

View File

@ -225,7 +225,7 @@ class ClientAPITest extends UloggerAPITestCase {
'accuracy' => $this->testAccuracy,
'provider' => $this->testProvider,
'comment' => $this->testComment,
'imageid' => $this->testImageId
'imageid' => $this->testImage
],
];
$response = $this->http->post('/client/index.php', $options);
@ -246,11 +246,11 @@ class ClientAPITest extends UloggerAPITestCase {
"accuracy" => $this->testAccuracy,
"provider" => $this->testProvider,
"comment" => $this->testComment,
"image_id" => $this->testImageId
"image" => $this->testImage
];
$actual = $this->getConnection()->createQueryTable(
"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");
}
@ -275,7 +275,7 @@ class ClientAPITest extends UloggerAPITestCase {
'accuracy' => $this->testAccuracy,
'provider' => $this->testProvider,
'comment' => $this->testComment,
'imageid' => $this->testImageId
'imageid' => $this->testImage
],
];
$response = $this->http->post('/client/index.php', $options);
@ -306,7 +306,7 @@ class ClientAPITest extends UloggerAPITestCase {
'accuracy' => $this->testAccuracy,
'provider' => $this->testProvider,
'comment' => $this->testComment,
'imageid' => $this->testImageId
'imageid' => $this->testImage
],
];
@ -343,7 +343,7 @@ class ClientAPITest extends UloggerAPITestCase {
'accuracy' => $this->testAccuracy,
'provider' => $this->testProvider,
'comment' => $this->testComment,
'imageid' => $this->testImageId
'imageid' => $this->testImage
],
];

View File

@ -88,12 +88,12 @@ class ImportTest extends UloggerAPITestCase {
"accuracy" => null,
"provider" => "gps",
"comment" => null,
"image_id" => null
"image" => null
];
$actual = $this->getConnection()->createQueryTable(
"positions",
"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");
@ -110,7 +110,7 @@ class ImportTest extends UloggerAPITestCase {
"accuracy" => null,
"provider" => "gps",
"comment" => null,
"image_id" => null
"image" => null
];
$this->assertTableContains($expected, $actual, "Wrong actual table data");
}
@ -204,12 +204,12 @@ class ImportTest extends UloggerAPITestCase {
"accuracy" => null,
"provider" => "gps",
"comment" => null,
"image_id" => null
"image" => null
];
$actual = $this->getConnection()->createQueryTable(
"positions",
"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");
}
@ -304,12 +304,12 @@ class ImportTest extends UloggerAPITestCase {
"accuracy" => $this->testAccuracy,
"provider" => $this->testProvider,
"comment" => null,
"image_id" => null
"image" => null
];
$actual = $this->getConnection()->createQueryTable(
"positions",
"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");
}
@ -385,12 +385,12 @@ class ImportTest extends UloggerAPITestCase {
"accuracy" => null,
"provider" => "gps",
"comment" => null,
"image_id" => null
"image" => null
];
$actual = $this->getConnection()->createQueryTable(
"positions",
"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");
}
@ -472,12 +472,12 @@ class ImportTest extends UloggerAPITestCase {
"accuracy" => null,
"provider" => "gps",
"comment" => null,
"image_id" => null
"image" => null
];
$actual = $this->getConnection()->createQueryTable(
"positions",
"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");
$expected = [
@ -493,7 +493,7 @@ class ImportTest extends UloggerAPITestCase {
"accuracy" => null,
"provider" => "gps",
"comment" => null,
"image_id" => null
"image" => null
];
$this->assertTableContains($expected, $actual, "Wrong actual table data");
}
@ -584,12 +584,12 @@ class ImportTest extends UloggerAPITestCase {
"accuracy" => null,
"provider" => "gps",
"comment" => null,
"image_id" => null
"image" => null
];
$actual = $this->getConnection()->createQueryTable(
"positions",
"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");
$expected = [
@ -605,7 +605,7 @@ class ImportTest extends UloggerAPITestCase {
"accuracy" => null,
"provider" => "gps",
"comment" => null,
"image_id" => null
"image" => null
];
$this->assertTableContains($expected, $actual, "Wrong actual table data");
}

View File

@ -11,15 +11,15 @@ class PositionTest extends UloggerDatabaseTestCase {
$trackId = $this->addTestTrack($userId);
$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->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->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");
$expected = [
"id" => $posId,
@ -34,11 +34,11 @@ class PositionTest extends UloggerDatabaseTestCase {
"accuracy" => $this->testAccuracy,
"provider" => $this->testProvider,
"comment" => $this->testComment,
"image_id" => $this->testImageId
"image" => $this->testImage
];
$actual = $this->getConnection()->createQueryTable(
"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");

View File

@ -41,16 +41,16 @@ class TrackTest extends UloggerDatabaseTestCase {
$this->assertEquals(1, $this->getConnection()->getRowCount('tracks'), "Wrong row count");
$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->assertFalse($posId, "Adding position with nonexistant track should fail");
$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->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");
$expected = [
"id" => $posId,
@ -65,11 +65,11 @@ class TrackTest extends UloggerDatabaseTestCase {
"accuracy" => $this->testAccuracy,
"provider" => $this->testProvider,
"comment" => $this->testComment,
"image_id" => $this->testImageId
"image" => $this->testImage
];
$actual = $this->getConnection()->createQueryTable(
"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");

View File

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

View File

@ -18,7 +18,8 @@
*/
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
@ -51,9 +52,9 @@
/** @param String Provider */
public $provider;
/** @param String Comment */
public $comment; // not used yet
/** @param int Image id */
public $imageId; // not used yet
public $comment;
/** @param String Image path */
public $image;
public $isValid = false;
@ -66,7 +67,7 @@
if (!empty($positionId)) {
$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.comment, p.image_id, u.login, t.name
p.comment, p.image, u.login, t.name
FROM " . self::db()->table('positions') . " p
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)
@ -104,12 +105,12 @@
* @param int $accuracy Optional
* @param string $provider Optional
* @param string $comment Optional
* @param int $imageId Optional
* @param int $image Optional
* @return int|bool New position id in database, false on error
*/
public static function add($userId, $trackId, $timestamp, $lat, $lon,
$altitude = NULL, $speed = NULL, $bearing = NULL, $accuracy = NULL,
$provider = NULL, $comment = NULL, $imageId = NULL) {
$provider = NULL, $comment = NULL, $image = NULL) {
$positionId = false;
if (is_numeric($lat) && is_numeric($lon) && is_numeric($timestamp) && is_numeric($userId) && is_numeric($trackId)) {
$track = new uTrack($trackId);
@ -118,11 +119,11 @@
$table = self::db()->table('positions');
$query = "INSERT INTO $table
(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('?') . ", ?, ?, ?, ?, ?, ?, ?, ?, ?)";
$stmt = self::db()->prepare($query);
$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);
$positionId = self::db()->lastInsertId("${table}_id_seq");
} catch (PDOException $e) {
@ -151,6 +152,7 @@
$where .= " AND track_id = ?";
$args[] = $trackId;
}
self::removeImages($userId, $trackId);
try {
$query = "DELETE FROM " . self::db()->table('positions') . " $where";
$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,
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
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)
@ -205,7 +207,7 @@
public static function getLastAllUsers() {
$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.comment, p.image_id, u.login, t.name
p.comment, p.image, u.login, t.name
FROM " . self::db()->table('positions') . " p
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)
@ -224,6 +226,7 @@
} catch (PDOException $e) {
// TODO: handle exception
syslog(LOG_ERR, $e->getMessage());
$positionsArr = false;
}
return $positionsArr;
}
@ -234,17 +237,17 @@
* @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
* @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) {
$rules = [];
public static function getAll($userId = NULL, $trackId = NULL, $afterId = NULL, $rules = []) {
if (!empty($userId)) {
$rules[] = "p.user_id = " . self::db()->quote($userId);
}
if (!empty($trackId)) {
$rules[] = "p.track_id = " . self::db()->quote($trackId);
}
if (!empty($trackId)) {
if (!empty($afterId)) {
$rules[] = "p.id > " . self::db()->quote($afterId);
}
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,
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
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)
@ -269,10 +272,53 @@
} catch (PDOException $e) {
// TODO: handle exception
syslog(LOG_ERR, $e->getMessage());
$positionsArr = false;
}
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
*
@ -322,7 +368,7 @@
$position->accuracy = $row['accuracy'];
$position->provider = $row['provider'];
$position->comment = $row['comment'];
$position->imageId = $row['image_id'];
$position->image = $row['image'];
$position->isValid = true;
return $position;
}
@ -350,7 +396,7 @@
$stmt->bindColumn('accuracy', $this->accuracy, PDO::PARAM_INT);
$stmt->bindColumn('provider', $this->provider);
$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('name', $this->trackName);
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);
}
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) {
if (is_string(($val = self::requestValue($name, $default, $type)))) {
return trim($val);

View File

@ -208,7 +208,7 @@ function getQueries($dbDriver) {
`accuracy` int(11) DEFAULT NULL,
`provider` varchar(100) 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_user_id` (`user_id`),
FOREIGN KEY(`user_id`) REFERENCES `$tUsers`(`id`),
@ -249,7 +249,7 @@ function getQueries($dbDriver) {
accuracy INT DEFAULT NULL,
provider VARCHAR(100) 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(track_id) REFERENCES $tTracks(id)
)";
@ -289,7 +289,7 @@ function getQueries($dbDriver) {
`accuracy` integer DEFAULT NULL,
`provider` varchar(100) 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(`track_id`) REFERENCES `$tTracks`(`id`)
)";

View File

@ -56,7 +56,7 @@ CREATE TABLE positions (
accuracy int DEFAULT NULL,
provider varchar(100) 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(track_id) REFERENCES tracks(id)
);

View File

@ -55,7 +55,7 @@ CREATE TABLE `positions` (
`accuracy` int(11) DEFAULT NULL,
`provider` varchar(100) 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 `index_puser_id` (`user_id`),
FOREIGN KEY(`user_id`) REFERENCES `users`(`id`),

View File

@ -52,7 +52,7 @@ CREATE TABLE `positions` (
`accuracy` integer DEFAULT NULL,
`provider` varchar(100) 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(`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->startElement('root');
foreach ($positionsArr as $position) {
/** @var uPosition $prevPosition */
$xml->startElement("position");
$xml->writeAttribute("id", $position->id);
$xml->writeElement("latitude", $position->latitude);
$xml->writeElement("longitude", $position->longitude);
$xml->writeElement("altitude", ($position->altitude) ? round($position->altitude) : $position->altitude);
$xml->writeElement("speed", $position->speed);
$xml->writeElement("bearing", $position->bearing);
$xml->writeElement("timestamp", $position->timestamp);
$xml->writeElement("accuracy", $position->accuracy);
$xml->writeElement("provider", $position->provider);
$xml->writeElement("comments", $position->comment);
$xml->writeElement("username", $position->userLogin);
$xml->writeElement("trackid", $position->trackId);
$xml->writeElement("trackname", $position->trackName);
$distance = !$last && isset($prevPosition) ? $position->distanceTo($prevPosition) : 0;
$xml->writeElement("distance", round($distance));
$seconds = !$last && isset($prevPosition) ? $position->secondsTo($prevPosition) : 0;
$xml->writeElement("seconds", $seconds);
$xml->endElement();
$prevPosition = $position;
if (!empty($positionsArr)) {
foreach ($positionsArr as $position) {
/** @var uPosition $prevPosition */
$xml->startElement("position");
$xml->writeAttribute("id", $position->id);
$xml->writeElement("latitude", $position->latitude);
$xml->writeElement("longitude", $position->longitude);
$xml->writeElement("altitude", ($position->altitude) ? round($position->altitude) : $position->altitude);
$xml->writeElement("speed", $position->speed);
$xml->writeElement("bearing", $position->bearing);
$xml->writeElement("timestamp", $position->timestamp);
$xml->writeElement("accuracy", $position->accuracy);
$xml->writeElement("provider", $position->provider);
$xml->writeElement("comments", $position->comment);
$xml->writeElement("username", $position->userLogin);
$xml->writeElement("trackid", $position->trackId);
$xml->writeElement("trackname", $position->trackName);
$distance = !$last && isset($prevPosition) ? $position->distanceTo($prevPosition) : 0;
$xml->writeElement("distance", round($distance));
$seconds = !$last && isset($prevPosition) ? $position->secondsTo($prevPosition) : 0;
$xml->writeElement("seconds", $seconds);
$xml->endElement();
$prevPosition = $position;
}
}
$xml->endElement();

View File

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