@param ServerRequestInterface   $request
@param Tree                     $tree
@param UserInterface            $user
@param bool                     $families
@param ModuleListInterface|null $moduleListInterface

@return ResponseInterface
483303
public function individualOrFamilyList(ServerRequestInterface $request, Tree $tree, UserInterface $user, bool $families, ?ModuleListInterface $moduleListInterface): ResponseInterface
    {
        // This action can show lists of both families and individuals.
        //route is assumed to be 'module'
        $module = $request->getQueryParams()['module'];
        $action = $request->getQueryParams()['action'];
        
        ob_start();

        // We show three different lists: initials, surnames and individuals

        // All surnames beginning with this letter where "@"=unknown and ","=none
        $alpha = $request->getQueryParams()['alpha'] ?? '';

        // All individuals with this surname
        $surname = $request->getQueryParams()['surname'] ??  '';

        // All individuals
        $show_all = $request->getQueryParams()['show_all'] ?? 'no';

        // Long lists can be broken down by given name
        $show_all_firstnames = $request->getQueryParams()['show_all_firstnames'] ?? 'no';
        if ($show_all_firstnames === 'yes') {
            $falpha = '';
        } else {
            // All first names beginning with this letter
            $falpha = $request->getQueryParams()['falpha'] ?? '';
        }

        $show_marnm = $request->getQueryParams()['show_marnm'] ?? '';
        switch ($show_marnm) {
            case 'no':
            case 'yes':
                $user->setPreference($families ? 'family-list-marnm' : 'individual-list-marnm', $show_marnm);
                break;
            default:
                $show_marnm = $user->getPreference($families ? 'family-list-marnm' : 'individual-list-marnm');
        }

        // Make sure selections are consistent.
        // i.e. can’t specify show_all and surname at the same time.
        if ($show_all === 'yes') {
            if ($show_all_firstnames === 'yes') {
                $alpha   = '';
                $surname = '';
                $legend  = I18N::translate('All');
                $params  = [
                    'ged'      => $tree->name(),
                    'show_all' => 'yes',
                ];
                $show    = 'indi';
            } elseif ($falpha) {
                $alpha   = '';
                $surname = '';
                $legend  = I18N::translate('All') . ', ' . e($falpha) . '…';
                $params  = [
                    'ged'      => $tree->name(),
                    'show_all' => 'yes',
                ];
                $show    = 'indi';
            } else {
                $alpha   = '';
                $surname = '';
                $legend  = I18N::translate('All');
                $params  = [
                    'ged'      => $tree->name(),
                    'show_all' => 'yes',
                ];
                $show    = $request->getQueryParams()['show'] ?? 'surn';
            }
        } elseif ($surname) {
            $alpha    = $this->localization_service->initialLetter($surname); // so we can highlight the initial letter
            $show_all = 'no';
            if ($surname === '@N.N.') {
                $legend = I18N::translateContext('Unknown surname', '…');
            } else {
                // The surname parameter is a root/canonical form.
                // Display it as the actual surname
                $legend = implode('/', array_keys($this->individual_list_service->surnames($surname, $alpha, $show_marnm === 'yes', $families, WT_LOCALE, I18N::collation())));
            }
            $params = [
                'ged'     => $tree->name(),
                'surname' => $surname,
                'falpha'  => $falpha,
            ];
            switch ($falpha) {
                case '':
                    break;
                case '@':
                    $legend .= ', ' . I18N::translateContext('Unknown given name', '…');
                    break;
                default:
                    $legend .= ', ' . e($falpha) . '…';
                    break;
            }
            $show = 'indi'; // SURN list makes no sense here
        } elseif ($alpha === '@') {
            $show_all = 'no';
            $legend   = I18N::translateContext('Unknown surname', '…');
            $params   = [
                'alpha' => $alpha,
                'ged'   => $tree->name(),
            ];
            $show     = 'indi'; // SURN list makes no sense here
        } elseif ($alpha === ',') {
            $show_all = 'no';
            $legend   = I18N::translate('None');
            $params   = [
                'alpha' => $alpha,
                'ged'   => $tree->name(),
            ];
            $show     = 'indi'; // SURN list makes no sense here
        } elseif ($alpha) {
            $show_all = 'no';
            $legend   = e($alpha) . '…';
            $params   = [
                'alpha' => $alpha,
                'ged'   => $tree->name(),
            ];
            $show     = $request->getQueryParams()['show'] ?? 'surn';
        } else {
            $show_all = 'no';
            $legend   = '…';
            $params   = [
                'ged' => $tree->name(),
            ];
            $show     = 'none'; // Don't show lists until something is chosen
        }
        $legend = '<span dir="auto">' . $legend . '</span>';

        if ($families) {
            $title = I18N::translate('Families') . ' — ' . $legend;
        } else {
            $title = I18N::translate('Individuals') . ' — ' . $legend;
        } ?>
        <div class="d-flex flex-column wt-page-options wt-page-options-individual-list d-print-none">
            <ul class="d-flex flex-wrap list-unstyled justify-content-center wt-initials-list wt-initials-list-surname">

                <?php foreach ($this->individual_list_service->surnameAlpha($show_marnm === 'yes', $families, WT_LOCALE, I18N::collation()) as $letter => $count) : ?>
                    <li class="wt-initials-list-item d-flex">
                        <?php if ($count > 0) : ?>
                            <a href="<?= e(route('module', ['module' => $module, 'action' => $action, 'alpha' => $letter, 'ged' => $tree->name()])) ?>" class="wt-initial px-1<?= $letter === $alpha ? ' active' : '' ?> '" title="<?= I18N::number($count) ?>"><?= $this->surnameInitial((string) $letter) ?></a>
                        <?php else : ?>
                            <span class="wt-initial px-1 text-muted"><?= $this->surnameInitial((string) $letter) ?></span>

                        <?php endif ?>
                    </li>
                <?php endforeach ?>

                <?php if (Session::has('initiated')) : ?>
                    <!-- Search spiders don't get the "show all" option as the other links give them everything. -->
                    <li class="wt-initials-list-item d-flex">
                        <a class="wt-initial px-1<?= $show_all === 'yes' ? ' active' : '' ?>" href="<?= e(route('module', ['module' => $module, 'action' => $action, 'show_all' => 'yes'] + $params)) ?>"><?= I18N::translate('All') ?></a>
                    </li>
                <?php endif ?>
            </ul>

            <!-- Search spiders don't get an option to show/hide the surname sublists, nor does it make sense on the all/unknown/surname views -->
            <?php if ($show !== 'none' && Session::has('initiated')) : ?>
                <?php if ($show_marnm === 'yes') : ?>
                    <p>
                        <a href="<?= e(route('module', ['module' => $module, 'action' => $action, 'show' => $show, 'show_marnm' => 'no'] + $params)) ?>">
                            <?= I18N::translate('Exclude individuals with “%s” as a married name', $legend) ?>
                        </a>
                    </p>
                <?php else : ?>
                    <p>
                        <a href="<?= e(route('module', ['module' => $module, 'action' => $action, 'show' => $show, 'show_marnm' => 'yes'] + $params)) ?>">
                            <?= I18N::translate('Include individuals with “%s” as a married name', $legend) ?>
                        </a>
                    </p>
                <?php endif ?>

                <?php if ($alpha !== '@' && $alpha !== ',' && !$surname) : ?>
                    <?php if ($show === 'surn') : ?>
                        <p>
                            <a href="<?= e(route('module', ['module' => $module, 'action' => $action, 'show' => 'indi', 'show_marnm' => 'no'] + $params)) ?>">
                                <?= I18N::translate('Show the list of individuals') ?>
                            </a>
                        </p>
                    <?php else : ?>
                        <p>
                            <a href="<?= e(route('module', ['module' => $module, 'action' => $action, 'show' => 'surn', 'show_marnm' => 'no'] + $params)) ?>">
                                <?= I18N::translate('Show the list of surnames') ?>
                            </a>
                        </p>
                    <?php endif ?>
                <?php endif ?>
            <?php endif ?>
        </div>

        <div class="wt-page-content">
            <?php

            if ($show === 'indi' || $show === 'surn') {
                $surns = $this->individual_list_service->surnames($surname, $alpha, $show_marnm === 'yes', $families, WT_LOCALE, I18N::collation());
                if ($show === 'surn') {
                    // Show the surname list
                    switch ($tree->getPreference('SURNAME_LIST_STYLE')) {
                        case 'style1':
                            echo FunctionsPrintLists::surnameList($surns, 3, true, $moduleListInterface, $tree);
                            break;
                        case 'style3':
                            echo FunctionsPrintLists::surnameTagCloud($surns, $moduleListInterface, true, $tree);
                            break;
                        case 'style2':
                        default:
                            echo view('lists/surnames-table', [
                                'surnames' => $surns,
                                'families' => $families,
                                'module'   => $moduleListInterface,
                            ]);
                            break;
                    }
                } else {
                    // Show the list
                    $count = 0;
                    foreach ($surns as $surnames) {
                        foreach ($surnames as $total) {
                            $count += $total;
                        }
                    }
                    // Don't sublists short lists.
                    if ($count < $tree->getPreference('SUBLIST_TRIGGER_I')) {
                        $falpha = '';
                    } else {
                        $givn_initials = $this->individual_list_service->givenAlpha($surname, $alpha, $show_marnm === 'yes', $families, WT_LOCALE, I18N::collation());
                        // Break long lists by initial letter of given name
                        if ($surname || $show_all === 'yes') {
                            if ($show_all === 'no') {
                                echo '<h2 class="wt-page-title">', I18N::translate('Individuals with surname %s', $legend), '</h2>';
                            }
                            // Don't show the list until we have some filter criteria
                            $show = ($falpha || $show_all_firstnames === 'yes') ? 'indi' : 'none';
                            $list = [];
                            echo '<ul class="d-flex flex-wrap list-unstyled justify-content-center wt-initials-list wt-initials-list-given-names">';
                            foreach ($givn_initials as $givn_initial => $count) {
                                echo '<li class="wt-initials-list-item d-flex">';
                                if ($count > 0) {
                                    if ($show === 'indi' && $givn_initial === $falpha && $show_all_firstnames === 'no') {
                                        echo '<a class="wt-initial px-1 active" href="' . e(route('module', ['module' => $module, 'action' => $action, 'falpha' => $givn_initial] + $params)) . '" title="' . I18N::number($count) . '">' . $this->givenNameInitial((string) $givn_initial) . '</a>';
                                    } else {
                                        echo '<a class="wt-initial px-1" href="' . e(route('module', ['module' => $module, 'action' => $action, 'falpha' => $givn_initial] + $params)) . '" title="' . I18N::number($count) . '">' . $this->givenNameInitial((string) $givn_initial) . '</a>';
                                    }
                                } else {
                                    echo '<span class="wt-initial px-1 text-muted">' . $this->givenNameInitial((string) $givn_initial) . '</span>';
                                }
                                echo '</li>';
                            }
                            // Search spiders don't get the "show all" option as the other links give them everything.
                            if (Session::has('initiated')) {
                                echo '<li class="wt-initials-list-item d-flex">';
                                if ($show_all_firstnames === 'yes') {
                                    echo '<span class="wt-initial px-1 warning">' . I18N::translate('All') . '</span>';
                                } else {
                                    echo '<a class="wt-initial px-1" href="' . e(route('module', ['module' => $module, 'action' => $action, 'show_all_firstnames' => 'yes'] + $params)) . '" title="' . I18N::number($count) . '">' . I18N::translate('All') . '</a>';
                                }
                                echo '</li>';
                            }
                            echo '</ul>';
                            echo '<p class="text-center alpha_index">', implode(' | ', $list), '</p>';
                        }
                    }
                    if ($show === 'indi') {
                        if (!$families) {
                            echo view('lists/individuals-table', [
                                'individuals' => $this->individual_list_service->individuals($surname, $alpha, $falpha, $show_marnm === 'yes', false, I18N::collation()),
                                'sosa'        => false,
                                'tree'        => $tree,
                            ]);
                        } else {
                            echo view('lists/families-table', [
                                'families' => $this->individual_list_service->families($surname, $alpha, $falpha, $show_marnm === 'yes'),
                                'tree'     => $tree,
                            ]);
                        }
                    }
                }
            } ?>
        </div>
        <?php

        $html = ob_get_clean();

        // @TODO convert this to use views
        return $this->viewResponse('individual-list-page', [
            'content' => $html,
            'title'   => $title,
        ]);
    }
