@param ServerRequestInterface $request
@param Tree                   $tree
@param UserInterface          $user

@return ResponseInterface
495499
public function list(ServerRequestInterface $request, Tree $tree, UserInterface $user): ResponseInterface
    {
        $soundex_dm  = (bool) $request->get('soundex_dm');
        $soundex_std = (bool) $request->get('soundex_std');
        $surname     = $request->get('surname', '');

        // Highlight direct-line ancestors of this individual.
        $self = Individual::getInstance($tree->getUserPreference($user, 'gedcomid'), $tree);

        if ($surname !== '') {
            $individuals = $this->loadIndividuals($tree, $surname, $soundex_dm, $soundex_std);
        } else {
            $individuals = [];
        }

        if ($self !== null) {
            $ancestors = $this->allAncestors($self);
        } else {
            $ancestors = [];
        }

        // @TODO - convert this to use views
        $html = view('branches-list', [
            'branches' => $this->getPatriarchsHtml($tree, $individuals, $ancestors, $surname, $soundex_dm, $soundex_std),
        ]);

        return response($html);
    }
449835
public function advanced(ServerRequestInterface $request, Tree $tree): ResponseInterface
    {
        $default_fields = array_fill_keys(self::DEFAULT_ADVANCED_FIELDS, '');

        $fields      = $request->get('fields', $default_fields);
        $modifiers   = $request->get('modifiers', []);
        $other_field = $request->get('other_field', '');
        $other_value = $request->get('other_value', '');

        if ($other_field !== '' && $other_value !== '') {
            $fields[$other_field] = $other_value;
        }

        $other_fields = $this->otherFields($fields);
        $date_options = $this->dateOptions();
        $name_options = $this->nameOptions();

        if (!empty(array_filter($fields))) {
            $individuals = $this->search_service->searchIndividualsAdvanced([$tree], $fields, $modifiers);
        } else {
            $individuals = [];
        }

        $title = I18N::translate('Advanced search');

        return $this->viewResponse('search-advanced-page', [
            'date_options' => $date_options,
            'fields'       => $fields,
            'individuals'  => $individuals,
            'modifiers'    => $modifiers,
            'name_options' => $name_options,
            'other_fields' => $other_fields,
            'title'        => $title,
        ]);
    }
921455
public function treePageUpdate(ServerRequestInterface $request, Tree $tree): ResponseInterface
    {
        $defaults = (bool) $request->get('defaults');

        if ($defaults) {
            $main_blocks = $this->treeBlocks(-1, 'main')->all();
            $side_blocks = $this->treeBlocks(-1, 'side')->all();
        } else {
            $main_blocks = (array) $request->get('main');
            $side_blocks = (array) $request->get('side');
        }

        $this->updateTreeBlocks($tree->id(), $main_blocks, $side_blocks);

        return redirect(route('tree-page', ['ged' => $tree->name()]));
    }
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);
    }
174711
public function select2Source(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
            ->searchSourcesByName([$tree], [$query], $offset, $limit)
            ->map(static function (Source $source): array {
                return [
                    'id'    => $source->xref(),
                    'text'  => view('selects/source', ['source' => $source]),
                    'title' => ' ',
                ];
            });

        return response([
            'results'    => $results->slice(0, self::RESULTS_PER_PAGE)->all(),
            'pagination' => [
                'more' => $results->count() > self::RESULTS_PER_PAGE,
            ],
        ]);
    }
