diff --git a/.github/workflows/test-application.yaml b/.github/workflows/test-application.yaml index d4cb96d5..05db6314 100644 --- a/.github/workflows/test-application.yaml +++ b/.github/workflows/test-application.yaml @@ -41,12 +41,25 @@ jobs: SYMFONY_DEPRECATIONS_HELPER: weak - php-version: '8.1' - lint: true + lint: false + dependency-versions: 'highest' + tools: 'composer:v2' + env: + SYMFONY_DEPRECATIONS_HELPER: weak + + - php-version: '8.2' + lint: false dependency-versions: 'highest' tools: 'composer:v2' env: SYMFONY_DEPRECATIONS_HELPER: weak + - php-version: '8.3' + lint: true + dependency-versions: 'highest' + tools: 'composer:v2' + env: + SYMFONY_DEPRECATIONS_HELPER: weak services: mysql: image: mysql:5.7 @@ -71,6 +84,11 @@ jobs: if: ${{ matrix.php-version == '7.2' }} run: composer remove php-cs-fixer/shim --dev --no-interaction + - name: Install additional lowest dependencies + if: ${{ matrix.dependency-versions == 'lowest' }} + run: | + composer require symfony/swiftmailer-bundle --no-interaction --no-update + - name: Install composer dependencies uses: ramsey/composer-install@v1 with: diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 7434ac29..3a06d535 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -17,38 +17,33 @@ $config->setRiskyAllowed(true) ->setRules([ '@Symfony' => true, - '@Symfony:risky' => true, - 'ordered_imports' => true, - 'concat_space' => ['spacing' => 'one'], 'array_syntax' => ['syntax' => 'short'], - 'phpdoc_align' => ['align' => 'left'], - 'class_definition' => [ - 'multi_line_extends_each_single_line' => true, - ] , - 'linebreak_after_opening_tag' => true, -// 'declare_strict_types' => true, - 'method_argument_space' => ['on_multiline' => 'ensure_fully_multiline'], + 'class_definition' => false, + 'concat_space' => ['spacing' => 'one'], + 'function_declaration' => ['closure_function_spacing' => 'none'], + 'header_comment' => ['header' => $header], 'native_constant_invocation' => true, 'native_function_casing' => true, 'native_function_invocation' => ['include' => ['@internal']], - 'no_php4_constructor' => true, + 'global_namespace_import' => ['import_classes' => false, 'import_constants' => false, 'import_functions' => false], 'no_superfluous_phpdoc_tags' => ['allow_mixed' => true, 'remove_inheritdoc' => true], - 'no_unreachable_default_argument_value' => true, - 'no_useless_else' => true, - 'no_useless_return' => true, - 'php_unit_strict' => true, - 'phpdoc_order' => true, - 'semicolon_after_instruction' => true, - 'strict_comparison' => true, - 'strict_param' => true, - 'array_indentation' => true, - 'multiline_whitespace_before_semicolons' => true, + 'ordered_imports' => true, + 'phpdoc_align' => ['align' => 'left'], + 'phpdoc_types_order' => false, 'single_line_throw' => false, - 'visibility_required' => ['elements' => ['property', 'method', 'const']], + 'single_line_comment_spacing' => false, 'phpdoc_to_comment' => [ 'ignored_tags' => ['todo', 'var'], ], - 'trailing_comma_in_multiline' => ['elements' => ['arrays', /*'arguments', 'parameters' */]], + 'phpdoc_separation' => [ + 'groups' => [ + ['Serializer\\*', 'VirtualProperty', 'Accessor', 'Type', 'Groups', 'Expose', 'Exclude', 'SerializedName', 'Inline', 'ExclusionPolicy'], + ], + ], + 'get_class_to_class_keyword' => false, // should be enabled as soon as support for php < 8 is dropped + 'nullable_type_declaration_for_default_null_value' => true, + 'no_null_property_initialization' => false, + 'fully_qualified_strict_types' => false, ]) ->setFinder($finder); diff --git a/Controller/AbstractController.php b/Controller/AbstractController.php index 38cf035d..148a1b16 100644 --- a/Controller/AbstractController.php +++ b/Controller/AbstractController.php @@ -26,7 +26,7 @@ use Symfony\Component\Form\FormInterface; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\HttpException; -use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface; +use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface; /** * Contains helper function for all controllers. @@ -75,12 +75,26 @@ protected function setUserPasswordAndSalt(User $user, FormInterface $form): User } $user->setSalt($salt); - $password = $this->getUserPasswordEncoder()->encodePassword($user, $plainPassword); + $password = $this->encodePassword($user, $plainPassword); $user->setPassword($password); return $user; } + protected function encodePassword(User $user, string $plainPassword): string + { + if ($this->container->has('?security.password_hasher')) { + /** @var UserPasswordHasherInterface $hasher */ + $hasher = $this->container->get('?security.password_hasher'); + + return $hasher->hashPassword($user, $plainPassword); + } + /** @var \Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface $encoder */ + $encoder = $this->container->get('?security.password_encoder'); + + return $encoder->encodePassword($user, $plainPassword); + } + /** * Check if user should be logged in. */ @@ -132,9 +146,6 @@ private function getTemplateAttributes(array $custom = []): array return $this->getTemplateAttributeResolver()->resolve($custom); } - /** - * @return User - */ public function getUser(): ?User { $user = parent::getUser(); @@ -175,7 +186,7 @@ private function addAddress(User $user): void /** * @param mixed[] $parameters */ - public function render(string $view, array $parameters = [], Response $response = null): Response + public function render(string $view, array $parameters = [], ?Response $response = null): Response { return parent::render( $view, @@ -207,11 +218,6 @@ protected function getSaltGenerator(): SaltGenerator return $this->container->get('sulu_security.salt_generator'); } - protected function getUserPasswordEncoder(): UserPasswordEncoderInterface - { - return $this->container->get('security.password_encoder'); - } - protected function getTemplateAttributeResolver(): TemplateAttributeResolverInterface { return $this->container->get('sulu_website.resolver.template_attribute'); @@ -232,9 +238,13 @@ public static function getSubscribedServices(): array $subscribedServices['sulu_community.community_manager.registry'] = CommunityManagerRegistryInterface::class; $subscribedServices['sulu_core.webspace.request_analyzer'] = RequestAnalyzerInterface::class; $subscribedServices['sulu_security.salt_generator'] = SaltGenerator::class; - $subscribedServices['security.password_encoder'] = UserPasswordEncoderInterface::class; $subscribedServices['sulu_website.resolver.template_attribute'] = TemplateAttributeResolverInterface::class; $subscribedServices['doctrine.orm.entity_manager'] = EntityManagerInterface::class; + $subscribedServices['?security.password_hasher'] = UserPasswordHasherInterface::class; + + if (\class_exists('Symfony\\Component\\Security\\Core\\Encoder\\UserPasswordEncoderInterface')) { + $subscribedServices['?security.password_encoder'] = 'Symfony\\Component\\Security\\Core\\Encoder\\UserPasswordEncoderInterface'; + } return $subscribedServices; } diff --git a/Controller/BlacklistItemController.php b/Controller/BlacklistItemController.php index 1d46d1ce..75c628b5 100644 --- a/Controller/BlacklistItemController.php +++ b/Controller/BlacklistItemController.php @@ -34,6 +34,7 @@ * Provides admin-api for blacklist-items. * * @NamePrefix("sulu_community.") + * * @RouteResource("blacklist-item") */ class BlacklistItemController extends AbstractRestController implements ClassResourceInterface @@ -148,7 +149,7 @@ public function deleteAction(int $id): Response */ public function cdeleteAction(Request $request): Response { - $ids = \array_map(function ($id) { + $ids = \array_map(function($id) { return (int) $id; }, \array_filter(\explode(',', (string) $request->query->get('ids', '')))); diff --git a/Controller/SaveMediaTrait.php b/Controller/SaveMediaTrait.php index 15762e4f..36b22a0b 100644 --- a/Controller/SaveMediaTrait.php +++ b/Controller/SaveMediaTrait.php @@ -99,7 +99,7 @@ private function saveMedia(UploadedFile $uploadedFile, ?int $id, string $locale, */ private function getSystemCollectionManager(): SystemCollectionManagerInterface { - return $this->get('sulu_media.system_collections.manager'); + return $this->container->get('sulu_media.system_collections.manager'); } /** @@ -107,7 +107,7 @@ private function getSystemCollectionManager(): SystemCollectionManagerInterface */ private function getMediaManager(): MediaManagerInterface { - return $this->get('sulu_media.media_manager'); + return $this->container->get('sulu_media.media_manager'); } /** diff --git a/DependencyInjection/CompilerPass/CommunityManagerCompilerPass.php b/DependencyInjection/CompilerPass/CommunityManagerCompilerPass.php index bde28d33..5be3d11a 100644 --- a/DependencyInjection/CompilerPass/CommunityManagerCompilerPass.php +++ b/DependencyInjection/CompilerPass/CommunityManagerCompilerPass.php @@ -38,7 +38,6 @@ * }, * delete_user: bool, * } - * * @phpstan-type Config array{ * from: string|string[], * to: string|string[], diff --git a/DependencyInjection/Configuration.php b/DependencyInjection/Configuration.php index ce818d04..cc9cdcb3 100644 --- a/DependencyInjection/Configuration.php +++ b/DependencyInjection/Configuration.php @@ -136,7 +136,7 @@ public function getConfigTreeBuilder() ->end() ->beforeNormalization() ->ifString() - ->then(function ($value) { + ->then(function($value) { return [ self::EMAIL_FROM_NAME => $value, self::EMAIL_FROM_EMAIL => $value, @@ -151,7 +151,7 @@ public function getConfigTreeBuilder() ->end() ->beforeNormalization() ->ifString() - ->then(function ($value) { + ->then(function($value) { return [ self::EMAIL_TO_NAME => $value, self::EMAIL_TO_EMAIL => $value, diff --git a/DependencyInjection/SuluCommunityExtension.php b/DependencyInjection/SuluCommunityExtension.php index d456bdac..a219ffe1 100644 --- a/DependencyInjection/SuluCommunityExtension.php +++ b/DependencyInjection/SuluCommunityExtension.php @@ -153,7 +153,7 @@ public function prepend(ContainerBuilder $container): void 'orm' => [ 'dql' => [ 'string_functions' => [ - 'regexp' => RegExp::class, + 'regexp' => Regexp::class, ], ], ], diff --git a/Entity/BlacklistItem.php b/Entity/BlacklistItem.php index e7bfd6ee..d8d85ccf 100644 --- a/Entity/BlacklistItem.php +++ b/Entity/BlacklistItem.php @@ -44,10 +44,6 @@ class BlacklistItem */ private $type; - /** - * @param string $pattern - * @param string $type - */ public function __construct(?string $pattern = null, ?string $type = null) { $this->type = $type; diff --git a/EventListener/CompletionListener.php b/EventListener/CompletionListener.php index d61acb1e..cb738e64 100644 --- a/EventListener/CompletionListener.php +++ b/EventListener/CompletionListener.php @@ -104,7 +104,7 @@ public function onRequest(RequestEvent $event): void $request = $event->getRequest(); $completionUrl = $this->router->generate('sulu_community.completion'); - if (!$event->isMasterRequest() + if (!$event->isMainRequest() || !$request->isMethodSafe() || $request->isXmlHttpRequest() || $request->getPathInfo() === $completionUrl diff --git a/EventListener/LastLoginListener.php b/EventListener/LastLoginListener.php index 97d60bd1..73f84587 100644 --- a/EventListener/LastLoginListener.php +++ b/EventListener/LastLoginListener.php @@ -66,7 +66,7 @@ public static function getSubscribedEvents() */ public function onRequest(RequestEvent $event): void { - if (!$event->isMasterRequest()) { + if (!$event->isMainRequest()) { return; } diff --git a/Form/Type/RegistrationType.php b/Form/Type/RegistrationType.php index 176ea05f..0534bbae 100644 --- a/Form/Type/RegistrationType.php +++ b/Form/Type/RegistrationType.php @@ -73,7 +73,7 @@ public function configureOptions(OptionsResolver $resolver): void [ 'data_class' => User::class, 'validation_groups' => ['registration'], - 'empty_data' => function (FormInterface $form) { + 'empty_data' => function(FormInterface $form) { $user = new User(); $user->setContact(new Contact()); diff --git a/Mail/Mail.php b/Mail/Mail.php index 76f0d352..a4b7f52e 100644 --- a/Mail/Mail.php +++ b/Mail/Mail.php @@ -28,8 +28,6 @@ class Mail * user_template: string|null, * admin_template: string|null, * } $config - * - * @return Mail */ public static function create($from, $to, array $config): self { diff --git a/Mail/MailFactory.php b/Mail/MailFactory.php index 663ca4eb..55c91f34 100644 --- a/Mail/MailFactory.php +++ b/Mail/MailFactory.php @@ -12,6 +12,10 @@ namespace Sulu\Bundle\CommunityBundle\Mail; use Sulu\Bundle\SecurityBundle\Entity\User; +use Symfony\Component\Mailer\Mailer; +use Symfony\Component\Mailer\MailerInterface; +use Symfony\Component\Mime\Address; +use Symfony\Component\Mime\Email; use Symfony\Contracts\Translation\LocaleAwareInterface; use Symfony\Contracts\Translation\TranslatorInterface; use Twig\Environment; @@ -22,7 +26,7 @@ class MailFactory implements MailFactoryInterface { /** - * @var \Swift_Mailer + * @var MailerInterface|\Swift_Mailer */ protected $mailer; @@ -36,7 +40,7 @@ class MailFactory implements MailFactoryInterface */ protected $translator; - public function __construct(\Swift_Mailer $mailer, Environment $twig, TranslatorInterface $translator) + public function __construct($mailer, Environment $twig, TranslatorInterface $translator) { $this->mailer = $mailer; $this->twig = $twig; @@ -80,12 +84,49 @@ protected function sendEmail($from, $to, string $subject, string $template, arra { $body = $this->twig->render($template, $data); - $message = new \Swift_Message(); - $message->setSubject($this->translator->trans($subject)); - $message->setFrom($from); - $message->setTo($to); - $message->setBody($body, 'text/html'); + if ($this->mailer instanceof \Swift_Mailer) { + $email = $this->mailer->createMessage() + ->setSubject($this->translator->trans($subject)) + ->setFrom($from) + ->setTo($to) + ->setBody($body, 'text/html'); + } else { + if (!$this->getAddress($from) || !$this->getAddress($to)) { + return; + } - $this->mailer->send($message); + $email = (new Email()) + ->subject($this->translator->trans($subject)) + ->from($this->getAddress($from)) + ->to($this->getAddress($to)) + ->html($body); + } + + $this->mailer->send($email); + } + + /** + * Convert string/array email address to an Address object. + * + * @param mixed $address + */ + protected function getAddress($address): ?Address + { + $name = ''; + + if (\is_array($address)) { + if (empty($address)) { + return null; + } elseif (!isset($address['email'])) { + $email = $address[\array_keys($address)[0]]; + } else { + $email = $address['email']; + $name = $address['name'] ?? ''; + } + } else { + $email = $address; + } + + return new Address($email, $name); } } diff --git a/Manager/CommunityManager.php b/Manager/CommunityManager.php index 9e2a073f..a5471d4b 100644 --- a/Manager/CommunityManager.php +++ b/Manager/CommunityManager.php @@ -153,7 +153,6 @@ public function login(User $user, Request $request): void $token = new UsernamePasswordToken( $user, - null, $this->getConfigProperty(Configuration::FIREWALL), $user->getRoles() ); diff --git a/Manager/CommunityManagerInterface.php b/Manager/CommunityManagerInterface.php index f26e8a16..ff6beca8 100644 --- a/Manager/CommunityManagerInterface.php +++ b/Manager/CommunityManagerInterface.php @@ -34,7 +34,6 @@ * }, * delete_user: bool, * } - * * @phpstan-type Config array{ * from: string|string[], * to: string|string[], @@ -109,9 +108,9 @@ public function getConfig(): array; * * @param TConfig $property * - * @throws \InvalidArgumentException - * * @return Config[TTypeConfig] + * + * @throws \InvalidArgumentException */ public function getConfigProperty(string $property); @@ -124,9 +123,9 @@ public function getConfigProperty(string $property); * @param TConfig $type * @param TTypeConfigProperty $property * - * @throws \InvalidArgumentException - * * @return Config[TConfig][TTypeConfigProperty] + * + * @throws \InvalidArgumentException */ public function getConfigTypeProperty(string $type, string $property); @@ -137,8 +136,6 @@ public function sendEmails(string $type, User $user): void; /** * Save profile for given user. - * - * @return User */ public function saveProfile(User $user): ?User; } diff --git a/Tests/Application/Kernel.php b/Tests/Application/Kernel.php index d7cf5351..a72f8f28 100644 --- a/Tests/Application/Kernel.php +++ b/Tests/Application/Kernel.php @@ -35,7 +35,7 @@ public function registerContainerConfiguration(LoaderInterface $loader): void { parent::registerContainerConfiguration($loader); - $loader->load(__DIR__ . '/config/config_' . $this->getContext() . '.yml'); + $loader->load(__DIR__ . '/config/config.php'); } /** diff --git a/Tests/Application/config/config.php b/Tests/Application/config/config.php new file mode 100644 index 00000000..4fd9d10b --- /dev/null +++ b/Tests/Application/config/config.php @@ -0,0 +1,28 @@ +getParameter('sulu.context'); + + $loader->import('context_' . $context . '.yml'); + + if ('website' === $context) { + if (\version_compare(Kernel::VERSION, '6.0.0', '>=')) { + $loader->import('security-6.yml'); + } else { + $loader->import('security-5-4.yml'); + } + } +}; diff --git a/Tests/Application/config/config_admin.yml b/Tests/Application/config/context_admin.yml similarity index 81% rename from Tests/Application/config/config_admin.yml rename to Tests/Application/config/context_admin.yml index f5976990..32bb46b2 100644 --- a/Tests/Application/config/config_admin.yml +++ b/Tests/Application/config/context_admin.yml @@ -1,5 +1,5 @@ imports: - - config.yml + - { resource: sulu.yml } parameters: secret: test diff --git a/Tests/Application/config/context_website.yml b/Tests/Application/config/context_website.yml new file mode 100644 index 00000000..26d562ea --- /dev/null +++ b/Tests/Application/config/context_website.yml @@ -0,0 +1,9 @@ +imports: + - { resource: sulu.yml } + +framework: + router: { resource: '%kernel.project_dir%/config/routing_website.yml' } + profiler: { only_exceptions: false } + +sulu_test: + enable_test_user_provider: true diff --git a/Tests/Application/config/config_website.yml b/Tests/Application/config/security-5-4.yml similarity index 79% rename from Tests/Application/config/config_website.yml rename to Tests/Application/config/security-5-4.yml index 663a718c..d961f1e9 100644 --- a/Tests/Application/config/config_website.yml +++ b/Tests/Application/config/security-5-4.yml @@ -1,10 +1,3 @@ -imports: - - config.yml - -framework: - router: { resource: '%kernel.project_dir%/config/routing_website.yml' } - profiler: { only_exceptions: false } - security: access_decision_manager: strategy: affirmative @@ -34,6 +27,3 @@ security: logout: path: sulu_community.logout target: / - -sulu_test: - enable_test_user_provider: true diff --git a/Tests/Application/config/security-6.yml b/Tests/Application/config/security-6.yml new file mode 100644 index 00000000..9c354cc1 --- /dev/null +++ b/Tests/Application/config/security-6.yml @@ -0,0 +1,29 @@ +security: + access_decision_manager: + strategy: affirmative + + password_hashers: + Sulu\Bundle\SecurityBundle\Entity\User: 'plaintext' + + providers: + testprovider: + id: test_user_provider + + access_control: + - { path: /profile, roles: ROLE_USER } + - { path: /completion, roles: ROLE_USER } + + firewalls: + sulu-io: + pattern: ^/ + entry_point: http_basic + http_basic: + provider: testprovider + form_login: + login_path: sulu_community.login + check_path: sulu_community.login + default_target_path: sulu_community.profile + always_use_default_target_path: true + logout: + path: sulu_community.logout + target: / diff --git a/Tests/Application/config/config.yml b/Tests/Application/config/sulu.yml similarity index 86% rename from Tests/Application/config/config.yml rename to Tests/Application/config/sulu.yml index 204b8d49..c9cf83f6 100644 --- a/Tests/Application/config/config.yml +++ b/Tests/Application/config/sulu.yml @@ -1,5 +1,6 @@ -swiftmailer: - disable_delivery: true +framework: + mailer: + dsn: 'null://null' doctrine: orm: diff --git a/Tests/Functional/Controller/RegistrationTest.php b/Tests/Functional/Controller/RegistrationTest.php index 50201359..b57ac46b 100644 --- a/Tests/Functional/Controller/RegistrationTest.php +++ b/Tests/Functional/Controller/RegistrationTest.php @@ -22,10 +22,10 @@ use Sulu\Bundle\TestBundle\Testing\SuluTestCase; use Sulu\Component\HttpKernel\SuluKernel; use Symfony\Bundle\FrameworkBundle\KernelBrowser; -use Symfony\Bundle\SwiftmailerBundle\DataCollector\MessageDataCollector; use Symfony\Component\DomCrawler\Crawler; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpKernel\Profiler\Profile; +use Symfony\Component\Mime\RawMessage; /** * This testcases covers the whole registration, confirmation and login process. @@ -113,6 +113,13 @@ public function testConfirmation(): User public function testLogin(): void { $this->testConfirmation(); + $user = $this->findUser(); + + if ($user) { + $user->setSalt(''); + $user->setPassword('my-sulu'); + $this->getEntityManager()->flush(); + } $crawler = $this->client->request('GET', '/login'); $this->assertHttpStatusCode(200, $this->client->getResponse()); @@ -181,8 +188,12 @@ public function testRegistrationBlacklistedBlocked(): void $this->assertNull($this->findUser()); } - public function testRegistrationBlacklistedRequested(): \Swift_Message + public function testRegistrationBlacklistedRequested(): ?RawMessage { + if (\class_exists(\Swift_Mailer::class)) { + $this->markTestSkipped('Skip test for swift mailer.'); + } + $this->createBlacklistItem($this->getEntityManager(), '*@sulu.io', BlacklistItem::TYPE_REQUEST); $crawler = $this->client->request('GET', '/registration'); @@ -209,11 +220,10 @@ public function testRegistrationBlacklistedRequested(): \Swift_Message $profile = $this->client->getProfile(); $this->assertNotFalse($profile, 'Could not found response profile, is profiler activated?'); - /** @var MessageDataCollector $mailCollector */ - $mailCollector = $profile->getCollector('swiftmailer'); - $this->assertSame(1, $mailCollector->getMessageCount()); - $message = $mailCollector->getMessages()[0]; - $this->assertSame('admin@localhost', \key($message->getTo())); + $this->assertEmailCount(1); + + $message = $this->getMailerMessage(); + $this->assertSame('admin@localhost', $message->getTo()[0]->getAddress()); return $message; } @@ -223,7 +233,7 @@ public function testBlacklistConfirm(): void $message = $this->testRegistrationBlacklistedRequested(); $emailCrawler = new Crawler(); - $emailCrawler->addContent($message->getBody()); + $emailCrawler->addContent($message->getHtmlBody()); $links = $emailCrawler->filter('a'); $firstLink = $links->first()->attr('href'); @@ -241,11 +251,10 @@ public function testBlacklistConfirm(): void $profile = $this->client->getProfile(); $this->assertNotFalse($profile, 'Could not found response profile, is profiler activated?'); - /** @var MessageDataCollector $mailCollector */ - $mailCollector = $profile->getCollector('swiftmailer'); - $this->assertSame(1, $mailCollector->getMessageCount()); - $message = $mailCollector->getMessages()[0]; - $this->assertSame('hikaru@sulu.io', \key($message->getTo())); + $this->assertEmailCount(1); + + $message = $this->getMailerMessage(); + $this->assertSame('hikaru@sulu.io', $message->getTo()[0]->getAddress()); } public function testBlacklistBlocked(): void @@ -253,7 +262,7 @@ public function testBlacklistBlocked(): void $message = $this->testRegistrationBlacklistedRequested(); $emailCrawler = new Crawler(); - $emailCrawler->addContent($message->getBody()); + $emailCrawler->addContent($message->getHtmlBody()); $links = $emailCrawler->filter('a'); $lastLink = $links->last()->attr('href'); @@ -271,13 +280,15 @@ public function testBlacklistBlocked(): void $profile = $this->client->getProfile(); $this->assertNotFalse($profile, 'Could not found response profile, is profiler activated?'); - /** @var MessageDataCollector $mailCollector */ - $mailCollector = $profile->getCollector('swiftmailer'); - $this->assertSame(0, $mailCollector->getMessageCount()); + $this->assertEmailCount(0); } public function testPasswordForget(): void { + if (\class_exists(\Swift_Mailer::class)) { + $this->markTestSkipped('Skip test for swift mailer.'); + } + $user = $this->testConfirmation(); $crawler = $this->client->request('GET', '/password-forget'); @@ -298,14 +309,13 @@ public function testPasswordForget(): void $profile = $this->client->getProfile(); $this->assertNotFalse($profile, 'Could not found response profile, is profiler activated?'); - /** @var MessageDataCollector $mailCollector */ - $mailCollector = $profile->getCollector('swiftmailer'); - $this->assertSame(1, $mailCollector->getMessageCount()); - $message = $mailCollector->getMessages()[0]; - $this->assertSame('hikaru@sulu.io', \key($message->getTo())); + $this->assertEmailCount(1); + + $message = $this->getMailerMessage(); + $this->assertSame('hikaru@sulu.io', $message->getTo()[0]->getAddress()); $emailCrawler = new Crawler(); - $emailCrawler->addContent($message->getBody()); + $emailCrawler->addContent($message->getHtmlBody()); $links = $emailCrawler->filter('a'); $firstLink = $links->first()->attr('href'); @@ -325,10 +335,10 @@ public function testPasswordForget(): void ); $this->client->submit($form); - $this->getEntityManager()->clear(); + //$this->getEntityManager()->clear(); /** @var User $user */ - $user = $this->findUser(); + $user = $this->findUser('sulu'); $password = $user->getPassword(); $this->assertNotNull($password); $this->assertStringStartsWith('my-new-password', $password); @@ -339,14 +349,12 @@ public function testPasswordForget(): void */ private function findUser(string $username = 'sulu'): ?User { - // clear entity-manager to ensure newest user - $this->getEntityManager()->clear(); - $repository = $this->getContainer()->get('sulu.repository.user'); try { /** @var User $user */ $user = $repository->findUserByUsername($username); + $this->getEntityManager()->refresh($user); return $user; } catch (NoResultException $exception) { diff --git a/Tests/Functional/Entity/BlacklistItemRepositoryTest.php b/Tests/Functional/Entity/BlacklistItemRepositoryTest.php index 6f9e2016..2bd84c90 100644 --- a/Tests/Functional/Entity/BlacklistItemRepositoryTest.php +++ b/Tests/Functional/Entity/BlacklistItemRepositoryTest.php @@ -39,7 +39,7 @@ public function testFindBySender(): void $entityManager->clear(); $items = \array_map( - function (BlacklistItem $item) { + function(BlacklistItem $item) { return ['pattern' => $item->getPattern(), 'type' => $item->getType()]; }, $repository->findBySender('test@sulu.io') diff --git a/Tests/Unit/Listener/BlacklistListenerTest.php b/Tests/Unit/Listener/BlacklistListenerTest.php index a80079f4..b36e9349 100644 --- a/Tests/Unit/Listener/BlacklistListenerTest.php +++ b/Tests/Unit/Listener/BlacklistListenerTest.php @@ -92,7 +92,7 @@ public function testValidateEmail(): void $this->entityManager->persist( Argument::that( - function (BlacklistUser $item) use ($user) { + function(BlacklistUser $item) use ($user) { return '123-123-123' === $item->getToken() && 'sulu-io' === $item->getWebspaceKey() && $item->getUser() === $user->reveal(); diff --git a/Tests/Unit/Listener/EmailConfirmationListenerTest.php b/Tests/Unit/Listener/EmailConfirmationListenerTest.php index 7da5a115..95d61be6 100644 --- a/Tests/Unit/Listener/EmailConfirmationListenerTest.php +++ b/Tests/Unit/Listener/EmailConfirmationListenerTest.php @@ -114,7 +114,7 @@ public function testSendConfirmation(): void $this->entityManager->persist( Argument::that( - function (EmailConfirmationToken $token) { + function(EmailConfirmationToken $token) { return '123-123-123' === $token->getToken() && $token->getUser() === $this->user->reveal(); } ) diff --git a/Tests/Unit/Mail/MailFactoryTest.php b/Tests/Unit/Mail/MailFactoryTest.php index e2c7f3a5..6712b334 100644 --- a/Tests/Unit/Mail/MailFactoryTest.php +++ b/Tests/Unit/Mail/MailFactoryTest.php @@ -17,13 +17,15 @@ use Sulu\Bundle\CommunityBundle\Mail\Mail; use Sulu\Bundle\CommunityBundle\Mail\MailFactory; use Sulu\Bundle\SecurityBundle\Entity\User; +use Symfony\Component\Mailer\MailerInterface; +use Symfony\Component\Mime\Email; use Symfony\Component\Translation\Translator; use Twig\Environment; class MailFactoryTest extends TestCase { /** - * @var ObjectProphecy<\Swift_Mailer> + * @var ObjectProphecy */ private $mailer; @@ -49,10 +51,11 @@ class MailFactoryTest extends TestCase protected function setUp(): void { - $this->mailer = $this->prophesize(\Swift_Mailer::class); + $this->mailer = $this->prophesize(MailerInterface::class); $this->twig = $this->prophesize(Environment::class); $this->translator = $this->prophesize(Translator::class); $this->translator->getLocale()->willReturn('en'); + $this->translator->trans('testcase')->willReturn('Test case'); $this->user = $this->prophesize(User::class); $this->user->getEmail()->willReturn('test@example.com'); $this->user->getLocale()->willReturn('de'); @@ -70,22 +73,18 @@ public function testSendEmails(): void $this->twig->render('admin-template', Argument::any())->willReturn('Admin-Template'); $this->mailer->send( - Argument::that( - function (\Swift_Message $message) { - return 'User-Template' === $message->getBody() - && $message->getFrom() === ['test@sulu.io' => null] - && $message->getTo() === ['test@example.com' => null]; - } - ) + (new Email()) + ->subject('Test case') + ->from('test@sulu.io') + ->to('test@example.com') + ->html('User-Template') )->shouldBeCalledTimes(1); $this->mailer->send( - Argument::that( - function (\Swift_Message $message) { - return 'Admin-Template' === $message->getBody() - && $message->getFrom() === ['test@sulu.io' => null] - && $message->getTo() === ['user@sulu.io' => null]; - } - ) + (new Email()) + ->subject('Test case') + ->from('test@sulu.io') + ->to('user@sulu.io') + ->html('Admin-Template') )->shouldBeCalledTimes(1); $mail = new Mail('test@sulu.io', 'user@sulu.io', 'testcase', 'user-template', 'admin-template'); @@ -100,22 +99,18 @@ public function testSendEmailsNoAdminTemplate(): void $this->twig->render('admin-template', Argument::any())->willReturn('Admin-Template'); $this->mailer->send( - Argument::that( - function (\Swift_Message $message) { - return 'User-Template' === $message->getBody() - && $message->getFrom() === ['test@sulu.io' => null] - && $message->getTo() === ['test@example.com' => null]; - } - ) + (new Email()) + ->subject('Test case') + ->from('test@sulu.io') + ->to('test@example.com') + ->html('User-Template') )->shouldBeCalledTimes(1); $this->mailer->send( - Argument::that( - function (\Swift_Message $message) { - return 'Admin-Template' === $message->getBody() - && $message->getFrom() === ['test@sulu.io' => null] - && $message->getTo() === ['user@sulu.io' => null]; - } - ) + (new Email()) + ->subject('Test case') + ->from('test@sulu.io') + ->to('user@sulu.io') + ->html('Admin-Template') )->shouldNotBeCalled(); $mail = new Mail('test@sulu.io', 'user@sulu.io', 'testcase', 'user-template', null); @@ -130,22 +125,18 @@ public function testSendEmailsNoUserTemplate(): void $this->twig->render('admin-template', Argument::any())->willReturn('Admin-Template'); $this->mailer->send( - Argument::that( - function (\Swift_Message $message) { - return 'User-Template' === $message->getBody() - && $message->getFrom() === ['test@sulu.io' => null] - && $message->getTo() === ['test@example.com' => null]; - } - ) + (new Email()) + ->subject('Test case') + ->from('test@sulu.io') + ->to('test@example.com') + ->html('User-Template') )->shouldNotBeCalled(); $this->mailer->send( - Argument::that( - function (\Swift_Message $message) { - return 'Admin-Template' === $message->getBody() - && $message->getFrom() === ['test@sulu.io' => null] - && $message->getTo() === ['user@sulu.io' => null]; - } - ) + (new Email()) + ->subject('Test case') + ->from('test@sulu.io') + ->to('user@sulu.io') + ->html('Admin-Template') )->shouldBeCalledTimes(1); $mail = new Mail('test@sulu.io', 'user@sulu.io', 'testcase', null, 'admin-template'); diff --git a/Tests/phpstan/object-manager.php b/Tests/phpstan/object-manager.php index 8a0eff18..596d140d 100644 --- a/Tests/phpstan/object-manager.php +++ b/Tests/phpstan/object-manager.php @@ -34,7 +34,7 @@ // this is a workaround for the following phpstan issue: https://github.com/phpstan/phpstan-doctrine/issues/98 $resolveTargetEntityListener = \current(\array_filter( $objectManager->getEventManager()->getListeners('loadClassMetadata'), - static function ($listener) { + static function($listener) { return $listener instanceof ResolveTargetEntityListener; } )); diff --git a/composer.json b/composer.json index 0d6bbec9..24ad0f83 100644 --- a/composer.json +++ b/composer.json @@ -8,44 +8,50 @@ "beberlei/doctrineextensions": "^1.0", "doctrine/doctrine-bundle": "^1.10 || ^2.0", "doctrine/orm": "^2.5.3", - "doctrine/persistence": "^1.3 || ^2.0", + "doctrine/persistence": "^1.3 || ^2.0 || ^3.0", + "doctrine/phpcr-bundle": "^2 || ^3.0", "jms/serializer-bundle": "^3.3 || ^4.0", "massive/build-bundle": "^0.3 || ^0.4 || ^0.5", - "sulu/sulu": "^2.0.6 || ^2.5@dev", - "symfony/config": "^4.4 || ^5.0", - "symfony/console": "^4.4 || ^5.0", - "symfony/dependency-injection": "^4.4 || ^5.0", - "symfony/event-dispatcher": "^4.4 || ^5.0", - "symfony/form": "^4.4 || ^5.0", - "symfony/framework-bundle": "^4.4 || ^5.0", - "symfony/http-foundation": "^4.4 || ^5.0", - "symfony/http-kernel": "^4.4 || ^5.0", - "symfony/intl": "^4.4 || ^5.0", - "symfony/routing": "^4.4 || ^5.0", - "symfony/security-bundle": "^4.4 || ^5.0", - "symfony/swiftmailer-bundle": "^3.1.4" + "sulu/sulu": "^2.4.0 || ^2.6@dev", + "symfony/config": "^5.4 || ^6.2", + "symfony/console": "^5.4 || ^6.2", + "symfony/dependency-injection": "^5.4 || ^6.2", + "symfony/event-dispatcher": "^5.4 || ^6.2", + "symfony/form": "^5.4 || ^6.2", + "symfony/framework-bundle": "^5.4 || ^6.2", + "symfony/http-foundation": "^5.4 || ^6.2", + "symfony/http-kernel": "^5.4 || ^6.2", + "symfony/intl": "^5.4 || ^6.2", + "symfony/mailer": "^5.4 || ^6.2", + "symfony/routing": "^5.4 || ^6.2", + "symfony/security-bundle": "^5.4 || ^6.2" }, "require-dev": { "doctrine/data-fixtures": "^1.3.3", + "friendsofsymfony/jsrouting-bundle": "^2.6 || ^3.0", "handcraftedinthealps/zendsearch": "^2.0", "jackalope/jackalope-doctrine-dbal": "^1.3.4", "jangregor/phpstan-prophecy": "^1.0", "massive/search-bundle": "^2.0", "php-cs-fixer/shim": "^3.9", + "phpspec/prophecy": "^1.16", "phpstan/phpstan": "1.0", "phpstan/phpstan-doctrine": "^1.0", "phpstan/phpstan-phpunit": "^1.0", "phpstan/phpstan-symfony": "^1.0", "phpunit/phpunit": "^8.2", - "symfony/browser-kit": "^4.4 || ^5.0", - "symfony/css-selector": "^4.4 || ^5.0", - "symfony/dotenv": "^4.4 || ^5.0", + "symfony/browser-kit": "^5.4 || ^6.2", + "symfony/css-selector": "^5.4 || ^6.2", + "symfony/dotenv": "^5.4 || ^6.2", "symfony/monolog-bundle": "^3.1", - "symfony/phpunit-bridge": "^4.4 || ^5.0", - "symfony/stopwatch": "^4.4 || ^5.0", - "symfony/var-dumper": "^4.4 || ^5.0", + "symfony/phpunit-bridge": "^5.4 || ^6.2", + "symfony/stopwatch": "^5.4 || ^6.2", + "symfony/var-dumper": "^5.4 || ^6.2", "thecodingmachine/phpstan-strict-rules": "^1.0" }, + "conflict": { + "dantleech/phpcr-migrations-bundle": "<1.2.0" + }, "keywords": [ "registration", "login", @@ -111,6 +117,9 @@ ] }, "config": { - "sort-packages": true + "sort-packages": true, + "allow-plugins": { + "php-http/discovery": true + } } } diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 3c960b48..f35cf768 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,5 +1,25 @@ parameters: ignoreErrors: + - + message: "#^Else branch is unreachable because previous condition is always true\\.$#" + count: 1 + path: Command/InitCommand.php + + - + message: "#^Call to method encodePassword\\(\\) on an unknown class Symfony\\\\Component\\\\Security\\\\Core\\\\Encoder\\\\UserPasswordEncoderInterface\\.$#" + count: 1 + path: Controller/AbstractController.php + + - + message: "#^Method Sulu\\\\Bundle\\\\CommunityBundle\\\\Controller\\\\AbstractController\\:\\:getSubscribedServices\\(\\) should return array\\ but returns array\\\\.$#" + count: 1 + path: Controller/AbstractController.php + + - + message: "#^PHPDoc tag @var for variable \\$encoder contains unknown class Symfony\\\\Component\\\\Security\\\\Core\\\\Encoder\\\\UserPasswordEncoderInterface\\.$#" + count: 1 + path: Controller/AbstractController.php + - message: "#^Strict comparison using \\=\\=\\= between true and array\\{from\\: array\\\\|string, to\\: array\\\\|string, webspace_key\\: string, role\\: string, firewall\\: string, maintenance\\: array\\{enabled\\: bool, template\\: string\\}, login\\: array\\{enabled\\: bool, template\\: string, service\\: string\\|null, embed_template\\: string, type\\: string, options\\: array, activate_user\\: bool, auto_login\\: bool, \\.\\.\\.\\}, registration\\: array\\{enabled\\: bool, template\\: string, service\\: string\\|null, embed_template\\: string, type\\: string, options\\: array, activate_user\\: bool, auto_login\\: bool, \\.\\.\\.\\}, \\.\\.\\.\\} will always evaluate to false\\.$#" count: 1 @@ -250,6 +270,31 @@ parameters: count: 1 path: EventListener/MailListener.php + - + message: "#^Call to method createMessage\\(\\) on an unknown class Swift_Mailer\\.$#" + count: 1 + path: Mail/MailFactory.php + + - + message: "#^Call to method send\\(\\) on an unknown class Swift_Mailer\\.$#" + count: 1 + path: Mail/MailFactory.php + + - + message: "#^Class Swift_Mailer not found\\.$#" + count: 1 + path: Mail/MailFactory.php + + - + message: "#^Method Sulu\\\\Bundle\\\\CommunityBundle\\\\Mail\\\\MailFactory\\:\\:__construct\\(\\) has parameter \\$mailer with no type specified\\.$#" + count: 1 + path: Mail/MailFactory.php + + - + message: "#^Property Sulu\\\\Bundle\\\\CommunityBundle\\\\Mail\\\\MailFactory\\:\\:\\$mailer has unknown class Swift_Mailer as its type\\.$#" + count: 1 + path: Mail/MailFactory.php + - message: "#^Method Sulu\\\\Bundle\\\\CommunityBundle\\\\Manager\\\\CommunityManager\\:\\:getConfigProperty\\(\\) should return array\\{from\\: array\\\\|string, to\\: array\\\\|string, webspace_key\\: string, role\\: string, firewall\\: string, maintenance\\: array\\{enabled\\: bool, template\\: string\\}, login\\: array\\{enabled\\: bool, template\\: string, service\\: string\\|null, embed_template\\: string, type\\: string, options\\: array, activate_user\\: bool, auto_login\\: bool, \\.\\.\\.\\}, registration\\: array\\{enabled\\: bool, template\\: string, service\\: string\\|null, embed_template\\: string, type\\: string, options\\: array, activate_user\\: bool, auto_login\\: bool, \\.\\.\\.\\}, \\.\\.\\.\\} but returns array\\\\|string\\.$#" count: 1 @@ -275,6 +320,11 @@ parameters: count: 1 path: Manager/CommunityManager.php + - + message: "#^Parameter \\#2 \\$firewallName of class Symfony\\\\Component\\\\Security\\\\Core\\\\Authentication\\\\Token\\\\UsernamePasswordToken constructor expects string, array\\\\|string\\> given\\.$#" + count: 1 + path: Manager/CommunityManager.php + - message: "#^Parameter \\#2 \\$to of static method Sulu\\\\Bundle\\\\CommunityBundle\\\\Mail\\\\Mail\\:\\:create\\(\\) expects array\\\\|string, array\\\\|string\\> given\\.$#" count: 1 @@ -291,7 +341,17 @@ parameters: path: Manager/CommunityManager.php - - message: "#^Parameter \\#3 \\$roles of class Symfony\\\\Component\\\\Security\\\\Core\\\\Authentication\\\\Token\\\\UsernamePasswordToken constructor expects array\\, array\\\\|string\\> given\\.$#" + message: "#^If condition is always true\\.$#" count: 1 - path: Manager/CommunityManager.php + path: Tests/Application/config/config.php + + - + message: "#^Cannot call method getHtmlBody\\(\\) on Symfony\\\\Component\\\\Mime\\\\RawMessage\\|null\\.$#" + count: 3 + path: Tests/Functional/Controller/RegistrationTest.php + + - + message: "#^Cannot call method getTo\\(\\) on Symfony\\\\Component\\\\Mime\\\\RawMessage\\|null\\.$#" + count: 3 + path: Tests/Functional/Controller/RegistrationTest.php