584482
public function addSpouseAction(ServerRequestInterface $request, Tree $tree): ResponseInterface
    {
        $xref = $request->get('xref', '');

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

        Auth::checkFamilyAccess($family, true);

        $this->glevels = $request->get('glevels', []);
        $this->tag     = $request->get('tag', []);
        $this->text    = $request->get('text', []);
        $this->islink  = $request->get('islink', []);

        // Create the new spouse
        $this->splitSource(); // separate SOUR record from the rest

        $gedrec = '0 @@ INDI';
        $gedrec .= $this->addNewName($request, $tree);
        $gedrec .= $this->addNewSex($request);
        if (preg_match_all('/([A-Z0-9_]+)/', $tree->getPreference('QUICK_REQUIRED_FACTS'), $matches)) {
            foreach ($matches[1] as $match) {
                $gedrec .= $this->addNewFact($request, $tree, $match);
            }
        }

        if ((bool) $request->get('SOUR_INDI')) {
            $gedrec = $this->handleUpdates($gedrec);
        } else {
            $gedrec = $this->updateRest($gedrec);
        }
        $gedrec .= "\n1 FAMS @" . $family->xref() . '@';
        $spouse = $tree->createIndividual($gedrec);

        // Update the existing family - add marriage, etc
        if ($family->facts(['HUSB'])->first() instanceof Fact) {
            $family->createFact('1 WIFE @' . $spouse->xref() . '@', true);
        } else {
            $family->createFact('1 HUSB @' . $spouse->xref() . '@', true);
        }
        $famrec = '';
        if (preg_match_all('/([A-Z0-9_]+)/', $tree->getPreference('QUICK_REQUIRED_FAMFACTS'), $matches)) {
            foreach ($matches[1] as $match) {
                $famrec .= $this->addNewFact($request, $tree, $match);
            }
        }
        if ((bool) $request->get('SOUR_FAM')) {
            $famrec = $this->handleUpdates($famrec);
        } else {
            $famrec = $this->updateRest($famrec);
        }
        $family->createFact(trim($famrec), true); // trim leading \n

        if ($request->get('goto') === 'new') {
            return redirect($spouse->url());
        }

        return redirect($family->url());
    }