686375
public function mediaList(ServerRequestInterface $request, Tree $tree): ResponseInterface
    {
        //route is assumed to be 'module'
        $module = $request->get('module');
        $action = $request->get('action');
        
        $formats = GedcomTag::getFileFormTypes();

        $action2   = $request->get('action2');
        $page      = (int) $request->get('page');
        $max       = (int) $request->get('max', 20);
        $folder    = $request->get('folder', '');
        $filter    = $request->get('filter', '');
        $subdirs   = $request->get('subdirs', '');
        $form_type = $request->get('form_type', '');

        $folders = $this->allFolders($tree);

        if ($action2 === '1') {
            $media_objects = $this->allMedia(
                $tree,
                $folder,
                $subdirs === '1' ? 'include' : 'exclude',
                'title',
                $filter,
                $form_type
            );
        } else {
            $media_objects = [];
        }

        // Pagination
        $count = count($media_objects);
        $pages = (int) (($count + $max - 1) / $max);
        $page  = max(min($page, $pages), 1);

        $media_objects = array_slice($media_objects, ($page - 1) * $max, $max);

        return $this->viewResponse('media-list-page', [
            'count'         => $count,
            'filter'        => $filter,
            'folder'        => $folder,
            'folders'       => $folders,
            'formats'       => $formats,
            'form_type'     => $form_type,
            'max'           => $max,
            'media_objects' => $media_objects,
            'page'          => $page,
            'pages'         => $pages,
            'subdirs'       => $subdirs,
            'title'         => I18N::translate('Media'),
            'module'        => $module,
            'action'        => $action,
        ]);
    }
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(),
            ]),
        ]);
    }
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);
    }
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,
            ],
        ]);
    }
491068
public function linkSpouseToIndividual(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 using an existing individual');
            $label = I18N::translate('Husband');
        } else {
            $title = $individual->fullName() . ' - ' . I18N::translate('Add a wife using an existing individual');
            $label = I18N::translate('Wife');
        }

        return $this->viewResponse('edit/link-spouse-to-individual', [
            'individual' => $individual,
            'label'      => $label,
            'title'      => $title,
        ]);
    }
596651
public function messagePage(ServerRequestInterface $request, UserInterface $user): ResponseInterface
    {
        $referer = $request->getHeaderLine('referer');

        $body    = $request->get('body', '');
        $subject = $request->get('subject', '');
        $to      = $request->get('to', '');
        $url     = $request->get('url', $referer);

        $to_user = $this->user_service->findByUserName($to);

        if ($to_user === null || $to_user->getPreference('contactmethod') === 'none') {
            throw new AccessDeniedHttpException('Invalid contact user id');
        }

        $title = I18N::translate('Send a message');

        return $this->viewResponse('message-page', [
            'body'    => $body,
            'from'    => $user,
            'subject' => $subject,
            'title'   => $title,
            'to'      => $to_user,
            'url'     => $url,
        ]);
    }
809973
public function acceptChanges(ServerRequestInterface $request, Tree $tree): ResponseInterface
    {
        $xref = $request->get('xref', '');

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

        Auth::checkRecordAccess($record, false);

        if ($record && Auth::isModerator($tree)) {
            if ($record->isPendingDeletion()) {
                /* I18N: %s is the name of a genealogy record */
                FlashMessages::addMessage(I18N::translate('“%s” has been deleted.', $record->fullName()));
            } else {
                /* I18N: %s is the name of a genealogy record */
                FlashMessages::addMessage(I18N::translate('The changes to “%s” have been accepted.', $record->fullName()));
            }
            FunctionsImport::acceptAllChanges($record->xref(), $record->tree());
        }

        return response();
    }
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,
        ]);
    }
448915
public function getPersonsAction(ServerRequestInterface $request, Tree $tree): ResponseInterface
    {
        $q        = $request->get('q', '');
        $instance = $request->get('instance', '');
        $treeview = new TreeView($instance);

        return response($treeview->getPersons($tree, $q));
    }
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);
    }
