Show a list of all individual or family records.

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

@return ResponseInterface
516071
public function individualList(ServerRequestInterface $request, Tree $tree, UserInterface $user, ?ModuleListInterface $moduleListInterface): ResponseInterface
    {
        return $this->individualOrFamilyList($request, $tree, $user, false, $moduleListInterface);
    }
20108
public function general(ServerRequestInterface $request, Tree $tree): ResponseInterface
    {
        $query = $request->get('query', '');

        // What type of records to search?
        $search_individuals  = (bool) $request->get('search_individuals');
        $search_families     = (bool) $request->get('search_families');
        $search_repositories = (bool) $request->get('search_repositories');
        $search_sources      = (bool) $request->get('search_sources');
        $search_notes        = (bool) $request->get('search_notes');

        // Default to individuals only
        if (!$search_individuals && !$search_families && !$search_repositories && !$search_sources && !$search_notes) {
            $search_individuals = true;
        }

        // What to search for?
        $search_terms = $this->extractSearchTerms($query);

        // What trees to seach?
        if (Site::getPreference('ALLOW_CHANGE_GEDCOM') === '1') {
            $all_trees = Tree::getAll();
        } else {
            $all_trees = [$tree];
        }

        $search_tree_names = (array) $request->get('search_trees', []);

        $search_trees = array_filter($all_trees, static function (Tree $tree) use ($search_tree_names): bool {
            return in_array($tree->name(), $search_tree_names, true);
        });

        if (empty($search_trees)) {
            $search_trees = [$tree];
        }

        // Do the search
        if ($search_individuals && !empty($search_terms)) {
            $individuals = $this->search_service->searchIndividuals($search_trees, $search_terms);
        } else {
            $individuals = new Collection();
        }

        if ($search_families && !empty($search_terms)) {
            $tmp1 = $this->search_service->searchFamilies($search_trees, $search_terms);
            $tmp2 = $this->search_service->searchFamilyNames($search_trees, $search_terms);

            $families = $tmp1->merge($tmp2)->unique();
        } else {
            $families = new Collection();
        }

        if ($search_repositories && !empty($search_terms)) {
            $repositories = $this->search_service->searchRepositories($search_trees, $search_terms);
        } else {
            $repositories = new Collection();
        }

        if ($search_sources && !empty($search_terms)) {
            $sources = $this->search_service->searchSources($search_trees, $search_terms);
        } else {
            $sources = new Collection();
        }

        if ($search_notes && !empty($search_terms)) {
            $notes = $this->search_service->searchNotes($search_trees, $search_terms);
        } else {
            $notes = new Collection();
        }

        // If only 1 item is returned, automatically forward to that item
        if ($individuals->count() === 1 && $families->isEmpty() && $sources->isEmpty() && $notes->isEmpty()) {
            return redirect($individuals->first()->url());
        }

        if ($individuals->isEmpty() && $families->count() === 1 && $sources->isEmpty() && $notes->isEmpty()) {
            return redirect($families->first()->url());
        }

        if ($individuals->isEmpty() && $families->isEmpty() && $sources->count() === 1 && $notes->isEmpty()) {
            return redirect($sources->first()->url());
        }

        if ($individuals->isEmpty() && $families->isEmpty() && $sources->isEmpty() && $notes->count() === 1) {
            return redirect($notes->first()->url());
        }

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

        return $this->viewResponse('search-general-page', [
            'all_trees'           => $all_trees,
            'families'            => $families,
            'individuals'         => $individuals,
            'notes'               => $notes,
            'query'               => $query,
            'repositories'        => $repositories,
            'search_families'     => $search_families,
            'search_individuals'  => $search_individuals,
            'search_notes'        => $search_notes,
            'search_repositories' => $search_repositories,
            'search_sources'      => $search_sources,
            'search_trees'        => $search_trees,
            'sources'             => $sources,
            'title'               => $title,
        ]);
    }
