16 | */
17 |
18 | use Silverback\ApiComponentsBundle\HttpCache\HttpCachePurger;
19 | use Symfony\Component\DependencyInjection\ContainerInterface;
20 | use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
21 | use Symfony\Component\DependencyInjection\Reference;
22 |
23 | return static function (ContainerConfigurator $configurator) {
24 | $services = $configurator->services();
25 |
26 | $services
27 | ->set('silverback.api_components.http_cache.purger')
28 | ->class(HttpCachePurger::class)
29 | ->args([
30 | new Reference('api_platform.iri_converter'),
31 | new Reference('api_platform.resource_class_resolver'),
32 | new Reference('api_platform.http_cache.purger', ContainerInterface::NULL_ON_INVALID_REFERENCE),
33 | ])
34 | ->tag('silverback_api_components.resource_changed_propagator');
35 | $services->alias(HttpCachePurger::class, 'silverback.api_components.http_cache.purger');
36 | };
37 |
--------------------------------------------------------------------------------
/src/Resources/views/assets/css/emails.css:
--------------------------------------------------------------------------------
1 | .logo {
2 | width: auto;
3 | max-height: 140px;
4 | max-width: 100%;
5 | }
6 |
--------------------------------------------------------------------------------
/src/Resources/views/assets/images/email_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/components-web-app/api-components-bundle/2ce9f6b83caffaba9f4ec1ecdb460d672cde9c1d/src/Resources/views/assets/images/email_logo.png
--------------------------------------------------------------------------------
/src/Resources/views/emails/_signature.html.twig:
--------------------------------------------------------------------------------
1 | Many thanks, {{ website_name }} Team
2 |
--------------------------------------------------------------------------------
/src/Resources/views/emails/_template.html.twig:
--------------------------------------------------------------------------------
1 | {% apply inky_to_html|inline_css(source('@SilverbackApiComponents/assets/css/foundation-emails.css'), source('@SilverbackApiComponents/assets/css/emails.css')) %}
2 |
3 |
13 |
14 |
15 |
16 |
17 | {% block content %}{% endblock %}
18 |
19 |
20 |
21 |
22 | {% endapply %}
23 |
--------------------------------------------------------------------------------
/src/Resources/views/emails/user_change_email_confirmation.html.twig:
--------------------------------------------------------------------------------
1 | {% extends "@SilverbackApiComponents/emails/_template.html.twig" %}
2 |
3 | {% block content %}
4 | Confirm Your New Email Address
5 |
6 | Hello {{ user.username }},
7 | It looks like you have requested to change your email address to {{ user.newEmailAddress }}.
8 | Click the link below to complete the request and change your email address.
9 | Confirm Email Address Change
10 | {% include "@SilverbackApiComponents/emails/_signature.html.twig" %}
11 | {% endblock %}
12 |
--------------------------------------------------------------------------------
/src/Resources/views/emails/user_enabled.html.twig:
--------------------------------------------------------------------------------
1 | {% extends "@SilverbackApiComponents/emails/_template.html.twig" %}
2 |
3 | {% block content %}
4 | Your Account Has Been Enabled
5 |
6 |
7 |
8 | Hello {{ user.username }},
9 | We have reviewed your website registration and your account has now been enabled. You can sign in with the password you provided during the sign up process.
10 | Login
11 | {% include "@SilverbackApiComponents/emails/_signature.html.twig" %}
12 | {% endblock %}
13 |
--------------------------------------------------------------------------------
/src/Resources/views/emails/user_password_changed.html.twig:
--------------------------------------------------------------------------------
1 | {% extends "@SilverbackApiComponents/emails/_template.html.twig" %}
2 |
3 | {% block content %}
4 | Your Password Has Been Updated
5 |
6 |
7 |
8 | Hello {{ user.username }},
9 | Your password has just been updated. If this was you no further action is required. If this was not you please use the forgot password feature to reset your password.
10 | {% include "@SilverbackApiComponents/emails/_signature.html.twig" %}
11 | {% endblock %}
12 |
--------------------------------------------------------------------------------
/src/Resources/views/emails/user_password_reset.html.twig:
--------------------------------------------------------------------------------
1 | {% extends "@SilverbackApiComponents/emails/_template.html.twig" %}
2 |
3 | {% block content %}
4 | Forgot Your Password?
5 |
6 | Hello {{ user.username }},
7 | We understand you've forgotten your password. Don't worry, it happens.
8 | Click the link below to reset your password.
9 | Reset Password
10 | {% include "@SilverbackApiComponents/emails/_signature.html.twig" %}
11 | {% endblock %}
12 |
--------------------------------------------------------------------------------
/src/Resources/views/emails/user_username_changed.html.twig:
--------------------------------------------------------------------------------
1 | {% extends "@SilverbackApiComponents/emails/_template.html.twig" %}
2 |
3 | {% block content %}
4 | Your Email Address Has Been Changed
5 |
6 |
7 |
8 | Hello {{ user.username }},
9 | Your email address has been successfully updated. If this was not you please get in touch to restore access to your account.
10 | {% include "@SilverbackApiComponents/emails/_signature.html.twig" %}
11 | {% endblock %}
12 |
--------------------------------------------------------------------------------
/src/Resources/views/emails/user_verify_email.html.twig:
--------------------------------------------------------------------------------
1 | {% extends "@SilverbackApiComponents/emails/_template.html.twig" %}
2 |
3 | {% block content %}
4 | Verify Your Email Address
5 |
6 | Hello {{ user.username }},
7 | It looks like you have requested to change your email address to this one.
8 | Click the link below to verify this is the correct email address.
9 | Verify Email Address
10 | {% include "@SilverbackApiComponents/emails/_signature.html.twig" %}
11 | {% endblock %}
12 |
--------------------------------------------------------------------------------
/src/Resources/views/emails/user_welcome.html.twig:
--------------------------------------------------------------------------------
1 | {% extends "@SilverbackApiComponents/emails/_template.html.twig" %}
2 |
3 | {% block content %}
4 | Welcome to {{ website_name }}
5 |
6 | Hello {{ user.username }},
7 | Thank you for signing up to {{ website_name }}.
8 | {% if redirect_url is defined and redirect_url is not null %}
9 | Please click the link below to verify your email address.
10 | Verify Email Address
11 | {% endif %}
12 | {% include "@SilverbackApiComponents/emails/_signature.html.twig" %}
13 | {% endblock %}
14 |
--------------------------------------------------------------------------------
/src/Security/EventListener/AccessDeniedListener.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Silverback\ApiComponentsBundle\Security\EventListener;
15 |
16 | use Lexik\Bundle\JWTAuthenticationBundle\Response\JWTAuthenticationFailureResponse;
17 | use Silverback\ApiComponentsBundle\EventListener\Api\ApiEventListenerTrait;
18 | use Silverback\ApiComponentsBundle\Mercure\MercureAuthorization;
19 | use Symfony\Component\HttpKernel\Event\ResponseEvent;
20 |
21 | class AccessDeniedListener
22 | {
23 | use ApiEventListenerTrait;
24 |
25 | public function __construct(
26 | private readonly MercureAuthorization $mercureAuthorization,
27 | ) {
28 | }
29 |
30 | public function onPostRespond(ResponseEvent $event): void
31 | {
32 | $request = $event->getRequest();
33 | $attributes = $this->getAttributes($request);
34 | if (
35 | !($operation = $attributes['operation'] ?? null)
36 | || 'me' !== $operation->getName()
37 | || !($response = $event->getResponse()) instanceof JWTAuthenticationFailureResponse) {
38 | return;
39 | }
40 | $response->headers->setCookie($this->mercureAuthorization->getClearAuthorizationCookie());
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/Security/EventListener/LogoutListener.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Silverback\ApiComponentsBundle\Security\EventListener;
15 |
16 | use Lexik\Bundle\JWTAuthenticationBundle\Security\Http\Cookie\JWTCookieProvider;
17 | use Silverback\ApiComponentsBundle\Mercure\MercureAuthorization;
18 | use Silverback\ApiComponentsBundle\RefreshToken\Storage\RefreshTokenStorageInterface;
19 | use Symfony\Component\HttpFoundation\Response;
20 | use Symfony\Component\Security\Http\Event\LogoutEvent;
21 |
22 | /**
23 | * @author Daniel West
24 | */
25 | class LogoutListener
26 | {
27 | public function __construct(
28 | private readonly RefreshTokenStorageInterface $storage,
29 | private readonly JWTCookieProvider $cookieProvider,
30 | private readonly MercureAuthorization $mercureAuthorization,
31 | ) {
32 | }
33 |
34 | public function __invoke(LogoutEvent $event): void
35 | {
36 | $this->storage->expireAll($event->getToken()->getUser());
37 | $response = $event->getResponse() ?? new Response();
38 | $response->headers->setCookie($this->cookieProvider->createCookie('x.x.x', null, 1));
39 | $response->headers->setCookie($this->mercureAuthorization->getClearAuthorizationCookie());
40 | $response->headers->remove('Location');
41 | $response->setStatusCode(Response::HTTP_OK)->setContent('');
42 | $event->setResponse($response);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Security/TokenGenerator.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Silverback\ApiComponentsBundle\Security;
15 |
16 | /**
17 | * @author Daniel West
18 | */
19 | class TokenGenerator
20 | {
21 | public static function generateToken(int $length = 16): string
22 | {
23 | return bin2hex(random_bytes($length));
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Security/Voter/AbstractRoutableVoter.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Silverback\ApiComponentsBundle\Security\Voter;
15 |
16 | use Silverback\ApiComponentsBundle\Entity\Core\RoutableInterface;
17 | use Symfony\Component\Security\Core\Authorization\Voter\Voter;
18 |
19 | /**
20 | * @author Daniel West
21 | */
22 | abstract class AbstractRoutableVoter extends Voter
23 | {
24 | public const READ_ROUTABLE = 'read_routable';
25 |
26 | protected function supports(string $attribute, $subject): bool
27 | {
28 | if (self::READ_ROUTABLE !== $attribute) {
29 | return false;
30 | }
31 | if (!$subject instanceof RoutableInterface) {
32 | throw new \InvalidArgumentException(\sprintf('$subject must be of type `%s`', RoutableInterface::class));
33 | }
34 |
35 | return true;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Security/Voter/RouteVoter.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Silverback\ApiComponentsBundle\Security\Voter;
15 |
16 | use ApiPlatform\Metadata\ResourceAccessCheckerInterface;
17 | use Silverback\ApiComponentsBundle\Entity\Core\Route;
18 | use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
19 | use Symfony\Component\Security\Core\Authorization\Voter\Voter;
20 |
21 | /**
22 | * @author Daniel West
23 | */
24 | class RouteVoter extends Voter
25 | {
26 | public const READ_ROUTE = 'read_route';
27 | private ?array $config;
28 | private ResourceAccessCheckerInterface $resourceAccessChecker;
29 |
30 | public function __construct(?array $config, ResourceAccessCheckerInterface $resourceAccessChecker)
31 | {
32 | $this->config = $config;
33 | $this->resourceAccessChecker = $resourceAccessChecker;
34 | }
35 |
36 | protected function supports($attribute, $subject): bool
37 | {
38 | return self::READ_ROUTE === $attribute && $subject instanceof Route && $this->config;
39 | }
40 |
41 | /**
42 | * @param Route $subject
43 | */
44 | protected function voteOnAttribute($attribute, $subject, TokenInterface $token): bool
45 | {
46 | foreach ($this->config as $routeConfig) {
47 | $routeRegex = str_replace('\*', '(.*)', preg_quote($routeConfig['route'], '#'));
48 | if (!$this->resourceAccessChecker->isGranted($subject::class, $routeConfig['security']) && preg_match(\sprintf('#%s#', $routeRegex), $subject->getPath())) {
49 | return false;
50 | }
51 | }
52 |
53 | return true;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/Security/Voter/SiteConfigParameterVoter.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Silverback\ApiComponentsBundle\Security\Voter;
15 |
16 | use Silverback\ApiComponentsBundle\Entity\Core\Route;
17 | use Silverback\ApiComponentsBundle\Entity\Core\SiteConfigParameter;
18 | use Symfony\Component\ExpressionLanguage\Expression;
19 | use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
20 | use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
21 | use Symfony\Component\Security\Core\Authorization\Voter\Voter;
22 | use Symfony\Component\Security\Core\Exception\AuthenticationCredentialsNotFoundException;
23 |
24 | /**
25 | * @author Daniel West
26 | */
27 | class SiteConfigParameterVoter extends Voter
28 | {
29 | public const NAME = 'read_site_config';
30 |
31 | public function __construct(private readonly string $permission, private readonly AuthorizationCheckerInterface $authorizationChecker)
32 | {
33 | }
34 |
35 | protected function supports($attribute, $subject): bool
36 | {
37 | return self::NAME === $attribute && $subject instanceof SiteConfigParameter && $this->permission;
38 | }
39 |
40 | /**
41 | * @param Route $subject
42 | */
43 | protected function voteOnAttribute($attribute, $subject, TokenInterface $token): bool
44 | {
45 | return $this->isGranted();
46 | }
47 |
48 | private function isGranted(): bool
49 | {
50 | try {
51 | return $this->authorizationChecker->isGranted(new Expression($this->permission));
52 | } catch (AuthenticationCredentialsNotFoundException $e) {
53 | return false;
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/src/Serializer/ContextBuilder/TimestampedContextBuilder.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Silverback\ApiComponentsBundle\Serializer\ContextBuilder;
15 |
16 | use ApiPlatform\State\SerializerContextBuilderInterface;
17 | use Silverback\ApiComponentsBundle\Serializer\MappingLoader\TimestampedLoader;
18 | use Symfony\Component\HttpFoundation\Request;
19 |
20 | /**
21 | * @author Daniel West
22 | */
23 | final class TimestampedContextBuilder implements SerializerContextBuilderInterface
24 | {
25 | private SerializerContextBuilderInterface $decorated;
26 |
27 | public function __construct(SerializerContextBuilderInterface $decorated)
28 | {
29 | $this->decorated = $decorated;
30 | }
31 |
32 | public function createFromRequest(Request $request, bool $normalization, ?array $extractedAttributes = null): array
33 | {
34 | $context = $this->decorated->createFromRequest($request, $normalization, $extractedAttributes);
35 |
36 | if (empty($resourceClass = $context['resource_class']) || empty($context['groups']) || \in_array('Route:manifest:read', $context['groups'], true)) {
37 | return $context;
38 | }
39 |
40 | $reflectionClass = new \ReflectionClass($resourceClass);
41 | if ($normalization) {
42 | $context['groups'][] = \sprintf('%s:%s:read', $reflectionClass->getShortName(), TimestampedLoader::GROUP_NAME);
43 | } else {
44 | $context['groups'][] = \sprintf('%s:%s:write', $reflectionClass->getShortName(), TimestampedLoader::GROUP_NAME);
45 | }
46 |
47 | return $context;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Serializer/ContextBuilder/UploadableContextBuilder.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Silverback\ApiComponentsBundle\Serializer\ContextBuilder;
15 |
16 | use ApiPlatform\State\SerializerContextBuilderInterface;
17 | use Silverback\ApiComponentsBundle\Serializer\MappingLoader\UploadableLoader;
18 | use Symfony\Component\HttpFoundation\Request;
19 |
20 | /**
21 | * @author Daniel West
22 | */
23 | final class UploadableContextBuilder implements SerializerContextBuilderInterface
24 | {
25 | private SerializerContextBuilderInterface $decorated;
26 |
27 | public function __construct(SerializerContextBuilderInterface $decorated)
28 | {
29 | $this->decorated = $decorated;
30 | }
31 |
32 | public function createFromRequest(Request $request, bool $normalization, ?array $extractedAttributes = null): array
33 | {
34 | $context = $this->decorated->createFromRequest($request, $normalization, $extractedAttributes);
35 |
36 | if (empty($resourceClass = $context['resource_class']) || empty($context['groups']) || \in_array('Route:manifest:read', $context['groups'], true)) {
37 | return $context;
38 | }
39 |
40 | $reflectionClass = new \ReflectionClass($resourceClass);
41 | if ($normalization) {
42 | $context['groups'][] = \sprintf('%s:%s:read', $reflectionClass->getShortName(), UploadableLoader::GROUP_NAME);
43 | } else {
44 | $context['groups'][] = \sprintf('%s:%s:write', $reflectionClass->getShortName(), UploadableLoader::GROUP_NAME);
45 | }
46 |
47 | return $context;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Serializer/ResourceMetadata/ResourceMetadataInterface.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Silverback\ApiComponentsBundle\Serializer\ResourceMetadata;
15 |
16 | use Silverback\ApiComponentsBundle\Metadata\PageDataMetadata;
17 | use Symfony\Component\Validator\ConstraintViolationListInterface;
18 |
19 | interface ResourceMetadataInterface
20 | {
21 | public function getResourceMetadata(): ?self;
22 |
23 | public function getPageDataMetadata(): ?PageDataMetadata;
24 |
25 | public function setPageDataMetadata(PageDataMetadata $pageDataMetadata): void;
26 |
27 | public function getStaticComponent(): ?string;
28 |
29 | public function setStaticComponent(string $staticComponentIri): void;
30 |
31 | public function getCollection(): ?bool;
32 |
33 | public function setCollection(bool $collection): void;
34 |
35 | public function getPersisted(): ?bool;
36 |
37 | public function setPersisted(?bool $persisted): void;
38 |
39 | public function getPublishable(): ?ResourcePublishableMetadata;
40 |
41 | public function setPublishable(bool $published, ?string $publishedAt = null): void;
42 |
43 | public function getViolations(): ?ConstraintViolationListInterface;
44 |
45 | public function setViolations(?ConstraintViolationListInterface $violationList): void;
46 |
47 | public function getMediaObjects(): ?array;
48 |
49 | public function setMediaObjects(array $mediaObjects): void;
50 | }
51 |
--------------------------------------------------------------------------------
/src/Serializer/ResourceMetadata/ResourceMetadataProvider.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Silverback\ApiComponentsBundle\Serializer\ResourceMetadata;
15 |
16 | class ResourceMetadataProvider
17 | {
18 | public array $metadatas = [];
19 |
20 | public function findResourceMetadata(object $object): ResourceMetadata
21 | {
22 | $hash = spl_object_id($object);
23 | if ($this->resourceMetadataExists($object)) {
24 | return $this->metadatas[$hash]['metadata'];
25 | }
26 | $this->metadatas[$hash] = [
27 | 'resource' => $object,
28 | 'metadata' => new ResourceMetadata(),
29 | ];
30 |
31 | return $this->metadatas[$hash]['metadata'];
32 | }
33 |
34 | public function resourceMetadataExists(object $object): bool
35 | {
36 | $hash = spl_object_id($object);
37 |
38 | return isset($this->metadatas[$hash]);
39 | }
40 |
41 | public function getMetadatas(): array
42 | {
43 | return $this->metadatas;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Serializer/ResourceMetadata/ResourcePublishableMetadata.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Silverback\ApiComponentsBundle\Serializer\ResourceMetadata;
15 |
16 | use Symfony\Component\Serializer\Annotation\Groups;
17 |
18 | class ResourcePublishableMetadata
19 | {
20 | public function __construct(
21 | #[Groups('cwa_resource:metadata')]
22 | public bool $published,
23 | #[Groups('cwa_resource:metadata')]
24 | public ?string $publishedAt = null,
25 | ) {
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/Serializer/SerializeFormatResolver.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Silverback\ApiComponentsBundle\Serializer;
15 |
16 | use Symfony\Component\HttpFoundation\Request;
17 | use Symfony\Component\HttpFoundation\RequestStack;
18 |
19 | /**
20 | * @author Daniel West
21 | */
22 | final class SerializeFormatResolver implements SerializeFormatResolverInterface
23 | {
24 | private RequestStack $requestStack;
25 | private string $defaultFormat;
26 |
27 | public function __construct(RequestStack $requestStack, string $defaultFormat = 'jsonld')
28 | {
29 | $this->requestStack = $requestStack;
30 | $this->defaultFormat = $defaultFormat;
31 | }
32 |
33 | public function getFormat(): string
34 | {
35 | $request = $this->requestStack->getMainRequest();
36 | if (!$request) {
37 | return $this->defaultFormat;
38 | }
39 |
40 | return $this->getFormatFromRequest($request);
41 | }
42 |
43 | public function getFormatFromRequest(Request $request): string
44 | {
45 | // Symfony 6.2 deprecated getContentType in favor of getContentTypeFormat
46 | $contentTypeMethod = method_exists($request, 'getContentTypeFormat') ? 'getContentTypeFormat' : 'getContentType';
47 |
48 | return $request->getRequestFormat(null) ?: $request->{$contentTypeMethod}() ?: $this->defaultFormat;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/Serializer/SerializeFormatResolverInterface.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Silverback\ApiComponentsBundle\Serializer;
15 |
16 | use Symfony\Component\HttpFoundation\Request;
17 |
18 | /**
19 | * @author Daniel West
20 | */
21 | interface SerializeFormatResolverInterface
22 | {
23 | public function getFormat(): string;
24 |
25 | public function getFormatFromRequest(Request $request): string;
26 | }
27 |
--------------------------------------------------------------------------------
/src/Utility/ApiResourceRouteFinder.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Silverback\ApiComponentsBundle\Utility;
15 |
16 | use Silverback\ApiComponentsBundle\Exception\InvalidArgumentException;
17 | use Symfony\Component\Routing\Exception\ExceptionInterface as RoutingExceptionInterface;
18 | use Symfony\Component\Routing\RouterInterface;
19 |
20 | /**
21 | * @author Daniel West
22 | */
23 | final class ApiResourceRouteFinder
24 | {
25 | private RouterInterface $router;
26 |
27 | public function __construct(RouterInterface $router)
28 | {
29 | $this->router = $router;
30 | }
31 |
32 | public function findByIri(string $iri): array
33 | {
34 | try {
35 | $parameters = $this->router->match($iri);
36 | } catch (RoutingExceptionInterface $e) {
37 | throw new InvalidArgumentException(\sprintf('No route matches "%s".', $iri), $e->getCode(), $e);
38 | }
39 |
40 | if (!isset($parameters['_api_resource_class'])) {
41 | throw new InvalidArgumentException(\sprintf('No resource associated to "%s".', $iri));
42 | }
43 |
44 | return $parameters;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Utility/ClassInfoTrait.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Silverback\ApiComponentsBundle\Utility;
15 |
16 | /**
17 | * Retrieves information about a class.
18 | *
19 | * @internal
20 | *
21 | * @author Kévin Dunglas
22 | */
23 | trait ClassInfoTrait
24 | {
25 | /**
26 | * Get class name of the given object.
27 | */
28 | private function getObjectClass(object $object): string
29 | {
30 | return $this->getRealClassName($object::class);
31 | }
32 |
33 | /**
34 | * Get the real class name of a class name that could be a proxy.
35 | */
36 | private function getRealClassName(string $className): string
37 | {
38 | // __CG__: Doctrine Common Marker for Proxy (ODM < 2.0 and ORM < 3.0)
39 | // __PM__: Ocramius Proxy Manager (ODM >= 2.0)
40 | $positionCg = strrpos($className, '\\__CG__\\');
41 | $positionPm = strrpos($className, '\\__PM__\\');
42 |
43 | if (false === $positionCg && false === $positionPm) {
44 | return $className;
45 | }
46 |
47 | if (false !== $positionCg) {
48 | return substr($className, $positionCg + 8);
49 | }
50 |
51 | $className = ltrim($className, '\\');
52 |
53 | return substr(
54 | $className,
55 | 8 + $positionPm,
56 | strrpos($className, '\\') - ($positionPm + 8)
57 | );
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/Validator/Constraints/ComponentPosition.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Silverback\ApiComponentsBundle\Validator\Constraints;
15 |
16 | use Symfony\Component\Validator\Constraint;
17 |
18 | /**
19 | * @author Daniel West
20 | *
21 | * @Annotation
22 | */
23 | #[\Attribute(\Attribute::TARGET_CLASS)]
24 | class ComponentPosition extends Constraint
25 | {
26 | public string $message = 'The IRI `{{ iri }}` is not permitted to be added to the collection `{{ reference }}`. Allowed IRIs: {{ allowed }}';
27 | public string $restrictedMessage = 'The IRI `{{ iri }}` must be specifically allowed within the collection {{ reference }}';
28 |
29 | public function getTargets(): string|array
30 | {
31 | return self::CLASS_CONSTRAINT;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/Validator/Constraints/FormTypeClass.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Silverback\ApiComponentsBundle\Validator\Constraints;
15 |
16 | use Silverback\ApiComponentsBundle\Form\AbstractType;
17 | use Silverback\ApiComponentsBundle\Form\FormTypeInterface;
18 | use Symfony\Component\Validator\Constraint;
19 |
20 | /**
21 | * @author Daniel West
22 | *
23 | * @Annotation
24 | */
25 | class FormTypeClass extends Constraint
26 | {
27 | public string $message;
28 |
29 | public function __construct($options = null)
30 | {
31 | $conditionsStr = vsprintf(
32 | 'It should extend %s, implement %s or tagged %s',
33 | [
34 | AbstractType::class,
35 | FormTypeInterface::class,
36 | 'silverback_api_components.form_type',
37 | ]
38 | );
39 | $this->message = 'The string "{{ string }}" does not refer to a class configured correctly as a form type. ' . $conditionsStr;
40 |
41 | parent::__construct($options);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Validator/Constraints/NewEmailAddress.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Silverback\ApiComponentsBundle\Validator\Constraints;
15 |
16 | use Symfony\Component\Validator\Constraint;
17 |
18 | /**
19 | * @Annotation
20 | */
21 | #[\Attribute(\Attribute::TARGET_CLASS)]
22 | class NewEmailAddress extends Constraint
23 | {
24 | public string $message = 'Your new email address should be different.';
25 | public string $uniqueMessage = 'Someone else is already registered with that email address.';
26 |
27 | public function getTargets(): string|array
28 | {
29 | return self::CLASS_CONSTRAINT;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Validator/Constraints/ResourceIri.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Silverback\ApiComponentsBundle\Validator\Constraints;
15 |
16 | use Symfony\Component\Validator\Constraint;
17 |
18 | /**
19 | * @author Daniel West
20 | *
21 | * @Annotation
22 | */
23 | #[\Attribute(\Attribute::TARGET_PROPERTY)]
24 | class ResourceIri extends Constraint
25 | {
26 | public string $message = '{{ value }} is not a valid IRI';
27 | }
28 |
--------------------------------------------------------------------------------
/src/Validator/Constraints/ResourceIriValidator.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Silverback\ApiComponentsBundle\Validator\Constraints;
15 |
16 | use Silverback\ApiComponentsBundle\Exception\InvalidArgumentException;
17 | use Silverback\ApiComponentsBundle\Utility\ApiResourceRouteFinder;
18 | use Symfony\Component\Validator\Constraint;
19 | use Symfony\Component\Validator\ConstraintValidator;
20 |
21 | /**
22 | * @author Daniel West
23 | */
24 | class ResourceIriValidator extends ConstraintValidator
25 | {
26 | private ApiResourceRouteFinder $resourceRouteFinder;
27 |
28 | public function __construct(ApiResourceRouteFinder $resourceRouteFinder)
29 | {
30 | $this->resourceRouteFinder = $resourceRouteFinder;
31 | }
32 |
33 | /**
34 | * @param ResourceIri $constraint
35 | */
36 | public function validate($iri, Constraint $constraint): void
37 | {
38 | try {
39 | $this->resourceRouteFinder->findByIri((string) $iri);
40 | } catch (InvalidArgumentException $e) {
41 | $this->context->buildViolation($constraint->message)
42 | ->setParameter('{{ value }}', $iri ?? 'null')
43 | ->addViolation();
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Validator/TimestampedValidator.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Silverback\ApiComponentsBundle\Validator;
15 |
16 | use ApiPlatform\Validator\ValidatorInterface;
17 | use Silverback\ApiComponentsBundle\AttributeReader\TimestampedAttributeReader;
18 |
19 | /**
20 | * Builds and add validation group for timestamped resources.
21 | *
22 | * @author Daniel West
23 | */
24 | final class TimestampedValidator implements ValidatorInterface
25 | {
26 | private ValidatorInterface $decorated;
27 | private TimestampedAttributeReader $annotationReader;
28 |
29 | public function __construct(ValidatorInterface $decorated, TimestampedAttributeReader $annotationReader)
30 | {
31 | $this->decorated = $decorated;
32 | $this->annotationReader = $annotationReader;
33 | }
34 |
35 | /**
36 | * {@inheritdoc}
37 | */
38 | public function validate($data, array $context = []): void
39 | {
40 | if (
41 | \is_object($data)
42 | && $this->annotationReader->isConfigured($data)
43 | ) {
44 | $context['groups'] = $context['groups'] ?? ['Default'];
45 | $context['groups'][] = (new \ReflectionClass($data::class))->getShortName() . ':timestamped';
46 | }
47 |
48 | $this->decorated->validate($data, $context);
49 | }
50 | }
51 |
--------------------------------------------------------------------------------