1221120
public function getChartAction(ServerRequestInterface $request, Tree $tree, UserInterface $user): ResponseInterface
    {
        $ajax       = (bool) ($request->getQueryParams()['ajax'] ?? false);
        $xref       = $request->getQueryParams()['xref'] ?? '';
        $individual = Individual::getInstance($xref, $tree);

        Auth::checkIndividualAccess($individual);
        Auth::checkComponentAccess($this, 'chart', $tree, $user);

        $show_spouse = (bool) ($request->getQueryParams()['show_spouse'] ?? false);
        $generations = (int) ($request->getQueryParams()['generations'] ?? self::DEFAULT_GENERATIONS);
        $generations = min($generations, self::MAXIMUM_GENERATIONS);
        $generations = max($generations, self::MINIMUM_GENERATIONS);

        // Generations of ancestors/descendants in each mini-tree.
        $book_size = (int) ($request->getQueryParams()['book_size'] ?? 2);
        $book_size = min($book_size, 5);
        $book_size = max($book_size, 2);

        if ($ajax) {
            return $this->chart($individual, $generations, $book_size, $show_spouse);
        }

        $ajax_url = $this->chartUrl($individual, [
            'ajax'        => true,
            'book_size'   => $book_size,
            'generations' => $generations,
            'show_spouse' => $show_spouse,
        ]);

        return $this->viewResponse('modules/family-book-chart/page', [
            'ajax_url'            => $ajax_url,
            'book_size'           => $book_size,
            'generations'         => $generations,
            'individual'          => $individual,
            'maximum_generations' => self::MAXIMUM_GENERATIONS,
            'minimum_generations' => self::MINIMUM_GENERATIONS,
            'module_name'         => $this->name(),
            'show_spouse'         => $show_spouse,
            'title'               => $this->chartTitle($individual),
        ]);
    }
176294
public function showChanges(ServerRequestInterface $request, Tree $tree): ResponseInterface
    {
        $url = $request->get('url', route('tree-page', ['ged' => $tree->name()]));

        $rows = DB::table('change')
            ->join('user', 'user.user_id', '=', 'change.user_id')
            ->join('gedcom', 'gedcom.gedcom_id', '=', 'change.gedcom_id')
            ->where('status', '=', 'pending')
            ->orderBy('change.gedcom_id')
            ->orderBy('change.xref')
            ->orderBy('change.change_id')
            ->select(['change.*', 'user.user_name', 'user.real_name', 'gedcom_name'])
            ->get();

        $changes = [];
        foreach ($rows as $row) {
            $row->change_time = Carbon::make($row->change_time);

            $change_tree = Tree::findById((int) $row->gedcom_id);

            preg_match('/^0 (?:@' . Gedcom::REGEX_XREF . '@ )?(' . Gedcom::REGEX_TAG . ')/', $row->old_gedcom . $row->new_gedcom, $match);

            switch ($match[1]) {
                case 'INDI':
                    $row->record = new Individual($row->xref, $row->old_gedcom, $row->new_gedcom, $change_tree);
                    break;
                case 'FAM':
                    $row->record = new Family($row->xref, $row->old_gedcom, $row->new_gedcom, $change_tree);
                    break;
                case 'SOUR':
                    $row->record = new Source($row->xref, $row->old_gedcom, $row->new_gedcom, $change_tree);
                    break;
                case 'REPO':
                    $row->record = new Repository($row->xref, $row->old_gedcom, $row->new_gedcom, $change_tree);
                    break;
                case 'OBJE':
                    $row->record = new Media($row->xref, $row->old_gedcom, $row->new_gedcom, $change_tree);
                    break;
                case 'NOTE':
                    $row->record = new Note($row->xref, $row->old_gedcom, $row->new_gedcom, $change_tree);
                    break;
                default:
                    $row->record = new GedcomRecord($row->xref, $row->old_gedcom, $row->new_gedcom, $change_tree);
                    break;
            }

            $changes[$row->gedcom_id][$row->xref][] = $row;
        }

        $title = I18N::translate('Pending changes');

        // If the current tree has changes, activate that tab.  Otherwise activate the first tab.
        if (empty($changes[$tree->id()])) {
            reset($changes);
            $active_tree_id = key($changes);
        } else {
            $active_tree_id = $tree->id();
        }

        return $this->viewResponse('pending-changes-page', [
            'active_tree_id' => $active_tree_id,
            'changes'        => $changes,
            'title'          => $title,
            'url'            => $url,
        ]);
    }