1166865
public function getChartAction(ServerRequestInterface $request, Tree $tree, UserInterface $user): ResponseInterface
    {
        Auth::checkComponentAccess($this, 'chart', $tree, $user);

        $ajax      = (bool) $request->get('ajax');
        $xrefs     = (array) $request->get('xrefs', []);
        $addxref   = $request->get('addxref', '');
        $addfam    = (bool) $request->get('addfam', false);
        $placename = $request->get('placename', '');
        $start     = $request->get('start', '');
        $end       = $request->get('end', '');

        $place      = new Place($placename, $tree);
        $start_date = new Date($start);
        $end_date   = new Date($end);

        $xrefs = array_unique($xrefs);

        // Add an individual, and family members
        $individual = Individual::getInstance($addxref, $tree);
        if ($individual !== null) {
            $xrefs[] = $addxref;
            if ($addfam) {
                $xrefs = array_merge($xrefs, $this->closeFamily($individual));
            }
        }

        // Select by date and/or place.
        if ($start_date->isOK() && $end_date->isOK() && $placename !== '') {
            $date_xrefs  = $this->findIndividualsByDate($start_date, $end_date, $tree);
            $place_xrefs = $this->findIndividualsByPlace($place, $tree);
            $xrefs       = array_intersect($date_xrefs, $place_xrefs);
        } elseif ($start_date->isOK() && $end_date->isOK()) {
            $xrefs = $this->findIndividualsByDate($start_date, $end_date, $tree);
        } elseif ($placename !== '') {
            $xrefs = $this->findIndividualsByPlace($place, $tree);
        }

        // Filter duplicates and private individuals.
        $xrefs = array_unique($xrefs);
        $xrefs = array_filter($xrefs, static function (string $xref) use ($tree): bool {
            $individual = Individual::getInstance($xref, $tree);

            return $individual !== null && $individual->canShow();
        });

        if ($ajax) {
            $subtitle = $this->subtitle(count($xrefs), $start_date, $end_date, $placename);

            return $this->chart($tree, $xrefs, $subtitle);
        }

        $ajax_url = route('module', [
            'ajax'   => true,
            'module' => $this->name(),
            'action' => 'Chart',
            'ged'    => $tree->name(),
            'xrefs'  => $xrefs,
        ]);

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

        return $this->viewResponse('modules/lifespans-chart/page', [
            'ajax_url'    => $ajax_url,
            'module_name' => $this->name(),
            'reset_url'   => $reset_url,
            'title'       => $this->title(),
            'xrefs'       => $xrefs,
        ]);
    }
341206
public function index(ServerRequestInterface $request, UserInterface $user): ResponseInterface
    {
        $filter = $request->getQueryParams()['filter'] ?? '';

        $all_users = $this->user_service->all();

        $page_size = (int) $user->getPreference(' admin_users_page_size', '10');

        $title = I18N::translate('User administration');

        return $this->viewResponse('admin/users', [
            'all_users' => $all_users,
            'filter'    => $filter,
            'page_size' => $page_size,
            'title'     => $title,
        ]);
    }
583611
public function edit(Tree $tree, UserInterface $user): ResponseInterface
    {
        $my_individual_record = Individual::getInstance($tree->getUserPreference(Auth::user(), 'gedcomid'), $tree);
        $contact_methods      = FunctionsEdit::optionsContactMethods();
        $default_individual   = Individual::getInstance($tree->getUserPreference(Auth::user(), 'rootid'), $tree);
        $installed_languages  = FunctionsEdit::optionsInstalledLanguages();
        $show_delete_option   = !$user->getPreference('canadmin');
        $themes               = $this->themeOptions();
        $timezone_ids         = DateTimeZone::listIdentifiers();
        $timezones            = array_combine($timezone_ids, $timezone_ids);
        $title                = I18N::translate('My account');

        return $this->viewResponse('edit-account-page', [
            'contact_methods'      => $contact_methods,
            'default_individual'   => $default_individual,
            'installed_languages'  => $installed_languages,
            'my_individual_record' => $my_individual_record,
            'show_delete_option'   => $show_delete_option,
            'themes'               => $themes,
            'timezones'            => $timezones,
            'title'                => $title,
            'user'                 => $user,
        ]);
    }
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();
    }
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,
        ]);
    }
673121
public function verify(ServerRequestInterface $request, Tree $tree, UserService $user_service): ResponseInterface
    {
        $username = $request->get('username', '');
        $token    = $request->get('token', '');

        $title = I18N::translate('User verification');

        $user = $user_service->findByUserName($username);

        if ($user instanceof User && $user->getPreference('reg_hashcode') === $token) {
            // switch language to webmaster settings
            $webmaster = $user_service->find((int) $tree->getPreference('WEBMASTER_USER_ID'));

            if ($webmaster instanceof User) {
                I18N::init($webmaster->getPreference('language'));

                /* I18N: %s is a server name/URL */
                $subject = I18N::translate('New user at %s', WT_BASE_URL . ' ' . $tree->title());

                Mail::send(
                    new TreeUser($tree),
                    $webmaster,
                    new TreeUser($tree),
                    $subject,
                    view('emails/verify-notify-text', ['user' => $user]),
                    view('emails/verify-notify-html', ['user' => $user])
                );

                $mail1_method = $webmaster->getPreference('CONTACT_METHOD');

                if ($mail1_method !== 'messaging3' && $mail1_method !== 'mailto' && $mail1_method !== 'none') {
                    DB::table('message')->insert([
                        'sender'     => $username,
                        'ip_address' => $request->getClientIp(),
                        'user_id'    => $webmaster->id(),
                        'subject'    => $subject,
                        'body'       => view('emails/verify-notify-text', ['user' => $user]),
                    ]);
                }
                I18N::init(WT_LOCALE);
            }

            $user
                ->setPreference('verified', '1')
                ->setPreference('reg_timestamp', date('U'))
                ->setPreference('reg_hashcode', '');

            Log::addAuthenticationLog('User ' . $username . ' verified their email address');

            return $this->viewResponse('verify-success-page', [
                'title' => $title,
            ]);
        }

        return $this->viewResponse('verify-failure-page', [
            'title' => $title,
        ]);
    }
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;
    }
