Process a form to create a new source.

@param ServerRequestInterface $request
@param Tree                   $tree

@return ResponseInterface
325344
public function createSourceAction(ServerRequestInterface $request, Tree $tree): ResponseInterface
    {
        $title               = $request->get('source-title', '');
        $abbreviation        = $request->get('source-abbreviation', '');
        $author              = $request->get('source-author', '');
        $publication         = $request->get('source-publication', '');
        $repository          = $request->get('source-repository', '');
        $call_number         = $request->get('source-call-number', '');
        $text                = $request->get('source-text', '');
        $privacy_restriction = $request->get('privacy-restriction', '');
        $edit_restriction    = $request->get('edit-restriction', '');

        // Fix whitespace
        $title        = trim(preg_replace('/\s+/', ' ', $title));
        $abbreviation = trim(preg_replace('/\s+/', ' ', $abbreviation));
        $author       = trim(preg_replace('/\s+/', ' ', $author));
        $publication  = trim(preg_replace('/\s+/', ' ', $publication));
        $repository   = trim(preg_replace('/\s+/', ' ', $repository));
        $call_number  = trim(preg_replace('/\s+/', ' ', $call_number));

        // Convert line endings to GEDDCOM continuations
        $text = str_replace([
            "\r\n",
            "\r",
            "\n",
        ], "\n1 CONT ", $text);

        $gedcom = "0 @@ SOUR\n\n1 TITL " . $title;

        if ($abbreviation !== '') {
            $gedcom .= "\n1 ABBR " . $abbreviation;
        }

        if ($author !== '') {
            $gedcom .= "\n1 AUTH " . $author;
        }

        if ($publication !== '') {
            $gedcom .= "\n1 PUBL " . $publication;
        }

        if ($text !== '') {
            $gedcom .= "\n1 TEXT " . $text;
        }

        if ($repository !== '') {
            $gedcom .= "\n1 REPO @" . $repository . '@';

            if ($call_number !== '') {
                $gedcom .= "\n2 CALN " . $call_number;
            }
        }

        if (in_array($privacy_restriction, [
            'none',
            'privacy',
            'confidential',
        ])) {
            $gedcom .= "\n1 RESN " . $privacy_restriction;
        }

        if (in_array($edit_restriction, ['locked'])) {
            $gedcom .= "\n1 RESN " . $edit_restriction;
        }

        $record = $tree->createRecord($gedcom);

        // id and text are for select2 / autocomplete
        // html is for interactive modals
        return response([
            'id'   => $record->xref(),
            'text' => view('selects/source', [
                'source' => $record,
            ]),
            'html' => view('modals/record-created', [
                'title' => I18N::translate('The source has been created'),
                'name'  => $record->fullName(),
                'url'   => $record->url(),
            ]),
        ]);
    }
991998
public function linkChildToFamily(ServerRequestInterface $request, Tree $tree): ResponseInterface
    {
        $xref = $request->get('xref', '');

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

        Auth::checkIndividualAccess($individual, true);

        $title = $individual->fullName() . ' - ' . I18N::translate('Link this individual to an existing family as a child');

        return $this->viewResponse('edit/link-child-to-family', [
            'individual' => $individual,
            'title'      => $title,
        ]);
    }
581263
public function select2Repository(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
            ->searchRepositories([$tree], [$query], $offset, $limit)
            ->map(static function (Repository $repository): array {
                return [
                    'id'    => $repository->xref(),
                    'text'  => view('selects/repository', ['repository' => $repository]),
                    'title' => ' ',
                ];
            });

        return response([
            'results'    => $results->slice(0, self::RESULTS_PER_PAGE)->all(),
            'pagination' => [
                'more' => $results->count() > self::RESULTS_PER_PAGE,
            ],
        ]);
    }
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,
        ]);
    }
