xref: /webtrees/app/Module/PlaceHierarchyListModule.php (revision 24f2a3af38709f9bf0a739b30264240d20ba34e8)
167992b6aSRichard Cissee<?php
23976b470SGreg Roach
367992b6aSRichard Cissee/**
467992b6aSRichard Cissee * webtrees: online genealogy
590949315SGreg Roach * Copyright (C) 2021 webtrees development team
667992b6aSRichard Cissee * This program is free software: you can redistribute it and/or modify
767992b6aSRichard Cissee * it under the terms of the GNU General Public License as published by
867992b6aSRichard Cissee * the Free Software Foundation, either version 3 of the License, or
967992b6aSRichard Cissee * (at your option) any later version.
1067992b6aSRichard Cissee * This program is distributed in the hope that it will be useful,
1167992b6aSRichard Cissee * but WITHOUT ANY WARRANTY; without even the implied warranty of
1267992b6aSRichard Cissee * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1367992b6aSRichard Cissee * GNU General Public License for more details.
1467992b6aSRichard Cissee * You should have received a copy of the GNU General Public License
1589f7189bSGreg Roach * along with this program. If not, see <https://www.gnu.org/licenses/>.
1667992b6aSRichard Cissee */
17fcfa147eSGreg Roach
1867992b6aSRichard Cisseedeclare(strict_types=1);
1967992b6aSRichard Cissee
2067992b6aSRichard Cisseenamespace Fisharebest\Webtrees\Module;
2167992b6aSRichard Cissee
224b2f1dbbSGreg Roachuse Aura\Router\RouterContainer;
2367992b6aSRichard Cisseeuse Fisharebest\Webtrees\Auth;
244b2f1dbbSGreg Roachuse Fisharebest\Webtrees\Contracts\UserInterface;
2567992b6aSRichard Cisseeuse Fisharebest\Webtrees\I18N;
264b2f1dbbSGreg Roachuse Fisharebest\Webtrees\Place;
274b2f1dbbSGreg Roachuse Fisharebest\Webtrees\PlaceLocation;
284b2f1dbbSGreg Roachuse Fisharebest\Webtrees\Services\ModuleService;
294b2f1dbbSGreg Roachuse Fisharebest\Webtrees\Services\SearchService;
304b2f1dbbSGreg Roachuse Fisharebest\Webtrees\Services\UserService;
314b2f1dbbSGreg Roachuse Fisharebest\Webtrees\Site;
324b2f1dbbSGreg Roachuse Fisharebest\Webtrees\Statistics;
335229eadeSGreg Roachuse Fisharebest\Webtrees\Tree;
344b2f1dbbSGreg Roachuse Fisharebest\Webtrees\Webtrees;
354b2f1dbbSGreg Roachuse Illuminate\Database\Capsule\Manager as DB;
366ccdf4f0SGreg Roachuse Psr\Http\Message\ResponseInterface;
376ccdf4f0SGreg Roachuse Psr\Http\Message\ServerRequestInterface;
384b2f1dbbSGreg Roachuse Psr\Http\Server\RequestHandlerInterface;
39f3874e19SGreg Roach
404b2f1dbbSGreg Roachuse function array_chunk;
414b2f1dbbSGreg Roachuse function array_pop;
424b2f1dbbSGreg Roachuse function array_reverse;
435229eadeSGreg Roachuse function assert;
444b2f1dbbSGreg Roachuse function ceil;
454b2f1dbbSGreg Roachuse function count;
464b2f1dbbSGreg Roachuse function is_file;
474b2f1dbbSGreg Roachuse function redirect;
484b2f1dbbSGreg Roachuse function route;
494b2f1dbbSGreg Roachuse function view;
5067992b6aSRichard Cissee
5167992b6aSRichard Cissee/**
5267992b6aSRichard Cissee * Class IndividualListModule
5367992b6aSRichard Cissee */
544b2f1dbbSGreg Roachclass PlaceHierarchyListModule extends AbstractModule implements ModuleListInterface, RequestHandlerInterface
5567992b6aSRichard Cissee{
5667992b6aSRichard Cissee    use ModuleListTrait;
5767992b6aSRichard Cissee
584b2f1dbbSGreg Roach    protected const ROUTE_URL = '/tree/{tree}/place-list';
594b2f1dbbSGreg Roach
604b2f1dbbSGreg Roach    /** @var int The default access level for this module.  It can be changed in the control panel. */
614b2f1dbbSGreg Roach    protected $access_level = Auth::PRIV_USER;
624b2f1dbbSGreg Roach
634b2f1dbbSGreg Roach    /** @var SearchService */
644b2f1dbbSGreg Roach    private $search_service;
654b2f1dbbSGreg Roach
664b2f1dbbSGreg Roach    /**
674b2f1dbbSGreg Roach     * PlaceHierarchy constructor.
684b2f1dbbSGreg Roach     *
694b2f1dbbSGreg Roach     * @param SearchService $search_service
704b2f1dbbSGreg Roach     */
714b2f1dbbSGreg Roach    public function __construct(SearchService $search_service)
724b2f1dbbSGreg Roach    {
734b2f1dbbSGreg Roach        $this->search_service = $search_service;
744b2f1dbbSGreg Roach    }
754b2f1dbbSGreg Roach
764b2f1dbbSGreg Roach    /**
774b2f1dbbSGreg Roach     * Initialization.
784b2f1dbbSGreg Roach     *
794b2f1dbbSGreg Roach     * @return void
804b2f1dbbSGreg Roach     */
814b2f1dbbSGreg Roach    public function boot(): void
824b2f1dbbSGreg Roach    {
834b2f1dbbSGreg Roach        $router_container = app(RouterContainer::class);
844b2f1dbbSGreg Roach        assert($router_container instanceof RouterContainer);
854b2f1dbbSGreg Roach
864b2f1dbbSGreg Roach        $router_container->getMap()
874b2f1dbbSGreg Roach            ->get(static::class, static::ROUTE_URL, $this);
884b2f1dbbSGreg Roach    }
894b2f1dbbSGreg Roach
9067992b6aSRichard Cissee    /**
910cfd6963SGreg Roach     * How should this module be identified in the control panel, etc.?
9267992b6aSRichard Cissee     *
9367992b6aSRichard Cissee     * @return string
9467992b6aSRichard Cissee     */
9567992b6aSRichard Cissee    public function title(): string
9667992b6aSRichard Cissee    {
9767992b6aSRichard Cissee        /* I18N: Name of a module/list */
9867992b6aSRichard Cissee        return I18N::translate('Place hierarchy');
9967992b6aSRichard Cissee    }
10067992b6aSRichard Cissee
10167992b6aSRichard Cissee    /**
10267992b6aSRichard Cissee     * A sentence describing what this module does.
10367992b6aSRichard Cissee     *
10467992b6aSRichard Cissee     * @return string
10567992b6aSRichard Cissee     */
10667992b6aSRichard Cissee    public function description(): string
10767992b6aSRichard Cissee    {
1089e0868cbSGreg Roach        /* I18N: Description of the “Place hierarchy” module */
10967992b6aSRichard Cissee        return I18N::translate('The place hierarchy.');
11067992b6aSRichard Cissee    }
11167992b6aSRichard Cissee
11267992b6aSRichard Cissee    /**
11367992b6aSRichard Cissee     * CSS class for the URL.
11467992b6aSRichard Cissee     *
11567992b6aSRichard Cissee     * @return string
11667992b6aSRichard Cissee     */
11767992b6aSRichard Cissee    public function listMenuClass(): string
11867992b6aSRichard Cissee    {
11967992b6aSRichard Cissee        return 'menu-list-plac';
12067992b6aSRichard Cissee    }
12167992b6aSRichard Cissee
1224db4b4a9SGreg Roach    /**
1234b2f1dbbSGreg Roach     * @param Tree    $tree
1244b2f1dbbSGreg Roach     * @param mixed[] $parameters
1254db4b4a9SGreg Roach     *
1264b2f1dbbSGreg Roach     * @return string
1274db4b4a9SGreg Roach     */
1284b2f1dbbSGreg Roach    public function listUrl(Tree $tree, array $parameters = []): string
12967992b6aSRichard Cissee    {
1304b2f1dbbSGreg Roach        $parameters['tree'] = $tree->name();
1315229eadeSGreg Roach
1324b2f1dbbSGreg Roach        return route(static::class, $parameters);
13367992b6aSRichard Cissee    }
13467992b6aSRichard Cissee
1354db4b4a9SGreg Roach    /**
136*24f2a3afSGreg Roach     * @return array<string>
1374db4b4a9SGreg Roach     */
13867992b6aSRichard Cissee    public function listUrlAttributes(): array
13967992b6aSRichard Cissee    {
14067992b6aSRichard Cissee        return [];
14167992b6aSRichard Cissee    }
1424b2f1dbbSGreg Roach
1434b2f1dbbSGreg Roach    /**
1444b2f1dbbSGreg Roach     * @param Tree $tree
1454b2f1dbbSGreg Roach     *
1464b2f1dbbSGreg Roach     * @return bool
1474b2f1dbbSGreg Roach     */
1484b2f1dbbSGreg Roach    public function listIsEmpty(Tree $tree): bool
1494b2f1dbbSGreg Roach    {
1504b2f1dbbSGreg Roach        return !DB::table('places')
1514b2f1dbbSGreg Roach            ->where('p_file', '=', $tree->id())
1524b2f1dbbSGreg Roach            ->exists();
1534b2f1dbbSGreg Roach    }
1544b2f1dbbSGreg Roach
1554b2f1dbbSGreg Roach    /**
1564b2f1dbbSGreg Roach     * Handle URLs generated by older versions of webtrees
1574b2f1dbbSGreg Roach     *
1584b2f1dbbSGreg Roach     * @param ServerRequestInterface $request
1594b2f1dbbSGreg Roach     *
1604b2f1dbbSGreg Roach     * @return ResponseInterface
1614b2f1dbbSGreg Roach     */
1624b2f1dbbSGreg Roach    public function getListAction(ServerRequestInterface $request): ResponseInterface
1634b2f1dbbSGreg Roach    {
1644b2f1dbbSGreg Roach        return redirect($this->listUrl($request->getAttribute('tree'), $request->getQueryParams()));
1654b2f1dbbSGreg Roach    }
1664b2f1dbbSGreg Roach
1674b2f1dbbSGreg Roach    /**
1684b2f1dbbSGreg Roach     * @param ServerRequestInterface $request
1694b2f1dbbSGreg Roach     *
1704b2f1dbbSGreg Roach     * @return ResponseInterface
1714b2f1dbbSGreg Roach     */
1724b2f1dbbSGreg Roach    public function handle(ServerRequestInterface $request): ResponseInterface
1734b2f1dbbSGreg Roach    {
1744b2f1dbbSGreg Roach        $tree = $request->getAttribute('tree');
1754b2f1dbbSGreg Roach        assert($tree instanceof Tree);
1764b2f1dbbSGreg Roach
1774b2f1dbbSGreg Roach        $user = $request->getAttribute('user');
1784b2f1dbbSGreg Roach        assert($user instanceof UserInterface);
1794b2f1dbbSGreg Roach
1804b2f1dbbSGreg Roach        Auth::checkComponentAccess($this, ModuleListInterface::class, $tree, $user);
1814b2f1dbbSGreg Roach
1824b2f1dbbSGreg Roach        $action2  = $request->getQueryParams()['action2'] ?? 'hierarchy';
1834b2f1dbbSGreg Roach        $place_id = (int) ($request->getQueryParams()['place_id'] ?? 0);
1844b2f1dbbSGreg Roach        $place    = Place::find($place_id, $tree);
1854b2f1dbbSGreg Roach
1864b2f1dbbSGreg Roach        // Request for a non-existent place?
1874b2f1dbbSGreg Roach        if ($place_id !== $place->id()) {
1884b2f1dbbSGreg Roach            return redirect($place->url());
1894b2f1dbbSGreg Roach        }
1904b2f1dbbSGreg Roach
1914b2f1dbbSGreg Roach        $content = '';
1924b2f1dbbSGreg Roach        $showmap = Site::getPreference('map-provider') !== '';
1934b2f1dbbSGreg Roach        $data    = null;
1944b2f1dbbSGreg Roach
1954b2f1dbbSGreg Roach        if ($showmap) {
1964b2f1dbbSGreg Roach            $content .= view('modules/place-hierarchy/map', [
1974b2f1dbbSGreg Roach                'data'     => $this->mapData($tree, $place),
1984b2f1dbbSGreg Roach                'provider' => [
1994b2f1dbbSGreg Roach                    'url'    => 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
2004b2f1dbbSGreg Roach                    'options' => [
2014b2f1dbbSGreg Roach                        'attribution' => '<a href="https://www.openstreetmap.org/copyright">&copy; OpenStreetMap</a> contributors',
2024b2f1dbbSGreg Roach                        'max_zoom'    => 19
2034b2f1dbbSGreg Roach                    ]
2044b2f1dbbSGreg Roach                ]
2054b2f1dbbSGreg Roach            ]);
2064b2f1dbbSGreg Roach        }
2074b2f1dbbSGreg Roach
2084b2f1dbbSGreg Roach        switch ($action2) {
2094b2f1dbbSGreg Roach            case 'list':
2104b2f1dbbSGreg Roach            default:
2114b2f1dbbSGreg Roach                $alt_link = I18N::translate('Show place hierarchy');
2124b2f1dbbSGreg Roach                $alt_url  = $this->listUrl($tree, ['action2' => 'hierarchy', 'place_id' => $place_id]);
2134b2f1dbbSGreg Roach                $content .= view('modules/place-hierarchy/list', ['columns' => $this->getList($tree)]);
2144b2f1dbbSGreg Roach                break;
2154b2f1dbbSGreg Roach            case 'hierarchy':
2164b2f1dbbSGreg Roach            case 'hierarchy-e':
2174b2f1dbbSGreg Roach                $alt_link = I18N::translate('Show all places in a list');
2184b2f1dbbSGreg Roach                $alt_url  = $this->listUrl($tree, ['action2' => 'list', 'place_id' => 0]);
2194b2f1dbbSGreg Roach                $data     = $this->getHierarchy($place);
2204b2f1dbbSGreg Roach                $content .= (null === $data || $showmap) ? '' : view('place-hierarchy', $data);
2214b2f1dbbSGreg Roach                if (null === $data || $action2 === 'hierarchy-e') {
2224b2f1dbbSGreg Roach                    $content .= view('modules/place-hierarchy/events', [
2234b2f1dbbSGreg Roach                        'indilist' => $this->search_service->searchIndividualsInPlace($place),
2244b2f1dbbSGreg Roach                        'famlist'  => $this->search_service->searchFamiliesInPlace($place),
2254b2f1dbbSGreg Roach                        'tree'     => $place->tree(),
2264b2f1dbbSGreg Roach                    ]);
2274b2f1dbbSGreg Roach                }
2284b2f1dbbSGreg Roach        }
2294b2f1dbbSGreg Roach
2304b2f1dbbSGreg Roach        if ($data !== null && $action2 !== 'hierarchy-e' && $place->gedcomName() !== '') {
2314b2f1dbbSGreg Roach            $events_link = $this->listUrl($tree, ['action2' => 'hierarchy-e', 'place_id' => $place_id]);
2324b2f1dbbSGreg Roach        } else {
2334b2f1dbbSGreg Roach            $events_link = '';
2344b2f1dbbSGreg Roach        }
2354b2f1dbbSGreg Roach
2364b2f1dbbSGreg Roach        $breadcrumbs = $this->breadcrumbs($place);
2374b2f1dbbSGreg Roach
2384b2f1dbbSGreg Roach        return $this->viewResponse('modules/place-hierarchy/page', [
2394b2f1dbbSGreg Roach            'alt_link'    => $alt_link,
2404b2f1dbbSGreg Roach            'alt_url'     => $alt_url,
2414b2f1dbbSGreg Roach            'breadcrumbs' => $breadcrumbs['breadcrumbs'],
2424b2f1dbbSGreg Roach            'content'     => $content,
2434b2f1dbbSGreg Roach            'current'     => $breadcrumbs['current'],
2444b2f1dbbSGreg Roach            'events_link' => $events_link,
2454b2f1dbbSGreg Roach            'place'       => $place,
2464b2f1dbbSGreg Roach            'title'       => I18N::translate('Place hierarchy'),
2474b2f1dbbSGreg Roach            'tree'        => $tree,
2484b2f1dbbSGreg Roach            'world_url'   => $this->listUrl($tree)
2494b2f1dbbSGreg Roach        ]);
2504b2f1dbbSGreg Roach    }
2514b2f1dbbSGreg Roach
2524b2f1dbbSGreg Roach    /**
2534b2f1dbbSGreg Roach     * @param Tree $tree
2544b2f1dbbSGreg Roach     *
2554b2f1dbbSGreg Roach     * @return array<array<Place>>
2564b2f1dbbSGreg Roach     */
2574b2f1dbbSGreg Roach    private function getList(Tree $tree): array
2584b2f1dbbSGreg Roach    {
2594b2f1dbbSGreg Roach        $places = $this->search_service->searchPlaces($tree, '')
2604b2f1dbbSGreg Roach            ->sort(static function (Place $x, Place $y): int {
2614b2f1dbbSGreg Roach                return $x->gedcomName() <=> $y->gedcomName();
2624b2f1dbbSGreg Roach            })
2634b2f1dbbSGreg Roach            ->all();
2644b2f1dbbSGreg Roach
2654b2f1dbbSGreg Roach        $count = count($places);
2664b2f1dbbSGreg Roach
2674b2f1dbbSGreg Roach        if ($places === []) {
2684b2f1dbbSGreg Roach            return [];
2694b2f1dbbSGreg Roach        }
2704b2f1dbbSGreg Roach
2714b2f1dbbSGreg Roach        $columns = $count > 20 ? 3 : 2;
2724b2f1dbbSGreg Roach
2734b2f1dbbSGreg Roach        return array_chunk($places, (int) ceil($count / $columns));
2744b2f1dbbSGreg Roach    }
2754b2f1dbbSGreg Roach
2764b2f1dbbSGreg Roach
2774b2f1dbbSGreg Roach    /**
2784b2f1dbbSGreg Roach     * @param Place $place
2794b2f1dbbSGreg Roach     *
2804b2f1dbbSGreg Roach     * @return array{'tree':Tree,'col_class':string,'columns':array<array<Place>>,'place':Place}|null
2814b2f1dbbSGreg Roach     */
2824b2f1dbbSGreg Roach    private function getHierarchy(Place $place): ?array
2834b2f1dbbSGreg Roach    {
2844b2f1dbbSGreg Roach        $child_places = $place->getChildPlaces();
2854b2f1dbbSGreg Roach        $numfound     = count($child_places);
2864b2f1dbbSGreg Roach
2874b2f1dbbSGreg Roach        if ($numfound > 0) {
2884b2f1dbbSGreg Roach            $divisor = $numfound > 20 ? 3 : 2;
2894b2f1dbbSGreg Roach
2904b2f1dbbSGreg Roach            return [
2914b2f1dbbSGreg Roach                'tree'      => $place->tree(),
2924b2f1dbbSGreg Roach                'col_class' => 'w-' . ($divisor === 2 ? '25' : '50'),
2934b2f1dbbSGreg Roach                'columns'   => array_chunk($child_places, (int) ceil($numfound / $divisor)),
2944b2f1dbbSGreg Roach                'place'     => $place,
2954b2f1dbbSGreg Roach            ];
2964b2f1dbbSGreg Roach        }
2974b2f1dbbSGreg Roach
2984b2f1dbbSGreg Roach        return null;
2994b2f1dbbSGreg Roach    }
3004b2f1dbbSGreg Roach
3014b2f1dbbSGreg Roach    /**
3024b2f1dbbSGreg Roach     * @param Place $place
3034b2f1dbbSGreg Roach     *
3044b2f1dbbSGreg Roach     * @return array{'breadcrumbs':array<Place>,'current':Place|null}
3054b2f1dbbSGreg Roach     */
3064b2f1dbbSGreg Roach    private function breadcrumbs(Place $place): array
3074b2f1dbbSGreg Roach    {
3084b2f1dbbSGreg Roach        $breadcrumbs = [];
3094b2f1dbbSGreg Roach        if ($place->gedcomName() !== '') {
3104b2f1dbbSGreg Roach            $breadcrumbs[] = $place;
3114b2f1dbbSGreg Roach            $parent_place  = $place->parent();
3124b2f1dbbSGreg Roach            while ($parent_place->gedcomName() !== '') {
3134b2f1dbbSGreg Roach                $breadcrumbs[] = $parent_place;
3144b2f1dbbSGreg Roach                $parent_place  = $parent_place->parent();
3154b2f1dbbSGreg Roach            }
3164b2f1dbbSGreg Roach            $breadcrumbs = array_reverse($breadcrumbs);
3174b2f1dbbSGreg Roach            $current     = array_pop($breadcrumbs);
3184b2f1dbbSGreg Roach        } else {
3194b2f1dbbSGreg Roach            $current = null;
3204b2f1dbbSGreg Roach        }
3214b2f1dbbSGreg Roach
3224b2f1dbbSGreg Roach        return [
3234b2f1dbbSGreg Roach            'breadcrumbs' => $breadcrumbs,
3244b2f1dbbSGreg Roach            'current'     => $current,
3254b2f1dbbSGreg Roach        ];
3264b2f1dbbSGreg Roach    }
3274b2f1dbbSGreg Roach
3284b2f1dbbSGreg Roach    /**
3294b2f1dbbSGreg Roach     * @param Tree  $tree
3304b2f1dbbSGreg Roach     * @param Place $placeObj
3314b2f1dbbSGreg Roach     *
3324b2f1dbbSGreg Roach     * @return array<mixed>
3334b2f1dbbSGreg Roach     */
3344b2f1dbbSGreg Roach    protected function mapData(Tree $tree, Place $placeObj): array
3354b2f1dbbSGreg Roach    {
3364b2f1dbbSGreg Roach        $places    = $placeObj->getChildPlaces();
3374b2f1dbbSGreg Roach        $features  = [];
3384b2f1dbbSGreg Roach        $sidebar   = '';
3394b2f1dbbSGreg Roach        $show_link = true;
3404b2f1dbbSGreg Roach
3414b2f1dbbSGreg Roach        if ($places === []) {
3424b2f1dbbSGreg Roach            $places[] = $placeObj;
3434b2f1dbbSGreg Roach            $show_link = false;
3444b2f1dbbSGreg Roach        }
3454b2f1dbbSGreg Roach
3464b2f1dbbSGreg Roach        foreach ($places as $id => $place) {
3474b2f1dbbSGreg Roach            $location = new PlaceLocation($place->gedcomName());
3484b2f1dbbSGreg Roach
34990949315SGreg Roach            if ($location->latitude() === null || $location->longitude() === null) {
3504b2f1dbbSGreg Roach                $sidebar_class = 'unmapped';
3514b2f1dbbSGreg Roach            } else {
3524b2f1dbbSGreg Roach                $sidebar_class = 'mapped';
3534b2f1dbbSGreg Roach                $features[]    = [
3544b2f1dbbSGreg Roach                    'type'       => 'Feature',
3554b2f1dbbSGreg Roach                    'id'         => $id,
3564b2f1dbbSGreg Roach                    'geometry'   => [
3574b2f1dbbSGreg Roach                        'type'        => 'Point',
3584b2f1dbbSGreg Roach                        'coordinates' => [$location->longitude(), $location->latitude()],
3594b2f1dbbSGreg Roach                    ],
3604b2f1dbbSGreg Roach                    'properties' => [
3614b2f1dbbSGreg Roach                        'tooltip' => $place->gedcomName(),
3624b2f1dbbSGreg Roach                        'popup'   => view('modules/place-hierarchy/popup', [
3634b2f1dbbSGreg Roach                            'showlink'  => $show_link,
3644b2f1dbbSGreg Roach                            'place'     => $place,
3654b2f1dbbSGreg Roach                            'latitude'  => $location->latitude(),
3664b2f1dbbSGreg Roach                            'longitude' => $location->longitude(),
3674b2f1dbbSGreg Roach                        ]),
3684b2f1dbbSGreg Roach                    ],
3694b2f1dbbSGreg Roach                ];
3704b2f1dbbSGreg Roach            }
3714b2f1dbbSGreg Roach
3724b2f1dbbSGreg Roach            $statistics = new Statistics(app(ModuleService::class), $tree, app(UserService::class));
3734b2f1dbbSGreg Roach
3744b2f1dbbSGreg Roach            //Stats
3754b2f1dbbSGreg Roach            $placeStats = [];
3764b2f1dbbSGreg Roach            foreach (['INDI', 'FAM'] as $type) {
3774b2f1dbbSGreg Roach                $tmp               = $statistics->statsPlaces($type, '', $place->id());
3784b2f1dbbSGreg Roach                $placeStats[$type] = $tmp === [] ? 0 : $tmp[0]->tot;
3794b2f1dbbSGreg Roach            }
3804b2f1dbbSGreg Roach            $sidebar .= view('modules/place-hierarchy/sidebar', [
3814b2f1dbbSGreg Roach                'showlink'      => $show_link,
3824b2f1dbbSGreg Roach                'id'            => $id,
3834b2f1dbbSGreg Roach                'place'         => $place,
3844b2f1dbbSGreg Roach                'sidebar_class' => $sidebar_class,
3854b2f1dbbSGreg Roach                'stats'         => $placeStats,
3864b2f1dbbSGreg Roach            ]);
3874b2f1dbbSGreg Roach        }
3884b2f1dbbSGreg Roach
3894b2f1dbbSGreg Roach        return [
3904b2f1dbbSGreg Roach            'bounds'  => (new PlaceLocation($placeObj->gedcomName()))->boundingRectangle(),
3914b2f1dbbSGreg Roach            'sidebar' => $sidebar,
3924b2f1dbbSGreg Roach            'markers' => [
3934b2f1dbbSGreg Roach                'type'     => 'FeatureCollection',
3944b2f1dbbSGreg Roach                'features' => $features,
3954b2f1dbbSGreg Roach            ]
3964b2f1dbbSGreg Roach        ];
3974b2f1dbbSGreg Roach    }
39867992b6aSRichard Cissee}
399