279588
public function postDeleteMessageAction(ServerRequestInterface $request, Tree $tree): ResponseInterface
    {
        $message_ids = (array) $request->get('message_id', []);

        DB::table('message')
            ->where('user_id', '=', Auth::id())
            ->whereIn('message_id', $message_ids)
            ->delete();

        if ($request->get('ctype') === 'user') {
            $url = route('user-page', ['ged' => $tree->name()]);
        } else {
            $url = route('tree-page', ['ged' => $tree->name()]);
        }

        return redirect($url);
    }
1118443
public function getAncestorsAction(ServerRequestInterface $request): ResponseInterface
    {
        $tree = app(Tree::class);
        $xref = $request->getQueryParams()['xref'] ?? '';

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

        return response(view('modules/hourglass-chart/parents', [
            'family'      => $family,
            'generations' => 1,
        ]));
    }
170041
private function treeBlock(ServerRequestInterface $request, Tree $tree, UserInterface $user): ModuleBlockInterface
    {
        $block_id = (int) $request->get('block_id');

        $block = DB::table('block')
            ->where('block_id', '=', $block_id)
            ->where('gedcom_id', '=', $tree->id())
            ->whereNull('user_id')
            ->first();

        if ($block === null) {
            throw new NotFoundHttpException();
        }

        $module = $this->module_service->findByName($block->module_name);

        if (!$module instanceof ModuleBlockInterface) {
            throw new NotFoundHttpException();
        }

        if ($block->user_id !== $user->id() && !Auth::isAdmin()) {
            throw new AccessDeniedHttpException();
        }

        return $module;
    }