53342
public function addMediaFileAction(ServerRequestInterface $request, Tree $tree): ResponseInterface
    {
        $xref  = $request->get('xref', '');
        $media = Media::getInstance($xref, $tree);
        $title = $request->get('title');
        $type  = $request->get('type');

        // Tidy whitespace
        $type  = trim(preg_replace('/\s+/', ' ', $type));
        $title = trim(preg_replace('/\s+/', ' ', $title));

        if ($media === null || $media->isPendingDeletion() || !$media->canEdit()) {
            return redirect(route('tree-page', ['ged' => $tree->name()]));
        }

        $file = $this->uploadFile($request, $tree);

        if ($file === '') {
            FlashMessages::addMessage(I18N::translate('There was an error uploading your file.'));

            return redirect($media->url());
        }

        $gedcom = '1 FILE ' . $file;
        if ($type !== '') {
            $gedcom .= "\n2 FORM\n3 TYPE " . $type;
        }
        if ($title !== '') {
            $gedcom .= "\n2 TITL " . $title;
        }

        $media->createFact($gedcom, true);

        // Accept the changes, to keep the filesystem in sync with the GEDCOM data.
        FunctionsImport::acceptAllChanges($media->xref(), $tree);

        return redirect($media->url());
    }
1164014
public function select2Place(ServerRequestInterface $request, Tree $tree): ResponseInterface
    {
        $page  = (int) $request->get('page');
        $query = $request->get('q', '');

        return response($this->placeSearch($tree, $page, $query, true));
    }
1289854
public function sourceList(Tree $tree): ResponseInterface
    {
        $sources = $this->allSources($tree);

        return $this->viewResponse('source-list-page', [
            'sources' => $sources,
            'title'   => I18N::translate('Sources'),
        ]);
    }
565146
public function show(ServerRequestInterface $request, Tree $tree): ResponseInterface
    {
        $xref   = $request->get('xref', '');
        $record = GedcomRecord::getInstance($xref, $tree);

        Auth::checkRecordAccess($record);

        if ($this->hasCustomPage($record)) {
            return redirect($record->url());
        }

        return $this->viewResponse('gedcom-record-page', [
            'facts'         => $record->facts(),
            'families'      => $record->linkedFamilies($record::RECORD_TYPE),
            'individuals'   => $record->linkedIndividuals($record::RECORD_TYPE),
            'meta_robots'   => 'index,follow',
            'notes'         => $record->linkedNotes($record::RECORD_TYPE),
            'media_objects' => $record->linkedMedia($record::RECORD_TYPE),
            'record'        => $record,
            'sources'       => $record->linkedSources($record::RECORD_TYPE),
            'title'         => $record->fullName(),
        ]);
    }
312861
public function step(ServerRequestInterface $request, ?Tree $tree): ResponseInterface
    {
        $step = $request->getQueryParams()['step'] ?? self::STEP_CHECK;

        switch ($step) {
            case self::STEP_CHECK:
                return $this->wizardStepCheck();

            case self::STEP_PREPARE:
                return $this->wizardStepPrepare();

            case self::STEP_PENDING:
                return $this->wizardStepPending();

            case self::STEP_EXPORT:
                if ($tree === null) {
                    throw new RuntimeException('Exporting a non-existant tree?');
                }

                return $this->wizardStepExport($tree);

            case self::STEP_DOWNLOAD:
                return $this->wizardStepDownload();

            case self::STEP_UNZIP:
                return $this->wizardStepUnzip();

            case self::STEP_COPY:
                return $this->wizardStepCopy();

            case self::STEP_CLEANUP:
                return $this->wizardStepCleanup();
        }

        throw new NotFoundHttpException();
    }
830660
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
    {
        /** @var Tree|null $tree */
        $tree = app(Tree::class);

        // The tree exists, we have access to it, and it is fully imported.
        if ($tree instanceof Tree && $tree->getPreference('imported') === '1') {
            return redirect(route('tree-page', ['ged' => $tree->name()]));
        }

        // Not logged in?
        if (!Auth::check()) {
            return redirect(route('login', ['url' => (string) $request->getUri()]));
        }

        // No tree or tree not imported?
        if (Auth::isAdmin()) {
            return redirect(route('admin-trees'));
        }

        return $this->viewResponse('errors/no-tree-access', ['title' => '']);
    }
