From b1262919dadd67965b90cd14f985aca91e9fefcb Mon Sep 17 00:00:00 2001 From: Hugo Sales Date: Thu, 25 Nov 2021 23:08:30 +0000 Subject: [PATCH] [TOOLS] Fix (most) issues found by PHPStan --- plugins/Favourite/Controller/Favourite.php | 95 ++++++++++++------- plugins/Repeat/Controller/Repeat.php | 34 ++++--- plugins/Reply/Controller/Reply.php | 19 ++-- plugins/Reply/Entity/NoteReply.php | 19 ++-- src/Controller/Network.php | 2 +- src/Controller/UserPanel.php | 8 +- src/Core/Cache.php | 14 +-- src/Core/GNUsocial.php | 2 + src/Entity/AttachmentThumbnail.php | 2 +- src/Security/Authenticator.php | 26 ++--- src/Security/EmailVerifier.php | 40 ++++---- src/Util/Common.php | 2 +- src/Util/Exception/BugFoundException.php | 49 ++++++++++ src/Util/Exception/EmailTakenException.php | 4 +- src/Util/Exception/NicknameTakenException.php | 4 +- src/Util/Form/FormFields.php | 28 +++--- src/Util/Nickname.php | 4 +- src/Util/TemporaryFile.php | 3 +- 18 files changed, 221 insertions(+), 134 deletions(-) create mode 100644 src/Util/Exception/BugFoundException.php diff --git a/plugins/Favourite/Controller/Favourite.php b/plugins/Favourite/Controller/Favourite.php index 772e25eda0..030edc849b 100644 --- a/plugins/Favourite/Controller/Favourite.php +++ b/plugins/Favourite/Controller/Favourite.php @@ -1,6 +1,6 @@ $id]; + $user = Common::ensureLoggedIn(); + $actor_id = $user->getId(); + $opts = ['id' => $id]; $add_favourite_note = DB::find('note', $opts); - if (is_null($add_favourite_note)) { + if (\is_null($add_favourite_note)) { throw new NoSuchNoteException(); } @@ -62,7 +64,7 @@ class Favourite extends Controller [ 'label' => _m('Favourite note!'), 'attr' => [ - 'title' => _m('Favourite this note!') + 'title' => _m('Favourite this note!'), ], ], ], @@ -71,41 +73,52 @@ class Favourite extends Controller $form_add_to_favourite->handleRequest($request); if ($form_add_to_favourite->isSubmitted()) { - $opts = ['note_id' => $id, 'actor_id' => $user->getId()]; + $opts = ['note_id' => $id, 'actor_id' => $user->getId()]; $note_already_favourited = DB::find('favourite', $opts); - if (is_null($note_already_favourited)) { + if (\is_null($note_already_favourited)) { $opts = ['note_id' => $id, 'actor_id' => $user->getId()]; DB::persist(FavouriteEntity::create($opts)); DB::flush(); } - if (array_key_exists('from', $get_params = $this->params())) { - # TODO anchor on element id - throw new RedirectException($get_params['from']); + // Redirect user to where they came from + // Prevent open redirect + if (!\is_null($from = $this->string('from'))) { + if (Router::isAbsolute($from)) { + Log::warning("Actor {$actor_id} attempted to reply to a note and then get redirected to another host, or the URL was invalid ({$from})"); + throw new ClientException(_m('Can not redirect to outside the website from here'), 400); // 400 Bad request (deceptive) + } else { + // TODO anchor on element id + throw new RedirectException($from); + } + } else { + // If we don't have a URL to return to, go to the instance root + throw new RedirectException('root'); } } return [ - '_template' => 'favourite/add_to_favourites.html.twig', - 'note' => $add_favourite_note, - 'add_favourite' => $form_add_to_favourite->createView(), + '_template' => 'favourite/add_to_favourites.html.twig', + 'note' => $add_favourite_note, + 'add_favourite' => $form_add_to_favourite->createView(), ]; } /** - * @throws RedirectException - * @throws NoSuchNoteException - * @throws InvalidFormException * @throws \App\Util\Exception\ServerException + * @throws InvalidFormException * @throws NoLoggedInUser + * @throws NoSuchNoteException + * @throws RedirectException */ public function favouriteRemoveNote(Request $request, int $id): array { - $user = Common::ensureLoggedIn(); - $opts = ['note_id' => $id, 'actor_id' => $user->getId()]; + $user = Common::ensureLoggedIn(); + $actor_id = $user->getId(); + $opts = ['note_id' => $id, 'actor_id' => $user->getId()]; $remove_favourite_note = DB::find('favourite', $opts); - if (is_null($remove_favourite_note)) { + if (\is_null($remove_favourite_note)) { throw new NoSuchNoteException(); } @@ -114,7 +127,7 @@ class Favourite extends Controller [ 'label' => _m('Remove favourite'), 'attr' => [ - 'title' => _m('Remove note from favourites.') + 'title' => _m('Remove note from favourites.'), ], ], ], @@ -127,17 +140,27 @@ class Favourite extends Controller DB::flush(); } - if (array_key_exists('from', $get_params = $this->params())) { - # TODO anchor on element id - throw new RedirectException($get_params['from']); + // Redirect user to where they came from + // Prevent open redirect + if (!\is_null($from = $this->string('from'))) { + if (Router::isAbsolute($from)) { + Log::warning("Actor {$actor_id} attempted to reply to a note and then get redirected to another host, or the URL was invalid ({$from})"); + throw new ClientException(_m('Can not redirect to outside the website from here'), 400); // 400 Bad request (deceptive) + } else { + // TODO anchor on element id + throw new RedirectException($from); + } + } else { + // If we don't have a URL to return to, go to the instance root + throw new RedirectException('root'); } } $note = DB::find('note', ['id' => $id]); return [ - '_template' => 'favourite/remove_from_favourites.html.twig', - 'note' => $note, - 'remove_favourite' => $form_remove_favourite->createView(), + '_template' => 'favourite/remove_from_favourites.html.twig', + 'note' => $note, + 'remove_favourite' => $form_remove_favourite->createView(), ]; } @@ -155,8 +178,8 @@ class Favourite extends Controller Event::handle('FormatNoteList', [$notes, &$notes_out]); return [ - '_template' => 'network/feed.html.twig', - 'notes' => $notes_out, + '_template' => 'network/feed.html.twig', + 'notes' => $notes_out, 'page_title' => 'Favourites timeline.', ]; } @@ -170,9 +193,9 @@ class Favourite extends Controller /** * Reverse favourites stream * - * @return array template * @throws NoLoggedInUser user not logged in * + * @return array template */ public function reverseFavouritesByActorId(Request $request, int $id): array { @@ -189,8 +212,8 @@ class Favourite extends Controller Event::handle('FormatNoteList', [$notes, &$notes_out]); return [ - '_template' => 'network/feed.html.twig', - 'notes' => $notes, + '_template' => 'network/feed.html.twig', + 'notes' => $notes, 'page_title' => 'Reverse favourites timeline.', ]; } diff --git a/plugins/Repeat/Controller/Repeat.php b/plugins/Repeat/Controller/Repeat.php index cf16f7dcc6..11008f159c 100644 --- a/plugins/Repeat/Controller/Repeat.php +++ b/plugins/Repeat/Controller/Repeat.php @@ -26,17 +26,18 @@ namespace Plugin\Repeat\Controller; use App\Core\Controller; use App\Core\DB\DB; use App\Core\Form; -use App\Entity\Actor; -use Component\Posting\Posting; use function App\Core\I18n\_m; use App\Core\Log; use App\Core\Router\Router; +use App\Entity\Actor; +use App\Entity\Language; use App\Entity\Note; use App\Util\Common; use App\Util\Exception\ClientException; use App\Util\Exception\NoLoggedInUser; use App\Util\Exception\NoSuchNoteException; use App\Util\Exception\RedirectException; +use Component\Posting\Posting; use Plugin\Repeat\Entity\NoteRepeat; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\HttpFoundation\Request; @@ -54,9 +55,9 @@ class Repeat extends Controller */ public function repeatAddNote(Request $request, int $id): bool|array { - $user = Common::ensureLoggedIn(); + $user = Common::ensureLoggedIn(); - $actor_id = $user->getId(); + $actor_id = $user->getId(); $opts = ['actor_id' => $actor_id, 'repeat_of' => $id]; $note_already_repeated = DB::count('note_repeat', $opts) >= 1; @@ -88,9 +89,10 @@ class Repeat extends Controller // Create a new note with the same content as the original $repeat = Posting::storeLocalNote( actor: Actor::getById($actor_id), - content:$note->getContent(), + content: $note->getContent(), content_type: $note->getContentType(), - processed_attachments: $note->getAttachmentsWithTitle() + language: Language::getFromId($note->getLanguageId())->getLocale(), + processed_attachments: $note->getAttachmentsWithTitle(), ); // Find the id of the note we just created @@ -112,13 +114,13 @@ class Repeat extends Controller // Redirect user to where they came from // Prevent open redirect - if (\array_key_exists('from', (array) $get_params = $this->params())) { - if (Router::isAbsolute($get_params['from'])) { - Log::warning("Actor {$actor_id} attempted to reply to a note and then get redirected to another host, or the URL was invalid ({$get_params['from']})"); + if (!\is_null($from = $this->string('from'))) { + if (Router::isAbsolute($from)) { + Log::warning("Actor {$actor_id} attempted to reply to a note and then get redirected to another host, or the URL was invalid ({$from})"); throw new ClientException(_m('Can not redirect to outside the website from here'), 400); // 400 Bad request (deceptive) } else { // TODO anchor on element id - throw new RedirectException($get_params['from']); + throw new RedirectException($from); } } else { // If we don't have a URL to return to, go to the instance root @@ -143,6 +145,7 @@ class Repeat extends Controller public function repeatRemoveNote(Request $request, int $id): array { $user = Common::ensureLoggedIn(); + $actor_id = $user->getId(); $opts = ['id' => $id]; $remove_repeat_note = DB::find('note', $opts); if (\is_null($remove_repeat_note)) { @@ -176,16 +179,17 @@ class Repeat extends Controller // Redirect user to where they came from // Prevent open redirect - if (\array_key_exists('from', (array) $get_params = $this->params())) { - if (Router::isAbsolute($get_params['from'])) { - Log::warning("Actor {$actor_id} attempted to reply to a note and then get redirected to another host, or the URL was invalid ({$get_params['from']})"); + if (!\is_null($from = $this->string('from'))) { + if (Router::isAbsolute($from)) { + Log::warning("Actor {$actor_id} attempted to reply to a note and then get redirected to another host, or the URL was invalid ({$from})"); throw new ClientException(_m('Can not redirect to outside the website from here'), 400); // 400 Bad request (deceptive) } else { // TODO anchor on element id - throw new RedirectException($get_params['from']); + throw new RedirectException($from); } } else { - throw new RedirectException('root'); // If we don't have a URL to return to, go to the instance root + // If we don't have a URL to return to, go to the instance root + throw new RedirectException('root'); } } diff --git a/plugins/Reply/Controller/Reply.php b/plugins/Reply/Controller/Reply.php index 174b7284ba..36404eb1f4 100644 --- a/plugins/Reply/Controller/Reply.php +++ b/plugins/Reply/Controller/Reply.php @@ -39,6 +39,7 @@ use App\Util\Exception\ClientException; use App\Util\Exception\InvalidFormException; use App\Util\Exception\NoSuchNoteException; use App\Util\Exception\RedirectException; +use App\Util\Form\FormFields; use Component\Posting\Posting; use Plugin\Reply\Entity\NoteReply; use Symfony\Component\Form\Extension\Core\Type\FileType; @@ -70,13 +71,10 @@ class Reply extends Controller throw new NoSuchNoteException(); } + // TODO shouldn't this be the posting form? $form = Form::create([ - ['content', TextareaType::class, [ - 'label' => _m('Reply'), - 'label_attr' => ['class' => 'section-form-label'], - 'help' => _m('Please input your reply.'), - ], - ], + ['content', TextareaType::class, ['label' => _m('Reply'), 'label_attr' => ['class' => 'section-form-label'], 'help' => _m('Please input your reply.')]], + FormFields::language($user->getActor(), context_actor: $note->getActor(), label: 'Note language', help: null), ['attachments', FileType::class, ['label' => ' ', 'multiple' => true, 'required' => false]], ['replyform', SubmitType::class, ['label' => _m('Submit')]], ]); @@ -91,6 +89,7 @@ class Reply extends Controller actor: Actor::getWithPK($actor_id), content: $data['content'], content_type: 'text/plain', // TODO + language: $data['language'], attachments: $data['attachments'], ); @@ -116,13 +115,13 @@ class Reply extends Controller // Redirect user to where they came from // Prevent open redirect - if (\array_key_exists('from', (array) $get_params = $this->params())) { - if (Router::isAbsolute($get_params['from'])) { - Log::warning("Actor {$actor_id} attempted to reply to a note and then get redirected to another host, or the URL was invalid ({$get_params['from']})"); + if (!\is_null($from = $this->string('from'))) { + if (Router::isAbsolute($from)) { + Log::warning("Actor {$actor_id} attempted to reply to a note and then get redirected to another host, or the URL was invalid ({$from})"); throw new ClientException(_m('Can not redirect to outside the website from here'), 400); // 400 Bad request (deceptive) } else { // TODO anchor on element id - throw new RedirectException($get_params['from']); + throw new RedirectException($from); } } else { // If we don't have a URL to return to, go to the instance root diff --git a/plugins/Reply/Entity/NoteReply.php b/plugins/Reply/Entity/NoteReply.php index d14d2a5377..50f8107368 100644 --- a/plugins/Reply/Entity/NoteReply.php +++ b/plugins/Reply/Entity/NoteReply.php @@ -1,6 +1,6 @@ $note->getId()] + ['note_id' => $note->getId()], ); } public static function getReplyToNote(Note $note): ?int { $result = DB::dql('select nr.reply_to from note_reply nr ' - . 'where nr.note_id = :note_id', ['note_id' => $note->getId()]); + . 'where nr.note_id = :note_id', ['note_id' => $note->getId()], ); - if (!isEmpty($result)) { + if (!empty($result)) { return $result['reply_to']; } @@ -106,19 +105,19 @@ class NoteReply extends Entity public static function schemaDef(): array { return [ - 'name' => 'note_reply', + 'name' => 'note_reply', 'fields' => [ - 'note_id' => ['type' => 'int', 'not null' => true, 'foreign key' => true, 'target' => 'Note.id', 'multiplicity' => 'one to one', 'description' => 'The id of the reply itself'], + 'note_id' => ['type' => 'int', 'not null' => true, 'foreign key' => true, 'target' => 'Note.id', 'multiplicity' => 'one to one', 'description' => 'The id of the reply itself'], 'actor_id' => ['type' => 'int', 'not null' => true, 'foreign key' => true, 'target' => 'Actor.id', 'multiplicity' => 'one to one', 'description' => 'Who made this reply'], 'reply_to' => ['type' => 'int', 'not null' => true, 'foreign key' => true, 'target' => 'Note.id', 'multiplicity' => 'one to one', 'description' => 'Note this is a reply of'], ], 'primary key' => ['note_id'], 'foreign keys' => [ - 'note_id_to_id_fkey' => ['note', ['note_id' => 'id']], - 'note_reply_to_id_fkey' => ['note', ['reply_to' => 'id']], + 'note_id_to_id_fkey' => ['note', ['note_id' => 'id']], + 'note_reply_to_id_fkey' => ['note', ['reply_to' => 'id']], 'actor_reply_to_id_fkey' => ['actor', ['actor_id' => 'id']], ], - 'indexes' => [ + 'indexes' => [ 'note_reply_to_idx' => ['reply_to'], ], ]; diff --git a/src/Controller/Network.php b/src/Controller/Network.php index 37d787bae9..98b11b0141 100644 --- a/src/Controller/Network.php +++ b/src/Controller/Network.php @@ -44,7 +44,7 @@ use App\Entity\Note; use App\Util\Common; use App\Util\Exception\ClientException; use App\Util\Exception\NotFoundException; -use NotImplementedException; +use App\Util\Exception\NotImplementedException; use Symfony\Component\HttpFoundation\Request; class Network extends Controller diff --git a/src/Controller/UserPanel.php b/src/Controller/UserPanel.php index a774f8b4f5..46753f93d8 100644 --- a/src/Controller/UserPanel.php +++ b/src/Controller/UserPanel.php @@ -45,6 +45,7 @@ use App\Core\Form; use function App\Core\I18n\_m; use App\Core\Log; use App\Entity\ActorLanguage; +use App\Entity\Language; use App\Entity\UserNotificationPrefs; use App\Util\Common; use App\Util\Exception\AuthenticationException; @@ -63,6 +64,7 @@ use Symfony\Component\Form\Extension\Core\Type\IntegerType; use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Core\Type\TextareaType; use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Form\SubmitButton; use Symfony\Component\HttpFoundation\Request; // }}} Imports @@ -202,12 +204,14 @@ class UserPanel extends Controller $form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { - $go_back = $form->get('go_back')->isClicked(); + /** @var SubmitButton $button */ + $button = $form->get('go_back'); + $go_back = $button->isClicked(); $data = $form->getData(); asort($data); // Sort by the order value $data = array_keys($data); // This keeps the order and gives us a unique number for each foreach ($data as $order => $locale) { - $lang = Cache::getHashMapKey('languages', $locale); + $lang = Language::getFromLocale($locale); $actor_lang = DB::getReference('actor_language', ['actor_id' => $user->getId(), 'language_id' => $lang->getId()]); $actor_lang->setOrdering($order + 1); } diff --git a/src/Core/Cache.php b/src/Core/Cache.php index ef7c533fe2..c0aaf59191 100644 --- a/src/Core/Cache.php +++ b/src/Core/Cache.php @@ -29,13 +29,14 @@ use App\Entity\LocalUser; use App\Entity\Note; use App\Util\Common; use App\Util\Exception\ConfigurationException; +use App\Util\Exception\NotImplementedException; use Functional as F; use InvalidArgumentException; -use NotImplementedException; use Redis; use RedisCluster; use Symfony\Component\Cache\Adapter; use Symfony\Component\Cache\Adapter\ChainAdapter; +use Symfony\Component\Cache\CacheItem; abstract class Cache { @@ -154,7 +155,7 @@ abstract class Cache * Retrieve a list from the cache, with a different implementation * for redis and others, trimming to $max_count if given * - * @param callable(?Item $item, bool &$save): string|object|array $calculate + * @param callable(?CacheItem $item, bool &$save): (string|object|array) $calculate */ public static function getList(string $key, callable $calculate, string $pool = 'default', ?int $max_count = null, ?int $left = null, ?int $right = null, float $beta = 1.0): array { @@ -191,7 +192,8 @@ abstract class Cache return self::$redis[$pool]->lRange($key, $left ?? 0, ($right ?? $max_count ?? 0) - 1); } else { return self::get($key, function () use ($calculate, $max_count) { - $res = $calculate(null); + $save = true; + $res = $calculate(null, $save); if ($max_count != -1) { $res = \array_slice($res, 0, $max_count); } @@ -261,7 +263,7 @@ abstract class Cache * Retrieve a hashmap from the cache, with a different implementation * for redis and others. Different from lists, works with string map_keys * - * @param callable(?Item $item, bool &$save): string|object|array $calculate + * @param callable(?CacheItem $item, bool &$save): (string|object|array) $calculate * @TODO cleanup */ public static function getHashMap(string $map_key, callable $calculate, string $pool = 'default', float $beta = 1.0): array @@ -286,7 +288,7 @@ abstract class Cache $save = true; // Pass by reference $res = $calculate(null, $save); if ($save) { - self::setHashMap($map_key, $res, $pool, $beta); + self::setHashMap($map_key, $res, $pool); return $res; } } @@ -314,7 +316,7 @@ abstract class Cache self::$redis[$pool]->exec(); } } else { - self::set($map_key, \array_slice($value, 0, $max_count), $pool); + self::set($map_key, $value, $pool); } } diff --git a/src/Core/GNUsocial.php b/src/Core/GNUsocial.php index e717f52fb2..6e56b8d995 100644 --- a/src/Core/GNUsocial.php +++ b/src/Core/GNUsocial.php @@ -49,6 +49,7 @@ use App\Core\I18n\I18n; use App\Core\Queue\Queue; use App\Core\Router\Router; use App\Kernel; +use App\Security\EmailVerifier; use App\Util\Common; use App\Util\Exception\ConfigurationException; use App\Util\Formatting; @@ -172,6 +173,7 @@ class GNUsocial implements EventSubscriberInterface Router::setRouter($this->router); HTTPClient::setClient($this->client); Formatting::setTwig($this->twig); + EmailVerifier::setHelpers($this->email_verify_helper, $this->mailer_helper); Cache::setupCache(); DB::initTableMap(); diff --git a/src/Entity/AttachmentThumbnail.php b/src/Entity/AttachmentThumbnail.php index ca43c1301f..db86e4b7f6 100644 --- a/src/Entity/AttachmentThumbnail.php +++ b/src/Entity/AttachmentThumbnail.php @@ -165,7 +165,7 @@ class AttachmentThumbnail extends Entity public static function sizeStrToInt(string $size) { - return self::SIZE_MAP[$size] ?? self::SIZE_MAP[self::SIZE_SMALL]; + return self::SIZE_MAP[$size] ?? self::SIZE_SMALL; } private ?Attachment $attachment = null; diff --git a/src/Security/Authenticator.php b/src/Security/Authenticator.php index 9ba4290e25..ec7b540f82 100644 --- a/src/Security/Authenticator.php +++ b/src/Security/Authenticator.php @@ -1,6 +1,6 @@ csrfTokenManager = $csrfTokenManager; } - /** - * @param Request $request - * @return bool - */ public function supports(Request $request): bool { return self::LOGIN_ROUTE === $request->attributes->get('_route') && $request->isMethod('POST'); @@ -92,10 +93,11 @@ class Authenticator extends AbstractFormLoginAuthenticator implements Authentica * Get a user given credentials and a CSRF token * * @param array $credentials result of self::getCredentials - * @param UserProviderInterface $userProvider - * @return ?LocalUser + * * @throws NoSuchActorException * @throws ServerException + * + * @return ?LocalUser */ public function getUser($credentials, UserProviderInterface $userProvider): ?LocalUser { @@ -110,7 +112,7 @@ class Authenticator extends AbstractFormLoginAuthenticator implements Authentica } elseif (Nickname::isValid($credentials['nickname_or_email'])) { $user = LocalUser::getByNickname($credentials['nickname_or_email']); } - if (is_null($user)) { + if (\is_null($user)) { throw new NoSuchActorException('No such local user.'); } $credentials['nickname'] = $user->getNickname(); @@ -124,8 +126,8 @@ class Authenticator extends AbstractFormLoginAuthenticator implements Authentica /** * @param array $credentials result of self::getCredentials - * @param LocalUser $user - * @return bool + * @param LocalUser $user + * * @throws ServerException */ public function checkCredentials($credentials, $user): bool @@ -144,7 +146,7 @@ class Authenticator extends AbstractFormLoginAuthenticator implements Authentica { $nickname = $token->getUser(); if ($nickname instanceof Stringable) { - $nickname = (string)$nickname; + $nickname = (string) $nickname; } elseif ($nickname instanceof UserInterface) { $nickname = $nickname->getUserIdentifier(); } diff --git a/src/Security/EmailVerifier.php b/src/Security/EmailVerifier.php index c5da57b521..4e88bac100 100644 --- a/src/Security/EmailVerifier.php +++ b/src/Security/EmailVerifier.php @@ -4,7 +4,8 @@ declare(strict_types = 1); namespace App\Security; -use Doctrine\ORM\EntityManagerInterface; +use App\Core\DB\DB; +use App\Entity\LocalUser; use Symfony\Bridge\Twig\Mime\TemplatedEmail; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Mailer\MailerInterface; @@ -12,22 +13,23 @@ use Symfony\Component\Security\Core\User\UserInterface; use SymfonyCasts\Bundle\VerifyEmail\Exception\VerifyEmailExceptionInterface; use SymfonyCasts\Bundle\VerifyEmail\VerifyEmailHelperInterface; -class EmailVerifier +abstract class EmailVerifier { - private $verifyEmailHelper; - private $mailer; - private $entityManager; + private static $verifyEmailHelper; + private static $mailer; - public function __construct(VerifyEmailHelperInterface $helper, MailerInterface $mailer, EntityManagerInterface $manager) + public static function setHelpers(VerifyEmailHelperInterface $helper, MailerInterface $mailer) { - $this->verifyEmailHelper = $helper; - $this->mailer = $mailer; - $this->entityManager = $manager; + self::$verifyEmailHelper = $helper; + self::$mailer = $mailer; } - public function sendEmailConfirmation(string $verifyEmailRouteName, UserInterface $user, TemplatedEmail $email): void + /** + * @param LocalUser $user + */ + public static function sendEmailConfirmation(string $verifyEmailRouteName, UserInterface $user, TemplatedEmail $email): void { - $signatureComponents = $this->verifyEmailHelper->generateSignature( + $signatureComponents = self::$verifyEmailHelper->generateSignature( $verifyEmailRouteName, $user->getId(), $user->getOutgoingEmail(), @@ -41,19 +43,19 @@ class EmailVerifier $email->context($context); - $this->mailer->send($email); + self::$mailer->send($email); } /** + * @param LocalUser $user + * * @throws VerifyEmailExceptionInterface */ - public function handleEmailConfirmation(Request $request, UserInterface $user): void + public static function handleEmailConfirmation(Request $request, UserInterface $user): void { - $this->verifyEmailHelper->validateEmailConfirmation($request->getUri(), $user->getId(), $user->getOutgoingEmail()); - - $user->setIsVerified(true); - - $this->entityManager->persist($user); - $this->entityManager->flush(); + self::$verifyEmailHelper->validateEmailConfirmation($request->getUri(), $user->getId(), $user->getOutgoingEmail()); + $user->setIsEmailVerified(true); + DB::persist($user); + DB::flush(); } } diff --git a/src/Util/Common.php b/src/Util/Common.php index bb0976be94..fefaf674a7 100644 --- a/src/Util/Common.php +++ b/src/Util/Common.php @@ -99,7 +99,7 @@ abstract class Common /** * Set sysadmin's configuration preferences for GNU social * - * @param $transient keep this setting in memory only + * @param bool $transient keep this setting in memory only */ public static function setConfig(string $section, string $setting, $value, bool $transient = false): void { diff --git a/src/Util/Exception/BugFoundException.php b/src/Util/Exception/BugFoundException.php new file mode 100644 index 0000000000..a00b09321d --- /dev/null +++ b/src/Util/Exception/BugFoundException.php @@ -0,0 +1,49 @@ +. + +// }}} + +/** + * Class for 'assertion' exceptions. Logs when an unexpected state is found and is treated as a ServerException downstream + * HTTP code 500 + * + * @category Exception + * @package GNUsocial + * + * @author Hugo Sales + * @copyright 2021 Free Software Foundation, Inc http://www.fsf.org + * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later + */ + +namespace App\Util\Exception; + +use App\Core\Log; +use Throwable; + +class BugFoundException extends ServerException +{ + public function __construct(string $log_message, string $message = '', int $code = 500, ?Throwable $previous = null) + { + parent::__construct($message, $code, $previous); + $frame = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, limit: 2)[1]; + Log::critical("{$log_message} in {$frame['file']}:{$frame['line']}"); + } +} diff --git a/src/Util/Exception/EmailTakenException.php b/src/Util/Exception/EmailTakenException.php index 864aab306a..39e843f55f 100644 --- a/src/Util/Exception/EmailTakenException.php +++ b/src/Util/Exception/EmailTakenException.php @@ -43,13 +43,13 @@ class EmailTakenException extends EmailException public function __construct(?Actor $actor = null, ?string $msg = null, int $code = 400) { - $this->profile = $actor; + $this->actor = $actor; parent::__construct($msg, $code); } protected function defaultMessage(): string { - // TRANS: Validation error in form for registration, profile and group settings, etc. + // TRANS: Validation error in form for registration, actor and group settings, etc. return _m('Email is already in use on this server.'); } } diff --git a/src/Util/Exception/NicknameTakenException.php b/src/Util/Exception/NicknameTakenException.php index c45c51dcdd..05776d66a2 100644 --- a/src/Util/Exception/NicknameTakenException.php +++ b/src/Util/Exception/NicknameTakenException.php @@ -52,13 +52,13 @@ class NicknameTakenException extends NicknameException public function __construct(?Actor $actor = null, ?string $msg = null, int $code = 400) { - $this->profile = $actor; + $this->actor = $actor; parent::__construct($msg, $code); } protected function defaultMessage(): string { - // TRANS: Validation error in form for registration, profile and group settings, etc. + // TRANS: Validation error in form for registration, actor and group settings, etc. return _m('Nickname is already in use on this server.'); } } diff --git a/src/Util/Form/FormFields.php b/src/Util/Form/FormFields.php index 186f076a59..60bdba6849 100644 --- a/src/Util/Form/FormFields.php +++ b/src/Util/Form/FormFields.php @@ -1,9 +1,10 @@ PasswordType::class, + 'type' => PasswordType::class, 'first_options' => [ 'label' => _m('Password'), 'label_attr' => ['class' => 'section-form-label'], 'attr' => array_merge(['placeholder' => _m('********'), 'required' => $options['required'] ?? true], $options['attr'] ?? []), 'constraints' => $constraints, - 'help' => _m('Write a password with at least {min_length} characters, and a maximum of {max_length}.', ['min_length' => Common::config('password', 'min_length'), 'max_length' => Common::config('password', 'max_length')]), + 'help' => _m('Write a password with at least {min_length} characters, and a maximum of {max_length}.', ['min_length' => Common::config('password', 'min_length'), 'max_length' => Common::config('password', 'max_length')]), ], 'second_options' => [ 'label' => _m('Repeat Password'), @@ -45,8 +45,8 @@ abstract class FormFields 'required' => $options['required'] ?? true, 'constraints' => $constraints, ], - 'mapped' => false, - 'required' => $options['required'] ?? true, + 'mapped' => false, + 'required' => $options['required'] ?? true, 'invalid_message' => _m('The password fields must match'), ], ]; @@ -67,24 +67,24 @@ abstract class FormFields 'constraints' => [ new NotBlank(['message' => _m('Please enter a password')]), new Length(['min' => Common::config('password', 'min_length'), 'minMessage' => _m(['Your password should be at least # characters'], ['count' => Common::config('password', 'min_length')]), - 'max' => Common::config('password', 'max_length'), 'maxMessage' => _m(['Your password should be at most # characters'], ['count' => Common::config('password', 'max_length')]),]), - ],], + 'max' => Common::config('password', 'max_length'), 'maxMessage' => _m(['Your password should be at most # characters'], ['count' => Common::config('password', 'max_length')]), ]), + ], ], ]; } - public static function language(Actor $actor, ?Actor $context_actor, string $label, string $help, bool $multiple = false, bool $required = true, ?bool $use_short_display = null): array + public static function language(Actor $actor, ?Actor $context_actor, string $label, ?string $help = null, bool $multiple = false, bool $required = true, ?bool $use_short_display = null): array { [$language_choices, $preferred_language_choices] = Language::getSortedLanguageChoices($actor, $context_actor, use_short_display: $use_short_display); return [ 'language' . ($multiple ? 's' : ''), ChoiceType::class, [ - 'label' => _m($label), + 'label' => _m($label), 'preferred_choices' => $preferred_language_choices, - 'choices' => $language_choices, - 'required' => $required, - 'multiple' => $multiple, - 'help' => _m($help), + 'choices' => $language_choices, + 'required' => $required, + 'multiple' => $multiple, + 'help' => _m($help), ], ]; } diff --git a/src/Util/Nickname.php b/src/Util/Nickname.php index de4b508472..53732bf41e 100644 --- a/src/Util/Nickname.php +++ b/src/Util/Nickname.php @@ -24,7 +24,7 @@ declare(strict_types = 1); namespace App\Util; use App\Core\DB\DB; -use App\Core\Log; +use App\Util\Exception\BugFoundException; use App\Util\Exception\DuplicateFoundException; use App\Util\Exception\NicknameEmptyException; use App\Util\Exception\NicknameException; @@ -155,7 +155,7 @@ class Nickname } catch (NotFoundException) { // continue } catch (DuplicateFoundException) { - Log::critial("Duplicate entry in `local_user` for nickname={$nickname}"); + throw new BugFoundException("Duplicate entry in `local_user` for nickname={$nickname}"); } break; // @codeCoverageIgnoreStart diff --git a/src/Util/TemporaryFile.php b/src/Util/TemporaryFile.php index c4770db561..9893f14118 100644 --- a/src/Util/TemporaryFile.php +++ b/src/Util/TemporaryFile.php @@ -22,6 +22,7 @@ declare(strict_types = 1); namespace App\Util; use function App\Core\I18n\_m; +use App\Util\Exception\BugFoundException; use App\Util\Exception\ServerException; use App\Util\Exception\TemporaryFileException; use LogicException; @@ -129,7 +130,7 @@ class TemporaryFile extends SplFileInfo if (!\is_null($this->resource) && $this->resource !== false) { $path = $this->getRealPath(); if ($path === false) { - throw new BugFoundException(); + throw new BugFoundException('Tried to cleanup a file but it\'s real path is false, while resource isn\'t'); } $this->close(); if (file_exists($path)) {