Add track edit/delete feature

This commit is contained in:
Bartek Fabiszewski 2017-04-14 17:24:09 +02:00
parent a64213940a
commit 7088d326ab
11 changed files with 301 additions and 18 deletions

View File

@ -53,6 +53,10 @@ select {
#menu input[type = "checkbox"] {
width: auto;
}
.menulink {
display: block;
margin-top: .2em;
}
#main {
height: 100%;
margin-right: 165px;

View File

@ -102,17 +102,27 @@
}
/**
* Delete all user's positions
* Delete all user's positions, optionally limit to given track
*
* @param string $userId User id
* @param int $userId User id
* @param int $trackId Optional track id
* @return bool True if success, false otherwise
*/
public function deleteAll($userId) {
public function deleteAll($userId, $trackId = NULL) {
$ret = false;
if (!empty($userId)) {
$query = "DELETE FROM positions WHERE user_id = ?";
$args = [];
$where = "WHERE user_id = ?";
$args[0] = "i";
$args[1] = &$userId;
if (!empty($trackId)) {
$where .= " AND track_id = ?";
$args[0] .= "i";
$args[2] = &$trackId;
}
$query = "DELETE FROM positions $where";
$stmt = self::$db->prepare($query);
$stmt->bind_param('i', $userId);
call_user_func_array([ $stmt, 'bind_param' ], $args);
$stmt->execute();
if (!self::$db->error && !$stmt->errno) {
$ret = true;
@ -127,6 +137,7 @@
* (for given user if specified)
*
* @param int $userId Optional user id
* @return uPosition Self
*/
public function getLast($userId = NULL) {
if (!empty($userId)) {
@ -145,6 +156,7 @@
$where
ORDER BY p.time DESC LIMIT 1";
$this->loadWithQuery($query, $params);
return $this;
}
/**

View File

@ -18,6 +18,7 @@
*/
require_once(ROOT_DIR . "/helpers/db.php");
require_once(ROOT_DIR . "/helpers/position.php");
/**
* Track handling
@ -77,6 +78,64 @@
return $trackId;
}
/**
* Delete track with all positions
*
* @return bool True if success, false otherwise
*/
public function delete() {
$ret = false;
if ($this->isValid) {
// delete positions
$position = new uPosition();
if ($position->deleteAll($this->userId, $this->id) === false) {
return false;
}
// delete track metadata
$query = "DELETE FROM tracks WHERE id = ?";
$stmt = self::$db->prepare($query);
$stmt->bind_param('i', $this->id);
$stmt->execute();
if (!self::$db->error && !$stmt->errno) {
$ret = true;
$this->id = NULL;
$this->userId = NULL;
$this->name = NULL;
$this->comment = NULL;
$this->isValid = false;
}
$stmt->close();
}
return $ret;
}
/**
* Update track
*
* @param string|null $name New name (not empty string) or NULL if not changed
* @param string|null $comment New comment or NULL if not changed (to remove content use empty string: "")
* @return bool True if success, false otherwise
*/
public function update($name = NULL, $comment = NULL) {
$ret = false;
if (empty($name)) { $name = $this->name; }
if (is_null($comment)) { $comment = $this->comment; }
if ($comment == "") { $comment = NULL; }
if ($this->isValid) {
$query = "UPDATE tracks SET name = ?, comment = ? WHERE id = ?";
$stmt = self::$db->prepare($query);
$stmt->bind_param('ssi', $name, $comment, $this->id);
$stmt->execute();
if (!self::$db->error && !$stmt->errno) {
$ret = true;
$this->name = $name;
$this->comment = $comment;
}
$stmt->close();
}
return $ret;
}
/**
* Delete all user's tracks
*

View File

@ -80,6 +80,7 @@
var init_latitude = '<?= $config::$init_latitude ?>';
var init_longitude = '<?= $config::$init_longitude ?>';
var lang = <?= json_encode($lang) ?>;
var admin = <?= json_encode($user->isAdmin) ?>;
var auth = '<?= ($user->isValid) ? $user->login : "null" ?>';
var pass_regex = <?= $config->passRegex() ?>;
</script>
@ -95,6 +96,9 @@
<?php if ($user->isAdmin): ?>
<script type="text/javascript" src="js/admin.js"></script>
<?php endif; ?>
<?php if ($user->isValid): ?>
<script type="text/javascript" src="js/track.js"></script>
<?php endif; ?>
<script type="text/javascript" src="js/pass.js"></script>
<script type="text/javascript" src="//www.google.com/jsapi"></script>
<script type="text/javascript">
@ -186,15 +190,18 @@
<div id="export">
<u><?= $lang["download"] ?></u><br>
<a href="javascript:void(0);" onclick="load('kml', userid, trackid);">kml</a><br>
<a href="javascript:void(0);" onclick="load('gpx', userid, trackid);">gpx</a><br>
<a class="menulink" href="javascript:void(0);" onclick="load('kml', userid, trackid);">kml</a>
<a class="menulink" href="javascript:void(0);" onclick="load('gpx', userid, trackid);">gpx</a>
</div>
<?php if ($user->isAdmin): ?>
<?php if ($user->isValid): ?>
<div id="admin_menu">
<u><?= $lang["adminmenu"] ?></u><br>
<a href="javascript:void(0);" onclick="addUser()"><?= $lang["adduser"] ?></a><br>
<a href="javascript:void(0);" onclick="editUser()"><?= $lang["edituser"] ?></a><br>
<?php if ($user->isAdmin): ?>
<a class="menulink" href="javascript:void(0);" onclick="addUser()"><?= $lang["adduser"] ?></a>
<a class="menulink" href="javascript:void(0);" onclick="editUser()"><?= $lang["edituser"] ?></a>
<?php endif; ?>
<a class="menulink" href="javascript:void(0);" onclick="editTrack()"><?= $lang["edittrack"] ?></a>
</div>
<?php endif; ?>

View File

@ -47,7 +47,7 @@ function editUser() {
}
function confirmedDelete(login) {
return confirm(sprintf(lang['deletewarn'], '"' + login + '"'));
return confirm(sprintf(lang['userdelwarn'], '"' + login + '"'));
}
function submitUser(action) {

View File

@ -113,6 +113,7 @@ function getXHR() {
}
function loadTrack(userid, trackid, update) {
if (trackid < 0) { return; }
if (latest == 1) { trackid = 0; }
var xhr = getXHR();
xhr.onreadystatechange = function () {
@ -274,7 +275,11 @@ function setTrack(t) {
}
function selectTrack(f) {
trackid = f.options[f.selectedIndex].value;
if (f.selectedIndex >= 0) {
trackid = f.options[f.selectedIndex].value;
} else {
trackid = 0;
}
document.getElementById('latest').checked = false;
if (latest == 1) { toggleLatest(); }
loadTrack(userid, trackid, 1);

100
js/track.js Normal file
View File

@ -0,0 +1,100 @@
/* μlogger
*
* Copyright(C) 2017 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/>.
*/
function editTrack() {
var userForm = document.getElementsByName('user')[0];
var trackUser = userForm.options[userForm.selectedIndex].text;
if (trackUser != auth && !admin) {
alert(lang['owntrackswarn']);
return;
}
var trackForm = document.getElementsByName('track')[0];
if (trackForm.selectedIndex < 0) {
return;
}
var trackId = trackForm.options[trackForm.selectedIndex].value;
var trackName = trackForm.options[trackForm.selectedIndex].text;
var message = '<div style="float:left">' + sprintf(lang['editingtrack'], '<b>' + htmlEncode(trackName) + '</b>') + '</div>';
message += '<div class="red-button"><b><a href="javascript:void(0);" onclick="submitTrack(\'delete\'); return false">' + lang['deltrack'] + '</a></b></div>';
message += '<div style="clear: both; padding-bottom: 1em;"></div>';
var form = '<form id="trackForm" method="post" onsubmit="submitTrack(\'update\'); return false">';
form += '<input type="hidden" name="trackid" value="' + trackId + '">';
form += '<label><b>' + lang['trackname'] + '</b></label><input type="text" placeholder="' + lang['trackname'] + '" name="trackname" value="' + htmlEncode(trackName) + '" required>';
form += '<div class="buttons"><button type="button" onclick="removeModal()">' + lang['cancel'] + '</button><button type="submit">' + lang['submit'] + '</button></div>';
form += '</form>';
showModal(message + form);
}
function confirmedDelete(name) {
return confirm(sprintf(lang['trackdelwarn'], '"' + name + '"'));
}
function submitTrack(action) {
var form = document.getElementById('trackForm');
var trackId = parseInt(form.elements['trackid'].value);
var trackName = form.elements['trackname'].value.trim();
if (isNaN(trackId)) {
alert(lang['allrequired']);
return;
}
if (action != 'delete') {
if (!trackName) {
alert(lang['allrequired']);
return;
}
} else {
if (!confirmedDelete(trackName)) {
return;
}
}
var xhr = getXHR();
xhr.onreadystatechange = function() {
if (xhr.readyState == 4 && xhr.status == 200) {
var xml = xhr.responseXML;
var message = "";
if (xml) {
var root = xml.getElementsByTagName('root');
if (root.length && getNode(root[0], 'error') == 0) {
removeModal();
alert(lang['actionsuccess']);
var f = document.getElementsByName('track')[0];
if (action == 'delete') {
// select current track in tracks form
f.remove(f.selectedIndex);
clearMap();
selectTrack(f);
} else {
f.options[f.selectedIndex].innerHTML = htmlEncode(trackName);
}
return;
}
errorMsg = getNode(root[0], 'message');
if (errorMsg) { message = errorMsg; }
}
alert(lang['actionfailure'] + '\n' + message);
xhr = null;
}
}
xhr.open('POST', 'utils/handletrack.php', true);
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
var params = 'action=' + action + '&trackid=' + trackId + '&trackname=' + encodeURIComponent(trackName);
params = params.replace(/%20/g, '+');
xhr.send(params);
return;
}

View File

@ -38,7 +38,7 @@
}
// choose password messages based on config
$lang['passrules'] = $lang["passrules"][$config::$pass_strength];
$lang['passrules'] = isset($lang["passrules"][$config::$pass_strength]) ? $lang["passrules"][$config::$pass_strength] : "";
$lang['passlenmin'] = sprintf($lang["passlenmin"], $config::$pass_lenmin);
?>

View File

@ -72,13 +72,19 @@ $lang["allrequired"] = "All fields are required";
$lang["passnotmatch"] = "Passwords don't match";
$lang["actionsuccess"] = "Action completed successfully";
$lang["actionfailure"] = "Something went wrong";
$lang["deletewarn"] = "Warning!\n\nYou are going to permanently delete user %s, together with all their routes and positions.\n\nAre you sure?"; // substitutes user login
$lang["userdelwarn"] = "Warning!\n\nYou are going to permanently delete user %s, together with all their routes and positions.\n\nAre you sure?"; // substitutes user login
$lang["editinguser"] = "You are editing user %s"; // substitutes user login
$lang["selfeditwarn"] = "Your can't edit your own user with this tool";
$lang["apifailure"] = "Sorry, can't load %s API"; // substitures api name (gmaps or openlayers)
$lang["trackdelwarn"] = "Warning!\n\nYou are going to permanently delete track %s and all its positions.\n\nAre you sure?"; // substitutes track name
$lang["editingtrack"] = "You are editing track %s"; // substitutes track name
$lang["deltrack"] = "Remove track";
$lang["trackname"] = "Track name";
$lang["edittrack"] = "Edit track";
$lang["passlenmin"] = "Password must be at least %d characters"; // substitutes password minimum length
$lang["passrules"][1] = "It should contain at least one upper case letter, one lower case letter";
$lang["passrules"][2] = "It should contain at least one upper case letter, one lower case letter and one digit";
$lang["passrules"][3] = "It should contain at least one upper case letter, one lower case letter, one digit and one non-alphanumeric character";
$lang["passrules"][1] = "It should contain at least one lower case letter, one upper case letter";
$lang["passrules"][2] = "It should contain at least one lower case letter, one upper case letter and one digit";
$lang["passrules"][3] = "It should contain at least one lower case letter, one upper case letter, one digit and one non-alphanumeric character";
$lang["owntrackswarn"] = "Your can only edit your own tracks";
?>

View File

@ -70,9 +70,19 @@ $lang["allrequired"] = "Wszystkie pola są wymagane";
$lang["passnotmatch"] = "Hasła nie pasują do siebie";
$lang["actionsuccess"] = "Operacja zakończona pomyślnie";
$lang["actionfailure"] = "Wystąpił błąd";
$lang["deletewarn"] = "Uwaga!\n\nZamierzasz całkowicie usunąć użytkownika %s, razem ze wszystkimi jego trasami i pozycjami.\n\nCzy na pewno?";
$lang["userdeletewarn"] = "Uwaga!\n\nZamierzasz całkowicie usunąć użytkownika %s, razem ze wszystkimi jego trasami i pozycjami.\n\nCzy na pewno?";
$lang["editinguser"] = "Edytujesz użytkownika %s";
$lang["selfeditwarn"] = "Nie można edytować własnego użytkownika za pomocą tego narzędzia";
$lang["apifailure"] = "Niestety ładowanie API %s nie powiodło się";
$lang["trackdelwarn"] = "Uwaga!\n\nZamierzasz całkowicie usunąć trasę %s wraz ze wszystkimi pozycjami.\n\nCzy na pewno?";
$lang["editingtrack"] = "Edytujesz trasę %s";
$lang["deltrack"] = "Usuń trasę";
$lang["trackname"] = "Nazwa trasy";
$lang["edittrack"] = "Edytuj trasę";
$lang["passlenmin"] = "Hasło musi się składać z minimum %d znaków";
$lang["passrules"][1] = "Powinno ono zawierać przynajmniej jedną małą i jedną wielką literę";
$lang["passrules"][2] = "Powinno ono zawierać przynajmniej jedną małą, jedną wielką literę i jedną cyfrę";
$lang["passrules"][3] = "Powinno ono zawierać przynajmniej jedną małą, jedną wielką literę, jedną cyfrę i jeden znak specjalny (nie alfanumeryczny)";
$lang["owntrackswarn"] = "Możesz edytować tylko swoje własne trasy";
?>

80
utils/handletrack.php Normal file
View File

@ -0,0 +1,80 @@
<?php
/* μlogger
*
* Copyright(C) 2017 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(dirname(__DIR__) . "/auth.php"); // sets $mysqli, $user
require_once(ROOT_DIR . "/helpers/track.php");
/**
* Exit with xml response
* @param boolean $isError Error if true
* @param string $errorMessage Optional error message
*/
function exitWithStatus($isError, $errorMessage = NULL) {
header("Content-type: text/xml");
$xml = new XMLWriter();
$xml->openURI("php://output");
$xml->startDocument("1.0");
$xml->setIndent(true);
$xml->startElement('root');
$xml->writeElement("error", (int) $isError);
if ($isError) {
$xml->writeElement("message", $errorMessage);
}
$xml->endElement();
$xml->endDocument();
$xml->flush();
exit;
}
$action = isset($_REQUEST['action']) ? $_REQUEST['action'] : NULL;
$trackId = isset($_REQUEST['trackid']) ? trim($_REQUEST['trackid']) : NULL;
$trackName = isset($_REQUEST['trackname']) ? trim($_REQUEST['trackname']) : NULL;
if (empty($action) || empty($trackId)) {
exitWithStatus(true, $lang["servererror"]);
}
$track = new uTrack($trackId);
if (!$track->isValid || (!$user->isAdmin && $user->id != $track->userId)) {
exitWithStatus(true, $lang["servererror"]);
}
switch ($action) {
case 'update':
if (empty($trackName)) {
exitWithStatus(true, $lang["servererror"]);
}
if ($track->update($trackName) === false) {
exitWithStatus(true, $mysqli->error);
}
break;
case 'delete':
if ($track->delete() === false) {
exitWithStatus(true, $mysqli->error);
}
break;
default:
exitWithStatus(true, $lang["servererror"]);
break;
}
exitWithStatus(false);
?>