@param ServerRequestInterface $request
@param Tree                   $tree

@return ResponseInterface
7964
public function select2Submitter(ServerRequestInterface $request, Tree $tree): ResponseInterface
    {
        $page  = (int) $request->get('page');
        $query = $request->get('q', '');

        // Fetch one more row than we need, so we can know if more rows exist.
        $offset = $page * self::RESULTS_PER_PAGE;
        $limit  = self::RESULTS_PER_PAGE + 1;

        $results = $this->search_service
            ->searchSubmitters([$tree], [$query], $offset, $limit)
            ->map(static function (GedcomRecord $submitter): array {
                return [
                    'id'    => $submitter->xref(),
                    'text'  => view('selects/submitter', ['submitter' => $submitter]),
                    'title' => ' ',
                ];
            });

        return response([
            'results'    => $results->slice(0, self::RESULTS_PER_PAGE)->all(),
            'pagination' => [
                'more' => $results->count() > self::RESULTS_PER_PAGE,
            ],
        ]);
    }
113090
public function addChild(ServerRequestInterface $request, Tree $tree): ResponseInterface
    {
        $xref   = $request->get('xref', '');
        $gender = $request->get('gender', 'U');

        $family = Family::getInstance($xref, $tree);

        Auth::checkFamilyAccess($family, true);

        $title = $family->fullName() . ' - ' . I18N::translate('Add a child');

        return $this->viewResponse('edit/new-individual', [
            'tree'       => $tree,
            'title'      => $title,
            'nextaction' => 'add_child_to_family_action',
            'individual' => null,
            'family'     => $family,
            'name_fact'  => null,
            'famtag'     => 'CHIL',
            'gender'     => $gender,
        ]);
    }
214228
public function changeFamilyMembers(ServerRequestInterface $request, Tree $tree): ResponseInterface
    {
        $xref   = $request->get('xref', '');
        $family = Family::getInstance($xref, $tree);
        Auth::checkFamilyAccess($family, true);

        $title = I18N::translate('Change family members') . ' – ' . $family->fullName();

        return $this->viewResponse('edit/change-family-members', [
            'tree'     => $tree,
            'title'    => $title,
            'family'   => $family,
            'father'   => $family->husband(),
            'mother'   => $family->wife(),
            'children' => $family->children(),
        ]);
    }
1166383
public function mediaThumbnail(ServerRequestInterface $request, Tree $tree): ResponseInterface
    {
        $xref    = $request->get('xref', '');
        $fact_id = $request->get('fact_id', '');
        $media   = Media::getInstance($xref, $tree);

        if ($media === null) {
            return $this->httpStatusAsImage(StatusCodeInterface::STATUS_NOT_FOUND);
        }

        if (!$media->canShow()) {
            return $this->httpStatusAsImage(StatusCodeInterface::STATUS_FORBIDDEN);
        }

        // @TODO handle SVG files
        foreach ($media->mediaFiles() as $media_file) {
            if ($media_file->factId() === $fact_id) {
                if ($media_file->isExternal()) {
                    return redirect($media_file->filename());
                }

                if ($media_file->isImage()) {
                    return $this->generateImage($media_file, $request->getQueryParams());
                }

                return $this->fileExtensionAsImage($media_file->extension());
            }
        }

        return $this->httpStatusAsImage(StatusCodeInterface::STATUS_NOT_FOUND);
    }
424596
public function treePageBlockEdit(ServerRequestInterface $request, Tree $tree, UserInterface $user): ResponseInterface
    {
        $block_id = (int) $request->get('block_id');
        $block    = $this->treeBlock($request, $tree, $user);
        $title    = $block->title() . ' — ' . I18N::translate('Preferences');

        return $this->viewResponse('modules/edit-block-config', [
            'block'      => $block,
            'block_id'   => $block_id,
            'cancel_url' => route('tree-page', ['ged' => $tree->name()]),
            'title'      => $title,
            'tree'       => $tree,
        ]);
    }
235605
public function addFact(ServerRequestInterface $request, Tree $tree): ResponseInterface
    {
        $xref = $request->get('xref', '');
        $fact = $request->get('fact', '');

        $record = GedcomRecord::getInstance($xref, $tree);
        Auth::checkRecordAccess($record, true);

        $title = $record->fullName() . ' - ' . GedcomTag::getLabel($fact, $record);

        return $this->viewResponse('edit/add-fact', [
            'fact'   => $fact,
            'record' => $record,
            'title'  => $title,
            'tree'   => $tree,
        ]);
    }
1045914
public function quick(ServerRequestInterface $request, Tree $tree): ResponseInterface
    {
        $query = $request->get('query', '');

        // Was the search query an XREF in the current tree?
        // If so, go straight to it.
        $record = GedcomRecord::getInstance($query, $tree);

        if ($record !== null && $record->canShow()) {
            return redirect($record->url());
        }

        return $this->general($request, $tree);
    }