44800
public function getDescendantsAction(ServerRequestInterface $request): ResponseInterface
    {
        $tree = app(Tree::class);
        $xref = $request->getQueryParams()['xref'] ?? '';

        $show_spouse = (bool) ($request->getQueryParams()['show_spouse'] ?? false);
        $individual  = Individual::getInstance($xref, $tree);

        Auth::checkIndividualAccess($individual);

        $children = $individual->spouseFamilies()->map(static function (Family $family): Collection {
            return $family->children();
        })->flatten();

        return response(view('modules/hourglass-chart/children', [
            'children'    => $children,
            'generations' => 1,
            'show_spouse' => $show_spouse,
        ]));
    }
1117610
public function update(ServerRequestInterface $request, UserInterface $user): ResponseInterface
    {
        $user_id        = (int) $request->getParsedBody()['user_id'];
        $username       = $request->getParsedBody()['username'];
        $real_name      = $request->getParsedBody()['real_name'];
        $email          = $request->getParsedBody()['email'];
        $password       = $request->getParsedBody()['password'];
        $theme          = $request->getParsedBody()['theme'];
        $language       = $request->getParsedBody()['language'];
        $timezone       = $request->getParsedBody()['timezone'];
        $contact_method = $request->getParsedBody()['contact_method'];
        $comment        = $request->getParsedBody()['comment'];
        $auto_accept    = (bool) ($request->getParsedBody()['auto_accept'] ?? false);
        $canadmin       = (bool) ($request->getParsedBody()['canadmin'] ?? false);
        $visible_online = (bool) ($request->getParsedBody()['visible_online'] ?? false);
        $verified       = (bool) ($request->getParsedBody()['verified'] ?? false);
        $approved       = (bool) ($request->getParsedBody()['approved'] ?? false);

        $edit_user = $this->user_service->find($user_id);

        if ($edit_user === null) {
            throw new NotFoundHttpException(I18N::translate('%1$s does not exist', 'user_id:' . $user_id));
        }

        // We have just approved a user.  Tell them
        if ($edit_user->getPreference('verified_by_admin') !== '1' && $approved) {
            I18N::init($edit_user->getPreference('language'));

            Mail::send(
                Auth::user(),
                $edit_user,
                Auth::user(),
                /* I18N: %s is a server name/URL */
                I18N::translate('New user at %s', WT_BASE_URL),
                view('emails/approve-user-text', ['user' => $edit_user, 'site_url' => WT_BASE_URL]),
                view('emails/approve-user-html', ['user' => $edit_user, 'site_url' => WT_BASE_URL])
            );
        }

        $edit_user
            ->setRealName($real_name)
            ->setPreference('theme', $theme)
            ->setPreference('language', $language)
            ->setPreference('TIMEZONE', $timezone)
            ->setPreference('contactmethod', $contact_method)
            ->setPreference('comment', $comment)
            ->setPreference('auto_accept', (string) $auto_accept)
            ->setPreference('visibleonline', (string) $visible_online)
            ->setPreference('verified', (string) $verified)
            ->setPreference('verified_by_admin', (string) $approved);

        if ($password !== '') {
            $edit_user->setPassword($password);
        }

        // We cannot change our own admin status. Another admin will need to do it.
        if ($edit_user->id() !== $user->id()) {
            $edit_user->setPreference('canadmin', $canadmin ? '1' : '0');
        }

        foreach (Tree::getAll() as $tree) {
            $path_length = (int) $request->getParsedBody()['RELATIONSHIP_PATH_LENGTH' . $tree->id()];
            $gedcom_id   = $request->getParsedBody()['gedcomid' . $tree->id()];
            $can_edit    = $request->getParsedBody()['canedit' . $tree->id()];

            // Do not allow a path length to be set if the individual ID is not
            if ($gedcom_id === '') {
                $path_length = 0;
            }

            $tree->setUserPreference($edit_user, 'gedcomid', $gedcom_id);
            $tree->setUserPreference($edit_user, 'canedit', $can_edit);
            $tree->setUserPreference($edit_user, 'RELATIONSHIP_PATH_LENGTH', (string) $path_length);
        }

        if ($edit_user->email() !== $email && $this->user_service->findByEmail($email) instanceof User) {
            FlashMessages::addMessage(I18N::translate('Duplicate email address. A user with that email already exists.') . $email, 'danger');

            return redirect(route('admin-users-edit', ['user_id' => $edit_user->id()]));
        }

        if ($edit_user->userName() !== $username && $this->user_service->findByUserName($username) instanceof User) {
            FlashMessages::addMessage(I18N::translate('Duplicate username. A user with that username already exists. Please choose another username.'), 'danger');

            return redirect(route('admin-users-edit', ['user_id' => $edit_user->id()]));
        }

        $edit_user
            ->setEmail($email)
            ->setUserName($username);

        return redirect(route('admin-users'));
    }
