Load a block and check we have permission to edit it.

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

@return ModuleBlockInterface
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;
    }
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'));
    }
646548
public function treePageEdit(Tree $tree): ResponseInterface
    {
        $main_blocks = $this->treeBlocks($tree->id(), 'main');
        $side_blocks = $this->treeBlocks($tree->id(), 'side');

        $all_blocks = $this->availableTreeBlocks();
        $title      = I18N::translate('Change the “Home page” blocks');
        $url_cancel = route('tree-page', ['ged' => $tree->name()]);
        $url_save   = route('tree-page-update', ['ged' => $tree->name()]);

        return $this->viewResponse('edit-blocks-page', [
            'all_blocks'  => $all_blocks,
            'can_reset'   => true,
            'main_blocks' => $main_blocks,
            'side_blocks' => $side_blocks,
            'title'       => $title,
            'url_cancel'  => $url_cancel,
            'url_save'    => $url_save,
        ]);
    }
1275685
public function getDescendantsAction(ServerRequestInterface $request, Tree $tree): ResponseInterface
    {
        $xref = $request->get('xref', '');

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

        if ($individual !== null && $individual->canShow()) {
            $html = $this->loadSpouses($individual, 1);
        } else {
            $html = '';
        }

        return response($html);
    }
1338005
public function familyList(ServerRequestInterface $request, Tree $tree, UserInterface $user, ?ModuleListInterface $moduleListInterface): ResponseInterface
    {
        return $this->individualOrFamilyList($request, $tree, $user, true, $moduleListInterface);
    }
911549
public function editFact(ServerRequestInterface $request, Tree $tree): ResponseInterface
    {
        $xref    = $request->get('xref', '');
        $fact_id = $request->get('fact_id', '');

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

        // Find the fact to edit
        $edit_fact = null;
        foreach ($record->facts() as $fact) {
            if ($fact->id() === $fact_id && $fact->canEdit()) {
                $edit_fact = $fact;
                break;
            }
        }
        if ($edit_fact === null) {
            throw new NotFoundHttpException();
        }

        $can_edit_raw = Auth::isAdmin() || $tree->getPreference('SHOW_GEDCOM_RECORD');

        $title = $record->fullName() . ' - ' . GedcomTag::getLabel($edit_fact->getTag());

        return $this->viewResponse('edit/edit-fact', [
            'can_edit_raw' => $can_edit_raw,
            'edit_fact'    => $edit_fact,
            'record'       => $record,
            'title'        => $title,
            'tree'         => $tree,
        ]);
    }
331349
public function postAdminAction(ServerRequestInterface $request, UserInterface $user, Tree $tree = null): ResponseInterface
    {
        // We need a tree to work with.
        if ($tree === null) {
            throw new NotFoundHttpException();
        }

        // Admins only.
        if (!Auth::isAdmin($user)) {
            throw new AccessDeniedHttpException();
        }

        $plugin = $request->get('plugin', '');
        $update = $request->get('update', '');
        $xref   = $request->getParsedBody()['xref'] ?? '';

        $plugins = $this->getPluginList();
        $plugin  = $plugins[$plugin] ?? null;

        if ($plugin === null) {
            throw new NotFoundHttpException();
        }
        $plugin->getOptions($request);

        $all_data = $this->allData($plugin, $tree);

        $parameters = $request->getParsedBody();
        unset($parameters['csrf']);
        unset($parameters['update']);

        switch ($update) {
            case 'one':
                $record = $this->getRecord($all_data[$xref], $tree);
                if ($plugin->doesRecordNeedUpdate($record)) {
                    $new_gedcom = $plugin->updateRecord($record);
                    $record->updateRecord($new_gedcom, false);
                }

                $parameters['xref'] = $this->findNextXref($plugin, $xref, $all_data, $tree);
                break;

            case 'all':
                foreach ($all_data as $xref => $value) {
                    $record = $this->getRecord($value, $tree);
                    if ($plugin->doesRecordNeedUpdate($record)) {
                        $new_gedcom = $plugin->updateRecord($record);
                        $record->updateRecord($new_gedcom, false);
                    }
                }
                $parameters['xref'] = '';
                break;
        }

        $url = route('module', $parameters);

        return redirect($url);
    }
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'));
    }
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,
        ]);
    }
491786
public function editName(ServerRequestInterface $request, Tree $tree): ResponseInterface
    {
        $fact_id = $request->get('fact_id', '');
        $xref    = $request->get('xref', '');

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

        Auth::checkIndividualAccess($individual, true);

        // Find the fact to edit
        foreach ($individual->facts() as $fact) {
            if ($fact->id() === $fact_id && $fact->canEdit()) {
                return $this->viewResponse('edit/new-individual', [
                    'tree'       => $tree,
                    'title'      => I18N::translate('Edit the name'),
                    'nextaction' => 'update',
                    'individual' => $individual,
                    'family'     => null,
                    'name_fact'  => $fact,
                    'famtag'     => '',
                    'gender'     => $individual->sex(),
                ]);
            }
        }

        throw new NotFoundHttpException();
    }
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);
    }
364020
private function getBlockModule(Tree $tree, int $block_id): ModuleBlockInterface
    {
        $active_blocks = $this->module_service->findByComponent(ModuleBlockInterface::class, $tree, Auth::user());

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

        $block = $active_blocks->first(static function (ModuleInterface $module) use ($module_name): bool {
            return $module->name() === $module_name;
        });

        if ($block === null) {
            throw new NotFoundHttpException('Block not found');
        }

        return $block;
    }
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,
        ]);
    }
401936
public function postAdminEditAction(ServerRequestInterface $request, Tree $tree): ResponseInterface
    {
        $block_id    = (int) $request->get('block_id');
        $faqbody     = $request->get('faqbody', '');
        $header      = $request->get('header', '');
        $languages   = $request->get('languages', []);
        $gedcom_id   = (int) $request->get('gedcom_id') ?: null;
        $block_order = (int) $request->get('block_order');

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

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

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

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

        return redirect($url);
    }
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,
        ]);
    }
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);
    }
533231
public function editRawFactAction(ServerRequestInterface $request, Tree $tree): ResponseInterface
    {
        $xref    = $request->get('xref', '');
        $fact_id = $request->get('fact_id');
        $gedcom  = $request->get('gedcom');

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

        // Cleanup the client’s bad editing?
        $gedcom = preg_replace('/[\r\n]+/', "\n", $gedcom); // Empty lines
        $gedcom = trim($gedcom); // Leading/trailing spaces

        Auth::checkRecordAccess($record, true);

        foreach ($record->facts() as $fact) {
            if (!$fact->isPendingDeletion() && $fact->id() === $fact_id && $fact->canEdit()) {
                $record->updateFact($fact_id, $gedcom, false);
                break;
            }
        }

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