149489
public function mergeRecords(ServerRequestInterface $request, Tree $tree): ResponseInterface
    {
        $title = I18N::translate('Merge records') . ' — ' . e($tree->title());

        $xref1 = $request->get('xref1', '');
        $xref2 = $request->get('xref2', '');

        $record1 = GedcomRecord::getInstance($xref1, $tree);
        $record2 = GedcomRecord::getInstance($xref2, $tree);

        if ($xref1 !== '' && $record1 === null) {
            $xref1 = '';
        }

        if ($xref2 !== '' && $record2 === null) {
            $xref2 = '';
        }

        if ($record1 === $record2) {
            $xref2 = '';
        }

        if ($record1 !== null && $record2 && $record1::RECORD_TYPE !== $record2::RECORD_TYPE) {
            $xref2 = '';
        }

        if ($xref1 === '' || $xref2 === '') {
            return $this->viewResponse('admin/merge-records-step-1', [
                'individual1' => $record1 instanceof Individual ? $record1 : null,
                'individual2' => $record2 instanceof Individual ? $record2 : null,
                'family1'     => $record1 instanceof Family ? $record1 : null,
                'family2'     => $record2 instanceof Family ? $record2 : null,
                'source1'     => $record1 instanceof Source ? $record1 : null,
                'source2'     => $record2 instanceof Source ? $record2 : null,
                'repository1' => $record1 instanceof Repository ? $record1 : null,
                'repository2' => $record2 instanceof Repository ? $record2 : null,
                'media1'      => $record1 instanceof Media ? $record1 : null,
                'media2'      => $record2 instanceof Media ? $record2 : null,
                'note1'       => $record1 instanceof Note ? $record1 : null,
                'note2'       => $record2 instanceof Note ? $record2 : null,
                'title'       => $title,
            ]);
        }

        // Facts found both records
        $facts = [];
        // Facts found in only one record
        $facts1 = [];
        $facts2 = [];

        foreach ($record1->facts() as $fact) {
            if (!$fact->isPendingDeletion() && $fact->getTag() !== 'CHAN') {
                $facts1[$fact->id()] = $fact;
            }
        }

        foreach ($record2->facts() as $fact) {
            if (!$fact->isPendingDeletion() && $fact->getTag() !== 'CHAN') {
                $facts2[$fact->id()] = $fact;
            }
        }

        foreach ($facts1 as $id1 => $fact1) {
            foreach ($facts2 as $id2 => $fact2) {
                if ($fact1->id() === $fact2->id()) {
                    $facts[] = $fact1;
                    unset($facts1[$id1]);
                    unset($facts2[$id2]);
                }
            }
        }

        return $this->viewResponse('admin/merge-records-step-2', [
            'facts'   => $facts,
            'facts1'  => $facts1,
            'facts2'  => $facts2,
            'record1' => $record1,
            'record2' => $record2,
            'title'   => $title,
        ]);
    }
478158
public function place(ServerRequestInterface $request, Tree $tree): ResponseInterface
    {
        $query = $request->get('query', '');
        $data  = [];

        foreach ($this->search_service->searchPlaces($tree, $query) as $place) {
            $data[] = ['value' => $place->gedcomName()];
        }

        if (empty($data) && $tree->getPreference('GEONAMES_ACCOUNT')) {
            // No place found? Use an external gazetteer
            $url =
                'http://api.geonames.org/searchJSON' .
                '?name_startsWith=' . urlencode($query) .
                '&lang=' . WT_LOCALE .
                '&fcode=CMTY&fcode=ADM4&fcode=PPL&fcode=PPLA&fcode=PPLC' .
                '&style=full' .
                '&username=' . $tree->getPreference('GEONAMES_ACCOUNT');

            // try to use curl when file_get_contents not allowed
            if (ini_get('allow_url_fopen')) {
                $json = file_get_contents($url);
            } elseif (function_exists('curl_init')) {
                $ch = curl_init();
                curl_setopt($ch, CURLOPT_URL, $url);
                curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
                $json = curl_exec($ch);
                curl_close($ch);
            } else {
                return response([]);
            }

            $places = json_decode($json, true);
            if (isset($places['geonames']) && is_array($places['geonames'])) {
                foreach ($places['geonames'] as $k => $place) {
                    $data[] = ['value' => $place['name'] . ', ' . $place['adminName2'] . ', ' . $place['adminName1'] . ', ' . $place['countryName']];
                }
            }
        }

        return response($data);
    }
453701
public function reorderChildren(ServerRequestInterface $request, Tree $tree): ResponseInterface
    {
        $xref   = $request->get('xref', '');
        $family = Family::getInstance($xref, $tree);

        Auth::checkFamilyAccess($family, true);

        $title = $family->fullName() . ' — ' . I18N::translate('Re-order children');

        return $this->viewResponse('edit/reorder-children', [
            'title'  => $title,
            'family' => $family,
        ]);
    }
