request = $request;
        $this->pdm = new PersistentDataManager();
        $this->mapRepository = new MapRepository();
        $this->placeRepository = new PlaceRepository();
        $this->userPlayedPlaceRepository = new UserPlayedPlaceRepository();
    }
    public function authorize(): bool
    {
        $user = $this->request->user();
        return $user !== null && $user->hasPermission(IUser::PERMISSION_ADMIN);
    }
    public function getMapEditor(): IContent
    {
        $mapId = (int) $this->request->query('mapId');
        if ($mapId) {
            $map = $this->mapRepository->getById($mapId);
            $places = $this->getPlaces($map);
        } else {
            $map = new Map();
            $map->setName(self::$unnamedMapName);
            $places = [];
        }
        return new HtmlContent('admin/map_editor', [
            'mapId' => $mapId,
            'mapName' => $map->getName(),
            'mapDescription' => str_replace('
', "\n", $map->getDescription()),
            'bounds' => $map->getBounds()->toArray(),
            'places' => &$places
        ]);
    }
    public function getPlace(): IContent
    {
        $placeId = (int) $this->request->query('placeId');
        $place = $this->placeRepository->getById($placeId);
        return new JsonContent(['panoId' => $place->getFreshPanoId()]);
    }
    public function saveMap(): IContent
    {
        $mapId = (int) $this->request->query('mapId');
        \Container::$dbConnection->startTransaction();
        if ($mapId) {
            $map = $this->mapRepository->getById($mapId);
        } else {
            $map = new Map();
            $map->setName(self::$unnamedMapName);
            $this->pdm->saveToDb($map);
        }
        if (isset($_POST['added'])) {
            $addedIds = [];
            foreach ($_POST['added'] as $placeRaw) {
                $placeRaw = json_decode($placeRaw, true);
                $place = new Place();
                $place->setMap($map);
                $place->setLat((float) $placeRaw['lat']);
                $place->setLng((float) $placeRaw['lng']);
                $place->setPov(new Pov(
                    (float) $placeRaw['pov']['heading'],
                    (float) $placeRaw['pov']['pitch'],
                    (float) $placeRaw['pov']['zoom']
                ));
                if ($placeRaw['panoId'] === -1) {
                    $place->setPanoIdCachedTimestampDate(new DateTime('-1 day'));
                }
                $this->pdm->saveToDb($place);
                $addedIds[] = ['tempId' => $placeRaw['id'], 'id' => $place->getId()];
            }
        } else {
            $addedIds = [];
        }
        if (isset($_POST['edited'])) {
            foreach ($_POST['edited'] as $placeRaw) {
                $placeRaw = json_decode($placeRaw, true);
                $place = $this->placeRepository->getById((int) $placeRaw['id']);
                $place->setLat((float) $placeRaw['lat']);
                $place->setLng((float) $placeRaw['lng']);
                $place->setPov(new Pov(
                    (float) $placeRaw['pov']['heading'],
                    (float) $placeRaw['pov']['pitch'],
                    (float) $placeRaw['pov']['zoom']
                ));
                $place->setPanoIdCachedTimestampDate(new DateTime('-1 day'));
                $this->pdm->saveToDb($place);
            }
        }
        if (isset($_POST['deleted'])) {
            foreach ($_POST['deleted'] as $placeRaw) {
                $placeRaw = json_decode($placeRaw, true);
                $place = $this->placeRepository->getById((int) $placeRaw['id']);
                $this->deletePlace($place);
            }
        }
        $mapBounds = $this->calculateMapBounds($map);
        $map->setBounds($mapBounds);
        $map->setArea($mapBounds->calculateApproximateArea());
        if (isset($_POST['name'])) {
            $map->setName($_POST['name'] ? $_POST['name'] : self::$unnamedMapName);
        }
        if (isset($_POST['description'])) {
            $map->setDescription(str_replace(["\n", "\r\n"], '
', $_POST['description']));
        }
        $this->pdm->saveToDb($map);
        \Container::$dbConnection->commit();
        return new JsonContent(['mapId' => $map->getId(), 'added' => $addedIds]);
    }
    public function deleteMap(): IContent
    {
        $mapId = (int) $this->request->query('mapId');
        $map = $this->mapRepository->getById($mapId);
        \Container::$dbConnection->startTransaction();
        $this->deletePlaces($map);
        $this->pdm->deleteFromDb($map);
        \Container::$dbConnection->commit();
        return new JsonContent(['success' => true]);
    }
    private function deletePlace(Place $place): void
    {
        foreach ($this->userPlayedPlaceRepository->getAllByPlace($place) as $userPlayedPlace) {
            $this->pdm->deleteFromDb($userPlayedPlace);
        }
        $this->pdm->deleteFromDb($place);
    }
    private function deletePlaces(Map $map): void
    {
        foreach ($this->placeRepository->getAllForMap($map) as $place) {
            $this->deletePlace($place);
        }
    }
    private function calculateMapBounds(Map $map): Bounds
    {
        $bounds = new Bounds();
        foreach ($this->placeRepository->getAllForMap($map) as $place) {
            $bounds->extend($place->getPosition());
        }
        return $bounds;
    }
    private function &getPlaces(Map $map): array
    {
        $places = [];
        foreach ($this->placeRepository->getAllForMap($map) as $place) {
            $noPano = $place->getPanoIdCachedTimestampDate() !== null && $place->getPanoIdCached() === null;
            $placeId = $place->getId();
            $places[$placeId] = [
                'id' => $placeId,
                'lat' => $place->getLat(),
                'lng' => $place->getLng(),
                'panoId' => null,
                'pov' => $place->getPov()->toArray(),
                'noPano' => $noPano
            ];
        }
        return $places;
    }
}