603902
public function getPageAction(ServerRequestInterface $request, Tree $tree, UserInterface $user): ResponseInterface
    {
        Auth::checkComponentAccess($this, 'list', $tree, $user);
      
        $listController = new BranchesController(app(ModuleService::class));
        return $listController->page($request);
    }
541559
public function addChildAction(ServerRequestInterface $request, Tree $tree): ResponseInterface
    {
        $xref = $request->get('xref', '');

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

        Auth::checkIndividualAccess($individual, true);

        $PEDI = $request->get('PEDI', '');

        $this->glevels = $request->get('glevels', []);
        $this->tag     = $request->get('tag', []);
        $this->text    = $request->get('text', []);
        $this->islink  = $request->get('islink', []);

        // Create a family
        if ($individual->sex() === 'F') {
            $gedcom = "0 @@ FAM\n1 WIFE @" . $individual->xref() . '@';
        } else {
            $gedcom = "0 @@ FAM\n1 HUSB @" . $individual->xref() . '@';
        }
        $family = $tree->createFamily($gedcom);

        // Link the parent to the family
        $individual->createFact('1 FAMS @' . $family->xref() . '@', true);

        // Create a child
        $this->splitSource(); // separate SOUR record from the rest

        $gedcom = '0 @@ INDI';
        $gedcom .= $this->addNewName($request, $tree);
        $gedcom .= $this->addNewSex($request);
        $gedcom .= "\n" . GedcomCodePedi::createNewFamcPedi($PEDI, $family->xref());
        if (preg_match_all('/([A-Z0-9_]+)/', $tree->getPreference('QUICK_REQUIRED_FACTS'), $matches)) {
            foreach ($matches[1] as $match) {
                $gedcom .= $this->addNewFact($request, $tree, $match);
            }
        }
        if ((bool) $request->get('SOUR_INDI')) {
            $gedcom = $this->handleUpdates($gedcom);
        } else {
            $gedcom = $this->updateRest($gedcom);
        }

        $child = $tree->createIndividual($gedcom);

        // Link the family to the child
        $family->createFact('1 CHIL @' . $child->xref() . '@', true);

        if ($request->get('goto') === 'new') {
            return redirect($child->url());
        }

        return redirect($individual->url());
    }
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,
        ]);
    }