1304069
public function addSpouse(ServerRequestInterface $request, Tree $tree): ResponseInterface
    {
        $xref = $request->get('xref', '');

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

        Auth::checkIndividualAccess($individual, true);

        if ($individual->sex() === 'F') {
            $title  = $individual->fullName() . ' - ' . I18N::translate('Add a husband');
            $famtag = 'HUSB';
            $gender = 'M';
        } else {
            $title  = $individual->fullName() . ' - ' . I18N::translate('Add a wife');
            $famtag = 'WIFE';
            $gender = 'F';
        }

        return $this->viewResponse('edit/new-individual', [
            'tree'       => $tree,
            'title'      => $title,
            'nextaction' => 'add_spouse_to_individual_action',
            'individual' => $individual,
            'family'     => null,
            'name_fact'  => null,
            'famtag'     => $famtag,
            'gender'     => $gender,
        ]);
    }
1086915
public function create(ServerRequestInterface $request): ResponseInterface
    {
        $tree_name  = $request->get('tree_name', '');
        $tree_title = $request->get('tree_title', '');

        // We use the tree name as a file name, so no directory separators allowed.
        $tree_name = basename($tree_name);

        if (Tree::findByName($tree_name)) {
            FlashMessages::addMessage(I18N::translate('The family tree “%s” already exists.', e($tree_name)), 'danger');

            $url = route('admin-trees');
        } else {
            $tree = Tree::create($tree_name, $tree_title);
            FlashMessages::addMessage(I18N::translate('The family tree “%s” has been created.', e($tree->name())), 'success');

            $url = route('admin-trees', ['ged' => $tree->name()]);
        }

        return redirect($url);
    }
916180
public function reorderNames(ServerRequestInterface $request, Tree $tree): ResponseInterface
    {
        $xref       = $request->get('xref', '');
        $individual = Individual::getInstance($xref, $tree);

        Auth::checkIndividualAccess($individual, true);

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

        return $this->viewResponse('edit/reorder-names', [
            'title'      => $title,
            '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,
        ]);
    }
815552
public function replaceAction(ServerRequestInterface $request, Tree $tree): ResponseInterface
    {
        $search  = $request->get('search', '');
        $replace = $request->get('replace', '');
        $context = $request->get('context', '');

        switch ($context) {
            case 'all':
                $records = $this->search_service->searchIndividuals([$tree], [$search]);
                $count   = $this->replaceRecords($records, $search, $replace);
                FlashMessages::addMessage(I18N::plural('%s individual has been updated.', '%s individuals have been updated.', $count, I18N::number($count)));

                $records = $this->search_service->searchFamilies([$tree], [$search]);
                $count   = $this->replaceRecords($records, $search, $replace);
                FlashMessages::addMessage(I18N::plural('%s family has been updated.', '%s families have been updated.', $count, I18N::number($count)));

                $records = $this->search_service->searchRepositories([$tree], [$search]);
                $count   = $this->replaceRecords($records, $search, $replace);
                FlashMessages::addMessage(I18N::plural('%s repository has been updated.', '%s repositories have been updated.', $count, I18N::number($count)));

                $records = $this->search_service->searchSources([$tree], [$search]);
                $count   = $this->replaceRecords($records, $search, $replace);
                FlashMessages::addMessage(I18N::plural('%s source has been updated.', '%s sources have been updated.', $count, I18N::number($count)));

                $records = $this->search_service->searchNotes([$tree], [$search]);
                $count   = $this->replaceRecords($records, $search, $replace);
                FlashMessages::addMessage(I18N::plural('%s note has been updated.', '%s notes have been updated.', $count, I18N::number($count)));
                break;

            case 'name':
                $adv_name_tags = preg_split("/[\s,;: ]+/", $tree->getPreference('ADVANCED_NAME_FACTS'));
                $name_tags     = array_unique(array_merge([
                    'NAME',
                    'NPFX',
                    'GIVN',
                    'SPFX',
                    'SURN',
                    'NSFX',
                    '_MARNM',
                    '_AKA',
                ], $adv_name_tags));

                $records = $this->search_service->searchIndividuals([$tree], [$search]);
                $count   = $this->replaceIndividualNames($records, $search, $replace, $name_tags);
                FlashMessages::addMessage(I18N::plural('%s individual has been updated.', '%s individuals have been updated.', $count, I18N::number($count)));
                break;

            case 'place':
                $records = $this->search_service->searchIndividuals([$tree], [$search]);
                $count   = $this->replacePlaces($records, $search, $replace);
                FlashMessages::addMessage(I18N::plural('%s individual has been updated.', '%s individuals have been updated.', $count, I18N::number($count)));

                $records = $this->search_service->searchFamilies([$tree], [$search]);
                $count   = $this->replacePlaces($records, $search, $replace);
                FlashMessages::addMessage(I18N::plural('%s family has been updated.', '%s families have been updated.', $count, I18N::number($count)));
                break;
        }

        $url = route('search-replace', [
            'search'  => $search,
            'replace' => $replace,
            'context' => $context,
            'ged'     => $tree->name(),
        ]);

        return redirect($url);
    }
