[CORE][Actor][Settings] Fix nickname change and refactor Core Form::handle so it's harder to repeat these mistakes again

Minor improvements on actor->getLocal
This commit is contained in:
Diogo Peralta Cordeiro 2022-02-15 21:16:58 +00:00
parent 397b54a207
commit f66e178dfc
Signed by: diogo
GPG Key ID: 18D2D35001FBFAB0
7 changed files with 72 additions and 22 deletions

View File

@ -99,7 +99,7 @@ class Group extends Controller
return [ return [
'_template' => 'group/settings.html.twig', '_template' => 'group/settings.html.twig',
'group' => $group_actor, 'group' => $group_actor,
'personal_info_form' => ActorForms::personalInfo($request, $actor, $local_group)->createView(), 'personal_info_form' => ActorForms::personalInfo(request: $request, scope: $actor, target: $group_actor)->createView(),
'open_details_query' => $this->string('open'), 'open_details_query' => $this->string('open'),
]; ];
} else { } else {

View File

@ -117,7 +117,7 @@ class LocalGroup extends Entity
public function getActor() public function getActor()
{ {
return DB::findOneBy('actor', ['id' => $this->actor_id]); return DB::findOneBy(Actor::class, ['id' => $this->actor_id]);
} }
public static function getByNickname(string $nickname): ?self public static function getByNickname(string $nickname): ?self

View File

@ -94,7 +94,7 @@ class PersonSettings extends Controller
$user = Common::ensureLoggedIn(); $user = Common::ensureLoggedIn();
$actor = $user->getActor(); $actor = $user->getActor();
$personal_form = ActorForms::personalInfo($request, $actor, $user); $personal_form = ActorForms::personalInfo(request: $request, scope: $actor, target: $actor);
$email_form = self::email($request); $email_form = self::email($request);
$password_form = self::password($request); $password_form = self::password($request);
$notifications_form_array = self::notifications($request); $notifications_form_array = self::notifications($request);

View File

@ -136,12 +136,21 @@ abstract class Form
} }
/** /**
* Handle the full life cycle of a form. Creates it with @see * Handle the full life cycle of a form. Creates it with @param array $form_definition
* @param Request $request
* @param object|null $target
* @param array $extra_args If specified, is used as $target->set($data[$key], $extra_args[$key])
* @param callable|null $after_step
* @param array $create_args
* @param SymfForm|null $testing_only_form
* @return mixed
* @throws RedirectException
* @throws ServerException
* @see
* self::create and inserts the submitted values into the database * self::create and inserts the submitted values into the database
* *
* @throws ServerException
*/ */
public static function handle(array $form_definition, Request $request, ?object $target, array $extra_args = [], ?callable $extra_step = null, array $create_args = [], ?SymfForm $testing_only_form = null): mixed public static function handle(array $form_definition, Request $request, ?object $target, array $extra_args = [], ?callable $before_step = null, ?callable $after_step = null, array $create_args = [], ?SymfForm $testing_only_form = null): mixed
{ {
$form = $testing_only_form ?? self::create($form_definition, $target, ...$create_args); $form = $testing_only_form ?? self::create($form_definition, $target, ...$create_args);
@ -162,6 +171,13 @@ abstract class Form
} }
unset($data['translation_domain'], $data['save']); unset($data['translation_domain'], $data['save']);
if (isset($before_step)) {
// @codeCoverageIgnoreStart
$before_step($data, $extra_args);
// @codeCoverageIgnoreEnd
}
foreach ($data as $key => $val) { foreach ($data as $key => $val) {
$method = 'set' . ucfirst(Formatting::snakeCaseToCamelCase($key)); $method = 'set' . ucfirst(Formatting::snakeCaseToCamelCase($key));
if (method_exists($target, $method)) { if (method_exists($target, $method)) {
@ -175,9 +191,9 @@ abstract class Form
} }
} }
if (isset($extra_step)) { if (isset($after_step)) {
// @codeCoverageIgnoreStart // @codeCoverageIgnoreStart
$extra_step($data, $extra_args); $after_step($data, $extra_args);
// @codeCoverageIgnoreEnd // @codeCoverageIgnoreEnd
} }