882475
public function getChartAction(ServerRequestInterface $request, Tree $tree, UserInterface $user): ResponseInterface
    {
        $ajax = (bool) $request->get('ajax');

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

        $individual1 = Individual::getInstance($xref, $tree);
        $individual2 = Individual::getInstance($xref2, $tree);

        $recursion = (int) $request->get('recursion', '0');
        $ancestors = (int) $request->get('ancestors', '0');

        $ancestors_only = (int) $tree->getPreference('RELATIONSHIP_ANCESTORS', static::DEFAULT_ANCESTORS);
        $max_recursion  = (int) $tree->getPreference('RELATIONSHIP_RECURSION', static::DEFAULT_RECURSION);

        $recursion = min($recursion, $max_recursion);

        if ($individual1 instanceof Individual) {
            Auth::checkIndividualAccess($individual1);
        }

        if ($individual2 instanceof Individual) {
            Auth::checkIndividualAccess($individual2);
        }

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

        if ($individual1 instanceof Individual && $individual2 instanceof Individual) {
            if ($ajax) {
                return $this->chart($individual1, $individual2, $recursion, $ancestors);
            }

            /* I18N: %s are individual’s names */
            $title = I18N::translate('Relationships between %1$s and %2$s', $individual1->fullName(), $individual2->fullName());

            $ajax_url = $this->chartUrl($individual1, [
                'ajax'      => true,
                'xref2'     => $individual2->xref(),
                'recursion' => $recursion,
                'ancestors' => $ancestors,
            ]);
        } else {
            $title = I18N::translate('Relationships');

            $ajax_url = '';
        }

        return $this->viewResponse('modules/relationships-chart/page', [
            'ajax_url'          => $ajax_url,
            'ancestors'         => $ancestors,
            'ancestors_only'    => $ancestors_only,
            'ancestors_options' => $this->ancestorsOptions(),
            'individual1'       => $individual1,
            'individual2'       => $individual2,
            'max_recursion'     => $max_recursion,
            'module_name'       => $this->name(),
            'recursion'         => $recursion,
            'recursion_options' => $this->recursionOptions($max_recursion),
            'title'             => $title,
        ]);
    }
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,
        ]);
    }
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,
        ]);
    }
