summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/Http/RequestHandlers/MapDataDelete.php2
-rw-r--r--app/Http/RequestHandlers/MapDataDeleteUnused.php61
-rw-r--r--app/Http/RequestHandlers/MapDataImportAction.php11
-rw-r--r--app/Http/Routes/WebRoutes.php2
-rw-r--r--app/Services/MapDataService.php107
-rw-r--r--phpstan-baseline.neon22
-rw-r--r--resources/views/admin/locations.phtml14
-rw-r--r--resources/views/admin/map-import-form.phtml25
8 files changed, 170 insertions, 74 deletions
diff --git a/app/Http/RequestHandlers/MapDataDelete.php b/app/Http/RequestHandlers/MapDataDelete.php
index ba7c7b3949..ddeb4c67b6 100644
--- a/app/Http/RequestHandlers/MapDataDelete.php
+++ b/app/Http/RequestHandlers/MapDataDelete.php
@@ -28,7 +28,7 @@ use function redirect;
use function route;
/**
- * Delete a place location from the control panel.
+ * Delete a location from the control panel.
*/
class MapDataDelete implements RequestHandlerInterface
{
diff --git a/app/Http/RequestHandlers/MapDataDeleteUnused.php b/app/Http/RequestHandlers/MapDataDeleteUnused.php
new file mode 100644
index 0000000000..1b57b012e6
--- /dev/null
+++ b/app/Http/RequestHandlers/MapDataDeleteUnused.php
@@ -0,0 +1,61 @@
+<?php
+
+/**
+ * webtrees: online genealogy
+ * Copyright (C) 2021 webtrees development team
+ * This program 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 <https://www.gnu.org/licenses/>.
+ */
+
+declare(strict_types=1);
+
+namespace Fisharebest\Webtrees\Http\RequestHandlers;
+
+use Fisharebest\Webtrees\PlaceLocation;
+use Fisharebest\Webtrees\Services\MapDataService;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use Psr\Http\Server\RequestHandlerInterface;
+
+use function redirect;
+use function route;
+
+/**
+ * Delete unused locations from the control panel.
+ */
+class MapDataDeleteUnused implements RequestHandlerInterface
+{
+ private MapDataService $map_data_service;
+
+ /**
+ * Dependency injection.
+ *
+ * @param MapDataService $map_data_service
+ */
+ public function __construct(MapDataService $map_data_service)
+ {
+ $this->map_data_service = $map_data_service;
+ }
+
+ /**
+ * @param ServerRequestInterface $request
+ *
+ * @return ResponseInterface
+ */
+ public function handle(ServerRequestInterface $request): ResponseInterface
+ {
+ $this->map_data_service->deleteUnusedLocations(null, [0]);
+
+ $url = route(MapDataList::class);
+
+ return redirect($url);
+ }
+}
diff --git a/app/Http/RequestHandlers/MapDataImportAction.php b/app/Http/RequestHandlers/MapDataImportAction.php
index 52cd2ebbc5..51b358ea9b 100644
--- a/app/Http/RequestHandlers/MapDataImportAction.php
+++ b/app/Http/RequestHandlers/MapDataImportAction.php
@@ -88,7 +88,6 @@ class MapDataImportAction implements RequestHandlerInterface
$serverfile = $params['serverfile'] ?? '';
$options = $params['import-options'] ?? '';
- $clear_database = (bool) ($params['cleardatabase'] ?? false);
$local_file = $request->getUploadedFiles()['localfile'] ?? null;
$places = [];
@@ -155,16 +154,6 @@ class MapDataImportAction implements RequestHandlerInterface
fclose($fp);
- if ($clear_database) {
- // Child places are deleted via on-delete-cascade...
- DB::table('place_location')
- ->whereNull('parent_id')
- ->delete();
-
- // Automatically import any new/missing places.
- $this->map_data_service->importMissingLocations();
- }
-
$added = 0;
$updated = 0;
diff --git a/app/Http/Routes/WebRoutes.php b/app/Http/Routes/WebRoutes.php
index 0d168907a8..1ce7fb721b 100644
--- a/app/Http/Routes/WebRoutes.php
+++ b/app/Http/Routes/WebRoutes.php
@@ -147,6 +147,7 @@ use Fisharebest\Webtrees\Http\RequestHandlers\ManageMediaPage;
use Fisharebest\Webtrees\Http\RequestHandlers\ManageTrees;
use Fisharebest\Webtrees\Http\RequestHandlers\MapDataAdd;
use Fisharebest\Webtrees\Http\RequestHandlers\MapDataDelete;
+use Fisharebest\Webtrees\Http\RequestHandlers\MapDataDeleteUnused;
use Fisharebest\Webtrees\Http\RequestHandlers\MapDataEdit;
use Fisharebest\Webtrees\Http\RequestHandlers\MapDataExportCSV;
use Fisharebest\Webtrees\Http\RequestHandlers\MapDataExportGeoJson;
@@ -380,6 +381,7 @@ class WebRoutes
$router->post(UsersCleanupAction::class, '/users-cleanup');
$router->get(MapDataAdd::class, '/map-data-add{/parent_id}');
$router->post(MapDataDelete::class, '/map-data-delete/{place_id}');
+ $router->post(MapDataDeleteUnused::class, '/map-data-delete-unused');
$router->get(MapDataEdit::class, '/map-data-edit/{place_id}');
$router->get(MapDataExportCSV::class, '/map-data-csv{/parent_id}');
$router->get(MapDataExportGeoJson::class, '/map-data-geojson{/parent_id}');
diff --git a/app/Services/MapDataService.php b/app/Services/MapDataService.php
index 174d1efd0a..747a0d6a6a 100644
--- a/app/Services/MapDataService.php
+++ b/app/Services/MapDataService.php
@@ -19,13 +19,13 @@ declare(strict_types=1);
namespace Fisharebest\Webtrees\Services;
+use Fisharebest\Webtrees\FlashMessages;
use Fisharebest\Webtrees\Gedcom;
use Fisharebest\Webtrees\I18N;
use Fisharebest\Webtrees\PlaceLocation;
use Illuminate\Database\Capsule\Manager as DB;
use Illuminate\Database\Query\Expression;
use Illuminate\Support\Collection;
-use stdClass;
use function abs;
use function array_filter;
@@ -74,7 +74,7 @@ class MapDataService
*
* @param PlaceLocation $location
*
- * @return array<string,array<stdClass>>
+ * @return array<string,array<object>>
*/
public function activePlaces(PlaceLocation $location): array
{
@@ -124,7 +124,7 @@ class MapDataService
'p8.p_place AS part_8',
])
->get()
- ->map(static function (stdClass $row): string {
+ ->map(static function (object $row): string {
return implode(Gedcom::PLACE_SEPARATOR, array_filter((array) $row));
});
@@ -149,7 +149,7 @@ class MapDataService
'p8.place AS part_8',
])
->get()
- ->map(static function (stdClass $row): string {
+ ->map(static function (object $row): string {
return implode(Gedcom::PLACE_SEPARATOR, array_filter((array) $row));
});
@@ -161,37 +161,9 @@ class MapDataService
}
/**
- * Find all active places that match a location
- *
- * @param PlaceLocation $location
- *
- * @return array<string>
- */
- private function placeIdsForLocation(PlaceLocation $location): array
- {
- $hierarchy = [];
-
- while ($location->id() !== null) {
- array_unshift($hierarchy, $location->locationName());
- $location = $location->parent();
- }
-
- $place_ids = ['0'];
-
- foreach ($hierarchy as $place_name) {
- $place_ids = DB::table('places')
- ->whereIn('p_parent_id', $place_ids)
- ->where('p_place', '=', $place_name)
- ->groupBy(['p_id'])
- ->pluck('p_id')
- ->all();
- }
-
- return $place_ids;
- }
-
- /**
* @param int $id
+ *
+ * @return void
*/
public function deleteRecursively(int $id): void
{
@@ -202,6 +174,41 @@ class MapDataService
}
/**
+ * @param int|null $parent_location_id
+ * @param array<int> $parent_place_ids
+ *
+ * @return void
+ */
+ public function deleteUnusedLocations(?int $parent_location_id, array $parent_place_ids): void
+ {
+ if ($parent_location_id === null) {
+ $location_query = DB::table('place_location')
+ ->whereNull('parent_id');
+ } else {
+ $location_query = DB::table('place_location')
+ ->where('parent_id', '=', $parent_location_id);
+ }
+
+ foreach ($location_query->get() as $location) {
+ $places = DB::table('places')
+ ->whereIn('p_parent_id', $parent_place_ids)
+ ->where('p_place', '=', $location->place)
+ ->get();
+
+ if ($places->isEmpty()) {
+ FlashMessages::addMessage(I18N::translate('ā€œ%sā€ has been deleted.', e($location->place)));
+
+ DB::table('place_location')
+ ->where('id', '=', $location->id)
+ ->delete();
+ } else {
+ $place_ids = $places->map(static fn(object $place): int => (int) $place->p_id)->all();
+ $this->deleteUnusedLocations((int) $location->id, $place_ids);
+ }
+ }
+ }
+
+ /**
* Find a list of child places.
* How many children does each child place have? How many have co-ordinates?
*
@@ -252,7 +259,7 @@ class MapDataService
new Expression('SUM(' . $expression . ') AS no_coord'),
])
->get()
- ->map(static function (stdClass $row): stdClass {
+ ->map(static function (object $row): object {
$row->child_count = (int) $row->child_count;
$row->no_coord = (int) $row->no_coord;
$row->key = mb_strtolower($row->place);
@@ -282,6 +289,36 @@ class MapDataService
}
/**
+ * Find all active places that match a location
+ *
+ * @param PlaceLocation $location
+ *
+ * @return array<string>
+ */
+ private function placeIdsForLocation(PlaceLocation $location): array
+ {
+ $hierarchy = [];
+
+ while ($location->id() !== null) {
+ array_unshift($hierarchy, $location->locationName());
+ $location = $location->parent();
+ }
+
+ $place_ids = ['0'];
+
+ foreach ($hierarchy as $place_name) {
+ $place_ids = DB::table('places')
+ ->whereIn('p_parent_id', $place_ids)
+ ->where('p_place', '=', $place_name)
+ ->groupBy(['p_id'])
+ ->pluck('p_id')
+ ->all();
+ }
+
+ return $place_ids;
+ }
+
+ /**
* @param float $degrees
* @param string $positive
* @param string $negative
diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon
index 3fa20b6f85..bbcacb2878 100644
--- a/phpstan-baseline.neon
+++ b/phpstan-baseline.neon
@@ -1430,13 +1430,33 @@ parameters:
path: app/Services/HomePageService.php
-
+ message: "#^Access to an undefined property object\\:\\:\\$child_count\\.$#"
+ count: 2
+ path: app/Services/MapDataService.php
+
+ -
+ message: "#^Access to an undefined property object\\:\\:\\$key\\.$#"
+ count: 1
+ path: app/Services/MapDataService.php
+
+ -
+ message: "#^Access to an undefined property object\\:\\:\\$no_coord\\.$#"
+ count: 2
+ path: app/Services/MapDataService.php
+
+ -
+ message: "#^Access to an undefined property object\\:\\:\\$p_id\\.$#"
+ count: 1
+ path: app/Services/MapDataService.php
+
+ -
message: "#^Access to an undefined property object\\:\\:\\$parent_id\\.$#"
count: 1
path: app/Services/MapDataService.php
-
message: "#^Access to an undefined property object\\:\\:\\$place\\.$#"
- count: 1
+ count: 2
path: app/Services/MapDataService.php
-
diff --git a/resources/views/admin/locations.phtml b/resources/views/admin/locations.phtml
index fb6fa30c41..3c5dcd4beb 100644
--- a/resources/views/admin/locations.phtml
+++ b/resources/views/admin/locations.phtml
@@ -2,6 +2,7 @@
use Fisharebest\Webtrees\Http\RequestHandlers\MapDataAdd;
use Fisharebest\Webtrees\Http\RequestHandlers\MapDataDelete;
+use Fisharebest\Webtrees\Http\RequestHandlers\MapDataDeleteUnused;
use Fisharebest\Webtrees\Http\RequestHandlers\MapDataEdit;
use Fisharebest\Webtrees\Http\RequestHandlers\MapDataExportCSV;
use Fisharebest\Webtrees\Http\RequestHandlers\MapDataExportGeoJson;
@@ -137,7 +138,7 @@ use Illuminate\Support\Collection;
<tfoot>
<tr>
- <td colspan="7">
+ <td colspan="4">
<a class="btn btn-primary" href="<?= e(route(MapDataAdd::class, ['parent_id' => $parent_id])) ?>">
<?= view('icons/add') ?>
<?= /* I18N: A button label. */
@@ -163,6 +164,17 @@ use Illuminate\Support\Collection;
I18N::translate('import file') ?>
</a>
</td>
+ <td colspan="2">
+ <?php if (count($active) !== count($placelist)) : ?>
+ <form class="form-inline d-inline" method="post" action="<?= route(MapDataDeleteUnused::class) ?>">
+ <?= csrf_field() ?>
+ <button type="submit" class="btn btn-danger">
+ <?= view('icons/delete') ?>
+ <?= I18N::translate('Delete unused locations') ?>
+ </button>
+ </form>
+ <?php endif ?>
+ </td>
</tr>
</tfoot>
</table>
diff --git a/resources/views/admin/map-import-form.phtml b/resources/views/admin/map-import-form.phtml
index 4498275224..042d34864c 100644
--- a/resources/views/admin/map-import-form.phtml
+++ b/resources/views/admin/map-import-form.phtml
@@ -54,16 +54,6 @@ use Fisharebest\Webtrees\View;
</div>
</div>
- <!-- CLEAR DATABASE -->
- <fieldset class="row form-group">
- <legend class="col-form-label col-sm-4">
- <?= I18N::translate('Delete all existing geographic data before importing the file.') ?>
- </legend>
- <div class="col-sm-8">
- <?= view('components/radios-inline', ['name' => 'cleardatabase', 'options' => [I18N::translate('no'), I18N::translate('yes')], 'selected' => 0]) ?>
- </div>
- </fieldset>
-
<!-- Import options -->
<fieldset class="row form-group">
<legend class="col-form-label col-sm-4">
@@ -88,18 +78,3 @@ use Fisharebest\Webtrees\View;
</div>
</div>
</form>
-
-<?php View::push('javascript') ?>
- <script>
- $('#upload_form').on('submit', function(e) {
- let self = this;
- e.preventDefault();
- if($('input[name="cleardatabase"]:checked').val() === '1') {
- if (!confirm('<?= I18N::translate('Really delete all geographic data?') ?> ')) {
- return false;
- }
- }
- self.submit();
- });
- </script>
-<?php View::endpush() ?>