├── .all-contributorsrc ├── .editorconfig ├── .symfony.insight.yaml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── behat.yml ├── codecov.yml ├── composer.json ├── infection.json5.dist ├── phpunit.coverage.xml.dist ├── rector.php └── src ├── Action ├── Uploadable │ ├── DownloadAction.php │ └── UploadAction.php └── User │ ├── EmailAddressConfirmAction.php │ ├── PasswordRequestAction.php │ └── VerifyEmailAddressAction.php ├── Annotation ├── Publishable.php ├── Timestamped.php ├── Uploadable.php └── UploadableField.php ├── ApiPlatform ├── Api │ ├── IriConverter.php │ └── MercureIriConverter.php ├── Metadata │ ├── Property │ │ ├── ComponentPropertyMetadataFactory.php │ │ └── ImagineFiltersPropertyMetadataFactory.php │ └── Resource │ │ ├── ComponentResourceMetadataFactory.php │ │ ├── RoutableResourceMetadataCollectionFactory.php │ │ ├── RoutingPrefixResourceMetadataCollectionFactory.php │ │ ├── UploadableResourceMetadataCollectionFactory.php │ │ └── UserResourceMetadataCollectionFactory.php └── Serializer │ └── VersionedDocumentationNormalizer.php ├── AttributeReader ├── AttributeReader.php ├── AttributeReaderInterface.php ├── PublishableAttributeReader.php ├── TimestampedAttributeReader.php ├── UploadableAttributeReader.php └── UploadableAttributeReaderInterface.php ├── Cache └── CachedTrait.php ├── Command ├── FormCachePurgeCommand.php ├── RefreshTokensExpireCommand.php └── UserCreateCommand.php ├── DataProvider ├── PageDataProvider.php └── StateProvider │ ├── FormStateProvider.php │ ├── PageDataMetadataStateProvider.php │ ├── RouteStateProvider.php │ └── UserStateProvider.php ├── DependencyInjection ├── CompilerPass │ ├── ApiPlatformCompilerPass.php │ ├── DoctrineOrmCompilerPass.php │ ├── FlysystemCompilerPass.php │ ├── ImagineCompilerPass.php │ ├── SerializerCompilerPass.php │ └── ValidatorCompilerPass.php ├── Configuration.php └── SilverbackApiComponentsExtension.php ├── Doctrine └── Extension │ └── ORM │ ├── PublishableExtension.php │ ├── RoutableExtension.php │ ├── RouteExtension.php │ └── TablePrefixExtension.php ├── Entity ├── Component │ ├── Collection.php │ └── Form.php ├── Core │ ├── AbstractComponent.php │ ├── AbstractPage.php │ ├── AbstractPageData.php │ ├── AbstractRefreshToken.php │ ├── ComponentGroup.php │ ├── ComponentInterface.php │ ├── ComponentPosition.php │ ├── FileInfo.php │ ├── Layout.php │ ├── Page.php │ ├── PageDataInterface.php │ ├── RoutableInterface.php │ ├── Route.php │ └── SiteConfigParameter.php ├── User │ ├── AbstractUser.php │ └── UserInterface.php └── Utility │ ├── IdTrait.php │ ├── ImagineFiltersInterface.php │ ├── PublishableTrait.php │ ├── TimestampedTrait.php │ ├── UiTrait.php │ └── UploadableTrait.php ├── Event ├── CommandLogEvent.php ├── FormSuccessEvent.php ├── ImagineRemoveEvent.php ├── ImagineStoreEvent.php ├── JWTRefreshedEvent.php ├── ResourceChangedEvent.php └── UserEmailMessageEvent.php ├── EventListener ├── Api │ ├── ApiEventListenerTrait.php │ ├── CollectionApiEventListener.php │ ├── ComponentPositionEventListener.php │ ├── ComponentUsageEventListener.php │ ├── FormApiEventListener.php │ ├── OrphanedComponentEventListener.php │ ├── PublishableEventListener.php │ ├── RouteEventListener.php │ ├── UploadableEventListener.php │ └── UserEventListener.php ├── Doctrine │ ├── DoctrineResourceFlushListenerInterface.php │ ├── PropagateUpdatesListener.php │ ├── PublishableListener.php │ ├── SqlLiteForeignKeyEnabler.php │ ├── TimestampedListener.php │ └── UploadableListener.php ├── Form │ ├── EntityPersistFormListener.php │ ├── FormSuccessEventListenerInterface.php │ └── User │ │ ├── ChangePasswordListener.php │ │ ├── NewEmailAddressListener.php │ │ ├── PasswordUpdateListener.php │ │ └── UserRegisterListener.php ├── Imagine │ └── ImagineEventListener.php ├── Jwt │ ├── JWTClearTokenListener.php │ └── JWTEventListener.php ├── Mailer │ └── MessageEventListener.php └── ResourceChangedEventListener.php ├── Exception ├── ApiPlatformAuthenticationException.php ├── BadMethodCallException.php ├── ExceptionInterface.php ├── FileMissingException.php ├── FileNotImageException.php ├── InvalidArgumentException.php ├── MailerTransportException.php ├── OutOfBoundsException.php ├── PageDataNotFoundException.php ├── RfcComplianceException.php ├── TokenAuthenticationException.php ├── UnexpectedValueException.php ├── UnparseableRequestHeaderException.php ├── UnsupportedAnnotationException.php ├── UserDisabledException.php └── UserEmailAddressUnverified.php ├── Factory ├── Form │ └── FormViewFactory.php ├── Uploadable │ ├── ApiUrlGenerator.php │ ├── MediaObjectFactory.php │ ├── PublicUrlGenerator.php │ ├── TemporaryUrlGenerator.php │ └── UploadableUrlGeneratorInterface.php └── User │ ├── Mailer │ ├── AbstractUserEmailFactory.php │ ├── ChangeEmailConfirmationEmailFactory.php │ ├── PasswordChangedEmailFactory.php │ ├── PasswordResetEmailFactory.php │ ├── UserEnabledEmailFactory.php │ ├── UsernameChangedEmailFactory.php │ ├── VerifyEmailFactory.php │ └── WelcomeEmailFactory.php │ └── UserFactory.php ├── Filter └── OrSearchFilter.php ├── Flysystem ├── FilesystemFactory.php └── FilesystemProvider.php ├── Form ├── AbstractType.php ├── FormTypeInterface.php └── Type │ └── User │ ├── ChangePasswordType.php │ ├── NewEmailAddressType.php │ ├── PasswordUpdateType.php │ ├── UserLoginType.php │ └── UserRegisterType.php ├── Helper ├── ComponentPosition │ └── ComponentPositionSortValueHelper.php ├── Form │ ├── FormCachePurger.php │ └── FormSubmitHelper.php ├── Publishable │ └── PublishableStatusChecker.php ├── RefererUrlResolver.php ├── Route │ ├── RouteGenerator.php │ └── RouteGeneratorInterface.php ├── Timestamped │ └── TimestampedDataPersister.php ├── Uploadable │ ├── FileInfoCacheManager.php │ └── UploadableFileManager.php └── User │ ├── EmailAddressManager.php │ ├── UserDataProcessor.php │ └── UserMailer.php ├── HttpCache ├── HttpCachePurger.php └── ResourceChangedPropagatorInterface.php ├── Imagine ├── CacheManager.php ├── FlysystemCacheResolver.php └── FlysystemDataLoader.php ├── Mercure ├── DispatchTrait.php ├── MercureAuthorization.php ├── MercureResourcePublisher.php └── PublishableAwareHub.php ├── Metadata ├── ComponentUsageMetadata.php ├── Factory │ ├── CachedPageDataMetadataFactory.php │ ├── ComponentUsageMetadataFactory.php │ ├── PageDataMetadataFactory.php │ ├── PageDataMetadataFactoryInterface.php │ └── PageDataPropertyMetadataFactory.php ├── PageDataComponentMetadata.php ├── PageDataMetadata.php ├── PageDataPropertyMetadata.php └── Provider │ └── PageDataMetadataProvider.php ├── Model ├── Form │ ├── FormView.php │ └── LoginForm.php └── Uploadable │ ├── DataUriFile.php │ ├── MediaObject.php │ └── UploadedDataUriFile.php ├── OpenApi └── OpenApiFactory.php ├── RamseyUuid └── UuidUriVariableTransformer │ └── UuidUriVariableTransformer.php ├── RefreshToken ├── RefreshToken.php └── Storage │ ├── DoctrineRefreshTokenStorage.php │ └── RefreshTokenStorageInterface.php ├── Repository ├── Core │ ├── AbstractPageDataRepository.php │ ├── ComponentPositionRepository.php │ ├── FileInfoRepository.php │ ├── LayoutRepository.php │ ├── RefreshTokenRepository.php │ ├── RouteRepository.php │ └── SiteConfigParameterRepository.php └── User │ ├── UserRepository.php │ └── UserRepositoryInterface.php ├── Resources ├── config │ ├── api_platform │ │ ├── collection │ │ │ └── resource.xml │ │ ├── form │ │ │ └── resource.xml │ │ ├── page_data_metadata │ │ │ ├── properties.xml │ │ │ └── resource.xml │ │ ├── resource_metadata │ │ │ ├── properties.xml │ │ │ └── resource.xml │ │ └── uploadable │ │ │ ├── properties.xml │ │ │ └── resource.xml │ ├── doctrine-orm │ │ ├── Component.Collection.orm.xml │ │ ├── Component.Form.orm.xml │ │ ├── Core.AbstractComponent.orm.xml │ │ ├── Core.AbstractPage.orm.xml │ │ ├── Core.AbstractPageData.orm.xml │ │ ├── Core.AbstractRefreshToken.orm.xml │ │ ├── Core.ComponentGroup.orm.xml │ │ ├── Core.ComponentPosition.orm.xml │ │ ├── Core.FileInfo.orm.xml │ │ ├── Core.Layout.orm.xml │ │ ├── Core.Page.orm.xml │ │ ├── Core.Route.orm.xml │ │ ├── Core.SiteConfigParameter.orm.xml │ │ └── User.AbstractUser.orm.xml │ ├── routing │ │ ├── all.xml │ │ └── security.xml │ ├── services.php │ ├── services_doctrine_orm_http_cache_purger.php │ ├── services_doctrine_orm_mercure_publisher.php │ └── services_normalizers.php └── views │ ├── assets │ ├── css │ │ ├── emails.css │ │ └── foundation-emails.css │ └── images │ │ └── email_logo.png │ └── emails │ ├── _signature.html.twig │ ├── _template.html.twig │ ├── user_change_email_confirmation.html.twig │ ├── user_enabled.html.twig │ ├── user_password_changed.html.twig │ ├── user_password_reset.html.twig │ ├── user_username_changed.html.twig │ ├── user_verify_email.html.twig │ └── user_welcome.html.twig ├── Security ├── EventListener │ ├── AccessDeniedListener.php │ ├── DenyAccessListener.php │ └── LogoutListener.php ├── JWTManager.php ├── TokenGenerator.php ├── UserChecker.php └── Voter │ ├── AbstractRoutableVoter.php │ ├── ComponentVoter.php │ ├── RoutableVoter.php │ ├── RouteVoter.php │ └── SiteConfigParameterVoter.php ├── Serializer ├── ContextBuilder │ ├── ComponentPositionContextBuilder.php │ ├── CwaResourceContextBuilder.php │ ├── PublishableContextBuilder.php │ ├── TimestampedContextBuilder.php │ ├── UploadableContextBuilder.php │ └── UserContextBuilder.php ├── MappingLoader │ ├── CwaResourceLoader.php │ ├── PublishableLoader.php │ ├── TimestampedLoader.php │ └── UploadableLoader.php ├── Normalizer │ ├── AbstractResourceNormalizer.php │ ├── CollectionNormalizer.php │ ├── ComponentPositionNormalizer.php │ ├── DataUriNormalizer.php │ ├── MetadataNormalizer.php │ ├── PageDataNormalizer.php │ ├── PersistedNormalizer.php │ ├── PublishableNormalizer.php │ ├── RouteNormalizer.php │ ├── TimestampedNormalizer.php │ ├── UploadableNormalizer.php │ └── UserNormalizer.php ├── ResourceMetadata │ ├── ResourceMetadata.php │ ├── ResourceMetadataInterface.php │ ├── ResourceMetadataProvider.php │ └── ResourcePublishableMetadata.php ├── SerializeFormatResolver.php └── SerializeFormatResolverInterface.php ├── SilverbackApiComponentsBundle.php ├── Utility ├── ApiResourceRouteFinder.php ├── ClassInfoTrait.php ├── ClassMetadataTrait.php └── ResourceClassInfoTrait.php └── Validator ├── ClassNameValidator.php ├── Constraints ├── ComponentPosition.php ├── ComponentPositionValidator.php ├── FormTypeClass.php ├── FormTypeClassValidator.php ├── NewEmailAddress.php ├── NewEmailAddressValidator.php ├── ResourceIri.php ├── ResourceIriValidator.php └── UserPasswordValidator.php ├── MappingLoader └── TimestampedLoader.php ├── PublishableValidator.php └── TimestampedValidator.php /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "api-components-bundle", 3 | "projectOwner": "components-web-app", 4 | "repoType": "github", 5 | "repoHost": "https://github.com", 6 | "files": [ 7 | "README.md", 8 | "docs/index.md" 9 | ], 10 | "imageSize": 60, 11 | "commit": true, 12 | "commitConvention": "none", 13 | "contributors": [ 14 | { 15 | "login": "vincentchalamon", 16 | "name": "Vincent", 17 | "avatar_url": "https://avatars1.githubusercontent.com/u/407859?v=4", 18 | "profile": "https://les-tilleuls.coop", 19 | "contributions": [ 20 | "code", 21 | "ideas", 22 | "review" 23 | ] 24 | }, 25 | { 26 | "login": "PierreRebeilleau", 27 | "name": "Pierre Rebeilleau", 28 | "avatar_url": "https://avatars1.githubusercontent.com/u/49146882?v=4", 29 | "profile": "https://github.com/PierreRebeilleau", 30 | "contributions": [ 31 | "test" 32 | ] 33 | }, 34 | { 35 | "login": "chalasr", 36 | "name": "Robin Chalas", 37 | "avatar_url": "https://avatars0.githubusercontent.com/u/7502063?v=4", 38 | "profile": "https://github.com/chalasr", 39 | "contributions": [ 40 | "code" 41 | ] 42 | }, 43 | { 44 | "login": "soyuka", 45 | "name": "Antoine Bluchet", 46 | "avatar_url": "https://avatars3.githubusercontent.com/u/1321971?v=4", 47 | "profile": "https://soyuka.me", 48 | "contributions": [ 49 | "bug" 50 | ] 51 | }, 52 | { 53 | "login": "maxhelias", 54 | "name": "Maxime Helias", 55 | "avatar_url": "https://avatars2.githubusercontent.com/u/12966574?v=4", 56 | "profile": "https://twitter.com/maxhelias", 57 | "contributions": [ 58 | "doc" 59 | ] 60 | } 61 | ], 62 | "contributorsPerLine": 6 63 | } 64 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | # Change these settings to your own preference 9 | indent_style = space 10 | indent_size = 4 11 | 12 | # We recommend you to keep these unchanged 13 | end_of_line = lf 14 | charset = utf-8 15 | trim_trailing_whitespace = true 16 | insert_final_newline = true 17 | 18 | [*.feature] 19 | indent_style = space 20 | indent_size = 2 21 | 22 | [*.js] 23 | indent_style = space 24 | indent_size = 2 25 | 26 | [*.json] 27 | indent_style = space 28 | indent_size = 2 29 | 30 | [*.md] 31 | trim_trailing_whitespace = false 32 | 33 | [*.php] 34 | indent_style = space 35 | indent_size = 4 36 | 37 | [*.sh] 38 | indent_style = tab 39 | indent_size = 2 40 | 41 | [*.vcl] 42 | indent_style = space 43 | indent_size = 2 44 | 45 | [*.xml] 46 | indent_style = space 47 | indent_size = 4 48 | 49 | [*.{yaml,yml}] 50 | indent_style = space 51 | indent_size = 4 52 | trim_trailing_whitespace = false 53 | 54 | [.gitmodules] 55 | indent_style = tab 56 | indent_size = 4 57 | 58 | [.php_cs{,.dist}] 59 | indent_style = space 60 | indent_size = 4 61 | 62 | [.travis.yml] 63 | indent_style = space 64 | indent_size = 2 65 | 66 | [composer.json] 67 | indent_style = space 68 | indent_size = 4 69 | 70 | [docker-compose{,.*}.{yaml,yml}] 71 | indent_style = space 72 | indent_size = 2 73 | 74 | [Dockerfile] 75 | indent_style = tab 76 | indent_size = 4 77 | 78 | [package.json] 79 | indent_style = space 80 | indent_size = 2 81 | 82 | [phpunit.xml{,.dist}] 83 | indent_style = space 84 | indent_size = 4 85 | -------------------------------------------------------------------------------- /.symfony.insight.yaml: -------------------------------------------------------------------------------- 1 | php_version: 8.4 2 | rules: 3 | doctrine.public_doctrine_property: 4 | enabled: false 5 | pre_composer_script: | 6 | #!/bin/bash 7 | # rm composer.lock 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## alpha.2 4 | - Added getters and setters for `ComponentPosition::$pageDataProperty` 5 | - `RouteGenerator::createFromPage` refactored to `RouteGenerator::create` 6 | - `RouteGenerator::create` now accepts any object implementing new `RoutableIterface` 7 | - Added `RouteGeneratorInterface` 8 | - Added `RoutableInterface` 9 | - Add `RouteGeneratorInterface` alias to service `silverback.helper.route_generator` 10 | - Bug fix - RouteGenerator allow for old entity not to have a route index 11 | - Bug fix - route generator will also persist timestamp fields 12 | - Bug fix - mapping of parentRoute. Change to Many-To-One from One-To-One. 13 | - ComponentPositionNormalizer - do not normalize if no request 14 | - Simple use of parentRoute of pageData (data structure needs a re-think though - parent routes, parent pages and how they should work) 15 | - Bug fix: Only apply doctrine extension for routes to the route resources 16 | - Bug fix: Return expired jwt cookie on logout 17 | - Feature: Added a `location` property to ComponentGroup resource so reusable page templates can decide which collection to display where 18 | - Feature: Return roles (and hierarchical roles granted) with users from /me route 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Silverback Internet Services Limited 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "tests/" 3 | - "features/" 4 | - "src/Resources/config" 5 | - ".php-cs-fixer.dist.php" 6 | -------------------------------------------------------------------------------- /infection.json5.dist: -------------------------------------------------------------------------------- 1 | { 2 | "source": { 3 | "directories": [ 4 | "src" 5 | ], 6 | "excludes": [ 7 | "Resources/config" 8 | ] 9 | }, 10 | "timeout": 20, 11 | "phpUnit": { 12 | "configDir": ".", 13 | "customPath": "vendor\/bin\/phpunit" 14 | }, 15 | "logs": { 16 | "text": "infection.log" 17 | }, 18 | "mutators": { 19 | "@default": true 20 | }, 21 | "testFramework":"phpunit", 22 | "bootstrap":"./tests/Functional/app/bootstrap.php" 23 | } 24 | -------------------------------------------------------------------------------- /phpunit.coverage.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | 13 | . 14 | 15 | 16 | tests 17 | features 18 | vendor 19 | src/Resources/config 20 | .php-cs-fixer.dist.php 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | tests 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /rector.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 | use Rector\CodeQuality\Rector\Class_\InlineConstructorDefaultToPropertyRector; 15 | use Rector\Config\RectorConfig; 16 | use Rector\Doctrine\Set\DoctrineSetList; 17 | use Rector\Set\ValueObject\LevelSetList; 18 | use Rector\Symfony\Set\SymfonySetList; 19 | 20 | return static function (RectorConfig $rectorConfig): void { 21 | $rectorConfig->paths([ 22 | __DIR__ . '/features', 23 | __DIR__ . '/src', 24 | __DIR__ . '/tests', 25 | ]); 26 | 27 | // register a single rule 28 | $rectorConfig->rule(InlineConstructorDefaultToPropertyRector::class); 29 | 30 | // define sets of rules 31 | // $rectorConfig->sets([ 32 | // LevelSetList::UP_TO_PHP_81 33 | // ]); 34 | $rectorConfig->sets([ 35 | DoctrineSetList::ANNOTATIONS_TO_ATTRIBUTES, 36 | SymfonySetList::ANNOTATIONS_TO_ATTRIBUTES, 37 | ]); 38 | }; 39 | -------------------------------------------------------------------------------- /src/Action/Uploadable/DownloadAction.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\Action\Uploadable; 15 | 16 | use Silverback\ApiComponentsBundle\AttributeReader\UploadableAttributeReader; 17 | use Silverback\ApiComponentsBundle\Exception\InvalidArgumentException; 18 | use Silverback\ApiComponentsBundle\Helper\Uploadable\UploadableFileManager; 19 | use Symfony\Component\HttpFoundation\Request; 20 | 21 | /** 22 | * @author Daniel West 23 | */ 24 | class DownloadAction 25 | { 26 | public function __invoke(object $data, string $property, Request $request, UploadableAttributeReader $annotationReader, UploadableFileManager $uploadableFileManager) 27 | { 28 | if (!$annotationReader->isConfigured($data)) { 29 | throw new InvalidArgumentException(\sprintf('%s is not an uploadable resource. It should not be configured to use %s.', $data::class, __CLASS__)); 30 | } 31 | 32 | return $uploadableFileManager->getFileResponse($data, $property, $request->query->getBoolean('download', false)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Action/User/EmailAddressConfirmAction.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\Action\User; 15 | 16 | use Silverback\ApiComponentsBundle\Exception\InvalidArgumentException; 17 | use Silverback\ApiComponentsBundle\Exception\UnexpectedValueException; 18 | use Silverback\ApiComponentsBundle\Helper\User\EmailAddressManager; 19 | use Symfony\Component\HttpFoundation\Response; 20 | 21 | /** 22 | * @author Daniel West 23 | */ 24 | class EmailAddressConfirmAction 25 | { 26 | private EmailAddressManager $emailAddressManager; 27 | 28 | public function __construct(EmailAddressManager $emailAddressManager) 29 | { 30 | $this->emailAddressManager = $emailAddressManager; 31 | } 32 | 33 | public function __invoke(string $username, string $emailAddress, string $token): Response 34 | { 35 | try { 36 | $this->emailAddressManager->confirmNewEmailAddress($username, $emailAddress, $token); 37 | } catch (InvalidArgumentException $exception) { 38 | return new Response(null, Response::HTTP_NOT_FOUND); 39 | } catch (UnexpectedValueException $exception) { 40 | return new Response(null, Response::HTTP_UNAUTHORIZED); 41 | } 42 | 43 | $response = new Response(null, Response::HTTP_OK); 44 | $response->setCache([ 45 | 'private' => true, 46 | 's_maxage' => 0, 47 | 'max_age' => 0, 48 | ]); 49 | 50 | return $response; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Action/User/VerifyEmailAddressAction.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\Action\User; 15 | 16 | use Silverback\ApiComponentsBundle\Exception\InvalidArgumentException; 17 | use Silverback\ApiComponentsBundle\Helper\User\EmailAddressManager; 18 | use Symfony\Component\HttpFoundation\Response; 19 | 20 | /** 21 | * @author Daniel West 22 | */ 23 | class VerifyEmailAddressAction 24 | { 25 | private EmailAddressManager $emailAddressManager; 26 | 27 | public function __construct(EmailAddressManager $emailAddressManager) 28 | { 29 | $this->emailAddressManager = $emailAddressManager; 30 | } 31 | 32 | public function __invoke(string $username, string $token): Response 33 | { 34 | try { 35 | $this->emailAddressManager->verifyEmailAddress($username, $token); 36 | } catch (InvalidArgumentException $exception) { 37 | return new Response(null, Response::HTTP_NOT_FOUND); 38 | } 39 | 40 | $response = new Response(null, Response::HTTP_OK); 41 | $response->setCache([ 42 | 'private' => true, 43 | 's_maxage' => 0, 44 | 'max_age' => 0, 45 | ]); 46 | 47 | return $response; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Annotation/Publishable.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\Annotation; 15 | 16 | /** 17 | * @author Vincent Chalamon 18 | */ 19 | #[\Attribute(\Attribute::TARGET_CLASS)] 20 | final class Publishable 21 | { 22 | public string $fieldName; 23 | 24 | public ?string $isGranted; 25 | 26 | public string $associationName; 27 | 28 | public string $reverseAssociationName; 29 | 30 | /** 31 | * @var string[] 32 | */ 33 | public ?array $validationGroups; 34 | 35 | public function __construct(string $fieldName = 'publishedAt', ?string $isGranted = null, string $associationName = 'publishedResource', string $reverseAssociationName = 'draftResource', ?array $validationGroups = null) 36 | { 37 | $this->fieldName = $fieldName; 38 | $this->isGranted = $isGranted; 39 | $this->associationName = $associationName; 40 | $this->reverseAssociationName = $reverseAssociationName; 41 | $this->validationGroups = $validationGroups; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Annotation/Timestamped.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\Annotation; 15 | 16 | /** 17 | * @author Daniel West 18 | */ 19 | #[\Attribute(\Attribute::TARGET_CLASS)] 20 | final class Timestamped 21 | { 22 | public string $createdAtField; 23 | 24 | public string $modifiedAtField; 25 | 26 | public function __construct(string $createdAtField = 'createdAt', string $modifiedAtField = 'modifiedAt') 27 | { 28 | $this->createdAtField = $createdAtField; 29 | $this->modifiedAtField = $modifiedAtField; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Annotation/Uploadable.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\Annotation; 15 | 16 | /** 17 | * @author Vincent Chalamon 18 | */ 19 | #[\Attribute(\Attribute::TARGET_CLASS)] 20 | final class Uploadable 21 | { 22 | } 23 | -------------------------------------------------------------------------------- /src/Annotation/UploadableField.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\Annotation; 15 | 16 | /** 17 | * @author Vincent Chalamon 18 | */ 19 | #[\Attribute(\Attribute::TARGET_PROPERTY)] 20 | final class UploadableField 21 | { 22 | // Nice to have - feature to configure the IRI in the output media objects for this field 23 | // public string $iri = 'http://schema.org/MediaObject'; 24 | 25 | public function __construct( 26 | public string $adapter, 27 | public string $urlGenerator = 'api', 28 | public string $property = 'filename', 29 | public ?string $prefix = null, 30 | public ?array $imagineFilters = [], 31 | ) { 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/ApiPlatform/Api/IriConverter.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\ApiPlatform\Api; 15 | 16 | use ApiPlatform\Metadata\IriConverterInterface; 17 | use ApiPlatform\Metadata\Operation; 18 | use ApiPlatform\Metadata\UrlGeneratorInterface; 19 | use Silverback\ApiComponentsBundle\Entity\Core\Route; 20 | 21 | /** 22 | * @author Daniel West 23 | */ 24 | class IriConverter implements IriConverterInterface 25 | { 26 | public function __construct(private IriConverterInterface $decorated) 27 | { 28 | } 29 | 30 | public function getResourceFromIri(string $iri, array $context = [], ?Operation $operation = null): object 31 | { 32 | return $this->decorated->getResourceFromIri($iri, $context, $operation); 33 | } 34 | 35 | public function getIriFromResource($resource, int $referenceType = UrlGeneratorInterface::ABS_PATH, ?Operation $operation = null, array $context = []): ?string 36 | { 37 | if ($resource instanceof Route && ($path = $resource->getPath())) { 38 | return '/_/routes/' . $path; 39 | } 40 | 41 | return $this->decorated->getIriFromResource($resource, $referenceType, $operation, $context); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/ApiPlatform/Api/MercureIriConverter.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\ApiPlatform\Api; 15 | 16 | use ApiPlatform\Metadata\IriConverterInterface; 17 | use ApiPlatform\Metadata\Operation; 18 | use ApiPlatform\Metadata\UrlGeneratorInterface; 19 | use Silverback\ApiComponentsBundle\Helper\Publishable\PublishableStatusChecker; 20 | 21 | /** 22 | * @author Daniel West 23 | */ 24 | class MercureIriConverter implements IriConverterInterface 25 | { 26 | public function __construct(private IriConverterInterface $decorated, private PublishableStatusChecker $publishableStatusChecker) 27 | { 28 | } 29 | 30 | public function getResourceFromIri(string $iri, array $context = [], ?Operation $operation = null): object 31 | { 32 | return $this->decorated->getResourceFromIri($iri, $context, $operation); 33 | } 34 | 35 | public function getIriFromResource($resource, int $referenceType = UrlGeneratorInterface::ABS_PATH, ?Operation $operation = null, array $context = []): ?string 36 | { 37 | $iri = $this->decorated->getIriFromResource($resource, $referenceType, $operation, $context); 38 | 39 | if (\is_string($resource)) { 40 | return $iri; 41 | } 42 | 43 | if ($this->publishableStatusChecker->getAttributeReader()->isConfigured($resource) && !$this->publishableStatusChecker->isActivePublishedAt($resource)) { 44 | $iri .= '?draft=1'; 45 | } 46 | 47 | return $iri; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/ApiPlatform/Metadata/Property/ComponentPropertyMetadataFactory.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\ApiPlatform\Metadata\Property; 15 | 16 | use ApiPlatform\Metadata\ApiProperty; 17 | use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface; 18 | use Silverback\ApiComponentsBundle\Entity\Core\AbstractComponent; 19 | 20 | /** 21 | * We should allow componentPositions to be writable. API Platform will not do this automatically based on 22 | * AbstractComponent xml definitions or groups as we define them using our decorators. 23 | * 24 | * @author Daniel West 25 | */ 26 | class ComponentPropertyMetadataFactory implements PropertyMetadataFactoryInterface 27 | { 28 | private PropertyMetadataFactoryInterface $decorated; 29 | 30 | public function __construct(PropertyMetadataFactoryInterface $decorated) 31 | { 32 | $this->decorated = $decorated; 33 | } 34 | 35 | public function create(string $resourceClass, string $property, array $options = []): ApiProperty 36 | { 37 | $propertyMetadata = $this->decorated->create($resourceClass, $property, $options); 38 | if ('componentPositions' === $property && !is_a($resourceClass, AbstractComponent::class, true)) { 39 | return $propertyMetadata; 40 | } 41 | 42 | return $propertyMetadata->withWritableLink(true); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/ApiPlatform/Metadata/Property/ImagineFiltersPropertyMetadataFactory.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\ApiPlatform\Metadata\Property; 15 | 16 | use ApiPlatform\Metadata\ApiProperty; 17 | use ApiPlatform\Metadata\Property\Factory\PropertyMetadataFactoryInterface; 18 | use Silverback\ApiComponentsBundle\Entity\Utility\ImagineFiltersInterface; 19 | 20 | /** 21 | * @author Daniel West 22 | */ 23 | class ImagineFiltersPropertyMetadataFactory implements PropertyMetadataFactoryInterface 24 | { 25 | private PropertyMetadataFactoryInterface $decorated; 26 | 27 | public function __construct(PropertyMetadataFactoryInterface $decorated) 28 | { 29 | $this->decorated = $decorated; 30 | } 31 | 32 | public function create(string $resourceClass, string $property, array $options = []): ApiProperty 33 | { 34 | $propertyMetadata = $this->decorated->create($resourceClass, $property, $options); 35 | if ('imagineFilters' !== $property || !is_a($resourceClass, ImagineFiltersInterface::class, true)) { 36 | return $propertyMetadata; 37 | } 38 | 39 | return $propertyMetadata->withReadable(false); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/ApiPlatform/Serializer/VersionedDocumentationNormalizer.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\ApiPlatform\Serializer; 15 | 16 | use ApiPlatform\Documentation\Documentation; 17 | use ApiPlatform\Hydra\Serializer\DocumentationNormalizer; 18 | use Silverback\ApiComponentsBundle\OpenApi\OpenApiFactory; 19 | use Symfony\Component\Serializer\Exception\ExceptionInterface; 20 | use Symfony\Component\Serializer\Normalizer\NormalizerInterface; 21 | 22 | /** 23 | * @author Daniel West 24 | */ 25 | class VersionedDocumentationNormalizer implements NormalizerInterface 26 | { 27 | private NormalizerInterface|DocumentationNormalizer $decorated; 28 | 29 | public function __construct(NormalizerInterface|DocumentationNormalizer $decorated) 30 | { 31 | $this->decorated = $decorated; 32 | } 33 | 34 | /** 35 | * @param Documentation $object 36 | * 37 | * @throws ExceptionInterface 38 | */ 39 | public function normalize($object, ?string $format = null, array $context = []): array 40 | { 41 | $doc = $this->decorated->normalize($object, $format, $context); 42 | if ('' !== $object->getVersion()) { 43 | $doc['info'] = ['version' => OpenApiFactory::getExtendedVersion($object->getVersion())]; 44 | } 45 | 46 | return $doc; 47 | } 48 | 49 | public function supportsNormalization($data, ?string $format = null, array $context = []): bool 50 | { 51 | return $this->decorated->supportsNormalization($data, $format, $context); 52 | } 53 | 54 | public function getSupportedTypes(?string $format): array 55 | { 56 | return $this->decorated->getSupportedTypes($format); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/AttributeReader/AttributeReaderInterface.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\AttributeReader; 15 | 16 | /** 17 | * @author Vincent Chalamon 18 | * @author Daniel West 19 | */ 20 | interface AttributeReaderInterface 21 | { 22 | public function getConfiguration(object|string $class); 23 | 24 | public function isConfigured(object|string $class): bool; 25 | } 26 | -------------------------------------------------------------------------------- /src/AttributeReader/PublishableAttributeReader.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\AttributeReader; 15 | 16 | use Silverback\ApiComponentsBundle\Annotation\Publishable; 17 | 18 | /** 19 | * @author Vincent Chalamon 20 | */ 21 | final class PublishableAttributeReader extends AttributeReader 22 | { 23 | /** 24 | * @throws \ReflectionException 25 | */ 26 | public function getConfiguration(object|string $class): Publishable 27 | { 28 | $publishable = $this->getClassAttributeConfiguration($class, Publishable::class); 29 | if (!$publishable instanceof Publishable) { 30 | throw new \LogicException(\sprintf('getClassAnnotationConfiguration should return the type %s', Publishable::class)); 31 | } 32 | 33 | return $publishable; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/AttributeReader/TimestampedAttributeReader.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\AttributeReader; 15 | 16 | use Silverback\ApiComponentsBundle\Annotation\Timestamped; 17 | 18 | /** 19 | * @author Daniel West 20 | */ 21 | final class TimestampedAttributeReader extends AttributeReader 22 | { 23 | public function getConfiguration(object|string $class): Timestamped 24 | { 25 | $timestamped = $this->getClassAttributeConfiguration($class, Timestamped::class); 26 | if (!$timestamped instanceof Timestamped) { 27 | throw new \LogicException(\sprintf('getClassAnnotationConfiguration should return the type %s', Timestamped::class)); 28 | } 29 | 30 | return $timestamped; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/AttributeReader/UploadableAttributeReaderInterface.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\AttributeReader; 15 | 16 | use Silverback\ApiComponentsBundle\Annotation\Uploadable; 17 | use Silverback\ApiComponentsBundle\Annotation\UploadableField; 18 | 19 | /** 20 | * @author Daniel West 21 | */ 22 | interface UploadableAttributeReaderInterface extends AttributeReaderInterface 23 | { 24 | public function getConfiguration(object|string $class): Uploadable; 25 | 26 | public function isFieldConfigured(\ReflectionProperty $property): bool; 27 | 28 | public function getPropertyConfiguration(\ReflectionProperty $property): UploadableField; 29 | 30 | public function getConfiguredProperties(object|string $data, bool $skipUploadableCheck = false): iterable; 31 | } 32 | -------------------------------------------------------------------------------- /src/Cache/CachedTrait.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\Cache; 15 | 16 | use Psr\Cache\CacheException; 17 | use Psr\Cache\CacheItemPoolInterface; 18 | 19 | /** 20 | * @description Copied from API Platform implementation - no author specified in file 21 | * 22 | * @internal 23 | */ 24 | trait CachedTrait 25 | { 26 | private CacheItemPoolInterface $cacheItemPool; 27 | private array $localCache = []; 28 | 29 | private function getCached(string $cacheKey, callable $getValue) 30 | { 31 | if (\array_key_exists($cacheKey, $this->localCache)) { 32 | return $this->localCache[$cacheKey]; 33 | } 34 | 35 | try { 36 | $cacheItem = $this->cacheItemPool->getItem($cacheKey); 37 | } catch (CacheException $e) { 38 | return $this->localCache[$cacheKey] = $getValue(); 39 | } 40 | 41 | if ($cacheItem->isHit()) { 42 | return $this->localCache[$cacheKey] = $cacheItem->get(); 43 | } 44 | 45 | $value = $getValue(); 46 | 47 | $cacheItem->set($value); 48 | $this->cacheItemPool->save($cacheItem); 49 | 50 | return $this->localCache[$cacheKey] = $value; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Command/FormCachePurgeCommand.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\Command; 15 | 16 | use Silverback\ApiComponentsBundle\Event\CommandLogEvent; 17 | use Silverback\ApiComponentsBundle\Helper\Form\FormCachePurger; 18 | use Symfony\Component\Console\Attribute\AsCommand; 19 | use Symfony\Component\Console\Command\Command; 20 | use Symfony\Component\Console\Input\InputInterface; 21 | use Symfony\Component\Console\Output\OutputInterface; 22 | use Symfony\Component\EventDispatcher\EventDispatcherInterface; 23 | 24 | /** 25 | * @author Daniel West 26 | */ 27 | #[AsCommand(name: 'silverback:api-components:form-cache-purge', description: 'Purges the varnish cache for forms. Sets the `modified` timestamp to the file last modified date')] 28 | class FormCachePurgeCommand extends Command 29 | { 30 | private FormCachePurger $formCachePurger; 31 | private EventDispatcherInterface $dispatcher; 32 | 33 | public function __construct(FormCachePurger $formCachePurger, EventDispatcherInterface $dispatcher) 34 | { 35 | parent::__construct(); 36 | $this->formCachePurger = $formCachePurger; 37 | $this->dispatcher = $dispatcher; 38 | } 39 | 40 | /** 41 | * @return int|void|null 42 | */ 43 | protected function execute(InputInterface $input, OutputInterface $output): int 44 | { 45 | $this->dispatcher->addListener( 46 | CommandLogEvent::class, 47 | static function (CommandLogEvent $event) use ($output) { 48 | $output->writeln($event->getSubject()); 49 | } 50 | ); 51 | $this->formCachePurger->clear(); 52 | $output->writeln('Form cache purge complete'); 53 | 54 | return 0; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/DataProvider/StateProvider/PageDataMetadataStateProvider.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\DataProvider\StateProvider; 15 | 16 | use ApiPlatform\Metadata\CollectionOperationInterface; 17 | use ApiPlatform\Metadata\Operation; 18 | use ApiPlatform\State\ProviderInterface; 19 | use Silverback\ApiComponentsBundle\Metadata\Factory\PageDataMetadataFactoryInterface; 20 | use Silverback\ApiComponentsBundle\Metadata\Provider\PageDataMetadataProvider; 21 | 22 | /** 23 | * @author Daniel West 24 | */ 25 | class PageDataMetadataStateProvider implements ProviderInterface 26 | { 27 | public function __construct(private readonly PageDataMetadataFactoryInterface $pageDataMetadataFactory, private readonly PageDataMetadataProvider $pageDataMetadataProvider) 28 | { 29 | } 30 | 31 | public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null 32 | { 33 | if ($operation instanceof CollectionOperationInterface) { 34 | return $this->pageDataMetadataProvider->createAll(); 35 | } 36 | 37 | return $this->pageDataMetadataFactory->create($uriVariables['resourceClass']); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/DataProvider/StateProvider/RouteStateProvider.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\DataProvider\StateProvider; 15 | 16 | use ApiPlatform\Doctrine\Orm\State\CollectionProvider; 17 | use ApiPlatform\Doctrine\Orm\State\ItemProvider; 18 | use ApiPlatform\Metadata\CollectionOperationInterface; 19 | use ApiPlatform\Metadata\Operation; 20 | use ApiPlatform\State\ProviderInterface; 21 | use Silverback\ApiComponentsBundle\Repository\Core\RouteRepository; 22 | 23 | /** 24 | * @author Daniel West 25 | */ 26 | class RouteStateProvider implements ProviderInterface 27 | { 28 | private RouteRepository $routeRepository; 29 | private ProviderInterface $defaultProvider; 30 | 31 | public function __construct(RouteRepository $routeRepository, ProviderInterface $defaultProvider) 32 | { 33 | $this->routeRepository = $routeRepository; 34 | $this->defaultProvider = $defaultProvider; 35 | } 36 | 37 | public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null 38 | { 39 | if ($operation instanceof CollectionOperationInterface) { 40 | return $this->defaultProvider->provide($operation->withProvider(CollectionProvider::class), $uriVariables, $context); 41 | } 42 | 43 | $id = $uriVariables['id']; 44 | if (!\is_string($id)) { 45 | return $this->defaultProvider->provide($operation->withProvider(ItemProvider::class), $uriVariables, $context); 46 | } 47 | 48 | return $this->routeRepository->findOneByIdOrPath($id); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/DataProvider/StateProvider/UserStateProvider.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\DataProvider\StateProvider; 15 | 16 | use ApiPlatform\Metadata\Operation; 17 | use ApiPlatform\State\ProviderInterface; 18 | use Silverback\ApiComponentsBundle\Repository\User\UserRepositoryInterface; 19 | use Symfony\Component\HttpFoundation\RequestStack; 20 | 21 | /** 22 | * @author Daniel West 23 | */ 24 | class UserStateProvider implements ProviderInterface 25 | { 26 | private UserRepositoryInterface $userRepository; 27 | private RequestStack $requestStack; 28 | 29 | public function __construct(UserRepositoryInterface $userRepository, RequestStack $requestStack) 30 | { 31 | $this->userRepository = $userRepository; 32 | $this->requestStack = $requestStack; 33 | } 34 | 35 | public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null 36 | { 37 | $request = $this->requestStack->getCurrentRequest(); 38 | if (!$request || !($id = $request->attributes->get('id'))) { 39 | return null; 40 | } 41 | 42 | return $this->userRepository->loadUserByIdentifier($id); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/DependencyInjection/CompilerPass/ApiPlatformCompilerPass.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\DependencyInjection\CompilerPass; 15 | 16 | use Silverback\ApiComponentsBundle\EventListener\Api\CollectionApiEventListener; 17 | use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; 18 | use Symfony\Component\DependencyInjection\ContainerBuilder; 19 | 20 | /** 21 | * @author Daniel West 22 | */ 23 | class ApiPlatformCompilerPass implements CompilerPassInterface 24 | { 25 | public function process(ContainerBuilder $container): void 26 | { 27 | $itemsPerPageParameterName = $container->getParameter('api_platform.collection.pagination.items_per_page_parameter_name'); 28 | 29 | $container->getDefinition(CollectionApiEventListener::class)->setArgument('$itemsPerPageParameterName', $itemsPerPageParameterName); 30 | 31 | if ($container->hasAlias('api_platform.http_cache.purger')) { 32 | // we have implemented fully custom logic 33 | $container->removeDefinition('api_platform.doctrine.listener.http_cache.purge'); 34 | } else { 35 | $container->removeDefinition('silverback.api_components.http_cache.purger'); 36 | } 37 | 38 | $apiPlatformMercurePublishListener = 'api_platform.doctrine.orm.listener.mercure.publish'; 39 | if ($container->hasDefinition($apiPlatformMercurePublishListener)) { 40 | // we have implemented fully custom logic 41 | $container->removeDefinition($apiPlatformMercurePublishListener); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/DependencyInjection/CompilerPass/DoctrineOrmCompilerPass.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\DependencyInjection\CompilerPass; 15 | 16 | use Doctrine\Bundle\DoctrineBundle\DependencyInjection\Compiler\DoctrineOrmMappingsPass; 17 | use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; 18 | use Symfony\Component\DependencyInjection\ContainerBuilder; 19 | 20 | /** 21 | * @author Daniel West 22 | */ 23 | class DoctrineOrmCompilerPass implements CompilerPassInterface 24 | { 25 | public function process(ContainerBuilder $container): void 26 | { 27 | $bundleRoot = $container->getParameter('kernel.bundles_metadata')['SilverbackApiComponentsBundle']['path']; 28 | $namespace = 'Silverback\ApiComponentsBundle\Entity'; 29 | $modelDir = realpath($bundleRoot . '/Resources/config/doctrine-orm'); 30 | $mappingPass = DoctrineOrmMappingsPass::createXmlMappingDriver( 31 | [ 32 | $modelDir => $namespace, 33 | ], 34 | ['api_components.orm.manager_name.default'] 35 | ); 36 | $mappingPass->process($container); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/DependencyInjection/CompilerPass/FlysystemCompilerPass.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\DependencyInjection\CompilerPass; 15 | 16 | use League\Flysystem\Filesystem; 17 | use Silverback\ApiComponentsBundle\Flysystem\FilesystemFactory; 18 | use Silverback\ApiComponentsBundle\Flysystem\FilesystemProvider; 19 | use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; 20 | use Symfony\Component\DependencyInjection\ContainerBuilder; 21 | use Symfony\Component\DependencyInjection\Definition; 22 | use Symfony\Component\DependencyInjection\Reference; 23 | 24 | /** 25 | * @author Daniel West 26 | */ 27 | class FlysystemCompilerPass implements CompilerPassInterface 28 | { 29 | public function process(ContainerBuilder $container): void 30 | { 31 | $adapters = $container->findTaggedServiceIds(FilesystemProvider::FILESYSTEM_ADAPTER_TAG); 32 | foreach ($adapters as $adaperId => $tags) { 33 | foreach ($tags as $attributes) { 34 | $definition = new Definition(); 35 | $definition 36 | ->setClass(Filesystem::class) 37 | ->setFactory([new Reference(FilesystemFactory::class), 'create']) 38 | ->setArguments([ 39 | $attributes['alias'], 40 | $attributes['config'] ?? [], 41 | ]); 42 | $serviceName = \sprintf('api_components.filesystem.%s', $attributes['alias']); 43 | $container 44 | ->setDefinition($serviceName, $definition) 45 | ->addTag(FilesystemProvider::FILESYSTEM_TAG, ['alias' => $attributes['alias']]); 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/DependencyInjection/CompilerPass/ImagineCompilerPass.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\DependencyInjection\CompilerPass; 15 | 16 | use Silverback\ApiComponentsBundle\Imagine\CacheManager; 17 | use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; 18 | use Symfony\Component\DependencyInjection\ContainerBuilder; 19 | 20 | /** 21 | * @author Daniel West 22 | */ 23 | class ImagineCompilerPass implements CompilerPassInterface 24 | { 25 | public function process(ContainerBuilder $container): void 26 | { 27 | $definition = $container->getDefinition('liip_imagine.cache.manager'); 28 | $definition->setClass(CacheManager::class); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/DependencyInjection/CompilerPass/SerializerCompilerPass.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\DependencyInjection\CompilerPass; 15 | 16 | use Silverback\ApiComponentsBundle\Serializer\MappingLoader\CwaResourceLoader; 17 | use Silverback\ApiComponentsBundle\Serializer\MappingLoader\PublishableLoader; 18 | use Silverback\ApiComponentsBundle\Serializer\MappingLoader\TimestampedLoader; 19 | use Silverback\ApiComponentsBundle\Serializer\MappingLoader\UploadableLoader; 20 | use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; 21 | use Symfony\Component\DependencyInjection\ContainerBuilder; 22 | use Symfony\Component\DependencyInjection\Reference; 23 | 24 | /** 25 | * @author Vincent Chalamon 26 | */ 27 | class SerializerCompilerPass implements CompilerPassInterface 28 | { 29 | public function process(ContainerBuilder $container): void 30 | { 31 | $definitions = ['serializer.mapping.chain_loader', 'serializer.mapping.cache_warmer']; 32 | foreach ($definitions as $definitonId) { 33 | $definition = $container->getDefinition($definitonId); 34 | $definition->replaceArgument( 35 | 0, 36 | array_merge( 37 | $definition->getArgument(0), 38 | [ 39 | new Reference(PublishableLoader::class), 40 | new Reference(TimestampedLoader::class), 41 | new Reference(CwaResourceLoader::class), 42 | new Reference(UploadableLoader::class), 43 | ] 44 | ) 45 | ); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/DependencyInjection/CompilerPass/ValidatorCompilerPass.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\DependencyInjection\CompilerPass; 15 | 16 | use Silverback\ApiComponentsBundle\Validator\MappingLoader\TimestampedLoader; 17 | use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; 18 | use Symfony\Component\DependencyInjection\ContainerBuilder; 19 | use Symfony\Component\DependencyInjection\Reference; 20 | 21 | /** 22 | * @author Vincent Chalamon 23 | */ 24 | final class ValidatorCompilerPass implements CompilerPassInterface 25 | { 26 | public function process(ContainerBuilder $container): void 27 | { 28 | $container 29 | ->getDefinition('validator.builder') 30 | ->addMethodCall('addLoader', [new Reference(TimestampedLoader::class)]); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Entity/Component/Form.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\Entity\Component; 15 | 16 | use ApiPlatform\Metadata\ApiProperty; 17 | use Doctrine\ORM\Mapping as ORM; 18 | use Silverback\ApiComponentsBundle\Annotation as Silverback; 19 | use Silverback\ApiComponentsBundle\Entity\Core\AbstractComponent; 20 | use Silverback\ApiComponentsBundle\Entity\Utility\TimestampedTrait; 21 | use Silverback\ApiComponentsBundle\Model\Form\FormView; 22 | use Silverback\ApiComponentsBundle\Validator\Constraints as AcbAssert; 23 | use Symfony\Component\Validator\Constraints as Assert; 24 | use Symfony\Component\Validator\Mapping\ClassMetadata; 25 | 26 | /** 27 | * @author Daniel West 28 | */ 29 | #[Silverback\Timestamped] 30 | #[ORM\Entity] 31 | class Form extends AbstractComponent 32 | { 33 | use TimestampedTrait; 34 | 35 | #[ORM\Column(nullable: false)] 36 | public string $formType; 37 | 38 | #[ApiProperty(writable: false)] 39 | public ?FormView $formView = null; 40 | 41 | public static function loadValidatorMetadata(ClassMetadata $metadata): void 42 | { 43 | $metadata->addPropertyConstraints( 44 | 'formType', 45 | [ 46 | new Assert\NotBlank(), 47 | new AcbAssert\FormTypeClass(), 48 | ] 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Entity/Core/AbstractPageData.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\Entity\Core; 15 | 16 | use ApiPlatform\Metadata\ApiResource; 17 | use ApiPlatform\Metadata\Get; 18 | use Symfony\Component\Serializer\Annotation\Groups; 19 | use Symfony\Component\Validator\Constraints as Assert; 20 | 21 | /** 22 | * We must define this as an API resource, otherwise when serializing and the relation is to this class, 23 | * API Platform does not know that it will be a resource and will make it an object, not an IRI. (same notes as AbstractComponent). 24 | * 25 | * @author Daniel West 26 | */ 27 | #[ApiResource] 28 | #[Get] 29 | abstract class AbstractPageData extends AbstractPage implements PageDataInterface 30 | { 31 | #[Assert\NotBlank(message: 'Please select a page template')] 32 | #[Groups(['Route:manifest:read'])] 33 | public Page $page; 34 | } 35 | -------------------------------------------------------------------------------- /src/Entity/Core/AbstractRefreshToken.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\Entity\Core; 15 | 16 | use Silverback\ApiComponentsBundle\Entity\Utility\IdTrait; 17 | use Silverback\ApiComponentsBundle\RefreshToken\RefreshToken; 18 | 19 | /** 20 | * @author Vincent Chalamon 21 | */ 22 | abstract class AbstractRefreshToken extends RefreshToken 23 | { 24 | use IdTrait; 25 | } 26 | -------------------------------------------------------------------------------- /src/Entity/Core/ComponentInterface.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\Entity\Core; 15 | 16 | /** 17 | * @author Daniel West 18 | */ 19 | interface ComponentInterface 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /src/Entity/Core/FileInfo.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\Entity\Core; 15 | 16 | use Silverback\ApiComponentsBundle\Entity\Utility\IdTrait; 17 | 18 | /** 19 | * Optional entity only mapped if imagine installed. 20 | * 21 | * @author Daniel West 22 | */ 23 | class FileInfo 24 | { 25 | use IdTrait; 26 | 27 | public string $path; 28 | public string $mimeType; 29 | public int $fileSize; 30 | public ?int $width; 31 | public ?int $height; 32 | public ?string $filter; 33 | 34 | public function __construct(string $path, string $mimeType, int $fileSize, ?int $width, ?int $height, ?string $filter = null) 35 | { 36 | $this->path = $path; 37 | $this->mimeType = $mimeType; 38 | $this->width = $width; 39 | $this->height = $height; 40 | $this->fileSize = $fileSize; 41 | $this->filter = $filter; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Entity/Core/PageDataInterface.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\Entity\Core; 15 | 16 | interface PageDataInterface 17 | { 18 | } 19 | -------------------------------------------------------------------------------- /src/Entity/Core/RoutableInterface.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\Entity\Core; 15 | 16 | /** 17 | * @author Daniel West 18 | */ 19 | interface RoutableInterface 20 | { 21 | public function getTitle(): ?string; 22 | 23 | public function getRoute(): ?Route; 24 | 25 | /** 26 | * @return static 27 | */ 28 | public function setRoute(?Route $route); 29 | 30 | public function getParentRoute(): ?Route; 31 | 32 | /** 33 | * @return static 34 | */ 35 | public function setParentRoute(?Route $parentRoute); 36 | } 37 | -------------------------------------------------------------------------------- /src/Entity/Utility/IdTrait.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\Entity\Utility; 15 | 16 | use ApiPlatform\Metadata\ApiProperty; 17 | use Doctrine\ORM\Mapping as ORM; 18 | use Ramsey\Uuid\Doctrine\UuidGenerator; 19 | use Ramsey\Uuid\UuidInterface; 20 | 21 | /** 22 | * Reusable trait by application developer so keep annotations as we cannot map with XML. 23 | * 24 | * @author Daniel West 25 | */ 26 | trait IdTrait 27 | { 28 | /** 29 | * Must allow return `null` for lowest dependencies. 30 | */ 31 | #[ORM\Id] 32 | #[ORM\Column(type: 'uuid', unique: true, nullable: false)] 33 | #[ORM\GeneratedValue(strategy: 'CUSTOM')] 34 | #[ORM\CustomIdGenerator(class: UuidGenerator::class)] 35 | #[ApiProperty(readable: false, identifier: true)] 36 | protected ?UuidInterface $id = null; 37 | 38 | public function getId(): ?UuidInterface 39 | { 40 | return $this->id; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Entity/Utility/ImagineFiltersInterface.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\Entity\Utility; 15 | 16 | use Symfony\Component\HttpFoundation\Request; 17 | 18 | /** 19 | * @author Daniel West 20 | */ 21 | interface ImagineFiltersInterface 22 | { 23 | public function getImagineFilters(string $property, ?Request $request): array; 24 | } 25 | -------------------------------------------------------------------------------- /src/Entity/Utility/PublishableTrait.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\Entity\Utility; 15 | 16 | /** 17 | * @author Vincent Chalamon 18 | */ 19 | trait PublishableTrait 20 | { 21 | // Needs to be protected instead of published so reflection can read property when merging draft into published 22 | protected ?\DateTimeInterface $publishedAt = null; 23 | 24 | protected ?self $publishedResource = null; 25 | 26 | protected ?self $draftResource = null; 27 | 28 | /** @return static */ 29 | public function setPublishedAt(?\DateTimeInterface $publishedAt) 30 | { 31 | $this->publishedAt = $publishedAt; 32 | 33 | return $this; 34 | } 35 | 36 | public function getPublishedAt(): ?\DateTimeInterface 37 | { 38 | return $this->publishedAt; 39 | } 40 | 41 | /** @return static */ 42 | public function setPublishedResource($publishedResource) 43 | { 44 | $this->publishedResource = $publishedResource; 45 | 46 | return $this; 47 | } 48 | 49 | public function getPublishedResource(): ?self 50 | { 51 | return $this->publishedResource; 52 | } 53 | 54 | public function getDraftResource(): ?self 55 | { 56 | return $this->draftResource; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Entity/Utility/TimestampedTrait.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\Entity\Utility; 15 | 16 | /** 17 | * @author Daniel West 18 | */ 19 | trait TimestampedTrait 20 | { 21 | public ?\DateTimeImmutable $createdAt = null; 22 | 23 | public ?\DateTime $modifiedAt = null; 24 | 25 | /** @return static */ 26 | public function setCreatedAt(\DateTimeImmutable $createdAt) 27 | { 28 | if (!$this->createdAt) { 29 | $this->createdAt = $createdAt; 30 | } 31 | 32 | return $this; 33 | } 34 | 35 | public function getCreatedAt(): ?\DateTimeImmutable 36 | { 37 | return $this->createdAt; 38 | } 39 | 40 | /** @return static */ 41 | public function setModifiedAt(\DateTime $modifiedAt) 42 | { 43 | $this->modifiedAt = $modifiedAt; 44 | 45 | return $this; 46 | } 47 | 48 | public function getModifiedAt(): ?\DateTime 49 | { 50 | return $this->modifiedAt; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Entity/Utility/UploadableTrait.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\Entity\Utility; 15 | 16 | /** 17 | * @author Daniel West 18 | * @author Vincent Chalamon 19 | */ 20 | trait UploadableTrait 21 | { 22 | private ?string $filename = null; 23 | 24 | public function getFilename(): ?string 25 | { 26 | return $this->filename; 27 | } 28 | 29 | /** 30 | * @return static 31 | */ 32 | public function setFilename(string $filename) 33 | { 34 | $this->filename = $filename; 35 | 36 | return $this; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Event/CommandLogEvent.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\Event; 15 | 16 | use Symfony\Component\EventDispatcher\GenericEvent; 17 | 18 | class CommandLogEvent extends GenericEvent 19 | { 20 | } 21 | -------------------------------------------------------------------------------- /src/Event/FormSuccessEvent.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\Event; 15 | 16 | use Silverback\ApiComponentsBundle\Entity\Component\Form; 17 | use Symfony\Contracts\EventDispatcher\Event; 18 | 19 | /** 20 | * @author Daniel West 21 | */ 22 | class FormSuccessEvent extends Event 23 | { 24 | private Form $form; 25 | 26 | public $result; 27 | 28 | public function __construct(Form $form) 29 | { 30 | $this->form = $form; 31 | } 32 | 33 | public function getForm(): Form 34 | { 35 | return $this->form; 36 | } 37 | 38 | public function getFormData() 39 | { 40 | return $this->form->formView->getForm()->getData(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Event/ImagineRemoveEvent.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\Event; 15 | 16 | use Symfony\Contracts\EventDispatcher\Event; 17 | 18 | /** 19 | * @author Daniel West 20 | */ 21 | class ImagineRemoveEvent extends Event 22 | { 23 | public ?array $paths; 24 | public ?array $filters; 25 | 26 | public function __construct(?array $paths, ?array $filters) 27 | { 28 | $this->paths = $paths; 29 | $this->filters = $filters; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Event/ImagineStoreEvent.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\Event; 15 | 16 | use Liip\ImagineBundle\Binary\BinaryInterface; 17 | use Symfony\Contracts\EventDispatcher\Event; 18 | 19 | /** 20 | * @author Daniel West 21 | */ 22 | class ImagineStoreEvent extends Event 23 | { 24 | public BinaryInterface $binary; 25 | public string $path; 26 | public string $filter; 27 | 28 | public function __construct(BinaryInterface $binary, string $path, string $filter) 29 | { 30 | $this->binary = $binary; 31 | $this->path = $path; 32 | $this->filter = $filter; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Event/JWTRefreshedEvent.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\Event; 15 | 16 | use Symfony\Contracts\EventDispatcher\Event; 17 | 18 | /** 19 | * @author Vincent Chalamon 20 | */ 21 | final class JWTRefreshedEvent extends Event 22 | { 23 | public const EVENT_NAME = 'lexik_jwt_authentication.on_jwt_refreshed'; 24 | 25 | private string $token; 26 | 27 | public function __construct(string $token) 28 | { 29 | $this->token = $token; 30 | } 31 | 32 | public function getToken(): string 33 | { 34 | return $this->token; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Event/ResourceChangedEvent.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\Event; 15 | 16 | use Symfony\Contracts\EventDispatcher\Event; 17 | 18 | /** 19 | * @author Daniel West 20 | */ 21 | class ResourceChangedEvent extends Event 22 | { 23 | public function __construct(private readonly object $resource, private readonly string $type) 24 | { 25 | } 26 | 27 | public function getResource(): object 28 | { 29 | return $this->resource; 30 | } 31 | 32 | public function getType(): string 33 | { 34 | return $this->type; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Event/UserEmailMessageEvent.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\Event; 15 | 16 | use Symfony\Bridge\Twig\Mime\TemplatedEmail; 17 | use Symfony\Contracts\EventDispatcher\Event; 18 | 19 | /** 20 | * @author Daniel West 21 | */ 22 | class UserEmailMessageEvent extends Event 23 | { 24 | private string $factoryClass; 25 | private TemplatedEmail $email; 26 | 27 | public function __construct(string $factoryClass, TemplatedEmail $email) 28 | { 29 | $this->factoryClass = $factoryClass; 30 | $this->email = $email; 31 | } 32 | 33 | public function getFactoryClass(): string 34 | { 35 | return $this->factoryClass; 36 | } 37 | 38 | public function setEmail(TemplatedEmail $email): self 39 | { 40 | $this->email = $email; 41 | 42 | return $this; 43 | } 44 | 45 | public function getEmail(): TemplatedEmail 46 | { 47 | return $this->email; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/EventListener/Api/ApiEventListenerTrait.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\EventListener\Api; 15 | 16 | use ApiPlatform\Metadata\Operation; 17 | use JetBrains\PhpStorm\ArrayShape; 18 | use Symfony\Component\HttpFoundation\Request; 19 | 20 | /** 21 | * @author Daniel West 22 | */ 23 | trait ApiEventListenerTrait 24 | { 25 | #[ArrayShape(['data' => 'mixed', 'class' => 'string', 'operation' => Operation::class])] 26 | private function getAttributes(Request $request): array 27 | { 28 | $operation = $request->attributes->get('_api_operation'); 29 | $data = $request->attributes->get('data'); 30 | $class = null; 31 | if (\is_object($data)) { 32 | $class = $data::class; 33 | } else { 34 | $normalizationContext = $request->attributes->get('_api_normalization_context'); 35 | if ($normalizationContext && isset($normalizationContext['resource_class'])) { 36 | $class = $normalizationContext['resource_class']; 37 | } 38 | } 39 | 40 | return [ 41 | 'data' => $data, 42 | 'class' => $class, 43 | 'operation' => $operation, 44 | ]; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/EventListener/Api/ComponentUsageEventListener.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\EventListener\Api; 15 | 16 | use Silverback\ApiComponentsBundle\Entity\Core\ComponentInterface; 17 | use Silverback\ApiComponentsBundle\Metadata\Factory\ComponentUsageMetadataFactory; 18 | use Symfony\Component\HttpKernel\Event\RequestEvent; 19 | 20 | /** 21 | * If the usage endpoint is used for a component, this will override the output with usage metadata. 22 | * 23 | * @author Daniel West 24 | */ 25 | class ComponentUsageEventListener 26 | { 27 | private ComponentUsageMetadataFactory $metadataFactory; 28 | 29 | public function __construct(ComponentUsageMetadataFactory $metadataFactory) 30 | { 31 | $this->metadataFactory = $metadataFactory; 32 | } 33 | 34 | public function onPostRead(RequestEvent $event): void 35 | { 36 | $request = $event->getRequest(); 37 | $data = $request->attributes->get('data'); 38 | $operationName = $request->attributes->get('_api_operation_name'); 39 | 40 | if ( 41 | empty($data) 42 | || !$data instanceof ComponentInterface 43 | || !str_ends_with($operationName, '_get_usage') 44 | ) { 45 | return; 46 | } 47 | 48 | $request->attributes->set('data', $this->metadataFactory->create($data)); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/EventListener/Doctrine/DoctrineResourceFlushListenerInterface.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\EventListener\Doctrine; 15 | 16 | use Doctrine\ORM\Event\OnFlushEventArgs; 17 | use Doctrine\ORM\Event\PostFlushEventArgs; 18 | 19 | /** 20 | * Purges desired resources on when doctrine is flushed from the proxy cache. 21 | * Will purge resources with related mapping too. 22 | * 23 | * @author Daniel West 24 | * 25 | * @experimental 26 | */ 27 | interface DoctrineResourceFlushListenerInterface 28 | { 29 | public function onFlush(OnFlushEventArgs $eventArgs): void; 30 | 31 | public function postFlush(PostFlushEventArgs $eventArgs): void; 32 | } 33 | -------------------------------------------------------------------------------- /src/EventListener/Doctrine/SqlLiteForeignKeyEnabler.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\EventListener\Doctrine; 15 | 16 | use Doctrine\DBAL\Platforms\SqlitePlatform; 17 | use Doctrine\ORM\Event\PreFlushEventArgs; 18 | 19 | class SqlLiteForeignKeyEnabler 20 | { 21 | public function preFlush(PreFlushEventArgs $args): void 22 | { 23 | $conn = $args->getObjectManager()->getConnection(); 24 | if (!$conn->getDatabasePlatform() instanceof SqlitePlatform) { 25 | return; 26 | } 27 | 28 | $conn->executeStatement('PRAGMA foreign_keys = ON;'); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/EventListener/Form/FormSuccessEventListenerInterface.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\EventListener\Form; 15 | 16 | use Silverback\ApiComponentsBundle\Event\FormSuccessEvent; 17 | 18 | interface FormSuccessEventListenerInterface 19 | { 20 | public function __invoke(FormSuccessEvent $event): void; 21 | } 22 | -------------------------------------------------------------------------------- /src/EventListener/Form/User/ChangePasswordListener.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\EventListener\Form\User; 15 | 16 | use Silverback\ApiComponentsBundle\Entity\User\AbstractUser; 17 | use Silverback\ApiComponentsBundle\EventListener\Form\EntityPersistFormListener; 18 | use Silverback\ApiComponentsBundle\Form\Type\User\ChangePasswordType; 19 | 20 | /** 21 | * @author Daniel West 22 | */ 23 | class ChangePasswordListener extends EntityPersistFormListener 24 | { 25 | public function __construct() 26 | { 27 | parent::__construct(ChangePasswordType::class, AbstractUser::class); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/EventListener/Form/User/NewEmailAddressListener.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\EventListener\Form\User; 15 | 16 | use Silverback\ApiComponentsBundle\Entity\User\AbstractUser; 17 | use Silverback\ApiComponentsBundle\EventListener\Form\EntityPersistFormListener; 18 | use Silverback\ApiComponentsBundle\Form\Type\User\NewEmailAddressType; 19 | 20 | /** 21 | * @author Daniel West 22 | */ 23 | class NewEmailAddressListener extends EntityPersistFormListener 24 | { 25 | public function __construct() 26 | { 27 | parent::__construct(NewEmailAddressType::class, AbstractUser::class); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/EventListener/Form/User/PasswordUpdateListener.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\EventListener\Form\User; 15 | 16 | use Silverback\ApiComponentsBundle\Entity\User\AbstractUser; 17 | use Silverback\ApiComponentsBundle\Event\FormSuccessEvent; 18 | use Silverback\ApiComponentsBundle\EventListener\Form\EntityPersistFormListener; 19 | use Silverback\ApiComponentsBundle\Form\Type\User\PasswordUpdateType; 20 | use Silverback\ApiComponentsBundle\Helper\User\UserDataProcessor; 21 | use Symfony\Component\HttpFoundation\Response; 22 | 23 | /** 24 | * @author Daniel West 25 | */ 26 | class PasswordUpdateListener extends EntityPersistFormListener 27 | { 28 | public function __construct(private readonly UserDataProcessor $userDataProcessor) 29 | { 30 | parent::__construct(PasswordUpdateType::class, AbstractUser::class, false); 31 | } 32 | 33 | public function __invoke(FormSuccessEvent $event): void 34 | { 35 | $formDataUser = $event->getFormData(); 36 | if ( 37 | !$formDataUser instanceof AbstractUser 38 | || PasswordUpdateType::class !== $event->getForm()->formType 39 | ) { 40 | return; 41 | } 42 | 43 | $user = $this->userDataProcessor->passwordReset($formDataUser->getUsername(), (string) $formDataUser->plainNewPasswordConfirmationToken, (string) $formDataUser->getPlainPassword()); 44 | 45 | if (!$user) { 46 | $event->result = new Response(null, Response::HTTP_NOT_FOUND); 47 | 48 | return; 49 | } 50 | 51 | parent::__invoke($event); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/EventListener/Form/User/UserRegisterListener.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\EventListener\Form\User; 15 | 16 | use Silverback\ApiComponentsBundle\Entity\User\AbstractUser; 17 | use Silverback\ApiComponentsBundle\EventListener\Form\EntityPersistFormListener; 18 | use Silverback\ApiComponentsBundle\Form\Type\User\UserRegisterType; 19 | 20 | /** 21 | * @author Daniel West 22 | */ 23 | class UserRegisterListener extends EntityPersistFormListener 24 | { 25 | public function __construct() 26 | { 27 | parent::__construct(UserRegisterType::class, AbstractUser::class); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/EventListener/Imagine/ImagineEventListener.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\EventListener\Imagine; 15 | 16 | use Silverback\ApiComponentsBundle\Entity\Core\FileInfo; 17 | use Silverback\ApiComponentsBundle\Event\ImagineRemoveEvent; 18 | use Silverback\ApiComponentsBundle\Event\ImagineStoreEvent; 19 | use Silverback\ApiComponentsBundle\Helper\Uploadable\FileInfoCacheManager; 20 | 21 | /** 22 | * This will listen to cache events in imagine bundle and persist the metadata to the database 23 | * ISSUE! We may be in the middle of other transactions too as the main part of our REST API. 24 | * We should modify this to isolate this to only persist/flush changes made here. 25 | * 26 | * Possibly duplicate the entity manager? If we can just create a new Unit Of Work? 27 | * 28 | * @author Daniel West 29 | */ 30 | class ImagineEventListener 31 | { 32 | private FileInfoCacheManager $fileInfoCacheManager; 33 | 34 | public function __construct(FileInfoCacheManager $fileInfoCacheManager) 35 | { 36 | $this->fileInfoCacheManager = $fileInfoCacheManager; 37 | } 38 | 39 | public function onStore(ImagineStoreEvent $event): void 40 | { 41 | $content = $event->binary->getContent(); 42 | [$width, $height] = getimagesizefromstring($content); 43 | $fileSize = \strlen($content); 44 | 45 | $fileInfo = new FileInfo($event->path, $event->binary->getMimeType(), $fileSize, $width, $height, $event->filter); 46 | $this->fileInfoCacheManager->saveCache($fileInfo); 47 | } 48 | 49 | public function onRemove(ImagineRemoveEvent $event): void 50 | { 51 | $this->fileInfoCacheManager->deleteCaches($event->paths, $event->filters); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/EventListener/Jwt/JWTClearTokenListener.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\EventListener\Jwt; 15 | 16 | use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTExpiredEvent; 17 | use Lexik\Bundle\JWTAuthenticationBundle\Event\JWTInvalidEvent; 18 | use Lexik\Bundle\JWTAuthenticationBundle\Security\Http\Cookie\JWTCookieProvider; 19 | use Silverback\ApiComponentsBundle\Mercure\MercureAuthorization; 20 | use Symfony\Component\HttpFoundation\Response; 21 | 22 | /** 23 | * @author Daniel West 24 | */ 25 | class JWTClearTokenListener 26 | { 27 | public function __construct( 28 | private readonly JWTCookieProvider $cookieProvider, 29 | private readonly MercureAuthorization $mercureAuthorization, 30 | ) { 31 | } 32 | 33 | public function onJwtInvalid(JWTInvalidEvent $event): void 34 | { 35 | $this->clearJwtCookie($event->getResponse()); 36 | } 37 | 38 | public function onJwtExpired(JWTExpiredEvent $event): void 39 | { 40 | $this->clearJwtCookie($event->getResponse()); 41 | } 42 | 43 | private function clearJwtCookie(Response $response): void 44 | { 45 | $response->headers->setCookie($this->cookieProvider->createCookie('x.x.x', null, 1)); 46 | $response->headers->setCookie($this->mercureAuthorization->getClearAuthorizationCookie()); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/EventListener/Mailer/MessageEventListener.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\EventListener\Mailer; 15 | 16 | use Symfony\Component\Mailer\Event\MessageEvent; 17 | use Symfony\Component\Mime\Address; 18 | use Symfony\Component\Mime\Email; 19 | 20 | /** 21 | * @author Daniel West 22 | */ 23 | class MessageEventListener 24 | { 25 | private string $fromEmailAddress; 26 | 27 | public function __construct(string $fromEmailAddress) 28 | { 29 | $this->fromEmailAddress = $fromEmailAddress; 30 | } 31 | 32 | public function __invoke(MessageEvent $messageEvent): void 33 | { 34 | $message = $messageEvent->getMessage(); 35 | if (!$message instanceof Email) { 36 | return; 37 | } 38 | // symfony/mime 5.2 deprecated fromString 39 | if (method_exists(Address::class, 'create')) { 40 | $toEmailAddress = Address::create($this->fromEmailAddress); 41 | } else { 42 | $toEmailAddress = Address::fromString($this->fromEmailAddress); 43 | } 44 | $message->from($toEmailAddress); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/EventListener/ResourceChangedEventListener.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\EventListener; 15 | 16 | use Silverback\ApiComponentsBundle\Event\ResourceChangedEvent; 17 | use Silverback\ApiComponentsBundle\HttpCache\ResourceChangedPropagatorInterface; 18 | 19 | class ResourceChangedEventListener 20 | { 21 | public function __construct(private readonly iterable $resourceChangedPropagators) 22 | { 23 | } 24 | 25 | public function __invoke(ResourceChangedEvent $event): void 26 | { 27 | /** @var ResourceChangedPropagatorInterface $resourceChangedPropagator */ 28 | foreach ($this->resourceChangedPropagators as $resourceChangedPropagator) { 29 | $resourceChangedPropagator->add($event->getResource(), $event->getType()); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Exception/ApiPlatformAuthenticationException.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\Exception; 15 | 16 | use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException; 17 | 18 | /** 19 | * @author Daniel West 20 | */ 21 | class ApiPlatformAuthenticationException extends CustomUserMessageAuthenticationException 22 | { 23 | } 24 | -------------------------------------------------------------------------------- /src/Exception/BadMethodCallException.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\Exception; 15 | 16 | /** 17 | * @author Daniel West 18 | */ 19 | class BadMethodCallException extends \BadMethodCallException 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /src/Exception/ExceptionInterface.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\Exception; 15 | 16 | /** 17 | * @author Daniel West 18 | */ 19 | interface ExceptionInterface extends \Throwable 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /src/Exception/FileMissingException.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\Exception; 15 | 16 | /** 17 | * @author Daniel West 18 | */ 19 | class FileMissingException extends \RuntimeException 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /src/Exception/FileNotImageException.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\Exception; 15 | 16 | /** 17 | * @author Daniel West 18 | */ 19 | class FileNotImageException extends \RuntimeException 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /src/Exception/InvalidArgumentException.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\Exception; 15 | 16 | /** 17 | * @author Daniel West 18 | */ 19 | class InvalidArgumentException extends \InvalidArgumentException 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /src/Exception/MailerTransportException.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\Exception; 15 | 16 | use Symfony\Component\Mailer\Exception\TransportException; 17 | 18 | /** 19 | * @author Daniel West 20 | */ 21 | class MailerTransportException extends TransportException 22 | { 23 | } 24 | -------------------------------------------------------------------------------- /src/Exception/OutOfBoundsException.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\Exception; 15 | 16 | /** 17 | * @author Daniel West 18 | */ 19 | class OutOfBoundsException extends \OutOfBoundsException 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /src/Exception/PageDataNotFoundException.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\Exception; 15 | 16 | /** 17 | * @author Daniel West 18 | */ 19 | class PageDataNotFoundException extends \Exception implements ExceptionInterface 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /src/Exception/RfcComplianceException.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\Exception; 15 | 16 | use Symfony\Component\Mime\Exception\RfcComplianceException as SymfonyRfcComplianceExceptionAlias; 17 | 18 | /** 19 | * @author Daniel West 20 | */ 21 | class RfcComplianceException extends SymfonyRfcComplianceExceptionAlias 22 | { 23 | } 24 | -------------------------------------------------------------------------------- /src/Exception/TokenAuthenticationException.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\Exception; 15 | 16 | use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException; 17 | 18 | /** 19 | * @author Daniel West 20 | */ 21 | class TokenAuthenticationException extends CustomUserMessageAuthenticationException 22 | { 23 | } 24 | -------------------------------------------------------------------------------- /src/Exception/UnexpectedValueException.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\Exception; 15 | 16 | /** 17 | * @author Daniel West 18 | */ 19 | class UnexpectedValueException extends \UnexpectedValueException 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /src/Exception/UnparseableRequestHeaderException.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\Exception; 15 | 16 | /** 17 | * @author Daniel West 18 | */ 19 | class UnparseableRequestHeaderException extends \RuntimeException 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /src/Exception/UnsupportedAnnotationException.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\Exception; 15 | 16 | /** 17 | * @author Daniel West 18 | */ 19 | class UnsupportedAnnotationException extends \LogicException 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /src/Exception/UserDisabledException.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\Exception; 15 | 16 | use Symfony\Component\Security\Core\Exception\DisabledException; 17 | 18 | /** 19 | * @author Daniel West 20 | */ 21 | class UserDisabledException extends DisabledException 22 | { 23 | public function getMessageKey(): string 24 | { 25 | return $this->getMessage(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Exception/UserEmailAddressUnverified.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\Exception; 15 | 16 | use Symfony\Component\Security\Core\Exception\LockedException; 17 | 18 | /** 19 | * @author Daniel West 20 | */ 21 | class UserEmailAddressUnverified extends LockedException 22 | { 23 | public function getMessageKey(): string 24 | { 25 | return $this->getMessage(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Factory/Form/FormViewFactory.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\Factory\Form; 15 | 16 | use ApiPlatform\Metadata\IriConverterInterface; 17 | use Silverback\ApiComponentsBundle\Entity\Component\Form; 18 | use Silverback\ApiComponentsBundle\Model\Form\FormView; 19 | use Symfony\Component\Form\FormFactoryInterface; 20 | use Symfony\Component\HttpFoundation\UrlHelper; 21 | 22 | /** 23 | * @author Daniel West 24 | */ 25 | class FormViewFactory 26 | { 27 | private FormFactoryInterface $formFactory; 28 | private IriConverterInterface $iriConverter; 29 | private UrlHelper $urlHelper; 30 | 31 | public function __construct(FormFactoryInterface $formFactory, IriConverterInterface $iriConverter, UrlHelper $urlHelper) 32 | { 33 | $this->formFactory = $formFactory; 34 | $this->iriConverter = $iriConverter; 35 | $this->urlHelper = $urlHelper; 36 | } 37 | 38 | public function create(Form $form): FormView 39 | { 40 | $builder = $this->formFactory->createBuilder($form->formType); 41 | 42 | if (!$builder->getAction()) { 43 | $builder->setAction($this->getFormAction($form)); 44 | } 45 | 46 | return new FormView($builder->getForm()); 47 | } 48 | 49 | private function getFormAction(Form $form): string 50 | { 51 | return $this->urlHelper->getAbsoluteUrl($this->iriConverter->getIriFromResource($form) . '/submit'); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Factory/Uploadable/ApiUrlGenerator.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\Factory\Uploadable; 15 | 16 | use ApiPlatform\Metadata\IriConverterInterface; 17 | use League\Flysystem\Filesystem; 18 | use Symfony\Component\HttpFoundation\UrlHelper; 19 | use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter; 20 | 21 | class ApiUrlGenerator implements UploadableUrlGeneratorInterface 22 | { 23 | public function __construct(private readonly IriConverterInterface $iriConverter, private readonly UrlHelper $urlHelper) 24 | { 25 | } 26 | 27 | public function generateUrl(object $object, string $fileProperty, Filesystem $filesystem, string $path): string 28 | { 29 | $resourceId = $this->iriConverter->getIriFromResource($object); 30 | $converter = new CamelCaseToSnakeCaseNameConverter(); 31 | 32 | return $this->urlHelper->getAbsoluteUrl(\sprintf('%s/download/%s', $resourceId, $converter->normalize($fileProperty))); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Factory/Uploadable/PublicUrlGenerator.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\Factory\Uploadable; 15 | 16 | use League\Flysystem\Filesystem; 17 | 18 | class PublicUrlGenerator implements UploadableUrlGeneratorInterface 19 | { 20 | public function __construct(private readonly array $config = []) 21 | { 22 | } 23 | 24 | public function generateUrl(object $object, string $fileProperty, Filesystem $filesystem, string $path): string 25 | { 26 | return $filesystem->publicUrl($path, $this->config); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Factory/Uploadable/TemporaryUrlGenerator.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\Factory\Uploadable; 15 | 16 | use League\Flysystem\Filesystem; 17 | 18 | class TemporaryUrlGenerator implements UploadableUrlGeneratorInterface 19 | { 20 | public function __construct(private readonly array $config = [], private readonly string $expires = '+3 days') 21 | { 22 | } 23 | 24 | public function generateUrl(object $object, string $fileProperty, Filesystem $filesystem, string $path): string 25 | { 26 | return $filesystem->temporaryUrl($path, new \DateTime($this->expires), $this->config); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Factory/Uploadable/UploadableUrlGeneratorInterface.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\Factory\Uploadable; 15 | 16 | use League\Flysystem\Filesystem; 17 | 18 | interface UploadableUrlGeneratorInterface 19 | { 20 | public const TAG = 'silveback.api_components.uploadable.url_generator'; 21 | 22 | public function generateUrl(object $object, string $fileProperty, Filesystem $filesystem, string $path): string; 23 | } 24 | -------------------------------------------------------------------------------- /src/Factory/User/Mailer/ChangeEmailConfirmationEmailFactory.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\Factory\User\Mailer; 15 | 16 | use Silverback\ApiComponentsBundle\Entity\User\AbstractUser; 17 | use Silverback\ApiComponentsBundle\Exception\InvalidArgumentException; 18 | use Symfony\Component\Mime\RawMessage; 19 | 20 | /** 21 | * @author Daniel West 22 | */ 23 | final class ChangeEmailConfirmationEmailFactory extends AbstractUserEmailFactory 24 | { 25 | public const MESSAGE_ID_PREFIX = 'cec'; 26 | 27 | public function create(AbstractUser $user, array $context = []): ?RawMessage 28 | { 29 | if (!$this->enabled) { 30 | return null; 31 | } 32 | 33 | $this->initUser($user); 34 | 35 | $token = $user->plainNewEmailConfirmationToken; 36 | $user->plainNewEmailConfirmationToken = null; 37 | if (!$token) { 38 | throw new InvalidArgumentException('A `new email confirmation token` must be set to send the confirmation email'); 39 | } 40 | 41 | $context['redirect_url'] = $this->getTokenUrl($token, $user->getUsername(), $user->getNewEmailAddress()); 42 | 43 | return $this->createEmailMessage($context); 44 | } 45 | 46 | protected static function getContextKeys(): ?array 47 | { 48 | return array_merge( 49 | parent::getContextKeys(), 50 | [ 51 | 'redirect_url', 52 | ] 53 | ); 54 | } 55 | 56 | protected function getTemplate(): string 57 | { 58 | return 'user_change_email_confirmation.html.twig'; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Factory/User/Mailer/PasswordChangedEmailFactory.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\Factory\User\Mailer; 15 | 16 | use Silverback\ApiComponentsBundle\Entity\User\AbstractUser; 17 | use Symfony\Component\Mime\RawMessage; 18 | 19 | /** 20 | * @author Daniel West 21 | */ 22 | final class PasswordChangedEmailFactory extends AbstractUserEmailFactory 23 | { 24 | public const MESSAGE_ID_PREFIX = 'pce'; 25 | 26 | public function create(AbstractUser $user, array $context = []): ?RawMessage 27 | { 28 | if (!$this->enabled) { 29 | return null; 30 | } 31 | $this->initUser($user); 32 | 33 | return $this->createEmailMessage($context); 34 | } 35 | 36 | protected function getTemplate(): string 37 | { 38 | return 'user_password_changed.html.twig'; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Factory/User/Mailer/PasswordResetEmailFactory.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\Factory\User\Mailer; 15 | 16 | use Silverback\ApiComponentsBundle\Entity\User\AbstractUser; 17 | use Silverback\ApiComponentsBundle\Exception\InvalidArgumentException; 18 | use Symfony\Component\Mime\RawMessage; 19 | 20 | /** 21 | * @author Daniel West 22 | */ 23 | final class PasswordResetEmailFactory extends AbstractUserEmailFactory 24 | { 25 | public const MESSAGE_ID_PREFIX = 'pre'; 26 | 27 | public function create(AbstractUser $user, array $context = []): ?RawMessage 28 | { 29 | if (!$this->enabled) { 30 | return null; 31 | } 32 | 33 | $this->initUser($user); 34 | 35 | $token = $user->plainNewPasswordConfirmationToken; 36 | $user->plainNewPasswordConfirmationToken = null; 37 | if (!$token) { 38 | throw new InvalidArgumentException('A new password confirmation token must be set to send the `password reset` email'); 39 | } 40 | 41 | $context['redirect_url'] = $this->getTokenUrl($token, $user->getUsername()); 42 | 43 | return $this->createEmailMessage($context); 44 | } 45 | 46 | protected static function getContextKeys(): ?array 47 | { 48 | return array_merge( 49 | parent::getContextKeys(), 50 | [ 51 | 'redirect_url', 52 | ] 53 | ); 54 | } 55 | 56 | protected function getTemplate(): string 57 | { 58 | return 'user_password_reset.html.twig'; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Factory/User/Mailer/UserEnabledEmailFactory.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\Factory\User\Mailer; 15 | 16 | use Silverback\ApiComponentsBundle\Entity\User\AbstractUser; 17 | use Symfony\Component\Mime\RawMessage; 18 | 19 | /** 20 | * @author Daniel West 21 | */ 22 | final class UserEnabledEmailFactory extends AbstractUserEmailFactory 23 | { 24 | public const MESSAGE_ID_PREFIX = 'uee'; 25 | 26 | public function create(AbstractUser $user, array $context = []): ?RawMessage 27 | { 28 | if (!$this->enabled) { 29 | return null; 30 | } 31 | $this->initUser($user); 32 | 33 | return $this->createEmailMessage($context); 34 | } 35 | 36 | protected function getTemplate(): string 37 | { 38 | return 'user_enabled.html.twig'; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Factory/User/Mailer/UsernameChangedEmailFactory.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\Factory\User\Mailer; 15 | 16 | use Silverback\ApiComponentsBundle\Entity\User\AbstractUser; 17 | use Symfony\Component\Mime\RawMessage; 18 | 19 | /** 20 | * @author Daniel West 21 | */ 22 | final class UsernameChangedEmailFactory extends AbstractUserEmailFactory 23 | { 24 | public const MESSAGE_ID_PREFIX = 'uce'; 25 | 26 | public function create(AbstractUser $user, array $context = []): ?RawMessage 27 | { 28 | if (!$this->enabled) { 29 | return null; 30 | } 31 | $this->initUser($user); 32 | 33 | return $this->createEmailMessage($context); 34 | } 35 | 36 | protected function getTemplate(): string 37 | { 38 | return 'user_username_changed.html.twig'; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Factory/User/Mailer/VerifyEmailFactory.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\Factory\User\Mailer; 15 | 16 | use Silverback\ApiComponentsBundle\Entity\User\AbstractUser; 17 | use Silverback\ApiComponentsBundle\Exception\InvalidArgumentException; 18 | use Symfony\Component\Mime\RawMessage; 19 | 20 | /** 21 | * @author Daniel West 22 | */ 23 | final class VerifyEmailFactory extends AbstractUserEmailFactory 24 | { 25 | public const MESSAGE_ID_PREFIX = 'ver'; 26 | 27 | public function create(AbstractUser $user, array $context = []): ?RawMessage 28 | { 29 | if (!$this->enabled) { 30 | return null; 31 | } 32 | 33 | $this->initUser($user); 34 | 35 | $token = $user->plainEmailAddressVerifyToken; 36 | $user->plainEmailAddressVerifyToken = null; 37 | if (!$token) { 38 | throw new InvalidArgumentException('An `email verify token` must be set to send the verification email'); 39 | } 40 | 41 | $context['redirect_url'] = $this->getTokenUrl($token, $user->getUsername()); 42 | 43 | return $this->createEmailMessage($context); 44 | } 45 | 46 | protected static function getContextKeys(): ?array 47 | { 48 | return array_merge( 49 | parent::getContextKeys(), 50 | [ 51 | 'redirect_url', 52 | ] 53 | ); 54 | } 55 | 56 | protected function getTemplate(): string 57 | { 58 | return 'user_verify_email.html.twig'; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Factory/User/Mailer/WelcomeEmailFactory.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\Factory\User\Mailer; 15 | 16 | use Silverback\ApiComponentsBundle\Entity\User\AbstractUser; 17 | use Symfony\Component\Mime\RawMessage; 18 | 19 | /** 20 | * @author Daniel West 21 | */ 22 | final class WelcomeEmailFactory extends AbstractUserEmailFactory 23 | { 24 | public const MESSAGE_ID_PREFIX = 'wef'; 25 | 26 | public function create(AbstractUser $user, array $context = []): ?RawMessage 27 | { 28 | if (!$this->enabled) { 29 | return null; 30 | } 31 | 32 | $this->initUser($user); 33 | 34 | $token = $user->plainEmailAddressVerifyToken; 35 | $user->plainEmailAddressVerifyToken = null; 36 | if ($token) { 37 | $context['redirect_url'] = $this->getTokenUrl($token, $user->getUsername()); 38 | } 39 | 40 | return $this->createEmailMessage($context); 41 | } 42 | 43 | protected function getTemplate(): string 44 | { 45 | return 'user_welcome.html.twig'; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Flysystem/FilesystemFactory.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\Flysystem; 15 | 16 | use League\Flysystem\Filesystem; 17 | use Symfony\Component\DependencyInjection\Exception\RuntimeException; 18 | use Symfony\Component\DependencyInjection\ServiceLocator; 19 | 20 | /** 21 | * @author Daniel West 22 | */ 23 | class FilesystemFactory 24 | { 25 | public function __construct(private readonly ServiceLocator $adapters) 26 | { 27 | } 28 | 29 | /** 30 | * @throws RuntimeException 31 | */ 32 | public function create(string $name, array $config = []): Filesystem 33 | { 34 | return new Filesystem($this->getAdapter($name), $config); 35 | } 36 | 37 | public function getAdapter(string $name) 38 | { 39 | return $this->adapters->get($name); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Flysystem/FilesystemProvider.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\Flysystem; 15 | 16 | use League\Flysystem\Filesystem; 17 | use Psr\Container\ContainerExceptionInterface; 18 | use Symfony\Component\DependencyInjection\ServiceLocator; 19 | 20 | /** 21 | * @author Daniel West 22 | * @author Vincent Chalamon 23 | */ 24 | class FilesystemProvider 25 | { 26 | public const FILESYSTEM_ADAPTER_TAG = 'silverback.api_components.filesystem_adapter'; 27 | public const FILESYSTEM_TAG = 'silverback.api_components.filesystem'; 28 | 29 | public function __construct(private readonly ServiceLocator $filesystems) 30 | { 31 | } 32 | 33 | /** 34 | * @throws ContainerExceptionInterface 35 | */ 36 | public function getFilesystem(string $name): Filesystem 37 | { 38 | return $this->filesystems->get($name); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Form/AbstractType.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\Form; 15 | 16 | use Silverback\ApiComponentsBundle\Helper\Form\FormSubmitHelper; 17 | use Symfony\Component\Form\AbstractType as BaseAbstractType; 18 | use Symfony\Component\Form\FormInterface; 19 | use Symfony\Component\Form\FormView; 20 | 21 | /** 22 | * @author Daniel West 23 | */ 24 | class AbstractType extends BaseAbstractType implements FormTypeInterface 25 | { 26 | public function buildView(FormView $view, FormInterface $form, array $options): void 27 | { 28 | $view->vars[FormSubmitHelper::FORM_REALTIME_VALIDATE_DISABLED] = $options[FormSubmitHelper::FORM_REALTIME_VALIDATE_DISABLED] ?? false; 29 | $view->vars[FormSubmitHelper::FORM_API_DISABLED] = $options[FormSubmitHelper::FORM_API_DISABLED] ?? false; 30 | $view->vars[FormSubmitHelper::FORM_POST_APP_PROXY] = $options[FormSubmitHelper::FORM_POST_APP_PROXY] ?? null; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Form/FormTypeInterface.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\Form; 15 | 16 | use Symfony\Component\Form\FormTypeInterface as BaseFormTypeInterface; 17 | 18 | /** 19 | * @author Daniel West 20 | */ 21 | interface FormTypeInterface extends BaseFormTypeInterface 22 | { 23 | } 24 | -------------------------------------------------------------------------------- /src/Helper/Route/RouteGeneratorInterface.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\Helper\Route; 15 | 16 | use Silverback\ApiComponentsBundle\Entity\Core\RoutableInterface; 17 | use Silverback\ApiComponentsBundle\Entity\Core\Route; 18 | 19 | /** 20 | * @author Daniel West 21 | */ 22 | interface RouteGeneratorInterface 23 | { 24 | public function create(RoutableInterface $object, ?Route $route = null): Route; 25 | 26 | public function createRedirect(string $fromPath, Route $targetRoute): Route; 27 | } 28 | -------------------------------------------------------------------------------- /src/Helper/Timestamped/TimestampedDataPersister.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\Helper\Timestamped; 15 | 16 | use Doctrine\Persistence\ManagerRegistry; 17 | use Silverback\ApiComponentsBundle\AttributeReader\TimestampedAttributeReader; 18 | use Silverback\ApiComponentsBundle\Utility\ClassMetadataTrait; 19 | 20 | /** 21 | * @author Daniel West 22 | */ 23 | class TimestampedDataPersister 24 | { 25 | use ClassMetadataTrait; 26 | 27 | private TimestampedAttributeReader $annotationReader; 28 | 29 | public function __construct(ManagerRegistry $registry, TimestampedAttributeReader $annotationReader) 30 | { 31 | $this->initRegistry($registry); 32 | $this->annotationReader = $annotationReader; 33 | } 34 | 35 | public function persistTimestampedFields(object $timestamped, bool $isNew): void 36 | { 37 | $configuration = $this->annotationReader->getConfiguration($timestamped); 38 | $classMetadata = $this->getClassMetadata($timestamped); 39 | $classMetadata->setFieldValue( 40 | $timestamped, 41 | $configuration->createdAtField, 42 | $isNew ? 43 | new \DateTimeImmutable() : 44 | $classMetadata->getFieldValue($timestamped, $configuration->createdAtField) 45 | ); 46 | $classMetadata->setFieldValue($timestamped, $configuration->modifiedAtField, new \DateTime()); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Helper/Uploadable/FileInfoCacheManager.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\Helper\Uploadable; 15 | 16 | use Doctrine\ORM\EntityManagerInterface; 17 | use Silverback\ApiComponentsBundle\Entity\Core\FileInfo; 18 | use Silverback\ApiComponentsBundle\Repository\Core\FileInfoRepository; 19 | 20 | class FileInfoCacheManager 21 | { 22 | private EntityManagerInterface $entityManager; 23 | private FileInfoRepository $repository; 24 | 25 | public function __construct(EntityManagerInterface $entityManager, FileInfoRepository $fileInfoRepository) 26 | { 27 | $this->entityManager = $entityManager; 28 | $this->repository = $fileInfoRepository; 29 | } 30 | 31 | public function saveCache(FileInfo $fileInfo): void 32 | { 33 | $this->entityManager->persist($fileInfo); 34 | $this->entityManager->flush(); 35 | } 36 | 37 | public function deleteCaches(array $paths, ?array $filters): void 38 | { 39 | $this->entityManager->getConnection()->beginTransaction(); 40 | $results = $this->repository->findByPathsAndFilters($paths, $filters); 41 | foreach ($results as $result) { 42 | $this->entityManager->remove($result); 43 | } 44 | $this->entityManager->getConnection()->commit(); 45 | } 46 | 47 | public function resolveCache(string $path, ?string $filter = null): ?FileInfo 48 | { 49 | return $this->repository->findOneByPathAndFilter($path, $filter); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/HttpCache/ResourceChangedPropagatorInterface.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\HttpCache; 15 | 16 | interface ResourceChangedPropagatorInterface 17 | { 18 | public function add(object $item, ?string $type = null): void; 19 | 20 | public function propagate(): void; 21 | 22 | public function reset(): void; 23 | } 24 | -------------------------------------------------------------------------------- /src/Imagine/CacheManager.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\Imagine; 15 | 16 | use Liip\ImagineBundle\Binary\BinaryInterface; 17 | use Liip\ImagineBundle\Imagine\Cache\CacheManager as ImagineCacheManager; 18 | use Silverback\ApiComponentsBundle\Event\ImagineRemoveEvent; 19 | use Silverback\ApiComponentsBundle\Event\ImagineStoreEvent; 20 | 21 | /** 22 | * @author Daniel West 23 | */ 24 | final class CacheManager extends ImagineCacheManager 25 | { 26 | public function store(BinaryInterface $binary, $path, $filter, $resolver = null): void 27 | { 28 | parent::store($binary, $path, $filter, $resolver); 29 | // when we dispatch the event, mercure processes may run, resulting in us fetching the resource, and then fetching 30 | // the resource will request the image, look for a cache that isn't there and then call this again. SO must trigger after we actually have the cache saved 31 | $event = new ImagineStoreEvent($binary, $path, $filter); 32 | $this->dispatch($event, ImagineStoreEvent::class); 33 | } 34 | 35 | public function remove($paths = null, $filters = null): void 36 | { 37 | parent::remove($paths, $filters); 38 | $event = new ImagineRemoveEvent($paths, $filters); 39 | $this->dispatch($event, ImagineRemoveEvent::class); 40 | } 41 | 42 | private function dispatch($event, $eventName): void 43 | { 44 | $this->dispatcher->dispatch($event, $eventName); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Imagine/FlysystemDataLoader.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\Imagine; 15 | 16 | use Liip\ImagineBundle\Binary\Loader\LoaderInterface; 17 | use Liip\ImagineBundle\Exception\Binary\Loader\NotLoadableException; 18 | use Liip\ImagineBundle\Model\Binary; 19 | use Silverback\ApiComponentsBundle\Flysystem\FilesystemProvider; 20 | use Symfony\Component\Mime\MimeTypes; 21 | 22 | /** 23 | * @author Daniel West 24 | */ 25 | class FlysystemDataLoader implements LoaderInterface 26 | { 27 | protected FilesystemProvider $filesystemProvider; 28 | private ?string $adapter = null; 29 | 30 | public function __construct(FilesystemProvider $filesystemProvider) 31 | { 32 | $this->filesystemProvider = $filesystemProvider; 33 | } 34 | 35 | public function setAdapter(?string $adapter): void 36 | { 37 | $this->adapter = $adapter; 38 | } 39 | 40 | /** 41 | * {@inheritdoc} 42 | */ 43 | public function find($path): Binary 44 | { 45 | $filesystem = $this->filesystemProvider->getFilesystem($this->adapter); 46 | 47 | // This should be finding the file that we have uploaded into a location already - source file locator 48 | if (false === $filesystem->fileExists($path)) { 49 | throw new NotLoadableException(\sprintf('Source image "%s" not found.', $path)); 50 | } 51 | 52 | $mimeType = $filesystem->mimeType($path); 53 | 54 | $extension = MimeTypes::getDefault()->getExtensions($mimeType)[0]; 55 | 56 | return new Binary($filesystem->read($path), $mimeType, $extension); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Mercure/DispatchTrait.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\Mercure; 15 | 16 | use Symfony\Component\Messenger\Envelope; 17 | use Symfony\Component\Messenger\Exception\HandlerFailedException; 18 | use Symfony\Component\Messenger\MessageBusInterface; 19 | 20 | /** 21 | * @internal 22 | */ 23 | trait DispatchTrait 24 | { 25 | private ?MessageBusInterface $messageBus; 26 | 27 | /** 28 | * @param object|Envelope $message 29 | */ 30 | private function dispatch(object $message): Envelope 31 | { 32 | if (!$this->messageBus instanceof MessageBusInterface) { 33 | throw new \InvalidArgumentException('The message bus is not set.'); 34 | } 35 | 36 | if (!class_exists(HandlerFailedException::class)) { 37 | return $this->messageBus->dispatch($message); 38 | } 39 | 40 | try { 41 | return $this->messageBus->dispatch($message); 42 | } catch (HandlerFailedException $e) { 43 | // unwrap the exception thrown in handler for Symfony Messenger >= 4.3 44 | while ($e instanceof HandlerFailedException) { 45 | /** @var \Throwable $e */ 46 | $e = $e->getPrevious(); 47 | } 48 | 49 | throw $e; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Metadata/ComponentUsageMetadata.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\Metadata; 15 | 16 | use Symfony\Component\Serializer\Annotation\Groups; 17 | 18 | /** 19 | * @author Daniel West 20 | */ 21 | class ComponentUsageMetadata 22 | { 23 | #[Groups(['AbstractComponent:cwa_resource:read'])] 24 | private int $positionCount; 25 | 26 | #[Groups(['AbstractComponent:cwa_resource:read'])] 27 | private int $pageDataCount; 28 | 29 | public function __construct(?int $positionCount = null, ?int $pageDataCount = null) 30 | { 31 | $this->positionCount = $positionCount; 32 | $this->pageDataCount = $pageDataCount; 33 | } 34 | 35 | public function getPositionCount(): int 36 | { 37 | return $this->positionCount; 38 | } 39 | 40 | public function getPageDataCount(): int 41 | { 42 | return $this->pageDataCount; 43 | } 44 | 45 | #[Groups(['AbstractComponent:cwa_resource:read'])] 46 | public function getTotal(): int 47 | { 48 | return $this->positionCount + $this->pageDataCount; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Metadata/Factory/CachedPageDataMetadataFactory.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\Metadata\Factory; 15 | 16 | use Psr\Cache\CacheItemPoolInterface; 17 | use Silverback\ApiComponentsBundle\Cache\CachedTrait; 18 | use Silverback\ApiComponentsBundle\Metadata\PageDataMetadata; 19 | 20 | /** 21 | * @author Daniel West 22 | * 23 | * @description Based on API Platform CachedResourceMetadataFactory by Teoh Han Hui 24 | */ 25 | class CachedPageDataMetadataFactory implements PageDataMetadataFactoryInterface 26 | { 27 | use CachedTrait; 28 | 29 | public const CACHE_KEY_PREFIX = 'page_data_metadata_'; 30 | 31 | private PageDataMetadataFactoryInterface $decorated; 32 | 33 | public function __construct(CacheItemPoolInterface $cacheItemPool, PageDataMetadataFactoryInterface $decorated) 34 | { 35 | $this->cacheItemPool = $cacheItemPool; 36 | $this->decorated = $decorated; 37 | } 38 | 39 | /** 40 | * {@inheritdoc} 41 | */ 42 | public function create(string $resourceClass): PageDataMetadata 43 | { 44 | $cacheKey = self::CACHE_KEY_PREFIX . md5($resourceClass); 45 | 46 | return $this->getCached($cacheKey, function () use ($resourceClass) { 47 | return $this->decorated->create($resourceClass); 48 | }); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Metadata/Factory/PageDataMetadataFactoryInterface.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\Metadata\Factory; 15 | 16 | use Silverback\ApiComponentsBundle\Exception\PageDataNotFoundException; 17 | use Silverback\ApiComponentsBundle\Metadata\PageDataMetadata; 18 | 19 | /** 20 | * @author Daniel West 21 | */ 22 | interface PageDataMetadataFactoryInterface 23 | { 24 | /** 25 | * Creates a page data metadata. 26 | * 27 | * @throws PageDataNotFoundException 28 | */ 29 | public function create(string $resourceClass): PageDataMetadata; 30 | } 31 | -------------------------------------------------------------------------------- /src/Metadata/Factory/PageDataPropertyMetadataFactory.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\Metadata\Factory; 15 | 16 | /** 17 | * @author Daniel West 18 | */ 19 | class PageDataPropertyMetadataFactory 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /src/Metadata/PageDataComponentMetadata.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\Metadata; 15 | 16 | use Doctrine\Common\Collections\ArrayCollection; 17 | 18 | /** 19 | * @author Daniel West 20 | */ 21 | class PageDataComponentMetadata 22 | { 23 | private array $pageDataResources; 24 | private ArrayCollection $properties; 25 | 26 | public function __construct(array $pageDataResources, ArrayCollection $properties) 27 | { 28 | $this->pageDataResources = $pageDataResources; 29 | $this->properties = $properties; 30 | } 31 | 32 | public function getPageDataResources(): array 33 | { 34 | return $this->pageDataResources; 35 | } 36 | 37 | public function getProperties(): ArrayCollection 38 | { 39 | return $this->properties; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Metadata/PageDataPropertyMetadata.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\Metadata; 15 | 16 | use Symfony\Component\Serializer\Annotation\Groups; 17 | 18 | /** 19 | * @internal 20 | * 21 | * @author Daniel West 22 | */ 23 | class PageDataPropertyMetadata 24 | { 25 | #[Groups(['AbstractPageData:cwa_resource:read', 'PageDataMetadata:cwa_resource:read'])] 26 | private string $property; 27 | 28 | private string $componentClass; 29 | 30 | #[Groups(['AbstractPageData:cwa_resource:read', 'PageDataMetadata:cwa_resource:read'])] 31 | private string $componentShortName; 32 | 33 | public function __construct(string $property, string $componentClass, string $componentShortName) 34 | { 35 | $this->property = $property; 36 | $this->componentClass = $componentClass; 37 | $this->componentShortName = $componentShortName; 38 | } 39 | 40 | public function getProperty(): string 41 | { 42 | return $this->property; 43 | } 44 | 45 | public function getComponentClass(): string 46 | { 47 | return $this->componentClass; 48 | } 49 | 50 | public function getComponentShortName(): string 51 | { 52 | return $this->componentShortName; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Metadata/Provider/PageDataMetadataProvider.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\Metadata\Provider; 15 | 16 | use ApiPlatform\Metadata\Resource\Factory\ResourceNameCollectionFactoryInterface; 17 | use Silverback\ApiComponentsBundle\Entity\Core\PageDataInterface; 18 | use Silverback\ApiComponentsBundle\Metadata\Factory\PageDataMetadataFactoryInterface; 19 | use Silverback\ApiComponentsBundle\Metadata\PageDataMetadata; 20 | 21 | /** 22 | * @author Daniel West 23 | */ 24 | class PageDataMetadataProvider 25 | { 26 | private ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory; 27 | private PageDataMetadataFactoryInterface $pageDataMetadataFactory; 28 | 29 | public function __construct( 30 | ResourceNameCollectionFactoryInterface $resourceNameCollectionFactory, 31 | PageDataMetadataFactoryInterface $pageDataMetadataFactory, 32 | ) { 33 | $this->resourceNameCollectionFactory = $resourceNameCollectionFactory; 34 | $this->pageDataMetadataFactory = $pageDataMetadataFactory; 35 | } 36 | 37 | /** 38 | * @return PageDataMetadata[]|iterable 39 | */ 40 | public function createAll(): iterable 41 | { 42 | foreach ($this->resourceNameCollectionFactory->create() as $pageDataResourceClass) { 43 | $reflectionClass = new \ReflectionClass($pageDataResourceClass); 44 | if (!$reflectionClass->implementsInterface(PageDataInterface::class)) { 45 | continue; 46 | } 47 | 48 | yield $this->pageDataMetadataFactory->create($pageDataResourceClass); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Model/Form/LoginForm.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\Model\Form; 15 | 16 | /** 17 | * @author Daniel West 18 | */ 19 | class LoginForm 20 | { 21 | protected int $id = 0; 22 | public string $_username = ''; 23 | public string $_password = ''; 24 | 25 | public function getId(): int 26 | { 27 | return $this->id; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Model/Uploadable/MediaObject.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\Model\Uploadable; 15 | 16 | use Ramsey\Uuid\Uuid; 17 | 18 | /** 19 | * @author Daniel West 20 | */ 21 | final class MediaObject 22 | { 23 | private string $id; 24 | 25 | public string $contentUrl; 26 | 27 | public int $fileSize; 28 | 29 | public string $mimeType; 30 | 31 | public ?int $width = null; 32 | 33 | public ?int $height = null; 34 | 35 | public ?string $imagineFilter = null; 36 | 37 | // defined otherwise the IRI mapping in API Platform does not work with just the getter method 38 | private ?string $formattedFileSize = null; 39 | 40 | public function __construct() 41 | { 42 | $this->id = Uuid::uuid4()->getHex()->toString(); 43 | } 44 | 45 | public function getId(): string 46 | { 47 | return $this->id; 48 | } 49 | 50 | public function getFormattedFileSize(): string 51 | { 52 | return $this->formattedFileSize ?? $this->fileSize < 0 ? '' : $this->convertSizeToString($this->fileSize); 53 | } 54 | 55 | private function convertSizeToString(int $bytes): string 56 | { 57 | if ($bytes >= 1073741824) { 58 | return number_format($bytes / 1073741824, 1) . 'GB'; 59 | } 60 | 61 | if ($bytes >= 1048576) { 62 | return number_format($bytes / 1048576, 1) . 'MB'; 63 | } 64 | 65 | if ($bytes >= 1024) { 66 | return number_format($bytes / 1024, 1) . 'KB'; 67 | } 68 | 69 | return $bytes . 'B'; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Model/Uploadable/UploadedDataUriFile.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\Model\Uploadable; 15 | 16 | use Symfony\Component\HttpFoundation\File\UploadedFile; 17 | 18 | /** 19 | * @author Daniel West 20 | */ 21 | class UploadedDataUriFile extends UploadedFile 22 | { 23 | /** 24 | * @param null $mimeType 25 | */ 26 | public function __construct(DataUriFile $file, ?string $originalName = null, ?string $mimeType = null, ?int $error = null, bool $test = false) 27 | { 28 | parent::__construct($file->getPathname(), $originalName ?: $file->getFilename(), $mimeType, $error, $test); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/RamseyUuid/UuidUriVariableTransformer/UuidUriVariableTransformer.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\RamseyUuid\UuidUriVariableTransformer; 15 | 16 | use ApiPlatform\Exception\InvalidUriVariableException as LegacyInvalidUriVariableException; 17 | use ApiPlatform\Metadata\Exception\InvalidUriVariableException; 18 | use ApiPlatform\Metadata\UriVariableTransformerInterface; 19 | use ApiPlatform\RamseyUuid\UriVariableTransformer\UuidUriVariableTransformer as BaseUuidUriVariableTransformer; 20 | 21 | /** 22 | * @author Daniel West 23 | */ 24 | class UuidUriVariableTransformer implements UriVariableTransformerInterface 25 | { 26 | private BaseUuidUriVariableTransformer $decorated; 27 | 28 | public function __construct(BaseUuidUriVariableTransformer $decorated) 29 | { 30 | $this->decorated = $decorated; 31 | } 32 | 33 | public function transform($value, array $types, array $context = []) 34 | { 35 | try { 36 | return $this->decorated->transform($value, $types, $context); 37 | } catch (InvalidUriVariableException|LegacyInvalidUriVariableException $exception) { 38 | return $value; 39 | } 40 | } 41 | 42 | public function supportsTransformation($value, array $types, array $context = []): bool 43 | { 44 | return $this->decorated->supportsTransformation($value, $types, $context); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/RefreshToken/RefreshToken.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\RefreshToken; 15 | 16 | use Symfony\Component\Security\Core\User\UserInterface; 17 | 18 | /** 19 | * @author Vincent Chalamon 20 | */ 21 | class RefreshToken 22 | { 23 | protected ?\DateTimeInterface $createdAt = null; 24 | protected ?\DateTimeInterface $expiresAt = null; 25 | protected ?UserInterface $user = null; 26 | protected ?int $version = null; 27 | 28 | public function getCreatedAt(): ?\DateTimeInterface 29 | { 30 | return $this->createdAt; 31 | } 32 | 33 | /** 34 | * @return static 35 | */ 36 | public function setCreatedAt(\DateTimeInterface $createdAt) 37 | { 38 | $this->createdAt = $createdAt; 39 | 40 | return $this; 41 | } 42 | 43 | public function getExpiresAt(): ?\DateTimeInterface 44 | { 45 | return $this->expiresAt; 46 | } 47 | 48 | /** 49 | * @return static 50 | */ 51 | public function setExpiresAt(\DateTimeInterface $expiresAt) 52 | { 53 | $this->expiresAt = $expiresAt; 54 | 55 | return $this; 56 | } 57 | 58 | public function getUser(): ?UserInterface 59 | { 60 | return $this->user; 61 | } 62 | 63 | /** 64 | * @return static 65 | */ 66 | public function setUser(UserInterface $user) 67 | { 68 | $this->user = $user; 69 | 70 | return $this; 71 | } 72 | 73 | public function isExpired(): bool 74 | { 75 | return new \DateTimeImmutable() > $this->expiresAt; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/RefreshToken/Storage/RefreshTokenStorageInterface.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\RefreshToken\Storage; 15 | 16 | use Silverback\ApiComponentsBundle\RefreshToken\RefreshToken; 17 | use Symfony\Component\Security\Core\User\UserInterface; 18 | 19 | /** 20 | * @author Vincent Chalamon 21 | */ 22 | interface RefreshTokenStorageInterface 23 | { 24 | public function findOneByUser(UserInterface $user): ?RefreshToken; 25 | 26 | public function create(UserInterface $user): RefreshToken; 27 | 28 | public function createAndExpire(UserInterface $user, RefreshToken $refreshToken): RefreshToken; 29 | 30 | public function createAndExpireAll(UserInterface $user): RefreshToken; 31 | 32 | public function expireAll(?UserInterface $user): void; 33 | 34 | public function expireToken(RefreshToken $refreshToken): void; 35 | } 36 | -------------------------------------------------------------------------------- /src/Repository/Core/AbstractPageDataRepository.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\Repository\Core; 15 | 16 | use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; 17 | use Doctrine\Persistence\ManagerRegistry; 18 | use Silverback\ApiComponentsBundle\Entity\Core\AbstractPageData; 19 | 20 | /** 21 | * @author Daniel West 22 | * 23 | * @method AbstractPageData|null find($id, $lockMode = null, $lockVersion = null) 24 | * @method AbstractPageData|null findOneBy(array $criteria, array $orderBy = null) 25 | * @method AbstractPageData[] findAll() 26 | * @method AbstractPageData[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) 27 | */ 28 | class AbstractPageDataRepository extends ServiceEntityRepository 29 | { 30 | public function __construct(ManagerRegistry $registry) 31 | { 32 | parent::__construct($registry, AbstractPageData::class); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Repository/Core/ComponentPositionRepository.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\Repository\Core; 15 | 16 | use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; 17 | use Doctrine\Persistence\ManagerRegistry; 18 | use Silverback\ApiComponentsBundle\Entity\Core\ComponentInterface; 19 | use Silverback\ApiComponentsBundle\Entity\Core\ComponentPosition; 20 | 21 | /** 22 | * @author Daniel West 23 | * 24 | * @method ComponentPosition|null find($id, $lockMode = null, $lockVersion = null) 25 | * @method ComponentPosition|null findOneBy(array $criteria, array $orderBy = null) 26 | * @method ComponentPosition[] findAll() 27 | * @method ComponentPosition[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) 28 | */ 29 | class ComponentPositionRepository extends ServiceEntityRepository 30 | { 31 | public function __construct(ManagerRegistry $registry) 32 | { 33 | parent::__construct($registry, ComponentPosition::class); 34 | } 35 | 36 | public function findByComponent(ComponentInterface $component): array 37 | { 38 | return $this->findBy([ 39 | 'component' => $component, 40 | ]); 41 | } 42 | 43 | public function findByPageDataProperties(array $properties): array 44 | { 45 | $qb = $this->createQueryBuilder('cp'); 46 | $expr = $qb->expr(); 47 | foreach ($properties as $index => $property) { 48 | $key = \sprintf(':positionName%d', $index); 49 | $qb->orWhere($expr->eq('cp.pageDataProperty', $key)); 50 | $qb->setParameter($key, $property); 51 | } 52 | 53 | return $qb->getQuery()->getResult(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/Repository/Core/LayoutRepository.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\Repository\Core; 15 | 16 | use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; 17 | use Doctrine\Persistence\ManagerRegistry; 18 | use Silverback\ApiComponentsBundle\Entity\Core\Layout; 19 | 20 | /** 21 | * @author Daniel West 22 | * 23 | * @method Layout|null find($id, $lockMode = null, $lockVersion = null) 24 | * @method Layout|null findOneBy(array $criteria, array $orderBy = null) 25 | * @method Layout[] findAll() 26 | * @method Layout[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) 27 | */ 28 | class LayoutRepository extends ServiceEntityRepository 29 | { 30 | public function __construct(ManagerRegistry $registry) 31 | { 32 | parent::__construct($registry, Layout::class); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Repository/Core/SiteConfigParameterRepository.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\Repository\Core; 15 | 16 | use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository; 17 | use Doctrine\Persistence\ManagerRegistry; 18 | use Silverback\ApiComponentsBundle\Entity\Core\SiteConfigParameter; 19 | 20 | /** 21 | * @author Daniel West 22 | * 23 | * @method SiteConfigParameter|null find($id, $lockMode = null, $lockVersion = null) 24 | * @method SiteConfigParameter|null findOneBy(array $criteria, array $orderBy = null) 25 | * @method SiteConfigParameter[] findAll() 26 | * @method SiteConfigParameter[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) 27 | */ 28 | class SiteConfigParameterRepository extends ServiceEntityRepository 29 | { 30 | public function __construct(ManagerRegistry $registry) 31 | { 32 | parent::__construct($registry, SiteConfigParameter::class); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Repository/User/UserRepositoryInterface.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\Repository\User; 15 | 16 | use Silverback\ApiComponentsBundle\Entity\User\AbstractUser; 17 | use Symfony\Bridge\Doctrine\Security\User\UserLoaderInterface; 18 | 19 | /** 20 | * @author Daniel West 21 | * 22 | * @method AbstractUser|null find($id, $lockMode = null, $lockVersion = null) 23 | * @method AbstractUser|null findOneBy(array $criteria, array $orderBy = null) 24 | * @method AbstractUser[] findAll() 25 | * @method AbstractUser[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null) 26 | */ 27 | interface UserRepositoryInterface extends UserLoaderInterface 28 | { 29 | public function findOneByEmail(string $value): ?AbstractUser; 30 | 31 | public function findOneWithPasswordResetToken(string $username): ?AbstractUser; 32 | 33 | public function findOneByUsernameAndNewEmailAddress(string $username, string $email): ?AbstractUser; 34 | 35 | public function loadUserByIdentifier(string $identifier): ?AbstractUser; 36 | 37 | public function findExistingUserByNewEmail(AbstractUser $user): ?AbstractUser; 38 | } 39 | -------------------------------------------------------------------------------- /src/Resources/config/api_platform/collection/resource.xml: -------------------------------------------------------------------------------- 1 | 5 | 8 | 9 | -------------------------------------------------------------------------------- /src/Resources/config/api_platform/page_data_metadata/properties.xml: -------------------------------------------------------------------------------- 1 | 5 | 11 | 15 | 16 | -------------------------------------------------------------------------------- /src/Resources/config/api_platform/page_data_metadata/resource.xml: -------------------------------------------------------------------------------- 1 | 5 | 32 | 33 | -------------------------------------------------------------------------------- /src/Resources/config/api_platform/resource_metadata/properties.xml: -------------------------------------------------------------------------------- 1 | 5 | 9 | 13 | 17 | 21 | 25 | 29 | 33 | 34 | -------------------------------------------------------------------------------- /src/Resources/config/api_platform/resource_metadata/resource.xml: -------------------------------------------------------------------------------- 1 | 5 | 16 | 17 | -------------------------------------------------------------------------------- /src/Resources/config/api_platform/uploadable/resource.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | true 9 | 10 | 11 | 12 | http://schema.org/MediaObject 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/Resources/config/doctrine-orm/Component.Collection.orm.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/Resources/config/doctrine-orm/Component.Form.orm.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/Resources/config/doctrine-orm/Core.AbstractComponent.orm.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/Resources/config/doctrine-orm/Core.AbstractPage.orm.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Resources/config/doctrine-orm/Core.AbstractRefreshToken.orm.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/Resources/config/doctrine-orm/Core.ComponentGroup.orm.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/Resources/config/doctrine-orm/Core.ComponentPosition.orm.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/Resources/config/doctrine-orm/Core.FileInfo.orm.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/Resources/config/doctrine-orm/Core.Layout.orm.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/Resources/config/doctrine-orm/Core.SiteConfigParameter.orm.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/Resources/config/routing/all.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /src/Resources/config/routing/security.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 18 | 19 | 22 | 23 | -------------------------------------------------------------------------------- /src/Resources/config/services_doctrine_orm_http_cache_purger.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 | /* 15 | * @author Daniel West 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 | 4 | 5 | 6 | 7 | 8 |
9 | 10 |
11 |
12 |
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 | 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 | 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 | 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 | 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 | 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 | --------------------------------------------------------------------------------