676429
public function treePrivacyUpdate(ServerRequestInterface $request, Tree $tree): ResponseInterface
    {
        $delete_default_resn_id = (array) $request->get('delete');

        DB::table('default_resn')
            ->whereIn('default_resn_id', $delete_default_resn_id)
            ->delete();

        $xrefs     = (array) $request->get('xref');
        $tag_types = (array) $request->get('tag_type');
        $resns     = (array) $request->get('resn');

        foreach ($xrefs as $n => $xref) {
            $tag_type = $tag_types[$n];
            $resn     = $resns[$n];

            // Delete any existing data
            if ($tag_type !== '' && $xref !== '') {
                DB::table('default_resn')
                    ->where('gedcom_id', '=', $tree->id())
                    ->where('tag_type', '=', $tag_type)
                    ->where('xref', '=', $xref)
                    ->delete();
            }

            if ($tag_type !== '' && $xref === '') {
                DB::table('default_resn')
                    ->where('gedcom_id', '=', $tree->id())
                    ->where('tag_type', '=', $tag_type)
                    ->whereNull('xref')
                    ->delete();
            }

            if ($tag_type === '' && $xref !== '') {
                DB::table('default_resn')
                    ->where('gedcom_id', '=', $tree->id())
                    ->whereNull('tag_type')
                    ->where('xref', '=', $xref)
                    ->delete();
            }

            // Add (or update) the new data
            if ($tag_type !== '' || $xref !== '') {
                DB::table('default_resn')->insert([
                    'gedcom_id' => $tree->id(),
                    'xref'      => $xref === '' ? null : $xref,
                    'tag_type'  => $tag_type === '' ? null : $tag_type,
                    'resn'      => $resn,
                ]);
            }
        }

        $tree->setPreference('HIDE_LIVE_PEOPLE', $request->get('HIDE_LIVE_PEOPLE'));
        $tree->setPreference('KEEP_ALIVE_YEARS_BIRTH', $request->get('KEEP_ALIVE_YEARS_BIRTH', '0'));
        $tree->setPreference('KEEP_ALIVE_YEARS_DEATH', $request->get('KEEP_ALIVE_YEARS_DEATH', '0'));
        $tree->setPreference('MAX_ALIVE_AGE', $request->get('MAX_ALIVE_AGE', '100'));
        $tree->setPreference('REQUIRE_AUTHENTICATION', $request->get('REQUIRE_AUTHENTICATION'));
        $tree->setPreference('SHOW_DEAD_PEOPLE', $request->get('SHOW_DEAD_PEOPLE'));
        $tree->setPreference('SHOW_LIVING_NAMES', $request->get('SHOW_LIVING_NAMES'));
        $tree->setPreference('SHOW_PRIVATE_RELATIONSHIPS', $request->get('SHOW_PRIVATE_RELATIONSHIPS'));

        FlashMessages::addMessage(I18N::translate('The preferences for the family tree “%s” have been updated.', e($tree->title()), 'success'));

        // Coming soon...
        if ((bool) $request->get('all_trees')) {
            FlashMessages::addMessage(I18N::translate('The preferences for all family trees have been updated.', e($tree->title())), 'success');
        }
        if ((bool) $request->get('new_trees')) {
            FlashMessages::addMessage(I18N::translate('The preferences for new family trees have been updated.', e($tree->title())), 'success');
        }

        return redirect(route('admin-trees', ['ged' => $tree->name()]));
    }
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,
            ],
        ]);
    }