1181721
public function getChartAction(Tree $tree, UserInterface $user): ResponseInterface
    {
        Auth::checkComponentAccess($this, 'chart', $tree, $user);

        $tabs = [
            I18N::translate('Individuals') => route('module', [
                'module'  => $this->name(),
                'action'  => 'Individuals',
                'ged'     => $tree->name()
            ]),
            I18N::translate('Families') => route('module', [
                'module'  => $this->name(),
                'action'  => 'Families',
                'ged'     => $tree->name()
            ]),
            I18N::translate('Other') => route('module', [
                'module'  => $this->name(),
                'action'  => 'Other',
                'ged'     => $tree->name()
            ]),
            I18N::translate('Custom') => route('module', [
                'module'  => $this->name(),
                'action'  => 'Custom',
                'ged'     => $tree->name()
            ]),
        ];

        return $this->viewResponse('modules/statistics-chart/page', [
            'tabs'  => $tabs,
            'title' => $this->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,
        ]);
    }
153080
public function userPageUpdate(ServerRequestInterface $request, Tree $tree, UserInterface $user): ResponseInterface
    {
        $defaults = (bool) $request->get('defaults');

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

        $this->updateUserBlocks($user->id(), $main_blocks, $side_blocks);

        return redirect(route('user-page', ['ged' => $tree->name()]));
    }
281128
public function forgotPasswordAction(ServerRequestInterface $request, Tree $tree, UserService $user_service): ResponseInterface
    {
        $identifier = $request->get('identifier', '');

        $user = $user_service->findByIdentifier($identifier);

        if ($user instanceof User) {
            $password = $this->createNewPassword();
            $user->setPassword($password);

            Log::addAuthenticationLog('Password request was sent to user: ' . $user->userName());

            Mail::send(
                new TreeUser($tree),
                $user,
                new TreeUser($tree),
                I18N::translate('Lost password request'),
                view('emails/password-reset-text', [
                    'user'         => $user,
                    'new_password' => $password,
                ]),
                view('emails/password-reset-html', [
                    'user'         => $user,
                    'new_password' => $password,
                ])
            );

            FlashMessages::addMessage(I18N::translate('A new password has been created and emailed to %s. You can change this password after you sign in.', e($identifier)), 'success');

            return redirect(route('login', ['username' => $user->userName()]));
        }

        FlashMessages::addMessage(I18N::translate('There is no account with the username or email “%s”.', e($identifier)), 'danger');

        return redirect(route('forgot-password'));
    }
691124
public function getChartAction(ServerRequestInterface $request, Tree $tree, UserInterface $user): ResponseInterface
    {
        $xref = $request->get('xref', '');

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

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

        $tv = new TreeView('tv');

        [$html, $js] = $tv->drawViewport($individual, 4);

        return $this->viewResponse('interactive-tree-page', [
            'html'       => $html,
            'individual' => $individual,
            'js'         => $js,
            'title'      => $this->chartTitle($individual),
            'tree'       => $tree,
        ]);
    }
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),
        ]);
    }
797675
public function getDetailsAction(ServerRequestInterface $request, Tree $tree): ResponseInterface
    {
        $pid        = $request->get('pid', Gedcom::REGEX_XREF);
        $individual = Individual::getInstance($pid, $tree);

        if ($individual === null) {
            throw new IndividualNotFoundException();
        }

        if (!$individual->canShow()) {
            throw new IndividualAccessDeniedException();
        }

        $instance = $request->get('instance', '');
        $treeview = new TreeView($instance);

        return response($treeview->getDetails($individual));
    }
151233
public function getDownloadFormAction(Tree $tree, UserInterface $user): ResponseInterface
    {
        $title = I18N::translate('Family tree clippings cart') . ' — ' . I18N::translate('Download');

        return $this->viewResponse('modules/clippings/download', [
            'is_manager' => Auth::isManager($tree, $user),
            'is_member'  => Auth::isMember($tree, $user),
            'title'      => $title,
        ]);
    }