Fix issues in last position of all users, add tests, closes #9

This commit is contained in:
Bartek Fabiszewski 2019-03-02 23:53:45 +01:00
parent 25c1b24c49
commit 5c8381d29e
8 changed files with 212 additions and 118 deletions

View File

@ -150,6 +150,87 @@ class InternalAPITest extends UloggerAPITestCase {
$this->assertEquals((string) $position->trackname, $this->testTrackName, "Wrong trackname"); $this->assertEquals((string) $position->trackname, $this->testTrackName, "Wrong trackname");
} }
public function testGetPositionsUserLatest() {
$this->assertTrue($this->authenticate(), "Authentication failed");
$trackId = $this->addTestTrack($this->testUserId);
$this->addTestPosition($this->testUserId, $trackId, $this->testTimestamp);
$this->addTestPosition($this->testUserId, $trackId, $this->testTimestamp + 3);
$this->assertEquals(1, $this->getConnection()->getRowCount("tracks"), "Wrong row count");
$this->assertEquals(2, $this->getConnection()->getRowCount("positions"), "Wrong row count");
$userId = $this->addTestUser($this->testUser, password_hash($this->testPass, PASSWORD_DEFAULT));
$trackId2 = $this->addTestTrack($userId);
$this->addTestPosition($userId, $trackId2, $this->testTimestamp + 2);
$this->addTestPosition($userId, $trackId2, $this->testTimestamp + 1);
$this->assertEquals(2, $this->getConnection()->getRowCount("tracks"), "Wrong row count");
$this->assertEquals(4, $this->getConnection()->getRowCount("positions"), "Wrong row count");
$options = [
"http_errors" => false,
"query" => [ "userid" => $this->testUserId, "last" => 1 ],
];
$response = $this->http->get("/utils/getpositions.php", $options);
$this->assertEquals(200, $response->getStatusCode(), "Unexpected status code");
$xml = $this->getXMLfromResponse($response);
$this->assertTrue($xml !== false, "XML object is false");
$this->assertEquals($xml->position->count(), 1, "Wrong count of positions");
$position = $xml->position[0];
$this->assertEquals((int) $position["id"], 2, "Wrong position id");
$this->assertEquals((float) $position->latitude, $this->testLat, "Wrong latitude");
$this->assertEquals((float) $position->longitude, $this->testLon, "Wrong longitude");
$this->assertEquals((int) $position->timestamp, $this->testTimestamp + 3, "Wrong timestamp");
$this->assertEquals((string) $position->username, $this->testAdminUser, "Wrong username");
$this->assertEquals((string) $position->trackname, $this->testTrackName, "Wrong trackname");
}
public function testGetPositionsAllUsersLatest() {
$this->assertTrue($this->authenticate(), "Authentication failed");
$userId = $this->addTestUser($this->testUser, password_hash($this->testPass, PASSWORD_DEFAULT));
$trackId = $this->addTestTrack($this->testUserId);
$this->addTestPosition($this->testUserId, $trackId, $this->testTimestamp);
$this->addTestPosition($this->testUserId, $trackId, $this->testTimestamp + 3);
$this->assertEquals(1, $this->getConnection()->getRowCount("tracks"), "Wrong row count");
$this->assertEquals(2, $this->getConnection()->getRowCount("positions"), "Wrong row count");
$trackName = "Track 2";
$trackId2 = $this->addTestTrack($userId, $trackName);
$this->addTestPosition($userId, $trackId2, $this->testTimestamp + 2);
$this->addTestPosition($userId, $trackId2, $this->testTimestamp + 1);
$this->assertEquals(2, $this->getConnection()->getRowCount("tracks"), "Wrong row count");
$this->assertEquals(4, $this->getConnection()->getRowCount("positions"), "Wrong row count");
$options = [
"http_errors" => false,
"query" => [ "last" => 1 ],
];
$response = $this->http->get("/utils/getpositions.php", $options);
$this->assertEquals(200, $response->getStatusCode(), "Unexpected status code");
$xml = $this->getXMLfromResponse($response);
$this->assertTrue($xml !== false, "XML object is false");
$this->assertEquals($xml->position->count(), 2, "Wrong count of positions");
$position = $xml->position[0];
$this->assertEquals((int) $position["id"], 2, "Wrong position id");
$this->assertEquals((float) $position->latitude, $this->testLat, "Wrong latitude");
$this->assertEquals((float) $position->longitude, $this->testLon, "Wrong longitude");
$this->assertEquals((int) $position->timestamp, $this->testTimestamp + 3, "Wrong timestamp");
$this->assertEquals((string) $position->username, $this->testAdminUser, "Wrong username");
$this->assertEquals((string) $position->trackname, $this->testTrackName, "Wrong trackname");
$position = $xml->position[1];
$this->assertEquals((int) $position["id"], 3, "Wrong position id");
$this->assertEquals((float) $position->latitude, $this->testLat, "Wrong latitude");
$this->assertEquals((float) $position->longitude, $this->testLon, "Wrong longitude");
$this->assertEquals((int) $position->timestamp, $this->testTimestamp + 2, "Wrong timestamp");
$this->assertEquals((string) $position->username, $this->testUser, "Wrong username");
$this->assertEquals((string) $position->trackname, $trackName, "Wrong trackname");
}
public function testGetPositionsNoTrackId() { public function testGetPositionsNoTrackId() {
$this->assertTrue($this->authenticate(), "Authentication failed"); $this->assertTrue($this->authenticate(), "Authentication failed");
@ -169,15 +250,7 @@ class InternalAPITest extends UloggerAPITestCase {
$xml = $this->getXMLfromResponse($response); $xml = $this->getXMLfromResponse($response);
$this->assertTrue($xml !== false, "XML object is false"); $this->assertTrue($xml !== false, "XML object is false");
$this->assertEquals($xml->position->count(), 1, "Wrong count of positions"); $this->assertEquals(0, $xml->position->count(), "Wrong count of positions");
$position = $xml->position[0];
$this->assertEquals((int) $position["id"], 2, "Wrong position id");
$this->assertEquals((float) $position->latitude, $this->testLat, "Wrong latitude");
$this->assertEquals((float) $position->longitude, $this->testLon, "Wrong longitude");
$this->assertEquals((int) $position->timestamp, $this->testTimestamp + 1, "Wrong timestamp");
$this->assertEquals((string) $position->username, $this->testAdminUser, "Wrong username");
$this->assertEquals((string) $position->trackname, $this->testTrackName, "Wrong trackname");
} }
public function testGetPositionsNoUserId() { public function testGetPositionsNoUserId() {
@ -199,8 +272,8 @@ class InternalAPITest extends UloggerAPITestCase {
$xml = $this->getXMLfromResponse($response); $xml = $this->getXMLfromResponse($response);
$this->assertTrue($xml !== false, "XML object is not false"); $this->assertTrue($xml !== false, "XML object is false");
$this->assertEquals($xml->position->count(), 0, "Wrong count of positions"); $this->assertEquals(0, $xml->position->count(), "Wrong count of positions");
} }
public function testGetPositionsNoAuth() { public function testGetPositionsNoAuth() {

View File

@ -106,6 +106,38 @@ class PositionTest extends UloggerDatabaseTestCase {
$this->assertEquals($lastPosition->id, $pos4, "Wrong last position (user)"); $this->assertEquals($lastPosition->id, $pos4, "Wrong last position (user)");
} }
public function testGetLastAllUsers() {
$userId = $this->addTestUser();
$userId2 = $this->addTestUser($this->testUser2);
$trackId1 = $this->addTestTrack($userId);
$trackId2 = $this->addTestTrack($userId);
$pos1 = $this->addTestPosition($userId, $trackId1, $this->testTimestamp + 3);
$pos2 = $this->addTestPosition($userId2, $trackId2, $this->testTimestamp + 1);
$pos3 = $this->addTestPosition($userId, $trackId1, $this->testTimestamp);
$pos4 = $this->addTestPosition($userId2, $trackId2, $this->testTimestamp + 2);
$this->assertEquals(2, $this->getConnection()->getRowCount('tracks'), "Wrong row count");
$this->assertEquals(4, $this->getConnection()->getRowCount('positions'), "Wrong row count");
$posArr = uPosition::getLastAllUsers();
$this->assertEquals(2, count($posArr), "Wrong row count");
foreach ($posArr as $position) {
/** @var uPosition $position */
switch ($position->id) {
case 1:
$this->assertEquals($this->testTimestamp + 3, $position->timestamp);
$this->assertEquals($userId, $position->userId);
$this->assertEquals($trackId1, $position->trackId);
break;
case 4:
$this->assertEquals($this->testTimestamp + 2, $position->timestamp);
$this->assertEquals($userId2, $position->userId);
$this->assertEquals($trackId2, $position->trackId);
break;
default:
$this->assert("Unexpected position: {$position->id}");
}
}
}
public function testGetAll() { public function testGetAll() {
$userId = $this->addTestUser(); $userId = $this->addTestUser();
$userId2 = $this->addTestUser($this->testUser2); $userId2 = $this->addTestUser($this->testUser2);

View File

@ -203,53 +203,33 @@
} }
/** /**
* Get last position data from database * Get last positions for all users
* (for all users)
* *
* @return array|bool Array of uPosition positions, false on error * @return array|bool Array of uPosition positions, false on error
*/ */
public static function getLastAllUsers() { public static function getLastAllUsers() {
$query = "SELECT $query = "SELECT p.id, " . self::db()->unix_timestamp('p.time') . " AS tstamp, p.user_id, p.track_id,
p.id, p.latitude, p.longitude, p.altitude, p.speed, p.bearing, p.accuracy, p.provider,
UNIX_TIMESTAMP(p.time) AS tstamp, p.comment, p.image_id, u.login, t.name
p.user_id, FROM " . self::db()->table('positions') . " p
p.track_id, LEFT JOIN " . self::db()->table('users') . " u ON (p.user_id = u.id)
p.latitude, LEFT JOIN " . self::db()->table('tracks') . " t ON (p.track_id = t.id)
p.longitude,
p.altitude,
p.speed,
p.bearing,
p.accuracy,
p.provider,
p.comment,
p.image_id,
u.login
FROM
" . self::db()->table('positions') . " p
LEFT JOIN " . self::db()->table('users') . " u
ON ( p.user_id = u.id )
WHERE p.id = ( WHERE p.id = (
SELECT SELECT p2.id FROM " . self::db()->table('positions') . " p2
p2.id WHERE p2.user_id = p.user_id
FROM ORDER BY p2.time DESC, p2.id DESC
" . self::db()->table('positions') . " p2
WHERE
p2.user_id = p.user_id
ORDER BY
p2.time DESC,
p2.id DESC
LIMIT 1 LIMIT 1
)"; )";
$result = self::db()->query($query);
if ($result === false) {
return false;
}
$positionsArr = []; $positionsArr = [];
while ($row = $result->fetch_assoc()) { try {
$result = self::db()->query($query);
while ($row = $result->fetch()) {
$positionsArr[] = self::rowToObject($row); $positionsArr[] = self::rowToObject($row);
} }
$result->close(); } catch (PDOException $e) {
// TODO: handle exception
syslog(LOG_ERR, $e->getMessage());
}
return $positionsArr; return $positionsArr;
} }
@ -281,8 +261,8 @@
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)
$where $where
ORDER BY p.time, p.id"; ORDER BY p.time, p.id";
try {
$positionsArr = []; $positionsArr = [];
try {
$result = self::db()->query($query); $result = self::db()->query($query);
while ($row = $result->fetch()) { while ($row = $result->fetch()) {
$positionsArr[] = self::rowToObject($row); $positionsArr[] = self::rowToObject($row);

View File

@ -68,9 +68,9 @@ function displayTrack(xml, update) {
var p = parsePosition(positions[i], i); var p = parsePosition(positions[i], i);
totalMeters += p.distance; totalMeters += p.distance;
totalSeconds += p.seconds; totalSeconds += p.seconds;
p['totalMeters'] = totalMeters; p.totalMeters = totalMeters;
p['totalSeconds'] = totalSeconds; p.totalSeconds = totalSeconds;
p['coordinates'] = new google.maps.LatLng(p.latitude, p.longitude); p.coordinates = new google.maps.LatLng(p.latitude, p.longitude);
// set marker // set marker
setMarker(p, i, posLen); setMarker(p, i, posLen);
// update polyline // update polyline
@ -126,7 +126,7 @@ function setMarker(p, i, posLen) {
// marker // marker
var marker = new google.maps.Marker({ var marker = new google.maps.Marker({
map: map, map: map,
position: p.coordinates, position: new google.maps.LatLng(p.latitude, p.longitude),
title: (new Date(p.timestamp * 1000)).toLocaleString() title: (new Date(p.timestamp * 1000)).toLocaleString()
}); });
if (latest == 1) { marker.setIcon('images/marker-red.png') } if (latest == 1) { marker.setIcon('images/marker-red.png') }
@ -179,6 +179,15 @@ function getBounds() {
return [lon_sw, lat_sw, lon_ne, lat_ne]; return [lon_sw, lat_sw, lon_ne, lat_ne];
} }
function zoomToExtent() {
var latlngbounds = new google.maps.LatLngBounds();
for (var i = 0; i < markers.length; i++) {
var coordinates = new google.maps.LatLng(markers[i].position.lat(), markers[i].position.lng());
latlngbounds.extend(coordinates);
}
map.fitBounds(latlngbounds);
}
function zoomToBounds(b) { function zoomToBounds(b) {
var sw = new google.maps.LatLng(b[1], b[0]); var sw = new google.maps.LatLng(b[1], b[0]);
var ne = new google.maps.LatLng(b[3], b[2]); var ne = new google.maps.LatLng(b[3], b[2]);

View File

@ -290,6 +290,7 @@ function cleanup() {
document.getElementById('map-canvas').innerHTML = ''; document.getElementById('map-canvas').innerHTML = '';
} }
function displayTrack(xml, update) { function displayTrack(xml, update) {
altitudes = {}; altitudes = {};
var totalMeters = 0; var totalMeters = 0;
@ -301,8 +302,8 @@ function displayTrack(xml, update) {
var p = parsePosition(positions[i], i); var p = parsePosition(positions[i], i);
totalMeters += p.distance; totalMeters += p.distance;
totalSeconds += p.seconds; totalSeconds += p.seconds;
p['totalMeters'] = totalMeters; p.totalMeters = totalMeters;
p['totalSeconds'] = totalSeconds; p.totalSeconds = totalSeconds;
// set marker // set marker
setMarker(p, i, posLen); setMarker(p, i, posLen);
// update polyline // update polyline
@ -409,6 +410,10 @@ function getBounds() {
return [lon_sw, lat_sw, lon_ne, lat_ne]; return [lon_sw, lat_sw, lon_ne, lat_ne];
} }
function zoomToExtent() {
map.getView().fit(layerMarkers.getSource().getExtent())
}
function zoomToBounds(b) { function zoomToBounds(b) {
var bounds = ol.proj.transformExtent(b, 'EPSG:4326', 'EPSG:900913'); var bounds = ol.proj.transformExtent(b, 'EPSG:4326', 'EPSG:900913');
map.getView().fit(bounds); map.getView().fit(bounds);

View File

@ -134,16 +134,6 @@ function getXHR() {
return xmlhttp; return xmlhttp;
} }
function reload(userid, trackid){
var usersSelect = document.getElementsByName('user')[0];
if (usersSelect[usersSelect.selectedIndex].text == lang['allusers']) {
loadLastPositionAllUsers();
}
else{
loadTrack(userid, trackid, 0);
}
}
function loadTrack(userid, trackid, update) { function loadTrack(userid, trackid, update) {
var title = document.getElementById('track').getElementsByClassName('menutitle')[0]; var title = document.getElementById('track').getElementsByClassName('menutitle')[0];
if (trackid < 0) { return; } if (trackid < 0) { return; }
@ -170,7 +160,6 @@ function loadTrack(userid, trackid, update) {
} }
function loadLastPositionAllUsers() { function loadLastPositionAllUsers() {
if (latest == 1) { trackid = 0; }
var xhr = getXHR(); var xhr = getXHR();
xhr.onreadystatechange = function () { xhr.onreadystatechange = function () {
if (xhr.readyState == 4) { if (xhr.readyState == 4) {
@ -179,16 +168,22 @@ function loadLastPositionAllUsers() {
var xml = xhr.responseXML; var xml = xhr.responseXML;
var positions = xml.getElementsByTagName('position'); var positions = xml.getElementsByTagName('position');
var posLen = positions.length; var posLen = positions.length;
var timestampMax = 0;
for (var i = 0; i < posLen; i++) { for (var i = 0; i < posLen; i++) {
var p = parsePosition(positions[i], i); var p = parsePosition(positions[i], i);
// set marker // set marker
setMarker(p, i, posLen); setMarker(p, i, posLen);
if (p.timestamp > timestampMax) {
timestampMax = p.timestamp;
} }
} }
zoomToExtent();
updateSummary(timestampMax);
}
xhr = null; xhr = null;
} }
} }
xhr.open('GET', 'utils/getpositions.php?trackid=' + trackid + '&userid=' + userid + '&last=' + latest, true); xhr.open('GET', 'utils/getpositions.php?last=' + latest, true);
xhr.send(); xhr.send();
} }
@ -264,15 +259,6 @@ function getPopupHtml(p, i, count) {
'<img class="icon" alt="' + lang['tdistance'] + '" title="' + lang['tdistance'] + '" src="images/distance_blue.svg"> ' + '<img class="icon" alt="' + lang['tdistance'] + '" title="' + lang['tdistance'] + '" src="images/distance_blue.svg"> ' +
(p.totalMeters.toKm() * factor_km).toFixed(2) + ' ' + unit_km + '<br>' + '</div>'; (p.totalMeters.toKm() * factor_km).toFixed(2) + ' ' + unit_km + '<br>' + '</div>';
} }
if (p.username == null){
p.username = lang["nousername"];
}
if (p.trackname == null){
p.trackname = lang["notrackname"];
}
if (p.comments == null){
p.comments = lang["nocomment"];
}
var popup = var popup =
'<div id="popup">' + '<div id="popup">' +
'<div id="pheader">' + '<div id="pheader">' +
@ -407,27 +393,22 @@ Number.prototype.toKmH = function() {
return Math.round(this * 3600 / 10) / 100; return Math.round(this * 3600 / 10) / 100;
}; };
// negate value // toggle latest
function toggleLatest() { function toggleLatest() {
var usersSelect = document.getElementsByName('user')[0]; var usersSelect = document.getElementsByName('user')[0];
if (latest == 0) { if (latest == 0) {
if (usersSelect.options[usersSelect.length-1].text != lang['allusers']){ if (!hasAllUsers() && usersSelect.length > 2) {
var option = document.createElement("option"); usersSelect.options.add(new Option('- ' + lang['allusers'] + ' -', 'all'), usersSelect.options[1]);
option.text = lang['allusers'];
if (usersSelect.length >= 2){
usersSelect.add(option);
}
} }
latest = 1; latest = 1;
loadTrack(userid, 0, 1); loadTrack(userid, 0, 1);
} } else {
else { if (hasAllUsers()) {
if (usersSelect.options[usersSelect.length-1].text == lang['allusers']){ usersSelect.selectedIndex = 0;
usersSelect.remove(usersSelect.length-1); usersSelect.remove(1);
} }
latest = 0; latest = 0;
loadTrack(userid, trackid, 1); loadTrack(userid, trackid, 1);
} }
} }
@ -449,17 +430,10 @@ function selectTrack(f) {
function selectUser(f) { function selectUser(f) {
userid = f.options[f.selectedIndex].value; userid = f.options[f.selectedIndex].value;
if (isSelectedAllUsers()) {
if (f.options[f.selectedIndex].text == lang['allusers']){ clearOptions(document.getElementsByName('track')[0]);
var trackSelect = document.getElementsByName('track')[0];
var length = trackSelect.options.length;
for (i = 0; i < length; i++) {
trackSelect.options[i] = null;
}
loadLastPositionAllUsers(); loadLastPositionAllUsers();
} } else {
else{
document.getElementById('latest').checked = false;
getTracks(userid); getTracks(userid);
} }
} }
@ -514,23 +488,41 @@ function clearOptions(el) {
} }
} }
function reload(userid, trackid) {
if (isSelectedAllUsers()) {
loadLastPositionAllUsers();
} else {
loadTrack(userid, trackid, 0);
}
}
function autoReload() { function autoReload() {
if (live == 0) { if (live == 0) {
live = 1; live = 1;
var usersSelect = document.getElementsByName('user')[0]; if (isSelectedAllUsers()) {
if (usersSelect[usersSelect.selectedIndex].text == lang['allusers']) {
auto = setInterval(function () { loadLastPositionAllUsers(); }, interval * 1000); auto = setInterval(function () { loadLastPositionAllUsers(); }, interval * 1000);
} } else {
else{
auto = setInterval(function () { loadTrack(userid, trackid, 0); }, interval * 1000); auto = setInterval(function () { loadTrack(userid, trackid, 0); }, interval * 1000);
} }
} } else {
else {
live = 0; live = 0;
clearInterval(auto); clearInterval(auto);
} }
} }
function isSelectedAllUsers() {
var usersSelect = document.getElementsByName('user')[0];
return usersSelect[usersSelect.selectedIndex].value == 'all';
}
function hasAllUsers() {
var usersSelect = document.getElementsByName('user')[0];
if (usersSelect.length > 2 && usersSelect.options[1].value == 'all') {
return true;
}
return false;
}
function setTime() { function setTime() {
var i = parseInt(prompt(lang['newinterval'])); var i = parseInt(prompt(lang['newinterval']));
if (!isNaN(i) && i != interval) { if (!isNaN(i) && i != interval) {
@ -600,7 +592,11 @@ function waitAndInit(api) {
zoomToBounds(savedBounds); zoomToBounds(savedBounds);
update = 0; update = 0;
} }
if (latest && isSelectedAllUsers()) {
loadLastPositionAllUsers();
} else {
loadTrack(userid, trackid, update); loadTrack(userid, trackid, update);
}
// save current api as default // save current api as default
setCookie('api', api, 30); setCookie('api', api, 30);
} }

View File

@ -121,9 +121,9 @@ $lang["iparsefailure"] = "Parsing failed";
$lang["idatafailure"] = "No track data in imported file"; $lang["idatafailure"] = "No track data in imported file";
$lang["isizefailure"] = "The uploaded file size should not exceed %d bytes"; // substitutes number of bytes $lang["isizefailure"] = "The uploaded file size should not exceed %d bytes"; // substitutes number of bytes
$lang["imultiple"] = "Notice, multiple tracks imported (%d)"; // substitutes number of imported tracks $lang["imultiple"] = "Notice, multiple tracks imported (%d)"; // substitutes number of imported tracks
$lang["allusers"] = "All Users"; $lang["allusers"] = "All users";
$lang["notrackname"] = "No Track"; $lang["notrackname"] = "No track";
$lang["nousername"] = "No User"; $lang["nousername"] = "No user";
$lang["nocomment"] = "No Comment"; $lang["nocomment"] = "No comment";
?> ?>

View File

@ -34,7 +34,7 @@ if ($userId) {
if ($trackId) { if ($trackId) {
// get all track data // get all track data
$positionsArr = uPosition::getAll($userId, $trackId); $positionsArr = uPosition::getAll($userId, $trackId);
} else { } else if ($last) {
// get data only for latest point // get data only for latest point
$position = uPosition::getLast($userId); $position = uPosition::getLast($userId);
if ($position->isValid) { if ($position->isValid) {
@ -42,9 +42,8 @@ if ($userId) {
} }
} }
} }
} } else if ($last) {
else{ if (uConfig::$public_tracks || ($auth->isAuthenticated() && ($auth->isAdmin()))) {
if ($last) {
$positionsArr = uPosition::getLastAllUsers(); $positionsArr = uPosition::getLastAllUsers();
} }
} }
@ -72,9 +71,9 @@ foreach ($positionsArr as $position) {
$xml->writeElement("username", $position->userLogin); $xml->writeElement("username", $position->userLogin);
$xml->writeElement("trackid", $position->trackId); $xml->writeElement("trackid", $position->trackId);
$xml->writeElement("trackname", $position->trackName); $xml->writeElement("trackname", $position->trackName);
$distance = isset($prevPosition) ? $position->distanceTo($prevPosition) : 0; $distance = !$last && isset($prevPosition) ? $position->distanceTo($prevPosition) : 0;
$xml->writeElement("distance", round($distance)); $xml->writeElement("distance", round($distance));
$seconds = isset($prevPosition) ? $position->secondsTo($prevPosition) : 0; $seconds = !$last && isset($prevPosition) ? $position->secondsTo($prevPosition) : 0;
$xml->writeElement("seconds", $seconds); $xml->writeElement("seconds", $seconds);
$xml->endElement(); $xml->endElement();
$prevPosition = $position; $prevPosition = $position;