883295
public function tab(ServerRequestInterface $request, Tree $tree, UserInterface $user): ResponseInterface
    {
        $xref        = $request->get('xref', '');
        $record      = Individual::getInstance($xref, $tree);
        $module_name = $request->get('module');
        $module      = $this->module_service->findByName($module_name);

        Auth::checkIndividualAccess($record);
        Auth::checkComponentAccess($module, 'tab', $tree, $user);

        $layout = view('layouts/ajax', [
            'content' => $module->getTabContent($record),
        ]);

        return response($layout);
    }
130503
public function changesLog(ServerRequestInterface $request, UserService $user_service): ResponseInterface
    {
        $tree_list = [];
        foreach (Tree::getAll() as $tree) {
            if (Auth::isManager($tree)) {
                $tree_list[$tree->name()] = $tree->title();
            }
        }

        $user_list = ['' => ''];
        foreach ($user_service->all() as $tmp_user) {
            $user_list[$tmp_user->userName()] = $tmp_user->userName();
        }

        $action = $request->getQueryParams()['action'] ?? '';

        // @TODO This ought to be a POST action
        if ($action === 'delete') {
            $this->changesQuery($request)->delete();
        }

        // First and last change in the database.
        $earliest = DB::table('change')->min('change_time');
        $latest   = DB::table('change')->max('change_time');

        $earliest = $earliest ? Carbon::make($earliest) : Carbon::now();
        $latest   = $latest ? Carbon::make($latest) : Carbon::now();

        $earliest = $earliest->toDateString();
        $latest   = $latest->toDateString();

        $ged      = $request->getQueryParams()['ged'] ?? '';
        $from     = $request->getQueryParams()['from'] ?? $earliest;
        $to       = $request->getQueryParams()['to'] ?? $latest;
        $type     = $request->getQueryParams()['type'] ?? '';
        $oldged   = $request->getQueryParams()['oldged'] ?? '';
        $newged   = $request->getQueryParams()['newged'] ?? '';
        $xref     = $request->getQueryParams()['xref'] ?? '';
        $username = $request->getQueryParams()['username'] ?? '';
        $search   = $request->getQueryParams()['search'] ?? [];
        $search   = $search['value'] ?? null;

        if (!array_key_exists($ged, $tree_list)) {
            $ged = reset($tree_list);
        }

        $statuses = [
            ''         => '',
            /* I18N: the status of an edit accepted/rejected/pending */
            'accepted' => I18N::translate('accepted'),
            /* I18N: the status of an edit accepted/rejected/pending */
            'rejected' => I18N::translate('rejected'),
            /* I18N: the status of an edit accepted/rejected/pending */
            'pending'  => I18N::translate('pending'),
        ];

        return $this->viewResponse('admin/changes-log', [
            'action'    => $action,
            'earliest'  => $earliest,
            'from'      => $from,
            'ged'       => $ged,
            'latest'    => $latest,
            'newged'    => $newged,
            'oldged'    => $oldged,
            'search'    => $search,
            'statuses'  => $statuses,
            'title'     => I18N::translate('Changes log'),
            'to'        => $to,
            'tree_list' => $tree_list,
            'type'      => $type,
            'username'  => $username,
            'user_list' => $user_list,
            'xref'      => $xref,
        ]);
    }