797784
public function addName(ServerRequestInterface $request, Tree $tree): ResponseInterface
    {
        $xref = $request->get('xref', '');

        $individual = Individual::getInstance($xref, $tree);

        Auth::checkIndividualAccess($individual, true);

        $title = $individual->fullName() . ' — ' . I18N::translate('Add a name');

        return $this->viewResponse('edit/new-individual', [
            'tree'       => $tree,
            'title'      => $title,
            'nextaction' => 'update',
            'individual' => $individual,
            'family'     => null,
            'name_fact'  => null,
            'famtag'     => '',
            'gender'     => $individual->sex(),
        ]);
    }
601732
public function editNameAction(ServerRequestInterface $request, Tree $tree): ResponseInterface
    {
        $xref = $request->get('xref', '');

        $individual = Individual::getInstance($xref, $tree);

        Auth::checkIndividualAccess($individual, true);

        // @TODO - Move the name-specific code to this function?
        return app(EditGedcomRecordController::class)->updateFact($request, $tree);
    }
894076
public function getAdminEditAction(ServerRequestInterface $request, Tree $tree): ResponseInterface
    {
        $this->layout = 'layouts/administration';

        $block_id = (int) $request->get('block_id');

        if ($block_id === 0) {
            // Creating a new story
            $individual  = Individual::getInstance($request->get('xref', ''), $tree);
            $story_title = '';
            $story_body  = '';
            $languages   = [];

            $title = I18N::translate('Add a story') . ' — ' . e($tree->title());
        } else {
            // Editing an existing story
            $xref = (string) DB::table('block')
                ->where('block_id', '=', $block_id)
                ->value('xref');

            $individual  = Individual::getInstance($xref, $tree);
            $story_title = $this->getBlockSetting($block_id, 'title', '');
            $story_body  = $this->getBlockSetting($block_id, 'story_body', '');
            $languages   = explode(',', $this->getBlockSetting($block_id, 'languages'));

            $title = I18N::translate('Edit the story') . ' — ' . e($tree->title());
        }

        return $this->viewResponse('modules/stories/edit', [
            'block_id'    => $block_id,
            'languages'   => $languages,
            'story_body'  => $story_body,
            'story_title' => $story_title,
            'title'       => $title,
            'tree'        => $tree,
            'individual'  => $individual,
        ]);
    }
1290106
public function getAddMediaAction(ServerRequestInterface $request, Tree $tree): ResponseInterface
    {
        $xref = $request->get('xref', '');

        $media = Media::getInstance($xref, $tree);

        if ($media === null) {
            throw new MediaNotFoundException();
        }

        $options = $this->mediaOptions($media);

        $title = I18N::translate('Add %s to the clippings cart', $media->fullName());

        return $this->viewResponse('modules/clippings/add-options', [
            'options' => $options,
            'default' => key($options),
            'record'  => $media,
            'title'   => $title,
            'tree'    => $tree,
        ]);
    }
206811
public function postAddMediaAction(ServerRequestInterface $request, Tree $tree): ResponseInterface
    {
        $xref = $request->get('xref', '');

        $media = Media::getInstance($xref, $tree);

        if ($media === null) {
            throw new MediaNotFoundException();
        }

        $this->addRecordToCart($media);

        return redirect($media->url());
    }
1304689
public function treePageDefaultUpdate(ServerRequestInterface $request): ResponseInterface
    {
        $main_blocks = (array) $request->get('main');
        $side_blocks = (array) $request->get('side');

        $this->updateTreeBlocks(-1, $main_blocks, $side_blocks);

        return redirect(route('admin-control-panel'));
    }
401936
public function postAdminEditAction(ServerRequestInterface $request, Tree $tree): ResponseInterface
    {
        $block_id    = (int) $request->get('block_id');
        $faqbody     = $request->get('faqbody', '');
        $header      = $request->get('header', '');
        $languages   = $request->get('languages', []);
        $gedcom_id   = (int) $request->get('gedcom_id') ?: null;
        $block_order = (int) $request->get('block_order');

        if ($block_id !== 0) {
            DB::table('block')
                ->where('block_id', '=', $block_id)
                ->update([
                    'gedcom_id'   => $gedcom_id,
                    'block_order' => $block_order,
                ]);
        } else {
            DB::table('block')->insert([
                'gedcom_id'   => $gedcom_id,
                'module_name' => $this->name(),
                'block_order' => $block_order,
            ]);

            $block_id = (int) DB::connection()->getPdo()->lastInsertId();
        }

        $this->setBlockSetting($block_id, 'faqbody', $faqbody);
        $this->setBlockSetting($block_id, 'header', $header);
        $this->setBlockSetting($block_id, 'languages', implode(',', $languages));

        $url = route('module', [
            'module' => $this->name(),
            'action' => 'Admin',
            'ged'    => $tree->name(),
        ]);

        return redirect($url);
    }