From 46e3ddb88b50ade97cdb9611cc633ee0519ce5d8 Mon Sep 17 00:00:00 2001 From: Arne Hamann Date: Sat, 14 Jan 2023 12:11:01 +0100 Subject: [PATCH 01/19] Fix Favorite share public page Signed-off-by: Arne Hamann --- src/components/MapContainer.vue | 4 +- src/components/PublicFavoriteShareSideBar.vue | 2 +- src/components/map/ClickPopup.vue | 2 +- src/components/map/FavoritePopup.vue | 2 +- src/data/types/index.js | 59 ++++++++++--------- src/publicFavoriteShare.js | 2 +- src/{ => views}/PublicFavoriteShare.vue | 14 ++--- 7 files changed, 44 insertions(+), 41 deletions(-) rename src/{ => views}/PublicFavoriteShare.vue (90%) diff --git a/src/components/MapContainer.vue b/src/components/MapContainer.vue index ac7b86d74..e9672902d 100644 --- a/src/components/MapContainer.vue +++ b/src/components/MapContainer.vue @@ -55,7 +55,7 @@ - +
diff --git a/src/components/map/ClickPopup.vue b/src/components/map/ClickPopup.vue index 9af90160a..f5e4f1241 100644 --- a/src/components/map/ClickPopup.vue +++ b/src/components/map/ClickPopup.vue @@ -89,7 +89,7 @@ export default { props: { isVisible: VueTypes.bool.isRequired.def(false), - latLng: Types.LatLng.def(null), + latLng: Types.LatLng.def({ lat: 0, lng: 0 }), allowCategoryCustomization: VueTypes.bool.isRequired.def(false), allowEdits: VueTypes.bool.isRequired.def(false), }, diff --git a/src/components/map/FavoritePopup.vue b/src/components/map/FavoritePopup.vue index 4ddcbb081..55a16ca20 100644 --- a/src/components/map/FavoritePopup.vue +++ b/src/components/map/FavoritePopup.vue @@ -78,7 +78,7 @@ export default { }, props: { - favorite: Types.Favorite.isRequired.def({}), + favorite: Types.Favorite.isRequired, isVisible: VueTypes.bool.isRequired.def(false), allowEdits: VueTypes.bool.isRequired.def(false), allowCategoryCustomization: VueTypes.bool.def(true), diff --git a/src/data/types/index.js b/src/data/types/index.js index 41b1a3167..e1921c958 100644 --- a/src/data/types/index.js +++ b/src/data/types/index.js @@ -23,43 +23,46 @@ import * as VueTypes from 'vue-types' const LatLng = VueTypes.shape({ - lat: VueTypes.number, - lng: VueTypes.number, + lat: VueTypes.number(), + lng: VueTypes.number(), }) const Favorite = VueTypes.shape({ - id: VueTypes.number, - name: VueTypes.oneOfType([VueTypes.string, null]), - comment: VueTypes.oneOfType([VueTypes.string, null]), - category: VueTypes.string, - extensions: VueTypes.oneOfType([VueTypes.string, null]), - date_created: VueTypes.number, - date_modified: VueTypes.number, - lat: VueTypes.number, - lng: VueTypes.number, + id: VueTypes.number(), + name: VueTypes.oneOfType([VueTypes.string(), null]), + comment: VueTypes.oneOfType([VueTypes.string(), null]), + category: VueTypes.string(), + extensions: VueTypes.oneOfType([VueTypes.string(), null]), + date_created: VueTypes.number(), + date_modified: VueTypes.number(), + lat: VueTypes.number(), + lng: VueTypes.number(), + isDeletable: VueTypes.oneOfType([VueTypes.bool(), null]), + isUpdateable: VueTypes.oneOfType([VueTypes.bool(), null]), + isShareable: VueTypes.oneOfType([VueTypes.bool(), null]), }) const OSMGeoCodeResult = VueTypes.shape({ address: VueTypes.shape({ - country: VueTypes.string, - county: VueTypes.string, - country_code: VueTypes.string, - postcode: VueTypes.string, - village: VueTypes.string, - state: VueTypes.string, - city: VueTypes.string, - pedestrian: VueTypes.string, - house_number: VueTypes.string, - road: VueTypes.string, + country: VueTypes.string(), + county: VueTypes.string(), + country_code: VueTypes.string(), + postcode: VueTypes.string(), + village: VueTypes.string(), + state: VueTypes.string(), + city: VueTypes.string(), + pedestrian: VueTypes.string(), + house_number: VueTypes.string(), + road: VueTypes.string(), }).loose, - display_name: VueTypes.string, - lat: VueTypes.string, - lon: VueTypes.string, - osm_id: VueTypes.number, - osm_type: VueTypes.string, - place_id: VueTypes.number, + display_name: VueTypes.string(), + lat: VueTypes.string(), + lon: VueTypes.string(), + osm_id: VueTypes.number(), + osm_type: VueTypes.string(), + place_id: VueTypes.number(), - error: VueTypes.string, + error: VueTypes.string(), }).loose export default { diff --git a/src/publicFavoriteShare.js b/src/publicFavoriteShare.js index 13d154ea5..2546669fd 100644 --- a/src/publicFavoriteShare.js +++ b/src/publicFavoriteShare.js @@ -21,7 +21,7 @@ */ import Vue from 'vue' -import PublicFavoriteShare from './PublicFavoriteShare' +import PublicFavoriteShare from './views/PublicFavoriteShare' import './bootstrap' import store from './store/publicFavoriteShareStore' diff --git a/src/PublicFavoriteShare.vue b/src/views/PublicFavoriteShare.vue similarity index 90% rename from src/PublicFavoriteShare.vue rename to src/views/PublicFavoriteShare.vue index 9a8c72fb2..e77ded102 100644 --- a/src/PublicFavoriteShare.vue +++ b/src/views/PublicFavoriteShare.vue @@ -20,9 +20,9 @@ --> diff --git a/src/components/PhotoSuggestionsSidebarTab.vue b/src/components/PhotoSuggestionsSidebarTab.vue index c35f8fa67..12b8efe88 100644 --- a/src/components/PhotoSuggestionsSidebarTab.vue +++ b/src/components/PhotoSuggestionsSidebarTab.vue @@ -110,6 +110,7 @@ import NcActions from '@nextcloud/vue/dist/Components/NcActions' import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton' import NcListItem from '@nextcloud/vue/dist/Components/NcListItem' import optionsController from '../optionsController' +import {getToken} from "../utils/common"; export default { name: 'PhotoSuggestionsSidebarTab', @@ -161,7 +162,7 @@ export default { methods: { previewUrl(photo) { if (photo && photo.hasPreview) { - const token = optionsController.token + const token = getToken() return token ? generateUrl('apps/files_sharing/publicpreview/') + token + '?file=' + encodeURIComponent(photo.path) + '&x=341&y=256&a=1' : generateUrl('core') + '/preview?fileId=' + photo.fileId + '&x=341&y=256&a=1' diff --git a/src/components/Sidebar.vue b/src/components/Sidebar.vue index 9ef0f9b0a..f65174351 100644 --- a/src/components/Sidebar.vue +++ b/src/components/Sidebar.vue @@ -86,6 +86,7 @@ import moment from '@nextcloud/moment' import { Type as ShareTypes } from '@nextcloud/sharing' import axios from '@nextcloud/axios' import FileInfo from '../services/FileInfo' +import optionsController from "../optionsController"; export default { name: 'Sidebar', @@ -526,7 +527,7 @@ export default { this.title = title } - if (path && path.trim() !== '') { + if (path && path.trim() !== '' && !isPublic()) { // reset data, keep old fileInfo to not reload all tabs and just hide them this.error = null this.loading = true diff --git a/src/components/map/PhotoSuggestionsLayer.vue b/src/components/map/PhotoSuggestionsLayer.vue index f45000437..f77dd6b20 100644 --- a/src/components/map/PhotoSuggestionsLayer.vue +++ b/src/components/map/PhotoSuggestionsLayer.vue @@ -65,7 +65,7 @@ import { LMarker, LTooltip, LPopup } from 'vue2-leaflet' import Vue2LeafletMarkerCluster from 'vue2-leaflet-markercluster' import optionsController from '../../optionsController' -import { binSearch } from '../../utils/common' +import {binSearch, getToken} from '../../utils/common' const PHOTO_MARKER_VIEW_SIZE = 40 @@ -338,7 +338,7 @@ export default { }, getPreviewUrl(photo) { if (photo && photo.hasPreview) { - const token = optionsController.token + const token = getToken() return token ? generateUrl('apps/files_sharing/publicpreview/') + token + '?file=' + encodeURIComponent(photo.path) + '&x=341&y=256&a=1' : generateUrl('core') + '/preview?fileId=' + photo.fileId + '&x=341&y=256&a=1' diff --git a/src/components/map/PhotosLayer.vue b/src/components/map/PhotosLayer.vue index f23be4565..71ffa62ee 100644 --- a/src/components/map/PhotosLayer.vue +++ b/src/components/map/PhotosLayer.vue @@ -75,7 +75,7 @@ import { LMarker, LTooltip, LPopup } from 'vue2-leaflet' import Vue2LeafletMarkerCluster from 'vue2-leaflet-markercluster' import optionsController from '../../optionsController' -import {binSearch} from "../../utils/common"; +import {binSearch, getToken} from "../../utils/common"; const PHOTO_MARKER_VIEW_SIZE = 40 @@ -336,7 +336,7 @@ export default { }, getPreviewUrl(photo) { if (photo && photo.hasPreview) { - const token = optionsController.token + const token = getToken() return token ? generateUrl('apps/files_sharing/publicpreview/') + token + '?file=' + encodeURIComponent(photo.path) + '&x=341&y=256&a=1' : generateUrl('core') + '/preview?fileId=' + photo.fileId + '&x=341&y=256&a=1' diff --git a/src/main.js b/src/main.js index b74ee0966..754a666e4 100644 --- a/src/main.js +++ b/src/main.js @@ -31,10 +31,11 @@ import VueClipboard from 'vue-clipboard2' import Tooltip from '@nextcloud/vue/dist/Directives/Tooltip' import { emit } from '@nextcloud/event-bus' -import {generateUrl} from "@nextcloud/router" +import { generateUrl } from '@nextcloud/router' // Fixing Some leaflet webpack stuff See https://vue2-leaflet.netlify.app/faq/#my-map-and-or-markers-don-t-fully-render-what-gives import L from 'leaflet' +import { isPublic } from './utils/common' delete L.Icon.Default.prototype._getIconUrl L.Icon.Default.mergeOptions({ diff --git a/src/optionsController.js b/src/optionsController.js index 08e5a47b5..ddb4f346d 100644 --- a/src/optionsController.js +++ b/src/optionsController.js @@ -1,5 +1,6 @@ import * as network from './network' import { showWarning } from '@nextcloud/dialogs' +import {getToken} from "./utils/common"; const optionsController = { bounds: [ @@ -20,9 +21,6 @@ const optionsController = { myMapId: (window.location.pathname.includes('/apps/maps/m/')) ? parseInt(window.location.pathname.split('/apps/maps/m/')[1].split('/')[0]) : null, - token: (window.location.pathname.includes('/apps/maps/s/')) - ? window.location.pathname.split('/apps/maps/s/')[1].split('/')[0] - : null, myMapListShow: true, myMapsEnabled: true, disabledFavoriteCategories: [], @@ -35,12 +33,12 @@ const optionsController = { this.optionValues[k] = newOptionValues[k] } if (this.optionValues.isUpdateable) { - network.saveOptionValues(newOptionValues, this.myMapId, this.token) + network.saveOptionValues(newOptionValues, this.myMapId, getToken()) } }, restoreOptions(successCB = null) { - network.getOptionValues(this.myMapId, this.token) + network.getOptionValues(this.myMapId, getToken()) .then((response) => { this.handleOptionValues(response.data) if (successCB) { diff --git a/src/utils/common.js b/src/utils/common.js index f225389a8..82bee2e85 100644 --- a/src/utils/common.js +++ b/src/utils/common.js @@ -24,6 +24,7 @@ import { generateUrl } from '@nextcloud/router' import { showInfo } from '@nextcloud/dialogs' import axios from '@nextcloud/axios' +import {getCurrentUser} from "@nextcloud/auth"; export const getPublicShareCategory = () => { const el = document.querySelector('.header-appname') @@ -96,3 +97,18 @@ export const binSearch = (sortedArray, test) => { } return -1 } + +/** + * Is the current user an unauthenticated user? + */ +export const isPublic = function() { + return !getCurrentUser() +} + +/** + * Get the current share link token + */ +export const getToken = function() { + return document.getElementById('sharingToken') + && document.getElementById('sharingToken').value +} diff --git a/src/views/App.vue b/src/views/App.vue index 7016ae2e7..03e73f4f7 100644 --- a/src/views/App.vue +++ b/src/views/App.vue @@ -220,7 +220,7 @@ import AppNavigationDevicesItem from '../components/AppNavigationDevicesItem' import AppNavigationMyMapsItem from '../components/AppNavigationMyMapsItem' import optionsController from '../optionsController' import { getLetterColor, hslToRgb, Timer, getDeviceInfoFromUserAgent2, isComputer, isPhone, sleep } from '../utils' -import { binSearch } from '../utils/common' +import {binSearch, getToken, isPublic} from '../utils/common' import { poiSearchData } from '../utils/poiData' import { processGpx } from '../tracksUtils' @@ -308,8 +308,10 @@ export default { myMapsEnabled: optionsController.myMapsEnabled, myMapId: optionsController.myMapId, selectedMyMap: null, - // Public Page - token: optionsController.token, + // PublicPage + token: (window.location.pathname.includes('/apps/maps/s/') + ? window.location.pathname.split('/apps/maps/s/')[1].split('/')[0] + : null), } }, @@ -709,7 +711,9 @@ export default { this.selectedTrack.selected = false this.selectedTrack = null } - window.OCA.Files.Sidebar.state.file = '' + if (!isPublic()) { + window.OCA.Files.Sidebar.state.file = '' + } }, onToggleTrackme(enabled) { if (enabled) { @@ -878,7 +882,7 @@ export default { return } this.photosLoading = true - network.getPhotos(this.myMapId, this.token).then( + network.getPhotos(this.myMapId, getToken()).then( /* async (response) => { for (let i = 0; i * 500 < response.data.length; i++) { this.photos.push(...response.data.slice(i * 500, (i + 1) * 500)) @@ -1022,7 +1026,7 @@ export default { }, onPhotosClearCache() { this.photosLoading = true - network.clearPhotoCache(this.token).then(() => { + network.clearPhotoCache(getToken()).then(() => { showSuccess(t('maps', 'Cleared photo cache')) }).catch((error) => { console.error(error) @@ -1065,7 +1069,7 @@ export default { return } this.photosLoading = true - network.getPhotoSuggestions(this.myMapId, this.token).then((response) => { + network.getPhotoSuggestions(this.myMapId, getToken()).then((response) => { this.photoSuggestions = response.data.sort((a, b) => { if (a.dateTaken < b.dateTaken) { return -1 @@ -1390,7 +1394,7 @@ export default { this.favoritesLoading = true this.favorites = {} this.favoriteCategoryTokens = {} - network.getFavorites(this.myMapId, this.token).then((response) => { + network.getFavorites(this.myMapId, getToken()).then((response) => { response.data.forEach((f) => { if (!f.category) { f.category = t('maps', 'Personal') @@ -1403,7 +1407,7 @@ export default { }).then(() => { this.favoritesLoading = false }) - network.getSharedFavoriteCategories(this.myMapId, this.token).then((response) => { + network.getSharedFavoriteCategories(this.myMapId, getToken()).then((response) => { this.favoriteCategoryTokens = {} response.data.forEach((s) => { this.favoriteCategoryTokens[s.category] = s.token @@ -1503,7 +1507,7 @@ export default { this.selectedFavorite = f }, onFavoriteEdit(f, save = true) { - network.editFavorite(f.id, f.name, f.category, f.comment, f.lat, f.lng, this.myMapId, this.token).then((response) => { + network.editFavorite(f.id, f.name, f.category, f.comment, f.lat, f.lng, this.myMapId, getToken()).then((response) => { if (save) { this.saveAction({ type: 'favoriteEdit', @@ -1522,7 +1526,7 @@ export default { }) }, onFavoriteDelete(favid, save = true) { - network.deleteFavorite(favid, this.myMapId, this.token).then((response) => { + network.deleteFavorite(favid, this.myMapId, getToken()).then((response) => { if (save) { this.saveAction({ type: 'favoriteDelete', @@ -1536,7 +1540,7 @@ export default { }) }, onFavoritesDelete(favids, save = true) { - network.deleteFavorites(favids, this.myMapId, this.token).then((response) => { + network.deleteFavorites(favids, this.myMapId, getToken()).then((response) => { if (save) { const deleted = favids.map((favid) => { return { ...this.favorites[favid] } @@ -1655,7 +1659,7 @@ export default { if (category === null) { category = this.lastUsedFavoriteCategory } - return network.addFavorite(latLng.lat, latLng.lng, name, category, comment, extensions, this.myMapId, this.token).then((response) => { + return network.addFavorite(latLng.lat, latLng.lng, name, category, comment, extensions, this.myMapId, getToken()).then((response) => { const fav = response.data if (!fav.category) { fav.category = t('maps', 'Personal') @@ -1688,7 +1692,7 @@ export default { }) }, onRenameFavoriteCategory(e, save = true) { - network.renameFavoriteCategory([e.old], e.new, this.myMapId, this.token).then((response) => { + network.renameFavoriteCategory([e.old], e.new, this.myMapId, getToken()).then((response) => { if (save) { this.saveAction({ type: 'favoriteRenameCategory', From e90b557618ee0d50dd67a0379f7a020c4c86288e Mon Sep 17 00:00:00 2001 From: Arne Hamann Date: Wed, 25 Jan 2023 00:34:07 +0100 Subject: [PATCH 12/19] Fixed that photos from cache might be arrays, while from db are stdClass Signed-off-by: Arne Hamann --- lib/Controller/PublicPhotosController.php | 26 ++++++++++++----------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/lib/Controller/PublicPhotosController.php b/lib/Controller/PublicPhotosController.php index afed01cd7..4103a46d0 100644 --- a/lib/Controller/PublicPhotosController.php +++ b/lib/Controller/PublicPhotosController.php @@ -125,12 +125,13 @@ public function getPhotos(): DataResponse { $pre_path = $this->root->getUserFolder($owner)->getPath(); $result = $this->geophotoService->getAll($owner, $folder, true, false); $photos = array_map(function ($photo) use ($folder, $permissions, $pre_path) { - $photo['isCreatable'] = ($permissions & (1 << 2)) && $photo['isCreatable']; - $photo['isUpdateable'] = ($permissions & (1 << 1)) && $photo['isUpdateable']; - $photo['isDeletable'] = ($permissions & (1 << 3)) && $photo['isDeletable']; - $photo['path'] = $folder->getRelativePath($pre_path.$photo['path']); - $photo['filename'] = $photo['path']; - return $photo; + $photo_object = (object) $photo; + $photo_object->isCreatable = ($permissions & (1 << 2)) && $photo['isCreatable']; + $photo_object->isUpdateable = ($permissions & (1 << 1)) && $photo['isUpdateable']; + $photo_object->isDeletable = ($permissions & (1 << 3)) && $photo['isDeletable']; + $photo_object->path = $folder->getRelativePath($pre_path.$photo['path']); + $photo_object->filename = $photo_object->path; + return $photo_object; }, $result); } else { throw new NotPermittedException(); @@ -157,12 +158,13 @@ public function getNonLocalizedPhotos(): DataResponse { $pre_path = $this->root->getUserFolder($owner)->getPath(); $result = $this->geophotoService->getNonLocalized($owner, $folder, true, false); $photos = array_map(function ($photo) use ($folder, $permissions, $pre_path) { - $photo['isCreatable'] = ($permissions & (1 << 2)) && $photo['isCreatable']; - $photo['isUpdateable'] = ($permissions & (1 << 1)) && $photo['isUpdateable']; - $photo['isDeletable'] = ($permissions & (1 << 3)) && $photo['isDeletable']; - $photo['path'] = $folder->getRelativePath($pre_path.$photo['path']); - $photo['filename'] = $photo['path']; - return $photo; + $photo_object = (object) $photo; + $photo_object->isCreatable = ($permissions & (1 << 2)) && $photo['isCreatable']; + $photo_object->isUpdateable = ($permissions & (1 << 1)) && $photo['isUpdateable']; + $photo_object->isDeletable = ($permissions & (1 << 3)) && $photo['isDeletable']; + $photo_object->path = $folder->getRelativePath($pre_path.$photo['path']); + $photo_object->filename = $photo_object->path; + return $photo_object; }, $result); } else { throw new NotPermittedException(); From 8d08153fce62d2c9b256b2a3e5ac371427ed1d31 Mon Sep 17 00:00:00 2001 From: Arne Hamann Date: Wed, 25 Jan 2023 00:51:18 +0100 Subject: [PATCH 13/19] Disable photoSuggstion Save if readOnly Signed-off-by: Arne Hamann --- src/components/PhotoSuggestionsSidebarTab.vue | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/PhotoSuggestionsSidebarTab.vue b/src/components/PhotoSuggestionsSidebarTab.vue index 12b8efe88..58ae1b7c3 100644 --- a/src/components/PhotoSuggestionsSidebarTab.vue +++ b/src/components/PhotoSuggestionsSidebarTab.vue @@ -93,7 +93,7 @@ {{ t('maps', 'Save') }} @@ -154,6 +154,9 @@ export default { return filtered }, []) }, + readOnly() { + return !this.photoSuggestions.some((f) => (f.isUpdateable)) + }, }, watch: { From 65d5b57061f9c466d4731910700142c12b631748 Mon Sep 17 00:00:00 2001 From: Arne Hamann Date: Thu, 26 Jan 2023 00:44:37 +0100 Subject: [PATCH 14/19] Permissions non localized photos Signed-off-by: Arne Hamann --- lib/Service/GeophotoService.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/Service/GeophotoService.php b/lib/Service/GeophotoService.php index 8d35d2c53..8d759ac7c 100644 --- a/lib/Service/GeophotoService.php +++ b/lib/Service/GeophotoService.php @@ -232,6 +232,10 @@ public function getNonLocalized (string $userId, $folder=null, bool $respectNome $file_object->lastmod = $file->getMTime(); $file_object->size = $file->getSize(); $file_object->path = $path; + $file_object->isReadable = $file->isReadable(); + $file_object->isUpdateable = $file->isUpdateable(); + $file_object->isShareable = $file->isShareable(); + $file_object->isDeletable = $file->isDeletable(); $file_object->hasPreview = in_array($cacheEntry->getMimeType(), $previewEnableMimetypes); $filesById[] = $file_object; } From 11671406f00fcf1a3fffa946fee075b46b3a6fdc Mon Sep 17 00:00:00 2001 From: Arne Hamann Date: Thu, 26 Jan 2023 10:47:16 +0100 Subject: [PATCH 15/19] Fixed is Public Signed-off-by: Arne Hamann --- src/components/Sidebar.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Sidebar.vue b/src/components/Sidebar.vue index f65174351..2549b303c 100644 --- a/src/components/Sidebar.vue +++ b/src/components/Sidebar.vue @@ -86,7 +86,7 @@ import moment from '@nextcloud/moment' import { Type as ShareTypes } from '@nextcloud/sharing' import axios from '@nextcloud/axios' import FileInfo from '../services/FileInfo' -import optionsController from "../optionsController"; +import {isPublic} from "../utils/common"; export default { name: 'Sidebar', From 22d167e063c94d88bd30d528af0bc1bcd952c55c Mon Sep 17 00:00:00 2001 From: Arne Hamann Date: Thu, 26 Jan 2023 10:47:28 +0100 Subject: [PATCH 16/19] Public Tracks Signed-off-by: Arne Hamann --- appinfo/routes.php | 5 + lib/Controller/PublicPhotosController.php | 2 +- lib/Controller/PublicTracksController.php | 313 ++++++++++++++++++++++ src/components/AppNavigationTrackItem.vue | 4 +- src/network.js | 12 +- src/views/App.vue | 6 +- src/views/TrackMetadataTab.vue | 3 +- 7 files changed, 332 insertions(+), 13 deletions(-) create mode 100644 lib/Controller/PublicTracksController.php diff --git a/appinfo/routes.php b/appinfo/routes.php index 43749fab2..95c1caf78 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -110,6 +110,11 @@ ['name' => 'tracks#getTrackContentByFileId', 'url' => '/tracks/file/{id}', 'verb' => 'GET'], ['name' => 'tracks#editTrack', 'url' => '/tracks/{id}', 'verb' => 'PUT'], + ['name' => 'PublicTracks#getTracks', 'url' => '/s/{token}/tracks', 'verb' => 'GET'], + ['name' => 'PublicTracks#getTrackFileContent', 'url' => '/s/{token}/tracks/{id}', 'verb' => 'GET'], + ['name' => 'PublicTracks#getTrackContentByFileId', 'url' => '/s/{token}/tracks/file/{id}', 'verb' => 'GET'], + ['name' => 'PublicTracks#editTrack', 'url' => '/s/{token}/tracks/{id}', 'verb' => 'PUT'], + // devices API [ 'name' => 'devices_api#preflighted_cors', diff --git a/lib/Controller/PublicPhotosController.php b/lib/Controller/PublicPhotosController.php index 4103a46d0..5b46b761f 100644 --- a/lib/Controller/PublicPhotosController.php +++ b/lib/Controller/PublicPhotosController.php @@ -44,8 +44,8 @@ public function __construct($appName, IInitialStateService $initialStateService, IURLGenerator $urlGenerator, ShareManager $shareManager, - ISession $session, IUserManager $userManager, + ISession $session, GeophotoService $GeophotoService, PhotofilesService $photofilesService, IRootFolder $root) { diff --git a/lib/Controller/PublicTracksController.php b/lib/Controller/PublicTracksController.php new file mode 100644 index 000000000..d3555860e --- /dev/null +++ b/lib/Controller/PublicTracksController.php @@ -0,0 +1,313 @@ + + * @copyright Julien Veyssier 2019 + */ + +namespace OCA\Maps\Controller; + +use OCP\App\IAppManager; + +use OCP\EventDispatcher\IEventDispatcher; +use OCP\Files\IRootFolder; +use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; +use OCP\IInitialStateService; +use OCP\ISession; +use OCP\IURLGenerator; +use OCP\IConfig; +use \OCP\IL10N; + +use OCP\AppFramework\Http; +use OCP\AppFramework\Http\RedirectResponse; + +use OCP\AppFramework\Http\ContentSecurityPolicy; + +use OCP\IRequest; +use OCP\AppFramework\Http\TemplateResponse; +use OCP\AppFramework\Http\DataResponse; +use OCP\AppFramework\Controller; +use OCP\AppFramework\ApiController; +use OCP\Constants; +use OCP\Share; +use OCP\IUserManager; +use OCP\Share\Exceptions\ShareNotFound; +use OCP\Share\IManager; +use OCP\IServerContainer; +use OCP\IGroupManager; +use OCP\ILogger; + +use OCA\Maps\Service\TracksService; +use OCP\Share\IManager as ShareManager; + +/** + * @param string $text + * @return string + */ +function remove_utf8_bom(string $text): string { + $bom = pack('H*','EFBBBF'); + $text = preg_replace("/^$bom/", '', $text); + return $text; +} + +class PublicTracksController extends PublicPageController { + + protected IConfig $config; + protected ShareManager $shareManager; + protected IUserManager $userManager; + protected IL10N $l; + protected ILogger $logger; + protected TracksService $tracksService; + protected $appName; + protected IRootFolder $root; + + public function __construct($appName, + IRequest $request, + IEventDispatcher $eventDispatcher, + IConfig $config, + IInitialStateService $initialStateService, + IURLGenerator $urlGenerator, + ShareManager $shareManager, + IUserManager $userManager, + ISession $session, + IServerContainer $serverContainer, + IGroupManager $groupManager, + IL10N $l, + ILogger $logger, + TracksService $tracksService, + IRootFolder $root) { + parent::__construct($appName, $request, $eventDispatcher, $config, $initialStateService, $urlGenerator, $shareManager, $userManager, $session); + $this->tracksService = $tracksService; + $this->logger = $logger; + $this->groupManager = $groupManager; + $this->l = $l; + $this->root = $root; + } + + /** + * Validate the permissions of the share + * + * @param Share\IShare $share + * @return bool + */ + private function validateShare(\OCP\Share\IShare $share) { + // If the owner is disabled no access to the link is granted + $owner = $this->userManager->get($share->getShareOwner()); + if ($owner === null || !$owner->isEnabled()) { + return false; + } + + // If the initiator of the share is disabled no access is granted + $initiator = $this->userManager->get($share->getSharedBy()); + if ($initiator === null || !$initiator->isEnabled()) { + return false; + } + + return $share->getNode()->isReadable() && $share->getNode()->isShareable(); + } + + /** + * @return \OCP\Share\IShare + * @throws NotFoundException + */ + private function getShare() { + // Check whether share exists + try { + $share = $this->shareManager->getShareByToken($this->getToken()); + } catch (ShareNotFound $e) { + // The share does not exists, we do not emit an ShareLinkAccessedEvent + throw new NotFoundException(); + } + + if (!$this->validateShare($share)) { + throw new NotFoundException(); + } + return $share; + } + + /** + * @return \OCP\Files\File|\OCP\Files\Folder + * @throws NotFoundException + */ + private function getShareNode() { + \OC_User::setIncognitoMode(true); + + $share = $this->getShare(); + + return $share->getNode(); + } + + /** + * @PublicPage + * @return DataResponse + * @throws NotFoundException + * @throws NotPermittedException + * @throws \OC\User\NoUserException + */ + public function getTracks(): DataResponse { + $share = $this->getShare(); + $permissions = $share->getPermissions(); + $folder = $this->getShareNode(); + $isReadable = (bool) ($permissions & (1 << 0)); + if ($isReadable) { + $owner = $share->getShareOwner(); + $pre_path = $this->root->getUserFolder($owner)->getPath(); + $tracks = $this->tracksService->getTracksFromDB($owner, $folder, true, false); + $new_tracks = array_map(function ($track) use ($folder, $permissions, $pre_path) { + $track['isCreatable'] = ($permissions & (1 << 2)) && $track['isCreatable']; + $track['isUpdateable'] = ($permissions & (1 << 1)) && $track['isUpdateable']; + $track['isDeletable'] = ($permissions & (1 << 3)) && $track['isDeletable']; + $track['path'] = $folder->getRelativePath($pre_path.$track['path']); + $track['filename'] = $track['path']; + return $track; + }, $tracks); + } else { + throw new NotPermittedException(); + } + return new DataResponse($new_tracks); + } + + /** + * @PublicPage + * @param $id + * @return DataResponse + * @throws NotFoundException + * @throws NotPermittedException + * @throws \OCP\Files\InvalidPathException + */ + public function getTrackContentByFileId($id) { + $share = $this->getShare(); + $permissions = $share->getPermissions(); + $folder = $this->getShareNode(); + $isReadable = (bool) ($permissions & (1 << 0)); + if (!$isReadable) { + throw new NotPermittedException(); + } + $owner = $share->getShareOwner(); + $track = $this->tracksService->getTrackByFileIDFromDB($id, $owner); + $res = is_null($track) ? null : $folder->getById($track['file_id']); + if (is_array($res) and count($res) > 0) { + $trackFile = array_shift($res); + if ($trackFile->getType() === \OCP\Files\FileInfo::TYPE_FILE) { + $trackContent = remove_utf8_bom($trackFile->getContent()); + // compute metadata if necessary + // first time we get it OR the file changed + if (!$track['metadata'] || $track['etag'] !== $trackFile->getEtag()) { + $metadata = $this->tracksService->generateTrackMetadata($trackFile); + $this->tracksService->editTrackInDB($track['id'], null, $metadata, $trackFile->getEtag()); + } + else { + $metadata = $track['metadata']; + } + return new DataResponse([ + 'metadata'=>$metadata, + 'content'=>$trackContent + ]); + } + else { + return new DataResponse($this->l->t('Bad file type'), 400); + } + } + else { + return new DataResponse($this->l->t('File not found'), 400); + } + } + + /** + * @PublicPage + * @param $id + * @return DataResponse + * @throws NotFoundException + * @throws \OCP\Files\InvalidPathException + */ + public function getTrackFileContent($id): DataResponse { + $track = $this->tracksService->getTrackFromDB($id); + $res = is_null($track) ? null : $this->getShareNode()->getById($track['file_id']); + if (is_array($res) and count($res) > 0) { + $trackFile = array_shift($res); + if ($trackFile->getType() === \OCP\Files\FileInfo::TYPE_FILE) { + $trackContent = remove_utf8_bom($trackFile->getContent()); + // compute metadata if necessary + // first time we get it OR the file changed + if (!$track['metadata'] || $track['etag'] !== $trackFile->getEtag()) { + $metadata = $this->tracksService->generateTrackMetadata($trackFile); + $this->tracksService->editTrackInDB($track['id'], null, $metadata, $trackFile->getEtag()); + } + else { + $metadata = $track['metadata']; + } + return new DataResponse([ + 'metadata'=>$metadata, + 'content'=>$trackContent + ]); + } + else { + return new DataResponse($this->l->t('Bad file type'), 400); + } + } + else { + return new DataResponse($this->l->t('File not found'), 400); + } + } + + /** + * @PublicPage + * @param $id + * @param $color + * @param $metadata + * @param $etag + * @return DataResponse + * @throws NotFoundException + * @throws NotPermittedException + */ + public function editTrack($id, $color, $metadata, $etag): DataResponse { + $share = $this->getShare(); + $permissions = $share->getPermissions(); + $folder = $this->getShareNode(); + $isUpdateable = (bool) ($permissions & (1 << 1)); + if ($isUpdateable) { + $owner = $share->getShareOwner(); + $track = $this->tracksService->getTrackFromDB($id, $owner); + if ($track !== null) { + $this->tracksService->editTrackInDB($id, $color, $metadata, $etag); + return new DataResponse('EDITED'); + } + else { + return new DataResponse($this->l->t('No such track'), 400); + } + } else { + throw new NotPermittedException(); + } + } + + /** + * @NoAdminRequired + * @param $id + * @return DataResponse + */ + public function deleteTrack($id): DataResponse { + $share = $this->getShare(); + $permissions = $share->getPermissions(); + $folder = $this->getShareNode(); + $isUpdateable = (bool) ($permissions & (1 << 1)); + //It's allowed to delete a track from the share, if the share is updateable + if ($isUpdateable) { + $owner = $share->getShareOwner(); + $track = $this->tracksService->getTrackFromDB($id, $owner); + if ($track !== null) { + $this->tracksService->deleteTrackFromDB($id); + return new DataResponse('DELETED'); + } else { + return new DataResponse($this->l->t('No such track'), 400); + } + } else { + throw new NotPermittedException(); + } + } + +} diff --git a/src/components/AppNavigationTrackItem.vue b/src/components/AppNavigationTrackItem.vue index 8f18f9b28..ed831c341 100644 --- a/src/components/AppNavigationTrackItem.vue +++ b/src/components/AppNavigationTrackItem.vue @@ -46,7 +46,7 @@ @click="$emit('elevation', track)"> {{ t('maps', 'Show track elevation') }} - {{ t('maps', 'Change color') }} - diff --git a/src/network.js b/src/network.js index f7bebbd7e..e3e14b7ab 100644 --- a/src/network.js +++ b/src/network.js @@ -347,33 +347,33 @@ export function resetPhotosCoords(paths, myMapId = null) { return axios.delete(url, req) } -export function getTracks(myMapId = null) { +export function getTracks(myMapId = null, token = null) { const conf = { params: { myMapId, }, } - const url = generateUrl('/apps/maps/tracks') + const url = generateUrl('/apps/maps' + (token ? '/s/' + token : '') + '/tracks') return axios.get(url, conf) } -export function getTrack(id, myMapId = null, isFileId = false) { +export function getTrack(id, myMapId = null, isFileId = false, token = null) { const conf = { params: { myMapId, }, } - const url = generateUrl('/apps/maps/tracks/' + (isFileId ? 'file/' : '') + id) + const url = generateUrl('/apps/maps' + (token ? '/s/' + token : '') + '/tracks/' + (isFileId ? 'file/' : '') + id) // return axios.get(url, { responseType: 'json' }) return axios.get(url, conf) } -export function editTrack(id, color, myMapId = null) { +export function editTrack(id, color, myMapId = null, token = null) { const req = { color, myMapId, } - const url = generateUrl('/apps/maps/tracks/' + id) + const url = generateUrl('/apps/maps' + (token ? '/s/' + token : '') + '/tracks/' + id) return axios.put(url, req) } diff --git a/src/views/App.vue b/src/views/App.vue index 03e73f4f7..b0a3dbacf 100644 --- a/src/views/App.vue +++ b/src/views/App.vue @@ -1790,7 +1790,7 @@ export default { return } this.tracksLoading = true - network.getTracks(this.myMapId).then((response) => { + network.getTracks(this.myMapId, this.token).then((response) => { this.tracks = response.data.map((track) => { if (track.metadata) { try { @@ -1860,7 +1860,7 @@ export default { }, getTrack(track, enable = false, save = true, zoom = false) { track.loading = true - network.getTrack(track.id, this.myMapId).then((response) => { + network.getTrack(track.id, this.myMapId, false, this.token).then((response) => { if (!track.metadata) { try { track.metadata = JSON.parse(response.data.metadata) @@ -1898,7 +1898,7 @@ export default { }, onChangeTrackColor(e) { e.track.color = e.color - network.editTrack(e.track.id, e.color, this.myMapId).then((response) => { + network.editTrack(e.track.id, e.color, this.myMapId, this.token).then((response) => { console.debug(response.data) }).catch((error) => { console.error(error) diff --git a/src/views/TrackMetadataTab.vue b/src/views/TrackMetadataTab.vue index 80bf48704..eb2fb5fd7 100644 --- a/src/views/TrackMetadataTab.vue +++ b/src/views/TrackMetadataTab.vue @@ -30,6 +30,7 @@ import TrackSidebarMetadataTab from '../components/TrackMetadataTab.vue' import * as network from '../network.js' import { processGpx } from '../tracksUtils.js' +import {getToken} from "../utils/common"; export default { name: 'TrackMetadataTab', @@ -65,7 +66,7 @@ export default { getTrack(trackFileId) { this.loading = true const track = {} - network.getTrack(trackFileId, null, true).then((response) => { + network.getTrack(trackFileId, null, true, getToken()).then((response) => { if (!track.metadata) { try { track.metadata = JSON.parse(response.data.metadata) From 9e96f961d3cd4b138281176e60c4948b33fae9dc Mon Sep 17 00:00:00 2001 From: Arne Hamann Date: Thu, 26 Jan 2023 20:39:27 +0100 Subject: [PATCH 17/19] Public Contacts Signed-off-by: Arne Hamann --- appinfo/routes.php | 3 + lib/Controller/PublicContactsController.php | 281 ++++++++++++++++++ src/components/map/ContactLayer.vue | 11 +- src/components/map/ContactsLayer.vue | 3 +- src/components/map/routing/RoutingMachine.vue | 2 +- src/network.js | 4 +- src/views/App.vue | 6 +- 7 files changed, 300 insertions(+), 10 deletions(-) create mode 100644 lib/Controller/PublicContactsController.php diff --git a/appinfo/routes.php b/appinfo/routes.php index 95c1caf78..196b1ded8 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -48,6 +48,9 @@ ['name' => 'contacts#deleteContactAddress', 'url' => '/contacts/{bookid}/{uri}', 'verb' => 'DELETE'], ['name' => 'contacts#getContactLetterAvatar', 'url' => '/contacts-avatar', 'verb' => 'GET'], + ['name' => 'PublicContacts#getContacts', 'url' => '/s/{token}/contacts', 'verb' => 'GET'], + ['name' => 'PublicContacts#getContactLetterAvatar', 'url' => '/s/{token}/contacts-avatar', 'verb' => 'GET'], + // routing ['name' => 'routing#exportRoute', 'url' => '/exportRoute', 'verb' => 'POST'], diff --git a/lib/Controller/PublicContactsController.php b/lib/Controller/PublicContactsController.php new file mode 100644 index 000000000..5c4cfecfe --- /dev/null +++ b/lib/Controller/PublicContactsController.php @@ -0,0 +1,281 @@ + + * @copyright Julien Veyssier 2019 + */ + +namespace OCA\Maps\Controller; + +use OC\Files\Node\Node; +use OCP\EventDispatcher\IEventDispatcher; +use OCP\Files\IRootFolder; +use OCP\Files\NotFoundException; +use OCP\Files\NotPermittedException; +use OCP\IConfig; +use OCP\IInitialStateService; +use OCP\IRequest; +use OCP\IAvatarManager; +use OCP\AppFramework\Http\DataDisplayResponse; +use OCP\AppFramework\Http\DataResponse; +use OCP\IDBConnection; +use OCP\AppFramework\Controller; +use OCP\Contacts\IManager; +use OCA\Maps\Service\AddressService; +use \OCP\DB\QueryBuilder\IQueryBuilder; +use \OCA\DAV\CardDAV\CardDavBackend; +use OCP\ISession; +use OCP\IURLGenerator; +use OCP\IUserManager; +use OCP\Share\Exceptions\ShareNotFound; +use OCP\Share\IManager as ShareManager; +use \Sabre\VObject\Property\Text; +use \Sabre\VObject\Reader; + +class PublicContactsController extends PublicPageController { + protected IManager $contactsManager; + protected AddressService $addressService; + protected IQueryBuilder $qb; + protected CardDavBackend $cdBackend; + protected IAvatarManager $avatarManager; + protected IRootFolder $root; + + /** + * @param $appName + * @param IRequest $request + * @param IEventDispatcher $eventDispatcher + * @param IConfig $config + * @param IInitialStateService $initialStateService + * @param IURLGenerator $urlGenerator + * @param ShareManager $shareManager + * @param IUserManager $userManager + * @param ISession $session + * @param IDBConnection $dbconnection + * @param IManager $contactsManager + * @param AddressService $addressService + * @param CardDavBackend $cdBackend + * @param IAvatarManager $avatarManager + * @param IRootFolder $root + */ + public function __construct($appName, + IRequest $request, + IEventDispatcher $eventDispatcher, + IConfig $config, + IInitialStateService $initialStateService, + IURLGenerator $urlGenerator, + ShareManager $shareManager, + IUserManager $userManager, + ISession $session, + IManager $contactsManager, + IDBConnection $dbconnection, + AddressService $addressService, + CardDavBackend $cdBackend, + IAvatarManager $avatarManager, + IRootFolder $root){ + parent::__construct($appName, $request, $eventDispatcher, $config, $initialStateService, $urlGenerator, $shareManager, $userManager, $session); + $this->avatarManager = $avatarManager; + $this->contactsManager = $contactsManager; + $this->addressService = $addressService; + $this->qb = $dbconnection->getQueryBuilder(); + $this->cdBackend = $cdBackend; + $this->root = $root; + } + + /** + * Validate the permissions of the share + * + * @param Share\IShare $share + * @return bool + */ + private function validateShare(\OCP\Share\IShare $share) { + // If the owner is disabled no access to the link is granted + $owner = $this->userManager->get($share->getShareOwner()); + if ($owner === null || !$owner->isEnabled()) { + return false; + } + + // If the initiator of the share is disabled no access is granted + $initiator = $this->userManager->get($share->getSharedBy()); + if ($initiator === null || !$initiator->isEnabled()) { + return false; + } + + return $share->getNode()->isReadable() && $share->getNode()->isShareable(); + } + + /** + * @return \OCP\Share\IShare + * @throws NotFoundException + */ + private function getShare() { + // Check whether share exists + try { + $share = $this->shareManager->getShareByToken($this->getToken()); + } catch (ShareNotFound $e) { + // The share does not exists, we do not emit an ShareLinkAccessedEvent + throw new NotFoundException(); + } + + if (!$this->validateShare($share)) { + throw new NotFoundException(); + } + return $share; + } + + /** + * @return \OCP\Files\File|\OCP\Files\Folder + * @throws NotFoundException + */ + private function getShareNode() { + \OC_User::setIncognitoMode(true); + + $share = $this->getShare(); + + return $share->getNode(); + } + + /** + * @PublicPage + * + * @return DataResponse + * @throws NotFoundException + * @throws NotPermittedException + * @throws \OCP\Files\InvalidPathException + */ + public function getContacts(): DataResponse { + $share = $this->getShare(); + $permissions = $share->getPermissions(); + $folder = $this->getShareNode(); + $isReadable = (bool) ($permissions & (1 << 0)); + if ($isReadable) { + //Fixme add contacts for my-maps + $result = []; + $files = $folder->search('.vcf'); + foreach ($files as $file) { +// $cards = explode("END:VCARD\r\n", $file->getContent()); + $cards = [$file->getContent()]; + foreach ($cards as $card) { + $vcard = Reader::read($card."END:VCARD\r\n"); + if (isset($vcard->GEO)) { + $geo = $vcard->GEO; + if (is_string($geo->getValue()) && strlen($geo->getValue()) > 1) { + $result[] = $this->vCardToArray($permissions, $file, $vcard, $geo->getValue()); + } elseif (is_countable($geo) && count($geo)>0 && is_iterable($geo)) { + foreach ($geo as $g) { + if (strlen($g->getValue()) > 1) { + $result[] = $this->vCardToArray($permissions, $file, $vcard, $g->getValue()); + } + } + } + } + if (isset($vcard->ADR) && count($vcard->ADR) > 0) { + foreach ($vcard->ADR as $adr) { + $geo = $this->addressService->addressToGeo($adr->getValue(), $file->getId()); + //var_dump($adr->parameters()['TYPE']->getValue()); + $adrtype = ''; + if (isset($adr->parameters()['TYPE'])) { + $adrtype = $adr->parameters()['TYPE']->getValue(); + } + if (is_string($geo) && strlen($geo) > 1) { + $result[] = $this->vCardToArray($permissions, $file, $vcard, $geo, $adrtype, $adr->getValue(), $file->getId()); + } + } + } + } + } + return new DataResponse($result); + } else { + throw new NotPermittedException(); + } + } + + /** + * @param int $sharePermissions + * @param Node $file + * @param \Sabre\VObject\Document $vcard + * @param string $geo + * @param string|null $adrtype + * @param string|null $adr + * @param int|null $fileId + * @return array + * @throws NotFoundException + * @throws \OCP\Files\InvalidPathException + */ + private function vCardToArray(int $sharePermissions, Node $file, \Sabre\VObject\Document $vcard, string $geo, ?string $adrtype=null, ?string $adr=null, ?int $fileId = null): array { + $FNArray = $vcard->FN ? $vcard->FN->getJsonValue() : []; + $fn = array_shift($FNArray); + $NArray = $vcard->N ? $vcard->N->getJsonValue() : []; + $n = array_shift($NArray); + if (!is_null($n)) { + if (is_array($n)) { + $n = $this->N2FN(array_shift($n)); + } elseif (is_string($n)) { + $n = $this->N2FN($n); + } + + } + $UIDArray = $vcard->UID->getJsonValue(); + $uid = array_shift($UIDArray); + $groups = $vcard->CATEGORIES; + if (!is_null($groups)) { + $groups = $groups->getValue(); + } else { + $groups = ''; + } + $result = [ + 'FN' => $fn ?? $n ?? '???', + 'UID' => $uid, + 'HAS_PHOTO' => (isset($vcard->PHOTO) && $vcard->PHOTO !== null), + 'FILEID' => $fileId, + 'ADR' => $adr ?? '', + 'ADRTYPE' => $adrtype ?? '', + 'PHOTO' => $vcard->PHOTO ?? '', + 'GEO' => $geo, + 'GROUPS' => $groups, + 'isReadable' => $file->isReadable() && ($sharePermissions & (1 << 0)), + 'isDeletable' => $file->isDeletable() && ($sharePermissions & (1 << 1)), + 'isUpdateable' => $file->isUpdateable() && ($sharePermissions & (1 << 3)), + ]; + return $result; + } + + /** + * @param string $n + * @return string|null + */ + private function N2FN(string $n): ?string { + if ($n) { + $spl = explode($n, ';'); + if (count($spl) >= 4) { + return $spl[3] . ' ' . $spl[1] . ' ' . $spl[0]; + } + else { + return null; + } + } + else { + return null; + } + } + + + /** + * @PublicPage + * @NoCSRFRequired + * + * @param string $name + * @return DataDisplayResponse + * @throws NotFoundException + * @throws NotPermittedException + */ + public function getContactLetterAvatar(string $name): DataDisplayResponse { + $av = $this->avatarManager->getGuestAvatar($name); + $avatarContent = $av->getFile(64)->getContent(); + return new DataDisplayResponse($avatarContent); + } +} diff --git a/src/components/map/ContactLayer.vue b/src/components/map/ContactLayer.vue index 8d1661816..1ba06abf6 100644 --- a/src/components/map/ContactLayer.vue +++ b/src/components/map/ContactLayer.vue @@ -38,11 +38,12 @@ -
@@ -97,6 +98,7 @@ import { LMarker, LTooltip, LPopup } from 'vue2-leaflet' import optionsController from '../../optionsController' import { geoToLatLng } from '../../utils/mapUtils' +import { getToken, isPublic } from '../../utils/common' const CONTACT_MARKER_VIEW_SIZE = 40 @@ -159,7 +161,7 @@ export default { + '/' + encodeURIComponent(this.contact.BOOKURI) + '/' + encodeURIComponent(this.contact.URI) + '?photo').replace(/index\.php\//, '') } else { - return generateUrl('/apps/maps/contacts-avatar?name=' + encodeURIComponent(this.contact.FN)) + return generateUrl('/apps/maps' + (isPublic() ? '/s/' + getToken() : '') + '/contacts-avatar?name=' + encodeURIComponent(this.contact.FN)) } }, formattedAddressLines() { @@ -181,6 +183,9 @@ export default { }, methods: { + isPublic() { + return isPublic() + }, onMarkerClick(e) { this.click = 'left' this.popupOptions.offset = L.point(-5, 10) diff --git a/src/components/map/ContactsLayer.vue b/src/components/map/ContactsLayer.vue index b1b9e8232..7450fe3e9 100644 --- a/src/components/map/ContactsLayer.vue +++ b/src/components/map/ContactsLayer.vue @@ -19,6 +19,7 @@ import Vue2LeafletMarkerCluster from 'vue2-leaflet-markercluster' import ContactLayer from './ContactLayer' import optionsController from '../../optionsController' +import {getToken, isPublic} from "../../utils/common"; const CONTACT_MARKER_VIEW_SIZE = 40 @@ -107,7 +108,7 @@ export default { + '/' + encodeURIComponent(contact.BOOKURI) + '/' + encodeURIComponent(contact.URI) + '?photo').replace(/index\.php\//, '') } else { - return generateUrl('/apps/maps/contacts-avatar?name=' + encodeURIComponent(contact.FN)) + return generateUrl('/apps/maps' + (isPublic() ? '/s/' + getToken() : '') + '/contacts-avatar?name=' + encodeURIComponent(contact.FN)) } }, }, diff --git a/src/components/map/routing/RoutingMachine.vue b/src/components/map/routing/RoutingMachine.vue index 787dfa3f5..38648c27d 100644 --- a/src/components/map/routing/RoutingMachine.vue +++ b/src/components/map/routing/RoutingMachine.vue @@ -54,7 +54,7 @@ export default { methods: { initRouting() { - if (this.nbRouters > 0 || getCurrentUser().isAdmin) { + if (this.nbRouters > 0 || getCurrentUser()?.isAdmin) { this.initRoutingControl() } }, diff --git a/src/network.js b/src/network.js index e3e14b7ab..ccd2c2ff5 100644 --- a/src/network.js +++ b/src/network.js @@ -46,13 +46,13 @@ export function sendMyPosition(lat, lng, name, acc, ts, myMapId = null) { return axios.post(url, req) } -export function getContacts(myMapId = null) { +export function getContacts(myMapId = null, token = null) { const conf = { params: { myMapId, }, } - const url = generateUrl('/apps/maps/contacts') + const url = generateUrl('/apps/maps' + (token ? '/s/' + token : '') + '/contacts') return axios.get(url, conf) } diff --git a/src/views/App.vue b/src/views/App.vue index b0a3dbacf..a1db655d5 100644 --- a/src/views/App.vue +++ b/src/views/App.vue @@ -299,13 +299,13 @@ export default { // devices devicesLoading: false, devices: [], - devicesEnabled: optionsController.devicesEnabled, + devicesEnabled: optionsController.devicesEnabled && !isPublic(), exportingDevices: false, importingDevices: false, // myMaps myMapsLoading: false, myMaps: [], - myMapsEnabled: optionsController.myMapsEnabled, + myMapsEnabled: optionsController.myMapsEnabled && !isPublic(), myMapId: optionsController.myMapId, selectedMyMap: null, // PublicPage @@ -1259,7 +1259,7 @@ export default { } } - network.getContacts(this.myMapId).then((response) => { + network.getContacts(this.myMapId, this.token).then((response) => { this.contacts = response.data this.buildContactGroups() }).catch((error) => { From 7140c43d3c2bb3021ae3ec40ca4d87cc856cd9b5 Mon Sep 17 00:00:00 2001 From: Arne Hamann Date: Thu, 26 Jan 2023 20:48:42 +0100 Subject: [PATCH 18/19] Hide share Buttons on PublicPage Signed-off-by: Arne Hamann --- src/components/AppNavigationContactsItem.vue | 10 ++++++++-- src/components/AppNavigationFavoritesItem.vue | 2 +- src/components/AppNavigationTrackItem.vue | 6 +++++- src/components/map/ContactLayer.vue | 3 ++- src/components/map/DeviceLayer.vue | 8 ++++++-- src/components/map/FavoriteMarker.vue | 7 ++++++- src/components/map/PhotosLayer.vue | 8 ++++++-- src/components/map/TrackLayer.vue | 8 ++++++-- 8 files changed, 40 insertions(+), 12 deletions(-) diff --git a/src/components/AppNavigationContactsItem.vue b/src/components/AppNavigationContactsItem.vue index 91a959138..f9ef0a20e 100644 --- a/src/components/AppNavigationContactsItem.vue +++ b/src/components/AppNavigationContactsItem.vue @@ -24,7 +24,8 @@ @click="onZoomAllClick"> {{ t('maps', 'Zoom') }} - {{ t('maps', 'Copy to map') }} @@ -53,7 +54,8 @@ @click="onZoomGroupClick(gid)"> {{ t('maps', 'Zoom') }} - {{ t('maps', 'Copy to map') }} @@ -69,6 +71,7 @@ import NcAppNavigationItem from '@nextcloud/vue/dist/Components/NcAppNavigationI import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton' import NcCounterBubble from '@nextcloud/vue/dist/Components/NcCounterBubble' import optionsController from '../optionsController' +import {isPublic} from "../utils/common"; export default { name: 'AppNavigationContactsItem', @@ -132,6 +135,9 @@ export default { onGroupClick(groupName) { this.$emit('group-clicked', groupName) }, + isPublic() { + return isPublic() + }, }, } diff --git a/src/components/AppNavigationFavoritesItem.vue b/src/components/AppNavigationFavoritesItem.vue index b9f21f369..2a91c0023 100644 --- a/src/components/AppNavigationFavoritesItem.vue +++ b/src/components/AppNavigationFavoritesItem.vue @@ -100,7 +100,7 @@ @click="$emit('export-category', catid)"> {{ t('maps', 'Export') }} - diff --git a/src/components/AppNavigationTrackItem.vue b/src/components/AppNavigationTrackItem.vue index ed831c341..20f46dfa0 100644 --- a/src/components/AppNavigationTrackItem.vue +++ b/src/components/AppNavigationTrackItem.vue @@ -54,7 +54,7 @@ {{ t('maps', 'Change color') }} - @@ -67,6 +67,7 @@ diff --git a/src/components/map/ContactLayer.vue b/src/components/map/ContactLayer.vue index 1ba06abf6..df0762175 100644 --- a/src/components/map/ContactLayer.vue +++ b/src/components/map/ContactLayer.vue @@ -78,7 +78,8 @@ @click="onDeleteAddressClick()"> {{ contact.ADR?t('maps', 'Delete this address'):t('maps', 'Delete this location') }} - {{ t('maps', 'Copy to map') }} diff --git a/src/components/map/DeviceLayer.vue b/src/components/map/DeviceLayer.vue index 476a26e82..b4bfc9bdb 100644 --- a/src/components/map/DeviceLayer.vue +++ b/src/components/map/DeviceLayer.vue @@ -24,7 +24,8 @@ @click="$emit('export', device)"> {{ t('maps', 'Export') }} - {{ t('maps', 'Link to map') }} @@ -63,7 +64,7 @@ import { LMarker, LTooltip, LPopup, LFeatureGroup, LPolyline } from 'vue2-leafle import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton' import { isComputer } from '../../utils' -import { binSearch } from '../../utils/common' +import {binSearch, isPublic} from '../../utils/common' import optionsController from '../../optionsController' import moment from '@nextcloud/moment' @@ -200,6 +201,9 @@ export default { } this.$emit('point-hover', hoverPoint) }, + isPublic() { + return isPublic() + }, }, } diff --git a/src/components/map/FavoriteMarker.vue b/src/components/map/FavoriteMarker.vue index be4b46dab..cbe470f6b 100644 --- a/src/components/map/FavoriteMarker.vue +++ b/src/components/map/FavoriteMarker.vue @@ -29,7 +29,8 @@ {{ t('maps', 'Delete favorite') }} - {{ t('maps', 'Copy to map') }} @@ -42,6 +43,7 @@ import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton' import L from 'leaflet' import { LMarker, LTooltip, LPopup } from 'vue2-leaflet' +import {isPublic} from "../../utils/common"; export default { name: 'FavoriteMarker', @@ -116,6 +118,9 @@ export default { onRightClick(e) { this.$refs.marker.mapObject.openPopup() }, + isPublic() { + return isPublic() + }, }, } diff --git a/src/components/map/PhotosLayer.vue b/src/components/map/PhotosLayer.vue index 71ffa62ee..f8a12264a 100644 --- a/src/components/map/PhotosLayer.vue +++ b/src/components/map/PhotosLayer.vue @@ -34,7 +34,8 @@ {{ t('maps', 'Remove geo data') }} - {{ t('maps', 'Copy to map') }} @@ -75,7 +76,7 @@ import { LMarker, LTooltip, LPopup } from 'vue2-leaflet' import Vue2LeafletMarkerCluster from 'vue2-leaflet-markercluster' import optionsController from '../../optionsController' -import {binSearch, getToken} from "../../utils/common"; +import {binSearch, getToken, isPublic} from "../../utils/common"; const PHOTO_MARKER_VIEW_SIZE = 40 @@ -478,6 +479,9 @@ export default { m.update() }) }, + isPublic() { + return isPublic() + }, }, } diff --git a/src/components/map/TrackLayer.vue b/src/components/map/TrackLayer.vue index 6c9e28c18..b32fa46d0 100644 --- a/src/components/map/TrackLayer.vue +++ b/src/components/map/TrackLayer.vue @@ -12,7 +12,8 @@ {{ t('maps', 'Display elevation') }} - {{ t('maps', 'Copy to map') }} @@ -59,7 +60,7 @@ import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton' import moment from '@nextcloud/moment' import optionsController from '../../optionsController' -import { binSearch } from '../../utils/common' +import {binSearch, isPublic} from '../../utils/common' const TRACK_MARKER_VIEW_SIZE = 40 const WAYPOINT_MARKER_VIEW_SIZE = 30 @@ -264,6 +265,9 @@ export default { this.$emit('point-hover', hoverPoint) } }, + isPublic() { + return isPublic() + }, }, } From aefc18434a94ffa451025a742f16d4a8f88f0410 Mon Sep 17 00:00:00 2001 From: Arne Hamann Date: Thu, 26 Jan 2023 21:13:31 +0100 Subject: [PATCH 19/19] Track Sidebar on public page Signed-off-by: Arne Hamann --- src/components/Sidebar.vue | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/components/Sidebar.vue b/src/components/Sidebar.vue index 2549b303c..9df19e692 100644 --- a/src/components/Sidebar.vue +++ b/src/components/Sidebar.vue @@ -23,6 +23,8 @@ @cancel="$emit('cancel-photo-suggestions')" @save="$emit('save-photo-suggestions-selection',$event)" @zoom="$emit('zoom-photo-suggestion', $event)" /> +