View File

@ -32,10 +32,12 @@ use App\Core\Router\Router;
use App\Util\Exception\BugFoundException; use App\Util\Exception\BugFoundException;
use App\Util\Exception\NicknameException; use App\Util\Exception\NicknameException;
use App\Util\Exception\NotFoundException; use App\Util\Exception\NotFoundException;
use App\Util\Exception\NotImplementedException;
use App\Util\Formatting; use App\Util\Formatting;
use App\Util\Nickname; use App\Util\Nickname;
use Component\Avatar\Avatar; use Component\Avatar\Avatar;
use Component\Group\Entity\GroupMember; use Component\Group\Entity\GroupMember;
use Component\Group\Entity\LocalGroup;
use Component\Language\Entity\ActorLanguage; use Component\Language\Entity\ActorLanguage;
use Component\Language\Entity\Language; use Component\Language\Entity\Language;
use Component\Subscription\Entity\ActorSubscription; use Component\Subscription\Entity\ActorSubscription;
@ -264,13 +266,24 @@ class Actor extends Entity
]; ];
} }
public function getLocalUser(): ?LocalUser /**
* Note: You will receive a Local from Database, it's a persisted instance
*
* @return LocalUser|LocalGroup Returns the local, if actor is local, null otherwise
*/
public function getLocal(): LocalUser|LocalGroup|null
{ {
if ($this->getIsLocal()) { if ($this->getIsLocal()) {
return DB::findOneBy('local_user', ['id' => $this->getId()]); switch ($this->getType()) {
} else { case Actor::PERSON:
throw new NotFoundException('This is a remote actor.'); return DB::findOneBy(LocalUser::class, ['id' => $this->getId()]);
case Actor::GROUP:
return DB::findOneBy(LocalGroup::class, ['actor_id' => $this->getId()]);
case Actor::BOT:
throw new NotImplementedException('We don\'t exactly have a local application abstraction yet...');
}
} }
return null;
} }
public function getAvatarUrl(string $size = 'medium') public function getAvatarUrl(string $size = 'medium')

View File

