Add basic position editing
This commit is contained in:
parent
260d576fc2
commit
9417d75632
11
css/main.css
11
css/main.css
@ -289,6 +289,17 @@ label[for=user] {
|
||||
color: #f0f8ff;
|
||||
}
|
||||
|
||||
#pfooter div:first-child {
|
||||
width: 40%;
|
||||
float: left;
|
||||
}
|
||||
|
||||
#pfooter div:last-child {
|
||||
width: 40%;
|
||||
float: right;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#bottom {
|
||||
position: absolute;
|
||||
z-index: 10000;
|
||||
|
@ -71,7 +71,7 @@ require_once(ROOT_DIR . "/helpers/upload.php");
|
||||
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)
|
||||
WHERE id = ? LIMIT 1";
|
||||
WHERE p.id = ? LIMIT 1";
|
||||
$params = [ $positionId ];
|
||||
try {
|
||||
$this->loadWithQuery($query, $params);
|
||||
@ -91,6 +91,15 @@ require_once(ROOT_DIR . "/helpers/upload.php");
|
||||
return uDb::getInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Has image
|
||||
*
|
||||
* @return bool True if has image
|
||||
*/
|
||||
public function hasImage() {
|
||||
return !empty($this->image);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add position
|
||||
*
|
||||
@ -135,6 +144,70 @@ require_once(ROOT_DIR . "/helpers/upload.php");
|
||||
return $positionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save position to database
|
||||
*
|
||||
* @return bool True if success, false otherwise
|
||||
*/
|
||||
public function update() {
|
||||
$ret = false;
|
||||
if ($this->isValid) {
|
||||
try {
|
||||
$query = "UPDATE " . self::db()->table('positions') . " SET
|
||||
time = " . self::db()->from_unixtime('?') . ", user_id = ?, track_id = ?, latitude = ?, longitude = ?, altitude = ?,
|
||||
speed = ?, bearing = ?, accuracy = ?, provider = ?, comment = ?, image = ? WHERE id = ?";
|
||||
$stmt = self::db()->prepare($query);
|
||||
$params = [
|
||||
$this->timestamp,
|
||||
$this->userId,
|
||||
$this->trackId,
|
||||
$this->latitude,
|
||||
$this->longitude,
|
||||
$this->altitude,
|
||||
$this->speed,
|
||||
$this->bearing,
|
||||
$this->accuracy,
|
||||
$this->provider,
|
||||
$this->comment,
|
||||
$this->image,
|
||||
$this->id
|
||||
];
|
||||
$stmt->execute($params);
|
||||
$ret = true;
|
||||
} catch (PDOException $e) {
|
||||
// TODO: handle exception
|
||||
syslog(LOG_ERR, $e->getMessage());
|
||||
}
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete positions
|
||||
*
|
||||
* @return bool True if success, false otherwise
|
||||
*/
|
||||
public function delete() {
|
||||
$ret = false;
|
||||
if ($this->isValid) {
|
||||
try {
|
||||
$query = "DELETE FROM " . self::db()->table('positions') . " WHERE id = ?";
|
||||
$stmt = self::db()->prepare($query);
|
||||
$stmt->execute([ $this->id ]);
|
||||
if ($this->hasImage()) {
|
||||
uUpload::delete($this->image);
|
||||
}
|
||||
$ret = true;
|
||||
$this->id = NULL;
|
||||
$this->isValid = false;
|
||||
} catch (PDOException $e) {
|
||||
// TODO: handle exception
|
||||
syslog(LOG_ERR, $e->getMessage());
|
||||
}
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all user's positions, optionally limit to given track
|
||||
*
|
||||
|
@ -309,9 +309,11 @@ export default class OpenLayersApi {
|
||||
* Close popup
|
||||
*/
|
||||
popupClose() {
|
||||
// eslint-disable-next-line no-undefined
|
||||
this.popup.setPosition(undefined);
|
||||
this.popup.getElement().firstElementChild.innerHTML = '';
|
||||
if (this.popup) {
|
||||
// eslint-disable-next-line no-undefined
|
||||
this.popup.setPosition(undefined);
|
||||
this.popup.getElement().firstElementChild.innerHTML = '';
|
||||
}
|
||||
this.viewModel.model.markerSelect = null;
|
||||
}
|
||||
|
||||
@ -492,6 +494,7 @@ export default class OpenLayersApi {
|
||||
* Clear map
|
||||
*/
|
||||
clearMap() {
|
||||
this.popupClose();
|
||||
if (this.layerTrack) {
|
||||
this.layerTrack.getSource().clear();
|
||||
}
|
||||
|
@ -17,9 +17,10 @@
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { lang as $, config } from './initializer.js';
|
||||
import { lang as $, auth, config } from './initializer.js';
|
||||
import GoogleMapsApi from './mapapi/api_gmaps.js';
|
||||
import OpenLayersApi from './mapapi/api_openlayers.js';
|
||||
import PositionDialogModel from './positiondialogmodel.js';
|
||||
import ViewModel from './viewmodel.js';
|
||||
import uDialog from './dialog.js';
|
||||
import uObserve from './observe.js';
|
||||
@ -138,12 +139,14 @@ export default class MapViewModel extends ViewModel {
|
||||
|
||||
/**
|
||||
* Get popup html
|
||||
* @param {number} id Position ID
|
||||
* @param {number} id Position index
|
||||
* @returns {HTMLDivElement}
|
||||
*/
|
||||
getPopupElement(id) {
|
||||
const pos = this.state.currentTrack.positions[id];
|
||||
const count = this.state.currentTrack.length;
|
||||
const user = this.state.currentTrack.user;
|
||||
const isEditable = auth.user && (auth.isAdmin || auth.user === user);
|
||||
let date = '–––';
|
||||
let time = '–––';
|
||||
if (pos.timestamp > 0) {
|
||||
@ -157,6 +160,10 @@ export default class MapViewModel extends ViewModel {
|
||||
} else if (pos.provider === 'network') {
|
||||
provider = ` <img class="icon" alt="${$._('network')}" title="${$._('network')}" src="images/network_dark.svg">`;
|
||||
}
|
||||
let editLink = '';
|
||||
if (isEditable) {
|
||||
editLink = `<a id="editposition" class="menu-link" data-bind="onUserAdd">${$._('editposition')}</a>`;
|
||||
}
|
||||
let stats = '';
|
||||
if (!this.state.showLatest) {
|
||||
stats =
|
||||
@ -182,7 +189,7 @@ export default class MapViewModel extends ViewModel {
|
||||
${(pos.altitude !== null) ? `<img class="icon" alt="${$._('altitude')}" title="${$._('altitude')}" src="images/altitude_dark.svg">${$.getLocaleAltitude(pos.altitude, true)}<br>` : ''}
|
||||
${(pos.accuracy !== null) ? `<img class="icon" alt="${$._('accuracy')}" title="${$._('accuracy')}" src="images/accuracy_dark.svg">${$.getLocaleAccuracy(pos.accuracy, true)}${provider}<br>` : ''}
|
||||
</div>${stats}</div>
|
||||
<div id="pfooter">${$._('pointof', id + 1, count)}</div>`;
|
||||
<div id="pfooter"><div>${$._('pointof', id + 1, count)}</div><div>${editLink}</div></div>`;
|
||||
const node = document.createElement('div');
|
||||
node.setAttribute('id', 'popup');
|
||||
node.innerHTML = html;
|
||||
@ -196,6 +203,13 @@ export default class MapViewModel extends ViewModel {
|
||||
modal.show();
|
||||
}
|
||||
}
|
||||
if (isEditable) {
|
||||
const edit = node.querySelector('#editposition');
|
||||
edit.onclick = () => {
|
||||
const vm = new PositionDialogModel(this.state, id);
|
||||
vm.init();
|
||||
}
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
|
@ -61,6 +61,17 @@ export default class uObserve {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger notify of property observers
|
||||
* @param {Object} obj
|
||||
* @param {string} property
|
||||
*/
|
||||
static forceUpdate(obj, property) {
|
||||
const value = obj._values[property];
|
||||
const observers = obj._observers[property];
|
||||
this.notify(observers, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if object property is observed;
|
||||
* Optionally check if it is observed by given observer
|
||||
|
@ -17,6 +17,7 @@
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import uAjax from './ajax.js';
|
||||
import uUtils from './utils.js';
|
||||
|
||||
/**
|
||||
@ -91,4 +92,60 @@ export default class uPosition {
|
||||
get totalSpeed() {
|
||||
return this.totalSeconds ? this.totalMeters / this.totalSeconds : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {Promise<void, Error>}
|
||||
*/
|
||||
delete() {
|
||||
return uPosition.update({
|
||||
action: 'delete',
|
||||
posid: this.id
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {Promise<void, Error>}
|
||||
*/
|
||||
save() {
|
||||
return uPosition.update({
|
||||
action: 'update',
|
||||
posid: this.id,
|
||||
comment: this.comment
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Save track data
|
||||
* @param {Object} data
|
||||
* @return {Promise<void, Error>}
|
||||
*/
|
||||
static update(data) {
|
||||
return uAjax.post('utils/handleposition.php', data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate distance to target point using haversine formula
|
||||
* @param {uPosition} target
|
||||
* @return {number} Distance in meters
|
||||
*/
|
||||
distanceTo(target) {
|
||||
const lat1 = uUtils.deg2rad(this.latitude);
|
||||
const lon1 = uUtils.deg2rad(this.longitude);
|
||||
const lat2 = uUtils.deg2rad(target.latitude);
|
||||
const lon2 = uUtils.deg2rad(target.longitude);
|
||||
const latD = lat2 - lat1;
|
||||
const lonD = lon2 - lon1;
|
||||
const bearing = 2 * Math.asin(Math.sqrt((Math.sin(latD / 2) ** 2) + Math.cos(lat1) * Math.cos(lat2) * (Math.sin(lonD / 2) ** 2)));
|
||||
return bearing * 6371000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate time elapsed since target point
|
||||
* @param {uPosition} target
|
||||
* @return {number} Number of seconds
|
||||
*/
|
||||
secondsTo(target) {
|
||||
return this.timestamp - target.timestamp;
|
||||
}
|
||||
|
||||
}
|
||||
|
114
js/src/positiondialogmodel.js
Normal file
114
js/src/positiondialogmodel.js
Normal file
@ -0,0 +1,114 @@
|
||||
/*
|
||||
* μlogger
|
||||
*
|
||||
* Copyright(C) 2020 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/>.
|
||||
*/
|
||||
|
||||
import { lang as $ } from './initializer.js';
|
||||
import ViewModel from './viewmodel.js';
|
||||
import uDialog from './dialog.js';
|
||||
import uObserve from './observe.js';
|
||||
import uUtils from './utils.js';
|
||||
|
||||
/**
|
||||
* @class PositionDialogModel
|
||||
*/
|
||||
export default class PositionDialogModel extends ViewModel {
|
||||
|
||||
/**
|
||||
* @param {uState} state
|
||||
* @param {number} positionIndex
|
||||
*/
|
||||
constructor(state, positionIndex) {
|
||||
super({
|
||||
onPositionDelete: null,
|
||||
onPositionUpdate: null,
|
||||
onCancel: null,
|
||||
comment: ''
|
||||
});
|
||||
this.state = state;
|
||||
this.positionIndex = positionIndex;
|
||||
this.position = this.state.currentTrack.positions[positionIndex];
|
||||
this.model.onPositionDelete = () => this.onPositionDelete();
|
||||
this.model.onPositionUpdate = () => this.onPositionUpdate();
|
||||
this.model.onCancel = () => this.onCancel();
|
||||
}
|
||||
|
||||
init() {
|
||||
const html = this.getHtml();
|
||||
this.dialog = new uDialog(html);
|
||||
this.dialog.show();
|
||||
this.bindAll(this.dialog.element);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {string}
|
||||
*/
|
||||
getHtml() {
|
||||
return `<div style="float:left;">${$._('editingposition', this.positionIndex + 1, `<b>${uUtils.htmlEncode(this.position.trackname)}</b>`)}</div>
|
||||
<div class="red-button button-resolve"><b><a data-bind="onPositionDelete">${$._('delposition')}</a></b></div>
|
||||
<div style="clear: both; padding-bottom: 1em;"></div>
|
||||
<form id="positionForm">
|
||||
<label><b>${$._('comment')}</b></label><br>
|
||||
<textarea style="width:100%;" maxlength="255" rows="5" placeholder="${$._('comment')}" name="comment" data-bind="comment">${uUtils.htmlEncode(this.position.comment)}</textarea>
|
||||
<div class="buttons">
|
||||
<button class="button-reject" data-bind="onCancel" type="button">${$._('cancel')}</button>
|
||||
<button class="button-resolve" data-bind="onPositionUpdate" type="submit">${$._('submit')}</button>
|
||||
</div>
|
||||
</form>`;
|
||||
}
|
||||
|
||||
onPositionDelete() {
|
||||
if (uDialog.isConfirmed($._('positiondelwarn', this.positionIndex + 1, uUtils.htmlEncode(this.position.trackname)))) {
|
||||
this.position.delete()
|
||||
.then(() => {
|
||||
const track = this.state.currentTrack;
|
||||
this.state.currentTrack = null;
|
||||
track.positions.splice(this.positionIndex, 1);
|
||||
track.recalculatePositions();
|
||||
this.state.currentTrack = track;
|
||||
this.dialog.destroy();
|
||||
}).catch((e) => { uUtils.error(e, `${$._('actionfailure')}\n${e.message}`); });
|
||||
}
|
||||
}
|
||||
|
||||
onPositionUpdate() {
|
||||
if (this.validate()) {
|
||||
this.position.comment = this.model.comment;
|
||||
this.position.save()
|
||||
.then(() => {
|
||||
uObserve.forceUpdate(this.state, 'currentTrack');
|
||||
this.dialog.destroy()
|
||||
})
|
||||
.catch((e) => { uUtils.error(e, `${$._('actionfailure')}\n${e.message}`); });
|
||||
}
|
||||
}
|
||||
|
||||
onCancel() {
|
||||
this.dialog.destroy();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate form
|
||||
* @return {boolean} True if valid
|
||||
*/
|
||||
validate() {
|
||||
if (this.model.comment === this.position.comment) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@ -49,6 +49,8 @@ export default class uTrack extends uPositionSet {
|
||||
this.user = user;
|
||||
this.plotData = [];
|
||||
this.maxId = 0;
|
||||
this.totalMeters = 0;
|
||||
this.totalSeconds = 0;
|
||||
this.listItem(id, name);
|
||||
}
|
||||
|
||||
@ -59,8 +61,14 @@ export default class uTrack extends uPositionSet {
|
||||
|
||||
clear() {
|
||||
super.clear();
|
||||
this.clearTrackCounters();
|
||||
}
|
||||
|
||||
clearTrackCounters() {
|
||||
this.maxId = 0;
|
||||
this.plotData.length = 0;
|
||||
this.totalMeters = 0;
|
||||
this.totalSeconds = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -84,30 +92,16 @@ export default class uTrack extends uPositionSet {
|
||||
* @param {boolean=} isUpdate If true append to old data
|
||||
*/
|
||||
fromJson(posArr, isUpdate = false) {
|
||||
let totalMeters = 0;
|
||||
let totalSeconds = 0;
|
||||
let positions = [];
|
||||
if (isUpdate && this.hasPositions) {
|
||||
positions = this.positions;
|
||||
const last = positions[this.length - 1];
|
||||
totalMeters = last.totalMeters;
|
||||
totalSeconds = last.totalSeconds;
|
||||
} else {
|
||||
this.clear();
|
||||
}
|
||||
for (const pos of posArr) {
|
||||
const position = uPosition.fromJson(pos);
|
||||
totalMeters += position.meters;
|
||||
totalSeconds += position.seconds;
|
||||
position.totalMeters = totalMeters;
|
||||
position.totalSeconds = totalSeconds;
|
||||
this.calculatePosition(position);
|
||||
positions.push(position);
|
||||
if (position.altitude != null) {
|
||||
this.plotData.push({ x: position.totalMeters, y: position.altitude });
|
||||
}
|
||||
if (position.id > this.maxId) {
|
||||
this.maxId = position.id;
|
||||
}
|
||||
}
|
||||
// update at the end to avoid observers update invidual points
|
||||
this.positions = positions;
|
||||
@ -243,4 +237,31 @@ export default class uTrack extends uPositionSet {
|
||||
return uAjax.post('utils/handletrack.php', data);
|
||||
}
|
||||
|
||||
recalculatePositions() {
|
||||
this.clearTrackCounters();
|
||||
let previous = null;
|
||||
for (const position of this.positions) {
|
||||
position.meters = previous ? position.distanceTo(previous) : 0;
|
||||
position.seconds = previous ? position.secondsTo(previous) : 0;
|
||||
this.calculatePosition(position);
|
||||
previous = position;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate position total counters and plot data
|
||||
* @param {uPosition} position
|
||||
*/
|
||||
calculatePosition(position) {
|
||||
this.totalMeters += position.meters;
|
||||
this.totalSeconds += position.seconds;
|
||||
position.totalMeters = this.totalMeters;
|
||||
position.totalSeconds = this.totalSeconds;
|
||||
if (position.altitude != null) {
|
||||
this.plotData.push({ x: position.totalMeters, y: position.altitude });
|
||||
}
|
||||
if (position.id > this.maxId) {
|
||||
this.maxId = position.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -317,4 +317,13 @@ export default class uUtils {
|
||||
console.error(details);
|
||||
alert(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Degrees to radians
|
||||
* @param {number} degrees
|
||||
* @return {number}
|
||||
*/
|
||||
static deg2rad(degrees) {
|
||||
return degrees * Math.PI / 180;
|
||||
}
|
||||
}
|
||||
|
@ -65,9 +65,12 @@ export default class ViewModel {
|
||||
observers.forEach(/** @param {HTMLElement} element */ (element) => {
|
||||
const name = element.dataset[dataProp];
|
||||
if (name === key) {
|
||||
if (element instanceof HTMLInputElement || element instanceof HTMLSelectElement) {
|
||||
if (element instanceof HTMLInputElement ||
|
||||
element instanceof HTMLSelectElement ||
|
||||
element instanceof HTMLTextAreaElement) {
|
||||
this.onChangeBind(element, key);
|
||||
} else if (element instanceof HTMLAnchorElement || element instanceof HTMLButtonElement) {
|
||||
} else if (element instanceof HTMLAnchorElement ||
|
||||
element instanceof HTMLButtonElement) {
|
||||
this.onClickBind(element, key);
|
||||
} else {
|
||||
this.viewUpdateBind(element, key);
|
||||
|
@ -111,6 +111,11 @@ $lang["editingtrack"] = "You are editing track %s"; // substitutes track name
|
||||
$lang["deltrack"] = "Remove track";
|
||||
$lang["trackname"] = "Track name";
|
||||
$lang["edittrack"] = "Edit track";
|
||||
$lang["positiondelwarn"] = "Warning!\n\nYou are going to permanently delete position %d of track %s.\n\nAre you sure?"; // substitutes position index and track name
|
||||
$lang["editingposition"] = "You are editing position #%d of track %s"; // substitutes position index and track name
|
||||
$lang["delposition"] = "Remove position";
|
||||
$lang["comment"] = "Comment";
|
||||
$lang["editposition"] = "Edit position";
|
||||
$lang["passlenmin"] = "Password must be at least %d characters"; // substitutes password minimum length
|
||||
$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";
|
||||
|
65
utils/handleposition.php
Normal file
65
utils/handleposition.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/* μlogger
|
||||
*
|
||||
* Copyright(C) 2020 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__) . "/helpers/auth.php");
|
||||
require_once(ROOT_DIR . "/helpers/lang.php");
|
||||
require_once(ROOT_DIR . "/helpers/track.php");
|
||||
require_once(ROOT_DIR . "/helpers/utils.php");
|
||||
require_once(ROOT_DIR . "/helpers/config.php");
|
||||
|
||||
$auth = new uAuth();
|
||||
|
||||
$action = uUtils::postString('action');
|
||||
$positionId = uUtils::postInt('posid');
|
||||
$comment = uUtils::postString('comment');
|
||||
|
||||
$lang = (new uLang(uConfig::$lang))->getStrings();
|
||||
|
||||
if (empty($action) || empty($positionId)) {
|
||||
uUtils::exitWithError($lang["servererror"]);
|
||||
}
|
||||
$position = new uPosition($positionId);
|
||||
if (!$position->isValid ||
|
||||
(!$auth->isAuthenticated() || (!$auth->isAdmin() && $auth->user->id !== $position->userId))) {
|
||||
uUtils::exitWithError($lang["servererror"]);
|
||||
}
|
||||
|
||||
switch ($action) {
|
||||
|
||||
case 'update':
|
||||
$position->comment = $comment;
|
||||
if ($position->update() === false) {
|
||||
uUtils::exitWithError($lang["servererror"]);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'delete':
|
||||
if ($position->delete() === false) {
|
||||
uUtils::exitWithError($lang["servererror"]);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
uUtils::exitWithError($lang["servererror"]);
|
||||
break;
|
||||
}
|
||||
|
||||
uUtils::exitWithSuccess();
|
||||
|
||||
?>
|
Loading…
x
Reference in New Issue
Block a user