409975
public function show(ServerRequestInterface $request, Tree $tree, SearchService $search_service): ResponseInterface
    {
        $action2    = $request->getQueryParams()['action2'] ?? 'hierarchy';
        $parent     = $request->getQueryParams()['parent'] ?? [];
        $fqpn       = implode(Gedcom::PLACE_SEPARATOR, array_reverse($parent));
        $place      = new Place($fqpn, $tree);
        $content    = '';
        $showmap    = Site::getPreference('map-provider') !== '';
        $data       = null;
        $note       = false;

        if ($showmap) {
            $note = true;
            $content .= view('place-map', [
                'module' => self::MAP_MODULE,
                'ref'    => $fqpn,
                'type'   => 'placelist',
                'data'   => $this->mapData($tree, $fqpn),
            ]);
        }

        switch ($action2) {
            case 'list':
                $nextaction = ['hierarchy' => I18N::translate('Show place hierarchy')];
                $content .= view('place-list', $this->getList($tree, $search_service));
                break;
            case 'hierarchy':
            case 'hierarchy-e':
                $nextaction = ['list' => I18N::translate('Show all places in a list')];
                $data       = $this->getHierarchy($tree, $place, $parent);
                $content .= (null === $data || $showmap) ? '' : view('place-hierarchy', $data);
                if (null === $data || $action2 === 'hierarchy-e') {
                    $content .= view('place-events', $this->getEvents($tree, $place));
                }
                break;
            default:
                throw new NotFoundHttpException('Invalid action');
        }

        $breadcrumbs = $this->breadcrumbs($place);

        //route is assumed to be 'module'
        $module = $request->get('module');
        $action = $request->get('action');
        
        return $this->viewResponse(
            'places-page',
            [
                'title'          => I18N::translate('Places'),
                'note'           => $note,
                'tree'           => $tree,
                'current'        => $breadcrumbs['current'],
                'breadcrumbs'    => $breadcrumbs['breadcrumbs'],
                'parent'         => $parent,
                'place'          => $fqpn,
                'content'        => $content,
                'showeventslink' => null !== $data && $place->gedcomName() !== '' && $action2 !== 'hierarchy-e',
                'nextaction'     => $nextaction,
                'module'         => $module,
                'action'         => $action,
            ]
        );
    }
766014
public function postAdminDeleteAction(ServerRequestInterface $request, Tree $tree): ResponseInterface
    {
        $block_id = (int) $request->get('block_id');

        DB::table('block_setting')->where('block_id', '=', $block_id)->delete();

        DB::table('block')->where('block_id', '=', $block_id)->delete();

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

        return redirect($url);
    }