[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:
		@@ -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 {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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')
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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,
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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());
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user