├── .ddev
├── apache
│ ├── 10.conf
│ └── 20.conf
├── commands
│ ├── host
│ │ ├── ci
│ │ └── docs
│ └── web
│ │ ├── .install-12
│ │ ├── .install-13
│ │ ├── cache-flush
│ │ ├── data
│ │ ├── fix
│ │ ├── install
│ │ └── next
├── config.yaml
├── docker-compose.web.yaml
├── homeadditions
│ └── .config
│ │ └── mc
│ │ └── ini
└── test
│ ├── files
│ ├── config
│ │ └── sites
│ │ │ └── main
│ │ │ └── config.yaml
│ ├── patches
│ │ └── typo3-cms-impexp-disable-error-on-sys-file-warning.patch
│ └── src
│ │ ├── site
│ │ ├── Classes
│ │ │ ├── EventListener
│ │ │ │ ├── AfterCreateContextForOperationEventListener.php
│ │ │ │ ├── AfterDeserializeOperationEventListener.php
│ │ │ │ ├── AfterProcessOperationEventListener.php
│ │ │ │ ├── BeforeFilterAccessGrantedEventListener.php
│ │ │ │ ├── BeforeOperationAccessGrantedEventListener.php
│ │ │ │ └── BeforeOperationAccessGrantedPostDenormalizeEventListener.php
│ │ │ └── Logger
│ │ │ │ └── HtmlFileLogger.php
│ │ ├── Configuration
│ │ │ ├── .htaccess
│ │ │ └── Services.yaml
│ │ └── composer.json
│ │ └── t3apinews
│ │ ├── Classes
│ │ └── Domain
│ │ │ └── Model
│ │ │ ├── Category.php
│ │ │ ├── File.php
│ │ │ ├── FileReference.php
│ │ │ ├── News.php
│ │ │ └── Tag.php
│ │ ├── Configuration
│ │ ├── Extbase
│ │ │ └── Persistence
│ │ │ │ └── Classes.php
│ │ ├── TCA
│ │ │ └── Overrides
│ │ │ │ └── sys_template.php
│ │ ├── TsConfig
│ │ │ └── Page
│ │ │ │ └── mod.tsconfig
│ │ └── TypoScript
│ │ │ ├── constants.typoscript
│ │ │ └── setup.typoscript
│ │ ├── Resources
│ │ ├── Private
│ │ │ ├── .htaccess
│ │ │ └── Serializer
│ │ │ │ └── GeorgRinger.News
│ │ │ │ ├── Domain.Model.FileReference.yml
│ │ │ │ ├── TYPO3.CMS.Core.Resource.File.yml_
│ │ │ │ ├── TYPO3.CMS.Core.Resource.FileReference.yml_
│ │ │ │ └── TYPO3.CMS.Extbase.Domain.Model.FileReference.yml_
│ │ └── Public
│ │ │ └── Icons
│ │ │ └── Extension.svg
│ │ ├── composer.json
│ │ └── ext_localconf.php
│ ├── impexp
│ ├── data.xml
│ └── data.xml.files
│ │ └── c7254f44aa10b6f89e328731672eda5082fd4976
│ ├── index.php
│ ├── utils-install.sh
│ └── utils.sh
├── .editorconfig
├── .github
├── ISSUE_TEMPLATE
│ ├── bug_report.yml
│ ├── config.yml
│ └── feature_request.yml
├── release.yml
└── workflows
│ ├── TYPO3_12.yml
│ ├── TYPO3_13.yml
│ └── release.yaml
├── .gitignore
├── .php-cs-fixer.php
├── Build
├── phpunit
│ ├── FunctionalTests.xml
│ ├── FunctionalTestsBootstrap.php
│ ├── UnitTests.xml
│ └── UnitTestsBootstrap.php
└── postman
│ ├── .gitignore
│ ├── .nvmrc
│ ├── package-lock.json
│ ├── package.json
│ └── run.sh
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── Classes
├── Annotation
│ ├── ApiFilter.php
│ ├── ApiResource.php
│ ├── ORM
│ │ └── Cascade.php
│ └── Serializer
│ │ ├── Exclude.php
│ │ ├── Groups.php
│ │ ├── MaxDepth.php
│ │ ├── ReadOnlyProperty.php
│ │ ├── SerializedName.php
│ │ ├── Type
│ │ ├── CurrentFeUser.php
│ │ ├── Image.php
│ │ ├── PasswordHash.php
│ │ ├── RecordUri.php
│ │ ├── Rte.php
│ │ ├── TypeInterface.php
│ │ └── Typolink.php
│ │ └── VirtualProperty.php
├── Configuration
│ ├── Configuration.php
│ └── CorsOptions.php
├── Controller
│ ├── AdministrationController.php
│ └── OpenApiController.php
├── Dispatcher
│ ├── AbstractDispatcher.php
│ ├── Bootstrap.php
│ └── HeadlessDispatcher.php
├── Domain
│ ├── Model
│ │ ├── AbstractOperation.php
│ │ ├── AbstractOperationResourceSettings.php
│ │ ├── ApiFilter.php
│ │ ├── ApiFilterStrategy.php
│ │ ├── ApiResource.php
│ │ ├── CollectionOperation.php
│ │ ├── CollectionOperationFactory.php
│ │ ├── ItemOperation.php
│ │ ├── OperationInterface.php
│ │ ├── Pagination.php
│ │ ├── PersistenceSettings.php
│ │ └── UploadSettings.php
│ └── Repository
│ │ ├── ApiResourceRepository.php
│ │ └── CommonRepository.php
├── Event
│ ├── AfterCreateContextForOperationEvent.php
│ ├── AfterDeserializeOperationEvent.php
│ ├── AfterProcessOperationEvent.php
│ ├── BeforeFilterAccessGrantedEvent.php
│ ├── BeforeOperationAccessGrantedEvent.php
│ └── BeforeOperationAccessGrantedPostDenormalizeEvent.php
├── EventListener
│ ├── AddHydraCollectionResponseSerializationGroupEventListener.php
│ └── EnrichSerializationContextEventListener.php
├── Exception
│ ├── AbstractException.php
│ ├── ExceptionInterface.php
│ ├── MethodNotAllowedException.php
│ ├── OpenApiSupportingExceptionInterface.php
│ ├── OperationNotAllowedException.php
│ ├── ResourceNotFoundException.php
│ ├── RouteNotFoundException.php
│ └── ValidationException.php
├── ExpressionLanguage
│ ├── ConditionFunctionsProvider.php
│ ├── ConditionProvider.php
│ ├── Resolver.php
│ ├── T3apiCoreFunctionsProvider.php
│ └── T3apiCoreProvider.php
├── Factory
│ └── ApiResourceFactory.php
├── Filter
│ ├── AbstractFilter.php
│ ├── BooleanFilter.php
│ ├── ContainFilter.php
│ ├── DistanceFilter.php
│ ├── FilterInterface.php
│ ├── NumericFilter.php
│ ├── OpenApiSupportingFilterInterface.php
│ ├── OrderFilter.php
│ ├── RangeFilter.php
│ ├── SearchFilter.php
│ └── UidFilter.php
├── Hook
│ └── EnrichHashBase.php
├── Middleware
│ ├── T3apiRequestLanguageResolver.php
│ └── T3apiRequestResolver.php
├── OperationHandler
│ ├── AbstractCollectionOperationHandler.php
│ ├── AbstractItemOperationHandler.php
│ ├── AbstractOperationHandler.php
│ ├── CollectionGetOperationHandler.php
│ ├── CollectionMethodNotAllowedOperationHandler.php
│ ├── CollectionPostOperationHandler.php
│ ├── FileUploadOperationHandler.php
│ ├── ItemDeleteOperationHandler.php
│ ├── ItemGetOperationHandler.php
│ ├── ItemMethodNotAllowedOperationHandler.php
│ ├── ItemPatchOperationHandler.php
│ ├── ItemPutOperationHandler.php
│ ├── OperationHandlerInterface.php
│ └── OptionsOperationHandler.php
├── Processor
│ ├── CorsProcessor.php
│ └── ProcessorInterface.php
├── Provider
│ └── ApiResourcePath
│ │ ├── ApiResourcePathProvider.php
│ │ └── LoadedExtensionsDomainModelApiResourcePathProvider.php
├── Response
│ ├── AbstractCollectionResponse.php
│ ├── HydraCollectionResponse.php
│ └── MainEndpointResponse.php
├── Routing
│ └── Enhancer
│ │ └── ResourceEnhancer.php
├── Security
│ ├── AbstractAccessChecker.php
│ ├── FilterAccessChecker.php
│ └── OperationAccessChecker.php
├── Serializer
│ ├── Accessor
│ │ └── AccessorStrategy.php
│ ├── Construction
│ │ ├── ExtbaseObjectConstructor.php
│ │ ├── InitializedObjectConstructor.php
│ │ └── ObjectConstructorChain.php
│ ├── ContextBuilder
│ │ ├── AbstractContextBuilder.php
│ │ ├── ContextBuilderInterface.php
│ │ ├── DeserializationContextBuilder.php
│ │ └── SerializationContextBuilder.php
│ ├── Handler
│ │ ├── AbstractDomainObjectHandler.php
│ │ ├── AbstractHandler.php
│ │ ├── CurrentFeUserHandler.php
│ │ ├── DeserializeHandlerInterface.php
│ │ ├── FileReferenceHandler.php
│ │ ├── ImageHandler.php
│ │ ├── ObjectStorageHandler.php
│ │ ├── PasswordHashHandler.php
│ │ ├── RecordUriHandler.php
│ │ ├── RteHandler.php
│ │ ├── SerializeHandlerInterface.php
│ │ └── TypolinkHandler.php
│ └── Subscriber
│ │ ├── AbstractEntitySubscriber.php
│ │ ├── CurrentFeUserSubscriber.php
│ │ ├── FileReferenceSubscriber.php
│ │ ├── GenerateMetadataSubscriber.php
│ │ ├── ResourceTypeSubscriber.php
│ │ └── ThrowableSubscriber.php
├── Service
│ ├── CorsService.php
│ ├── ExpressionLanguageService.php
│ ├── FileReferenceService.php
│ ├── FileUploadService.php
│ ├── FilesystemService.php
│ ├── OpenApiBuilder.php
│ ├── PropertyInfoService.php
│ ├── ReflectionService.php
│ ├── RouteService.php
│ ├── SerializerMetadataService.php
│ ├── SerializerService.php
│ ├── SiteService.php
│ ├── StorageService.php
│ ├── UrlService.php
│ └── ValidationService.php
├── Utility
│ ├── FileUtility.php
│ └── ParameterUtility.php
└── ViewHelpers
│ └── InlineViewHelper.php
├── Configuration
├── Backend
│ └── Modules.php
├── ExpressionLanguage.php
├── Icons.php
├── JavaScriptModules.php
├── RequestMiddlewares.php
├── Routing
│ └── config.yaml
└── Services.yaml
├── Documentation
├── Cors
│ └── Index.rst
├── Customization
│ ├── ApiResourcePath
│ │ └── Index.rst
│ ├── CollectionResponseSchema
│ │ └── Index.rst
│ ├── ExpressionLanguage
│ │ └── Index.rst
│ └── Index.rst
├── Events
│ └── Index.rst
├── Filtering
│ ├── BuiltinFilters
│ │ ├── BooleanFilter
│ │ │ └── Index.rst
│ │ ├── ContainFilter
│ │ │ └── Index.rst
│ │ ├── DistanceFilter
│ │ │ └── Index.rst
│ │ ├── Index.rst
│ │ ├── NumericFilter
│ │ │ └── Index.rst
│ │ ├── OrderFilter
│ │ │ └── Index.rst
│ │ ├── RangeFilter
│ │ │ └── Index.rst
│ │ ├── SearchFilter
│ │ │ └── Index.rst
│ │ └── UidFilter
│ │ │ └── Index.rst
│ ├── CustomFilters
│ │ └── Index.rst
│ ├── Index.rst
│ ├── SqlInOperator
│ │ └── Index.rst
│ └── SqlOrOperator
│ │ └── Index.rst
├── GettingStarted
│ └── Index.rst
├── HandlingCascadePersistence
│ └── Index.rst
├── HandlingFileUpload
│ └── Index.rst
├── Index.rst
├── Integration
│ └── Index.rst
├── Miscellaneous
│ ├── Changelog
│ │ └── Index.rst
│ ├── CommonIssues
│ │ └── Index.rst
│ ├── Development
│ │ ├── CommandsList
│ │ │ └── Index.rst
│ │ ├── Index.rst
│ │ ├── Introduction
│ │ │ └── Index.rst
│ │ └── TypicalUseCases
│ │ │ ├── BugsFixing
│ │ │ └── Index.rst
│ │ │ └── Index.rst
│ └── Index.rst
├── Multilanguage
│ └── Index.rst
├── Operations
│ ├── CustomizingOperationHandler
│ │ └── Index.rst
│ └── Index.rst
├── Pagination
│ ├── ClientSide
│ │ └── Index.rst
│ ├── Index.rst
│ └── ServerSide
│ │ └── Index.rst
├── Security
│ └── Index.rst
├── Serialization
│ ├── ContextGroups
│ │ └── Index.rst
│ ├── Customization
│ │ └── Index.rst
│ ├── Exceptions
│ │ └── Index.rst
│ ├── Handlers
│ │ └── Index.rst
│ ├── Index.rst
│ ├── Subscribers
│ │ └── Index.rst
│ └── YamlMetadata
│ │ └── Index.rst
├── Sitemap.rst
├── UseCases
│ ├── CurrentUserAssignment
│ │ └── Index.rst
│ ├── CurrentUserEndpoint
│ │ └── Index.rst
│ └── Index.rst
└── guides.xml
├── LICENSE.md
├── README.rst
├── Resources
├── Private
│ ├── .htaccess
│ ├── Language
│ │ ├── locallang.xlf
│ │ └── locallang_modadministration.xlf
│ ├── Serializer
│ │ └── Metadata
│ │ │ ├── SourceBroker.T3api.Response.AbstractCollectionResponse.yml
│ │ │ ├── SourceBroker.T3api.Response.HydraCollectionResponse.yml
│ │ │ ├── SourceBroker.T3api.Response.MainEndpointResponse.yml
│ │ │ ├── TYPO3.CMS.Core.Resource.AbstractFile.yml
│ │ │ ├── TYPO3.CMS.Core.Resource.File.yml
│ │ │ ├── TYPO3.CMS.Core.Resource.FileReference.yml
│ │ │ ├── TYPO3.CMS.Core.Resource.Folder.yml
│ │ │ ├── TYPO3.CMS.Core.Resource.ResourceStorage.yml
│ │ │ ├── TYPO3.CMS.Extbase.Domain.Model.AbstractFileFolder.yml
│ │ │ ├── TYPO3.CMS.Extbase.Domain.Model.FileReference.yml
│ │ │ ├── TYPO3.CMS.Extbase.DomainObject.AbstractDomainObject.yml
│ │ │ ├── TYPO3.CMS.Extbase.Persistence.Generic.LazyObjectStorage.yml
│ │ │ ├── TYPO3.CMS.Extbase.Persistence.ObjectStorage.yml
│ │ │ └── Throwable.yml
│ └── Templates
│ │ └── Administration
│ │ └── Documentation.html
└── Public
│ ├── Css
│ ├── swagger-custom.css
│ └── swagger-ui.css
│ ├── ESM
│ └── swagger-init.js
│ ├── Icons
│ └── Extension.svg
│ └── JavaScript
│ ├── swagger-ui-bundle.js
│ └── swagger-ui-standalone-preset.js
├── Tests
├── Functional
│ └── Domain
│ │ └── Repository
│ │ └── ApiResourceRepositoryTest.php
├── Postman
│ ├── fixtures
│ │ └── test1.jpg
│ ├── t3apinews.crud.json
│ ├── t3apinews.language.header.json
│ └── t3apinews.language.prefix.json
└── Unit
│ ├── Domain
│ └── Model
│ │ ├── ApiFilterTest.php
│ │ └── PaginationTest.php
│ ├── Fixtures
│ ├── Annotation
│ │ └── Serializer
│ │ │ └── Type
│ │ │ └── ExampleTypeWithNestedParams.php
│ └── Domain
│ │ └── Model
│ │ ├── AbstractEntry.php
│ │ ├── Address.php
│ │ ├── Category.php
│ │ ├── Company.php
│ │ ├── ContactDataTrait.php
│ │ ├── Group.php
│ │ ├── IdentifiableInterface.php
│ │ ├── Person.php
│ │ ├── Tag.php
│ │ └── TaggableInterface.php
│ ├── Service
│ └── SerializerMetadataServiceTest.php
│ └── Utility
│ └── FileUtilityTest.php
├── composer.json
├── ext_emconf.php
├── ext_localconf.php
├── phpstan-baseline.neon
└── phpstan.neon
/.ddev/apache/10.conf:
--------------------------------------------------------------------------------
1 |
2 | ServerName t3api.ddev.site
3 | DocumentRoot /var/www/html/.test
4 |
5 | AllowOverride All
6 | Allow from All
7 |
8 |
9 | RewriteEngine On
10 | RewriteCond %{HTTP:X-Forwarded-Proto} =https
11 | RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} -d
12 | RewriteRule ^(.+[^/])$ https://%{HTTP_HOST}$1/ [redirect,last]
13 | SetEnvIf X-Forwarded-Proto "https" HTTPS=on
14 | ErrorLog /dev/stdout
15 | CustomLog ${APACHE_LOG_DIR}/access.log combined
16 | Alias "/phpstatus" "/var/www/phpstatus.php"
17 |
18 |
19 |
20 | ServerName t3api.ddev.site
21 | DocumentRoot /var/www/html/.test
22 |
23 | AllowOverride All
24 | Allow from All
25 |
26 |
27 | RewriteEngine On
28 | RewriteCond %{HTTP:X-Forwarded-Proto} =https
29 | RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} -d
30 | RewriteRule ^(.+[^/])$ https://%{HTTP_HOST}$1/ [redirect,last]
31 | SetEnvIf X-Forwarded-Proto "https" HTTPS=on
32 | ErrorLog /dev/stdout
33 | CustomLog ${APACHE_LOG_DIR}/access.log combined
34 | Alias "/phpstatus" "/var/www/phpstatus.php"
35 |
36 | SSLEngine on
37 | SSLCertificateFile /etc/ssl/certs/master.crt
38 | SSLCertificateKeyFile /etc/ssl/certs/master.key
39 |
40 |
--------------------------------------------------------------------------------
/.ddev/apache/20.conf:
--------------------------------------------------------------------------------
1 |
2 | ServerName sub.t3api.ddev.site
3 | ServerAlias *.t3api.ddev.site
4 | ServerAlias *.api.t3api.ddev.site
5 | DocumentRoot /var/www/html/.test
6 |
7 | AllowOverride All
8 | Allow from All
9 |
10 |
11 | RewriteEngine On
12 | RewriteCond %{HTTP_HOST} ^([a-z0-9-]+)\.t3api\.ddev\.site$ [OR]
13 | RewriteCond %{HTTP_HOST} ^([a-z0-9-]+)\.api\.t3api\.ddev\.site$
14 | RewriteRule ^(.*)$ /var/www/html/.test/%1/public/$1 [L]
15 |
16 | RewriteCond %{HTTP:X-Forwarded-Proto} =https
17 | RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} -d
18 | RewriteRule ^(.+[^/])$ https://%{HTTP_HOST}$1/ [redirect,last]
19 | SetEnvIf X-Forwarded-Proto "https" HTTPS=on
20 | ErrorLog /dev/stdout
21 | CustomLog ${APACHE_LOG_DIR}/access.log combined
22 | Alias "/phpstatus" "/var/www/phpstatus.php"
23 |
24 |
25 |
26 | ServerName sub.t3api.ddev.site
27 | ServerAlias *.t3api.ddev.site
28 | ServerAlias *.api.t3api.ddev.site
29 | DocumentRoot /var/www/html/.test
30 |
31 | AllowOverride All
32 | Allow from All
33 |
34 |
35 | RewriteEngine On
36 | RewriteCond %{HTTP_HOST} ^([a-z0-9-]+)\.t3api\.ddev\.site$ [OR]
37 | RewriteCond %{HTTP_HOST} ^([a-z0-9-]+)\.api\.t3api\.ddev\.site$
38 | RewriteRule ^(.*)$ /var/www/html/.test/%1/public/$1 [L]
39 |
40 | RewriteCond %{HTTP:X-Forwarded-Proto} =https
41 | RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} -d
42 | RewriteRule ^(.+[^/])$ https://%{HTTP_HOST}$1/ [redirect,last]
43 | SetEnvIf X-Forwarded-Proto "https" HTTPS=on
44 | ErrorLog /dev/stdout
45 | CustomLog ${APACHE_LOG_DIR}/access.log combined
46 | Alias "/phpstatus" "/var/www/phpstatus.php"
47 |
48 | SSLEngine on
49 | SSLCertificateFile /etc/ssl/certs/master.crt
50 | SSLCertificateKeyFile /etc/ssl/certs/master.key
51 |
52 |
--------------------------------------------------------------------------------
/.ddev/commands/host/docs:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | ## Description: [ExtDev] Build docs or open docs editing in watch mode. Mode "watch" is default.
4 | ## Usage: "docs [watch|build|test]"
5 | ## Example: "ddev docs" "ddev docs watch" "ddev docs build" "ddev docs test"
6 |
7 | MODE=${1:-watch}
8 |
9 | if [ "$MODE" == "build" ]; then
10 | mkdir -p Documentation-GENERATED-temp
11 | docker run --rm --pull always -v ./:/project/ ghcr.io/typo3-documentation/render-guides:latest --no-progress --config Documentation
12 | elif [ "$MODE" == "watch" ]; then
13 | mkdir -p Documentation-GENERATED-temp
14 | open http://localhost:5173/Documentation-GENERATED-temp/Index.html
15 | docker run --rm -it --pull always \
16 | -v "./Documentation:/project/Documentation" \
17 | -v "./Documentation-GENERATED-temp:/project/Documentation-GENERATED-temp" \
18 | -p 5173:5173 ghcr.io/garvinhicking/typo3-documentation-browsersync:latest
19 | elif [ "$MODE" == "ci" ]; then
20 | mkdir -p Documentation-GENERATED-temp
21 | docker run --rm --pull always -v "$(pwd)":/project -t ghcr.io/typo3-documentation/render-guides:latest --config=Documentation --no-progress --fail-on-log
22 | else
23 | echo "Invalid mode. Please use 'build', 'watch', or 'test'."
24 | exit 1
25 | fi
26 |
--------------------------------------------------------------------------------
/.ddev/commands/web/.install-12:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | ## Description: [ExtDev] Install TYPO3 12 integration instance.
4 | ## Usage: install
5 | ## Example: "ddev install 12"
6 |
7 | set +x
8 | set -e
9 |
10 | source .ddev/test/utils-install.sh
11 | install_start "12"
12 |
13 | composer req typo3/cms-backend:'^12.4' typo3/cms-core:'^12.4' typo3/cms-extbase:'^12.4' typo3/cms-filelist:'^12.4' \
14 | typo3/cms-fluid:'^12.4' typo3/cms-frontend:'^12.4' typo3/cms-recycler:'^12.4' typo3/cms-tstemplate:'^12.4' \
15 | typo3/cms-info:'^12.4' typo3/cms-lowlevel:'^12.4' typo3/cms-rte-ckeditor:'^12.4' typo3/cms-impexp:'^12.4' \
16 | typo3/cms-install:'^12.4' \
17 | helhum/typo3-console:'^8.2.1' \
18 | cweagans/composer-patches:'^1.7.3' georgringer/news:'^12.1' \
19 | sourcebroker/t3apinews:'^1.0.0' v/site:'^1.0.0' \
20 | sourcebroker/t3api:'@dev' \
21 | --no-progress --no-interaction --working-dir "$BASE_PATH"
22 |
23 | install_end
24 |
--------------------------------------------------------------------------------
/.ddev/commands/web/.install-13:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | ## Description: [ExtDev] Install TYPO3 13 integration instance.
4 | ## Usage: install
5 | ## Example: "ddev install 13"
6 |
7 | set +x
8 | set -e
9 |
10 | source .ddev/test/utils-install.sh
11 | install_start "13"
12 |
13 | composer req typo3/cms-backend:'^13.3' typo3/cms-core:'^13.3' typo3/cms-extbase:'^13.3' typo3/cms-filelist:'^13.3' \
14 | typo3/cms-fluid:'^13.3' typo3/cms-frontend:'^13.3' typo3/cms-recycler:'^13.3' typo3/cms-tstemplate:'^13.3' \
15 | typo3/cms-info:'^13.3' typo3/cms-lowlevel:'^13.3' typo3/cms-rte-ckeditor:'^13.3' typo3/cms-impexp:'^13.3' \
16 | typo3/cms-install:'^13.3' \
17 | helhum/typo3-console:'^8.2.1' \
18 | cweagans/composer-patches:'^1.7.3' georgringer/news:'^12.1' \
19 | sourcebroker/t3apinews:'^1.0.0' v/site:'^1.0.0' \
20 | sourcebroker/t3api:'@dev' \
21 | --no-progress --no-interaction --working-dir "$BASE_PATH"
22 |
23 | install_end
24 |
--------------------------------------------------------------------------------
/.ddev/commands/web/cache-flush:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | ## Description: [ExtDev] Flush cache for all available TYPO3 integration instances.
4 | ## Usage: cache-flush
5 | ## Example: "ddev cache-flush"
6 |
7 | source .ddev/test/utils.sh
8 |
9 | mapfile -t versions < <(get_supported_typo3_versions)
10 | for version in "${versions[@]}"; do
11 | TYPO3_PATH=".test/${version}/vendor/bin/typo3"
12 | if [ -f "$TYPO3_PATH" ]; then
13 | message green "Cache flush TYPO3 v${version}..."
14 | /usr/bin/php $TYPO3_PATH cache:flush
15 | else
16 | message red "TYPO3 binary not found for version ${version}"
17 | fi
18 | done
19 |
--------------------------------------------------------------------------------
/.ddev/commands/web/fix:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | ## Description: [ExtDev] Run all possible automate fixes.
4 | ## Usage: fix
5 | ## Example: "ddev fix"
6 |
7 | composer fix
8 |
--------------------------------------------------------------------------------
/.ddev/commands/web/install:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | ## Description: [ExtDev] Install defined TYPO3 testing instance. If no param it fallback to first supported TYPO3. If "all" it installs all supported TYPO3.
4 | ## Usage: install
5 | ## Example: "ddev install, ddev install 12, ddev install all"
6 |
7 | source .ddev/test/utils.sh
8 |
9 | TYPO3=${1}
10 |
11 | if [ "$TYPO3" == "all" ]; then
12 | mapfile -t versions < <(get_supported_typo3_versions)
13 | for version in "${versions[@]}"; do
14 | message green "Installing TYPO3 v${version}..."
15 | .ddev/commands/web/.install-$version
16 | done
17 | else
18 | if [ -z "$TYPO3" ]; then
19 | TYPO3=$(get_lowest_supported_typo3_versions)
20 | else
21 | if ! check_typo3_version "$TYPO3"; then
22 | exit 1
23 | fi
24 | fi
25 | ".ddev/commands/web/.install-$TYPO3"
26 | fi
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/.ddev/docker-compose.web.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | web:
3 | environment:
4 | - EXTENSION_KEY=t3api
5 | - PACKAGE_NAME=sourcebroker/t3api
6 |
7 | - TYPO3_VERSIONS=12 13
8 | - TYPO3_VERSIONS_12_PHP=8.1 8.2 8.3 8.4
9 | - TYPO3_VERSIONS_13_PHP=8.2 8.3 8.4
10 |
11 | - TYPO3_INSTALL_DB_DRIVER=mysqli
12 | - TYPO3_INSTALL_DB_USER=root
13 | - TYPO3_INSTALL_DB_PASSWORD=root
14 | - TYPO3_INSTALL_DB_HOST=db
15 | - TYPO3_INSTALL_DB_PORT=3306
16 | - TYPO3_INSTALL_DB_UNIX_SOCKET=
17 | - TYPO3_INSTALL_DB_USE_EXISTING=0
18 | - TYPO3_INSTALL_ADMIN_USER=admin
19 | - TYPO3_INSTALL_ADMIN_PASSWORD=Password1!
20 | - TYPO3_INSTALL_SITE_NAME=EXT:t3api
21 | - TYPO3_INSTALL_SITE_SETUP_TYPE=site
22 | - TYPO3_INSTALL_WEB_SERVER_CONFIG=apache
23 |
--------------------------------------------------------------------------------
/.ddev/test/files/config/sites/main/config.yaml:
--------------------------------------------------------------------------------
1 | base: /
2 | languages:
3 | -
4 | title: English
5 | enabled: true
6 | languageId: 0
7 | base: /
8 | typo3Language: default
9 | locale: en_US.UTF-8
10 | iso-639-1: en
11 | navigationTitle: English
12 | hreflang: en-us
13 | direction: ltr
14 | flag: us
15 | websiteTitle: ''
16 | -
17 | title: German
18 | enabled: true
19 | base: /de/
20 | typo3Language: de
21 | locale: de_DE.UTF-8
22 | iso-639-1: de
23 | websiteTitle: ''
24 | navigationTitle: Deutsche
25 | hreflang: de-de
26 | direction: ltr
27 | fallbackType: strict
28 | fallbacks: ''
29 | flag: de
30 | languageId: 1
31 | -
32 | title: Polish
33 | enabled: true
34 | base: /pl/
35 | typo3Language: pl
36 | locale: pl_PL.UTF-8
37 | iso-639-1: pl
38 | websiteTitle: ''
39 | navigationTitle: Polski
40 | hreflang: pl-pl
41 | direction: ltr
42 | fallbackType: fallback
43 | fallbacks: '0'
44 | flag: pl
45 | languageId: 2
46 | rootPageId: 1
47 | websiteTitle: ''
48 | imports:
49 | -
50 | resource: 'EXT:t3api/Configuration/Routing/config.yaml'
51 | routeEnhancers:
52 | News:
53 | type: Extbase
54 | limitToPages:
55 | - 5
56 | extension: News
57 | plugin: Pi1
58 | routes:
59 | - routePath: '/'
60 | _controller: 'News::list'
61 | - routePath: '/page-{page}'
62 | _controller: 'News::list'
63 | _arguments:
64 | page: 'currentPage'
65 | - routePath: '/{news-title}'
66 | _controller: 'News::detail'
67 | _arguments:
68 | news-title: news
69 | defaultController: 'News::list'
70 | defaults:
71 | page: '0'
72 | aspects:
73 | news-title:
74 | type: PersistedAliasMapper
75 | tableName: tx_news_domain_model_news
76 | routeFieldName: path_segment
77 | page:
78 | type: StaticRangeMapper
79 | start: '1'
80 | end: '100'
81 |
--------------------------------------------------------------------------------
/.ddev/test/files/patches/typo3-cms-impexp-disable-error-on-sys-file-warning.patch:
--------------------------------------------------------------------------------
1 | --- Classes/ImportExport.php.orig 2024-06-01 18:06:29.331590962 +0000
2 | +++ Classes/ImportExport.php 2024-06-01 18:07:47.435376487 +0000
3 | @@ -489,7 +489,7 @@
4 | $this->addError('Updating sys_file records is not supported! They will be imported as new records!');
5 | }
6 | if ($this->forceAllUids && $table === 'sys_file') {
7 | - $this->addError('Forcing uids of sys_file records is not supported! They will be imported as new records!');
8 | +# $this->addError('Forcing uids of sys_file records is not supported! They will be imported as new records!');
9 | }
10 | }
11 |
12 |
--------------------------------------------------------------------------------
/.ddev/test/files/src/site/Classes/EventListener/AfterCreateContextForOperationEventListener.php:
--------------------------------------------------------------------------------
1 | getOperation();
18 | $logMessage = sprintf(
19 | '
20 | Operation processed: %s
21 | Method: %s
22 | Path: %s
23 | ',
24 | get_class($operation),
25 | $operation->getMethod(),
26 | $operation->getPath()
27 | );
28 |
29 | $logContext = [
30 | 'operation' => $operation,
31 | 'request' => $event->getRequest(),
32 | 'context' => $event->getContext(),
33 | ];
34 |
35 | $this->logger->warning($logMessage, $logContext);
36 | }
37 | }
38 |
39 |
--------------------------------------------------------------------------------
/.ddev/test/files/src/site/Classes/EventListener/AfterDeserializeOperationEventListener.php:
--------------------------------------------------------------------------------
1 | getOperation();
17 | $logMessage = sprintf(
18 | '
19 | Operation processed: %s
20 | Method: %s
21 | Path: %s
22 | ',
23 | get_class($operation),
24 | $operation->getMethod(),
25 | $operation->getPath()
26 | );
27 |
28 | $logContext = [
29 | 'operation' => $operation,
30 | 'object' => $event->getObject(),
31 | ];
32 |
33 | $this->logger->warning($logMessage, $logContext);
34 | }
35 | }
36 |
37 |
--------------------------------------------------------------------------------
/.ddev/test/files/src/site/Classes/EventListener/AfterProcessOperationEventListener.php:
--------------------------------------------------------------------------------
1 | getOperation();
18 | $logMessage = sprintf(
19 | '
20 | Operation processed: %s
21 | Method: %s
22 | Path: %s
23 | ',
24 | get_class($operation),
25 | $operation->getMethod(),
26 | $operation->getPath()
27 | );
28 |
29 | $logContext = [
30 | 'operation' => $operation,
31 | 'result' => $event->getResult(),
32 | ];
33 |
34 | $this->logger->warning($logMessage, $logContext);
35 | }
36 | }
37 |
38 |
--------------------------------------------------------------------------------
/.ddev/test/files/src/site/Classes/EventListener/BeforeFilterAccessGrantedEventListener.php:
--------------------------------------------------------------------------------
1 | getFilter();
17 |
18 | $logMessage = sprintf(
19 | 'Filter processed: %s
',
20 | $filter->getFilterClass(),
21 | );
22 |
23 | $logContext = [
24 | 'filter' => $filter,
25 | 'expressionLanguageVariables' => $event->getExpressionLanguageVariables(),
26 | ];
27 |
28 | $this->logger->warning($logMessage, $logContext);
29 | }
30 | }
31 |
32 |
--------------------------------------------------------------------------------
/.ddev/test/files/src/site/Classes/EventListener/BeforeOperationAccessGrantedEventListener.php:
--------------------------------------------------------------------------------
1 | getOperation();
17 | $logMessage = sprintf(
18 | '
19 | Operation processed: %s
20 | Method: %s
21 | Path: %s
22 | ',
23 | get_class($operation),
24 | $operation->getMethod(),
25 | $operation->getPath()
26 | );
27 |
28 | $logContext = [
29 | 'operation' => $operation,
30 | 'expressionLanguageVariables' => $event->getExpressionLanguageVariables(),
31 | ];
32 |
33 | $this->logger->warning($logMessage, $logContext);
34 | }
35 | }
36 |
37 |
--------------------------------------------------------------------------------
/.ddev/test/files/src/site/Classes/EventListener/BeforeOperationAccessGrantedPostDenormalizeEventListener.php:
--------------------------------------------------------------------------------
1 | getOperation();
17 | $logMessage = sprintf(
18 | '
19 | Operation processed: %s
20 | Method: %s
21 | Path: %s
22 | ',
23 | get_class($operation),
24 | $operation->getMethod(),
25 | $operation->getPath()
26 | );
27 |
28 | $logContext = [
29 | 'operation' => $operation,
30 | 'expressionLanguageVariables' => $event->getExpressionLanguageVariables(),
31 | ];
32 |
33 | $this->logger->warning($logMessage, $logContext);
34 | }
35 | }
36 |
37 |
--------------------------------------------------------------------------------
/.ddev/test/files/src/site/Configuration/.htaccess:
--------------------------------------------------------------------------------
1 | deny from all
2 |
--------------------------------------------------------------------------------
/.ddev/test/files/src/site/Configuration/Services.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | _defaults:
3 | autowire: true
4 | autoconfigure: true
5 | public: false
6 |
7 | V\Site\:
8 | resource: '../Classes/*'
9 |
10 | V\Site\EventListener\AfterProcessOperationEventListener:
11 | tags:
12 | - name: 'event.listener'
13 |
14 | V\Site\EventListener\AfterCreateContextForOperationEventListener:
15 | tags:
16 | - name: 'event.listener'
17 |
18 | V\Site\EventListener\AfterDeserializeOperationEventListener:
19 | tags:
20 | - name: 'event.listener'
21 |
22 | V\Site\EventListener\BeforeFilterAccessGrantedEventListener:
23 | tags:
24 | - name: 'event.listener'
25 |
26 | V\Site\EventListener\BeforeOperationAccessGrantedEventListener:
27 | tags:
28 | - name: 'event.listener'
29 |
30 | V\Site\EventListener\BeforeOperationAccessGrantedPostDenormalizeEventListener:
31 | tags:
32 | - name: 'event.listener'
33 |
--------------------------------------------------------------------------------
/.ddev/test/files/src/site/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "v/site",
3 | "description": "T3api testing",
4 | "license": "GPL-2.0-or-later",
5 | "type": "typo3-cms-extension",
6 | "authors": [
7 | {
8 | "name": "",
9 | "email": "no-email@given.com"
10 | }
11 | ],
12 | "suggest": {},
13 | "conflict": {},
14 | "extra": {
15 | "typo3/cms": {
16 | "extension-key": "site"
17 | }
18 | },
19 | "version": "1.0.0",
20 | "autoload": {
21 | "psr-4": {
22 | "V\\Site\\": "Classes/"
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/.ddev/test/files/src/t3apinews/Classes/Domain/Model/File.php:
--------------------------------------------------------------------------------
1 | [
12 | 'tableName' => 'tx_news_domain_model_news',
13 | ],
14 | Tag::class => [
15 | 'tableName' => 'tx_news_domain_model_tag',
16 | ],
17 | FileReference::class => [
18 | 'tableName' => 'sys_file_reference',
19 | ],
20 | Category::class => [
21 | 'tableName' => 'sys_category',
22 | ],
23 | ];
24 |
--------------------------------------------------------------------------------
/.ddev/test/files/src/t3apinews/Configuration/TCA/Overrides/sys_template.php:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/.ddev/test/files/src/t3apinews/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sourcebroker/t3apinews",
3 | "license": [
4 | "GPL-2.0-or-later"
5 | ],
6 | "type": "typo3-cms-extension",
7 | "description": "T3api sample for ext:news",
8 | "authors": [
9 | {
10 | "name": "SourceBroker Team",
11 | "role": "Developer"
12 | }
13 | ],
14 | "require": {
15 | "typo3/cms-core": "^12.4 || ^13.1",
16 | "sourcebroker/t3api": "@dev",
17 | "georgringer/news": "@dev"
18 | },
19 | "autoload": {
20 | "psr-4": {
21 | "SourceBroker\\T3apinews\\": "Classes"
22 | }
23 | },
24 | "replace": {
25 | "typo3-ter/t3apinews": "self.version"
26 | },
27 | "config": {
28 | "vendor-dir": ".Build/vendor",
29 | "bin-dir": ".Build/bin"
30 | },
31 | "extra": {
32 | "typo3/cms": {
33 | "extension-key": "t3apinews",
34 | "app-dir": ".Build",
35 | "web-dir": ".Build/public"
36 | }
37 | },
38 | "version": "1.0.0"
39 | }
40 |
--------------------------------------------------------------------------------
/.ddev/test/files/src/t3apinews/ext_localconf.php:
--------------------------------------------------------------------------------
1 | ');
9 |
10 | $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['t3api']['serializerMetadataDirs'] = array_merge(
11 | $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['t3api']['serializerMetadataDirs'] ?? [],
12 | [
13 | 'GeorgRinger\News' => \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath('t3apinews') . 'Resources/Private/Serializer/GeorgRinger.News',
14 | ]
15 | );
16 |
17 | }
18 | );
19 |
--------------------------------------------------------------------------------
/.ddev/test/impexp/data.xml.files/c7254f44aa10b6f89e328731672eda5082fd4976:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sourcebroker/t3api/fdf013c5687c92c8072c86b25fa8246e14b10f76/.ddev/test/impexp/data.xml.files/c7254f44aa10b6f89e328731672eda5082fd4976
--------------------------------------------------------------------------------
/.ddev/test/index.php:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | Run 'ddev install all' to install all testing instances below.
15 |
16 | https://{$version}.{$extensionKey}.ddev.site/typo3 user: {$typo3AdminUser}, pass: {$typo3AdminPassword}";
21 | } else {
22 | echo "- Version {$version} is not installed. Run 'ddev install {$version}' or 'ddev ci {$version}' to install.
";
23 | }
24 | }
25 | ?>
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig is awesome: http://EditorConfig.org
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | # Unix-style newlines with a newline ending every file
7 | [*]
8 | charset = utf-8
9 | end_of_line = lf
10 | indent_style = space
11 | indent_size = 4
12 | insert_final_newline = true
13 | trim_trailing_whitespace = true
14 |
15 | # TS/JS-Files
16 | [*.{ts,js}]
17 | indent_size = 2
18 |
19 | # JSON-Files
20 | [*.json]
21 | indent_style = tab
22 |
23 | # ReST-Files
24 | [*.{rst,rst.txt}]
25 | indent_size = 4
26 | max_line_length = 80
27 |
28 | # Markdown-Files
29 | [*.md]
30 | max_line_length = 80
31 |
32 | # YAML-Files
33 | [*.{yaml,yml}]
34 | indent_size = 2
35 |
36 | # NEON-Files
37 | [*.neon]
38 | indent_size = 2
39 | indent_style = tab
40 |
41 | # package.json
42 | [package.json]
43 | indent_size = 2
44 |
45 | # TypoScript
46 | [*.{typoscript,tsconfig}]
47 | indent_size = 2
48 |
49 | # XLF-Files
50 | [*.xlf]
51 | indent_style = tab
52 |
53 | # SQL-Files
54 | [*.sql]
55 | indent_style = tab
56 | indent_size = 2
57 |
58 | # .htaccess
59 | [{_.htaccess,.htaccess}]
60 | indent_style = tab
61 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 | contact_links:
3 | - name: Community support
4 | url: https://github.com/sourcebroker/t3api/discussions
5 | about: Please ask and answer questions here.
6 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.yml:
--------------------------------------------------------------------------------
1 | name: Feature request
2 | description: Suggest an idea for this extension.
3 | title: "[FEATURE]"
4 | labels:
5 | - enhancement
6 | assignees:
7 | - kszymukowicz
8 | body:
9 | - type: textarea
10 | attributes:
11 | label: Is your feature request related to a problem?
12 | description: A clear description of what the problem is.
13 | placeholder: I'm always frustrated when [...]
14 | validations:
15 | required: true
16 | - type: textarea
17 | attributes:
18 | label: Describe the solution you'd like
19 | description: A clear description of what you want to happen.
20 | validations:
21 | required: true
22 | - type: textarea
23 | attributes:
24 | label: Describe alternatives you've considered
25 | description: A clear description of any alternative solutions or features you've considered.
26 | - type: textarea
27 | attributes:
28 | label: Additional context
29 | description: Add any other context or screenshots about the feature request here.
30 | - type: checkboxes
31 | id: terms
32 | attributes:
33 | label: Code of Conduct
34 | description: >
35 | By submitting this issue, you agree to follow our
36 | [Code of Conduct](https://github.com/sourcebroker/t3api/blob/main/CODE_OF_CONDUCT.md).
37 | options:
38 | - label: I agree to follow this project's Code of Conduct.
39 | required: true
40 | - type: markdown
41 | attributes:
42 | value: >
43 | :bulb: **Tip:** Have you already looked into https://github.com/sourcebroker/t3api/discussions/categories/ideas?
44 | Maybe your idea has already been discussed there.
45 |
--------------------------------------------------------------------------------
/.github/release.yml:
--------------------------------------------------------------------------------
1 | changelog:
2 | exclude:
3 | labels:
4 | - duplicate
5 | - 'good first issue'
6 | - 'help wanted'
7 | - invalid
8 | - question
9 | - wontfix
10 | categories:
11 | - title: ⚡ Breaking
12 | labels:
13 | - breaking
14 | - title: 🚀 Improved
15 | labels:
16 | - enhancement
17 | - title: 🚑 Fixed
18 | labels:
19 | - bug
20 | - title: 👷 Changed
21 | labels:
22 | - maintenance
23 | - title: 📖 Documentation
24 | labels:
25 | - documentation
26 | - title: ⚙️ Dependencies
27 | labels:
28 | - dependencies
29 | - title: Other changes
30 | labels:
31 | - "*"
32 |
--------------------------------------------------------------------------------
/.github/workflows/TYPO3_12.yml:
--------------------------------------------------------------------------------
1 | name: TYPO3 12
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 | branches:
9 | - main
10 |
11 | jobs:
12 | test:
13 | runs-on: ubuntu-latest
14 | strategy:
15 | matrix:
16 | typo3: ["12"]
17 | php: ["8.1", "8.2", "8.3", "8.4"]
18 | composer: ["lowest", "highest"]
19 | env:
20 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
21 | steps:
22 | - uses: actions/checkout@v2
23 | - name: Run tests
24 | uses: ddev/github-action-setup-ddev@v1
25 | - run: |
26 | if [ -n "$GH_TOKEN" ] && ! ddev composer config --global --list | grep -q "github-oauth.github.com"; then
27 | echo "Add composer github-oauth.github.com to ddev web container."
28 | ddev composer config --global github-oauth.github.com ${{ env.GH_TOKEN }}
29 | fi
30 | - run: ddev ci ${{ matrix.typo3 }} ${{ matrix.php }} ${{ matrix.composer }}
31 |
32 |
--------------------------------------------------------------------------------
/.github/workflows/TYPO3_13.yml:
--------------------------------------------------------------------------------
1 | name: TYPO3 13
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 | branches:
9 | - main
10 |
11 | jobs:
12 | test:
13 | runs-on: ubuntu-latest
14 | strategy:
15 | matrix:
16 | typo3: ["13"]
17 | php: ["8.2", "8.3", "8.4"]
18 | composer: ["lowest", "highest"]
19 | env:
20 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
21 | steps:
22 | - uses: actions/checkout@v2
23 | - name: Run tests
24 | uses: ddev/github-action-setup-ddev@v1
25 | - run: |
26 | if [ -n "$GH_TOKEN" ] && ! ddev composer config --global --list | grep -q "github-oauth.github.com"; then
27 | echo "Add composer github-oauth.github.com to ddev web container."
28 | ddev composer config --global github-oauth.github.com ${{ env.GH_TOKEN }}
29 | fi
30 | - run: ddev ci ${{ matrix.typo3 }} ${{ matrix.php }} ${{ matrix.composer }}
31 |
32 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Git global ignore file
2 | # for local exclude patterns please edit .git/info/exclude
3 | # Example file see https://github.com/TYPO3-Documentation/T3DocTeam/blob/master/.gitignore
4 |
5 | # ignore generated documentation
6 | *GENERATED*
7 |
8 | # ignore typical clutter of IDEs and editors (this could be added in .git/info/exclude,
9 | # but we add it here for convenience)
10 | *~
11 | *.bak
12 | *.idea
13 | *.project
14 | *.swp
15 | .cache
16 |
17 | /composer.lock
18 | /.Build
19 | /.test
20 | /var/
21 | /.php_cs.cache
22 | /.php-cs-fixer.cache
23 | /composer.json.orig
24 |
--------------------------------------------------------------------------------
/.php-cs-fixer.php:
--------------------------------------------------------------------------------
1 | setParallelConfig(ParallelConfigFactory::detect())
9 | ->setFinder((new PhpCsFixer\Finder())
10 | ->in(realpath(__DIR__))
11 | ->ignoreVCSIgnored(true)
12 | ->notPath('/^Build\/phpunit\/(UnitTestsBootstrap|FunctionalTestsBootstrap).php/')
13 | ->notPath('/^Configuration\//')
14 | ->notPath('/^Documentation\//')
15 | ->notPath('/^Documentation-GENERATED-temp\//')
16 | ->notName('/^ext_(emconf|localconf|tables).php/')
17 | );
18 |
19 | return $config;
20 |
--------------------------------------------------------------------------------
/Build/phpunit/FunctionalTests.xml:
--------------------------------------------------------------------------------
1 |
2 |
14 |
30 |
31 |
32 | ../../Tests/Functional/
33 |
34 |
35 |
36 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/Build/phpunit/FunctionalTestsBootstrap.php:
--------------------------------------------------------------------------------
1 | defineOriginalRootPath();
28 | $testbase->createDirectory(ORIGINAL_ROOT . 'typo3temp/var/tests');
29 | $testbase->createDirectory(ORIGINAL_ROOT . 'typo3temp/var/transient');
30 | })();
31 |
--------------------------------------------------------------------------------
/Build/phpunit/UnitTests.xml:
--------------------------------------------------------------------------------
1 |
2 |
14 |
30 |
31 |
32 | ../../Tests/Unit/
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/Build/postman/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 |
--------------------------------------------------------------------------------
/Build/postman/.nvmrc:
--------------------------------------------------------------------------------
1 | 22
2 |
--------------------------------------------------------------------------------
/Build/postman/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "newman",
3 | "version": "1.0.0",
4 | "description": "",
5 | "devDependencies": {
6 | "newman": "^6.1.2"
7 | },
8 | "author": "",
9 | "license": ""
10 | }
11 |
--------------------------------------------------------------------------------
/Build/postman/run.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | source .ddev/test/utils.sh
4 |
5 | set +x
6 | set -e
7 |
8 | POSTMAN_BUILD_PATH=/var/www/html/Build/postman/
9 | cd ${POSTMAN_BUILD_PATH} || exit
10 |
11 | if [[ ! -e ".nvmrc" ]]; then
12 | echo_red "No file .nvmrc with node version in folder: ${POSTMAN_BUILD_PATH}" && exit 1
13 | fi
14 |
15 | if [[ ! -d "node_modules" ]]; then
16 | npm ci
17 | fi
18 |
19 | TYPO3=""
20 | TEST_FILE=""
21 |
22 | while [[ $# -gt 0 ]]; do
23 | case $1 in
24 | --file)
25 | TEST_FILE="$2"
26 | shift 2
27 | ;;
28 | *)
29 | TYPO3="$1"
30 | shift
31 | ;;
32 | esac
33 | done
34 |
35 | if [ -z "$TYPO3" ]; then
36 | TYPO3=$("../../.Build/bin/typo3" | grep -oP 'TYPO3 CMS \K[0-9]+')
37 | fi
38 |
39 | if ! check_typo3_version "$TYPO3"; then
40 | exit 1
41 | fi
42 |
43 | if [[ ! -d "/var/www/html/.test/$TYPO3" ]]; then
44 | echo_red "Can not test. Install first TYPO3 $TYPO3 with command 'ddev install $TYPO3'"
45 | else
46 | DOMAINS=("https://$TYPO3.$EXTENSION_KEY.ddev.site")
47 | for DOMAIN in "${DOMAINS[@]}"; do
48 | if [[ -n "$TEST_FILE" ]]; then
49 | ./node_modules/.bin/newman run "../../Tests/Postman/$TEST_FILE" --verbose --bail --env-var "baseUrl=$DOMAIN"
50 | else
51 | for TEST_FILE in ../../Tests/Postman/*.json; do
52 | ./node_modules/.bin/newman run "$TEST_FILE" --verbose --bail --env-var "baseUrl=$DOMAIN"
53 | done
54 | fi
55 | done
56 | fi
57 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Code of Conduct
2 |
3 | This project uses following code of conduct: https://typo3.org/community/values/code-of-conduct
4 |
5 | By contributing to this project or engaging with community members, you agree to abide by this code of conduct.
6 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Please have a look in the [development section][1] in documentation.
4 |
5 | [1]: https://docs.typo3.org/p/sourcebroker/t3api/main/en-us/Miscellaneous/Development/Index.html
6 |
--------------------------------------------------------------------------------
/Classes/Annotation/ApiResource.php:
--------------------------------------------------------------------------------
1 | itemOperations = $values['itemOperations'] ?? $this->itemOperations;
32 | $this->collectionOperations = $values['collectionOperations'] ?? $this->collectionOperations;
33 | $this->attributes = array_merge(
34 | $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['t3api']['pagination'] ?? [],
35 | $values['attributes'] ?? []
36 | );
37 | }
38 |
39 | public function getItemOperations(): array
40 | {
41 | return $this->itemOperations;
42 | }
43 |
44 | public function getCollectionOperations(): array
45 | {
46 | return $this->collectionOperations;
47 | }
48 |
49 | public function getAttributes(): array
50 | {
51 | return $this->attributes;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Classes/Annotation/ORM/Cascade.php:
--------------------------------------------------------------------------------
1 | values = (array)$values['value'];
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Classes/Annotation/Serializer/Exclude.php:
--------------------------------------------------------------------------------
1 |
15 | * @Required
16 | */
17 | public $groups;
18 | }
19 |
--------------------------------------------------------------------------------
/Classes/Annotation/Serializer/MaxDepth.php:
--------------------------------------------------------------------------------
1 | feUserClass = $options['value'];
43 | }
44 |
45 | public function getParams(): array
46 | {
47 | return [$this->feUserClass];
48 | }
49 |
50 | public function getName(): string
51 | {
52 | return CurrentFeUserHandler::TYPE;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/Classes/Annotation/Serializer/Type/Image.php:
--------------------------------------------------------------------------------
1 | width, $this->height, $this->maxWidth, $this->maxHeight, $this->cropVariant];
43 | }
44 |
45 | public function getName(): string
46 | {
47 | return ImageHandler::TYPE;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Classes/Annotation/Serializer/Type/PasswordHash.php:
--------------------------------------------------------------------------------
1 | identifier];
24 | }
25 |
26 | public function getName(): string
27 | {
28 | return RecordUriHandler::TYPE;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Classes/Annotation/Serializer/Type/Rte.php:
--------------------------------------------------------------------------------
1 | allowCredentials = isset($options['allowCredentials']) ? (bool)$options['allowCredentials'] : $this->allowCredentials;
26 | $this->allowOrigin = isset($options['allowOrigin']) ? (array)$options['allowOrigin'] : $this->allowOrigin;
27 | $this->allowHeaders = isset($options['allowHeaders']) ? (array)$options['allowHeaders'] : $this->allowHeaders;
28 | $this->allowHeaders = array_merge(
29 | $this->allowHeaders,
30 | (isset($options['simpleHeaders']) ? (array)$options['simpleHeaders'] : [])
31 | );
32 | $this->allowHeaders = array_map('strtolower', $this->allowHeaders);
33 | $this->allowMethods = isset($options['allowMethods']) ?
34 | array_map('strtoupper', (array)$options['allowMethods']) : $this->allowMethods;
35 | $this->exposeHeaders = isset($options['exposeHeaders']) ? (array)$options['exposeHeaders'] : $this->exposeHeaders;
36 | $this->maxAge = isset($options['maxAge']) ? (int)$options['maxAge'] : $this->maxAge;
37 | $this->originRegex = isset($options['originRegex']) ? (bool)$options['originRegex'] : $this->originRegex;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Classes/Controller/OpenApiController.php:
--------------------------------------------------------------------------------
1 | getQueryParams()['site'] ?? null;
38 | $site = $this->siteFinder->getSiteByIdentifier($siteIdentifier);
39 |
40 | $imitateSiteRequest = $request->withAttribute('site', $site);
41 | $GLOBALS['TYPO3_REQUEST'] = $imitateSiteRequest;
42 | $output = OpenApiBuilder::build($this->apiResourceRepository->getAll())->toJson();
43 | $GLOBALS['TYPO3_REQUEST'] = $request;
44 |
45 | $response = new Response();
46 | $response = $response->withHeader('Content-Type', 'application/json');
47 | $response->getBody()->write($output);
48 |
49 | return $response;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/Classes/Dispatcher/HeadlessDispatcher.php:
--------------------------------------------------------------------------------
1 | name = !empty($strategy) ? $strategy : '';
17 | } elseif (is_array($strategy)) {
18 | $this->name = $strategy['name'] ?? '';
19 | $this->condition = $strategy['condition'] ?? '';
20 | } else {
21 | throw new \InvalidArgumentException(
22 | sprintf('%s::$strategy has to be either string or array', self::class),
23 | 1587649745
24 | );
25 | }
26 | }
27 |
28 | public function getName(): ?string
29 | {
30 | return $this->name;
31 | }
32 |
33 | public function getCondition(): ?string
34 | {
35 | return $this->condition;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Classes/Domain/Model/CollectionOperation.php:
--------------------------------------------------------------------------------
1 | pagination = Pagination::create($params['attributes'] ?? [], $apiResource->getPagination());
20 | }
21 |
22 | public function addFilter(ApiFilter $apiFilter): void
23 | {
24 | $this->filters[] = $apiFilter;
25 | }
26 |
27 | /**
28 | * @return ApiFilter[]
29 | */
30 | public function getFilters(): array
31 | {
32 | return $this->filters;
33 | }
34 |
35 | public function getPagination(): Pagination
36 | {
37 | return $this->pagination;
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Classes/Domain/Model/CollectionOperationFactory.php:
--------------------------------------------------------------------------------
1 | storagePids = GeneralUtility::intExplode(',', $attributes['storagePid']);
25 | }
26 | $persistenceSettings->recursionLevel = (int)($attributes['recursive'] ?? $persistenceSettings->recursionLevel);
27 |
28 | return $persistenceSettings;
29 | }
30 |
31 | /**
32 | * @return int[]
33 | */
34 | public function getStoragePids(): array
35 | {
36 | return $this->storagePids;
37 | }
38 |
39 | public function getRecursionLevel(): int
40 | {
41 | return $this->recursionLevel;
42 | }
43 |
44 | public function getMainStoragePid(): int
45 | {
46 | if (empty($this->storagePids)) {
47 | return 0;
48 | }
49 |
50 | return $this->storagePids[0];
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/Classes/Event/AfterCreateContextForOperationEvent.php:
--------------------------------------------------------------------------------
1 | operation = $operation;
25 | $this->request = $request;
26 | $this->context = $context;
27 | }
28 |
29 | public function getOperation(): OperationInterface
30 | {
31 | return $this->operation;
32 | }
33 |
34 | public function getRequest(): Request
35 | {
36 | return $this->request;
37 | }
38 |
39 | public function getContext(): Context
40 | {
41 | return $this->context;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Classes/Event/AfterDeserializeOperationEvent.php:
--------------------------------------------------------------------------------
1 | operation = $operation;
19 | $this->object = $object;
20 | }
21 |
22 | public function getOperation(): OperationInterface
23 | {
24 | return $this->operation;
25 | }
26 |
27 | public function getObject(): AbstractDomainObject
28 | {
29 | return $this->object;
30 | }
31 |
32 | public function setObject($object): void
33 | {
34 | $this->object = $object;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Classes/Event/AfterProcessOperationEvent.php:
--------------------------------------------------------------------------------
1 | operation = $operation;
21 | $this->result = $result;
22 | }
23 |
24 | public function getOperation(): OperationInterface
25 | {
26 | return $this->operation;
27 | }
28 |
29 | public function getResult()
30 | {
31 | return $this->result;
32 | }
33 |
34 | public function setResult($result): void
35 | {
36 | $this->result = $result;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Classes/Event/BeforeFilterAccessGrantedEvent.php:
--------------------------------------------------------------------------------
1 | filter = $filter;
20 | $this->expressionLanguageVariables = $expressionLanguageVariables;
21 | }
22 |
23 | public function getFilter(): ApiFilter
24 | {
25 | return $this->filter;
26 | }
27 |
28 | public function getExpressionLanguageVariables(): array
29 | {
30 | return $this->expressionLanguageVariables;
31 | }
32 |
33 | public function setExpressionLanguageVariable(string $name, $value): void
34 | {
35 | $this->expressionLanguageVariables[$name] = $value;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Classes/Event/BeforeOperationAccessGrantedEvent.php:
--------------------------------------------------------------------------------
1 | operation = $operation;
20 | $this->expressionLanguageVariables = $expressionLanguageVariables;
21 | }
22 |
23 | public function getOperation(): OperationInterface
24 | {
25 | return $this->operation;
26 | }
27 |
28 | public function getExpressionLanguageVariables(): array
29 | {
30 | return $this->expressionLanguageVariables;
31 | }
32 |
33 | public function setExpressionLanguageVariable(string $name, $value): void
34 | {
35 | $this->expressionLanguageVariables[$name] = $value;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Classes/Event/BeforeOperationAccessGrantedPostDenormalizeEvent.php:
--------------------------------------------------------------------------------
1 | operation = $operation;
20 | $this->expressionLanguageVariables = $expressionLanguageVariables;
21 | }
22 |
23 | public function getOperation(): OperationInterface
24 | {
25 | return $this->operation;
26 | }
27 |
28 | public function getExpressionLanguageVariables(): array
29 | {
30 | return $this->expressionLanguageVariables;
31 | }
32 |
33 | public function setExpressionLanguageVariable(string $name, $value): void
34 | {
35 | $this->expressionLanguageVariables[$name] = $value;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Classes/EventListener/AddHydraCollectionResponseSerializationGroupEventListener.php:
--------------------------------------------------------------------------------
1 | getOperation();
17 | $context = $createContextForOperationEvent->getContext();
18 |
19 | $collectionResponseClass = Configuration::getCollectionResponseClass();
20 | if (
21 | (
22 | $collectionResponseClass === HydraCollectionResponse::class
23 | || is_subclass_of($collectionResponseClass, HydraCollectionResponse::class)
24 | )
25 | && $createContextForOperationEvent->getOperation() instanceof CollectionOperation
26 | && $context->hasAttribute('groups')
27 | && $operation->isMethodGet()
28 | ) {
29 | $context->setGroups(array_merge(
30 | $context->getAttribute('groups'),
31 | ['__hydra_collection_response']
32 | ));
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Classes/EventListener/EnrichSerializationContextEventListener.php:
--------------------------------------------------------------------------------
1 | GeneralUtility::getIndpEnv('TYPO3_HOST_ONLY'),
16 | 'TYPO3_PORT' => GeneralUtility::getIndpEnv('TYPO3_PORT'),
17 | 'TYPO3_REQUEST_HOST' => GeneralUtility::getIndpEnv('TYPO3_REQUEST_HOST'),
18 | 'TYPO3_REQUEST_URL' => GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL'),
19 | 'TYPO3_REQUEST_SCRIPT' => GeneralUtility::getIndpEnv('TYPO3_REQUEST_SCRIPT'),
20 | 'TYPO3_REQUEST_DIR' => GeneralUtility::getIndpEnv('TYPO3_REQUEST_DIR'),
21 | 'TYPO3_SITE_URL' => GeneralUtility::getIndpEnv('TYPO3_SITE_URL'),
22 | 'TYPO3_SITE_PATH' => GeneralUtility::getIndpEnv('TYPO3_SITE_PATH'),
23 | 'TYPO3_SITE_SCRIPT' => GeneralUtility::getIndpEnv('TYPO3_SITE_SCRIPT'),
24 | 'TYPO3_DOCUMENT_ROOT' => GeneralUtility::getIndpEnv('TYPO3_DOCUMENT_ROOT'),
25 | 'TYPO3_SSL' => GeneralUtility::getIndpEnv('TYPO3_SSL'),
26 | 'TYPO3_PROXY' => GeneralUtility::getIndpEnv('TYPO3_PROXY'),
27 | ];
28 |
29 | foreach ($attributes as $name => $value) {
30 | $createContextForOperationEvent
31 | ->getContext()
32 | ->setAttribute($name, $value);
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Classes/Exception/ExceptionInterface.php:
--------------------------------------------------------------------------------
1 | title = self::translate('exception.method_not_allowed.title');
15 |
16 | try {
17 | $className = (new \ReflectionClass($operation))->getShortName();
18 | } catch (\ReflectionException $exception) {
19 | $className = self::class;
20 | }
21 |
22 | parent::__construct(
23 | self::translate(
24 | 'exception.method_not_allowed.description',
25 | [
26 | $operation->getMethod(),
27 | $className,
28 | ]
29 | ),
30 | $code
31 | );
32 | }
33 |
34 | public function getStatusCode(): int
35 | {
36 | return Response::HTTP_METHOD_NOT_ALLOWED;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Classes/Exception/OpenApiSupportingExceptionInterface.php:
--------------------------------------------------------------------------------
1 | statusCode(SymfonyResponse::HTTP_NOT_FOUND)
18 | ->description(self::translate('exception.resource_not_found.title'));
19 | }
20 |
21 | public function __construct(OperationInterface $operation, int $code)
22 | {
23 | $this->title = self::translate('exception.operation_not_allowed.title');
24 |
25 | parent::__construct(
26 | self::translate(
27 | 'exception.operation_not_allowed.description',
28 | [$operation->getPath()]
29 | ),
30 | $code
31 | );
32 | }
33 |
34 | public function getStatusCode(): int
35 | {
36 | return Response::HTTP_FORBIDDEN;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Classes/Exception/ResourceNotFoundException.php:
--------------------------------------------------------------------------------
1 | statusCode(SymfonyResponse::HTTP_NOT_FOUND)
17 | ->description(self::translate('exception.resource_not_found.title'));
18 | }
19 |
20 | public function __construct(string $resourceType, int $uid, int $code)
21 | {
22 | $this->title = self::translate('exception.resource_not_found.title');
23 | parent::__construct(
24 | self::translate('exception.resource_not_found.description', [$resourceType, $uid]),
25 | $code
26 | );
27 | }
28 |
29 | public function getStatusCode(): int
30 | {
31 | return Response::HTTP_NOT_FOUND;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Classes/Exception/RouteNotFoundException.php:
--------------------------------------------------------------------------------
1 | title = self::translate('exception.route_not_found.title');
14 | parent::__construct(self::translate('exception.route_not_found.description'), $code);
15 | }
16 |
17 | public function getStatusCode(): int
18 | {
19 | return Response::HTTP_NOT_FOUND;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Classes/ExpressionLanguage/ConditionFunctionsProvider.php:
--------------------------------------------------------------------------------
1 | expressionLanguageProviders = [
16 | ConditionFunctionsProvider::class,
17 | ];
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Classes/ExpressionLanguage/T3apiCoreFunctionsProvider.php:
--------------------------------------------------------------------------------
1 | getForceAbsoluteUrlFunction(),
20 | ];
21 | }
22 |
23 | protected function getForceAbsoluteUrlFunction(): ExpressionFunction
24 | {
25 | return new ExpressionFunction(
26 | 'force_absolute_url',
27 | static function (): void {},
28 | static function ($existingVariables, string $url, string $fallbackHost): string {
29 | return UrlService::forceAbsoluteUrl($url, $fallbackHost);
30 | }
31 | );
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Classes/ExpressionLanguage/T3apiCoreProvider.php:
--------------------------------------------------------------------------------
1 | expressionLanguageProviders = [
14 | T3apiCoreFunctionsProvider::class,
15 | ];
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Classes/Factory/ApiResourceFactory.php:
--------------------------------------------------------------------------------
1 | annotationReader = new AnnotationReader();
20 | }
21 |
22 | public function createApiResourceFromFqcn(string $fqcn): ?ApiResource
23 | {
24 | /** @var ApiResourceAnnotation $apiResourceAnnotation */
25 | $apiResourceAnnotation = $this->annotationReader->getClassAnnotation(
26 | new \ReflectionClass($fqcn),
27 | ApiResourceAnnotation::class
28 | );
29 |
30 | if (!$apiResourceAnnotation instanceof ApiResourceAnnotation) {
31 | return null;
32 | }
33 |
34 | $apiResource = new ApiResource($fqcn, $apiResourceAnnotation);
35 |
36 | $this->addFiltersToApiResource($apiResource);
37 |
38 | return $apiResource;
39 | }
40 |
41 | protected function addFiltersToApiResource(ApiResource $apiResource): void
42 | {
43 | $filterAnnotations = array_filter(
44 | $this->annotationReader->getClassAnnotations(new \ReflectionClass($apiResource->getEntity())),
45 | static function ($annotation): bool {
46 | return $annotation instanceof ApiFilterAnnotation;
47 | }
48 | );
49 |
50 | foreach ($filterAnnotations as $filterAnnotation) {
51 | foreach (ApiFilter::createFromAnnotations($filterAnnotation) as $apiFilter) {
52 | $apiResource->addFilter($apiFilter);
53 | }
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Classes/Filter/BooleanFilter.php:
--------------------------------------------------------------------------------
1 | name($apiFilter->getParameterName())
24 | ->schema(Schema::boolean()),
25 | ];
26 | }
27 |
28 | /**
29 | * @inheritDoc
30 | */
31 | public function filterProperty(
32 | string $property,
33 | $values,
34 | QueryInterface $query,
35 | ApiFilter $apiFilter
36 | ): ?ConstraintInterface {
37 | return $query->equals($property, ParameterUtility::toBoolean(((array)$values)[0]));
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Classes/Filter/FilterInterface.php:
--------------------------------------------------------------------------------
1 | name($apiFilter->getParameterName())
24 | ->schema(Schema::integer()),
25 | ];
26 | }
27 |
28 | /**
29 | * @inheritDoc
30 | * @throws InvalidQueryException
31 | */
32 | public function filterProperty(
33 | string $property,
34 | $values,
35 | QueryInterface $query,
36 | ApiFilter $apiFilter
37 | ): ?ConstraintInterface {
38 | return $query->in($property, array_map('intval', (array)$values));
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Classes/Filter/OpenApiSupportingFilterInterface.php:
--------------------------------------------------------------------------------
1 | 'order',
20 | ];
21 |
22 | /**
23 | * @return Parameter[]
24 | */
25 | public static function getOpenApiParameters(ApiFilter $apiFilter): array
26 | {
27 | return [
28 | Parameter::create()
29 | ->name($apiFilter->getParameterName() . '[' . $apiFilter->getProperty() . ']')
30 | ->in(Parameter::IN_QUERY)
31 | ->schema(Schema::string()->enum('asc', 'desc')),
32 | ];
33 | }
34 |
35 | /**
36 | * @inheritDoc
37 | */
38 | public function filterProperty(
39 | string $property,
40 | $values,
41 | QueryInterface $query,
42 | ApiFilter $apiFilter
43 | ): ?ConstraintInterface {
44 | if (!isset($values[$property])) {
45 | return null;
46 | }
47 |
48 | $defaultDirection = $apiFilter->getStrategy()->getName();
49 | $direction = strtoupper($values[$property] !== '' ? $values[$property] : $defaultDirection);
50 |
51 | if ($direction === '') {
52 | return null;
53 | }
54 |
55 | if (!in_array($direction, [QueryInterface::ORDER_ASCENDING, QueryInterface::ORDER_DESCENDING], true)) {
56 | throw new \InvalidArgumentException(sprintf('Unknown order direction `%s`', $direction), 1560890654236);
57 | }
58 |
59 | $query->setOrderings(array_merge($query->getOrderings(), [$property => $direction]));
60 |
61 | return null;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Classes/Filter/UidFilter.php:
--------------------------------------------------------------------------------
1 | getQuerySettings()->setRespectSysLanguage(false);
21 | $languageAspect = new LanguageAspect(
22 | $query->getQuerySettings()->getLanguageAspect()->getId(),
23 | $query->getQuerySettings()->getLanguageAspect()->getContentId(),
24 | LanguageAspect::OVERLAYS_ON
25 | );
26 | $query->getQuerySettings()->setLanguageAspect($languageAspect);
27 |
28 | return parent::filterProperty($property, $values, $query, $apiFilter);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Classes/Hook/EnrichHashBase.php:
--------------------------------------------------------------------------------
1 | getAttribute('language');
20 | $t3apiHeaderLanguageUid = $this->getT3apiLanguageUid($request);
21 |
22 | if ($t3apiHeaderLanguageUid !== null
23 | && RouteService::routeHasT3ApiResourceEnhancerQueryParam($request)
24 | && ($language instanceof SiteLanguage && $language->getLanguageId() !== $t3apiHeaderLanguageUid)
25 | ) {
26 | $request->withAttribute('t3apiHeaderLanguageRequest', true);
27 | $request = $request->withAttribute(
28 | 'language',
29 | $request->getAttribute('site')->getLanguageById($t3apiHeaderLanguageUid)
30 | );
31 | }
32 | return $handler->handle($request);
33 | }
34 |
35 | protected function getT3apiLanguageUid(ServerRequestInterface $request): ?int
36 | {
37 | $languageHeader = $request->getHeader($GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['t3api']['languageHeader']);
38 | return !empty($languageHeader) ? (int)array_shift($languageHeader) : null;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Classes/Middleware/T3apiRequestResolver.php:
--------------------------------------------------------------------------------
1 | bootstrap = $bootstrap;
22 | }
23 |
24 | /**
25 | * @throws \Throwable
26 | */
27 | public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
28 | {
29 | if (RouteService::routeHasT3ApiResourceEnhancerQueryParam($request)) {
30 | return $this->bootstrap->process($this->cleanupRequest($request));
31 | }
32 |
33 | return $handler->handle($request);
34 | }
35 |
36 | /**
37 | * Removes `t3apiResource` query parameter as it may break further functionality.
38 | * This parameter is needed only to reach a handler - further processing should not rely on it.
39 | */
40 | private function cleanupRequest(ServerRequestInterface $request): ServerRequestInterface
41 | {
42 | $cleanedQueryParams = $request->getQueryParams();
43 | unset($cleanedQueryParams[ResourceEnhancer::PARAMETER_NAME]);
44 |
45 | return $request->withQueryParams($cleanedQueryParams);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Classes/OperationHandler/AbstractCollectionOperationHandler.php:
--------------------------------------------------------------------------------
1 | operationAccessChecker->isGranted($operation)) {
28 | throw new OperationNotAllowedException($operation, 1574416639472);
29 | }
30 | return null;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Classes/OperationHandler/AbstractItemOperationHandler.php:
--------------------------------------------------------------------------------
1 | getRepositoryForOperation($operation);
31 |
32 | /** @var AbstractDomainObject|null $object */
33 | $object = $repository->findByUid((int)$route['id']);
34 |
35 | if (!$object instanceof AbstractDomainObject) {
36 | throw new ResourceNotFoundException(
37 | $operation->getApiResource()->getEntity(),
38 | (int)$route['id'],
39 | 1581461016515
40 | );
41 | }
42 |
43 | if (!$this->operationAccessChecker->isGranted($operation, ['object' => $object])) {
44 | throw new OperationNotAllowedException($operation, 1574411504130);
45 | }
46 |
47 | return $object;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Classes/OperationHandler/CollectionGetOperationHandler.php:
--------------------------------------------------------------------------------
1 | isMethodGet();
20 | }
21 |
22 | /** @noinspection ReferencingObjectsInspection */
23 | public function handle(OperationInterface $operation, Request $request, array $route, ?ResponseInterface &$response)
24 | {
25 | /** @var CollectionOperation $operation */
26 | parent::handle($operation, $request, $route, $response);
27 | $collectionResponseClass = Configuration::getCollectionResponseClass();
28 | $repository = $this->getRepositoryForOperation($operation);
29 |
30 | if (!is_subclass_of($collectionResponseClass, AbstractCollectionResponse::class)) {
31 | throw new \InvalidArgumentException(
32 | sprintf(
33 | 'Collection response class (`%s`) has to be an instance of `%s`',
34 | $collectionResponseClass,
35 | AbstractCollectionResponse::class
36 | )
37 | );
38 | }
39 |
40 | /** @var AbstractCollectionResponse $responseObject */
41 | $responseObject = GeneralUtility::makeInstance(
42 | $collectionResponseClass,
43 | $operation,
44 | $request,
45 | $repository->findFiltered($operation->getFilters(), $request)
46 | );
47 |
48 | return $responseObject;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Classes/OperationHandler/CollectionMethodNotAllowedOperationHandler.php:
--------------------------------------------------------------------------------
1 | isMethodPost();
22 | }
23 |
24 | /**
25 | * @return mixed|AbstractDomainObject|void
26 | * @throws ValidationException
27 | * @throws OperationNotAllowedException
28 | */
29 | public function handle(OperationInterface $operation, Request $request, array $route, ?ResponseInterface &$response)
30 | {
31 | /** @var CollectionOperation $operation */
32 | parent::handle($operation, $request, $route, $response);
33 | $repository = $this->getRepositoryForOperation($operation);
34 |
35 | $object = $this->deserializeOperation($operation, $request);
36 | $this->validationService->validateObject($object);
37 | $repository->add($object);
38 | GeneralUtility::makeInstance(PersistenceManager::class)->persistAll();
39 |
40 | $response = $response ? $response->withStatus(201) : $response;
41 |
42 | return $object;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Classes/OperationHandler/ItemDeleteOperationHandler.php:
--------------------------------------------------------------------------------
1 | isMethodDelete();
21 | }
22 |
23 | /**
24 | * @return mixed|null
25 | * @noinspection ReferencingObjectsInspection
26 | * @throws OperationNotAllowedException
27 | * @throws ResourceNotFoundException
28 | */
29 | public function handle(OperationInterface $operation, Request $request, array $route, ?ResponseInterface &$response)
30 | {
31 | /** @var ItemOperation $operation */
32 | $repository = $this->getRepositoryForOperation($operation);
33 | $object = parent::handle($operation, $request, $route, $response);
34 | $repository->remove($object);
35 | GeneralUtility::makeInstance(PersistenceManager::class)->persistAll();
36 | $object = null;
37 |
38 | return null;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Classes/OperationHandler/ItemGetOperationHandler.php:
--------------------------------------------------------------------------------
1 | isMethodGet();
20 | }
21 |
22 | /**
23 | * @noinspection ReferencingObjectsInspection
24 | * @throws OperationNotAllowedException
25 | * @throws ResourceNotFoundException
26 | */
27 | public function handle(
28 | OperationInterface $operation,
29 | Request $request,
30 | array $route,
31 | ?ResponseInterface &$response
32 | ): AbstractDomainObject {
33 | /** @var ItemOperation $operation */
34 | return parent::handle($operation, $request, $route, $response);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Classes/OperationHandler/ItemMethodNotAllowedOperationHandler.php:
--------------------------------------------------------------------------------
1 | isMethodPatch();
24 | }
25 |
26 | /**
27 | * @noinspection ReferencingObjectsInspection
28 | * @throws UnknownObjectException
29 | * @throws OperationNotAllowedException
30 | * @throws ValidationException
31 | * @throws ResourceNotFoundException
32 | */
33 | public function handle(
34 | OperationInterface $operation,
35 | Request $request,
36 | array $route,
37 | ?ResponseInterface &$response
38 | ): AbstractDomainObject {
39 | /** @var ItemOperation $operation */
40 | $repository = $this->getRepositoryForOperation($operation);
41 | $object = parent::handle($operation, $request, $route, $response);
42 | $this->deserializeOperation($operation, $request, $object);
43 | $this->validationService->validateObject($object);
44 | $repository->update($object);
45 | GeneralUtility::makeInstance(PersistenceManager::class)->persistAll();
46 |
47 | return $object;
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Classes/OperationHandler/OperationHandlerInterface.php:
--------------------------------------------------------------------------------
1 | isCorsRequest($request)
19 | || $this->isPreflightRequest($request)
20 | ) {
21 | return;
22 | }
23 |
24 | $options = $this->corsService->getOptions();
25 |
26 | $requestOrigin = $request->headers->get('Origin');
27 |
28 | if (!$this->corsService->isAllowedOrigin($requestOrigin, $options)) {
29 | $response = $response->withoutHeader('Access-Control-Allow-Origin');
30 | }
31 |
32 | $response = $response->withHeader(
33 | 'Access-Control-Allow-Origin',
34 | $requestOrigin
35 | );
36 |
37 | if ($options->allowCredentials) {
38 | $response = $response->withHeader('Access-Control-Allow-Credentials', 'true');
39 | }
40 |
41 | if ($options->exposeHeaders !== []) {
42 | $response = $response->withHeader(
43 | 'Access-Control-Expose-Headers',
44 | strtolower(implode(', ', $options->exposeHeaders))
45 | );
46 | }
47 | }
48 |
49 | protected function isCorsRequest(Request $request): bool
50 | {
51 | return $request->headers->has('Origin')
52 | && $request->headers->get('Origin')
53 | !== $request->getSchemeAndHttpHost();
54 | }
55 |
56 | protected function isPreflightRequest(Request $request): bool
57 | {
58 | return $request->getMethod() === Request::METHOD_OPTIONS;
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/Classes/Processor/ProcessorInterface.php:
--------------------------------------------------------------------------------
1 |
13 | */
14 | public function getAll(): iterable;
15 | }
16 |
--------------------------------------------------------------------------------
/Classes/Provider/ApiResourcePath/LoadedExtensionsDomainModelApiResourcePathProvider.php:
--------------------------------------------------------------------------------
1 | operation = $operation;
29 | $this->request = $request;
30 | $this->query = $query;
31 | }
32 |
33 | public function getMembers(): array
34 | {
35 | if ($this->membersCache === null) {
36 | $this->membersCache = $this->applyPagination()->execute()->toArray();
37 | }
38 |
39 | return $this->membersCache;
40 | }
41 |
42 | public function getTotalItems(): int
43 | {
44 | if ($this->totalItemsCache === null) {
45 | $this->totalItemsCache = $this->query->execute()->count();
46 | }
47 |
48 | return $this->totalItemsCache;
49 | }
50 |
51 | protected function applyPagination(): QueryInterface
52 | {
53 | $pagination = $this->operation->getPagination()->setParametersFromRequest($this->request);
54 |
55 | if (!$pagination->isEnabled()) {
56 | return $this->query;
57 | }
58 |
59 | return (clone $this->query)
60 | ->setLimit($pagination->getNumberOfItemsPerPage())
61 | ->setOffset($pagination->getOffset());
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Classes/Response/MainEndpointResponse.php:
--------------------------------------------------------------------------------
1 | apiResourceRepository->getAll() as $apiResource) {
22 | if (!$apiResource->getMainCollectionOperation() instanceof CollectionOperation) {
23 | continue;
24 | }
25 |
26 | $resources[$apiResource->getEntity()] = $apiResource->getMainCollectionOperation()->getRoute()->getPath();
27 | }
28 |
29 | return $resources;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Classes/Routing/Enhancer/ResourceEnhancer.php:
--------------------------------------------------------------------------------
1 | configuration = $configuration;
27 | }
28 |
29 | /**
30 | * {@inheritdoc}
31 | */
32 | public function enhanceForMatching(RouteCollection $collection): void
33 | {
34 | /** @var Route $variant */
35 | $variant = clone $collection->get('default');
36 | $variant->setPath($this->getBasePath() . sprintf('/{%s?}', self::PARAMETER_NAME));
37 | $variant->setRequirement(self::PARAMETER_NAME, '.*');
38 | $collection->add('enhancer_' . $this->getBasePath() . spl_object_hash($variant), $variant);
39 | }
40 |
41 | /**
42 | * {@inheritdoc}
43 | * // @todo Think if it ever could be needed
44 | */
45 | public function enhanceForGeneration(RouteCollection $collection, array $parameters): void {}
46 |
47 | protected function getBasePath(): string
48 | {
49 | static $basePath;
50 |
51 | return $basePath ?? $basePath = RouteService::getApiBasePath();
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Classes/Security/FilterAccessChecker.php:
--------------------------------------------------------------------------------
1 | eventDispatcher->dispatch($event);
19 | $expressionLanguageVariables = $event->getExpressionLanguageVariables();
20 |
21 | if (empty($filter->getStrategy()->getCondition())) {
22 | return true;
23 | }
24 |
25 | $variables = array_merge($expressionLanguageVariables, ['t3apiFilter' => $filter]);
26 |
27 | return $this->getExpressionLanguageResolver($variables)->evaluate($filter->getStrategy()->getCondition());
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Classes/Security/OperationAccessChecker.php:
--------------------------------------------------------------------------------
1 | eventDispatcher->dispatch($event);
20 | $expressionLanguageVariables = $event->getExpressionLanguageVariables();
21 |
22 | if ($operation->getSecurity() === '') {
23 | return true;
24 | }
25 |
26 | $variables = array_merge($expressionLanguageVariables, ['t3apiOperation' => $operation]);
27 |
28 | return $this->getExpressionLanguageResolver($variables)->evaluate($operation->getSecurity());
29 | }
30 |
31 | public function isGrantedPostDenormalize(
32 | OperationInterface $operation,
33 | array $expressionLanguageVariables = []
34 | ): bool {
35 | $event = new BeforeOperationAccessGrantedPostDenormalizeEvent(
36 | $operation,
37 | $expressionLanguageVariables
38 | );
39 | $this->eventDispatcher->dispatch($event);
40 | $expressionLanguageVariables = $event->getExpressionLanguageVariables();
41 |
42 | if ($operation->getSecurityPostDenormalize() === '') {
43 | return true;
44 | }
45 |
46 | $variables = array_merge($expressionLanguageVariables, ['t3apiOperation' => $operation]);
47 |
48 | return $this->getExpressionLanguageResolver($variables)->evaluate($operation->getSecurityPostDenormalize());
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Classes/Serializer/Construction/ExtbaseObjectConstructor.php:
--------------------------------------------------------------------------------
1 | name);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Classes/Serializer/Construction/InitializedObjectConstructor.php:
--------------------------------------------------------------------------------
1 | hasAttribute('target') && $context->getDepth() === 1) {
29 | return $context->getAttribute('target');
30 | }
31 | return null;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Classes/Serializer/ContextBuilder/AbstractContextBuilder.php:
--------------------------------------------------------------------------------
1 | eventDispatcher->dispatch(
23 | new AfterCreateContextForOperationEvent(
24 | $operation,
25 | $request,
26 | $context
27 | )
28 | );
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Classes/Serializer/ContextBuilder/ContextBuilderInterface.php:
--------------------------------------------------------------------------------
1 | enableMaxDepthChecks();
21 | }
22 |
23 | /**
24 | * @param null $targetObject
25 | * @return DeserializationContext
26 | */
27 | public function createFromOperation(OperationInterface $operation, Request $request, mixed $targetObject = null): Context
28 | {
29 | $context = $this->create();
30 |
31 | // There is a fallback to `normalizationContext` because of backward compatibility. Until version 1.2.x
32 | // `denormalizationContext` did not exist and same attributes were used for both contexts
33 | $attributes = $operation->getDenormalizationContext() ?? $operation->getNormalizationContext() ?? [];
34 |
35 | if ($targetObject !== null) {
36 | $attributes['target'] = $targetObject;
37 | }
38 |
39 | foreach ($attributes as $attributeName => $attributeValue) {
40 | $context->setAttribute($attributeName, $attributeValue);
41 | }
42 |
43 | $this->dispatchAfterCreateContextForOperationEvent(
44 | $operation,
45 | $request,
46 | $context
47 | );
48 | return $context;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Classes/Serializer/ContextBuilder/SerializationContextBuilder.php:
--------------------------------------------------------------------------------
1 | enableMaxDepthChecks()
21 | ->setSerializeNull(true);
22 | }
23 |
24 | /**
25 | * @return SerializationContext
26 | */
27 | public function createFromOperation(
28 | OperationInterface $operation,
29 | Request $request
30 | ): Context {
31 | $context = $this->create();
32 |
33 | $attributes = $operation->getNormalizationContext() ?? [];
34 |
35 | foreach ($attributes as $attributeName => $attributeValue) {
36 | $context->setAttribute($attributeName, $attributeValue);
37 | }
38 |
39 | $this->dispatchAfterCreateContextForOperationEvent(
40 | $operation,
41 | $request,
42 | $context
43 | );
44 | return $context;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Classes/Serializer/Handler/CurrentFeUserHandler.php:
--------------------------------------------------------------------------------
1 | persistenceManager->getObjectByIdentifier($data, $type['params'][0]);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Classes/Serializer/Handler/DeserializeHandlerInterface.php:
--------------------------------------------------------------------------------
1 | passwordHashFactory->getDefaultHashInstance('FE')->getHashedPassword($data);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Classes/Serializer/Handler/RecordUriHandler.php:
--------------------------------------------------------------------------------
1 | getObject();
40 |
41 | if (!$entity instanceof AbstractDomainObject) {
42 | throw new \InvalidArgumentException(
43 | sprintf('Object has to extend %s to build URI', AbstractDomainObject::class),
44 | 1562229270419
45 | );
46 | }
47 |
48 | $url = $this->contentObjectRenderer->typoLink_URL([
49 | 'parameter' => sprintf('t3://record?identifier=%s&uid=%s', $type['params'][0], $entity->getUid()),
50 | ]);
51 | return UrlService::forceAbsoluteUrl(
52 | $url,
53 | $context->getAttribute('TYPO3_SITE_URL')
54 | );
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/Classes/Serializer/Handler/RteHandler.php:
--------------------------------------------------------------------------------
1 | getContentObjectRenderer()
29 | ->parseFunc($text, [], '< lib.parseFunc_RTE');
30 | }
31 |
32 | protected function getContentObjectRenderer(): ContentObjectRenderer
33 | {
34 | static $contentObjectRenderer;
35 |
36 | if (!$contentObjectRenderer instanceof ContentObjectRenderer) {
37 | $contentObjectRenderer
38 | = GeneralUtility::makeInstance(ContentObjectRenderer::class);
39 | }
40 |
41 | return $contentObjectRenderer;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Classes/Serializer/Handler/SerializeHandlerInterface.php:
--------------------------------------------------------------------------------
1 | contentObjectRenderer->typoLink_URL([
35 | 'parameter' => $typolinkParameter,
36 | ]);
37 |
38 | return UrlService::forceAbsoluteUrl($url, $context->getAttribute('TYPO3_SITE_URL'));
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Classes/Serializer/Subscriber/GenerateMetadataSubscriber.php:
--------------------------------------------------------------------------------
1 | Events::PRE_SERIALIZE,
20 | 'method' => 'onPreSerialize',
21 | ],
22 | [
23 | 'event' => Events::PRE_DESERIALIZE,
24 | 'method' => 'onPreDeserialize',
25 | ],
26 | ];
27 | }
28 |
29 | /**
30 | * @throws \ReflectionException
31 | */
32 | public function onPreSerialize(ObjectEvent $event): void
33 | {
34 | if (class_exists($event->getType()['name'])) {
35 | SerializerMetadataService::generateAutoloadForClass($event->getType()['name']);
36 | }
37 | }
38 |
39 | /**
40 | * @throws \ReflectionException
41 | */
42 | public function onPreDeserialize(PreDeserializeEvent $event): void
43 | {
44 | if (class_exists($event->getType()['name'])) {
45 | SerializerMetadataService::generateAutoloadForClass($event->getType()['name']);
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Classes/Serializer/Subscriber/ResourceTypeSubscriber.php:
--------------------------------------------------------------------------------
1 | Events::POST_SERIALIZE,
21 | 'method' => 'onPostSerialize',
22 | ],
23 | ];
24 | }
25 |
26 | public function onPostSerialize(ObjectEvent $event): void
27 | {
28 | if (!$event->getObject() instanceof AbstractDomainObject) {
29 | return;
30 | }
31 |
32 | $entity = $event->getObject();
33 | /** @var SerializationVisitorInterface $visitor */
34 | $visitor = $event->getVisitor();
35 |
36 | $type = get_class($entity);
37 | $visitor->visitProperty(
38 | new StaticPropertyMetadata(AbstractDomainObject::class, '@type', $type),
39 | $type
40 | );
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Classes/Service/CorsService.php:
--------------------------------------------------------------------------------
1 | isWildcard($options->allowOrigin)) {
21 | return true;
22 | }
23 |
24 | if ($options->originRegex) {
25 | foreach ($options->allowOrigin as $originRegexp) {
26 | if (preg_match('{' . $originRegexp . '}i', $origin)) {
27 | return true;
28 | }
29 | }
30 | } elseif (in_array($origin, $options->allowOrigin, true)) {
31 | return true;
32 | }
33 |
34 | return false;
35 | }
36 |
37 | public function isWildcard($option): bool
38 | {
39 | return $option === true
40 | || (is_array($option) && in_array('*', $option, true))
41 | || (is_string($option) && $option === '*');
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Classes/Service/ExpressionLanguageService.php:
--------------------------------------------------------------------------------
1 | getExpressionLanguage();
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Classes/Service/FileReferenceService.php:
--------------------------------------------------------------------------------
1 | getPublicUrl() === null || $originalResource->getPublicUrl() === '') {
15 | trigger_error(
16 | sprintf(
17 | 'Could not get public URL for file UID:%d. It is probably missing in filesystem.',
18 | $originalResource->getProperty('uid')
19 | ),
20 | E_USER_WARNING
21 | );
22 | return null;
23 | }
24 |
25 | return UrlService::forceAbsoluteUrl(
26 | $originalResource->getPublicUrl(),
27 | $context->getAttribute('TYPO3_SITE_URL')
28 | );
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Classes/Service/FilesystemService.php:
--------------------------------------------------------------------------------
1 | clearAllActive($directory);
27 | }
28 |
29 | if ($keepOriginalDirectory) {
30 | GeneralUtility::mkdir($directory);
31 | }
32 |
33 | clearstatcache();
34 | $result = GeneralUtility::rmdir($temporaryDirectory, true);
35 | }
36 | }
37 |
38 | return $result;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Classes/Service/PropertyInfoService.php:
--------------------------------------------------------------------------------
1 | getProperty($propertyName);
21 | $annotations = $annotationReader->getPropertyAnnotations($propertyReflection);
22 | $cascadeAnnotations = array_filter(
23 | $annotations,
24 | static function ($annotation): bool {
25 | return $annotation instanceof Cascade;
26 | }
27 | );
28 |
29 | /** @var Cascade $cascadeAnnotation */
30 | foreach ($cascadeAnnotations as $cascadeAnnotation) {
31 | if (in_array('persist', $cascadeAnnotation->values, true)) {
32 | return true;
33 | }
34 | }
35 | } catch (\Exception $exception) {
36 | throw new \RuntimeException(
37 | 'It was not possible to check if property allows cascade persistence due to exception',
38 | 1584949881062,
39 | $exception
40 | );
41 | }
42 |
43 | return false;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/Classes/Service/ReflectionService.php:
--------------------------------------------------------------------------------
1 | getDescendantPageIdsRecursive(
30 | $startPid,
31 | $recursionDepth,
32 | );
33 |
34 | if ($pids !== '') {
35 | $recursiveStoragePids[] = $pids;
36 | }
37 | }
38 |
39 | return array_unique(array_merge($storagePids, ...$recursiveStoragePids));
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Classes/Service/UrlService.php:
--------------------------------------------------------------------------------
1 | validatorResolver->getBaseValidatorConjunction(get_class($obj));
22 | $validationResults = $validator->validate($obj);
23 |
24 | if ($validationResults->hasErrors()) {
25 | throw new ValidationException($validationResults, 1581461085077);
26 | }
27 |
28 | return $validationResults;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Classes/Utility/FileUtility.php:
--------------------------------------------------------------------------------
1 | getPathName();
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/Classes/Utility/ParameterUtility.php:
--------------------------------------------------------------------------------
1 | [
5 | 'parent' => 'tools',
6 | 'position' => ['before' => '*'],
7 | 'access' => 'group,user',
8 | 'iconIdentifier' => 'ext-t3api',
9 | 'labels' => 'LLL:EXT:t3api/Resources/Private/Language/locallang_modadministration.xlf:mlang_tabs_tab',
10 | 'inheritNavigationComponentFromMainModule' => false,
11 | 'path' => '/module/t3api',
12 | 'routes' => [
13 | '_default' => [
14 | 'target' => \SourceBroker\T3api\Controller\AdministrationController::class . '::documentationAction',
15 | ],
16 | 'open_api_resources' => [
17 | 'target' => \SourceBroker\T3api\Controller\OpenApiController::class . '::resourcesAction',
18 | ],
19 | ],
20 | ],
21 | ];
22 |
--------------------------------------------------------------------------------
/Configuration/ExpressionLanguage.php:
--------------------------------------------------------------------------------
1 | [
7 | \SourceBroker\T3api\ExpressionLanguage\ConditionProvider::class,
8 | \SourceBroker\T3api\ExpressionLanguage\T3apiCoreProvider::class,
9 | ],
10 | ];
11 |
--------------------------------------------------------------------------------
/Configuration/Icons.php:
--------------------------------------------------------------------------------
1 | [
5 | 'provider' => \TYPO3\CMS\Core\Imaging\IconProvider\SvgIconProvider::class,
6 | 'source' => 'EXT:t3api/Resources/Public/Icons/Extension.svg',
7 | ],
8 | ];
9 |
--------------------------------------------------------------------------------
/Configuration/JavaScriptModules.php:
--------------------------------------------------------------------------------
1 | [
5 | 'core',
6 | 'backend',
7 | ],
8 | 'imports' => [
9 | '@sourcebroker/t3api/' => 'EXT:t3api/Resources/Public/ESM/',
10 | ],
11 | ];
12 |
--------------------------------------------------------------------------------
/Configuration/RequestMiddlewares.php:
--------------------------------------------------------------------------------
1 | [
7 | 'sourcebroker/t3api/prepare-api-request' => [
8 | 'target' => \SourceBroker\T3api\Middleware\T3apiRequestLanguageResolver::class,
9 | 'after' => [
10 | 'typo3/cms-frontend/site',
11 | ],
12 | 'before' => [
13 | 'typo3/cms-frontend/tsfe',
14 | ],
15 | ],
16 | 'sourcebroker/t3api/process-api-request' => [
17 | 'target' => \SourceBroker\T3api\Middleware\T3apiRequestResolver::class,
18 | 'after' => [
19 | 'typo3/cms-frontend/prepare-tsfe-rendering',
20 | ],
21 | 'before' => [
22 | 'typo3/cms-frontend/shortcut-and-mountpoint-redirect',
23 | ],
24 | ],
25 | ],
26 | ];
27 |
--------------------------------------------------------------------------------
/Configuration/Routing/config.yaml:
--------------------------------------------------------------------------------
1 | routeEnhancers:
2 | T3api:
3 | type: T3apiResourceEnhancer
4 |
--------------------------------------------------------------------------------
/Documentation/Customization/ApiResourcePath/Index.rst:
--------------------------------------------------------------------------------
1 | .. _customization_api-resource-path:
2 |
3 | Api Resource Path
4 | =================
5 |
6 | By default t3api will search for API Resource classes in
7 | :folder:`Classes/Domain/Model/*.php` of currently loaded extensions. This behaviour
8 | is defined in :class:`LoadedExtensionsDomainModelApiResourcePathProvider`
9 | and registered in :file:`ext_localconf.php` like this:
10 |
11 | .. code-block:: php
12 |
13 | $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['t3api']['apiResourcePathProviders'] = [
14 | \SourceBroker\T3api\Provider\ApiResourcePath\LoadedExtensionsDomainModelApiResourcePathProvider::class,
15 | ];
16 |
17 | The same way you can add your own providers for additional patches.
18 |
--------------------------------------------------------------------------------
/Documentation/Customization/CollectionResponseSchema/Index.rst:
--------------------------------------------------------------------------------
1 | .. _customization_collection-response-schema:
2 |
3 | Collection response schema
4 | ===========================
5 |
6 | @todo - write docs
7 |
--------------------------------------------------------------------------------
/Documentation/Customization/ExpressionLanguage/Index.rst:
--------------------------------------------------------------------------------
1 | .. _customization_expression-language:
2 |
3 | Expression Language
4 | =====================
5 |
6 | `Symfony expression language `__ is `widely used in TYPO3 core `__. T3api also uses it in two places:
7 |
8 | - :ref:`Serialization (and deserialization) `
9 | - :ref:`Security (checking access to operations and filters) `
10 |
11 | T3api utilizes TYPO3 core expression language feature. So, if you would like to extend expression language used in t3api core, you need to register new provider in `the same way as you do it e.g. for TS `__. Just notice that appropriate context has to be specified as array key (in this case it's ``t3api``). Code below shows how to register ``MyCustomProvider`` in ``t3api`` context.
12 |
13 | .. code-block:: php
14 | :caption: typo3conf/ext/my_ext/Configuration/ExpressionLanguage.php
15 |
16 | return [
17 | 't3api' => [
18 | \Vendor\MyExt\ExpressionLanguage\MyCustomProvider::class,
19 | ],
20 | ];
21 |
22 | After registration of custom provider follow TYPO3 documentation how to `add additional variables `__ and `additional functions `__ into expression language. Now you can use your custom variables and functions inside t3api security check or serialization and deserialization expressions.
23 |
--------------------------------------------------------------------------------
/Documentation/Customization/Index.rst:
--------------------------------------------------------------------------------
1 | .. _customization:
2 |
3 | ================
4 | Customization
5 | ================
6 |
7 | .. toctree::
8 | :maxdepth: 3
9 | :hidden:
10 |
11 | ApiResourcePath/Index
12 | ExpressionLanguage/Index
13 | CollectionResponseSchema/Index
14 |
--------------------------------------------------------------------------------
/Documentation/Filtering/BuiltinFilters/BooleanFilter/Index.rst:
--------------------------------------------------------------------------------
1 | .. _filtering_filters_boolean-filter:
2 |
3 | BooleanFilter
4 | ==============
5 |
6 | Should be used to filter items by boolean fields.
7 |
8 | Syntax: ``?property=``
9 |
10 | .. code-block:: php
11 |
12 | use SourceBroker\T3api\Annotation as T3api;
13 | use SourceBroker\T3api\Filter\SearchFilter;
14 |
15 | /**
16 | * @T3api\ApiResource (
17 | * collectionOperations={
18 | * "get"={
19 | * "method"="GET",
20 | * "path"="/news/news",
21 | * },
22 | * },
23 | * )
24 | *
25 | * @T3api\ApiFilter(
26 | * BooleanFilter::class,
27 | * properties={"istopnews"}
28 | * )
29 | */
30 | class News extends \GeorgRinger\News\Domain\Model\News
31 | {
32 | }
33 |
34 | .. admonition:: Real examples. Run "ddev restart && ddev ci 13" and try those links below.
35 |
36 | * | Get list of news which are "Top News":
37 | | https://13.t3api.ddev.site/_api/news/news?istopnews=true
38 | |
39 | * | Get list of news which are "Top News" and sort by title
40 | | `https://13.t3api.ddev.site/_api/news/news?istopnews=true&order[title]=asc `__
41 |
--------------------------------------------------------------------------------
/Documentation/Filtering/BuiltinFilters/ContainFilter/Index.rst:
--------------------------------------------------------------------------------
1 | .. _filtering_filters_contain-filter:
2 |
3 | ContainFilter
4 | ==============
5 |
6 | @todo - write docs
7 |
--------------------------------------------------------------------------------
/Documentation/Filtering/BuiltinFilters/DistanceFilter/Index.rst:
--------------------------------------------------------------------------------
1 | .. _filtering_filters_distance-filter:
2 |
3 | DistanceFilter
4 | ===============
5 |
6 | Distance filter allows to filter map points points by radius. Map points kept in the database needs to contain latitude and longitude to use this filter.
7 |
8 | Configuration for distance filter looks a little bit different than for other build-in filter. Because distance filter is not based on single field it should not contain ``properties`` definition. Instead of that it is needed to specify which model properties contain latitude and longitude in ``arguments``. Moreover, as ``properties`` is not defined, ``parameterName`` is required. Beside default values in ``arguments``, distance filter accepts also:
9 |
10 | - ``latProperty`` (``string``) - Name of the property which holds latitude
11 | - ``lngProperty`` (``string``) - name of the property which holds longitude
12 | - ``unit`` (ENUM: "mi", "km"; default "km") - Unit of the radius
13 | - ``radius`` (``float/int``; default ``100``) - Radius to filter in; if ``allowClientRadius`` is set to ``true``, then used as default value.
14 | - ``allowClientRadius`` (``bool``; default ``false``) - Set to ``true`` allow to change the radius from GET parameter.
15 |
16 | .. code-block:: php
17 |
18 | use SourceBroker\T3api\Filter\DistanceFilter;
19 |
20 | /**
21 | * @T3api\ApiFilter(
22 | * DistanceFilter::class,
23 | * arguments={
24 | * "parameterName"="position",
25 | * "latProperty"="gpsLatitude",
26 | * "lngProperty"="gpsLongitude",
27 | * "radius"="100",
28 | * "unit"="km",
29 | * }
30 | * )
31 | */
32 | class Item extends \TYPO3\CMS\Extbase\DomainObject\AbstractEntity
33 | {
34 | }
35 |
36 |
--------------------------------------------------------------------------------
/Documentation/Filtering/BuiltinFilters/Index.rst:
--------------------------------------------------------------------------------
1 | .. _filtering_builtin-filters:
2 |
3 | ================
4 | Built-in filters
5 | ================
6 |
7 | There are several build in filters:
8 |
9 | .. toctree::
10 | :maxdepth: 3
11 |
12 | BooleanFilter/Index
13 | ContainFilter/Index
14 | DistanceFilter/Index
15 | NumericFilter/Index
16 | OrderFilter/Index
17 | RangeFilter/Index
18 | SearchFilter/Index
19 | UidFilter/Index
20 |
21 |
--------------------------------------------------------------------------------
/Documentation/Filtering/BuiltinFilters/NumericFilter/Index.rst:
--------------------------------------------------------------------------------
1 | .. _filtering_filters_numeric-filter:
2 |
3 | NumericFilter
4 | ==============
5 |
6 | Should be used to filter items by numeric fields.
7 |
8 | Syntax: ``?property=`` or ``?property[]=&property[]=``.
9 |
10 | .. code-block:: php
11 |
12 | use SourceBroker\T3api\Annotation as T3api;
13 | use SourceBroker\T3api\Filter\NumericFilter;
14 |
15 | /**
16 | * @T3api\ApiResource (
17 | * collectionOperations={
18 | * "get"={
19 | * "path"="/users",
20 | * },
21 | * },
22 | * )
23 | * @T3api\ApiFilter(
24 | * NumericFilter::class,
25 | * properties={"address.number", "height"},
26 | * )
27 | */
28 | class User extends \TYPO3\CMS\Extbase\Domain\Model\FrontendUser
29 | {
30 | }
31 |
--------------------------------------------------------------------------------
/Documentation/Filtering/BuiltinFilters/OrderFilter/Index.rst:
--------------------------------------------------------------------------------
1 | .. _filtering_filters_order-filter:
2 |
3 | OrderFilter
4 | ============
5 |
6 | Allows to change default ordering of collection responses.
7 |
8 | Syntax: ``?order[property]=``
9 |
10 | It is possible to order by single field (query string :uri:`?order[title]=asc`) or by multiple of them (query string :uri:`?order[title]=asc&order[datetime]=desc`).
11 |
12 | It may happen that conflict of names will occur if ``order`` is also the name of property with enabled another filter. Solution in such cases would be a change of parameter name used by ``OrderFilter``. It can be done using argument ``orderParameterName``, as on example below:
13 |
14 | .. code-block:: php
15 |
16 | use SourceBroker\T3api\Annotation as T3api;
17 | use SourceBroker\T3api\Filter\OrderFilter;
18 |
19 | /**
20 | * @T3api\ApiResource (
21 | * collectionOperations={
22 | * "get"={
23 | * "path"="/news/news",
24 | * },
25 | * },
26 | * )
27 | *
28 | * @T3api\ApiFilter(
29 | * OrderFilter::class,
30 | * properties={"title","datetime"}
31 | * arguments={"orderParameterName": "myOrderParameterName"},
32 | * )
33 | */
34 | class News extends \GeorgRinger\News\Domain\Model\News
35 | {
36 | }
37 |
38 |
39 | .. admonition:: Real examples. Run "ddev restart && ddev ci 13" and try those links below.
40 |
41 | * | Get list of news sorted by titles ascending:
42 | | `https://13.t3api.ddev.site/_api/news/news?order[title]=asc `__
43 | |
44 | * | Get list of news sorted by date descending and then by title ascending:
45 | | `https://13.t3api.ddev.site/_api/news/news?order[datetime]=desc&order[title]=asc `__
46 |
--------------------------------------------------------------------------------
/Documentation/Filtering/SqlInOperator/Index.rst:
--------------------------------------------------------------------------------
1 | .. _filtering_sql-in-operator:
2 |
3 | SQL "IN" operator
4 | ==================
5 |
6 | When using query params ``?property=`` only items which match exactly such condition are returned. But it is possible to pass multiple values. If you would like to receive all items which ``property`` matches ``value1`` **or** ``value2`` then you can send ``property`` as an array in query string: ``?property[]=&property[]=``. From build-in filters ``NumericFilter`` and ``SearchFilter`` are the filters which support ``IN`` operator.
7 |
--------------------------------------------------------------------------------
/Documentation/Filtering/SqlOrOperator/Index.rst:
--------------------------------------------------------------------------------
1 | .. _filtering_sql-or-operator:
2 |
3 | SQL "OR" conjunction
4 | =====================
5 |
6 | By default ``AND`` conjunction is used between all applied filters but there is a ways to change conjunction to ``OR``. Frontend applications often needs single input field which searches multiple fields. To create such filter it is needed to set same ``parameterName`` for multiple fields. Example code below means that request to URL ``/users?search=john`` will return records where any of the fields (``firstName``, ``middleName``, ``lastName`` or ``address.street``) matches searched text. If we would not determine ``parameterName`` in filter arguments this configuration would work as separate filters for every property.
7 |
8 | .. code-block:: php
9 |
10 | use SourceBroker\T3api\Annotation as T3api;
11 | use SourceBroker\T3api\Filter\SearchFilter;
12 |
13 | /**
14 | * @T3api\ApiResource (
15 | * collectionOperations={
16 | * "get"={
17 | * "path"="/users",
18 | * },
19 | * },
20 | * )
21 | *
22 | * @T3api\ApiFilter(
23 | * SearchFilter::class,
24 | * properties={
25 | * "firstName": "partial",
26 | * "middleName": "partial",
27 | * "lastName": "partial",
28 | * "address.street": "partial",
29 | * },
30 | * arguments={
31 | * "parameterName": "search",
32 | * }
33 | * )
34 | */
35 | class User extends \TYPO3\CMS\Extbase\Domain\Model\FrontendUser
36 | {
37 | }
38 |
--------------------------------------------------------------------------------
/Documentation/Index.rst:
--------------------------------------------------------------------------------
1 | .. _start:
2 |
3 | =============================================================
4 | TYPO3 REST API
5 | =============================================================
6 |
7 | :Version:
8 | |release|
9 |
10 | :Language:
11 | en
12 |
13 | :Authors:
14 | Inscript Team
15 |
16 | :Email:
17 | office@inscript.dev
18 |
19 | :Website:
20 | https://www.inscript.team/
21 |
22 | :License:
23 | This extension documentation is published under the
24 | `CC BY-NC-SA 4.0 `__ (Creative Commons)
25 | license
26 |
27 | :Rendered:
28 | |today|
29 |
30 |
31 | The content of this document is related to TYPO3 CMS, a GNU/GPL CMS/Framework available from `typo3.org
32 | `_ .
33 |
34 | This documentation is for the TYPO3 extension `t3api `_.
35 |
36 | If you find an error or something is missing, please: `Report a Problem
37 | `__.
38 |
39 |
40 | **Table of Contents**
41 |
42 | .. toctree::
43 | :maxdepth: 5
44 | :titlesonly:
45 | :glob:
46 |
47 | GettingStarted/Index
48 | Operations/Index
49 | Filtering/Index
50 | Pagination/Index
51 | Security/Index
52 | Serialization/Index
53 | Integration/Index
54 | Customization/Index
55 | Events/Index
56 | Multilanguage/Index
57 | HandlingFileUpload/Index
58 | HandlingCascadePersistence/Index
59 | UseCases/Index
60 | Cors/Index
61 | Miscellaneous/Index
62 | Sitemap
63 |
--------------------------------------------------------------------------------
/Documentation/Integration/Index.rst:
--------------------------------------------------------------------------------
1 | .. _integration:
2 |
3 | ============
4 | Integration
5 | ============
6 |
7 | @todo - write docs
8 |
9 | Integration with other extensions
10 | ====================================
11 |
12 | @todo - write docs
13 | @todo - write docs: configure serializer for classes which can not be override :ref:`serialization_yaml_metadata`
14 |
15 | News extension - Example integration
16 | ======================================
17 |
18 | @todo - write docs t3apinews
19 |
20 | Inline output
21 | ======================================
22 |
23 | @todo - write docs: If you would like to include your JSON directly in TYPO3 HTML output (e.g. to omit waiting for initial request
24 | to API) you can use xxxViewHelper as follows:
25 |
26 | .. code-block:: html
27 |
28 |
32 |
33 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/Documentation/Miscellaneous/CommonIssues/Index.rst:
--------------------------------------------------------------------------------
1 | .. _common_issues:
2 |
3 | =====================================
4 | Common Issues
5 | =====================================
6 |
7 | "&cHash empty" issue
8 | ==========================================================
9 |
10 | Request parameters could not be validated (&cHash empty)
11 |
12 | If you are receiving such TYPO3's error when trying to access endpoints potential reasons could be:
13 |
14 | - t3api version lower than 2.1.
15 | - TYPO3 global configuration or one of the installed extensions is resetting ``cacheHash.excludedParameters`` value. Check if ``t3apiResource`` is inside collection of excluded parameters defined in ``$GLOBALS['TYPO3_CONF_VARS']['FE']['cacheHash']['excludedParameters']``. Such value is added by t3api core so you should use merge ``excludedParameters`` instead of override.
16 |
--------------------------------------------------------------------------------
/Documentation/Miscellaneous/Development/Index.rst:
--------------------------------------------------------------------------------
1 | .. _development:
2 |
3 | ============
4 | Development
5 | ===========
6 |
7 | .. toctree::
8 | :maxdepth: 3
9 |
10 | Introduction/Index
11 | CommandsList/Index
12 | TypicalUseCases/Index
13 |
--------------------------------------------------------------------------------
/Documentation/Miscellaneous/Development/Introduction/Index.rst:
--------------------------------------------------------------------------------
1 | .. _development_introduction:
2 |
3 | =============
4 | Introduction
5 | =============
6 |
7 | During development of t3api, you will be dealing with two different TYPO3 installations,
8 | which serve different purposes.
9 |
10 | Unit and functional testing installation
11 | ++++++++++++++++++++++++++++++++++++++++
12 |
13 | * It is a minimal TYPO3 installation, which is used for running unit and functional tests.
14 | * Files are under directory :directory:`.Build`
15 | * You can install it manually with :bash:`ddev composer i`
16 | * It is installed automatically while using command :ref:`_development_commands_list_ddev_ci`
17 | * There can be only one TYPO3 version installed at one time.
18 |
19 | Integration and manual testing installation
20 | +++++++++++++++++++++++++++++++++++++++++++
21 |
22 | * This installation is a full TYPO3 accessible under :uri:`https://[TYPO3_VERSION].t3api.ddev.site`
23 | and it is used for testing REST API endpoints using Postman tests. It is also used for manual testing.
24 | * Files are under directory :directory:`/.test/[TYPO3_VERSION]`
25 | * You can install it manually using command :ref:`_development_commands_list_ddev_install`
26 | * It is installed automatically while using command :ref:`_development_commands_list_ddev_ci`
27 | * There can be multiple TYPO3 versions integrations installations at one time each under different url.
28 |
--------------------------------------------------------------------------------
/Documentation/Miscellaneous/Development/TypicalUseCases/Index.rst:
--------------------------------------------------------------------------------
1 | .. _development_typical_use_cases:
2 |
3 | =================
4 | Typical Use Cases
5 | =================
6 |
7 |
8 | .. toctree::
9 | :maxdepth: 3
10 |
11 | BugsFixing/Index
12 |
--------------------------------------------------------------------------------
/Documentation/Miscellaneous/Index.rst:
--------------------------------------------------------------------------------
1 | .. _miscellaneous:
2 |
3 | ===============
4 | Miscellaneous
5 | ===============
6 |
7 | .. toctree::
8 | :maxdepth: 3
9 |
10 | Changelog/Index
11 | CommonIssues/Index
12 | Development/Index
13 |
--------------------------------------------------------------------------------
/Documentation/Pagination/ClientSide/Index.rst:
--------------------------------------------------------------------------------
1 | .. _pagination_client-side:
2 |
3 | ===============
4 | Client side
5 | ===============
6 |
7 | @todo - write docs
8 |
--------------------------------------------------------------------------------
/Documentation/Pagination/Index.rst:
--------------------------------------------------------------------------------
1 | .. _pagination:
2 |
3 | ===========
4 | Pagination
5 | ===========
6 |
7 | @todo - write docs
8 |
9 | Global configuration
10 | ======================
11 |
12 | @todo - write docs
13 |
14 | Resource specific configuration
15 | ================================
16 |
17 | @todo - write docs
18 |
19 | .. toctree::
20 | :maxdepth: 3
21 | :hidden:
22 |
23 | ServerSide/Index
24 | ClientSide/Index
25 |
--------------------------------------------------------------------------------
/Documentation/Pagination/ServerSide/Index.rst:
--------------------------------------------------------------------------------
1 | .. _pagination_server-side:
2 |
3 | ===============
4 | Server side
5 | ===============
6 |
7 | @todo - write docs
8 |
--------------------------------------------------------------------------------
/Documentation/Security/Index.rst:
--------------------------------------------------------------------------------
1 | .. _security:
2 |
3 | =========
4 | Security
5 | =========
6 |
7 | - @todo - write docs: general usage of expression language inside of "security" annotation
8 | - @todo - write docs: general usage of expression language inside of "security_post_denormalize" annotation
9 | - @todo - write docs: example usage of frontend user
10 | - @todo - write docs: example usage of frontend user group
11 | - @todo - write docs: example usage of backend user
12 | - @todo - write docs: example usage of backend user group
13 | - @todo - write docs: example usage of `object` in items operations (+ information that `object` doesn't exist in collection operation)
14 | - @todo - write docs: information that most of the conditions from typoscript can be used (https://docs.typo3 .org/m/typo3/reference-typoscript/master/en-us/Conditions/Index.html#description; `tree` is an example object which can not be used)
15 |
--------------------------------------------------------------------------------
/Documentation/Serialization/ContextGroups/Index.rst:
--------------------------------------------------------------------------------
1 | .. _serialization_context-groups:
2 |
3 | ===============
4 | Context groups
5 | ===============
6 |
7 | @todo - write docs
8 |
9 | Customization
10 | ===============
11 |
12 | @todo - write docs: describe how to customize serialization context using ``\SourceBroker\T3api\Serializer\ContextBuilder\ContextBuilderInterface::SIGNAL_CUSTOMIZE_SERIALIZER_CONTEXT_ATTRIBUTES`` (e.g. how to conditionally include fields if current user belongs to specified group)
13 |
--------------------------------------------------------------------------------
/Documentation/Serialization/Customization/Index.rst:
--------------------------------------------------------------------------------
1 | .. _serialization_expression-language:
2 |
3 | ====================
4 | Expression language
5 | ====================
6 |
7 | In some places of serialization and deserialization process Symfony expression language is used.
8 |
9 | @todo add detailed info what variables and functions are supported by default (function `force_absolute_url`, variables in `context`, `object` in AccessorStrategy execution)
10 |
11 | :ref:`Here you can find out how to customize and extend expression language for serialization `
12 |
--------------------------------------------------------------------------------
/Documentation/Serialization/Exceptions/Index.rst:
--------------------------------------------------------------------------------
1 | .. _serialization_exceptions:
2 |
3 | ==========
4 | Exceptions
5 | ==========
6 |
7 | TYPO3 may encounter issues with FileReference or File objects, such as when a file
8 | is missing, inaccessible, or if the relation is broken. These issues can interrupt
9 | the serialization process and a jsonified error will be returned.
10 | To address this, we've introduced a configuration option that allows for the
11 | graceful handling of specific exceptions during the serialization process.
12 | This configuration is defined on a per-class basis, meaning that different
13 | classes can have different sets of exceptions that are handled gracefully.
14 |
15 | Once configured, these exceptions will not interrupt the serialization process
16 | for the respective class. Instead, they will be handled appropriately,
17 | allowing the serialization process to continue uninterrupted.
18 | This ensures that a problem with a single object does not prevent the successful
19 | serialization of other objects.
20 |
21 |
22 | Setting responsible for that is:
23 |
24 | .. code-block:: php
25 | $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['t3api']['serializer']['exclusionForExceptionsInAccessorStrategyGetValue']
26 |
27 |
28 | Example value:
29 |
30 | .. code-block:: php
31 | $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['t3api']['serializer']['exclusionForExceptionsInAccessorStrategyGetValue'] = [
32 | \SourceBroker\T3apinews\Domain\Model\FileReference::class => [
33 | \TYPO3\CMS\Core\Resource\Exception\FileDoesNotExistException::class,
34 | ],
35 | ];
36 |
37 | Asterix as "all exceptions" is also supported:
38 | Example:
39 |
40 | .. code-block:: php
41 | $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['t3api']['serializer']['exclusionForExceptionsInAccessorStrategyGetValue'] = [
42 | \SourceBroker\T3apinews\Domain\Model\FileReference::class => ['*'],
43 | ];
44 |
--------------------------------------------------------------------------------
/Documentation/Serialization/Handlers/Index.rst:
--------------------------------------------------------------------------------
1 | .. _serialization_handlers:
2 |
3 | =========
4 | Handlers
5 | =========
6 |
7 | @todo - write docs
8 |
9 | FileReference
10 | ===============
11 |
12 | @todo - write docs
13 |
14 | Image
15 | ===============
16 |
17 | @todo - write docs
18 |
19 | RecordUri
20 | ===========
21 |
22 | @todo - write docs
23 |
24 | Typolink
25 | =========
26 |
27 | @todo - write docs
28 |
29 | Custom handlers
30 | ================
31 |
32 | @todo - write docs
33 |
--------------------------------------------------------------------------------
/Documentation/Serialization/Index.rst:
--------------------------------------------------------------------------------
1 | .. _serialization:
2 |
3 | Serialization
4 | ==============
5 |
6 | @todo - write docs
7 |
8 | .. toctree::
9 | :maxdepth: 3
10 | :hidden:
11 |
12 | ContextGroups/Index
13 | Handlers/Index
14 | Subscribers/Index
15 | YamlMetadata/Index
16 | Customization/Index
17 | Exceptions/Index
18 |
--------------------------------------------------------------------------------
/Documentation/Serialization/YamlMetadata/Index.rst:
--------------------------------------------------------------------------------
1 | .. _serialization_yaml-metadata:
2 |
3 | ============
4 | Yaml metadata
5 | ============
6 |
7 | @todo - write docs: how to use serializerMetadataDirs
8 |
--------------------------------------------------------------------------------
/Documentation/Sitemap.rst:
--------------------------------------------------------------------------------
1 | :template: sitemap.html
2 |
3 | .. _sitemap:
4 |
5 | =======
6 | Sitemap
7 | =======
8 |
--------------------------------------------------------------------------------
/Documentation/UseCases/Index.rst:
--------------------------------------------------------------------------------
1 | .. _use-cases:
2 |
3 | ================
4 | Use cases
5 | ================
6 |
7 | .. toctree::
8 | :maxdepth: 3
9 |
10 | CurrentUserEndpoint/Index
11 | CurrentUserAssignment/Index
12 |
--------------------------------------------------------------------------------
/Documentation/guides.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
14 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/Resources/Private/.htaccess:
--------------------------------------------------------------------------------
1 | # Apache < 2.3
2 |
3 | Order allow,deny
4 | Deny from all
5 | Satisfy All
6 |
7 |
8 | # Apache >= 2.3
9 |
10 | Require all denied
11 |
12 |
--------------------------------------------------------------------------------
/Resources/Private/Language/locallang.xlf:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Resource not found
8 |
9 |
10 | Could not find resource type `%s` with uid %s
11 |
12 |
13 | Route not found
14 |
15 |
16 | Could not find route
17 |
18 |
19 | Validation error
20 |
21 |
22 | An error occurred during object validation
23 |
24 |
25 | Method not allowed
26 |
27 |
28 | Method `%s` is not allowed for %s
29 |
30 |
31 | Operation not allowed
32 |
33 |
34 | You are not allowed to access operation `%s`
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/Resources/Private/Language/locallang_modadministration.xlf:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Web API
8 |
9 |
10 | Check your REST API documentation
11 |
12 |
13 | This module provides an easy way to check available endpoints of your REST API and test it using SwaggerUI
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/Resources/Private/Serializer/Metadata/SourceBroker.T3api.Response.AbstractCollectionResponse.yml:
--------------------------------------------------------------------------------
1 | SourceBroker\T3api\Response\AbstractCollectionResponse:
2 | properties:
3 | operation:
4 | exclude: true
5 | query:
6 | exclude: true
7 | request:
8 | exclude: true
9 | membersCache:
10 | exclude: true
11 | totalItemsCache:
12 | exclude: true
13 |
--------------------------------------------------------------------------------
/Resources/Private/Serializer/Metadata/SourceBroker.T3api.Response.HydraCollectionResponse.yml:
--------------------------------------------------------------------------------
1 | SourceBroker\T3api\Response\HydraCollectionResponse:
2 | virtual_properties:
3 | getMembers:
4 | name: members
5 | serialized_name: hydra:member
6 | type: array
7 | groups: ['__hydra_collection_response']
8 | getTotalItems:
9 | name: totalItems
10 | serialized_name: hydra:totalItems
11 | type: int
12 | groups: ['__hydra_collection_response']
13 | getView:
14 | name: view
15 | serialized_name: hydra:view
16 | type: array
17 | groups: ['__hydra_collection_response']
18 | getSearch:
19 | name: search
20 | serialized_name: hydra:search
21 | type: array
22 | groups: ['__hydra_collection_response']
23 |
--------------------------------------------------------------------------------
/Resources/Private/Serializer/Metadata/SourceBroker.T3api.Response.MainEndpointResponse.yml:
--------------------------------------------------------------------------------
1 | SourceBroker\T3api\Response\MainEndpointResponse:
2 | properties:
3 | apiResourceRepository:
4 | exclude: true
5 | virtual_properties:
6 | getResources:
7 | name: resources
8 | serialized_name: resources
9 | type: array
10 |
--------------------------------------------------------------------------------
/Resources/Private/Serializer/Metadata/TYPO3.CMS.Core.Resource.AbstractFile.yml:
--------------------------------------------------------------------------------
1 | TYPO3\CMS\Core\Resource\AbstractFile:
2 | properties:
3 | storage:
4 | type: TYPO3\CMS\Core\Resource\ResourceStorage
5 | exclude: true
6 | properties:
7 | type: array
8 | identifier:
9 | type: string
10 | name:
11 | type: string
12 | deleted:
13 | type: bool
14 | virtual_properties:
15 | getPublicUrl:
16 | type: string
17 | absolutePublicUrl:
18 | type: string
19 | exp: force_absolute_url(object.getPublicUrl(), context.getAttribute('TYPO3_SITE_URL'))
20 |
--------------------------------------------------------------------------------
/Resources/Private/Serializer/Metadata/TYPO3.CMS.Core.Resource.File.yml:
--------------------------------------------------------------------------------
1 | TYPO3\CMS\Core\Resource\File:
2 | properties:
3 | metaDataLoaded:
4 | exclude: true
5 | metaDataProperties:
6 | exclude: true
7 | metaDataAspect:
8 | exclude: true
9 | indexingInProgress:
10 | exclude: true
11 | virtual_properties:
12 | uid:
13 | exp: object.getProperties()['uid']
14 | type: integer
15 |
--------------------------------------------------------------------------------
/Resources/Private/Serializer/Metadata/TYPO3.CMS.Core.Resource.FileReference.yml:
--------------------------------------------------------------------------------
1 | TYPO3\CMS\Core\Resource\FileReference:
2 | properties:
3 | propertiesOfFileReference:
4 | exclude: true
5 | uidOfFileReference:
6 | exclude: true
7 | driver:
8 | exclude: true
9 | mergedProperties:
10 | exclude: true
11 |
--------------------------------------------------------------------------------
/Resources/Private/Serializer/Metadata/TYPO3.CMS.Core.Resource.Folder.yml:
--------------------------------------------------------------------------------
1 | TYPO3\CMS\Core\Resource\Folder:
2 | properties:
3 | fileAndFolderNameFilters:
4 | exclude: true
5 | storage:
6 | type: TYPO3\CMS\Core\Resource\ResourceStorage
7 | exclude: true
8 |
--------------------------------------------------------------------------------
/Resources/Private/Serializer/Metadata/TYPO3.CMS.Core.Resource.ResourceStorage.yml:
--------------------------------------------------------------------------------
1 | TYPO3\CMS\Core\Resource\ResourceStorage:
2 | properties:
3 | driver:
4 | exclude: true
5 | fileProcessingService:
6 | exclude: true
7 | userPermissions:
8 | exclude: true
9 | signalSlotDispatcher:
10 | exclude: true
11 | fileAndFolderNameFilters:
12 | exclude: true
13 | isOnline:
14 | exclude: true
15 | isDefault:
16 | exclude: true
17 | processingFolder:
18 | type: TYPO3\CMS\Core\Resource\Folder
19 | exclude: true
20 | processingFolders:
21 | type: array
22 | exclude: true
23 |
--------------------------------------------------------------------------------
/Resources/Private/Serializer/Metadata/TYPO3.CMS.Extbase.Domain.Model.AbstractFileFolder.yml:
--------------------------------------------------------------------------------
1 | TYPO3\CMS\Extbase\Domain\Model\AbstractFileFolder:
2 | properties:
3 | originalResource:
4 | type: \TYPO3\CMS\Core\Resource\ResourceInterface
5 | exclude: true
6 |
--------------------------------------------------------------------------------
/Resources/Private/Serializer/Metadata/TYPO3.CMS.Extbase.Domain.Model.FileReference.yml:
--------------------------------------------------------------------------------
1 | TYPO3\CMS\Extbase\Domain\Model\FileReference:
2 | properties:
3 | uidLocal:
4 | exclude: true
5 | configurationManager:
6 | exclude: true
7 |
--------------------------------------------------------------------------------
/Resources/Private/Serializer/Metadata/TYPO3.CMS.Extbase.DomainObject.AbstractDomainObject.yml:
--------------------------------------------------------------------------------
1 | TYPO3\CMS\Extbase\DomainObject\AbstractDomainObject:
2 | properties:
3 | uid:
4 | readOnly: true
5 | type: integer
6 | pid:
7 | type: integer
8 | _localizedUid:
9 | exclude: true
10 | _languageUid:
11 | exclude: true
12 | _versionedUid:
13 | exclude: true
14 | _isClone:
15 | exclude: true
16 | _cleanProperties:
17 | exclude: true
18 |
--------------------------------------------------------------------------------
/Resources/Private/Serializer/Metadata/TYPO3.CMS.Extbase.Persistence.Generic.LazyObjectStorage.yml:
--------------------------------------------------------------------------------
1 | TYPO3\CMS\Extbase\Persistence\Generic\LazyObjectStorage:
2 | properties:
3 | warning:
4 | exclude: true
5 | dataMapper:
6 | exclude: true
7 | parentObject:
8 | exclude: true
9 | propertyName:
10 | exclude: true
11 | fieldValue:
12 | exclude: true
13 | isInitialized:
14 | exclude: true
15 | objectManager:
16 | exclude: true
17 |
--------------------------------------------------------------------------------
/Resources/Private/Serializer/Metadata/TYPO3.CMS.Extbase.Persistence.ObjectStorage.yml:
--------------------------------------------------------------------------------
1 | TYPO3\CMS\Extbase\Persistence\ObjectStorage:
2 | properties:
3 | warning:
4 | exclude: true
5 | storage:
6 | exclude: true
7 | isModified:
8 | exclude: true
9 | addedObjectsPositions:
10 | exclude: true
11 | removedObjectsPositions:
12 | exclude: true
13 | positionCounter:
14 | exclude: true
15 |
--------------------------------------------------------------------------------
/Resources/Private/Serializer/Metadata/Throwable.yml:
--------------------------------------------------------------------------------
1 | Exception:
2 | properties:
3 | message:
4 | serialized_name: 'hydra:title'
5 | trace:
6 | exclude: true
7 | string:
8 | exclude: true
9 | code:
10 | serialized_name: 'hydra:code'
11 | file:
12 | exclude: true
13 | line:
14 | exclude: true
15 | previous:
16 | exclude: true
17 |
--------------------------------------------------------------------------------
/Resources/Private/Templates/Administration/Documentation.html:
--------------------------------------------------------------------------------
1 | {namespace core=TYPO3\CMS\Core\ViewHelpers}
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/Resources/Public/Css/swagger-custom.css:
--------------------------------------------------------------------------------
1 | /* Hides server selection as it is useless for now - we display always only one server */
2 | #t3api-swagger-ui .scheme-container,
3 | #t3api-swagger-ui .info .link
4 | {
5 | display: none;
6 | }
7 |
--------------------------------------------------------------------------------
/Resources/Public/ESM/swagger-init.js:
--------------------------------------------------------------------------------
1 | let checkExist = setInterval(function() {
2 | if (window.SwaggerUIBundle) {
3 | clearInterval(checkExist);
4 | const element = document.getElementById('t3api-swagger-ui');
5 | window.ui = SwaggerUIBundle({
6 | url: element.dataset.specUrl,
7 | dom_id: '#t3api-swagger-ui',
8 | deepLinking: true,
9 | presets: [
10 | SwaggerUIBundle.presets.apis,
11 | SwaggerUIStandalonePreset
12 | ],
13 | plugins: [
14 | SwaggerUIBundle.plugins.DownloadUrl
15 | ],
16 | });
17 | }
18 | }, 100);
19 |
--------------------------------------------------------------------------------
/Resources/Public/Icons/Extension.svg:
--------------------------------------------------------------------------------
1 |
15 |
--------------------------------------------------------------------------------
/Tests/Postman/fixtures/test1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sourcebroker/t3api/fdf013c5687c92c8072c86b25fa8246e14b10f76/Tests/Postman/fixtures/test1.jpg
--------------------------------------------------------------------------------
/Tests/Unit/Domain/Model/ApiFilterTest.php:
--------------------------------------------------------------------------------
1 | subject = new ApiFilter('filterClass', 'property', 'strategy', ['argument' => 'argumentValue']);
18 | }
19 |
20 | protected function tearDown(): void
21 | {
22 | parent::tearDown();
23 | }
24 |
25 | /**
26 | * @test
27 | */
28 | public function getFilterClassReturnsInitialValueForString()
29 | {
30 | self::assertSame(
31 | 'filterClass',
32 | $this->subject->getFilterClass()
33 | );
34 | }
35 |
36 | /**
37 | * @test
38 | */
39 | public function getStrategyReturnsInitialValueForApiFilterStrategy()
40 | {
41 | self::assertSame(
42 | 'strategy',
43 | $this->subject->getStrategy()->getName()
44 | );
45 | }
46 |
47 | /**
48 | * @test
49 | */
50 | public function getPropertyReturnsInitialValueForString()
51 | {
52 | self::assertSame(
53 | 'property',
54 | $this->subject->getProperty()
55 | );
56 | }
57 |
58 | /**
59 | * @test
60 | */
61 | public function getArgumentsReturnsInitialValueForArray()
62 | {
63 | self::assertSame(
64 | ['argument' => 'argumentValue'],
65 | $this->subject->getArguments()
66 | );
67 | }
68 |
69 | /**
70 | * @test
71 | */
72 | public function getArgumentReturnsInitialValueForAccessingSingleItem()
73 | {
74 | self::assertSame(
75 | 'argumentValue',
76 | $this->subject->getArgument('argument')
77 | );
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/Tests/Unit/Fixtures/Annotation/Serializer/Type/ExampleTypeWithNestedParams.php:
--------------------------------------------------------------------------------
1 | value = $options['value'];
22 | $this->config = $options['config'] ?? $this->config;
23 | }
24 |
25 | public function getParams(): array
26 | {
27 | return [
28 | $this->value,
29 | $this->config,
30 | ];
31 | }
32 |
33 | public function getName(): string
34 | {
35 | return 'ExampleTypeWithNestedParams';
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Tests/Unit/Fixtures/Domain/Model/AbstractEntry.php:
--------------------------------------------------------------------------------
1 |
22 | */
23 | protected ObjectStorage $groups;
24 |
25 | protected bool $hidden;
26 |
27 | /**
28 | * @var ObjectStorage
29 | */
30 | protected ObjectStorage $categories;
31 |
32 | public function getId(): int
33 | {
34 | return $this->id;
35 | }
36 |
37 | /**
38 | * @VirtualProperty
39 | * @return int[]
40 | */
41 | public function getTagIds(): array
42 | {
43 | return [122, 83, 110];
44 | }
45 |
46 | /**
47 | * @VirtualProperty("groupIds")
48 | * @return array
49 | */
50 | public function getIdsOfAssignedGroups(): array
51 | {
52 | return [10, 27, 35];
53 | }
54 |
55 | /**
56 | * @VirtualProperty
57 | * @return ObjectStorage
58 | */
59 | public function getTags(): ObjectStorage
60 | {
61 | return new ObjectStorage();
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/Tests/Unit/Fixtures/Domain/Model/Address.php:
--------------------------------------------------------------------------------
1 | bankAccountNumber;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/Tests/Unit/Fixtures/Domain/Model/Group.php:
--------------------------------------------------------------------------------
1 | firstName . ' ' . $this->lastName;
32 | }
33 |
34 | /**
35 | * @VirtualProperty("privateAddress")
36 | * @ExampleTypeWithNestedParams(
37 | * "PrivateAddress",
38 | * config={
39 | * "parameter1": "value1",
40 | * "parameter2": {
41 | * "value2a",
42 | * "value2b",
43 | * },
44 | * "parameter3": {
45 | * "parameter3a": "value3a",
46 | * "parameter3b": 3,
47 | * },
48 | * }
49 | * )
50 | */
51 | public function getPrivateAddress(): ?Address
52 | {
53 | return $this->address;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/Tests/Unit/Fixtures/Domain/Model/Tag.php:
--------------------------------------------------------------------------------
1 | 'T3api',
6 | 'description' => 'REST API for your TYPO3 project. Config with annotations, build in filtering, pagination, typolinks, image processing, serialization contexts, responses in Hydra/JSON-LD format.',
7 | 'category' => 'plugin',
8 | 'author' => 'Inscript Team',
9 | 'author_email' => 'office@inscript.dev',
10 | 'state' => 'stable',
11 | 'version' => '4.0.3',
12 | 'constraints' => [
13 | 'depends' => [
14 | 'php' => '8.1.0-8.3.99',
15 | 'typo3' => '12.4.00-13.4.99',
16 | ],
17 | 'conflicts' => [],
18 | 'suggests' => [],
19 | ],
20 | ];
21 |
--------------------------------------------------------------------------------
/phpstan-baseline.neon:
--------------------------------------------------------------------------------
1 | parameters:
2 | ignoreErrors:
3 | - '#Unsafe usage of new static\(\)#'
4 | - '#Access to an undefined property Metadata\\PropertyMetadata::\$type.#'
5 | typo3:
6 | requestGetAttributeMapping:
7 | t3apiHeaderLanguageRequest: bool
8 |
--------------------------------------------------------------------------------
/phpstan.neon:
--------------------------------------------------------------------------------
1 | includes:
2 | - phpstan-baseline.neon
3 |
4 | parameters:
5 | parallel:
6 | maximumNumberOfProcesses: 5
7 |
8 | level: 2
9 |
10 | paths:
11 | - Classes
12 | - Configuration
13 | - Tests
14 |
--------------------------------------------------------------------------------