@ -59,7 +59,10 @@ class ActorForms
/** /**
* Actor personal information panel * Actor personal information panel
* *
* @throws \App\Util\Exception\NicknameException * @param Request $request
* @param Actor $scope The perpetrator of the change
* @param Actor $target The victim of changes
* @return mixed
* @throws ClientException * @throws ClientException
* @throws NicknameEmptyException * @throws NicknameEmptyException
* @throws NicknameInvalidException * @throws NicknameInvalidException
@ -67,9 +70,15 @@ class ActorForms
* @throws NicknameTakenException * @throws NicknameTakenException
* @throws NicknameTooLongException * @throws NicknameTooLongException
* @throws ServerException * @throws ServerException
* @throws \App\Util\Exception\NicknameException
*/ */
public static function personalInfo(Request $request, Actor $target, LocalUser|LocalGroup $user): mixed public static function personalInfo(Request $request, Actor $scope, Actor $target): mixed
{ {
// Is $target in $scope's sight?
if (!$scope->canModerate($target)) {
throw new ClientException(_m('You do not have permissions to change :nickname\'s settings', [':nickname' => $target->getNickname()]));
}
// Defining the various form fields // Defining the various form fields
$form_definition = [ $form_definition = [
['nickname', TextType::class, ['label' => _m('Nickname'), 'required' => true, 'help' => _m('1-64 lowercase letters or numbers, no punctuation or spaces.')]], ['nickname', TextType::class, ['label' => _m('Nickname'), 'required' => true, 'help' => _m('1-64 lowercase letters or numbers, no punctuation or spaces.')]],
@ -82,14 +91,26 @@ class ActorForms
]; ];
// Setting nickname normalised and setting actor cache // Setting nickname normalised and setting actor cache
$extra_step = static function ($data, $extra_args) use ($user, $target) { $before_step = static function ($data, $extra_args) use ($target) {
if (!strcmp($user->getNickname(), $data['nickname'])) { if ($target->getNickname() !== $data['nickname']) {
$data['nickname'] = Nickname::normalize($data['nickname'], check_already_used: false, which: Nickname::CHECK_LOCAL_GROUP, check_is_allowed: true); // We must only check if is both already used and allowed if the actor is local
$check_is_allowed = $target->getIsLocal();
if ($target->isGroup()) {
$data['nickname'] = Nickname::normalize($data['nickname'], check_already_used: $check_is_allowed, which: Nickname::CHECK_LOCAL_GROUP, check_is_allowed: $check_is_allowed);
} else {
$data['nickname'] = Nickname::normalize($data['nickname'], check_already_used: $check_is_allowed, which: Nickname::CHECK_LOCAL_USER, check_is_allowed: $check_is_allowed);
}
// We will set $target actor's nickname in the form::handle,
// but if it is local, we must update the local reference as well
if (!is_null($local = $target->getLocal())) {
$local->setNickname($data['nickname']);
}
} }
if (!strcmp($target->getFullname(), $data['full_name'])) { if ($target->getFullname() !== $data['full_name']) {
$data['full_name'] = trim($data['full_name']); $data['full_name'] = trim($data['full_name']);
if (mb_strlen($data['full_name']) > 64) { if (mb_strlen($data['full_name']) > 64) {
throw new ClientException('Fullname cannot be more than 64 character long.'); throw new ClientException(_m('Full name cannot be more than 64 character long.'));
} }
} }
@ -104,7 +125,7 @@ class ActorForms
$request, $request,
target: $target, target: $target,
extra_args: [], extra_args: [],
extra_step: $extra_step, before_step: $before_step,
); );
} }
} }

View File

@ -115,12 +115,12 @@ class FormTest extends GNUsocialTestCase
$mock_form->method('isSubmitted')->willReturn(true); $mock_form->method('isSubmitted')->willReturn(true);
$mock_form->method('isValid')->willReturn(true); $mock_form->method('isValid')->willReturn(true);
$mock_form->method('getData')->willReturn($data); $mock_form->method('getData')->willReturn($data);
$ret = Form::handle(form_definition: [/* not normal usage */], request: $mock_request, target: null, extra_args: [], extra_step: null, create_args: [], testing_only_form: $mock_form); $ret = Form::handle(form_definition: [/* not normal usage */], request: $mock_request, target: null, extra_args: [], before_step: null, after_step: null, create_args: [], testing_only_form: $mock_form);
static::assertSame($data, $ret); static::assertSame($data, $ret);
$actor = Actor::create(['nickname' => 'form_testing_new_user', 'is_local' => false]); $actor = Actor::create(['nickname' => 'form_testing_new_user', 'is_local' => false]);
DB::persist($actor); DB::persist($actor);
$ret = Form::handle(form_definition: [/* not normal usage */], request: $mock_request, target: $actor, extra_args: [], extra_step: null, create_args: [], testing_only_form: $mock_form); $ret = Form::handle(form_definition: [/* not normal usage */], request: $mock_request, target: $actor, extra_args: [], before_step: null, after_step: null, create_args: [], testing_only_form: $mock_form);
static::assertSame($mock_form, $ret); static::assertSame($mock_form, $ret);
static::assertSame($data['fullname'], $actor->getFullname()); static::assertSame($data['fullname'], $actor->getFullname());
static::assertSame($data['homepage'], $actor->getHomepage()); static::assertSame($data['homepage'], $actor->getHomepage());