diff --git a/.tests/lib/BaseDatabaseTestCase.php b/.tests/lib/BaseDatabaseTestCase.php index 35dc482..c59fad9 100644 --- a/.tests/lib/BaseDatabaseTestCase.php +++ b/.tests/lib/BaseDatabaseTestCase.php @@ -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() { diff --git a/.tests/tests/ClientAPITest.php b/.tests/tests/ClientAPITest.php index 1b99e8d..7cd135d 100644 --- a/.tests/tests/ClientAPITest.php +++ b/.tests/tests/ClientAPITest.php @@ -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 ], ]; diff --git a/.tests/tests/ImportTest.php b/.tests/tests/ImportTest.php index a59a9f1..f01bb9f 100644 --- a/.tests/tests/ImportTest.php +++ b/.tests/tests/ImportTest.php @@ -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"); } diff --git a/.tests/tests/PositionTest.php b/.tests/tests/PositionTest.php index 98a6f0b..3d04d4d 100644 --- a/.tests/tests/PositionTest.php +++ b/.tests/tests/PositionTest.php @@ -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"); diff --git a/.tests/tests/TrackTest.php b/.tests/tests/TrackTest.php index 2d4f089..30e8bb6 100644 --- a/.tests/tests/TrackTest.php +++ b/.tests/tests/TrackTest.php @@ -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"); diff --git a/client/index.php b/client/index.php index 43addbd..3de795d 100644 --- a/client/index.php +++ b/client/index.php @@ -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"); diff --git a/helpers/position.php b/helpers/position.php index 3031c84..60162ac 100644 --- a/helpers/position.php +++ b/helpers/position.php @@ -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)) { diff --git a/helpers/upload.php b/helpers/upload.php new file mode 100644 index 0000000..15a4380 --- /dev/null +++ b/helpers/upload.php @@ -0,0 +1,166 @@ +. + */ + +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; + } +} \ No newline at end of file diff --git a/helpers/utils.php b/helpers/utils.php index fc20279..ed8e955 100644 --- a/helpers/utils.php +++ b/helpers/utils.php @@ -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); diff --git a/scripts/setup.php b/scripts/setup.php index f86246a..6ca89a3 100644 --- a/scripts/setup.php +++ b/scripts/setup.php @@ -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`) )"; diff --git a/scripts/ulogger.pgsql b/scripts/ulogger.pgsql index 568ebec..f993a08 100644 --- a/scripts/ulogger.pgsql +++ b/scripts/ulogger.pgsql @@ -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) ); diff --git a/scripts/ulogger.sql b/scripts/ulogger.sql index cd90b8e..0a495af 100644 --- a/scripts/ulogger.sql +++ b/scripts/ulogger.sql @@ -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`), diff --git a/scripts/ulogger.sqlite b/scripts/ulogger.sqlite index 2171d96..6f0e099 100644 --- a/scripts/ulogger.sqlite +++ b/scripts/ulogger.sqlite @@ -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`) ); diff --git a/uploads/README b/uploads/README new file mode 100644 index 0000000..38da4b5 --- /dev/null +++ b/uploads/README @@ -0,0 +1 @@ +This folder is for uploaded images. It should be writable by PHP. diff --git a/utils/getpositions.php b/utils/getpositions.php index f684613..823aa4c 100644 --- a/utils/getpositions.php +++ b/utils/getpositions.php @@ -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(); diff --git a/utils/import.php b/utils/import.php index bcc7698..17d87a4 100644 --- a/utils/import.php +++ b/utils/import.php @@ -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"]);