diff --git a/.ddev/config.yaml b/.ddev/config.yaml index b0478b76..94579c56 100644 --- a/.ddev/config.yaml +++ b/.ddev/config.yaml @@ -1,7 +1,7 @@ name: contao-utils-bundle type: php docroot: "" -php_version: "7.4" +php_version: "8.1" webserver_type: nginx-fpm router_http_port: "80" router_https_port: "443" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3a5c7cdb..a16d100b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,13 +9,8 @@ jobs: strategy: fail-fast: false matrix: - php: [ 7.2, 7.3, 7.4, 8.0, 8.1 ] - contao: [ 4.9.*, 4.13.* ] - exclude: - - php: 7.2 - contao: 4.13.* - - php: 7.3 - contao: 4.13.* + php: [ 8.1, 8.2 ] + contao: [ 4.13.*, 5.2.* ] steps: - name: Setup PHP @@ -31,7 +26,7 @@ jobs: - name: Install the dependencies id: composer-install - run: composer require contao/core-bundle:${{ matrix.contao }} --no-interaction + run: composer require contao/core-bundle:${{ matrix.contao }} contao/test-case:${{ matrix.contao }} --no-interaction - name: Run the unit tests if: steps.composer-install.conclusion == 'success' && steps.composer-install.outcome == 'success' @@ -43,7 +38,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: 7.4 + php-version: 8.2 extensions: dom, fileinfo, filter, gd, hash, intl, json, mbstring, pcre, pdo, zlib coverage: xdebug tools: php-cs-fixer, phpunit @@ -52,7 +47,7 @@ jobs: uses: actions/checkout@v3 - name: Install the dependencies - run: composer require contao/core-bundle:4.13.* --no-interaction + run: composer require contao/core-bundle:4.13.* contao/test-case:4.13.* --no-interaction - name: Generate the coverage report run: php vendor/bin/phpunit -c phpunit.xml.dist --coverage-clover build/logs/clover.xml @@ -70,7 +65,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: 8.0 + php-version: 8.2 - name: Checkout uses: actions/checkout@v2 diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b37d8df..ffb4a1d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,2072 +2,8 @@ All notable changes to this project will be documented in this file. -## [2.229.2] - 2023-09-07 -- Fixed: undefined method exception in RequestCleaner - -## [2.229.1] - 2023-09-06 -- Fixed: PHP8 warning fix for `getImageData()` in `Twig\ImageExtension.php` -- Changed: method collection/reflection, cosmetic improvements in `Classes\ClassUtil.php` - -## [2.229.0] - 2023-08-28 -- Changed: removed request bundle dependency - -## [2.228.2] - 2023-08-17 -- Fixed: missing alt text in image twig tag - -## [2.228.1] - 2023-07-04 -- Fixed: compatibility issue with symfony 3 - -## [2.228.0] - 2023-05-22 -- Added: absolute url option to RequestUtil::generateBackendRoute() ([#65]) - -## [2.227.1] - 2023-04-27 -- Fixed: warnings -- Deprecated: StringUtil::startWith and StringUtil::endsWith - -## [2.227.0] - 2023-04-05 -- Changed: reduced usage of request bundle ([#64]) -- Deprecated: UrlUtil::removeQueryStringParameterToUrl() (renamed to removeQueryStringParameterFromUrl()) -- Fixed: typo in UrlUtil::removeQueryStringParameterToUrl() -- Fixed: various warnings -- Fixed: various deprecations - -## [2.226.1] - 2023-03-20 -- Fixed: warning in ImageUtil - -## [2.226.0] - 2023-02-24 -- Added: Check Util folder with phpstan ([#63]) -- Added: Util/DcaUtil::getDcaFields() ([#62]) -- Deprecated: DcaUtil::getFields() - -## [2.225.0] - 2023-01-25 -- Added: UrlUtil to Utils service ([#61]) - -## [2.224.2] - 2023-01-20 -- Fixed: overwritten picture property of ImageExtension - -## [2.224.1] - 2023-01-18 -- Fixed: array index issue - -## [2.224.0] - 2023-01-02 -- Added: HtmlUtil::generateDataAttributesString() ([#60]) - -## [2.223.3] - 2022-12-23 -- Fixed: php8-related bug - -## [2.223.2] - 2022-12-21 -- Fixed: Exception if Choice is used as options callback - -## [2.223.1] - 2022-12-20 -- Fixed: Choices cache not taking locale into account -- Fixed: used deprecated ServiceSubscriber if available in AbstractServiceSubscriber - -## [2.223.0] - 2022-11-09 -- Added: HtmlUtil ([#59]) - -## [2.222.1] - 2022-11-07 -- Fixed: used wrong argument in entity finder for blocks - -## [2.222.0] - 2022-10-17 -- Changed: migrated AccordionUtil::structureAccordionSingle() -- Fixed: used non namespaced twig error class - -## [2.221.2] - 2022-10-17 -- Changed: updated dependencies -- Fixed: accordion util not respect element start and end date ([#58]) -- Fixed: ci pipeline used outdated actions - - -## [2.221.1] - 2022-10-05 -- Fixed: [IcsUtil] set endDate to startDate if not set - -## [2.221.0] - 2022-09-27 -- Added: entity finder helper ([#53]) -- Added: news support to entity finder ([#53]) - -## [2.220.1] - 2022-09-20 -- Fixed: 2.220.0 breaks contao 4.4 support - -## [2.220.0] - 2022-09-20 -- Changed: Migrate routing util to utils ([#56]) -- Fixed: array index issue - -## [2.219.0] - 2022-09-13 -- Added: RequestUtil::isIndexPage() ([#55]) -- Fixed: font name evaluation in FPDFIWriter - -## [2.218.1] - 2022-07-18 -- Fixed: PHP 8 warning - -## [2.218.0] - 2022-07-15 -- Added: Utils::user::findActiveUsersByGroup() (migrated from user util) ([#51]) - -## [2.217.2] - 2022-06-07 -- Fixed: patchwork utf8 class used ([#50]) - -## [2.217.1] - 2022-06-07 -- Fixed: typed properties in FileUtil - -## [2.217.0] - 2022-05-31 -- Added: Utils::file::getPathFromUuid() -- Added: Utils::request::getCurrentRootPageModel() -- Fixed: [DatabaseUtil] invalid field name added to dca -- Fixed: Warnings in php 8 -- Deprecated: FileUtils::getPathFromUuid(); - -## [2.216.0] - 2022-05-17 -- Added: ArrayUtil::insertAfterKey() ([#47]) -- Fixed: DcaUtil::doGenerateDcOperationsButtons() for 4.13 ([#46]) - -## [2.215.2] - 2022-05-09 -- Fixed: display special chars in alt attribute - -## [2.215.1] - 2022-05-05 -- Fixed: incompatiblity with symfony 5 - -## [2.215.0] - 2022-05-03 -- Changed: Update utils class structure ([#44]) -- Changed: Migrate ArrayUtil::removeValue ([#45]) - -## [2.214.0] - 2022-04-28 -- Added: Utils/RequestUtil::getBaseUrl() -- Changed: migrated RequestUtil::isNewVisitor() to Utils/RequestUtil -- Deprecated: RequestUtil - -## [2.213.0] - 2022-04-08 -- Added: [Entity finder] allow search for inserttags from event (added `ExtendEntityFinderEvent::addInserttag()`) -- Fixed: [Entity finder] only one parent found for each entity - -## [2.212.1] - 2022-03-28 -- Fixed: a potential error upon cache warmup and a missing system/tmp folder ([#43], [@qzminski]) - -## [2.212.0] - 2022-03-22 -- Added: entity finder ([#42]) -- Fixed: some incompatibilities with different symfony versions ([#42]) - -## [2.211.0] - 2022-03-07 -- Added: Utils/DcaUtils::getPaletteFields() -- Changed: updated test setup - -## [2.210.1] - 2022-02-28 -- Fixed: exception in DcaUtil -- Fixed: deprecation -- Fixed: missing parameter in DcaUtil test - -## [2.210.0] - 2022-02-23 -- Added: DcaUtils::explodePalette() in Utils namespace - -## [2.209.6] - 2022-02-16 - -- Fixed: array index issues in php 8+ - -## [2.209.5] - 2022-02-16 - -- Fixed: array index issues in php 8+ - -## [2.209.4] - 2022-02-15 - -- Fixed: array index issues in php 8+ - -## [2.209.3] - 2022-02-14 - -- Fixed: array index issues in php 8+ - -## [2.209.2] - 2022-02-14 - -- Fixed: array index issues in php 8+ - -## [2.209.1] - 2022-02-10 - -- Fixed: `DatabaseUtil::composeWhereForQueryBuilder()` for contao 4.13+ - -## [2.209.0] - 2022-02-09 -- Fixed: `ContainerUtil::isMaintenanceModeActive()` for contao 4.13+ - -## [2.208.1] - 2022-01-18 -- Fixed: invalid composer.json - -## [2.208.0] - 2022-01-18 -- Added: AccordionUtil in Utils namespace -- Changed: rewrote AccordionUtil::structureAccordionStartStop() to support nested accordions ([#40]) -- Fixed: test configuration for Utils and AbstractServiceSubscriber classes - -## [2.207.0] - 2021-12-21 -- Added: ModelUtil to Utils service (most services are migrated, but not all now) -- Added: UserUtil to Utils service (not all services migrated) -- Added: ArrayUtil to Utils service (with one method) -- Deprecated: most ModelUtil methods -- Fixed: coverage report used contao 4.4 -- Fixed: some test setup issues - -## [2.206.1] - 2021-10-13 -- Fixed: contao 4.4 incompatible code in DcaUtil - -## [2.206.0] - 2021-10-13 - -- Added: RequestUtil::getCurrentPageModel() ([#37]) -- Added: AbstractServiceSubscriber to make service subscriber compatible to symfony 3, 4 and 5 ([#39]) -- Changed: ContainerUtil::isPreviewMode() now uses TokenChecker::isPreviewMode() where available (Contao 4.5+) ([#37]) -- Changed: Refactored ContainerUtil and Utils class inheriting from AbstractServiceSubscriber -- Fixed: failing tests ([#37]) -- Fixed: remove FrontendPageListener as it add linebreak to czech language pages with many side effects, that must be an optional feature. If you need such a functionality, please add a listener by yourself! ([#38]) -- Fixed: service subscriber not registered correctly ([#39]) - -## [2.205.3] - 2021-10-08 - -- Fixed: UserUtil::findActiveByGroups(#35) - -## [2.205.2] - 2021-09-27 - -- Added: missing default value for author field - -## [2.205.1] - 2021-09-24 - -- Changed: separator character from `_` to `-` (file sanitize) - -## [2.205.0] - 2021-09-22 - -- Added: config parameter `skipReplaceInsertTags` in `FormUtil::prepareSpecialValueForOutput()` - -## [2.204.2] - 2021-09-17 - -- Fixed: visibility of `FileUtil::getParentFoldersByUuid()` - -## [2.204.1] - 2021-09-15 - -- Fixed: preview mode for contao 4.9 - -## [2.204.0] - 2021-09-03 - -- Added: new option `selectFields` for `DatabaseUtil::findResultByPk()`, `DatabaseUtil::findOneResultBy()`, `DatabaseUtil::findResultsBy()` -- Changed: enhanced ContainerUtil documentation -- Deprecated: deprecated the old StringUtil class as whole -- Fixed: issues with CI - -## [2.203.3] - 2021-08-17 - -- Fixed: `CreateImageSizeItemsCommand` - -## [2.203.2] - 2021-08-17 - -- Changed: Refactored `CreateImageSizeItemsCommand` -> external method now available - -## [2.203.1] - 2021-08-11 - -- Fixed: SalutationUtil methods for "other" gender - -## [2.203.0] - 2021-08-11 - -- Added: translations for "other" gender - -## [2.202.4] - 2021-08-10 - -- Fixed: `DcaUtil::addAuthorFieldAndCallback()` -> default value for author is not 0 again (BC) - -## [2.202.3] - 2021-07-26 - -- fixed palette manipulator handling in `DcaUtil::flattenPaletteForSubEntities()` - -## [2.202.2] - 2021-07-26 - -- fixed palette manipulator handling in `DcaUtil::flattenPaletteForSubEntities()` - -## [2.202.1] - 2021-07-26 - -- fixed palette handling issues in `DcaUtil::flattenPaletteForSubEntities()` by using `PaletteManipulator` -- fixed return object in `DatabaseUtil::update()` and `DatabaseUtil::delete()` - -## [2.202.0] - 2021-07-20 - -- added twig filter `|bin2uuid` in order to convert a binary uuid to a textual one - -## [2.201.0] - 2021-07-20 - -- added documentation for command `huh:utils:create-image-size-items` - -## [2.200.0] - 2021-07-15 - -- enhanced `ModelInstanceChoice` to respect more title fields and contain the ID - -## [2.199.1] - 2021-07-15 - -- fixed new author type for `DcaUtil::addAuthorFieldAndCallback()`: php session id (disabled to readonly) - -## [2.199.0] - 2021-07-15 - -- added new author type for `DcaUtil::addAuthorFieldAndCallback()`: php session id (not changeable in backend; only - visible in backend if it had been set before in frontend -> then readonly) - -## [2.198.0] - 2021-07-14 - -- added `DcaUtil::isSubPaletteField()` -- added `DcaUtil::getSubPaletteFieldSelector()` - -## [2.197.0] - 2021-07-13 - -- added possibility to add a custom database object to `DatabaseUtil::insert()`, `DatabaseUtil::update()` - and `DatabaseUtil::delete()` - -## [2.196.3] - 2021-07-09 - -- fixed bug in DcaUtil::aliasExist() - -## [2.196.2] - 2021-07-07 - -- fixed bug in deprecated StringUtil::camelCaseToDashed() - -## [2.196.1] - 2021-07-05 - -- fixed install issue with symfony 5 - -## [2.196.0] - 2021-07-01 - -- Add utils service and migrate services to Util namespace ([#24]) -- refactored ClassUtil and ModelUtil to use dependency injection -- updates some test and skipped some tests to make github action working - -## [2.195.1] - 2021-06-29 - -- fixed twig filter `|file_content` - -## [2.195.0] - 2021-06-29 - -- added Polish translations - -## [2.194.1] - 2021-06-24 - -- enhanced `DcaUtil::generateAlias()` (now supports customizable alias field name) - -## [2.194.0] - 2021-06-18 - -- added `DcaUtil::getCurrentPaletteName()` - -## [2.193.1] - 2021-06-09 - -- fixed twig filter `|file_content` - -## [2.193.0] - 2021-06-01 - -- added `jsonPost()` in javascript ajax util -- fixed issues in js ajax util - -## [2.192.2] - 2021-05-28 - -- fixed js method `DomUtil::getTextWithoutChildren()` - -## [2.192.1] - 2021-05-17 - -- fixed hours tranformation for ISO8601 (#31) - -## [2.192.0] - 2021-04-16 - -- added `fields` option for `DcaUtil::setFieldsToReadOnly()` - -## [2.191.0] - 2021-03-23 - -- added params for operators IN and NOT IN in DatabaseUtil::composeWhereForQueryBuilder (#29) - -## [2.190.0] - 2021-03-11 - -- added ImageUtil::prepareImage() - -## [2.189.0] - 2021-03-10 - -- experimental allowed php 8 -- deprecated PDFCreator in favor of PDFCreator library -- moved from samidoc to phpDocumentator -- fixed deprecation in Configuration class - -## [2.188.10] - 2021-02-22 - -- fixed missing symfony/config component - -## [2.188.9] - 2021-02-15 - -- avoid replaceInsertTags getting invalid path - -## [2.188.8] - 2021-02-15 - -- fixed choice cache (not run in the constructor anymore) - -## [2.188.7] - 2021-02-09 - -- fixed autowiring for UserUtil and MemberUtil - -## [2.188.6] - 2021-02-01 - -- fixed twig picture template if width or height are null - -## [2.188.5] - 2021-01-18 - -- added PersonTrait to UserUtil and MemberUtil, added tests for PersonTrait (#26) - -## [2.188.4] - 2021-01-18 - -- **BC BREAK**: fixed error in `DatabaseUtil::composeWhereForQueryBuilder()` -> `IN` and `NOT IN` statement with empty - values leads to an unfullfillable condition, not to skipping the filter anymore (since it was a bug, the bc break was - necessary) - -## [2.188.3] - 2021-01-15 - -- fixed sorting in `ModelInstanceChoice` to be a natural sorting (switched asort to natcasesort) - -## [2.188.2] - 2021-01-11 - -- fixed missing public service attributes for twig extensions (contao 4.9+) - -## [2.188.1] - 2020-12-22 - -- fixed random fields added in loadDataContainer hooks not added to the database (#25) - -## [2.188.0] - 2020-12-18 - -- added support for page specific date/time formats in DateUtil - -## [2.187.0] - 2020-12-15 - -- started preparation for next major version with a new bundle and extension class (old ones are still available, but - should not be referenced anymore, see [Upgrade guide](UPGRADE.md)) (#23) -- [BEHAVIOR CHANGE] added configuration option for databaseTreeCache warmer, cache warmer for databasetree cache is not - executed by default anymore, but can be activated by configuration, see [Readme](README.md) (#23) -- added option to disabled the inclusion of utils bundle assets (#23) -- updated encore bundle integration, minimum supported encore bundle version is now 1.5 - -## [2.186.0] - 2020-12-15 - -- fixed DownloadExtension (see https://github.com/heimrichhannot/contao-utils-bundle/issues/22) - -## [2.185.0] - 2020-11-30 - -- added DatabaseUtil::createWhereForSerializedBlob() option parameter and inline_values option - -## [2.184.1] - 2020-11-25 - -- fixed loading attribute has no default value in picture.html.twig -- enhance an annotation in ModelUtil - -## [2.184.0] - 2020-11-24 - -- added `DateUtil::convertSecondsToHumanReadableFormat()` - -## [2.183.0] - 2020-11-23 - -- added support for `loading` attribute if set from outside (lazy loading) - -## [2.182.1] - 2020-11-10 - -- added a parameter to `DcaUtil::setDefaultsFromDca()` for BC reasons - -## [2.182.0] - 2020-11-10 - -- fixed `DcaUtil::setDefaultsFromDca()` to also respect sql default values - -## [2.181.4] - 2020-11-05 - -- added check for empty request in `ContainerUtil` (e.g. in command situations) - -## [2.181.3] - 2020-10-28 - -- fixed `UrlUtil::getBaseUrl` to enable non-app_dev.php base url for versions later than 4.8 - -## [2.181.2] - 2020-10-13 - -- fixed twig image filter not allowed image size to be an id or name - -## [2.181.1] - 2020-10-06 - -- modified autowiring for InsertTagsListener - -## [2.181.0] - 2020-10-06 - -- refactored InsertTagsListener -- added Tests for InsertTagsListener - -## [2.180.2] - 2020-10-05 - -- changed template rendering in InsertTagsListener::replaceTwigTag() to TemplateUtil::renderTwigTemplate - -## [2.180.1] - 2020-09-30 - -- fix remove dump from image_gallery template - -## [2.180.0] - 2020-09-28 - -- added `IcsUtil` - -## [2.179.0] - 2020-09-22 - -- added `ContentUtil` - -## [2.178.2] - 2020-09-18 - -- do not throw error when calling protected or private methods in ClassUtil::jsonSerialize() when ignoreMethodVisibility - is set to true - -## [2.178.1] - 2020-09-15 - -- added state as possibility for `LocationUtil::computeCoordinatesByString()` - -## [2.178.0] - 2020-09-09 - -- js: added `GeneralUtil.runRecursiveFunction()` - -## [2.177.6] - 2020-08-31 - -- fixed class inheritance for CfgTagModel to respect the one existing - -## [2.177.5] - 2020-08-26 - -- added missing default check for includeCopyright in `image.html.twig` - -## [2.177.4] - 2020-08-25 - -- fixed uncatched errors in DcaUtil::getConfigByArrayOrCallbackOrFunction() when used in frontend and contao 4.9 - -## [2.177.3] - 2020-08-20 - -- fixed caption and copyrights issue in `image.html.twig` -> the flag `includeCopyright` has to be added - to `image.html.twig` in order to print the copyright without a caption set - -## [2.177.2] - 2020-08-20 - -- removed database tree cache for `tl_page` from utils-bundle since only needed for contao-blocks -- fixed missing prefixes in database tree cache - -## [2.177.1] - 2020-08-19 - -- fixed caption and copyrights issue - -## [2.177.0] - 2020-08-19 - -- added callbacks to PdfCreator - -## [2.176.1] - 2020-08-19 - -- fixed MpdfCreator font directory support - -## [2.176.0] - 2020-08-18 - -- added PdfCreator as replacement for PdfWriterpull - -## [2.175.2] - 2020-08-13 - -- show twig template start and stop comments in dev mode - -## [2.175.1] - 2020-08-12 - -- fixed comparison issue in `DatabaseUtil::doBulkInsert()` - -## [2.175.0] - 2020-08-11 - -- added support for divers gender-based salutations - -## [2.174.0] - 2020-08-07 - -- added possibility to add a custom backend route in `DcaUtil::getPopupWizardLink()` - -## [2.173.1] - 2020-07-22 - -- added alias for curlRequestUtil - -## [2.173.0] - 2020-07-22 - -- DcaUtil::generateAlias() now accepts null as table parameter to skip unique check - -## [2.172.1] - 2020-07-21 - -- fixed a error with php version prior to 7.4 - -## [2.172.0] - 2020-07-21 - -- added DcaUtil::aliasExist() - -## [2.171.5] - 2020-07-20 - -- fixed UrlUtil::getCurrentUrl() options parameter not optional - -## [2.171.4] - 2020-07-16 - -- fixed `StringExtension::autolink()` (German dates like 15.7. have been translated to links) - -## [2.171.3] - 2020-07-16 - -- fixed unicode characters for `StringUtil::replaceUnicodeEmojisByHtml()` to also contain skin tones - -## [2.171.2] - 2020-07-15 - -- added latest unicode characters for `StringUtil::replaceUnicodeEmojisByHtml()` - -## [2.171.1] - 2020-07-06 - -- fixed type hinting - -## [2.171.0] - 2020-06-30 - -- added ignoreLogin to `MemberUtil::findActiveByGroups()` - -## [2.170.0] - 2020-06-30 - -- fixed attributes issue in `image.html.twig` -> now link gets its correct `attributes`; "wrong" `linkAttributes` is - still in place for compatibility reasons -- fixed lightbox issues in `ImageUtil::addToTemplate()` - -## [2.169.2] - 2020-06-23 - -- fixed `DatabaseUtil::findResultsBy()` to accept also null as columns and values - -## [2.169.1] - 2020-06-23 - -- fixed `DcaUtil::generateAlias()` - -## [2.169.0] - 2020-06-23 - -- added `ContainerUtil::isPreviewMode()` - -## [2.168.2] - 2020-06-23 - -- fixed `DcaUtil::generateAlias()` - -## [2.168.1] - 2020-06-23 - -- fixed `DcaUtil::generateAlias()` - -## [2.168.0] - 2020-06-23 - -- fixed `DcaUtil::generateAlias()` - -## [2.167.0] - 2020-06-22 - -- added `FileUtil::getExtensionFromFileContent()` -- added `FileUtil::getExtensionByMimeType()` -- fixed `FileUtil::retrieveFileContent()` - -## [2.166.0] - 2020-06-19 - -- added `FileUtil::retrieveFileContent()` - -## [2.165.0] - 2020-06-15 - -- added option to pass multiple tables into `DcaUtil::generateAlias()` as a comma separated list - -## [2.164.2] - 2020-06-10 - -- fixed warning if no result in `DcaUtil::generateAlias()` - -## [2.164.1] - 2020-05-29 - -- fixed size issue in twig image filter - -## [2.164.0] - 2020-05-25 - -- added new twig filter `|file_content` (takes uuid as binary or string) - -## [2.163.0] - 2020-05-13 - -- added new tests to `TestExtension` -- fixed copyright issue in `image.html.twig` - -## [2.162.0] - 2020-04-28 - -- revoked 2.161.0 and 2.161.1 due to problems in contao 4.9 -> pages can't be saved anymore - -## [2.161.0] - 2020-04-24 - -- added TemplateLocator class -- enhanced UtilsCacheWarmer -- some code enhancements - -## [2.160.0] - 2020-04-22 - -- added `TestExtension` for checking types in twig (e.g. `if [] is string` or `if '1' is numeric`) - -## [2.159.1] - 2020-04-22 - -- fixed type hinting for `DateUtil::generateAlias()` - -## [2.159.0] - 2020-04-21 - -- added `choices.yml` containing the service definitions for the choices - -## [2.158.0] - 2020-04-21 - -- added `DatabaseUtil::beginTransaction()` and `DatabaseUtil::commitTransaction()` - -## [2.157.3] - 2020-04-21 - -- fixed types in `DatabaseUtil` - -## [2.157.2] - 2020-04-20 - -- fixed types in replaceInsertTags() from StringUtil - -## [2.157.1] - 2020-04-16 - -- removed `select()` from `DatabaseUtil` as it's already covered by the various `findBy` methods - -## [2.157.0] - 2020-04-16 - -- added `DcaUtil::getNewSortingPosition()` - -## [2.156.1] - 2020-04-14 - -- fixed session bug for contao 4.9 in `LocationUtil` - -## [2.156.0] - 2020-04-14 - -- added `DatabaseUtil::select()` - -## [2.155.2] - 2020-04-09 - -- fixed PageUtil service definition for symfony 4 - -## [2.155.1] - 2020-04-08 - -- added empty check for `imageSize` in `ImageUtil` - -## [2.155.0] - 2020-04-08 - -- added `PageUtil` - -## [2.154.0] - 2020-04-06 - -- added attributes and linkText option to DcaUtil::getPopupWizardLink() - -## [2.153.0] - 2020-04-06 - -- partly rewrote DcaUtil::getPopupWizardLink() -- deprecated DcaUtil::getPopupWizardLink() string as first parameter -- updated documentation -- updated tests - -## [2.152.1] - 2020-04-06 - -- fixed `StringUtil::replaceInsertTags()` - -## [2.152.0] - 2020-04-03 - -- added `StringUtil::replaceInsertTags()` - -## [2.151.1] - 2020-03-26 - -- fixed `UrlUtil::getBaseUrl()` - -## [2.151.0] - 2020-03-26 - -- added `ContainerUtil::isMaintenanceModeActive()` - -## [2.150.0] - 2020-03-19 - -- added `UserUtil::isAdmin()` - -## [2.149.0] - 2020-03-17 - -- added `FileUtil::getParentFoldersByUuid()` - -## [2.148.1] - 2020-03-12 - -- updated documentation - -## [2.148.0] - 2020-03-12 - -- added FileStorageUtil as replacement for FileCache -- deprecated FileCache -- replaced FileCache with FileStorageUtil in PdfPreview -- fixed PdfPreview not working when destination folder does not exist2.148. - -## [2.147.0] - 2020-03-10 - -- fixed nested record style - -## [2.146.0] - 2020-03-10 - -- added `DcaExtension` -- added `DcaExtension::fieldLabel` - -## [2.145.0] - 2020-03-09 - -- added `DatabaseUtil::insert()` -- added `DatabaseUtil::update()` -- added `DatabaseUtil::delete()` - -## [2.144.0] - 2020-03-06 - -- added `DcaUtil::prepareRowEntryForList()` -- added `DcaUtil::getFieldLabel()` - -## [2.143.0] - 2020-03-05 - -- added `DcaUtil::getRenderedDiff()` -- added `ArrayUtil::implodeRecursive()` - -## [2.142.0] - 2020-03-05 - -- added `DcaUtil::setFieldsToReadOnly()` -- added `DcaUtil::getTranslatedModuleNameByTable()` - -## [2.141.1] - 2020-03-03 - -- added option `restrictFields` to `FormUtil::getModelDataAsNotificationTokens()` - -## [2.141.0] - 2020-03-03 - -- added DateUtil::getDaysBetween() -- fixed UrlUtil::getBaseUrl() - -## [2.140.0] - 2020-03-02 - -- fixed service definitions for contao 4.9 - -## [2.139.0] - 2020-02-27 - -- added UrlUtil::getBaseUrl() - -## [2.138.0] - 2020-02-27 - -- added FormUtil::getModelDataAsNotificationTokens() - -## [2.137.0] - 2020-02-26 - -- added DcaUtil::activateNotificationType() - -## [2.136.1] - 2020-02-26 - -- fixed adding formData to uri -- updated dependencies - -## [2.136.0] - 2020-02-26 - -- added DcaUtil::getAuthorNameLinkByUserId and DcaUtil::getAuthorNameByUserId - -## [2.135.2] - 2020-02-26 - -- fixed DcaUtil::loadLanguageFile() - -## [2.135.1] - 2020-02-19 - -- fixed PdfWriter::generate() to generate correct path - -## [2.135.0] - 2020-02-18 - -- added UrlUtil::getRelativePath() - -## [2.134.0] - 2020-02-17 - -- added absoluteUrl option to UrlUtil::removeQueryString() -- added absoluteUrl option to UrlUtil::prepareUrl() -- added RequestUtil::isNewVisitor() -- deprecated UrlUtil::isNewVisitor() - -## [2.133.0] - 2020-02-06 - -- added label formatting for `ModelInstanceChoice` - -## [2.132.0] - 2020-02-05 - -- added `replace_inserttag` twig filter - -## [2.131.0] - 2020-02-03 - -- added `AnonymizerUtil` with email anonymizer -- added `anonymize_email` twig filter - -## [2.130.0] - 2020-01-30 - -- added `FileUtil::getFolderContent()` -- added `DatabaseUtil::findResultsBy()` - -## [2.129.0] - 2020-01-27 - -- added `FileUtil::getFileIdFromPath()` - -## [2.128.1] - 2020-01-27 - -- added reference for php operators - -## [2.128.0] - 2020-01-23 - -- added comparison util - -## [2.127.0] - 2020-01-16 - -- added support for dc_multilingual 4 - -## [2.126.0] - 2020-01-09 - -- added `DatabaseUtil::findResultByPk()` -- added `DatabaseUtil::findOneResultBy()` - -## [2.125.0] - 2020-01-07 - -- added `ContainerUtil::isDev()` - -## [2.124.0] - 2019-12-13 - -- updated `DcaUtil::getDataContainers()` to list all database data containers -- added `DcaUtil::getDataContainers()` option to only list database data containers -- updated dca util tests - -## [2.123.1] - 2019-12-11 - -- added `ContainerUtil::isFrontendCron()` -- fixed `StringUtil::replaceUnicodeEmojisByHtml()` - -## [2.123.0] - 2019-11-27 - -- replace inserttags in `FormUtil::prepareSpecialValueForOutput` - -## [2.122.0] - 2019-11-26 - -- `CreateImageSizeCommand` - -## [2.121.0] - 2019-11-21 - -- added video twig template - -## [2.120.1] - 2019-11-14 - -- updated some service definitions for better symfony 4 compatibility - -## [2.120.0] - 2019-11-12 - -- added `RsceUtil` - -## [2.119.4] - 2019-11-05 - -- fixed module isSubModuleOf in ModuleUtil - -## [2.119.3] - 2019-11-05 - -- fixed accordion ptable - -## [2.119.2] - 2019-11-05 - -- fixed CfgTagModel - -## [2.119.1] - 2019-10-30 - -### Fixed - -- margins in FPDIWriter -- text length calculation in StringUtil::truncateHtml - -## [2.119.0] - 2019-10-28 - -### Added - -- support for exists() in filedata twig filter - -## [2.118.0] - 2019-10-28 - -### Added - -- copyright support for image - -## [2.117.2] - 2019-10-23 - -### Fixed - -- location util - -## [2.117.1] - 2019-10-22 - -### Fixed - -- download extension - -## [2.117.0] - 2019-10-21 - -### Added - -- polyfills to package.json: `element-closest`, `nodelist-foreach-polyfill` - -## [2.116.0] - 2019-10-10 - -### Added - -- ModuleUtil::getModuleClass - -### Fixed - -- ModuleUtil::isSubModuleOf - -## [2.115.0] - 2019-10-01 - -### Added - -- js: event-util::createEventObject - -## [2.114.0] - 2019-09-30 - -### Added - -- graceful degredation for picture (svg) - -## [2.113.0] - 2019-09-20 - -### Removed - -- choice caching from AbstractChoice for backend - -## [2.112.0] - 2019-09-17 - -### Added - -- ArrayExtension - -## [2.111.0] - 2019-09-17 - -### Added - -- StringUtil::replaceUnicodeEmojisByHtml() - -## [2.110.0] - 2019-09-03 - -### Added - -- subClass for dc operations template - -### Changed - -- DcaUtil::generateDcOperationsButtons() -> support for options - -## [2.109.0] - 2019-09-03 - -### Added - -- styling for backend sub records - -## [2.108.0] - 2019-08-29 - -### Changed - -- DateUtil::getFormattedDateTime() now supports translating months - -### Fixed - -- issues in DateUtil::getFormattedDateTime() - -## [2.107.0] - 2019-08-27 - -### Added - -- StringExtension (twig) - -## [2.106.0] - 2019-08-16 - -### Changed - -- enhanced DcaUtil::doGenerateDcOperationsButtons() - -### Added - -- DcaUtil::getPopupWizardLink() - -## [2.105.0] - 2019-08-14 - -### Added - -- ContainerUtil::isInstall() - -## [2.104.4] - 2019-08-13 - -### Changed - -- DcaUtil::addAuthorFieldAndCallback() has an additional parameter for prefixing the fields now - -## [2.104.3] - 2019-08-08 - -### Fixed - -- removed trailing comma in FPDIWriter (#11) - -## [2.104.2] - 2019-08-07 - -### Fixed - -- autowiring issue with FolderUtil and Symfony 4 (#9) - -## [2.104.1] - 2019-08-07 - -### Changed - -- updated tests - -## [2.104.0] - 2019-08-06 - -### Added - -- FPDIWriter - -### Changed - -- refactored PdfWriter - -## [2.103.1] - 2019-08-05 - -### Fixed - -- parameter name (tmp_folder instead of tmpFoldergit st) - -## [2.103.0] - 2019-08-05 - -### Added - -- FileArchiveUtil (huh.utils.file_archive) -- FolderUtil (huh.utils.folder) -- Configuration - -## [2.102.0] - 2019-07-31 - -### Added - -- FileUtil::getFileContentFromUuid() -- LocationUtil::getCoordinatesFromGpx() -- LocationUtil::getCoordinatesFromKml() -- StringUtil::convertXmlToArray() - -## [2.101.1] - 2019-07-29 - -### Fixed - -- ajax FormData issue - -## [2.101.0] - 2019-07-10 - -### Added - -- node module ajax-util set responseType - -## [2.100.2] - 2019-07-09 - -### Fixed - -- debug code in files util - -## [2.100.1] - 2019-06-28 - -### Fixed - -- js ajaxUtil for supporting objects - -## [2.100.0] - 2019-06-24 - -### Added - -- `ImageExtension::getImageGallery()` - -## [2.99.1] - 2019-06-17 - -### Changed - -- submitted data in request - -## [2.99.0] - 2019-06-17 - -### Added - -- `image_gallery` as Twig extension to show gallery items as image objects as list - -## [2.98.3] - 2019-06-14 - -### Added - -- `readableFilesize` as Twig attribute to `FileExtension.php` in TwigUtil - -## [2.98.2] - 2019-06-14 - -### Added - -- description for usage of js ajaxUtil in readme - -## [2.98.1] - 2019-06-14 - -### Changed - -- moved afterSubmit callback in ajax util to correct position - -## [2.98.0] - 2019-06-13 - -### Changed - -- moved js from component directly into bundle - -## [2.97.2] - 2019-06-11 - -### Fixed - -- service injection - -## [2.97.1] - 2019-06-06 - -### Fixed - -- `huh.utils.dca` method `addAliasToDca` now properly supports replacement of fields by considering fieldset and field - separator (comma and semikolon) - -## [2.97.0] - 2019-05-29 - -### Changed - -- removed `srcset` parameter related to `source` inside `picture.html.twig` to fix mobile issues - -## [2.96.0] - 2019-05-07 - -### Added - -- `aspectRatio` parameter (boolean) support added to `lazyload` attribute inside `picture.html.twig` - -## [2.95.0] - 2019-05-07 - -### Added - -- FormUtil::getBackendFormField() - -## [2.94.0] - 2019-04-29 - -### Changed - -- replaced `html2text/html2text` with `soundasleep/html2text` due to GPL licence (`html2text/html2text`) incompatibility - -## [2.93.0] - 2019-04-26 - -### Changed - -- replaced `roderik/pwgen-php` with `hackzilla/password-generator` due to GPL licence (`roderik/pwgen-php`) - incompatibility - -### Added - -- GNU LESSER GENERAL PUBLIC LICENSE - -## [2.91.2] - 2019-04-26 - -### Fixed - -- database error in AccordionUtil::structureAccordionSingle() - -## [2.91.1] - 2019-04-25 - -### Fixed - -- w3c validator error in `picture.html.twig` occured by lazyload technique - -## [2.91.0] - 2019-04-25 - -### Added - -- invoke `huh.utils.listener.frontend_page` that ensure line breaks for several languages (in cs for instance one - syllable words like a should stay together, e.g. `a stavět` -> `a stavět`) - -## [2.90.3] - 2019-04-18 - -### Added - -- add possibility to invoke custom lazy loading config into `picture.html.twig` - -## [2.90.2] - 2019-04-18 - -### Changed - -- inject service container in DateUtil -- updated tests -- updated documentation - -### Fixed - -- loadDataContainer Hook error when empty database - -## [2.90.1] - 2019-04-18 - -### Fixed - -- non-public services in ContainerUtil - -## [2.90.0] - 2019-04-16 - -### Changed - -- made $fileExtension parameter optional in FileCache::exist() and FileCache::get() -- FileCache now uses kernel.project_dir instead of contao.web_dir for root path -- refactoring due changes in internal coding standards -- refactored service loading into Plugin class -- updated a lot of tests - -### Fixed - -- FileCache::getNamespace() error when namespace not initialized - -### Fixed - -- possible warnings in AccordionUtil - -## [2.89.2] - 2019-04-09 - -### Changed - -- `huh.utils.form` method `prepareSpecialValueForOutput` for `multiColumnEditor` formatted with linebreaks and tabs - -## [2.89.1] - 2019-04-09 - -### Changed - -- `huh.utils.form` method `prepareSpecialValueForOutput` now supports `multiColumnEditor` bundle only - -## [2.89.0] - 2019-04-08 - -### Changed - -- `DcaUtil::getFields()`: order in label swapped for usability reasons - -## [2.88.0] - 2019-04-08 - -### Changed - -- default modal width to 1024 in `DcaUtil::getModalEditLink()` and `DcaUtil::getArchiveModalEditLink` - -### Fixed - -- `DcaUtil::getEditLink()` now respects symfony environment -- `DcaUtil::getModalEditLink` now respects symfony environment -- `DcaUtil::getArchiveModalEditLink` now respects symfony environment - -## [2.87.4] - 2019-04-08 - -### Added - -- `UrlUtil::getJumpToPageUrl()` -- `UrlUtil::addAutoItemToPage()` - -## [2.87.3] - 2019-04-08 - -### Fixed - -- `ModuleUtil::getModulesByType()` - -## [2.87.2] - 2019-04-08 - -### Fixed - -- `ModuleUtil::isSubModuleOf()` - -## [2.87.1] - 2019-04-08 - -### Added - -- error handling for coordinate retrieval -- `LocationUtil::computeCoordinatesInSaveCallback()` - -## [2.87.0] - 2019-04-08 - -### Added - -- tl_settings::utilsGoogleApiKey as a central point for specifying a google api key -- localizations - -## [2.86.2] - 2019-03-29 - -### Fixed - -- TemplateUtil::isTemplatePartEmpty treated null as not empty - -## [2.86.1] - 2019-03-27 - -### Fixed - -- drop `hash` and `handle` from Twig Extension (`FileExtension`), big performance impact due to additional db/file - system access - -## [2.86.0] - 2019-03-25 - -### Added - -- `huh.utils.string` ensureLineBreaks() that fixes line breaks for one-syllable words in czech language (should not - stand alone at the end), this is done by `huh.utils.listener.frontend_page` listener automatically - -## [2.85.0] - 2019-03-25 - -### Added - -- ModuleUtil -- DateUtil::getFormattedDateTime(), DateUtil::getFormattedDateTimeByEvent() -- DcaUtil::loadLanguageFile() -- MemberUtil::findOrCreate() - -### Fixed - -- MemberUtil -- DcaUtil - -## [2.84.1] - 2019-03-21 - -### Fixed - -- twig templates did not render error messages, because they were catched before in ClassUtil and Twig Extensions - -## [2.84.0] - 2019-03-20 - -### Added - -- polyfills for js - -### Changed - -- js generation to use webpack 0.24+ - -## [2.83.1] - 2019-03-15 - -### Fixed - -- added `ignoreMethods` to prevent file access in twig `FileExtension` - -## [2.83.0] - 2019-03-14 - -### Added - -- added options support for `skippedMethods` and `ignoreMethods` in `huh.utils.class` method `jsonSerialize` -- catch Exceptions in `huh.utils.class` method `jsonSerialize` method calls in order to skip invalid method calls and - continue serialization -- twig `FileExtension` ('file_data`) now skips all file content related method calls and magic getter properties (handle - out of memory exceptions) - -## [2.82.1] - 2019-03-14 - -### Changed - -- added some polish translations - -## [2.82.0] - 2019-03-14 - -### Changed - -- updated dependency `tijsverkoyen/css-to-inline-styles` to ^2.2 - -## [2.81.2] - 2019-03-12 - -### Fixed - -- `queryBuilder->setParameter` can't handle `.` in wildcard parameter. Replaced `.` in wildcard with `_` - -## [2.81.1] - 2019-03-08 - -### Fixed - -- added missing closing `
` tag in `image.html.twig` (did not tag 2.80.1) - -## [2.81.0] - 2019-03-08 - -### Added - -- FileExtension - -## [2.80.1] - 2019-03-08 - -### Fixed - -- added missing closing `
` tag in `image.html.twig` - -## [2.80.0] - 2019-03-08 - -### Changed - -- `image.html.twig` syntax is now compatible with `heimrichhannot/contao-speed-bundle` lazyload component from - version `1.8` - -## [2.75.1] - 2019-03-06 - -### Fixed - -- `eventUtil::addDynamicEventListener()` now check that `matches` function exist in object - -## [2.75.0] - 2019-03-05 - -### Fixed - -- `eventUtil::addDynamicEventListener()` argument `scope` added (default: `document`) and moved `disableBubbling` to 5th - argument -- `eventUtil::addDynamicEventListener()` now works with window eventListeners like `load` or `resize` - -## [2.74.0] - 2019-02-26 - -### Changed - -- `ImageExtension:getImage()` $template parameter no longer yields namespace, now uses `huh.utils.template` - method `getTemplate` - -### Added - -- `RenderTwigTemplateEvent` (huh.utils.template.render) to manipulate template name and context data before rendering - twig templates -- ratio, width and height parameter to picture attribute in `huh.utils.image` addToTemplateData - -## [2.73.0] - 2019-02-20 - -### Added - -- `getParentRecords` added in `huh.utils.cache.database_tree` - -## [2.72.0] - 2019-02-20 - -### Added - -- `getPreviewFromPdf` method in `huh.utils.file` - -## [2.71.0] - 2019-02-20 - -### Added - -- twig filter `image_data` in `ImageExtension` to get image data as array - -## [2.70.2] - 2019-02-19 - -### Fixed - -- compile error due calling `System:getContainer` in `TemplateUtil` constructor - -## [2.70.1] - 2019-02-19 - -### Fixed - -- `TemplateUtil::getTemplateGroup` now returns file extension in file name if not html.twig - -## [2.70.0] - 2019-02-18 - -### Changed - -- `TemplateUtil` now supports all twig formats -- `TemplateUtil::getTemplate` now throws an error if template not exist - -## [2.69.1] - 2019-02-15 - -### Added - -- `label_callback` argument `$context` in `huh.utils.choice.model_instance` - -## [2.69.0] - 2019-02-14 - -### Added - -- `label_callback` added to `$context` in order to adjust label in `huh.utils.choice.model_instance` - -## [2.68.2] - 2019-02-13 - -### Fixed - -- AccordionUtil - -## [2.68.1] - 2019-02-12 - -### Fixed - -- `huh.utils.cache.database_tree` now properly clear cache on `oncut_callback`, `ondelete_callback`, `onsubmit_callback` -- `huh.utils.template` returned wrong path for `/templates` twig templates -- Unit test errors - -## [2.68.0] - 2019-02-11 - -### Added - -- `huh.utils.cache.database_tree` to replace `Database->getChildRecords()` and provide proper caching - -## [2.67.2] - 2019-02-08 - -### Fixed - -- JavaScript webpack generation issue - -### Removed - -- package.json -> browserslist (now using the default option) - -## [2.67.1] - 2019-02-08 - -### Fixed - -- JavaScript: `domUtil.getAllParentNodes()` - -## [2.67.0] - 2019-02-05 - -### Added - -- `tns-lazy-img` class to `picture.html.twig` in order to - fix `https://github.com/heimrichhannot/contao-tiny-slider-list-bundle` lazy load handling on ios safari - -## [2.66.1] - 2019-02-04 - -### Added - -- `huh.utils.template` method `getTemplate` returns template name plus format if no template was found - -## [2.66.0] - 2019-02-04 - -### Added - -- `huh.cache.warm_internal` service that provides an twig template cache in production mode (for performance reasons) -- `huh.utils.template` method `getAllTemplates` that provides better cache handling, invoked on every contao request - as `initializeSystem` Hook - -### Fixed - -- `huh.utils.template` template caching, improves performance on method `getTemplate` by factor 4 - -## [2.65.2] - 2019-01-29 - -### Fixed - -- ModelUtil columns array issue - -## [2.65.1] - 2019-01-28 - -### Fixed - -- ModelInstanceChoice::collect() - -## [2.65.0] - 2019-01-25 - -### Removed - -- `DcaUtil::addDcMultilingualSupport` -- `DcaUtil::addDcMultilingualTranslatableAliasEval` - --> please -use [heimrichhannot/contao-dc-multilingual-utils-bundle](https://github.com/heimrichhannot/contao-dc-multilingual-utils-bundle) -instead - -## [2.64.1] - 2019-01-24 - -### Fixed - -- lazyload in `picture.html.twig` only set `height` (do not set `width`, otherwise lazyload will break) - -## [2.64.0] - 2019-01-24 - -### Added - -- `StringUtil::camelCaseToSnake` - -## [2.63.0] - 2019-01-24 - -### Added - -- DcaUtil::addDcMultilingualTranslatableAliasEval -- support for table prefixed fields in DatabaseUtil::composeWhereForQueryBuilder() - -### Fixed - -- ModelUtil::findParentsRecursively() order issue - -## [2.62.0] - 2019-01-24 - -### Changed - -- lazyload in `picture.html.twig` now uses `width` and `height` styles instead of `padding-bottom` -- removed `attributes` from anchor inside `image.html.twig` and added `linkAttributes` - -## [2.61.0] - 2019-01-23 - -### Changed - -- js workflow optimized -- yarn dependency `contao-utils-bunlde` to `@hundh/contao-utils-bundle` - -## [2.60.8] - 2019-01-22 - -### Fixed - -- js - -## [2.60.7] - 2019-01-18 - -### Fixed - -- correct calculation of padding for portrait format images in picture template - -## [2.60.6] - 2019-01-08 - -### Changed - -- names of util params in singular - -## [2.60.5] - 2019-01-07 - -### Fixed - -- adept fixes from 2.60.4 for `TemplateUtil::getBundleTemplate` and `TemplateUtil::getTemplate` - -## [2.60.4] - 2019-01-07 - -### Fixed - -- `TemplateUtil::getTemplateGroup` now finds all twig templates in views, not just `html.twig` - -## [2.60.3] - 2018-12-21 - -### Fixed - -- set class from img on `image-wrapper` in `picture.html.twig` to handle padding-bottom dimensions (border) - -## [2.60.2] - 2018-12-21 - -### Fixed - -- added styles to `picture.html.twig` that adjusts padding-bottom for media query image sizes - -## [2.60.1] - 2018-12-19 - -### Fixed - -- js utils - -## [2.60.0] - 2018-12-19 - -### Fixed - -- refactoring for js utils - -## [2.59.2] - 2018-12-17 - -### Fixed - -- Make `AccordionUtil` work with multiple articles per page - -## [2.59.1] - 2018-12-17 - -### Fixed - -- Make `AccordionUtil` work with multiple articles per page - -## [2.59.0] - 2018-12-17 - -### Added - -- DcaUtil::addDcMultilingualSupport() - -### Fixed - -- ModelUtil::fixTablePrefixForDcMultilingual() to support "order" in Model's $options - -## [2.58.0] - 2018-12-14 - -### Added - -- `attributes` parameter to `download.html.twig` - -## [2.57.0] - 2018-12-13 - -### Changed - -- size parameter in ImageExtension now also supports serialized strings - -## [2.56.1] - 2018-12-12 - -### Fixed - -- ModelUtil::addPublishedCheckToModelArrays() - -## [2.56.0] - 2018-12-12 - -### Added - -- `figureAttributes` attribute to `image.html.twig` - -## [2.55.0] - 2018-12-12 - -### Added - -- ModelUtil::callModelMethod(), ModelUtil::addPublishedCheckToModelArrays() - -## [2.54.0] - 2018-12-12 - -### Added - -- DcaUtil::getDCTable() - -### Fixed - -- localization issue in FormUtil::prepareSpecialValueForOutput() - -## [2.53.0] - 2018-12-11 - -### Added - -- css classes from img-class `contao picture sizes` added to `figure` element with prefix `figure-` - -## [2.52.0] - 2018-12-07 - -### Added - -- ClassUtil::callInaccessibleMethod() - -## [2.51.1] - 2018-12-06 - -### Fixed - -- strict comparison issues - -## [2.51.0] - 2018-12-06 - -### Added - -- DcaUtil::generateSitemap() - -## [2.50.0] - 2018-12-04 - -### Added - -- `image_width` and `image_caption` twig filters - -## [2.49.3] - 2018-11-30 - -### Fixed - -- Added missing twig template for `DownloadExtension` - -## [2.49.2] - 2018-11-30 - -### Fixed - -- DateUtil - -## [2.49.1] - 2018-11-30 - -### Fixed - -- replace inserttags in Twig `DownloadExtension` - -## [2.49.0] - 2018-11-30 - -### Fixed - -- urldecode file path in Twig `DownloadExtension` to support - -### Added - -- Twig Plugin `DownloadExtension` function `download_title` - -## [2.48.0] - 2018-11-30 - -### Added - -- Twig Plugin `DownloadExtension` with `download`, `download_link`, `download_path`, `download_data` - -## [2.47.1] - 2018-11-29 - -### Fixed - -- `huh.util.image` addToTemplateData() now returns if image from uuid was not found - -## [2.47.0] - 2018-11-29 - -### Added - -- DateExtension - -## [2.46.2] - 2018-11-28 - -### Fixed - -- restore img css class in `picture.html.twig` while lazyload is active - -## [2.46.1] - 2018-11-26 - -### Fixed - -- DateUtil::getShortMonthTranslationMap(), DateUtil::getMonthTranslationMap() - -## [2.46.0] - 2018-11-26 - -### Changed - -- made support for properties in ClassUtil::jsonSerialize() optional - -## [2.45.0] - 2018-11-26 - -### Added - -- support for properties in ClassUtil::jsonSerialize() - -## [2.44.2] - 2018-11-19 - -### Fixed - -- contao 4.6.8 uses contao.csrf.token_manager service to validate token - -## [2.44.1] - 2018-11-19 - -### Fixed - -- symfony 4.x and contao 4.6+ support - -## [2.44.0] - 2018-11-15 - -### Added - -- TemplateUtil::getTemplateGroup now also search within bundle views folders - -## [2.43.0] - 2018-11-14 - -### Added - -- ModelUtil -> Controller::replaceInsertTags for values - -## [2.42.2] - 2018-11-14 - -### Fixed - -- ClassUtil json serialization - -## [2.42.0] - 2018-11-14 - -### Added - -- new methods to DateUtil: isMonthInDateFormat(), isDayInDateFormat(), isYearInDateFormat(), getMonthTranslationMap(), - getShortMonthTranslationMap(), translateMonthsToEnglish(), translateMonths() - -## [2.41.0] - 2018-11-14 - -### Added - -- static `ArrayUtil::insertBeforeKey` to modify execution order in hooks and callbacks - -## [2.40.1] - 2018-11-13 - -### Fixed - -- `huh.utils.template` do not cache non existing templates within `getTemplate()` - -## [2.40.0] - 2018-11-12 - -### Added - -- italian translation, thanks to `MicioMax` - -## [2.39.0] - 2018-11-08 - -### Added - -- DcaUtil::generateDcOperationsButtons() - -## [2.38.0] - 2018-11-08 - -### Added - -- `image` twig extension - -### Fixed - -- small bugs in `huh.utils.image` addToTemplateData() function - -## [2.37.0] - 2018-11-08 - -### Added - -- `target="_blank"` to `image.html.twig` template if `target` variable is true - -## [2.36.0] - 2018-11-08 - -### Added - -- DcaUtil::isDcMultilingual & DcaUtil::loadDc - -### Fixed - -- dc_multilingual support in ModelUtil - -## [2.35.0] - 2018-11-08 - -### Added - -- `huh.utils.image` addToTemplateData() now supports also `uuid` instead of `file path` value - -## [2.34.3] - 2018-11-07 - -### Fixed - -- AccordionUtil - -## [2.34.2] - 2018-11-07 - -### Fixed - -- dc_multilingual support in ModelUtil - -## [2.34.1] - 2018-11-07 - -### Fixed - -- linkTitle issue in `image.html.twig` - -## [2.34.0] - 2018-11-06 - -### Added - -- `huh.utils.url` method `isNewVisitor()` to detect if user referer and current host with scheme match - -## [2.33.0] - 2018-11-05 - -### Added - -- AccordionUtil - -## [2.32.0] - 2018-11-05 - -### Added - -- TemplateUtil::renderTwigTemplate() - -## [2.31.0] - 2018-11-02 - -### Added - -- TemplateUtil::getPageAliasAsCssClass() - -## [2.30.4] - 2018-11-01 - -### Fixed - -- TemplateUtil::getBundleTemplate -> did not respect the bundle order - -## [2.30.2] - 2018-10-22 - -### Fixed - -- `huh.utils.listener.insert_tags` was not public - -## [2.30.1] - 2018-10-12 - -### Fixed - -- surround `$file->imageSize` in `ImageUtil::addToTemplateData` with catch block, to prevent error messages for non - existing images to stop working site - -## [2.30.0] - 2018-09-25 - -### Added - -- Inserttag `twig` (Example:`{{twig::logo.html.twig::a:1:{s:3:"foo";s:3:"bar";}}}`) to render twig templates from - inserttags with custom serialized data - - -[@qzminski]: https://github.com/qzminski -[#65]: https://github.com/heimrichhannot/contao-utils-bundle/pull/65 -[#64]: https://github.com/heimrichhannot/contao-utils-bundle/pull/64 -[#63]: https://github.com/heimrichhannot/contao-utils-bundle/pull/63 -[#62]: https://github.com/heimrichhannot/contao-utils-bundle/pull/62 -[#61]: https://github.com/heimrichhannot/contao-utils-bundle/pull/61 -[#60]: https://github.com/heimrichhannot/contao-utils-bundle/pull/60 -[#59]: https://github.com/heimrichhannot/contao-utils-bundle/pull/59 -[#56]: https://github.com/heimrichhannot/contao-utils-bundle/pull/56 -[#55]: https://github.com/heimrichhannot/contao-utils-bundle/pull/55 -[#53]: https://github.com/heimrichhannot/contao-utils-bundle/pull/53 -[#51]: https://github.com/heimrichhannot/contao-utils-bundle/pull/51 -[#50]: https://github.com/heimrichhannot/contao-utils-bundle/pull/50 -[#47]: https://github.com/heimrichhannot/contao-utils-bundle/pull/47 -[#46]: https://github.com/heimrichhannot/contao-utils-bundle/pull/46 -[#45]: https://github.com/heimrichhannot/contao-utils-bundle/pull/45 -[#44]: https://github.com/heimrichhannot/contao-utils-bundle/pull/44 -[#43]: https://github.com/heimrichhannot/contao-utils-bundle/pull/43 -[#42]: https://github.com/heimrichhannot/contao-utils-bundle/pull/42 -[#40]: https://github.com/heimrichhannot/contao-utils-bundle/pull/40 -[#38]: https://github.com/heimrichhannot/contao-utils-bundle/pull/38 -[#37]: https://github.com/heimrichhannot/contao-utils-bundle/pull/37 -[#24]: https://github.com/heimrichhannot/contao-utils-bundle/pull/24 +## [3.0.0] - 2023-08-07 +- Changed: DcaUtil::getDcaFields() array options now throw error if not of type array +- Removed: ContainerUtil::isBundleActive() +- Removed: UrlUtil::removeQueryStringParameterToUrl() +- Changed: RoutingUtil::generateBackendRoute() route argument moved to options array diff --git a/README.md b/README.md index 01020802..0d883555 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,21 @@ [![](https://img.shields.io/packagist/v/heimrichhannot/contao-utils-bundle.svg)](https://packagist.org/packages/heimrichhannot/contao-utils-bundle) [![](https://img.shields.io/packagist/dt/heimrichhannot/contao-utils-bundle.svg)](https://packagist.org/packages/heimrichhannot/contao-utils-bundle) -[![Build Status](https://travis-ci.org/heimrichhannot/contao-utils-bundle.svg?branch=master)](https://travis-ci.org/heimrichhannot/contao-utils-bundle) -[![Coverage Status](https://coveralls.io/repos/github/heimrichhannot/contao-utils-bundle/badge.svg?branch=master)](https://coveralls.io/github/heimrichhannot/contao-utils-bundle?branch=master) +![example branch parameter](https://github.com/heimrichhannot/contao-utils-bundle/actions/workflows/ci.yml/badge.svg?branch=v3) +[![Coverage Status](https://coveralls.io/repos/github/heimrichhannot/contao-utils-bundle/badge.svg?branch=v3)](https://coveralls.io/github/heimrichhannot/contao-utils-bundle?branch=master) -This bundle offers various utility functionality for the Contao CMS. +Utils Bundle is a collection of many small helper to solve repeating task. +At the center there is a utils service allow access to all util function. +In addition, there are DcaField helpers, the Entity finder command and some nice twig filters. +## Features +* Utils-Service - A service allow access all bundles utils functions. +* DcaField registration - An nice api to add typical dca fields to your dca fields without repeating yourself or annoying order restrictions. + * AuthorField - Add an author field with automatic filling of the default value and optional frontend member support +* Entity Finder - A command to search for any contao entities in your database. +* Twig Filters -## Install +## Install 1. Install via composer: ``` @@ -16,242 +24,100 @@ This bundle offers various utility functionality for the Contao CMS. ``` 1. Update database -### Additional Requirements: - -Add following dependencies to your project composer file, if you want to use one of the following utils: - -Util | Dependency -----------------------|----------- -[~~PdfCreator - mPDF~~](docs/utils/pdf/pdfcreator.md) | `"mpdf/mpdf": "^7.0\|^8.0"` -huh.utils.pdf.preview | `"spatie/pdf-to-image": "^1.8"` or/and `"alchemy/ghostscript": "^4.1"` ## Usage -> We're currently in a process moving all services into the Utils namespace and make them all accessible from a new Utils service. - -This Bundle is a collection of utils to solve recurring tasks. See the [API Documentation](https://heimrichhannot.github.io/contao-utils-bundle/) to see all util-classes and -methods. - -The default way to access the util methods is the `Utils`-service (currently not all services are available there yet). The utils service is best used with dependency injection, but is also available from the service container as public service for usage in legacy code. - - ```php - use HeimrichHannot\UtilsBundle\Util\Utils; +### Utils service + +The Utils service is the core functionality of this bundle. It provides access to a lot of util functions help solving recurring tasks. +It's build as one service from which you can access all utils services. +The utils service is best used with dependency injection, but is also available from the service container as public service for usage in legacy code. +You can check the [API Documentation](https://heimrichhannot.github.io/contao-utils-bundle/namespaces/heimrichhannot-utilsbundle-util.html) to see all available functions. + +```php +use HeimrichHannot\UtilsBundle\Util\Utils; + +/** + * A class containing examples usage of utils services. Please don't expect it to be useful :) + */ +class MyClass{ + /** @var Utils */ + protected $utils; + + public function __construct(Utils $utils) { + $this->utils = $utils; + } - class MyClass{ - /** @var Utils */ - protected $utils; + public function someActions(): bool { + $dcaFields = $this->utils->dca()->getDcaFields('tl_content'); + $this->utils->array()->removeValue('headline', $dcaFields); + foreach ($dcaFields as $dcaField) { + echo $this->utils->string()->camelCaseToDashed($dcaField); + } - public function __construct(Utils $utils) { - $this->utils = $utils; - } - - public function someActions(): bool { - return $this->utils->string()->startsWith('Lorem ipsum dolor sit amet', 'Lorem'); - } - } - ``` - -To access services that are not available through the `Utils`-service, inject or call them directly. - -> Keep in mind that all services are about to be moved to the Utils namespace and will be deprecated (and removed in version 3.0) in the future. - - -Available [Service](src/Resources/config/services.yml) (as of version 2.131): - -``` -huh.utils.accordion -huh.utils.anonymizer -huh.utils.array -huh.utils.cache.database -huh.utils.cache.database_tree -huh.utils.cache.file -huh.utils.cache.remote_image_cache -huh.utils.choice.data_container -huh.utils.choice.field -huh.utils.choice.message -huh.utils.choice.model_instance -huh.utils.choice.twig_template -huh.utils.class -huh.utils.code -huh.utils.comparison -huh.utils.container -huh.utils.database -huh.utils.date -huh.utils.dca -huh.utils.encryption -huh.utils.file -huh.utils.file_archive -huh.utils.folder -huh.utils.form -huh.utils.image -huh.utils.location -huh.utils.member -huh.utils.model -huh.utils.module -huh.utils.pdf.preview -huh.utils.request.curl -huh.utils.routing -huh.utils.salutation -huh.utils.string -huh.utils.template -huh.utils.url -huh.utils.user -``` - -## Documentation - -[API Documentation](https://heimrichhannot.github.io/contao-utils-bundle/) - -### Utils - -[~~PdfCreator~~](docs/utils/pdf/pdfcreator.md) - ~~High-level API to create pdf files with PHP~~ (PDFCreator was moved into it's [own library](https://github.com/heimrichhannot/pdf-creator)) - -### Using with Webpack/Encore - -The bundle assets (js) are prepared to be used with webpack/encore. If you don't use [Foxy](https://github.com/fxpio/foxy), you need to add `"@hundh/contao-utils-bundle": "^1.5.0"` to your project package.json. - -See [package Repository](https://github.com/heimrichhannot-contao-components/contao-utils-bundle) for documentation. - -### JavaScript Utils - -#### Ajax Util -This util offers a shorthand for POST- and GET-ajax-requests. - -``` -static post(url, data, config) {...} -# or -static get(url, data, config) {...} -``` -Both take the following parameters - -name | description -| --- | --- | -| url | The url the request will be send to. | -| data | The data that is used for the request. It has to be passed as JSON. | -| config | An object that can hold the onSuccess-, onError-, beforeSubmit-, afterSubmit-callback and the request headers | - - -Parameters of config - -name | description -| --- | --- | -| onSuccess | Is called when the request was successfull. The request is passed as parameter. | -| onError | Is called when the request had an error. The request is passed as parameter. | -| beforeSubmit | Is called before the request is submitted. The url, data and config are passed as parameters. | -| afterSubmit | Is called before the request is submitted. The url, data and config are passed as parameters. | -| headers | The headers will be set when the request is initialized. | - -The contents of the config parameter are all optional. If you pass an empty config to the ajaxUtil a silent request will be processed. -The data will be transformed in the fitting format accordion wether you use a POST- or a GET-request. - -To use the shorthands import the utilsBundle into your script. After that just call the method an pass the needed parameters. - + $rootPageModel = $this->utils->request()->getCurrentRootPageModel(); + echo $this->utils->anonymize()->anonymizeEmail($rootPageModel->adminEmail); + + $groupUsers = $this->utils->user()->findActiveUsersByGroup([1,2]); + $this->utils->url()->addQueryStringParameterToUrl('user='.$groupUsers[0]->username, 'https://example.org'); + + if ($this->utils->container()->isBackend()) { + $where = $this->utils->database()->createWhereForSerializedBlob('dumbData', ['foo', 'bar']); + $model = $this->utils->model()->findOneModelInstanceBy('tl_content', [$where->createAndWhere()], [$where->values]); + echo '
utils->html()->generateAttributeString($model->getHtmlAttributes()).'>
'; + } +} ``` -import "@hundh/contao-utils-bundle"; -... -utilsBundle.ajax.get(url, data, config); -# or -utilsBundle.ajax.post(url, data, config); -``` +### Dca Fields -### Configuration +The bundle provides some common dca fields that can be used in your dca files. -Following configuration parameter can be overridden: +#### Author field -```yaml -# Default configuration for extension with alias: "huh_utils" -huh_utils: - tmp_folder: files/tmp/huh_utils_bundle +Add an author field to your dca. It will be initialized with the current backend user. On copy, it will be set to the current user. - # Default folder where to store pdf preview images. - pdfPreviewFolder: null - cache: +```php +# contao/dca/tl_example.php +use HeimrichHannot\UtilsBundle\Dca\AuthorField; - # Enable database tree cache is generated on cache warmup. - enable_generate_database_tree_cache: false - - # Load utils bundle assets. Default value will be changed to false in next major version. - enable_load_assets: true +AuthorField::register('tl_example'); ``` -## Twig Extensions - -These bundle add server twig filters: +You can pass additional options to adjust the field: -Filter | Parameter | Description ------------------ | --------- | ----------- -autolink | array options | Create a link if string is an url. -anonymize_email | - | Returns an anonymized email address. max.muster@example.org will be max.****@example.org -deserialize | bool force_array = false | Deserialize an serialized array (using `\Contao\StringUtil`) -download | download = true, data = [], template = "@HeimrichHannotContaoUtils\/download.html.twig" | -download_data | data = [] | -download_link | data = [] | -download_path | data = [] | -download_title | data = [] | -file_data | data = [], jsonSerializeOptions = [] -file_path | | -image | size = null, data = [], template = "image.html.twig" | -image_caption | | -image_data | size = null, data = [] | -image_gallery | template = "image_gallery.html.twig" -image_size | | -image_width | | -localized_date | format = null | -replace_inserttag | bool cache = true | Replace contao inserttag in twig string. - - -### Image Extension - -Use the image extension to resize contao images inside your twig template. +```php +# contao/dca/tl_example.php +use HeimrichHannot\UtilsBundle\Dca\AuthorField; +AuthorField::register('tl_example') + ->setType(AuthorField::TYPE_MEMBER) // can be one of TYPE_USER (default) or TYPE_MEMBER. Use TYPE_MEMBER to set a frontend member instead of a backend user + ->setFieldNamePrefix('example') // custom prefix for the field name + ->setUseDefaultLabel(false) // set to false to disable the default label and set a custom label in your dca translations + ->setExclude(false) // set the dca field exclude option + ->setSearch(false) // set the dca field search option + ->setFilter(false) // set the dca field filter option +; ``` -{% for box in boxes %} - {{ box.image|image([0,0,6],{'href' : box.url, 'linkTitle' : 'vmd.content.more.default'|trans})|raw }} -{% endfor %} -``` - -#### Arguments -- size: array containing width, height or image size config id (`Theme image size id`) -- data: additional image data like css class, linkTitle or href -### Download Extension -Use the download extension to render download elements, get download links, download path or download data. +### Entity Finder -``` -{% set downloadData = singleSRC|download_data %} {#get download data #} -{{ singleSRC|download(true,{'link': 'customLinkTitleHtml'}, '@HeimrichHannotContaoUtils/download.html.twig') {#render as download link and send file to browser link #} -{{ singleSRC|download(false,{'link': 'customLinkTitleHtml'}, '@HeimrichHannotContaoUtils/download.html.twig') {#render as download link and open download in news window #} -{{ singleSRC|download_link) {#get send file to browser download link #} -{{ singleSRC|download_path) {#get download file path #} -{{ singleSRC|download_title) {#get download title for link title attribute e.g. #} -``` +The entity finder is a command to search for any contao entities in your database. -## Insert tags +**[Entity finder](docs/commands/entity_finder.md)** -| Insert tag | Description | -|---|---| -| {{twig::*}} | This tag will be replaced with the rendered output of a given twig template, that can be sourced in `bundle/src/Resources/views` or contao root `/templates` directory (replace 1st * with template name e.g. `svg_logo_company` and 2nd * with serialized parameters that should be passed to the template) | -## Commands +### Twig Filters -**[Entity finder](docs/commands/entity_finder.md)** - A command to search for any contao entities in your database. +This bundle contains currently one twig filter: -**Image size creator** - Creates image size items for a given image size entity. +### anonymize_email -``` -Description: - Creates image size items for a given image size entity. Image size entities with existing image size items will be skipped. +Returns an anonymized email address. max.muster@example.org will be max.****@example.org -Usage: - huh:utils:create-image-size-items [ []] - -Arguments: - image-size-ids The comma separated ids of the image size. Skip the parameter in order to create image size items for all image size entities. - breakpoints The comma separated breakpoints as pixel amounts (defaults to "576,768,992,1200,1400"). [default: "576,768,992,1200,1400"] - -Example: - huh:utils:create-image-size-items 1,2 +```twig +{{ user.email|anonymize_email }} ``` \ No newline at end of file diff --git a/composer.json b/composer.json index 8bfa4ce6..a48f122d 100644 --- a/composer.json +++ b/composer.json @@ -6,37 +6,28 @@ "require": { "ext-dom": "*", "ext-simplexml": "*", - "php": "^7.2|^8.0", - "ausi/slug-generator": "^1.1", - "contao/core-bundle": "^4.4", + "php": "^8.1", + "contao/core-bundle": "^4.13 || ^5.0", "doctrine/dbal": "^2.13 || ^3.0", - "hackzilla/password-generator": "^1.4", "psr/log": "^1.0 || ^2.0 || ^3.0", - "soundasleep/html2text": "^1.1", - "symfony/cache": "^3.4|^4.4|^5.0", - "symfony/config": "^3.4|^4.4|^5.0", - "symfony/filesystem": "^3.4|^4.4|^5.0", - "symfony/http-foundation": "^3.4|^4.4|^5.0", - "symfony/http-kernel": "^3.4|^4.4|^5.0", - "symfony/monolog-bridge": "^3.4|^4.4|^5.0", - "symfony/string": "^5.2", - "tijsverkoyen/css-to-inline-styles": "^2.2", - "twig/twig": "^1.26 || ^2.7 || ^3.0" + "symfony/config": "^5.4 || ^6.0", + "symfony/event-dispatcher-contracts": "^1.0 || ^2.0 || ^3.0", + "symfony/filesystem": "^5.4 || ^6.0", + "symfony/http-foundation": "^5.4 || ^6.0", + "symfony/http-kernel": "^5.4 || ^6.0", + "symfony/string": "^5.2 || ^6.0", + "twig/twig": "^3.0" }, "require-dev": { - "contao/core-bundle": "4.9.*", - "contao/test-case": "^4.0", + "contao/test-case": "^4.0 || ^5.0", "contao/manager-plugin": "^2.0", "heimrichhannot/contao-test-utilities-bundle": "^0.1", "phpunit/phpunit": "^8.0 || ^9.0", "php-coveralls/php-coveralls": "^2.0", - "symfony/phpunit-bridge": "^3.2 || ^4.0 || ^5.0", + "symfony/phpunit-bridge": "^5.4 || ^6.0", "phpstan/phpstan": "^1.10", "phpstan/phpstan-symfony": "^1.2" }, - "conflict": { - "heimrichhannot/contao-encore-bundle": "<1.5" - }, "autoload": { "psr-4": { "HeimrichHannot\\UtilsBundle\\": "src/" @@ -51,18 +42,11 @@ "preferred-install": "dist", "allow-plugins": { "contao-components/installer": true, - "contao/manager-plugin": true + "contao/manager-plugin": true, + "php-http/discovery": false } }, "extra": { - "contao-manager-plugin": "HeimrichHannot\\UtilsBundle\\ContaoManager\\Plugin", - "foxy": true - }, - "suggest": { - "mpdf/mpdf": "Required by huh.utils.pdf.writer service in version ^7.0", - "setasign/fpdi-tcpdf": "Required by huh.utils.pdf.fpdi-writer service in version ^2.2", - "spatie/pdf-to-image": "Optional requirement for huh.utils.pdf.preview in version ^1.8. Please read the docs.", - "alchemy/ghostscript": "Optional requirement for huh.utils.pdf.preview in version ^4.1. Please read the docs.", - "eluceo/ical": "Optional requirement for IcsUtil in version ^0.16. Please read the docs." + "contao-manager-plugin": "HeimrichHannot\\UtilsBundle\\ContaoManager\\Plugin" } } diff --git a/config/services.yml b/config/services.yml new file mode 100644 index 00000000..8191054d --- /dev/null +++ b/config/services.yml @@ -0,0 +1,25 @@ +services: + _instanceof: + Symfony\Component\DependencyInjection\ContainerAwareInterface: + calls: + - ["setContainer", ["@service_container"]] + + HeimrichHannot\UtilsBundle\Util\Utils: + public: true + autowire: true + autoconfigure: true + + HeimrichHannot\UtilsBundle\: + resource: '../src/{Command,EntityFinder,EventListener,Twig,Util}/*' + exclude: '../src/Util/Utils.php' + autowire: true + autoconfigure: true + bind: + $projectDir: '%kernel.project_dir%' + $csrfTokenName: '%contao.csrf_token_name%' + + HeimrichHannot\UtilsBundle\Util\ContainerUtil: + autowire: true + autoconfigure: true + tags: + - { name: 'container.service_subscriber', key: 'monolog.logger.contao', id: 'monolog.logger.contao' } \ No newline at end of file diff --git a/docs/config/SamiDocConfig.php b/docs/config/SamiDocConfig.php index 8ee023d7..e254d984 100644 --- a/docs/config/SamiDocConfig.php +++ b/docs/config/SamiDocConfig.php @@ -14,7 +14,7 @@ ->name('*.php') ->exclude('Resources') ->exclude('ContaoManager') - ->exclude('HeimrichHannotContaoUtilsBundle') + ->exclude('HeimrichHannotUtilsBundle') ->in('./src'); return new Sami($iterator, [ diff --git a/docs/utils/pdf/pdf_writer.md b/docs/utils/pdf/pdf_writer.md deleted file mode 100644 index fe95fa4e..00000000 --- a/docs/utils/pdf/pdf_writer.md +++ /dev/null @@ -1,49 +0,0 @@ -# PDF Writer `huh.utils.pdf.writer` - -> PDF writer is deprecated and will be removed in a future version. Please use PDFCreator instead - -Example to create a custom pdf. -``` -$pdf = System::getContainer()->get('huh.utils.pdf.writer') - ->mergeConfig(['margin_left' => 15, 'margin_right' => 15, 'margin_top' => 15, 'margin_bottom' => 15]) - ->setHtml('

PDF-Example

') - ->addFontDirectories(StringUtil::trimsplit(',', 'files/pdf-fonts/fonts,web/build/fonts')) - ->setFileName('test.pdf'); - - if (null !== ($masterTemplatePath = System::getContainer()->get('huh.utils.file')->getPathFromUuid($this->readerConfigElement->syndicationPdfMasterTemplate))) { - $pdf->setTemplate($masterTemplatePath); - } - - $pdf->generate($this->download); -``` - -## Use custom fonts - -You can provide multiple paths to a directory containing additional fonts. -The directory **must contain** a `mpdf-config.php` file, that must return an array with the additional mpdf font-configuration. - -**Example:** - -You declare for instance the direcory `files/pdf-fonts/` that contains the `.ttf` or `.otf` or `.ttc` font files and the `mpdf-config.php`, than the following configuration should be made. - -``` -getDefaults(); -$fontData = $defaultFontConfig['fontdata']; - -return [ - 'fontdata' => $fontData +[ - 'roboto' => [ - 'R' => 'Roboto-Regular.ttf' - ], - 'fontawesome' => [ - 'R' => 'fontawesome-webfont.ttf' - ] - ], - 'default_font' => 'roboto' -]; -``` -*Example: mpdf-config.php* - -More Information: https://mpdf.github.io/fonts-languages/fonts-in-mpdf-7-x.html \ No newline at end of file diff --git a/docs/utils/pdf/pdfcreator.md b/docs/utils/pdf/pdfcreator.md deleted file mode 100644 index 1607cd2e..00000000 --- a/docs/utils/pdf/pdfcreator.md +++ /dev/null @@ -1,67 +0,0 @@ -# PdfCreator - -> PDFCreator was moved into it's [own library](https://github.com/heimrichhannot/pdf-creator). Please use that one instead of the version in utils bundle as it's marked deprecated now. - -PdfCreator is a high level API for PDF writing with PHP. - -## Example - -```php -use HeimrichHannot\UtilsBundle\PdfCreator\PdfCreatorFactory; -use HeimrichHannot\UtilsBundle\PdfCreator\Concrete\MpdfCreator; - -$pdf = PdfCreatorFactory::createInstance(MpdfCreator::getType()); -$pdf->setHtmlContent($this->compile()) - ->setFilename($this->getFileName()) - ->setFormat('A4') - ->setOrientation($pdf::ORIENTATION_PORTRAIT) - ->addFont( - "/path_to_project/assets/fonts/my_great_font.tff", - "myGreatFont", - $pdf::FONT_STYLE_REGUALAR, - "normal" - ) - ->setMargins(15, 10, 15,10) - ->setTemplateFilePath("/path_to_project/assets/pdf/mastertemplate.pdf") - ->setOutputMode($pdf::OUTPUT_MODE_DOWNLOAD) - ->render() -; -``` - -## Usage - -### Use callback for custom adjustments - -Due the high level approach not all specific library functionality could be supported. To add specific configuration, you use the callback mechanism comes with this api. - -Callback | Description --------- | ----------- -BeforeCreateLibraryInstanceCallback | Is evaluated before the library instance is created and allows to modifiy the constructor parameters. -BeforeOutputPdfCallback | Is evaluated before the library method to output the pdf is called and provide the library instance and the output method parameters. - -```php -use HeimrichHannot\UtilsBundle\PdfCreator\BeforeCreateLibraryInstanceCallback; -use HeimrichHannot\UtilsBundle\PdfCreator\BeforeOutputPdfCallback;use HeimrichHannot\UtilsBundle\PdfCreator\PdfCreatorFactory; -use HeimrichHannot\UtilsBundle\PdfCreator\Concrete\MpdfCreator; - -$pdf = PdfCreatorFactory::createInstance(MpdfCreator::getType()); - -$pdf->setBeforeCreateInstanceCallback(function (BeforeCreateLibraryInstanceCallback $callbackData) { - $parameter = $callbackData->getConstructorParameters(); - $parameter['config']['fonttrans'] = [ - 'rotis-sans-serif-w01-bold' => 'rotis-sans-serif', - 'rotissansserifw01-bold' => 'rotis-sans-serif', - ]; - $callbackData->setConstructorParameters($parameter); - return $callbackData; -}); - -$pdf->setBeforeOutputPdfCallback(function (BeforeOutputPdfCallback $callbackData) use ($pdf) { - $mpdf = $callbackData->getLibraryInstance(); - $mpdf->AddPage(); - $parameters = $callbackData->getOutputParameters(); - $parameters['name'] = 'custom_'.$pdf->getFilename(); - $callbackData->setOutputParameters($parameters); -}); - -``` \ No newline at end of file diff --git a/phpstan.neon b/phpstan.neon index e717ba4d..2c7feef9 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -3,11 +3,11 @@ parameters: paths: - src/Util - tests/Util -# universalObjectCratesClasses: + universalObjectCratesClasses: # - Contao\LayoutModel # - Contao\Model # - Contao\Template -# - PHPUnit\Framework\MockObject\MockObject + - PHPUnit\Framework\MockObject\MockObject # ignoreErrors: # - message: '#^Call to an undefined method PHPUnit\\Framework\\MockObject\\MockObject\:\:setName\(\)\.$#' # path: tests/EventListener/Contao/ParseTemplateListenerTest.php diff --git a/src/Accordion/AccordionUtil.php b/src/Accordion/AccordionUtil.php deleted file mode 100644 index 73c4aacf..00000000 --- a/src/Accordion/AccordionUtil.php +++ /dev/null @@ -1,226 +0,0 @@ -framework = $container->get('contao.framework'); - $this->container = $container; - } - - /** - * Adds the following flags to the template data: - * - first - * - last - * - parentId. - * - * This is needed if your want to group multiple single accordion elements into an accordion wrapper like in bootstrap 4. - * - * @param array $data Data describing the accordion. Usually this is taken from \Contao\Template::getData(). - * @param string $prefix The prefix for the flags - */ - public function structureAccordionSingle(array &$data, string $prefix = 'accordion_'): void - { - if (!isset($data['id']) || !isset($data['pid']) || !isset($data['ptable'])) { - return; - } - - $cacheKey = $data['ptable'].'_'.$data['pid']; - - if (!isset($this->accordionSingleCache[$cacheKey])) { - if (null !== ($elements = $this->container->get('huh.utils.model')->findModelInstancesBy( - 'tl_content', - [ - 'tl_content.ptable=?', - 'tl_content.pid=?', - 'tl_content.invisible!=1', - ], - [ - $data['ptable'], - $data['pid'], - ], - [ - 'order' => 'sorting ASC', - ] - ))) { - $lastOneIsAccordionSingle = false; - $elementGroup = []; - $this->accordionSingleCache[$cacheKey] = []; - - foreach ($elements as $i => $element) { - if ('accordionSingle' === $element->type) { - $elementGroup[] = $element->row(); - } else { - if ($lastOneIsAccordionSingle) { - $this->accordionSingleCache[$cacheKey][] = $elementGroup; - $elementGroup = []; - } - - $lastOneIsAccordionSingle = false; - - continue; - } - - $lastOneIsAccordionSingle = true; - - if ($i === \count($elements) - 1) { - $this->accordionSingleCache[$cacheKey][] = $elementGroup; - $elementGroup = []; - } - } - } - } - - if (isset($this->accordionSingleCache[$cacheKey]) && \is_array($this->accordionSingleCache[$cacheKey])) { - foreach ($this->accordionSingleCache[$cacheKey] as $elementGroup) { - foreach ($elementGroup as $i => $element) { - if ($data['id'] == $element['id']) { - if (0 === $i) { - $data[$prefix.'first'] = true; - } - - if ($i === \count($elementGroup) - 1) { - $data[$prefix.'last'] = true; - } - - $data[$prefix.'parentId'] = $elementGroup[0]['id']; - - break 2; - } - } - } - } - } - - /** - * Adds the following flags to the template data: - * - first - * - last - * - parentId. - * - * @param array $data Data describing the accordion. Usually this is taken from \Contao\Template::getData(). - * @param string $prefix The prefix for the flags - * - * @deprecated Use Utils service instead - * @codeCoverageIgnore - */ - public function structureAccordionStartStop(array &$data, string $prefix = 'accordion_') - { - if (!isset($data['id']) || !isset($data['pid'])) { - return; - } - - $cacheKey = $data['ptable'].'_'.$data['pid']; - - if (!isset($this->accordionStartStopCache[$cacheKey])) { - if (null !== ($elements = $this->container->get('huh.utils.model')->findModelInstancesBy( - 'tl_content', - [ - 'tl_content.ptable=?', - 'tl_content.pid=?', - 'tl_content.invisible!=1', - ], - [ - $data['ptable'], - $data['pid'], - ], - [ - 'order' => 'sorting ASC', - ] - ))) { - $lastOneIsAccordionStop = false; - $this->accordionStartStopCache[$cacheKey] = []; - - foreach ($elements as $i => $element) { - if ('accordionStart' === $element->type) { - if (\count($this->accordionStartStopCache[$cacheKey]) < 1) { - $this->accordionStartStopCache[$cacheKey][] = []; - } - - $this->accordionStartStopCache[$cacheKey][\count($this->accordionStartStopCache[$cacheKey]) - 1][] = $element->row(); - - $lastOneIsAccordionStop = false; - } elseif ('accordionStop' === $element->type) { - $this->accordionStartStopCache[$cacheKey][\count($this->accordionStartStopCache[$cacheKey]) - 1][] = $element->row(); - - $lastOneIsAccordionStop = true; - - continue; - } elseif ($lastOneIsAccordionStop) { - $this->accordionStartStopCache[$cacheKey][] = []; - $lastOneIsAccordionStop = false; - } - } - - // remove trailing empty arrays - $cleaned = []; - - foreach ($this->accordionStartStopCache[$cacheKey] as $elementGroup) { - if (!empty($elementGroup)) { - $cleaned[] = $elementGroup; - } - } - - $this->accordionStartStopCache[$cacheKey] = $cleaned; - } - } - - if (isset($this->accordionStartStopCache[$cacheKey]) && \is_array($this->accordionStartStopCache[$cacheKey])) { - foreach ($this->accordionStartStopCache[$cacheKey] as $elementGroup) { - foreach ($elementGroup as $i => $element) { - if ($data['id'] == $element['id']) { - if (0 === $i) { - $data[$prefix.'first'] = true; - } - - if ($i === \count($elementGroup) - 1) { - $data[$prefix.'last'] = true; - } - - $data[$prefix.'parentId'] = $elementGroup[0]['id']; - - break 2; - } - } - } - } - } -} diff --git a/src/Arrays/ArrayUtil.php b/src/Arrays/ArrayUtil.php deleted file mode 100644 index 11e63a49..00000000 --- a/src/Arrays/ArrayUtil.php +++ /dev/null @@ -1,256 +0,0 @@ -framework = $container->get('contao.framework'); - $this->container = $container; - } - - /** - * Filter an Array by given prefixes. - * - * @param array $prefixes - * - * @return array the filtered array or $arrData if $prefix is empty - */ - public function filterByPrefixes(array $data = [], $prefixes = []) - { - $extract = []; - - if (!\is_array($prefixes) || empty($prefixes)) { - return $data; - } - - foreach ($data as $key => $value) { - foreach ($prefixes as $prefix) { - if ($this->container->get('huh.utils.string')->startsWith($key, $prefix)) { - $extract[$key] = $value; - } - } - } - - return $extract; - } - - /** - * sort an array alphabetically by some key in the second layer (x => array(key1, key2, key3)). - */ - public function aasort(array &$array, $key) - { - $sorter = []; - $ret = []; - reset($array); - - foreach ($array as $ii => $va) { - $sorter[$ii] = $va[$key]; - } - - asort($sorter); - - foreach ($sorter as $ii => $va) { - $ret[$ii] = $array[$ii]; - } - - $array = $ret; - } - - /** - * Removes a value in an array. - * - * @param $value - * - * @return bool Returns true if the value has been found and removed, false in other cases - * - * @deprecated Use Utils service instead - * @codeCoverageIgnore - */ - public function removeValue($value, array &$array): bool - { - if (false !== ($intPosition = array_search($value, $array))) { - unset($array[$intPosition]); - - return true; - } - - return false; - } - - public function removePrefix(string $prefix, array $array): array - { - $result = []; - - foreach ($array as $key => $value) { - $result[$this->container->get('huh.utils.string')->removeLeadingString($prefix, $key)] = $value; - } - - return $result; - } - - /** - * Insert a value into an existing array by key name. - * - * @param array $current The target array - * @param string $key the existing target key in the array - * @param mixed $value the new value to be inserted - * @param int $offset offset for inserting the new value - * @param bool $strict use strict behavior for array search - * - * @deprecated Use utils service instead - * @codeCoverageIgnore - */ - public function insertInArrayByName(array &$current, string $key, $value, int $offset = 0, bool $strict = false) - { - if (false !== ($intIndex = array_search($key, array_keys($current), $strict))) { - array_insert($current, $intIndex + $offset, $value); - } - } - - /** - * Creates a stdClass from array. - * - * @param $array - */ - public function arrayToObject(array $array): \stdClass - { - $objResult = new \stdClass(); - - foreach ($array as $varKey => $varValue) { - $objResult->{$varKey} = $varValue; - } - - return $objResult; - } - - /** - * Returns a row of an multidimensional array by field value. Returns false, if no row found. - * - * @param string|int $key The array key (fieldname) - * @param mixed $value - * @param array $haystack a multidimensional array - * @param bool $strictType Specifiy if type comparison should be strict (type-safe) - * - * @return mixed - */ - public function getArrayRowByFieldValue($key, $value, array $haystack, bool $strictType = false) - { - foreach ($haystack as $row) { - if (!\is_array($row)) { - continue; - } - - if (!isset($row[$key])) { - continue; - } - - if (true === $strictType) { - if ($value === $row[$key]) { - return $row; - } - } else { - if ($row[$key] == $value) { - return $row; - } - } - } - - return false; - } - - /** - * Flattens an multidimensional array to one dimension. Keys are not preserved. - * - * @return array - */ - public function flattenArray(array $array) - { - $return = []; - array_walk_recursive( - $array, - function ($a) use (&$return) { - $return[] = $a; - } - ); - - return $return; - } - - /** - * Insert a new entry before an specific key in array. - * If key not exist, the new entry is added to the end of the array. - * Array is passed as reference. - * - * Usage example: contao config.php to make your hook entry run before another. - * - * @param array $array Array the new entry should inserted to - * @param string $key The key where the new entry should be added before - * @param string $newKey The key of the entry that should be added - * @param mixed $newValue The value of the entry that should be added - * - * @deprecated Use Utils::insertBeforeKey() instead - */ - public static function insertBeforeKey(array &$array, string $key, string $newKey, $newValue) - { - if (\array_key_exists($key, $array)) { - $new = []; - - foreach ($array as $k => $value) { - if ($k === $key) { - $new[$newKey] = $newValue; - } - $new[$k] = $value; - } - $array = $new; - } else { - $array[$newKey] = $newValue; - } - } - - public function implodeRecursive($var, $binary = false) - { - if (!\is_array($var)) { - return $binary ? StringUtil::binToUuid($var) : $var; - } - - if (!\is_array(current($var))) { - if ($binary) { - $var = array_map(function ($v) { - return $v ? (Validator::isBinaryUuid($v) ? StringUtil::binToUuid($v) : $v) : ''; - }, $var); - } - - return implode(', ', $var); - } - - $buffer = ''; - - foreach ($var as $k => $v) { - $buffer .= $k.': '.$this->implodeRecursive($v)."\n"; - } - - return trim($buffer); - } -} diff --git a/src/Cache/DatabaseCacheUtil.php b/src/Cache/DatabaseCacheUtil.php deleted file mode 100644 index 61c55bad..00000000 --- a/src/Cache/DatabaseCacheUtil.php +++ /dev/null @@ -1,107 +0,0 @@ - 'd', 'value' => 1]; - - /** - * @var ContaoFrameworkInterface - */ - protected $framework; - - /** - * @var Database - */ - protected $database; - /** - * @var ContainerInterface - */ - protected $container; - - public function __construct(ContainerInterface $container) - { - $this->framework = $container->get('contao.framework'); - $this->database = $this->framework->createInstance(Database::class); - $this->container = $container; - } - - /** - * Check for a given cache key. - */ - public function keyExists(string $key): bool - { - $result = $this->database->prepare('SELECT * FROM tl_db_cache WHERE cacheKey = ?')->execute($key); - - return $result->numRows > 0; - } - - /** - * Retrieve a value from cache. - * - * @return mixed - */ - public function getValue(string $key) - { - if (!Config::get('activateDbCache')) { - return false; - } - - // clean expired values at first (self-purification) - $this->database->prepare('DELETE FROM tl_db_cache WHERE expiration < ?')->execute(time()); - - $result = $this->database->prepare('SELECT * FROM tl_db_cache WHERE cacheKey = ?')->execute($key); - - if ($result->numRows > 0) { - return $result->cacheValue; - } - - return false; - } - - /** - * Store a given value to cache. - * - * @param $value - * - * @throws \Exception - * - * @return bool - */ - public function cacheValue(string $key, $value) - { - if (!Config::get('activateDbCache')) { - return false; - } - - if ($this->getValue($key)) { - throw new \Exception('Duplicate entry in tl_db_cache for key '.$key); - } - - $now = time(); - - $this->database->prepare('INSERT INTO tl_db_cache (tstamp, expiration, cacheKey, cacheValue) VALUES (?, ?, ?, ?)')->execute( - $now, - $now + $this->container->get('huh.utils.date')->getTimePeriodInSeconds( - StringUtil::deserialize(Config::get('dbCacheMaxTime'), true) - ), - $key, - $value - ); - - return true; - } -} diff --git a/src/Cache/DatabaseTreeCache.php b/src/Cache/DatabaseTreeCache.php deleted file mode 100644 index cb237e0c..00000000 --- a/src/Cache/DatabaseTreeCache.php +++ /dev/null @@ -1,327 +0,0 @@ -framework = $framework; - $this->filesystem = $filesystem; - $this->modelUtil = $modelUtil; - $this->database = $this->framework->createInstance(Database::class); - $this->cacheDir = \Contao\System::getContainer()->getParameter('kernel.cache_dir').'/tree_cache'; - $this->containerUtil = $containerUtil; - $this->requestStack = $requestStack; - } - - /** - * Generate tree cache. - */ - public function loadDataContainer($table) - { - if (!$this->database->tableExists($table)) { - return; - } - - if (!isset($GLOBALS['TL_DCA'][$table]) || !isset($GLOBALS['TL_DCA'][$table]['config']['treeCache']) || !\is_array($GLOBALS['TL_DCA'][$table]['config']['treeCache'])) { - return; - } - - if ($this->containerUtil->isInstall() || !$this->requestStack->getCurrentRequest()) { - return; - } - - if (!$this->isCompleteInstallation($table)) { - return; - } - - $configurations = $GLOBALS['TL_DCA'][$table]['config']['treeCache']; - - foreach ($configurations as $key => $config) { - $this->addConfigToTreeCache($table, $key, $config); - } - } - - public function addConfigToTreeCache(string $table, string $key, array $config = []) - { - $filename = $table.'_'.$key.'.php'; - - if (file_exists($this->cacheDir.'/'.$filename)) { - return; - } - - if (null === ($roots = $this->modelUtil->findModelInstancesBy($table, $config['columns'] ?? [], $config['values'] ?? [], $config['options']))) { - return; - } - - $tree = $this->generateCacheTree($table, $roots->fetchEach($key), $key, $config); - - $this->filesystem->dumpFile( - $this->cacheDir.'/'.$filename, - sprintf("getTreeCache($table, $key))) { - return $this->database->getChildRecords($ids, $table); - } - - foreach ($ids as $i => $id) { - if (!isset($tree[$id]) || !\is_array($tree[$id])) { - continue; - } - - $children = array_merge($children, $tree[$id]); - - if (1 === $maxLevels) { - continue; - } - - if ($maxLevels > 0 && $level > $maxLevels) { - return []; - } - - if (!empty($nested = self::getChildRecords($table, $tree[$id], $maxLevels, $key, $children, ++$level))) { - $children = $nested; - } else { - $depth = 0; - } - } - - return $children; - } - - /** - * Get all parent records for given child entity. - * - * @param string $table The database table - * @param int $id The current entity id - * @param int $maxLevels The max stop level - * @param string Custom index key (default: primary key from model) - * @param array $parents Internal children return array - * @param int $level Internal depth attribute - * - * @return array An array containing all children for given parent entities - */ - public function getParentRecords(string $table, int $id, $maxLevels = null, string $key = 'id', array $parents = [], int $level = 0): array - { - if (null === ($tree = $this->getTreeCache($table, $key))) { - return $this->database->getParentRecords($id, $table); - } - - if (isset($tree[$id]) && 0 === $level) { - $parents[] = $id; - } - - foreach ($tree as $pid => $ids) { - if (!\in_array($id, $ids)) { - continue; - } - - $parents[] = $pid; - - if (1 === $maxLevels) { - continue; - } - - if ($maxLevels > 0 && $level > $maxLevels) { - return []; - } - - if (!empty($nested = self::getParentRecords($table, $pid, $maxLevels, $key, $parents, ++$level))) { - $parents = $nested; - } else { - $level = 0; - } - } - - return $parents; - } - - /** - * Get the tree cache for a given table and key. - * - * @param string $table The database table - * @param string Custom index key (default: primary key from model) - */ - public function getTreeCache($table, $key): ?array - { - $filename = $table.'_'.$key.'.php'; - - if (file_exists($this->cacheDir.'/'.$filename)) { - self::$cache[$table.'_'.$key] = (include $this->cacheDir.'/'.$filename); - } - - return self::$cache[$table.'_'.$key] ?? null; - } - - /** - * Generate the flat cache tree. - * - * @param string $table The database table - * @param string $key Custom index key (default: primary key from model) - * @param array $ids Root identifiers (parent ids) - * @param array $config Tree config - * @param array $return Internal return array - * - * @return array The flat cache tree - */ - public function generateCacheTree(string $table, array $ids = [], string $key = 'id', array $config = [], $return = []): array - { - foreach ($ids as $id) { - if (null === ($children = $this->modelUtil->findModelInstancesBy($table, [$table.'.pid = ?'], $id, $config['options']))) { - $return[$id] = []; - - continue; - } - - while ($children->next()) { - $return[$children->pid][$children->{$key}] = $children->{$key}; - $return = $this->generateCacheTree($table, [$children->{$key}], $key, $config, $return); - } - } - - return $return; - } - - /** - * Generate all cache trees. - * - * @param $cacheDir - */ - public function generateAllCacheTree($cacheDir) - { - $this->cacheDir = $cacheDir; - - $tables = $this->database->listTables(); - - foreach ($tables as $table) { - // trigger loadDataContainer TL_HOOK - System::getContainer()->get('huh.utils.dca')->loadDc($table); - } - } - - /** - * Register a dca to the tree cache. - * - * @param string $table (The dca table) - * @param array $columns Parent sql filter columns (e.g. `tl_page.type`) - * @param array $values Parent sql filter values (e.g. `root` for `tl_page.type`) - * @param array $options SQL Options for sorting - * @param string Custom index key (default: primary key from model) - * - * @return bool Acknowledge state if register succeeded - */ - public function registerDcaToCacheTree(string $table, array $columns = [], array $values = [], array $options = [], string $key = 'id') - { - System::getContainer()->get('huh.utils.dca')->loadDc($table); - - if (!isset($GLOBALS['TL_DCA'][$table])) { - return false; - } - - $GLOBALS['TL_DCA'][$table]['config']['treeCache'][$key] = [ - 'columns' => $columns, - 'values' => $values, - 'options' => $options, - 'key' => $key, - ]; - - $GLOBALS['TL_DCA'][$table]['config']['ondelete_callback']['huh.utils.cache.database_tree'] = ['huh.utils.cache.database_tree', 'purgeCacheTree']; - $GLOBALS['TL_DCA'][$table]['config']['oncut_callback']['huh.utils.cache.database_tree'] = ['huh.utils.cache.database_tree', 'purgeCacheTree']; - $GLOBALS['TL_DCA'][$table]['config']['onsubmit_callback']['huh.utils.cache.database_tree'] = ['huh.utils.cache.database_tree', 'purgeCacheTree']; - $GLOBALS['TL_DCA'][$table]['config']['onrestore_callback']['huh.utils.cache.database_tree'] = ['huh.utils.cache.database_tree', 'purgeCacheTree']; - - return true; - } - - /** - * Purge the tree cache completely in order to take table relations into consideration. - */ - public function purgeCacheTree() - { - $this->filesystem->remove($this->cacheDir); - } - - private function isCompleteInstallation($table) - { - try { - $this->modelUtil->findOneModelInstanceBy($table, [], []); - } catch (\Exception $e) { - return false; - } - - return true; - } -} diff --git a/src/Cache/FileCache.php b/src/Cache/FileCache.php deleted file mode 100644 index 99cc8532..00000000 --- a/src/Cache/FileCache.php +++ /dev/null @@ -1,276 +0,0 @@ -cacheFolder = $container->getParameter('huh.utils.filecache.folder'); - $this->fileUtil = $container->get('huh.utils.file'); - $this->projectDir = $container->getParameter('kernel.project_dir'); - $this->generatePath(); - $this->container = $container; - } - - /** - * Checks if a cached file already exist in cache. Namespace is taken into account. - * - * @param string $identifier The identifier - * @param string $fileExtension If not set, a file with no file extension is searched - * - * @throws \Exception - * - * @deprecated Will be removed in 3.0. Use FileStorageUtil instead - * - * @return bool - */ - public function exist(string $identifier, string $fileExtension = '') - { - $fileName = $this->getCacheFileName($identifier); - $cachePath = $this->cacheFolderWithNamespace.'/'.$fileName; - - if (!empty($fileExtension)) { - $cachePath .= '.'.$fileExtension; - } - $file = new File($cachePath); - - if ($file->exists()) { - return true; - } - - return false; - } - - /** - * Get the file path for the given identifier. Namespace is taken into account. - * - * @param callable $saveCallback A callback handles the file save functionality. Get filepath, filename and the identifier as parameter. Expects a boolean return value. - * - * @throws \Exception - * - * @return bool|string returns the path of the cached file or false, if cached file could not be found - * - * @deprecated Will be removed in 3.0. Use FileStorageUtil instead - */ - public function get(string $identifier, string $fileExtension = '', callable $saveCallback = null) - { - $fileName = $this->getCacheFileName($identifier); - $cachePath = $this->cacheFolderWithNamespace.'/'.$fileName; - - if (!empty($fileExtension)) { - $fileName .= '.'.$fileExtension; - $cachePath .= '.'.$fileExtension; - } - $file = new File($cachePath); - - if (!$file->exists()) { - if (null !== $saveCallback) { - if ($saveCallback($identifier, $this->cacheFolderWithNamespace, $fileName)) { - return $this->cacheFolderWithNamespace.'/'.$fileName; - } - } - - return false; - } - - return $file->path; - } - - /** - * Generate a file name for cache. - * - * If a identifier is given, you get the resulting file name. - * If no identifier is given, if will return a unique file name without extension. - * - * @param string $identifier An identifier for the cache. For example be the source file name or path. If empty, a unique filename will be generated. - * @param string $prefix Adds a prefix to the generated name. Only if $identifier is empty. - * @param bool $more_entropy A longer name for the unique filename. Only if $identifier is empty. Default - * @param string $fileExtension optional: If set, the file extension will be appended to the generated file name - * - * @return string a unique filename for caching - * - * @deprecated Will be removed in 3.0. Use FileStorageUtil instead - */ - public function generateCacheName(string $identifier = '', string $prefix = '', bool $more_entropy = true, string $fileExtension = '') - { - if (empty($identifier)) { - $fileName = uniqid($prefix, $more_entropy); - } else { - $fileName = $this->getCacheFileName($identifier); - } - - if (!empty($fileExtension)) { - $fileName .= '.'.$fileExtension; - } - - return $fileName; - } - - /** - * Get the cache file name by the given identifier. - * - * @param $identifier - * - * @return string - * - * @deprecated Will be removed in 3.0. Use FileStorageUtil instead - */ - public function getCacheFileName($identifier) - { - return StringUtil::generateAlias($identifier); - } - - /** - * Same as generateCacheName, but returns complete path to cache. - * - * If no filename is given, you need to add the file extension by yourself! - * - * @param string $filename The filename of the file that should be cached. If empty, a unique filename will be generated. - * @param string $prefix Adds a prefix to the generated name. Only if $filename is empty. - * @param bool $more_entropy A longer name for the unique filename. Only if filename is empty. Default - * - * @return string the path including the filename to save the file to the cache - * - * @deprecated Will be removed in 3.0. Use FileStorageUtil instead - */ - public function getCacheFilePath(string $filename = '', string $prefix = '', bool $more_entropy = true) - { - return $this->cacheFolderWithNamespace.'/'.$this->generateCacheName($filename, $prefix, $more_entropy); - } - - /** - * Returns the absolute path to the cache folder. - * - * @return string - * - * @deprecated Will be removed in 3.0. Use FileStorageUtil instead - */ - public function getAbsoluteCachePath() - { - return $this->projectDir.'/'.$this->cacheFolderWithNamespace; - } - - /** - * @deprecated Will be removed in 3.0. Use FileStorageUtil instead - */ - public function getNamespace(): string - { - return $this->namespace; - } - - /** - * @deprecated Will be removed in 3.0. Use FileStorageUtil instead - */ - public function setNamespace(string $namespace) - { - $this->namespace = trim($namespace, ' /'); - $this->generatePath(); - } - - /** - * The cache folder (without namespace). - * - * @deprecated Will be removed in 3.0. Use FileStorageUtil instead - */ - public function getCacheFolder(): string - { - return $this->cacheFolder; - } - - /** - * Set cache folder (without namespace). - * - * @deprecated Will be removed in 3.0. Use FileStorageUtil instead - */ - public function setCacheFolder(string $cacheFolder) - { - $this->cacheFolder = $cacheFolder; - $this->generatePath(); - } - - /** - * Get the cache folder (including namespace). - * - * @deprecated Will be removed in 3.0. Use FileStorageUtil instead - */ - public function getCacheFolderWithNamespace(): string - { - return $this->cacheFolderWithNamespace; - } - - /** - * Recreates the path to the current cache folder. - */ - protected function generatePath() - { - $filesystem = new Filesystem(); - $path = $this->cacheFolder; - $path = trim($path, '/'); - - if (!empty($this->namespace)) { - $path .= '/'.$this->namespace; - } - - if (!$filesystem->exists($this->projectDir.'/'.$path)) { - $filesystem->mkdir($this->projectDir.'/'.$path); - } - $this->cacheFolderWithNamespace = $path; - } -} diff --git a/src/Cache/RemoteImageCache.php b/src/Cache/RemoteImageCache.php deleted file mode 100644 index 4652b3e7..00000000 --- a/src/Cache/RemoteImageCache.php +++ /dev/null @@ -1,78 +0,0 @@ -framework = $container->get('contao.framework'); - $this->container = $container; - } - - /** - * Get a remote file from cache and cache file, if not already in cache. - * - * Returns false, if remote file could not be fetched or given uuid is not valid. - * Else returns the url or, if $returnUuid is set true, the uuid of the image. - * - * @param string $identifier Used as filename of the cached image. Should be unique within the folder scope - * @param string $folder Folder path or uuid of the file - * @param string $remoteUrl The url of the cached (or to cache) file - * @param bool $returnUuid Return uuid instead of the path - * - * @throws \Exception - * - * @return bool|string - */ - public function get(string $identifier, $folder, $remoteUrl, $returnUuid = false) - { - $strFilename = $identifier.'.jpg'; - - if (Validator::isUuid($folder)) { - $objFolder = $this->container->get('huh.utils.file')->getFolderFromUuid($folder); - - if (false === $objFolder) { - return false; - } - $folder = $objFolder->value; - } - - $objFile = new File(rtrim($folder, '/').'/'.$strFilename); - - if ($objFile->exists() && $objFile->size > 0) { - return $returnUuid ? $objFile->getModel()->uuid : $objFile->path; - } - - $strContent = $this->container->get('huh.utils.request.curl')->request($remoteUrl); - - if (!$strContent || !\is_string($strContent)) { - return false; - } - - $objFile->write($strContent); - $objFile->close(); - - return $returnUuid ? $objFile->getModel()->uuid : $objFile->path; - } -} diff --git a/src/Cache/UtilCacheWarmer.php b/src/Cache/UtilCacheWarmer.php deleted file mode 100644 index 3e5b5345..00000000 --- a/src/Cache/UtilCacheWarmer.php +++ /dev/null @@ -1,116 +0,0 @@ -filesystem = $filesystem; - $this->connection = $connection; - $this->templateUtil = $templateUtil; - $this->framework = $framework; - } - - /** - * {@inheritdoc} - */ - public function warmUp($cacheDir) - { - if (!$this->isCompleteInstallation()) { - return; - } - - $this->framework->initialize(); - - $this->generateTemplateMapper($cacheDir); - $this->generateDatabaseTreeCache($cacheDir); - } - - /** - * {@inheritdoc} - */ - public function isOptional() - { - return true; - } - - /** - * Checks if the installation is complete. - * - * @return bool - */ - private function isCompleteInstallation() - { - try { - $this->connection->query('SELECT COUNT(*) FROM tl_page'); - } catch (\Exception $e) { - return false; - } - - return true; - } - - /** - * Generates the template mapper array. - * - * @param string $cacheDir The cache directory - */ - private function generateTemplateMapper($cacheDir) - { - $files = $this->templateUtil->getAllTemplates(); - - if (empty($files)) { - return; - } - - $this->filesystem->dumpFile( - $cacheDir.'/contao/config/twig-templates.php', - sprintf("get('huh.utils.cache.database_tree')->generateAllCacheTree($cacheDir); - } -} diff --git a/src/Choice/AbstractChoice.php b/src/Choice/AbstractChoice.php deleted file mode 100644 index 87c925c8..00000000 --- a/src/Choice/AbstractChoice.php +++ /dev/null @@ -1,137 +0,0 @@ -framework = $framework; - } - - /** - * @return mixed - */ - public function getContext() - { - return $this->context; - } - - /** - * @param mixed $context - */ - public function setContext($context) - { - $this->context = $context; - - return $this; - } - - public function getChoices($context = []) - { - if (!$context) { - $context = []; - } - - $this->setContext($context); - - $choices = $this->collect(); - - return $choices; - } - - public function getCachedChoices($context = []) - { - if (null === $context) { - $context = []; - } - - if (\is_array($context) && !isset($context['locale']) && ($request = System::getContainer()->get('request_stack')->getCurrentRequest())) { - $context['locale'] = $request->getLocale(); - } - - $this->setContext($context); - - // disable cache while in debug mode or backend - if (true === System::getContainer()->getParameter('kernel.debug') || System::getContainer()->get('huh.utils.container')->isBackend()) { - return $this->getChoices($this->getContext()); - } - - $this->cacheKey = 'choice.'.preg_replace('#Choice$#', '', (new \ReflectionClass($this))->getShortName()); - - // add unique identifier based on context - if (null !== $this->getContext() && false !== ($json = json_encode($this->getContext(), \JSON_FORCE_OBJECT))) { - $this->cacheKey .= '.'.sha1($json); - } - - if (!$this->cache) { - $this->cache = new FilesystemAdapter('', 0, System::getContainer()->get('kernel')->getCacheDir()); - } - - $cache = $this->cache->getItem($this->cacheKey); - - if (!$cache->isHit() || empty($cache->get())) { - $choices = $this->getChoices($this->getContext()); - - if (!\is_array($choices)) { - $choices = []; - } - - // TODO: clear cache on delegated field save_callback - $cache->expiresAfter(\DateInterval::createFromDateString('4 hour')); - $cache->set($choices); - - $this->cache->save($cache); - } - - return $cache->get(); - } - - /** - * @return array - */ - abstract protected function collect(); -} diff --git a/src/Choice/DataContainerChoice.php b/src/Choice/DataContainerChoice.php deleted file mode 100644 index e638a8e9..00000000 --- a/src/Choice/DataContainerChoice.php +++ /dev/null @@ -1,40 +0,0 @@ -get('contao.resource_finder')->findIn('dca')->name('tl_*.php') as $file) { - /** @var \SplFileInfo $file */ - $name = $file->getBasename('.php'); - - if (\in_array($name, $choices)) { - continue; - } - - $choices[] = $name; - } - } catch (\InvalidArgumentException $e) { - } - - sort($choices); - - return $choices; - } -} diff --git a/src/Choice/FieldChoice.php b/src/Choice/FieldChoice.php deleted file mode 100644 index b4e224e5..00000000 --- a/src/Choice/FieldChoice.php +++ /dev/null @@ -1,24 +0,0 @@ -getContext(); - - return System::getContainer()->get('huh.utils.dca')->getFields($context['dataContainer'], $context); - } -} diff --git a/src/Choice/MessageChoice.php b/src/Choice/MessageChoice.php deleted file mode 100644 index 0333067b..00000000 --- a/src/Choice/MessageChoice.php +++ /dev/null @@ -1,46 +0,0 @@ -getContext(); - - if (!\is_array($prefixes)) { - $prefixes = [$prefixes]; - } - - $translator = System::getContainer()->get('translator'); - - $catalog = $translator->getCatalogue(); - $all = $catalog->all(); - $messages = $all['messages']; - - if (!\is_array($messages)) { - return $choices; - } - - $choices = System::getContainer()->get('huh.utils.array')->filterByPrefixes($messages, $prefixes); - - foreach ($choices as $key => $value) { - $choices[$key] = $value.'['.$key.']'; - } - - return $choices; - } -} diff --git a/src/Choice/ModelInstanceChoice.php b/src/Choice/ModelInstanceChoice.php deleted file mode 100644 index d752a3dd..00000000 --- a/src/Choice/ModelInstanceChoice.php +++ /dev/null @@ -1,103 +0,0 @@ -getContext(); - $choices = []; - - $instances = System::getContainer()->get('huh.utils.model')->findModelInstancesBy($context['dataContainer'], $context['columns'] ?? [], $context['values'] ?? null, isset($context['options']) ? (\is_array($context['options']) ? $context['options'] : []) : []); - - if (null === $instances) { - return $choices; - } - - while ($instances->next()) { - $labelPattern = $context['labelPattern'] ?? null; - - if (!$labelPattern) { - $labelPattern = 'ID %id%'; - - switch ($context['dataContainer']) { - case 'tl_member': - $labelPattern = '%firstname% %lastname% (ID %id%)'; - - break; - - default: - foreach (static::TITLE_FIELDS as $titleField) { - if (isset($GLOBALS['TL_DCA'][$context['dataContainer']]['fields'][$titleField])) { - $labelPattern = '%'.$titleField.'% (ID %id%)'; - - break; - } - } - - break; - } - } - - $skipFormatting = $context['skipFormatting'] ?? false; - - if (!$skipFormatting) { - $dca = &$GLOBALS['TL_DCA']['tl_submission']; - $dc = new DC_Table_Utils($context['dataContainer']); - $dc->id = $instances->id; - $dc->activeRecord = $instances->current(); - - $label = preg_replace_callback( - '@%([^%]+)%@i', - function ($matches) use ($instances, $dca, $context, $dc) { - return System::getContainer()->get('huh.utils.form')->prepareSpecialValueForOutput( - $matches[1], - $instances->{$matches[1]}, - $dc - ); - }, - $labelPattern - ); - } else { - $label = preg_replace_callback( - '@%([^%]+)%@i', - function ($matches) use ($instances) { - return $instances->{$matches[1]}; - }, - $labelPattern - ); - } - - if (null !== ($callbackLabel = System::getContainer()->get('huh.utils.dca')->getConfigByArrayOrCallbackOrFunction($context, 'label', [$label, $instances->row(), $context]))) { - $label = $callbackLabel; - } - - $choices[$instances->id] = $label; - } - - if (!isset($context['skipSorting']) || !$context['skipSorting']) { - natcasesort($choices); - } - - return $choices; - } -} diff --git a/src/Choice/TwigTemplateChoice.php b/src/Choice/TwigTemplateChoice.php deleted file mode 100644 index 879ea475..00000000 --- a/src/Choice/TwigTemplateChoice.php +++ /dev/null @@ -1,62 +0,0 @@ -getContext(); - - if (!\is_array($prefixes)) { - $prefixes = [$prefixes]; - } - - $prefixes = array_filter($prefixes); - - $kernel = System::getContainer()->get('kernel'); - - $bundles = $kernel->getBundles(); - $pattern = !empty($prefixes) ? ('/(^'.implode('|^', $prefixes).').*twig/') : '*.twig'; - - if (\is_array($bundles)) { - foreach ($bundles as $key => $value) { - $path = $kernel->locateResource("@$key"); - $finder = new Finder(); - $finder->in($path); - $finder->files()->name($pattern); - $twigKey = preg_replace('/Bundle$/', '', $key); - - foreach ($finder as $val) { - $explodurl = explode('Resources'.\DIRECTORY_SEPARATOR.'views'.\DIRECTORY_SEPARATOR, $val->getRelativePathname()); - $string = end($explodurl); - $choices[$val->getBasename('.html.twig')] = "@$twigKey/$string"; - } - } - } - - if (!System::getContainer()->has('huh.utils.container')) { - return $choices; - } - - foreach ($prefixes as $prefix) { - $choices = array_merge($choices, System::getContainer()->get('huh.utils.template')->getTemplateGroup($prefix, 'html.twig')); - } - - return $choices; - } -} diff --git a/src/Classes/ClassUtil.php b/src/Classes/ClassUtil.php deleted file mode 100644 index c06370cb..00000000 --- a/src/Classes/ClassUtil.php +++ /dev/null @@ -1,275 +0,0 @@ -arrayUtil = $arrayUtil; - $this->stringUtil = $stringUtil; - } - - /** - * @return array - */ - public function getParentClasses(string $class, array $parents = []) - { - $strParent = get_parent_class($class); - - if ($strParent) { - $parents[] = $strParent; - - $parents = $this->getParentClasses($strParent, $parents); - } - - return $parents; - } - - /** - * Filter class constants by given prefixes and return the extracted constants. - * - * @param string $class the class that should be searched for constants in - * @param array $prefixes an array of prefixes that should be used to filter the class constants - * @param bool $returnValueAsKey boolean Return the extracted array keys from its value, if true - * - * @throws \ReflectionException - * - * @return array the extracted constants as array - */ - public function getConstantsByPrefixes(string $class, array $prefixes = [], bool $returnValueAsKey = true) - { - $arrExtract = []; - - if (!class_exists($class)) { - return $arrExtract; - } - - $objReflection = new \ReflectionClass($class); - $arrConstants = $objReflection->getConstants(); - - if (!\is_array($arrConstants)) { - return $arrExtract; - } - - $arrExtract = $this->arrayUtil->filterByPrefixes($arrConstants, $prefixes); - - return $returnValueAsKey ? array_combine($arrExtract, $arrExtract) : $arrExtract; - } - - /** - * Returns all classes in the given namespace. - * - * @return array - */ - public function getClassesInNamespace(string $namespace) - { - $arrOptions = []; - - foreach (get_declared_classes() as $strName) { - if ($this->stringUtil->startsWith($strName, $namespace)) { - $arrOptions[$strName] = $strName; - } - } - - asort($arrOptions); - - return $arrOptions; - } - - /** - * Returns all children of a given class. - * - * @param string $strNamespace - * - * @return array - */ - public function getChildClasses(string $qualifiedClassName) - { - $arrOptions = []; - - foreach (get_declared_classes() as $strName) { - if (\in_array($qualifiedClassName, $this->getParentClasses($strName))) { - $arrOptions[$strName] = $strName; - } - } - - asort($arrOptions); - - return $arrOptions; - } - - /** - * @param $method - * @param \ReflectionClass $rc - * @return int|null - */ - private static function getMethodNameStartIndex($method, \ReflectionClass $rc): ?int - { - $len = 3; - $prefix = substr($method->name, 0, $len); - - /** vvv Prefixes with length 3. vvv */ - - // get{MethodName}() - if ('get' === $prefix) - return $len; - - // has{MethodName}() - if ('has' === $prefix) { - $name = ucfirst(substr($method->name, 3, strlen($method->name))); - if ($rc->hasMethod("is$name") || $rc->hasMethod("get$name")) - return 0; - return $len; - } - - /** vvv Prefixes with length 2. vvv */ - - $len = 2; - $prefix = substr($method->name, 0, $len); - - // is{MethodName}() - if ('is' === $prefix) { - $name = ucfirst(substr($method->name, 2, strlen($method->name))); - if ($rc->hasMethod("has$name") || $rc->hasMethod("get$name")) - return 0; - return $len; - } - - return null; - } - - /** - * Serialize a class object to JSON by iterating over all public getters (get(), is(), ...). - * - * @param $object - * @param array $data - * @param array $options - * - * @throws \ReflectionException if the class or method does not exist - */ - public function jsonSerialize($object, $data = [], $options = []): array - { - $class = \get_class($object); - - $rc = new \ReflectionClass($object); - - // get values of properties - if (isset($options['includeProperties']) && $options['includeProperties']) { - foreach ($rc->getProperties() as $reflectionProperty) { - $propertyName = $reflectionProperty->getName(); - - $property = $rc->getProperty($propertyName); - - if (isset($options['ignorePropertyVisibility']) && $options['ignorePropertyVisibility']) { - $property->setAccessible(true); - } - - $data[$propertyName] = $property->getValue($object); - - if (\is_object($data[$propertyName])) { - if (!($data[$propertyName] instanceof \JsonSerializable)) { - unset($data[$propertyName]); - - continue; - } - - $data[$propertyName] = $this->jsonSerialize($data[$propertyName]); - } - } - } - - if (isset($options['ignoreMethods']) && $options['ignoreMethods']) { - return $data; - } - - // get values of methods - if (isset($options['ignoreMethodVisibility']) && $options['ignoreMethodVisibility']) { - $methods = $rc->getMethods(); - } else { - $methods = $rc->getMethods(\ReflectionMethod::IS_PUBLIC); - } - - // add all public getter Methods - foreach ($methods as $method) { - - $start = self::getMethodNameStartIndex($method, $rc); - if ($start === null) continue; - - // skip methods with parameters - $rm = new \ReflectionMethod($class, $method->name); - - if ($rm->getNumberOfRequiredParameters() > 0) { - continue; - } - - if (isset($options['skippedMethods']) && \is_array($options['skippedMethods']) && \in_array($method->name, $options['skippedMethods'])) { - continue; - } - - $property = lcfirst(substr($method->name, $start)); - - if (!$method->isPublic()) { - $method->setAccessible(true); - $data[$property] = $method->invoke($object); - } else { - $data[$property] = $object->{$method->name}(); - } - - if (\is_object($data[$property])) { - if (!($data[$property] instanceof \JsonSerializable)) { - unset($data[$property]); - - continue; - } - $data[$property] = $this->jsonSerialize($data[$property]); - } - } - - return $data; - } - - /** - * Calls an object's method which is inaccessible. - * - * @param $entity - * - * @throws \ReflectionException - * - * @return mixed|null - */ - public function callInaccessibleMethod($entity, string $method) - { - $rc = new \ReflectionClass($entity); - - if ($rc->hasMethod($method)) { - $method = $rc->getMethod($method); - $method->setAccessible(true); - - return $method->invoke($entity); - } - - return null; - } -} diff --git a/src/Command/CreateImageSizeItemsCommand.php b/src/Command/CreateImageSizeItemsCommand.php deleted file mode 100644 index 8cf7c522..00000000 --- a/src/Command/CreateImageSizeItemsCommand.php +++ /dev/null @@ -1,179 +0,0 @@ -contaoFramework = $contaoFramework; - $this->utils = $utils; - } - - /** - * {@inheritdoc} - */ - protected function configure() - { - $this - ->setDescription('Creates image size items for a given image size entity. Image size entities with existing image size items will be skipped.') - ->addArgument( - 'image-size-ids', - InputArgument::OPTIONAL, - 'The comma separated ids of the image size. Skip the parameter in order to create image size items for all image size entities.' - ) - ->addArgument( - 'breakpoints', - InputArgument::OPTIONAL, - 'The comma separated breakpoints as pixel amounts (defaults to "576,768,992,1200,1400").', implode(',', static::DEFAULT_BREAKPOINTS) - ); - } - - /** - * {@inheritdoc} - */ - protected function execute(InputInterface $input, OutputInterface $output) - { - $this->contaoFramework->initialize(); - - $io = new SymfonyStyle($input, $output); - - // prevent sql injection - $imageSizeIds = preg_replace('@[^0-9,]@i', '', $input->getArgument('image-size-ids')); - - $imageSizeIds = explode(',', $imageSizeIds); - $breakpoints = explode(',', $input->getArgument('breakpoints')); - - $this->createImageSizes($io, $breakpoints, $imageSizeIds); - - return 0; - } - - /** - * @param ImageSizeModel $imageSize - */ - protected function createItem(Model $imageSize, int $index, int $breakpoint, ?int $nextBreakpoint, int $mode) - { - $item = new ImageSizeItemModel(); - - $item->tstamp = time(); - $item->pid = $imageSize->id; - $item->sorting = 128 * $index; - $item->densities = $imageSize->densities; - $item->resizeMode = $imageSize->resizeMode; - - switch ($mode) { - case static::MODE_FIRST: - $item->media = '(max-width: '.($breakpoint - 1).'px)'; - $item->width = $breakpoint - 1; - - break; - - case static::MODE_INTERMEDIATE: - $item->media = '(min-width: '.$breakpoint.'px) and (max-width: '.($nextBreakpoint - 1).'px)'; - $item->width = $nextBreakpoint - 1; - - break; - - case static::MODE_LAST: - $item->media = '(min-width: '.$breakpoint.'px)'; - $item->width = max($breakpoint, $imageSize->width); - - break; - } - - $item->height = ($item->width * $imageSize->height) / $imageSize->width; - - $item->save(); - } - - private function createImageSizes(SymfonyStyle $io, array $breakpoints, array $imageSizeIds = []) - { - sort($breakpoints); - - $creationCount = 0; - $columns = (empty($imageSizeIds) ? [] : ['tl_image_size.id IN ('.implode(',', $imageSizeIds).')']); - - if (null === ($imageSizes = $this->utils->model()->findModelInstancesBy('tl_image_size', $columns, []))) { - $io->error('No image sizes found for the given ids.'); - - return false; - } - - /** @var ImageSizeModel $imageSize */ - foreach ($imageSizes as $imageSize) { - $existingItems = $this->utils->model()->findModelInstancesBy('tl_image_size_item', ['tl_image_size_item.pid=?'], [$imageSize->id]); - - if (null !== $existingItems) { - $io->warning('Skipping image size ID '.$imageSize->id.' because it already has existing image size items.'); - - continue; - } - - $j = 0; - - // first - $this->createItem($imageSize, $j++, $breakpoints[0], null, static::MODE_FIRST); - ++$creationCount; - - // intermediates - foreach ($breakpoints as $i => $breakpoint) { - if ($i === \count($breakpoints) - 1) { - continue; - } - - $this->createItem($imageSize, $j++, $breakpoint, $breakpoints[$i + 1], static::MODE_INTERMEDIATE); - - ++$creationCount; - } - - // last - $this->createItem($imageSize, $j++, $breakpoints[\count($breakpoints) - 1], null, static::MODE_LAST); - ++$creationCount; - } - - $io->success($creationCount.' image size items have been created.'); - - return true; - } -} diff --git a/src/Command/EntityFinderCommand.php b/src/Command/EntityFinderCommand.php index 454c4aa8..d928f546 100644 --- a/src/Command/EntityFinderCommand.php +++ b/src/Command/EntityFinderCommand.php @@ -30,42 +30,26 @@ class EntityFinderCommand extends Command { protected static $defaultName = 'huh:utils:entity_finder'; + protected static $defaultDescription = 'A command to find where an entity is included.'; - /** @var ContaoFramework */ - private $contaoFramework; - /** - * @var EventDispatcherInterface - */ - private $eventDispatcher; - /** - * @var Connection - */ - private $connection; - /** - * @var EntityFinderHelper - */ - private $entityFinderHelper; - - public function __construct(ContaoFramework $contaoFramework, EventDispatcherInterface $eventDispatcher, Connection $connection, EntityFinderHelper $entityFinderHelper) + public function __construct( + private ContaoFramework $contaoFramework, + private EventDispatcherInterface $eventDispatcher, + private Connection $connection, + private EntityFinderHelper $entityFinderHelper) { parent::__construct(); - - $this->contaoFramework = $contaoFramework; - $this->eventDispatcher = $eventDispatcher; - $this->connection = $connection; - $this->entityFinderHelper = $entityFinderHelper; } - protected function configure() + protected function configure(): void { $this ->addArgument('table', InputArgument::REQUIRED, 'The database table') ->addArgument('id', InputArgument::REQUIRED, 'The entity id or alias (id is better supported).') - ->setDescription('A command to find where an entity is included.') ; } - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $this->contaoFramework->initialize(); $io = new SymfonyStyle($input, $output); diff --git a/src/Comparison/CompareUtil.php b/src/Comparison/CompareUtil.php deleted file mode 100644 index 6e79ae6e..00000000 --- a/src/Comparison/CompareUtil.php +++ /dev/null @@ -1,137 +0,0 @@ -framework = $framework; - } - - /** - * @param $value1 - * @param $value2 - */ - public function compareValue(string $operator, $value1, $value2 = null): bool - { - if (\in_array($operator, self::PHP_SINGLE_VALUE_OPERATORS)) { - return $this->compareSingleValue($value1, $operator); - } - - if (!$value1 || !$value2) { - return false; - } - - switch ($operator) { - case self::PHP_OPERATOR_EQUAL: - return $value1 == $value2; - - break; - - case self::PHP_OPERATOR_UNEQUAL: - return $value1 != $value2; - - break; - - case self::PHP_OPERATOR_LIKE: - return false !== strpos($value1, $value2); - - break; - - case self::PHP_OPERATOR_UNLIKE: - return false === strpos($value1, $value2); - - break; - - case self::PHP_OPERATOR_IN_ARRAY: - return \in_array($value2, $value1); - - break; - - case self::PHP_OPERATOR_NOT_IN_ARRAY: - return !\in_array($value2, $value1); - - break; - - case self::PHP_OPERATOR_LOWER: - return $value1 < $value2; - - break; - - case self::PHP_OPERATOR_LOWER_EQUAL: - return $value1 <= $value2; - - break; - - case self::PHP_OPERATOR_GREATER: - return $value1 > $value2; - - break; - - case self::PHP_OPERATOR_GREATER_EQUAL: - return $value1 >= $value2; - } - } - - /** - * @param $value - */ - public function compareSingleValue($value, string $operator): bool - { - switch ($operator) { - case self::PHP_OPERATOR_IS_NULL: - return null === $value; - - break; - - case self::PHP_OPERATOR_IS_NOT_NULL: - return null !== $value; - } - } -} diff --git a/src/Container/ContainerUtil.php b/src/Container/ContainerUtil.php deleted file mode 100644 index 28c823ea..00000000 --- a/src/Container/ContainerUtil.php +++ /dev/null @@ -1,272 +0,0 @@ -container = $container; - $this->utils = $utils; - } - - /** - * Returns the active bundles. - * - * @codeCoverageIgnore - * - * @deprecated Use kernel.bundles parameter or KernelInterface::getBundles() - */ - public function getActiveBundles(): array - { - return $this->container->getParameter('kernel.bundles'); - } - - /** - * Checks if some bundle is active. Pass in the class name (e.g. 'HeimrichHannot\FilterBundle\HeimrichHannotContaoFilterBundle' or the legacy Contao 3 name like 'news'). - * - * @return bool - * - * @codeCoverageIgnore - * - * @deprecated Use utils service instead - */ - public function isBundleActive(string $bundleName) - { - return $this->utils->container()->isBundleActive($bundleName); - } - - /** - * @return bool - * - * @codeCoverageIgnore - * - * @deprecated Use utils service instead - */ - public function isBackend() - { - return $this->utils->container()->isBackend(); - } - - /** - * @return bool - * - * @codeCoverageIgnore - * - * @deprecated Use utils service instead - */ - public function isFrontend() - { - return $this->utils->container()->isFrontend(); - } - - /** - * @return bool - * - * @codeCoverageIgnore - * - * @deprecated Use utils service instead - */ - public function isFrontendCron() - { - return $this->utils->container()->isFrontendCron(); - } - - /** - * @return bool - * - * @codeCoverageIgnore - * - * @deprecated Use utils service instead - */ - public function isInstall() - { - return $this->utils->container()->isInstall(); - } - - /** - * @return bool - * - * @codeCoverageIgnore - * - * @deprecated Use utils service instead - */ - public function isDev() - { - return $this->utils->container()->isDev(); - } - - /** - * @return mixed - * - * @codeCoverageIgnore - * - * @deprecated Use RequestStack::getCurrentRequest() instead - */ - public function getCurrentRequest() - { - return $this->container->get('request_stack')->getCurrentRequest(); - } - - /** - * @param string $category Use constants in ContaoContext - * - * @codeCoverageIgnore - * - * @deprecated Use utils service instead - */ - public function log(string $text, string $function, string $category) - { - $this->utils->container()->log($text, $function, $category); - } - - /** - * Returns the project root path. - * - * @return mixed - * - * @codeCoverageIgnore - * - * @deprecated Use KernelInterface::getProjectDir or kernel.project_dir parameter - */ - public function getProjectDir() - { - return $this->container->getParameter('kernel.project_dir'); - } - - /** - * Returns the web folder path. - * - * @return mixed - * - * @codeCoverageIgnore - * - * @deprecated Use contao.web_dir parameter - */ - public function getWebDir() - { - return $this->container->getParameter('contao.web_dir'); - } - - /** - * Returns the path to the bundle in vendor folder - * Attention: resolves symlinks! - * - * @param string $bundleClass The bundle class class constant (VendorMyBundle::class) - * - * @return bool|string False on error - * - * @codeCoverageIgnore - * - * @deprecated Use utils service instead - */ - public function getBundlePath(string $bundleClass) - { - $result = $this->utils->container()->getBundlePath($bundleClass); - - if (null === $result) { - return false; - } - - return $result; - } - - /** - * Returns the path or paths to a ressource within a bundle - * Attention: resolves symlinks! - * - * @param string $bundleClass The bundle class class constant (VendorMyBundle::class) - * @param string $ressourcePath a ressource or path to ressource - * @param bool $first Returns only first occurrence if multiple paths found - * - * @return bool|string|array False on error - * - * @codeCoverageIgnore - * - * @deprecated Use utils service instead - */ - public function getBundleResourcePath(string $bundleClass, string $ressourcePath = '', $first = false) - { - $result = $this->utils->container()->getBundleResourcePath($bundleClass, $ressourcePath, $first); - - if (null === $result) { - return false; - } - - return $result; - } - - /** - * Recursively merges a config.yml with a $extensionConfigs array in the context of ExtensionPluginInterface::getExtensionConfig(). - * Must be static, because on Plugin::getExtensionConfig() no contao.framework nor service huh.utils.container is available. - * - * @return array - * - * @codeCoverageIgnore - * - * @deprecated Use ConfigPluginInterface with class_exist instead - */ - public static function mergeConfigFile( - string $activeExtensionName, - string $extensionName, - array $extensionConfigs, - string $configFile - ) { - if ($activeExtensionName === $extensionName && file_exists($configFile)) { - $config = Yaml::parseFile($configFile); - - $extensionConfigs = array_merge_recursive(\is_array($extensionConfigs) ? $extensionConfigs : [], \is_array($config) ? $config : []); - } - - return $extensionConfigs; - } - - /** - * @return bool - * - * @codeCoverageIgnore - * - * @deprecated Use utils service instead - */ - public function isMaintenanceModeActive() - { - return $this->utils->container()->isMaintenanceModeActive(); - } - - /** - * @return bool - * - * @codeCoverageIgnore - * - * @deprecated Use utils service instead - */ - public function isPreviewMode() - { - return $this->utils->container()->isPreviewMode(); - } -} diff --git a/src/ContaoManager/Plugin.php b/src/ContaoManager/Plugin.php index 25834a04..e1882554 100644 --- a/src/ContaoManager/Plugin.php +++ b/src/ContaoManager/Plugin.php @@ -13,7 +13,6 @@ use Contao\ManagerPlugin\Bundle\Config\BundleConfig; use Contao\ManagerPlugin\Bundle\Parser\ParserInterface; use Contao\ManagerPlugin\Config\ConfigPluginInterface; -use HeimrichHannot\UtilsBundle\HeimrichHannotContaoUtilsBundle; use HeimrichHannot\UtilsBundle\HeimrichHannotUtilsBundle; use Symfony\Component\Config\Loader\LoaderInterface; @@ -25,11 +24,8 @@ class Plugin implements BundlePluginInterface, ConfigPluginInterface public function getBundles(ParserInterface $parser) { return [ - BundleConfig::create(HeimrichHannotContaoUtilsBundle::class) - ->setLoadAfter([ContaoCoreBundle::class]), BundleConfig::create(HeimrichHannotUtilsBundle::class)->setLoadAfter([ ContaoCoreBundle::class, - HeimrichHannotContaoUtilsBundle::class, ]), ]; } @@ -39,14 +35,6 @@ public function getBundles(ParserInterface $parser) */ public function registerContainerConfiguration(LoaderInterface $loader, array $managerConfig) { - $loader->load('@HeimrichHannotContaoUtilsBundle/Resources/config/choices.yml'); - $loader->load('@HeimrichHannotContaoUtilsBundle/Resources/config/parameters.yml'); - $loader->load('@HeimrichHannotContaoUtilsBundle/Resources/config/services.yml'); - $loader->load('@HeimrichHannotContaoUtilsBundle/Resources/config/twig.yml'); - $loader->load('@HeimrichHannotContaoUtilsBundle/Resources/config/utils.yml'); - - if (class_exists('HeimrichHannot\EncoreBundle\HeimrichHannotContaoEncoreBundle')) { - $loader->load('@HeimrichHannotContaoUtilsBundle/Resources/config/config_encore.yml'); - } + $loader->load('@HeimrichHannotUtilsBundle/config/services.yml'); } } diff --git a/src/Content/ContentUtil.php b/src/Content/ContentUtil.php deleted file mode 100644 index 48c4841b..00000000 --- a/src/Content/ContentUtil.php +++ /dev/null @@ -1,70 +0,0 @@ -framework = $container->get('contao.framework'); - $this->container = $container; - } - - public function getMultilingualElements($id, $ptable) - { - if (null === ($contentElements = $this->container->get('huh.utils.model')->findModelInstancesBy('tl_content', ['tl_content.pid=?', 'tl_content.ptable=?'], [ - $id, - $ptable, - ], ['order' => 'tl_content.sorting ASC']))) { - return ''; - } - - foreach ($contentElements as $contentElement) { - $types = [ - 'colsetStart', - 'colsetPart', - 'colsetEnd', - 'accordionStart', - 'accordionStop', - 'tiny-slider-content-start', - 'tiny-slider-content-separator', - 'tiny-slider-content-stop', - ]; - - $skip = \in_array($contentElement->type, $types); - - if ($this->container->get('huh.utils.dca')->isDcMultilingual('tl_calendar_events') && - $GLOBALS['TL_DCA']['tl_calendar_events']['config']['fallbackLang'] !== $GLOBALS['TL_LANGUAGE']) { - if (!$contentElement->langPid && !$skip) { - continue; - } - - if (!$skip) { - $contentElement->id = $contentElement->langPid; - } - } - - $result .= Controller::getContentElement($contentElement); - } - - return $result; - } -} diff --git a/src/Database/DatabaseUtil.php b/src/Database/DatabaseUtil.php deleted file mode 100644 index 6fe82e4e..00000000 --- a/src/Database/DatabaseUtil.php +++ /dev/null @@ -1,1005 +0,0 @@ - 'like', - self::OPERATOR_UNLIKE => 'notLike', - self::OPERATOR_EQUAL => 'eq', - self::OPERATOR_UNEQUAL => 'neq', - self::OPERATOR_LOWER => 'lt', - self::OPERATOR_LOWER_EQUAL => 'lte', - self::OPERATOR_GREATER => 'gt', - self::OPERATOR_GREATER_EQUAL => 'gte', - self::OPERATOR_IN => 'in', - self::OPERATOR_NOT_IN => 'notIn', - self::OPERATOR_IS_NULL => 'isNull', - self::OPERATOR_IS_NOT_NULL => 'isNotNull', - ]; - - /** @var ContaoFrameworkInterface */ - protected $framework; - - public function __construct(ContaoFrameworkInterface $framework) - { - $this->framework = $framework; - } - - /** - * Process a query in pieces, run callback within each cycle. - * - * @param string $countQuery The query that count the total rows, must contain "Select COUNT(*) as total" - * @param string $query The query, with the rows that should be iterated over - * @param callable $callback A callback that should be triggered after each cycle, contains $arrRows of current cycle - * @param string $key The key of the value that should be set as key identifier for the returned result array entries - * @param int $bulkSize The bulk size - * - * @return bool|int False if nothing to do, otherwise return the total number of processes entities - */ - public function processInPieces(string $countQuery, string $query, $callback = null, string $key = null, int $bulkSize = 5000) - { - /** @var Database $database */ - $database = $this->framework->createInstance(Database::class); - $total = $database->execute($countQuery); - - if ($total->total < 1) { - return false; - } - - $bulkSize = (int) $bulkSize; - $totalCount = $total->total; - $cycles = $totalCount / $bulkSize; - - for ($i = 0; $i <= $cycles; ++$i) { - $result = $database->prepare($query)->limit($bulkSize, $i * $bulkSize)->execute(); - - if ($result->numRows < 1) { - return false; - } - - if (\is_callable($callback)) { - $return = []; - - while (false !== ($row = $result->fetchAssoc())) { - if ($key) { - if (isset($row[$key])) { - $return[$row[$key]] = $row; - } - - continue; - } - - $return[] = $row; - } - - \call_user_func_array($callback, [$return]); - } - } - - return $totalCount; - } - - /** - * Bulk insert SQL of given data. - * - * @param string $table The database table, where new items should be stored inside - * @param array $data An array of values associated to its field - * @param array $fixedValues A array of fixed values associated to its field that should be set for each row as fixed values - * @param mixed $onDuplicateKey null = Throw error on duplicates, self::ON_DUPLICATE_KEY_IGNORE = ignore error duplicates (skip this entries), - * self::ON_DUPLICATE_KEY_UPDATE = update existing entries - * @param callable $callback A callback that should be triggered after each cycle, contains $arrValues of current cycle - * @param callable $itemCallback A callback to change the insert values for each items, contains $arrValues as first argument, $arrFields as - * second, $arrOriginal as third, expects an array as return value with same order as $arrFields, if no array is - * returned, insert of the row will be skipped item insert - * @param int $bulkSize The bulk size - * @param string $pk The primary key of the current table (default: id) - */ - public function doBulkInsert( - string $table, - array $data = [], - array $fixedValues = [], - $onDuplicateKey = null, - $callback = null, - $itemCallback = null, - int $bulkSize = 100, - string $pk = 'id' - ) { - /** @var Database $database */ - $database = $this->framework->createInstance(Database::class); - - if (!$database->tableExists($table) || empty($data)) { - return null; - } - - $fields = $database->getFieldNames($table, true); - System::getContainer()->get('huh.utils.array')->removeValue($pk, $fields); // unset id - $fields = array_values($fields); - - $bulkSize = (int) $bulkSize; - - $query = ''; - $duplicateKey = ''; - $startQuery = sprintf('INSERT %s INTO %s (%s) VALUES ', self::ON_DUPLICATE_KEY_IGNORE === $onDuplicateKey ? 'IGNORE' : '', $table, implode(',', $fields)); - - if (self::ON_DUPLICATE_KEY_UPDATE === $onDuplicateKey) { - $duplicateKey = ' ON DUPLICATE KEY UPDATE '.implode( - ',', - array_map( - function ($val) { - // escape double quotes - return $val.' = VALUES('.$val.')'; - }, - $fields - ) - ); - } - - $i = 0; - - $columnWildcards = array_map( - function ($val) { - return '?'; - }, - $fields - ); - - foreach ($data as $key => $varData) { - if (0 === $i) { - $values = []; - $return = []; - $query = $startQuery; - } - - $columns = $columnWildcards; - - if ($varData instanceof Model) { - $varData = $varData->row(); - } - - foreach ($fields as $n => $strField) { - $varValue = isset($varData[$strField]) ? $varData[$strField] : 'DEFAULT'; - - if (\in_array($strField, array_keys($fixedValues))) { - $varValue = $fixedValues[$strField]; - } - - // replace SQL Keyword DEFAULT within wildcards ? - if ('DEFAULT' === $varValue) { - $columns[$n] = 'DEFAULT'; - - continue; - } - - $return[$i][$strField] = $varValue; - } - - // manipulate the item - if (\is_callable($itemCallback)) { - if (!isset($return[$i])) { - continue; - } - $varCallback = \call_user_func_array($itemCallback, [$return[$i], $fields, $varData]); - - if (!\is_array($varCallback)) { - continue; - } - - foreach ($fields as $n => $strField) { - $varValue = isset($varCallback[$strField]) ? $varCallback[$strField] : 'DEFAULT'; - - // replace SQL Keyword DEFAULT within wildcards ? - if ('DEFAULT' === $varValue) { - $columns[$n] = 'DEFAULT'; - - continue; - } - - $columns[$n] = '?'; - $return[$i][$strField] = $varValue; - } - } - - // add values to insert array - $values = array_merge($values, array_values($return[$i])); - - $query .= '('.implode(',', $columns).'),'; - - ++$i; - - if ($bulkSize === $i) { - $query = rtrim($query, ','); - - if (self::ON_DUPLICATE_KEY_UPDATE === $onDuplicateKey) { - $query .= $duplicateKey; - } - - $database->prepare($query)->execute($values); - - if (\is_callable($callback)) { - \call_user_func_array($callback, [$return]); - } - - $query = ''; - - $i = 0; - } - } - - // remaining elements < $intBulkSize - if ($query) { - $query = rtrim($query, ','); - - if (self::ON_DUPLICATE_KEY_UPDATE === $onDuplicateKey) { - $query .= $duplicateKey; - } - - $database->prepare($query)->execute($values); - - if (\is_callable($callback)) { - \call_user_func_array($callback, [$return]); - } - } - } - - /** - * Create a where condition for a field that contains a serialized blob. - * - * @param string $field The field the condition should be checked against accordances - * @param array $values The values array to check the field against - * @param string $connective SQL_CONDITION_OR | SQL_CONDITION_AND - * @param array $options Pass additional options. - * - * Options: - * - inline_values: (bool) Inline the values in the sql part instead of using ? ('REGEXP (':"3"')' instead of 'REGEXP (?)'). Return value not change (still an array with an values index) - * - * @return array - */ - public function createWhereForSerializedBlob(string $field, array $values, string $connective = self::SQL_CONDITION_OR, array $options = []) - { - $where = null; - $returnValues = []; - $inlineValues = $options['inline_values'] ?? false; - - if (!\in_array($connective, [self::SQL_CONDITION_OR, self::SQL_CONDITION_AND])) { - throw new \Exception('Unknown sql junctor'); - } - - foreach ($values as $val) { - if (null !== $where) { - $where .= " $connective "; - } - - $value = ":\"$val\""; - - $where .= self::SQL_CONDITION_AND == $connective ? '(' : ''; - - $where .= "$field REGEXP (".($inlineValues ? $value : '?').')'; - - $where .= self::SQL_CONDITION_AND == $connective ? ')' : ''; - - $returnValues[] = $value; - } - - return ["($where)", $returnValues]; - } - - /** - * Transforms verbose operators to valid MySQL operators (aka junctors). - * Supports: like, unlike, equal, unequal, lower, greater, lowerequal, greaterequal, in, notin. - * - * @return string|bool The transformed operator or false if not supported - */ - public function transformVerboseOperator(string $verboseOperator) - { - switch ($verboseOperator) { - case static::OPERATOR_LIKE: - return 'LIKE'; - - case static::OPERATOR_UNLIKE: - return 'NOT LIKE'; - - case static::OPERATOR_EQUAL: - return '='; - - case static::OPERATOR_UNEQUAL: - return '!='; - - case static::OPERATOR_LOWER: - return '<'; - - case static::OPERATOR_GREATER: - return '>'; - - case static::OPERATOR_LOWER_EQUAL: - return '<='; - - case static::OPERATOR_GREATER_EQUAL: - return '>='; - - case static::OPERATOR_IN: - return 'IN'; - - break; - - case static::OPERATOR_NOT_IN: - return 'NOT IN'; - - case static::OPERATOR_IS_NULL: - return 'NOT IN'; - - case static::OPERATOR_IS_NOT_NULL: - return 'IS NOT NULL'; - - case static::OPERATOR_IS_EMPTY: - return '=""'; - - case static::OPERATOR_IS_NOT_EMPTY: - return '!=""'; - } - - return false; - } - - /** - * Computes a MySQL condition appropriate for the given operator. - * - * @param mixed $value - * @param string $table - * - * @return array Returns array($strQuery, $arrValues) - */ - public function computeCondition(string $field, string $operator, $value, string $table = null, bool $skipTablePrefix = false) - { - $operator = trim(strtolower($operator)); - $values = []; - $pattern = '?'; - $addQuotes = false; - - $explodedField = explode('.', $field); - - // remove table if already added to field name - if (\count($explodedField) > 1) { - $field = end($explodedField); - } - - if ($table) { - Controller::loadDataContainer($table); - - $dca = &$GLOBALS['TL_DCA'][$table]['fields'][$field]; - - if (isset($dca['sql']) && false !== stripos($dca['sql'], 'blob')) { - $addQuotes = true; - } - } - - switch ($operator) { - case static::OPERATOR_UNLIKE: - if (\is_array($value)) { - foreach ($value as $val) { - $values[] = Controller::replaceInsertTags('%'.($addQuotes ? '"'.$val.'"' : $val).'%', false); - } - - break; - } - $values[] = Controller::replaceInsertTags('%'.($addQuotes ? '"'.$value.'"' : $value).'%', false); - - break; - - case static::OPERATOR_EQUAL: - $values[] = Controller::replaceInsertTags(\is_array($value) ? implode(' ', $value) : $value, false); - - break; - - case static::OPERATOR_UNEQUAL: - case '<>': - $values[] = Controller::replaceInsertTags(\is_array($value) ? implode(' ', $value) : $value, false); - - break; - - case static::OPERATOR_LOWER: - $pattern = 'CAST(? AS DECIMAL)'; - $values[] = Controller::replaceInsertTags(\is_array($value) ? implode(' ', $value) : $value, false); - - break; - - case static::OPERATOR_GREATER: - $pattern = 'CAST(? AS DECIMAL)'; - $values[] = Controller::replaceInsertTags(\is_array($value) ? implode(' ', $value) : $value, false); - - break; - - case static::OPERATOR_LOWER_EQUAL: - $pattern = 'CAST(? AS DECIMAL)'; - $values[] = Controller::replaceInsertTags(\is_array($value) ? implode(' ', $value) : $value, false); - - break; - - case static::OPERATOR_GREATER_EQUAL: - $pattern = 'CAST(? AS DECIMAL)'; - $values[] = Controller::replaceInsertTags(\is_array($value) ? implode(' ', $value) : $value, false); - - break; - - case static::OPERATOR_IN: - $value = array_filter(explode(',', Controller::replaceInsertTags($value, false))); - - // skip if empty to avoid sql error - if (empty($value)) { - break; - } - - $pattern = '('.implode( - ',', - array_map( - function ($val) { - return '"'.addslashes(trim($val)).'"'; - }, - $value - ) - ).')'; - - break; - - case static::OPERATOR_NOT_IN: - $value = array_filter(explode(',', Controller::replaceInsertTags($value, false))); - - // skip if empty to avoid sql error - if (empty($value)) { - break; - } - - $pattern = '('.implode( - ',', - array_map( - function ($val) { - return '"'.addslashes(trim($val)).'"'; - }, - $value - ) - ).')'; - - break; - - case static::OPERATOR_IS_NULL: - case static::OPERATOR_IS_NOT_NULL: - case static::OPERATOR_IS_EMPTY: - case static::OPERATOR_IS_NOT_EMPTY: - $pattern = ''; - - break; - - default: - if (\is_array($value)) { - foreach ($value as $val) { - $values[] = Controller::replaceInsertTags('%'.($addQuotes ? '"'.$val.'"' : $val).'%', false); - } - - break; - } - $values[] = Controller::replaceInsertTags('%'.($addQuotes ? '"'.$value.'"' : $value).'%', false); - - break; - } - - $operator = $this->transformVerboseOperator($operator); - - return [(!$skipTablePrefix && $table ? $table.'.' : '')."$field $operator $pattern", $values]; - } - - /** - * @param null $value - * @param array $options {wildcardSuffix: string} - * - * @return string - */ - public function composeWhereForQueryBuilder(QueryBuilder $queryBuilder, string $field, string $operator, array $dca = null, $value = null, array $options = []) - { - $wildcardSuffix = $options['wildcardSuffix'] ?? ''; - $wildcard = ':'.str_replace('.', '_', $field).$wildcardSuffix; - $wildcardParameterName = substr($wildcard, 1); - $where = ''; - - // remove dot for table prefixes - if (false !== strpos($wildcard, '.')) { - $wildcard = str_replace('.', '_', $wildcard); - } - - switch ($operator) { - case self::OPERATOR_LIKE: - $where = $queryBuilder->expr()->like($field, $wildcard); - $queryBuilder->setParameter($wildcardParameterName, '%'.Controller::replaceInsertTags(\is_array($value) ? implode(' ', $value) : $value, false).'%'); - - break; - - case self::OPERATOR_UNLIKE: - $where = $queryBuilder->expr()->notLike($field, $wildcard); - $queryBuilder->setParameter($wildcardParameterName, '%'.Controller::replaceInsertTags(\is_array($value) ? implode(' ', $value) : $value, false).'%'); - - break; - - case self::OPERATOR_EQUAL: - $where = $queryBuilder->expr()->eq($field, $wildcard); - $queryBuilder->setParameter($wildcardParameterName, Controller::replaceInsertTags(\is_array($value) ? implode(' ', $value) : $value, false)); - - break; - - case self::OPERATOR_UNEQUAL: - $where = $queryBuilder->expr()->neq($field, $wildcard); - $queryBuilder->setParameter($wildcardParameterName, Controller::replaceInsertTags(\is_array($value) ? implode(' ', $value) : $value, false)); - - break; - - case self::OPERATOR_LOWER: - $where = $queryBuilder->expr()->lt($field, $wildcard); - $queryBuilder->setParameter($wildcardParameterName, Controller::replaceInsertTags(\is_array($value) ? implode(' ', $value) : $value, false)); - - break; - - case self::OPERATOR_LOWER_EQUAL: - $where = $queryBuilder->expr()->lte($field, $wildcard); - $queryBuilder->setParameter($wildcardParameterName, Controller::replaceInsertTags(\is_array($value) ? implode(' ', $value) : $value, false)); - - break; - - case self::OPERATOR_GREATER: - $where = $queryBuilder->expr()->gt($field, $wildcard); - $queryBuilder->setParameter($wildcardParameterName, Controller::replaceInsertTags(\is_array($value) ? implode(' ', $value) : $value, false)); - - break; - - case self::OPERATOR_GREATER_EQUAL: - $where = $queryBuilder->expr()->gte($field, $wildcard); - $queryBuilder->setParameter($wildcardParameterName, Controller::replaceInsertTags(\is_array($value) ? implode(' ', $value) : $value, false)); - - break; - - case self::OPERATOR_IN: - $value = array_filter(!\is_array($value) ? explode(',', $value) : $value); - - // if empty add an unfulfillable condition in order to avoid an sql error - if (empty($value)) { - $where = $queryBuilder->expr()->eq(1, 2); - } else { - $where = $queryBuilder->expr()->in($field, $wildcard); - $preparedValue = array_map( - function ($val) { - return addslashes(Controller::replaceInsertTags(trim($val), false)); - }, - $value - ); - $queryBuilder->setParameter($wildcardParameterName, $preparedValue, Connection::PARAM_STR_ARRAY); - } - - break; - - case self::OPERATOR_NOT_IN: - $value = array_filter(!\is_array($value) ? explode(',', $value) : $value); - - // if empty add an unfulfillable condition in order to avoid an sql error - if (empty($value)) { - $where = $queryBuilder->expr()->eq(1, 2); - } else { - $where = $queryBuilder->expr()->notIn($field, $wildcard); - $preparedValue = array_map( - function ($val) { - return addslashes(Controller::replaceInsertTags(trim($val), false)); - }, - $value - ); - $queryBuilder->setParameter($wildcardParameterName, $preparedValue, Connection::PARAM_STR_ARRAY); - } - - break; - - case self::OPERATOR_IS_NULL: - $where = $queryBuilder->expr()->isNull($field); - - break; - - case self::OPERATOR_IS_NOT_NULL: - $where = $queryBuilder->expr()->isNotNull($field); - - break; - - case self::OPERATOR_IS_EMPTY: - $where = $queryBuilder->expr()->eq($field, '\'\''); - - break; - - case self::OPERATOR_IS_NOT_EMPTY: - $where = $queryBuilder->expr()->neq($field, '\'\''); - - break; - - case self::OPERATOR_REGEXP: - case self::OPERATOR_NOT_REGEXP: - $where = $field.(self::OPERATOR_NOT_REGEXP == $operator ? ' NOT REGEXP ' : ' REGEXP ').$wildcard; - - if (\is_array($dca) && isset($dca['eval']['multiple']) && $dca['eval']['multiple']) { - // match a serialized blob - if (\is_array($value)) { - // build a regexp alternative, e.g. (:"1";|:"2";) - $queryBuilder->setParameter( - $wildcardParameterName, - '('.implode( - '|', - array_map( - function ($val) { - return ':"'.Controller::replaceInsertTags($val, false).'";'; - }, - $value - ) - ).')' - ); - } else { - $queryBuilder->setParameter($wildcardParameterName, ':"'.Controller::replaceInsertTags($value, false).'";'); - } - } else { - // TODO: this makes no sense, yet - $queryBuilder->setParameter($wildcardParameterName, Controller::replaceInsertTags(\is_array($value) ? implode(' ', $value) : $value, false)); - } - - break; - } - - return $where; - } - - public function getChildRecords(array $parentIds, string $table, array $options = []): array - { - $children = []; - $db = $this->framework->createInstance(Database::class); - $sorting = (isset($options['sorting']) && $options['sorting'] ? ' ORDER BY '.$db->findInSet($table.'.pid', $parentIds).', sorting' : false); - $fetchRows = isset($options['fetchRows']) && $options['fetchRows']; - $recursive = isset($options['recursive']) && $options['recursive']; - - $childRecords = $db->query('SELECT '.($fetchRows ? '*' : "$table.id, $table.pid")." FROM $table WHERE $table.pid IN(".implode(',', $parentIds).')'.$sorting); - - if ($childRecords->numRows > 0) { - while ($childRecords->next()) { - $row = $childRecords->row(); - $children[] = $fetchRows ? $row : $row['id']; - } - - if ($recursive) { - $children = array_merge($children, $this->getChildRecords($childRecords->fetchEach('id'), $table, $options)); - } - } - - return $children; - } - - /** - * Returns a database result for a given table and id(primary key). - * - * @param mixed $pk - * - * @return mixed - */ - public function findResultByPk(string $table, $pk, array $options = []) - { - /* @var Database $db */ - if (!($db = $this->framework->getAdapter(Database::class))) { - return null; - } - - $options = array_merge( - [ - 'limit' => 1, - 'column' => 'id', - 'value' => $pk, - ], - $options - ); - - $options['table'] = $table; - - if (isset($options['selectFields'])) { - $query = $this->createQueryWithoutRelations($options['selectFields']); - } else { - $query = \Contao\Model\QueryBuilder::find($options); - } - - $statement = $db->getInstance()->prepare($query); - - // Defaults for limit and offset - if (!isset($options['limit'])) { - $options['limit'] = 0; - } - - if (!isset($options['offset'])) { - $options['offset'] = 0; - } - - // Limit - if ($options['limit'] > 0 || $options['offset'] > 0) { - $statement->limit($options['limit'], $options['offset']); - } - - return $statement->execute($options['value']); - } - - /** - * Return a single database result by table and search criteria. - * - * @return mixed - */ - public function findOneResultBy(string $table, ?array $columns, ?array $values, array $options = []) - { - /* @var Database $db */ - if (!($db = $this->framework->getAdapter(Database::class))) { - return null; - } - - $options = array_merge( - [ - 'limit' => 1, - 'column' => $columns, - 'value' => $values, - ], - $options - ); - - $options['table'] = $table; - - if (isset($options['selectFields'])) { - $query = $this->createQueryWithoutRelations($options['selectFields']); - } else { - $query = \Contao\Model\QueryBuilder::find($options); - } - - $statement = $db->getInstance()->prepare($query); - - if (!isset($options['offset'])) { - $options['offset'] = 0; - } - - // Limit - if ($options['limit'] > 0 || $options['offset'] > 0) { - $statement->limit($options['limit'], $options['offset']); - } - - return $statement->execute($options['value']); - } - - public function findResultsBy(string $table, ?array $columns, ?array $values, array $options = []) - { - /* @var Database $db */ - if (!($db = $this->framework->getAdapter(Database::class))) { - return null; - } - - if (null !== $columns) { - $options = array_merge( - [ - 'column' => $columns, - ], - $options - ); - } - - if (null !== $values) { - $options = array_merge( - [ - 'value' => $values, - ], - $options - ); - } - - $options['table'] = $table; - - if (isset($options['selectFields'])) { - $query = $this->createQueryWithoutRelations($options); - } else { - $query = \Contao\Model\QueryBuilder::find($options); - } - - $statement = $db->getInstance()->prepare($query); - - // Defaults for limit and offset - if (!isset($options['limit'])) { - $options['limit'] = 0; - } - - if (!isset($options['offset'])) { - $options['offset'] = 0; - } - - // Limit - if ($options['limit'] > 0 || $options['offset'] > 0) { - $statement->limit($options['limit'], $options['offset']); - } - - return isset($options['value']) ? $statement->execute($options['value']) : $statement->execute(); - } - - public function insert(string $table, array $set, array $options = []) - { - $db = $options['databaseObject'] ?? null; - - if (!$db) { - /* @var Database $db */ - if (!($db = $this->framework->getAdapter(Database::class))) { - return null; - } - - $db = $db->getInstance(); - } - - $columnNames = implode(',', array_keys($set)); - - $wildcards = implode(',', array_map(function () { - return '?'; - }, $set)); - - $query = "INSERT INTO $table ($columnNames) VALUES ($wildcards)"; - - return \call_user_func_array([$db->prepare($query), 'execute'], array_values($set)); - } - - public function update(string $table, array $set, string $where = null, array $whereValues = [], array $options = []) - { - $db = $options['databaseObject'] ?? null; - - if (!$db) { - /* @var Database $db */ - if (!($db = $this->framework->getAdapter(Database::class))) { - return null; - } - - $db = $db->getInstance(); - } - - $assignments = implode(',', array_map(function ($column) { - return "$column=?"; - }, array_keys($set))); - - $query = "UPDATE $table SET $assignments".($where ? " WHERE $where" : ''); - - return \call_user_func_array([$db->prepare($query), 'execute'], array_merge(array_values($set), $whereValues)); - } - - public function delete(string $table, string $where = null, array $whereValues = [], array $options = []) - { - $db = $options['databaseObject'] ?? null; - - if (!$db) { - /* @var Database $db */ - if (!($db = $this->framework->getAdapter(Database::class))) { - return null; - } - - $db = $db->getInstance(); - } - - $query = "DELETE FROM $table".($where ? " WHERE $where" : ''); - - return \call_user_func_array([$db->prepare($query), 'execute'], $whereValues); - } - - public function beginTransaction() - { - /* @var Database $db */ - if (!($db = $this->framework->createInstance(Database::class))) { - return null; - } - - $db->beginTransaction(); - } - - public function commitTransaction() - { - /* @var Database $db */ - if (!($db = $this->framework->createInstance(Database::class))) { - return null; - } - - $db->commitTransaction(); - } - - /** - * Adapted from \Contao\Model\QueryBuilder::find(). - * - * @return string - */ - private function createQueryWithoutRelations(array $options) - { - $fields = $options['selectFields'] ?? []; - - $query = 'SELECT '.(empty($fields) ? '*' : implode(', ', $fields)).' FROM '.$options['table']; - - // Where condition - if (isset($options['column'])) { - $query .= ' WHERE '.(\is_array($options['column']) ? implode(' AND ', $options['column']) : $options['table'].'.'.Database::quoteIdentifier($options['column']).'=?'); - } - - // Group by - if (isset($options['group'])) { - @trigger_error('Using the "group" option has been deprecated and will no longer work in Contao 5.0. See https://github.com/contao/contao/issues/1680', \E_USER_DEPRECATED); - $query .= ' GROUP BY '.$options['group']; - } - - // Having (see #6446) - if (isset($options['having'])) { - $query .= ' HAVING '.$options['having']; - } - - // Order by - if (isset($options['order'])) { - $query .= ' ORDER BY '.$options['order']; - } - - return $query; - } -} diff --git a/src/Date/DateUtil.php b/src/Date/DateUtil.php deleted file mode 100644 index 99d15dee..00000000 --- a/src/Date/DateUtil.php +++ /dev/null @@ -1,482 +0,0 @@ -framework = $container->get('contao.framework'); - $this->container = $container; - } - - /** - * Get the timestamp based on input date, no matter input is timestamp or string date. - * - * @param string|int|\DateTime|null $date The input date/timestamp/insertTag - * @param bool $replaceInsertTags Disable/enable {{date::}} insertTag support - * @param string|null $timezone A valid timezone from DateTimeZone::ALL, if provided the timezone offset will be added to the timestamp - * - * @throws \Exception Throws error in case of an error when creating new DateTime instances - * - * @return int The integer timestamp presentation of the input date with added timezone offset - */ - public function getTimeStamp($date = null, $replaceInsertTags = true, $timezone = null) - { - if (null === $date) { - return 0; - } - - if ($date instanceof \DateTime) { - $timezone ? $date->setTimezone(new \DateTimeZone($timezone)) : null; - - return $date->getTimestamp(); - } - - if (true === $replaceInsertTags) { - $date = Controller::replaceInsertTags($date, false); - } - - if (is_numeric($date)) { - $dateTime = new \DateTime(null, $timezone ? new \DateTimeZone($timezone) : null); - $dateTime->setTimestamp($date); - - return $dateTime->getTimestamp(); - } - - if (false !== ($dateTime = strtotime($date))) { - $dateTime = new \DateTime($date, $timezone ? new \DateTimeZone($timezone) : null); - - return $dateTime->getTimestamp(); - } - - return 0; - } - - /** - * Returns the time in seconds of an given time period. - * - * @param string|array $timePeriod Array or serialized string containing an value and an unit key - * - * @return float|int|null - */ - public function getTimePeriodInSeconds($timePeriod) - { - $timePeriod = StringUtil::deserialize($timePeriod, true); - - if (!isset($timePeriod['unit']) || !isset($timePeriod['value'])) { - return null; - } - - $factor = 1; - - switch ($timePeriod['unit']) { - case 'm': - $factor = 60; - - break; - - case 'h': - $factor = 60 * 60; - - break; - - case 'd': - $factor = 24 * 60 * 60; - - break; - } - - return $timePeriod['value'] * $factor; - } - - /** - * Format a php date formate pattern to an RFC3339 compliant format. - * - * @param string $format The php date format (see: http://php.net/manual/de/function.date.php#refsect1-function.date-parameters) - * - * @return string The RFC3339 compliant format (see: http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Time-Format-Syntax or http://www.unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table) - */ - public function transformPhpDateFormatToRFC3339(string $format): string - { - $mapping = [ - 'd' => 'dd', //Day of the month, 2 digits with leading zeros (01 to 31) - 'D' => 'E', // A textual representation of a day, three letters (Mon through Sun) - 'j' => 'd', // Day of the month without leading zeros (1 to 31) - 'l' => 'EEEE', // A full textual representation of the day of the week (Sunday through Saturday) - 'N' => 'd', // ISO-8601 numeric representation of the day of the week (added in PHP 5.1.0) (1 (for Monday) through 7 (for Sunday)) - 'S' => '', // Not supported yet: English ordinal suffix for the day of the month, 2 characters (st, nd, rd or th. Works well with j) - 'w' => 'e', // Numeric representation of the day of the week (0 (for Sunday) through 6 (for Saturday)) - 'z' => 'D', // The day of the year (starting from 0) (0 through 365) - 'W' => 'w', // ISO-8601 week number of year, weeks starting on Monday (Example: 42 (the 42nd week in the year)) - 'F' => 'MMMM', // A full textual representation of a month, such as January or March (January through December) - 'm' => 'MM', // Numeric representation of a month, with leading zeros (01 through 12) - 'M' => 'MMM', // A short textual representation of a month, three letters (Jan through Dec) - 'n' => 'M', // Numeric representation of a month, without leading zeros (1 through 12) - 't' => '', // Not supported yet: Number of days in the given month (28 through 31) - 'L' => '', // Not supported yet: Whether it's a leap year (1 if it is a leap year, 0 otherwise.) - 'o' => 'Y', // ISO-8601 week-numbering year. This has the same value as Y, except that if the ISO week number (W) belongs to the previous or next year, that year is used instead. (added in PHP 5.1.0) (Examples: 1999 or 2003) - 'Y' => 'yyyy', // A full numeric representation of a year, 4 digits (Examples: 1999 or 2003) - 'y' => 'yy', // A two digit representation of a year (Examples: 99 or 03) - 'a' => '', // Not supported yet: Lowercase Ante meridiem and Post meridiem (am or pm) - 'A' => 'a', // Uppercase Ante meridiem and Post meridiem (AM or PM) - 'B' => '', // Not supported yet: Swatch Internet time (000 through 999) - 'g' => 'h', // 12-hour format of an hour without leading zeros (1 through 12) - 'G' => 'H', // 24-hour format of an hour without leading zeros (0 through 23) - 'h' => 'hh', // 12-hour format of an hour with leading zeros (01 through 12) - 'H' => 'HH', // 24-hour format of an hour with leading zeros (00 through 23) - 'i' => 'mm', // Minutes with leading zeros (00 to 59) - 's' => 'ss', // Seconds, with leading zeros (00 to 59) - 'u' => '', // Not supported yet: Microseconds (added in PHP 5.2.2). Note that date() will always generate 000000 since it takes an integer parameter, whereas DateTime::format() does support microseconds if DateTime was created with microseconds. (Example: 654321) - 'v' => '', // Not supported yet: Milliseconds (added in PHP 7.0.0). Same note applies as for u. (Example: 654) - 'e' => 'VV', // Timezone identifier (added in PHP 5.1.0) (Examples: UTC, GMT, Atlantic/Azores) - 'I' => '', // Not supported yet: Whether or not the date is in daylight saving time (1 if Daylight Saving Time, 0 otherwise.) - 'O' => 'xx', // Difference to Greenwich time (GMT) in hours (Example: +0200) - 'P' => 'xxx', // Difference to Greenwich time (GMT) with colon between hours and minutes (added in PHP 5.1.3) (Example: +02:00) - 'T' => '', // Not supported yet: Timezone abbreviation (Examples: EST, MDT) - 'Z' => '', // Not supported yet: Timezone offset in seconds. The offset for timezones west of UTC is always negative, and for those east of UTC is always positive. (-43200 through 50400) - 'c' => "yyyy-MM-dd'T'HH:mm:ssxxx", // ISO 8601 date (added in PHP 5) (2004-02-12T15:19:21+00:00) - 'r' => '', // Not supported yet: » RFC 2822 formatted date (Example: Thu, 21 Dec 2000 16:01:07 +0200) - 'U' => '', // Not supported yet: Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT) - ]; - - $chunks = str_split($format); - - foreach ($chunks as $k => $v) { - if (!isset($mapping[$v])) { - continue; - } - - $chunks[$k] = $mapping[$v]; - } - - return preg_replace('/([a-zA-Z])/', '$1', implode('', $chunks)); - } - - /** - * Format a php date formate pattern to an ISO8601 compliant format. - * - * @param string $format The date format (e.g. "d.m.y H:i") - * - * @return string The ISO8601 compliant format (see: https://de.wikipedia.org/wiki/ISO_8601) - */ - public function transformPhpDateFormatToISO8601(string $format): string - { - $mapping = [ - 'd' => 'DD', //Day of the month, 2 digits with leading zeros (01 to 31) - 'D' => 'D', // A textual representation of a day, three letters (Mon through Sun) - 'j' => 'd', // Day of the month without leading zeros (1 to 31) - 'l' => 'DD', // A full textual representation of the day of the week (Sunday through Saturday) - 'N' => '', // ISO-8601 numeric representation of the day of the week (added in PHP 5.1.0) (1 (for Monday) through 7 (for Sunday)) - 'S' => '', // Not supported yet: English ordinal suffix for the day of the month, 2 characters (st, nd, rd or th. Works well with j) - 'w' => '', // Numeric representation of the day of the week (0 (for Sunday) through 6 (for Saturday)) - 'z' => 'o', // The day of the year (starting from 0) (0 through 365) - 'W' => '', // ISO-8601 week number of year, weeks starting on Monday (Example: 42 (the 42nd week in the year)) - 'F' => 'MM', // A full textual representation of a month, such as January or March (January through December) - 'm' => 'MM', // Numeric representation of a month, with leading zeros (01 through 12) - 'M' => 'M', // A short textual representation of a month, three letters (Jan through Dec) - 'n' => 'm', // Numeric representation of a month, without leading zeros (1 through 12) - 't' => '', // Not supported yet: Number of days in the given month (28 through 31) - 'L' => '', // Not supported yet: Whether it's a leap year (1 if it is a leap year, 0 otherwise.) - 'o' => '', // ISO-8601 week-numbering year. This has the same value as Y, except that if the ISO week number (W) belongs to the previous or next year, that year is used instead. (added in PHP 5.1.0) (Examples: 1999 or 2003) - 'Y' => 'YYYY', // A full numeric representation of a year, 4 digits (Examples: 1999 or 2003) - 'y' => 'y', // A two digit representation of a year (Examples: 99 or 03) - 'a' => '', // Not supported yet: Lowercase Ante meridiem and Post meridiem (am or pm) - 'A' => '', // Uppercase Ante meridiem and Post meridiem (AM or PM) - 'B' => '', // Not supported yet: Swatch Internet time (000 through 999) - 'g' => '', // 12-hour format of an hour without leading zeros (1 through 12) - 'G' => '', // 24-hour format of an hour without leading zeros (0 through 23) - 'h' => '', // 12-hour format of an hour with leading zeros (01 through 12) - 'H' => 'hh', // 24-hour format of an hour with leading zeros (00 through 23) - 'i' => 'mm', // Minutes with leading zeros (00 to 59) - 's' => 'ss', // Seconds, with leading zeros (00 to 59) - 'u' => '', // Not supported yet: Microseconds (added in PHP 5.2.2). Note that date() will always generate 000000 since it takes an integer parameter, whereas DateTime::format() does support microseconds if DateTime was created with microseconds. (Example: 654321) - 'v' => '', // Not supported yet: Milliseconds (added in PHP 7.0.0). Same note applies as for u. (Example: 654) - 'e' => '', // Timezone identifier (added in PHP 5.1.0) (Examples: UTC, GMT, Atlantic/Azores) - 'I' => '', // Not supported yet: Whether or not the date is in daylight saving time (1 if Daylight Saving Time, 0 otherwise.) - 'O' => '', // Difference to Greenwich time (GMT) in hours (Example: +0200) - 'P' => 'z', // Difference to Greenwich time (GMT) with colon between hours and minutes (added in PHP 5.1.3) (Example: +02:00) - 'T' => '', // Not supported yet: Timezone abbreviation (Examples: EST, MDT) - 'Z' => '', // Not supported yet: Timezone offset in seconds. The offset for timezones west of UTC is always negative, and for those east of UTC is always positive. (-43200 through 50400) - 'c' => "YYYY-MM-DD'T'hh:mm:ssz", // ISO 8601 date (added in PHP 5) (2004-02-12T15:19:21+00:00) - 'r' => '', // Not supported yet: » RFC 2822 formatted date (Example: Thu, 21 Dec 2000 16:01:07 +0200) - 'U' => '', // Not supported yet: Seconds since the Unix Epoch (January 1 1970 00:00:00 GMT) - ]; - - $chunks = str_split($format); - - foreach ($chunks as $k => $v) { - if (!isset($mapping[$v])) { - continue; - } - - $chunks[$k] = $mapping[$v]; - } - - return preg_replace('/([a-zA-Z])/', '$1', implode('', $chunks)); - } - - /** - * transfer a given timestamp to a gmt timestamp at midnight. - * - * @return int - */ - public function getGMTMidnightTstamp(int $tstamp) - { - $date = new \DateTime(date('Y-m-d', $tstamp)); - $date->setTimezone(new \DateTimeZone('GMT')); - $date->setTime(0, 0, 0); - - return $date->getTimestamp(); - } - - /** - * Checks if a form of month is available in the date format. - * - * @return bool - */ - public function isMonthInDateFormat(string $dateFormat) - { - return false !== strpos($dateFormat, 'F') || - false !== strpos($dateFormat, 'M') || - false !== strpos($dateFormat, 'm') || - false !== strpos($dateFormat, 'n'); - } - - /** - * Checks if a form of day is available in the date format. - * - * @return bool - */ - public function isDayInDateFormat(string $dateFormat) - { - return false !== strpos($dateFormat, 'd') || - false !== strpos($dateFormat, 'D') || - false !== strpos($dateFormat, 'j') || - false !== strpos($dateFormat, 'l') || - false !== strpos($dateFormat, 'N') || - false !== strpos($dateFormat, 'z'); - } - - /** - * Checks if a form of year is available in the date format. - * - * @return bool - */ - public function isYearInDateFormat(string $dateFormat) - { - return false !== strpos($dateFormat, 'o') || - false !== strpos($dateFormat, 'Y') || - false !== strpos($dateFormat, 'y'); - } - - public function getMonthTranslationMap() - { - $map = []; - - $months = [ - 'January', - 'February', - 'March', - 'April', - 'May', - 'June', - 'July', - 'August', - 'September', - 'October', - 'November', - 'December', - ]; - - System::loadLanguageFile('default'); - - foreach ($GLOBALS['TL_LANG']['MONTHS'] as $index => $translated) { - $map[$months[$index]] = $translated; - } - - return $map; - } - - public function getShortMonthTranslationMap() - { - $map = []; - - $months = [ - 'Jan', - 'Feb', - 'Mar', - 'Apr', - 'May', - 'Jun', - 'Jul', - 'Aug', - 'Sep', - 'Oct', - 'Nov', - 'Dec', - ]; - - System::loadLanguageFile('default'); - - foreach ($GLOBALS['TL_LANG']['MONTHS_SHORT'] as $index => $translated) { - $map[$months[$index]] = $translated; - } - - return $map; - } - - /** - * Translates available months inside a given string into their English representations taking into account the current language. - * - * @return mixed|string - */ - public function translateMonthsToEnglish(string $date) - { - foreach ($this->getMonthTranslationMap() as $english => $translated) { - if (false !== strpos($date, $translated)) { - $date = str_replace($translated, $english, $date); - } - } - - foreach ($this->getShortMonthTranslationMap() as $english => $translated) { - if (false !== strpos($date, $translated)) { - $date = str_replace($translated, $english, $date); - } - } - - return $date; - } - - /** - * Translates available months inside a given string from English to the current language. - * - * @return mixed|string - */ - public function translateMonths(string $date) - { - foreach (array_flip($this->getMonthTranslationMap()) as $translated => $english) { - if (false !== strpos($date, $english)) { - $date = str_replace($english, $translated, $date); - } - } - - foreach (array_flip($this->getShortMonthTranslationMap()) as $translated => $english) { - if (false !== strpos($date, $english)) { - $date = str_replace($english, $translated, $date); - } - } - - return $date; - } - - public function getFormattedDateTime($startDate, $endDate = 0, $addTime = false, $startTime = 0, $endTime = 0, array $options = []): ?string - { - $dateFormat = $options['dateFormat'] ?? Date::getNumericDateFormat(); - $datimFormat = $options['datimFormat'] ?? Date::getNumericDatimFormat(); - $timeFormat = $options['timeFormat'] ?? Date::getNumericTimeFormat(); - $separator = $options['separator'] ?? ' – '; - $translateMonths = $options['translateMonths'] ?? false; - - $startDateFormatted = date($dateFormat, $startDate); - $endDateFormatted = date($dateFormat, $endDate); - - if ($addTime) { - if (!$endDate || $startDateFormatted === $endDateFormatted) { - $startTimeFormatted = date($timeFormat, $startTime); - $endTimeFormatted = date($timeFormat, $endTime); - - if ($startTimeFormatted === $endTimeFormatted) { - $result = $startDateFormatted.' '.$startTimeFormatted; - } else { - $result = $startDateFormatted.' '.$startTimeFormatted.$separator.$endTimeFormatted; - } - - return $translateMonths ? $this->translateMonths($result) : $result; - } - $startDateTimeFormatted = date($datimFormat, $startTime); - $endDateTimeFormatted = date($datimFormat, $endTime); - - if (!$endTime || $startDateTimeFormatted === $endDateTimeFormatted) { - return $translateMonths ? $this->translateMonths($startDateTimeFormatted) : $startDateTimeFormatted; - } - - return $translateMonths ? $this->translateMonths($startDateTimeFormatted.$separator.$endDateTimeFormatted) : $startDateTimeFormatted.$separator.$endDateTimeFormatted; - } - - if (!$endDate || $startDateFormatted === $endDateFormatted) { - return $translateMonths ? $this->translateMonths($startDateFormatted) : $startDateFormatted; - } - - return $translateMonths ? $this->translateMonths($startDateFormatted.$separator.$endDateFormatted) : $startDateFormatted.$separator.$endDateFormatted; - } - - public function getFormattedDateTimeByEvent(Model $event): ?string - { - return $this->getFormattedDateTime($event->startDate, $event->endDate, $event->addTime, $event->startTime, $event->endTime); - } - - /** - * @param bool $returnFullDays If true, only full days are returned (0.67 -> 0) - * - * @return float|int - */ - public function getDaysBetween(int $smallerTimestamp, int $largerTimestamp = null, bool $returnFullDays = false) - { - $largerTimestamp = null === $largerTimestamp ? time() : $largerTimestamp; - - $result = ($largerTimestamp - $smallerTimestamp) / (60 * 60 * 24); - - return $returnFullDays ? floor($result) : $result; - } - - public function convertSecondsToHumanReadableFormat($seconds) - { - $days = floor($seconds / 86400); - $seconds -= ($days * 86400); - - $hours = floor($seconds / 3600); - $seconds -= ($hours * 3600); - - $minutes = floor($seconds / 60); - $seconds -= ($minutes * 60); - - $values = [ - 'day' => $days, - 'hour' => $hours, - 'minute' => $minutes, - 'second' => $seconds, - ]; - - $parts = []; - - foreach ($values as $text => $value) { - if ($value > 0) { - $parts[] = $value.' '.strtolower($GLOBALS['TL_LANG']['MSC'][$text.($value > 1 ? 's' : '')]); - } - } - - return implode(', ', $parts); - } -} diff --git a/src/Dca/AuthorField.php b/src/Dca/AuthorField.php new file mode 100644 index 00000000..4cb3034c --- /dev/null +++ b/src/Dca/AuthorField.php @@ -0,0 +1,32 @@ + + */ + public static function getRegistrations(): array + { + return static::$tables; + } +} \ No newline at end of file diff --git a/src/Dca/AuthorFieldOptions.php b/src/Dca/AuthorFieldOptions.php new file mode 100644 index 00000000..dfd6c17f --- /dev/null +++ b/src/Dca/AuthorFieldOptions.php @@ -0,0 +1,106 @@ +table = $table; + } + + + public function getTable(): string + { + return $this->table; + } + + public function getType(): string + { + return $this->type; + } + + public function setType(string $type): AuthorFieldOptions + { + $this->type = $type; + return $this; + } + + public function hasFieldNamePrefix(): bool + { + return !empty($this->fieldNamePrefix); + } + + public function getFieldNamePrefix(): string + { + return $this->fieldNamePrefix; + } + + public function setFieldNamePrefix(string $fieldNamePrefix): AuthorFieldOptions + { + $this->fieldNamePrefix = $fieldNamePrefix; + return $this; + } + + public function isUseDefaultLabel(): bool + { + return $this->useDefaultLabel; + } + + public function setUseDefaultLabel(bool $useDefaultLabel): AuthorFieldOptions + { + $this->useDefaultLabel = $useDefaultLabel; + return $this; + } + + public function isExclude(): bool + { + return $this->exclude; + } + + public function setExclude(bool $exclude): AuthorFieldOptions + { + $this->exclude = $exclude; + return $this; + } + + public function isSearch(): bool + { + return $this->search; + } + + public function setSearch(bool $search): AuthorFieldOptions + { + $this->search = $search; + return $this; + } + + public function isFilter(): bool + { + return $this->filter; + } + + public function setFilter(bool $filter): AuthorFieldOptions + { + $this->filter = $filter; + return $this; + } +} \ No newline at end of file diff --git a/src/Dca/DcaUtil.php b/src/Dca/DcaUtil.php deleted file mode 100644 index fd98eed2..00000000 --- a/src/Dca/DcaUtil.php +++ /dev/null @@ -1,1795 +0,0 @@ -container = $container; - $this->framework = $framework; - $this->routingUtil = $routingUtil; - $this->connection = $connection; - } - - /** - * Get a contao backend modal edit link. - * - * @param string $module Name of the module - * @param int $id Id of the entity - * @param string|null $label The label text - * - * @return string The edit link - */ - public function getEditLink(string $module, int $id, string $label = null): string - { - $url = $this->container->get('huh.utils.url')->getCurrentUrl([ - 'skipParams' => true, - ]); - - if (!$id) { - return ''; - } - - $label = sprintf(StringUtil::specialchars($label ?: $GLOBALS['TL_LANG']['tl_content']['editalias'][1]), $id); - - return sprintf( - ' %s', - $module, - $id, - $this->container->get('security.csrf.token_manager')->getToken($this->container->getParameter('contao.csrf_token_name'))->getValue(), - $label, - Image::getHtml('alias.svg', $label, 'style="vertical-align:top"') - ); - } - - /** - * Get a contao backend modal edit link. - * - * @param string $module Name of the module - * @param int $id Id of the entity - * @param string|null $label The label text - * @param string $table The dataContainer table - * @param int $width The modal window width - * - * @return string The modal edit link - * - * @deprecated Use DcaUtil::getPopupWizardLink() instead - */ - public function getModalEditLink(string $module, int $id, string $label = null, string $table = '', int $width = 1024): string - { - $url = $this->container->get('huh.utils.url')->getCurrentUrl([ - 'skipParams' => true, - ]); - - if (!$id) { - return ''; - } - - $label = sprintf(StringUtil::specialchars($label ?: $GLOBALS['TL_LANG']['tl_content']['editalias'][1]), $id); - - return sprintf( - ' %s', - $module, - $id, - ($table ? '&table='.$table : ''), - $this->container->get('security.csrf.token_manager')->getToken($this->container->getParameter('contao.csrf_token_name'))->getValue(), - $label, - $width, - $label, - Image::getHtml('alias.svg', $label, 'style="vertical-align:top"') - ); - } - - /** - * Get a contao backend modal archive edit link. - * - * @param string $module Name of the module - * @param int $id Id of the entity - * @param string $table The dataContainer table - * @param string|null $label The label text - * @param int $width The modal window width - * - * @return string The modal archive edit link - * - * @deprecated Use DcaUtil::getPopupWizardLink() instead - */ - public function getArchiveModalEditLink(string $module, int $id, string $table, string $label = null, int $width = 1024): string - { - $url = $this->container->get('huh.utils.url')->getCurrentUrl([ - 'skipParams' => true, - ]); - - if (!$id) { - return ''; - } - - $label = sprintf(StringUtil::specialchars($label ?: $GLOBALS['TL_LANG']['tl_content']['editalias'][1]), $id); - - return sprintf( - ' %s', - $module, - $id, - $table, - $this->container->get('security.csrf.token_manager')->getToken($this->container->getParameter('contao.csrf_token_name'))->getValue(), - $label, - $width, - $label, - Image::getHtml('alias.svg', $label, 'style="vertical-align:top"') - ); - } - - /** - * Get a contao backend popup link. - * - * Options: - * - attributes: (array) Link attributes as key value pairs. Will override title and style option. href and onclick are not allowed and will be removed from list. - * - title: (string) Overrride default link title - * - style: (string) Override default css style properties - * - onclick: (string) Override default onclick javascript code - * - icon: (string) Link icon to show as link text. Overrides default icon. - * - linkText: (string) A linkTitle to show as link text. Will be displayed after the link icon. Default empty. - * - url-only: (boolean) Return only url instead of a complete link element - * - * @param array $parameter An array of parameter. Using string is deprecated and will be removed in a future version. - * - * @return string - */ - public function getPopupWizardLink($parameter, array $options = []) - { - if (\is_string($parameter)) { - @trigger_error('Using string as parameter is deprecated and will be removed in a future version.', \E_USER_DEPRECATED); - $result = []; - $query = parse_url($parameter, \PHP_URL_QUERY); - - if (\is_string($query)) { - $parameter = $query; - } - parse_str($parameter, $result); - $parameter = $result; - } - - $route = $options['route'] ?? 'contao_backend'; - - $parameter['popup'] = 1; - $parameter['nb'] = 1; - - $url = $this->routingUtil->generateBackendRoute($parameter, true, true, $route); - - if (isset($options['url-only']) && true === $options['url-only']) { - return $url; - } - - $attributes = []; - - if (isset($options['attributes'])) { - $attributes = $options['attributes']; - } - - // title - if (!isset($options['title']) || !$options['title']) { - $title = $GLOBALS['TL_LANG']['tl_content']['edit'][0]; - } else { - $title = StringUtil::specialchars($options['title']); - } - - if (!isset($attributes['title'])) { - $attributes['title'] = $title; - } - - // style - $style = !isset($options['style']) ? 'padding-left: 5px; padding-top: 2px; display: inline-block;' : $options['style']; - - if (!empty($style) && !isset($attributes['style'])) { - $attributes['style'] = $style; - } - - // onclick - if (!isset($options['onclick']) || !$options['onclick']) { - $popupWidth = !isset($options['popupWidth']) || !$options['popupWidth'] ? 991 : $options['popupWidth']; - $popupTitle = !isset($options['popupTitle']) || !$options['popupTitle'] ? $title : $options['popupTitle']; - - $onclick = sprintf( - 'onclick="Backend.openModalIframe({\'width\':%s,\'title\':\'%s'.'\',\'url\':this.href});return false"', - $popupWidth, - $popupTitle - ); - } else { - $onclick = $options['onclick']; - } - - if (!isset($attributes['onclick'])) { - $attributes['onclick'] = $onclick; - } - - // link text and icon - $linkText = ''; - - if (!isset($options['icon'])) { - $linkText .= $this->framework->getAdapter(Image::class)->getHtml('alias.svg', $title, 'style="vertical-align:top"'); - } elseif (!empty($options['icon'])) { - $linkText = $this->framework->getAdapter(Image::class)->getHtml($options['icon'], $title, 'style="vertical-align:top"'); - } - - if (isset($options['linkText']) || !empty($options['linkText'])) { - $linkText .= $options['linkText']; - } - - // Attributes - $attributeQuery = ''; - - foreach ($attributes as $key => $value) { - if (\in_array($key, ['href', 'onclick'])) { - continue; - } - $attributeQuery .= $key.'="'.htmlspecialchars($value).'" '; - } - - return sprintf( - '%s', - $url, - $attributeQuery, - $onclick, - $linkText - ); - } - - /** - * Set initial $varData from dca. - * - * @param string $strTable Dca table name - * @param mixed $varData Object or array - * - * @return mixed Object or array with the default values - */ - public function setDefaultsFromDca($strTable, $varData = null, bool $includeSql = false) - { - $this->framework->getAdapter(Controller::class)->loadDataContainer($strTable); - - if (empty($GLOBALS['TL_DCA'][$strTable])) { - return $varData; - } - - $dbFields = []; - - foreach (Database::getInstance()->listFields($strTable) as $data) { - if (!isset($data['default'])) { - continue; - } - - $dbFields[$data['name']] = $data['default']; - } - - // Get all default values for the new entry - foreach ($GLOBALS['TL_DCA'][$strTable]['fields'] as $k => $v) { - $addDefaultValue = false; - $defaultValue = null; - - // check sql definition - if ($includeSql && isset($dbFields[$k])) { - $addDefaultValue = true; - $defaultValue = $dbFields[$k]; - } - - // check dca default value - if (\array_key_exists('default', $v)) { - $addDefaultValue = true; - $defaultValue = \is_array($v['default']) ? serialize($v['default']) : $v['default']; - } - - if (!$addDefaultValue) { - continue; - } - - // Encrypt the default value (see #3740) - if ($GLOBALS['TL_DCA'][$strTable]['fields'][$k]['eval']['encrypt'] ?? false) { - $defaultValue = $this->container->get('huh.utils.encryption')->encrypt($defaultValue); - } - - if ($addDefaultValue) { - if (\is_object($varData)) { - $varData->{$k} = $defaultValue; - } else { - if (null === $varData) { - $varData = []; - } - - if (\is_array($varData)) { - $varData[$k] = $defaultValue; - } - } - } - } - - return $varData; - } - - /** - * Retrieves an array from a dca config (in most cases eval) in the following priorities:. - * - * 1. The value associated to $array[$property] - * 2. The value retrieved by $array[$property . '_callback'] which is a callback array like ['Class', 'method'] or ['service.id', 'method'] - * 3. The value retrieved by $array[$property . '_callback'] which is a function closure array like ['Class', 'method'] - * - * @param $property - * - * @return mixed|null The value retrieved in the way mentioned above or null - */ - public function getConfigByArrayOrCallbackOrFunction(array $array, $property, array $arguments = []) - { - if (isset($array[$property])) { - return $array[$property]; - } - - if (!isset($array[$property.'_callback'])) { - return null; - } - - if (\is_array($array[$property.'_callback'])) { - $callback = $array[$property.'_callback']; - - if (!isset($callback[0]) || !isset($callback[1])) { - return null; - } - - try { - $instance = Controller::importStatic($callback[0]); - } catch (\Exception $e) { - return null; - } - - if (!method_exists($instance, $callback[1])) { - return null; - } - - try { - return \call_user_func_array([$instance, $callback[1]], $arguments); - } catch (\Error $e) { - return null; - } - } elseif (\is_callable($array[$property.'_callback'])) { - try { - return \call_user_func_array($array[$property.'_callback'], $arguments); - } catch (\Error $e) { - return null; - } - } - - return null; - } - - /** - * Sets the current date as the date added -> usually used on submit. - */ - public function setDateAdded(DataContainer $dc) - { - $modelUtil = $this->container->get('huh.utils.model'); - - if (null === $dc || null === ($model = $modelUtil->findModelInstanceByPk($dc->table, $dc->id)) || $model->dateAdded > 0) { - return null; - } - - $this->framework->createInstance(Database::class)->prepare("UPDATE $dc->table SET dateAdded=? WHERE id=? AND dateAdded = 0")->execute(time(), $dc->id); - } - - /** - * Sets the current date as the date added -> usually used on copy. - * - * @param $insertId - */ - public function setDateAddedOnCopy($insertId, DataContainer $dc) - { - $modelUtil = $this->container->get('huh.utils.model'); - - if (null === $dc || null === ($model = $modelUtil->findModelInstanceByPk($dc->table, $insertId)) || $model->dateAdded > 0) { - return null; - } - - $this->framework->createInstance(Database::class)->prepare("UPDATE $dc->table SET dateAdded=? WHERE id=? AND dateAdded = 0")->execute(time(), $insertId); - } - - /** - * Returns a list of fields as an option array for dca fields. - * - * Possible options: - * - array inputTypes Restrict to certain input types - * - array evalConditions restrict to certain dca eval - * - bool localizeLabels - * - bool skipSorting - * - * @deprecated Use Utils service instead - * @codeCoverageIgnore - */ - public function getFields(string $table, array $options = []): array - { - $fields = []; - - if (!$table) { - return $fields; - } - - $this->framework->getAdapter(Controller::class)->loadDataContainer($table); - System::loadLanguageFile($table); - - if (!isset($GLOBALS['TL_DCA'][$table]['fields'])) { - return $fields; - } - - foreach ($GLOBALS['TL_DCA'][$table]['fields'] as $name => $data) { - // restrict to certain input types - if (isset($options['inputTypes']) && \is_array($options['inputTypes']) && !empty($options['inputTypes']) && (isset($data['inputType']) && !\in_array($data['inputType'], $options['inputTypes']))) { - continue; - } - - // restrict to certain dca eval - if (isset($options['evalConditions']) && \is_array($options['evalConditions']) && !empty($options['evalConditions'])) { - foreach ($options['evalConditions'] as $key => $value) { - if (!isset($data['eval'][$key]) || $data['eval'][$key] !== $value) { - continue 2; - } - } - } - - if (isset($options['localizeLabels']) && !$options['localizeLabels']) { - $fields[$name] = $name; - } else { - $label = $name; - - if (isset($data['label'][0]) && $data['label'][0]) { - $label .= ' ['.$data['label'][0].']'; - } - - $fields[$name] = $label; - } - } - - if (!isset($options['skipSorting']) || !$options['skipSorting']) { - asort($fields); - } - - return $fields; - } - - /** - * Adds an override selector to every field in $fields to the dca associated with $destinationTable. - */ - public function addOverridableFields(array $fields, string $sourceTable, string $destinationTable, array $options = []) - { - $this->framework->getAdapter(Controller::class)->loadDataContainer($sourceTable); - System::loadLanguageFile($sourceTable); - $sourceDca = $GLOBALS['TL_DCA'][$sourceTable]; - - $this->framework->getAdapter(Controller::class)->loadDataContainer($destinationTable); - System::loadLanguageFile($destinationTable); - $destinationDca = &$GLOBALS['TL_DCA'][$destinationTable]; - - foreach ($fields as $field) { - // add override boolean field - $overrideFieldname = 'override'.ucfirst($field); - - $destinationDca['fields'][$overrideFieldname] = [ - 'label' => &$GLOBALS['TL_LANG'][$destinationTable][$overrideFieldname], - 'exclude' => true, - 'inputType' => 'checkbox', - 'eval' => ['tl_class' => 'w50', 'submitOnChange' => true, 'isOverrideSelector' => true], - 'sql' => "char(1) NOT NULL default ''", - ]; - - if (isset($options['checkboxDcaEvalOverride']) && \is_array($options['checkboxDcaEvalOverride'])) { - $destinationDca['fields'][$overrideFieldname]['eval'] = array_merge($destinationDca['fields'][$overrideFieldname]['eval'], $options['checkboxDcaEvalOverride']); - } - - // important: nested selectors need to be in reversed order -> see DC_Table::getPalette() - $destinationDca['palettes']['__selector__'] = array_merge([$overrideFieldname], isset($destinationDca['palettes']['__selector__']) && \is_array($destinationDca['palettes']['__selector__']) ? $destinationDca['palettes']['__selector__'] : []); - - // copy field - $destinationDca['fields'][$field] = $sourceDca['fields'][$field]; - - // subpalette - $destinationDca['subpalettes'][$overrideFieldname] = $field; - - if (!isset($options['skipLocalization']) || !$options['skipLocalization']) { - $GLOBALS['TL_LANG'][$destinationTable][$overrideFieldname] = [ - $this->container->get('translator')->trans('huh.utils.misc.override.label', [ - '%fieldname%' => $GLOBALS['TL_DCA'][$sourceTable]['fields'][$field]['label'][0] ?? $field, - ]), - $this->container->get('translator')->trans('huh.utils.misc.override.desc', [ - '%fieldname%' => $GLOBALS['TL_DCA'][$sourceTable]['fields'][$field]['label'][0] ?? $field, - ]), - ]; - } - } - } - - /** - * Retrieves a property of given contao model instances by *ascending* priority, i.e. the last instance of $instances - * will have the highest priority. - * - * CAUTION: This function assumes that you have used addOverridableFields() in this class!! That means, that a value in a - * model instance is only used if it's either the first instance in $arrInstances or "overrideFieldname" is set to true - * in the instance. - * - * @param string $property The property name to retrieve - * @param array $instances An array of instances in ascending priority. Instances can be passed in the following form: - * ['tl_some_table', $instanceId] or $objInstance - * - * @return mixed - */ - public function getOverridableProperty(string $property, array $instances) - { - $result = null; - $preparedInstances = []; - - // prepare instances - foreach ($instances as $instance) { - if (\is_array($instance)) { - if (null !== ($objInstance = $this->container->get('huh.utils.model')->findModelInstanceByPk($instance[0], $instance[1]))) { - $preparedInstances[] = $objInstance; - } - } elseif ($instance instanceof Model || \is_object($instance)) { - $preparedInstances[] = $instance; - } - } - - foreach ($preparedInstances as $i => $preparedInstance) { - if (0 == $i || $preparedInstance->{'override'.ucfirst($property)}) { - $result = $preparedInstance->{$property}; - } - } - - return $result; - } - - /** - * This function transforms an entity's palette (that can also contain sub palettes and concatenated type selectors) to a flatten - * palette where every field can be overridden. - * - * CAUTION: This function assumes that you have used addOverridableFields() for adding the fields that are overridable. The latter ones - * are $overridableFields - * - * This function is useful if you want to adjust a palette for sub entities that can override properties of their ancestor(s). - * Use $this->getOverridableProperty() for computing the correct value respecting the entity hierarchy. - */ - public function flattenPaletteForSubEntities(string $table, array $overridableFields) - { - $this->framework->getAdapter(Controller::class)->loadDataContainer($table); - - $pm = PaletteManipulator::create(); - - $dca = &$GLOBALS['TL_DCA'][$table]; - $arrayUtil = $this->container->get('huh.utils.array'); - - // Contao 4.4 fix - $replaceFields = []; - - // palette - foreach ($overridableFields as $field) { - if (true === ($dca['fields'][$field]['eval']['submitOnChange'] ?? false)) { - unset($dca['fields'][$field]['eval']['submitOnChange']); - - if (\in_array($field, $dca['palettes']['__selector__'])) { - // flatten concatenated type selectors - foreach ($dca['subpalettes'] as $selector => $subPaletteFields) { - if (false !== strpos($selector, $field.'_')) { - if ($dca['subpalettes'][$selector]) { - $subPaletteFields = explode(',', $dca['subpalettes'][$selector]); - - foreach (array_reverse($subPaletteFields) as $subPaletteField) { - $pm->addField($subPaletteField, $field); - } - } - - // remove nested field in order to avoid its normal "selector" behavior - $arrayUtil->removeValue($field, $dca['palettes']['__selector__']); - unset($dca['subpalettes'][$selector]); - } - } - - // flatten sub palettes - if (isset($dca['subpalettes'][$field]) && $dca['subpalettes'][$field]) { - $subPaletteFields = explode(',', $dca['subpalettes'][$field]); - - foreach (array_reverse($subPaletteFields) as $subPaletteField) { - $pm->addField($subPaletteField, $field); - } - - // remove nested field in order to avoid its normal "selector" behavior - $arrayUtil->removeValue($field, $dca['palettes']['__selector__']); - unset($dca['subpalettes'][$field]); - } - } - } - - $replaceFields[] = $field; - -// $pm->addField('override'.ucfirst($field), $field)->removeField($field); - } - - $pm->applyToPalette('default', $table); - - foreach ($replaceFields as $replaceField) { - $dca['palettes']['default'] = str_replace($replaceField, 'override'.ucfirst($replaceField), $dca['palettes']['default']); - } - } - - /** - * Return if the current alias already exist in table. - * - * @throws \Doctrine\DBAL\DBALException - */ - public function aliasExist(string $alias, int $id, string $table, $options = []): bool - { - $aliasField = $options['aliasField'] ?? 'alias'; - - $stmt = $this->connection->prepare('SELECT id FROM '.$table.' WHERE '.$aliasField.'=? AND id!=?'); - - return $stmt->executeQuery([$alias, $id])->rowCount() > 0; - } - - /** - * Generate an alias with unique check. - * - * @param mixed $alias The current alias (if available) - * @param int $id The entity's id - * @param string|null $table The entity's table (pass a comma separated list if the validation should be expanded to multiple tables like tl_news AND tl_member. ATTENTION: the first table needs to be the one we're currently in). Pass null to skip unqiue check. - * @param string $title The value to use as a base for the alias - * @param bool $keepUmlauts Set to true if German umlauts should be kept - * - * @throws \Exception - * - * @return string - */ - public function generateAlias(?string $alias, int $id, ?string $table, string $title, bool $keepUmlauts = true, $options = []) - { - $autoAlias = false; - $aliasField = $options['aliasField'] ?? 'alias'; - - // Generate alias if there is none - if (empty($alias)) { - $autoAlias = true; - $alias = StringUtil::generateAlias($title); - } - - if (!$keepUmlauts) { - $alias = preg_replace(['/ä/i', '/ö/i', '/ü/i', '/ß/i'], ['ae', 'oe', 'ue', 'ss'], $alias); - } - - if (null === $table) { - return $alias; - } - - $originalAlias = $alias; - - // multiple tables? - if (false !== strpos($table, ',')) { - $tables = explode(',', $table); - - foreach ($tables as $i => $partTable) { - // the table in which the entity is - if (0 === $i) { - if ($this->aliasExist($alias, $id, $table, $options)) { - if (!$autoAlias) { - throw new \InvalidArgumentException(sprintf($GLOBALS['TL_LANG']['ERR']['aliasExists'], $alias)); - } - - $alias = $originalAlias.'-'.$id; - } - } else { - // another table - $stmt = $this->connection->prepare("SELECT id FROM {$partTable} WHERE ' . $aliasField . '=?"); - - // Check whether the alias exists - if ($stmt->execute([$alias])->rowCount() > 0) { - throw new \InvalidArgumentException(sprintf($GLOBALS['TL_LANG']['ERR']['aliasExists'], $alias)); - } - } - } - } else { - if (!$this->aliasExist($alias, $id, $table, $options)) { - return $alias; - } - - // Check whether the alias exists - if (!$autoAlias) { - throw new \Exception(sprintf($GLOBALS['TL_LANG']['ERR']['aliasExists'], $alias)); - } - - // Add ID to alias - $alias .= '-'.$id; - } - - return $alias; - } - - public function addAuthorFieldAndCallback(string $table, string $fieldPrefix = '') - { - $this->framework->getAdapter(Controller::class)->loadDataContainer($table); - - // callbacks - $GLOBALS['TL_DCA'][$table]['config']['oncreate_callback']['setAuthorIDOnCreate'] = [self::class, 'setAuthorIDOnCreate']; - $GLOBALS['TL_DCA'][$table]['config']['onload_callback']['modifyAuthorPaletteOnLoad'] = [self::class, 'modifyAuthorPaletteOnLoad', true]; - - // fields - $GLOBALS['TL_DCA'][$table]['fields'][$fieldPrefix ? $fieldPrefix.ucfirst(static::PROPERTY_AUTHOR_TYPE) : static::PROPERTY_AUTHOR_TYPE] = [ - 'label' => &$GLOBALS['TL_LANG']['MSC']['utilsBundle']['authorType'], - 'exclude' => true, - 'filter' => true, - 'default' => static::AUTHOR_TYPE_NONE, - 'inputType' => 'select', - 'options' => [ - static::AUTHOR_TYPE_NONE, - static::AUTHOR_TYPE_MEMBER, - static::AUTHOR_TYPE_USER, - // session is only added if it's already set in the dca - ], - 'reference' => $GLOBALS['TL_LANG']['MSC']['utilsBundle']['authorType'], - 'eval' => ['doNotCopy' => true, 'submitOnChange' => true, 'mandatory' => true, 'tl_class' => 'w50 clr'], - 'sql' => "varchar(255) NOT NULL default 'none'", - ]; - - $GLOBALS['TL_DCA'][$table]['fields'][$fieldPrefix ? $fieldPrefix.ucfirst(static::PROPERTY_AUTHOR) : static::PROPERTY_AUTHOR] = [ - 'label' => &$GLOBALS['TL_LANG']['MSC']['utilsBundle']['author'], - 'exclude' => true, - 'search' => true, - 'filter' => true, - 'inputType' => 'select', - 'default' => '0', - 'options_callback' => function () { - return $this->container->get('huh.utils.choice.model_instance')->getCachedChoices([ - 'dataContainer' => 'tl_member', - 'labelPattern' => '%firstname% %lastname% (ID %id%)', - ]); - }, - 'save_callback' => [function ($value, $dc) { - if (!$value) { - return 0; - } - - return $value; - }], - 'eval' => [ - 'doNotCopy' => true, - 'chosen' => true, - 'includeBlankOption' => true, - 'tl_class' => 'w50', - ], - 'sql' => "varchar(64) NOT NULL default '0'", - ]; - } - - public function setAuthorIDOnCreate(string $table, int $id, array $row, DataContainer $dc) - { - /** @var Model $model */ - $model = $this->container->get(ModelUtil::class)->findModelInstanceByPk($table, $id); - /** @var Database $db */ - $db = $this->framework->createInstance(Database::class); - - if (null === $model - || !$db->fieldExists(static::PROPERTY_AUTHOR_TYPE, $table) - || !$db->fieldExists(static::PROPERTY_AUTHOR, $table)) { - return false; - } - - $stmt = $db->prepare( - 'UPDATE '.$model::getTable() - .' SET '.static::PROPERTY_AUTHOR_TYPE.'=?, '.static::PROPERTY_AUTHOR.'=?' - .' WHERE id=?' - ); - - if ($this->container->get('huh.utils.container')->isFrontend()) { - if (FE_USER_LOGGED_IN) { - $stmt->execute(static::AUTHOR_TYPE_MEMBER, $this->framework->getAdapter(FrontendUser::class)->getInstance()->id, $model->id); - } else { - // php session - $stmt->execute(static::AUTHOR_TYPE_SESSION, session_id(), $model->id); - } - } else { - $stmt->execute(static::AUTHOR_TYPE_USER, $this->framework->getAdapter(BackendUser::class)->getInstance()->id, $model->id); - } - } - - public function modifyAuthorPaletteOnLoad(DataContainer $dc) - { - if (!$this->container->get('huh.utils.container')->isBackend()) { - return false; - } - - if (null === $dc || !$dc->id || !$dc->table) { - return false; - } - - if (null === ($model = $this->container->get('huh.utils.model')->findModelInstanceByPk($dc->table, $dc->id))) { - return false; - } - - $dca = &$GLOBALS['TL_DCA'][$dc->table]; - - // author handling - if ($model->{static::PROPERTY_AUTHOR_TYPE} == static::AUTHOR_TYPE_NONE) { - unset($dca['fields'][static::PROPERTY_AUTHOR]); - } - - if ($model->{static::PROPERTY_AUTHOR_TYPE} == static::AUTHOR_TYPE_USER) { - $dca['fields'][static::PROPERTY_AUTHOR]['options_callback'] = function () { - return $this->container->get(ModelInstanceChoice::class)->getCachedChoices([ - 'dataContainer' => 'tl_user', - 'labelPattern' => '%name% (ID %id%)', - ]); - }; - } - - if ($model->{static::PROPERTY_AUTHOR_TYPE} == static::AUTHOR_TYPE_SESSION) { - $dca['fields'][static::PROPERTY_AUTHOR_TYPE]['options'] = array_merge($dca['fields'][static::PROPERTY_AUTHOR_TYPE]['options'], [static::AUTHOR_TYPE_SESSION]); - // do not allow to edit in backend - $dca['fields'][static::PROPERTY_AUTHOR_TYPE]['eval']['readonly'] = true; - - unset($dca['fields'][static::PROPERTY_AUTHOR]['options_callback']); - $dca['fields'][static::PROPERTY_AUTHOR]['inputType'] = 'text'; - // do not allow to edit in backend - $dca['fields'][static::PROPERTY_AUTHOR]['eval']['readonly'] = true; - $dca['fields'][static::PROPERTY_AUTHOR]['label'][0] = $GLOBALS['TL_LANG']['MSC']['utilsBundle'][static::PROPERTY_AUTHOR_TYPE][self::AUTHOR_TYPE_SESSION]; - } - } - - /** - * Returns (nearly) all registered datacontainers as array. - * - * Options: - * - bool onlyTableType: Return only table data containers - * - * @return array - */ - public function getDataContainers(array $options = []) - { - $dcaTables = $this->framework->createInstance(Database::class)->listTables(); - - if (isset($options['onlyTableType']) && true === $options['onlyTableType']) { - return $dcaTables; - } - - foreach ($GLOBALS['BE_MOD'] as $arrSection) { - foreach ($arrSection as $strModule => $arrModule) { - foreach ($arrModule as $strKey => $varValue) { - if (\is_array($arrModule['tables'] ?? null)) { - $dcaTables = array_merge($dcaTables, $arrModule['tables']); - } - } - } - } - $dcaTables = array_unique($dcaTables); - asort($dcaTables); - - return array_values($dcaTables); - } - - /** - * @param bool $includeNotificationCenterPlusTokens - * - * @return array - */ - public function getNewNotificationTypeArray($includeNotificationCenterPlusTokens = false) - { - $type = [ - 'recipients' => ['admin_email'], - 'email_subject' => ['admin_email'], - 'email_text' => ['admin_email'], - 'email_html' => ['admin_email'], - 'file_name' => ['admin_email'], - 'file_content' => ['admin_email'], - 'email_sender_name' => ['admin_email'], - 'email_sender_address' => ['admin_email'], - 'email_recipient_cc' => ['admin_email'], - 'email_recipient_bcc' => ['admin_email'], - 'email_replyTo' => ['admin_email'], - 'attachment_tokens' => [], - ]; - - if ($includeNotificationCenterPlusTokens) { - foreach ($type as $field => $tokens) { - $type[$field] = array_unique(array_merge([ - 'env_*', - 'page_*', - 'user_*', - 'date', - 'last_update', - ], $tokens)); - } - } - - return $type; - } - - public function activateNotificationType($strGroup, $strType, $arrType) - { - $GLOBALS['NOTIFICATION_CENTER']['NOTIFICATION_TYPE'] = array_merge_recursive( - (array) $GLOBALS['NOTIFICATION_CENTER']['NOTIFICATION_TYPE'], - [ - $strGroup => [ - $strType => $arrType, - ], - ] - ); - } - - /** - * Adds an alias field to the dca and to the desired palettes. - * - * @param $dca - * @param $generateAliasCallback mixed The callback to call for generating the alias - * @param $paletteField String The field after which to insert the alias field in the palettes - * @param array $palettes The palettes in which to insert the field - */ - public function addAliasToDca(string $dca, $generateAliasCallback, string $paletteField, array $palettes = ['default']) - { - Controller::loadDataContainer($dca); - - $arrDca = &$GLOBALS['TL_DCA'][$dca]; - - // add to palettes - foreach ($palettes as $strPalette) { - $arrDca['palettes'][$strPalette] = preg_replace('/('.$paletteField.')(;|,)/', '$1,alias$2', $arrDca['palettes'][$strPalette]); - } - - // add field - $arrDca['fields']['alias'] = [ - 'label' => &$GLOBALS['TL_LANG']['MSC']['alias'], - 'exclude' => true, - 'search' => true, - 'inputType' => 'text', - 'eval' => ['rgxp' => 'alias', 'unique' => true, 'maxlength' => 128, 'tl_class' => 'w50'], - 'save_callback' => [$generateAliasCallback], - 'sql' => "varchar(128) COLLATE utf8_bin NOT NULL default ''", - ]; - } - - /** - * @param $strField - * @param $strTable - * - * @return mixed - */ - public function getLocalizedFieldName($strField, $strTable) - { - Controller::loadDataContainer($strTable); - System::loadLanguageFile($strTable); - - return $GLOBALS['TL_DCA'][$strTable]['fields'][$strField]['label'][0] ?: $strField; - } - - /** - * Load a data container in a testable way. - */ - public function loadDc(string $table) - { - if (!isset($GLOBALS['TL_DCA'][$table]) || null === $GLOBALS['TL_DCA'][$table]) { - /** @var Controller $controller */ - $controller = $this->framework->getAdapter(Controller::class); - - $controller->loadDataContainer($table); - } - } - - /** - * Load a language file in a testable way. - */ - public function loadLanguageFile(string $table) - { - /** @var System $system */ - $system = $this->framework->getAdapter(System::class); - - $system->loadLanguageFile($table); - } - - public function isDcMultilingual(string $table) - { - $this->loadDc($table); - - $bundleName = 'Terminal42\DcMultilingualBundle\Terminal42DcMultilingualBundle'; - - return isset($GLOBALS['TL_DCA'][$table]['config']['dataContainer']) && - 'Multilingual' === $GLOBALS['TL_DCA'][$table]['config']['dataContainer'] && - $this->container->get('huh.utils.container')->isBundleActive($bundleName); - } - - public function isDcMultilingual3() - { - return class_exists('Terminal42\DcMultilingualBundle\Model\Multilingual') && - !method_exists('Terminal42\DcMultilingualBundle\Model\Multilingual', 'createModelFromDbResult'); - } - - public function generateDcOperationsButtons($row, $table, $rootIds = [], $options = []) - { - $return = ''; - - // Edit multiple - if ('select' == Input::get('act')) { - $return .= ''; - } // Regular buttons - else { - $return .= $this->doGenerateDcOperationsButtons($row, $table, $rootIds, false, null, $options); - - // no picker support due to DataContainer not being extensible - } - - return $return; - } - - public function doGenerateDcOperationsButtons($arrRow, $strTable, $arrRootIds = [], $blnCircularReference = false, $arrChildRecordIds = null, $options = []) - { - if (empty($GLOBALS['TL_DCA'][$strTable]['list']['operations'])) { - return ''; - } - - $return = ''; - - $skipOperations = $options['skipOperations'] ?? []; - $operations = $options['operations'] ?? array_keys($GLOBALS['TL_DCA'][$strTable]['list']['operations']); - - if (!empty($skipOperations) && !isset($options['operations'])) { - $operations = array_diff($operations, $skipOperations); - } - - foreach ($GLOBALS['TL_DCA'][$strTable]['list']['operations'] as $k => $v) { - if (!\in_array($k, $operations)) { - continue; - } - - $v = \is_array($v) ? $v : [$v]; - $id = StringUtil::specialchars(rawurldecode($arrRow['id'])); - - $label = isset($v['label']) ? (\is_array($v['label']) ? $v['label'][0] : $v['label']) : $k; - $title = sprintf(isset($v['label']) ? (\is_array($v['label']) ? $v['label'][1] : $v['label']) : $k, $id); - $attributes = ('' != $v['attributes']) ? ' '.ltrim(sprintf($v['attributes'], $id, $id)) : ''; - - parse_str(StringUtil::decodeEntities($v['href'] ?? ''), $params); - - if (version_compare(VERSION, '4.13', '>=') && \in_array($k, ['toggle', 'feature'])) { - $state = $arrRow[$params['field']] ? 1 : 0; - - if ($v['reverse'] ?? false) { - $state = $arrRow[$params['field']] ? 0 : 1; - } - - $icon = $v['icon']; - $_icon = pathinfo($v['icon'], \PATHINFO_FILENAME).'_.'.pathinfo($v['icon'], \PATHINFO_EXTENSION); - - if (false !== strpos($v['icon'], '/')) { - $_icon = \dirname($v['icon']).'/'.$_icon; - } - - if ('visible.svg' == $icon) { - $_icon = 'invisible.svg'; - } - - if (false === strpos($attributes, 'onclick')) { - $attributes = sprintf( - 'onclick="Backend.getScrollOffset();return AjaxRequest.toggleField(this,'.($state ? 'true' : 'false').')"', - $id, $id - ); - } - } - - // Add the key as CSS class - if (false !== strpos($attributes, 'class="')) { - $attributes = str_replace('class="', 'class="'.$k.' ', $attributes); - } else { - $attributes = ' class="'.$k.'"'.$attributes; - } - - // Call a custom function instead of using the default button - if (\is_array($v['button_callback'])) { - $callback = System::importStatic($v['button_callback'][0]); - $return .= $callback->{$v['button_callback'][1]}($arrRow, $v['href'], $label, $title, $v['icon'], $attributes, $strTable, $arrRootIds, $arrChildRecordIds, $blnCircularReference, null, null, $this); - - continue; - } elseif (\is_callable($v['button_callback'])) { - $return .= $v['button_callback']($arrRow, $v['href'], $label, $title, $v['icon'], $attributes, $strTable, $arrRootIds, $arrChildRecordIds, $blnCircularReference, null, null, $this); - - continue; - } - - // Generate all buttons except "move up" and "move down" buttons - if ('move' != $k && 'move' != $v) { - if ('show' == $k) { - $return .= ''.Image::getHtml($v['icon'], $label).' '; - } else { - $href = Controller::addToUrl($v['href'].'&id='.$arrRow['id'].(Input::get('nb') ? '&nc=1' : '')).'&rt='.RequestToken::get(); - - if (version_compare(VERSION, '4.13', '>=') && \in_array($k, ['toggle', 'feature'])) { - $icon = Image::getHtml($state ? $icon : $_icon, $label, 'data-icon="'.Image::getPath($icon).'" data-icon-disabled="'.Image::getPath($_icon).'" data-state="'.$state.'"'); - } else { - $icon = Image::getHtml($v['icon'], $label); - } - - $return .= ''.$icon.' '; - } - - continue; - } - } - - return trim($return); - } - - public function generateSitemap() - { - $automator = System::importStatic('Automator'); - $automator->generateSitemap(); - } - - /** - * Mostly used for Form::prepareSpecialValueForOutput(). - * - * @param $activeRecord - */ - public function getDCTable(string $table, $activeRecord): DC_Table_Utils - { - $dc = new DC_Table_Utils($table); - $dc->activeRecord = $activeRecord; - $dc->id = $activeRecord->id; - - return $dc; - } - - public function getAuthorNameByUserId($id) - { - if (null !== ($user = $this->container->get('huh.utils.model')->findModelInstanceByPk('tl_user', $id))) { - return $user->name; - } - - return false; - } - - public function getAuthorNameLinkByUserId($id) - { - if (null !== ($user = $this->container->get('huh.utils.model')->findModelInstanceByPk('tl_user', $id))) { - return ''.Controller::replaceInsertTags('{{email_open::'.$user->email.'}}').$user->name.''; - } - - return false; - } - - public function setFieldsToReadOnly(&$dca, array $config = []) - { - $skipFields = $config['skipFields'] ?? []; - $fields = $config['fields'] ?? []; - - foreach ($dca['fields'] as $field => &$data) { - if (!empty($fields)) { - if (!\in_array($field, $fields)) { - continue; - } - } elseif (\in_array($field, $skipFields)) { - continue; - } - - switch ($data['inputType']) { - case 'checkbox': - case 'radio': - case 'radioTable': - $data['eval']['disabled'] = true; - - break; - - case 'select': - case 'imageSize': - $data['eval']['readonly'] = true; - $data['eval']['class'] = 'readonly'; - - break; - - case 'fileTree': - case 'metaWizard': - case 'tagsinput': - $data['eval']['readonly'] = true; - $data['eval']['tl_class'] = $data['eval']['tl_class'].' readonly'; - - break; - - case 'multiColumnEditor': - $data['eval']['readonly'] = true; - - $this->setFieldsToReadOnly($data['eval']['multiColumnEditor'], $config); - - break; - - default: - $data['eval']['readonly'] = true; - - // TODO dispatch event for custom - break; - } - } - } - - public function getTranslatedModuleNameByTable(string $table) - { - foreach ($GLOBALS['BE_MOD'] as $groupName => $groupModules) { - if (empty($groupModules)) { - continue; - } - - foreach ($groupModules as $moduleName => $moduleConfig) { - if (!isset($moduleConfig['tables']) || !\is_array($moduleConfig['tables'])) { - continue; - } - - if (\in_array($table, $moduleConfig['tables'])) { - return StringUtil::specialchars($GLOBALS['TL_LANG']['MOD'][$moduleName][0]); - } - } - } - - return false; - } - - public function getRenderedDiff(string $table, array $source, array $target, array $config = []) - { - $result = ''; - - $skipFields = $config['skipFields'] ?? []; - $restrictFields = $config['restrictFields'] ?? []; - $tableCallbacks = $config['tableCallbacks'] ?? []; - - $this->loadDc($table); - $this->loadLanguageFile($table); - - $dca = $GLOBALS['TL_DCA'][$table]; - - $arrayUtil = System::getContainer()->get('huh.utils.array'); - - // Get the order fields - $dcaExtractor = DcaExtractor::getInstance($table); - $fields = $dcaExtractor->getFields(); - $orderFields = $dcaExtractor->getOrderFields(); - - // Find the changed fields and highlight the changes - foreach ($target as $k => $v) { - if (empty($restrictFields) && \in_array($k, $skipFields)) { - continue; - } - - if (!empty($restrictFields) && !\in_array($k, $restrictFields)) { - continue; - } - - if ($source[$k] != $target[$k]) { - if ($dca['fields'][$k]['eval']['doNotShow'] || $dca['fields'][$k]['eval']['hideInput']) { - continue; - } - - $isBinary = 0 === strncmp($fields[$k], 'binary(', 7) || 0 === strncmp($fields[$k], 'blob ', 5); - - if ($dca['fields'][$k]['eval']['multiple'] || \in_array($k, $orderFields)) { - if (isset($dca['fields'][$k]['eval']['csv'])) { - $delimiter = $dca['fields'][$k]['eval']['csv']; - - if (isset($target[$k])) { - $target[$k] = preg_replace('/'.preg_quote($delimiter, ' ?/').'/', $delimiter.' ', $target[$k]); - } - - if (isset($source[$k])) { - $source[$k] = preg_replace('/'.preg_quote($delimiter, ' ?/').'/', $delimiter.' ', $source[$k]); - } - } else { - // Convert serialized arrays into strings - if (\is_array(($tmp = StringUtil::deserialize($target[$k]))) && !\is_array($target[$k])) { - $target[$k] = $arrayUtil->implodeRecursive($tmp, $isBinary); - } - - if (\is_array(($tmp = StringUtil::deserialize($source[$k]))) && !\is_array($source[$k])) { - $source[$k] = $arrayUtil->implodeRecursive($tmp, $isBinary); - } - } - } - - unset($tmp); - - // Convert binary UUIDs to their hex equivalents (see #6365) - if ($isBinary) { - if (Validator::isBinaryUuid($target[$k])) { - $target[$k] = StringUtil::binToUuid($target[$k]); - } - - if (Validator::isBinaryUuid($source[$k])) { - $source[$k] = StringUtil::binToUuid($source[$k]); - } - } - - // Convert date fields - if ('date' == $dca['fields'][$k]['eval']['rgxp']) { - $target[$k] = \Date::parse(Config::get('dateFormat'), $target[$k] ?: ''); - $source[$k] = \Date::parse(Config::get('dateFormat'), $source[$k] ?: ''); - } elseif ('time' == $dca['fields'][$k]['eval']['rgxp']) { - $target[$k] = \Date::parse(Config::get('timeFormat'), $target[$k] ?: ''); - $source[$k] = \Date::parse(Config::get('timeFormat'), $source[$k] ?: ''); - } elseif ('datim' == $dca['fields'][$k]['eval']['rgxp'] || 'tstamp' == $k) { - $target[$k] = \Date::parse(Config::get('datimFormat'), $target[$k] ?: ''); - $source[$k] = \Date::parse(Config::get('datimFormat'), $source[$k] ?: ''); - } - - // Decode entities if the "decodeEntities" flag is not set (see #360) - if (empty($dca['fields'][$k]['eval']['decodeEntities'])) { - $target[$k] = StringUtil::decodeEntities($target[$k]); - $source[$k] = StringUtil::decodeEntities($source[$k]); - } - - // Convert strings into arrays - if (!\is_array($target[$k])) { - $target[$k] = explode("\n", $target[$k]); - } - - if (!\is_array($source[$k])) { - $source[$k] = explode("\n", $source[$k]); - } - - // custom callbacks to modify data - if (isset($tableCallbacks[$table]) && \is_callable($tableCallbacks[$table])) { - $tableCallbacks[$table]($k, $v, $source, $target); - } - - $diff = new \Diff($source[$k], $target[$k]); - $result .= $diff->render(new DiffRenderer(['field' => ($dca['fields'][$k]['label'][0] ?: (isset($GLOBALS['TL_LANG']['MSC'][$k]) ? (\is_array($GLOBALS['TL_LANG']['MSC'][$k]) ? $GLOBALS['TL_LANG']['MSC'][$k][0] : $GLOBALS['TL_LANG']['MSC'][$k]) : $k))])); - } - } - - // Identical versions - if ('' == $result) { - $result = '

'.$GLOBALS['TL_LANG']['MSC']['identicalVersions'].'

'; - } - - return $result; - } - - public function prepareRowEntryForList($table, string $field, $value) - { - $this->loadDc($table); - $this->loadLanguageFile($table); - - $dca = $GLOBALS['TL_DCA'][$table]; - - $arrayUtil = System::getContainer()->get('huh.utils.array'); - - // Get the order fields - $dcaExtractor = DcaExtractor::getInstance($table); - $fields = $dcaExtractor->getFields(); - $orderFields = $dcaExtractor->getOrderFields(); - - if ($dca['fields'][$field]['eval']['doNotShow'] || $dca['fields'][$field]['eval']['hideInput']) { - return ''; - } - - $sql = \is_array($fields[$field]) ? $fields[$field]['type'] : $fields[$field]; - - $isBinary = 0 === strncmp($sql, 'binary(', 7) || 0 === strncmp($sql, 'blob ', 5); - - if ($dca['fields'][$field]['eval']['multiple'] || \in_array($field, $orderFields)) { - if (isset($dca['fields'][$field]['eval']['csv'])) { - $delimiter = $dca['fields'][$field]['eval']['csv']; - - if ($value) { - $value = preg_replace('/'.preg_quote($delimiter, ' ?/').'/', $delimiter.' ', $value); - } - } else { - // Convert serialized arrays into strings - if (\is_array(($tmp = StringUtil::deserialize($value))) && !\is_array($value)) { - $value = $arrayUtil->implodeRecursive($tmp, $isBinary); - } - } - } - - unset($tmp); - - // Convert binary UUIDs to their hex equivalents (see #6365) - if ($isBinary) { - if (Validator::isBinaryUuid($value)) { - $value = StringUtil::binToUuid($value); - } - } - - // Convert date fields - if ('date' == $dca['fields'][$field]['eval']['rgxp']) { - $value = \Date::parse(Config::get('dateFormat'), $value ?: ''); - } elseif ('time' == $dca['fields'][$field]['eval']['rgxp']) { - $value = \Date::parse(Config::get('timeFormat'), $value ?: ''); - } elseif ('datim' == $dca['fields'][$field]['eval']['rgxp'] || 'tstamp' == $field) { - $value = \Date::parse(Config::get('datimFormat'), $value ?: ''); - } - - // Decode entities if the "decodeEntities" flag is not set (see #360) - if (empty($dca['fields'][$field]['eval']['decodeEntities'])) { - $value = StringUtil::decodeEntities($value); - } - - return $value; - } - - public function getFieldLabel(string $table, string $field) - { - $this->loadDc($table); - $this->loadLanguageFile($table); - - $dca = $GLOBALS['TL_DCA'][$table]; - - return $dca['fields'][$field]['label'][0] ?: (isset($GLOBALS['TL_LANG']['MSC'][$field]) ? (\is_array($GLOBALS['TL_LANG']['MSC'][$field]) ? $GLOBALS['TL_LANG']['MSC'][$field][0] : $GLOBALS['TL_LANG']['MSC'][$field]) : $field); - } - - /** - * Returns the set of pid and sorting to be used in an sql update statement. Also updates the existing records according to the usage. - * - * The method can be used in several ways: - * - *
    - *
  • Insert in an archive of a certain pid as first item: $pid must be set (0 is also ok), $insertAfterId needs to be null
  • - *
  • Insert after a record of a certain id: $insertAfterId must be set, $pid can be set if necessary
  • - *
- * - * @example - * - * // insert a new record after another one with the ID 82 - * - * $news = new \Contao\NewsModel(); - * $news->pid = 3; - * $news->tstamp = time(); - * $news->title = 'Something'; - * $news->save(); - * $set = System::getContainer()->get('huh.utils.dca')->getNewSortingPosition( - * 'tl_news', $news->id, 3, 82 - * ); - * - * // store the returned set to the news record created above as usual - * - * Hint: Mostly taken from DC_Table::getNewPosition(). Removed: handling if only a pid field is present, mode handling (since we don't have it in this context). - */ - public function getNewSortingPosition(string $table, int $id, $pid = null, $insertAfterId = null): array - { - $set = []; - - /* @var Database $db */ - if (!($db = $this->framework->createInstance(Database::class))) { - return $set; - } - - // If there is pid and sorting - if ($db->fieldExists('pid', $table) && $db->fieldExists('sorting', $table)) { - // PID is set (insert after or into the parent record) - if (is_numeric($pid)) { - // ID is set (insert after the current record) - if ($insertAfterId) { - $objCurrentRecord = $db->prepare("SELECT * FROM $table WHERE id=? AND pid=?") - ->limit(1) - ->execute($insertAfterId, $pid); - - // Select current record - if ($objCurrentRecord->numRows) { - $newSorting = null; - $curSorting = $objCurrentRecord->sorting; - - $objNextSorting = $db->prepare("SELECT MIN(sorting) AS sorting FROM $table WHERE sorting>? AND pid=?") - ->execute($curSorting, $pid); - - // Select sorting value of the next record - if ($objNextSorting->numRows && null !== $objNextSorting->sorting) { - $nxtSorting = $objNextSorting->sorting; - - // Resort if the new sorting value is no integer or bigger than a MySQL integer field - if (0 != (($curSorting + $nxtSorting) % 2) || $nxtSorting >= 4294967295) { - $count = 1; - - $objNewSorting = $db->prepare("SELECT id, sorting FROM $table WHERE pid=? AND id!=? ORDER BY sorting")->execute($pid, $id); - - while ($objNewSorting->next()) { - $db->prepare("UPDATE $table SET sorting=? WHERE id=? AND pid=?") - ->execute(($count++ * 128), $objNewSorting->id, $pid); - - if ($objNewSorting->sorting == $curSorting) { - $newSorting = ($count++ * 128); - } - } - } // Else new sorting = (current sorting + next sorting) / 2 - else { - $newSorting = (($curSorting + $nxtSorting) / 2); - } - } // Else new sorting = (current sorting + 128) - else { - $newSorting = ($curSorting + 128); - } - - // Set new sorting - $set['sorting'] = (int) $newSorting; - - return $set; - } - } else { - // insert in first place - $newPID = null; - $newSorting = null; - - $newPID = $pid; - - $minSorting = $db->prepare("SELECT MIN(sorting) AS sorting FROM $table WHERE pid=?")->execute($pid); - - // Select sorting value of the first record - if ($minSorting->numRows) { - $curSorting = $minSorting->sorting; - - // Resort if the new sorting value is not an integer or smaller than 1 - if (0 != ($curSorting % 2) || $curSorting < 1) { - $objNewSorting = $db->prepare("SELECT id FROM $table WHERE pid=? ORDER BY sorting")->execute($pid); - - $count = 2; - $newSorting = 128; - - while ($objNewSorting->next()) { - $db->prepare("UPDATE $table SET sorting=? WHERE id=?") - ->limit(1) - ->execute(($count++ * 128), $objNewSorting->id); - } - } // Else new sorting = (current sorting / 2) - else { - $newSorting = ($curSorting / 2); - } - } // Else new sorting = 128 - else { - $newSorting = 128; - } - - // Set new sorting and new parent ID - $set['pid'] = (int) $newPID; - $set['sorting'] = (int) $newSorting; - } - } - } // If there is only sorting - elseif ($db->fieldExists('sorting', $table)) { - // ID is set (insert after the current record) - if ($insertAfterId) { - $objCurrentRecord = $db->prepare("SELECT * FROM $table WHERE id=?") - ->limit(1) - ->execute($insertAfterId); - - // Select current record - if ($objCurrentRecord->numRows) { - $newSorting = null; - $curSorting = $objCurrentRecord->sorting; - - $objNextSorting = $db->prepare("SELECT MIN(sorting) AS sorting FROM $table WHERE sorting>?") - ->execute($curSorting); - - // Select sorting value of the next record - if ($objNextSorting->numRows) { - $nxtSorting = $objNextSorting->sorting; - - // Resort if the new sorting value is no integer or bigger than a MySQL integer field - if (0 != (($curSorting + $nxtSorting) % 2) || $nxtSorting >= 4294967295) { - $count = 1; - - $objNewSorting = $db->execute("SELECT id, sorting FROM $table ORDER BY sorting"); - - while ($objNewSorting->next()) { - $db->prepare("UPDATE $table SET sorting=? WHERE id=?") - ->execute(($count++ * 128), $objNewSorting->id); - - if ($objNewSorting->sorting == $curSorting) { - $newSorting = ($count++ * 128); - } - } - } // Else new sorting = (current sorting + next sorting) / 2 - else { - $newSorting = (($curSorting + $nxtSorting) / 2); - } - } // Else new sorting = (current sorting + 128) - else { - $newSorting = ($curSorting + 128); - } - - // Set new sorting - $set['sorting'] = (int) $newSorting; - - return $set; - } - } - - // ID is not set or not found (insert at the end) - $objNextSorting = $db->execute('SELECT MAX(sorting) AS sorting FROM '.$table); - $set['sorting'] = ((int) $objNextSorting->sorting + 128); - } - - return $set; - } - - /** - * Taken from \Contao\DataContainer. - */ - public function getCurrentPaletteName(string $table, int $id): ?string - { - // Check whether there are selector fields - if (!empty($GLOBALS['TL_DCA'][$table]['palettes']['__selector__'])) { - $sValues = []; - $subpalettes = []; - - $objFields = Database::getInstance()->prepare('SELECT * FROM '.$table.' WHERE id=?') - ->limit(1) - ->execute($id); - - // Get selector values from DB - if ($objFields->numRows > 0) { - foreach ($GLOBALS['TL_DCA'][$table]['palettes']['__selector__'] as $name) { - $trigger = $objFields->$name; - - // Overwrite the trigger - if (Input::post('FORM_SUBMIT') == $table) { - $key = ('editAll' == Input::get('act')) ? $name.'_'.$id : $name; - - if (isset($_POST[$key])) { - $trigger = Input::post($key); - } - } - - if ($trigger) { - if ('checkbox' == ($GLOBALS['TL_DCA'][$table]['fields'][$name]['inputType'] ?? null) && !($GLOBALS['TL_DCA'][$table]['fields'][$name]['eval']['multiple'] ?? null)) { - $sValues[] = $name; - - // Look for a subpalette - if (isset($GLOBALS['TL_DCA'][$table]['subpalettes'][$name])) { - $subpalettes[$name] = $GLOBALS['TL_DCA'][$table]['subpalettes'][$name]; - } - } else { - $sValues[] = $trigger; - $key = $name.'_'.$trigger; - - // Look for a subpalette - if (isset($GLOBALS['TL_DCA'][$table]['subpalettes'][$key])) { - $subpalettes[$name] = $GLOBALS['TL_DCA'][$table]['subpalettes'][$key]; - } - } - } - } - } - - // Build possible palette names from the selector values - if (empty($sValues)) { - $names = ['default']; - } elseif (\count($sValues) > 1) { - foreach ($sValues as $k => $v) { - // Unset selectors that just trigger subpalettes (see #3738) - if (isset($GLOBALS['TL_DCA'][$table]['subpalettes'][$v])) { - unset($sValues[$k]); - } - } - - $names = $this->combiner($sValues); - } else { - $names = [$sValues[0]]; - } - - // Get an existing palette - foreach ($names as $paletteName) { - if (isset($GLOBALS['TL_DCA'][$table]['palettes'][$paletteName])) { - return $paletteName; - } - } - } - - return null; - } - - /** - * Returns true if the field is in at least one sub palette. - */ - public function isSubPaletteField(string $field, string $table): bool - { - $this->framework->getAdapter(Controller::class)->loadDataContainer($table); - - if (!isset($GLOBALS['TL_DCA'][$table]['subpalettes']) || !\is_array($GLOBALS['TL_DCA'][$table]['subpalettes'])) { - return false; - } - - foreach ($GLOBALS['TL_DCA'][$table]['subpalettes'] as $fields) { - if (\in_array($field, explode(',', $fields))) { - return true; - } - } - - return false; - } - - /** - * Returns the selector of the sub palette a field is placed in. Currently doesn't support fields in multiple sub palettes. - */ - public function getSubPaletteFieldSelector(string $field, string $table): string - { - $this->framework->getAdapter(Controller::class)->loadDataContainer($table); - - if (!isset($GLOBALS['TL_DCA'][$table]['subpalettes']) || !\is_array($GLOBALS['TL_DCA'][$table]['subpalettes'])) { - return false; - } - - foreach ($GLOBALS['TL_DCA'][$table]['subpalettes'] as $name => $fields) { - if (\in_array($field, explode(',', $fields))) { - return $name; - } - } - - return false; - } - - /** - * Taken from \Contao\DataContainer. - */ - private function combiner($names) - { - $return = ['']; - $names = array_values($names); - - for ($i = 0, $c = \count($names); $i < $c; ++$i) { - $buffer = []; - - foreach ($return as $k => $v) { - $buffer[] = (0 == $k % 2) ? $v : $v.$names[$i]; - $buffer[] = (0 == $k % 2) ? $v.$names[$i] : $v; - } - - $return = $buffer; - } - - return array_filter($return); - } -} diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php deleted file mode 100644 index 1652d19e..00000000 --- a/src/DependencyInjection/Configuration.php +++ /dev/null @@ -1,45 +0,0 @@ -root('huh_utils'); - } else { - $rootNode = $treeBuilder->getRootNode(); - } - - $rootNode->children() - ->scalarNode('tmp_folder')->defaultValue('files/tmp/huh_utils_bundle')->end() - ->scalarNode('pdfPreviewFolder')->defaultNull()->info('Default folder where to store pdf preview images.')->end() - ->arrayNode('cache') - ->children() - ->booleanNode('enable_generate_database_tree_cache')->defaultFalse()->info('Enable database tree cache is generated on cache warmup.')->end() - ->end() - ->end() - ->booleanNode('enable_load_assets')->defaultTrue()->info('Load utils bundle assets. Default value will be changed to false in next major version.')->end() - ->end(); - - return $treeBuilder; - } -} diff --git a/src/DependencyInjection/HeimrichHannotUtilsExtension.php b/src/DependencyInjection/HeimrichHannotUtilsExtension.php deleted file mode 100644 index 3966bd3d..00000000 --- a/src/DependencyInjection/HeimrichHannotUtilsExtension.php +++ /dev/null @@ -1,33 +0,0 @@ -processConfiguration($configuration, $configs); - - /* @todo Remove this passage in version 3.0 */ - if (!isset($config['pdfPreviewFolder'])) { - $config['pdfPreviewFolder'] = $container->getParameter('huh.utils.filecache.folder').\DIRECTORY_SEPARATOR.'pdfPreview'; - } - - $container->setParameter('huh_utils', $config); - } - - public function getAlias() - { - return 'huh_utils'; - } -} diff --git a/src/DependencyInjection/UtilsBundleExtension.php b/src/DependencyInjection/UtilsBundleExtension.php deleted file mode 100644 index 9ef8614f..00000000 --- a/src/DependencyInjection/UtilsBundleExtension.php +++ /dev/null @@ -1,32 +0,0 @@ -getExtensionConfig($this->getAlias()); - $config = $this->processConfiguration(new Configuration(), $configs); - $container->prependExtensionConfig('huh_utils', $config); - } -} diff --git a/src/Driver/DC_Table_Utils.php b/src/Driver/DC_Table_Utils.php deleted file mode 100644 index edebcef5..00000000 --- a/src/Driver/DC_Table_Utils.php +++ /dev/null @@ -1,184 +0,0 @@ -get('session'); - $request = System::getContainer()->get('request_stack')->getCurrentRequest(); - - // Check the request token (see #4007) - if ($request->query->has('act')) { - if (!$request->query->get('rt') || !RequestToken::validate($request->query->get('rt'))) { - $objSession->set('INVALID_TOKEN_URL', Environment::get('request')); - $this->redirect('contao/confirm.php'); - } - } - - Controller::loadDataContainer($strTable); - - $this->intId = $request->query->get('id'); - - // Clear the clipboard - if ($request->query->has('clipboard')) { - $objSession->set('CLIPBOARD', []); - $this->redirect($this->getReferer()); - } - - // Check whether the table is defined - if ('' == $strTable || !isset($GLOBALS['TL_DCA'][$strTable])) { - System::getContainer()->get('monolog.logger.contao')->log('Could not load the data container configuration for "'.$strTable.'"', __METHOD__, TL_ERROR); - trigger_error('Could not load the data container configuration', E_USER_ERROR); - } - - // Set IDs and redirect - if ('tl_select' == $request->request->get('FORM_SUBMIT')) { - $ids = $request->request->get('IDS'); - - if (empty($ids) || !\is_array($ids)) { - $this->reload(); - } - - $session = $objSession->all(); - $session['CURRENT']['IDS'] = $ids; - $objSession->replace($session); - - if ($request->request->has('edit')) { - $this->redirect(str_replace('act=select', 'act=editAll', \Environment::get('request'))); - } elseif ($request->request->has('delete')) { - $this->redirect(str_replace('act=select', 'act=deleteAll', \Environment::get('request'))); - } elseif ($request->request->has('override')) { - $this->redirect(str_replace('act=select', 'act=overrideAll', \Environment::get('request'))); - } elseif ($request->request->has('cut') || $request->request->has('copy')) { - $arrClipboard = $objSession->get('CLIPBOARD'); - - $arrClipboard[$strTable] = [ - 'id' => $ids, - 'mode' => ($request->request->has('cut') ? 'cutAll' : 'copyAll'), - ]; - - $objSession->set('CLIPBOARD', $arrClipboard); - - // Support copyAll in the list view (see #7499) - if ($request->request->has('copy') && $GLOBALS['TL_DCA'][$strTable]['list']['sorting']['mode'] < 4) { - $this->redirect(str_replace('act=select', 'act=copyAll', \Environment::get('request'))); - } - - $this->redirect($this->getReferer()); - } - } - - $this->strTable = $strTable; - $this->ptable = $GLOBALS['TL_DCA'][$this->strTable]['config']['ptable'] ?? null; - $this->ctable = $GLOBALS['TL_DCA'][$this->strTable]['config']['ctable'] ?? null; - $this->treeView = isset($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['mode']) && \in_array($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['mode'], [5, 6]); - $this->root = null; - $this->arrModule = $arrModule; - - // FIX: Don't call onload_callbacks for performance reasons - - // Get the IDs of all root records (tree view) - if ($this->treeView) { - $table = (6 == $GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['mode']) ? $this->ptable : $this->strTable; - - // Unless there are any root records specified, use all records with parent ID 0 - if (!isset($GLOBALS['TL_DCA'][$table]['list']['sorting']['root']) || (isset($GLOBALS['TL_DCA'][$table]['list']['sorting']['root']) && false === $GLOBALS['TL_DCA'][$table]['list']['sorting']['root'])) { - $objIds = $this->Database->prepare('SELECT id FROM '.$table.' WHERE pid=?'.($this->Database->fieldExists('sorting', $table) ? ' ORDER BY sorting' : ''))->execute(0); - - if ($objIds->numRows > 0) { - $this->root = $objIds->fetchEach('id'); - } - } // Get root records from global configuration file - elseif (isset($GLOBALS['TL_DCA'][$table]['list']['sorting']['root']) && \is_array($GLOBALS['TL_DCA'][$table]['list']['sorting']['root'])) { - $this->root = $this->eliminateNestedPages($GLOBALS['TL_DCA'][$table]['list']['sorting']['root'], $table, $this->Database->fieldExists('sorting', $table)); - } - } // Get the IDs of all root records (list view or parent view) - elseif (isset($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['root']) && \is_array($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['root'])) { - $this->root = array_unique($GLOBALS['TL_DCA'][$this->strTable]['list']['sorting']['root']); - } - - $route = $request->attributes->get('_route'); - - // Store the current referer - if (!empty($this->ctable) && !$request->query->get('act') && !$request->query->get('key') && !$request->query->get('token') && 'contao_backend' == $route - && !Environment::get('isAjaxRequest')) { - $strKey = $request->query->get('popup') ? 'popupReferer' : 'referer'; - $strRefererId = $request->attributes->get('_contao_referer_id'); - - $session = $objSession->get($strKey); - $session[$strRefererId][$this->strTable] = substr(\Environment::get('requestUri'), \strlen(\Environment::get('path')) + 1); - $objSession->set($strKey, $session); - } - } - - /** - * Create a DataContainer instance from a given Model. - * - * @return static - */ - public static function createFromModel(Model $model) - { - $table = $model->getTable(); - - $dc = new static($table); - - $dc->strTable = $model->getTable(); - $dc->activeRecord = $model; - $dc->intId = $model->id; - - return $dc; - } - - /** - * Create a DataContainer instance from given model data. - * - * @param Model $model - * @param string $field - * - * @return static - */ - public static function createFromModelData(array $modelData, string $table, string $field = null) - { - $dc = new static($table); - - $dc->strTable = $table; - $dc->activeRecord = null; - - if (isset($modelData['id']) && $modelData['id'] > 0) { - $dc->activeRecord = System::getContainer()->get('huh.utils.model')->findModelInstanceByPk($table, $modelData['id']); - $dc->intId = $modelData['id']; - } - - if ($field) { - $dc->strField = $field; - } - - return $dc; - } -} diff --git a/src/EntityFinder/EntityFinderHelper.php b/src/EntityFinder/EntityFinderHelper.php index e311c881..cab3b6a1 100644 --- a/src/EntityFinder/EntityFinderHelper.php +++ b/src/EntityFinder/EntityFinderHelper.php @@ -8,20 +8,18 @@ namespace HeimrichHannot\UtilsBundle\EntityFinder; +use Contao\CoreBundle\Framework\ContaoFramework; use Contao\Model\Collection; use Contao\ModuleModel; -use HeimrichHannot\UtilsBundle\Database\DatabaseUtil; +use HeimrichHannot\UtilsBundle\Util\Utils; class EntityFinderHelper { - /** - * @var DatabaseUtil - */ - private $databaseUtil; - - public function __construct(DatabaseUtil $databaseUtil) + public function __construct( + private Utils $utils, + private ContaoFramework $framework, + ) { - $this->databaseUtil = $databaseUtil; } /** @@ -35,10 +33,13 @@ public function __construct(DatabaseUtil $databaseUtil) */ public function findModulesByTypeAndSerializedValue(string $type, string $field, array $values): ?Collection { - [$columns[], $values] = $this->databaseUtil->createWhereForSerializedBlob(ModuleModel::getTable().'.'.$field, $values); + $blobQuery = $this->utils->database()->createWhereForSerializedBlob(ModuleModel::getTable().'.'.$field, $values); + $columns = [$blobQuery->createOrWhere()]; + $values = $blobQuery->values; + $columns[] = ModuleModel::getTable().'.type=?'; $values[] = $type; - return ModuleModel::findBy($columns, $values); + return $this->framework->getAdapter(ModuleModel::class)->findBy($columns, $values); } } diff --git a/src/Event/AbstractEvent.php b/src/Event/AbstractEvent.php deleted file mode 100644 index 7bcf6065..00000000 --- a/src/Event/AbstractEvent.php +++ /dev/null @@ -1,21 +0,0 @@ -table = $table; - $this->id = $id; - $this->parents = $parents; - $this->onlyText = $onlyText; - $this->inserttags = $inserttags; - $this->entityFinderHelper = $entityFinderHelper; } public function getTable(): string diff --git a/src/Event/RenderTwigTemplateEvent.php b/src/Event/RenderTwigTemplateEvent.php deleted file mode 100644 index 18593f85..00000000 --- a/src/Event/RenderTwigTemplateEvent.php +++ /dev/null @@ -1,54 +0,0 @@ -template = $template; - $this->context = $context; - } - - public function getTemplate(): string - { - return $this->template; - } - - public function setTemplate(string $template) - { - $this->template = $template; - } - - public function getContext(): array - { - return $this->context; - } - - public function setContext(array $context) - { - $this->context = $context; - } -} diff --git a/src/EventListener/DcaAuthorListener.php b/src/EventListener/DcaAuthorListener.php new file mode 100644 index 00000000..bd5f0299 --- /dev/null +++ b/src/EventListener/DcaAuthorListener.php @@ -0,0 +1,120 @@ +framework = $framework; + $this->security = $security; + } + + /** + * @Hook("loadDataContainer") + */ + public function onLoadDataContainer(string $table): void + { + if (!isset(AuthorField::getRegistrations()[$table])) { + return; + } + + $options = AuthorField::getRegistrations()[$table]; + + $authorFieldName = $this->getAuthorFieldName($options); + + $authorField = [ + 'exclude' => $options->isExclude(), + 'search' => $options->isSearch(), + 'filter' => $options->isFilter(), + 'inputType' => 'select', + 'eval' => [ + 'doNotCopy' => true, + 'mandatory' => true, + 'chosen' => true, + 'includeBlankOption' => true, + 'tl_class' => 'w50' + ], + 'sql' => "int(10) unsigned NOT NULL default 0", + ]; + + if ($options->isUseDefaultLabel()) { + $authorField['label'] = &$GLOBALS['TL_LANG']['MSC']['utilsBundle']['author']; + } + + $authorField['default'] = 0; + if (AuthorField::TYPE_USER === $options->getType()) { + if ($this->security->getUser() instanceof BackendUser) { + $authorField['default'] = $this->security->getUser()->id; + } + $authorField['foreignKey'] = 'tl_user.name'; + $authorField['relation'] = ['type'=>'hasOne', 'load'=>'lazy']; + } elseif (AuthorField::TYPE_MEMBER === $options->getType()) { + if ($this->security->getUser() instanceof FrontendUser) { + $authorField['default'] = $this->security->getUser()->id; + } + $authorField['foreignKey'] = "tl_member.CONCAT(firstname,' ',lastname)"; + $authorField['relation'] = ['type'=>'hasOne', 'load'=>'lazy']; + } + + $GLOBALS['TL_DCA'][$table]['fields'][$authorFieldName] = $authorField; + $GLOBALS['TL_DCA'][$table]['config']['oncopy_callback'][] = [self::class, 'onConfigCopyCallback']; + } + + + public function onConfigCopyCallback(int $insertId, DataContainer $dc): void + { + $options = AuthorField::getRegistrations()[$dc->table]; + $authorFieldName = $this->getAuthorFieldName($options); + + /** @var class-string $modelClass */ + $modelClass = $this->framework->getAdapter(Model::class)->getClassFromTable($dc->table); + $model = $this->framework->getAdapter($modelClass)->findByPk($insertId); + if (!$model) { + return; + } + + $model->{$authorFieldName} = 0; + if (AuthorField::TYPE_USER === $options->getType()) { + if ($this->security->getUser() instanceof BackendUser) { + $model->{$authorFieldName} = $this->security->getUser()->id; + } + } elseif (AuthorField::TYPE_MEMBER === $options->getType()) { + if ($this->security->getUser() instanceof FrontendUser) { + $model->{$authorFieldName} = $this->security->getUser()->id; + } + } + $model->save(); + } + + /** + * @param AuthorFieldOptions $options + * @return string + */ + protected function getAuthorFieldName(AuthorFieldOptions $options): string + { + if (!$options->hasFieldNamePrefix()) { + return 'author'; + } + if (str_ends_with($options->getFieldNamePrefix(), '_')) { + return $options->getFieldNamePrefix() . 'author'; + } else { + return $options->getFieldNamePrefix() . 'Author'; + } + } +} \ No newline at end of file diff --git a/src/EventListener/InitializeSystemListener.php b/src/EventListener/InitializeSystemListener.php deleted file mode 100644 index 190d3e39..00000000 --- a/src/EventListener/InitializeSystemListener.php +++ /dev/null @@ -1,48 +0,0 @@ -containerUtil = $containerUtil; - $this->bundleConfig = $bundleConfig; - } - - public function __invoke(): void - { - if ($this->containerUtil->isBackend()) { - $GLOBALS['TL_CSS']['utils-bundle'] = 'bundles/heimrichhannotcontaoutils/css/contao-utils-bundle.be.css|static'; - } - - if (isset($this->bundleConfig['enable_load_assets']) && true === $this->bundleConfig['enable_load_assets']) { - array_insert($GLOBALS['TL_JAVASCRIPT'], 1, [ - 'contao-utils-bundle' => 'bundles/heimrichhannotcontaoutils/js/contao-utils-bundle.js|static', - ]); - } - } -} diff --git a/src/EventListener/InsertTagsListener.php b/src/EventListener/InsertTagsListener.php deleted file mode 100644 index b35a4e8a..00000000 --- a/src/EventListener/InsertTagsListener.php +++ /dev/null @@ -1,122 +0,0 @@ -eventDispatcher = $eventDispatcher; - $this->twig = $twig; - $this->templateUtil = $templateUtil; - $this->contaoFramework = $contaoFramework; - } - - /** - * Replaces calendar insert tags. - * - * @return string|false - */ - public function onReplaceInsertTags(string $tag) - { - $elements = explode('::', $tag); - $key = strtolower($elements[0]); - $attributes = \array_slice($elements, 1); - - if (\in_array($key, $this->supportedTags)) { - return $this->replaceSupportedTags($key, $attributes); - } - - return false; - } - - /** - * Replace supported tags. - * - * @throws \Psr\Cache\InvalidArgumentException - * @throws \Twig_Error_Loader - * @throws \Twig_Error_Runtime - * @throws \Twig_Error_Syntax - */ - protected function replaceSupportedTags(string $key, array $attributes = []): string - { - switch ($key) { - case 'twig': - return $this->replaceTwigTag($attributes); - } - - return ''; - } - - /** - * Replace twig template insert tags {{twig::logo.html.twig::a:1:{s:3:"foo";s:3:"bar";}}}. - * - * @throws \Psr\Cache\InvalidArgumentException - * @throws \Twig_Error_Loader - * @throws \Twig_Error_Runtime - * @throws \Twig_Error_Syntax - */ - protected function replaceTwigTag(array $attributes = []): string - { - if (!isset($attributes[0]) || empty($attributes[0])) { - return ''; - } - - $data = []; - - if (isset($attributes[1]) && !empty($attributes[1])) { - $data = StringUtil::deserialize($attributes[1], true); - } - - $template = $this->templateUtil->getTemplate(preg_replace('#.html.twig$#i', '', $attributes[0])); - - if (is_subclass_of($this->eventDispatcher, 'Symfony\Contracts\EventDispatcher\EventDispatcherInterface')) { - $event = $this->eventDispatcher->dispatch(new RenderTwigTemplateEvent($template, $data), RenderTwigTemplateEvent::NAME); - } else { - /** @noinspection PhpParamsInspection */ - $event = $this->eventDispatcher->dispatch(RenderTwigTemplateEvent::NAME, new RenderTwigTemplateEvent($template, $data)); - } - - return $this->contaoFramework->getAdapter(Controller::class)->replaceInsertTags($this->twig->render($event->getTemplate(), $event->getContext())); - } -} diff --git a/src/File/FileArchiveUtil.php b/src/File/FileArchiveUtil.php deleted file mode 100644 index a9f1dfdd..00000000 --- a/src/File/FileArchiveUtil.php +++ /dev/null @@ -1,79 +0,0 @@ -projectDir = $projectDir; - $this->utilsConfig = $utilsConfig; - $this->folderUtil = $folderUtil; - } - - /** - * Create a temporary zip file and return the file path. - * - * @param FilesModel[]|array $items - * - * @throws \Exception - * - * @return string the path to the temporary zip file - */ - public function createFileArchive(array $items, string $archiveName) - { - $filesystem = new Filesystem(); - $tmpFolder = $this->utilsConfig['tmp_folder'].\DIRECTORY_SEPARATOR.'file_archive_util'; - $absoluteTmpFolder = $this->projectDir.\DIRECTORY_SEPARATOR.$tmpFolder; - - if (!$filesystem->exists($absoluteTmpFolder)) { - $filesystem->mkdir($absoluteTmpFolder); - } - - $unique = false; - - while (!$unique) { - $fileName = uniqid($archiveName.'_'.date('Ymd').'_').'.zip'; - $unique = !$filesystem->exists($absoluteTmpFolder.'/'.$fileName); - } - $filePath = $tmpFolder.\DIRECTORY_SEPARATOR.$fileName; - - $this->folderUtil->createPublicFolder($tmpFolder); - - $zipWriter = new ZipWriter($filePath); - - foreach ($items as $item) { - $zipWriter->addFile($item->path, $item->name); - } - - $zipWriter->close(); - - return $filePath; - } -} diff --git a/src/File/FileStorage.php b/src/File/FileStorage.php deleted file mode 100644 index c653866c..00000000 --- a/src/File/FileStorage.php +++ /dev/null @@ -1,172 +0,0 @@ -rootPath = rtrim($rootPath, \DIRECTORY_SEPARATOR); - $this->relativeStoragePath = trim($storagePath, \DIRECTORY_SEPARATOR); - $this->generator = new SlugGenerator(); - $this->filesystem = new Filesystem(); - $this->defaultFileExtension = $defaultFileExtension; - } - - /** - * Get the storage path for given identifier. Caution: Identifier will be normalized! - * - * Options: - * - fileExtension: (string) Override the default file extension. - * - * @param string $key the key for the item in store - * @param string|null $default Default value to return if key does not exist - * @param array $options Additional options - * - * @throws \Exception - * - * @return string|null return the path of the item from the storage, or $default if not found - */ - public function get(string $key, ?string $default = null, array $options = []): ?string - { - $filename = $this->createFilename($key, $options); - $absoluteFilePath = $this->createAbsoluteFilePath($filename); - - if (!$this->filesystem->exists($absoluteFilePath)) { - return $default; - } - - return $this->createRelativeFilePath($filename); - } - - /** - * Persist a file in the file storage, uniquely references by an identifier. Caution: identifier will be normalized! - * - * Value: - * - callable: Gets a FileStorageCallback object as parameter and must return bool (true on success, false otherwise). - * - string: Will be directly written to the file. - * - * Options: - * - fileExtension: (string) Override the default file extension. - * - * @param string $identifier the key of the item to store - * @param callable|string $value The value of the item to store. Must be a callable or string. - * - * @throws \InvalidArgumentException Is thrown when value is neither callable or string - * @throws \UnexpectedValueException is thrown if callback not returning a bool - * - * @return string return the path of the item from the storage - */ - public function set(string $identifier, $value, array $options = []): string - { - $filename = $this->createFilename($identifier, $options); - $relativeFilePath = $this->createRelativeFilePath($filename); - $absoluteFilePath = $this->createAbsoluteFilePath($filename); - - if (\is_callable($value)) { - $fileStorageCallback = new FileStorageCallback($identifier, $filename, $relativeFilePath, $absoluteFilePath, $this->rootPath, $this->relativeStoragePath); - $result = $value($fileStorageCallback); - - if (!\is_bool($result)) { - throw new \UnexpectedValueException('Invalid callback return type. Must be bool, was '.\gettype($result).'.'); - } - - if (!$result) { - throw new \RuntimeException('File could not be created. Callback returned false.'); - } - } else { - if (!\is_string($value)) { - throw new \InvalidArgumentException('Invalid value, must be of type string or callable, was '.\gettype($value).'.'); - } - $this->filesystem->dumpFile($absoluteFilePath, $value); - } - - return $relativeFilePath; - } - - /** - * Normalize the key. - * - * @return string - */ - protected function normalizeKey(string $key) - { - return $this->generator->generate($key, ['validChars' => 'a-z0-9']); - } - - /** - * Create the filename out of key and file extension. - */ - protected function createFilename(string $key, array $options): string - { - $filename = $this->normalizeKey($key); - $fileExtension = null; - - if (!empty($this->defaultFileExtension)) { - $fileExtension = $this->defaultFileExtension; - } - - if (isset($options['fileExtension']) && \is_string($options['fileExtension'])) { - $fileExtension = $options['fileExtension']; - } - - if ($fileExtension) { - $filename .= '.'.$fileExtension; - } - - return $filename; - } - - /** - * Return the absoulte path to the file. - */ - protected function createAbsoluteFilePath(string $filename): string - { - $storagePath = $this->rootPath.\DIRECTORY_SEPARATOR.$this->relativeStoragePath.\DIRECTORY_SEPARATOR.$filename; - - return $storagePath; - } - - /** - * Return the relative path to the file. - */ - protected function createRelativeFilePath(string $filename): string - { - $storagePath = $this->relativeStoragePath.\DIRECTORY_SEPARATOR.$filename; - - return $storagePath; - } -} diff --git a/src/File/FileStorageCallback.php b/src/File/FileStorageCallback.php deleted file mode 100644 index 394affaf..00000000 --- a/src/File/FileStorageCallback.php +++ /dev/null @@ -1,80 +0,0 @@ -identifier = $identifier; - $this->filename = $filename; - $this->relativeFilePath = $relativeFilePath; - $this->absoluteFilePath = $absoluteFilePath; - $this->rootPath = $rootPath; - $this->relativeStoragePath = $relativeStoragePath; - } - - public function getIdentifier(): string - { - return $this->identifier; - } - - public function getFilename(): string - { - return $this->filename; - } - - public function getRelativeFilePath(): string - { - return $this->relativeFilePath; - } - - public function getAbsoluteFilePath(): string - { - return $this->absoluteFilePath; - } - - public function getRootPath(): string - { - return $this->rootPath; - } - - public function getRelativeStoragePath(): string - { - return $this->relativeStoragePath; - } -} diff --git a/src/File/FileStorageUtil.php b/src/File/FileStorageUtil.php deleted file mode 100644 index 505496f3..00000000 --- a/src/File/FileStorageUtil.php +++ /dev/null @@ -1,40 +0,0 @@ -projectDir = $projectDir; - } - - /** - * Returns a new FileStorage instance. - * - * See PdfPreview for example usage. - * - * @param string $storagePath The path where to store the files relative to the project dir - * @param string $fileExtension The default file extension of the stored files. E.g. jpg, txt, ... - * - * @return FileStorage - */ - public function createFileStorage(string $storagePath, string $fileExtension = '') - { - return new FileStorage($this->projectDir, $storagePath, $fileExtension); - } -} diff --git a/src/File/FileUtil.php b/src/File/FileUtil.php deleted file mode 100644 index b604d381..00000000 --- a/src/File/FileUtil.php +++ /dev/null @@ -1,617 +0,0 @@ -framework = $container->get('contao.framework'); - $this->container = $container; - } - - /** - * Get a unique filename within given target folder, remove uniqid() suffix from file (optional, add $prefix) and append file count by name to - * file if file with same name already exists in target folder. - * - * @param string $target The target file path - * @param string $prefix A uniqid prefix from the given target file, that was added to the file before and should be removed again - * @param $i integer Internal counter for recursion usage or if you want to add the number to the file - * - * @throws \Exception - * - * @return string|false The filename with the target folder and unique id or false if something went wrong (e.g. target does not exist) - */ - public function getUniqueFileNameWithinTarget($target, $prefix = null, $i = 0) - { - $target = ltrim(str_replace($this->container->getParameter('kernel.project_dir'), '', $target), '/'); - $file = new File($target); - - $path = $target; - - if ($file->extension) { - $path = str_replace('.'.$file->extension, '', $target); - } - - if ($prefix && false !== ($pos = strpos($path, $prefix))) { - $path = str_replace(substr($path, $pos, \strlen($path)), '', $path); - $target = $path.'.'.$file->extension; - } - - // Create the parent folder - if (!file_exists($file->dirname)) { - $folder = new Folder(ltrim(str_replace($this->container->getParameter('kernel.project_dir'), '', $file->dirname), '/')); - - // something went wrong with folder creation - if (null === $folder->getModel()) { - return false; - } - } - - if (file_exists($this->container->getParameter('kernel.project_dir').'/'.$target)) { - // remove suffix - if ($i > 0 && $this->container->get('huh.utils.string')->endsWith($path, '-'.$i)) { - $path = rtrim($path, '-'.$i); - } - - // increment counter & add extension again - ++$i; - - // for performance reasons, add new unique id to path to make recursion come to end after 100 iterations - if ($i > 100) { - return $this->getUniqueFileNameWithinTarget($this->addUniqueIdToFilename($path.'.'.$file->extension, null, false)); - } - - return $this->getUniqueFileNameWithinTarget($path.'-'.$i.'.'.$file->extension, $prefix, $i); - } - - return $target; - } - - /** - * Returns the file list for a given directory. - * - * @param string $dir - the absolute local path to the directory (e.g. /dir/mydir) - * @param string $baseUrl - the relative uri (e.g. /tl_files/mydir) - * @param string $protectedBaseUrl - domain + request uri -> absUrl will be domain + request uri + ?file=$baseUrl/filename.ext - * - * @return array file list containing file objects - */ - public function getFileList($dir, $baseUrl, $protectedBaseUrl = null) - { - $results = []; - - if (is_dir($dir)) { - if ($handler = opendir($dir)) { - while (false !== ($file = readdir($handler))) { - if ('.' == substr($file, 0, 1)) { - continue; - } - - $fileArray = []; - $fileArray['filename'] = htmlentities($file); - - if ($protectedBaseUrl) { - $fileArray['absUrl'] = - $protectedBaseUrl.(empty($_GET) ? '?' : '&').'file='.str_replace('//', '', $baseUrl.'/'.$file); - } else { - $fileArray['absUrl'] = str_replace('\\', '/', str_replace('//', '', $baseUrl.'/'.$file)); - } - - $fileArray['path'] = str_replace($fileArray['filename'], '', $fileArray['absUrl']); - $fileArray['filesize'] = - $this->formatSizeUnits(filesize(str_replace('\\', '/', str_replace('//', '', $dir.'/'.$file))), true); - - $results[] = $fileArray; - } - - closedir($handler); - } - } - - $this->container->get('huh.utils.array')->aasort($results, 'filename'); - - return $results; - } - - public function formatSizeUnits(int $bytes, $keepTogether = false) - { - if ($bytes >= 1073741824) { - $bytes = number_format($bytes / 1073741824, 2).($keepTogether ? ' ' : ' ').'GB'; - } elseif ($bytes >= 1048576) { - $bytes = number_format($bytes / 1048576, 2).($keepTogether ? ' ' : ' ').'MB'; - } elseif ($bytes >= 1024) { - $bytes = number_format($bytes / 1024, 2).($keepTogether ? ' ' : ' ').'KB'; - } elseif ($bytes > 1) { - $bytes = $bytes.($keepTogether ? ' ' : ' ').'Bytes'; - } elseif (1 == $bytes) { - $bytes = $bytes.($keepTogether ? ' ' : ' ').'Byte'; - } else { - $bytes = '0'.($keepTogether ? ' ' : ' ').'Bytes'; - } - - return $bytes; - } - - public function getPathWithoutFilename($pathToFile) - { - $path = pathinfo($pathToFile); - - if (!isset($path['dirname'])) { - return ''; - } - - return $path['dirname']; - } - - public function getFileExtension($path) - { - if (is_dir($path)) { - return ''; - } - - return pathinfo($path, \PATHINFO_EXTENSION); - } - - /** - * @param $uuid - * @param bool $checkIfExists - * - * @return string|null Return the path of the file, or null if not exists - * - * @deprecated Use Utils::file instead - * @codeCoverageIgnore - */ - public function getPathFromUuid($uuid, $checkIfExists = true) - { - if (null !== ($file = $this->framework->getAdapter(FilesModel::class)->findByUuid($uuid))) { - if (!$checkIfExists) { - return $file->path; - } - - if (file_exists($this->container->getParameter('kernel.project_dir').'/'.$file->path)) { - return $file->path; - } - } - - return null; - } - - public function getFileContentFromUuid($uuid) - { - $file = $this->getFileFromUuid($uuid); - - if (!$file || !$file->exists()) { - return null; - } - - return file_get_contents(System::getContainer()->get('huh.utils.container')->getProjectDir().'/'.$file->path); - } - - /** - * @param $uuid - * - * @throws \Exception - * - * @return File|null Return the file object - */ - public function getFileFromUuid($uuid) - { - if ($path = $this->getPathFromUuid($uuid)) { - if (is_dir($this->container->get('huh.utils.container')->getProjectDir().\DIRECTORY_SEPARATOR.$path)) { - return null; - } - - return new File($path); - } - } - - /** - * @param $uuid - * @param bool $doNotCreate - * - * @throws \Exception - * - * @return bool|Folder Return the folder object - */ - public function getFolderFromUuid($uuid) - { - if ($path = $this->getPathFromUuid($uuid)) { - return new Folder($path); - } - - return false; - } - - /** - * Add a unique identifier to a file name. - * - * @param string $fileName The file name, can be with or without path - * @param string $prefix add a prefix to the unique identifier, with an empty prefix, the returned string will be 13 characters long - * @param bool $moreEntropy if set to TRUE, will add additional entropy (using the combined linear congruential generator) at the end of the - * return value, which increases the likelihood that the result will be unique - * - * @return string Filename with timestamp based unique identifier - */ - public function addUniqueIdToFilename($fileName, $prefix = null, $moreEntropy = true) - { - $file = new File($fileName); - - $directory = ltrim(str_replace($this->container->getParameter('kernel.project_dir'), '', $file->dirname), '/'); - - return ($directory ? $directory.'/' : '').$file->filename.uniqid($prefix, $moreEntropy).($file->extension ? '.' - .$file->extension : ''); - } - - /** - * Sanitize filename and removes "id-" prefix generated by contao standardize method. - * - * @param string $fileName The file name, can be with or without path - * @param int $maxCount Max filename length - * @param bool $preserveUppercase Set to true if you want to lower case the file name - * - * @return string The sanitized filename - */ - public function sanitizeFileName($fileName, $maxCount = 0, $preserveUppercase = false) - { - $file = new File($fileName); - - $name = $file->filename; - - $validChars = 'a-z0-9_'; - - if ($preserveUppercase) { - $validChars .= 'A-Z'; - } - - $name = (new SlugGenerator((new SlugOptions())->setValidChars($validChars)->setTransforms(['Upper', 'Lower', 'ASCII', 'Upper', 'Lower'])->setDelimiter('')))->generate($name, []); - - if ('id-' != $name && !$this->container->get('huh.utils.string')->startsWith($fileName, 'id-')) { - $name = preg_replace('/^(id-)/', '', $name); - } - - if ($maxCount > 0) { - $name = substr($name, 0, $maxCount - 1); - } - - $directory = ltrim(str_replace($this->container->getParameter('kernel.project_dir'), '', $file->dirname), '/'); - - return ($directory ? $directory.'/' : '').$name.($file->extension ? ('.'.strtolower($file->extension)) : ''); - } - - public function sendTextAsFileToBrowser($content, $fileName) - { - header('Content-Disposition: attachment; filename="'.$fileName.'"'); - header('Content-Type: text/plain'); - header('Connection: close'); - echo $content; - - exit(); - } - - /** - * Get real folder from datacontainer attribute. - * - * @param mixed $folder The folder as uuid, function, callback array('CLASS', 'method') or string (files/...) - * @param DataContainer|null $dc Optional \DataContainer, required for function and callback - * - * @throws \Exception If ../ is part of the path - * - * @return mixed|null The folder path or null - */ - public function getFolderFromDca($folder, DataContainer $dc = null) - { - // upload folder - if (\is_array($folder) && null !== $dc) { - $callback = $folder; - $folder = System::importStatic($callback[0])->{$callback[1]}($dc); - } elseif (\is_callable($folder) && null !== $dc) { - $method = $folder; - $folder = $method($dc); - } elseif (\is_string($folder)) { - if (false !== strpos($folder, '../')) { - throw new \Exception("Invalid target path $folder"); - } - } - - if ($folder instanceof File) { - $folder = $folder->value; - } elseif ($folder instanceof FilesModel) { - $folder = $folder->path; - } - - if (Validator::isUuid($folder)) { - $folderObj = $this->getFolderFromUuid($folder); - $folder = $folderObj->value; - } - - return $folder; - } - - /** - * @param $file - * - * @return int|string - */ - public function getFileLineCount($file) - { - $count = 0; - - try { - if (false === strpos($file, $this->container->getParameter('kernel.project_dir'))) { - $file = $this->container->getParameter('kernel.project_dir').$file; - } - - $handle = fopen($file, 'r'); - } catch (\Exception $exception) { - return $exception->getMessage(); - } - - while (!feof($handle)) { - $line = fgets($handle); - ++$count; - } - - fclose($handle); - - return $count; - } - - /** - * convert pdf to png and return a preview file - * delete the other png files. - * - * @deprecated Dublicate to PdfPreview util - */ - public function getPreviewFromPdf(FilesModel $file, int $page = 0): FilesModel - { - $strippedName = str_replace('.'.$file->extension, '', $file->name); - $previewFileName = 'preview-'.$strippedName.'.'.static::FILE_UTIL_CONVERT_FILE_TYPE; - $folder = str_replace($file->name, '', $file->path); - $target = $folder.\DIRECTORY_SEPARATOR.$previewFileName; - - // ghostscript - /** @var Transcoder $transcoder */ - $transcoder = $this->framework->getAdapter(Transcoder::class)->create(); - $transcoder->toImage($file->path, $target); - - // get all created images - $folderFiles = scandir($folder); - $pdfPreviewFiles = []; - $needle = '/preview-'.$strippedName.'*\.'.static::FILE_UTIL_CONVERT_FILE_TYPE.'/'; - - foreach ($folderFiles as $file) { - if (!preg_match($needle, $file)) { - continue; - } - - $pdfPreviewFiles[] = $file; - } - - $preview = null; - - foreach ($pdfPreviewFiles as $key => $value) { - if ($page != $key && file_exists($value)) { - unlink($value); - - continue; - } - - $preview = $value; - } - - if (null === ($previewFile = $this->framework->getAdapter(FilesModel::class)->findByPath($preview))) { - $previewFile = $this->framework->getAdapter(Dbafs::class)->addResource($folder.\DIRECTORY_SEPARATOR.$preview); - } - - return $previewFile; - } - - public function getFileIdFromPath($path) - { - if (is_dir(System::getContainer()->get('huh.utils.container')->getProjectDir().'/'.$path)) { - if (null !== ($folder = (new Folder($path)))) { - return $folder->getModel()->id; - } - } else { - if (null !== ($file = (new File($path)))) { - return $file->getModel()->id; - } - } - - return null; - } - - public function getFolderContent($parentIds, $table, $options = [], $return = []) - { - $returnRows = $options['returnRows'] ?? false; - $sorting = $options['sorting'] ?? false; - $where = $options['where'] ?? ''; - - if (!\is_array($parentIds)) { - $parentIds = [$parentIds]; - } - - if (empty($parentIds)) { - return $return; - } - - $values = []; - - $parentIds = array_map(function ($pid) use (&$values) { - $pid = \is_array($pid) ? $pid['id'] : $pid; - - $values[] = bin2hex(Validator::isStringUuid($pid) ? StringUtil::uuidToBin($pid) : $pid); - - return 'UNHEX(?)'; - }, $parentIds); - - $query = 'SELECT '.($returnRows ? '*' : 'id, pid').' FROM '.$table.' WHERE pid IN('.implode(',', $parentIds).')'.($where ? " AND $where" : '').($sorting ? ' ORDER BY '.$sorting : ''); - - $objChilds = $this->framework->createInstance(Database::class)->prepare($query)->execute($values); - - if ($objChilds->numRows > 0) { - if ($sorting) { - $arrChilds = []; - $arrOrdered = []; - - while ($objChilds->next()) { - $arrChilds[] = $returnRows ? $objChilds->row() : $objChilds->id; - $arrOrdered[$objChilds->pid][] = $returnRows ? $objChilds->row() : $objChilds->id; - } - - foreach (array_reverse(array_keys($arrOrdered)) as $pid) { - $pos = (int) array_search($pid, $return); - array_insert($return, $pos + 1, $arrOrdered[$pid]); - } - - $return = $this->getFolderContent($arrChilds, $table, $options, $return); - } else { - if ($returnRows) { - while ($objChilds->next()) { - $arrChilds[] = $objChilds->row(); - } - } else { - $arrChilds = $objChilds->fetchEach('id'); - } - - $return = array_merge($arrChilds, $this->getFolderContent($arrChilds, $table, $options, $return)); - } - } - - return $return; - } - - public function getParentFoldersByUuid($uuid, array $config = []) - { - $returnRows = $config['returnRows'] ?? true; - - $parents = []; - $firstSkipped = false; - - while ($uuid && null !== ($parent = System::getContainer()->get('huh.utils.model')->callModelMethod('tl_files', 'findByUuid', $uuid))) { - $uuid = $parent->pid; - - // skip the file object passed into the function (only the parents should be returned) - if (!$firstSkipped) { - $firstSkipped = true; - - continue; - } - - $parents[] = $returnRows ? $parent : $parent->id; - } - - return $parents; - } - - /** - * Tries to get the binary content from a file in various sources and returns it if possible. - * - * Possible sources: - * - url - * - contao uuid - * - string is already a binary file content - * - * @param $source - * - * @return bool|mixed Returns false if the file content could not be retrieved - */ - public function retrieveFileContent($source, $silent = true) - { - // url - if (Validator::isUrl($source)) { - $client = new Client(); - $request = $client->request('GET', $source); - - if (200 === $request->getStatusCode()) { - $content = $request->getBody()->__toString(); - - if ($content) { - return $content; - } - } else { - if (!$silent) { - $body = $request->getBody()->__toString(); - - Message::addError(sprintf($GLOBALS['TL_LANG']['ERR']['httpRequestError'], $source, 'Code '.$request->getStatusCode().': '.$body)); - } - } - } - - // contao uuid - if (Validator::isUuid($source)) { - $content = $this->getFileContentFromUuid($source); - - if (false !== $content) { - return $content; - } - } - - // already binary -> ctype_print() checks if non-printable characters are in the string -> if so, it's most likely a file - if (!ctype_print($source)) { - return $source; - } - - return false; - } - - public function getExtensionFromFileContent($content) - { - if (!class_exists('\finfo')) { - return false; - } - - $finfo = new \finfo(\FILEINFO_MIME_TYPE); - - return $this->getExtensionByMimeType($finfo->buffer($content)); - } - - public function getExtensionByMimeType($mimeType) - { - foreach ($GLOBALS['TL_MIME'] as $extension => $data) { - if ($data[0] === $mimeType) { - if ('jpeg' === $extension) { - $extension = 'jpg'; - } - - return $extension; - } - } - - return false; - } -} diff --git a/src/File/FolderUtil.php b/src/File/FolderUtil.php deleted file mode 100644 index 7582c7a2..00000000 --- a/src/File/FolderUtil.php +++ /dev/null @@ -1,68 +0,0 @@ -webDir = $webDir; - $this->kernel = $kernel; - $this->symlinksCommand = $symlinksCommand; - } - - /** - * Creates an symlink to the given folder in the web director, if not already exist. - * - * @throws \Exception - */ - public function createPublicFolder(string $folderPath): void - { - if (!is_dir($this->webDir.\DIRECTORY_SEPARATOR.$folderPath)) { - $folder = new Folder($folderPath); - $folder->unprotect(); - - $application = new Application(); - $application->add($this->symlinksCommand); - $application->setAutoExit(false); - $input = new ArrayInput([ - 'command' => 'contao:symlinks', - ]); - $output = new NullOutput(); - $result = $application->run($input, $output); - - if ($result > 0) { - throw new \Exception('The symlink command exited with errors.'); - } - } - } -} diff --git a/src/Form/FormUtil.php b/src/Form/FormUtil.php deleted file mode 100644 index db37e393..00000000 --- a/src/Form/FormUtil.php +++ /dev/null @@ -1,409 +0,0 @@ -framework = $framework; - $this->container = $container; - } - - /** - * Get a new widget instance based on given attributes from a Data Container array. - * - * @param string $name The field name in the form - * @param array $data The field configuration array - * @param mixed $value The field value - * @param string $dbName The field name in the database - * @param string $table The table name in the database - * @param DataContainer|null $dc An optional DataContainer object - * @param string $mode The contao mode, use FE or BE to get proper widget/form type - * - * @return Widget|null The new widget based on given attributes - */ - public function getWidgetFromAttributes(string $name, array $data, $value = null, string $dbName = '', string $table = '', DataContainer $dc = null, string $mode = ''): ?Widget - { - if ('' === $mode) { - $mode = System::getContainer()->get('huh.utils.container')->isFrontend() ? 'FE' : 'BE'; - } - - if ('hidden' === $data['inputType']) { - $mode = 'FE'; - } - - $mode = strtoupper($mode); - $mode = \in_array($mode, ['FE', 'BE']) ? $mode : 'FE'; - $class = 'FE' === $mode ? $GLOBALS['TL_FFL'][$data['inputType']] : $GLOBALS['BE_FFL'][$data['inputType']]; - /** @var $widget Widget */ - $widget = $this->framework->getAdapter(Widget::class); - - if (empty($class) || !class_exists($class)) { - return null; - } - - return new $class($widget->getAttributesFromDca($data, $name, $value, $dbName, $table, $dc)); - } - - /** - * Prepares a special field's value. If an array is inserted, the function will call itself recursively. - * - * Possible config options: - * - * * preserveEmptyArrayValues -> preserves array values even if they're empty - * * skipLocalization -> skips usage of "reference" array defined in the field's dca - * * skipDcaLoading: boolean -> skip calling Controller::loadDataContainer on $dc->table - * * skipOptionCaching -> skip caching options if $value is an array - * * _dcaOverride: Array Set a custom dca from outside, which will be used instead of global dca value. - * - * @param $value - * - * @return string - */ - public function prepareSpecialValueForOutput(string $field, $value, DataContainer $dc, array $config = [], bool $isRecursiveCall = false) - { - $value = StringUtil::deserialize($value); - - /** @var Controller $controller */ - $controller = $this->framework->getAdapter(Controller::class); - - /** @var System $system */ - $system = $this->framework->getAdapter(System::class); - - /** @var CfgTagModel $cfgTagModel */ - $cfgTagModel = $this->framework->getAdapter(CfgTagModel::class); - - $system->loadLanguageFile('default'); - - // prepare data - $table = $dc->table; - - if (!isset($config['skipDcaLoading']) || !$config['skipDcaLoading']) { - $controller->loadDataContainer($table); - $system->loadLanguageFile($table); - } - - $arraySeparator = $config['arraySeparator'] ?? ', '; - $skipReplaceInsertTags = $config['skipReplaceInsertTags'] ?? false; - - // dca can be overridden from outside - if (isset($config['_dcaOverride']) && \is_array($config['_dcaOverride'])) { - $data = $config['_dcaOverride']; - } elseif (!isset($GLOBALS['TL_DCA'][$table]['fields'][$field]) || !\is_array($GLOBALS['TL_DCA'][$table]['fields'][$field])) { - return $value; - } else { - $data = $GLOBALS['TL_DCA'][$table]['fields'][$field]; - } - - $inputType = $data['inputType'] ?? null; - - // multicolumneditor - $mceFieldSeparator = $config['mceFieldSeparator'] ?? "\t"; - $mceRowSeparator = $config['mceRowSeparator'] ?? "\t\n"; - $skipMceFieldLabels = $config['skipMceFieldLabels'] ?? false; - $skipMceFieldLabelFormatting = $config['skipMceFieldLabelFormatting'] ?? false; - $skipMceFields = isset($config['skipMceFields']) && \is_array($config['skipMceFields']) ? $config['skipMceFields'] : []; - $mceFields = isset($config['mceFields']) && \is_array($config['mceFields']) ? $config['mceFields'] : []; - - if ('multiColumnEditor' == $inputType - && $this->container->get('huh.utils.container')->isBundleActive('HeimrichHannot\MultiColumnEditorBundle\HeimrichHannotContaoMultiColumnEditorBundle')) { - if (\is_array($value)) { - $formatted = ''; - - foreach ($value as $row) { - // new line - add "\t\n" after each line and not only "\n" to prevent outlook line break remover - $formatted .= $mceRowSeparator; - - foreach ($row as $fieldName => $fieldValue) { - if (\in_array($fieldName, $skipMceFields) || (\is_array($mceFields) && !\in_array($fieldName, $mceFields))) { - continue; - } - - $dca = $data['eval']['multiColumnEditor']['fields'][$fieldName]; - - $label = ''; - - if (!$skipMceFieldLabels) { - $label = ($dca['label'][0] ?: $fieldName).': '; - - if ($skipMceFieldLabelFormatting) { - $label = $fieldName.': '; - } - } - - // indent new line - $formatted .= $mceFieldSeparator.$label.$this->prepareSpecialValueForOutput($fieldName, $fieldValue, $dc, array_merge($config, [ - '_dcaOverride' => $dca, - ])); - } - } - - // new line - add "\t\n" after each line and not only "\n" to prevent outlook line break remover - $formatted .= $mceRowSeparator; - - return $formatted; - } - } - - // inputUnit - if ('inputUnit' == $inputType) { - $data = StringUtil::deserialize($value, true); - - if (!isset($data['value'])) { - $data['value'] = ''; - } - - if (!isset($data['unit'])) { - $data['unit'] = ''; - } - - return $data['value'].$arraySeparator.$data['unit']; - } - - // Recursively apply logic to array - if (\is_array($value)) { - foreach ($value as $k => $v) { - $result = $this->prepareSpecialValueForOutput($field, $v, $dc, $config, true); - - if (isset($config['preserveEmptyArrayValues']) && $config['preserveEmptyArrayValues']) { - $value[$k] = $result; - } else { - if (null !== $result && !empty($result)) { - $value[$k] = $result; - } else { - unset($value[$k]); - } - } - } - - // reset caches - $this->optionsCache = null; - - return implode($arraySeparator, $value); - } - - $reference = null; - - if (isset($data['reference']) && (!isset($config['skipLocalization']) || !$config['skipLocalization'])) { - $reference = $data['reference']; - } - - $rgxp = null; - - if (isset($data['eval']['rgxp'])) { - $rgxp = $data['eval']['rgxp']; - } - - if ((!isset($config['skipOptionCaching']) || !$config['skipOptionCaching']) && null !== $this->optionsCache) { - $options = $this->optionsCache; - } else { - try { - $options = $this->container->get('huh.utils.dca')->getConfigByArrayOrCallbackOrFunction($data, 'options', [$dc]); - } catch (\ErrorException $e) { - $options = []; - } - - $this->optionsCache = !\is_array($options) ? [] : $options; - } - - // foreignKey - if (isset($data['foreignKey'])) { - [$foreignTable, $foreignField] = explode('.', $data['foreignKey']); - - if (null !== ($instance = $this->container->get('huh.utils.model')->findModelInstanceByPk($foreignTable, $value))) { - $value = $instance->{$foreignField}; - } - } - - if ('explanation' == $inputType) { - if (isset($data['eval']['text'])) { - return $data['eval']['text']; - } - } elseif ('cfgTags' == $inputType) { - $collection = $cfgTagModel->findBy(['source=?', 'id = ?'], [$data['eval']['tagsManager'], $value]); - $value = null; - - if (null !== $collection) { - $result = $collection->fetchEach('name'); - $value = implode($arraySeparator, $result); - } - } elseif ('date' == $rgxp) { - $value = Date::parse(Config::get('dateFormat'), $value); - } elseif ('time' == $rgxp) { - $value = Date::parse(Config::get('timeFormat'), $value); - } elseif ('datim' == $rgxp) { - $value = Date::parse(Config::get('datimFormat'), $value); - } elseif (Validator::isBinaryUuid($value)) { - $strPath = $this->container->get('huh.utils.file')->getPathFromUuid($value); - $value = $strPath ? Environment::get('url').'/'.$strPath : StringUtil::binToUuid($value); - } // Replace boolean checkbox value with "yes" and "no" - else { - if ((isset($data['eval']['isBoolean']) && $data['eval']['isBoolean']) || ('checkbox' == $inputType && !($data['eval']['multiple'] ?? false))) { - $value = ('' != $value) ? $GLOBALS['TL_LANG']['MSC']['yes'] : $GLOBALS['TL_LANG']['MSC']['no']; - } elseif (\is_array($options) && array_is_assoc($options)) { - $value = isset($options[$value]) ? $options[$value] : $value; - } - } - - if (\is_array($reference)) { - $value = isset($reference[$value]) ? ((\is_array($reference[$value])) ? $reference[$value][0] : $reference[$value]) : $value; - } - - if (isset($data['eval']['encrypt']) && $data['eval']['encrypt']) { - [$encrypted, $iv] = explode('.', $value); - - $value = $this->container->get('huh.utils.encryption')->decrypt($encrypted, $iv); - } - - // reset caches - if (!$isRecursiveCall) { - $this->optionsCache = null; - } - - if (!$skipReplaceInsertTags) { - $value = Controller::replaceInsertTags($value); - } - - // Convert special characters (see #1890) - return StringUtil::specialchars($value); - } - - public function escapeAllHtmlEntities($table, $field, $value) - { - if (!$value) { - return $value; - } - - Controller::loadDataContainer($table); - - $data = $GLOBALS['TL_DCA'][$table]['fields'][$field]; - - $preservedTags = isset($data['eval']['allowedTags']) ? $data['eval']['allowedTags'] : Config::get('allowedTags'); - - $requestCleaner = new RequestCleaner(); - - if ( - isset($data['eval']) - && ( - ($data['eval']['allowHtml'] ?? false) - || \strlen($data['eval']['rte'] ?? '') - || ($data['eval']['preserveTags'] ?? false) - ) - ) { - // always decode entities if HTML is allowed - $value = $requestCleaner->cleanHtml($value, true, true, $preservedTags); - } elseif (\is_array($data['options'] ?? false) || isset($data['options_callback']) || isset($data['foreignKey'])) { - // options should not be strict cleaned, as they might contain html tags like - $value = $requestCleaner->cleanHtml($value, true, true, $preservedTags); - } else { - $value = $requestCleaner->clean($value, $data['eval']['decodeEntities'] ?? false, true); - } - - return $value; - } - - /** - * Get an instance of Widget by passing fieldname and dca data. - * - * @param string $fieldName The field name - * @param array $dca The DCA - * @param array|null $value - * @param string $dbField The database field name - * @param string $table The table - * @param null $dataContainer object The data container - * - * @return Widget|null - */ - public function getBackendFormField(string $fieldName, array $dca, $value = null, $dbField = '', $table = '', $dataContainer = null) - { - if (!($strClass = $GLOBALS['BE_FFL'][$dca['inputType']])) { - return null; - } - - return new $strClass(Widget::getAttributesFromDca($dca, $fieldName, $value, $dbField, $table, $dataContainer)); - } - - public function getModelDataAsNotificationTokens(array $data, string $prefix, DataContainer $dc, array $config = []) - { - $prefix = $prefix ?? 'form_'; - $skipRawValues = $config['skipRawValues'] ?? false; - $rawValuePrefix = $config['rawValuePrefix'] ?? 'raw_'; - $skipFormattedValues = $config['skipFormattedValues'] ?? false; - $formattedValuePrefix = $config['formattedValuePrefix'] ?? ''; - $skipFields = $config['skipFields'] ?? []; - $restrictFields = $config['restrictFields'] ?? []; - $formatOptions = $config['formatOptions'] ?? []; - - $result = []; - - // raw values - if (!$skipRawValues) { - foreach ($data as $field => $value) { - if (empty($restrictFields) && \in_array($field, $skipFields)) { - continue; - } - - if (!empty($restrictFields) && !\in_array($field, $restrictFields)) { - continue; - } - - $result[$prefix.$rawValuePrefix.$field] = $value; - } - } - - // formatted values - if (!$skipFormattedValues) { - foreach ($data as $field => $value) { - if (empty($restrictFields) && \in_array($field, $skipFields)) { - continue; - } - - if (!empty($restrictFields) && !\in_array($field, $restrictFields)) { - continue; - } - - $result[$prefix.$formattedValuePrefix.$field] = $this->prepareSpecialValueForOutput( - $field, $value, $dc, $formatOptions - ); - } - } - - return $result; - } -} diff --git a/src/HeimrichHannotContaoUtilsBundle.php b/src/HeimrichHannotContaoUtilsBundle.php deleted file mode 100644 index 231735f2..00000000 --- a/src/HeimrichHannotContaoUtilsBundle.php +++ /dev/null @@ -1,20 +0,0 @@ -framework = $container->get('contao.framework'); - $this->container = $container; - } - - /** - * @throws \Exception - */ - public function generateIcs(array $data, array $options = []): ?string - { - if (!class_exists('Eluceo\iCal\Component\Event')) { - throw new \Exception('The composer package eluceo/ical could not be found and is required by this service. Please install it via "composer require eluceo/ical ^0.16".'); - } - - // prepare data - $adjustTime = $options['adjustTime'] ?? false; - - $addTime = $data['addTime'] ?? false; - $end = null; - - if (empty($data['endDate'] ?? null)) { - $data['endDate'] = $data['startDate']; - } - - if ($adjustTime && $addTime) { - $data['startTime'] = strtotime(date('Y-m-d', $data['startDate']).' '.date('H:i:s', $data['startTime'])); - $data['endTime'] = strtotime(date('Y-m-d', $data['endDate']).' '.date('H:i:s', $data['endTime'])); - } - - if ($addTime && isset($data['startTime']) && $data['startTime']) { - $start = (new \DateTime())->setTimestamp($data['startTime']); - } else { - $start = (new \DateTime())->setTimestamp($data['startDate']); - $start->setTime(0, 0, 0); - } - - if (isset($data['endDate']) && $data['endDate']) { - // workaround for allday events - $end = (new \DateTime())->setTimestamp($data['endDate']); - $end->setTime(0, 0, 0); - } - - if ($addTime && isset($data['endTime']) && $data['endTime']) { - $end = (new \DateTime())->setTimestamp($data['endTime']); - } - - // create an event - $event = new \Eluceo\iCal\Component\Event(); - - $event->setNoTime(!$addTime); - $event->setDtStart($start); - - if (null !== $end) { - $event->setDtEnd($end); - } - - if (isset($data['title']) && $data['title']) { - $event->setSummary(strip_tags($data['title'])); - } - - if (isset($data['description']) && $data['description']) { - // preserve linebreaks - $description = preg_replace('@@i', "\n", $data['description']); - $description = preg_replace('@

\s*

@i', "\n\n", $description); - $description = str_replace(['

', '

'], '', $description); - - $event->setDescription(strip_tags($description)); - } - - // compose location out of various fields - $locationData = []; - - if (isset($data['location']) && $data['location']) { - $locationData['location'] = $data['location']; - } - - if (isset($data['street']) && $data['street']) { - $locationData['street'] = $data['street']; - } - - if (isset($data['postal']) && $data['postal']) { - $locationData['postal'] = $data['postal']; - } - - if (isset($data['city']) && $data['city']) { - $locationData['city'] = $data['city']; - } - - if (isset($data['country']) && $data['country']) { - $locationData['country'] = $data['country']; - } - - if (!empty($locationData)) { - $result = []; - - if (isset($locationData['location'])) { - $result[] = $locationData['location']; - } - - if (isset($locationData['street'])) { - $result[] = $locationData['street']; - } - - if (isset($locationData['postal']) && isset($locationData['city'])) { - $result[] = $locationData['postal'].' '.$locationData['city']; - } elseif (isset($locationData['city'])) { - $result[] = $locationData['city']; - } - - if (isset($locationData['country'])) { - $result[] = $locationData['country']; - } - - $event->setLocation(implode(', ', $result)); - } - - if (isset($data['url']) && $data['url']) { - $event->setUrl(strip_tags($data['url'])); - } - - // create a calendar - $calendar = new \Eluceo\iCal\Component\Calendar(Environment::get('url')); - - $calendar->setTimezone(\Config::get('timeZone')); - $calendar->addComponent($event); - - return $calendar->render(); - } -} diff --git a/src/Image/ImageUtil.php b/src/Image/ImageUtil.php deleted file mode 100644 index 19c0f886..00000000 --- a/src/Image/ImageUtil.php +++ /dev/null @@ -1,412 +0,0 @@ -framework = $container->get('contao.framework'); - $this->container = $container; - } - - /** - * Add an image to a template. - * - * Advanced version of Controller::addImageToTemplate - * with custom imageField and imageSelectorField and array instead of FrontendTemplate. - * - * @param string $imageField the image field name (typical singleSRC) - * @param string $imageSelectorField the image selector field indicated if an image is added (typical addImage) - * @param array $templateData An array to add the generated data to - * @param array $item The source data containing the imageField and imageSelectorField - * @param int|null $maxWidth An optional maximum width of the image - * @param string|null $lightboxId An optional lightbox ID - * @param string|null $lightboxName An optional lightbox name - * @param FilesModel|null $model an optional file model used to read meta data - */ - public function addToTemplateData( - string $imageField, - string $imageSelectorField, - array &$templateData, - array $item, - int $maxWidth = null, - string $lightboxId = null, - string $lightboxName = null, - FilesModel $model = null - ) { - $containerUtil = $this->container->get('huh.utils.container'); - $rootDir = $this->container->getParameter('kernel.project_dir'); - - try { - if (Validator::isUuid($item[$imageField])) { - $file = $this->container->get('huh.utils.file')->getFileFromUuid($item[$imageField]); - - if (null === $file) { - return; - } - } else { - $file = new File($item[$imageField]); - } - $imgSize = $file->imageSize; - } catch (\Exception $e) { - return; - } - - if (null === $model) { - $model = $file->getModel(); - } - - $size = StringUtil::deserialize($item['size'] ?? ''); - - if (is_numeric($size)) { - $size = [0, 0, (int) $size]; - } elseif (!\is_array($size)) { - $size = []; - } - - $size += [0, 0, 'crop']; - - if (null === $maxWidth) { - $maxWidth = ($containerUtil->isBackend()) ? 320 : Config::get('maxImageWidth'); - } - - $marginArray = ($containerUtil->isBackend()) ? '' : StringUtil::deserialize($item['imagemargin'] ?? ''); - - // Store the original dimensions - $templateData['width'] = $imgSize[0] ?? 0; - $templateData['height'] = $imgSize[1] ?? 0; - - // Adjust the image size - if ($maxWidth > 0) { - // Subtract the margins before deciding whether to resize (see #6018) - if (\is_array($marginArray) && 'px' == $marginArray['unit']) { - $margin = (int) $marginArray['left'] + (int) $marginArray['right']; - - // Reset the margin if it exceeds the maximum width (see #7245) - if ($maxWidth - $margin < 1) { - $marginArray['left'] = ''; - $marginArray['right'] = ''; - } else { - $maxWidth -= $margin; - } - } - - if ($size[0] > $maxWidth || (!$size[0] && !$size[1] && (!$imgSize[0] || $imgSize[0] > $maxWidth))) { - // See #2268 (thanks to Thyon) - $ratio = ($size[0] && $size[1]) ? $size[1] / $size[0] : (($imgSize[0] && $imgSize[1]) ? $imgSize[1] / $imgSize[0] : 0); - - $size[0] = $maxWidth; - $size[1] = floor($maxWidth * $ratio); - } - } - - // Disable responsive images in the back end (see #7875) - if ($containerUtil->isBackend()) { - unset($size[2]); - } - - $imageFile = $file; - - try { - $src = $this->container->get('contao.image.image_factory')->create($rootDir.'/'.$file->path, $size)->getUrl($rootDir); - $picture = $this->container->get('contao.image.picture_factory')->create($rootDir.'/'.$file->path, $size); - - $picture = [ - 'img' => $picture->getImg($rootDir, TL_FILES_URL), - 'sources' => $picture->getSources($rootDir, TL_FILES_URL), - 'ratio' => '1.0', - 'copyright' => $file->getModel()->copyright, - ]; - - if ($src !== $file->path) { - $imageFile = new File(rawurldecode($src)); - } - } catch (\Exception $e) { - $this->container->get('monolog.logger.contao')->log( - LogLevel::ERROR, - 'Image "'.$file->path.'" could not be processed: '.$e->getMessage(), - ['contao' => new ContaoContext(__METHOD__, TL_ERROR)] - ); - - $src = ''; - $picture = ['img' => ['src' => '', 'srcset' => ''], 'sources' => []]; - } - - // Image dimensions - if (false !== ($imgSize = $imageFile->imageSize) && $imageFile->exists()) { - $templateData['arrSize'] = $imgSize; - $templateData['imgSize'] = ' width="'.$imgSize[0].'" height="'.$imgSize[1].'"'; - - $picture['size'] = $imgSize; - $picture['width'] = $imgSize[0]; - $picture['height'] = $imgSize[1]; - $picture['ratio'] = $imgSize[1] > 0 ? ($imgSize[0] / $imgSize[1]) : '1.0'; - } - - $meta = []; - - // Load the meta data - if ($model instanceof FilesModel) { - if ($containerUtil->isFrontend()) { - global $objPage; - - $meta = Frontend::getMetaData($model->meta, $objPage->language); - - if (empty($meta) && null !== $objPage->rootFallbackLanguage) { - $meta = Frontend::getMetaData($model->meta, $objPage->rootFallbackLanguage); - } - } else { - $meta = Frontend::getMetaData($model->meta, $GLOBALS['TL_LANGUAGE']); - } - - $this->container->get('contao.framework')->getAdapter(Controller::class)->loadDataContainer('tl_files'); - - // Add any missing fields - foreach (array_keys($GLOBALS['TL_DCA']['tl_files']['fields']['meta']['eval']['metaFields']) as $k) { - if (!isset($meta[$k])) { - $meta[$k] = ''; - } - } - - $meta['imageTitle'] = $meta['title']; - $meta['imageUrl'] = $meta['link']; - unset($meta['title'], $meta['link']); - - // Add the meta data to the item - if (!($item['overwriteMeta'] ?? false)) { - foreach ($meta as $k => $v) { - switch ($k) { - case 'alt': - case 'imageTitle': - $item[$k] = StringUtil::specialchars($v); - - break; - - default: - $item[$k] = $v; - - break; - } - } - } - } - - $picture['alt'] = StringUtil::specialchars($item['alt']); - - $fullsize = (bool) ($item['fullsize'] ?? false); - - // Move the title to the link tag so it is shown in the lightbox - if ($fullsize && ($item['imageTitle'] ?? false) && !($item['linkTitle'] ?? false)) { - $item['linkTitle'] = $item['imageTitle']; - unset($item['imageTitle']); - } - - if (isset($item['imageTitle'])) { - $picture['title'] = StringUtil::specialchars($item['imageTitle']); - } - - // empty the attributes in order to avoid passing the link attributes to the img element - $picture['attributes'] = ''; - - $templateData['picture'] = $picture; - - // Provide an ID for single lightbox images in HTML5 (see #3742) - if (null === $lightboxId && $fullsize) { - $lightboxId = substr(md5($lightboxName.'_'.$item['id']), 0, 6); - } - - // Float image - if ($item['floating'] ?? false) { - $templateData['floatClass'] = ' float_'.$item['floating']; - } - - // Do not override the "href" key (see #6468) - $hrefKey = (isset($templateData['href']) && '' != $templateData['href']) ? 'imageHref' : 'href'; - - // Image link - if ($item['imageUrl'] && $containerUtil->isFrontend()) { - $templateData[$hrefKey] = $item['imageUrl']; - $templateData['attributes'] = ''; - - if ($fullsize) { - // Open images in the lightbox - if (preg_match('/\.(jpe?g|gif|png)$/', $item['imageUrl'])) { - // Do not add the TL_FILES_URL to external URLs (see #4923) - if (0 !== strncmp($item['imageUrl'], 'http://', 7) && 0 !== strncmp($item['imageUrl'], 'https://', 8)) { - $templateData[$hrefKey] = TL_FILES_URL.System::urlEncode($item['imageUrl']); - } - - $templateData['attributes'] = ' data-lightbox="'.$lightboxId.'"'; - } else { - $templateData['attributes'] = ' target="_blank"'; - } - } - } // Fullsize view - elseif ($fullsize && $containerUtil->isFrontend()) { - $templateData[$hrefKey] = TL_FILES_URL.System::urlEncode($file->path); - $templateData['attributes'] = ' data-lightbox="'.$lightboxId.'"'; - } - - // Add the meta data to the template - foreach (array_keys($meta) as $k) { - $templateData[$k] = $item[$k] ?? null; - } - - // Do not urlEncode() here because getImage() already does (see #3817) - $templateData['src'] = TL_FILES_URL.$src; - $templateData[$imageField] = $file->path; - $templateData['linkTitle'] = $item['linkTitle'] ?? ($item['title'] ?? null); - $templateData['fullsize'] = $fullsize; - $templateData['addBefore'] = ('below' != ($item['floating'] ?? '')); - $templateData['margin'] = Controller::generateMargin($marginArray); - $templateData[$imageSelectorField] = true; - - // HOOK: modify image template data - if (isset($GLOBALS['TL_HOOKS']['addImageToTemplateData']) && \is_array($GLOBALS['TL_HOOKS']['addImageToTemplateData'])) { - foreach ($GLOBALS['TL_HOOKS']['addImageToTemplateData'] as $callback) { - $templateData = System::importStatic($callback[0])->{$callback[1]}($templateData, $imageField, $imageSelectorField, $item, $maxWidth, $lightboxId, $lightboxName, $model); - } - } - } - - /** - * Convert sizes like 2em, 10cm or 12pt to pixels. - * - * @param string $size The size string - * - * @return int The pixel value - */ - public function getPixelValue(string $size) - { - $value = preg_replace('/[^0-9.-]+/', '', $size); - $unit = preg_replace('/[^acehimnprtvwx%]/', '', $size); - - // Convert 16px = 1em = 2ex = 12pt = 1pc = 1/6in = 2.54/6cm = 25.4/6mm = 100% - switch ($unit) { - case '': - case 'px': - return (int) round($value); - - break; - - case 'em': - return (int) round($value * 16); - - break; - - case 'ex': - return (int) round($value * 16 / 2); - - break; - - case 'pt': - return (int) round($value * 16 / 12); - - break; - - case 'pc': - return (int) round($value * 16); - - break; - - case 'in': - return (int) round($value * 16 * 6); - - break; - - case 'cm': - return (int) round($value * 16 / (2.54 / 6)); - - break; - - case 'mm': - return (int) round($value * 16 / (25.4 / 6)); - - break; - - case '%': - return (int) round($value * 16 / 100); - - break; - } - - return 0; - } - - /** - * Prepares one image for a typical Contao template. - * - * Possible option keys: - * - imageField: (string) The name of the field containers the image uuid. Default: singleSRC - * - imageSelectorField: (?string) The name of the field that indicated if an images is added. Set to null to skip check. Default: addImage - * - maxWidth: (int) An optional maximum width of the image. Passed directly to Controller::addImageToTemplate() - * - lightboxId: (string) An optional lightbox ID. Passed directly to Controller::addImageToTemplate() - * - * @param array $data the model/module/element data - * @param array $options Additional options - * - * @return array - */ - public function prepareImage(array $data, array $options = []): ?array - { - $defaultOptions = [ - 'imageField' => 'singleSRC', - 'imageSelectorField' => 'addImage', - ]; - $options = array_merge($defaultOptions, $options); - - if (null !== $options['imageSelectorField'] && (!isset($data[$options['imageSelectorField']]) || !$data[$options['imageSelectorField']])) { - return null; - } - - $fileModel = FilesModel::findByUuid($data[$options['imageField']]); - - if (!$fileModel || !is_file($this->container->getParameter('kernel.project_dir').'/'.$fileModel->path)) { - return null; - } - - $image = $data; - $image['singleSRC'] = $fileModel->path; - - $template = new FrontendTemplate(); - - Controller::addImageToTemplate( - $template, - $image, - isset($options['maxWidth']) ? $options['maxWidth'] : null, - isset($options['lightboxId']) ? $options['lightboxId'] : null - ); - - return $template->getData(); - } -} diff --git a/src/Location/LocationUtil.php b/src/Location/LocationUtil.php deleted file mode 100644 index fffcf365..00000000 --- a/src/Location/LocationUtil.php +++ /dev/null @@ -1,227 +0,0 @@ -framework = $framework; - } - - /** - * Computes the coordinates from a given address. Supported array keys are:. - * - * - street - * - postal - * - city - * - country - * - * @return array|bool - */ - public function computeCoordinatesByArray(array $data) - { - $criteria = [ - 'street', - 'postal', - 'city', - 'state', - 'country', - ]; - - $sortedData = []; - - // keep the right order - foreach ($criteria as $name) { - if (isset($data[$name])) { - $sortedData[] = $data[$name]; - } - } - - return $this->computeCoordinatesByString(implode(' ', $sortedData)); - } - - /** - * Computes the coordinates from a given address. Supported array keys are:. - * - * - street - * - postal - * - city - * - country - * - * @return array|bool - */ - public function computeCoordinatesByString(string $address, string $apiKey = '') - { - $curlUtil = System::getContainer()->get('huh.utils.request.curl'); - - $url = sprintf(static::GOOGLE_MAPS_GEOCODE_URL, urlencode($address)); - - if ($apiKey) { - $url = System::getContainer()->get('huh.utils.url')->addQueryString('key='.$apiKey, $url); - } elseif (Config::get('utilsGoogleApiKey')) { - $url = System::getContainer()->get('huh.utils.url')->addQueryString('key='.Config::get('utilsGoogleApiKey'), $url); - } - - $result = $curlUtil->request($url); - - if (!$result) { - return false; - } - - $response = json_decode($result, true); - - if (isset($response['error_message'])) { - /** @var AttributeBagInterface $objSession */ - $session = System::getContainer()->get('session')->getBag('contao_backend'); - - $session->set('utils.location.error', $response['error_message']); - - return false; - } - - return ['lat' => $response['results'][0]['geometry']['location']['lat'], 'lng' => $response['results'][0]['geometry']['location']['lng']]; - } - - public function computeCoordinatesInSaveCallback($value, \Contao\DataContainer $dc) - { - $data = [ - 'street' => $dc->activeRecord->street, - 'postal' => $dc->activeRecord->postal, - 'city' => $dc->activeRecord->city, - ]; - - if ($value || empty(array_filter($data))) { - return $value; - } - - $result = System::getContainer()->get('huh.utils.location')->computeCoordinatesByArray([ - 'street' => $dc->activeRecord->street, - 'postal' => $dc->activeRecord->postal, - 'city' => $dc->activeRecord->city, - ]); - - if (false === $result || !\is_array($result)) { - /** @var AttributeBagInterface $objSession */ - $session = System::getContainer()->get('session')->getBag('contao_backend'); - - if ($error = $session->get('utils.location.error')) { - throw new \Exception($session->get('utils.location.error')); - } - - return ''; - } - - return $result['lat'].','.$result['lng']; - } - - /** - * @param $kmlData string The KML data - * - * @return array - */ - public function getCoordinatesFromKml(string $kmlData, array $options = []) - { - $kmlData = System::getContainer()->get('huh.utils.string')->convertXmlToArray($kmlData); - - if (!\is_array($kmlData) || !isset($kmlData['Document']['Placemark']['LineString']['coordinates'])) { - return null; - } - - $coordinates = preg_replace('/\s+/', ' ', $kmlData['Document']['Placemark']['LineString']['coordinates']); - $coordinates = explode(' ', $coordinates); - - foreach ($coordinates as $coordinate) { - if (!$coordinate) { - continue; - } - - $exploded = explode(',', $coordinate); - - if (\count($exploded) < 2) { - continue; - } - - $location = [ - 'lat' => $exploded[1], - 'lng' => $exploded[0], - ]; - - if ((!isset($options['skipAltitude']) || !$options['skipAltitude']) && \count($exploded) > 2) { - $location['alt'] = $exploded[2]; - } - - $locations[] = $location; - } - - return $locations; - } - - /** - * @param $gpxData string The KML data - * - * @return array - */ - public function getCoordinatesFromGpx(string $gpxData, array $options = []) - { - $locations = []; - $gpxData = System::getContainer()->get('huh.utils.string')->convertXmlToArray($gpxData); - - if (!\is_array($gpxData) || !isset($gpxData['trk'])) { - return null; - } - - if (isset($gpxData['trk']['name'])) { - foreach ($gpxData['trk']['trkseg']['trkpt'] as $trkPt) { - $location = [ - 'lat' => $trkPt['@attributes']['lat'], - 'lng' => $trkPt['@attributes']['lon'], - ]; - - if (isset($trkPt['ele']) && $trkPt['ele']) { - $location['alt'] = $trkPt['ele']; - } - - $locations[] = $location; - } - } else { - foreach ($gpxData['trk'] as $trk) { - if (!\is_array($trk['trkseg']['trkpt'])) { - continue; - } - - foreach ($trk['trkseg']['trkpt'] as $trkPt) { - $location = [ - 'lat' => $trkPt['@attributes']['lat'], - 'lng' => $trkPt['@attributes']['lon'], - ]; - - if (isset($trkPt['ele']) && $trkPt['ele']) { - $location['alt'] = $trkPt['ele']; - } - - $locations[] = $location; - } - } - } - - return $locations; - } -} diff --git a/src/Member/MemberUtil.php b/src/Member/MemberUtil.php deleted file mode 100644 index 29d627a8..00000000 --- a/src/Member/MemberUtil.php +++ /dev/null @@ -1,187 +0,0 @@ -framework = $framework; - $this->modelUtil = $modelUtil; - } - - /** - * Adds a new home dir to a member. Therefore a folder named with the members's id is created in $varRootFolder. - * - * @param $member MemberModel|int The member as object or member id - * @param $booleanProperty string The name of the boolean member property (e.g. "assignDir") - * @param $propertyName string The name of the member property (e.g. "homeDir") - * @param $rootFolder string|object The base folder as instance of \FilesModel, path string or uuid - * @param bool|false $overwrite bool Determines if an existing folder can be overridden - * - * @return bool|string returns true, if a directory has already been linked with the member, the folders uuid if successfully added and false if - * errors occurred - */ - public static function addHomeDir( - $member, - string $booleanProperty = 'assignDir', - string $propertyName = 'homeDir', - $rootFolder = 'files/members', - $overwrite = false - ) { - if (null === ($member = is_numeric($member) ? System::getContainer()->get('huh.utils.model')->findModelInstanceByPk('tl_member', $member) : $member)) { - return false; - } - - // already set - if ($member->{$booleanProperty} && $member->{$propertyName} && !$overwrite) { - return true; - } - - if (!($rootFolder instanceof FilesModel)) { - if (Validator::isUuid($rootFolder)) { - $folderModel = FilesModel::findByUuid($rootFolder); - $path = $folderModel->path; - } else { - $path = $rootFolder; - } - } else { - $path = $rootFolder->path; - } - - $path = str_replace(System::getContainer()->getParameter('kernel.project_dir'), '', $path); - - if (!$path) { - return false; - } - - $member->{$booleanProperty} = true; - $path = ltrim($path, '/').'/'.$member->id; - - $homeDir = new Folder($path); - - $member->{$propertyName} = $homeDir->getModel()->uuid; - - $member->save(); - - return $homeDir->getModel()->uuid; - } - - /** - * Returns a member home dir and creates one, if desired. - * - * @param $member MemberModel|int The member as object or member id - * @param $booleanProperty string The name of the boolean member property (e.g. "assignDir") - * @param $propertyName string The name of the member property (e.g. "homeDir") - * @param $rootFolder string|FilesModel The base folder as instance of FilesModel, path string or uuid - * @param bool|false $overwrite bool Determines if an existing folder can be overridden - * - * @return bool|string returns the home dir or false if an error occurred - */ - public static function getHomeDir( - $member, - string $booleanProperty = 'assignDir', - string $propertyName = 'homeDir', - $rootFolder = 'files/members', - $overwrite = false - ) { - if (null === ($member = is_numeric($member) ? System::getContainer()->get('huh.utils.model')->findModelInstanceByPk('tl_member', $member) : $member)) { - return false; - } - - $varResult = static::addHomeDir($member, $booleanProperty, $propertyName, $rootFolder, $overwrite); - - if (false === $varResult) { - return false; - } - - return System::getContainer()->get('huh.utils.file')->getPathFromUuid($member->{$propertyName}); - } - - /** - * Find active members by member group. - * - * Options (pass via options array): - * - ignoreLogin: (bool) Ignore login field when check for active state. Default: false - * - * @return MemberModel|MemberModel[]|\Contao\Model\Collection|null - */ - public function findActiveByGroups(array $groups, array $options = []) - { - if (empty($groups)) { - return null; - } - - /** @var $adapter MemberModel */ - if (null === $adapter = $this->framework->getAdapter(MemberModel::class)) { - return null; - } - - $t = $adapter->getTable(); - $time = \Date::floorToMinute(); - $values = []; - - $columns = ["($t.start='' OR $t.start<='$time') AND ($t.stop='' OR $t.stop>'".($time + 60)."') AND $t.disable=''"]; - - if (!isset($options['ignoreLogin']) || !$options['ignoreLogin']) { - $columns[] = "$t.login='1'"; - } - - if (!empty(array_filter($groups))) { - [$columns[], $tmpValues] = System::getContainer()->get('huh.utils.database')->createWhereForSerializedBlob('groups', array_filter($groups)); - $values = array_merge(array_values($values), array_values($tmpValues)); - } - - return $adapter->findBy($columns, $values, $options); - } - - public function findOrCreate(string $email) - { - /** @var $adapter MemberModel */ - if (null === $adapter = $this->framework->getAdapter(MemberModel::class)) { - return null; - } - - $member = $adapter->findByEmail($email); - - if (null === $member) { - $member = new \MemberModel(); - $member->dateAdded = time(); - $member->tstamp = time(); - $member->email = trim(strtolower($email)); - $member->save(); - } - - return $member; - } -} diff --git a/src/Model/CfgTagModel.php b/src/Model/CfgTagModel.php deleted file mode 100644 index 186f9e27..00000000 --- a/src/Model/CfgTagModel.php +++ /dev/null @@ -1,55 +0,0 @@ -get('contao.framework')->getAdapter(self::class))) { - return null; - } - - return $adapter->findBy('source', $source, $arrOptions); - } - - public static function getSourcesAsOptions(\DataContainer $dc) - { - $options = []; - $tags = System::getContainer()->get('contao.framework')->getAdapter(Database::class)->getInstance()->prepare('SELECT source FROM tl_cfg_tag GROUP BY source')->execute(); - - if (null !== $tags) { - $options = $tags->fetchEach('source'); - - asort($options); - } - - return $options; - } -} diff --git a/src/Model/ModelUtil.php b/src/Model/ModelUtil.php deleted file mode 100644 index e07cb34f..00000000 --- a/src/Model/ModelUtil.php +++ /dev/null @@ -1,569 +0,0 @@ -dcaUtil = $dcaUtil; - $this->framework = $contaoFramework; - $this->session = $session; - $this->requestStack = $requestStack; - $this->formUtil = $formUtil; - $this->kernelBundles = $kernelBundles; - } - - /** - * Set the entity defaults from dca config (for new model entry). - * - * @return Model The modified model, containing the default values from all dca fields - * - * @deprecated Use DcaUtil::setDefaultsFromDca instead - * @codeCoverageIgnore - */ - public function setDefaultsFromDca(Model $objModel) - { - return $this->dcaUtil->setDefaultsFromDca($objModel->getTable(), $objModel); - } - - /** - * Returns a model instance if for a given table and id(primary key). - * Return null, if model type or model instance with given id not exist. - * - * @param mixed $pk - * - * @return mixed - * - * @deprecated Use Utils service instead - * @codeCoverageIgnore - */ - public function findModelInstanceByPk(string $table, $pk, array $options = []) - { - /* @var Model $adapter */ - if (!($modelClass = $this->framework->getAdapter(Model::class)->getClassFromTable($table))) { - return null; - } - - if (null === ($adapter = $this->framework->getAdapter($modelClass))) { - return null; - } - - return $adapter->findByPk($pk, $options); - } - - /** - * Returns model instances by given table and search criteria. - * - * @param mixed $columns - * @param mixed $values - * - * @return Model[]|Collection|null - * - * @deprecated Use Utils service instead - * @codeCoverageIgnore - */ - public function findModelInstancesBy(string $table, $columns, $values, array $options = []) - { - /* @var Model $adapter */ - if (!($modelClass = $this->framework->getAdapter(Model::class)->getClassFromTable($table))) { - return null; - } - - if (null === ($adapter = $this->framework->getAdapter($modelClass))) { - return null; - } - - $this->fixTablePrefixForDcMultilingual($table, $columns, $options); - - if (\is_array($values) && (!isset($options['skipReplaceInsertTags']) || !$options['skipReplaceInsertTags'])) { - $values = array_map('\Contao\Controller::replaceInsertTags', $values); - } - - if (empty($columns)) { - $columns = null; - } - - return $adapter->findBy($columns, $values, $options); - } - - /** - * Return a single model instance by table and search criteria. - * - * @return mixed - * - * @deprecated Use Utils service instead - * @codeCoverageIgnore - */ - public function findOneModelInstanceBy(string $table, array $columns, array $values, array $options = []) - { - /* @var Model $adapter */ - if (!($modelClass = $this->framework->getAdapter(Model::class)->getClassFromTable($table))) { - return null; - } - - if (null === ($adapter = $this->framework->getAdapter($modelClass))) { - return null; - } - - $this->fixTablePrefixForDcMultilingual($table, $columns, $options); - - if (\is_array($values) && (!isset($options['skipReplaceInsertTags']) || !$options['skipReplaceInsertTags'])) { - $values = array_map('\Contao\Controller::replaceInsertTags', $values); - } - - if (empty($columns)) { - $columns = null; - } - - return $adapter->findOneBy($columns, $values, $options); - } - - /** - * Returns multiple model instances by given table and ids. - * - * @return mixed - * - * @deprecated Use Utils service instead - * @codeCoverageIgnore - */ - public function findMultipleModelInstancesByIds(string $table, array $ids, array $options = []) - { - /* @var Model $adapter */ - if (!($modelClass = $this->framework->getAdapter(Model::class)->getClassFromTable($table))) { - return null; - } - - if (null === ($adapter = $this->framework->getAdapter($modelClass))) { - return null; - } - - if ($this->dcaUtil->isDcMultilingual($table) && $this->dcaUtil->isDcMultilingual3()) { - $table = 't1'; - } - - return $adapter->findBy(["$table.id IN(".implode(',', array_map('\intval', $ids)).')'], null, $options); - } - - /** - * Returns multiple model instances by given table and id or alias. - * - * @param mixed $idOrAlias - * - * @return mixed - * - * @deprecated Use Utils service instead - * @codeCoverageIgnore - */ - public function findModelInstanceByIdOrAlias(string $table, $idOrAlias, array $options = []) - { - if (!($modelClass = $this->framework->getAdapter(Model::class)->getClassFromTable($table))) { - return null; - } - - /* @var Model $adapter */ - if (null === ($adapter = $this->framework->getAdapter($modelClass))) { - return null; - } - - if ($this->dcaUtil->isDcMultilingual($table) && $this->dcaUtil->isDcMultilingual3()) { - $table = 't1'; - } - - $options = array_merge( - [ - 'limit' => 1, - 'column' => !is_numeric($idOrAlias) ? ["$table.alias=?"] : ["$table.id=?"], - 'value' => $idOrAlias, - 'return' => 'Model', - ], - $options - ); - - return $adapter->findByIdOrAlias($idOrAlias, $options); - } - - public function callModelMethod(string $table, string $method, ...$args) - { - if (!($modelClass = $this->framework->getAdapter(Model::class)->getClassFromTable($table))) { - return null; - } - - if (null === ($adapter = $this->framework->getAdapter($modelClass))) { - return null; - } - - return \call_user_func_array([$adapter, $method], $args); - } - - /** - * Fixes existing table prefixed already aliased in MultilingualQueryBuilder::buildQueryBuilderForFind(). - * - * @param $columns - * - * @return array|mixed - */ - public function fixTablePrefixForDcMultilingual(string $table, &$columns, array &$options = []) - { - if (!$this->dcaUtil->isDcMultilingual($table) || !$this->dcaUtil->isDcMultilingual3()) { - return $columns; - } - - if (\is_array($columns)) { - $fixed = []; - - foreach ($columns as $column) { - $fixed[] = str_replace($table.'.', 't1.', $column); - } - - $columns = $fixed; - } else { - $columns = str_replace($table.'.', 't1.', $columns); - } - - if (\is_array($options) && isset($options['order'])) { - $options['order'] = str_replace($table.'.', 't1.', $options['order']); - } - } - - public function getDcMultilingualTranslationRecord($table, $id, $language = null) - { - Controller::loadDataContainer($table); - $dca = $GLOBALS['TL_DCA'][$table]; - - $pidColumnName = $dca['config']['langPid'] ?: 'langPid'; - $langColumnName = $dca['config']['langColumnName'] ?: 'language'; - $language = $language ?: $this->getCurrentDcMultilingualLanguage($table, $id); - - if (!$language) { - return false; - } - - $record = $this->framework->createInstance(Database::class)->prepare("SELECT * FROM $table WHERE $pidColumnName=? AND $langColumnName=?")->limit(1)->execute($id, $language); - - if (null !== $record) { - return $record->row(); - } - } - - /** - * Get the current dc_multilingual language even DC_Multilingual::edit() didn't run. - * This can be used in onload_callbacks for example since here DC_Multilingual::edit() didn't run, yet. - * - * @return bool|mixed - */ - public function getCurrentDcMultilingualLanguage(string $table, int $id) - { - $translatableLangs = $this->getDcMultilingualTranslatableLanguages($table); - - /** @var SessionInterface $objSessionBag */ - $objSessionBag = $this->session->getBag('contao_backend'); - $sessionKey = 'dc_multilingual:'.$table.':'.$id; - - /** @var Request $request */ - $request = $this->requestStack->getCurrentRequest(); - - if ('tl_language' === $request->request->get('FORM_SUBMIT')) { - $language = $request->request->get('language'); - } elseif ($objSessionBag->has($sessionKey)) { - $language = $objSessionBag->get($sessionKey); - } - - if (\in_array($language, $translatableLangs)) { - return $language; - } - - return false; - } - - public function getDcMultilingualTranslatableLanguages(string $table) - { - Controller::loadDataContainer($table); - $dca = $GLOBALS['TL_DCA'][$table]; - - // Languages array - if (isset($dca['config']['languages'])) { - return $dca['config']['languages']; - } - - return $this->getDcMultilingualRootPageLanguages(); - } - - /** - * Get the list of languages based on root pages. - * - * @return array - */ - public function getDcMultilingualRootPageLanguages() - { - $pages = $this->framework->createInstance(Database::class)->execute("SELECT DISTINCT language FROM tl_page WHERE type='root' AND language!=''"); - $languages = $pages->fetchEach('language'); - - array_walk( - $languages, - function (&$value) { - $value = str_replace('-', '_', $value); - } - ); - - return $languages; - } - - /** - * Recursively finds the root parent. - * - * @return Model - */ - public function findRootParentRecursively(string $parentProperty, string $table, Model $instance, bool $returnInstanceIfNoParent = true) - { - if (!$instance || !$instance->{$parentProperty} - || null === ($parentInstance = $this->findModelInstanceByPk($table, $instance->{$parentProperty}))) { - return $returnInstanceIfNoParent ? $instance : null; - } - - return $this->findRootParentRecursively($parentProperty, $table, $parentInstance); - } - - /** - * Returns an array of a model instance's parents in ascending order, i.e. the root parent comes first. - */ - public function findParentsRecursively(string $parentProperty, string $table, Model $instance): array - { - $parents = []; - - if (!$instance->{$parentProperty} || null === ($parentInstance = $this->findModelInstanceByPk($table, $instance->{$parentProperty}))) { - return $parents; - } - - return array_merge($this->findParentsRecursively($parentProperty, $table, $parentInstance), [$parentInstance]); - } - - /** - * Find all model instances for a given table. - * - * @param string $table The table name - * @param array $arrOptions Additional query options - */ - public function findAllModelInstances(string $table, array $arrOptions = []): ?Collection - { - if (!($modelClass = $this->framework->getAdapter(Model::class)->getClassFromTable($table))) { - return null; - } - - /* @var Model $adapter */ - if (null === ($adapter = $this->framework->getAdapter($modelClass))) { - return null; - } - - return $adapter->findAll($arrOptions); - } - - /** - * @param Model|object $instance - * - * @return mixed - */ - public function computeStringPattern(string $pattern, $instance, string $table, array $specialValueConfig = []) - { - Controller::loadDataContainer($table); - - $dca = &$GLOBALS['TL_DCA'][$table]; - $dc = new DC_Table_Utils($table); - $dc->id = $instance->id; - $dc->activeRecord = $instance; - - return preg_replace_callback( - '@%([^%]+)%@i', - function ($matches) use ($instance, $dc, $specialValueConfig) { - return $this->formUtil->prepareSpecialValueForOutput($matches[1], $instance->{$matches[1]}, $dc, $specialValueConfig); - }, - $pattern - ); - } - - /** - * @param $instance - * @param $table - * - * @return Model|mixed - */ - public function getModelInstanceIfId($instance, $table) - { - if ($instance instanceof Model) { - return $instance; - } - - if ($instance instanceof Collection) { - return $instance->current(); - } - - return $this->findModelInstanceByPk($table, $instance); - } - - /** - * Determine if given value is newer than DataContainer value. - * - * @param mixed $newValue - */ - public function hasValueChanged($newValue, DataContainer $dc): bool - { - if (null !== ($entity = $this->findModelInstanceByPk($dc->table, $dc->id))) { - return $newValue != $entity->{$dc->field}; - } - - return true; - } - - /** - * Get model instance value for given field. - * - * @return mixed|null - */ - public function getModelInstanceFieldValue(string $field, string $table, int $id) - { - if (null !== ($entity = $this->findModelInstanceByPk($table, $id))) { - return $entity->{$property}; - } - - return null; - } - - /** - * Find module pages. - * - * Returns page ids or models, where a frontend module is integrated - * - * Also search within blocks (heimrichhannot/contao-blocks) - * - * @param bool $collection Return PageModel Collection if true. Default: false - * @param bool $useCache If true, a filesystem cache will be used to save pages ids. Default: true - * - * @return array|Collection|PageModel|PageModel[]|null An array of page Ids (can be empty if no page found!), a PageModel collection or null - */ - public function findModulePages(ModuleModel $module, $collection = false, $useCache = true) - { - // temporary fix for symfony 5 - if (!class_exists('Symfony\Component\Cache\Simple\FilesystemCache')) { - $useCache = false; - } - - if ($useCache) { - $cache = new FilesystemCache(); - $modulePagesCache = $cache->get('huh.utils.model.modulepages'); - } - - $pageIds = []; - $cacheHit = false; - - if ($useCache && $cache->has('huh.utils.model.modulepages')) { - $modulePagesCache = $cache->get('huh.utils.model.modulepages'); - - if (\is_array($modulePagesCache) && \array_key_exists($module->id, $modulePagesCache)) { - $pageIds = $modulePagesCache[$module->id]; - $cacheHit = true; - } - } - - if (!$cacheHit) { - /** @var Database $db */ - $db = $this->framework->createInstance(Database::class); - $result = $db->prepare("SELECT `tl_page`.`id` FROM `tl_page` JOIN `tl_article` ON `tl_article`.`pid` = `tl_page`.`id` JOIN `tl_content` ON `tl_content`.`pid` = `tl_article`.`id` WHERE `tl_content`.`type` = 'module' AND `tl_content`.`module` = ?")->execute($module->id); - - if ($result->count() > 0) { - $pageIds = $result->fetchEach('id'); - } - - if (\array_key_exists('blocks', $this->kernelBundles)) { - $result = $db->prepare( - "SELECT `tl_page`.`id` FROM `tl_page` - JOIN `tl_article` ON `tl_article`.`pid` = `tl_page`.`id` - JOIN `tl_content` ON `tl_content`.`pid` = `tl_article`.`id` - JOIN `tl_block` ON `tl_block`.`module` = `tl_content`.`module` - JOIN `tl_block_module` ON `tl_block_module`.`pid` = `tl_block`.`id` - WHERE `tl_block_module`.`type` = 'default' AND `tl_block_module`.`module` = ?" - )->execute($module->id); - - if ($result->count() > 0) { - $pageIds = array_unique(array_merge($pageIds, $result->fetchEach('id'))); - } - } - - if ($useCache) { - $modulePagesCache[$module->id] = $pageIds; - $cache->set('huh.utils.model.modulepages', $modulePagesCache); - } - } - - if ($collection) { - return $this->framework->getAdapter(PageModel::class)->findMultipleByIds($pageIds); - } - - return $pageIds; - } - - /** - * @deprecated Use Utils service instead - * @codeCoverageIgnore - */ - public function addPublishedCheckToModelArrays(string $table, string $publishedField, string $startField, string $stopField, array &$columns, array $options = []) - { - $t = $table; - - if (isset($options['ignoreFePreview']) || !BE_USER_LOGGED_IN) { - $time = Date::floorToMinute(); - - $invertPublishedField = isset($options['invertPublishedField']) && $options['invertPublishedField']; - $invertStartStopFields = isset($options['invertStartStopFields']) && $options['invertStartStopFields']; - - $columns[] = "($t.$startField".($invertStartStopFields ? '!=' : '=')."'' OR $t.$startField".($invertStartStopFields ? '>' : '<=')."'$time') AND ($t.$stopField".($invertStartStopFields ? '!=' : '=')."'' OR $t.$stopField".($invertStartStopFields ? '<=' : '>')."'".($time + 60)."') AND $t.$publishedField".($invertPublishedField ? '!=' : '=')."'1'"; - } - } -} diff --git a/src/Module/ModuleUtil.php b/src/Module/ModuleUtil.php deleted file mode 100644 index e46a6ea3..00000000 --- a/src/Module/ModuleUtil.php +++ /dev/null @@ -1,125 +0,0 @@ -framework = $framework; - } - - /** - * Get the class name of a given module. - * - * @param mixed $module Module as module type string, module model object or module object - * - * @return bool - */ - public function getClassByModule($module): ?string - { - if ($module instanceof Module || $module instanceof ModuleModel) { - return Module::findClass($module->type); - } - - if (\is_string($module)) { - return Module::findClass($module); - } - - return null; - } - - /** - * Check whether a module is a sub module of another. - * - * @param mixed $module1 First module as class string, module type string, module model object or module object - * @param mixed $module2 Second module as class string, module type string, module model object or module object - * @param bool $trueIfSame Return true if $module1 and $module2 are the same - */ - public function isSubModuleOf($module1, $module2, $trueIfSame = false): bool - { - $module1 = $this->getModuleClass($module1); - - if (empty($module1)) { - return false; - } - - $module2 = $this->getModuleClass($module2); - - if (empty($module2)) { - return false; - } - - return $trueIfSame && $module1 === $module2 || is_subclass_of($module1, $module2); - } - - /** - * Get the full qualified class name for a given module. - * - * @param ModuleModel|Module|string $module a module object, a module model object, a full qualified model class name or model type - * - * @return string - */ - public function getModuleClass($module) - { - if ((\is_string($module) && !class_exists($module)) || $module instanceof ModuleModel) { - $module = $this->getClassByModule($module); - } - - if (\is_object($module)) { - $module = \get_class($module); - } - - if (!$module || !class_exists($module)) { - return ''; - } - - return $module; - } - - public function getModulesByType(string $type, array $options = []): Collection - { - $modelOptions = $options['modelOptions'] ?? [ - 'order' => 'tl_module.name ASC', - ]; - - $includeSubModules = $options['includeSubModules'] ?? false; - - $modules = System::getContainer()->get('huh.utils.model')->findModelInstancesBy('tl_module', ['tl_module.type=?'], [$type], $modelOptions); - - if (null === $modules) { - return new Collection([], 'tl_module'); - } - - $result = $modules->getModels(); - - if ($includeSubModules) { - $modules = System::getContainer()->get('huh.utils.model')->findAllModelInstances('tl_module', $modelOptions); - - while ($modules->next()) { - if ($this->isSubModuleOf($modules->current(), Module::findClass($type))) { - $result[] = $modules->current(); - } - } - } - - return new Collection($result, 'tl_module'); - } -} diff --git a/src/Page/PageUtil.php b/src/Page/PageUtil.php deleted file mode 100644 index 68a57556..00000000 --- a/src/Page/PageUtil.php +++ /dev/null @@ -1,95 +0,0 @@ -framework = $container->get('contao.framework'); - $this->container = $container; - } - - public function retrieveGlobalPageFromCurrentPageId(int $id): ?PageModel - { - if (null === ($page = $this->container->get('huh.utils.model')->findModelInstanceByPk('tl_page', $id))) { - return null; - } - - if (self::PAGE_MODEL_TYPE_ROOT == $page->type) { - return $page; - } - - if (null === ($parentPages = $this->framework->getAdapter(PageModel::class)->findParentsById($id))) { - return $page; - } - - // get inheritted values from parent pages - foreach ($parentPages as $parentPage) { - $diffValues = array_diff_assoc($parentPage->row(), $page->row()); - - if (empty($diffValues)) { - continue; - } - - foreach ($diffValues as $key => $value) { - if ($page->{$key}) { - continue; - } - $page->{$key} = $value; - } - } - - // retrieve parameters which don't come from parent pages - $page->dateFormat = $GLOBALS['TL_CONFIG']['dateFormat']; - $page->timeFormat = $GLOBALS['TL_CONFIG']['timeFormat']; - $page->datimFormat = $GLOBALS['TL_CONFIG']['datimFormat']; - $this->setParametersFromLayout($page); - - return $page; - } - - protected function setParametersFromLayout(PageModel &$page): void - { - if (!$page->layout) { - return; - } - - // get values from layout - if (null === ($layout = $this->container->get('huh.utils.model')->findModelInstanceByPk('tl_layout', - $page->layout))) { - return; - } - - $page->template = $layout->template; - $page->outputFormat = $layout->doctype; - - // get values from theme - if (null === ($theme = $this->container->get('huh.utils.model')->findModelInstanceByPk('tl_theme', - $layout->pid))) { - return; - } - - $page->templateGroup = $theme->templates; - } -} diff --git a/src/Pagination/TextualPagination.php b/src/Pagination/TextualPagination.php deleted file mode 100644 index 0f9caf8b..00000000 --- a/src/Pagination/TextualPagination.php +++ /dev/null @@ -1,92 +0,0 @@ -teasers = $teasers; - $this->singlePageUrl = $singlePageUrl; - - if (null === $objTemplate) { - /** @var FrontendTemplate|object $objTemplate */ - $objTemplate = new FrontendTemplate('textual_pagination'); - } - - $this->objTemplate = $objTemplate; - } - - /** - * Generate all page links and return them as array. - * - * @return array The page links as array - */ - public function getItemsAsArray() - { - $items = []; - - foreach ($this->teasers as $page => $teaser) { - if ($page == $this->intPage) { - $items[] = [ - 'page' => $page, - 'href' => null, - 'title' => null, - 'text' => $teaser, - ]; - } else { - $items[] = [ - 'page' => $page, - 'href' => $this->linkToPage($page), - 'title' => StringUtil::specialchars(sprintf($GLOBALS['TL_LANG']['MSC']['goToPage'], $page)), - 'text' => $teaser, - ]; - } - } - - if ($this->singlePageUrl) { - $items[] = [ - 'page' => 'singlePage', - 'href' => $this->singlePageUrl, - 'title' => null, - 'text' => $GLOBALS['TL_LANG']['MSC']['readOnSinglePage'], - ]; - } - - return $items; - } -} diff --git a/src/Pdf/AbstractPdfWriter.php b/src/Pdf/AbstractPdfWriter.php deleted file mode 100644 index 68c0c9ef..00000000 --- a/src/Pdf/AbstractPdfWriter.php +++ /dev/null @@ -1,206 +0,0 @@ -setDefaultConfig(); - } - - abstract public function setDefaultConfig(); - - /** - * Prepare the current object. - */ - public function prepare() - { - $this->pdf = $this->getPdf(); - - $this->pdf->writeHTML($this->html); - - $this->isPrepared = true; - - return $this->pdf; - } - - /** - * Generate the pdf. - * - * @param string $mode - */ - abstract public function generate($mode = self::OUTPUT_MODE_DOWNLOAD): void; - - /** - * Get html including styles. - * - * @return string - */ - public function getHtml(): ?string - { - return $this->html; - } - - /** - * Set html including styles. - * - * @return PdfWriter - */ - public function setHtml(string $html): self - { - $this->html = $html; - - return $this; - } - - /** - * Get current pdf object. - * - * @param bool $init Set true if you want to create a new pdf regardless there is always an existing pdf - */ - abstract public function getPdf(bool $init = false); - - /** - * Get the pdf file name. - * - * @return string - */ - public function getFileName(): ?string - { - return $this->fileName; - } - - /** - * Set the pdf filename. - * - * @return PdfWriter Current pdf writer instance - */ - public function setFileName(string $fileName): self - { - if (!preg_match('#\.pdf$#i', $fileName)) { - $fileName .= '.pdf'; - } - - $this->fileName = System::getContainer()->get('huh.utils.file')->sanitizeFileName($fileName); - - return $this; - } - - /** - * Get the pdf config. - */ - public function getConfig(): array - { - return $this->config; - } - - /** - * Set pdf config, replace default with custom config. - * - * @return PdfWriter Current pdf writer instance - */ - public function setConfig(array $config): self - { - $this->config = $config; - - return $this; - } - - /** - * Merge current pdf config with given. - * - * @return PdfWriter Current pdf writer instance - */ - public function mergeConfig(array $config): self - { - $this->config = array_merge($this->config, $config); - - return $this; - } - - /** - * Check if prepare was already triggered. - */ - public function isPrepared(): bool - { - return $this->isPrepared; - } - - /** - * @return string - */ - public function getFolder(): ?string - { - return $this->folder; - } - - public function setFolder(string $folder): void - { - $this->folder = $folder; - } -} diff --git a/src/Pdf/FPDIWriter.php b/src/Pdf/FPDIWriter.php deleted file mode 100644 index 582af7f4..00000000 --- a/src/Pdf/FPDIWriter.php +++ /dev/null @@ -1,213 +0,0 @@ -config = [ - 'encoding' => \Config::get('characterSet'), - 'format' => 'A4', - 'orientation' => 'P', - 'unit' => 'mm', - ]; - } - - /** - * Get the master template path. - * - * @return string - */ - public function getTemplate(): ?string - { - return $this->template; - } - - /** - * Set the master template path. - * - * @return PdfWriter Current pdf writer instance - */ - public function setTemplate(string $template): self - { - $projectDir = System::getContainer()->get('huh.utils.container')->getProjectDir(); - - $this->template = $projectDir.\DIRECTORY_SEPARATOR.ltrim(preg_replace('#^'.$projectDir.'#', '', $template), \DIRECTORY_SEPARATOR); - - return $this; - } - - /** - * Prepare the current fpdi object. - */ - public function prepare(): Fpdi - { - $this->pdf = $this->getPdf(); - - // set the custom pdf template - if (null !== $this->getTemplate() && file_exists($this->getTemplate())) { - $pageCount = $this->pdf->setSourceFile($this->getTemplate()); - - $tplIdx = $this->pdf->importPage($pageCount); - $this->pdf->fpdiUseImportedPage($tplIdx); - } - - $this->pdf->writeHTML($this->html); - - $this->isPrepared = true; - - return $this->pdf; - } - - /** - * @param string $mode - */ - public function generate($mode = self::OUTPUT_MODE_DOWNLOAD): void - { - if (null === $this->pdf || !$this->isPrepared()) { - $this->prepare(); - } - - $outputMode = ''; - $filename = $this > $this->getFileName(); - - switch ($mode) { - case static::OUTPUT_MODE_DOWNLOAD: - $outputMode = 'D'; - - break; - - case static::OUTPUT_MODE_FILE: - if ($folder = $this->getFolder()) { - $filename = rtrim($folder, '/').'/'.$this->getFileName(); - } - - $outputMode = 'F'; - - break; - - case static::OUTPUT_MODE_INLINE: - $outputMode = 'I'; - - break; - } - - $this->pdf->Output($filename, $outputMode); - } - - /** - * Get current pdf object. - * - * @param bool $init Set true if you want to create a new pdf regardless there is always an existing pdf - * - * @return - */ - public function getPdf(bool $init = false): Fpdi - { - if (null === $this->pdf || true === $init) { - $this->pdf = new Fpdi( - $this->config['orientation'], - $this->config['unit'], - $this->config['format'], - true, - $this->config['encoding'] - ); - - $this->pdf->setCellMargins( - $this->config['margins']['left'], - $this->config['margins']['top'], - $this->config['margins']['right'], - $this->config['margins']['bottom'] - ); - - // avoid having black borders in header and footer (see https://stackoverflow.com/a/17172044/1463757) - $this->pdf->SetPrintHeader(false); - $this->pdf->SetPrintFooter(false); - - $this->pdf->AddPage(); - } - - return $this->pdf; - } - - /** - * @param $family - * @param $weight - * @param $filename string The absolute filename including path - */ - public function addFont($family, $weight, $filename) - { - $projectDir = System::getContainer()->get('huh.utils.container')->getProjectDir(); - $tcpdfDir = $projectDir.'/vendor/tecnickcom/tcpdf'; - - // add font to tcpdf - $fontParts = pathinfo($filename); - - $fontname = strtolower($fontParts['filename']); - $fontname = preg_replace('/[^a-z0-9_]/', '', $fontname); - - $definitionFile = $tcpdfDir.'/fonts/'.$fontname.'.php'; - $definitionFileFallback = $tcpdfDir.'/fonts/'.str_replace('regular', '', $fontname).'.php'; - - if (!file_exists($definitionFile)) { - \TCPDF_FONTS::addTTFfont($filename); - } - - if (!file_exists($definitionFile)) { - if (file_exists($definitionFileFallback)) { - $definitionFile = $definitionFileFallback; - } else { - throw new \Exception('The font "'.$filename.'" couldn\'t be added to TCPDF\'s font dir.'); - } - } - - // add font to pdf document - $pdf = $this->getPdf(); - - $pdf->AddFont($family, $weight, $definitionFile); - } - - /** - * Check if prepare was already triggered. - */ - public function isPrepared(): bool - { - return $this->isPrepared; - } -} diff --git a/src/Pdf/PdfPreview.php b/src/Pdf/PdfPreview.php deleted file mode 100644 index 97e75b34..00000000 --- a/src/Pdf/PdfPreview.php +++ /dev/null @@ -1,208 +0,0 @@ -projectDir = $projectDir; - $this->containerUtil = $containerUtil; - $this->fileStorageUtil = $fileStorageUtil; - $this->utilsConfig = $utilsConfig; - } - - /** - * @param string $pdfPath The path to the pdf file - * @param array $options Additional rendering options. See generatePdfPreview - * - * @throws \Exception - * - * @return string - */ - public function getCachedPdfPreview(string $pdfPath, array $options = [], string $fileExtension = 'jpg') - { - $storage = $this->fileStorageUtil->createFileStorage($this->utilsConfig['pdfPreviewFolder'], $fileExtension); - $imagePath = $storage->get($pdfPath, null); - - if (!$imagePath) { - $pdfCache = $this; - $imagePath = $storage->set($pdfPath, function (FileStorageCallback $fileStorageCallback) use ($pdfCache, $options) { - return $pdfCache->generatePdfPreview( - $fileStorageCallback->getIdentifier(), - $fileStorageCallback->getRelativeFilePath(), - $options - ); - }); - } - - return $imagePath ? $imagePath : null; - } - - /** - * Generate a image preview of the given pdf. - * - * Possible PdfTranscoder: spatie (spatie/pdf-to-image), alchemy (alchemy/ghostscript) - * - * Possible file extensions: jpg, jpeg, png - * - * Additional options: - * - string pdfTranscoder The pdf transcoder to use (default: spatie) - * - int page The page to render (default: 1) - * - int compressionQuality Pdf compression quality (default: null) (spatie only) - * - int resolution Raster resolution (default: 144)(spatie only) - * - bool absolutePdfPath Set true if pdf path is absolute (default: false) - * - bool absoluteImagePath Set true if image path is absolute (default: false) - * - * @param string $pdfPath the relative path to the pdf file - * @param string $imagePath the relative path where the image file should be saved (including file name and extension) - * @param array $options Additional rendering options - * - * @throws \Exception - * - * @return bool - */ - public function generatePdfPreview(string $pdfPath, string $imagePath, array $options = []) - { - if (!isset($options['absolutePdfPath']) || true !== $options['absolutePdfPath']) { - $pdfPath = $this->projectDir.'/'.$pdfPath; - } - - if (!isset($options['absoluteImagePath']) || true !== $options['absoluteImagePath']) { - $imagePath = $this->projectDir.'/'.$imagePath; - } - $pdfTranscoder = isset($options['pdfTranscoder']) ? $options['pdfTranscoder'] : ''; - - $previewFolder = pathinfo($imagePath, PATHINFO_DIRNAME); - - if (!is_dir($previewFolder)) { - mkdir($previewFolder); - } - - switch ($pdfTranscoder) { - case 'alchemy': - return $this->alchemyPdf($pdfPath, $imagePath, $options); - - case 'spatie': - default: - return $this->spatiePdf($pdfPath, $imagePath, $options); - } - } - - /** - * @throws \Exception - * - * @return bool - */ - protected function spatiePdf(string $pdfPath, string $imagePath, array $options = []) - { - try { - $this->containerUtil->isBundleActive('spatie/pdf-to-image'); - } catch (\Exception $e) { - throw new \Exception('Package spatie/pdf-to-image is not installed. Please install or use another pdf ttranscoder.'); - } - $imageExtension = pathinfo($imagePath, PATHINFO_EXTENSION); - - try { - $pdf = new Pdf($pdfPath); - - if (isset($option['page']) && $options['page'] > 0) { - $pdf->setPage($options['page']); - } - - if (isset($option['compressionQuality']) && $options['compressionQuality'] > 0) { - $pdf->setCompressionQuality($options['compressionQuality']); - } - - if (isset($option['resolution']) && $options['resolution'] > 0) { - $pdf->setResolution($options['resolution']); - } - - if (!empty($imageExtension)) { - $pdf->setOutputFormat($imageExtension); - } - $pdf->saveImage($imagePath); - } catch (\Exception $e) { - return false; - } - - return true; - } - - /** - * @throws \Exception - * - * @return bool - */ - protected function alchemyPdf(string $pdfPath, string $imagePath, array $options = []) - { - try { - $this->containerUtil->isBundleActive('alchemy/ghostscript'); - } catch (\Exception $e) { - throw new \Exception('Package alchemy/ghostscript is not installed. Please install or use another pdf transcoder.'); - } - $imageExtension = pathinfo($imagePath, PATHINFO_EXTENSION); - $allowedExtensions = ['jpg', 'jpeg', 'png']; - - if (!\in_array($imageExtension, $allowedExtensions)) { - throw new InvalidTypeException('Only one of the following file types is allowed: '.implode(', ', $allowedExtensions)); - } - - if ('jpg' === $imageExtension) { - $imageExtension = 'jpeg'; - } - - $command = [ - '-sDEVICE='.$imageExtension, - '-dNOPAUSE', - '-dBATCH', - '-dSAFER', - '-sOutputFile='.$imagePath, - ]; - - if (isset($option['page']) && \is_int($options['page']) && $options['page'] > 0) { - $command[] = sprintf('-dFirstPage=%d', $options['page']); - $command[] = sprintf('-dLastPage=%d', $options['page']); - } - - try { - $command[] = $pdfPath; - $transcoder = Transcoder::create(); - $transcoder->command($command); - } catch (\Exception $e) { - return false; - } - - return true; - } -} diff --git a/src/Pdf/PdfWriter.php b/src/Pdf/PdfWriter.php deleted file mode 100644 index 36914ba6..00000000 --- a/src/Pdf/PdfWriter.php +++ /dev/null @@ -1,193 +0,0 @@ -config = [ - 'mode' => \Config::get('characterSet'), - 'format' => 'A4', - 'orientation' => 'P', - ]; - } - - /** - * Get the master template path. - * - * @return string - */ - public function getTemplate(): ?string - { - return $this->template; - } - - /** - * Set the master template path. - * - * @return PdfWriter Current pdf writer instance - */ - public function setTemplate(string $template): self - { - $projectDir = System::getContainer()->get('huh.utils.container')->getProjectDir(); - - $this->template = $projectDir.\DIRECTORY_SEPARATOR.ltrim(preg_replace('#^'.$projectDir.'#', '', $template), \DIRECTORY_SEPARATOR); - - return $this; - } - - /** - * Prepare the current mpdf object. - */ - public function prepare(): Mpdf - { - $this->pdf = $this->getPdf(); - - // set the custom pdf template - if (null !== $this->getTemplate() && file_exists($this->getTemplate())) { - $this->pdf->SetImportUse(); - - $pageCount = $this->pdf->SetSourceFile($this->getTemplate()); - $tplIdx = $this->pdf->ImportPage($pageCount); - $this->pdf->UseTemplate($tplIdx); - $this->pdf->UseTemplate($tplIdx); - } - - $this->pdf->WriteHTML($this->html); - - $this->isPrepared = true; - - return $this->pdf; - } - - /** - * @param string $mode - */ - public function generate($mode = self::OUTPUT_MODE_DOWNLOAD): void - { - if (null === $this->pdf || !$this->isPrepared()) { - $this->prepare(); - } - - $outputMode = ''; - $filename = $this->getFileName(); - - switch ($mode) { - case static::OUTPUT_MODE_DOWNLOAD: - $outputMode = 'D'; - - break; - - case static::OUTPUT_MODE_FILE: - if ($folder = $this->getFolder()) { - $projectDir = System::getContainer()->get('huh.utils.container')->getProjectDir(); - $filename = $projectDir.'/'.rtrim($folder, '/').'/'.$filename; - } - - $outputMode = 'F'; - - break; - - case static::OUTPUT_MODE_INLINE: - $outputMode = 'I'; - - break; - } - - $this->pdf->output($filename, $outputMode); - } - - /** - * Add font directories to the config. - * - * @param array $paths Directory pathseader - * - * @return PdfWriter Current pdf writer instance - */ - public function addFontDirectories(array $paths): self - { - $projectDir = System::getContainer()->get('huh.utils.container')->getProjectDir(); - - $defaultConfig = (new ConfigVariables())->getDefaults(); - $fontDirs = $defaultConfig['fontDir']; - - foreach ($paths as $path) { - $fontDir = $projectDir.\DIRECTORY_SEPARATOR.ltrim($path, \DIRECTORY_SEPARATOR); - - if (!file_exists($fontDir) || !file_exists($fontDir.\DIRECTORY_SEPARATOR.'mpdf-config.php')) { - continue; - } - - $configPath = $fontDir.\DIRECTORY_SEPARATOR.'mpdf-config.php'; - $fontConfig = require_once $configPath; - - if (!\is_array($fontConfig)) { - continue; - } - - if (!isset($fontConfig['fontDir'])) { - $fontConfig['fontDir'] = array_merge($fontDirs, [ - $fontDir, - ]); - } - - $this->config = array_merge($this->config, $fontConfig); - } - - return $this; - } - - /** - * Get current pdf object. - * - * @param bool $init Set true if you want to create a new pdf regardless there is always an existing pdf - */ - public function getPdf(bool $init = false): Mpdf - { - $this->pdf = (null === $this->pdf || true === $init) ? new Mpdf($this->config) : $this->pdf; - - return $this->pdf; - } -} diff --git a/src/PdfCreator/AbstractPdfCreator.php b/src/PdfCreator/AbstractPdfCreator.php deleted file mode 100644 index d963f881..00000000 --- a/src/PdfCreator/AbstractPdfCreator.php +++ /dev/null @@ -1,296 +0,0 @@ -htmlContent; - } - - /** - * @param mixed $htmlContent - */ - public function setHtmlContent($htmlContent): self - { - $this->htmlContent = $htmlContent; - - return $this; - } - - abstract public function render(): void; - - /** - * @return string - */ - public function getFilename(): ?string - { - return $this->filename; - } - - public function setFilename(string $filename): self - { - $this->filename = $filename; - - return $this; - } - - /** - * @return string - */ - public function getOutputMode(): ?string - { - return $this->outputMode; - } - - public function setOutputMode(string $outputMode): self - { - if (!\in_array($outputMode, $this->getSupportedOutputModes())) { - trigger_error('Invalid output mode for '.static::class.'. Will fallback to default.'); - } else { - $this->outputMode = $outputMode; - } - - return $this; - } - - abstract public function getSupportedOutputModes(): array; - - public function getFolder(): ?string - { - return $this->folder; - } - - /** - * Absolute folder where to store pdf. - */ - public function setFolder(string $folder): self - { - $this->folder = $folder; - - return $this; - } - - public function getMediaType(): ?string - { - return $this->mediaType; - } - - /** - * @param string|null $mediaType - */ - public function setMediaType(string $mediaType): self - { - $this->mediaType = $mediaType; - - return $this; - } - - public function getFonts(): ?array - { - return $this->fonts; - } - - public function setFonts(array $fonts): self - { - $this->fonts = $fonts; - - return $this; - } - - /** - * @param string $filepath Absolute filepath to the font file - * @param string $family Font family name - * @param string $style Font style (regular, italic, ...), see AbstractPdfCreator::FONT_STYLE constants - * @param string $weight Font weight - * - * @return $this - */ - public function addFont(string $filepath, string $family, string $style, string $weight): self - { - $this->fonts[] = [ - 'filepath' => $filepath, - 'family' => $family, - 'style' => $style, - 'weight' => $weight, - ]; - } - - public function getMargins(): ?array - { - return $this->margins; - } - - /** - * Set document margins. - * - * @param array|null $margins - */ - public function setMargins(?int $top, ?int $right = null, ?int $bottom = null, ?int $left = null): self - { - $this->margins = [ - 'top' => $top, - 'right' => $right, - 'bottom' => $bottom, - 'left' => $left, - ]; - - return $this; - } - - public function getFormat(): ?string - { - return $this->format; - } - - /** - * Set the document format. - * - * @param string|array $format A format type like A4, A5, Letter, Legal,... or an array of integers (width and height in mm). - * - * @return $this - */ - public function setFormat($format): self - { - $this->format = $format; - - return $this; - } - - public function getOrientation(): ?string - { - return $this->orientation; - } - - /** - * Set orientation. Use AbstractPdfCreator::ORIENTATION_LANDSCAPE or AbstractPdfCreator::ORIENTATION_PORTRAIT. - */ - public function setOrientation(string $orientation): self - { - $this->orientation = $orientation; - - return $this; - } - - public function getTemplateFilePath(): ?string - { - return $this->templateFilePath; - } - - /** - * Set the absolute path to a pdf template file. - * - * @param string|null $templateFilePath - */ - public function setTemplateFilePath(string $templateFilePath): self - { - $this->templateFilePath = $templateFilePath; - - return $this; - } - - public function getBeforeCreateInstanceCallback(): ?callable - { - return $this->beforeCreateInstanceCallback; - } - - /** - * Add an callback to modify constructor parameters for pdf library. - * Callback gets an BeforeCreateLibraryInstanceCallback object as parameter and should return an BeforeCreateLibraryInstanceCallback object. - */ - public function setBeforeCreateInstanceCallback(?callable $beforeCreateInstanceCallback): self - { - $this->beforeCreateInstanceCallback = $beforeCreateInstanceCallback; - - return $this; - } - - public function getBeforeOutputPdfCallback(): ?callable - { - return $this->beforeOutputPdfCallback; - } - - /** - * Add an callback to modify the configuration or parameters before outputting the pdf file. - * Callback gets an BeforeOutputPdfCallback object as parameter and should return an BeforeOutputPdfCallback object. - */ - public function setBeforeOutputPdfCallback(?callable $beforeOutputPdfCallback): self - { - $this->beforeOutputPdfCallback = $beforeOutputPdfCallback; - - return $this; - } -} diff --git a/src/PdfCreator/BeforeCreateLibraryInstanceCallback.php b/src/PdfCreator/BeforeCreateLibraryInstanceCallback.php deleted file mode 100644 index 05cad531..00000000 --- a/src/PdfCreator/BeforeCreateLibraryInstanceCallback.php +++ /dev/null @@ -1,40 +0,0 @@ -constructorParameters = $constructorParameters; - } - - public function getConstructorParameters(): array - { - return $this->constructorParameters; - } - - public function setConstructorParameters(array $constructorParameters): void - { - $this->constructorParameters = $constructorParameters; - } -} diff --git a/src/PdfCreator/BeforeOutputPdfCallback.php b/src/PdfCreator/BeforeOutputPdfCallback.php deleted file mode 100644 index fe9827b6..00000000 --- a/src/PdfCreator/BeforeOutputPdfCallback.php +++ /dev/null @@ -1,55 +0,0 @@ -libraryInstance = $libraryInstance; - $this->outputParameters = $outputParameters; - } - - /** - * @return mixed - */ - public function getLibraryInstance() - { - return $this->libraryInstance; - } - - /** - * @param mixed $libraryInstance - */ - public function setLibraryInstance($libraryInstance): void - { - $this->libraryInstance = $libraryInstance; - } - - public function getOutputParameters(): array - { - return $this->outputParameters; - } - - public function setOutputParameters(array $outputParameters): void - { - $this->outputParameters = $outputParameters; - } -} diff --git a/src/PdfCreator/Concrete/MpdfCreator.php b/src/PdfCreator/Concrete/MpdfCreator.php deleted file mode 100644 index 17587fd8..00000000 --- a/src/PdfCreator/Concrete/MpdfCreator.php +++ /dev/null @@ -1,302 +0,0 @@ -= 0) { - throw new \Exception('Only mPDF library versions 7.x and 8.x are supported.'); - } - } - - public function render(): void - { - $config = []; - - if ($this->getMediaType()) { - $config['CSSselectMedia'] = $this->getMediaType(); - } - - $config = $this->applyDocumentFormatConfiguration($config); - - $config = $this->applyFonts($config); - - if ($this->getBeforeCreateInstanceCallback()) { - /** @var BeforeCreateLibraryInstanceCallback $result */ - $result = \call_user_func($this->getBeforeCreateInstanceCallback(), new BeforeCreateLibraryInstanceCallback(['config' => $config])); - - if ($result && isset($result->getConstructorParameters()['config'])) { - $config = $result->getConstructorParameters()['config']; - } - } - - $pdf = new Mpdf($config); - - $this->applyTemplate($pdf); - - if ($this->getHtmlContent()) { - $pdf->WriteHTML($this->getHtmlContent()); - } - - $outputMode = ''; - $filename = $this->getFilename() ?: ''; - - switch ($this->getOutputMode()) { - case static::OUTPUT_MODE_STRING: - $outputMode = Destination::STRING_RETURN; - - break; - - case static::OUTPUT_MODE_FILE: - if ($folder = $this->getFolder() && $this->getFilename()) { - $filename = rtrim($folder, '/').'/'.$filename; - } - - $outputMode = Destination::FILE; - - break; - - case static::OUTPUT_MODE_DOWNLOAD: - $outputMode = Destination::DOWNLOAD; - - break; - - case static::OUTPUT_MODE_INLINE: - $outputMode = Destination::INLINE; - - break; - } - - if ($this->getBeforeOutputPdfCallback()) { - /** @var BeforeOutputPdfCallback $result */ - $result = \call_user_func($this->getBeforeCreateInstanceCallback(), new BeforeOutputPdfCallback($pdf, [ - 'name' => $filename, - 'dest' => $outputMode, - ])); - - if ($result) { - if (isset($result->getOutputParameters()['name'])) { - $filename = $result->getOutputParameters()['name']; - } - - if (isset($result->getOutputParameters()['dest'])) { - $filename = $result->getOutputParameters()['dest']; - } - } - } - - $pdf->Output($filename, $outputMode); - } - - public function getSupportedOutputModes(): array - { - return static::OUTPUT_MODES; - } - - public static function getType(): string - { - return 'mpdf'; - } - - /** - * Add font directories to the config. Directory must contain mpdf-config.php. - * Fallback method for legacy implementation, will be removed in a future version. - * - * @param array $paths Absolute path to font dir - * - * @return self Current pdf creator instance - * - * @deprecated Use addFont instead - */ - public function addFontDirectories(array $paths): self - { - $defaultConfig = (new ConfigVariables())->getDefaults(); - $fontDirs = $defaultConfig['fontDir']; - - foreach ($paths as $fontDir) { - if (!file_exists($fontDir) || !file_exists($fontDir.\DIRECTORY_SEPARATOR.'mpdf-config.php')) { - continue; - } - - $configPath = $fontDir.\DIRECTORY_SEPARATOR.'mpdf-config.php'; - $fontConfig = require_once $configPath; - - if (!\is_array($fontConfig)) { - continue; - } - - if (!isset($fontConfig['fontDir'])) { - $fontConfig['fontDir'] = array_merge($fontDirs, [ - $fontDir, - ]); - } - - $this->legacyFontDirectoryConfig = array_merge($this->legacyFontDirectoryConfig ?: [], $fontConfig); - } - - return $this; - } - - protected function applyFonts(array $config): array - { - if ($this->legacyFontDirectoryConfig) { - $fontDirs = $this->legacyFontDirectoryConfig['fontDir']; - } else { - $defaultConfig = (new ConfigVariables())->getDefaults(); - $fontDirs = $defaultConfig['fontDir']; - } - - if ($this->legacyFontDirectoryConfig) { - $fontData = $this->legacyFontDirectoryConfig['fontdata']; - } else { - $defaultFontConfig = (new FontVariables())->getDefaults(); - $fontData = $defaultFontConfig['fontdata']; - } - - if ($this->getFonts()) { - $dirs = []; - $families = []; - - foreach ($this->getFonts() as $font) { - $file = pathinfo($font['filepath']); - $dirs[] = $file['dirname']; - $fontStyle = 'R'; - - switch ($font['style']) { - case static::FONT_STYLE_REGUALAR: - $fontStyle = 'R'; - - break; - - case static::FONT_STYLE_BOLD: - $fontStyle = 'B'; - - break; - - case static::FONT_STYLE_ITALIC: - $fontStyle = 'I'; - - break; - - case static::FONT_STYLE_BOLDITALIC: - $fontStyle = 'BI'; - - break; - } - $families[$font['family']][$fontStyle] = $file['basename']; - } - - $fontDirs = array_merge($fontDirs, array_unique($dirs)); - $fontData = array_merge($fontData, $families); - } - - $config['fontDir'] = $fontDirs; - $config['fontdata'] = $fontData; - - return $config; - } - - protected function applyDocumentFormatConfiguration(array $config): array - { - if ($this->getMargins()) { - if ($this->getMargins()['top']) { - $config['margin_top'] = $this->getMargins()['top']; - } - - if ($this->getMargins()['right']) { - $config['margin_right'] = $this->getMargins()['right']; - } - - if ($this->getMargins()['bottom']) { - $config['margin_bottom'] = $this->getMargins()['bottom']; - } - - if ($this->getMargins()['left']) { - $config['margin_left'] = $this->getMargins()['left']; - } - } - - if ($this->getOrientation()) { - switch ($this->getOrientation()) { - case static::ORIENTATION_PORTRAIT: - $config['orientation'] = 'P'; - - break; - - case static::ORIENTATION_LANDSCAPE: - $config['orientation'] = 'L'; - - break; - } - } - - if ($this->getFormat()) { - if (\is_string($this->getFormat()) && static::ORIENTATION_LANDSCAPE === $this->getOrientation()) { - $config['format'] = $this->getFormat().'-L'; - } else { - $config['format'] = $this->getFormat(); - } - } - - return $config; - } - - /** - * @throws \setasign\Fpdi\PdfParser\CrossReference\CrossReferenceException - * @throws \setasign\Fpdi\PdfParser\PdfParserException - * @throws \setasign\Fpdi\PdfParser\Type\PdfTypeException - */ - protected function applyTemplate(Mpdf $pdf): void - { - if ($this->getTemplateFilePath()) { - if (file_exists($this->getTemplateFilePath())) { - if (version_compare(Mpdf::VERSION, '8', '>')) { - $pageCount = $pdf->setSourceFile($this->getTemplateFilePath()); - $tplIdx = $pdf->importPage($pageCount); - $pdf->useTemplate($tplIdx); - } else { - // mpdf 7.x support - $pdf->SetImportUse(); - $pageCount = $pdf->SetSourceFile($this->getTemplateFilePath()); - $tplIdx = $pdf->ImportPage($pageCount); - $pdf->UseTemplate($tplIdx); - } - } else { - trigger_error('Pdf template does not exist.', E_USER_NOTICE); - } - } - } -} diff --git a/src/PdfCreator/PdfCreatorFactory.php b/src/PdfCreator/PdfCreatorFactory.php deleted file mode 100644 index 8ddb8a5d..00000000 --- a/src/PdfCreator/PdfCreatorFactory.php +++ /dev/null @@ -1,50 +0,0 @@ - MpdfCreator::class, - ]; - } -} diff --git a/src/Request/CurlRequest.php b/src/Request/CurlRequest.php deleted file mode 100644 index 440e0d16..00000000 --- a/src/Request/CurlRequest.php +++ /dev/null @@ -1,51 +0,0 @@ -handle = curl_init($url); - - return $this; - } - - public function setOption($name, $value): HttpRequestInterface - { - curl_setopt($this->handle, $name, $value); - - return $this; - } - - public function execute() - { - return curl_exec($this->handle); - } - - public function getInfo($name) - { - return curl_getinfo($this->handle, $name); - } - - public function close() - { - curl_close($this->handle); - } -} diff --git a/src/Request/CurlRequestUtil.php b/src/Request/CurlRequestUtil.php deleted file mode 100644 index 7c11502e..00000000 --- a/src/Request/CurlRequestUtil.php +++ /dev/null @@ -1,285 +0,0 @@ - 'Continue', - 101 => 'Switching Protocols', - 200 => 'OK', - 201 => 'Created', - 202 => 'Accepted', - 203 => 'Non-Authoritative Information', - 204 => 'No Content', - 205 => 'Reset Content', - 206 => 'Partial Content', - 300 => 'Multiple Choices', - 301 => 'Moved Permanently', - 302 => 'Found', - 303 => 'See Other', - 304 => 'Not Modified', - 305 => 'Use Proxy', - 306 => '(Unused)', - 307 => 'Temporary Redirect', - 400 => 'Bad Request', - 401 => 'Unauthorized', - 402 => 'Payment Required', - 403 => 'Forbidden', - 404 => 'Not Found', - 405 => 'Method Not Allowed', - 406 => 'Not Acceptable', - 407 => 'Proxy Authentication Required', - 408 => 'Request Timeout', - 409 => 'Conflict', - 410 => 'Gone', - 411 => 'Length Required', - 412 => 'Precondition Failed', - 413 => 'Request Entity Too Large', - 414 => 'Request-URI Too Long', - 415 => 'Unsupported Media Type', - 416 => 'Requested Range Not Satisfiable', - 417 => 'Expectation Failed', - 500 => 'Internal Server Error', - 501 => 'Not Implemented', - 502 => 'Bad Gateway', - 503 => 'Service Unavailable', - 504 => 'Gateway Timeout', - 505 => 'HTTP Version Not Supported', - ]; - - /** - * @var ContaoFrameworkInterface - */ - protected $framework; - /** - * @var ContainerInterface - */ - protected $container; - - /** - * @var HttpRequestInterface - */ - protected $handle = null; - - public function __construct(ContaoFrameworkInterface $framework, ContainerInterface $container) - { - $this->framework = $framework; - $this->container = $container; - } - - /** - * Executes a curl request while taking. - * - * @param $url - * @param bool $returnResponseHeaders - * - * @return array|mixed - */ - public function request(string $url, array $requestHeaders = [], $returnResponseHeaders = false) - { - $handle = $this->createCurlHandle($url); - - if ($proxy = Config::get('hpProxy')) { - $handle->setOption(CURLOPT_PROXY, $proxy); - } - - if (!empty($requestHeaders)) { - $handle->setOption(CURLOPT_HTTPHEADER, $this->prepareHeaders($requestHeaders)); - } - - if ($returnResponseHeaders) { - $handle->setOption(CURLOPT_HEADER, true); - } - - $response = $handle->execute(); - $statusCode = $handle->getInfo(CURLINFO_HTTP_CODE); - $handle->close(); - - if ($response && $returnResponseHeaders) { - return $this->splitResponseHeaderAndBody($response, $statusCode); - } - - return $response; - } - - /** - * Create a curl post request. - * - * @return array|mixed - */ - public function postRequest(string $url, array $requestHeaders = [], array $postFields = [], bool $returnResponseHeaders = false) - { - $handle = $this->createCurlHandle($url); - - if (Config::get('hpProxy')) { - $handle->setOption(CURLOPT_PROXY, Config::get('hpProxy')); - } - - if ($returnResponseHeaders) { - $handle->setOption(CURLOPT_HEADER, true); - } - - if (!empty($requestHeaders)) { - $handle->setOption(CURLOPT_HTTPHEADER, $this->prepareHeaders($requestHeaders)); - } - - if (!empty($postFields)) { - $handle->setOption(CURLOPT_POST, true); - $handle->setOption(CURLOPT_POSTFIELDS, http_build_query($postFields)); - } - - $response = $handle->execute(); - - $statusCode = $handle->getInfo(CURLINFO_HTTP_CODE); - $handle->close(); - - if ($response && $returnResponseHeaders) { - return $this->splitResponseHeaderAndBody($response, $statusCode); - } - - return $response; - } - - /** - * Recursivly send get request and terminates if termination condition is given or max request count is reached. - * - * @param callable $callback Termination condition callback. Return true to terminate. - * - * @return array|mixed|null - */ - public function recursiveGetRequest(int $maxRecursionCount, callable $callback, string $url, array $requestHeaders = [], bool $returnResponseHeaders = false) - { - $i = 0; - $terminate = false; - $result = null; - - while ($i++ < $maxRecursionCount && !$terminate) { - $result = $this->request($url, $requestHeaders, $returnResponseHeaders); - - $terminate = $callback($result, $url, $requestHeaders, $returnResponseHeaders, $maxRecursionCount, $i); - } - - return $result; - } - - /** - * Recursivly send post request and terminates if termination condition is given or max request count is reached. - * - * @return array|mixed|null - */ - public function recursivePostRequest(int $maxRecursionCount, callable $callback, string $url, array $requestHeaders = [], array $post = [], bool $returnResponseHeaders = false) - { - $i = 0; - $terminate = false; - $result = null; - - while ($i++ < $maxRecursionCount && !$terminate) { - $result = $this->postRequest($url, $requestHeaders, $post, $returnResponseHeaders); - - $terminate = $callback($result, $url, $requestHeaders, $post, $returnResponseHeaders, $maxRecursionCount, $i); - } - - return $result; - } - - /** - * @return array - */ - public function splitResponseHeaderAndBody(string $response, int $statusCode) - { - $headers = []; - - $split = strpos($response, "\r\n\r\n"); - $header = substr($response, 0, $split); - $body = str_replace($header."\r\n\r\n", '', $response); - - foreach (explode("\r\n", $header) as $i => $strLine) { - if (0 === $i) { - $headers['http_code'] = $statusCode; - } else { - list($strKey, $varValue) = explode(': ', $strLine); - $headers[$strKey] = $varValue; - } - } - - return [$headers, trim($body)]; - } - - /** - * Creates a linebreak separated list of the headers in $arrHeaders -> see request() and postRequest(). - * - * @return string - */ - public function prepareHeaderArrayForPrint(array $headers) - { - $result = ''; - $i = 0; - - foreach ($headers as $strKey => $strValue) { - $result .= "$strKey: $strValue"; - - if ($i++ != \count($headers) - 1) { - $result .= PHP_EOL; - } - } - - return $result; - } - - /** - * @return HttpRequestInterface|null - */ - public function getHandle() - { - return $this->handle; - } - - public function setHandle(HttpRequestInterface $handle) - { - $this->handle = $handle; - } - - /** - * Create the curl handle. - * - * @param $url - * - * @return CurlRequest - */ - public function createCurlHandle($url) - { - $handle = $this->handle ?: new CurlRequest(); - $handle->init($url); - $handle->setOption(CURLOPT_RETURNTRANSFER, true); - $handle->setOption(CURLOPT_TIMEOUT, 10); - - return $handle; - } - - /** - * Prepare headers for curl handle. - * - * @return array - */ - protected function prepareHeaders(array $headers) - { - $preparedHeaders = []; - - foreach ($headers as $strName => $varValue) { - $preparedHeaders[] = $strName.': '.$varValue; - } - - return $preparedHeaders; - } -} diff --git a/src/Request/HttpRequestInterface.php b/src/Request/HttpRequestInterface.php deleted file mode 100644 index 188174c9..00000000 --- a/src/Request/HttpRequestInterface.php +++ /dev/null @@ -1,22 +0,0 @@ - $childValue) { - $value[$i] = $this->clean($childValue, $decodeEntities, $encodeInsertTags, $tidy, $strictMode); - } - - return $value; - } - - // do not handle binary uuid - if (Validator::isUuid($value)) { - return $value; - } - - $value = $this->xssClean($value, $strictMode); - - if ($tidy) { - $value = $this->tidy($value); - } else { - // decodeEntities for tidy is more complex, because non allowed tags should be displayed as readable text, not as html entity - $value = Input::decodeEntities($value); - } - - // do not encodeSpecialChars when tidy did run, otherwise non allowed tags will be encoded twice - if (!$decodeEntities && !$tidy) { - $value = Input::encodeSpecialChars($value); - } - - if ($encodeInsertTags) { - $value = Input::encodeInsertTags($value); - } - - return $value; - } - - /** - * XSS clean, decodeEntities, tidy/strip tags, encode special characters and encode inserttags and return save, cleaned value(s). - * - * @param mixed $value The input value - * @param bool $decodeEntities If true, all entities will be decoded - * @param bool $encodeInsertTags If true, encode the opening and closing delimiters of insert tags - * @param string $allowedTags List of allowed html tags - * @param bool $tidy If true, varValue is tidied up - * @param bool $strictMode If true, the xss cleaner removes also JavaScript event handlers - * - * @return mixed The cleaned value - */ - public function cleanHtml($value, bool $decodeEntities = false, bool $encodeInsertTags = true, string $allowedTags = '', bool $tidy = true, bool $strictMode = true) - { - // do not clean, otherwise empty string will be returned, not null - if (null === $value) { - return $value; - } - - if (\is_array($value)) { - foreach ($value as $i => $childValue) { - $value[$i] = $this->cleanHtml($childValue, $decodeEntities, $encodeInsertTags, $allowedTags, $tidy, $strictMode); - } - - return $value; - } - - // do not handle binary uuid - if (Validator::isUuid($value)) { - return $value; - } - - $value = $this->xssClean($value, $strictMode); - - if ($tidy) { - $value = $this->tidy($value, $allowedTags, $decodeEntities); - } else { - // decodeEntities for tidy is more complex, because non allowed tags should be displayed as readable text, not as html entity - $value = Input::decodeEntities($value); - } - - // do not encodeSpecialChars when tidy did run, otherwise non allowed tags will be encoded twice - if (!$decodeEntities && !$tidy) { - $value = Input::encodeSpecialChars($value); - } - - if ($encodeInsertTags) { - $value = Input::encodeInsertTags($value); - } - - return $value; - } - - /** - * Clean a value and try to prevent XSS attacks. - * - * @param mixed $varValue A string or array - * @param bool $strictMode If true, the function removes also JavaScript event handlers - * - * @return mixed The cleaned string or array - */ - public function xssClean($varValue, bool $strictMode = false) - { - if (\is_array($varValue)) { - foreach ($varValue as $key => $value) { - $varValue[$key] = $this->xssClean($value, $strictMode); - } - - return $varValue; - } - - // do not xss clean binary uuids - if (Validator::isBinaryUuid($varValue)) { - return $varValue; - } - - // Fix issue StringUtils::decodeEntites() returning empty string when value is 0 in some contao 4.9 versions - if ('0' !== $varValue && 0 !== $varValue) { - $varValue = StringUtil::decodeEntities($varValue); - } - - $varValue = preg_replace('/(&#[A-Za-z0-9]+);?/i', '$1;', $varValue); - - // fix: "> or '>">'> - $varValue = preg_replace('/(?["|\']>)+(<[^\/^>]+>.*)/', '$1', $varValue); - - $varValue = Input::xssClean($varValue, $strictMode); - - return $varValue; - } - - /** - * Tidy an value. - * - * @param string $varValue Input value - * @param string $allowedTags Allowed tags as string `

` - * @param bool $decodeEntities If true, all entities will be decoded - * - * @return string The tidied string - */ - public function tidy($varValue, string $allowedTags = '', bool $decodeEntities = false): string - { - if (!$varValue) { - return $varValue; - } - - // do not tidy non-xss critical characters for performance - if (!preg_match('#"|\'|<|>|\(|\)#', StringUtil::decodeEntities($varValue))) { - return $varValue; - } - - // remove illegal white spaces after closing tag slash
- $varValue = preg_replace('@\/(\s+)>@', '/>', $varValue); - - // Encode opening tag arrow brackets - $varValue = preg_replace_callback('/<(?(?=!--)!--[\s\S]*--|(?(?=\?)\?[\s\S]*\?|(?(?=\/)\/[^.\-\d][^\/\]\'"[!#$%&()*+,;<=>?@^`{|}~ ]*|[^.\-\d][^\/\]\'"[!#$%&()*+,;<=>?@^`{|}~ ]*(?:\s[^.\-\d][^\/\]\'"[!#$%&()*+,;<=>?@^`{|}~ ]*(?:=(?:"[^"]*"|\'[^\']*\'|[^\'"<\s]*))?)*)\s?\/?))>/', function ($matches) { - return substr_replace($matches[0], '<', 0, 1); - }, $varValue); - - // Encode less than signs that are no tags with [lt] - $varValue = str_replace('<', '[lt]', $varValue); - - // After we saved less than signs with [lt] revert < sign to < - $varValue = StringUtil::decodeEntities($varValue); - - // Restore HTML comments - $varValue = str_replace(['<!--', '<!['], [' -

-
    - pages as $page): ?> - -
  • - - - -
  • - -
  • - - - -
  • - -
  • - - – -
  • - - -
-
- \ No newline at end of file diff --git a/src/Resources/npm-package/.gitignore b/src/Resources/npm-package/.gitignore deleted file mode 100644 index 833bbcba..00000000 --- a/src/Resources/npm-package/.gitignore +++ /dev/null @@ -1,15 +0,0 @@ -.build/ -.buildpath -.idea/ -nbproject/ -.project -.settings/ - -# nodejs -node_modules - -# yarn -yarn-error.log - -# others -debug.txt diff --git a/src/Resources/npm-package/README.md b/src/Resources/npm-package/README.md deleted file mode 100644 index 1b3b2408..00000000 --- a/src/Resources/npm-package/README.md +++ /dev/null @@ -1,73 +0,0 @@ -# Contao Utils Bundle Assets - -This package contains the frontend assets of the composer bundle [heimrichhannot/contao-utils-bundle](https://github.com/heimrichhannot/contao-utils-bundle). - -## Install - -`yarn add @hundh/contao-utils-bundle` - -## Usage - -### Ajax Util - -```js -import AjaxUtil from "@hundh/contao-utils-bundle/js/ajax-util"; - -let config = { - headers: {'X-Requested-With': 'XMLHttpRequest'}, - responseType: undefined, // set XMLHttpRequest.responseType - onSuccess: undefined, //on success callback - onError: undefined, // on error callback - beforeSubmit: undefined, //before submit callback - afterSubmit: undefined // after submit callback -}; - -/** -* @var {string} url -* @var {FormData|object} data -* @var {Object} config -*/ -AjaxUtil.get(url, data, config); -AjaxUtil.post(url, data, config); -``` - -### Webpack/Encore - -Usage example: - -``` -import { DomUtil, ArrayUtil } from '@hundh/contao-utils-bundle'; - -DomUtil.scrollTo(myNode, 100); -``` - -Following imports possible: -* ArrayUtil -* DomUtil -* EventUtil -* GeneralUtil -* UrlUtil -* UtilsBundle - -`UtilsBundle` holds all utilities classes: -* array -* dom -* event -* url -* util - -Example usage: `UtilsBundle.util.isTruthy(value)` - -### Legacy libraries - -If you run the package code at least once, the `UtilsBundle` object will be written to `window.utilsBundle;`. - -Usage example: -``` -let UtilsBundle = window.utilsBundle; -``` - - - - - diff --git a/src/Resources/npm-package/js/ajax-util.js b/src/Resources/npm-package/js/ajax-util.js deleted file mode 100644 index 5e103ac0..00000000 --- a/src/Resources/npm-package/js/ajax-util.js +++ /dev/null @@ -1,127 +0,0 @@ -import GeneralUtil from './general-util'; -import UrlUtil from './url-util'; - -class AjaxUtil { - static get(url, data, config) { - config = AjaxUtil.setDefaults(config); - - let request = AjaxUtil.initializeRequest('GET', UrlUtil.addParametersToUri(url, data), config), - submitData = { - config: config, - action: url, - data: data - }; - - AjaxUtil.doAjaxSubmit(request, submitData); - } - - static post(url, data, config) { - config = AjaxUtil.setDefaults(config); - - let request = AjaxUtil.initializeRequest('POST', url, config), - submitData = { - config: config, - action: url, - data: data - }; - - AjaxUtil.doAjaxSubmit(request, submitData); - } - - static jsonPost(url, data, config) { - config = AjaxUtil.setDefaults(config); - - // set correct header - if (typeof config.headers === 'object') { - if (!config.hasOwnProperty('Content-Type')) { - config.headers['Content-Type'] = 'application/json;charset=UTF-8'; - } - } else { - config.headers['Content-Type'] = 'application/json;charset=UTF-8'; - } - - // prepare data - if (typeof data === 'object') { - data = JSON.stringify(data); - } - - let request = AjaxUtil.initializeRequest('POST', url, config), - submitData = { - config: config, - action: url, - data: data - }; - - AjaxUtil.doAjaxSubmit(request, submitData); - } - - static doAjaxSubmit(request, submitData) { - let config = submitData.config; - - request.onload = function() { - if (request.status >= 200 && request.status < 400) { - GeneralUtil.call(config.onSuccess, request); - } else { - GeneralUtil.call(config.onError, request); - } - - GeneralUtil.call(config.afterSubmit, submitData.action, submitData.data, config); - }; - - GeneralUtil.call(config.beforeSubmit, submitData.action, submitData.data, config); - - if ('undefined' === typeof submitData.data) { - request.send(); - } else { - submitData.data = AjaxUtil.prepareDataForSend(submitData.data); - - request.send(submitData.data); - } - } - - static prepareDataForSend(data) { - if (!(data instanceof FormData) && typeof data === 'object') - { - let formData = new FormData(); - - Object.keys(data).forEach(field => { - formData.append(field, data[field]); - }); - - return formData; - } - - return data; - } - - static initializeRequest(method, url, config) { - let request = new XMLHttpRequest(); - - request.open(method, url, true); - request = AjaxUtil.setRequestHeaders(request, config); - - if (config.hasOwnProperty('responseType')) - { - request.responseType = config.responseType; - } - return request; - } - - static setRequestHeaders(request, config) { - if (config.hasOwnProperty('headers')) { - Object.keys(config.headers).forEach(key => { - request.setRequestHeader(key, config.headers[key]); - }); - } - return request; - } - - static setDefaults(config) { - if (!config.hasOwnProperty('headers')) { - config.headers = {'X-Requested-With': 'XMLHttpRequest'}; - } - return config; - } -} - -export default AjaxUtil; diff --git a/src/Resources/npm-package/js/array-util.js b/src/Resources/npm-package/js/array-util.js deleted file mode 100644 index 6a1df4b2..00000000 --- a/src/Resources/npm-package/js/array-util.js +++ /dev/null @@ -1,12 +0,0 @@ -class ArrayUtil { - static removeFromArray(value, array) { - for (var i = 0; i < array.length; i++) { - if (JSON.stringify(value) == JSON.stringify(array[i])) { - array.splice(i, 1); - } - } - return array; - } -} - -export default ArrayUtil \ No newline at end of file diff --git a/src/Resources/npm-package/js/contao-utils-bundle.js b/src/Resources/npm-package/js/contao-utils-bundle.js deleted file mode 100644 index 32cde1a4..00000000 --- a/src/Resources/npm-package/js/contao-utils-bundle.js +++ /dev/null @@ -1,28 +0,0 @@ -import './polyfills'; -import ArrayUtil from './array-util' -import DomUtil from './dom-util' -import EventUtil from './event-util' -import UrlUtil from './url-util' -import GeneralUtil from './general-util' -import AjaxUtil from './ajax-util' - -let utilsBundle = { - ajax: AjaxUtil, - array: ArrayUtil, - dom: DomUtil, - event: EventUtil, - url: UrlUtil, - util: GeneralUtil -}; - -window.utilsBundle = utilsBundle; - -export { - utilsBundle, - AjaxUtil, - ArrayUtil, - DomUtil, - EventUtil, - GeneralUtil, - UrlUtil -} diff --git a/src/Resources/npm-package/js/dom-util.js b/src/Resources/npm-package/js/dom-util.js deleted file mode 100644 index 87393b39..00000000 --- a/src/Resources/npm-package/js/dom-util.js +++ /dev/null @@ -1,77 +0,0 @@ -import ArrayUtil from './array-util'; - -class DomUtil { - static getTextWithoutChildren(element, notrim) { - let result = element.cloneNode(true); - - Array.prototype.forEach.call(result.children, child => { - child.remove(); - }); - - if (typeof notrim !== 'undefined' && notrim === true) { - return result.innerText; - } else { - return result.innerText.trim(); - } - } - - static scrollTo(element, offset = 0, delay = 0, force = false) { - let rect = element.getBoundingClientRect(); - let scrollPosition = (rect.top + window.pageYOffset - offset); - setTimeout(() => { - if (!this.elementInViewport(element) || force === true) - { - var isSmoothScrollSupported = 'scrollBehavior' in document.documentElement.style; - if (isSmoothScrollSupported) - { - window.scrollTo({ - 'top': scrollPosition, - 'behavior': 'smooth', - }); - } - else { - window.scrollTo(0, scrollPosition); - } - } - }, delay); - } - - static elementInViewport(el) { - let top = el.offsetTop; - let left = el.offsetLeft; - let width = el.offsetWidth; - let height = el.offsetHeight; - - while (el.offsetParent) { - el = el.offsetParent; - top += el.offsetTop; - left += el.offsetLeft; - } - - return ( - top < (window.pageYOffset + window.innerHeight) && - left < (window.pageXOffset + window.innerWidth) && - (top + height) > window.pageYOffset && - (left + width) > window.pageXOffset - ); - } - - static getAllParentNodes(node) { - var parents = []; - - while (node) { - parents.unshift(node); - node = node.parentNode; - } - - for (var i = 0; i < parents.length; i++) { - if (parents[i] === document) { - parents.splice(i, 1); - } - } - - return parents; - } -} - -export default DomUtil diff --git a/src/Resources/npm-package/js/event-util.js b/src/Resources/npm-package/js/event-util.js deleted file mode 100644 index 86d6f906..00000000 --- a/src/Resources/npm-package/js/event-util.js +++ /dev/null @@ -1,52 +0,0 @@ -import DomUtil from './dom-util'; -import GeneralUtil from './general-util' - -class EventUtil { - static addDynamicEventListener(eventName, selector, callback, scope, disableBubbling) { - if (typeof scope === 'undefined') { - scope = document; - } - - scope.addEventListener(eventName, function(e) { - - let parents; - - if (GeneralUtil.isTruthy(disableBubbling)) { - parents = [e.target]; - } else if (e.target !== document) { - parents = DomUtil.getAllParentNodes(e.target); - } - - // for instance window load/resize event - if (!Array.isArray(parents)) { - document.querySelectorAll(selector).forEach(function(item) { - callback(item, e); - }); - return; - } - - parents.reverse().forEach(function(item) { - if (item && item.matches(selector)) { - callback(item, e); - } - }); - }); - } - - static createEventObject(type, bubbles = false, cancelable = false, composed = false) { - if (typeof (Event) === 'function') { - return new Event(type, { - bubbles: bubbles, - cancelable: cancelable, - composed: composed - }); - } else { - let event = document.createEvent('Event'); - event.initEvent(type, bubbles, cancelable); - - return event; - } - } -} - -export default EventUtil diff --git a/src/Resources/npm-package/js/general-util.js b/src/Resources/npm-package/js/general-util.js deleted file mode 100644 index 55332530..00000000 --- a/src/Resources/npm-package/js/general-util.js +++ /dev/null @@ -1,47 +0,0 @@ -class GeneralUtil { - static isTruthy(value) { - return typeof value !== 'undefined' && value !== null; - } - - static call(func) { - if (typeof func === 'function') { - func.apply(this, Array.prototype.slice.call(arguments, 1)); - } - } - - /** - * Run a function recursively for a given set of arguments. - * - * function doLogic(argument, remainingArguments, callback) { - * // do your logic with argument - * utilsBundle.util.runRecursiveFunction(doLogic, remainingArguments, callback); - * } - * - * utilsBundle.util.runRecursiveFunction(doLogic, [1, 2, 3, 4], () => { - * // do something after all is done - * }); - * - * @param func - * @param args - * @param callback - * @param successIndex - */ - static runRecursiveFunction(func, args, callback, successIndex) { - if (args.length < 1) { - if (GeneralUtil.isTruthy(callback) && Array.isArray(callback)) { - GeneralUtil.call(callback[successIndex]); - } else { - GeneralUtil.call(callback); - } - - return; - } - - var argument = args[0], - remainingArgs = args.slice(1, args.length); - - func(argument, remainingArgs, callback); - } -} - -export default GeneralUtil diff --git a/src/Resources/npm-package/js/polyfills.js b/src/Resources/npm-package/js/polyfills.js deleted file mode 100644 index 91c9e426..00000000 --- a/src/Resources/npm-package/js/polyfills.js +++ /dev/null @@ -1,35 +0,0 @@ -// foreach on nodelists -import 'nodelist-foreach-polyfill'; - -// closest() and matches polyfill -import elementClosest from 'element-closest'; - -elementClosest(window); - -// replaceWith -function ReplaceWith(Ele) { - 'use-strict'; // For safari, and IE > 10 - var parent = this.parentNode, - i = arguments.length, - firstIsNode = +(parent && typeof Ele === 'object'); - if (!parent) return; - - while (i-- > firstIsNode) { - if (parent && typeof arguments[i] !== 'object') { - arguments[i] = document.createTextNode(arguments[i]); - } - if (!parent && arguments[i].parentNode) { - arguments[i].parentNode.removeChild(arguments[i]); - continue; - } - parent.insertBefore(this.previousSibling, arguments[i]); - } - if (firstIsNode) parent.replaceChild(Ele, this); -} - -if (!Element.prototype.replaceWith) - Element.prototype.replaceWith = ReplaceWith; -if (!CharacterData.prototype.replaceWith) - CharacterData.prototype.replaceWith = ReplaceWith; -if (!DocumentType.prototype.replaceWith) - CharacterData.prototype.replaceWith = ReplaceWith; \ No newline at end of file diff --git a/src/Resources/npm-package/js/url-util.js b/src/Resources/npm-package/js/url-util.js deleted file mode 100644 index 01c47911..00000000 --- a/src/Resources/npm-package/js/url-util.js +++ /dev/null @@ -1,166 +0,0 @@ -class UrlUtil { - static getParameterByName(name, url) - { - if (!url) - { - url = window.location.href; - } - - name = name.replace(/[\[\]]/g, "\\$&"); - - let regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"), - results = regex.exec(url); - - if (!results) - { - return null; - } - - if (!results[2]) - { - return ''; - } - - return decodeURIComponent(results[2].replace(/\+/g, " ")); - } - - static addParameterToUri(uri, key, value) - { - if (!uri) - { - uri = window.location.href; - } - - let re = new RegExp("([?&])" + key + "=.*?(&|#|$)(.*)", "gi"), - hash; - - if (re.test(uri)) - { - if (typeof value !== 'undefined' && value !== null) - { - return uri.replace(re, '$1' + key + "=" + value + '$2$3'); - } - else - { - hash = uri.split('#'); - uri = hash[0].replace(re, '$1$3').replace(/(&|\?)$/, ''); - - if (typeof hash[1] !== 'undefined' && hash[1] !== null) - { - uri += '#' + hash[1]; - } - - return uri; - } - } - else - { - if (typeof value !== 'undefined' && value !== null) - { - let separator = uri.indexOf('?') !== -1 ? '&' : '?'; - hash = uri.split('#'); - uri = hash[0] + separator + key + '=' + value; - - if (typeof hash[1] !== 'undefined' && hash[1] !== null) - { - uri += '#' + hash[1]; - } - - return uri; - } - else - { - return uri; - } - } - } - - static addParametersToUri(uri, parameters) - { - if(parameters instanceof FormData) { - for(let entry of parameters.entries()) { - if(parameters.has(entry[0])) { - uri = this.addParameterToUri(uri, entry[0], entry[1]); - } - } - } else { - for (let key in parameters) - { - if (parameters.hasOwnProperty(key)) - { - uri = this.addParameterToUri(uri, key, parameters[key]); - } - } - } - - return uri; - } - - static removeParameterFromUri(uri, parameter) - { - //prefer to use l.search if you have a location/link object - let uriparts = uri.split('?'); - - if (uriparts.length >= 2) - { - - let prefix = encodeURIComponent(parameter) + '='; - let pars = uriparts[1].split(/[&;]/g); - - //reverse iteration as may be destructive - for (let i = pars.length; i-- > 0;) - { - //idiom for string.startsWith - if (pars[i].lastIndexOf(prefix, 0) !== -1) - { - pars.splice(i, 1); - } - } - - uri = uriparts[0] + '?' + pars.join('&'); - return uri; - } - else - { - return uri; - } - } - - static removeParametersFromUri(uri, parameters) - { - for (let key in parameters) - { - if (parameters.hasOwnProperty(key)) - { - uri = this.removeParameterFromUri(uri, key); - } - } - - return uri; - } - - static replaceParameterInUri(uri, key, value) - { - this.addParameterToUri(this.removeParameterFromUri(uri, key), key, value); - } - - static parseQueryString(queryString) { - return JSON.parse('{"' + decodeURI(queryString).replace(/"/g, '\\"').replace(/&/g, '","').replace(/=/g,'":"') + '"}') - } - - static buildQueryString(parameters) { - let query = ''; - - for (let key in parameters) { - if ('' !== query) { - query += '&'; - } - - query += key + '=' + parameters[key]; - } - - return query; - } -} - -export default UrlUtil diff --git a/src/Resources/npm-package/package.json b/src/Resources/npm-package/package.json deleted file mode 100644 index a9fcaf38..00000000 --- a/src/Resources/npm-package/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "@hundh/contao-utils-bundle", - "version": "1.18.0", - "description": "This package contains the frontend assets of the composer bundle heimrichhannot/contao-utils-bundle.", - "main": "js/contao-utils-bundle.js", - "repository": "git@github.com:heimrichhannot-contao-components/contao-utils-bundle.git", - "author": "Heimrich & Hannot ", - "license": "MIT", - "dependencies": { - "element-closest": "^3.0", - "nodelist-foreach-polyfill": "^1.2" - } -} diff --git a/src/Resources/public/css/contao-utils-bundle.be.css b/src/Resources/public/css/contao-utils-bundle.be.css deleted file mode 100644 index b370a094..00000000 --- a/src/Resources/public/css/contao-utils-bundle.be.css +++ /dev/null @@ -1 +0,0 @@ -.tl_content_left .tl_content{border-bottom:0;border-top:1px solid #e9e9e9;padding-right:0;padding-left:30px}.tl_content_left .tl_content::before{content:url("/bundles/heimrichhannotcontaoutils/img/icon-subrecord-arrow.png");display:inline-block;float:left;margin-right:5px;margin-bottom:-5px}.tl_content_left .tl_content:first-of-type{margin-top:7px}.tl_content_left .tl_content:last-of-type{padding-bottom:0} diff --git a/src/Resources/public/img/icon-subrecord-arrow.png b/src/Resources/public/img/icon-subrecord-arrow.png deleted file mode 100644 index 079a7da7..00000000 Binary files a/src/Resources/public/img/icon-subrecord-arrow.png and /dev/null differ diff --git a/src/Resources/public/js/contao-utils-bundle.js b/src/Resources/public/js/contao-utils-bundle.js deleted file mode 100644 index 564f2b0e..00000000 --- a/src/Resources/public/js/contao-utils-bundle.js +++ /dev/null @@ -1 +0,0 @@ -!function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="/public/js/",n(n.s="EXSK")}({EXSK:function(e,t,n){"use strict";n.r(t),n.d(t,"utilsBundle",(function(){return w})),n.d(t,"AjaxUtil",(function(){return g})),n.d(t,"ArrayUtil",(function(){return i})),n.d(t,"DomUtil",(function(){return l})),n.d(t,"EventUtil",(function(){return p})),n.d(t,"GeneralUtil",(function(){return f})),n.d(t,"UrlUtil",(function(){return h}));n("Fqrg");function r(e){return(r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function o(e){var t=this.parentNode,n=arguments.length,o=+(t&&"object"===r(e));if(t){for(;n-- >o;)t&&"object"!==r(arguments[n])&&(arguments[n]=document.createTextNode(arguments[n])),t||!arguments[n].parentNode?t.insertBefore(this.previousSibling,arguments[n]):arguments[n].parentNode.removeChild(arguments[n]);o&&t.replaceChild(e,this)}}function a(e,t){for(var n=0;n1&&void 0!==arguments[1]?arguments[1]:0,r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:0,o=arguments.length>3&&void 0!==arguments[3]&&arguments[3],a=e.getBoundingClientRect(),i=a.top+window.pageYOffset-n;setTimeout((function(){t.elementInViewport(e)&&!0!==o||("scrollBehavior"in document.documentElement.style?window.scrollTo({top:i,behavior:"smooth"}):window.scrollTo(0,i))}),r)}},{key:"elementInViewport",value:function(e){for(var t=e.offsetTop,n=e.offsetLeft,r=e.offsetWidth,o=e.offsetHeight;e.offsetParent;)t+=(e=e.offsetParent).offsetTop,n+=e.offsetLeft;return twindow.pageYOffset&&n+r>window.pageXOffset}},{key:"getAllParentNodes",value:function(e){for(var t=[];e;)t.unshift(e),e=e.parentNode;for(var n=0;n1&&void 0!==arguments[1]&&arguments[1],n=arguments.length>2&&void 0!==arguments[2]&&arguments[2],r=arguments.length>3&&void 0!==arguments[3]&&arguments[3];if("function"==typeof Event)return new Event(e,{bubbles:t,cancelable:n,composed:r});var o=document.createEvent("Event");return o.initEvent(e,t,n),o}}],(n=null)&&s(t.prototype,n),r&&s(t,r),e}();function y(e,t){var n="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(!n){if(Array.isArray(e)||(n=function(e,t){if(!e)return;if("string"==typeof e)return d(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);"Object"===n&&e.constructor&&(n=e.constructor.name);if("Map"===n||"Set"===n)return Array.from(e);if("Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return d(e,t)}(e))||t&&e&&"number"==typeof e.length){n&&(e=n);var r=0,o=function(){};return{s:o,n:function(){return r>=e.length?{done:!0}:{done:!1,value:e[r++]}},e:function(e){throw e},f:o}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}var a,i=!0,u=!1;return{s:function(){n=n.call(e)},n:function(){var e=n.next();return i=e.done,e},e:function(e){u=!0,a=e},f:function(){try{i||null==n.return||n.return()}finally{if(u)throw a}}}}function d(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n=2){for(var r=encodeURIComponent(t)+"=",o=n[1].split(/[&;]/g),a=o.length;a-- >0;)-1!==o[a].lastIndexOf(r,0)&&o.splice(a,1);return e=n[0]+"?"+o.join("&")}return e}},{key:"removeParametersFromUri",value:function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e=this.removeParameterFromUri(e,n));return e}},{key:"replaceParameterInUri",value:function(e,t,n){this.addParameterToUri(this.removeParameterFromUri(e,t),t,n)}},{key:"parseQueryString",value:function(e){return JSON.parse('{"'+decodeURI(e).replace(/"/g,'\\"').replace(/&/g,'","').replace(/=/g,'":"')+'"}')}},{key:"buildQueryString",value:function(e){var t="";for(var n in e)""!==t&&(t+="&"),t+=n+"="+e[n];return t}}],(n=null)&&v(t.prototype,n),r&&v(t,r),e}();function m(e){return(m="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function b(e,t){for(var n=0;n=200&&t.status<400?f.call(r.onSuccess,t):f.call(r.onError,t),f.call(r.afterSubmit,n.action,n.data,r)},f.call(r.beforeSubmit,n.action,n.data,r),void 0===n.data?t.send():(n.data=e.prepareDataForSend(n.data),t.send(n.data))}},{key:"prepareDataForSend",value:function(e){if(!(e instanceof FormData)&&"object"===m(e)){var t=new FormData;return Object.keys(e).forEach((function(n){t.append(n,e[n])})),t}return e}},{key:"initializeRequest",value:function(t,n,r){var o=new XMLHttpRequest;return o.open(t,n,!0),o=e.setRequestHeaders(o,r),r.hasOwnProperty("responseType")&&(o.responseType=r.responseType),o}},{key:"setRequestHeaders",value:function(e,t){return t.hasOwnProperty("headers")&&Object.keys(t.headers).forEach((function(n){e.setRequestHeader(n,t.headers[n])})),e}},{key:"setDefaults",value:function(e){return e.hasOwnProperty("headers")||(e.headers={"X-Requested-With":"XMLHttpRequest"}),e}}],(n=null)&&b(t.prototype,n),r&&b(t,r),e}(),w={ajax:g,array:i,dom:l,event:p,url:h,util:f};window.utilsBundle=w},Fqrg:function(e,t){window.NodeList&&!NodeList.prototype.forEach&&(NodeList.prototype.forEach=function(e,t){t=t||window;for(var n=0;n - {{ link|raw }} ({{ filesize }}) -
diff --git a/src/Resources/views/image.html.twig b/src/Resources/views/image.html.twig deleted file mode 100644 index 8e758521..00000000 --- a/src/Resources/views/image.html.twig +++ /dev/null @@ -1,23 +0,0 @@ -
- - {% if href|default() %} - - {% endif %} - - {{ include('@HeimrichHannotContaoUtils/picture.html.twig', picture|merge({ - loading: loading|default('auto') - })) }} - - {% if href|default() %} - - {% endif %} - - {% set hasCopyright = picture.copyright|default and picture.copyright != 'a:1:{i:0;s:0:"";}' and picture.copyright != 'a:0:{}' %} - - {% if caption|default() %} -
{{ caption }}{% if hasCopyright %} (© {{ picture.copyright|deserialize(true)|join(', ')|raw|replace({'©': ''}) }}){% endif %}
- {% elseif (hasCopyright and includeCopyright|default) %} -
© {{ picture.copyright|deserialize(true)|join(', ')|raw|replace({'©': ''}) }}
- {% endif %} -
diff --git a/src/Resources/views/image_gallery.html.twig b/src/Resources/views/image_gallery.html.twig deleted file mode 100644 index 45ba64f8..00000000 --- a/src/Resources/views/image_gallery.html.twig +++ /dev/null @@ -1,17 +0,0 @@ -{% if imageGallery %} - {% block slides %} - - {% endblock %} -{% endif %} \ No newline at end of file diff --git a/src/Resources/views/picture.html.twig b/src/Resources/views/picture.html.twig deleted file mode 100644 index 8027d18f..00000000 --- a/src/Resources/views/picture.html.twig +++ /dev/null @@ -1,64 +0,0 @@ -{% set lazyload = lazyload|default() is same as(true) ? {class: 'lazy-img', src: 'data-src', srcset: 'data-srcset', aspectRatio: true} : lazyload|default() %} - -{% if lazyload|default() %} -{% set wrapperID = ('image-wrapper-' ~ random()) %} -
- {% if(lazyload.aspectRatio|default()) %} -
- {% endif %} - {% set wrapperStyles = wrapperStyles|default([])|merge([('#' ~ wrapperID ~ ':not(.loaded){max-width:' ~ (img.width|default ? img.width ~ 'px' : 'none') ~ ';max-height:' ~ (img.height|default == 0 ? 'none' : (img.height ~ 'px')) ~ '}')]) %} - {% set wrapperStyles = wrapperStyles|default([])|merge([('#' ~ wrapperID ~ ':not(.loaded) .image-aspect-ratio{padding-bottom:' ~ (img.width|default ? (img.height/img.width)*100 : 0)|number_format(2, '.') ~ '%}')]) %} - {% endif %} - {% if sources|default() %} - - - {% for source in sources %} - {% if lazyload|default() %} - - {% if source.media|default() %} - {% set wrapperStyles = wrapperStyles|merge([('@media ' ~ source.media ~ '{#' ~ wrapperID ~ ':not(.loaded){max-width:'~ (source.width == 0 ? 'none' : (source.width ~ 'px')) ~';max-height:' ~ (source.height == 0 ? 'none' : (source.height ~ 'px')) ~ '}}')]) %} - {% set wrapperStyles = wrapperStyles|merge([('@media ' ~ source.media ~ '{#' ~ wrapperID ~ ':not(.loaded) .image-aspect-ratio{padding-bottom:' ~ (source.width == 0 ? 0 : (source.height/source.width)*100)|number_format(2, '.') ~ '%}}')]) %} - {% endif %} - {% else %} - - {% endif %} - {% endfor %} - - {% endif %} - - {% if lazyload|default() %} - {{ alt|raw }} - {% else %} - {{ alt|raw }} - {% endif %} - - {% if sources %} - - {% endif %} - {% if lazyload|default() %} - {% apply spaceless %} - - {% endapply %} - {% if(lazyload.aspectRatio|default()) %} -
- {% endif %} -
-{% endif %} - -{% if sources|default() or img.src != img.srcset %} - -{% endif %} - - diff --git a/src/Resources/views/video.html.twig b/src/Resources/views/video.html.twig deleted file mode 100644 index 361e3285..00000000 --- a/src/Resources/views/video.html.twig +++ /dev/null @@ -1,23 +0,0 @@ -
- - - {% for file in files %} - - {% endfor %} - - -{% if posterImg|default %} -
- {{ include('@HeimrichHannotContaoUtils/image.html.twig', posterImg) }} - -
-
-{% endif %} - -
\ No newline at end of file diff --git a/src/Routing/RoutingUtil.php b/src/Routing/RoutingUtil.php deleted file mode 100644 index ee9b775f..00000000 --- a/src/Routing/RoutingUtil.php +++ /dev/null @@ -1,43 +0,0 @@ -utils = $utils; - } - - /** - * Generate a backend route with token and referer. - * - * @param array $params Url-Parameters - * - * @return string The backend route url - * - * @deprecated Use utils service instead - * @codeCoverageIgnore - */ - public function generateBackendRoute(array $params = [], bool $addToken = true, bool $addReferer = true, string $route = 'contao_backend') - { - return $this->utils->routing()->generateBackendRoute($params, $addToken, $addReferer, $route); - } -} diff --git a/src/Rsce/RsceUtil.php b/src/Rsce/RsceUtil.php deleted file mode 100644 index b7f28c87..00000000 --- a/src/Rsce/RsceUtil.php +++ /dev/null @@ -1,107 +0,0 @@ -formUtil = $formUtil; - } - - public function explodeRsceData(string $cteType, string $data, int $recordId = 0, $options = []) - { - $result = []; - $rsceData = json_decode($data, true); - $dca = CustomElements::getConfigByType($cteType); - - $skipFormat = isset($options['skipFormatting']) && $options['skipFormatting']; - $outputPrepareConfig = isset($options['outputPrepareConfig']) && \is_array($options['outputPrepareConfig']) ? $options['outputPrepareConfig'] : []; - - $skipFields = isset($options['skipFields']) && \is_array($options['skipFields']) ? $options['skipFields'] : []; - $fields = isset($options['fields']) && \is_array($options['fields']) ? $options['fields'] : []; - - $nestedSkipFields = isset($options['nestedSkipFields']) && \is_array($options['nestedSkipFields']) ? $options['nestedSkipFields'] : []; - $nestedFields = isset($options['nestedFields']) && \is_array($options['nestedFields']) ? $options['nestedFields'] : []; - $nestedFieldSeparator = $options['nestedFieldSeparator'] ?? "\t"; - $nestedRowSeparator = $options['nestedRowSeparator'] ?? "\t\n"; - $skipNestedFieldLabels = $options['skipNestedFieldLabels'] ?? false; - $skipNestedFieldLabelFormatting = $options['skipNestedFieldLabelFormatting'] ?? false; - - $dc = new DC_Table_Utils('tl_content'); - $dc->activeRecord = $rsceData; - $dc->id = $recordId; - - foreach ($dca['fields'] as $field => $fieldData) { - if (\in_array($field, $skipFields) || (\is_array($fields) && !\in_array($field, $fields))) { - continue; - } - - $value = $rsceData[$field]; - - if (isset($fieldData['fields']) && \is_array($fieldData['fields']) && \is_array($value)) { - $nestedResult = ''; - - foreach ($value as $row) { - $nestedResult .= $nestedRowSeparator; - - foreach ($row as $nestedField => $nestedFieldValue) { - if (\in_array($nestedField, $nestedSkipFields) || (\is_array($nestedFields) && !\in_array($nestedField, $nestedFields))) { - continue; - } - - $dca = $fieldData['fields'][$nestedField]; - - $label = ''; - - if (!$skipNestedFieldLabels) { - $label = ($dca['label'][0] ?: $nestedField).': '; - - if ($skipNestedFieldLabelFormatting) { - $label = $nestedField.': '; - } - } - - $nestedResult .= $nestedFieldSeparator.$label.$this->formUtil->prepareSpecialValueForOutput($nestedField, $nestedFieldValue, $dc, array_merge($outputPrepareConfig, [ - '_dcaOverride' => $dca, - ])); - } - } - - // new line - add "\t\n" after each line and not only "\n" to prevent outlook line break remover - $nestedResult .= $nestedRowSeparator; - - $result[$field] = $nestedResult; - - continue; - } - - if (!$skipFormat) { - $value = $this->formUtil->prepareSpecialValueForOutput( - $field, $value, $dc, array_merge($outputPrepareConfig, [ - '_dcaOverride' => $fieldData, - ]) - ); - } - - $result[$field] = $value; - } - - return $result; - } -} diff --git a/src/Salutation/SalutationUtil.php b/src/Salutation/SalutationUtil.php deleted file mode 100644 index 1545f5eb..00000000 --- a/src/Salutation/SalutationUtil.php +++ /dev/null @@ -1,190 +0,0 @@ -framework = $framework; - } - - /** - * Creates complete names by inserting an array of the person's data. - * - * Supported field names: firstname, lastname, academicTitle, additionalTitle, gender - * - * If some of the fields shouldn't go into the processed name, just leave them out of $arrData - */ - public function createNameByFields(string $language, array $data) - { - if ($language) { - /** @var System $system */ - $system = $this->framework->getAdapter(System::class); - - $system->loadLanguageFile('default', $language, true); - } - - $name = ''; - - if ($data['firstname']) { - $name = $data['firstname'].($data['lastname'] ? ' '.$data['lastname'] : ''); - } elseif ($data['lastname']) { - $name = $data['lastname']; - } - - if ($name && $data['academicTitle']) { - $name = $data['academicTitle'].' '.$name; - } - - if ($name && $data['additionalTitle']) { - $name = $data['additionalTitle'].' '.$name; - } - - if ($data['lastname'] && $data['gender'] && !\in_array($data['gender'], ['other', 'divers']) && ('en' != $language || !$data['academicTitle'])) { - $gender = $GLOBALS['TL_LANG']['MSC']['utilsBundle']['gender'.('female' == $data['gender'] ? 'Female' : 'Male')]; - - $name = $gender.' '.$name; - } - - if ($language) { - $system->loadLanguageFile('default', $GLOBALS['TL_LANGUAGE'], true); - } - - return $name; - } - - /** - * @param $language - * @param $entity object|array - * - * @return string - */ - public function createSalutation(string $language, $entity, array $options = []) - { - $informal = isset($options['informal']) && $options['informal']; - $firstnameOnly = isset($options['firstnameOnly']) && $options['firstnameOnly']; - - if (\is_array($entity)) { - $entity = System::getContainer()->get('huh.utils.array')->arrayToObject($entity); - } - - $hasFirstname = $entity->firstname; - $hasLastname = $entity->lastname; - $hasTitle = $entity->title && '-' != $entity->title && 'Titel' != $entity->title && 'Title' != $entity->title; - - if (!$hasTitle) { - $hasTitle = $entity->academicTitle && '-' != $entity->academicTitle && 'Titel' != $entity->academicTitle && 'Title' != $entity->academicTitle; - } - - if ($language) { - /** @var System $system */ - $system = $this->framework->getAdapter(System::class); - - $system->loadLanguageFile('default', $language, true); - } - - switch ($language) { - case 'en': - if (\in_array($entity->gender, ['other', 'divers'])) { - if ($informal) { - if ($hasFirstname && $firstnameOnly) { - $salutation = $GLOBALS['TL_LANG']['MSC']['utilsBundle']['salutationDivers'].' '.$entity->firstname; - } elseif ($hasFirstname && $hasLastname && !$firstnameOnly) { - $salutation = $GLOBALS['TL_LANG']['MSC']['utilsBundle']['salutationDivers'].' '.$entity->firstname.' '.$entity->lastname; - } else { - $salutation = $GLOBALS['TL_LANG']['MSC']['utilsBundle']['salutationDivers']; - } - } else { - $suffix = $entity->firstname && $entity->lastname ? ' '.$entity->firstname.' '.$entity->lastname : ''; - $salutation = $GLOBALS['TL_LANG']['MSC']['utilsBundle']['salutationDivers'].$suffix; - } - } else { - if ($informal) { - if ($hasFirstname && $firstnameOnly) { - $salutation = $GLOBALS['TL_LANG']['MSC']['utilsBundle']['salutation'].' '.$entity->firstname; - } elseif ($hasLastname) { - $salutationPart = $GLOBALS['TL_LANG']['MSC']['utilsBundle']['gender'.('female' == $entity->gender ? 'Female' : 'Male')]; - - $salutation = $GLOBALS['TL_LANG']['MSC']['utilsBundle']['salutation'].' '.$salutationPart.' '.$entity->lastname; - } else { - $salutation = $GLOBALS['TL_LANG']['MSC']['utilsBundle']['salutation']; - } - } elseif ($hasLastname) { - if ($hasTitle) { - $salutation = $GLOBALS['TL_LANG']['MSC']['utilsBundle']['salutation'].' '.($entity->title ?: $entity->academicTitle); - } else { - $salutation = - $GLOBALS['TL_LANG']['MSC']['utilsBundle']['salutation'.('female' == $entity->gender ? 'Female' : 'Male')]; - } - - $salutation = $salutation.' '.$entity->lastname; - } else { - $salutation = $GLOBALS['TL_LANG']['MSC']['utilsBundle']['salutation']; - } - } - - break; - - default: - // de - if (\in_array($entity->gender, ['other', 'divers'])) { - if ($informal) { - if ($hasFirstname && $firstnameOnly) { - $salutation = $GLOBALS['TL_LANG']['MSC']['utilsBundle']['salutationGenericInformal'].' '.$entity->firstname; - } elseif ($hasFirstname && $hasLastname && !$firstnameOnly) { - $salutation = $GLOBALS['TL_LANG']['MSC']['utilsBundle']['salutationGenericInformal'].' '.$entity->firstname.' '.$entity->lastname; - } else { - $salutation = $GLOBALS['TL_LANG']['MSC']['utilsBundle']['salutationGenericInformal']; - } - } else { - $suffix = $entity->firstname && $entity->lastname ? ', '.$entity->firstname.' '.$entity->lastname : ''; - $salutation = $GLOBALS['TL_LANG']['MSC']['utilsBundle']['salutationDivers'].$suffix; - } - } else { - if ($informal) { - if ($hasFirstname && $firstnameOnly) { - $salutation = $GLOBALS['TL_LANG']['MSC']['utilsBundle']['salutationGenericInformal'].' '.$entity->firstname; - } elseif ($hasLastname && !$firstnameOnly) { - $salutationPart = $GLOBALS['TL_LANG']['MSC']['utilsBundle']['gender'.('female' == $entity->gender ? 'Female' : 'Male')]; - $salutation = $GLOBALS['TL_LANG']['MSC']['utilsBundle']['salutationGenericInformal'].' '.$salutationPart.' '.$entity->lastname; - } else { - $salutation = $GLOBALS['TL_LANG']['MSC']['utilsBundle']['salutationGenericInformal']; - } - } elseif ($hasLastname && !$informal) { - $salutation = $GLOBALS['TL_LANG']['MSC']['utilsBundle']['salutation'.('female' == $entity->gender ? 'Female' : 'Male')]; - - if ($hasTitle) { - $salutation .= ' '.($entity->title ?: $entity->academicTitle); - } - - $salutation = $salutation.' '.$entity->lastname; - } else { - $salutation = $GLOBALS['TL_LANG']['MSC']['utilsBundle']['salutationGeneric']; - } - } - - break; - } - - if ($language) { - $system->loadLanguageFile('default', $GLOBALS['TL_LANGUAGE'], true); - } - - return $salutation; - } -} diff --git a/src/Security/CodeUtil.php b/src/Security/CodeUtil.php deleted file mode 100644 index 66c1ab86..00000000 --- a/src/Security/CodeUtil.php +++ /dev/null @@ -1,128 +0,0 @@ -()#/]'; - /** @var ContaoFrameworkInterface */ - protected $framework; - - protected static $blnPreventAmbiguous = true; - - public function __construct(ContaoFrameworkInterface $framework) - { - $this->framework = $framework; - } - - /** - * Generates a code by certain criteria. - * - * @return mixed - */ - public static function generate( - int $length = 8, - bool $preventAmbiguous = true, - array $alphabets = null, - array $rules = null, - string $allowedSpecialChars = null - ) { - $stringUtil = System::getContainer()->get('huh.utils.string'); - - $alphabets = \is_array($alphabets) ? $alphabets : static::DEFAULT_ALPHABETS; - $rules = \is_array($rules) ? $rules : static::DEFAULT_RULES; - $allowedSpecialChars = null !== $allowedSpecialChars ? $allowedSpecialChars : static::DEFAULT_ALLOWED_SPECIAL_CHARS; - - $generator = new ComputerPasswordGenerator(); - $generator - ->setLength($length) - ->setNumbers(\in_array(static::NUMBERS, $alphabets, true) && \in_array(static::NUMBERS, $rules, true)) - ->setUppercase(\in_array(static::CAPITAL_LETTERS, $alphabets, true) && \in_array(static::CAPITAL_LETTERS, $rules, true)) - ->setAvoidSimilar($preventAmbiguous) - ->setSymbols(\in_array(static::SPECIAL_CHARS, $alphabets, true) && \in_array(static::SPECIAL_CHARS, $rules, true)); - - $code = $generator->generatePassword(); - - // replace remaining ambiguous characters - if ($preventAmbiguous) { - $charReplacements = ['y', 'Y', 'z', 'Z', 'o', 'O', 'i', 'I', 'l']; - - foreach ($charReplacements as $char) { - $code = str_replace($char, $stringUtil->randomChar(!$preventAmbiguous), $code); - } - } - - // apply allowed alphabets - $forbiddenPattern = ''; - $allowedChars = ''; - - if (!\in_array(static::CAPITAL_LETTERS, $alphabets)) { - $forbiddenPattern .= 'A-Z'; - } else { - $allowedChars .= ($preventAmbiguous ? StringUtil::CAPITAL_LETTERS_NONAMBIGUOUS : StringUtil::CAPITAL_LETTERS); - } - - if (!\in_array(static::SMALL_LETTERS, $alphabets)) { - $forbiddenPattern .= 'a-z'; - } else { - $allowedChars .= ($preventAmbiguous ? StringUtil::SMALL_LETTERS_NONAMBIGUOUS : StringUtil::SMALL_LETTERS); - } - - if (!\in_array(static::NUMBERS, $alphabets)) { - $forbiddenPattern .= '0-9'; - } else { - $allowedChars .= ($preventAmbiguous ? StringUtil::NUMBERS_NONAMBIGUOUS : StringUtil::NUMBERS); - } - - if ('' === $allowedChars) { - return $code; - } - - if ($forbiddenPattern) { - $code = preg_replace_callback('@['.$forbiddenPattern.']{1}@', function () use ($allowedChars, $stringUtil) { - return $stringUtil->random($allowedChars); - }, $code); - } - - // special chars - if (!\in_array(static::SPECIAL_CHARS, $alphabets)) { - $code = preg_replace_callback('@[^'.$allowedChars.']{1}@', function () use ($allowedChars, $stringUtil) { - return $stringUtil->random($allowedChars); - }, $code); - } else { - $code = preg_replace_callback('@[^'.$allowedChars.']{1}@', function () use ($allowedSpecialChars, $stringUtil) { - return $stringUtil->random($allowedSpecialChars); - }, $code); - } - - return $code; - } -} diff --git a/src/Security/EncryptionUtil.php b/src/Security/EncryptionUtil.php deleted file mode 100644 index 0d8a82fe..00000000 --- a/src/Security/EncryptionUtil.php +++ /dev/null @@ -1,44 +0,0 @@ -framework = $framework; - } - - public function encrypt(string $plain, string $key = '', string $cipher = 'aes-256-ctr', $options = 0) - { - $key = '' !== $key ? $key : System::getContainer()->getParameter('secret'); - - if (\in_array($cipher, openssl_get_cipher_methods())) { - $ivLength = openssl_cipher_iv_length($cipher); - $iv = openssl_random_pseudo_bytes($ivLength); - - return [openssl_encrypt($plain, $cipher, $key, $options, $iv), base64_encode($iv)]; - } - - return false; - } - - public function decrypt(string $encrypted, string $iv, string $key = '', string $cipher = 'aes-256-ctr', $options = 0) - { - $key = '' !== $key ? $key : System::getContainer()->getParameter('secret'); - - return openssl_decrypt($encrypted, $cipher, $key, $options, base64_decode($iv, true)); - } -} diff --git a/src/String/StringUtil.php b/src/String/StringUtil.php deleted file mode 100644 index a6c1a09f..00000000 --- a/src/String/StringUtil.php +++ /dev/null @@ -1,482 +0,0 @@ -framework = $framework; - $this->utils = $utils; - } - - /** - * Check for the occurrence at the start of the string. - * - * @param $haystack string The string to search in - * @param $needle string The needle - * - * @return bool - * - * @deprecated Use utils service instead - * @codeCoverageIgnore - */ - public function startsWith($haystack, $needle) - { - return $this->utils->string()->startsWith($haystack, $needle); - } - - /** - * Check for the occurrence at the end of the string. - * - * @param string $haystack The string to search in - * @param string $needle The needle - * - * @return bool - * - * @deprecated Use utils service instead - * @codeCoverageIgnore - */ - public function endsWith($haystack, $needle) - { - return $this->utils->string()->endsWith($haystack, $needle); - } - - /** - * Convert a camel case string to a dashed string. - * - * Example: MyPrettyClass to my-pretty-class - * - * @param string $value - * - * @return string - * - * @deprecated Use utils service instead - * @codeCoverageIgnore - */ - public function camelCaseToDashed($value) - { - return $this->utils->string()->camelCaseToDashed($value); - } - - /** - * Convert a camel case string to a snake cased string. - * - * Example: MyPrettyClass to my_pretty_class - * - * @return string - * - * @deprecated Use utils service instead - * @codeCoverageIgnore - */ - public function camelCaseToSnake(string $value) - { - return $this->utils->string()->camelCaseToSnake($value); - } - - /** - * @codeCoverageIgnore - * - * @deprecated Use utils service instead - * - * @return mixed - */ - public function randomChar(bool $includeAmbiguousChars = false) - { - return $this->utils->string()->randomChar($includeAmbiguousChars); - } - - /** - * @codeCoverageIgnore - * - * @deprecated Use utils service instead - * - * @return mixed - */ - public function randomLetter(bool $includeAmbiguousChars = false) - { - return $this->utils->string()->randomLetter($includeAmbiguousChars); - } - - /** - * @codeCoverageIgnore - * - * @deprecated Use utils service instead - * - * @return mixed - */ - public function randomNumber(bool $includeAmbiguousChars = false) - { - return $this->utils->string()->randomNumber($includeAmbiguousChars); - } - - /** - * @codeCoverageIgnore - * - * @deprecated Use utils service instead - * - * @return mixed - */ - public function random(string $charList) - { - return $this->utils->string()->random($charList); - } - - /** - * Truncates a given string respecting html element. - * - * @return string - * - * @codeCoverageIgnore - * - * @deprecated Use utils service instead - */ - public function truncateHtml(string $text, int $length = 100, string $ending = ' …', bool $exact = false, bool $considerHtml = true) - { - $options = [ - 'exact' => $exact, - 'ending' => $ending, - ]; - - if (!$considerHtml) { - return $this->utils->string()->truncate($text, $length, $options); - } - - $open_tags = []; - - if ($considerHtml) { - // if the plain text is shorter than the maximum length, return the whole text - if (\strlen($this->html2Text($text, ['ignore_errors' => true])) <= $length) { - return $text; - } - // splits all html-tags to scanable lines - preg_match_all('/(<.+?>)?([^<>]*)/s', $text, $lines, PREG_SET_ORDER); - $total_length = \strlen($ending); - $truncate = ''; - - foreach ($lines as $line_matchings) { - // if there is any html-tag in this line, handle it and add it (uncounted) to the output - if (!empty($line_matchings[1])) { - // if it's an "empty element" with or without xhtml-conform closing slash - if (preg_match('/^<(\s*.+?\/\s*|\s*(img|br|input|hr|area|base|basefont|col|frame|isindex|link|meta|param)(\s.+?)?)>$/is', $line_matchings[1])) { - // do nothing - // if tag is a closing tag - } else { - if (preg_match('/^<\s*\/([^\s]+?)\s*>$/s', $line_matchings[1], $tag_matchings)) { - // delete tag from $open_tags list - $pos = array_search($tag_matchings[1], $open_tags); - - if (false !== $pos) { - unset($open_tags[$pos]); - } - // if tag is an opening tag - } else { - if (preg_match('/^<\s*([^\s>!]+).*?>$/s', $line_matchings[1], $tag_matchings)) { - // add tag to the beginning of $open_tags list - array_unshift($open_tags, strtolower($tag_matchings[1])); - } - } - } - // add html-tag to $truncate'd text - $truncate .= $line_matchings[1]; - } - // calculate the length of the plain text part of the line; handle entities as one character - $content_length = \strlen(preg_replace('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|[0-9a-f]{1,6};/i', ' ', $line_matchings[2])); - - if ($total_length + $content_length > $length) { - // the number of characters which are left - $left = $length - $total_length; - $entities_length = 0; - // search for html entities - if (preg_match_all('/&[0-9a-z]{2,8};|&#[0-9]{1,7};|[0-9a-f]{1,6};/i', $line_matchings[2], $entities, PREG_OFFSET_CAPTURE)) { - // calculate the real length of all entities in the legal range - foreach ($entities[0] as $entity) { - if ($entity[1] + 1 - $entities_length <= $left) { - --$left; - $entities_length += \strlen($entity[0]); - } else { - // no more characters left - break; - } - } - } - $truncate .= substr($line_matchings[2], 0, $left + $entities_length); - // maximum lenght is reached, so get off the loop - break; - } - $truncate .= $line_matchings[2]; - $total_length += $content_length; - - // if the maximum length is reached, get off the loop - if ($total_length >= $length) { - break; - } - } - } else { - if (\strlen($text) <= $length) { - return $text; - } - $truncate = substr($text, 0, $length - \strlen($ending)); - } - // if the words shouldn't be cut in the middle... - if (!$exact) { - // ...search the last occurance of a space... - $spacepos = strrpos($truncate, ' '); - - if (isset($spacepos)) { - // ...and cut the text in this position - $truncate = substr($truncate, 0, $spacepos); - } - } - // add the defined ending to the text - $truncate .= $ending; - - if ($considerHtml) { - // close all unclosed html-tags - foreach ($open_tags as $tag) { - $truncate .= ''; - } - } - - return $truncate; - } - - /** - * @return mixed|string - * - * @codeCoverageIgnore - * - * @deprecated Use utils service instead - */ - public function pregReplaceLast(string $regExp, string $subject) - { - return $this->utils->string()->pregReplaceLast($regExp, $subject); - } - - /** - * @codeCoverageIgnore - * - * @deprecated Use trim($string, "/") instead - */ - public function removeLeadingAndTrailingSlash(string $string): string - { - return rtrim(ltrim($string, '/'), '/'); - } - - /** - * @codeCoverageIgnore - * - * @deprecated Use utils service instead - */ - public function removeLeadingString(string $string, string $subject) - { - return $this->utils->string()->removeLeadingString($string, $subject, ['trim' => false]); - } - - /** - * @codeCoverageIgnore - * - * @deprecated Use utils service instead - */ - public function removeTrailingString(string $string, string $subject) - { - return $this->utils->string()->removeTrailingString($string, $subject, ['trim' => false]); - } - - /** - * Restore basic entities. - * - * @param string $string The string with the tags to be replaced - * - * @return string The string with the original entities - * - * @codeCoverageIgnore - * - * @deprecated use \Contao\StringUtil::restoreBasicEntities() instead - */ - public function restoreBasicEntities($string) - { - return str_replace(['[&]', '[&]', '[lt]', '[gt]', '[nbsp]', '[-]'], ['&', '&', '<', '>', ' ', '­'], $string); - } - - /** - * Convert css into inline styles. - * - * @param array $cssText the css as text (no paths allowed atm) - * - * @return string - * - * @codeCoverageIgnore - * - * @deprecated Use CssInliner-Library directly in your code. We recommend tijsverkoyen/css-to-inline-styles (https://github.com/tijsverkoyen/CssToInlineStyles) - */ - public function convertToInlineCss(string $text, string $cssText = null) - { - // apply the css inliner - $objCssInliner = new CssToInlineStyles(); - - return $objCssInliner->convert($text, $cssText); - } - - /** - * Converts html to text. - * - * @throws \Soundasleep\Html2TextException - * - * @return string - * - * @codeCoverageIgnore - * - * @deprecated Use html2text-library direct in your code, we recommend html2text/html2text (https://github.com/mtibben/html2text) - */ - public function html2Text(string $html, array $options = []) - { - $html = str_replace("\n", '', $html); // remove white spaces from html - $html = str_replace('

', '

', $html); // interpret paragrah as block element - $html = str_replace('
', '
', $html); // interpret div as block element - - return Html2Text::convert($html, $options); - } - - /** - * Convenience method for lower casing in a save callback. - * - * @param $value - * - * @return string - * - * @codeCoverageIgnore - * - * @deprecated Deprecated in favor of custom callback that could be used with contao service callbacks - */ - public function lowerCase($value, \DataContainer $objDc) - { - return trim(strtolower($value)); - } - - /** - * Ensure line breaks for several languages. - * - * @deprecated use locale util of utils service instead - */ - public function ensureLineBreaks(string $buffer, string $language = 'en'): string - { - switch ($language) { - case 'cs': - // in czech language, one-syllable words should not stand alone at the end (use   instead of whitespace) - $buffer = preg_replace('/(\s\w{1})(\s)/', '$1 ', $buffer); - - break; - } - - return $buffer; - } - - /** - * @codeCoverageIgnore - * - * @deprecated Use utils service instead - */ - public function convertXmlToArray(string $xmlData) - { - // CDATA fix (see https://stackoverflow.com/a/6534234/1463757) - $xmlData = preg_replace_callback('//', function ($matches) { - return trim(htmlspecialchars($matches[1])); - }, $xmlData); - - $kmlData = simplexml_load_string($xmlData); - - return json_decode(json_encode($kmlData), true); - } - - /** - * @return array|string|string[]|null - * - * @codeCoverageIgnore - * - * @deprecated Will be removed in version 3.0. Use elvanto/litemoji (https://github.com/elvanto/litemoji) instead. - */ - public function replaceUnicodeEmojisByHtml(?string $text) - { -// How to get the latest list: -// -// 1. Navigate to http://www.unicode.org/emoji/charts/full-emoji-list.html -// 2. Run this in the devtools of your browser and copy it into the $regExp variable below: -// -// let codes = []; -// -// document.querySelectorAll('.code > a').forEach((item) => { -// let cleanedCodes = []; -// -// item.innerText.split(' ').forEach((code) => { -// let cleanedCode = '\\\\x{' + code.replace('U+', '') + '}'; -// -// cleanedCodes.push(cleanedCode); -// }); -// -// codes.push(cleanedCodes.join('')); -// }); -// -// console.log(codes.join('|')); -// 3. Navigate to https://www.unicode.org/emoji/charts/full-emoji-modifiers.html and do the same and pass it to $regExpTones - $regExp = '\\x{1F600}|\\x{1F603}|\\x{1F604}|\\x{1F601}|\\x{1F606}|\\x{1F605}|\\x{1F923}|\\x{1F602}|\\x{1F642}|\\x{1F643}|\\x{1F609}|\\x{1F60A}|\\x{1F607}|\\x{1F970}|\\x{1F60D}|\\x{1F929}|\\x{1F618}|\\x{1F617}|\\x{263A}|\\x{1F61A}|\\x{1F619}|\\x{1F972}|\\x{1F60B}|\\x{1F61B}|\\x{1F61C}|\\x{1F92A}|\\x{1F61D}|\\x{1F911}|\\x{1F917}|\\x{1F92D}|\\x{1F92B}|\\x{1F914}|\\x{1F910}|\\x{1F928}|\\x{1F610}|\\x{1F611}|\\x{1F636}|\\x{1F60F}|\\x{1F612}|\\x{1F644}|\\x{1F62C}|\\x{1F925}|\\x{1F60C}|\\x{1F614}|\\x{1F62A}|\\x{1F924}|\\x{1F634}|\\x{1F637}|\\x{1F912}|\\x{1F915}|\\x{1F922}|\\x{1F92E}|\\x{1F927}|\\x{1F975}|\\x{1F976}|\\x{1F974}|\\x{1F635}|\\x{1F92F}|\\x{1F920}|\\x{1F973}|\\x{1F978}|\\x{1F60E}|\\x{1F913}|\\x{1F9D0}|\\x{1F615}|\\x{1F61F}|\\x{1F641}|\\x{2639}|\\x{1F62E}|\\x{1F62F}|\\x{1F632}|\\x{1F633}|\\x{1F97A}|\\x{1F626}|\\x{1F627}|\\x{1F628}|\\x{1F630}|\\x{1F625}|\\x{1F622}|\\x{1F62D}|\\x{1F631}|\\x{1F616}|\\x{1F623}|\\x{1F61E}|\\x{1F613}|\\x{1F629}|\\x{1F62B}|\\x{1F971}|\\x{1F624}|\\x{1F621}|\\x{1F620}|\\x{1F92C}|\\x{1F608}|\\x{1F47F}|\\x{1F480}|\\x{2620}|\\x{1F4A9}|\\x{1F921}|\\x{1F479}|\\x{1F47A}|\\x{1F47B}|\\x{1F47D}|\\x{1F47E}|\\x{1F916}|\\x{1F63A}|\\x{1F638}|\\x{1F639}|\\x{1F63B}|\\x{1F63C}|\\x{1F63D}|\\x{1F640}|\\x{1F63F}|\\x{1F63E}|\\x{1F648}|\\x{1F649}|\\x{1F64A}|\\x{1F48B}|\\x{1F48C}|\\x{1F498}|\\x{1F49D}|\\x{1F496}|\\x{1F497}|\\x{1F493}|\\x{1F49E}|\\x{1F495}|\\x{1F49F}|\\x{2763}|\\x{1F494}|\\x{2764}|\\x{1F9E1}|\\x{1F49B}|\\x{1F49A}|\\x{1F499}|\\x{1F49C}|\\x{1F90E}|\\x{1F5A4}|\\x{1F90D}|\\x{1F4AF}|\\x{1F4A2}|\\x{1F4A5}|\\x{1F4AB}|\\x{1F4A6}|\\x{1F4A8}|\\x{1F573}|\\x{1F4A3}|\\x{1F4AC}|\\x{1F441}\\x{FE0F}\\x{200D}\\x{1F5E8}\\x{FE0F}|\\x{1F5E8}|\\x{1F5EF}|\\x{1F4AD}|\\x{1F4A4}|\\x{1F44B}|\\x{1F91A}|\\x{1F590}|\\x{270B}|\\x{1F596}|\\x{1F44C}|\\x{1F90C}|\\x{1F90F}|\\x{270C}|\\x{1F91E}|\\x{1F91F}|\\x{1F918}|\\x{1F919}|\\x{1F448}|\\x{1F449}|\\x{1F446}|\\x{1F595}|\\x{1F447}|\\x{261D}|\\x{1F44D}|\\x{1F44E}|\\x{270A}|\\x{1F44A}|\\x{1F91B}|\\x{1F91C}|\\x{1F44F}|\\x{1F64C}|\\x{1F450}|\\x{1F932}|\\x{1F91D}|\\x{1F64F}|\\x{270D}|\\x{1F485}|\\x{1F933}|\\x{1F4AA}|\\x{1F9BE}|\\x{1F9BF}|\\x{1F9B5}|\\x{1F9B6}|\\x{1F442}|\\x{1F9BB}|\\x{1F443}|\\x{1F9E0}|\\x{1FAC0}|\\x{1FAC1}|\\x{1F9B7}|\\x{1F9B4}|\\x{1F440}|\\x{1F441}|\\x{1F445}|\\x{1F444}|\\x{1F476}|\\x{1F9D2}|\\x{1F466}|\\x{1F467}|\\x{1F9D1}|\\x{1F471}|\\x{1F468}|\\x{1F9D4}|\\x{1F468}\\x{200D}\\x{1F9B0}|\\x{1F468}\\x{200D}\\x{1F9B1}|\\x{1F468}\\x{200D}\\x{1F9B3}|\\x{1F468}\\x{200D}\\x{1F9B2}|\\x{1F469}|\\x{1F469}\\x{200D}\\x{1F9B0}|\\x{1F9D1}\\x{200D}\\x{1F9B0}|\\x{1F469}\\x{200D}\\x{1F9B1}|\\x{1F9D1}\\x{200D}\\x{1F9B1}|\\x{1F469}\\x{200D}\\x{1F9B3}|\\x{1F9D1}\\x{200D}\\x{1F9B3}|\\x{1F469}\\x{200D}\\x{1F9B2}|\\x{1F9D1}\\x{200D}\\x{1F9B2}|\\x{1F471}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F471}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9D3}|\\x{1F474}|\\x{1F475}|\\x{1F64D}|\\x{1F64D}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F64D}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F64E}|\\x{1F64E}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F64E}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F645}|\\x{1F645}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F645}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F646}|\\x{1F646}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F646}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F481}|\\x{1F481}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F481}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F64B}|\\x{1F64B}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F64B}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9CF}|\\x{1F9CF}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9CF}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F647}|\\x{1F647}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F647}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F926}|\\x{1F926}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F926}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F937}|\\x{1F937}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F937}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9D1}\\x{200D}\\x{2695}\\x{FE0F}|\\x{1F468}\\x{200D}\\x{2695}\\x{FE0F}|\\x{1F469}\\x{200D}\\x{2695}\\x{FE0F}|\\x{1F9D1}\\x{200D}\\x{1F393}|\\x{1F468}\\x{200D}\\x{1F393}|\\x{1F469}\\x{200D}\\x{1F393}|\\x{1F9D1}\\x{200D}\\x{1F3EB}|\\x{1F468}\\x{200D}\\x{1F3EB}|\\x{1F469}\\x{200D}\\x{1F3EB}|\\x{1F9D1}\\x{200D}\\x{2696}\\x{FE0F}|\\x{1F468}\\x{200D}\\x{2696}\\x{FE0F}|\\x{1F469}\\x{200D}\\x{2696}\\x{FE0F}|\\x{1F9D1}\\x{200D}\\x{1F33E}|\\x{1F468}\\x{200D}\\x{1F33E}|\\x{1F469}\\x{200D}\\x{1F33E}|\\x{1F9D1}\\x{200D}\\x{1F373}|\\x{1F468}\\x{200D}\\x{1F373}|\\x{1F469}\\x{200D}\\x{1F373}|\\x{1F9D1}\\x{200D}\\x{1F527}|\\x{1F468}\\x{200D}\\x{1F527}|\\x{1F469}\\x{200D}\\x{1F527}|\\x{1F9D1}\\x{200D}\\x{1F3ED}|\\x{1F468}\\x{200D}\\x{1F3ED}|\\x{1F469}\\x{200D}\\x{1F3ED}|\\x{1F9D1}\\x{200D}\\x{1F4BC}|\\x{1F468}\\x{200D}\\x{1F4BC}|\\x{1F469}\\x{200D}\\x{1F4BC}|\\x{1F9D1}\\x{200D}\\x{1F52C}|\\x{1F468}\\x{200D}\\x{1F52C}|\\x{1F469}\\x{200D}\\x{1F52C}|\\x{1F9D1}\\x{200D}\\x{1F4BB}|\\x{1F468}\\x{200D}\\x{1F4BB}|\\x{1F469}\\x{200D}\\x{1F4BB}|\\x{1F9D1}\\x{200D}\\x{1F3A4}|\\x{1F468}\\x{200D}\\x{1F3A4}|\\x{1F469}\\x{200D}\\x{1F3A4}|\\x{1F9D1}\\x{200D}\\x{1F3A8}|\\x{1F468}\\x{200D}\\x{1F3A8}|\\x{1F469}\\x{200D}\\x{1F3A8}|\\x{1F9D1}\\x{200D}\\x{2708}\\x{FE0F}|\\x{1F468}\\x{200D}\\x{2708}\\x{FE0F}|\\x{1F469}\\x{200D}\\x{2708}\\x{FE0F}|\\x{1F9D1}\\x{200D}\\x{1F680}|\\x{1F468}\\x{200D}\\x{1F680}|\\x{1F469}\\x{200D}\\x{1F680}|\\x{1F9D1}\\x{200D}\\x{1F692}|\\x{1F468}\\x{200D}\\x{1F692}|\\x{1F469}\\x{200D}\\x{1F692}|\\x{1F46E}|\\x{1F46E}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F46E}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F575}|\\x{1F575}\\x{FE0F}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F575}\\x{FE0F}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F482}|\\x{1F482}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F482}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F977}|\\x{1F477}|\\x{1F477}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F477}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F934}|\\x{1F478}|\\x{1F473}|\\x{1F473}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F473}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F472}|\\x{1F9D5}|\\x{1F935}|\\x{1F935}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F935}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F470}|\\x{1F470}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F470}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F930}|\\x{1F931}|\\x{1F469}\\x{200D}\\x{1F37C}|\\x{1F468}\\x{200D}\\x{1F37C}|\\x{1F9D1}\\x{200D}\\x{1F37C}|\\x{1F47C}|\\x{1F385}|\\x{1F936}|\\x{1F9D1}\\x{200D}\\x{1F384}|\\x{1F9B8}|\\x{1F9B8}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9B8}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9B9}|\\x{1F9B9}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9B9}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9D9}|\\x{1F9D9}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9D9}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9DA}|\\x{1F9DA}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9DA}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9DB}|\\x{1F9DB}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9DB}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9DC}|\\x{1F9DC}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9DC}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9DD}|\\x{1F9DD}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9DD}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9DE}|\\x{1F9DE}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9DE}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9DF}|\\x{1F9DF}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9DF}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F486}|\\x{1F486}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F486}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F487}|\\x{1F487}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F487}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F6B6}|\\x{1F6B6}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F6B6}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9CD}|\\x{1F9CD}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9CD}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9CE}|\\x{1F9CE}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9CE}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9D1}\\x{200D}\\x{1F9AF}|\\x{1F468}\\x{200D}\\x{1F9AF}|\\x{1F469}\\x{200D}\\x{1F9AF}|\\x{1F9D1}\\x{200D}\\x{1F9BC}|\\x{1F468}\\x{200D}\\x{1F9BC}|\\x{1F469}\\x{200D}\\x{1F9BC}|\\x{1F9D1}\\x{200D}\\x{1F9BD}|\\x{1F468}\\x{200D}\\x{1F9BD}|\\x{1F469}\\x{200D}\\x{1F9BD}|\\x{1F3C3}|\\x{1F3C3}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F3C3}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F483}|\\x{1F57A}|\\x{1F574}|\\x{1F46F}|\\x{1F46F}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F46F}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9D6}|\\x{1F9D6}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9D6}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9D7}|\\x{1F9D7}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9D7}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F93A}|\\x{1F3C7}|\\x{26F7}|\\x{1F3C2}|\\x{1F3CC}|\\x{1F3CC}\\x{FE0F}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F3CC}\\x{FE0F}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F3C4}|\\x{1F3C4}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F3C4}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F6A3}|\\x{1F6A3}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F6A3}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F3CA}|\\x{1F3CA}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F3CA}\\x{200D}\\x{2640}\\x{FE0F}|\\x{26F9}|\\x{26F9}\\x{FE0F}\\x{200D}\\x{2642}\\x{FE0F}|\\x{26F9}\\x{FE0F}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F3CB}|\\x{1F3CB}\\x{FE0F}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F3CB}\\x{FE0F}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F6B4}|\\x{1F6B4}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F6B4}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F6B5}|\\x{1F6B5}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F6B5}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F938}|\\x{1F938}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F938}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F93C}|\\x{1F93C}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F93C}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F93D}|\\x{1F93D}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F93D}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F93E}|\\x{1F93E}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F93E}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F939}|\\x{1F939}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F939}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9D8}|\\x{1F9D8}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9D8}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F6C0}|\\x{1F6CC}|\\x{1F9D1}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F9D1}|\\x{1F46D}|\\x{1F46B}|\\x{1F46C}|\\x{1F48F}|\\x{1F469}\\x{200D}\\x{2764}\\x{FE0F}\\x{200D}\\x{1F48B}\\x{200D}\\x{1F468}|\\x{1F468}\\x{200D}\\x{2764}\\x{FE0F}\\x{200D}\\x{1F48B}\\x{200D}\\x{1F468}|\\x{1F469}\\x{200D}\\x{2764}\\x{FE0F}\\x{200D}\\x{1F48B}\\x{200D}\\x{1F469}|\\x{1F491}|\\x{1F469}\\x{200D}\\x{2764}\\x{FE0F}\\x{200D}\\x{1F468}|\\x{1F468}\\x{200D}\\x{2764}\\x{FE0F}\\x{200D}\\x{1F468}|\\x{1F469}\\x{200D}\\x{2764}\\x{FE0F}\\x{200D}\\x{1F469}|\\x{1F46A}|\\x{1F468}\\x{200D}\\x{1F469}\\x{200D}\\x{1F466}|\\x{1F468}\\x{200D}\\x{1F469}\\x{200D}\\x{1F467}|\\x{1F468}\\x{200D}\\x{1F469}\\x{200D}\\x{1F467}\\x{200D}\\x{1F466}|\\x{1F468}\\x{200D}\\x{1F469}\\x{200D}\\x{1F466}\\x{200D}\\x{1F466}|\\x{1F468}\\x{200D}\\x{1F469}\\x{200D}\\x{1F467}\\x{200D}\\x{1F467}|\\x{1F468}\\x{200D}\\x{1F468}\\x{200D}\\x{1F466}|\\x{1F468}\\x{200D}\\x{1F468}\\x{200D}\\x{1F467}|\\x{1F468}\\x{200D}\\x{1F468}\\x{200D}\\x{1F467}\\x{200D}\\x{1F466}|\\x{1F468}\\x{200D}\\x{1F468}\\x{200D}\\x{1F466}\\x{200D}\\x{1F466}|\\x{1F468}\\x{200D}\\x{1F468}\\x{200D}\\x{1F467}\\x{200D}\\x{1F467}|\\x{1F469}\\x{200D}\\x{1F469}\\x{200D}\\x{1F466}|\\x{1F469}\\x{200D}\\x{1F469}\\x{200D}\\x{1F467}|\\x{1F469}\\x{200D}\\x{1F469}\\x{200D}\\x{1F467}\\x{200D}\\x{1F466}|\\x{1F469}\\x{200D}\\x{1F469}\\x{200D}\\x{1F466}\\x{200D}\\x{1F466}|\\x{1F469}\\x{200D}\\x{1F469}\\x{200D}\\x{1F467}\\x{200D}\\x{1F467}|\\x{1F468}\\x{200D}\\x{1F466}|\\x{1F468}\\x{200D}\\x{1F466}\\x{200D}\\x{1F466}|\\x{1F468}\\x{200D}\\x{1F467}|\\x{1F468}\\x{200D}\\x{1F467}\\x{200D}\\x{1F466}|\\x{1F468}\\x{200D}\\x{1F467}\\x{200D}\\x{1F467}|\\x{1F469}\\x{200D}\\x{1F466}|\\x{1F469}\\x{200D}\\x{1F466}\\x{200D}\\x{1F466}|\\x{1F469}\\x{200D}\\x{1F467}|\\x{1F469}\\x{200D}\\x{1F467}\\x{200D}\\x{1F466}|\\x{1F469}\\x{200D}\\x{1F467}\\x{200D}\\x{1F467}|\\x{1F5E3}|\\x{1F464}|\\x{1F465}|\\x{1FAC2}|\\x{1F463}|\\x{1F9B0}|\\x{1F9B1}|\\x{1F9B3}|\\x{1F9B2}|\\x{1F435}|\\x{1F412}|\\x{1F98D}|\\x{1F9A7}|\\x{1F436}|\\x{1F415}|\\x{1F9AE}|\\x{1F415}\\x{200D}\\x{1F9BA}|\\x{1F429}|\\x{1F43A}|\\x{1F98A}|\\x{1F99D}|\\x{1F431}|\\x{1F408}|\\x{1F408}\\x{200D}\\x{2B1B}|\\x{1F981}|\\x{1F42F}|\\x{1F405}|\\x{1F406}|\\x{1F434}|\\x{1F40E}|\\x{1F984}|\\x{1F993}|\\x{1F98C}|\\x{1F9AC}|\\x{1F42E}|\\x{1F402}|\\x{1F403}|\\x{1F404}|\\x{1F437}|\\x{1F416}|\\x{1F417}|\\x{1F43D}|\\x{1F40F}|\\x{1F411}|\\x{1F410}|\\x{1F42A}|\\x{1F42B}|\\x{1F999}|\\x{1F992}|\\x{1F418}|\\x{1F9A3}|\\x{1F98F}|\\x{1F99B}|\\x{1F42D}|\\x{1F401}|\\x{1F400}|\\x{1F439}|\\x{1F430}|\\x{1F407}|\\x{1F43F}|\\x{1F9AB}|\\x{1F994}|\\x{1F987}|\\x{1F43B}|\\x{1F43B}\\x{200D}\\x{2744}\\x{FE0F}|\\x{1F428}|\\x{1F43C}|\\x{1F9A5}|\\x{1F9A6}|\\x{1F9A8}|\\x{1F998}|\\x{1F9A1}|\\x{1F43E}|\\x{1F983}|\\x{1F414}|\\x{1F413}|\\x{1F423}|\\x{1F424}|\\x{1F425}|\\x{1F426}|\\x{1F427}|\\x{1F54A}|\\x{1F985}|\\x{1F986}|\\x{1F9A2}|\\x{1F989}|\\x{1F9A4}|\\x{1FAB6}|\\x{1F9A9}|\\x{1F99A}|\\x{1F99C}|\\x{1F438}|\\x{1F40A}|\\x{1F422}|\\x{1F98E}|\\x{1F40D}|\\x{1F432}|\\x{1F409}|\\x{1F995}|\\x{1F996}|\\x{1F433}|\\x{1F40B}|\\x{1F42C}|\\x{1F9AD}|\\x{1F41F}|\\x{1F420}|\\x{1F421}|\\x{1F988}|\\x{1F419}|\\x{1F41A}|\\x{1F40C}|\\x{1F98B}|\\x{1F41B}|\\x{1F41C}|\\x{1F41D}|\\x{1FAB2}|\\x{1F41E}|\\x{1F997}|\\x{1FAB3}|\\x{1F577}|\\x{1F578}|\\x{1F982}|\\x{1F99F}|\\x{1FAB0}|\\x{1FAB1}|\\x{1F9A0}|\\x{1F490}|\\x{1F338}|\\x{1F4AE}|\\x{1F3F5}|\\x{1F339}|\\x{1F940}|\\x{1F33A}|\\x{1F33B}|\\x{1F33C}|\\x{1F337}|\\x{1F331}|\\x{1FAB4}|\\x{1F332}|\\x{1F333}|\\x{1F334}|\\x{1F335}|\\x{1F33E}|\\x{1F33F}|\\x{2618}|\\x{1F340}|\\x{1F341}|\\x{1F342}|\\x{1F343}|\\x{1F347}|\\x{1F348}|\\x{1F349}|\\x{1F34A}|\\x{1F34B}|\\x{1F34C}|\\x{1F34D}|\\x{1F96D}|\\x{1F34E}|\\x{1F34F}|\\x{1F350}|\\x{1F351}|\\x{1F352}|\\x{1F353}|\\x{1FAD0}|\\x{1F95D}|\\x{1F345}|\\x{1FAD2}|\\x{1F965}|\\x{1F951}|\\x{1F346}|\\x{1F954}|\\x{1F955}|\\x{1F33D}|\\x{1F336}|\\x{1FAD1}|\\x{1F952}|\\x{1F96C}|\\x{1F966}|\\x{1F9C4}|\\x{1F9C5}|\\x{1F344}|\\x{1F95C}|\\x{1F330}|\\x{1F35E}|\\x{1F950}|\\x{1F956}|\\x{1FAD3}|\\x{1F968}|\\x{1F96F}|\\x{1F95E}|\\x{1F9C7}|\\x{1F9C0}|\\x{1F356}|\\x{1F357}|\\x{1F969}|\\x{1F953}|\\x{1F354}|\\x{1F35F}|\\x{1F355}|\\x{1F32D}|\\x{1F96A}|\\x{1F32E}|\\x{1F32F}|\\x{1FAD4}|\\x{1F959}|\\x{1F9C6}|\\x{1F95A}|\\x{1F373}|\\x{1F958}|\\x{1F372}|\\x{1FAD5}|\\x{1F963}|\\x{1F957}|\\x{1F37F}|\\x{1F9C8}|\\x{1F9C2}|\\x{1F96B}|\\x{1F371}|\\x{1F358}|\\x{1F359}|\\x{1F35A}|\\x{1F35B}|\\x{1F35C}|\\x{1F35D}|\\x{1F360}|\\x{1F362}|\\x{1F363}|\\x{1F364}|\\x{1F365}|\\x{1F96E}|\\x{1F361}|\\x{1F95F}|\\x{1F960}|\\x{1F961}|\\x{1F980}|\\x{1F99E}|\\x{1F990}|\\x{1F991}|\\x{1F9AA}|\\x{1F366}|\\x{1F367}|\\x{1F368}|\\x{1F369}|\\x{1F36A}|\\x{1F382}|\\x{1F370}|\\x{1F9C1}|\\x{1F967}|\\x{1F36B}|\\x{1F36C}|\\x{1F36D}|\\x{1F36E}|\\x{1F36F}|\\x{1F37C}|\\x{1F95B}|\\x{2615}|\\x{1FAD6}|\\x{1F375}|\\x{1F376}|\\x{1F37E}|\\x{1F377}|\\x{1F378}|\\x{1F379}|\\x{1F37A}|\\x{1F37B}|\\x{1F942}|\\x{1F943}|\\x{1F964}|\\x{1F9CB}|\\x{1F9C3}|\\x{1F9C9}|\\x{1F9CA}|\\x{1F962}|\\x{1F37D}|\\x{1F374}|\\x{1F944}|\\x{1F52A}|\\x{1F3FA}|\\x{1F30D}|\\x{1F30E}|\\x{1F30F}|\\x{1F310}|\\x{1F5FA}|\\x{1F5FE}|\\x{1F9ED}|\\x{1F3D4}|\\x{26F0}|\\x{1F30B}|\\x{1F5FB}|\\x{1F3D5}|\\x{1F3D6}|\\x{1F3DC}|\\x{1F3DD}|\\x{1F3DE}|\\x{1F3DF}|\\x{1F3DB}|\\x{1F3D7}|\\x{1F9F1}|\\x{1FAA8}|\\x{1FAB5}|\\x{1F6D6}|\\x{1F3D8}|\\x{1F3DA}|\\x{1F3E0}|\\x{1F3E1}|\\x{1F3E2}|\\x{1F3E3}|\\x{1F3E4}|\\x{1F3E5}|\\x{1F3E6}|\\x{1F3E8}|\\x{1F3E9}|\\x{1F3EA}|\\x{1F3EB}|\\x{1F3EC}|\\x{1F3ED}|\\x{1F3EF}|\\x{1F3F0}|\\x{1F492}|\\x{1F5FC}|\\x{1F5FD}|\\x{26EA}|\\x{1F54C}|\\x{1F6D5}|\\x{1F54D}|\\x{26E9}|\\x{1F54B}|\\x{26F2}|\\x{26FA}|\\x{1F301}|\\x{1F303}|\\x{1F3D9}|\\x{1F304}|\\x{1F305}|\\x{1F306}|\\x{1F307}|\\x{1F309}|\\x{2668}|\\x{1F3A0}|\\x{1F3A1}|\\x{1F3A2}|\\x{1F488}|\\x{1F3AA}|\\x{1F682}|\\x{1F683}|\\x{1F684}|\\x{1F685}|\\x{1F686}|\\x{1F687}|\\x{1F688}|\\x{1F689}|\\x{1F68A}|\\x{1F69D}|\\x{1F69E}|\\x{1F68B}|\\x{1F68C}|\\x{1F68D}|\\x{1F68E}|\\x{1F690}|\\x{1F691}|\\x{1F692}|\\x{1F693}|\\x{1F694}|\\x{1F695}|\\x{1F696}|\\x{1F697}|\\x{1F698}|\\x{1F699}|\\x{1F6FB}|\\x{1F69A}|\\x{1F69B}|\\x{1F69C}|\\x{1F3CE}|\\x{1F3CD}|\\x{1F6F5}|\\x{1F9BD}|\\x{1F9BC}|\\x{1F6FA}|\\x{1F6B2}|\\x{1F6F4}|\\x{1F6F9}|\\x{1F6FC}|\\x{1F68F}|\\x{1F6E3}|\\x{1F6E4}|\\x{1F6E2}|\\x{26FD}|\\x{1F6A8}|\\x{1F6A5}|\\x{1F6A6}|\\x{1F6D1}|\\x{1F6A7}|\\x{2693}|\\x{26F5}|\\x{1F6F6}|\\x{1F6A4}|\\x{1F6F3}|\\x{26F4}|\\x{1F6E5}|\\x{1F6A2}|\\x{2708}|\\x{1F6E9}|\\x{1F6EB}|\\x{1F6EC}|\\x{1FA82}|\\x{1F4BA}|\\x{1F681}|\\x{1F69F}|\\x{1F6A0}|\\x{1F6A1}|\\x{1F6F0}|\\x{1F680}|\\x{1F6F8}|\\x{1F6CE}|\\x{1F9F3}|\\x{231B}|\\x{23F3}|\\x{231A}|\\x{23F0}|\\x{23F1}|\\x{23F2}|\\x{1F570}|\\x{1F55B}|\\x{1F567}|\\x{1F550}|\\x{1F55C}|\\x{1F551}|\\x{1F55D}|\\x{1F552}|\\x{1F55E}|\\x{1F553}|\\x{1F55F}|\\x{1F554}|\\x{1F560}|\\x{1F555}|\\x{1F561}|\\x{1F556}|\\x{1F562}|\\x{1F557}|\\x{1F563}|\\x{1F558}|\\x{1F564}|\\x{1F559}|\\x{1F565}|\\x{1F55A}|\\x{1F566}|\\x{1F311}|\\x{1F312}|\\x{1F313}|\\x{1F314}|\\x{1F315}|\\x{1F316}|\\x{1F317}|\\x{1F318}|\\x{1F319}|\\x{1F31A}|\\x{1F31B}|\\x{1F31C}|\\x{1F321}|\\x{2600}|\\x{1F31D}|\\x{1F31E}|\\x{1FA90}|\\x{2B50}|\\x{1F31F}|\\x{1F320}|\\x{1F30C}|\\x{2601}|\\x{26C5}|\\x{26C8}|\\x{1F324}|\\x{1F325}|\\x{1F326}|\\x{1F327}|\\x{1F328}|\\x{1F329}|\\x{1F32A}|\\x{1F32B}|\\x{1F32C}|\\x{1F300}|\\x{1F308}|\\x{1F302}|\\x{2602}|\\x{2614}|\\x{26F1}|\\x{26A1}|\\x{2744}|\\x{2603}|\\x{26C4}|\\x{2604}|\\x{1F525}|\\x{1F4A7}|\\x{1F30A}|\\x{1F383}|\\x{1F384}|\\x{1F386}|\\x{1F387}|\\x{1F9E8}|\\x{2728}|\\x{1F388}|\\x{1F389}|\\x{1F38A}|\\x{1F38B}|\\x{1F38D}|\\x{1F38E}|\\x{1F38F}|\\x{1F390}|\\x{1F391}|\\x{1F9E7}|\\x{1F380}|\\x{1F381}|\\x{1F397}|\\x{1F39F}|\\x{1F3AB}|\\x{1F396}|\\x{1F3C6}|\\x{1F3C5}|\\x{1F947}|\\x{1F948}|\\x{1F949}|\\x{26BD}|\\x{26BE}|\\x{1F94E}|\\x{1F3C0}|\\x{1F3D0}|\\x{1F3C8}|\\x{1F3C9}|\\x{1F3BE}|\\x{1F94F}|\\x{1F3B3}|\\x{1F3CF}|\\x{1F3D1}|\\x{1F3D2}|\\x{1F94D}|\\x{1F3D3}|\\x{1F3F8}|\\x{1F94A}|\\x{1F94B}|\\x{1F945}|\\x{26F3}|\\x{26F8}|\\x{1F3A3}|\\x{1F93F}|\\x{1F3BD}|\\x{1F3BF}|\\x{1F6F7}|\\x{1F94C}|\\x{1F3AF}|\\x{1FA80}|\\x{1FA81}|\\x{1F3B1}|\\x{1F52E}|\\x{1FA84}|\\x{1F9FF}|\\x{1F3AE}|\\x{1F579}|\\x{1F3B0}|\\x{1F3B2}|\\x{1F9E9}|\\x{1F9F8}|\\x{1FA85}|\\x{1FA86}|\\x{2660}|\\x{2665}|\\x{2666}|\\x{2663}|\\x{265F}|\\x{1F0CF}|\\x{1F004}|\\x{1F3B4}|\\x{1F3AD}|\\x{1F5BC}|\\x{1F3A8}|\\x{1F9F5}|\\x{1FAA1}|\\x{1F9F6}|\\x{1FAA2}|\\x{1F453}|\\x{1F576}|\\x{1F97D}|\\x{1F97C}|\\x{1F9BA}|\\x{1F454}|\\x{1F455}|\\x{1F456}|\\x{1F9E3}|\\x{1F9E4}|\\x{1F9E5}|\\x{1F9E6}|\\x{1F457}|\\x{1F458}|\\x{1F97B}|\\x{1FA71}|\\x{1FA72}|\\x{1FA73}|\\x{1F459}|\\x{1F45A}|\\x{1F45B}|\\x{1F45C}|\\x{1F45D}|\\x{1F6CD}|\\x{1F392}|\\x{1FA74}|\\x{1F45E}|\\x{1F45F}|\\x{1F97E}|\\x{1F97F}|\\x{1F460}|\\x{1F461}|\\x{1FA70}|\\x{1F462}|\\x{1F451}|\\x{1F452}|\\x{1F3A9}|\\x{1F393}|\\x{1F9E2}|\\x{1FA96}|\\x{26D1}|\\x{1F4FF}|\\x{1F484}|\\x{1F48D}|\\x{1F48E}|\\x{1F507}|\\x{1F508}|\\x{1F509}|\\x{1F50A}|\\x{1F4E2}|\\x{1F4E3}|\\x{1F4EF}|\\x{1F514}|\\x{1F515}|\\x{1F3BC}|\\x{1F3B5}|\\x{1F3B6}|\\x{1F399}|\\x{1F39A}|\\x{1F39B}|\\x{1F3A4}|\\x{1F3A7}|\\x{1F4FB}|\\x{1F3B7}|\\x{1FA97}|\\x{1F3B8}|\\x{1F3B9}|\\x{1F3BA}|\\x{1F3BB}|\\x{1FA95}|\\x{1F941}|\\x{1FA98}|\\x{1F4F1}|\\x{1F4F2}|\\x{260E}|\\x{1F4DE}|\\x{1F4DF}|\\x{1F4E0}|\\x{1F50B}|\\x{1F50C}|\\x{1F4BB}|\\x{1F5A5}|\\x{1F5A8}|\\x{2328}|\\x{1F5B1}|\\x{1F5B2}|\\x{1F4BD}|\\x{1F4BE}|\\x{1F4BF}|\\x{1F4C0}|\\x{1F9EE}|\\x{1F3A5}|\\x{1F39E}|\\x{1F4FD}|\\x{1F3AC}|\\x{1F4FA}|\\x{1F4F7}|\\x{1F4F8}|\\x{1F4F9}|\\x{1F4FC}|\\x{1F50D}|\\x{1F50E}|\\x{1F56F}|\\x{1F4A1}|\\x{1F526}|\\x{1F3EE}|\\x{1FA94}|\\x{1F4D4}|\\x{1F4D5}|\\x{1F4D6}|\\x{1F4D7}|\\x{1F4D8}|\\x{1F4D9}|\\x{1F4DA}|\\x{1F4D3}|\\x{1F4D2}|\\x{1F4C3}|\\x{1F4DC}|\\x{1F4C4}|\\x{1F4F0}|\\x{1F5DE}|\\x{1F4D1}|\\x{1F516}|\\x{1F3F7}|\\x{1F4B0}|\\x{1FA99}|\\x{1F4B4}|\\x{1F4B5}|\\x{1F4B6}|\\x{1F4B7}|\\x{1F4B8}|\\x{1F4B3}|\\x{1F9FE}|\\x{1F4B9}|\\x{2709}|\\x{1F4E7}|\\x{1F4E8}|\\x{1F4E9}|\\x{1F4E4}|\\x{1F4E5}|\\x{1F4E6}|\\x{1F4EB}|\\x{1F4EA}|\\x{1F4EC}|\\x{1F4ED}|\\x{1F4EE}|\\x{1F5F3}|\\x{270F}|\\x{2712}|\\x{1F58B}|\\x{1F58A}|\\x{1F58C}|\\x{1F58D}|\\x{1F4DD}|\\x{1F4BC}|\\x{1F4C1}|\\x{1F4C2}|\\x{1F5C2}|\\x{1F4C5}|\\x{1F4C6}|\\x{1F5D2}|\\x{1F5D3}|\\x{1F4C7}|\\x{1F4C8}|\\x{1F4C9}|\\x{1F4CA}|\\x{1F4CB}|\\x{1F4CC}|\\x{1F4CD}|\\x{1F4CE}|\\x{1F587}|\\x{1F4CF}|\\x{1F4D0}|\\x{2702}|\\x{1F5C3}|\\x{1F5C4}|\\x{1F5D1}|\\x{1F512}|\\x{1F513}|\\x{1F50F}|\\x{1F510}|\\x{1F511}|\\x{1F5DD}|\\x{1F528}|\\x{1FA93}|\\x{26CF}|\\x{2692}|\\x{1F6E0}|\\x{1F5E1}|\\x{2694}|\\x{1F52B}|\\x{1FA83}|\\x{1F3F9}|\\x{1F6E1}|\\x{1FA9A}|\\x{1F527}|\\x{1FA9B}|\\x{1F529}|\\x{2699}|\\x{1F5DC}|\\x{2696}|\\x{1F9AF}|\\x{1F517}|\\x{26D3}|\\x{1FA9D}|\\x{1F9F0}|\\x{1F9F2}|\\x{1FA9C}|\\x{2697}|\\x{1F9EA}|\\x{1F9EB}|\\x{1F9EC}|\\x{1F52C}|\\x{1F52D}|\\x{1F4E1}|\\x{1F489}|\\x{1FA78}|\\x{1F48A}|\\x{1FA79}|\\x{1FA7A}|\\x{1F6AA}|\\x{1F6D7}|\\x{1FA9E}|\\x{1FA9F}|\\x{1F6CF}|\\x{1F6CB}|\\x{1FA91}|\\x{1F6BD}|\\x{1FAA0}|\\x{1F6BF}|\\x{1F6C1}|\\x{1FAA4}|\\x{1FA92}|\\x{1F9F4}|\\x{1F9F7}|\\x{1F9F9}|\\x{1F9FA}|\\x{1F9FB}|\\x{1FAA3}|\\x{1F9FC}|\\x{1FAA5}|\\x{1F9FD}|\\x{1F9EF}|\\x{1F6D2}|\\x{1F6AC}|\\x{26B0}|\\x{1FAA6}|\\x{26B1}|\\x{1F5FF}|\\x{1FAA7}|\\x{1F3E7}|\\x{1F6AE}|\\x{1F6B0}|\\x{267F}|\\x{1F6B9}|\\x{1F6BA}|\\x{1F6BB}|\\x{1F6BC}|\\x{1F6BE}|\\x{1F6C2}|\\x{1F6C3}|\\x{1F6C4}|\\x{1F6C5}|\\x{26A0}|\\x{1F6B8}|\\x{26D4}|\\x{1F6AB}|\\x{1F6B3}|\\x{1F6AD}|\\x{1F6AF}|\\x{1F6B1}|\\x{1F6B7}|\\x{1F4F5}|\\x{1F51E}|\\x{2622}|\\x{2623}|\\x{2B06}|\\x{2197}|\\x{27A1}|\\x{2198}|\\x{2B07}|\\x{2199}|\\x{2B05}|\\x{2196}|\\x{2195}|\\x{2194}|\\x{21A9}|\\x{21AA}|\\x{2934}|\\x{2935}|\\x{1F503}|\\x{1F504}|\\x{1F519}|\\x{1F51A}|\\x{1F51B}|\\x{1F51C}|\\x{1F51D}|\\x{1F6D0}|\\x{269B}|\\x{1F549}|\\x{2721}|\\x{2638}|\\x{262F}|\\x{271D}|\\x{2626}|\\x{262A}|\\x{262E}|\\x{1F54E}|\\x{1F52F}|\\x{2648}|\\x{2649}|\\x{264A}|\\x{264B}|\\x{264C}|\\x{264D}|\\x{264E}|\\x{264F}|\\x{2650}|\\x{2651}|\\x{2652}|\\x{2653}|\\x{26CE}|\\x{1F500}|\\x{1F501}|\\x{1F502}|\\x{25B6}|\\x{23E9}|\\x{23ED}|\\x{23EF}|\\x{25C0}|\\x{23EA}|\\x{23EE}|\\x{1F53C}|\\x{23EB}|\\x{1F53D}|\\x{23EC}|\\x{23F8}|\\x{23F9}|\\x{23FA}|\\x{23CF}|\\x{1F3A6}|\\x{1F505}|\\x{1F506}|\\x{1F4F6}|\\x{1F4F3}|\\x{1F4F4}|\\x{2640}|\\x{2642}|\\x{26A7}|\\x{2716}|\\x{2795}|\\x{2796}|\\x{2797}|\\x{267E}|\\x{203C}|\\x{2049}|\\x{2753}|\\x{2754}|\\x{2755}|\\x{2757}|\\x{3030}|\\x{1F4B1}|\\x{1F4B2}|\\x{2695}|\\x{267B}|\\x{269C}|\\x{1F531}|\\x{1F4DB}|\\x{1F530}|\\x{2B55}|\\x{2705}|\\x{2611}|\\x{2714}|\\x{274C}|\\x{274E}|\\x{27B0}|\\x{27BF}|\\x{303D}|\\x{2733}|\\x{2734}|\\x{2747}|\\x{00A9}|\\x{00AE}|\\x{2122}|\\x{0023}\\x{FE0F}\\x{20E3}|\\x{002A}\\x{FE0F}\\x{20E3}|\\x{0030}\\x{FE0F}\\x{20E3}|\\x{0031}\\x{FE0F}\\x{20E3}|\\x{0032}\\x{FE0F}\\x{20E3}|\\x{0033}\\x{FE0F}\\x{20E3}|\\x{0034}\\x{FE0F}\\x{20E3}|\\x{0035}\\x{FE0F}\\x{20E3}|\\x{0036}\\x{FE0F}\\x{20E3}|\\x{0037}\\x{FE0F}\\x{20E3}|\\x{0038}\\x{FE0F}\\x{20E3}|\\x{0039}\\x{FE0F}\\x{20E3}|\\x{1F51F}|\\x{1F520}|\\x{1F521}|\\x{1F522}|\\x{1F523}|\\x{1F524}|\\x{1F170}|\\x{1F18E}|\\x{1F171}|\\x{1F191}|\\x{1F192}|\\x{1F193}|\\x{2139}|\\x{1F194}|\\x{24C2}|\\x{1F195}|\\x{1F196}|\\x{1F17E}|\\x{1F197}|\\x{1F17F}|\\x{1F198}|\\x{1F199}|\\x{1F19A}|\\x{1F201}|\\x{1F202}|\\x{1F237}|\\x{1F236}|\\x{1F22F}|\\x{1F250}|\\x{1F239}|\\x{1F21A}|\\x{1F232}|\\x{1F251}|\\x{1F238}|\\x{1F234}|\\x{1F233}|\\x{3297}|\\x{3299}|\\x{1F23A}|\\x{1F235}|\\x{1F534}|\\x{1F7E0}|\\x{1F7E1}|\\x{1F7E2}|\\x{1F535}|\\x{1F7E3}|\\x{1F7E4}|\\x{26AB}|\\x{26AA}|\\x{1F7E5}|\\x{1F7E7}|\\x{1F7E8}|\\x{1F7E9}|\\x{1F7E6}|\\x{1F7EA}|\\x{1F7EB}|\\x{2B1B}|\\x{2B1C}|\\x{25FC}|\\x{25FB}|\\x{25FE}|\\x{25FD}|\\x{25AA}|\\x{25AB}|\\x{1F536}|\\x{1F537}|\\x{1F538}|\\x{1F539}|\\x{1F53A}|\\x{1F53B}|\\x{1F4A0}|\\x{1F518}|\\x{1F533}|\\x{1F532}|\\x{1F3C1}|\\x{1F6A9}|\\x{1F38C}|\\x{1F3F4}|\\x{1F3F3}|\\x{1F3F3}\\x{FE0F}\\x{200D}\\x{1F308}|\\x{1F3F3}\\x{FE0F}\\x{200D}\\x{26A7}\\x{FE0F}|\\x{1F3F4}\\x{200D}\\x{2620}\\x{FE0F}|\\x{1F1E6}\\x{1F1E8}|\\x{1F1E6}\\x{1F1E9}|\\x{1F1E6}\\x{1F1EA}|\\x{1F1E6}\\x{1F1EB}|\\x{1F1E6}\\x{1F1EC}|\\x{1F1E6}\\x{1F1EE}|\\x{1F1E6}\\x{1F1F1}|\\x{1F1E6}\\x{1F1F2}|\\x{1F1E6}\\x{1F1F4}|\\x{1F1E6}\\x{1F1F6}|\\x{1F1E6}\\x{1F1F7}|\\x{1F1E6}\\x{1F1F8}|\\x{1F1E6}\\x{1F1F9}|\\x{1F1E6}\\x{1F1FA}|\\x{1F1E6}\\x{1F1FC}|\\x{1F1E6}\\x{1F1FD}|\\x{1F1E6}\\x{1F1FF}|\\x{1F1E7}\\x{1F1E6}|\\x{1F1E7}\\x{1F1E7}|\\x{1F1E7}\\x{1F1E9}|\\x{1F1E7}\\x{1F1EA}|\\x{1F1E7}\\x{1F1EB}|\\x{1F1E7}\\x{1F1EC}|\\x{1F1E7}\\x{1F1ED}|\\x{1F1E7}\\x{1F1EE}|\\x{1F1E7}\\x{1F1EF}|\\x{1F1E7}\\x{1F1F1}|\\x{1F1E7}\\x{1F1F2}|\\x{1F1E7}\\x{1F1F3}|\\x{1F1E7}\\x{1F1F4}|\\x{1F1E7}\\x{1F1F6}|\\x{1F1E7}\\x{1F1F7}|\\x{1F1E7}\\x{1F1F8}|\\x{1F1E7}\\x{1F1F9}|\\x{1F1E7}\\x{1F1FB}|\\x{1F1E7}\\x{1F1FC}|\\x{1F1E7}\\x{1F1FE}|\\x{1F1E7}\\x{1F1FF}|\\x{1F1E8}\\x{1F1E6}|\\x{1F1E8}\\x{1F1E8}|\\x{1F1E8}\\x{1F1E9}|\\x{1F1E8}\\x{1F1EB}|\\x{1F1E8}\\x{1F1EC}|\\x{1F1E8}\\x{1F1ED}|\\x{1F1E8}\\x{1F1EE}|\\x{1F1E8}\\x{1F1F0}|\\x{1F1E8}\\x{1F1F1}|\\x{1F1E8}\\x{1F1F2}|\\x{1F1E8}\\x{1F1F3}|\\x{1F1E8}\\x{1F1F4}|\\x{1F1E8}\\x{1F1F5}|\\x{1F1E8}\\x{1F1F7}|\\x{1F1E8}\\x{1F1FA}|\\x{1F1E8}\\x{1F1FB}|\\x{1F1E8}\\x{1F1FC}|\\x{1F1E8}\\x{1F1FD}|\\x{1F1E8}\\x{1F1FE}|\\x{1F1E8}\\x{1F1FF}|\\x{1F1E9}\\x{1F1EA}|\\x{1F1E9}\\x{1F1EC}|\\x{1F1E9}\\x{1F1EF}|\\x{1F1E9}\\x{1F1F0}|\\x{1F1E9}\\x{1F1F2}|\\x{1F1E9}\\x{1F1F4}|\\x{1F1E9}\\x{1F1FF}|\\x{1F1EA}\\x{1F1E6}|\\x{1F1EA}\\x{1F1E8}|\\x{1F1EA}\\x{1F1EA}|\\x{1F1EA}\\x{1F1EC}|\\x{1F1EA}\\x{1F1ED}|\\x{1F1EA}\\x{1F1F7}|\\x{1F1EA}\\x{1F1F8}|\\x{1F1EA}\\x{1F1F9}|\\x{1F1EA}\\x{1F1FA}|\\x{1F1EB}\\x{1F1EE}|\\x{1F1EB}\\x{1F1EF}|\\x{1F1EB}\\x{1F1F0}|\\x{1F1EB}\\x{1F1F2}|\\x{1F1EB}\\x{1F1F4}|\\x{1F1EB}\\x{1F1F7}|\\x{1F1EC}\\x{1F1E6}|\\x{1F1EC}\\x{1F1E7}|\\x{1F1EC}\\x{1F1E9}|\\x{1F1EC}\\x{1F1EA}|\\x{1F1EC}\\x{1F1EB}|\\x{1F1EC}\\x{1F1EC}|\\x{1F1EC}\\x{1F1ED}|\\x{1F1EC}\\x{1F1EE}|\\x{1F1EC}\\x{1F1F1}|\\x{1F1EC}\\x{1F1F2}|\\x{1F1EC}\\x{1F1F3}|\\x{1F1EC}\\x{1F1F5}|\\x{1F1EC}\\x{1F1F6}|\\x{1F1EC}\\x{1F1F7}|\\x{1F1EC}\\x{1F1F8}|\\x{1F1EC}\\x{1F1F9}|\\x{1F1EC}\\x{1F1FA}|\\x{1F1EC}\\x{1F1FC}|\\x{1F1EC}\\x{1F1FE}|\\x{1F1ED}\\x{1F1F0}|\\x{1F1ED}\\x{1F1F2}|\\x{1F1ED}\\x{1F1F3}|\\x{1F1ED}\\x{1F1F7}|\\x{1F1ED}\\x{1F1F9}|\\x{1F1ED}\\x{1F1FA}|\\x{1F1EE}\\x{1F1E8}|\\x{1F1EE}\\x{1F1E9}|\\x{1F1EE}\\x{1F1EA}|\\x{1F1EE}\\x{1F1F1}|\\x{1F1EE}\\x{1F1F2}|\\x{1F1EE}\\x{1F1F3}|\\x{1F1EE}\\x{1F1F4}|\\x{1F1EE}\\x{1F1F6}|\\x{1F1EE}\\x{1F1F7}|\\x{1F1EE}\\x{1F1F8}|\\x{1F1EE}\\x{1F1F9}|\\x{1F1EF}\\x{1F1EA}|\\x{1F1EF}\\x{1F1F2}|\\x{1F1EF}\\x{1F1F4}|\\x{1F1EF}\\x{1F1F5}|\\x{1F1F0}\\x{1F1EA}|\\x{1F1F0}\\x{1F1EC}|\\x{1F1F0}\\x{1F1ED}|\\x{1F1F0}\\x{1F1EE}|\\x{1F1F0}\\x{1F1F2}|\\x{1F1F0}\\x{1F1F3}|\\x{1F1F0}\\x{1F1F5}|\\x{1F1F0}\\x{1F1F7}|\\x{1F1F0}\\x{1F1FC}|\\x{1F1F0}\\x{1F1FE}|\\x{1F1F0}\\x{1F1FF}|\\x{1F1F1}\\x{1F1E6}|\\x{1F1F1}\\x{1F1E7}|\\x{1F1F1}\\x{1F1E8}|\\x{1F1F1}\\x{1F1EE}|\\x{1F1F1}\\x{1F1F0}|\\x{1F1F1}\\x{1F1F7}|\\x{1F1F1}\\x{1F1F8}|\\x{1F1F1}\\x{1F1F9}|\\x{1F1F1}\\x{1F1FA}|\\x{1F1F1}\\x{1F1FB}|\\x{1F1F1}\\x{1F1FE}|\\x{1F1F2}\\x{1F1E6}|\\x{1F1F2}\\x{1F1E8}|\\x{1F1F2}\\x{1F1E9}|\\x{1F1F2}\\x{1F1EA}|\\x{1F1F2}\\x{1F1EB}|\\x{1F1F2}\\x{1F1EC}|\\x{1F1F2}\\x{1F1ED}|\\x{1F1F2}\\x{1F1F0}|\\x{1F1F2}\\x{1F1F1}|\\x{1F1F2}\\x{1F1F2}|\\x{1F1F2}\\x{1F1F3}|\\x{1F1F2}\\x{1F1F4}|\\x{1F1F2}\\x{1F1F5}|\\x{1F1F2}\\x{1F1F6}|\\x{1F1F2}\\x{1F1F7}|\\x{1F1F2}\\x{1F1F8}|\\x{1F1F2}\\x{1F1F9}|\\x{1F1F2}\\x{1F1FA}|\\x{1F1F2}\\x{1F1FB}|\\x{1F1F2}\\x{1F1FC}|\\x{1F1F2}\\x{1F1FD}|\\x{1F1F2}\\x{1F1FE}|\\x{1F1F2}\\x{1F1FF}|\\x{1F1F3}\\x{1F1E6}|\\x{1F1F3}\\x{1F1E8}|\\x{1F1F3}\\x{1F1EA}|\\x{1F1F3}\\x{1F1EB}|\\x{1F1F3}\\x{1F1EC}|\\x{1F1F3}\\x{1F1EE}|\\x{1F1F3}\\x{1F1F1}|\\x{1F1F3}\\x{1F1F4}|\\x{1F1F3}\\x{1F1F5}|\\x{1F1F3}\\x{1F1F7}|\\x{1F1F3}\\x{1F1FA}|\\x{1F1F3}\\x{1F1FF}|\\x{1F1F4}\\x{1F1F2}|\\x{1F1F5}\\x{1F1E6}|\\x{1F1F5}\\x{1F1EA}|\\x{1F1F5}\\x{1F1EB}|\\x{1F1F5}\\x{1F1EC}|\\x{1F1F5}\\x{1F1ED}|\\x{1F1F5}\\x{1F1F0}|\\x{1F1F5}\\x{1F1F1}|\\x{1F1F5}\\x{1F1F2}|\\x{1F1F5}\\x{1F1F3}|\\x{1F1F5}\\x{1F1F7}|\\x{1F1F5}\\x{1F1F8}|\\x{1F1F5}\\x{1F1F9}|\\x{1F1F5}\\x{1F1FC}|\\x{1F1F5}\\x{1F1FE}|\\x{1F1F6}\\x{1F1E6}|\\x{1F1F7}\\x{1F1EA}|\\x{1F1F7}\\x{1F1F4}|\\x{1F1F7}\\x{1F1F8}|\\x{1F1F7}\\x{1F1FA}|\\x{1F1F7}\\x{1F1FC}|\\x{1F1F8}\\x{1F1E6}|\\x{1F1F8}\\x{1F1E7}|\\x{1F1F8}\\x{1F1E8}|\\x{1F1F8}\\x{1F1E9}|\\x{1F1F8}\\x{1F1EA}|\\x{1F1F8}\\x{1F1EC}|\\x{1F1F8}\\x{1F1ED}|\\x{1F1F8}\\x{1F1EE}|\\x{1F1F8}\\x{1F1EF}|\\x{1F1F8}\\x{1F1F0}|\\x{1F1F8}\\x{1F1F1}|\\x{1F1F8}\\x{1F1F2}|\\x{1F1F8}\\x{1F1F3}|\\x{1F1F8}\\x{1F1F4}|\\x{1F1F8}\\x{1F1F7}|\\x{1F1F8}\\x{1F1F8}|\\x{1F1F8}\\x{1F1F9}|\\x{1F1F8}\\x{1F1FB}|\\x{1F1F8}\\x{1F1FD}|\\x{1F1F8}\\x{1F1FE}|\\x{1F1F8}\\x{1F1FF}|\\x{1F1F9}\\x{1F1E6}|\\x{1F1F9}\\x{1F1E8}|\\x{1F1F9}\\x{1F1E9}|\\x{1F1F9}\\x{1F1EB}|\\x{1F1F9}\\x{1F1EC}|\\x{1F1F9}\\x{1F1ED}|\\x{1F1F9}\\x{1F1EF}|\\x{1F1F9}\\x{1F1F0}|\\x{1F1F9}\\x{1F1F1}|\\x{1F1F9}\\x{1F1F2}|\\x{1F1F9}\\x{1F1F3}|\\x{1F1F9}\\x{1F1F4}|\\x{1F1F9}\\x{1F1F7}|\\x{1F1F9}\\x{1F1F9}|\\x{1F1F9}\\x{1F1FB}|\\x{1F1F9}\\x{1F1FC}|\\x{1F1F9}\\x{1F1FF}|\\x{1F1FA}\\x{1F1E6}|\\x{1F1FA}\\x{1F1EC}|\\x{1F1FA}\\x{1F1F2}|\\x{1F1FA}\\x{1F1F3}|\\x{1F1FA}\\x{1F1F8}|\\x{1F1FA}\\x{1F1FE}|\\x{1F1FA}\\x{1F1FF}|\\x{1F1FB}\\x{1F1E6}|\\x{1F1FB}\\x{1F1E8}|\\x{1F1FB}\\x{1F1EA}|\\x{1F1FB}\\x{1F1EC}|\\x{1F1FB}\\x{1F1EE}|\\x{1F1FB}\\x{1F1F3}|\\x{1F1FB}\\x{1F1FA}|\\x{1F1FC}\\x{1F1EB}|\\x{1F1FC}\\x{1F1F8}|\\x{1F1FD}\\x{1F1F0}|\\x{1F1FE}\\x{1F1EA}|\\x{1F1FE}\\x{1F1F9}|\\x{1F1FF}\\x{1F1E6}|\\x{1F1FF}\\x{1F1F2}|\\x{1F1FF}\\x{1F1FC}|\\x{1F3F4}\\x{E0067}\\x{E0062}\\x{E0065}\\x{E006E}\\x{E0067}\\x{E007F}|\\x{1F3F4}\\x{E0067}\\x{E0062}\\x{E0073}\\x{E0063}\\x{E0074}\\x{E007F}|\\x{1F3F4}\\x{E0067}\\x{E0062}\\x{E0077}\\x{E006C}\\x{E0073}\\x{E007F}'; - $regExpTones = '\\x{1F44B}\\x{1F3FB}|\\x{1F44B}\\x{1F3FC}|\\x{1F44B}\\x{1F3FD}|\\x{1F44B}\\x{1F3FE}|\\x{1F44B}\\x{1F3FF}|\\x{1F91A}\\x{1F3FB}|\\x{1F91A}\\x{1F3FC}|\\x{1F91A}\\x{1F3FD}|\\x{1F91A}\\x{1F3FE}|\\x{1F91A}\\x{1F3FF}|\\x{1F590}\\x{1F3FB}|\\x{1F590}\\x{1F3FC}|\\x{1F590}\\x{1F3FD}|\\x{1F590}\\x{1F3FE}|\\x{1F590}\\x{1F3FF}|\\x{270B}\\x{1F3FB}|\\x{270B}\\x{1F3FC}|\\x{270B}\\x{1F3FD}|\\x{270B}\\x{1F3FE}|\\x{270B}\\x{1F3FF}|\\x{1F596}\\x{1F3FB}|\\x{1F596}\\x{1F3FC}|\\x{1F596}\\x{1F3FD}|\\x{1F596}\\x{1F3FE}|\\x{1F596}\\x{1F3FF}|\\x{1F44C}\\x{1F3FB}|\\x{1F44C}\\x{1F3FC}|\\x{1F44C}\\x{1F3FD}|\\x{1F44C}\\x{1F3FE}|\\x{1F44C}\\x{1F3FF}|\\x{1F90C}\\x{1F3FB}|\\x{1F90C}\\x{1F3FC}|\\x{1F90C}\\x{1F3FD}|\\x{1F90C}\\x{1F3FE}|\\x{1F90C}\\x{1F3FF}|\\x{1F90F}\\x{1F3FB}|\\x{1F90F}\\x{1F3FC}|\\x{1F90F}\\x{1F3FD}|\\x{1F90F}\\x{1F3FE}|\\x{1F90F}\\x{1F3FF}|\\x{270C}\\x{1F3FB}|\\x{270C}\\x{1F3FC}|\\x{270C}\\x{1F3FD}|\\x{270C}\\x{1F3FE}|\\x{270C}\\x{1F3FF}|\\x{1F91E}\\x{1F3FB}|\\x{1F91E}\\x{1F3FC}|\\x{1F91E}\\x{1F3FD}|\\x{1F91E}\\x{1F3FE}|\\x{1F91E}\\x{1F3FF}|\\x{1F91F}\\x{1F3FB}|\\x{1F91F}\\x{1F3FC}|\\x{1F91F}\\x{1F3FD}|\\x{1F91F}\\x{1F3FE}|\\x{1F91F}\\x{1F3FF}|\\x{1F918}\\x{1F3FB}|\\x{1F918}\\x{1F3FC}|\\x{1F918}\\x{1F3FD}|\\x{1F918}\\x{1F3FE}|\\x{1F918}\\x{1F3FF}|\\x{1F919}\\x{1F3FB}|\\x{1F919}\\x{1F3FC}|\\x{1F919}\\x{1F3FD}|\\x{1F919}\\x{1F3FE}|\\x{1F919}\\x{1F3FF}|\\x{1F448}\\x{1F3FB}|\\x{1F448}\\x{1F3FC}|\\x{1F448}\\x{1F3FD}|\\x{1F448}\\x{1F3FE}|\\x{1F448}\\x{1F3FF}|\\x{1F449}\\x{1F3FB}|\\x{1F449}\\x{1F3FC}|\\x{1F449}\\x{1F3FD}|\\x{1F449}\\x{1F3FE}|\\x{1F449}\\x{1F3FF}|\\x{1F446}\\x{1F3FB}|\\x{1F446}\\x{1F3FC}|\\x{1F446}\\x{1F3FD}|\\x{1F446}\\x{1F3FE}|\\x{1F446}\\x{1F3FF}|\\x{1F595}\\x{1F3FB}|\\x{1F595}\\x{1F3FC}|\\x{1F595}\\x{1F3FD}|\\x{1F595}\\x{1F3FE}|\\x{1F595}\\x{1F3FF}|\\x{1F447}\\x{1F3FB}|\\x{1F447}\\x{1F3FC}|\\x{1F447}\\x{1F3FD}|\\x{1F447}\\x{1F3FE}|\\x{1F447}\\x{1F3FF}|\\x{261D}\\x{1F3FB}|\\x{261D}\\x{1F3FC}|\\x{261D}\\x{1F3FD}|\\x{261D}\\x{1F3FE}|\\x{261D}\\x{1F3FF}|\\x{1F44D}\\x{1F3FB}|\\x{1F44D}\\x{1F3FC}|\\x{1F44D}\\x{1F3FD}|\\x{1F44D}\\x{1F3FE}|\\x{1F44D}\\x{1F3FF}|\\x{1F44E}\\x{1F3FB}|\\x{1F44E}\\x{1F3FC}|\\x{1F44E}\\x{1F3FD}|\\x{1F44E}\\x{1F3FE}|\\x{1F44E}\\x{1F3FF}|\\x{270A}\\x{1F3FB}|\\x{270A}\\x{1F3FC}|\\x{270A}\\x{1F3FD}|\\x{270A}\\x{1F3FE}|\\x{270A}\\x{1F3FF}|\\x{1F44A}\\x{1F3FB}|\\x{1F44A}\\x{1F3FC}|\\x{1F44A}\\x{1F3FD}|\\x{1F44A}\\x{1F3FE}|\\x{1F44A}\\x{1F3FF}|\\x{1F91B}\\x{1F3FB}|\\x{1F91B}\\x{1F3FC}|\\x{1F91B}\\x{1F3FD}|\\x{1F91B}\\x{1F3FE}|\\x{1F91B}\\x{1F3FF}|\\x{1F91C}\\x{1F3FB}|\\x{1F91C}\\x{1F3FC}|\\x{1F91C}\\x{1F3FD}|\\x{1F91C}\\x{1F3FE}|\\x{1F91C}\\x{1F3FF}|\\x{1F44F}\\x{1F3FB}|\\x{1F44F}\\x{1F3FC}|\\x{1F44F}\\x{1F3FD}|\\x{1F44F}\\x{1F3FE}|\\x{1F44F}\\x{1F3FF}|\\x{1F64C}\\x{1F3FB}|\\x{1F64C}\\x{1F3FC}|\\x{1F64C}\\x{1F3FD}|\\x{1F64C}\\x{1F3FE}|\\x{1F64C}\\x{1F3FF}|\\x{1F450}\\x{1F3FB}|\\x{1F450}\\x{1F3FC}|\\x{1F450}\\x{1F3FD}|\\x{1F450}\\x{1F3FE}|\\x{1F450}\\x{1F3FF}|\\x{1F932}\\x{1F3FB}|\\x{1F932}\\x{1F3FC}|\\x{1F932}\\x{1F3FD}|\\x{1F932}\\x{1F3FE}|\\x{1F932}\\x{1F3FF}|\\x{1F64F}\\x{1F3FB}|\\x{1F64F}\\x{1F3FC}|\\x{1F64F}\\x{1F3FD}|\\x{1F64F}\\x{1F3FE}|\\x{1F64F}\\x{1F3FF}|\\x{270D}\\x{1F3FB}|\\x{270D}\\x{1F3FC}|\\x{270D}\\x{1F3FD}|\\x{270D}\\x{1F3FE}|\\x{270D}\\x{1F3FF}|\\x{1F485}\\x{1F3FB}|\\x{1F485}\\x{1F3FC}|\\x{1F485}\\x{1F3FD}|\\x{1F485}\\x{1F3FE}|\\x{1F485}\\x{1F3FF}|\\x{1F933}\\x{1F3FB}|\\x{1F933}\\x{1F3FC}|\\x{1F933}\\x{1F3FD}|\\x{1F933}\\x{1F3FE}|\\x{1F933}\\x{1F3FF}|\\x{1F4AA}\\x{1F3FB}|\\x{1F4AA}\\x{1F3FC}|\\x{1F4AA}\\x{1F3FD}|\\x{1F4AA}\\x{1F3FE}|\\x{1F4AA}\\x{1F3FF}|\\x{1F9B5}\\x{1F3FB}|\\x{1F9B5}\\x{1F3FC}|\\x{1F9B5}\\x{1F3FD}|\\x{1F9B5}\\x{1F3FE}|\\x{1F9B5}\\x{1F3FF}|\\x{1F9B6}\\x{1F3FB}|\\x{1F9B6}\\x{1F3FC}|\\x{1F9B6}\\x{1F3FD}|\\x{1F9B6}\\x{1F3FE}|\\x{1F9B6}\\x{1F3FF}|\\x{1F442}\\x{1F3FB}|\\x{1F442}\\x{1F3FC}|\\x{1F442}\\x{1F3FD}|\\x{1F442}\\x{1F3FE}|\\x{1F442}\\x{1F3FF}|\\x{1F9BB}\\x{1F3FB}|\\x{1F9BB}\\x{1F3FC}|\\x{1F9BB}\\x{1F3FD}|\\x{1F9BB}\\x{1F3FE}|\\x{1F9BB}\\x{1F3FF}|\\x{1F443}\\x{1F3FB}|\\x{1F443}\\x{1F3FC}|\\x{1F443}\\x{1F3FD}|\\x{1F443}\\x{1F3FE}|\\x{1F443}\\x{1F3FF}|\\x{1F476}\\x{1F3FB}|\\x{1F476}\\x{1F3FC}|\\x{1F476}\\x{1F3FD}|\\x{1F476}\\x{1F3FE}|\\x{1F476}\\x{1F3FF}|\\x{1F9D2}\\x{1F3FB}|\\x{1F9D2}\\x{1F3FC}|\\x{1F9D2}\\x{1F3FD}|\\x{1F9D2}\\x{1F3FE}|\\x{1F9D2}\\x{1F3FF}|\\x{1F466}\\x{1F3FB}|\\x{1F466}\\x{1F3FC}|\\x{1F466}\\x{1F3FD}|\\x{1F466}\\x{1F3FE}|\\x{1F466}\\x{1F3FF}|\\x{1F467}\\x{1F3FB}|\\x{1F467}\\x{1F3FC}|\\x{1F467}\\x{1F3FD}|\\x{1F467}\\x{1F3FE}|\\x{1F467}\\x{1F3FF}|\\x{1F9D1}\\x{1F3FB}|\\x{1F9D1}\\x{1F3FC}|\\x{1F9D1}\\x{1F3FD}|\\x{1F9D1}\\x{1F3FE}|\\x{1F9D1}\\x{1F3FF}|\\x{1F471}\\x{1F3FB}|\\x{1F471}\\x{1F3FC}|\\x{1F471}\\x{1F3FD}|\\x{1F471}\\x{1F3FE}|\\x{1F471}\\x{1F3FF}|\\x{1F468}\\x{1F3FB}|\\x{1F468}\\x{1F3FC}|\\x{1F468}\\x{1F3FD}|\\x{1F468}\\x{1F3FE}|\\x{1F468}\\x{1F3FF}|\\x{1F9D4}\\x{1F3FB}|\\x{1F9D4}\\x{1F3FC}|\\x{1F9D4}\\x{1F3FD}|\\x{1F9D4}\\x{1F3FE}|\\x{1F9D4}\\x{1F3FF}|\\x{1F468}\\x{1F3FB}\\x{200D}\\x{1F9B0}|\\x{1F468}\\x{1F3FC}\\x{200D}\\x{1F9B0}|\\x{1F468}\\x{1F3FD}\\x{200D}\\x{1F9B0}|\\x{1F468}\\x{1F3FE}\\x{200D}\\x{1F9B0}|\\x{1F468}\\x{1F3FF}\\x{200D}\\x{1F9B0}|\\x{1F468}\\x{1F3FB}\\x{200D}\\x{1F9B1}|\\x{1F468}\\x{1F3FC}\\x{200D}\\x{1F9B1}|\\x{1F468}\\x{1F3FD}\\x{200D}\\x{1F9B1}|\\x{1F468}\\x{1F3FE}\\x{200D}\\x{1F9B1}|\\x{1F468}\\x{1F3FF}\\x{200D}\\x{1F9B1}|\\x{1F468}\\x{1F3FB}\\x{200D}\\x{1F9B3}|\\x{1F468}\\x{1F3FC}\\x{200D}\\x{1F9B3}|\\x{1F468}\\x{1F3FD}\\x{200D}\\x{1F9B3}|\\x{1F468}\\x{1F3FE}\\x{200D}\\x{1F9B3}|\\x{1F468}\\x{1F3FF}\\x{200D}\\x{1F9B3}|\\x{1F468}\\x{1F3FB}\\x{200D}\\x{1F9B2}|\\x{1F468}\\x{1F3FC}\\x{200D}\\x{1F9B2}|\\x{1F468}\\x{1F3FD}\\x{200D}\\x{1F9B2}|\\x{1F468}\\x{1F3FE}\\x{200D}\\x{1F9B2}|\\x{1F468}\\x{1F3FF}\\x{200D}\\x{1F9B2}|\\x{1F469}\\x{1F3FB}|\\x{1F469}\\x{1F3FC}|\\x{1F469}\\x{1F3FD}|\\x{1F469}\\x{1F3FE}|\\x{1F469}\\x{1F3FF}|\\x{1F469}\\x{1F3FB}\\x{200D}\\x{1F9B0}|\\x{1F469}\\x{1F3FC}\\x{200D}\\x{1F9B0}|\\x{1F469}\\x{1F3FD}\\x{200D}\\x{1F9B0}|\\x{1F469}\\x{1F3FE}\\x{200D}\\x{1F9B0}|\\x{1F469}\\x{1F3FF}\\x{200D}\\x{1F9B0}|\\x{1F9D1}\\x{1F3FB}\\x{200D}\\x{1F9B0}|\\x{1F9D1}\\x{1F3FC}\\x{200D}\\x{1F9B0}|\\x{1F9D1}\\x{1F3FD}\\x{200D}\\x{1F9B0}|\\x{1F9D1}\\x{1F3FE}\\x{200D}\\x{1F9B0}|\\x{1F9D1}\\x{1F3FF}\\x{200D}\\x{1F9B0}|\\x{1F469}\\x{1F3FB}\\x{200D}\\x{1F9B1}|\\x{1F469}\\x{1F3FC}\\x{200D}\\x{1F9B1}|\\x{1F469}\\x{1F3FD}\\x{200D}\\x{1F9B1}|\\x{1F469}\\x{1F3FE}\\x{200D}\\x{1F9B1}|\\x{1F469}\\x{1F3FF}\\x{200D}\\x{1F9B1}|\\x{1F9D1}\\x{1F3FB}\\x{200D}\\x{1F9B1}|\\x{1F9D1}\\x{1F3FC}\\x{200D}\\x{1F9B1}|\\x{1F9D1}\\x{1F3FD}\\x{200D}\\x{1F9B1}|\\x{1F9D1}\\x{1F3FE}\\x{200D}\\x{1F9B1}|\\x{1F9D1}\\x{1F3FF}\\x{200D}\\x{1F9B1}|\\x{1F469}\\x{1F3FB}\\x{200D}\\x{1F9B3}|\\x{1F469}\\x{1F3FC}\\x{200D}\\x{1F9B3}|\\x{1F469}\\x{1F3FD}\\x{200D}\\x{1F9B3}|\\x{1F469}\\x{1F3FE}\\x{200D}\\x{1F9B3}|\\x{1F469}\\x{1F3FF}\\x{200D}\\x{1F9B3}|\\x{1F9D1}\\x{1F3FB}\\x{200D}\\x{1F9B3}|\\x{1F9D1}\\x{1F3FC}\\x{200D}\\x{1F9B3}|\\x{1F9D1}\\x{1F3FD}\\x{200D}\\x{1F9B3}|\\x{1F9D1}\\x{1F3FE}\\x{200D}\\x{1F9B3}|\\x{1F9D1}\\x{1F3FF}\\x{200D}\\x{1F9B3}|\\x{1F469}\\x{1F3FB}\\x{200D}\\x{1F9B2}|\\x{1F469}\\x{1F3FC}\\x{200D}\\x{1F9B2}|\\x{1F469}\\x{1F3FD}\\x{200D}\\x{1F9B2}|\\x{1F469}\\x{1F3FE}\\x{200D}\\x{1F9B2}|\\x{1F469}\\x{1F3FF}\\x{200D}\\x{1F9B2}|\\x{1F9D1}\\x{1F3FB}\\x{200D}\\x{1F9B2}|\\x{1F9D1}\\x{1F3FC}\\x{200D}\\x{1F9B2}|\\x{1F9D1}\\x{1F3FD}\\x{200D}\\x{1F9B2}|\\x{1F9D1}\\x{1F3FE}\\x{200D}\\x{1F9B2}|\\x{1F9D1}\\x{1F3FF}\\x{200D}\\x{1F9B2}|\\x{1F471}\\x{1F3FB}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F471}\\x{1F3FC}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F471}\\x{1F3FD}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F471}\\x{1F3FE}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F471}\\x{1F3FF}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F471}\\x{1F3FB}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F471}\\x{1F3FC}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F471}\\x{1F3FD}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F471}\\x{1F3FE}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F471}\\x{1F3FF}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9D3}\\x{1F3FB}|\\x{1F9D3}\\x{1F3FC}|\\x{1F9D3}\\x{1F3FD}|\\x{1F9D3}\\x{1F3FE}|\\x{1F9D3}\\x{1F3FF}|\\x{1F474}\\x{1F3FB}|\\x{1F474}\\x{1F3FC}|\\x{1F474}\\x{1F3FD}|\\x{1F474}\\x{1F3FE}|\\x{1F474}\\x{1F3FF}|\\x{1F475}\\x{1F3FB}|\\x{1F475}\\x{1F3FC}|\\x{1F475}\\x{1F3FD}|\\x{1F475}\\x{1F3FE}|\\x{1F475}\\x{1F3FF}|\\x{1F64D}\\x{1F3FB}|\\x{1F64D}\\x{1F3FC}|\\x{1F64D}\\x{1F3FD}|\\x{1F64D}\\x{1F3FE}|\\x{1F64D}\\x{1F3FF}|\\x{1F64D}\\x{1F3FB}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F64D}\\x{1F3FC}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F64D}\\x{1F3FD}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F64D}\\x{1F3FE}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F64D}\\x{1F3FF}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F64D}\\x{1F3FB}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F64D}\\x{1F3FC}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F64D}\\x{1F3FD}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F64D}\\x{1F3FE}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F64D}\\x{1F3FF}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F64E}\\x{1F3FB}|\\x{1F64E}\\x{1F3FC}|\\x{1F64E}\\x{1F3FD}|\\x{1F64E}\\x{1F3FE}|\\x{1F64E}\\x{1F3FF}|\\x{1F64E}\\x{1F3FB}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F64E}\\x{1F3FC}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F64E}\\x{1F3FD}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F64E}\\x{1F3FE}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F64E}\\x{1F3FF}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F64E}\\x{1F3FB}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F64E}\\x{1F3FC}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F64E}\\x{1F3FD}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F64E}\\x{1F3FE}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F64E}\\x{1F3FF}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F645}\\x{1F3FB}|\\x{1F645}\\x{1F3FC}|\\x{1F645}\\x{1F3FD}|\\x{1F645}\\x{1F3FE}|\\x{1F645}\\x{1F3FF}|\\x{1F645}\\x{1F3FB}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F645}\\x{1F3FC}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F645}\\x{1F3FD}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F645}\\x{1F3FE}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F645}\\x{1F3FF}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F645}\\x{1F3FB}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F645}\\x{1F3FC}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F645}\\x{1F3FD}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F645}\\x{1F3FE}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F645}\\x{1F3FF}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F646}\\x{1F3FB}|\\x{1F646}\\x{1F3FC}|\\x{1F646}\\x{1F3FD}|\\x{1F646}\\x{1F3FE}|\\x{1F646}\\x{1F3FF}|\\x{1F646}\\x{1F3FB}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F646}\\x{1F3FC}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F646}\\x{1F3FD}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F646}\\x{1F3FE}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F646}\\x{1F3FF}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F646}\\x{1F3FB}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F646}\\x{1F3FC}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F646}\\x{1F3FD}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F646}\\x{1F3FE}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F646}\\x{1F3FF}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F481}\\x{1F3FB}|\\x{1F481}\\x{1F3FC}|\\x{1F481}\\x{1F3FD}|\\x{1F481}\\x{1F3FE}|\\x{1F481}\\x{1F3FF}|\\x{1F481}\\x{1F3FB}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F481}\\x{1F3FC}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F481}\\x{1F3FD}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F481}\\x{1F3FE}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F481}\\x{1F3FF}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F481}\\x{1F3FB}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F481}\\x{1F3FC}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F481}\\x{1F3FD}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F481}\\x{1F3FE}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F481}\\x{1F3FF}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F64B}\\x{1F3FB}|\\x{1F64B}\\x{1F3FC}|\\x{1F64B}\\x{1F3FD}|\\x{1F64B}\\x{1F3FE}|\\x{1F64B}\\x{1F3FF}|\\x{1F64B}\\x{1F3FB}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F64B}\\x{1F3FC}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F64B}\\x{1F3FD}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F64B}\\x{1F3FE}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F64B}\\x{1F3FF}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F64B}\\x{1F3FB}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F64B}\\x{1F3FC}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F64B}\\x{1F3FD}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F64B}\\x{1F3FE}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F64B}\\x{1F3FF}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9CF}\\x{1F3FB}|\\x{1F9CF}\\x{1F3FC}|\\x{1F9CF}\\x{1F3FD}|\\x{1F9CF}\\x{1F3FE}|\\x{1F9CF}\\x{1F3FF}|\\x{1F9CF}\\x{1F3FB}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9CF}\\x{1F3FC}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9CF}\\x{1F3FD}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9CF}\\x{1F3FE}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9CF}\\x{1F3FF}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9CF}\\x{1F3FB}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9CF}\\x{1F3FC}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9CF}\\x{1F3FD}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9CF}\\x{1F3FE}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9CF}\\x{1F3FF}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F647}\\x{1F3FB}|\\x{1F647}\\x{1F3FC}|\\x{1F647}\\x{1F3FD}|\\x{1F647}\\x{1F3FE}|\\x{1F647}\\x{1F3FF}|\\x{1F647}\\x{1F3FB}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F647}\\x{1F3FC}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F647}\\x{1F3FD}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F647}\\x{1F3FE}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F647}\\x{1F3FF}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F647}\\x{1F3FB}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F647}\\x{1F3FC}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F647}\\x{1F3FD}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F647}\\x{1F3FE}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F647}\\x{1F3FF}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F926}\\x{1F3FB}|\\x{1F926}\\x{1F3FC}|\\x{1F926}\\x{1F3FD}|\\x{1F926}\\x{1F3FE}|\\x{1F926}\\x{1F3FF}|\\x{1F926}\\x{1F3FB}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F926}\\x{1F3FC}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F926}\\x{1F3FD}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F926}\\x{1F3FE}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F926}\\x{1F3FF}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F926}\\x{1F3FB}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F926}\\x{1F3FC}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F926}\\x{1F3FD}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F926}\\x{1F3FE}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F926}\\x{1F3FF}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F937}\\x{1F3FB}|\\x{1F937}\\x{1F3FC}|\\x{1F937}\\x{1F3FD}|\\x{1F937}\\x{1F3FE}|\\x{1F937}\\x{1F3FF}|\\x{1F937}\\x{1F3FB}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F937}\\x{1F3FC}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F937}\\x{1F3FD}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F937}\\x{1F3FE}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F937}\\x{1F3FF}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F937}\\x{1F3FB}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F937}\\x{1F3FC}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F937}\\x{1F3FD}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F937}\\x{1F3FE}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F937}\\x{1F3FF}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9D1}\\x{1F3FB}\\x{200D}\\x{2695}\\x{FE0F}|\\x{1F9D1}\\x{1F3FC}\\x{200D}\\x{2695}\\x{FE0F}|\\x{1F9D1}\\x{1F3FD}\\x{200D}\\x{2695}\\x{FE0F}|\\x{1F9D1}\\x{1F3FE}\\x{200D}\\x{2695}\\x{FE0F}|\\x{1F9D1}\\x{1F3FF}\\x{200D}\\x{2695}\\x{FE0F}|\\x{1F468}\\x{1F3FB}\\x{200D}\\x{2695}\\x{FE0F}|\\x{1F468}\\x{1F3FC}\\x{200D}\\x{2695}\\x{FE0F}|\\x{1F468}\\x{1F3FD}\\x{200D}\\x{2695}\\x{FE0F}|\\x{1F468}\\x{1F3FE}\\x{200D}\\x{2695}\\x{FE0F}|\\x{1F468}\\x{1F3FF}\\x{200D}\\x{2695}\\x{FE0F}|\\x{1F469}\\x{1F3FB}\\x{200D}\\x{2695}\\x{FE0F}|\\x{1F469}\\x{1F3FC}\\x{200D}\\x{2695}\\x{FE0F}|\\x{1F469}\\x{1F3FD}\\x{200D}\\x{2695}\\x{FE0F}|\\x{1F469}\\x{1F3FE}\\x{200D}\\x{2695}\\x{FE0F}|\\x{1F469}\\x{1F3FF}\\x{200D}\\x{2695}\\x{FE0F}|\\x{1F9D1}\\x{1F3FB}\\x{200D}\\x{1F393}|\\x{1F9D1}\\x{1F3FC}\\x{200D}\\x{1F393}|\\x{1F9D1}\\x{1F3FD}\\x{200D}\\x{1F393}|\\x{1F9D1}\\x{1F3FE}\\x{200D}\\x{1F393}|\\x{1F9D1}\\x{1F3FF}\\x{200D}\\x{1F393}|\\x{1F468}\\x{1F3FB}\\x{200D}\\x{1F393}|\\x{1F468}\\x{1F3FC}\\x{200D}\\x{1F393}|\\x{1F468}\\x{1F3FD}\\x{200D}\\x{1F393}|\\x{1F468}\\x{1F3FE}\\x{200D}\\x{1F393}|\\x{1F468}\\x{1F3FF}\\x{200D}\\x{1F393}|\\x{1F469}\\x{1F3FB}\\x{200D}\\x{1F393}|\\x{1F469}\\x{1F3FC}\\x{200D}\\x{1F393}|\\x{1F469}\\x{1F3FD}\\x{200D}\\x{1F393}|\\x{1F469}\\x{1F3FE}\\x{200D}\\x{1F393}|\\x{1F469}\\x{1F3FF}\\x{200D}\\x{1F393}|\\x{1F9D1}\\x{1F3FB}\\x{200D}\\x{1F3EB}|\\x{1F9D1}\\x{1F3FC}\\x{200D}\\x{1F3EB}|\\x{1F9D1}\\x{1F3FD}\\x{200D}\\x{1F3EB}|\\x{1F9D1}\\x{1F3FE}\\x{200D}\\x{1F3EB}|\\x{1F9D1}\\x{1F3FF}\\x{200D}\\x{1F3EB}|\\x{1F468}\\x{1F3FB}\\x{200D}\\x{1F3EB}|\\x{1F468}\\x{1F3FC}\\x{200D}\\x{1F3EB}|\\x{1F468}\\x{1F3FD}\\x{200D}\\x{1F3EB}|\\x{1F468}\\x{1F3FE}\\x{200D}\\x{1F3EB}|\\x{1F468}\\x{1F3FF}\\x{200D}\\x{1F3EB}|\\x{1F469}\\x{1F3FB}\\x{200D}\\x{1F3EB}|\\x{1F469}\\x{1F3FC}\\x{200D}\\x{1F3EB}|\\x{1F469}\\x{1F3FD}\\x{200D}\\x{1F3EB}|\\x{1F469}\\x{1F3FE}\\x{200D}\\x{1F3EB}|\\x{1F469}\\x{1F3FF}\\x{200D}\\x{1F3EB}|\\x{1F9D1}\\x{1F3FB}\\x{200D}\\x{2696}\\x{FE0F}|\\x{1F9D1}\\x{1F3FC}\\x{200D}\\x{2696}\\x{FE0F}|\\x{1F9D1}\\x{1F3FD}\\x{200D}\\x{2696}\\x{FE0F}|\\x{1F9D1}\\x{1F3FE}\\x{200D}\\x{2696}\\x{FE0F}|\\x{1F9D1}\\x{1F3FF}\\x{200D}\\x{2696}\\x{FE0F}|\\x{1F468}\\x{1F3FB}\\x{200D}\\x{2696}\\x{FE0F}|\\x{1F468}\\x{1F3FC}\\x{200D}\\x{2696}\\x{FE0F}|\\x{1F468}\\x{1F3FD}\\x{200D}\\x{2696}\\x{FE0F}|\\x{1F468}\\x{1F3FE}\\x{200D}\\x{2696}\\x{FE0F}|\\x{1F468}\\x{1F3FF}\\x{200D}\\x{2696}\\x{FE0F}|\\x{1F469}\\x{1F3FB}\\x{200D}\\x{2696}\\x{FE0F}|\\x{1F469}\\x{1F3FC}\\x{200D}\\x{2696}\\x{FE0F}|\\x{1F469}\\x{1F3FD}\\x{200D}\\x{2696}\\x{FE0F}|\\x{1F469}\\x{1F3FE}\\x{200D}\\x{2696}\\x{FE0F}|\\x{1F469}\\x{1F3FF}\\x{200D}\\x{2696}\\x{FE0F}|\\x{1F9D1}\\x{1F3FB}\\x{200D}\\x{1F33E}|\\x{1F9D1}\\x{1F3FC}\\x{200D}\\x{1F33E}|\\x{1F9D1}\\x{1F3FD}\\x{200D}\\x{1F33E}|\\x{1F9D1}\\x{1F3FE}\\x{200D}\\x{1F33E}|\\x{1F9D1}\\x{1F3FF}\\x{200D}\\x{1F33E}|\\x{1F468}\\x{1F3FB}\\x{200D}\\x{1F33E}|\\x{1F468}\\x{1F3FC}\\x{200D}\\x{1F33E}|\\x{1F468}\\x{1F3FD}\\x{200D}\\x{1F33E}|\\x{1F468}\\x{1F3FE}\\x{200D}\\x{1F33E}|\\x{1F468}\\x{1F3FF}\\x{200D}\\x{1F33E}|\\x{1F469}\\x{1F3FB}\\x{200D}\\x{1F33E}|\\x{1F469}\\x{1F3FC}\\x{200D}\\x{1F33E}|\\x{1F469}\\x{1F3FD}\\x{200D}\\x{1F33E}|\\x{1F469}\\x{1F3FE}\\x{200D}\\x{1F33E}|\\x{1F469}\\x{1F3FF}\\x{200D}\\x{1F33E}|\\x{1F9D1}\\x{1F3FB}\\x{200D}\\x{1F373}|\\x{1F9D1}\\x{1F3FC}\\x{200D}\\x{1F373}|\\x{1F9D1}\\x{1F3FD}\\x{200D}\\x{1F373}|\\x{1F9D1}\\x{1F3FE}\\x{200D}\\x{1F373}|\\x{1F9D1}\\x{1F3FF}\\x{200D}\\x{1F373}|\\x{1F468}\\x{1F3FB}\\x{200D}\\x{1F373}|\\x{1F468}\\x{1F3FC}\\x{200D}\\x{1F373}|\\x{1F468}\\x{1F3FD}\\x{200D}\\x{1F373}|\\x{1F468}\\x{1F3FE}\\x{200D}\\x{1F373}|\\x{1F468}\\x{1F3FF}\\x{200D}\\x{1F373}|\\x{1F469}\\x{1F3FB}\\x{200D}\\x{1F373}|\\x{1F469}\\x{1F3FC}\\x{200D}\\x{1F373}|\\x{1F469}\\x{1F3FD}\\x{200D}\\x{1F373}|\\x{1F469}\\x{1F3FE}\\x{200D}\\x{1F373}|\\x{1F469}\\x{1F3FF}\\x{200D}\\x{1F373}|\\x{1F9D1}\\x{1F3FB}\\x{200D}\\x{1F527}|\\x{1F9D1}\\x{1F3FC}\\x{200D}\\x{1F527}|\\x{1F9D1}\\x{1F3FD}\\x{200D}\\x{1F527}|\\x{1F9D1}\\x{1F3FE}\\x{200D}\\x{1F527}|\\x{1F9D1}\\x{1F3FF}\\x{200D}\\x{1F527}|\\x{1F468}\\x{1F3FB}\\x{200D}\\x{1F527}|\\x{1F468}\\x{1F3FC}\\x{200D}\\x{1F527}|\\x{1F468}\\x{1F3FD}\\x{200D}\\x{1F527}|\\x{1F468}\\x{1F3FE}\\x{200D}\\x{1F527}|\\x{1F468}\\x{1F3FF}\\x{200D}\\x{1F527}|\\x{1F469}\\x{1F3FB}\\x{200D}\\x{1F527}|\\x{1F469}\\x{1F3FC}\\x{200D}\\x{1F527}|\\x{1F469}\\x{1F3FD}\\x{200D}\\x{1F527}|\\x{1F469}\\x{1F3FE}\\x{200D}\\x{1F527}|\\x{1F469}\\x{1F3FF}\\x{200D}\\x{1F527}|\\x{1F9D1}\\x{1F3FB}\\x{200D}\\x{1F3ED}|\\x{1F9D1}\\x{1F3FC}\\x{200D}\\x{1F3ED}|\\x{1F9D1}\\x{1F3FD}\\x{200D}\\x{1F3ED}|\\x{1F9D1}\\x{1F3FE}\\x{200D}\\x{1F3ED}|\\x{1F9D1}\\x{1F3FF}\\x{200D}\\x{1F3ED}|\\x{1F468}\\x{1F3FB}\\x{200D}\\x{1F3ED}|\\x{1F468}\\x{1F3FC}\\x{200D}\\x{1F3ED}|\\x{1F468}\\x{1F3FD}\\x{200D}\\x{1F3ED}|\\x{1F468}\\x{1F3FE}\\x{200D}\\x{1F3ED}|\\x{1F468}\\x{1F3FF}\\x{200D}\\x{1F3ED}|\\x{1F469}\\x{1F3FB}\\x{200D}\\x{1F3ED}|\\x{1F469}\\x{1F3FC}\\x{200D}\\x{1F3ED}|\\x{1F469}\\x{1F3FD}\\x{200D}\\x{1F3ED}|\\x{1F469}\\x{1F3FE}\\x{200D}\\x{1F3ED}|\\x{1F469}\\x{1F3FF}\\x{200D}\\x{1F3ED}|\\x{1F9D1}\\x{1F3FB}\\x{200D}\\x{1F4BC}|\\x{1F9D1}\\x{1F3FC}\\x{200D}\\x{1F4BC}|\\x{1F9D1}\\x{1F3FD}\\x{200D}\\x{1F4BC}|\\x{1F9D1}\\x{1F3FE}\\x{200D}\\x{1F4BC}|\\x{1F9D1}\\x{1F3FF}\\x{200D}\\x{1F4BC}|\\x{1F468}\\x{1F3FB}\\x{200D}\\x{1F4BC}|\\x{1F468}\\x{1F3FC}\\x{200D}\\x{1F4BC}|\\x{1F468}\\x{1F3FD}\\x{200D}\\x{1F4BC}|\\x{1F468}\\x{1F3FE}\\x{200D}\\x{1F4BC}|\\x{1F468}\\x{1F3FF}\\x{200D}\\x{1F4BC}|\\x{1F469}\\x{1F3FB}\\x{200D}\\x{1F4BC}|\\x{1F469}\\x{1F3FC}\\x{200D}\\x{1F4BC}|\\x{1F469}\\x{1F3FD}\\x{200D}\\x{1F4BC}|\\x{1F469}\\x{1F3FE}\\x{200D}\\x{1F4BC}|\\x{1F469}\\x{1F3FF}\\x{200D}\\x{1F4BC}|\\x{1F9D1}\\x{1F3FB}\\x{200D}\\x{1F52C}|\\x{1F9D1}\\x{1F3FC}\\x{200D}\\x{1F52C}|\\x{1F9D1}\\x{1F3FD}\\x{200D}\\x{1F52C}|\\x{1F9D1}\\x{1F3FE}\\x{200D}\\x{1F52C}|\\x{1F9D1}\\x{1F3FF}\\x{200D}\\x{1F52C}|\\x{1F468}\\x{1F3FB}\\x{200D}\\x{1F52C}|\\x{1F468}\\x{1F3FC}\\x{200D}\\x{1F52C}|\\x{1F468}\\x{1F3FD}\\x{200D}\\x{1F52C}|\\x{1F468}\\x{1F3FE}\\x{200D}\\x{1F52C}|\\x{1F468}\\x{1F3FF}\\x{200D}\\x{1F52C}|\\x{1F469}\\x{1F3FB}\\x{200D}\\x{1F52C}|\\x{1F469}\\x{1F3FC}\\x{200D}\\x{1F52C}|\\x{1F469}\\x{1F3FD}\\x{200D}\\x{1F52C}|\\x{1F469}\\x{1F3FE}\\x{200D}\\x{1F52C}|\\x{1F469}\\x{1F3FF}\\x{200D}\\x{1F52C}|\\x{1F9D1}\\x{1F3FB}\\x{200D}\\x{1F4BB}|\\x{1F9D1}\\x{1F3FC}\\x{200D}\\x{1F4BB}|\\x{1F9D1}\\x{1F3FD}\\x{200D}\\x{1F4BB}|\\x{1F9D1}\\x{1F3FE}\\x{200D}\\x{1F4BB}|\\x{1F9D1}\\x{1F3FF}\\x{200D}\\x{1F4BB}|\\x{1F468}\\x{1F3FB}\\x{200D}\\x{1F4BB}|\\x{1F468}\\x{1F3FC}\\x{200D}\\x{1F4BB}|\\x{1F468}\\x{1F3FD}\\x{200D}\\x{1F4BB}|\\x{1F468}\\x{1F3FE}\\x{200D}\\x{1F4BB}|\\x{1F468}\\x{1F3FF}\\x{200D}\\x{1F4BB}|\\x{1F469}\\x{1F3FB}\\x{200D}\\x{1F4BB}|\\x{1F469}\\x{1F3FC}\\x{200D}\\x{1F4BB}|\\x{1F469}\\x{1F3FD}\\x{200D}\\x{1F4BB}|\\x{1F469}\\x{1F3FE}\\x{200D}\\x{1F4BB}|\\x{1F469}\\x{1F3FF}\\x{200D}\\x{1F4BB}|\\x{1F9D1}\\x{1F3FB}\\x{200D}\\x{1F3A4}|\\x{1F9D1}\\x{1F3FC}\\x{200D}\\x{1F3A4}|\\x{1F9D1}\\x{1F3FD}\\x{200D}\\x{1F3A4}|\\x{1F9D1}\\x{1F3FE}\\x{200D}\\x{1F3A4}|\\x{1F9D1}\\x{1F3FF}\\x{200D}\\x{1F3A4}|\\x{1F468}\\x{1F3FB}\\x{200D}\\x{1F3A4}|\\x{1F468}\\x{1F3FC}\\x{200D}\\x{1F3A4}|\\x{1F468}\\x{1F3FD}\\x{200D}\\x{1F3A4}|\\x{1F468}\\x{1F3FE}\\x{200D}\\x{1F3A4}|\\x{1F468}\\x{1F3FF}\\x{200D}\\x{1F3A4}|\\x{1F469}\\x{1F3FB}\\x{200D}\\x{1F3A4}|\\x{1F469}\\x{1F3FC}\\x{200D}\\x{1F3A4}|\\x{1F469}\\x{1F3FD}\\x{200D}\\x{1F3A4}|\\x{1F469}\\x{1F3FE}\\x{200D}\\x{1F3A4}|\\x{1F469}\\x{1F3FF}\\x{200D}\\x{1F3A4}|\\x{1F9D1}\\x{1F3FB}\\x{200D}\\x{1F3A8}|\\x{1F9D1}\\x{1F3FC}\\x{200D}\\x{1F3A8}|\\x{1F9D1}\\x{1F3FD}\\x{200D}\\x{1F3A8}|\\x{1F9D1}\\x{1F3FE}\\x{200D}\\x{1F3A8}|\\x{1F9D1}\\x{1F3FF}\\x{200D}\\x{1F3A8}|\\x{1F468}\\x{1F3FB}\\x{200D}\\x{1F3A8}|\\x{1F468}\\x{1F3FC}\\x{200D}\\x{1F3A8}|\\x{1F468}\\x{1F3FD}\\x{200D}\\x{1F3A8}|\\x{1F468}\\x{1F3FE}\\x{200D}\\x{1F3A8}|\\x{1F468}\\x{1F3FF}\\x{200D}\\x{1F3A8}|\\x{1F469}\\x{1F3FB}\\x{200D}\\x{1F3A8}|\\x{1F469}\\x{1F3FC}\\x{200D}\\x{1F3A8}|\\x{1F469}\\x{1F3FD}\\x{200D}\\x{1F3A8}|\\x{1F469}\\x{1F3FE}\\x{200D}\\x{1F3A8}|\\x{1F469}\\x{1F3FF}\\x{200D}\\x{1F3A8}|\\x{1F9D1}\\x{1F3FB}\\x{200D}\\x{2708}\\x{FE0F}|\\x{1F9D1}\\x{1F3FC}\\x{200D}\\x{2708}\\x{FE0F}|\\x{1F9D1}\\x{1F3FD}\\x{200D}\\x{2708}\\x{FE0F}|\\x{1F9D1}\\x{1F3FE}\\x{200D}\\x{2708}\\x{FE0F}|\\x{1F9D1}\\x{1F3FF}\\x{200D}\\x{2708}\\x{FE0F}|\\x{1F468}\\x{1F3FB}\\x{200D}\\x{2708}\\x{FE0F}|\\x{1F468}\\x{1F3FC}\\x{200D}\\x{2708}\\x{FE0F}|\\x{1F468}\\x{1F3FD}\\x{200D}\\x{2708}\\x{FE0F}|\\x{1F468}\\x{1F3FE}\\x{200D}\\x{2708}\\x{FE0F}|\\x{1F468}\\x{1F3FF}\\x{200D}\\x{2708}\\x{FE0F}|\\x{1F469}\\x{1F3FB}\\x{200D}\\x{2708}\\x{FE0F}|\\x{1F469}\\x{1F3FC}\\x{200D}\\x{2708}\\x{FE0F}|\\x{1F469}\\x{1F3FD}\\x{200D}\\x{2708}\\x{FE0F}|\\x{1F469}\\x{1F3FE}\\x{200D}\\x{2708}\\x{FE0F}|\\x{1F469}\\x{1F3FF}\\x{200D}\\x{2708}\\x{FE0F}|\\x{1F9D1}\\x{1F3FB}\\x{200D}\\x{1F680}|\\x{1F9D1}\\x{1F3FC}\\x{200D}\\x{1F680}|\\x{1F9D1}\\x{1F3FD}\\x{200D}\\x{1F680}|\\x{1F9D1}\\x{1F3FE}\\x{200D}\\x{1F680}|\\x{1F9D1}\\x{1F3FF}\\x{200D}\\x{1F680}|\\x{1F468}\\x{1F3FB}\\x{200D}\\x{1F680}|\\x{1F468}\\x{1F3FC}\\x{200D}\\x{1F680}|\\x{1F468}\\x{1F3FD}\\x{200D}\\x{1F680}|\\x{1F468}\\x{1F3FE}\\x{200D}\\x{1F680}|\\x{1F468}\\x{1F3FF}\\x{200D}\\x{1F680}|\\x{1F469}\\x{1F3FB}\\x{200D}\\x{1F680}|\\x{1F469}\\x{1F3FC}\\x{200D}\\x{1F680}|\\x{1F469}\\x{1F3FD}\\x{200D}\\x{1F680}|\\x{1F469}\\x{1F3FE}\\x{200D}\\x{1F680}|\\x{1F469}\\x{1F3FF}\\x{200D}\\x{1F680}|\\x{1F9D1}\\x{1F3FB}\\x{200D}\\x{1F692}|\\x{1F9D1}\\x{1F3FC}\\x{200D}\\x{1F692}|\\x{1F9D1}\\x{1F3FD}\\x{200D}\\x{1F692}|\\x{1F9D1}\\x{1F3FE}\\x{200D}\\x{1F692}|\\x{1F9D1}\\x{1F3FF}\\x{200D}\\x{1F692}|\\x{1F468}\\x{1F3FB}\\x{200D}\\x{1F692}|\\x{1F468}\\x{1F3FC}\\x{200D}\\x{1F692}|\\x{1F468}\\x{1F3FD}\\x{200D}\\x{1F692}|\\x{1F468}\\x{1F3FE}\\x{200D}\\x{1F692}|\\x{1F468}\\x{1F3FF}\\x{200D}\\x{1F692}|\\x{1F469}\\x{1F3FB}\\x{200D}\\x{1F692}|\\x{1F469}\\x{1F3FC}\\x{200D}\\x{1F692}|\\x{1F469}\\x{1F3FD}\\x{200D}\\x{1F692}|\\x{1F469}\\x{1F3FE}\\x{200D}\\x{1F692}|\\x{1F469}\\x{1F3FF}\\x{200D}\\x{1F692}|\\x{1F46E}\\x{1F3FB}|\\x{1F46E}\\x{1F3FC}|\\x{1F46E}\\x{1F3FD}|\\x{1F46E}\\x{1F3FE}|\\x{1F46E}\\x{1F3FF}|\\x{1F46E}\\x{1F3FB}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F46E}\\x{1F3FC}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F46E}\\x{1F3FD}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F46E}\\x{1F3FE}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F46E}\\x{1F3FF}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F46E}\\x{1F3FB}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F46E}\\x{1F3FC}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F46E}\\x{1F3FD}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F46E}\\x{1F3FE}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F46E}\\x{1F3FF}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F575}\\x{1F3FB}|\\x{1F575}\\x{1F3FC}|\\x{1F575}\\x{1F3FD}|\\x{1F575}\\x{1F3FE}|\\x{1F575}\\x{1F3FF}|\\x{1F575}\\x{1F3FB}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F575}\\x{1F3FC}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F575}\\x{1F3FD}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F575}\\x{1F3FE}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F575}\\x{1F3FF}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F575}\\x{1F3FB}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F575}\\x{1F3FC}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F575}\\x{1F3FD}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F575}\\x{1F3FE}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F575}\\x{1F3FF}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F482}\\x{1F3FB}|\\x{1F482}\\x{1F3FC}|\\x{1F482}\\x{1F3FD}|\\x{1F482}\\x{1F3FE}|\\x{1F482}\\x{1F3FF}|\\x{1F482}\\x{1F3FB}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F482}\\x{1F3FC}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F482}\\x{1F3FD}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F482}\\x{1F3FE}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F482}\\x{1F3FF}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F482}\\x{1F3FB}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F482}\\x{1F3FC}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F482}\\x{1F3FD}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F482}\\x{1F3FE}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F482}\\x{1F3FF}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F977}\\x{1F3FB}|\\x{1F977}\\x{1F3FC}|\\x{1F977}\\x{1F3FD}|\\x{1F977}\\x{1F3FE}|\\x{1F977}\\x{1F3FF}|\\x{1F477}\\x{1F3FB}|\\x{1F477}\\x{1F3FC}|\\x{1F477}\\x{1F3FD}|\\x{1F477}\\x{1F3FE}|\\x{1F477}\\x{1F3FF}|\\x{1F477}\\x{1F3FB}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F477}\\x{1F3FC}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F477}\\x{1F3FD}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F477}\\x{1F3FE}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F477}\\x{1F3FF}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F477}\\x{1F3FB}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F477}\\x{1F3FC}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F477}\\x{1F3FD}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F477}\\x{1F3FE}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F477}\\x{1F3FF}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F934}\\x{1F3FB}|\\x{1F934}\\x{1F3FC}|\\x{1F934}\\x{1F3FD}|\\x{1F934}\\x{1F3FE}|\\x{1F934}\\x{1F3FF}|\\x{1F478}\\x{1F3FB}|\\x{1F478}\\x{1F3FC}|\\x{1F478}\\x{1F3FD}|\\x{1F478}\\x{1F3FE}|\\x{1F478}\\x{1F3FF}|\\x{1F473}\\x{1F3FB}|\\x{1F473}\\x{1F3FC}|\\x{1F473}\\x{1F3FD}|\\x{1F473}\\x{1F3FE}|\\x{1F473}\\x{1F3FF}|\\x{1F473}\\x{1F3FB}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F473}\\x{1F3FC}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F473}\\x{1F3FD}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F473}\\x{1F3FE}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F473}\\x{1F3FF}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F473}\\x{1F3FB}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F473}\\x{1F3FC}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F473}\\x{1F3FD}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F473}\\x{1F3FE}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F473}\\x{1F3FF}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F472}\\x{1F3FB}|\\x{1F472}\\x{1F3FC}|\\x{1F472}\\x{1F3FD}|\\x{1F472}\\x{1F3FE}|\\x{1F472}\\x{1F3FF}|\\x{1F9D5}\\x{1F3FB}|\\x{1F9D5}\\x{1F3FC}|\\x{1F9D5}\\x{1F3FD}|\\x{1F9D5}\\x{1F3FE}|\\x{1F9D5}\\x{1F3FF}|\\x{1F935}\\x{1F3FB}|\\x{1F935}\\x{1F3FC}|\\x{1F935}\\x{1F3FD}|\\x{1F935}\\x{1F3FE}|\\x{1F935}\\x{1F3FF}|\\x{1F935}\\x{1F3FB}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F935}\\x{1F3FC}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F935}\\x{1F3FD}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F935}\\x{1F3FE}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F935}\\x{1F3FF}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F935}\\x{1F3FB}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F935}\\x{1F3FC}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F935}\\x{1F3FD}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F935}\\x{1F3FE}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F935}\\x{1F3FF}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F470}\\x{1F3FB}|\\x{1F470}\\x{1F3FC}|\\x{1F470}\\x{1F3FD}|\\x{1F470}\\x{1F3FE}|\\x{1F470}\\x{1F3FF}|\\x{1F470}\\x{1F3FB}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F470}\\x{1F3FC}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F470}\\x{1F3FD}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F470}\\x{1F3FE}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F470}\\x{1F3FF}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F470}\\x{1F3FB}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F470}\\x{1F3FC}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F470}\\x{1F3FD}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F470}\\x{1F3FE}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F470}\\x{1F3FF}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F930}\\x{1F3FB}|\\x{1F930}\\x{1F3FC}|\\x{1F930}\\x{1F3FD}|\\x{1F930}\\x{1F3FE}|\\x{1F930}\\x{1F3FF}|\\x{1F931}\\x{1F3FB}|\\x{1F931}\\x{1F3FC}|\\x{1F931}\\x{1F3FD}|\\x{1F931}\\x{1F3FE}|\\x{1F931}\\x{1F3FF}|\\x{1F469}\\x{1F3FB}\\x{200D}\\x{1F37C}|\\x{1F469}\\x{1F3FC}\\x{200D}\\x{1F37C}|\\x{1F469}\\x{1F3FD}\\x{200D}\\x{1F37C}|\\x{1F469}\\x{1F3FE}\\x{200D}\\x{1F37C}|\\x{1F469}\\x{1F3FF}\\x{200D}\\x{1F37C}|\\x{1F468}\\x{1F3FB}\\x{200D}\\x{1F37C}|\\x{1F468}\\x{1F3FC}\\x{200D}\\x{1F37C}|\\x{1F468}\\x{1F3FD}\\x{200D}\\x{1F37C}|\\x{1F468}\\x{1F3FE}\\x{200D}\\x{1F37C}|\\x{1F468}\\x{1F3FF}\\x{200D}\\x{1F37C}|\\x{1F9D1}\\x{1F3FB}\\x{200D}\\x{1F37C}|\\x{1F9D1}\\x{1F3FC}\\x{200D}\\x{1F37C}|\\x{1F9D1}\\x{1F3FD}\\x{200D}\\x{1F37C}|\\x{1F9D1}\\x{1F3FE}\\x{200D}\\x{1F37C}|\\x{1F9D1}\\x{1F3FF}\\x{200D}\\x{1F37C}|\\x{1F47C}\\x{1F3FB}|\\x{1F47C}\\x{1F3FC}|\\x{1F47C}\\x{1F3FD}|\\x{1F47C}\\x{1F3FE}|\\x{1F47C}\\x{1F3FF}|\\x{1F385}\\x{1F3FB}|\\x{1F385}\\x{1F3FC}|\\x{1F385}\\x{1F3FD}|\\x{1F385}\\x{1F3FE}|\\x{1F385}\\x{1F3FF}|\\x{1F936}\\x{1F3FB}|\\x{1F936}\\x{1F3FC}|\\x{1F936}\\x{1F3FD}|\\x{1F936}\\x{1F3FE}|\\x{1F936}\\x{1F3FF}|\\x{1F9D1}\\x{1F3FB}\\x{200D}\\x{1F384}|\\x{1F9D1}\\x{1F3FC}\\x{200D}\\x{1F384}|\\x{1F9D1}\\x{1F3FD}\\x{200D}\\x{1F384}|\\x{1F9D1}\\x{1F3FE}\\x{200D}\\x{1F384}|\\x{1F9D1}\\x{1F3FF}\\x{200D}\\x{1F384}|\\x{1F9B8}\\x{1F3FB}|\\x{1F9B8}\\x{1F3FC}|\\x{1F9B8}\\x{1F3FD}|\\x{1F9B8}\\x{1F3FE}|\\x{1F9B8}\\x{1F3FF}|\\x{1F9B8}\\x{1F3FB}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9B8}\\x{1F3FC}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9B8}\\x{1F3FD}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9B8}\\x{1F3FE}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9B8}\\x{1F3FF}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9B8}\\x{1F3FB}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9B8}\\x{1F3FC}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9B8}\\x{1F3FD}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9B8}\\x{1F3FE}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9B8}\\x{1F3FF}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9B9}\\x{1F3FB}|\\x{1F9B9}\\x{1F3FC}|\\x{1F9B9}\\x{1F3FD}|\\x{1F9B9}\\x{1F3FE}|\\x{1F9B9}\\x{1F3FF}|\\x{1F9B9}\\x{1F3FB}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9B9}\\x{1F3FC}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9B9}\\x{1F3FD}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9B9}\\x{1F3FE}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9B9}\\x{1F3FF}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9B9}\\x{1F3FB}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9B9}\\x{1F3FC}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9B9}\\x{1F3FD}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9B9}\\x{1F3FE}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9B9}\\x{1F3FF}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9D9}\\x{1F3FB}|\\x{1F9D9}\\x{1F3FC}|\\x{1F9D9}\\x{1F3FD}|\\x{1F9D9}\\x{1F3FE}|\\x{1F9D9}\\x{1F3FF}|\\x{1F9D9}\\x{1F3FB}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9D9}\\x{1F3FC}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9D9}\\x{1F3FD}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9D9}\\x{1F3FE}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9D9}\\x{1F3FF}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9D9}\\x{1F3FB}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9D9}\\x{1F3FC}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9D9}\\x{1F3FD}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9D9}\\x{1F3FE}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9D9}\\x{1F3FF}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9DA}\\x{1F3FB}|\\x{1F9DA}\\x{1F3FC}|\\x{1F9DA}\\x{1F3FD}|\\x{1F9DA}\\x{1F3FE}|\\x{1F9DA}\\x{1F3FF}|\\x{1F9DA}\\x{1F3FB}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9DA}\\x{1F3FC}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9DA}\\x{1F3FD}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9DA}\\x{1F3FE}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9DA}\\x{1F3FF}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9DA}\\x{1F3FB}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9DA}\\x{1F3FC}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9DA}\\x{1F3FD}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9DA}\\x{1F3FE}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9DA}\\x{1F3FF}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9DB}\\x{1F3FB}|\\x{1F9DB}\\x{1F3FC}|\\x{1F9DB}\\x{1F3FD}|\\x{1F9DB}\\x{1F3FE}|\\x{1F9DB}\\x{1F3FF}|\\x{1F9DB}\\x{1F3FB}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9DB}\\x{1F3FC}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9DB}\\x{1F3FD}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9DB}\\x{1F3FE}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9DB}\\x{1F3FF}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9DB}\\x{1F3FB}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9DB}\\x{1F3FC}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9DB}\\x{1F3FD}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9DB}\\x{1F3FE}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9DB}\\x{1F3FF}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9DC}\\x{1F3FB}|\\x{1F9DC}\\x{1F3FC}|\\x{1F9DC}\\x{1F3FD}|\\x{1F9DC}\\x{1F3FE}|\\x{1F9DC}\\x{1F3FF}|\\x{1F9DC}\\x{1F3FB}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9DC}\\x{1F3FC}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9DC}\\x{1F3FD}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9DC}\\x{1F3FE}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9DC}\\x{1F3FF}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9DC}\\x{1F3FB}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9DC}\\x{1F3FC}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9DC}\\x{1F3FD}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9DC}\\x{1F3FE}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9DC}\\x{1F3FF}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9DD}\\x{1F3FB}|\\x{1F9DD}\\x{1F3FC}|\\x{1F9DD}\\x{1F3FD}|\\x{1F9DD}\\x{1F3FE}|\\x{1F9DD}\\x{1F3FF}|\\x{1F9DD}\\x{1F3FB}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9DD}\\x{1F3FC}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9DD}\\x{1F3FD}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9DD}\\x{1F3FE}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9DD}\\x{1F3FF}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9DD}\\x{1F3FB}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9DD}\\x{1F3FC}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9DD}\\x{1F3FD}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9DD}\\x{1F3FE}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9DD}\\x{1F3FF}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F486}\\x{1F3FB}|\\x{1F486}\\x{1F3FC}|\\x{1F486}\\x{1F3FD}|\\x{1F486}\\x{1F3FE}|\\x{1F486}\\x{1F3FF}|\\x{1F486}\\x{1F3FB}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F486}\\x{1F3FC}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F486}\\x{1F3FD}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F486}\\x{1F3FE}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F486}\\x{1F3FF}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F486}\\x{1F3FB}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F486}\\x{1F3FC}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F486}\\x{1F3FD}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F486}\\x{1F3FE}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F486}\\x{1F3FF}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F487}\\x{1F3FB}|\\x{1F487}\\x{1F3FC}|\\x{1F487}\\x{1F3FD}|\\x{1F487}\\x{1F3FE}|\\x{1F487}\\x{1F3FF}|\\x{1F487}\\x{1F3FB}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F487}\\x{1F3FC}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F487}\\x{1F3FD}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F487}\\x{1F3FE}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F487}\\x{1F3FF}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F487}\\x{1F3FB}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F487}\\x{1F3FC}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F487}\\x{1F3FD}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F487}\\x{1F3FE}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F487}\\x{1F3FF}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F6B6}\\x{1F3FB}|\\x{1F6B6}\\x{1F3FC}|\\x{1F6B6}\\x{1F3FD}|\\x{1F6B6}\\x{1F3FE}|\\x{1F6B6}\\x{1F3FF}|\\x{1F6B6}\\x{1F3FB}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F6B6}\\x{1F3FC}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F6B6}\\x{1F3FD}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F6B6}\\x{1F3FE}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F6B6}\\x{1F3FF}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F6B6}\\x{1F3FB}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F6B6}\\x{1F3FC}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F6B6}\\x{1F3FD}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F6B6}\\x{1F3FE}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F6B6}\\x{1F3FF}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9CD}\\x{1F3FB}|\\x{1F9CD}\\x{1F3FC}|\\x{1F9CD}\\x{1F3FD}|\\x{1F9CD}\\x{1F3FE}|\\x{1F9CD}\\x{1F3FF}|\\x{1F9CD}\\x{1F3FB}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9CD}\\x{1F3FC}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9CD}\\x{1F3FD}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9CD}\\x{1F3FE}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9CD}\\x{1F3FF}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9CD}\\x{1F3FB}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9CD}\\x{1F3FC}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9CD}\\x{1F3FD}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9CD}\\x{1F3FE}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9CD}\\x{1F3FF}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9CE}\\x{1F3FB}|\\x{1F9CE}\\x{1F3FC}|\\x{1F9CE}\\x{1F3FD}|\\x{1F9CE}\\x{1F3FE}|\\x{1F9CE}\\x{1F3FF}|\\x{1F9CE}\\x{1F3FB}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9CE}\\x{1F3FC}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9CE}\\x{1F3FD}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9CE}\\x{1F3FE}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9CE}\\x{1F3FF}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9CE}\\x{1F3FB}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9CE}\\x{1F3FC}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9CE}\\x{1F3FD}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9CE}\\x{1F3FE}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9CE}\\x{1F3FF}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9D1}\\x{1F3FB}\\x{200D}\\x{1F9AF}|\\x{1F9D1}\\x{1F3FC}\\x{200D}\\x{1F9AF}|\\x{1F9D1}\\x{1F3FD}\\x{200D}\\x{1F9AF}|\\x{1F9D1}\\x{1F3FE}\\x{200D}\\x{1F9AF}|\\x{1F9D1}\\x{1F3FF}\\x{200D}\\x{1F9AF}|\\x{1F468}\\x{1F3FB}\\x{200D}\\x{1F9AF}|\\x{1F468}\\x{1F3FC}\\x{200D}\\x{1F9AF}|\\x{1F468}\\x{1F3FD}\\x{200D}\\x{1F9AF}|\\x{1F468}\\x{1F3FE}\\x{200D}\\x{1F9AF}|\\x{1F468}\\x{1F3FF}\\x{200D}\\x{1F9AF}|\\x{1F469}\\x{1F3FB}\\x{200D}\\x{1F9AF}|\\x{1F469}\\x{1F3FC}\\x{200D}\\x{1F9AF}|\\x{1F469}\\x{1F3FD}\\x{200D}\\x{1F9AF}|\\x{1F469}\\x{1F3FE}\\x{200D}\\x{1F9AF}|\\x{1F469}\\x{1F3FF}\\x{200D}\\x{1F9AF}|\\x{1F9D1}\\x{1F3FB}\\x{200D}\\x{1F9BC}|\\x{1F9D1}\\x{1F3FC}\\x{200D}\\x{1F9BC}|\\x{1F9D1}\\x{1F3FD}\\x{200D}\\x{1F9BC}|\\x{1F9D1}\\x{1F3FE}\\x{200D}\\x{1F9BC}|\\x{1F9D1}\\x{1F3FF}\\x{200D}\\x{1F9BC}|\\x{1F468}\\x{1F3FB}\\x{200D}\\x{1F9BC}|\\x{1F468}\\x{1F3FC}\\x{200D}\\x{1F9BC}|\\x{1F468}\\x{1F3FD}\\x{200D}\\x{1F9BC}|\\x{1F468}\\x{1F3FE}\\x{200D}\\x{1F9BC}|\\x{1F468}\\x{1F3FF}\\x{200D}\\x{1F9BC}|\\x{1F469}\\x{1F3FB}\\x{200D}\\x{1F9BC}|\\x{1F469}\\x{1F3FC}\\x{200D}\\x{1F9BC}|\\x{1F469}\\x{1F3FD}\\x{200D}\\x{1F9BC}|\\x{1F469}\\x{1F3FE}\\x{200D}\\x{1F9BC}|\\x{1F469}\\x{1F3FF}\\x{200D}\\x{1F9BC}|\\x{1F9D1}\\x{1F3FB}\\x{200D}\\x{1F9BD}|\\x{1F9D1}\\x{1F3FC}\\x{200D}\\x{1F9BD}|\\x{1F9D1}\\x{1F3FD}\\x{200D}\\x{1F9BD}|\\x{1F9D1}\\x{1F3FE}\\x{200D}\\x{1F9BD}|\\x{1F9D1}\\x{1F3FF}\\x{200D}\\x{1F9BD}|\\x{1F468}\\x{1F3FB}\\x{200D}\\x{1F9BD}|\\x{1F468}\\x{1F3FC}\\x{200D}\\x{1F9BD}|\\x{1F468}\\x{1F3FD}\\x{200D}\\x{1F9BD}|\\x{1F468}\\x{1F3FE}\\x{200D}\\x{1F9BD}|\\x{1F468}\\x{1F3FF}\\x{200D}\\x{1F9BD}|\\x{1F469}\\x{1F3FB}\\x{200D}\\x{1F9BD}|\\x{1F469}\\x{1F3FC}\\x{200D}\\x{1F9BD}|\\x{1F469}\\x{1F3FD}\\x{200D}\\x{1F9BD}|\\x{1F469}\\x{1F3FE}\\x{200D}\\x{1F9BD}|\\x{1F469}\\x{1F3FF}\\x{200D}\\x{1F9BD}|\\x{1F3C3}\\x{1F3FB}|\\x{1F3C3}\\x{1F3FC}|\\x{1F3C3}\\x{1F3FD}|\\x{1F3C3}\\x{1F3FE}|\\x{1F3C3}\\x{1F3FF}|\\x{1F3C3}\\x{1F3FB}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F3C3}\\x{1F3FC}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F3C3}\\x{1F3FD}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F3C3}\\x{1F3FE}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F3C3}\\x{1F3FF}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F3C3}\\x{1F3FB}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F3C3}\\x{1F3FC}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F3C3}\\x{1F3FD}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F3C3}\\x{1F3FE}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F3C3}\\x{1F3FF}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F483}\\x{1F3FB}|\\x{1F483}\\x{1F3FC}|\\x{1F483}\\x{1F3FD}|\\x{1F483}\\x{1F3FE}|\\x{1F483}\\x{1F3FF}|\\x{1F57A}\\x{1F3FB}|\\x{1F57A}\\x{1F3FC}|\\x{1F57A}\\x{1F3FD}|\\x{1F57A}\\x{1F3FE}|\\x{1F57A}\\x{1F3FF}|\\x{1F574}\\x{1F3FB}|\\x{1F574}\\x{1F3FC}|\\x{1F574}\\x{1F3FD}|\\x{1F574}\\x{1F3FE}|\\x{1F574}\\x{1F3FF}|\\x{1F9D6}\\x{1F3FB}|\\x{1F9D6}\\x{1F3FC}|\\x{1F9D6}\\x{1F3FD}|\\x{1F9D6}\\x{1F3FE}|\\x{1F9D6}\\x{1F3FF}|\\x{1F9D6}\\x{1F3FB}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9D6}\\x{1F3FC}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9D6}\\x{1F3FD}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9D6}\\x{1F3FE}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9D6}\\x{1F3FF}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9D6}\\x{1F3FB}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9D6}\\x{1F3FC}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9D6}\\x{1F3FD}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9D6}\\x{1F3FE}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9D6}\\x{1F3FF}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9D7}\\x{1F3FB}|\\x{1F9D7}\\x{1F3FC}|\\x{1F9D7}\\x{1F3FD}|\\x{1F9D7}\\x{1F3FE}|\\x{1F9D7}\\x{1F3FF}|\\x{1F9D7}\\x{1F3FB}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9D7}\\x{1F3FC}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9D7}\\x{1F3FD}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9D7}\\x{1F3FE}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9D7}\\x{1F3FF}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9D7}\\x{1F3FB}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9D7}\\x{1F3FC}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9D7}\\x{1F3FD}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9D7}\\x{1F3FE}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9D7}\\x{1F3FF}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F3C7}\\x{1F3FB}|\\x{1F3C7}\\x{1F3FC}|\\x{1F3C7}\\x{1F3FD}|\\x{1F3C7}\\x{1F3FE}|\\x{1F3C7}\\x{1F3FF}|\\x{1F3C2}\\x{1F3FB}|\\x{1F3C2}\\x{1F3FC}|\\x{1F3C2}\\x{1F3FD}|\\x{1F3C2}\\x{1F3FE}|\\x{1F3C2}\\x{1F3FF}|\\x{1F3CC}\\x{1F3FB}|\\x{1F3CC}\\x{1F3FC}|\\x{1F3CC}\\x{1F3FD}|\\x{1F3CC}\\x{1F3FE}|\\x{1F3CC}\\x{1F3FF}|\\x{1F3CC}\\x{1F3FB}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F3CC}\\x{1F3FC}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F3CC}\\x{1F3FD}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F3CC}\\x{1F3FE}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F3CC}\\x{1F3FF}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F3CC}\\x{1F3FB}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F3CC}\\x{1F3FC}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F3CC}\\x{1F3FD}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F3CC}\\x{1F3FE}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F3CC}\\x{1F3FF}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F3C4}\\x{1F3FB}|\\x{1F3C4}\\x{1F3FC}|\\x{1F3C4}\\x{1F3FD}|\\x{1F3C4}\\x{1F3FE}|\\x{1F3C4}\\x{1F3FF}|\\x{1F3C4}\\x{1F3FB}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F3C4}\\x{1F3FC}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F3C4}\\x{1F3FD}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F3C4}\\x{1F3FE}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F3C4}\\x{1F3FF}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F3C4}\\x{1F3FB}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F3C4}\\x{1F3FC}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F3C4}\\x{1F3FD}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F3C4}\\x{1F3FE}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F3C4}\\x{1F3FF}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F6A3}\\x{1F3FB}|\\x{1F6A3}\\x{1F3FC}|\\x{1F6A3}\\x{1F3FD}|\\x{1F6A3}\\x{1F3FE}|\\x{1F6A3}\\x{1F3FF}|\\x{1F6A3}\\x{1F3FB}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F6A3}\\x{1F3FC}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F6A3}\\x{1F3FD}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F6A3}\\x{1F3FE}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F6A3}\\x{1F3FF}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F6A3}\\x{1F3FB}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F6A3}\\x{1F3FC}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F6A3}\\x{1F3FD}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F6A3}\\x{1F3FE}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F6A3}\\x{1F3FF}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F3CA}\\x{1F3FB}|\\x{1F3CA}\\x{1F3FC}|\\x{1F3CA}\\x{1F3FD}|\\x{1F3CA}\\x{1F3FE}|\\x{1F3CA}\\x{1F3FF}|\\x{1F3CA}\\x{1F3FB}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F3CA}\\x{1F3FC}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F3CA}\\x{1F3FD}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F3CA}\\x{1F3FE}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F3CA}\\x{1F3FF}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F3CA}\\x{1F3FB}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F3CA}\\x{1F3FC}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F3CA}\\x{1F3FD}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F3CA}\\x{1F3FE}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F3CA}\\x{1F3FF}\\x{200D}\\x{2640}\\x{FE0F}|\\x{26F9}\\x{1F3FB}|\\x{26F9}\\x{1F3FC}|\\x{26F9}\\x{1F3FD}|\\x{26F9}\\x{1F3FE}|\\x{26F9}\\x{1F3FF}|\\x{26F9}\\x{1F3FB}\\x{200D}\\x{2642}\\x{FE0F}|\\x{26F9}\\x{1F3FC}\\x{200D}\\x{2642}\\x{FE0F}|\\x{26F9}\\x{1F3FD}\\x{200D}\\x{2642}\\x{FE0F}|\\x{26F9}\\x{1F3FE}\\x{200D}\\x{2642}\\x{FE0F}|\\x{26F9}\\x{1F3FF}\\x{200D}\\x{2642}\\x{FE0F}|\\x{26F9}\\x{1F3FB}\\x{200D}\\x{2640}\\x{FE0F}|\\x{26F9}\\x{1F3FC}\\x{200D}\\x{2640}\\x{FE0F}|\\x{26F9}\\x{1F3FD}\\x{200D}\\x{2640}\\x{FE0F}|\\x{26F9}\\x{1F3FE}\\x{200D}\\x{2640}\\x{FE0F}|\\x{26F9}\\x{1F3FF}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F3CB}\\x{1F3FB}|\\x{1F3CB}\\x{1F3FC}|\\x{1F3CB}\\x{1F3FD}|\\x{1F3CB}\\x{1F3FE}|\\x{1F3CB}\\x{1F3FF}|\\x{1F3CB}\\x{1F3FB}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F3CB}\\x{1F3FC}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F3CB}\\x{1F3FD}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F3CB}\\x{1F3FE}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F3CB}\\x{1F3FF}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F3CB}\\x{1F3FB}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F3CB}\\x{1F3FC}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F3CB}\\x{1F3FD}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F3CB}\\x{1F3FE}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F3CB}\\x{1F3FF}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F6B4}\\x{1F3FB}|\\x{1F6B4}\\x{1F3FC}|\\x{1F6B4}\\x{1F3FD}|\\x{1F6B4}\\x{1F3FE}|\\x{1F6B4}\\x{1F3FF}|\\x{1F6B4}\\x{1F3FB}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F6B4}\\x{1F3FC}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F6B4}\\x{1F3FD}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F6B4}\\x{1F3FE}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F6B4}\\x{1F3FF}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F6B4}\\x{1F3FB}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F6B4}\\x{1F3FC}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F6B4}\\x{1F3FD}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F6B4}\\x{1F3FE}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F6B4}\\x{1F3FF}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F6B5}\\x{1F3FB}|\\x{1F6B5}\\x{1F3FC}|\\x{1F6B5}\\x{1F3FD}|\\x{1F6B5}\\x{1F3FE}|\\x{1F6B5}\\x{1F3FF}|\\x{1F6B5}\\x{1F3FB}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F6B5}\\x{1F3FC}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F6B5}\\x{1F3FD}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F6B5}\\x{1F3FE}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F6B5}\\x{1F3FF}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F6B5}\\x{1F3FB}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F6B5}\\x{1F3FC}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F6B5}\\x{1F3FD}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F6B5}\\x{1F3FE}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F6B5}\\x{1F3FF}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F938}\\x{1F3FB}|\\x{1F938}\\x{1F3FC}|\\x{1F938}\\x{1F3FD}|\\x{1F938}\\x{1F3FE}|\\x{1F938}\\x{1F3FF}|\\x{1F938}\\x{1F3FB}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F938}\\x{1F3FC}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F938}\\x{1F3FD}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F938}\\x{1F3FE}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F938}\\x{1F3FF}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F938}\\x{1F3FB}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F938}\\x{1F3FC}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F938}\\x{1F3FD}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F938}\\x{1F3FE}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F938}\\x{1F3FF}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F93D}\\x{1F3FB}|\\x{1F93D}\\x{1F3FC}|\\x{1F93D}\\x{1F3FD}|\\x{1F93D}\\x{1F3FE}|\\x{1F93D}\\x{1F3FF}|\\x{1F93D}\\x{1F3FB}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F93D}\\x{1F3FC}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F93D}\\x{1F3FD}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F93D}\\x{1F3FE}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F93D}\\x{1F3FF}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F93D}\\x{1F3FB}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F93D}\\x{1F3FC}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F93D}\\x{1F3FD}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F93D}\\x{1F3FE}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F93D}\\x{1F3FF}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F93E}\\x{1F3FB}|\\x{1F93E}\\x{1F3FC}|\\x{1F93E}\\x{1F3FD}|\\x{1F93E}\\x{1F3FE}|\\x{1F93E}\\x{1F3FF}|\\x{1F93E}\\x{1F3FB}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F93E}\\x{1F3FC}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F93E}\\x{1F3FD}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F93E}\\x{1F3FE}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F93E}\\x{1F3FF}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F93E}\\x{1F3FB}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F93E}\\x{1F3FC}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F93E}\\x{1F3FD}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F93E}\\x{1F3FE}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F93E}\\x{1F3FF}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F939}\\x{1F3FB}|\\x{1F939}\\x{1F3FC}|\\x{1F939}\\x{1F3FD}|\\x{1F939}\\x{1F3FE}|\\x{1F939}\\x{1F3FF}|\\x{1F939}\\x{1F3FB}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F939}\\x{1F3FC}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F939}\\x{1F3FD}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F939}\\x{1F3FE}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F939}\\x{1F3FF}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F939}\\x{1F3FB}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F939}\\x{1F3FC}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F939}\\x{1F3FD}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F939}\\x{1F3FE}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F939}\\x{1F3FF}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9D8}\\x{1F3FB}|\\x{1F9D8}\\x{1F3FC}|\\x{1F9D8}\\x{1F3FD}|\\x{1F9D8}\\x{1F3FE}|\\x{1F9D8}\\x{1F3FF}|\\x{1F9D8}\\x{1F3FB}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9D8}\\x{1F3FC}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9D8}\\x{1F3FD}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9D8}\\x{1F3FE}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9D8}\\x{1F3FF}\\x{200D}\\x{2642}\\x{FE0F}|\\x{1F9D8}\\x{1F3FB}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9D8}\\x{1F3FC}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9D8}\\x{1F3FD}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9D8}\\x{1F3FE}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F9D8}\\x{1F3FF}\\x{200D}\\x{2640}\\x{FE0F}|\\x{1F6C0}\\x{1F3FB}|\\x{1F6C0}\\x{1F3FC}|\\x{1F6C0}\\x{1F3FD}|\\x{1F6C0}\\x{1F3FE}|\\x{1F6C0}\\x{1F3FF}|\\x{1F6CC}\\x{1F3FB}|\\x{1F6CC}\\x{1F3FC}|\\x{1F6CC}\\x{1F3FD}|\\x{1F6CC}\\x{1F3FE}|\\x{1F6CC}\\x{1F3FF}|\\x{1F9D1}\\x{1F3FB}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F9D1}\\x{1F3FB}|\\x{1F9D1}\\x{1F3FB}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F9D1}\\x{1F3FC}|\\x{1F9D1}\\x{1F3FB}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F9D1}\\x{1F3FD}|\\x{1F9D1}\\x{1F3FB}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F9D1}\\x{1F3FE}|\\x{1F9D1}\\x{1F3FB}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F9D1}\\x{1F3FF}|\\x{1F9D1}\\x{1F3FC}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F9D1}\\x{1F3FB}|\\x{1F9D1}\\x{1F3FC}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F9D1}\\x{1F3FC}|\\x{1F9D1}\\x{1F3FC}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F9D1}\\x{1F3FD}|\\x{1F9D1}\\x{1F3FC}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F9D1}\\x{1F3FE}|\\x{1F9D1}\\x{1F3FC}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F9D1}\\x{1F3FF}|\\x{1F9D1}\\x{1F3FD}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F9D1}\\x{1F3FB}|\\x{1F9D1}\\x{1F3FD}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F9D1}\\x{1F3FC}|\\x{1F9D1}\\x{1F3FD}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F9D1}\\x{1F3FD}|\\x{1F9D1}\\x{1F3FD}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F9D1}\\x{1F3FE}|\\x{1F9D1}\\x{1F3FD}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F9D1}\\x{1F3FF}|\\x{1F9D1}\\x{1F3FE}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F9D1}\\x{1F3FB}|\\x{1F9D1}\\x{1F3FE}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F9D1}\\x{1F3FC}|\\x{1F9D1}\\x{1F3FE}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F9D1}\\x{1F3FD}|\\x{1F9D1}\\x{1F3FE}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F9D1}\\x{1F3FE}|\\x{1F9D1}\\x{1F3FE}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F9D1}\\x{1F3FF}|\\x{1F9D1}\\x{1F3FF}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F9D1}\\x{1F3FB}|\\x{1F9D1}\\x{1F3FF}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F9D1}\\x{1F3FC}|\\x{1F9D1}\\x{1F3FF}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F9D1}\\x{1F3FD}|\\x{1F9D1}\\x{1F3FF}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F9D1}\\x{1F3FE}|\\x{1F9D1}\\x{1F3FF}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F9D1}\\x{1F3FF}|\\x{1F46D}\\x{1F3FB}|\\x{1F469}\\x{1F3FB}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F469}\\x{1F3FC}|\\x{1F469}\\x{1F3FB}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F469}\\x{1F3FD}|\\x{1F469}\\x{1F3FB}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F469}\\x{1F3FE}|\\x{1F469}\\x{1F3FB}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F469}\\x{1F3FF}|\\x{1F469}\\x{1F3FC}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F469}\\x{1F3FB}|\\x{1F46D}\\x{1F3FC}|\\x{1F469}\\x{1F3FC}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F469}\\x{1F3FD}|\\x{1F469}\\x{1F3FC}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F469}\\x{1F3FE}|\\x{1F469}\\x{1F3FC}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F469}\\x{1F3FF}|\\x{1F469}\\x{1F3FD}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F469}\\x{1F3FB}|\\x{1F469}\\x{1F3FD}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F469}\\x{1F3FC}|\\x{1F46D}\\x{1F3FD}|\\x{1F469}\\x{1F3FD}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F469}\\x{1F3FE}|\\x{1F469}\\x{1F3FD}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F469}\\x{1F3FF}|\\x{1F469}\\x{1F3FE}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F469}\\x{1F3FB}|\\x{1F469}\\x{1F3FE}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F469}\\x{1F3FC}|\\x{1F469}\\x{1F3FE}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F469}\\x{1F3FD}|\\x{1F46D}\\x{1F3FE}|\\x{1F469}\\x{1F3FE}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F469}\\x{1F3FF}|\\x{1F469}\\x{1F3FF}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F469}\\x{1F3FB}|\\x{1F469}\\x{1F3FF}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F469}\\x{1F3FC}|\\x{1F469}\\x{1F3FF}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F469}\\x{1F3FD}|\\x{1F469}\\x{1F3FF}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F469}\\x{1F3FE}|\\x{1F46D}\\x{1F3FF}|\\x{1F46B}\\x{1F3FB}|\\x{1F469}\\x{1F3FB}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F468}\\x{1F3FC}|\\x{1F469}\\x{1F3FB}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F468}\\x{1F3FD}|\\x{1F469}\\x{1F3FB}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F468}\\x{1F3FE}|\\x{1F469}\\x{1F3FB}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F468}\\x{1F3FF}|\\x{1F469}\\x{1F3FC}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F468}\\x{1F3FB}|\\x{1F46B}\\x{1F3FC}|\\x{1F469}\\x{1F3FC}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F468}\\x{1F3FD}|\\x{1F469}\\x{1F3FC}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F468}\\x{1F3FE}|\\x{1F469}\\x{1F3FC}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F468}\\x{1F3FF}|\\x{1F469}\\x{1F3FD}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F468}\\x{1F3FB}|\\x{1F469}\\x{1F3FD}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F468}\\x{1F3FC}|\\x{1F46B}\\x{1F3FD}|\\x{1F469}\\x{1F3FD}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F468}\\x{1F3FE}|\\x{1F469}\\x{1F3FD}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F468}\\x{1F3FF}|\\x{1F469}\\x{1F3FE}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F468}\\x{1F3FB}|\\x{1F469}\\x{1F3FE}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F468}\\x{1F3FC}|\\x{1F469}\\x{1F3FE}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F468}\\x{1F3FD}|\\x{1F46B}\\x{1F3FE}|\\x{1F469}\\x{1F3FE}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F468}\\x{1F3FF}|\\x{1F469}\\x{1F3FF}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F468}\\x{1F3FB}|\\x{1F469}\\x{1F3FF}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F468}\\x{1F3FC}|\\x{1F469}\\x{1F3FF}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F468}\\x{1F3FD}|\\x{1F469}\\x{1F3FF}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F468}\\x{1F3FE}|\\x{1F46B}\\x{1F3FF}|\\x{1F46C}\\x{1F3FB}|\\x{1F468}\\x{1F3FB}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F468}\\x{1F3FC}|\\x{1F468}\\x{1F3FB}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F468}\\x{1F3FD}|\\x{1F468}\\x{1F3FB}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F468}\\x{1F3FE}|\\x{1F468}\\x{1F3FB}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F468}\\x{1F3FF}|\\x{1F468}\\x{1F3FC}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F468}\\x{1F3FB}|\\x{1F46C}\\x{1F3FC}|\\x{1F468}\\x{1F3FC}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F468}\\x{1F3FD}|\\x{1F468}\\x{1F3FC}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F468}\\x{1F3FE}|\\x{1F468}\\x{1F3FC}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F468}\\x{1F3FF}|\\x{1F468}\\x{1F3FD}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F468}\\x{1F3FB}|\\x{1F468}\\x{1F3FD}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F468}\\x{1F3FC}|\\x{1F46C}\\x{1F3FD}|\\x{1F468}\\x{1F3FD}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F468}\\x{1F3FE}|\\x{1F468}\\x{1F3FD}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F468}\\x{1F3FF}|\\x{1F468}\\x{1F3FE}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F468}\\x{1F3FB}|\\x{1F468}\\x{1F3FE}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F468}\\x{1F3FC}|\\x{1F468}\\x{1F3FE}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F468}\\x{1F3FD}|\\x{1F46C}\\x{1F3FE}|\\x{1F468}\\x{1F3FE}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F468}\\x{1F3FF}|\\x{1F468}\\x{1F3FF}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F468}\\x{1F3FB}|\\x{1F468}\\x{1F3FF}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F468}\\x{1F3FC}|\\x{1F468}\\x{1F3FF}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F468}\\x{1F3FD}|\\x{1F468}\\x{1F3FF}\\x{200D}\\x{1F91D}\\x{200D}\\x{1F468}\\x{1F3FE}|\\x{1F46C}\\x{1F3FF}|\\x{1F3FB}|\\x{1F3FC}|\\x{1F3FD}|\\x{1F3FE}|\\x{1F3FF}'; - - return preg_replace_callback('/'.$regExp.'|'.$regExpTones.'/u', function ($matches) { - return '&#'.hexdec(bin2hex(mb_convert_encoding("$matches[0]", 'UTF-32', 'UTF-8'))).';'; - }, $text); - } - - /** - * Replace insert tags with their values. - * - * @param string $buffer The text with the tags to be replaced - * @param bool $cache If false, non-cacheable tags will be replaced - * - * @return string The text with the replaced tags - * - * @codeCoverageIgnore - * - * @deprecated Use Controller::replaceInsertTags as adapter - */ - public function replaceInsertTags(?string $buffer, bool $cache = true) - { - /** @var Controller $controller */ - $controller = $this->framework->getAdapter(Controller::class); - - return $controller->replaceInsertTags($buffer, $cache); - } -} diff --git a/src/Template/TemplateUtil.php b/src/Template/TemplateUtil.php deleted file mode 100644 index cb19e361..00000000 --- a/src/Template/TemplateUtil.php +++ /dev/null @@ -1,465 +0,0 @@ -kernel = $container->get('kernel'); - $this->container = $container; - $this->containerUtil = $container->get('huh.utils.container'); - } - - /** - * Get a list of all available templates. - */ - public function getAllTemplates() - { - $strCacheDir = $this->container->getParameter('kernel.cache_dir'); - - // Try to load from cache - if (file_exists($strCacheDir.'/contao/config/twig-templates.php')) { - self::$twigFiles = include $strCacheDir.'/contao/config/twig-templates.php'; - - return self::$twigFiles; - } - - $bundles = $this->kernel->getBundles(); - - if (\is_array($bundles)) { - foreach (array_reverse($bundles) as $key => $value) { - $path = $this->kernel->locateResource("@$key"); - - $dir = rtrim($path, '/').'/Resources/views'; - - if (!is_dir($dir)) { - continue; - } - $finder = new Finder(); - $twigKey = preg_replace('/Bundle$/', '', $key); - - foreach ($finder->in($dir)->files()->name('*.twig') as $file) { - /** @var SplFileInfo $file */ - $name = $file->getBasename(); - $legacyName = false !== strpos($name, 'html.twig') ? $file->getBasename('.html.twig') : $name; - - if (isset(self::$twigFiles[$name])) { - continue; - } - - self::$twigFiles[$name] = "@$twigKey/".$file->getRelativePathname(); - - if ($legacyName !== $name) { - self::$twigFiles[$legacyName] = self::$twigFiles[$name]; - } - } - } - } - - foreach ($this->container->get('contao.resource_finder')->findIn('templates')->name('*.twig') as $file) { - $name = $file->getBasename(); - $legacyName = false !== strpos($name, 'html.twig') ? $file->getBasename('.html.twig') : $name; - - /* @var SplFileInfo $file */ - self::$twigFiles[$name] = $file->getRealPath(); - - if ($legacyName !== $name) { - self::$twigFiles[$legacyName] = self::$twigFiles[$name]; - } - } - - // add root templates - $rootTemplates = $this->findTemplates($this->containerUtil->getProjectDir().'/templates/'); - - if (\is_array($rootTemplates)) { - foreach ($rootTemplates as $file) { - $name = basename($file); - $legacyName = false !== strpos($name, 'html.twig') ? basename($file, '.html.twig') : $name; - - self::$twigFiles[$name] = $file; - - if ($legacyName !== $name) { - self::$twigFiles[$legacyName] = self::$twigFiles[$name]; - } - } - } - - return self::$twigFiles; - } - - /** - * Return all template files of a particular group as array. - * - * @param string $prefix The template name prefix (e.g. "ce_") - * @param string $format The file extension - * - * @return array An array of template names (html.twig templates without file extension, others with file extension) - * - * @coversNothing As long as Controller::getTemplateGroup is not testable (ThemeModel…) - */ - public function getTemplateGroup(string $prefix, string $format = 'html.twig'): array - { - $arrTemplates = []; - - $objFilesystem = new Filesystem(); - $files = []; - - try { - $bundles = $this->kernel->getBundles(); - // if file from Controller::getTemplate() does not exist, search template in bundle views directory and return twig bundle path - if (\is_array($bundles) && '.twig' === substr($format, -5)) { - foreach (array_reverse($bundles) as $key => $value) { - $path = $this->kernel->locateResource("@$key"); - $dir = rtrim($path, '/').'/Resources/views'; - - if (!is_dir($dir)) { - continue; - } - $finder = new Finder(); - $finder->in($dir); - $finder->files()->name('/'.$prefix.'.*'.$format.'/'); - - /* @var SplFileInfo $file */ - foreach ($finder as $file) { - $strTemplate = 'html.twig' === $format ? $file->getBasename('.'.$format) : $file->getFilename(); // Backward compability for html.twig templates - $arrTemplates[$strTemplate]['name'] = $file->getBasename(); - $arrTemplates[$strTemplate]['scopes'][] = rtrim($objFilesystem->makePathRelative($file->getPath(), $this->containerUtil->getProjectDir()), '/'); - } - } - } - - foreach ($this->container->get('contao.resource_finder')->findIn('templates')->name('/'.$prefix.'.*'.$format.'/') as $file) { - /* @var SplFileInfo $file */ - $strTemplate = 'html.twig' === $format ? $file->getBasename('.'.$format) : $file->getFilename(); // Backward compability for html.twig templates - $arrTemplates[$strTemplate]['name'] = $file->getBasename(); - $arrTemplates[$strTemplate]['scopes'][] = rtrim($objFilesystem->makePathRelative($file->getPath(), $this->containerUtil->getProjectDir()), '/'); - } - } catch (\InvalidArgumentException $e) { - } - - // Get the default templates - foreach ($files as $strTemplate) { - $arrTemplates[$strTemplate]['name'] = basename($strTemplate); - $arrTemplates[$strTemplate]['scopes'][] = 'root'; - } - - $arrCustomized = $this->findTemplates($this->containerUtil->getProjectDir().'/templates/', $prefix, $format); - - // Add the customized templates - if (\is_array($arrCustomized)) { - foreach ($arrCustomized as $strFile) { - $strTemplate = 'html.twig' === $format ? basename($strFile, '.'.$format) : basename($strFile); // Backward compability for html.twig templates - $arrTemplates[$strTemplate]['name'] = basename($strFile); - $arrTemplates[$strTemplate]['scopes'][] = $GLOBALS['TL_LANG']['MSC']['global']; - } - } - - // Do not look for back end templates in theme folders (see #5379) - if ('be_' != $prefix && 'mail_' != $prefix) { - // Try to select the themes (see #5210) - try { - /** - * @var ThemeModel - */ - $adapter = $this->container->get('contao.framework')->getAdapter(ThemeModel::class); - - $objTheme = $adapter->findAll(['order' => 'name']); - } catch (\Exception $e) { - $objTheme = null; - } - - // Add the theme templates - if (null !== $objTheme) { - while ($objTheme->next()) { - if ('' != $objTheme->templates) { - $arrThemeTemplates = $this->findTemplates($this->containerUtil->getProjectDir().'/'.$objTheme->templates.'/', $prefix, $format); - - if (\is_array($arrThemeTemplates)) { - foreach ($arrThemeTemplates as $strFile) { - $strTemplate = 'html.twig' === $format ? basename($strFile, '.'.$format) : basename($strFile); // Backward compability for html.twig templates - $arrTemplates[$strTemplate]['name'] = basename($strFile); - $arrTemplates[$strTemplate]['scopes'][] = $objTheme->name; - } - } - } - } - } - } - - // Show the template sources (see #6875) - foreach ($arrTemplates as $k => $v) { - $scope = array_filter( - $v['scopes'], - function ($a) { - return 'root' != $a; - } - ); - - if (empty($v)) { - $arrTemplates[$k] = $v['name']; - } else { - $arrTemplates[$k] = $v['name'].' ('.implode(', ', $scope).')'; - } - } - - // Sort the template names - ksort($arrTemplates); - - return $arrTemplates; - } - - /** - * Find a particular template file and return its path. - * - * @param string $name The name of the template - * @param string $format The file extension - * - * @throws \InvalidArgumentException If $strFormat is unknown - * @throws \RuntimeException If the template group folder is insecure - * @throws \Twig_Error_Loader - * - * @return string The path to the template file - */ - public function getTemplate(string $name, string $format = 'twig'): string - { - // Check for a theme folder - if ($this->containerUtil->isFrontend()) { - /* @var PageModel $objPage */ - global $objPage; - - if ('' != $objPage->templateGroup) { - if (\Validator::isInsecurePath($objPage->templateGroup)) { - throw new \RuntimeException('Invalid path '.$objPage->templateGroup); - } - - $templates = $this->findTemplates($this->containerUtil->getProjectDir().'/'.$objPage->templateGroup, $name, $format); - - if (!empty($templates)) { - return reset($templates); - } - } - } - - if (!isset(self::$twigFiles[$name])) { - throw new LoaderError(sprintf('Unable to find template "%s".', $name)); - } - - return self::$twigFiles[$name]; - } - - /** - * Return the files matching a GLOB pattern. - * - * @param string $pattern - * - * @return array - */ - public function findTemplates(string $path, string $pattern = null, string $format = 'twig') - { - // Use glob() if possible - if (false === strpos($path, '/**/') && (\defined('GLOB_BRACE') || false === strpos($path, '{'))) { - $templates = glob(rtrim($path, '/').'/*.{'.$format.'}', \defined('GLOB_BRACE') ? \GLOB_BRACE : 0); - - return null === $pattern ? $templates : preg_grep('$'.$pattern.'$', $templates); - } - - $pattern = rtrim($path, '/').(null === $pattern ? '' : $pattern).'/*.{'.$format.'}'; - - $finder = new Finder(); - $regex = Glob::toRegex($pattern); - - // All files in the given template folder - $filesIterator = $finder->files()->followLinks()->sortByName()->in(\dirname($pattern)); - - // Match the actual regex and filter the files - $filesIterator = $filesIterator->filter( - function (\SplFileInfo $info) use ($regex) { - $path = $info->getPathname(); - - return preg_match($regex, $path) && $info->isFile(); - } - ); - - $files = iterator_to_array($filesIterator); - - return array_keys($files); - } - - /** - * remove TEMPLATE START/END comment from template if in debug mode. - * - * @param $section - * - * @return mixed - */ - public function removeTemplateComment($template) - { - $template = Controller::replaceInsertTags($template); - - if (!empty($template)) { - $template = preg_replace('//', '', $template); - $template = trim(preg_replace('/\r?\n|\r/', '', $template)); - } - - return $template; - } - - /** - * Return true, if the template part is empty. - * Template comments from debug and white spaces are treated as empty. - * - * Datatypes other than string (typical null) are also treated as empty. - * - * @param string $template - * - * @return bool - */ - public function isTemplatePartEmpty($template = null) - { - if (!\is_string($template)) { - return true; - } - - return empty($this->removeTemplateComment($template)); - } - - public function getPageAliasAsCssClass() - { - global $objPage; - - return str_replace(['/', 'ä', 'ö', 'ü'], ['_', 'ae', 'oe', 'ue'], $objPage->alias); - } - - /** - * Renders a twig template with data. - * - * @param string $name The twig template name - * @param array $context The twig template context data - * - * @throws \Psr\Cache\InvalidArgumentException - * @throws \Twig_Error_Loader - * @throws \Twig_Error_Runtime - * @throws \Twig_Error_Syntax - * - * @return string - */ - public function renderTwigTemplate(string $name, array $context = []) - { - if (is_subclass_of($this->container->get('event_dispatcher'), 'Symfony\Contracts\EventDispatcher\EventDispatcherInterface')) { - $event = $this->container->get('event_dispatcher')->dispatch( - new RenderTwigTemplateEvent( - $name, $context - ), - RenderTwigTemplateEvent::NAME - ); - } else { - /** @noinspection PhpParamsInspection */ - $event = $this->container->get('event_dispatcher')->dispatch( - RenderTwigTemplateEvent::NAME, - new RenderTwigTemplateEvent( - $name, $context - ) - ); - } - - $templatePath = $this->getTemplate($event->getTemplate()); - $buffer = $this->container->get('twig')->render( - $templatePath, - $event->getContext() - ); - - // Add start and end markers in debug mode - if (Config::get('debugMode')) { - $strRelPath = $templatePath; - $buffer = "\n\n$buffer\n\n"; - } - - return $buffer; - } - - /** - * Find a particular template file within all bundles and return its path. - * - * @param string $name The name of the template - * @param string $format The file extension - * - * @throws \InvalidArgumentException If $strFormat is unknown - * @throws \RuntimeException If the template group folder is insecure - * - * @return string The path to the template file - */ - public function getBundleTemplate(string $name, string $format = 'html.twig'): string - { - $templatePath = $name; - - $bundles = $this->kernel->getBundles(); - // if file from Controller::getTemplate() does not exist, search template in bundle views directory and return twig bundle path - if (\is_array($bundles) && '.twig' === substr($format, -5)) { - $pattern = $name.'.'.$format; - - foreach (array_reverse($bundles) as $key => $value) { - $path = $this->kernel->locateResource("@$key"); - $finder = new Finder(); - $finder->in($path); - $finder->files()->name($pattern); - $twigKey = preg_replace('/Bundle$/', '', $key); - - foreach ($finder as $val) { - $explodurl = explode('Resources'.\DIRECTORY_SEPARATOR.'views'.\DIRECTORY_SEPARATOR, $val->getRelativePathname()); - $string = end($explodurl); - $templatePath = "@$twigKey/$string"; - - break 2; - } - } - } - - return $templatePath; - } -} diff --git a/src/Traits/PersonTrait.php b/src/Traits/PersonTrait.php deleted file mode 100644 index 8fbd9145..00000000 --- a/src/Traits/PersonTrait.php +++ /dev/null @@ -1,107 +0,0 @@ -modelUtil->findModelInstanceByPk(static::TABLE, $userId))) { - return null; - } - - if (empty($groups = StringUtil::deserialize($userModel->groups, true))) { - return null; - } - - $columns = [static::TABLE.'_group.id IN('.implode(',', array_map('\intval', $groups)).')']; - - if ($this->modelUtil instanceof \HeimrichHannot\UtilsBundle\Util\Model\ModelUtil) { - /* @var \HeimrichHannot\UtilsBundle\Util\Model\ModelUtil $this->modelUtil */ - $this->modelUtil->addPublishedCheckToModelArrays(static::TABLE.'_group', $columns, [ - 'publishedField' => 'disable', - 'invertPublishedField' => true, - ]); - } else { - $this->modelUtil->addPublishedCheckToModelArrays(static::TABLE.'_group', 'disable', 'start', 'stop', $columns, ['invertPublishedField' => true]); - } - - return $this->modelUtil->findModelInstancesBy(static::TABLE.'_group', $columns, []); - } - - /** - * @param int $groupId - * - * Checks given user group is active and given user belongs to this group - */ - public function hasActiveGroup(int $userId, int $groupId): bool - { - $activeGroups = $this->getActiveGroups($userId); - - if (!$activeGroups) { - return false; - } - - foreach ($activeGroups as $group) { - if ((int) ($group->id) === $groupId) { - return true; - } - } - - return false; - } - - /** - * @throws \Exception - */ - protected function findActiveByGroups(ContaoFramework $contaoFramework, DatabaseUtil $databaseUtil, string $table, array $groups, array $options = []): ?Collection - { - /** @var class-string $modelClass */ - $modelClass = $contaoFramework->getAdapter(Model::class)->getClassFromTable($table); - - if (!\is_array($groups) || empty($groups = array_filter($groups, function ($k) { - return !empty($k) && is_numeric($k); - }))) { - return null; - } - - /** @var Model $adapter */ - $adapter = $contaoFramework->getAdapter($modelClass); - - $time = Date::floorToMinute(); - $values = []; - - $columns = ["($table.start='' OR $table.start<='$time') AND ($table.stop='' OR $table.stop>'".($time + 60)."') AND $table.disable=''"]; - - [$columns[], $tmpValues] = $databaseUtil->createWhereForSerializedBlob('groups', $groups); - $values = array_merge(array_values($values), array_values($tmpValues)); - - return $adapter->findBy($columns, $values, $options); - } -} diff --git a/src/Twig/ArrayExtension.php b/src/Twig/ArrayExtension.php deleted file mode 100644 index cd440921..00000000 --- a/src/Twig/ArrayExtension.php +++ /dev/null @@ -1,37 +0,0 @@ -get('huh.utils.date'); - - $date = date($format, $timestamp); - - // translate months - $date = $dateUtil->translateMonths($date); - - return $date; - } -} diff --git a/src/Twig/DcaExtension.php b/src/Twig/DcaExtension.php deleted file mode 100644 index 7085c8af..00000000 --- a/src/Twig/DcaExtension.php +++ /dev/null @@ -1,31 +0,0 @@ -container->get('huh.utils.dca')->getFieldLabel($table, $field); - } -} diff --git a/src/Twig/DownloadExtension.php b/src/Twig/DownloadExtension.php deleted file mode 100644 index aa630f1c..00000000 --- a/src/Twig/DownloadExtension.php +++ /dev/null @@ -1,244 +0,0 @@ -requestStack = $requestStack; - $this->utils = $utils; - $this->twig = $twig; - } - - /** - * Get list of twig filters. - * - * @return array|\Twig_SimpleFilter[] - */ - public function getFilters() - { - return [ - new TwigFilter('download', [$this, 'getDownload']), - new TwigFilter('download_link', [$this, 'getDownloadLink']), - new TwigFilter('download_path', [$this, 'getDownloadPath']), - new TwigFilter('download_data', [$this, 'getDownloadData']), - new TwigFilter('download_title', [$this, 'getDownloadTitle']), - ]; - } - - /** - * Get download data based on given path/uuid. - * - * @param mixed $path File path/uuid - * @param array $data Add custom data here - * - * @return array|null Download element data - */ - public function getDownloadData($path, array $data = []): ?array - { - if (Validator::isUuid($path)) { - $path = $this->utils->file()->getPathFromUuid($path); - } else { - $path = Controller::replaceInsertTags($path); - } - - if (!$path) { - return null; - } - - try { - $file = new File(urldecode($path)); - } catch (\Exception $e) { - return null; - } - - $model = $file->getModel(); - - if (null === $model) { - try { - Dbafs::addResource($file->path); - } catch (\Exception $e) { - return null; - } - - if (null === $model) { - return null; - } - } - - $request = $this->requestStack->getCurrentRequest(); - - if (!$request) { - return null; - } - - $requestedFile = $request->query->get('file'); - - // Send the file to the browser and do not send a 404 header (see #4632) - if ('' != $requestedFile && $requestedFile == $file->path) { - try { - ob_clean(); - Controller::sendFileToBrowser($requestedFile); - } catch (ResponseException $e) { - $e->getResponse()->send(); - } - - return null; - } - - $fileData['model'] = $file->getModel()->row(); - - $allowedDownload = StringUtil::trimsplit(',', strtolower(Config::get('allowedDownload'))); - - // Return if the file type is not allowed - if (!\in_array($file->extension, $allowedDownload)) { - return null; - } - - if (!isset($data['linkTitle'])) { - $fileData['linkTitle'] = StringUtil::specialchars($file->basename); - } - - if (!isset($data['titleText'])) { - $data['titleText'] = sprintf($GLOBALS['TL_LANG']['MSC']['download'], $file->basename); - } - - $strHref = Environment::get('request'); - - // Remove an existing file parameter (see #5683) - $strHref = $this->utils->url()->addQueryStringParameterToUrl('file='.System::urlEncode($file->value), $strHref); - - $fileData['link'] = $fileData['linkTitle']; - $fileData['title'] = StringUtil::specialchars($data['titleText']); - $fileData['href'] = $strHref; - $fileData['filesize'] = System::getReadableSize($file->filesize, 1); - $fileData['icon'] = Image::getPath($file->icon); - $fileData['mime'] = $file->mime; - $fileData['extension'] = $file->extension; - $fileData['path'] = $file->dirname; - - return array_merge($fileData, $data); - } - - /** - * Get download based on given path/uuid. - * - * @param mixed $path File path/uuid - * @param bool $download Return link as download link if true, as download path if false - * @param array $data Add custom data here - * @param string $template Use custom download template - * - * @throws \Twig\Error\LoaderError - * @throws \Twig\Error\RuntimeError - * @throws \Twig\Error\SyntaxError - * - * @return string Download html element - */ - public function getDownload($path, bool $download = true, array $data = [], string $template = '@HeimrichHannotContaoUtils/download.html.twig'): string - { - if (null === ($data = $this->getDownloadData($path, $data))) { - return ''; - } - - if (false === $download) { - $data['href'] = $data['model']['path']; - $data['target'] = true; - } - - try { - return $this->twig->render($template, $data); - } catch (Twig_Error $e) { - } catch (Error $error) { - } - - return ''; - } - - /** - * Get download link `?file=` based on given path/uuid. - * - * @param mixed $path File path/uuid - * @param array $data Add custom data here - * - * @return string File download link - */ - public function getDownloadLink($path, array $data = []): string - { - if (null === ($data = $this->getDownloadData($path, $data))) { - return ''; - } - - return $data['href']; - } - - /** - * Get download path based on given path/uuid. - * - * @param mixed $path File path/uuid - * @param array $data Add custom data here - * - * @return string File path - */ - public function getDownloadPath($path, array $data = []): string - { - if (null === ($data = $this->getDownloadData($path, $data))) { - return ''; - } - - return $data['model']['path']; - } - - /** - * Get download title based on given path/uuid. - * - * @param mixed $path File path/uuid - * @param array $data Add custom data here - * - * @return string Download title - */ - public function getDownloadTitle($path, array $data = []): string - { - if (null === ($data = $this->getDownloadData($path, $data))) { - return ''; - } - - return $data['title']; - } -} diff --git a/src/Twig/FileExtension.php b/src/Twig/FileExtension.php deleted file mode 100644 index 5e005ff1..00000000 --- a/src/Twig/FileExtension.php +++ /dev/null @@ -1,159 +0,0 @@ -container->get('huh.utils.file')->getFileFromUuid($file))) { - return []; - } - - $fileData = $this->container->get('huh.utils.class')->jsonSerialize($fileObj, $data, array_merge_recursive($jsonSerializeOptions, ['ignoreMethods' => true])); - - foreach (static::FILE_OBJECT_PROPERTIES as $property) { - try { - $fileData[$property] = $fileObj->{$property} ?? null; - } catch (\Exception $e) { - $fileData[$property] = null; - } - $fileData['readableFilesize'] = System::getReadableSize($fileObj->filesize, 1); - } - - $fileData['exists'] = $fileObj->exists(); - - return array_merge($fileData, $data); - } - - /** - * Get file path based on given uuid. - * - * @param mixed $file File uuid - * - * @return string File path - */ - public function getFilePath($file): string - { - if (null === ($fileObj = $this->container->get('huh.utils.file')->getFileFromUuid($file))) { - return ''; - } - - return $fileObj->path; - } - - public function getFileContent($file) - { - if (Validator::isUuid($file)) { - /** @var File $fileObj */ - if (null === ($fileObj = $this->container->get('huh.utils.file')->getFileFromUuid($file))) { - return ''; - } - } elseif (\is_string($file)) { - $file = str_replace($this->container->getParameter('kernel.project_dir').'/', '', $file); - - if (null === ($fileObj = new File($file)) || !$fileObj->exists()) { - return ''; - } - } else { - return null; - } - - return $fileObj->getContent(); - } -} diff --git a/src/Twig/ImageExtension.php b/src/Twig/ImageExtension.php deleted file mode 100644 index 5f5c108c..00000000 --- a/src/Twig/ImageExtension.php +++ /dev/null @@ -1,192 +0,0 @@ - 'URL', class => 'img css class']… - * @param string $template Use custom image template - * - * @throws \Psr\Cache\InvalidArgumentException - * @throws \Twig_Error_Loader - * @throws \Twig_Error_Runtime - * @throws \Twig_Error_Syntax - * - * @return string Image html element with given size - */ - public function getImage($image, $size = null, array $data = [], string $template = 'image.html.twig'): string - { - $imageData = $this->getImageData($image, $size, $data); - - if (empty($imageData)) { - return ''; - } - - return $this->container->get('huh.utils.template')->renderTwigTemplate($template, $imageData); - } - - /** - * Get image data based on given path/uuid. - * - * @param mixed $image File path/uuid - * @param string|array $size Array or serialized string containing [width, height, imageSize-ID] - * @param array $data Add image data here [href => 'URL', class => 'img css class']… - * - * @return array Image data - */ - public function getImageData($image, $size = null, array $data = []): array - { - // $imageData = []; - - $data['singleSRC'] = $image; - $size = $data['size'] = \is_array($size) ? $size : StringUtil::deserialize($size); - - // remove empty imageSize passed in - if (empty($size) || (\is_array($size) && empty(array_filter($size)))) { - unset($data['size']); - } - - // $this->container->get('huh.utils.image')->addToTemplateData('image', 'addImage', $imageData, $data); - - $fileModel = null; - - if (Validator::isUuid($image)) - { - if (!$fileModel = FilesModel::findByUuid($image)) - return []; - $data['singleSRC'] = $fileModel->path; - } - - $template = new FrontendTemplate(); - Controller::addImageToTemplate($template, $data, null, null, $fileModel); - - return $template->getData(); - - /* - if (empty($imageData)) { - return []; - } - - $result = array_merge($imageData, $data); - - if (isset($imageData['picture']) && isset($data['picture'])) { - $result['picture'] = array_merge($imageData['picture'], $data['picture']); - } - - return $result; - */ - } - - /** - * Get image caption based on given path/uuid. - * - * @param mixed $image File path/uuid - * - * @return string|null Image caption if available, else null - */ - public function getImageCaption($image): ?string - { - if (null === ($file = $this->container->get('huh.utils.file')->getFileFromUuid($image))) { - return null; - } - - $meta = StringUtil::deserialize($file->getModel()->meta, true); - - if (!isset($meta[$GLOBALS['TL_LANGUAGE']]['caption'])) { - return null; - } - - return $meta[$GLOBALS['TL_LANGUAGE']]['caption']; - } - - /** - * Get image width based on given path/uuid. - * - * @param mixed $image File path/uuid - * - * @return string|null Image caption if available, else null - */ - public function getImageWidth($image): ?string - { - if (null === ($file = $this->container->get('huh.utils.file')->getFileFromUuid($image))) { - return null; - } - - return $file->width; - } - - public function getImageGallery($images, string $template = 'image_gallery.html.twig'): string - { - $galleryArray = \is_array($images) ? $images : StringUtil::deserialize($images, true); - $galleryObjects = []; - - foreach ($galleryArray as $k => $v) { - $galleryObjects['imageGallery'][$k] = $this->getImageData($v); - $galleryObjects['imageGallery'][$k]['alt'] = $this->container->get('huh.utils.file')->getFileFromUuid($v)->name; - } - - if (empty($galleryObjects)) { - return []; - } - - return $this->container->get('huh.utils.template')->renderTwigTemplate($template, $galleryObjects); - } - - public function getImageSize($size) - { - $result = []; - - $size = \is_array($size) ? $size : StringUtil::deserialize($size, true); - - if (isset($size[2]) && $size[2] && - null !== ($imageSize = $this->container->get('huh.utils.model')->findModelInstanceByPk('tl_image_size', $size[2]))) { - $result['width'] = $imageSize->width; - $result['height'] = $imageSize->height; - } else { - $result['width'] = $size[0]; - $result['height'] = $size[1]; - } - - return $result; - } -} diff --git a/src/Twig/StringExtension.php b/src/Twig/StringExtension.php index 45e25887..bdde5ff3 100644 --- a/src/Twig/StringExtension.php +++ b/src/Twig/StringExtension.php @@ -8,27 +8,16 @@ namespace HeimrichHannot\UtilsBundle\Twig; -use Contao\Controller; -use Contao\CoreBundle\Framework\ContaoFrameworkInterface; -use HeimrichHannot\UtilsBundle\String\AnonymizerUtil; +use HeimrichHannot\UtilsBundle\Util\Utils; use Twig\Extension\AbstractExtension; use Twig\TwigFilter; class StringExtension extends AbstractExtension { - /** - * @var AnonymizerUtil - */ - private $anonymizerUtil; - /** - * @var ContaoFrameworkInterface - */ - private $framework; - - public function __construct(AnonymizerUtil $anonymizerUtil, ContaoFrameworkInterface $framework) + public function __construct( + protected Utils $utils, + ) { - $this->anonymizerUtil = $anonymizerUtil; - $this->framework = $framework; } /** @@ -39,32 +28,12 @@ public function __construct(AnonymizerUtil $anonymizerUtil, ContaoFrameworkInter public function getFilters() { return [ - new TwigFilter('autolink', [$this, 'autolink']), new TwigFilter('anonymize_email', [$this, 'anonymizeEmail']), - new TwigFilter('replace_inserttag', [$this, 'replaceInsertTag']), ]; } - public function autolink(string $text, array $options = []): string - { - return preg_replace_callback('@(?P(?:http(s)?://)[\w.-]+(?:\.[\w.-]+)+[\w\-._~:/?#\[\]\@!$&\'()*+,;=]+)@i', function ($matches) use ($options) { - if (!isset($matches['url'])) { - return ''; - } - - return ''.$matches['url'].''; - }, $text, $options['limit'] ?? -1); - } - public function anonymizeEmail(string $text): string { - return $this->anonymizerUtil->anonymizeEmail($text); - } - - public function replaceInsertTag(string $text, bool $cache = true) - { - $this->framework->initialize(); - - return $this->framework->getAdapter(Controller::class)->replaceInsertTags($text, $cache); + return $this->utils->anonymize()->anonymizeEmail($text); } } diff --git a/src/Twig/TestExtension.php b/src/Twig/TestExtension.php deleted file mode 100644 index 96565810..00000000 --- a/src/Twig/TestExtension.php +++ /dev/null @@ -1,101 +0,0 @@ -framework = $framework; - $this->requestUtil = $requestUtil; - $this->requestStack = $requestStack; - } - - /** - * Detect if user already visited our domain before. - * - * @deprecated please use RequestUtil::isNewVisitor() instead - * @codeCoverageIgnore - */ - public function isNewVisitor(): bool - { - @trigger_error(__METHOD__.' is deprecated and will be removed in a future version. Please use RequestUtil::isNewVisitor() instead.', \E_USER_DEPRECATED); - - return $this->requestUtil->isNewVisitor(); - } - - /** - * Return the current url with requestUri. - * - * Options: - * - * * skipParams: boolean - * - * @return string - */ - public function getCurrentUrl(array $options = []) - { - $url = Environment::get('url'); - - if (isset($options['skipParams']) && $options['skipParams']) { - $url .= parse_url(Environment::get('uri'), \PHP_URL_PATH); - } else { - $url .= Environment::get('requestUri'); - } - - return $url; - } - - /** - * Add a query string to the given URI string or page ID. - * - * @param string $query - * @param mixed $url - * - * @throws \InvalidArgumentException - * - * @return string - * - * @deprecated Use Utils service instead - * @codeCoverageIgnore - */ - public function addQueryString($query, $url = null) - { - $queryString = ''; - $url = static::prepareUrl($url); - $query = trim(ampersand($query, false), '&'); - - $explodedUrl = explode('?', $url, 2); - - if (2 === \count($explodedUrl)) { - [$script, $queryString] = $explodedUrl; - } else { - [$script] = $explodedUrl; - } - - parse_str($queryString, $queries); - - $queries = array_filter($queries); - unset($queries['language']); - - $href = ''; - - if (!empty($queries)) { - parse_str($query, $new); - $href = '?'.http_build_query(array_merge($queries, $new), '', '&'); - } elseif (!empty($query)) { - $href = '?'.$query; - } - - return $script.$href; - } - - /** - * Remove query parameters from the current URL. - * - * Options: - * - absoluteUrl: (boolean) Return absolute url instead of relative url. Only applicable if id or null is given as url. Default: false - * - * @param array $params List of parameters to remove from url - * @param string|int|null $url Full Uri, Page id or null (for current environment uri) - * - * @return string - * - * @deprecated Use utils service instead - * @codeCoverageIgnore - */ - public function removeQueryString(array $params, $url = null, array $options = []) - { - $strUrl = static::prepareUrl($url, $options); - - if (empty($params)) { - return $strUrl; - } - - $explodedUrl = explode('?', $strUrl, 2); - - if (2 === \count($explodedUrl)) { - [$script, $queryString] = $explodedUrl; - } else { - [$script] = $explodedUrl; - - return $script; - } - - parse_str($queryString, $queries); - - $queries = array_filter($queries); - $queries = array_diff_key($queries, array_flip($params)); - - $href = ''; - - if (!empty($queries)) { - $href .= '?'.http_build_query($queries); - } - - return $script.$href; - } - - /** - * @return PageModel|null - */ - public function getJumpToPageObject(int $jumpTo, bool $fallbackToObjPage = true) - { - global $objPage; - - if ($jumpTo && $jumpTo != $objPage->id - && null !== ($jumpToPage = System::getContainer()->get('huh.utils.model')->findModelInstanceByPk('tl_page', $jumpTo))) { - return $jumpToPage; - } - - return $fallbackToObjPage ? $objPage : null; - } - - public function getJumpToPageUrl(int $jumpTo, bool $fallbackToObjPage = true): ?string - { - $jumpToObject = $this->getJumpToPageObject($jumpTo, $fallbackToObjPage); - - if (null === $jumpToObject || !($jumpToObject instanceof Model)) { - return null; - } - - return $jumpToObject->getFrontendUrl(); - } - - public static function addAutoItemToPage(Model $page, Model $entity, $autoItemType = 'items') - { - $autoItem = ((\Config::get('useAutoItem') && !\Config::get('disableAlias')) ? '/' : '/'.$autoItemType.'/').((!\Config::get('disableAlias') && '' != $entity->alias) ? $entity->alias : $entity->id); - - return \Controller::generateFrontendUrl($page->row(), $autoItem); - } - - /** - * Redirect to another page. - * - * @param string $strLocation The target URL - * @param int $intStatus The HTTP status code (defaults to 303) - * @param bool $test For test purposes set to true to test exit/headers - * @param bool $skipSent Skip if headers already sent for test purposes - * - * @return int|array|null - */ - public function redirect($strLocation, $intStatus = 303, $test = false, $skipSent = false) - { - $headers = []; - - if (headers_sent() && !$skipSent) { - if ($test) { - return static::TERMINATE_HEADERS_ALREADY_SENT; - } - - // @codeCoverageIgnoreStart - exit; - // @codeCoverageIgnoreEnd - } - - $strLocation = str_replace('&', '&', $strLocation); - - // Make the location an absolute URL - if (!preg_match('@^https?://@i', $strLocation)) { - $strLocation = \Environment::get('base').ltrim($strLocation, '/'); - } - - // Ajax request - if (($request = $this->requestStack->getCurrentRequest()) && $request->isXmlHttpRequest()) { - $headers[] = 'HTTP/1.1 204 No Content'; - $headers[] = 'X-Ajax-Location: '.$strLocation; - } else { - // Add the HTTP header - switch ($intStatus) { - case 301: - $headers[] = 'HTTP/1.1 301 Moved Permanently'; - - break; - - case 302: - $headers[] = 'HTTP/1.1 302 Found'; - - break; - - case 303: - $headers[] = 'HTTP/1.1 303 See Other'; - - break; - - case 307: - $headers[] = 'HTTP/1.1 307 Temporary Redirect'; - - break; - } - - $headers[] = 'Location: '.$strLocation; - } - - if ($test) { - return $headers; - } - - // @codeCoverageIgnoreStart - foreach ($headers as $header) { - header($header); - } - - exit; - // @codeCoverageIgnoreEnd - } - - /** - * Add a url scheme to a given url. - */ - public function addURIScheme(string $url = '', string $protocol = 'http'): string - { - $scheme = $protocol.'://'; - - if ('' !== $url && false === System::getContainer()->get('huh.utils.string')->startsWith($url, $protocol)) { - $url = $scheme.$url; - } - - return $url; - } - - /** - * Prepare URL from ID and keep query string from current string. - * - * Options: - * - absoluteUrl: (boolean) Return absolute url instead of relative url. Only applicable if id or null is given as url. Default: false - * - * @param string|int|null Url or page id - * @param array $options pass additional options - * - * @return string - * - * @deprecated - * @codeCoverageIgnore - */ - public function prepareUrl($url = null, array $options = []) - { - if (null === $url) { - if (isset($options['absoluteUrl']) && true === $options['absoluteUrl']) { - $url = Environment::get('uri'); - } else { - $url = Environment::get('requestUri'); - } - } elseif (is_numeric($url)) { - /** @var PageModel $jumpTo */ - if (null === ($jumpTo = $this->framework->getAdapter(PageModel::class)->findByPk($url))) { - throw new \InvalidArgumentException('Given page id does not exist.'); - } - - if (isset($options['absoluteUrl']) && true === $options['absoluteUrl']) { - $url = $jumpTo->getAbsoluteUrl(); - } else { - $url = $jumpTo->getFrontendUrl(); - } - - [, $queryString] = explode('?', Environment::get('request'), 2); - - if ('' != $queryString) { - $url .= '?'.$queryString; - } - } - - $url = ampersand($url, false); - - return $url; - } - - /** - * Convert an absolute url to an relative url. - * - * Options: - * - removeLeadingSlash: (boolean) Remove a - * - * @param string $url The url that should be made relative - * @param array $options Pass additional options - * - * @throws InvalidUrlException - */ - public function getRelativePath(string $url, array $options = []): string - { - $urlParts = parse_url($url); - - if (false === $urlParts) { - throw new InvalidUrlException('Your given url is invalid and could not be parsed.'); - } - - $path = ''; - - if (isset($urlParts['path'])) { - $path .= $urlParts['path']; - - if (isset($options['removeLeadingSlash']) && true === $options['removeLeadingSlash']) { - $path = ltrim($path, '/'); - } - } - - if (isset($urlParts['query'])) { - $path .= '?'.$urlParts['query']; - } - - if (isset($urlParts['fragment'])) { - $path .= '#'.$urlParts['fragment']; - } - - return $path; - } - - /** - * @deprecated Use Utils::request() instead - */ - public function getBaseUrl(bool $absolute = false) - { - $url = $absolute ? Environment::get('url') : ''; - - if (version_compare(VERSION, '4.8', '<') && System::getContainer()->get('huh.utils.container')->isDev()) { - $url .= '/app_dev.php'; - } - - return $url; - } -} diff --git a/src/User/UserUtil.php b/src/User/UserUtil.php deleted file mode 100644 index b9e3019c..00000000 --- a/src/User/UserUtil.php +++ /dev/null @@ -1,95 +0,0 @@ -framework = $framework; - $this->modelUtil = $modelUtil; - } - - /** - * @return UserModel|UserModel[]|\Contao\Model\Collection|null - * - * @deprecated use Utils service instead - */ - public function findActiveByGroups(array $groups, array $options = []) - { - if (empty($groups)) { - return null; - } - - /** @var $adapter UserModel */ - if (null === $adapter = $this->framework->getAdapter(UserModel::class)) { - return null; - } - - $t = $adapter->getTable(); - $time = \Date::floorToMinute(); - $values = []; - - $columns = ["($t.start='' OR $t.start<='$time') AND ($t.stop='' OR $t.stop>'".($time + 60)."') AND $t.disable=''"]; - - if (!empty(array_filter($groups))) { - [$columns[], $tmpValues] = System::getContainer()->get('huh.utils.database')->createWhereForSerializedBlob('groups', array_filter($groups)); - $values = array_merge(array_values($values), array_values($tmpValues)); - } - - return $adapter->findBy($columns, $values, $options); - } - - public function hasAccessToField($table, $field) - { - $user = $this->framework->createInstance(BackendUser::class); - - if (null === ($objUser = $user) || !\is_array($user->alexf)) { - return false; - } - - return $objUser->isAdmin || \in_array($table.'::'.$field, $user->alexf); - } - - public function isAdmin(): bool - { - $user = $this->framework->createInstance(BackendUser::class); - - if (null === $user || !\is_array($user->alexf)) { - return false; - } - - return $user->isAdmin; - } -} diff --git a/src/Util/Ui/AccordionUtil.php b/src/Util/AccordionUtil.php similarity index 94% rename from src/Util/Ui/AccordionUtil.php rename to src/Util/AccordionUtil.php index c49d1785..df30e25d 100644 --- a/src/Util/Ui/AccordionUtil.php +++ b/src/Util/AccordionUtil.php @@ -6,27 +6,18 @@ * @license LGPL-3.0-or-later */ -namespace HeimrichHannot\UtilsBundle\Util\Ui; +namespace HeimrichHannot\UtilsBundle\Util; use Contao\ContentModel; use Contao\CoreBundle\Framework\ContaoFramework; class AccordionUtil { - /** - * @var ContaoFramework - */ - private $contaoFramework; - - /** @var array */ - private $accordionStartStopCache = []; - - /** @var array */ - private $accordionSingleCache; + private array $accordionStartStopCache = []; + private array $accordionSingleCache; - public function __construct(ContaoFramework $contaoFramework) + public function __construct(private ContaoFramework $contaoFramework) { - $this->contaoFramework = $contaoFramework; } public function structureAccordionStartStop(array &$data, string $prefix = 'accordion_'): void diff --git a/src/String/AnonymizerUtil.php b/src/Util/AnonymizeUtil.php similarity index 66% rename from src/String/AnonymizerUtil.php rename to src/Util/AnonymizeUtil.php index f5e5e596..99fd757b 100644 --- a/src/String/AnonymizerUtil.php +++ b/src/Util/AnonymizeUtil.php @@ -1,20 +1,11 @@ false, @@ -90,11 +90,9 @@ public function insertAfterKey(array &$array, string $key, $value, string $newKe /** * Removes a value in an array. * - * @param $value - * * @return bool Returns true if the value has been found and removed, false in other cases */ - public function removeValue($value, array &$array): bool + public function removeValue(mixed $value, array &$array): bool { if (false !== ($intPosition = array_search($value, $array))) { unset($array[$intPosition]); diff --git a/src/Util/Container/ContainerUtil.php b/src/Util/ContainerUtil.php similarity index 74% rename from src/Util/Container/ContainerUtil.php rename to src/Util/ContainerUtil.php index 1a1ae308..1f45481c 100644 --- a/src/Util/Container/ContainerUtil.php +++ b/src/Util/ContainerUtil.php @@ -6,14 +6,15 @@ * @license LGPL-3.0-or-later */ -namespace HeimrichHannot\UtilsBundle\Util\Container; +namespace HeimrichHannot\UtilsBundle\Util; +use Contao\CoreBundle\ContaoCoreBundle; use Contao\CoreBundle\Framework\ContaoFramework; -use Contao\CoreBundle\Framework\ContaoFrameworkInterface; use Contao\CoreBundle\Monolog\ContaoContext; use Contao\CoreBundle\Routing\ScopeMatcher; use Contao\CoreBundle\Security\Authentication\Token\TokenChecker; use Contao\Input; +use Contao\PageModel; use Contao\System; use HeimrichHannot\UtilsBundle\Util\AbstractServiceSubscriber; use Psr\Container\ContainerInterface; @@ -27,52 +28,14 @@ class ContainerUtil extends AbstractServiceSubscriber { - /** @var ContaoFramework */ - protected $framework; - /** - * @var array - */ - protected $kernelBundles; - /** - * @var RequestStack - */ - protected $requestStack; - /** - * @var KernelInterface - */ - protected $kernel; - /** - * @var ContainerInterface - */ - protected $locator; - - /** - * @var Filesystem - */ - protected $filesystem; - - /** - * @var ScopeMatcher - */ - private $scopeMatcher; - - public function __construct(ContainerInterface $locator, array $kernelBundles, KernelInterface $kernel, ContaoFrameworkInterface $framework, ScopeMatcher $scopeMatcher, RequestStack $requestStack, Filesystem $filesystem) + public function __construct( + private ContainerInterface $locator, + private KernelInterface $kernel, + private ContaoFramework $framework, + private ScopeMatcher $scopeMatcher, + private RequestStack $requestStack, + private Filesystem $filesystem) { - $this->scopeMatcher = $scopeMatcher; - $this->kernelBundles = $kernelBundles; - $this->framework = $framework; - $this->requestStack = $requestStack; - $this->kernel = $kernel; - $this->locator = $locator; - $this->filesystem = $filesystem; - } - - /** - * Checks if some bundle is active. Pass in the class name (e.g. 'HeimrichHannot\FilterBundle\HeimrichHannotContaoFilterBundle' or the legacy Contao 3 name like 'news'). - */ - public function isBundleActive(string $bundleName): bool - { - return \in_array($bundleName, array_merge(array_values($this->kernelBundles), array_keys($this->kernelBundles))); } /** @@ -187,12 +150,8 @@ public function getBundleResourcePath(string $bundleClass, string $ressourcePath /** * Return if currently in maintenance mode. */ - public function isMaintenanceModeActive($page = null): bool + public function isMaintenanceModeActive(PageModel $page = null): bool { - if (version_compare(VERSION, '4.13', '<')) { - return System::getContainer()->get('lexik_maintenance.driver.factory')->getDriver()->isExists(); - } - if ($page && $page->maintenanceMode) { return true; } diff --git a/src/Util/DatabaseUtil.php b/src/Util/DatabaseUtil.php new file mode 100644 index 00000000..72a69e76 --- /dev/null +++ b/src/Util/DatabaseUtil.php @@ -0,0 +1,19 @@ +field.' REGEXP ('.implode(") AND ".$this->field.' REGEXP (', $this->getValueList()).'))'; + } + + /** + * Return the where query with OR operation for each value. Values are inlined in the query ('REGEXP (':"3"')' instead of 'REGEXP (?)'). + */ + public function createInlineOrWhere(): string + { + return '('.$this->field.' REGEXP ('.implode(") OR ".$this->field.' REGEXP (', $this->getValueList()).'))'; + } + + /** + * Return the where query with AND operation and placeholder for each value ('REGEXP (?)'). Values can be obtained from the values property. + */ + public function createAndWhere(): string + { + + return '('.$this->field.' REGEXP ('.implode(") AND ".$this->field.' REGEXP (', array_fill(0, count($this->getValueList()), '?')).'))'; + } + + /** + * Return the where query with OR operation and placeholder for each value ('REGEXP (?)'). Values can be obtained from the values property. + */ + public function createOrWhere(): string + { + return '('.$this->field.' REGEXP ('.implode(") OR ".$this->field.' REGEXP (', array_fill(0, count($this->getValueList()), '?')).'))'; + } + + private function getValueList(): array + { + $returnValues = []; + + foreach ($this->values as $val) { + $returnValues[] = ":\"$val\""; + } + + return $returnValues; + } + +} \ No newline at end of file diff --git a/src/Util/Dca/DcaUtil.php b/src/Util/DcaUtil.php similarity index 69% rename from src/Util/Dca/DcaUtil.php rename to src/Util/DcaUtil.php index 0072b835..8c6e900e 100644 --- a/src/Util/Dca/DcaUtil.php +++ b/src/Util/DcaUtil.php @@ -6,30 +6,31 @@ * @license LGPL-3.0-or-later */ -namespace HeimrichHannot\UtilsBundle\Util\Dca; +namespace HeimrichHannot\UtilsBundle\Util; use Contao\Controller; use Contao\CoreBundle\DataContainer\PaletteNotFoundException; use Contao\CoreBundle\Framework\ContaoFramework; use Contao\StringUtil; +use HeimrichHannot\UtilsBundle\Util\DcaUtil\GetDcaFieldsOptions; class DcaUtil { - /** - * @var ContaoFramework - */ - private $contaoFramework; - public function __construct(ContaoFramework $contaoFramework) - { - $this->contaoFramework = $contaoFramework; + public function __construct( + private ContaoFramework $contaoFramework + ) { } /** * Return all fields of a palette including its subpalettes as array. * * Options: - * * skip_subpalettes (bool): Don't add subpalette fields to result. + * * skip_subpalettes (bool): Don't add subpalette fields to result. Default false + * + * @param array{ + * skip_subpalettes?: bool + * } $options */ public function getPaletteFields(string $table, string $palette, array $options = []): array { @@ -134,34 +135,9 @@ function ($a) use (&$arrFields) { /** * Return a list of dca fields for given table. * Fields can be filtered by given options. - * - * Options: - * - onlyDatabaseFields (bool): Return only fields with sql definition. Default false - * - allowedInputTypes (array): Return only fields of given types. - * - evalConditions (array): Return only fields with given eval key-value-pairs. - * - localizeLabels (bool): Return also the field labels (key = field name, value = field label). Default false - * - skipSorting (bool): Skip sorting fields by field name alphabetical. Default false */ - public function getDcaFields(string $table, array $options = []): array + public function getDcaFields(string $table, GetDcaFieldsOptions $options = new GetDcaFieldsOptions()): array { - $options = array_merge([ - 'onlyDatabaseFields' => false, - 'allowedInputTypes' => [], - 'evalConditions' => [], - 'localizeLabels' => false, - 'skipSorting' => false, - ], $options); - - if (!\is_array($options['allowedInputTypes'])) { - $options['allowedInputTypes'] = []; - trigger_error('DcaUtil::getDcaFields() option "allowedInputTypes" must be of type array!', \E_USER_WARNING); - } - - if (!\is_array($options['evalConditions'])) { - $options['evalConditions'] = []; - trigger_error('DcaUtil::getDcaFields() option "evalConditions" must be of type array!', \E_USER_WARNING); - } - $fields = []; $controller = $this->contaoFramework->getAdapter(Controller::class); @@ -173,35 +149,35 @@ public function getDcaFields(string $table, array $options = []): array } foreach ($GLOBALS['TL_DCA'][$table]['fields'] as $name => $data) { - if ($options['onlyDatabaseFields']) { + if ($options->isOnlyDatabaseFields()) { if (!isset($data['sql'])) { continue; } } // restrict to certain input types - if (!empty($options['allowedInputTypes']) && (!isset($data['inputType']) || !\in_array($data['inputType'], $options['allowedInputTypes']))) { + if ($options->isOnlyAllowedInputTypes() && (!isset($data['inputType']) || !\in_array($data['inputType'], $options->getAllowedInputTypes()))) { continue; } // restrict to certain dca eval - if (!empty($options['evalConditions'])) { - foreach ($options['evalConditions'] as $key => $value) { + if ($options->isHasEvalConditions()) { + foreach ($options->getEvalConditions() as $key => $value) { if (!isset($data['eval'][$key]) || $data['eval'][$key] !== $value) { continue 2; } } } - if (!$options['localizeLabels']) { + if (!$options->isLocalizeLabels()) { $fields[] = $name; } else { $fields[$name] = $data['label'][0] ?? $name; } } - if (!$options['skipSorting']) { - if ($options['localizeLabels']) { + if (!$options->isSkipSorting()) { + if ($options->isLocalizeLabels()) { asort($fields); } else { sort($fields); diff --git a/src/Util/DcaUtil/GetDcaFieldsOptions.php b/src/Util/DcaUtil/GetDcaFieldsOptions.php new file mode 100644 index 00000000..2a7747c8 --- /dev/null +++ b/src/Util/DcaUtil/GetDcaFieldsOptions.php @@ -0,0 +1,107 @@ + false, + * 'allowedInputTypes' => [], + * 'evalConditions' => [], + * 'localizeLabels' => false, + * 'skipSorting' => false, + */ + + private bool $onlyDatabaseFields = false; + private array $allowedInputTypes = []; + private array $evalConditions = []; + private bool $localizeLabels = false; + private bool $skipSorting = false; + + public function isOnlyDatabaseFields(): bool + { + return $this->onlyDatabaseFields; + } + + public static function create(): self + { + return new self(); + } + + /** + * Return only fields with sql definition. Default false + */ + public function setOnlyDatabaseFields(bool $onlyDatabaseFields): GetDcaFieldsOptions + { + $this->onlyDatabaseFields = $onlyDatabaseFields; + return $this; + } + + public function getAllowedInputTypes(): array + { + return $this->allowedInputTypes; + } + + /** + * Return only fields of given types. + */ + public function setAllowedInputTypes(array $allowedInputTypes): GetDcaFieldsOptions + { + $this->allowedInputTypes = $allowedInputTypes; + return $this; + } + + public function isOnlyAllowedInputTypes(): bool + { + return !empty($this->allowedInputTypes); + } + + public function getEvalConditions(): array + { + return $this->evalConditions; + } + + /** + * Return only fields with given eval key-value-pairs. + */ + public function setEvalConditions(array $evalConditions): GetDcaFieldsOptions + { + $this->evalConditions = $evalConditions; + return $this; + } + + public function isHasEvalConditions(): bool + { + return !empty($this->evalConditions); + } + + public function isLocalizeLabels(): bool + { + return $this->localizeLabels; + } + + /** + * Return also the field labels (key = field name, value = field label). Default false + */ + public function setLocalizeLabels(bool $localizeLabels): GetDcaFieldsOptions + { + $this->localizeLabels = $localizeLabels; + return $this; + } + + public function isSkipSorting(): bool + { + return $this->skipSorting; + } + + /** + * Skip sorting fields by field name alphabetical. Default false + */ + public function setSkipSorting(bool $skipSorting): GetDcaFieldsOptions + { + $this->skipSorting = $skipSorting; + return $this; + } + + +} \ No newline at end of file diff --git a/src/Util/File/FileUtil.php b/src/Util/FileUtil.php similarity index 76% rename from src/Util/File/FileUtil.php rename to src/Util/FileUtil.php index 9b70a7ee..e2c756d2 100644 --- a/src/Util/File/FileUtil.php +++ b/src/Util/FileUtil.php @@ -6,23 +6,17 @@ * @license LGPL-3.0-or-later */ -namespace HeimrichHannot\UtilsBundle\Util\File; +namespace HeimrichHannot\UtilsBundle\Util; use Contao\CoreBundle\Framework\ContaoFramework; use Contao\FilesModel; class FileUtil { - /** @var ContaoFramework */ - private $contaoFramework; - - /** @var string */ - private $projectDir; - - public function __construct(ContaoFramework $contaoFramework, string $projectDir) - { - $this->contaoFramework = $contaoFramework; - $this->projectDir = $projectDir; + public function __construct( + private ContaoFramework $contaoFramework, + private string $projectDir + ) { } /** @@ -32,6 +26,10 @@ public function __construct(ContaoFramework $contaoFramework, string $projectDir * - checkIfExist: (bool) Enable check if the the file exist. Default true * - absolutePath: (bool) Return absolute path instead of relative path. * + * @param array{ + * checkIfExist?: bool, + * absolutePath?: bool + * } $options Pass additional options. * @return string|null Return the path of the file, or null if not exists */ public function getPathFromUuid(string $uuid, array $options = []): ?string diff --git a/src/Util/Html/HtmlUtil.php b/src/Util/HtmlUtil.php similarity index 78% rename from src/Util/Html/HtmlUtil.php rename to src/Util/HtmlUtil.php index 7b5942dd..757572d3 100644 --- a/src/Util/Html/HtmlUtil.php +++ b/src/Util/HtmlUtil.php @@ -6,8 +6,10 @@ * @license LGPL-3.0-or-later */ -namespace HeimrichHannot\UtilsBundle\Util\Html; +namespace HeimrichHannot\UtilsBundle\Util; +use HeimrichHannot\UtilsBundle\Util\HtmlUtil\GenerateDataAttributesStringArrayHandling; +use HeimrichHannot\UtilsBundle\Util\HtmlUtil\GenerateDataAttributesStringOptions; use function Symfony\Component\String\u; class HtmlUtil @@ -17,6 +19,10 @@ class HtmlUtil * * Options: * - xhtml: (bool) XHTML format instead of HTML5 format. Default false + * + * @param array{ + * xhtml?: bool + * } $options */ public function generateAttributeString(array $attributes, array $options = []): string { @@ -43,18 +49,8 @@ public function generateAttributeString(array $attributes, array $options = []): * Options (additional to Options from HtmlUtl::generateAttributeString()): * - normalizeKeys: Array keys are normalized to lowercase dash-cased strings (e.g. Foo Bar_player is transformed to foo-bar-player) */ - public function generateDataAttributesString(array $attributes, array $options = []): string + public function generateDataAttributesString(array $attributes, GenerateDataAttributesStringOptions $options = new GenerateDataAttributesStringOptions()): string { - $options = array_merge([ - 'xhtml' => false, - 'normalizeKeys' => true, - 'array_handling' => 'reduce', - ], $options); - - if (!\in_array($options['array_handling'], ['reduce', 'encode'])) { - $options['array_handling'] = 'reduce'; - } - $dataAttributes = []; foreach ($attributes as $key => $value) { @@ -63,7 +59,7 @@ public function generateDataAttributesString(array $attributes, array $options = } if (\is_array($value)) { - if ('reduce' === $options['array_handling']) { + if ($options->getArrayHandling() === GenerateDataAttributesStringArrayHandling::REDUCE) { $value = implode(' ', array_reduce($value, function ($tokens, $token) { if (\is_string($token)) { $token = trim($token); @@ -81,12 +77,12 @@ public function generateDataAttributesString(array $attributes, array $options = if (empty($value)) { continue; } - } elseif ('encode' === $options['array_handling']) { + } else { $value = htmlspecialchars(json_encode($value), \ENT_QUOTES, 'UTF-8'); } } - if ($options['normalizeKeys']) { + if ($options->isNormalizeKeys()) { $key = str_replace('_', '-', u($key)->snake()); } @@ -97,8 +93,8 @@ public function generateDataAttributesString(array $attributes, array $options = $dataAttributes[$key] = $value; } - unset($options['normalizeKeys']); - - return $this->generateAttributeString($dataAttributes, $options); + return $this->generateAttributeString($dataAttributes, [ + 'xhtml' => $options->isXhtml() + ]); } } diff --git a/src/Util/HtmlUtil/GenerateDataAttributesStringArrayHandling.php b/src/Util/HtmlUtil/GenerateDataAttributesStringArrayHandling.php new file mode 100644 index 00000000..f761e8b2 --- /dev/null +++ b/src/Util/HtmlUtil/GenerateDataAttributesStringArrayHandling.php @@ -0,0 +1,9 @@ +xhtml; + } + + public function setXhtml(bool $xhtml): GenerateDataAttributesStringOptions + { + $this->xhtml = $xhtml; + return $this; + } + + public function isNormalizeKeys(): bool + { + return $this->normalizeKeys; + } + + /** + * Array keys are normalized to lowercase dash-cased strings (e.g. Foo Bar_player is transformed to foo-bar-player) + */ + public function setNormalizeKeys(bool $normalizeKeys): GenerateDataAttributesStringOptions + { + $this->normalizeKeys = $normalizeKeys; + return $this; + } + + public function getArrayHandling(): GenerateDataAttributesStringArrayHandling + { + return $this->arrayHandling; + } + + public function setArrayHandling(GenerateDataAttributesStringArrayHandling $arrayHandling): GenerateDataAttributesStringOptions + { + $this->arrayHandling = $arrayHandling; + return $this; + } +} \ No newline at end of file diff --git a/src/Util/Locale/LocaleUtil.php b/src/Util/LocaleUtil.php similarity index 93% rename from src/Util/Locale/LocaleUtil.php rename to src/Util/LocaleUtil.php index ed0630bf..cd2b3cdd 100644 --- a/src/Util/Locale/LocaleUtil.php +++ b/src/Util/LocaleUtil.php @@ -6,7 +6,7 @@ * @license LGPL-3.0-or-later */ -namespace HeimrichHannot\UtilsBundle\Util\Locale; +namespace HeimrichHannot\UtilsBundle\Util; class LocaleUtil { diff --git a/src/Util/Model/ModelUtil.php b/src/Util/ModelUtil.php similarity index 61% rename from src/Util/Model/ModelUtil.php rename to src/Util/ModelUtil.php index e2c87fbe..81996403 100644 --- a/src/Util/Model/ModelUtil.php +++ b/src/Util/ModelUtil.php @@ -6,43 +6,46 @@ * @license LGPL-3.0-or-later */ -namespace HeimrichHannot\UtilsBundle\Util\Model; +namespace HeimrichHannot\UtilsBundle\Util; use Contao\Controller; +use Contao\CoreBundle\Framework\Adapter; use Contao\CoreBundle\Framework\ContaoFramework; -use Contao\CoreBundle\Framework\ContaoFrameworkInterface; use Contao\Date; use Contao\Model; use Contao\Model\Collection; class ModelUtil { - /** - * @var ContaoFrameworkInterface - */ - protected $framework; - - public function __construct(ContaoFramework $contaoFramework) + public function __construct( + private ContaoFramework $framework + ) { - $this->framework = $contaoFramework; } /** * Adds an published check to your model query. * * Options: - * - publishedField: (string) The name of the published field. Default: "published" - * - startField: (string) The name of the start field. Default: "start" - * - stopField: (string) The name of the stop field. Default: "stop" - * - invertPublishedField: (bool) Set to true, if the published field should be evaluated inverted (for "hidden" or "invisible" fields. Default: false - * - invertStartStopFields: (bool) Set to true, if the start and stop fields should be evaluated in an inverted manner. Default: false - * - ignoreFePreview: (bool) Set to true, frontend preview should be ignored. Default: false + * - publishedField: The name of the published field. Default: "published" + * - startField: The name of the start field. Default: "start" + * - stopField: The name of the stop field. Default: "stop" + * - invertPublishedField: Set to true, if the published field should be evaluated inverted (for "hidden" or "invisible" fields. Default: false + * - invertStartStopFields: Set to true, if the start and stop fields should be evaluated in an inverted manner. Default: false + * - ignoreFePreview: Set to true, frontend preview should be ignored. Default: false * * @param string $table The table name * @param array $columns The columns array - * @param array $options pass additional options + * @param array{ + * publishedField?: string, + * startField?: string, + * stopField?: string, + * invertPublishedField?: bool, + * invertStartStopFields?: bool, + * ignoreFePreview?: bool + * } $options pass additional options */ - public function addPublishedCheckToModelArrays(string $table, array &$columns, array $options = []) + public function addPublishedCheckToModelArrays(string $table, array &$columns, array $options = []): void { $defaults = [ 'invertPublishedField' => false, @@ -69,12 +72,13 @@ public function addPublishedCheckToModelArrays(string $table, array &$columns, a * Options: * - skipReplaceInsertTags: (bool) Skip the replacement of inserttags. Default: false * - * @param mixed $columns - * @param mixed $values + * @param array{ + * skipReplaceInsertTags?: bool + * } $options * * @return Model[]|Collection|null */ - public function findModelInstancesBy(string $table, $columns, $values, array $options = []) + public function findModelInstancesBy(string $table, array|string|null $columns, int|string|array|null $values, array $options = []): Collection|Model|null { $defaults = [ 'skipReplaceInsertTags' => false, @@ -105,15 +109,14 @@ public function findModelInstancesBy(string $table, $columns, $values, array $op /** * Find a single model instance for given table by its primary key (id). * - * @param string $table The table - * @param mixed $pk The property value - * @param array $options An optional options array + * @param string $table The table + * @param int|string $pk The property value + * @param array $options An optional options array * * @return Model|null The model or null if the result is empty */ - public function findModelInstanceByPk(string $table, $pk, array $options = []): ?Model + public function findModelInstanceByPk(string $table, int|string $pk, array $options = []): ?Model { - /* @var Model $adapter */ if (!($modelClass = $this->framework->getAdapter(Model::class)->getClassFromTable($table))) { return null; } @@ -122,6 +125,7 @@ public function findModelInstanceByPk(string $table, $pk, array $options = []): return null; } + /** @var Model|Adapter $adapter */ return $adapter->findByPk($pk, $options); } @@ -129,22 +133,29 @@ public function findModelInstanceByPk(string $table, $pk, array $options = []): * Return a single model instance by table and search criteria. * * Options: - * - skipReplaceInsertTags: (bool) Skip the replacement of inserttags. Default: false + * - skipReplaceInsertTags: Skip the replacement of inserttags. Default: false + * + * @param array{ + * skipReplaceInsertTags?: bool + * } $options * - * @return mixed */ - public function findOneModelInstanceBy(string $table, array $columns, array $values, array $options = []) + public function findOneModelInstanceBy(string $table, array $columns, array $values, array $options = []): ?Model { - /* @var Model $adapter */ + $options = array_merge([ + 'skipReplaceInsertTags' => false, + ], $options); + if (!($modelClass = $this->framework->getAdapter(Model::class)->getClassFromTable($table))) { return null; } + if (null === ($adapter = $this->framework->getAdapter($modelClass))) { return null; } - if (\is_array($values) && (!isset($options['skipReplaceInsertTags']) || !$options['skipReplaceInsertTags'])) { + if (\is_array($values) && !$options['skipReplaceInsertTags']) { $values = array_map([$this->framework->getAdapter(Controller::class), 'replaceInsertTags'], $values); } @@ -152,17 +163,15 @@ public function findOneModelInstanceBy(string $table, array $columns, array $val $columns = null; } + /* @var Model|Adapter $adapter */ return $adapter->findOneBy($columns, $values, $options); } /** * Returns multiple model instances by given table and ids. - * - * @return Collection|Model[]|Model|null */ - public function findMultipleModelInstancesByIds(string $table, array $ids, array $options = []) + public function findMultipleModelInstancesByIds(string $table, array $ids, array $options = []): Collection|Model|null { - /* @var Model $adapter */ if (!($modelClass = $this->framework->getAdapter(Model::class)->getClassFromTable($table))) { return null; } @@ -171,17 +180,14 @@ public function findMultipleModelInstancesByIds(string $table, array $ids, array return null; } + /** @var Model|Adapter $adapter */ return $adapter->findBy(["$table.id IN(".implode(',', array_map('\intval', $ids)).')'], null, $options); } /** - * Returns multiple model instances by given table and id or alias. - * - * @param mixed $idOrAlias - * - * @return Model|null + * Returns model instance by given table and id or alias. */ - public function findModelInstanceByIdOrAlias(string $table, $idOrAlias, array $options = []) + public function findModelInstanceByIdOrAlias(string $table, int|string $idOrAlias, array $options = []): ?Model { if (!($modelClass = $this->framework->getAdapter(Model::class)->getClassFromTable($table))) { return null; @@ -204,4 +210,31 @@ public function findModelInstanceByIdOrAlias(string $table, $idOrAlias, array $o return $adapter->findByIdOrAlias($idOrAlias, $options); } + + /** + * Returns an array of a model instance's parents in ascending order, i.e. the root parent comes first. + * + * @template T of Model + * @param T $instance + * @param string $parentProperty + * @return array + */ + public function findParentsRecursively(Model $instance, string $parentProperty = 'pid'): array + { + $table = call_user_func([$instance, 'getTable']); + + $parents = []; + $model = $this->framework->getAdapter(Model::class); + $modelClass = $model->getClassFromTable($table); + + if (!$instance->{$parentProperty}) { + return $parents; + } + + if (null === ($parentInstance = $this->framework->getAdapter($modelClass)->findByPk($instance->{$parentProperty}))) { + return $parents; + } + + return array_merge($this->findParentsRecursively($parentInstance, $parentProperty), [$parentInstance]); + } } diff --git a/src/Util/Request/RequestUtil.php b/src/Util/RequestUtil.php similarity index 70% rename from src/Util/Request/RequestUtil.php rename to src/Util/RequestUtil.php index 0d58b203..089712c1 100644 --- a/src/Util/Request/RequestUtil.php +++ b/src/Util/RequestUtil.php @@ -6,37 +6,20 @@ * @license LGPL-3.0-or-later */ -namespace HeimrichHannot\UtilsBundle\Util\Request; +namespace HeimrichHannot\UtilsBundle\Util; use Contao\CoreBundle\Controller\ContentElement\AbstractContentElementController; use Contao\CoreBundle\Framework\ContaoFramework; -use Contao\Model; use Contao\PageModel; -use HeimrichHannot\UtilsBundle\Util\Model\ModelUtil; use Symfony\Component\HttpFoundation\RequestStack; class RequestUtil { - /** @var ModelUtil */ - protected $modelUtil; - /** - * @var RequestStack - */ - protected $requestStack; - /** - * @var array - */ - protected $kernelPackages; - - /** @var ContaoFramework */ - private $contaoFramework; - public function __construct(ModelUtil $modelUtil, RequestStack $requestStack, array $kernelPackages, ContaoFramework $contaoFramework) - { - $this->modelUtil = $modelUtil; - $this->requestStack = $requestStack; - $this->kernelPackages = $kernelPackages; - $this->contaoFramework = $contaoFramework; + public function __construct( + protected RequestStack $requestStack, + private ContaoFramework $contaoFramework + ) { } /** @@ -48,16 +31,6 @@ public function __construct(ModelUtil $modelUtil, RequestStack $requestStack, ar */ public function getCurrentPageModel(): ?PageModel { - $coreVersion = $this->kernelPackages['contao/core-bundle'] ?? '4.4.0'; - // Contao < 4.9 Fallback - if (version_compare($coreVersion, '4.9', '<')) { - if (isset($GLOBALS['objPage']) && $GLOBALS['objPage'] instanceof PageModel) { - return $GLOBALS['objPage']; - } - - return null; - } - $request = $this->requestStack->getCurrentRequest(); if (null === $request || !$request->attributes->has('pageModel')) { @@ -78,7 +51,7 @@ public function getCurrentPageModel(): ?PageModel return $GLOBALS['objPage']; } - return $this->modelUtil->findModelInstanceByPk(PageModel::getTable(), (int) $pageModel); + return $this->contaoFramework->getAdapter(PageModel::class)->findByPk((int)$pageModel); } /** @@ -94,7 +67,7 @@ public function getCurrentRootPageModel(PageModel $currentPage = null): ?PageMod $currentPage->loadDetails(); - return $this->modelUtil->findModelInstanceByPk(PageModel::getTable(), $currentPage->rootId); + return $this->contaoFramework->getAdapter(PageModel::class)->findByPk($currentPage->rootId); } /** @@ -102,14 +75,19 @@ public function getCurrentRootPageModel(PageModel $currentPage = null): ?PageMod * If no base url could be determined, an empty string is returned. * * Context: - * - (PageModel) pageModel: The current page model - * - (string) fallback: will be returned if no other base url could be determined + * - pageModel: The current page model + * - fallback: will be returned if no other base url could be determined * * Options: - * - (bool) throwException: Throw exception if no base url could be determined instead of returning empty string + * - throwException: Throw exception if no base url could be determined instead of returning empty string * - * @param array $context Pass additional context. Available content: pageModel, fallback - * @param array $options Pass addition options: Available options: throwException + * @param array{ + * pageModel?: PageModel, + * fallback?: string + * } $context Pass additional context. Available content: pageModel, fallback + * @param array{ + * throwException?: bool + * } $options Pass addition options: Available options: throwException */ public function getBaseUrl(array $context = [], array $options = []): string { diff --git a/src/Util/Routing/RoutingUtil.php b/src/Util/Routing/RoutingUtil.php deleted file mode 100644 index c3d1eaaa..00000000 --- a/src/Util/Routing/RoutingUtil.php +++ /dev/null @@ -1,98 +0,0 @@ -router = $router; - $this->container = $container; - $this->csrfTokenName = $csrfTokenName; - $this->requestStack = $requestStack; - } - - /** - * Generate a backend route with token and referer. - * - * Options: - * - absoluteUrl (bool): Return absolute url (default: false) - * - * @param array $params Url-Parameters - * - * @throws ContainerExceptionInterface - * @throws NotFoundExceptionInterface - * - * @return string The backend route url - */ - public function generateBackendRoute(array $params = [], bool $addToken = true, bool $addReferer = true, string $route = 'contao_backend', array $options = []): string - { - $options = array_merge( - ['absoluteUrl' => false], - $options - ); - - if ($addToken) { - // >= contao 4.6.8 uses contao.csrf.token_manager service to validate token - if ($this->container->has(ContaoCsrfTokenManager::class)) { - $params['rt'] = $this->container->get(ContaoCsrfTokenManager::class)->getToken($this->csrfTokenName)->getValue(); - } elseif ($this->container->has(CsrfTokenManagerInterface::class)) { - $params['rt'] = $this->container->get(CsrfTokenManagerInterface::class)->getToken($this->csrfTokenName)->getValue(); - } - } - - if ($addReferer && ($request = $this->requestStack->getCurrentRequest())) { - $params['ref'] = $request->get('_contao_referer_id'); - } - - return $this->router->generate( - $route, - $params, - $options['absoluteUrl'] ? UrlGeneratorInterface::ABSOLUTE_URL : UrlGeneratorInterface::ABSOLUTE_PATH - ); - } - - /** - * @codeCoverageIgnore - */ - public static function getSubscribedServices() - { - return [ - '?'.ContaoCsrfTokenManager::class, - '?'.CsrfTokenManagerInterface::class, - ]; - } -} diff --git a/src/Util/RoutingUtil.php b/src/Util/RoutingUtil.php new file mode 100644 index 00000000..dbf1a7df --- /dev/null +++ b/src/Util/RoutingUtil.php @@ -0,0 +1,73 @@ + false, + 'route' => 'contao_backend', + ], $options + ); + + if ($addToken) { + $params['rt'] = $this->tokenManager->getToken($this->csrfTokenName)->getValue(); + } + + if ($addReferer && ($request = $this->requestStack->getCurrentRequest())) { + $params['ref'] = $request->get('_contao_referer_id'); + } + + return $this->router->generate( + $options['route'], + $params, + $options['absoluteUrl'] ? UrlGeneratorInterface::ABSOLUTE_URL : UrlGeneratorInterface::ABSOLUTE_PATH + ); + } +} diff --git a/src/Util/Type/StringUtil.php b/src/Util/StringUtil.php similarity index 82% rename from src/Util/Type/StringUtil.php rename to src/Util/StringUtil.php index a3157375..f25dcc21 100644 --- a/src/Util/Type/StringUtil.php +++ b/src/Util/StringUtil.php @@ -6,9 +6,8 @@ * @license LGPL-3.0-or-later */ -namespace HeimrichHannot\UtilsBundle\Util\Type; +namespace HeimrichHannot\UtilsBundle\Util; -use Contao\CoreBundle\Framework\ContaoFrameworkInterface; use DOMDocument; use DOMNode; use DOMText; @@ -16,49 +15,12 @@ class StringUtil { - const CAPITAL_LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; - const CAPITAL_LETTERS_NONAMBIGUOUS = 'ABCDEFGHJKLMNPQRSTUVWX'; - const SMALL_LETTERS = 'abcdefghijklmnopqrstuvwxyz'; - const SMALL_LETTERS_NONAMBIGUOUS = 'abcdefghjkmnpqrstuvwx'; - const NUMBERS = '0123456789'; - const NUMBERS_NONAMBIGUOUS = '23456789'; - - /** - * @var ContaoFrameworkInterface - */ - protected $framework; - - public function __construct(ContaoFrameworkInterface $framework) - { - $this->framework = $framework; - } - - /** - * Check for the occurrence at the start of the string. - * - * @param $haystack string The string to search in - * @param $needle string The needle - * - * @deprecated Use str_starts_with instead - */ - public function startsWith(string $haystack, string $needle): bool - { - return '' === $needle || false !== strrpos($haystack, $needle, -\strlen($haystack)); - } - - /** - * Check for the occurrence at the end of the string. - * - * @param string $haystack The string to search in - * @param string $needle The needle - * - * @deprecated Use str_ends_with instead - */ - public function endsWith(string $haystack, string $needle): bool - { - // search forward starting from end minus needle length characters - return '' === $needle || (($temp = \strlen($haystack) - \strlen($needle)) >= 0 && false !== strpos($haystack, $needle, $temp)); - } + private const CAPITAL_LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + private const CAPITAL_LETTERS_NONAMBIGUOUS = 'ABCDEFGHJKLMNPQRSTUVWX'; + private const SMALL_LETTERS = 'abcdefghijklmnopqrstuvwxyz'; + private const SMALL_LETTERS_NONAMBIGUOUS = 'abcdefghjkmnpqrstuvwx'; + private const NUMBERS = '0123456789'; + private const NUMBERS_NONAMBIGUOUS = '23456789'; /** * Convert a camel case string to a dashed string. @@ -90,9 +52,9 @@ public function camelCaseToSnake(string $value): string public function randomChar(bool $includeAmbiguousChars = false, array $options = []): string { if ($includeAmbiguousChars) { - $chars = static::CAPITAL_LETTERS.static::SMALL_LETTERS.static::NUMBERS; + $chars = self::CAPITAL_LETTERS.self::SMALL_LETTERS.self::NUMBERS; } else { - $chars = static::CAPITAL_LETTERS_NONAMBIGUOUS.static::SMALL_LETTERS_NONAMBIGUOUS.static::NUMBERS_NONAMBIGUOUS; + $chars = self::CAPITAL_LETTERS_NONAMBIGUOUS.self::SMALL_LETTERS_NONAMBIGUOUS.self::NUMBERS_NONAMBIGUOUS; } return $this->random($chars, $options); @@ -106,9 +68,9 @@ public function randomChar(bool $includeAmbiguousChars = false, array $options = public function randomLetter(bool $includeAmbiguousChars = false, array $options = []): string { if ($includeAmbiguousChars) { - $chars = static::CAPITAL_LETTERS.static::SMALL_LETTERS; + $chars = self::CAPITAL_LETTERS.self::SMALL_LETTERS; } else { - $chars = static::CAPITAL_LETTERS_NONAMBIGUOUS.static::SMALL_LETTERS_NONAMBIGUOUS; + $chars = self::CAPITAL_LETTERS_NONAMBIGUOUS.self::SMALL_LETTERS_NONAMBIGUOUS; } return $this->random($chars, $options); @@ -122,9 +84,9 @@ public function randomLetter(bool $includeAmbiguousChars = false, array $options public function randomNumber(bool $includeAmbiguousChars = false, array $options = []): string { if ($includeAmbiguousChars) { - $chars = static::NUMBERS; + $chars = self::NUMBERS; } else { - $chars = static::NUMBERS_NONAMBIGUOUS; + $chars = self::NUMBERS_NONAMBIGUOUS; } return $this->random($chars, $options); diff --git a/src/Util/Request/UrlUtil.php b/src/Util/UrlUtil.php similarity index 84% rename from src/Util/Request/UrlUtil.php rename to src/Util/UrlUtil.php index a6668f8e..e896b855 100644 --- a/src/Util/Request/UrlUtil.php +++ b/src/Util/UrlUtil.php @@ -6,19 +6,16 @@ * @license LGPL-3.0-or-later */ -namespace HeimrichHannot\UtilsBundle\Util\Request; +namespace HeimrichHannot\UtilsBundle\Util; use HeimrichHannot\UtilsBundle\Exception\InvalidUrlException; use Symfony\Component\HttpFoundation\RequestStack; class UrlUtil { - /** @var RequestStack */ - private $requestStack; - public function __construct(RequestStack $requestStack) + public function __construct(private RequestStack $requestStack) { - $this->requestStack = $requestStack; } /** @@ -52,15 +49,6 @@ public function removeQueryStringParameterFromUrl(string $parameter, string $url return $this->buildUrlString($parsedUrl); } - /** - * @deprecated Use removeQueryStringParameterFromUrl() instead - * @codeCoverageIgnore - */ - public function removeQueryStringParameterToUrl(string $parameter, string $url = ''): string - { - return $this->removeQueryStringParameterFromUrl($parameter, $url); - } - /** * Add a query string parameter to an url. * If no url is given, the current request url is used. @@ -97,10 +85,12 @@ public function addQueryStringParameterToUrl(string $parameter, string $url = '' * Convert an absolute url to a relative url. * * Options: - * - removeLeadingSlash: (boolean) Remove leading slash from path + * - removeLeadingSlash: Remove leading slash from path * * @param string $url The url that should be made relative - * @param array $options Pass additional options + * @param array{ + * removeLeadingSlash?: bool + * } $options Pass additional options * * @throws InvalidUrlException */ diff --git a/src/Util/User/UserUtil.php b/src/Util/User/UserUtil.php deleted file mode 100644 index 228275df..00000000 --- a/src/Util/User/UserUtil.php +++ /dev/null @@ -1,44 +0,0 @@ -modelUtil = $modelUtil; - $this->databaseUtil = $databaseUtil; - $this->contaoFramework = $contaoFramework; - } - - public function findActiveUsersByGroup(array $groups, array $options = []): ?Collection - { - return $this->findActiveByGroups($this->contaoFramework, $this->databaseUtil, static::TABLE, $groups, $options); - } -} diff --git a/src/Util/UserUtil.php b/src/Util/UserUtil.php new file mode 100644 index 00000000..3e48053f --- /dev/null +++ b/src/Util/UserUtil.php @@ -0,0 +1,119 @@ + UserModel::getTable(), + UserType::MEMBER => MemberModel::getTable() + }; + + /** @var class-string $modelClass */ + $modelClass = $this->contaoFramework->getAdapter(Model::class)->getClassFromTable($table); + + if (!$modelClass) { + return null; + } + + if (!\is_array($groups) || empty($groups = array_filter($groups, function ($k) { + return !empty($k) && is_numeric($k); + }))) { + return null; + } + + /** @var Model $adapter */ + $adapter = $this->contaoFramework->getAdapter($modelClass); + + $time = Date::floorToMinute(); + $values = []; + + $columns = ["($table.start='' OR $table.start<='$time') AND ($table.stop='' OR $table.stop>'".($time + 60)."') AND $table.disable=''"]; + $columns[] = ''; + + $blobResult = $this->databaseUtil->createWhereForSerializedBlob('groups', $groups); + $columns[] = $blobResult->createOrWhere(); + $values = array_merge(array_values($values), array_values($blobResult->values)); + + return $adapter->findBy($columns, $values, $options); + } + + /** + * Returns all active users userGroups as a Collection of Models or null if user do not belong to any active userGroups + */ + public function getActiveGroups(UserModel|MemberModel $user): ?Collection + { + if (empty($groups = StringUtil::deserialize($user->groups, true))) { + return null; + } + + if ($user instanceof MemberModel) { + $groupTable = 'tl_member_group'; + } else { + $groupTable = 'tl_user_group'; + } + + $columns = [$groupTable.'.id IN('.implode(',', array_map('\intval', $groups)).')']; + + $this->modelUtil->addPublishedCheckToModelArrays($groupTable, $columns, [ + 'publishedField' => 'disable', + 'invertPublishedField' => true, + ]); + + return $this->modelUtil->findModelInstancesBy($groupTable, $columns, []); + } + + /** + * Returns true if the user or member (frontend user) is member of given group, false otherwise. + */ + public function hasActiveGroup(UserModel|MemberModel $user, int $groupId): bool + { + $activeGroups = $this->getActiveGroups($user); + + if (!$activeGroups) { + return false; + } + + foreach ($activeGroups as $group) { + if ((int) ($group->id) === $groupId) { + return true; + } + } + + return false; + } +} diff --git a/src/Util/UserUtil/UserType.php b/src/Util/UserUtil/UserType.php new file mode 100644 index 00000000..3409a2a4 --- /dev/null +++ b/src/Util/UserUtil/UserType.php @@ -0,0 +1,9 @@ +locator->get(AccordionUtil::class); } + public function anonymize(): AnonymizeUtil + { + return $this->locator->get(AnonymizeUtil::class); + } + public function array(): ArrayUtil { return $this->locator->get(ArrayUtil::class); @@ -53,6 +56,11 @@ public function container(): ContainerUtil return $this->locator->get(ContainerUtil::class); } + public function database(): DatabaseUtil + { + return $this->locator->get(DatabaseUtil::class); + } + public function dca(): DcaUtil { return $this->locator->get(DcaUtil::class); @@ -107,8 +115,10 @@ public static function getSubscribedServices() { return [ AccordionUtil::class, + AnonymizeUtil::class, ArrayUtil::class, ContainerUtil::class, + DatabaseUtil::class, DcaUtil::class, FileUtil::class, HtmlUtil::class, diff --git a/tests/AbstractUtilsTestCase.php b/tests/AbstractUtilsTestCase.php index 3b80ae76..54b3fd7c 100644 --- a/tests/AbstractUtilsTestCase.php +++ b/tests/AbstractUtilsTestCase.php @@ -13,6 +13,7 @@ use Contao\CoreBundle\Framework\Adapter; use Contao\Model; use Contao\Model\Collection; +use Contao\PageModel; use Contao\TestCase\ContaoTestCase; use Contao\UserModel; use PHPUnit\Framework\MockObject\MockBuilder; @@ -35,6 +36,9 @@ protected function mockModelAdapter() case 'tl_content': return ContentModel::class; + case 'tl_page': + return PageModel::class; + case 'tl_user': return UserModel::class; @@ -165,4 +169,9 @@ protected function mockControllerAdapter() return $adapter; } + + protected function getFixturesPath(): string + { + return __DIR__.'/Fixtures'; + } } diff --git a/tests/Arrays/ArrayUtilTest.php b/tests/Arrays/ArrayUtilTest.php deleted file mode 100644 index 47f2b2a4..00000000 --- a/tests/Arrays/ArrayUtilTest.php +++ /dev/null @@ -1,191 +0,0 @@ -getContainerMock()); - $this->assertInstanceOf(ArrayUtil::class, $instance); - } - - public function testAasort() - { - $arrayUtil = new ArrayUtil($this->getContainerMock()); - - $array = [0 => ['filename' => 'testfile3'], 1 => ['filename' => 'testfile1'], 2 => ['filename' => 'testfile2']]; - - $arrayUtil->aasort($array, 'filename'); - $this->assertSame([1 => ['filename' => 'testfile1'], 2 => ['filename' => 'testfile2'], 0 => ['filename' => 'testfile3']], $array); - } - - public function testFilterByPrefixes() - { - $arrayUtil = new ArrayUtil($this->getContainerMock()); - - $array = ['ls_0' => 0, 1 => 1, 2 => 2]; - $result = $arrayUtil->filterByPrefixes($array); - $this->assertSame($array, $result); - - $result = $arrayUtil->filterByPrefixes($array, [1]); - $this->assertSame([], $result); - - $result = $arrayUtil->filterByPrefixes($array, ['ls']); - $this->assertSame(['ls_0' => 0], $result); - } - - public function testRemovePrefix() - { - $arrayUtil = new ArrayUtil($this->getContainerMock()); - - $array = ['ls_prefix_1' => 1]; - $result = $arrayUtil->removePrefix('ls_', $array); - $this->assertSame(['prefix_1' => 1], $result); - } - - public function testArrayToObject() - { - $arrayUtil = new ArrayUtil($this->getContainerMock()); - $result = $arrayUtil->arrayToObject([]); - $this->assertInstanceOf(\stdClass::class, $result); - $this->assertCount(0, (array) $result); - - $result = $arrayUtil->arrayToObject(['id' => 4, 'title' => 'Hallo Welt!']); - $this->assertInstanceOf(\stdClass::class, $result); - $this->assertCount(2, (array) $result); - $this->assertSame('Hallo Welt!', $result->title); - - $result = $arrayUtil->arrayToObject(['id' => 4, 'title' => 'Hallo Welt!', 'content' => ['a', 'b', 'c']]); - $this->assertInstanceOf(\stdClass::class, $result); - $this->assertCount(3, (array) $result); - $this->assertSame('Hallo Welt!', $result->title); - $this->assertSame(['a', 'b', 'c'], $result->content); - } - - public function testGetArrayRowByFieldValue() - { - $arrayUtil = new ArrayUtil($this->getContainerMock()); - $this->assertSame(['id' => 5, 'hallo' => 'welt5'], $arrayUtil->getArrayRowByFieldValue('id', 5, [ - ['id' => 1, 'hallo' => 'welt'], - ['id' => 5, 'hallo' => 'welt5'], - ])); - $this->assertSame(['id' => 5, 'hallo' => 'welt5'], $arrayUtil->getArrayRowByFieldValue('id', 5, [ - ['id' => 1, 'hallo' => 'welt'], - 'id' => 5, - ['id' => 5, 'hallo' => 'welt5'], - ])); - $this->assertSame(['id' => 5, 'hallo' => 'welt5'], $arrayUtil->getArrayRowByFieldValue('id', 5, [ - ['id' => 1, 'hallo' => 'welt'], - ['pid' => 2, 'hallo' => 'sonnensystem2'], - 'id' => 5, - ['id' => 5, 'hallo' => 'welt5'], - ])); - $this->assertSame(['id' => '5', 'hallo' => 'welt5'], $arrayUtil->getArrayRowByFieldValue('id', 5, [ - ['id' => 1, 'hallo' => 'welt'], - 'id' => 5, - ['id' => '5', 'hallo' => 'welt5'], - ])); - $this->assertFalse($arrayUtil->getArrayRowByFieldValue('id', 5, [ - ['id' => 1, 'hallo' => 'welt'], - 'id' => 5, - ['id' => '5', 'hallo' => 'welt5'], - ], true)); - $this->assertFalse($arrayUtil->getArrayRowByFieldValue('id', 5, [ - ['id' => 1, 'hallo' => 'welt'], - ['id' => 4, 'hallo' => 'welt4'], - ])); - $this->assertFalse($arrayUtil->getArrayRowByFieldValue('id', 5, ['a', 'b'])); - } - - public function testFlattenArray() - { - $arrayUtil = new ArrayUtil($this->getContainerMock()); - $this->assertSame(['hallo'], $arrayUtil->flattenArray([1 => 'hallo'])); - $this->assertSame(['hallo'], $arrayUtil->flattenArray([1 => ['hallo']])); - $this->assertSame(['hallo'], $arrayUtil->flattenArray([1 => ['hallo']])); - $this->assertSame(['hallo', 'welt'], $arrayUtil->flattenArray([ - 1 => ['hallo', 'welt'], - ])); - $this->assertSame(['hallo', 'schöne', 'welt'], $arrayUtil->flattenArray([ - 1 => [ - 'hallo', - ['schöne'], - 'welt', ], - ])); - $this->assertSame(['hallo', 'schöne', 'kleine', 'welt', '!'], $arrayUtil->flattenArray([ - 1 => [ - 'hallo', - ], - ['schöne'], - 3 => 'kleine', - 4 => [ - 'welt', - ['satzzeichen' => '!'], - ], - ])); - } - - public function testInsertBeforeKey() - { - $current = ['hello' => 'world']; - ArrayUtil::insertBeforeKey($current, 'hello', 'foo', 'bar'); - $this->assertSame(['foo' => 'bar', 'hello' => 'world'], $current); - - $current = ['hello' => 'world']; - ArrayUtil::insertBeforeKey($current, 'tux', 'foo', 'bar'); - $this->assertSame(['hello' => 'world', 'foo' => 'bar'], $current); - } - - /** - * @param ContaoFramework $framework - * - * @return ContainerBuilder|ContainerInterface - */ - protected function getContainerMock(ContainerBuilder $container = null, $framework = null) - { - if (!$container) { - $container = $this->getContainerWithContaoConfiguration(); - } - - if (!$framework) { - $framework = $this->mockContaoFramework(); - } - $container->set('contao.framework', $framework); - - try { - /** @noinspection PhpParamsInspection */ - $stringUtil = new StringUtil($container->get('contao.framework')); - } catch (\Exception $e) { - $this->fail('Could net get service from container. Message: '.$e->getMessage()); - } - $container->set('huh.utils.string', $stringUtil); - - return $container; - } -} diff --git a/tests/Cache/DatabaseCacheUtilTest.php b/tests/Cache/DatabaseCacheUtilTest.php deleted file mode 100644 index f67b5362..00000000 --- a/tests/Cache/DatabaseCacheUtilTest.php +++ /dev/null @@ -1,118 +0,0 @@ -getContainerWithContaoConfiguration(); - } - $framework = $this->mockContaoFramework(); - $framework->method('createInstance')->willReturnCallback(function ($class) { - switch ($class) { - case Database::class: - $db = $this->mockAdapter(['prepare', 'execute']); - $db->method('prepare')->willReturnSelf(); - $db->method('execute')->willReturnCallback(function () { - $arrParams = \func_get_args(); - - if (!$arrParams || empty($arrParams)) { - throw new \Exception('No parameter set!'); - } - - switch ($arrParams[0]) { - case 'zero': - $resultSetMock = $this->mockClassWithProperties(Database\Result::class, [ - 'numRows' => 0, - ]); - - return $resultSetMock; - - case 'one': - $resultSetMock = $this->mockClassWithProperties(Database\Result::class, [ - 'numRows' => 1, - 'cacheValue' => '1', - ]); - - return $resultSetMock; - - default: - if (\count($arrParams) > 1) { - $this->assertTrue(is_numeric($arrParams[0])); - $this->assertTrue(is_numeric($arrParams[1])); - $this->assertTrue(\is_string($arrParams[2])); - $this->assertTrue(\is_string($arrParams[3])); - } - $resultSetMock = $this->mockClassWithProperties(Database\Result::class, [ - 'numRows' => 0, - ]); - - return $resultSetMock; - } - }); - - return $db; - } - }); - $container->set('contao.framework', $framework); - $dateUtilMock = new DateUtil($container); - $container->set('huh.utils.date', $dateUtilMock); - - return new DatabaseCacheUtil($container); - } - - public function testKeyExists() - { - $util = $this->getDatabaseCacheUtilMock(); - $this->assertFalse($util->keyExists('zero')); - $this->assertTrue($util->keyExists('one')); - } - - public function testGetValue() - { - $util = $this->getDatabaseCacheUtilMock(); - - Config::set('activateDbCache', false); - $this->assertFalse($util->getValue('hello')); - - Config::set('activateDbCache', true); - $this->assertFalse($util->getValue('zero')); - $this->assertSame('1', $util->getValue('one')); - } - - public function testCacheValue() - { - $util = $this->getDatabaseCacheUtilMock(); - - Config::set('activateDbCache', false); - $this->assertFalse($util->cacheValue('hello', 'world')); - - Config::set('activateDbCache', true); - $this->assertTrue($util->cacheValue('newkey', 'newvalue')); - } - - public function testCacheValueException() - { - $util = $this->getDatabaseCacheUtilMock(); - Config::set('activateDbCache', true); - - $this->expectException(\Exception::class); - - $this->assertTrue($util->cacheValue('one', 'value')); - } -} diff --git a/tests/Cache/RemoteImageCacheTest.php b/tests/Cache/RemoteImageCacheTest.php deleted file mode 100644 index b9700c16..00000000 --- a/tests/Cache/RemoteImageCacheTest.php +++ /dev/null @@ -1,170 +0,0 @@ -projectRoot = $this->getTempDir(); - - $fs = new Filesystem(); - $fs->mkdir($this->projectRoot.'/tmp'); - $fs->mkdir($this->projectRoot.'/system/tmp'); - } - - /** - * Tests the object instantiation. - */ - public function testCanBeInstantiated() - { - $instance = new RemoteImageCache($this->getContainerMock()); - $this->assertInstanceOf(RemoteImageCache::class, $instance); - } - - public function testGet() - { - $this->markTestSkipped(); - $container = $this->getContainerMock(); - $this->resetFilesInstance($container); - System::setContainer($container); - - $path = $this->projectRoot.'/tmp'; - $testFile = $path.'/test01.jpg'; - - $filesModel = new \stdClass(); - $filesModel->path = str_replace($this->projectRoot.\DIRECTORY_SEPARATOR, '', $path); - $filesModel->uuid = 'fade6980-1641-11e8-b642-0ed5f89f718b'; - - $filesModelAdapter = $this->mockAdapter(['findByUuid']); - $filesModelAdapter->method('findByUuid')->willReturn($filesModel); - - $framework = $this->mockContaoFramework([ - FilesModel::class => $filesModelAdapter, - ]); - - $container->set('contao.framework', $framework); - - $container->set('huh.utils.file', new FileUtil($container)); - - $fs = new Filesystem(); - $fs->dumpFile($testFile, 'test01'); - - $cache = new RemoteImageCache($container); - - $this->assertSame( - 'tmp/test01.jpg', - $cache->get('test01', 'tmp', 'http://www.google.de') - ); - $this->assertSame( - 'tmp/test01.jpg', - $cache->get('test01', 'fade6980-1641-11e8-b642-0ed5f89f718b', 'http://www.google.de') - ); - - $this->assertSame( - 'tmp/test02.jpg', - $cache->get('test02', 'tmp', 'http://www.google.de') - ); - - $this->assertFalse( - $cache->get('test03', 'tmp', 'remoteFalse') - ); - - $filesModelAdapter = $this->mockAdapter(['findByUuid']); - $filesModelAdapter->method('findByUuid')->willReturn(null); - - $framework = $this->mockContaoFramework([ - FilesModel::class => $filesModelAdapter, - ]); - $container->set('contao.framework', $framework); - - $container->set('huh.utils.file', new FileUtil($container)); - - $this->assertFalse($cache->get('test01', '0c23ab88-1642-11e8-b642-0ed5f89f718b', 'http://www.google.de')); - } - - protected function getContainerMock(ContainerBuilder $container = null) - { - if (!$container) { - $container = $this->getContainerWithContaoConfiguration($this->projectRoot); - } - - $requestStack = new RequestStack(); - $request = new Request(); - $request->attributes->set('_contao_referer_id', 'foobar'); - $requestStack->push($request); - $container->set('request_stack', $requestStack); - - System::setContainer($container); - - $container->set('contao.framework', $this->mockContaoFramework()); - - $curlMock = $this->createMock(CurlRequestUtil::class); - $curlMock->method('request')->willReturnCallback( - function ($argument) { - switch ($argument) { - case 'remoteNull': - return null; - - case 'remoteFalse': - return false; - - case 'remoteEmpty': - return 'null'; - - case 'remoteImage': - default: - return 'test01'; - } - } - ); - $container->set('huh.utils.request.curl', $curlMock); - - $fileUtilMock = $this->createMock(FileUtil::class); - $fileUtilMock->method('getFolderFromUuid')->willReturnCallback( - function ($argument) { - switch ($argument) { - case '0c23ab88-1642-11e8-b642-0ed5f89f718b': - return false; - - case 'fade6980-1641-11e8-b642-0ed5f89f718b': - default: - $folder = new \stdClass(); - $folder->value = 'tmp'; - - return $folder; - } - } - ); - $container->set('huh.utils.file', $fileUtilMock); - - return $container; - } -} diff --git a/tests/Choice/AbstractChoiceTest.php b/tests/Choice/AbstractChoiceTest.php deleted file mode 100644 index 08789806..00000000 --- a/tests/Choice/AbstractChoiceTest.php +++ /dev/null @@ -1,91 +0,0 @@ -mkdir($this->getTempDir()); - - $container = $this->getContainerWithContaoConfiguration(); - $dcaAdapter = $this->mockAdapter(['getFields']); - $dcaAdapter->method('getFields')->willReturn(['success']); - $container->set('huh.utils.dca', $dcaAdapter); - - $kernel = $this->mockAdapter(['getCacheDir', 'isDebug']); - $kernel->method('getCacheDir')->willReturn($this->getTempDir()); - $kernel->method('isDebug')->willReturn(false); - $container->set('kernel', $kernel); - $container->setParameter('kernel.debug', true); - System::setContainer($container); - } - - public function testCanBeInstantiated() - { - $fieldChoice = new FieldChoice($this->mockContaoFramework()); - $this->assertInstanceOf(AbstractChoice::class, $fieldChoice); - } - - public function testContext() - { - $fieldChoice = new FieldChoice($this->mockContaoFramework()); - $fieldChoice->setContext(['context']); - $context = $fieldChoice->getContext(); - $this->assertSame(['context'], $context); - } - - public function testGetChoices() - { - $fieldChoice = new FieldChoice($this->mockContaoFramework()); - $context = $fieldChoice->getChoices(); - $this->assertSame(['success'], $context); - } - - public function testGetCachedChoices() - { - $this->markTestSkipped(); - $container = System::getContainer(); - $dcaAdapter = $this->mockAdapter(['getFields']); - $dcaAdapter->method('getFields')->willReturn('success'); - $container->set('huh.utils.dca', $dcaAdapter); - $container->setParameter('kernel.debug', false); - System::setContainer($container); - - $fieldChoice = new FieldChoice($this->mockContaoFramework()); - $cachedChoices = $fieldChoice->getCachedChoices(['dataContainer' => 'success']); - $this->assertSame([], $cachedChoices); - - $container = System::getContainer(); - $kernel = $this->mockAdapter(['getCacheDir', 'isDebug']); - $kernel->method('getCacheDir')->willReturn($this->getTempDir()); - $kernel->method('isDebug')->willReturn(false); - $container->setParameter('kernel.debug', true); - $container->set('kernel', $kernel); - System::setContainer($container); - - $fieldChoice = new FieldChoice($this->mockContaoFramework()); - $cachedChoices = $fieldChoice->getCachedChoices(['dataContainer' => 'success']); - $this->assertSame('success', $cachedChoices); - } -} diff --git a/tests/Choice/DataContainerChoiceTest.php b/tests/Choice/DataContainerChoiceTest.php deleted file mode 100644 index d15ebb36..00000000 --- a/tests/Choice/DataContainerChoiceTest.php +++ /dev/null @@ -1,68 +0,0 @@ -mkdir($this->getTempDir()); - - $container = $this->getContainerWithContaoConfiguration(); - - $file1 = $this->createMock(\SplFileInfo::class); - $file1->method('getBasename')->willReturn('basename'); - - $file2 = $this->createMock(\SplFileInfo::class); - $file2->method('getBasename')->willReturn('basename'); - - $finder = $this->mockAdapter(['findIn', 'name']); - $finder->method('findIn')->willReturnSelf(); - $finder->method('name')->willReturn([$file1, $file2]); - - $container->set('contao.resource_finder', $finder); - - $kernel = $this->mockAdapter(['getCacheDir', 'isDebug']); - $kernel->method('getCacheDir')->willReturn($this->getTempDir()); - $kernel->method('isDebug')->willReturn(false); - $container->setParameter('kernel.debug', true); - $container->set('kernel', $kernel); - System::setContainer($container); - } - - public function testCollect() - { - $choice = new DataContainerChoice($this->mockContaoFramework()); - $choices = $choice->getChoices(); - $this->assertSame(['basename'], $choices); - - $container = System::getContainer(); - $finder = $this->mockAdapter(['findIn', 'name']); - $finder->method('findIn')->willReturnSelf(); - $finder->method('name')->willThrowException(new \InvalidArgumentException()); - $container->set('contao.resource_finder', $finder); - System::setContainer($container); - - $choices = $choice->getChoices(); - $this->assertSame([], $choices); - } -} diff --git a/tests/Choice/MessageChoiceTest.php b/tests/Choice/MessageChoiceTest.php deleted file mode 100644 index 2e34ed7e..00000000 --- a/tests/Choice/MessageChoiceTest.php +++ /dev/null @@ -1,69 +0,0 @@ -mkdir($this->getTempDir()); - - $container = $this->getContainerWithContaoConfiguration(); - - $kernel = $this->mockAdapter(['getCacheDir', 'isDebug']); - $kernel->method('getCacheDir')->willReturn($this->getTempDir()); - $kernel->method('isDebug')->willReturn(false); - $container->set('kernel', $kernel); - - $translator = $this->mockAdapter(['getCatalogue', 'all']); - $translator->method('getCatalogue')->willReturnSelf(); - $translator->method('all')->willReturn(['messages' => 'all']); - $container->set('translator', $translator); - $container->setParameter('kernel.debug', true); - - System::setContainer($container); - } - - public function testCollect() - { - $choice = new MessageChoice($this->mockContaoFramework()); - $choices = $choice->getChoices(); - $this->assertSame([], $choices); - - $container = System::getContainer(); - $translator = $this->mockAdapter(['getCatalogue', 'all']); - $translator->method('getCatalogue')->willReturnSelf(); - $translator->method('all')->willReturn(['messages' => ['all' => '41']]); - $container->set('translator', $translator); - - $arrayUtil = $this->createMock(ArrayUtil::class); - $arrayUtil->method('filterByPrefixes')->willReturn(['all' => '41']); - $container->set('huh.utils.array', $arrayUtil); - $container->set('contao.framework', $this->mockContaoFramework()); - System::setContainer($container); - - $choice = new MessageChoice($this->mockContaoFramework()); - $choices = $choice->getChoices(); - $this->assertSame(['all' => '41[all]'], $choices); - } -} diff --git a/tests/Choice/ModelInstanceChoiceTest.php b/tests/Choice/ModelInstanceChoiceTest.php deleted file mode 100644 index c0c7a283..00000000 --- a/tests/Choice/ModelInstanceChoiceTest.php +++ /dev/null @@ -1,85 +0,0 @@ -mkdir($this->getTempDir()); - - $container = $this->getContainerWithContaoConfiguration(); - - $kernel = $this->mockAdapter(['getCacheDir', 'isDebug']); - $kernel->method('getCacheDir')->willReturn($this->getTempDir()); - $kernel->method('isDebug')->willReturn(false); - $container->setParameter('kernel.debug', true); - $container->set('kernel', $kernel); - - $modelInstances = $this->mockClassWithProperties(CfgTagModel::class, ['id' => 12]); - $collection = $this->mockClassWithProperties(Model\Collection::class, ['id' => 12]); - $collection->method('next')->willReturn($modelInstances, $modelInstances); - $modelUtilAdapter = $this->mockAdapter(['findModelInstancesBy']); - $modelUtilAdapter->method('findModelInstancesBy')->willReturn($collection); - $container->set('huh.utils.model', $modelUtilAdapter); - - $dcaUtilMock = $this->mockAdapter(['getConfigByArrayOrCallbackOrFunction']); - $dcaUtilMock->method('getConfigByArrayOrCallbackOrFunction')->willReturn(null); - $container->set('huh.utils.dca', $dcaUtilMock); - - System::setContainer($container); - } - - public function testCanBeInstantiated() - { - $choice = new ModelInstanceChoice($this->mockContaoFramework()); - - $this->assertInstanceOf(ModelInstanceChoice::class, $choice); - } - - public function testCollect() - { - $choice = new ModelInstanceChoice($this->mockContaoFramework()); - $choices = $choice->getChoices(['dataContainer' => 'tl_member', 'columns' => [], 'values' => [], 'options' => [], 'labelPattern' => false, 'skipSorting' => false]); - $this->assertSame(['12' => ' (ID 12)'], $choices); - } - - public function testCollectDefault() - { - $GLOBALS['TL_DCA']['tl_test']['fields']['name'] = 'name'; - - $choice = new ModelInstanceChoice($this->mockContaoFramework()); - $choices = $choice->getChoices(['dataContainer' => 'tl_test', 'columns' => [], 'values' => [], 'options' => [], 'labelPattern' => false, 'skipSorting' => false]); - $this->assertSame(['12' => ''], $choices); - - $container = System::getContainer(); - $modelUtilAdapter = $this->mockAdapter(['findModelInstancesBy']); - $modelUtilAdapter->method('findModelInstancesBy')->willReturn(null); - $container->set('huh.utils.model', $modelUtilAdapter); - System::setContainer($container); - - $choices = $choice->getChoices(); - $this->assertSame([], $choices); - } -} diff --git a/tests/Choice/TwigTemplateChoiceTest.php b/tests/Choice/TwigTemplateChoiceTest.php deleted file mode 100644 index d5d3ddea..00000000 --- a/tests/Choice/TwigTemplateChoiceTest.php +++ /dev/null @@ -1,175 +0,0 @@ -mkdir($this->getTempDir()); - - $this->container = $this->getContainerWithContaoConfiguration(); - $this->container->set('contao.framework', $this->mockContaoFramework()); - - $this->container->set('huh.utils.array', new ArrayUtil($this->container)); - $this->container->set('huh.utils.string', new StringUtil($this->mockContaoFramework())); - - $translator = $this->mockAdapter(['getCatalogue', 'all']); - $translator->method('getCatalogue')->willReturnSelf(); - $translator->method('all')->willReturn(['messages' => 'all']); - $this->container->set('translator', $translator); - $this->container->setParameter('kernel.debug', true); - } - - /** - * Test collect(). - */ - public function testCollect() - { - $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\KernelInterface')->getMock(); - $kernel - ->expects($this->once()) - ->method('getBundles') - ->willReturn(['HeimrichHannotContaoUtilsBundle' => new HeimrichHannotContaoUtilsBundle()]); - - $kernel->method('locateResource') - ->willReturn(__DIR__.'/../../src'); - - $this->container->set('kernel', $kernel); - - System::setContainer($this->container); - - $choice = new TwigTemplateChoice($this->mockContaoFramework()); - $choices = $choice->getChoices(); - $this->assertNotEmpty($choices); - $this->assertContains('@HeimrichHannotContaoUtils/image.html.twig', $choices); - $this->assertContains('@HeimrichHannotContaoUtils/picture.html.twig', $choices); - } - - /** - * Test collect() by prefixes. - */ - public function testCollectByPrefixes() - { - $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\KernelInterface')->getMock(); - $kernel - ->expects($this->once()) - ->method('getBundles') - ->willReturn(['HeimrichHannotContaoUtilsBundle' => new HeimrichHannotContaoUtilsBundle()]); - - $kernel->method('locateResource') - ->willReturn(__DIR__.'/../../src'); - - $this->container->set('kernel', $kernel); - - System::setContainer($this->container); - - $choice = new TwigTemplateChoice($this->mockContaoFramework()); - $choices = $choice->getChoices(['image']); - $this->assertNotEmpty($choices); - - $this->assertContains('@HeimrichHannotContaoUtils/image.html.twig', $choices); - } - - /** - * Test collect() by prefixes. - */ - public function testCollectByMultiplePrefixes() - { - $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\KernelInterface')->getMock(); - $kernel - ->expects($this->once()) - ->method('getBundles') - ->willReturn(['HeimrichHannotContaoUtilsBundle' => new HeimrichHannotContaoUtilsBundle()]); - - $kernel->method('locateResource') - ->willReturn(__DIR__.'/../../src'); - - $this->container->set('kernel', $kernel); - - System::setContainer($this->container); - - $choice = new TwigTemplateChoice($this->mockContaoFramework()); - $choices = $choice->getChoices(['image', 'picture']); - $this->assertNotEmpty($choices); - - $this->assertContains('@HeimrichHannotContaoUtils/image.html.twig', $choices); - $this->assertContains('@HeimrichHannotContaoUtils/picture.html.twig', $choices); - } - - /** - * Test collect() by prefixes without match. - */ - public function testCollectByPrefixesWithoutMatch() - { - $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\KernelInterface')->getMock(); - $kernel - ->expects($this->once()) - ->method('getBundles') - ->willReturn(['HeimrichHannotContaoUtilsBundle' => new HeimrichHannotContaoUtilsBundle()]); - - $kernel->method('locateResource') - ->willReturn(__DIR__.'/../../src'); - - $this->container->set('kernel', $kernel); - - System::setContainer($this->container); - - $choice = new TwigTemplateChoice($this->mockContaoFramework()); - $choices = $choice->getChoices(['imageXYZ!"§']); - $this->assertEmpty($choices); - } - - /** - * Test collect() by prefixes with suffix. - */ - public function testCollectByPrefixesWithSuffix() - { - $kernel = $this->getMockBuilder('Symfony\Component\HttpKernel\KernelInterface')->getMock(); - $kernel - ->expects($this->once()) - ->method('getBundles') - ->willReturn(['HeimrichHannotContaoUtilsBundle' => new HeimrichHannotContaoUtilsBundle()]); - - $kernel->method('locateResource') - ->willReturn(__DIR__.'/../../src'); - - $this->container->set('kernel', $kernel); - - System::setContainer($this->container); - - $choice = new TwigTemplateChoice($this->mockContaoFramework()); - $choices = $choice->getChoices(['image.html']); - $this->assertNotEmpty($choices); - - $this->assertContains('@HeimrichHannotContaoUtils/image.html.twig', $choices); - } -} diff --git a/tests/Classes/ClassUtilTest.php b/tests/Classes/ClassUtilTest.php deleted file mode 100644 index 36aa510c..00000000 --- a/tests/Classes/ClassUtilTest.php +++ /dev/null @@ -1,100 +0,0 @@ -createMock(ArrayUtil::class); - $stringUtil = $parameters['stringUtil'] ?? $this->createMock(StringUtil::class); - - return new ClassUtil($arrayUtil, $stringUtil); - } - - public function testClassesInNamespace() - { - $stringUtilMock = $this->createMock(StringUtil::class); - $stringUtilMock->method('startsWith')->willReturnCallback(function (string $haystack, string $needle) { - return '' === $needle || false !== strrpos($haystack, $needle, -\strlen($haystack)); - }); - $classUtil = $this->getTestInstance([ - 'stringUtil' => $stringUtilMock, - ]); - - $classes = $classUtil->getClassesInNamespace('HeimrichHannot\UtilsBundle\Arrays'); - $this->assertSame(['HeimrichHannot\UtilsBundle\Arrays\ArrayUtil' => 'HeimrichHannot\UtilsBundle\Arrays\ArrayUtil'], $classes); - } - - public function testGetChildClasses() - { - $this->markTestSkipped(); - $classUtil = $this->getTestInstance(); - $childClasses = $classUtil->getChildClasses('HeimrichHannot\UtilsBundle\Choice\AbstractChoice'); - $this->assertSame([ - 'HeimrichHannot\UtilsBundle\Choice\DataContainerChoice' => 'HeimrichHannot\UtilsBundle\Choice\DataContainerChoice', - 'HeimrichHannot\UtilsBundle\Choice\FieldChoice' => 'HeimrichHannot\UtilsBundle\Choice\FieldChoice', - 'HeimrichHannot\UtilsBundle\Choice\MessageChoice' => 'HeimrichHannot\UtilsBundle\Choice\MessageChoice', - 'HeimrichHannot\UtilsBundle\Choice\ModelInstanceChoice' => 'HeimrichHannot\UtilsBundle\Choice\ModelInstanceChoice', - 'HeimrichHannot\UtilsBundle\Choice\TwigTemplateChoice' => 'HeimrichHannot\UtilsBundle\Choice\TwigTemplateChoice', - ], $childClasses); - } - - public function testGetConstantByPrefixes() - { - $this->markTestSkipped(); - $classUtil = $this->getTestInstance(); - $constants = $classUtil->getConstantsByPrefixes('HeimrichHannot\UtilsBundle\Dca\DcaUtil', ['AUTHOR_TYPE']); - $this->assertSame(['none' => 'none', 'member' => 'member', 'user' => 'user'], $constants); - - $constants = $classUtil->getConstantsByPrefixes('HeimrichHannot\UtilsBundle\Dca\DcaUaftil', ['AUTHOR_TYPE']); - $this->assertSame([], $constants); - - $constants = $classUtil->getConstantsByPrefixes('HeimrichHannot\UtilsBundle\Dca\DcaUtil', ['NO_CONST']); - $this->assertSame([], $constants); - } - - public function testGetParentClasses() - { - $classUtil = $this->getTestInstance(); - $classes = $classUtil->getParentClasses('HeimrichHannot\UtilsBundle\Choice\DataContainerChoice'); - $this->assertSame(['HeimrichHannot\UtilsBundle\Choice\AbstractChoice'], $classes); - } - - public function skipTestJsonSerialize() - { - if (!class_exists('HeimrichHannot\UtilsBundle\Tests\Classes\JsonSerializeTestClass')) { - include_once __DIR__.'/JsonSerializeTestClass.php'; - } - - $classUtil = $this->getTestInstance(); - - $testClass = new JsonSerializeTestClass(); - - $result = $classUtil->jsonSerialize($testClass); - - $this->assertNotEmpty($result); - $this->assertArrayHasKey('map', $result); - $this->assertArrayHasKey('isPublished', $result); - $this->assertTrue($result['isPublished']); - $this->assertArrayHasKey('hasPublished', $result); - $this->assertFalse($result['hasPublished']); - $this->assertArrayHasKey('addDetails', $result); - $this->assertTrue($result['addDetails']); - $this->assertArrayNotHasKey('protectedMap', $result); - $this->assertArrayHasKey('mapWithAttributes', $result); - } -} diff --git a/tests/Classes/JsonSerializeTestClass.php b/tests/Classes/JsonSerializeTestClass.php deleted file mode 100644 index a6dc3f1a..00000000 --- a/tests/Classes/JsonSerializeTestClass.php +++ /dev/null @@ -1,58 +0,0 @@ -privateVar = 'test'; - } - - public function getMap() - { - return ['map' => true]; - } - - public function getMapWithAttributes(array $attributes = []) - { - return ['map' => true]; - } - - public function getNestedObject() - { - return new \stdClass(); - } - - public function isPublished() - { - return true; - } - - public function hasPublished() - { - return false; - } - - public function isAddDetails() - { - return true; - } - - protected function getProtectedMap() - { - return ['protected_map' => true]; - } -} diff --git a/tests/ContaoManager/PluginTest.php b/tests/ContaoManager/PluginTest.php index 6404d841..978c2c4f 100644 --- a/tests/ContaoManager/PluginTest.php +++ b/tests/ContaoManager/PluginTest.php @@ -12,43 +12,11 @@ use Contao\ManagerPlugin\Bundle\Config\BundleConfig; use Contao\ManagerPlugin\Bundle\Parser\DelegatingParser; use Contao\TestCase\ContaoTestCase; -use HeimrichHannot\UtilsBundle\Accordion\AccordionUtil; -use HeimrichHannot\UtilsBundle\Arrays\ArrayUtil; -use HeimrichHannot\UtilsBundle\Cache\FileCache; -use HeimrichHannot\UtilsBundle\Cache\RemoteImageCache; -use HeimrichHannot\UtilsBundle\Classes\ClassUtil; -use HeimrichHannot\UtilsBundle\Container\ContainerUtil; use HeimrichHannot\UtilsBundle\ContaoManager\Plugin; -use HeimrichHannot\UtilsBundle\Date\DateUtil; -use HeimrichHannot\UtilsBundle\Dca\DcaUtil; -use HeimrichHannot\UtilsBundle\File\FileUtil; -use HeimrichHannot\UtilsBundle\HeimrichHannotContaoUtilsBundle; -use HeimrichHannot\UtilsBundle\Image\ImageUtil; -use HeimrichHannot\UtilsBundle\Model\ModelUtil; -use HeimrichHannot\UtilsBundle\Module\ModuleUtil; -use HeimrichHannot\UtilsBundle\Template\TemplateUtil; -use Symfony\Component\Config\Loader\DelegatingLoader; -use Symfony\Component\Config\Loader\LoaderResolver; -use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; -use Symfony\Component\HttpKernel\Config\FileLocator; -use Symfony\Component\HttpKernel\Kernel; +use HeimrichHannot\UtilsBundle\HeimrichHannotUtilsBundle; class PluginTest extends ContaoTestCase { - /** - * @var Plugin - */ - protected $plugin; - /** - * @var ContainerBuilder - */ - protected $container; - /** - * @var string - */ - protected $projectDir; - public function testGetBundles() { $plugin = new Plugin(); @@ -56,114 +24,9 @@ public function testGetBundles() /** @var BundleConfig[] $bundles */ $bundles = $plugin->getBundles(new DelegatingParser()); - $this->assertCount(2, $bundles); + $this->assertCount(1, $bundles); $this->assertInstanceOf(BundleConfig::class, $bundles[0]); - $this->assertSame(HeimrichHannotContaoUtilsBundle::class, $bundles[0]->getName()); + $this->assertSame(HeimrichHannotUtilsBundle::class, $bundles[0]->getName()); $this->assertSame([ContaoCoreBundle::class], $bundles[0]->getLoadAfter()); } - - public function skiptestRegisterContainerConfiguration() - { - $kernelMock = $this->createMock(Kernel::class); - $kernelMock->method('locateResource')->willReturnCallback(function ($file, $currentDir = null, $first = true, $triggerDeprecation = true) { - return $currentDir.'/../src/Resources/config/'.pathinfo($file, \PATHINFO_BASENAME); - }); - - $locator = new FileLocator($kernelMock, __DIR__.'/..'); - - $container = new ContainerBuilder(); - - $resolver = new LoaderResolver([ - new YamlFileLoader($container, $locator), - ]); - - $loader = new DelegatingLoader($resolver); - - $plugin = new Plugin(); - $plugin->registerContainerConfiguration($loader, []); - - $utils = [ - [ - 'alias' => AccordionUtil::class, - 'class' => AccordionUtil::class, - 'parameters' => 1, - ], - [ - 'alias' => 'huh.utils.accordion', - 'class' => AccordionUtil::class, - 'parameters' => 1, - ], - [ - 'alias' => 'huh.utils.array', - 'class' => ArrayUtil::class, - 'parameters' => 1, - ], - [ - 'alias' => 'huh.utils.cache.file', - 'class' => FileCache::class, - 'parameters' => 1, - ], - [ - 'alias' => 'huh.utils.cache.remote_image_cache', - 'class' => RemoteImageCache::class, - 'parameters' => 1, - ], - [ - 'alias' => 'huh.utils.class', - 'class' => ClassUtil::class, - 'parameters' => 1, - ], - [ - 'alias' => 'huh.utils.container', - 'class' => ContainerUtil::class, - 'parameters' => 3, - ], - [ - 'alias' => 'huh.utils.date', - 'class' => DateUtil::class, - 'parameters' => 1, - ], - [ - 'alias' => 'huh.utils.dca', - 'class' => DcaUtil::class, - 'parameters' => 1, - ], - [ - 'alias' => 'huh.utils.file', - 'class' => FileUtil::class, - 'parameters' => 1, - ], - [ - 'alias' => 'huh.utils.image', - 'class' => ImageUtil::class, - 'parameters' => 1, - ], - [ - 'alias' => 'huh.utils.model', - 'class' => ModelUtil::class, - 'parameters' => 1, - ], - [ - 'alias' => 'huh.utils.module', - 'class' => ModuleUtil::class, - 'parameters' => 1, - ], - [ - 'alias' => 'huh.utils.template', - 'class' => TemplateUtil::class, - 'parameters' => 1, - ], - ]; - - foreach ($utils as $util) { - $this->assertTrue($container->has($util['alias'])); - $definition = $container->findDefinition($util['alias']); - - if (null != $definition->getClass()) { - $this->assertSame($util['class'], $definition->getClass()); - } - $this->assertEmpty($definition->getArguments()); - $this->assertTrue($definition->isAutowired()); - } - } } diff --git a/tests/Database/DatabaseUtilTest.php b/tests/Database/DatabaseUtilTest.php deleted file mode 100644 index aa97ae39..00000000 --- a/tests/Database/DatabaseUtilTest.php +++ /dev/null @@ -1,391 +0,0 @@ -setParameter('contao.image.target_dir', TL_ROOT.'/data'); - $arrayUtils = new ArrayUtil($container); - $container->set('huh.utils.array', $arrayUtils); - System::setContainer($container); - } - - public function createTestInstance(array $parameters = []) - { - if (!isset($parameters['framework'])) { - $parameters['framework'] = $this->mockContaoFramework(); - } - - return new DatabaseUtil($parameters['framework']); - } - - /** - * Tests the object instantiation. - */ - public function testCanBeInstantiated() - { - $framework = $this->mockContaoFramework(); - $instance = new DatabaseUtil($framework); - $this->assertInstanceOf('HeimrichHannot\UtilsBundle\Database\DatabaseUtil', $instance); - } - - public function testProcessInPieces() - { - $countQuery = 'SELECT COUNT(*) as total FROM lawyers'; - $query = 'SELECT * FROM lawyers'; - - // perfect run - $total = $this->mockClassWithProperties(Database\Result::class, ['total' => 10]); - $result = $this->mockClassWithProperties(Database\Result::class, ['numRows' => 10]); - $result->method('fetchAssoc')->willReturn(['row' => 10], false); - $databaseAdapter = $this->mockAdapter(['execute', 'prepare', 'limit']); - $databaseAdapter->method('execute')->willReturn($total); - $databaseAdapter->method('prepare')->willReturn($databaseAdapter); - $limitAdapter = $this->mockAdapter(['execute']); - $limitAdapter->method('execute')->willReturn($result); - $databaseAdapter->method('limit')->willReturn($limitAdapter); - $framework = $this->mockContaoFramework(); - $framework->method('createInstance')->willReturn($databaseAdapter); - $databaseUtil = new DatabaseUtil($framework); - - $result = $databaseUtil->processInPieces($countQuery, $query, 'array_keys', 'row'); - $this->assertSame(10, $result); - - // total < 1 - $total = $this->mockClassWithProperties(Database\Result::class, ['total' => 0]); - $databaseAdapter = $this->mockAdapter(['execute', 'prepare', 'limit']); - $databaseAdapter->method('execute')->willReturn($total); - - $framework = $this->mockContaoFramework(); - $framework->method('createInstance')->willReturn($databaseAdapter); - $databaseUtil = new DatabaseUtil($framework); - $result = $databaseUtil->processInPieces($countQuery, $query, 'array_keys'); - $this->assertFalse($result); - - // numRows < 1 - $total = $this->mockClassWithProperties(Database\Result::class, ['total' => 10]); - $result = $this->mockClassWithProperties(Database\Result::class, ['numRows' => 0]); - $databaseAdapter = $this->mockAdapter(['execute', 'prepare', 'limit']); - $databaseAdapter->method('execute')->willReturn($total); - $databaseAdapter->method('prepare')->willReturn($databaseAdapter); - $limitAdapter = $this->mockAdapter(['execute']); - $limitAdapter->method('execute')->willReturn($result); - $databaseAdapter->method('limit')->willReturn($limitAdapter); - $framework = $this->mockContaoFramework(); - $framework->method('createInstance')->willReturn($databaseAdapter); - $databaseUtil = new DatabaseUtil($framework); - - $result = $databaseUtil->processInPieces($countQuery, $query, 'array_keys', 'row'); - $this->assertFalse($result); - - // return [] = $row - $total = $this->mockClassWithProperties(Database\Result::class, ['total' => 10]); - $result = $this->mockClassWithProperties(Database\Result::class, ['numRows' => 10]); - $result->method('fetchAssoc')->willReturn(['row' => 10], false); - $databaseAdapter = $this->mockAdapter(['execute', 'prepare', 'limit']); - $databaseAdapter->method('execute')->willReturn($total); - $databaseAdapter->method('prepare')->willReturn($databaseAdapter); - $limitAdapter = $this->mockAdapter(['execute']); - $limitAdapter->method('execute')->willReturn($result); - $databaseAdapter->method('limit')->willReturn($limitAdapter); - $framework = $this->mockContaoFramework(); - $framework->method('createInstance')->willReturn($databaseAdapter); - $databaseUtil = new DatabaseUtil($framework); - - $result = $databaseUtil->processInPieces($countQuery, $query, 'is_array'); - $this->assertSame(10, $result); - - // stupid input handling - $total = $this->mockClassWithProperties(Database\Result::class, ['total' => 10]); - $result = $this->mockClassWithProperties(Database\Result::class, ['numRows' => 10]); - $result->method('fetchAssoc')->willReturn(['row' => 10], false); - $databaseAdapter = $this->mockAdapter(['execute', 'prepare', 'limit']); - $databaseAdapter->method('execute')->willReturn($total); - $databaseAdapter->method('prepare')->willReturn($databaseAdapter); - $limitAdapter = $this->mockAdapter(['execute']); - $limitAdapter->method('execute')->willReturn($result); - $databaseAdapter->method('limit')->willReturn($limitAdapter); - $framework = $this->mockContaoFramework(); - $framework->method('createInstance')->willReturn($databaseAdapter); - $databaseUtil = new DatabaseUtil($framework); - - $result = $databaseUtil->processInPieces($countQuery, $query, null, null, -10); - $this->assertSame(10, $result); - - $result = $databaseUtil->processInPieces($countQuery, $query, 'is_array', 12); - $this->assertSame(10, $result); - } - - public function testDoBulkInsert() - { - $databaseAdapter = $this->mockAdapter(['tableExists', 'getFieldNames', 'execute', 'prepare']); - $databaseAdapter->method('tableExists')->willReturn(true); - $databaseAdapter->method('getFieldNames')->willReturn(['id', 'name', 'date', 'test']); - $databaseAdapter->method('prepare')->willReturnSelf(); - - // return null - $framework = $this->mockContaoFramework(); - $framework->method('createInstance')->willReturn($databaseAdapter); - $databaseUtil = new DatabaseUtil($framework); - $result = $databaseUtil->doBulkInsert('table', []); - $this->assertNull($result); - - // perfect run - $model = $this->createMock(Model::class); - $model->method('row')->willReturn(['name' => 'max', 'date' => time()]); - - $data = [['name' => 'DEFAULT', 'date' => time(), 'test' => 'DEFAULT'], ['name' => 'max', 'date' => time()], $model]; - - $result = $databaseUtil->doBulkInsert('table', $data, ['name' => 'Max'], 'UPDATE', 'is_array', function ($return, $fields, $varData) { return null; }, 2); - $this->assertNull($result); - - $result = $databaseUtil->doBulkInsert('table', $data, ['name' => 'Max'], 'UPDATE', 'is_array', function ($return, $fields, $varData) { return $varData; }, 2); - $this->assertNull($result); - - $result = $databaseUtil->doBulkInsert('table', $data, ['test' => 'DEFAULT', 'name' => 'DEFAULT', 'date' => 'DEFAULT'], 'UPDATE', 'is_array', function ($return, $fields, $varData) { return $varData; }, 2); - $this->assertNull($result); - - // names != name - $data = [['names' => 'DEFAULT', 'dates' => time()]]; - $result = $databaseUtil->doBulkInsert('table', $data, ['name' => 'Max'], 'UPDATE', 'is_array', function ($return, $fields, $varData) { return $varData; }, -1); - $this->assertNull($result); - } - - public function testCreateWhereForSerializeBlob() - { - $databaseUtil = new DatabaseUtil($this->mockContaoFramework()); - - // wrong connective - try { - $result = $databaseUtil->createWhereForSerializedBlob('field', [], 'blaa fu'); - } catch (\Exception $exception) { - $this->assertSame('Unknown sql junctor', $exception->getMessage()); - } - - // perfect run - $result = $databaseUtil->createWhereForSerializedBlob('field', ['value1', 'value2']); - $this->assertCount(2, $result); - $this->assertSame('(field REGEXP (?) OR field REGEXP (?))', $result[0]); - $this->assertCount(2, $result[1]); - } - - public function testTransformVerboseOperator() - { - $databaseUtil = new DatabaseUtil($this->mockContaoFramework()); - - $result = $databaseUtil->transformVerboseOperator('like'); - $this->assertSame('LIKE', $result); - $result = $databaseUtil->transformVerboseOperator('unlike'); - $this->assertSame('NOT LIKE', $result); - $result = $databaseUtil->transformVerboseOperator('equal'); - $this->assertSame('=', $result); - $result = $databaseUtil->transformVerboseOperator('unequal'); - $this->assertSame('!=', $result); - $result = $databaseUtil->transformVerboseOperator('lower'); - $this->assertSame('<', $result); - $result = $databaseUtil->transformVerboseOperator('greater'); - $this->assertSame('>', $result); - $result = $databaseUtil->transformVerboseOperator('lowerequal'); - $this->assertSame('<=', $result); - $result = $databaseUtil->transformVerboseOperator('greaterequal'); - $this->assertSame('>=', $result); - $result = $databaseUtil->transformVerboseOperator('in'); - $this->assertSame('IN', $result); - $result = $databaseUtil->transformVerboseOperator('notin'); - $this->assertSame('NOT IN', $result); - $result = $databaseUtil->transformVerboseOperator('isnull'); - $this->assertSame('NOT IN', $result); - $result = $databaseUtil->transformVerboseOperator('isnotnull'); - $this->assertSame('IS NOT NULL', $result); - $result = $databaseUtil->transformVerboseOperator('blaa'); - $this->assertFalse($result); - } - - public function skipTestComputeCondition() - { - $databaseUtil = new DatabaseUtil($this->mockContaoFramework()); - - $GLOBALS['TL_DCA']['table']['fields']['field'] = ['sql' => 'blob']; - - // perfect run - $result = $databaseUtil->computeCondition('field', 'like', 'value', 'table'); - $this->assertSame(['table.field LIKE ?', ['%"value"%']], $result); - $this->assertCount(2, $result); - - $result = $databaseUtil->computeCondition('field', 'unlike', 'value'); - $this->assertSame(['field NOT LIKE ?', ['%value%']], $result); - $this->assertCount(2, $result); - - $result = $databaseUtil->computeCondition('field', 'unlike', ['value']); - $this->assertSame(['field NOT LIKE ?', ['%value%']], $result); - $this->assertCount(2, $result); - - $result = $databaseUtil->computeCondition('field', 'equal', 'value'); - $this->assertSame(['field = ?', ['value']], $result); - $this->assertCount(2, $result); - - $result = $databaseUtil->computeCondition('field', 'unequal', 'value'); - $this->assertSame(['field != ?', ['value']], $result); - $this->assertCount(2, $result); - - $result = $databaseUtil->computeCondition('field', 'lower', 'value'); - $this->assertSame(['field < CAST(? AS DECIMAL)', ['value']], $result); - $this->assertCount(2, $result); - - $result = $databaseUtil->computeCondition('field', 'greater', 'value'); - $this->assertSame(['field > CAST(? AS DECIMAL)', ['value']], $result); - $this->assertCount(2, $result); - - $result = $databaseUtil->computeCondition('field', 'lowerequal', 'value'); - $this->assertSame(['field <= CAST(? AS DECIMAL)', ['value']], $result); - $this->assertCount(2, $result); - - $result = $databaseUtil->computeCondition('field', 'greaterequal', 'value'); - $this->assertSame(['field >= CAST(? AS DECIMAL)', ['value']], $result); - $this->assertCount(2, $result); - - $result = $databaseUtil->computeCondition('field', 'in', 'value'); - $this->assertSame(['field IN ("value")', []], $result); - $this->assertCount(2, $result); - - $result = $databaseUtil->computeCondition('field', 'notin', 'value'); - $this->assertSame(['field NOT IN ("value")', []], $result); - $this->assertCount(2, $result); - - $result = $databaseUtil->computeCondition('field', 'isnull', 'value'); - $this->assertSame(['field NOT IN ', []], $result); - $this->assertCount(2, $result); - - $result = $databaseUtil->computeCondition('field', 'isnotnull', 'value'); - $this->assertSame(['field IS NOT NULL ', []], $result); - $this->assertCount(2, $result); - - // error handling - $result = $databaseUtil->computeCondition('field', 'like', ['value'], 'table'); - $this->assertSame(['table.field LIKE ?', ['%"value"%']], $result); - $this->assertCount(2, $result); - } - - public function skipTestComposeWhereForQueryBuilder() - { - $databaseUtil = new DatabaseUtil($this->mockContaoFramework()); - - // perfect run - $result = $databaseUtil->composeWhereForQueryBuilder($this->getQueryBuilderMock(), 'field', 'like', ['eval' => ['multiple' => []]], null); - $this->assertSame('LIKE', $result); - $result = $databaseUtil->composeWhereForQueryBuilder($this->getQueryBuilderMock(), 'field', 'unlike', ['eval' => ['multiple' => []]], null); - $this->assertSame('NOT LIKE', $result); - $result = $databaseUtil->composeWhereForQueryBuilder($this->getQueryBuilderMock(), 'field', 'in', ['eval' => ['multiple' => []]], [1, 2]); - $this->assertSame('IN', $result); - $result = $databaseUtil->composeWhereForQueryBuilder($this->getQueryBuilderMock(), 'field', 'notin', ['eval' => ['multiple' => []]], [1, 2]); - $this->assertSame('NOT IN', $result); - $result = $databaseUtil->composeWhereForQueryBuilder($this->getQueryBuilderMock(), 'field', 'isnull', ['eval' => ['multiple' => []]], null); - $this->assertSame('IS NULL', $result); - $result = $databaseUtil->composeWhereForQueryBuilder($this->getQueryBuilderMock(), 'field', 'isnotnull', ['eval' => ['multiple' => []]], null); - $this->assertSame('IS NOT NULL', $result); - $result = $databaseUtil->composeWhereForQueryBuilder($this->getQueryBuilderMock(), 'field', 'greaterequal', ['eval' => ['multiple' => []]], null); - $this->assertSame('>=', $result); - $result = $databaseUtil->composeWhereForQueryBuilder($this->getQueryBuilderMock(), 'field', 'greater', ['eval' => ['multiple' => []]], null); - $this->assertSame('>', $result); - $result = $databaseUtil->composeWhereForQueryBuilder($this->getQueryBuilderMock(), 'field', 'lowerequal', ['eval' => ['multiple' => []]], null); - $this->assertSame('<=', $result); - $result = $databaseUtil->composeWhereForQueryBuilder($this->getQueryBuilderMock(), 'field', 'lower', ['eval' => ['multiple' => []]], null); - $this->assertSame('<', $result); - $result = $databaseUtil->composeWhereForQueryBuilder($this->getQueryBuilderMock(), 'field', 'unequal', ['eval' => ['multiple' => []]], null); - $this->assertSame('<>', $result); - $result = $databaseUtil->composeWhereForQueryBuilder($this->getQueryBuilderMock(), 'field', 'equal', ['eval' => ['multiple' => []]], null); - $this->assertSame('=', $result); - $result = $databaseUtil->composeWhereForQueryBuilder($this->getQueryBuilderMock(), 'field', 'regexp', ['eval' => ['multiple' => []]], null); - $this->assertSame('field REGEXP :field', $result); - $result = $databaseUtil->composeWhereForQueryBuilder($this->getQueryBuilderMock(), 'field', 'regexp', ['eval' => ['multiple' => true]], ['array']); - $this->assertSame('field REGEXP :field', $result); - $result = $databaseUtil->composeWhereForQueryBuilder($this->getQueryBuilderMock(), 'field', 'regexp', ['eval' => ['multiple' => true]], 'array'); - $this->assertSame('field REGEXP :field', $result); - $result = $databaseUtil->composeWhereForQueryBuilder($this->getQueryBuilderMock(), 'field', 'boo', ['eval' => ['multiple' => true]], 'array'); - $this->assertSame('', $result); - } - - /** - * @return \Doctrine\DBAL\Query\QueryBuilder|\PHPUnit_Framework_MockObject_MockObject - */ - public function getQueryBuilderMock() - { - $mock = $this->getMockBuilder(\Doctrine\DBAL\Query\QueryBuilder::class)->disableOriginalConstructor()->setMethods([ - 'expr', - 'like', - 'notLike', - 'in', - 'notIn', - 'isNull', - 'isNotNull', - 'gte', - 'gt', - 'lte', - 'lt', - 'neq', - 'eq', - 'setParameter', - ])->getMock(); - $mock->expects($this->any())->method('expr')->willReturnSelf(); - $mock->expects($this->any())->method('like')->willReturn('LIKE'); - $mock->expects($this->any())->method('notLike')->willReturn('NOT LIKE'); - $mock->expects($this->any())->method('in')->willReturn('IN'); - $mock->expects($this->any())->method('notIn')->willReturn('NOT IN'); - $mock->expects($this->any())->method('isNull')->willReturn('IS NULL'); - $mock->expects($this->any())->method('isNotNull')->willReturn('IS NOT NULL'); - $mock->expects($this->any())->method('gte')->willReturn('>='); - $mock->expects($this->any())->method('gt')->willReturn('>'); - $mock->expects($this->any())->method('lte')->willReturn('<='); - $mock->expects($this->any())->method('lt')->willReturn('<'); - $mock->expects($this->any())->method('neq')->willReturn('<>'); - $mock->expects($this->any())->method('eq')->willReturn('='); - $mock->expects($this->any())->method('setParameter'); - - return $mock; - } - - public function testCreateWhereForSerializedBlob() - { - $instance = $this->createTestInstance(); - $result = $instance->createWhereForSerializedBlob('test', ['A', 'B']); - $this->assertCount(2, $result); - $this->assertSame('(test REGEXP (?) OR test REGEXP (?))', $result[0]); - $this->assertCount(2, $result[1]); - $this->assertSame(':"A"', $result[1][0]); - - $instance = $this->createTestInstance(); - $result = $instance->createWhereForSerializedBlob('test', ['A', 'B'], $instance::SQL_CONDITION_AND); - $this->assertCount(2, $result); - $this->assertSame('((test REGEXP (?)) AND (test REGEXP (?)))', $result[0]); - $this->assertCount(2, $result[1]); - $this->assertSame(':"A"', $result[1][0]); - - $result = $instance->createWhereForSerializedBlob('test', ['A', 'B'], $instance::SQL_CONDITION_OR, ['inline_values' => true]); - $this->assertCount(2, $result); - $this->assertSame('(test REGEXP (:"A") OR test REGEXP (:"B"))', $result[0]); - $this->assertCount(2, $result[1]); - $this->assertSame(':"A"', $result[1][0]); - } -} diff --git a/tests/Date/DateUtilTest.php b/tests/Date/DateUtilTest.php deleted file mode 100644 index 07707fbf..00000000 --- a/tests/Date/DateUtilTest.php +++ /dev/null @@ -1,282 +0,0 @@ -assertInstanceOf(self::class, $instance); - } - - public function getDateUtilMock() - { - $container = $this->getContainerWithContaoConfiguration(); - - $framework = $this->mockContaoFramework(); - $container->set('contao.framework', $framework); - System::setContainer($container); - - return new DateUtil($container); - } - - /** - * @dataProvider timeStampProvider - */ - public function testGetTimeStamp($date, $expected, $replaceInsertTags = true, $timeZone = null, $compareFormat = null) - { - $this->markTestSkipped(); - - // Prevent "undefined index" errors - $errorReporting = error_reporting(); - error_reporting($errorReporting & ~\E_NOTICE); - - $instance = $this->getDateUtilMock(); - - if (null !== $timeZone) { - Config::set('timeZone', $timeZone); - } - - if ('NOW' === $expected) { - $expected = time(); - } - - if (null !== $compareFormat) { - $this->assertSame(Date::parse($compareFormat, $expected), Date::parse($compareFormat, $instance->getTimeStamp($date, $replaceInsertTags, $timeZone))); - - return; - } - - $this->assertSame($expected, $instance->getTimeStamp($date, $replaceInsertTags, $timeZone)); - } - - /** - * @dataProvider phpDateRFC3339Provider - */ - public function testTransformPhpDateFormatToRFC3339($format, $expected) - { - $instance = $this->getDateUtilMock(); - - $this->assertSame($expected, $instance->transformPhpDateFormatToRFC3339($format)); - } - - /** - * @dataProvider transformPhpDateFormatToRFC3339Provider - */ - public function testTransformPhpDateFormatToRFC3339WithDate($format, $locale, \DateTime $date, $expected) - { - $timezone = $date->getTimezone()->getName(); - $calendar = \IntlDateFormatter::GREGORIAN; - $pattern = null; - $dateFormat = \IntlDateFormatter::MEDIUM; // default from Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToLocalizedStringTransformer - $timeFormat = \IntlDateFormatter::SHORT; // default from Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToLocalizedStringTransformer - - $utils = $this->getDateUtilMock(); - $rfc3339Format = $utils->transformPhpDateFormatToRFC3339($format); - - $intlDateFormatter = new \IntlDateFormatter($locale, $dateFormat, $timeFormat, $timezone, $calendar, $pattern); - $intlDateFormatter->setPattern($rfc3339Format); - - $this->assertSame($expected, $intlDateFormatter->format($date)); - } - - /** - * The timestamp test data provider. - */ - public function timeStampProvider() - { - return [ - [null, 0, true, 'GMT', null], - [0, 0, true, 'GMT', null], - [1511022657, 1511022657, true, null, null], - [1511022657, 1511022657, true, 'GMT', null], - ['1511022657', 1511022657, true, 'GMT', null], - ['{{date::d.m.Y H:i}}', 'NOW', true, 'GMT', 'd.m.Y H:i'], - ['{{date::d.m.Y}}', 'NOW', true, 'GMT', 'd.m.Y'], - ['{{date::H:i}}', 'NOW', true, 'GMT', 'H:i'], - ['Mon, 12 Dec 2011 21:17:52 +0800', 1323695872, true, 'GMT', null], - ['24.04.2018 17:45', 1524591900, true, null, null], - ['24.04.2018 17:45', 1524617100, true, 'America/Los_Angeles', null], - ['ABCDEF"!§v231', 0, true, null, null], - [new \DateTime('Mon, 12 Dec 2011 21:17:52 +0800'), 1323695872, true, 'GMT', null], - ]; - } - - /** - * The php to rfc3339 test data provider. - */ - public function phpDateRFC3339Provider() - { - return [ - ['d', 'dd'], - ['D', 'E'], - ['j', 'd'], - ['l', 'EEEE'], - ['N', 'd'], - ['S', ''], - ['w', 'e'], - ['z', 'D'], - ['W', 'w'], - ['F', 'MMMM'], - ['m', 'MM'], - ['M', 'MMM'], - ['n', 'M'], - ['t', ''], - ['L', ''], - ['o', 'Y'], - ['Y', 'yyyy'], - ['y', 'yy'], - ['a', ''], - ['A', 'a'], - ['B', ''], - ['g', 'h'], - ['G', 'H'], - ['h', 'hh'], - ['H', 'HH'], - ['i', 'mm'], - ['s', 'ss'], - ['u', ''], - ['v', ''], - ['e', 'VV'], - ['I', ''], - ['O', 'xx'], - ['P', 'xxx'], - ['T', ''], - ['Z', ''], - ['c', "yyyy-MM-dd'T'HH:mm:ssxxx"], - ['r', ''], - ['U', ''], - ]; - } - - /** - * The php to rfc3339 test data provider. - */ - public function transformPhpDateFormatToRFC3339Provider() - { - $timeZone = 'Europe/Berlin'; - $date = new \DateTime(); - $date->setTimezone(new \DateTimeZone($timeZone)); - $date->setDate('2018', '04', '04'); - $date->setTime('16', '09', '02', '1234'); - - return [ - ['d', 'en-EN', $date, '04'], - ['D', 'en-EN', $date, 'Wed'], - ['j', 'en-EN', $date, '4'], - ['N', 'en-EN', $date, '4'], - ['S', 'en-EN', $date, ''], - ['w', 'en-EN', $date, '3'], - ['z', 'en-EN', $date, '94'], - ['W', 'en-EN', $date, '14'], - ['F', 'en-EN', $date, 'April'], - ['m', 'en-EN', $date, '04'], - ['M', 'en-EN', $date, 'Apr'], - ['M', 'en-EN', $date, 'Apr'], - ['n', 'en-EN', $date, '4'], - ['t', 'en-EN', $date, ''], - ['L', 'en-EN', $date, ''], - ['o', 'en-EN', $date, '2018'], - ['o', 'en-EN', $date, '2018'], - ['Y', 'en-EN', $date, '2018'], - ['y', 'en-EN', $date, '18'], - ['a', 'en-EN', $date, ''], - ['A', 'en-EN', $date, 'PM'], - ['B', 'en-EN', $date, ''], - ['g', 'en-EN', $date, '4'], - ['G', 'en-EN', $date, '16'], - ['h', 'en-EN', $date, '04'], - ['H', 'en-EN', $date, '16'], - ['i', 'en-EN', $date, '09'], - ['s', 'en-EN', $date, '02'], - ['u', 'en-EN', $date, ''], - ['v', 'en-EN', $date, ''], - ['e', 'en-EN', $date, $timeZone], - ['I', 'en-EN', $date, ''], - ['O', 'en-EN', $date, '+0200'], - ['P', 'en-EN', $date, '+02:00'], - ['T', 'en-EN', $date, ''], - ['Z', 'en-EN', $date, ''], - ['c', 'en-EN', $date, '2018-04-04T16:09:02+02:00'], - ['r', 'en-EN', $date, ''], - ['U', 'en-EN', $date, ''], - ['d.m.Y H:i', 'en-EN', $date, '04.04.2018 16:09'], - ['d.m.Y', 'en-EN', $date, '04.04.2018'], - ['H:i', 'en-EN', $date, '16:09'], - ['Y-m-d H.i', 'en-EN', $date, '2018-04-04 16.09'], - ['Y-m-d', 'en-EN', $date, '2018-04-04'], - ['H.i', 'en-EN', $date, '16.09'], - ]; - } - - public function transformPhpDateFormatToISO8601Provider() - { - return [ - ['F j, Y, g:i a', 'MM TT, JJJJ, hh:mm'], - ]; - } - - /** - * @dataProvider transformPhpDateFormatToISO8601Provider - */ - public function skip_testTransformPhpDateFormatToISO8601($format, $target) - { - $util = $this->getDateUtilMock(); - $this->assertSame($target, $util->transformPhpDateFormatToISO8601($format)); - } - - public function testGetTimePeriodInSeconds() - { - $date = $this->getDateUtilMock(); - - $timePeriod = serialize(['unit' => 'h', 'value' => 12]); - $result = $date->getTimePeriodInSeconds($timePeriod); - $this->assertSame(43200, $result); - - $timePeriod = serialize(['unit' => 'm', 'value' => 12]); - $result = $date->getTimePeriodInSeconds($timePeriod); - $this->assertSame(720, $result); - - $timePeriod = serialize(['unit' => 'd', 'value' => 12]); - $result = $date->getTimePeriodInSeconds($timePeriod); - $this->assertSame(1036800, $result); - - $timePeriod = serialize(['units' => 'h', 'value' => 12]); - $result = $date->getTimePeriodInSeconds($timePeriod); - $this->assertNull($result); - } - - public function testGetGMTMidnightTstamp() - { - $date = $this->getDateUtilMock(); - $time = time(); - - $dateTime = new \DateTime(date('Y-m-d', $time)); - $dateTime->setTimezone(new \DateTimeZone('Europe/Berlin')); - - $gmtTime = $date->getGMTMidnightTstamp($dateTime->getTimeStamp()); - - $gmtDateTime = new \DateTime(date('Y-m-d', $time)); - $gmtDateTime->setTimezone(new \DateTimeZone('GMT')); - $gmtDateTime->setTime(0, 0, 0); - - $this->assertSame($gmtDateTime->getTimestamp(), $gmtTime); - } -} diff --git a/tests/Dca/DcaUtilTest.php b/tests/Dca/DcaUtilTest.php deleted file mode 100644 index e2a2de12..00000000 --- a/tests/Dca/DcaUtilTest.php +++ /dev/null @@ -1,807 +0,0 @@ -getContainerMock(); - } - - if (!isset($properties['framework'])) { - $properties['framework'] = $this->getContaoFrameworkMock(); - } - - if (!isset($properties['routingutil'])) { - /** @var RoutingUtil|MockObject $routingUtilMock */ - $routingUtilMock = $this->createMock(RoutingUtil::class); - $routingUtilMock->method('generateBackendRoute')->willReturnCallback(function (array $params = [], $addToken = true, $addReferer = true) { - $url = '/contao'; - - if (true === $addToken) { - $params['rt'] = 'token'; - } - - if (true === $addReferer) { - $params['ref'] = 'referer'; - } - - if (!empty($params)) { - $url .= '?'.http_build_query($params); - } - - return $url; - }); - $properties['routingutil'] = $routingUtilMock; - } - - if (!isset($properties['connection'])) { - /** @var Connection $connectionMock */ - $connectionMock = $this->createMock(Connection::class); - $properties['connection'] = $connectionMock; - } - - $instance = new DcaUtil($properties['container'], $properties['framework'], $properties['routingutil'], $properties['connection']); - - return $instance; - } - - public function getContaoFrameworkMock(array $parameters = []): ContaoFrameworkInterface - { - if (!isset($parameters['adapters'])) { - $controllerAdapter = $this->mockAdapter(['loadDataContainer']); - $parameters['adapters'] = [ - Controller::class => $controllerAdapter, - ]; - } - - if (!isset($parameters['createInstanceCallback'])) { - $parameters['createInstanceCallback'] = function ($class) { - switch ($class) { - case Database::class: - return $this->getDatabaseMock(); - - break; - } - }; - } - - $framework = $this->mockContaoFramework($parameters['adapters']); - $framework->method('createInstance')->willReturnCallback($parameters['createInstanceCallback']); - - return $framework; - } - - public function getDatabaseMock() - { - $databaseAdapter = $this->mockAdapter([ - 'getInstance', - 'prepare', - 'execute', - 'fieldExists', - 'listTables', - ]); - $databaseAdapter->method('getInstance')->willReturnSelf(); - $databaseAdapter->method('prepare')->withAnyParameters()->willReturnSelf(); - $databaseAdapter->method('fieldExists')->willReturn(true); - $databaseAdapter->method('execute')->with($this->anything())->willReturnCallback(function ($alias) { - $result = new \stdClass(); - $result->numRows = 0; - $result->id = 5; - - switch ($alias) { - case 'existing-alias': - $result->numRows = 1; - $result->id = 1; - - break; - } - - return $result; - }); - $databaseAdapter->method('listTables')->willReturn($this->dataContainerTable); - - return $databaseAdapter; - } - - public function testInstantiation() - { - $util = $this->getTestInstance(); - $this->assertInstanceOf(DcaUtil::class, $util); - } - - public function testSetDefaultsFromDcaWithoutDataContainer() - { - $dcaUtil = $this->getTestInstance(); - $this->assertEmpty($dcaUtil->setDefaultsFromDca('tl_unknown_datacontainer')); - } - - public function testSetDefaultsFromDcaOnModel() - { - $GLOBALS['TL_DCA']['tl_test'] = []; - $GLOBALS['TL_DCA']['tl_test']['fields']['test'] = ['default' => 'test']; - - error_reporting(\E_ALL & ~\E_NOTICE); //Report all errors except E_NOTICE - - $dcaUtil = $this->getTestInstance(); - $data = $dcaUtil->setDefaultsFromDca('tl_test', new \stdClass()); - - $this->assertNotEmpty($data); - $this->assertSame('test', $data->test); - } - - public function testSetDefaultsFromDcaOnArray() - { - $GLOBALS['TL_DCA']['tl_test'] = []; - $GLOBALS['TL_DCA']['tl_test']['fields']['test'] = ['default' => 'test']; - - error_reporting(\E_ALL & ~\E_NOTICE); //Report all errors except E_NOTICE - - $dcaUtil = $this->getTestInstance(); - $data = $dcaUtil->setDefaultsFromDca('tl_test', []); - - $this->assertNotEmpty($data); - $this->assertSame('test', $data['test']); - } - - public function testSetDefaultsFromDcaOnNullData() - { - $GLOBALS['TL_DCA']['tl_test'] = []; - $GLOBALS['TL_DCA']['tl_test']['fields']['test'] = ['default' => 'test']; - - error_reporting(\E_ALL & ~\E_NOTICE); //Report all errors except E_NOTICE - - $dcaUtil = $this->getTestInstance(); - $data = $dcaUtil->setDefaultsFromDca('tl_test'); - - $this->assertNotEmpty($data); - $this->assertSame('test', $data['test']); - } - - public function testGetConfigByArrayOrCallbackOrFunction() - { - $dcaUtil = $this->getTestInstance(); - - $result = $dcaUtil->getConfigByArrayOrCallbackOrFunction(['array' => true], 'array'); - $this->assertTrue($result); - - $result = $dcaUtil->getConfigByArrayOrCallbackOrFunction(['array' => true], 'arrays'); - $this->assertNull($result); - - $result = $dcaUtil->getConfigByArrayOrCallbackOrFunction(['array_callback' => true], 'array'); - $this->assertNull($result); - - $result = $dcaUtil->getConfigByArrayOrCallbackOrFunction([ - 'test_callback' => function ($arguments) { - return $arguments; - }, - ], 'test', ['test']); - $this->assertSame('test', $result); - - $result = $dcaUtil->getConfigByArrayOrCallbackOrFunction(['deserialize_callback' => [StringUtil::class, 'deserialize']], 'deserialize', ['test']); - $this->assertSame('test', $result); - - $result = $dcaUtil->getConfigByArrayOrCallbackOrFunction(['array_callback' => ['test', 'test']], 'array'); - $this->assertNull($result); - - $result = $dcaUtil->getConfigByArrayOrCallbackOrFunction(['deserialize_callback' => [StringUtil::class, 'deseridalize']], 'deserialize', ['test']); - $this->assertNull($result); - - $result = $dcaUtil->getConfigByArrayOrCallbackOrFunction(['test_callback' => [new class() { - public function testCallback() - { - throw new \Error('Invalid method call'); - } - }, 'testCallback']], 'test', ['test']); - $this->assertNull($result); - - $result = $dcaUtil->getConfigByArrayOrCallbackOrFunction([ - 'test_callback' => function ($arguments) { - throw new \Error('Method does not exist!'); - }, - ], 'test', ['test']); - $this->assertNull($result); - } - - public function testSetDateAdded() - { - $databaseAdapter = $this->mockAdapter(['prepare', 'execute']); - $databaseAdapter->method('prepare')->willReturnSelf(); - $databaseAdapter->method('execute'); - $framework = $this->mockContaoFramework(); - $framework->method('createInstance')->willReturn($databaseAdapter); - - $container = $this->getContainerMock(); - - $model = $this->mockClassWithProperties(Model::class, ['dateAdded' => 0]); - $modelUtils = $this->mockAdapter(['findModelInstanceByPk']); - $modelUtils->method('findModelInstanceByPk')->willReturn($model); - $container->set('huh.utils.model', $modelUtils); - - $dcaUtil = $this->getTestInstance(['container' => $container, 'framework' => $framework]); - - $dcaUtil->setDateAdded($this->getDataContainerMock()); - - // fail run - $model = $this->mockClassWithProperties(Model::class, ['dateAdded' => 10]); - $modelUtils = $this->mockAdapter(['findModelInstanceByPk']); - $modelUtils->method('findModelInstanceByPk')->willReturn($model); - $container->set('huh.utils.model', $modelUtils); - - $databaseAdapter = $this->mockAdapter(['prepare', 'execute']); - $databaseAdapter->method('prepare')->willReturnSelf(); - $databaseAdapter->method('execute'); - - $framework = $this->mockContaoFramework(); - $framework->method('createInstance')->willReturn($databaseAdapter); - - $container->set('contao.framework', $framework); - - $dcaUtil = $this->getTestInstance(['container' => $container]); - $result = $dcaUtil->setDateAdded($this->getDataContainerMock()); - $this->assertNull($result); - } - - public function testSetDateAddedOnCopy() - { - $container = $this->getContainerMock(); - - $model = $this->mockClassWithProperties(Model::class, ['dateAdded' => 0]); - $modelUtils = $this->mockAdapter(['findModelInstanceByPk']); - $modelUtils->method('findModelInstanceByPk')->willReturn($model); - $container->set('huh.utils.model', $modelUtils); - - $databaseAdapter = $this->mockAdapter(['prepare', 'execute']); - $databaseAdapter->method('prepare')->willReturnSelf(); - $databaseAdapter->method('execute'); - - $framework = $this->mockContaoFramework(); - $framework->method('createInstance')->willReturn($databaseAdapter); - $container->set('contao.framework', $framework); - - $dcaUtil = $this->getTestInstance(['container' => $container]); - $dcaUtil->setDateAddedOnCopy(1, $this->getDataContainerMock()); - - // fail run - $model = $this->mockClassWithProperties(Model::class, ['dateAdded' => 10]); - $modelUtils = $this->mockAdapter(['findModelInstanceByPk']); - $modelUtils->method('findModelInstanceByPk')->willReturn($model); - - $container->set('huh.utils.model', $modelUtils); - - $databaseAdapter = $this->mockAdapter(['prepare', 'execute']); - $databaseAdapter->method('prepare')->willReturnSelf(); - $databaseAdapter->method('execute'); - - $framework = $this->mockContaoFramework(); - $framework->method('createInstance')->willReturn($databaseAdapter); - $container->set('contao.framework', $framework); - - $dcaUtil = $this->getTestInstance(['container' => $container]); - $result = $dcaUtil->setDateAddedOnCopy(1, $this->getDataContainerMock()); - $this->assertNull($result); - } - - public function testAddOverridableFields() - { - $GLOBALS['TL_LANG']['destinationTable']['overrideTitle'] = ['overrideTitle']; - $GLOBALS['TL_LANG']['destinationTable']['overrideAddSubmission'] = ['overrideAddSubmission']; - - $GLOBALS['TL_DCA']['sourceTable'] = [ - 'fields' => [ - 'title' => [ - 'label' => ['this is a title'], - 'exclude' => true, - 'search' => true, - 'inputType' => 'text', - 'eval' => ['maxlength' => 255, 'tl_class' => 'w50', 'mandatory' => true], - 'sql' => "varchar(255) NOT NULL default ''", - ], - 'addSubmission' => [ - 'label' => ['this is a title'], - 'exclude' => true, - 'filter' => true, - 'inputType' => 'checkbox', - 'eval' => ['doNotCopy' => true, 'submitOnChange' => true], - 'sql' => "char(1) NOT NULL default ''", - ], - ], - ]; - - $GLOBALS['TL_DCA']['destinationTable'] = ['palettes' => ['__selector__' => [], 'default' => '{general_legend},title, text;{submission_legend},addSubmission;{publish_legend},published'], 'subpalettes' => ['overrideTitle_test' => 'title', 'addSubmission']]; - - $dcaUtil = $this->getTestInstance(); - $dcaUtil->addOverridableFields(['title', 'addSubmission'], 'sourceTable', 'destinationTable', [ - 'skipLocalization' => true, - ]); - - $this->assertSame($GLOBALS['TL_DCA']['sourceTable']['fields']['title'], $GLOBALS['TL_DCA']['destinationTable']['fields']['title']); - $this->assertSame($GLOBALS['TL_DCA']['sourceTable']['fields']['addSubmission'], $GLOBALS['TL_DCA']['destinationTable']['fields']['addSubmission']); - $this->assertTrue(isset($GLOBALS['TL_DCA']['destinationTable']['fields']['overrideTitle'])); - $this->assertSame([ - 'label' => ['overrideTitle'], - 'exclude' => true, - 'inputType' => 'checkbox', - 'eval' => ['tl_class' => 'w50', 'submitOnChange' => true, 'isOverrideSelector' => true], - 'sql' => "char(1) NOT NULL default ''", - ], $GLOBALS['TL_DCA']['destinationTable']['fields']['overrideTitle']); - $this->assertTrue(isset($GLOBALS['TL_DCA']['destinationTable']['fields']['overrideAddSubmission'])); - $this->assertSame([ - 'label' => ['overrideAddSubmission'], - 'exclude' => true, - 'inputType' => 'checkbox', - 'eval' => ['tl_class' => 'w50', 'submitOnChange' => true, 'isOverrideSelector' => true], - 'sql' => "char(1) NOT NULL default ''", - ], $GLOBALS['TL_DCA']['destinationTable']['fields']['overrideAddSubmission']); - - $dcaUtil->addOverridableFields(['title', 'addSubmission'], 'sourceTable', 'destinationTable', ['checkboxDcaEvalOverride' => ['tl_class' => 'test'], 'skipLocalization' => false]); - $this->assertSame('test', $GLOBALS['TL_DCA']['destinationTable']['fields']['overrideAddSubmission']['eval']['tl_class']); - $this->assertSame('test', $GLOBALS['TL_DCA']['destinationTable']['fields']['overrideTitle']['eval']['tl_class']); - $this->assertSame(['huh.utils.misc.override.label', 'huh.utils.misc.override.desc'], $GLOBALS['TL_LANG']['destinationTable']['overrideAddSubmission']); - $this->assertSame(['huh.utils.misc.override.label', 'huh.utils.misc.override.desc'], $GLOBALS['TL_LANG']['destinationTable']['overrideTitle']); - } - - public function testGetOverridableProperty() - { - $utilsModelMocked = $this->mockClassWithProperties(Model::class, ['overrideTitle2' => 'title2', 'title2' => 'title2']); - - $dcaUtil = $this->getTestInstance(); - $result = $dcaUtil->getOverridableProperty('title', [$utilsModelMocked, 'instance' => ['table', 'pk']]); - - $this->assertSame('title', $result); - } - - public function testFlattenPaletteForSubEntities() - { - $dcaUtil = $this->getTestInstance(); - $dcaUtil->flattenPaletteForSubEntities('destinationTable', ['overrideTitle', 'overrideAddSubmission']); - $this->assertSame(['addSubmission'], $GLOBALS['TL_DCA']['destinationTable']['subpalettes']); - } - - public function testAliasExist() - { - $connection = $this->createMock(Connection::class); - $statement = $this->createMock(Statement::class); - - $statement->method('execute')->willReturnCallback(function (array $parameter = []) use ($statement) { - $statement->alias = $parameter[0]; - - if (\count($parameter) > 1) { - $statement->id = $parameter[1]; - } - }); - $statement->method('rowCount')->willReturnCallback(function () use ($statement) { - $data = [ - 'hello-world' => 0, - 'existing-alias' => 5, - 'another-table' => 7, - ]; - - if ('another-table' === $statement->alias) { - if (!isset($statement->anotherTableCount) || 0 === $statement->anotherTableCount) { - $statement->anotherTableCount = 1; - - return 0; - } - - return 1; - } - - if (\array_key_exists($statement->alias, $data)) { - if (isset($statement->id) && $data[$statement->alias] === $statement->id) { - return 0; - } - - return 1; - - return 1; - } - - return 0; - }); - $connection->method('prepare')->willReturn($statement); - - $instance = $this->getTestInstance(['connection' => $connection]); - $this->assertFalse($instance->aliasExist('hello-world', 0, 'tl_news')); - $this->assertTrue($instance->aliasExist('hello-world', 1, 'tl_news')); - $this->assertFalse($instance->aliasExist('goodbye-world', 1, 'tl_news')); - - return $instance; - } - - /** - * @param DcaUtil $util - * @depends testAliasExist - */ - public function testGenerateAlias($util) - { - $GLOBALS['TL_LANG']['ERR']['aliasExists'] = 'Alias %s already exist!'; - - $this->assertSame('alias', $util->generateAlias('alias', 15, 'tl_table', 'Alias')); - $this->assertSame('alias', $util->generateAlias('', 15, 'tl_table', 'Alias')); - $this->assertSame('hans-dieter', $util->generateAlias('', 15, 'tl_table', 'Hans Dieter')); - $this->assertSame('hans-däter', $util->generateAlias('', 15, 'tl_table', 'Hans Däter')); - $this->assertSame('hans-daeter', $util->generateAlias('', 15, 'tl_table', 'Hans Däter', false)); - $this->assertSame('existing-alias', $util->generateAlias('', 5, 'tl_table', 'Existing Alias')); - $this->assertSame('existing-alias-6', $util->generateAlias('', 6, 'tl_table', 'Existing Alias')); - $this->assertSame('existing-alias', $util->generateAlias('existing-alias', 5, 'tl_table', 'Existing Alias')); - $this->assertSame('ich-du-cookies-für-alle', $util->generateAlias('', 6, 'tl_table', 'Ich & du || Cookie\'s für $alle')); - $this->assertSame('ich-du-cookies-fuer-alle', $util->generateAlias('', 6, 'tl_table', 'Ich & du || Cookie\'s für $alle', false)); - - $this->assertSame('alias', $util->generateAlias('alias', 15, 'tl_table,tl_news', 'Alias')); - $this->assertSame('existing-alias-7', $util->generateAlias('', 7, 'tl_table,tl_news', 'Existing Alias')); - - $exception = false; - - try { - $util->generateAlias('existing-alias', 6, 'tl_table', 'Existing Alias'); - } catch (\Exception $e) { - $exception = true; - } - $this->assertTrue($exception); - - $exception = false; - - try { - $this->assertSame('existing-alias-7', $util->generateAlias('existing-alias', 7, 'tl_table,tl_news', 'Existing Alias')); - } catch (\Exception $e) { - $exception = true; - } - $this->assertTrue($exception); - - $exception = false; - - try { - $util->generateAlias('another-table', 7, 'tl_table,tl_news', 'Another Table'); - } catch (\Exception $e) { - $exception = true; - } - $this->assertTrue($exception); - - $exception = false; - - try { - $util->generateAlias('existing-alias', 6, null, 'Existing Alias'); - } catch (\Exception $e) { - $exception = true; - } - $this->assertFalse($exception); - } - - public function testAddAuthorFieldAndCallback() - { - $array['TL_DCA']['testTable']['config']['oncreate_callback']['setAuthorIDOnCreate'] = ['huh.utils.dca', 'setAuthorIDOnCreate']; - $array['TL_DCA']['testTable']['config']['onload_callback']['modifyAuthorPaletteOnLoad'] = ['huh.utils.dca', 'modifyAuthorPaletteOnLoad', true]; - - $array['TL_DCA']['testTable']['fields']['authorType'] = [ - 'label' => &$GLOBALS['TL_LANG']['MSC']['utilsBundle']['authorType'], - 'exclude' => true, - 'filter' => true, - 'default' => 'none', - 'inputType' => 'select', - 'options' => [ - 'none', - 'member', - 'user', - ], - 'reference' => $GLOBALS['TL_LANG']['MSC']['utilsBundle']['authorType'], - 'eval' => ['doNotCopy' => true, 'submitOnChange' => true, 'mandatory' => true, 'tl_class' => 'w50 clr'], - 'sql' => "varchar(255) NOT NULL default 'none'", - ]; - - $array['TL_DCA']['testTable']['fields']['author'] = [ - 'label' => &$GLOBALS['TL_LANG']['MSC']['utilsBundle']['author'], - 'exclude' => true, - 'search' => true, - 'filter' => true, - 'inputType' => 'select', - 'options_callback' => function () { - return \Contao\System::getContainer()->get('huh.utils.choice.model_instance')->getCachedChoices([ - 'dataContainer' => 'tl_member', - 'labelPattern' => '%firstname% %lastname% (ID %id%)', - ]); - }, - 'eval' => [ - 'doNotCopy' => true, - 'chosen' => true, - 'includeBlankOption' => true, - 'tl_class' => 'w50', - ], - 'sql' => "int(10) unsigned NOT NULL default '0'", - ]; - - $dcaUtil = $this->getTestInstance(); - $dcaUtil->addAuthorFieldAndCallback('testTable'); - - $this->assertSame($array['TL_DCA']['testTable']['fields']['author']['label'], $GLOBALS['TL_DCA']['testTable']['fields']['author']['label']); - $this->assertSame($array['TL_DCA']['testTable']['fields']['author']['eval'], $GLOBALS['TL_DCA']['testTable']['fields']['author']['eval']); - $this->assertSame($array['TL_DCA']['testTable']['fields']['author']['sql'], $GLOBALS['TL_DCA']['testTable']['fields']['author']['sql']); - $this->assertSame($array['TL_DCA']['testTable']['fields']['author']['inputType'], $GLOBALS['TL_DCA']['testTable']['fields']['author']['inputType']); - $this->assertSame($array['TL_DCA']['testTable']['fields']['authorType'], $GLOBALS['TL_DCA']['testTable']['fields']['authorType']); - $this->assertSame($array['TL_DCA']['testTable']['config']['onload_callback']['modifyAuthorPaletteOnLoad'], $GLOBALS['TL_DCA']['testTable']['config']['onload_callback']['modifyAuthorPaletteOnLoad']); - $this->assertSame($array['TL_DCA']['testTable']['config']['oncreate_callback']['setAuthorIDOnCreate'], $GLOBALS['TL_DCA']['testTable']['config']['oncreate_callback']['setAuthorIDOnCreate']); - } - - public function testSetAuthorIDOnCreate() - { - $frontendUserModel = $this->mockClassWithProperties(FrontendUser::class, ['id' => 2]); - $frontendUser = $this->mockAdapter(['getInstance']); - $frontendUser->method('getInstance')->willReturn($frontendUserModel); - - $framework = $this->mockContaoFramework([FrontendUser::class => $frontendUser]); - $framework->method('createInstance')->willReturn($this->getDatabaseMock()); - - $dcaUtil = $this->getTestInstance(['framework' => $framework]); - $dcaUtil->setAuthorIDOnCreate('table', 2, ['row'], $this->getDataContainerMock()); - - $container = $this->getContainerMock(); - $containerUtils = $this->mockAdapter(['isFrontend']); - $containerUtils->method('isFrontend')->willReturn(false); - $container->set('huh.utils.container', $containerUtils); - - $backendUserModel = $this->mockClassWithProperties(FrontendUser::class, ['id' => 2]); - $backendUser = $this->mockAdapter(['getInstance']); - $backendUser->method('getInstance')->willReturn($backendUserModel); - - $framework = $this->mockContaoFramework([BackendUser::class => $backendUser]); - $framework->method('createInstance')->willReturn($this->getDatabaseMock()); - - $dcaUtil = $this->getTestInstance(['container' => $container, 'framework' => $framework]); - $dcaUtil->setAuthorIDOnCreate('table', 2, ['row'], $this->getDataContainerMock()); - - $utilsModel = $this->createMock(ModelUtil::class); - $utilsModel->method('findModelInstanceByPk')->willReturn(null); - $container->set('huh.utils.model', $utilsModel); - - $dcaUtil = $this->getTestInstance(['container' => $container, $framework]); - $result = $dcaUtil->setAuthorIDOnCreate('table', 2, ['row'], $this->getDataContainerMock()); - $this->assertFalse($result); - } - - public function testModifyAuthorPaletteOnLoad() - { - $dcaUtil = $this->getTestInstance(); - $dcaUtil->modifyAuthorPaletteOnLoad($this->getDataContainerMock()); - - $this->assertArrayNotHasKey('author', $GLOBALS['TL_DCA']['testTable']['fields']); - - $container = $this->getContainerMock(); - $mockedModel = $this->mockClassWithProperties(Model::class, ['overrideTitle' => 'title', 'title' => 'title', 'author' => null, 'authorType' => 'user']); - $utilsModel = $this->createMock(ModelUtil::class); - $utilsModel->method('findModelInstanceByPk')->willReturn($mockedModel); - $container->set('huh.utils.model', $utilsModel); - - $dcaUtil = $this->getTestInstance(['container' => $container]); - - $dcaUtil->modifyAuthorPaletteOnLoad($this->getDataContainerMock()); - $this->arrayHasKey('options_callback', $GLOBALS['TL_DCA']['testTable']['fields']['author']); - - $utilsModel = $this->createMock(ModelUtil::class); - $utilsModel->method('findModelInstanceByPk')->willReturn(null); - $container->set('huh.utils.model', $utilsModel); - - $dcaUtil = $this->getTestInstance(['container' => $container]); - $result = $dcaUtil->modifyAuthorPaletteOnLoad($this->getDataContainerMock()); - $this->assertFalse($result); - - $dcaUtil = $this->getTestInstance(['container' => $container]); - $result = $dcaUtil->modifyAuthorPaletteOnLoad($this->getDataContainerMock(false)); - $this->assertFalse($result); - - $containerUtils = $this->mockAdapter(['isFrontend', 'isBackend']); - $containerUtils->method('isFrontend')->willReturn(true); - $containerUtils->method('isBackend')->willReturn(false); - $container->set('huh.utils.container', $containerUtils); - - $dcaUtil = $this->getTestInstance(['container' => $container]); - $result = $dcaUtil->modifyAuthorPaletteOnLoad($this->getDataContainerMock()); - $this->assertFalse($result); - } - - public function testGetDataContainers() - { - $GLOBALS['BE_MOD'] = [ - [ - [ - 'tables' => [ - 'tl_news', - ], - ], - [ - 'tables' => [ - 'tl_members', - ], - ], - ], - ]; - - $databaseContainers = ['tl_content', 'tl_module', 'tl_news']; - $this->dataContainerTable = $databaseContainers; - $dcaUtil = $this->getTestInstance(); - $result = $dcaUtil->getDataContainers(); - $this->assertCount(4, $result); - $this->assertSame(['tl_content', 'tl_members', 'tl_module', 'tl_news'], $result); - - $result = $dcaUtil->getDataContainers(['onlyTableType' => true]); - $this->assertCount(3, $result); - $this->assertSame($databaseContainers, $result); - } - - /** - * @group legacy - * @expectedDeprecation Using string as parameter is deprecated and will be removed in a future version. - */ - public function testGetPopupWizardLink() - { - $imageAdapter = $this->mockAdapter(['getHtml']); - $imageAdapter->method('getHtml')->willReturnCallback(function ($src, $alt = '', $attributes = '') { - return ''.$alt.''; - }); - $framework = $this->mockContaoFramework([ - Image::class => $imageAdapter, - ]); - - $dcautil = $this->getTestInstance(['framework' => $framework]); - $this->assertSame('/contao?popup=1&nb=1&rt=token&ref=referer', $dcautil->getPopupWizardLink([], ['url-only' => true])); - $this->assertSame('/contao?popup=1&nb=1&rt=token&ref=referer', $dcautil->getPopupWizardLink('', ['url-only' => true])); - $this->assertSame('/contao?do=md_recipient_lists&act=edit&id=1&popup=1&nb=1&rt=token&ref=referer', $dcautil->getPopupWizardLink([ - 'do' => 'md_recipient_lists', - 'act' => 'edit', - 'id' => 1, - ], ['url-only' => true])); - $this->assertSame('/contao?do=md_recipient_lists&act=edit&id=1&popup=1&nb=1&rt=token&ref=referer', $dcautil->getPopupWizardLink('do=md_recipient_lists&act=edit&id=1', ['url-only' => true])); - $this->assertSame('/contao?do=md_recipient_lists&act=edit&id=1&popup=1&nb=1&rt=token&ref=referer', $dcautil->getPopupWizardLink('https://example.org/contao?do=md_recipient_lists&act=edit&id=1', ['url-only' => true])); - - $GLOBALS['TL_LANG']['tl_content']['edit'][0] = 'Edit'; - - $result = $dcautil->getPopupWizardLink([ - 'do' => 'md_recipient_lists', - 'act' => 'edit', - 'id' => 1, - ], []); - $this->assertStringStartsWith('assertNotFalse(strpos($result, 'assertNotFalse(strpos($result, 'title="Edit"')); - - $result = $dcautil->getPopupWizardLink([ - 'do' => 'newsletter', - 'act' => 'edit', - 'id' => 1, - ], [ - 'title' => 'Hello', - 'icon' => 'hello.png', - ]); - $this->assertStringStartsWith('assertNotFalse(strpos($result, 'assertNotFalse(strpos($result, 'title="Hello"')); - $this->assertNotFalse(strpos($result, 'onclick="Backend.openModalIframe')); - - $result = $dcautil->getPopupWizardLink([ - 'do' => 'newsletter', - 'act' => 'edit', - 'id' => 1, - ], [ - 'title' => 'Hello', - 'icon' => 'hello.png', - 'onclick' => 'onclick="alert(\'Wow!\');"', - 'linkText' => 'Foo!', - 'attributes' => [ - 'title' => 'World', - 'class' => 'tl_button', - 'href' => 'abc.html', - 'onclick' => 'event();', - ], - ]); - $this->assertStringStartsWith('assertNotFalse(strpos($result, 'assertNotFalse(strpos($result, 'title="World"')); - $this->assertNotFalse(strpos($result, 'class="tl_button"')); - $this->assertNotFalse(strpos($result, 'onclick="alert(\'Wow!\');"')); - $this->assertNotFalse(strpos($result, 'Foo!')); - -// $this->assertNotFalse(strpos()); -// -// -// $this->assertString('', $dcautil->getPopupWizardLink([ -// 'do' => 'md_recipient_lists', -// 'act' => 'edit', -// 'id' => 1, -// ], [])); - } - - /** - * @return DataContainer|\PHPUnit_Framework_MockObject_MockObject - */ - public function getDataContainerMock($properties = true) - { - if ($properties) { - return $this->mockClassWithProperties(DataContainer::class, ['id' => 1, 'table' => 'testTable']); - } - - return $this->createMock(DataContainer::class); - } - - /** - * @param ContaoFramework $framework - * - * @return ContainerBuilder|ContainerInterface - */ - protected function getContainerMock(ContainerBuilder $container = null, $framework = null) - { - if (!$container) { - $container = $this->getContainerWithContaoConfiguration(); - } - - if (!$framework) { - $framework = $this->mockContaoFramework(); - } - $container->set('contao.framework', $framework); - - $translator = new Translator('de'); - $container->set('translator', $translator); - - $mockedModel = $this->mockClassWithProperties(Model::class, ['overrideTitle' => 'title', 'title' => 'title', 'author' => null, 'authorType' => 'none']); - $mockedModel->method('save'); - $utilsModel = $this->createMock(ModelUtil::class); - $utilsModel->method('findModelInstanceByPk')->willReturn($mockedModel); - $container->set('huh.utils.model', $utilsModel); - - $choiceModel = $this->mockAdapter(['getCachedChoices']); - $choiceModel->method('getCachedChoices')->willReturn(['dataContainer' => 'data', 'labelPattern' => 'label']); - $container->set('huh.utils.choice.model_instance', $choiceModel); - - $containerUtils = $this->mockAdapter(['isFrontend', 'isBackend']); - $containerUtils->method('isFrontend')->willReturn(true); - $containerUtils->method('isBackend')->willReturn(true); - $container->set('huh.utils.container', $containerUtils); - - $arrayUtil = new ArrayUtil($container); - $container->set('huh.utils.array', $arrayUtil); - - return $container; - } -} diff --git a/tests/Driver/DC_Table_UtilsTest.php b/tests/Driver/DC_Table_UtilsTest.php deleted file mode 100644 index 2a3d4343..00000000 --- a/tests/Driver/DC_Table_UtilsTest.php +++ /dev/null @@ -1,209 +0,0 @@ -getContainerWithContaoConfiguration(); - - $requestStack = new RequestStack(); - $requestStack->push(new \Symfony\Component\HttpFoundation\Request()); - - $backendMatcher = new RequestMatcher('/contao', 'test.com', null, ['192.168.1.0']); - $frontendMatcher = new RequestMatcher('/index', 'test.com', null, ['192.168.1.0']); - - $scopeMatcher = new ScopeMatcher($backendMatcher, $frontendMatcher); - - $request = new Request($this->mockContaoFramework(), $requestStack, $scopeMatcher); - - $container->set('huh.request', $request); - - $container->set('database_connection', $this->createMock(Connection::class)); - $container->set('request_stack', $this->createRequestStackMock()); - $container->set('router', $this->createRouterMock()); - $container->set('contao.framework', $this->mockContaoFramework()); - $container->set('session', new Session(new MockArraySessionStorage())); - - $dbalAdapter = $this->mockAdapter(['getParams']); - $dbalAdapter->method('getParams')->willReturn([]); - $container->set('doctrine.dbal.default_connection', $dbalAdapter); - - $modelUtilsAdapter = $this->mockAdapter(['findModelInstanceByPk']); - $modelUtilsAdapter->method('findModelInstanceByPk')->willReturn($this->createMock(Model::class)); - $container->set('huh.utils.model', $modelUtilsAdapter); - - System::setContainer($container); - - if (!interface_exists('listable')) { - include_once __DIR__.'/../../vendor/contao/core-bundle/src/Resources/contao/helper/interface.php'; - } - - if (!\function_exists('standardize')) { - include_once __DIR__.'/../../vendor/contao/core-bundle/src/Resources/contao/helper/functions.php'; - } - } - - public function createRequestStackMock() - { - $requestStack = new RequestStack(); - $request = new \Symfony\Component\HttpFoundation\Request(); - $request->attributes->set('_contao_referer_id', 'foobar'); - $requestStack->push($request); - - return $requestStack; - } - - public function createRouterMock() - { - $router = $this->createMock(RouterInterface::class); - $router->method('generate')->with('contao_backend', $this->anything())->willReturnCallback(function ($route, $params = []) { - $url = '/contao'; - - if (!empty($params)) { - $count = 0; - - foreach ($params as $key => $value) { - $url .= (0 === $count ? '?' : '&'); - $url .= $key.'='.$value; - ++$count; - } - } - - return $url; - }); - - return $router; - } - - public function testInstantiation() - { - $this->createGlobalDca('table'); - $dcTableUtils = new DC_Table_Utils('table'); - $this->assertInstanceOf(DC_Table_Utils::class, $dcTableUtils); - } - - public function testCreateFromModel() - { - $result = DC_Table_Utils::createFromModel($this->getModel()); - $this->assertInstanceOf(DC_Table_Utils::class, $result); - } - - public function testCreateFromModelData() - { - $result = DC_Table_Utils::createFromModelData(['id' => 12], 'table', 'field'); - $this->assertInstanceOf(DC_Table_Utils::class, $result); - } - - /** - * @return Model|\PHPUnit_Framework_MockObject_MockObject - */ - public function getModel() - { - $this->createGlobalDca('tl_cfg_tag'); - $model = new CfgTagModel($this->mockContaoFramework()); - - return $model; - } - - public function createGlobalDca($table) - { - $GLOBALS['TL_DCA'][$table] = [ - 'config' => [ - 'dataContainer' => 'Table', - 'ptable' => 'ptable', - 'ctable' => ['tl_content', 'ctable'], - 'enableVersioning' => true, - 'onsubmit_callback' => [], - 'oncopy_callback' => [], - 'onload_callback' => [], - 'sql' => [ - 'keys' => [ - 'id' => 'primary', - ], - ], - ], - 'list' => [ - 'label' => [ - 'fields' => ['title'], - 'format' => '%s', - ], - 'sorting' => [ - 'mode' => 1, - 'fields' => ['title'], - 'headerFields' => ['title'], - 'panelLayout' => 'filter;sort,search,limit', - 'root' => [], - ], - 'global_operations' => [ - 'all' => [ - 'label' => &$GLOBALS['TL_LANG']['MSC']['all'], - 'href' => 'act=select', - 'class' => 'header_edit_all', - 'attributes' => 'onclick="Backend.getScrollOffset();"', - ], - ], - 'operations' => [ - 'edit' => [ - 'label' => &$GLOBALS['TL_LANG']['table']['edit'], - 'href' => 'table=tl_content&ptable=table', - 'icon' => 'edit.gif', - ], - ], - ], - 'palettes' => [ - 'default' => '{general_legend},title;', - ], - - 'subpalettes' => [], - 'fields' => [ - 'id' => [ - 'sql' => 'int(10) unsigned NOT NULL auto_increment', - ], - 'pid' => [ - 'foreignKey' => 'ptable.id', - 'sql' => "int(10) unsigned NOT NULL default '0'", - 'relation' => ['type' => 'belongsTo', 'load' => 'eager'], - ], - 'title' => [ - 'label' => &$GLOBALS['TL_LANG']['table']['title'], - 'exclude' => true, - 'search' => true, - 'inputType' => 'text', - 'eval' => ['maxlength' => 255, 'tl_class' => 'w50', 'mandatory' => true], - 'sql' => "varchar(255) NOT NULL default ''", - ], - ], - ]; - } -} diff --git a/tests/EntityFinder/EntityFinderHelperTest.php b/tests/EntityFinder/EntityFinderHelperTest.php new file mode 100644 index 00000000..583798bd --- /dev/null +++ b/tests/EntityFinder/EntityFinderHelperTest.php @@ -0,0 +1,33 @@ +mockAdapter(['findBy']); + $moduleModel->expects($this->once())->method('findBy')->willReturn(null); + $framework = $this->mockContaoFramework([ + ModuleModel::class => $moduleModel, + ]); + + $databaseUtilMock = $this->createMock(DatabaseUtil::class); + $databaseUtilMock->method('createWhereForSerializedBlob')->willReturn( + new CreateWhereForSerializedBlobResult('field', []) + ); + $utils = $this->createMock(Utils::class); + $utils->method('database')->willReturn($databaseUtilMock); + + $instance = new EntityFinderHelper($utils, $framework); + + $this->assertNull($instance->findModulesByTypeAndSerializedValue('newslist', 'news_archives', [3])); + } +} diff --git a/tests/EventListener/DcaAuthorListenerTest.php b/tests/EventListener/DcaAuthorListenerTest.php new file mode 100644 index 00000000..52daeee8 --- /dev/null +++ b/tests/EventListener/DcaAuthorListenerTest.php @@ -0,0 +1,87 @@ +mockContaoFramework(); + $security = $parameters['security'] ?? $this->createMock(Security::class); + return new DcaAuthorListener($framework, $security); + } + + public function testOnLoadDataContainer() + { + + $testDca = [ + 'fields' => [] + ]; + $GLOBALS['TL_DCA']['tl_test'] = $testDca; + $instance = $this->getTestInstance(); + $instance->onLoadDataContainer('tl_test'); + $this->assertSame($testDca, $GLOBALS['TL_DCA']['tl_test']); + + AuthorField::register('tl_test'); + $instance->onLoadDataContainer('tl_test'); + $this->assertArrayHasKey('author', $GLOBALS['TL_DCA']['tl_test']['fields']); + + $testDca = ['fields' => []]; + $GLOBALS['TL_DCA']['tl_test'] = $testDca; + + AuthorField::register('tl_test')->setFieldNamePrefix('test_'); + $instance->onLoadDataContainer('tl_test'); + $this->assertArrayHasKey('test_author', $GLOBALS['TL_DCA']['tl_test']['fields']); + + AuthorField::register('tl_test')->setFieldNamePrefix('test'); + $instance->onLoadDataContainer('tl_test'); + $this->assertArrayHasKey('testAuthor', $GLOBALS['TL_DCA']['tl_test']['fields']); + } + + public function testOnConfigCopyCallback() + { + $testModel = $this->mockModelObject(Model::class); + $testModel->id = 4; + + $modelClassAdapter = $this->mockAdapter(['getClassFromTable']); + $modelClassAdapter->method('getClassFromTable')->willReturn('HuhUtilsTestModel'); + + $modelAdapter = $this->mockAdapter(['findByPk']); + $modelAdapter->method('findByPk')->willReturn($testModel); + + + $framework = $this->mockContaoFramework([ + Model::class => $modelClassAdapter, + 'HuhUtilsTestModel' => $modelAdapter + ]); + + $modelClassAdapter->method('getClassFromTable')->willReturn('tl_test'); + + $frontendUser = $this->createMock(FrontendUser::class); + $this->mockClassWithProperties(FrontendUser::class, ['id' => 1]); + + $security = $this->createMock(Security::class); + $security->method('getUser')->willReturn($frontendUser); + + $instance = $this->getTestInstance([ + 'framework' => $framework + ]); + + AuthorField::register('tl_test'); + + $dc = $this->mockClassWithProperties(DataContainer::class, ['table' => 'tl_test']); + + $instance->onConfigCopyCallback(1, $dc); + $this->assertSame(0, $testModel->author); + } +} \ No newline at end of file diff --git a/tests/EventListener/InsertTagsListenerTest.php b/tests/EventListener/InsertTagsListenerTest.php deleted file mode 100644 index bd070d97..00000000 --- a/tests/EventListener/InsertTagsListenerTest.php +++ /dev/null @@ -1,65 +0,0 @@ -createMock(EventDispatcherInterface::class); - $twig = $this->createMock(Environment::class); - $templateUtil = $this->createMock(TemplateUtil::class); - $controllerMock = $this->mockAdapter(['replaceInsertTags']); - - $contao = $this->mockContaoFramework([Controller::class => $controllerMock]); - - $controllerMock->method('replaceInsertTags')->willReturnArgument(0); - - $eventDispatcher->method('dispatch')->willReturnCallback(function ($name, $event) { - if (\is_object($name)) { - return $name; - } - - return $event; - }); - - $templateUtil->method('getTemplate')->willReturnArgument(0); - $twig->method('render')->willReturnCallback( - function ($templateName, $templateData) { - switch ($templateName) { - case 'accessibility_bar_bs4_fa': - return 'accessibility'; - } - - switch ($templateData) { - case ['name' => 'accessibility']: - return 'accessibility'; - } - - throw new LoaderError('Template not found'); - } - ); - - $testInstance = new InsertTagsListener($eventDispatcher, $twig, $templateUtil, $contao); - - $this->assertFalse($testInstance->onReplaceInsertTags('unsuported::accessibility_bar_bs4_fa')); - $this->assertSame('accessibility', $testInstance->onReplaceInsertTags('twig::accessibility_bar_bs4_fa')); - $this->assertSame('', $testInstance->onReplaceInsertTags('twig')); - $this->assertFalse($testInstance->onReplaceInsertTags('')); - $this->assertSame('accessibility', $testInstance->onReplaceInsertTags('twig::accessibility_bar_bs4_fa::name:accessibility')); - } -} diff --git a/tests/File/FileArchiveUtilTest.php b/tests/File/FileArchiveUtilTest.php deleted file mode 100644 index cca2cda4..00000000 --- a/tests/File/FileArchiveUtilTest.php +++ /dev/null @@ -1,62 +0,0 @@ -getTempDir(); - - if (!\defined('TL_ROOT')) { - \define('TL_ROOT', $this->getTempDir()); - } - - $container = $this->getContainerWithContaoConfiguration(); - $container->setParameter('kernel.project_dir', $tmpFolder); - $this->resetFilesInstance($container); - - $folderUtilMock = $this->createMock(FolderUtil::class); - $folderUtilMock->method('createPublicFolder'); - - $fileArchivUtil = new FileArchiveUtil($tmpFolder, ['tmp_folder' => 'utils-bundle'], $folderUtilMock); - - $filesystem = new Filesystem(); - $filesystem->dumpFile($tmpFolder.'/hello.txt', 'hello world'); - $filesystem->dumpFile($tmpFolder.'/hello.jpg', 'hello world'); - $filesystem->mkdir($tmpFolder.'/'.ZipWriter::TEMPORARY_FOLDER); - - $files = []; - $files[] = $this->mockModelObject(FilesModel::class, ['path' => 'hello.txt', 'name' => 'hello.txt']); - $files[] = $this->mockModelObject(FilesModel::class, ['path' => 'hello.jpg', 'name' => 'hello.jpg']); - - $archivePath = $fileArchivUtil->createFileArchive($files, 'test_archive'); - - $this->assertStringStartsWith('utils-bundle/file_archive_util/test_archive', $archivePath); - $this->assertStringEndsWith('.zip', $archivePath); - - $zipReader = new ZipReader($archivePath); - $this->assertCount(2, $zipReader->getFileList()); - $this->assertTrue($zipReader->getFile('hello.txt')); - $this->assertTrue($zipReader->getFile('hello.jpg')); - } -} diff --git a/tests/File/FileStorageCallbackTest.php b/tests/File/FileStorageCallbackTest.php deleted file mode 100644 index 563ca565..00000000 --- a/tests/File/FileStorageCallbackTest.php +++ /dev/null @@ -1,27 +0,0 @@ -assertSame('Hello', $instance->getIdentifier()); - $this->assertSame('hello.jpg', $instance->getFilename()); - $this->assertSame('files/storage/hello.jpg', $instance->getRelativeFilePath()); - $this->assertSame('/var/html/files/storage/hello.jpg', $instance->getAbsoluteFilePath()); - $this->assertSame('/var/html', $instance->getRootPath()); - $this->assertSame('files/storage', $instance->getRelativeStoragePath()); - } -} diff --git a/tests/File/FileStorageTest.php b/tests/File/FileStorageTest.php deleted file mode 100644 index ab3806f5..00000000 --- a/tests/File/FileStorageTest.php +++ /dev/null @@ -1,95 +0,0 @@ -getTempDir(), $tempFolder); - $files->dumpFile($this->getTempDir().'/'.$tempFolder.'/test', 'hallo'); - $this->assertSame($tempFolder.'/test', $instance->get('test')); - $this->assertSame($tempFolder.'/test', $instance->get('Test')); - $this->assertNull($instance->get('t€st')); - } - - public function testSet() - { - $files = new Filesystem(); - - $tempFolder = 'filestorage'; - $instance = new FileStorage($this->getTempDir(), $tempFolder); - $path = $instance->set('Test 1', 'Test 1'); - $this->assertSame(file_get_contents($this->getTempDir().'/'.$tempFolder.'/test-1'), 'Test 1'); - $path = $instance->set('Test 1', 'Test 1.1'); - $this->assertTrue($files->exists($this->getTempDir().'/'.$tempFolder.'/test-1')); - $this->assertSame(file_get_contents($this->getTempDir().'/'.$tempFolder.'/test-1'), 'Test 1.1'); - $this->assertStringStartsWith($tempFolder, $instance->get('Test 1')); - $this->assertStringStartsWith($tempFolder, $instance->get('test-1')); - - $tempFolder = 'filestoragecallback'; - $instance = new FileStorage($this->getTempDir(), $tempFolder); - $path = $instance->set('Test 1', function (FileStorageCallback $fileStorageCallback) { - $filesystem = new Filesystem(); - $filesystem->dumpFile($fileStorageCallback->getAbsoluteFilePath(), 'Hello World!'); - - return true; - }); - $this->assertStringStartsWith($tempFolder, $path); - $this->assertTrue($files->exists($this->getTempDir().'/'.$tempFolder.'/test-1')); - $this->assertSame('Hello World!', file_get_contents($this->getTempDir().'/'.$tempFolder.'/test-1')); - - $tempFolder = 'filestorage'; - $instance = new FileStorage($this->getTempDir(), $tempFolder, 'txt'); - $path = $instance->set('Test 1', 'Test 1'); - $this->assertSame(file_get_contents($this->getTempDir().'/'.$tempFolder.'/test-1.txt'), 'Test 1'); - $path = $instance->set('Test 1', 'Test 1.1', ['fileExtension' => 'md']); - $this->assertTrue($files->exists($this->getTempDir().'/'.$tempFolder.'/test-1.md')); - $this->assertSame(file_get_contents($this->getTempDir().'/'.$tempFolder.'/test-1.md'), 'Test 1.1'); - $this->assertStringStartsWith($tempFolder, $instance->get('Test 1')); - $this->assertStringStartsWith($tempFolder, $instance->get('test-1')); - - $tempFolder = 'filestoragecallback'; - $instance = new FileStorage($this->getTempDir(), $tempFolder); - - $unexpectedExceptionThrown = false; - - try { - $path = $instance->set('Test 1', function (FileStorageCallback $fileStorageCallback) { - $filesystem = new Filesystem(); - $filesystem->dumpFile($fileStorageCallback->getAbsoluteFilePath(), 'Hello World!'); - }); - } catch (\UnexpectedValueException $e) { - $unexpectedExceptionThrown = true; - } - $this->assertTrue($unexpectedExceptionThrown); - - $unexpectedArgumentThrown = false; - - try { - $path = $instance->set('Test 1', ['Hello' => 'World']); - } catch (\InvalidArgumentException $e) { - $unexpectedArgumentThrown = true; - } - $this->assertTrue($unexpectedArgumentThrown); - } -} diff --git a/tests/File/FileStorageUtilTest.php b/tests/File/FileStorageUtilTest.php deleted file mode 100644 index 109ac094..00000000 --- a/tests/File/FileStorageUtilTest.php +++ /dev/null @@ -1,28 +0,0 @@ -getTempDir()); - $fileStorage = $instance->createFileStorage('filestorageutil', 'jpg'); - $this->assertInstanceOf(FileStorage::class, $fileStorage); - - $fileStorage->set('testimage', 'FF DD'); - $filesystem = new Filesystem(); - $filesystem->exists($this->getTempDir().'/filestorageutil/testimage.jpg'); - } -} diff --git a/tests/File/FileUtilTest.php b/tests/File/FileUtilTest.php deleted file mode 100644 index 4bfd8021..00000000 --- a/tests/File/FileUtilTest.php +++ /dev/null @@ -1,368 +0,0 @@ -mkdir($this->getTempDir().'/files/'); - - if (!\function_exists('standardize')) { - include_once __DIR__.'/../../vendor/contao/core-bundle/src/Resources/contao/helper/functions.php'; - } - } - - public function testGetFileList() - { - $fileUtil = new FileUtil($this->getContainerMock()); - file_put_contents($this->getTempDir().'/files/testfile1', 'test'); - - $fileList = $fileUtil->getFileList($this->getTempDir().'/files', __DIR__, 'protectBaseUrl'); - $this->assertSame('protectBaseUrl?file='.__DIR__.'/testfile1', $fileList[0]['absUrl']); - - file_put_contents($this->getTempDir().'/files/testfile2', 'test'); - file_put_contents($this->getTempDir().'/files/testfile3', 'test'); - - $fileList = $fileUtil->getFileList($this->getTempDir().'/files', __DIR__); - - $this->assertCount(3, $fileList); - $this->assertArrayHasKey(0, $fileList); - $this->assertArrayHasKey('filename', $fileList[0]); - $this->assertNotSame('', $fileList[0]['filename']); - $this->assertArrayHasKey(1, $fileList); - $this->assertArrayHasKey('filename', $fileList[1]); - $this->assertNotSame('', $fileList[1]['filename']); - $this->assertArrayHasKey(2, $fileList); - $this->assertArrayHasKey('filename', $fileList[2]); - $this->assertNotSame('', $fileList[2]['filename']); - - $fileList = $fileUtil->getFileList($this->getTempDir().'/fileList', __DIR__); - - $this->assertCount(0, $fileList); - } - - public function testGetUniqueFileNameWithinTarget() - { - $container = $this->getContainerMock(); - $this->resetFilesInstance($container); - System::setContainer($container); // Need for contao core file class - $fileUtil = new FileUtil($container); - $projectDir = $container->getParameter('kernel.project_dir'); - - $fileName = $fileUtil->getUniqueFileNameWithinTarget('/files/test', 'te'); - $this->assertSame('files/_1.', $fileName); - - $fileName = $fileUtil->getUniqueFileNameWithinTarget($projectDir.'/test/test/test'); - $this->assertFalse($fileName); - - file_put_contents($projectDir.'/files/test', 'test'); - $fileName = $fileUtil->getUniqueFileNameWithinTarget($projectDir.'/files/test'); - $this->assertSame('files/test_1.', $fileName); - - file_put_contents($projectDir.'/files/test_10', 'test'); - $fileName = $fileUtil->getUniqueFileNameWithinTarget($projectDir.'/files/test_10', null, 10); - $this->assertNotSame('files/test', $fileName); - - $fileName = $fileUtil->getUniqueFileNameWithinTarget($projectDir.'/files/test', null, 100); - $this->assertNotSame('files/test', $fileName); - } - - public function testFormatSizeUnits() - { - $fileUtil = new FileUtil($this->getContainerMock()); - - $bytes = $fileUtil->formatSizeUnits(1073741824); - $this->assertSame('1.00 GB', $bytes); - $bytes = $fileUtil->formatSizeUnits(1048576); - $this->assertSame('1.00 MB', $bytes); - $bytes = $fileUtil->formatSizeUnits(1024); - $this->assertSame('1.00 KB', $bytes); - $bytes = $fileUtil->formatSizeUnits(3); - $this->assertSame('3 Bytes', $bytes); - $bytes = $fileUtil->formatSizeUnits(1); - $this->assertSame('1 Byte', $bytes); - $bytes = $fileUtil->formatSizeUnits(10737.41824); - $this->assertSame('10.49 KB', $bytes); - - try { - $bytes = $fileUtil->formatSizeUnits('107374,1824'); - } catch (\Exception $exception) { - $this->assertSame('A non well formed numeric value encountered', $exception->getMessage()); - } - $bytes = $fileUtil->formatSizeUnits(1073741894, true); - $this->assertSame('1.00 GB', $bytes); - $bytes = $fileUtil->formatSizeUnits(0.1073741894, true); - $this->assertSame('0 Bytes', $bytes); - } - - public function testGetPathWithoutFilename() - { - $fileUtil = new FileUtil($this->getContainerMock()); - $path = $fileUtil->getPathWithoutFilename($this->getTempDir().'/file/testfile1'); - $this->assertSame($this->getTempDir().'/file', $path); - - $path = $fileUtil->getPathWithoutFilename(''); - $this->assertSame('', $path); - - $path = $fileUtil->getPathWithoutFilename(1234); - $this->assertSame('.', $path); - } - - public function testGetFileExtension() - { - $fileUtil = new FileUtil($this->getContainerMock()); - $fileExtension = $fileUtil->getFileExtension($this->getTempDir().'/file/testfile1'); - $this->assertSame('', $fileExtension); - $fileExtension = $fileUtil->getFileExtension($this->getTempDir().'/file/testfile1.txt'); - $this->assertSame('txt', $fileExtension); - $fileExtension = $fileUtil->getFileExtension($this->getTempDir().'/file/testfile1.xml'); - $this->assertSame('xml', $fileExtension); - $fileExtension = $fileUtil->getFileExtension($this->getTempDir().'/file/testfile1...xml'); - $this->assertSame('xml', $fileExtension); - $fileExtension = $fileUtil->getFileExtension($this->getTempDir()); - $this->assertSame('', $fileExtension); - $fileExtension = $fileUtil->getFileExtension($this->getTempDir().'.xml'); - $this->assertSame('xml', $fileExtension); - $fileExtension = $fileUtil->getFileExtension(''); - $this->assertSame('', $fileExtension); - $fileExtension = $fileUtil->getFileExtension(1234); - $this->assertSame('', $fileExtension); - } - - public function testAddUniqueIdToFilename() - { - $container = $this->getContainerMock(); - System::setContainer($container); - $fileUtil = new FileUtil($container); - - $file = $fileUtil->addUniqueIdToFilename('testFile'); - $this->assertNotSame('testFile', $file); - } - - public function testSanitizeFileName() - { - $container = $this->getContainerMock(); - - $fileUtil = new FileUtil($this->getContainerMock()); - - $fileName = $fileUtil->sanitizeFileName('fileName'); - $this->assertSame('filename', $fileName); - - $fileName = $fileUtil->sanitizeFileName('fileName', 3); - $this->assertSame('fi', $fileName); - - $fileName = $fileUtil->sanitizeFileName('საბეჭდი_მანქანა'); - $this->assertSame('_', $fileName); - } - - public function skiptestGetFilesFromUuid() - { - $filesModel = $this->mockClassWithProperties(FilesModel::class, ['path' => 'files']); - $filesAdapter = $this->mockAdapter(['findByUuid']); - $filesAdapter->method('findByUuid')->willReturn($filesModel); - $framework = $this->mockContaoFramework([FilesModel::class => $filesAdapter]); - - $fileUtil = new FileUtil($this->getContainerMock(null, $framework)); - $file = $fileUtil->getFileFromUuid('uuid'); - $this->assertNull($file); - - file_put_contents($this->getTempDir().'/files/testFile', 'test'); - $filesModel = $this->mockClassWithProperties(FilesModel::class, ['path' => 'files/testFile']); - $filesAdapter = $this->mockAdapter(['findByUuid']); - $filesAdapter->method('findByUuid')->willReturn($filesModel); - $framework = $this->mockContaoFramework([FilesModel::class => $filesAdapter]); - - $fileUtil = new FileUtil($this->getContainerMock(null, $framework)); - $file = $fileUtil->getFileFromUuid('uuid'); - $this->assertInstanceOf(File::class, $file); - - $filesAdapter = $this->mockAdapter(['findByUuid']); - $filesAdapter->method('findByUuid')->willReturn(null); - $framework = $this->mockContaoFramework([FilesModel::class => $filesAdapter]); - - $fileUtil = new FileUtil($this->getContainerMock(null, $framework)); - $file = $fileUtil->getFileFromUuid('uuid'); - $this->assertNull($file); - } - - public function testGetPathFromUuid() - { - $filesModel = $this->mockClassWithProperties(FilesModel::class, ['path' => 'files/testfile1']); - $filesAdapter = $this->mockAdapter(['findByUuid']); - $filesAdapter->method('findByUuid')->willReturn($filesModel); - $framework = $this->mockContaoFramework([FilesModel::class => $filesAdapter]); - - $fileUtil = new FileUtil($this->getContainerMock(null, $framework)); - $path = $fileUtil->getPathFromUuid($this->getTempDir().'/files', false); - $this->assertSame('files/testfile1', $path); - - $path = $fileUtil->getPathFromUuid($this->getTempDir().'/files'); - $this->assertSame('files/testfile1', $path); - - $filesAdapter = $this->mockAdapter(['findByUuid']); - $filesAdapter->method('findByUuid')->willReturn(null); - $framework = $this->mockContaoFramework([FilesModel::class => $filesAdapter]); - - $fileUtil = new FileUtil($this->getContainerMock(null, $framework)); - $path = $fileUtil->getPathFromUuid($this->getTempDir().'files'); - $this->assertNull($path); - } - - public function testGetFolderFromUuid() - { - $filesModel = $this->mockClassWithProperties(FilesModel::class, ['path' => 'files']); - $filesAdapter = $this->mockAdapter(['findByUuid']); - $filesAdapter->method('findByUuid')->willReturn($filesModel); - $framework = $this->mockContaoFramework([FilesModel::class => $filesAdapter]); - - $fileUtil = new FileUtil($this->getContainerMock(null, $framework)); - - $path = $fileUtil->getFolderFromUuid('uuid'); - $this->assertInstanceOf(Folder::class, $path); - - $filesAdapter = $this->mockAdapter(['findByUuid']); - $filesAdapter->method('findByUuid')->willReturn(null); - $framework = $this->mockContaoFramework([FilesModel::class => $filesAdapter]); - - $fileUtil = new FileUtil($this->getContainerMock(null, $framework)); - $path = $fileUtil->getFolderFromUuid('uuid'); - $this->assertFalse($path); - } - - public function testGetFileLineCount() - { - file_put_contents($this->getTempDir().'/files/testFile', 'test'); - - $container = $this->getContainerMock(); - $fileUtil = new FileUtil($container); - - $lines = $fileUtil->getFileLineCount($this->getTempDir().'/files/testFile'); - $this->assertSame(1, $lines); - - $lines = $fileUtil->getFileLineCount('/foo'); - $this->assertTrue(false !== strpos($lines, 'fopen('.$container->getParameter('kernel.project_dir').'/foo): failed to open stream:')); - } - - public function testGetFolderFromDca() - { - $filesModel = $this->mockClassWithProperties(FilesModel::class, ['path' => 'files']); - $filesAdapter = $this->mockAdapter(['findByUuid']); - $filesAdapter->method('findByUuid')->willReturn($filesModel); - $framework = $this->mockContaoFramework([FilesModel::class => $filesAdapter]); - - $fileUtil = new FileUtil($this->getContainerMock(null, $framework)); - $folder = $fileUtil->getFolderFromDca($this->getTempDir().'files'); - $this->assertSame($this->getTempDir().'files', $folder); - - $folder = $fileUtil->getFolderFromDca('3712c116-1193-11e8-b642-0ed5f89f718b'); - $this->assertSame('files', $folder); - - $file = new File('files/dcaFile'); - $folder = $fileUtil->getFolderFromDca($file); - $this->assertSame('files/dcaFile', $folder); - - $file = $this->mockClassWithProperties(FilesModel::class, ['path' => 'files/dcaFile']); - $folder = $fileUtil->getFolderFromDca($file); - $this->assertSame('files/dcaFile', $folder); - - $folder = $fileUtil->getFolderFromDca( - function ($dca) { - return 'files/dcaFile'; - }, - $this->getDataContainerMock() - ); - $this->assertSame('files/dcaFile', $folder); - - $folder = $fileUtil->getFolderFromDca([self::class, 'getFolder'], $this->getDataContainerMock()); - $this->assertSame('files', $folder); - - try { - $fileUtil->getFolderFromDca('dlfjn../ds'); - } catch (\Exception $exception) { - $this->assertSame('Invalid target path dlfjn../ds', $exception->getMessage()); - } - } - - /** - * @return DataContainer|\PHPUnit_Framework_MockObject_MockObject - */ - public function getDataContainerMock($properties = true) - { - if ($properties) { - return $this->mockClassWithProperties(DataContainer::class, ['id' => 1, 'table' => 'testTable']); - } - - return $this->createMock(DataContainer::class); - } - - public function getFolder($dca) - { - return 'files'; - } - - /** - * @param ContaoFramework $framework - * - * @return ContainerBuilder|ContainerInterface - */ - protected function getContainerMock(ContainerBuilder $container = null, $framework = null) - { - if (!$container) { - $container = $this->getContainerWithContaoConfiguration($this->getTempDir()); - } - - if (!$framework) { - $filesModel = $this->mockClassWithProperties(FilesModel::class, ['path' => $this->getTempDir().'files']); - $filesAdapter = $this->mockAdapter(['findByUuid']); - $filesAdapter->method('findByUuid')->willReturn($filesModel); - $framework = $this->mockContaoFramework([FilesModel::class => $filesAdapter]); - } - $container->set('contao.framework', $framework); - $container->setParameter('contao.resources_paths', [__DIR__.'/../vendor/contao/core-bundle/src/Resources/contao']); - - $utils = $this->createMock(Utils::class); - - $utilsString = new StringUtil($this->mockContaoFramework(), $utils); - $container->set('huh.utils.string', $utilsString); - - /** @noinspection PhpParamsInspection */ - $containerUtils = new ContainerUtil($container, $utils); - $container->set('huh.utils.container', $containerUtils); - - $arrayUtils = new ArrayUtil($container); - $container->set('huh.utils.array', $arrayUtils); - - System::setContainer($container); - - return $container; - } -} diff --git a/tests/File/FolderUtilTest.php b/tests/File/FolderUtilTest.php deleted file mode 100644 index 93650851..00000000 --- a/tests/File/FolderUtilTest.php +++ /dev/null @@ -1,63 +0,0 @@ -getTempDir(); - - if (!\defined('TL_ROOT')) { - \define('TL_ROOT', $tmpFolder); - } - $filesystem = new Filesystem(); - $container = $this->getContainerWithContaoConfiguration($tmpFolder); - $container->setParameter('kernel.project_dir', $tmpFolder); - $container->set('filesystem', $filesystem); - $this->resetFilesInstance($container); - $resourceFinder = $this->createMock(ResourceFinderInterface::class); - - $kernel = $this->createMock(KernelInterface::class); - $symlinkCommand = $this->getMockBuilder(SymlinksCommand::class)->setConstructorArgs([$tmpFolder, $tmpFolder, $tmpFolder, $resourceFinder])->setMethods(['execute'])->getMock(); - $symlinkCommand->method('execute')->willReturn(0); - $folderUtil = new FolderUtil($tmpFolder.'/web', $kernel, $symlinkCommand); - - $folder = 'should_be_public'; - - $filesystem->mkdir($tmpFolder.'/'.$folder); - $filesystem->mkdir($tmpFolder.'/web'); - $filesystem->mkdir($tmpFolder.'/system/tmp'); - $folderUtil->createPublicFolder($folder); - - $this->assertTrue($filesystem->exists($tmpFolder.'/'.$folder.'/.public')); - - $symlinkCommand = $this->getMockBuilder(SymlinksCommand::class)->setConstructorArgs([$tmpFolder, $tmpFolder, $tmpFolder, $resourceFinder])->setMethods(['execute'])->getMock(); - $symlinkCommand->method('execute')->willReturn(1); - $folderUtil = new FolderUtil($tmpFolder.'/web', $kernel, $symlinkCommand); - $this->expectExceptionMessage('The symlink command exited with errors.'); - $folderUtil->createPublicFolder($folder); - } -} diff --git a/tests/Form/FormUtilTest.php b/tests/Form/FormUtilTest.php deleted file mode 100644 index 939c2cd6..00000000 --- a/tests/Form/FormUtilTest.php +++ /dev/null @@ -1,482 +0,0 @@ -dc = $this->getMockBuilder(DC_Table_Utils::class)->disableOriginalConstructor()->getMock(); - $this->dc->method('__get')->willReturnCallback(function ($key) { - switch ($key) { - case 'table': - return 'tl_test'; - - case 'id': - return 1; - - case 'field': - return 'myField'; - } - - return ''; - }); - - // mock language - $GLOBALS['TL_LANG']['tl_test']['myField'] = ['My field', 'This field is the test field.']; - $GLOBALS['TL_LANG']['tl_test']['firstname'] = ['Firstname', '']; - $GLOBALS['TL_LANG']['tl_test']['lastname'] = ['Lastname', '']; - $GLOBALS['TL_LANG']['tl_test']['language'] = ['Language', '']; - $GLOBALS['TL_LANG']['tl_test']['myFieldExplanation'] = '

Mein Feld

'; - $GLOBALS['TL_LANG']['tl_test']['reference'] = [ - 'first' => 'Erster', - 'de' => 'Deutsch', - 'en' => 'Englisch', - ]; - - $GLOBALS['TL_LANG']['MSC']['yes'] = 'Ja'; - $GLOBALS['TL_LANG']['MSC']['no'] = 'Nein'; - } - - public function testPrepareSpecialValueForOutputText() - { - $formUtil = $this->getFormUtilMock(); - $result = $formUtil->prepareSpecialValueForOutput('myField', 'myValue', $this->dc); - - // at first call without dca being set - $this->assertSame('myValue', $result); - - // mock dca - $GLOBALS['TL_DCA']['tl_test']['fields']['myField'] = [ - 'label' => &$GLOBALS['TL_LANG']['tl_test']['myField'], - 'inputType' => 'text', - 'eval' => ['maxlength' => 255, 'tl_class' => 'w50', 'mandatory' => true], - 'sql' => "varchar(255) NOT NULL default ''", - ]; - - $result = $formUtil->prepareSpecialValueForOutput('myField', 'myValue', $this->dc); - - $this->assertSame('myValue', $result); - - // test date & time - $time = time(); - - // test with rgxp=date - Config::set('dateFormat', 'd.m.Y'); - $GLOBALS['TL_DCA']['tl_test']['fields']['myField']['eval']['rgxp'] = 'date'; - - $result = $formUtil->prepareSpecialValueForOutput('myField', $time, $this->dc); - - $this->assertSame(date('d.m.Y', $time), $result); - - // test with rgxp=datim - Config::set('datimFormat', 'd.m.Y H:i'); - $GLOBALS['TL_DCA']['tl_test']['fields']['myField']['eval']['rgxp'] = 'datim'; - - $result = $formUtil->prepareSpecialValueForOutput('myField', $time, $this->dc); - - $this->assertSame(date('d.m.Y H:i', $time), $result); - - // test with rgxp=time - Config::set('timeFormat', 'H:i'); - $GLOBALS['TL_DCA']['tl_test']['fields']['myField']['eval']['rgxp'] = 'time'; - - $result = $formUtil->prepareSpecialValueForOutput('myField', $time, $this->dc); - - $this->assertSame(date('H:i', $time), $result); - - // test encryption - unset($GLOBALS['TL_DCA']['tl_test']['fields']['myField']['eval']['rgxp']); - $GLOBALS['TL_DCA']['tl_test']['fields']['myField']['eval']['encrypt'] = true; - - $plain = 'This is a test :-)(/$§()$/$=)___ fds'; - list($encrypted, $iv) = $this->getContainerMock()->get('huh.utils.encryption')->encrypt($plain); - - $result = $formUtil->prepareSpecialValueForOutput('myField', $encrypted.'.'.$iv, $this->dc); - - $this->assertSame($plain, $result); - } - - public function testPrepareSpecialValueForOutputArray() - { - $formUtil = $this->getFormUtilMock(); - // mock dca - $GLOBALS['TL_DCA']['tl_test']['fields']['myField'] = [ - 'label' => &$GLOBALS['TL_LANG']['tl_test']['myField'], - 'inputType' => 'select', - 'reference' => &$GLOBALS['TL_LANG']['tl_test']['reference'], - 'eval' => ['tl_class' => 'w50', 'mandatory' => true], - 'sql' => "varchar(255) NOT NULL default ''", - ]; - - // test with removing empty values - $result = $formUtil->prepareSpecialValueForOutput('myField', [ - 'first', - '', - 'third', - ], $this->dc); - - $this->assertSame('Erster, third', $result); - - // test with skipping localization - $result = $formUtil->prepareSpecialValueForOutput('myField', [ - 'first', - '', - 'third', - ], $this->dc, [ - 'skipLocalization' => true, - ]); - - $this->assertSame('first, third', $result); - - // test with removing empty values - $result = $formUtil->prepareSpecialValueForOutput('myField', [ - 'first', - '', - 'third', - ], $this->dc, [ - 'preserveEmptyArrayValues' => true, - ]); - - $this->assertSame('Erster, , third', $result); - - // test foreignKey - $GLOBALS['TL_DCA']['tl_test']['fields']['myField']['foreignKey'] = 'tl_test2.title'; - - $result = $formUtil->prepareSpecialValueForOutput('myField', [ - 'first', - 'third', - ], $this->dc); - - $this->assertSame('Foreign key title 1, Foreign key title 3', $result); - } - - public function testPrepareSpecialValueForOutputExplanation() - { - $formUtil = $this->getFormUtilMock(); - // mock dca - $GLOBALS['TL_DCA']['tl_test']['fields']['myField'] = [ - 'label' => &$GLOBALS['TL_LANG']['tl_test']['myField'], - 'inputType' => 'explanation', - 'eval' => ['text' => $GLOBALS['TL_LANG']['tl_test']['myFieldExplanation']], - ]; - - $result = $formUtil->prepareSpecialValueForOutput('myField', '', $this->dc); - - $this->assertSame('

Mein Feld

', $result); - } - - public function testPrepareSpecialValueForOutputCfgTags() - { - $formUtil = $this->getFormUtilMock(); - // mock dca - $GLOBALS['TL_DCA']['tl_test']['fields']['myField'] = [ - 'label' => &$GLOBALS['TL_LANG']['tl_test']['myField'], - 'inputType' => 'cfgTags', - 'eval' => [ - 'tagsManager' => 'app.test', - ], - 'relation' => [ - 'relationTable' => 'tl_test_tags', - ], - 'foreignKey' => 'tl_cfg_tag.name', // required for back end filter value to name conversion - ]; - - $result = $formUtil->prepareSpecialValueForOutput('myField', '', $this->dc); - - $this->assertSame('First tag, Third tag', $result); - } - - public function testPrepareSpecialValueForOutputUuid() - { - $formUtil = $this->getFormUtilMock(); - $value = StringUtil::uuidToBin('82f9119db59b11e787f2a08cfddc0261'); - Environment::set('url', 'http://localhost'); - - // mock dca - $GLOBALS['TL_DCA']['tl_test']['fields']['myField'] = [ - 'label' => &$GLOBALS['TL_LANG']['tl_test']['myField'], - 'inputType' => 'fileTree', - ]; - - $result = $formUtil->prepareSpecialValueForOutput('myField', $value, $this->dc); - - $this->assertSame('http://localhost/files/themes/img/myimage.png', $result); - } - - public function testPrepareSpecialValueForOutputIsBoolean() - { - $GLOBALS['TL_DCA']['tl_test']['fields']['myField'] = [ - 'label' => &$GLOBALS['TL_LANG']['tl_test']['myField'], - 'inputType' => 'checkbox', - 'eval' => [ - 'multiple' => true, - ], - ]; - - // dca util - $dcaUtil = $this->mockAdapter(['getConfigByArrayOrCallbackOrFunction']); - $dcaUtil->method('getConfigByArrayOrCallbackOrFunction')->willReturn([ - 'first' => 'Erster', - 'third' => 'Dritter', - ], [ - 'skipOptionCaching' => true, - ]); - - $container = $this->getContainerWithContaoConfiguration(); - $container->set('huh.utils.dca', $dcaUtil); - $formUtil = $this->getFormUtilMock($container); - - $result = $formUtil->prepareSpecialValueForOutput('myField', ['first', 'third'], $this->dc); - $this->assertSame('Erster, Dritter', $result); - - // mock dca - $GLOBALS['TL_DCA']['tl_test']['fields']['myField'] = [ - 'label' => &$GLOBALS['TL_LANG']['tl_test']['myField'], - 'inputType' => 'checkbox', - 'eval' => [ - 'isBoolean' => true, - ], - ]; - - $result = $formUtil->prepareSpecialValueForOutput('myField', '1', $this->dc); - $this->assertSame('Ja', $result); - - $result = $formUtil->prepareSpecialValueForOutput('myField', '', $this->dc); - $this->assertSame('Nein', $result); - } - - public function testPrepareSpecialValueForOutputMultiColumnEditor() - { - $formUtil = $this->getFormUtilMock(); - - $value = [ - ['firstname' => 'John1', 'lastname' => 'Doe1', 'language' => 'de'], - ['firstname' => 'John2', 'lastname' => 'Doe2', 'language' => 'en'], - ]; - - $GLOBALS['TL_DCA']['tl_test']['fields']['myField'] = [ - 'label' => &$GLOBALS['TL_LANG']['tl_test']['myField'], - 'inputType' => 'multiColumnEditor', - 'eval' => [ - 'multiColumnEditor' => [ - 'fields' => [ - 'firstname' => [ - 'label' => &$GLOBALS['TL_LANG']['tl_test']['firstname'], - 'inputType' => 'text', - ], - 'lastname' => [ - 'label' => &$GLOBALS['TL_LANG']['tl_test']['lastname'], - 'inputType' => 'text', - ], - 'language' => [ - 'label' => &$GLOBALS['TL_LANG']['tl_test']['language'], - 'inputType' => 'select', - 'options' => ['de', 'en'], - 'reference' => &$GLOBALS['TL_LANG']['tl_test']['reference'], - ], - ], - ], - ], - 'sql' => 'blob NULL', - ]; - - $result = $formUtil->prepareSpecialValueForOutput('myField', $value, $this->dc); - $this->assertSame("\t\n\tFirstname: John1\tLastname: Doe1\tLanguage: Deutsch\t\n\tFirstname: John2\tLastname: Doe2\tLanguage: Englisch\t\n", $result); - - $adapter = $this->mockAdapter(['getConfigByArrayOrCallbackOrFunction']); - $adapter->method('getConfigByArrayOrCallbackOrFunction')->willThrowException(new \ErrorException('This is an error exception')); - - $container = $this->getContainerWithContaoConfiguration(); - $container->set('huh.utils.dca', $adapter); - $formUtil = $this->getFormUtilMock($container); - - $result = $formUtil->prepareSpecialValueForOutput('myField', $value, $this->dc); - $this->assertSame("\t\n\tFirstname: John1\tLastname: Doe1\tLanguage: Deutsch\t\n\tFirstname: John2\tLastname: Doe2\tLanguage: Englisch\t\n", $result); - } - - public function testEscapeAllHtmlEntities() - { - $formUtil = $this->getFormUtilMock(); - - $GLOBALS['TL_DCA']['tl_test']['fields']['myField'] = [ - 'label' => &$GLOBALS['TL_LANG']['tl_test']['myField'], - 'inputType' => 'multiColumnEditor', - 'eval' => ['allowHtml' => true], - 'options' => ['test', 'test'], - 'sql' => 'blob NULL', - ]; - - $result = $formUtil->escapeAllHtmlEntities('tl_test', 'myField', ''); - $this->assertSame('<script>alert()</script>', $result); - - $GLOBALS['TL_DCA']['tl_test']['fields']['myField'] = [ - 'label' => &$GLOBALS['TL_LANG']['tl_test']['myField'], - 'inputType' => 'multiColumnEditor', - 'eval' => ['allowHtml' => false, 'rte' => '', 'preserveTags' => ''], - 'options' => ['test', 'test'], - 'sql' => 'blob NULL', - ]; - - $result = $formUtil->escapeAllHtmlEntities('tl_test', 'myField', ''); - $this->assertSame('<script>alert()</script>', $result); - - $GLOBALS['TL_DCA']['tl_test']['fields']['myField'] = [ - 'label' => &$GLOBALS['TL_LANG']['tl_test']['myField'], - 'inputType' => 'multiColumnEditor', - 'eval' => ['allowHtml' => false, 'rte' => '', 'preserveTags' => '', 'decodeEntities' => true], - 'options' => 'test', - 'sql' => 'blob NULL', - ]; - - $result = $formUtil->escapeAllHtmlEntities('tl_test', 'myField', ''); - $this->assertSame('<script>alert()</script>', $result); - - $result = $formUtil->escapeAllHtmlEntities('tl_test', 'myField', false); - $this->assertFalse($result); - } - - /** - * @return \Symfony\Component\DependencyInjection\ContainerBuilder - */ - protected function getContainerMock(ContainerBuilder $container = null) - { - if (!$container) { - $container = $this->getContainerWithContaoConfiguration(); - } - $container->set('contao.framework', $this->mockContaoFramework()); - - $requestStack = new RequestStack(); - $requestStack->push(new \Symfony\Component\HttpFoundation\Request()); - $backendMatcher = new RequestMatcher('/contao', 'test.com', null, ['192.168.1.0']); - $frontendMatcher = new RequestMatcher('/index', 'test.com', null, ['192.168.1.0']); - $scopeMatcher = new ScopeMatcher($backendMatcher, $frontendMatcher); - $request = new Request($this->mockContaoFramework(), $requestStack, $scopeMatcher); - $container->set('huh.request', $request); - - if (!$container->has('huh.utils.dca')) { - $dcaUtil = $this->mockAdapter(['getConfigByArrayOrCallbackOrFunction']); - $dcaUtil->method('getConfigByArrayOrCallbackOrFunction')->willReturn(null); - $container->set('huh.utils.dca', $dcaUtil); - } - - $fileUtil = $this->mockAdapter(['getPathFromUuid']); - $fileUtil->method('getPathFromUuid')->willReturn('files/themes/img/myimage.png'); - $container->set('huh.utils.file', $fileUtil); - - $containerUtil = $this->mockAdapter(['isBundleActive']); - $containerUtil->method('isBundleActive')->willReturn(true); - $container->set('huh.utils.container', $containerUtil); - - $foreignKeyInstance1 = $this->createMock(Model::class); - $foreignKeyInstance1->method('__get')->willReturnCallback(function ($key) { - switch ($key) { - case 'title': - return 'Foreign key title 1'; - } - - return ''; - }); - - $foreignKeyInstance3 = $this->createMock(Model::class); - $foreignKeyInstance3->method('__get')->willReturnCallback(function ($key) { - switch ($key) { - case 'title': - return 'Foreign key title 3'; - } - - return ''; - }); - $modelUtil = $this->mockAdapter(['findModelInstanceByPk']); - $modelUtil->method('findModelInstanceByPk')->willReturnCallback(function ($table, $id) use ($foreignKeyInstance1, $foreignKeyInstance3) { - switch ($id) { - case 'first': - return $foreignKeyInstance1; - - case 'third': - return $foreignKeyInstance3; - } - - return null; - }); - $container->set('huh.utils.model', $modelUtil); - - $encryptionUtils = new EncryptionUtil($this->mockContaoFramework()); - $container->set('huh.utils.encryption', $encryptionUtils); - - $container->setParameter('secret', Config::class); - - System::setContainer($container); - - return $container; - } - - protected function getFormUtilMock(ContainerBuilder $container = null) - { - // mock system classes - $controller = $this->mockAdapter([ - 'loadDataContainer', - ]); - - $system = $this->mockAdapter([ - 'loadLanguageFile', - ]); - - $cfgTagModel = $this->mockAdapter([ - 'findBy', - ]); - - $tagModelCollection = $this->createMock(Collection::class); - $tagModelCollection->method('fetchEach')->willReturn(['First tag', 'Third tag']); - - $cfgTagModel->method('findBy')->willReturn($tagModelCollection); - - $formUtil = new FormUtil( - $this->getContainerMock($container), - $this->mockContaoFramework( - [ - Controller::class => $controller, - System::class => $system, - CfgTagModel::class => $cfgTagModel, - ])); - - return $formUtil; - } -} diff --git a/tests/HeimrichHannotContaoUtilsBundleTest.php b/tests/HeimrichHannotContaoUtilsBundleTest.php deleted file mode 100644 index ddfd8863..00000000 --- a/tests/HeimrichHannotContaoUtilsBundleTest.php +++ /dev/null @@ -1,24 +0,0 @@ -assertInstanceOf(HeimrichHannotContaoUtilsBundle::class, $bundle); - } -} diff --git a/tests/Image/ImageUtilTest.php b/tests/Image/ImageUtilTest.php deleted file mode 100644 index 6572f6b4..00000000 --- a/tests/Image/ImageUtilTest.php +++ /dev/null @@ -1,353 +0,0 @@ -mkdir($this->getTempDir().'/files'); - - copy( - $this->getFixturesDir().'/files/screenshot.png', - $this->getTempDir().'/files/screenshot.png' - ); - } - - public function testAddToTemplateDataWithoutModel() - { - $templateData = []; - $imageArray['imagemargin'] = 'a:5:{s:6:"bottom";s:0:"";s:4:"left";s:0:"";s:5:"right";s:0:"";s:3:"top";s:0:"";s:4:"unit";s:0:"";}'; - $imageArray['singleSRC'] = 'de28ed4c-2eb5-11e9-ac5f-a08cfddc0261'; - $imageArray['size'] = 'a:3:{i:0;s:0:"2";i:1;s:0:"2";i:2;s:0:"2";}'; - $imageArray['alt'] = ''; - $imageArray['fullsize'] = true; - $imageArray['floating'] = false; - $imageArray['imageUrl'] = $this->getTempDir().'/files/screenshot.png'; - $imageArray['imageTitle'] = 'imageTitle'; - $imageArray['linkTitle'] = false; - $imageArray['id'] = 12; - - $templateData['href'] = true; - $templateData['singleSRC'] = []; - - $container = $this->getContainerMock(); - $monologLoggerMock = $this->mockAdapter(['log']); - $monologLoggerMock->method('log'); - $container->set('monolog.logger.contao', $monologLoggerMock); - - System::setContainer($container); - $image = new ImageUtil($container); - $image->addToTemplateData('singleSRC', 'addImage', $templateData, $imageArray); - - $this->assertNotSame(['href' => true, 'singleSRC' => []], $templateData); - $this->assertSame($this->getTempDir().'/files/screenshot.png', $templateData['singleSRC']); - $this->assertSame('imageTitle', $templateData['linkTitle']); - $this->assertSame($this->getTempDir().'/files/screenshot.png', $templateData['imageHref']); - $this->assertSame(' data-lightbox="5dc05b"', $templateData['attributes']); - - $image->addToTemplateData('singleSRC', 'addImage', $templateData, $imageArray); - } - - public function testAddToTemplateDataWithModel() - { - $GLOBALS['TL_LANG']['MSC']['deleteConfirmFile'] = 'delete'; - $templateData = []; - global $objPage; - - $objPage = $this->mockClassWithProperties(PageModel::class, ['language' => 'de', 'rootFallbackLanguage' => 'de']); - - $GLOBALS['TL_DCA']['tl_files']['fields']['meta']['eval']['metaFields'] = ['title' => 'maxlenght="255"', 'alt' => 'maxlenght="255"', 'link' => 'maxlenght="255"', 'caption' => 'maxlenght="255"']; - - $imageArray['imagemargin'] = 'a:5:{s:6:"bottom";i:10;s:4:"left";i:10;s:5:"right";i:10;s:3:"top";i:10;s:4:"unit";s:2:"px";}'; - $imageArray['singleSRC'] = 'de28ed4c-2eb5-11e9-ac5f-a08cfddc0261'; - $imageArray['size'] = 'a:3:{i:0;s:0:"2";i:1;s:0:"2";i:2;s:0:"2";}'; - $imageArray['alt'] = ''; - $imageArray['fullsize'] = true; - $imageArray['imageUrl'] = $this->getTempDir().'/files/screenshot.png'; - $imageArray['linkTitle'] = 'linkTitle'; - $imageArray['floating'] = 'floating'; - $imageArray['overwriteMeta'] = false; - $imageArray['caption'] = []; - $imageArray['id'] = 12; - $imageArray['imageTitle'] = 'imageTitle'; - - $templateData['href'] = true; - $templateData['singleSRC'] = []; - - $model = $this->mockClassWithProperties(FilesModel::class, ['meta' => 'a:1:{s:2:"de";a:4:{s:5:"title";s:9:"Diebstahl";s:3:"alt";s:0:"";s:4:"link";s:0:"";s:7:"caption";s:209:"Ob Stifte, Druckerpapier oder Büroklammern: Jeder vierte Arbeitnehmer lässt im Büro etwas mitgehen. Doch egal, wie günstig die gestohlenen Gegenstände sein mögen: Eine Abmahnung ist gerechtfertigt.";}}']); - - $container = $this->getContainerMock(); - $monologLoggerMock = $this->mockAdapter(['log']); - $monologLoggerMock->method('log'); - $container->set('monolog.logger.contao', $monologLoggerMock); - - $image = new ImageUtil($container); - $image->addToTemplateData('singleSRC', 'addImage', $templateData, $imageArray, 400, null, null, $model); - - $this->assertNotSame(['href' => true, 'singleSRC' => []], $templateData); - $this->assertSame($this->getTempDir().'/files/screenshot.png', $templateData['singleSRC']); - $this->assertSame('margin:10px;', $templateData['margin']); - $this->assertSame('Diebstahl', $templateData['imageTitle']); - $this->assertSame(' float_floating', $templateData['floatClass']); - } - - public function testAddToTemplateDataError() - { - $container = $this->getContainerMock(); - $exception = new \Exception(); - $pictureFactoryAdapter = $this->mockAdapter(['create']); - $pictureFactoryAdapter->method('create')->willThrowException($exception); - $container->set('contao.image.picture_factory', $pictureFactoryAdapter); - - $templateData = []; - global $objPage; - - $objPage = $this->mockClassWithProperties(PageModel::class, ['language' => 'de', 'rootFallbackLanguage' => 'de']); - - $GLOBALS['TL_DCA']['tl_files']['fields']['meta']['eval']['metaFields'] = ['title' => 'maxlenght="255"', 'alt' => 'maxlenght="255"', 'link' => 'maxlenght="255"', 'caption' => 'maxlenght="255"']; - - $imageArray['imagemargin'] = 'a:5:{s:6:"bottom";i:10;s:4:"left";i:10;s:5:"right";i:10;s:3:"top";i:10;s:4:"unit";s:2:"px";}'; - $imageArray['singleSRC'] = 'de28ed4c-2eb5-11e9-ac5f-a08cfddc0261'; - $imageArray['size'] = 'a:3:{i:0;s:0:"2";i:1;s:0:"2";i:2;s:0:"2";}'; - $imageArray['alt'] = ''; - $imageArray['fullsize'] = true; - $imageArray['floating'] = false; - $imageArray['imageUrl'] = 'files/screenshot.png'; - $imageArray['linkTitle'] = 'linkTitle'; - $imageArray['overwriteMeta'] = false; - $imageArray['caption'] = []; - $imageArray['id'] = 12; - $imageArray['imageTitle'] = 'imageTitle'; - - $templateData['href'] = true; - $templateData['singleSRC'] = []; - - $model = $this->mockClassWithProperties(FilesModel::class, ['meta' => 'a:1:{s:2:"de";a:4:{s:5:"title";s:9:"Diebstahl";s:3:"alt";s:0:"";s:4:"link";s:0:"";s:7:"caption";s:209:"Ob Stifte, Druckerpapier oder Büroklammern: Jeder vierte Arbeitnehmer lässt im Büro etwas mitgehen. Doch egal, wie günstig die gestohlenen Gegenstände sein mögen: Eine Abmahnung ist gerechtfertigt.";}}']); - - $monologLoggerMock = $this->mockAdapter(['log']); - $monologLoggerMock->expects($this->once())->method('log'); - $container->set('monolog.logger.contao', $monologLoggerMock); - - System::setContainer($container); - $image = new ImageUtil($container); - $image->addToTemplateData('singleSRC', 'addImage', $templateData, $imageArray, 4, 12, 'lightBoxName', $model); - $this->assertSame('', $templateData['src']); - $this->assertSame('margin-top:10px;margin-bottom:10px;', $templateData['margin']); - - $templateData = []; - $templateData['href'] = true; - $templateData['singleSRC'] = []; - - $imageArray['singleSRC'] = ''; - $imageArray['overwriteMeta'] = true; - $imageArray['fullsize'] = true; - $imageArray['imageUrl'] = 'files/screensho'; - - $image->addToTemplateData('singleSRC', 'addImage', $templateData, $imageArray, 400, 12, 'lightBoxName', $model); - $this->assertArrayNotHasKey('width', $templateData); - $this->assertArrayNotHasKey('height', $templateData); - $this->assertArrayNotHasKey('attributes', $templateData); - - $utilsContainer = $this->mockAdapter(['isBackend', 'isFrontend']); - $utilsContainer->method('isBackend')->willReturn(true); - $utilsContainer->method('isFrontend')->willReturn(false); - $container->set('huh.utils.container', $utilsContainer); - - $image = new ImageUtil($container); - - $templateData = []; - $templateData['href'] = true; - $templateData['singleSRC'] = []; - - $imageArray['imageUrl'] = $this->getTempDir().'/files/screenshot.png'; - $imageArray['singleSRC'] = $this->getTempDir().'/files/screenshot.png'; - $imageArray['size'] = 12; - - $image->addToTemplateData('singleSRC', 'addImage', $templateData, $imageArray, 4, 12, 'lightBoxName', $model); - $this->assertArrayNotHasKey('margin', $templateData); - } - - public function addImageToTemplateDataHook( - array $templateData, - string $imageField, - string $imageSelectorField, - array $item, - int $maxWidth = null, - string $lightboxId = null, - string $lightboxName = null, - FilesModel $model = null - ) { - $templateData['picture']['test'] = true; - - return $templateData; - } - - public function testAddToTemplateDataHook() - { - $GLOBALS['TL_HOOKS']['addImageToTemplateData'][] = [static::class, 'addImageToTemplateDataHook']; - - $GLOBALS['TL_LANG']['MSC']['deleteConfirmFile'] = 'delete'; - $templateData = []; - global $objPage; - - $objPage = $this->mockClassWithProperties(PageModel::class, ['language' => 'de', 'rootFallbackLanguage' => 'de']); - - $GLOBALS['TL_DCA']['tl_files']['fields']['meta']['eval']['metaFields'] = ['title' => 'maxlenght="255"', 'alt' => 'maxlenght="255"', 'link' => 'maxlenght="255"', 'caption' => 'maxlenght="255"']; - - $imageArray['imagemargin'] = 'a:5:{s:6:"bottom";i:10;s:4:"left";i:10;s:5:"right";i:10;s:3:"top";i:10;s:4:"unit";s:2:"px";}'; - $imageArray['singleSRC'] = 'de28ed4c-2eb5-11e9-ac5f-a08cfddc0261'; - $imageArray['size'] = 'a:3:{i:0;s:0:"2";i:1;s:0:"2";i:2;s:0:"2";}'; - $imageArray['alt'] = ''; - $imageArray['fullsize'] = true; - $imageArray['imageUrl'] = $this->getTempDir().'/files/screenshot.png'; - $imageArray['linkTitle'] = 'linkTitle'; - $imageArray['floating'] = 'floating'; - $imageArray['overwriteMeta'] = false; - $imageArray['caption'] = []; - $imageArray['id'] = 12; - $imageArray['imageTitle'] = 'imageTitle'; - - $templateData['href'] = true; - $templateData['singleSRC'] = []; - - $model = $this->mockClassWithProperties(FilesModel::class, ['meta' => 'a:1:{s:2:"de";a:4:{s:5:"title";s:9:"Diebstahl";s:3:"alt";s:0:"";s:4:"link";s:0:"";s:7:"caption";s:209:"Ob Stifte, Druckerpapier oder Büroklammern: Jeder vierte Arbeitnehmer lässt im Büro etwas mitgehen. Doch egal, wie günstig die gestohlenen Gegenstände sein mögen: Eine Abmahnung ist gerechtfertigt.";}}']); - - $container = $this->getContainerMock(); - $monologLoggerMock = $this->mockAdapter(['log']); - $monologLoggerMock->method('log'); - $container->set('monolog.logger.contao', $monologLoggerMock); - - $image = new ImageUtil($container); - $image->addToTemplateData('singleSRC', 'addImage', $templateData, $imageArray, 400, null, null, $model); - - $this->assertNotSame(['href' => true, 'singleSRC' => []], $templateData); - $this->assertSame($this->getTempDir().'/files/screenshot.png', $templateData['singleSRC']); - $this->assertSame('margin:10px;', $templateData['margin']); - $this->assertSame('Diebstahl', $templateData['imageTitle']); - $this->assertSame(' float_floating', $templateData['floatClass']); - $this->assertTrue($templateData['picture']['test']); - } - - public function testGetPixelValue() - { - $class = new ImageUtil($this->getContainerMock()); - - $result = $class->getPixelValue('10px'); - $this->assertSame(10, $result); - $result = $class->getPixelValue('10em'); - $this->assertSame(160, $result); - $result = $class->getPixelValue('10ex'); - $this->assertSame(80, $result); - $result = $class->getPixelValue('10pt'); - $this->assertSame(13, $result); - $result = $class->getPixelValue('10pc'); - $this->assertSame(160, $result); - $result = $class->getPixelValue('10in'); - $this->assertSame(960, $result); - $result = $class->getPixelValue('10cm'); - $this->assertSame(378, $result); - $result = $class->getPixelValue('10mm'); - $this->assertSame(38, $result); - $result = $class->getPixelValue('10%'); - $this->assertSame(2, $result); - $result = $class->getPixelValue('10%%%'); - $this->assertSame(0, $result); - } - - /** - * @param ContaoFramework $framework - * - * @return ContainerBuilder|ContainerInterface - */ - protected function getContainerMock(ContainerBuilder $container = null, $framework = null) - { - if (!$container) { - $container = $this->getContainerWithContaoConfiguration($this->getTempDir()); - } - - if (!$framework) { - $controllerAdapter = $this->mockAdapter(['loadDataContainer']); - - $framework = $this->mockContaoFramework([ - Controller::class => $controllerAdapter, - ]); - } - $container->set('contao.framework', $framework); - - $utilsContainer = $this->mockAdapter(['isBackend', 'isFrontend']); - $utilsContainer->method('isBackend')->willReturn(false); - $utilsContainer->method('isFrontend')->willReturn(true); - $container->set('huh.utils.container', $utilsContainer); - - $imageFile = $this->mockClassWithProperties(File::class, [ - 'path' => $this->getTempDir().'/files/screenshot.png', - 'imageSize' => [ - 800, - 1200, - 0, // replace this with IMAGETYPE_SVG when it becomes available - 'width="'. 1200 .'" height="'. 800 .'"', - 'bits' => 8, - 'channels' => 3, - 'mime' => 'image/png', - ], - 'extension' => 'png', - ]); - $fileUtil = $this->createMock(FileUtil::class); - $fileUtil->method('getFileFromUuid')->willReturn($imageFile); - $container->set('huh.utils.file', $fileUtil); - - $imageAdapter = $this->mockAdapter(['getUrl']); - $imageAdapter->method('getUrl')->willReturn('files/screenshot.png'); - $imageFactoryAdapter = $this->mockAdapter(['create']); - $imageFactoryAdapter->method('create')->willReturn($imageAdapter); - $container->set('contao.image.image_factory', $imageFactoryAdapter); - - $imageMock = $this->createMock(ImageInterface::class); - $pictureMock = new Picture(['src' => $imageMock, 'srcset' => []], []); - $pictureFactoryAdapter = $this->mockAdapter(['create']); - $pictureFactoryAdapter->method('create')->willReturn($pictureMock); - $container->set('contao.image.picture_factory', $pictureFactoryAdapter); - - return $container; - } -} diff --git a/tests/Model/CfgTagModelTest.php b/tests/Model/CfgTagModelTest.php deleted file mode 100644 index 523eba57..00000000 --- a/tests/Model/CfgTagModelTest.php +++ /dev/null @@ -1,66 +0,0 @@ -getContainerWithContaoConfiguration(); - $container->set('contao.framework', $this->mockContaoFramework([Database::class => $this->getDatabaseMock()])); - System::setContainer($container); - $options = CfgTagModel::getSourcesAsOptions($this->getDataContainerMock()); - $this->assertSame([1 => 'aoptions', 0 => 'options'], $options); - } - - public function getDatabaseMock() - { - $databaseAdapter = $this->mockAdapter([ - 'getInstance', - 'prepare', - 'execute', - 'fetchEach', - ]); - $databaseAdapter->method('getInstance')->willReturnSelf(); - $databaseAdapter->method('prepare')->withAnyParameters()->willReturnSelf(); - $databaseAdapter->method('fetchEach')->willReturn(['options', 'aoptions']); - $databaseAdapter->method('execute')->willReturnSelf(); - - return $databaseAdapter; - } - - /** - * @return DataContainer|\PHPUnit_Framework_MockObject_MockObject - */ - public function getDataContainerMock() - { - return $this->createMock(DataContainer::class); - } - - public function testFindAllBySource() - { - $model = $this->createMock(CfgTagModel::class); - $adapter = $this->mockAdapter(['findBy']); - $adapter->method('findBy')->willReturn($model); - - $cfgTagModel = new CfgTagModel($this->mockContaoFramework([CfgTagModel::class => $adapter])); - $result = $cfgTagModel->findAllBySource('source'); - $this->assertInstanceOf(CfgTagModel::class, $result); - - $cfgTagModel = new CfgTagModel($this->mockContaoFramework([CfgTagModel::class => null])); - $result = $cfgTagModel->findAllBySource('source'); - $this->assertNull($result); - } -} diff --git a/tests/Model/ModelUtilTest.php b/tests/Model/ModelUtilTest.php deleted file mode 100644 index 3110a992..00000000 --- a/tests/Model/ModelUtilTest.php +++ /dev/null @@ -1,355 +0,0 @@ -createMock(DcaUtil::class); - $framework = $parameters['framework'] ?? $this->prepareFramework(); - $session = $parameters['session'] ?? $this->createMock(SessionInterface::class); - $requestStack = $parameters['requestStack'] ?? $this->createMock(RequestStack::class); - $formUtil = $parameters['formUtil'] ?? $this->createMock(FormUtil::class); - $kernelBundles = $parameters['kernelBundles'] ?? []; - - return new ModelUtil($dcaUtil, $framework, $session, $requestStack, $formUtil, $kernelBundles); - } - - public function testInstantiation() - { - $util = $this->getTestInstance(); - $this->assertInstanceOf(ModelUtil::class, $util); - } - - public function testFindRootParentRecursively() - { - $modelAdapter = $this->mockAdapter( - [ - 'getClassFromTable', - ] - ); - $modelAdapter->method('getClassFromTable')->with($this->anything())->willReturnCallback( - function ($table) { - switch ($table) { - case 'tl_content': - return ContentModel::class; - - case 'tl_null_class': - return 'Huh\Null\Class\Nullclass'; - - case 'tl_cfg_tag': - return CfgTagModel::class; - - case 'null': - return null; - - default: - return null; - } - } - ); - $contentModelAdapter = $this->mockAdapter( - [ - 'findByPk', - ] - ); - $contentModelAdapter->method('findByPk')->willReturn($this->getModel(true)); - $framework = $this->mockContaoFramework([Model::class => $modelAdapter, ContentModel::class => $contentModelAdapter]); - $util = $this->getTestInstance(['framework' => $framework]); - - $result = $util->findRootParentRecursively('id', 'tl_content', $this->getModel()); - $this->assertInstanceOf(\Contao\Model::class, $result); - } - - public function testFindParentsRecursively() - { - $modelAdapter = $this->mockAdapter( - [ - 'getClassFromTable', - ] - ); - $modelAdapter->method('getClassFromTable')->with($this->anything())->willReturnCallback( - function ($table) { - switch ($table) { - case 'tl_content': - return ContentModel::class; - - case 'tl_null_class': - return 'Huh\Null\Class\Nullclass'; - - case 'tl_cfg_tag': - return CfgTagModel::class; - - case 'null': - return null; - - default: - return null; - } - } - ); - $contentModelAdapter = $this->mockAdapter( - [ - 'findByPk', - ] - ); - $contentModelAdapter->method('findByPk')->willReturn($this->getModel(true)); - $contaoFramework = $this->mockContaoFramework([Model::class => $modelAdapter, ContentModel::class => $contentModelAdapter]); - - $util = $this->getTestInstance(['framework' => $contaoFramework]); - - $result = $util->findParentsRecursively('id', 'tl_content', $this->getModel()); - $this->assertInstanceOf(MockObject::class, $result[0]); - - $result = $util->findParentsRecursively('id', 'tl_content', $this->getModel(true)); - $this->assertSame([], $result); - } - - public function skipTestFindModulePages() - { - $this->count = 0; - - /** @var ModuleModel $module */ - $module = $this->mockClassWithProperties(ModuleModel::class, ['id' => 1]); - $util = $this->getTestInstance(); - $this->assertCount(1, $util->findModulePages($module, false, false)); - - $util = $this->getTestInstance(['kernelBundles' => ['blocks' => 'blocks']]); - $this->assertCount(1, $util->findModulePages($module, false, false)); - - $this->assertSame( - $util->findModulePages($module, false, false), - $util->findModulePages($module, false, true) - ); - - /** @var ModuleModel $module */ - $module = $this->mockClassWithProperties(ModuleModel::class, ['id' => 2]); - $util = $this->getTestInstance(); - $this->assertCount(2, $util->findModulePages($module, false, false)); - $util = $this->getTestInstance(['kernelBundles' => ['blocks' => 'blocks']]); - $this->count = 0; - $this->assertCount(4, $util->findModulePages($module, false, false)); - $this->count = 0; - $this->assertCount(4, $util->findModulePages($module, true, false)); - $this->count = 0; - $this->assertCount(4, $util->findModulePages($module, true, true)); - } - - public function prepareFramework() - { - $modelAdapter = $this->mockAdapter( - [ - 'getClassFromTable', - ] - ); - $modelAdapter->method('getClassFromTable')->with($this->anything())->willReturnCallback( - function ($table) { - switch ($table) { - case 'tl_content': - return ContentModel::class; - - case 'tl_null_class': - return 'Huh\Null\Class\Nullclass'; - - case 'tl_cfg_tag': - return CfgTagModel::class; - - case 'null': - return null; - - default: - return null; - } - } - ); - - $contentModel = $this->mockClassWithProperties( - ContentModel::class, - [ - 'id' => 5, - 'alias' => 'alias', - 'pid' => 3, - ] - ); - $contentModelAdapter = $this->createContentModelAdapter($contentModel); - - $pageModelAdapter = $this->mockAdapter(['findMultipleByIds']); - $pageModelAdapter->method('findMultipleByIds')->willReturnArgument(0); - - $controllerAdapter = $this->mockAdapter(['loadDataContainer']); - $controllerAdapter->method('loadDataContainer')->willReturn(null); - - $framework = $this->mockContaoFramework( - [ - Controller::class => $controllerAdapter, - Model::class => $modelAdapter, - ContentModel::class => $contentModelAdapter, - CfgTagModel::class => null, - PageModel::class => $pageModelAdapter, - ] - ); - - $framework->method('createInstance')->willReturnCallback( - function ($classType) { - switch ($classType) { - case Database::class: - $db = $this->getMockBuilder(Database::class)->disableOriginalConstructor()->setMethods(['prepare', 'execute'])->getMock(); - $db->method('prepare')->willReturnSelf(); - $db->method('execute')->willReturnCallback( - function ($id) { - $result = $this->createMock(Database\Result::class); - - switch ($id) { - case 0: - default: - $result->method('count')->willReturn(0); - - break; - - case 1: - $result->method('count')->willReturn(1); - $result->method('fetchEach')->willReturn([1]); - - break; - - case 2: - if ($this->count > 0) { - $result->method('count')->willReturn(2); - $result->method('fetchEach')->willReturn([4, 5]); - } else { - $result->method('count')->willReturn(2); - $result->method('fetchEach')->willReturn([1, 2]); - } - - break; - } - ++$this->count; - - return $result; - } - ); - - return $db; - - break; - } - } - ); - - return $framework; - } - - public function createContentModelAdapter($contentModel) - { - $contentModelAdapter = $this->mockAdapter( - [ - 'findByPk', - 'findBy', - 'findOneBy', - ] - ); - $contentModelAdapter->method('findByPk')->with($this->anything(), $this->anything())->willReturnCallback( - function ($pk, $option) use ($contentModel) { - switch ($pk) { - case 'alias': - return $contentModel; - - case 5: - return $contentModel; - - default: - return null; - } - } - ); - $contentModelAdapter->method('findBy')->with($this->anything(), $this->anything(), $this->anything())->willReturnCallback( - function ($columns, $values, $options = []) use ($contentModel) { - if ('id' === $columns[0] && 5 === (int) $values[0]) { - return $contentModel; - } - - if ('pid' === $columns[0] && 3 === (int) $values[0]) { - $collection = new Model\Collection([$contentModel], 'tl_content'); - - return $collection; - } - - return null; - } - ); - - $model = $this->createMock(ContentModel::class); - $contentModelAdapter->method('findOneBy')->willReturn($model); - - return $contentModelAdapter; - } - - /** - * @return \Contao\Model|\PHPUnit_Framework_MockObject_MockObject - */ - public function getModel($idNull = false) - { - if ($idNull) { - return $this->mockClassWithProperties(Model::class, ['id' => null]); - } - - return $this->mockClassWithProperties(Model::class, ['id' => 5]); - } - - /** - * @param ContaoFramework $framework - * - * @return ContainerBuilder|ContainerInterface - */ - protected function getContainerMock(ContainerBuilder $container = null, $framework = null) - { - if (!$container) { - $container = $this->getContainerWithContaoConfiguration(); - } - - if (!$framework) { - $framework = $this->prepareFramework(); - } - $container->set('contao.framework', $framework); - $container->set('huh.utils.dca', new DcaUtil($container)); - - return $container; - } - - protected function createModelUtilMock() - { - $containerUtilMock = $this->createMock(ContainerUtil::class); - $framwork = $this->prepareFramework(); - - return new ModelUtil($framwork, $containerUtilMock); - } -} diff --git a/tests/ModelMockTrait.php b/tests/ModelMockTrait.php index bdf054a1..d65dcc05 100644 --- a/tests/ModelMockTrait.php +++ b/tests/ModelMockTrait.php @@ -8,12 +8,19 @@ namespace HeimrichHannot\UtilsBundle\Tests; +use Contao\Model; use PHPUnit\Framework\MockObject\MockObject; trait ModelMockTrait { /** * Mocks a class with magic properties. + * + * @template T of Model + * + * @param class-string $class + * + * @return MockObject&T */ protected function mockModelObject(string $class, array $properties = []): MockObject { diff --git a/tests/ModuleUtil/ModuleUtilTest.php b/tests/ModuleUtil/ModuleUtilTest.php deleted file mode 100644 index 18ab60fc..00000000 --- a/tests/ModuleUtil/ModuleUtilTest.php +++ /dev/null @@ -1,69 +0,0 @@ -mockAdapter(['findClass']); - $moduleMock->method('findClass')->willReturnCallback(function ($type) { - return ''; - }); - - $framework = $this->mockContaoFramework([ - Module::class => $moduleMock, - ]); - - return new ModuleUtil($framework); - } - - public function testIsSubModuleOf() - { - $class1 = new class() extends ModuleNavigation { - public function __construct() - { - } - }; - - $GLOBALS['FE_MOD']['test']['navigation_test'] = \get_class($class1); - $GLOBALS['FE_MOD']['navigation']['navigation'] = ModuleNavigation::class; - - $moduleUtil = $this->createModuleUtil(); - $this->assertFalse($moduleUtil->isSubModuleOf('a', 'b')); - - $this->assertTrue($moduleUtil->isSubModuleOf($class1, ModuleNavigation::class)); - $this->assertTrue($moduleUtil->isSubModuleOf(\get_class($class1), ModuleNavigation::class)); - $this->assertTrue($moduleUtil->isSubModuleOf($class1, 'navigation')); - $this->assertTrue($moduleUtil->isSubModuleOf(\get_class($class1), 'navigation')); - - $this->assertFalse($moduleUtil->isSubModuleOf($class1, 'navigation_a')); - $this->assertFalse($moduleUtil->isSubModuleOf(\get_class($class1), 'navigation_a')); - - $this->assertTrue($moduleUtil->isSubModuleOf('navigation_test', ModuleNavigation::class)); - $this->assertTrue($moduleUtil->isSubModuleOf('navigation_test', 'navigation')); - - $moduleModelMock = $this->mockModelObject(ModuleModel::class, ['type' => 'navigation_test']); - $this->assertTrue($moduleUtil->isSubModuleOf($moduleModelMock, ModuleNavigation::class)); - $this->assertTrue($moduleUtil->isSubModuleOf($moduleModelMock, 'navigation')); - - $moduleModelMock = $this->mockModelObject(ModuleModel::class, ['type' => 'navigation']); - $this->assertTrue($moduleUtil->isSubModuleOf('navigation_test', $moduleModelMock)); - $this->assertTrue($moduleUtil->isSubModuleOf($class1, $moduleModelMock)); - } -} diff --git a/tests/Pagination/TextualPaginationTest.php b/tests/Pagination/TextualPaginationTest.php deleted file mode 100644 index 3dce9172..00000000 --- a/tests/Pagination/TextualPaginationTest.php +++ /dev/null @@ -1,163 +0,0 @@ -getFixturesDir().'/vendor/contao/core-bundle/Resources/contao', - ])); - $this->container = $this->getContainerWithContaoConfiguration(); - $this->container->set('contao.resource_finder', $finder); - $this->container->setParameter('kernel.debug', true); - $this->container->setParameter('kernel.default_locale', 'de'); - $this->container->set('translator', new Translator('en')); - $this->kernel = $this->createMock(Kernel::class); - $this->kernel->method('getContainer')->willReturn($this->container); - $this->container->set('kernel', $this->kernel); - - if (!\function_exists('ampersand')) { - include_once __DIR__.'/../../vendor/contao/core-bundle/src/Resources/contao/helper/functions.php'; - } - } - - /** - * Tests the object instantiation. - */ - public function testCanBeInstantiated() - { - System::setContainer($this->container); - - $pagination = new TextualPagination([], '/test', 10, 10); - $this->assertInstanceOf('HeimrichHannot\UtilsBundle\Pagination\TextualPagination', $pagination); - } - - /** - * Test getItemsAsArray() without teasers. - */ - public function testGetItemsAsArrayWithoutTeasers() - { - System::setContainer($this->container); - - $pagination = new TextualPagination([], '', 10, 10); - $this->assertEmpty($pagination->getItemsAsArray()); - } - - /** - * Test getItemsAsArray() without teasers but single page url. - */ - public function testGetItemsAsArrayWithoutTeasersAndSinglePageUrl() - { - System::setContainer($this->container); - - $pagination = new TextualPagination([], '/test', 10, 10); - $result = $pagination->getItemsAsArray(); - $this->assertNotEmpty($result); - $this->assertSame([ - [ - 'page' => 'singlePage', - 'href' => '/test', - 'title' => null, - 'text' => 'Read on one page', - ], - ], $result); - } - - /** - * Test getItemsAsArray() on first page. - */ - public function testGetItemsOnFirstPage() - { - System::setContainer($this->container); - - $pagination = new TextualPagination([ - 1 => 'Teaser page 1', - 2 => 'Teaser page 2', - 3 => 'Teaser page 3', - 4 => 'Teaser page 4', - 5 => 'Teaser page 5', - ], '/test', 10, 2); - $result = $pagination->getItemsAsArray(); - $this->assertNotEmpty($result); - - $this->assertSame([ - [ - 'page' => 1, - 'href' => null, - 'title' => null, - 'text' => 'Teaser page 1', - ], - [ - 'page' => 2, - 'href' => '?page=2', - 'title' => 'Go to page 2', - 'text' => 'Teaser page 2', - ], - [ - 'page' => 3, - 'href' => '?page=3', - 'title' => 'Go to page 3', - 'text' => 'Teaser page 3', - ], - [ - 'page' => 4, - 'href' => '?page=4', - 'title' => 'Go to page 4', - 'text' => 'Teaser page 4', - ], - [ - 'page' => 5, - 'href' => '?page=5', - 'title' => 'Go to page 5', - 'text' => 'Teaser page 5', - ], - [ - 'page' => 'singlePage', - 'href' => '/test', - 'title' => null, - 'text' => 'Read on one page', - ], - ], $result); - } - - protected function getFixturesDir(): string - { - return __DIR__.\DIRECTORY_SEPARATOR.'../..'.\DIRECTORY_SEPARATOR.'Fixtures'; - } -} diff --git a/tests/Request/CurlRequestUtilTest.php b/tests/Request/CurlRequestUtilTest.php deleted file mode 100644 index 3718dde0..00000000 --- a/tests/Request/CurlRequestUtilTest.php +++ /dev/null @@ -1,205 +0,0 @@ -mockContaoFramework(), $this->getContainerWithContaoConfiguration()); - $this->assertInstanceOf(CurlRequestUtil::class, $curl); - } - - public function testRequest() - { - $curl = new CurlRequestUtil($this->mockContaoFramework(), $this->getContainerWithContaoConfiguration()); - - $curl->setHandle($this->createNewHandle()); - $result = $curl->request('https://www.heimrich-hannot.de', [], true); - $this->assertArrayHasKey(0, $result); - $this->assertArrayHasKey(1, $result); - $this->assertSame(200, $result[0]['http_code']); - $this->assertSame('text/html; charset=utf-8', $result[0]['Content-Type']); - $this->assertSame('Apache', $result[0]['Server']); - - $curl->setHandle($this->createNewHandle()); - $result = $curl->request('https://www.heimrich-hannot.de'); - $this->assertStringStartsWith('', $result); - - $curl->setHandle($this->createNewHandle()); - Config::set('hpProxy', 'http://proxy:80'); - $result = $curl->request('https://www.heimrich-hannot.de'); - $this->assertStringStartsWith('', $result); - - $handle = $this->createNewHandle(); - $handle->setResponseError(true); - $curl->setHandle($handle); - $this->assertFalse($result = $curl->request('https://www.heimrich-hannot.de', [], true)); - } - - public function testPostRequest() - { - $curl = new CurlRequestUtil($this->mockContaoFramework(), $this->getContainerWithContaoConfiguration()); - - $curl->setHandle($this->createNewHandle()); - $result = $curl->postRequest('https://www.heimrich-hannot.de', [\CURLINFO_CONTENT_TYPE => 'text/html; charset=utf-8'], ['test' => 'test'], true); - $this->assertArrayHasKey(0, $result); - $this->assertArrayHasKey(1, $result); - $this->assertSame(200, $result[0]['http_code']); - $this->assertSame('text/html; charset=utf-8', $result[0]['Content-Type']); - $this->assertSame('Apache', $result[0]['Server']); - - $curl->setHandle($this->createNewHandle()); - $result = $curl->postRequest('https://www.heimrich-hannot.de'); - $this->assertStringStartsWith('', $result); - - $curl->setHandle($this->createNewHandle()); - Config::set('hpProxy', 'http://proxy:80'); - $result = $curl->postRequest('https://www.heimrich-hannot.de'); - $this->assertStringStartsWith('', $result); - - $handle = $this->createNewHandle(); - $handle->setResponseError(true); - $curl->setHandle($handle); - $this->assertFalse($result = $curl->postRequest('https://www.heimrich-hannot.de', [], [], true)); - } - - public function testRecursiveGetRequest() - { - $curl = new CurlRequestUtil($this->mockContaoFramework(), $this->getContainerWithContaoConfiguration()); - $curl->setHandle($this->createNewHandle()); - $result = $curl->recursiveGetRequest(1, function ($result, $url, $requestHeaders, $returnResponseHeaders, $maxRecursionCount) { - return true; - }, 'https://www.heimrich-hannot.de'); - $this->assertStringStartsWith('', $result); - - $curl = new CurlRequestUtil($this->mockContaoFramework(), $this->getContainerWithContaoConfiguration()); - $curl->setHandle($this->createNewHandle()); - $result = $curl->recursiveGetRequest(1, function ($result, $url, $requestHeaders, $returnResponseHeaders, $maxRecursionCount) { - return false; - }, 'https://www.heimrich-hannot.de'); - $this->assertStringStartsWith('', $result); - - $curl = new CurlRequestUtil($this->mockContaoFramework(), $this->getContainerWithContaoConfiguration()); - $curl->setHandle($this->createNewHandle()); - $result = $curl->recursiveGetRequest(1, function ($result, $url, $requestHeaders, $returnResponseHeaders, $maxRecursionCount) { - return true; - }, 'https://www.heimrich-hannot.de', [], true); - $this->assertSame(2, \count($result)); - $this->assertStringStartsWith('', $result[1]); - - $curl = new CurlRequestUtil($this->mockContaoFramework(), $this->getContainerWithContaoConfiguration()); - $curl->setHandle($this->createNewHandle()); - $result = $curl->recursiveGetRequest(3, function ($result, $url, $requestHeaders, $returnResponseHeaders, $maxRecursionCount) { - return false; - }, 'https://www.heimrich-hannot.de'); - $this->assertStringStartsWith('', $result); - - $curl = new CurlRequestUtil($this->mockContaoFramework(), $this->getContainerWithContaoConfiguration()); - $curl->setHandle($this->createNewHandle()); - $result = $curl->recursiveGetRequest(3, function ($result, $url, $requestHeaders, $returnResponseHeaders, $maxRecursionCount, $i) { - if (2 == $i) { - return true; - } - - return false; - }, 'https://www.heimrich-hannot.de'); - $this->assertStringStartsWith('', $result); - } - - public function testRecursivelyPostRequest() - { - $curl = new CurlRequestUtil($this->mockContaoFramework(), $this->getContainerWithContaoConfiguration()); - $curl->setHandle($this->createNewHandle()); - $result = $curl->recursivePostRequest(1, function ($result, $url, $requestHeaders, $returnResponseHeaders, $maxRecursionCount) { - return true; - }, 'https://www.heimrich-hannot.de'); - $this->assertStringStartsWith('', $result); - - $curl = new CurlRequestUtil($this->mockContaoFramework(), $this->getContainerWithContaoConfiguration()); - $curl->setHandle($this->createNewHandle()); - $result = $curl->recursivePostRequest(1, function ($result, $url, $requestHeaders, $returnResponseHeaders, $maxRecursionCount) { - return false; - }, 'https://www.heimrich-hannot.de'); - $this->assertStringStartsWith('', $result); - - $curl = new CurlRequestUtil($this->mockContaoFramework(), $this->getContainerWithContaoConfiguration()); - $curl->setHandle($this->createNewHandle()); - $result = $curl->recursivePostRequest(1, function ($result, $url, $requestHeaders, $returnResponseHeaders, $maxRecursionCount) { - return true; - }, 'https://www.heimrich-hannot.de', [], [], true); - $this->assertSame(2, \count($result)); - $this->assertStringStartsWith('', $result[1]); - - $curl = new CurlRequestUtil($this->mockContaoFramework(), $this->getContainerWithContaoConfiguration()); - $curl->setHandle($this->createNewHandle()); - $result = $curl->recursivePostRequest(3, function ($result, $url, $requestHeaders, $returnResponseHeaders, $maxRecursionCount) { - return false; - }, 'https://www.heimrich-hannot.de'); - $this->assertStringStartsWith('', $result); - - $curl = new CurlRequestUtil($this->mockContaoFramework(), $this->getContainerWithContaoConfiguration()); - $curl->setHandle($this->createNewHandle()); - $result = $curl->recursivePostRequest(3, function ($result, $url, $requestHeaders, $returnResponseHeaders, $maxRecursionCount, $i) { - if (2 == $i) { - return true; - } - - return false; - }, 'https://www.heimrich-hannot.de'); - $this->assertStringStartsWith('', $result); - } - - public function testPrepareHeaders() - { - $curl = new CurlRequestUtil($this->mockContaoFramework(), $this->getContainerWithContaoConfiguration()); - $curl->setHandle($this->createNewHandle()); - $result = $curl->request('https://www.heimrich-hannot.de', [ - 'User-Agent', 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.5)', - 'Accept', 'text/html; charset=utf-8', ], true); - $this->assertSame('text/html; charset=utf-8', $result[0]['Content-Type']); - } - - public function testPrepareHeaderArrayForPrint() - { - $curl = new CurlRequestUtil($this->mockContaoFramework(), $this->getContainerWithContaoConfiguration()); - $result = $curl->prepareHeaderArrayForPrint([\CURLINFO_CONTENT_TYPE => 'text/plain', \CURLINFO_CONTENT_LENGTH_DOWNLOAD => '100']); - $this->assertSame(\CURLINFO_CONTENT_TYPE.': text/plain'.\PHP_EOL.\CURLINFO_CONTENT_LENGTH_DOWNLOAD.': 100', $result); - - $result = $curl->prepareHeaderArrayForPrint([]); - $this->assertSame('', $result); - } - - public function testGetSetHandle() - { - $curl = new CurlRequestUtil($this->mockContaoFramework(), $this->getContainerWithContaoConfiguration()); - $this->assertNull($curl->getHandle()); - $curl->postRequest('https://heimrich-hannot.de'); - $this->assertNull($curl->getHandle()); - $curl->setHandle($this->createNewHandle()); - $this->assertInstanceOf(StubCurlRequest::class, $curl->getHandle()); - } - - protected function createNewHandle() - { - $handle = new StubCurlRequest(); - $handle->setResponseHeader([ - 'http_code' => 200, - 'Content-Type' => 'text/html; charset=utf-8', - 'Server' => 'Apache', - ]); - $handle->setResponseBody(''); - - return $handle; - } -} diff --git a/tests/Request/StubCurlRequest.php b/tests/Request/StubCurlRequest.php deleted file mode 100644 index df6d6ee7..00000000 --- a/tests/Request/StubCurlRequest.php +++ /dev/null @@ -1,136 +0,0 @@ -url = $url; - - return $this; - } - - public function setOption($name, $value): HttpRequestInterface - { - switch ($name) { - case CURLOPT_CUSTOMREQUEST: - $this->type = $value; - - break; - - case CURLOPT_HEADER: - $this->responseHeader = $value; - - break; - } - - return $this; - } - - public function execute() - { - if ($this->hasError) { - $this->header['http_code'] = 0; - - return false; - } - - if ($this->responseHeader) { - return $this->parse_array_to_headers($this->header).$this->body; - } - - return $this->body; - } - - public function getInfo($name) - { - switch ($name) { - case CURLOPT_CUSTOMREQUEST: - return $this->type; - - case CURLINFO_HTTP_CODE: - return $this->header['http_code']; - - case CURLINFO_CONTENT_TYPE: - return 'text/html'; - } - } - - public function close() - { - // TODO: Implement close() method. - } - - /** - * @return StubCurlRequest - */ - public function setResponseHeader(array $header) - { - $this->header = $header; - - return $this; - } - - /** - * @return StubCurlRequest - */ - public function setResponseBody(string $body) - { - $this->body = $body; - - return $this; - } - - /** - * @return StubCurlRequest - */ - public function setResponseError(bool $hasError) - { - $this->hasError = $hasError; - - return $this; - } - - protected function parse_array_to_headers(array $headers) - { - $result = []; - $delimiter = "\r\n"; - - foreach ($headers as $name => $value) { - $result[] = sprintf('%s: %s', $name, $value); - } - - return implode($delimiter, $result).$delimiter.$delimiter; - } -} diff --git a/tests/Security/CodeUtilTest.php b/tests/Security/CodeUtilTest.php deleted file mode 100644 index cfece632..00000000 --- a/tests/Security/CodeUtilTest.php +++ /dev/null @@ -1,86 +0,0 @@ -mockContaoFramework()); - $container->set('huh.utils.string', $stringUtil); - - System::setContainer($container); - } - - public function testInstantiation() - { - $util = new CodeUtil($this->mockContaoFramework()); - $this->assertInstanceOf(CodeUtil::class, $util); - } - - public function testGenerate() - { - $codeUtil = new CodeUtil($this->mockContaoFramework()); - $code = $codeUtil::generate(8); - $this->assertSame(8, \strlen($code)); - - $code = $codeUtil::generate(16); - $this->assertSame(16, \strlen($code)); - - $code = $codeUtil::generate(16, true, ['asdeg35g8*']); - $this->assertSame(16, \strlen($code)); - - $code = $codeUtil::generate(16, true, ['capitalLetters']); - $this->assertSame($code, preg_replace('/^[a-z]+$/', '', $code)); - $this->assertSame(16, \strlen($code)); - $this->assertSame('', preg_replace('/^[A-Z]+$/', '', $code)); - - $code = $codeUtil::generate(16, true, ['smallLetters']); - $this->assertSame($code, preg_replace('/^[A-Z]+$/', '', $code)); - $this->assertSame(16, \strlen($code)); - $this->assertSame('', preg_replace('/^[a-z]+$/', '', $code)); - - $code = $codeUtil::generate(16, true, ['numbers']); - $this->assertSame($code, preg_replace('/^[A-Z]+$/', '', $code)); - $this->assertSame(16, \strlen($code)); - $this->assertSame('', preg_replace('/^[0-9]+$/', '', $code)); - - $code = $codeUtil::generate(16, true, ['capitalLetters', 'specialChars'], null, '$%&?!'); - $this->assertSame(16, \strlen($code)); - } - - public function testGenerateRegex() - { - $container = System::getContainer(); - - $stringUtil = $this->mockAdapter(['random']); - $stringUtil->method('random')->willReturn('Aa2'); - $container->set('huh.utils.string', $stringUtil); - - System::setContainer($container); - - $codeUtil = new CodeUtil($this->mockContaoFramework()); - $code = $codeUtil::generate(16, false, ['capitalLetters']); - $this->assertGreaterThan(16, \strlen($code)); - $this->assertSame(1, preg_match('@Aa2@', $code)); - - $code = $codeUtil::generate(16, false, ['capitalLetters', 'specialChars']); - $this->assertGreaterThan(16, \strlen($code)); - $this->assertSame(1, preg_match('@Aa2@', $code)); - } -} diff --git a/tests/Security/EncryptionUtilTest.php b/tests/Security/EncryptionUtilTest.php deleted file mode 100644 index 6446ae93..00000000 --- a/tests/Security/EncryptionUtilTest.php +++ /dev/null @@ -1,43 +0,0 @@ -getContainerWithContaoConfiguration(); - $container->setParameter('secret', Config::class); - System::setContainer($container); - } - - public function testInstantiation() - { - $encrypt = new EncryptionUtil($this->mockContaoFramework()); - $this->assertInstanceOf(EncryptionUtil::class, $encrypt); - } - - public function testEncrypt() - { - $encrypt = new EncryptionUtil($this->mockContaoFramework()); - $result = $encrypt->encrypt('plain', 'key', 'cypher'); - $this->assertFalse($result); - - $result = $encrypt->encrypt('plain'); - $this->assertSame(openssl_encrypt('plain', 'aes-256-ctr', System::getContainer()->getParameter('secret'), 0, base64_decode($result[1], true)), $result[0]); - $this->assertSame('plain', $encrypt->decrypt($result[0], $result[1])); - } -} diff --git a/tests/String/AnonymizerUtilTest.php b/tests/String/AnonymizerUtilTest.php deleted file mode 100644 index d034ebff..00000000 --- a/tests/String/AnonymizerUtilTest.php +++ /dev/null @@ -1,28 +0,0 @@ -getInstance(); - $this->assertSame('max.mus*******@example.org', $instance->anonymizeEmail('max.mustermann@example.org')); - $this->assertSame('digi****@heimrich-hannot.de', $instance->anonymizeEmail('digitales@heimrich-hannot.de')); - $this->assertSame('dasIstKeinE-Mail', $instance->anonymizeEmail('dasIstKeinE-Mail')); - } -} diff --git a/tests/Template/TemplateUtilTest.php b/tests/Template/TemplateUtilTest.php deleted file mode 100644 index 3fe9b6d4..00000000 --- a/tests/Template/TemplateUtilTest.php +++ /dev/null @@ -1,166 +0,0 @@ -mkdir($this->getTempDir()); - } - - /** - * @return TemplateUtil - */ - public function getTemplateUtilMock(ContainerInterface $container = null) - { - $util = new TemplateUtil($this->getContainerMock()); - - return $util; - } - - public function testInstantiation() - { - $util = $this->getTemplateUtilMock(); - $this->assertInstanceOf(TemplateUtil::class, $util); - } - - public function testGetTwigTemplate() - { - $container = $this->getContainerMock(); - $util = new TemplateUtil($container); - $util->getAllTemplates(); - - if (!\defined('TL_MODE')) { - \define('TL_MODE', 'FE'); - } - - $finder = new ResourceFinder( - ([ - $this->getFixturesDir(), - ]) - ); - - global $objPage; - - $objPage = new \stdClass(); - $objPage->templateGroup = ''; - - $this->assertSame($this->getFixturesDir().'/templates/test.html.twig', $util->getTemplate('test')); - } - - public function testGetTwigTemplateInThemePath() - { - if (!\defined('TL_MODE')) { - \define('TL_MODE', 'FE'); - } - - $container = $this->getContainerMock(); - - $containerUtil = $this->createMock(ContainerUtil::class); - $containerUtil->method('getProjectDir')->willReturn($this->getFixturesDir()); - $containerUtil->method('isFrontend')->willReturn(true); - $container->set('huh.utils.container', $containerUtil); - - $util = new TemplateUtil($container); - $util->getAllTemplates(); - - global $objPage; - $objPage = new \stdClass(); - $objPage->templateGroup = 'templates/myTheme'; - - $this->assertSame($this->getFixturesDir().'/templates/myTheme/test1.html.twig', $util->getTemplate('test1')); - } - - public function skipTestRemoveTemplateComment() - { - $container = $this->getContainerMock(); - $util = new TemplateUtil($container); - System::setContainer($container); - - $this->assertEmpty($util->removeTemplateComment(null)); - $this->assertSame( - '', - $util->removeTemplateComment( - ' - ' - ) - ); - } - - public function skipTestIsTemplatePartEmpty() - { - $util = $this->getTemplateUtilMock(); - - $this->assertTrue($util->isTemplatePartEmpty(' ')); - $this->assertTrue( - $util->isTemplatePartEmpty( - ' - - - -' - ) - ); - $this->assertFalse($util->isTemplatePartEmpty('
')); - $this->assertTrue($util->isTemplatePartEmpty(null)); - } - - protected function getContainerMock(ContainerBuilder $container = null) - { - if (!$container) { - $container = $this->getContainerWithContaoConfiguration(); - } - - if (!$container->has('kernel')) { - $kernel = $this->createMock(KernelInterface::class); - $kernel->method('getCacheDir')->willReturn($this->getTempDir()); - $kernel->method('isDebug')->willReturn(false); - $container->setParameter('kernel.debug', true); - $container->set('kernel', $kernel); - } - - $container->set('contao.resource_finder', new ResourceFinder([$this->getFixturesDir()])); - - if (!$container->has('request_stack')) { - $request = new Request(); - $requestStack = $this->createMock(RequestStack::class); - $requestStack->method('getCurrentRequest')->willReturn($request); - $container->set('request_stack', $requestStack); - } - - $container->setParameter('kernel.project_dir', $this->getFixturesDir()); - - if (!$container->has('huh.utils.container')) { - $containerUtil = $this->createMock(ContainerUtil::class); - $containerUtil->method('getProjectDir')->willReturn($this->getFixturesDir()); - $container->set('huh.utils.container', $containerUtil); - } - - return $container; - } -} diff --git a/tests/Twig/StringExtensionTest.php b/tests/Twig/StringExtensionTest.php deleted file mode 100644 index 5ebb1da7..00000000 --- a/tests/Twig/StringExtensionTest.php +++ /dev/null @@ -1,56 +0,0 @@ -mockContaoFramework(); - } - $anonymizerUtil = new AnonymizerUtil(); - $instance = new StringExtension($anonymizerUtil, $parameter['framework']); - - return $instance; - } - - public function testGetFilters() - { - $instance = $this->createInstance(); - $filters = $instance->getFilters(); - $this->assertInstanceOf(TwigFilter::class, $filters[0]); - } - - public function testAnonymizeEmail() - { - $instance = $this->createInstance(); - $this->assertSame('max.mus*******@example.org', $instance->anonymizeEmail('max.mustermann@example.org')); - $this->assertSame('digi****@heimrich-hannot.de', $instance->anonymizeEmail('digitales@heimrich-hannot.de')); - $this->assertSame('dasIstKeinE-Mail', $instance->anonymizeEmail('dasIstKeinE-Mail')); - } - - public function testReplaceInsertTag() - { - $controller = $this->mockAdapter(['replaceInsertTags']); - $controller->expects($this->once())->method('replaceInsertTags')->willReturnArgument(0); - $framework = $this->mockContaoFramework([ - Controller::class => $controller, - ]); - $framework->expects($this->once())->method('initialize'); - $instance = $this->createInstance(['framework' => $framework]); - $this->assertSame('No inserttag', $instance->replaceInsertTag('No inserttag')); - } -} diff --git a/tests/Url/UrlUtilTest.php b/tests/Url/UrlUtilTest.php deleted file mode 100644 index 6fb40631..00000000 --- a/tests/Url/UrlUtilTest.php +++ /dev/null @@ -1,299 +0,0 @@ -getContainerWithContaoConfiguration(); - $container->set('contao.framework', $this->mockContaoFramework()); - $container->set('request_stack', $this->createRequestStackMock()); - - $jumpToPage = $this->mockClassWithProperties(PageModel::class, ['id' => 2]); - $utilsModelAdapter = $this->mockAdapter(['findModelInstanceByPk']); - $utilsModelAdapter->method('findModelInstanceByPk')->willReturn($jumpToPage); - $container->set('huh.utils.model', $utilsModelAdapter); - $container->set('router', $this->createRouterMock()); - System::setContainer($container); - - if (!\function_exists('ampersand')) { - include_once __DIR__.'/../../vendor/contao/core-bundle/src/Resources/contao/helper/functions.php'; - } - } - - public function createTestInstance(array $parameter = []) - { - if (!isset($parameter['framework'])) { - $parameter['framework'] = $this->mockContaoFramework(); - } - - $requestStack = $parameter['requestStack'] ?? $this->createMock(RequestStack::class); - - /** @var RequestUtil|MockObject $requestUtil */ - $requestUtil = $this->createMock(RequestUtil::class); - - $instance = new UrlUtil($parameter['framework'], $requestUtil, $requestStack); - - return $instance; - } - - public function testGetCurrentUrl() - { - $urlUtil = $this->createTestInstance(); - - $url = $urlUtil->getCurrentUrl(); - $url2 = $urlUtil->getCurrentUrl(['skipParams' => false]); - $urlWithoutParams = $urlUtil->getCurrentUrl(['skipParams' => true]); - - $this->assertSame('http://localhost', $urlWithoutParams); - $this->assertSame('http://localhost?answer=12', $url); - $this->assertSame('http://localhost?answer=12', $url2); - } - - public function testGetJumpToPageObject() - { - $objPage = $this->mockClassWithProperties(Model::class, ['id' => 2]); - $GLOBALS['objPage'] = $objPage; - - $urlUtil = $this->createTestInstance(); - - $jumpToPage = $urlUtil->getJumpToPageObject(12); - - $this->assertInstanceOf(PageModel::class, $jumpToPage); - - $container = System::getContainer(); - $utilsModelAdapter = $this->mockAdapter(['findModelInstanceByPk']); - $utilsModelAdapter->method('findModelInstanceByPk')->willReturn(null); - $container->set('huh.utils.model', $utilsModelAdapter); - System::setContainer($container); - - $urlUtil = $this->createTestInstance(); - - $jumpToPage = $urlUtil->getJumpToPageObject(12); - $this->assertSame($objPage, $jumpToPage); - - $jumpToPage = $urlUtil->getJumpToPageObject(12, false); - $this->assertNull($jumpToPage); - } - - /** - * Test redirect() when headers_sent() is true. - */ - public function testRedirectHeadersAlreadySent() - { - $backendMatcher = new RequestMatcher('/contao', 'test.com', null, ['192.168.1.0']); - $frontendMatcher = new RequestMatcher('/index', 'test.com', null, ['192.168.1.0']); - - $scopeMatcher = new ScopeMatcher($backendMatcher, $frontendMatcher); - - $request = new Request(); - - $requestStack = new RequestStack(); - $requestStack->push($request); - - $urlUtil = $this->createTestInstance(['requestStack' => $requestStack]); - $this->assertSame(UrlUtil::TERMINATE_HEADERS_ALREADY_SENT, $urlUtil->redirect('/test?foo=bar&test=123', 301, true)); - } - - /** - * Test 301 redirect() html & in url. - */ - public function test301RedirectWithHtmlAmpersandParams() - { - $backendMatcher = new RequestMatcher('/contao', 'test.com', null, ['192.168.1.0']); - $frontendMatcher = new RequestMatcher('/index', 'test.com', null, ['192.168.1.0']); - - $scopeMatcher = new ScopeMatcher($backendMatcher, $frontendMatcher); - - $request = new Request(); - - $requestStack = new RequestStack(); - $requestStack->push($request); - - $urlUtil = $this->createTestInstance([ - 'requestStack' => $requestStack - ]); - $headers = $urlUtil->redirect('/test?foo=bar&test=123', 301, true, true); - $this->assertNotEmpty($headers); - $this->assertSame(['HTTP/1.1 301 Moved Permanently', 'Location: http://localhost/test?foo=bar&test=123'], $headers); - } - - /** - * Test 302 redirect(). - */ - public function test302Redirect() - { - $backendMatcher = new RequestMatcher('/contao', 'test.com', null, ['192.168.1.0']); - $frontendMatcher = new RequestMatcher('/index', 'test.com', null, ['192.168.1.0']); - - $scopeMatcher = new ScopeMatcher($backendMatcher, $frontendMatcher); - - $request = new Request(); - - $requestStack = new RequestStack(); - $requestStack->push($request); - - $urlUtil = $this->createTestInstance([ - 'requestStack' => $requestStack - ]); - $headers = $urlUtil->redirect('http://test.com/test?foo=bar', 302, true, true); - $this->assertNotEmpty($headers); - $this->assertSame(['HTTP/1.1 302 Found', 'Location: http://test.com/test?foo=bar'], $headers); - } - - /** - * Test 303 redirect(). - */ - public function test303Redirect() - { - $backendMatcher = new RequestMatcher('/contao', 'test.com', null, ['192.168.1.0']); - $frontendMatcher = new RequestMatcher('/index', 'test.com', null, ['192.168.1.0']); - - $scopeMatcher = new ScopeMatcher($backendMatcher, $frontendMatcher); - - $request = new Request(); - - $requestStack = new RequestStack(); - $requestStack->push($request); - - $urlUtil = $this->createTestInstance([ - 'requestStack' => $requestStack - ]); - $headers = $urlUtil->redirect('http://test.com/test?foo=bar', 303, true, true); - $this->assertNotEmpty($headers); - $this->assertSame(['HTTP/1.1 303 See Other', 'Location: http://test.com/test?foo=bar'], $headers); - } - - /** - * Test 307 redirect(). - */ - public function test307Redirect() - { - $backendMatcher = new RequestMatcher('/contao', 'test.com', null, ['192.168.1.0']); - $frontendMatcher = new RequestMatcher('/index', 'test.com', null, ['192.168.1.0']); - - $scopeMatcher = new ScopeMatcher($backendMatcher, $frontendMatcher); - - $request = new Request(); - - $requestStack = new RequestStack(); - $requestStack->push($request); - - $urlUtil = $this->createTestInstance([ - 'requestStack' => $requestStack - ]); - $headers = $urlUtil->redirect('http://test.com/test?foo=bar', 307, true, true); - $this->assertNotEmpty($headers); - $this->assertSame(['HTTP/1.1 307 Temporary Redirect', 'Location: http://test.com/test?foo=bar'], $headers); - } - - /** - * Test xhr/ajax redirect(). - */ - public function testXhrRedirect() - { - $backendMatcher = new RequestMatcher('/contao', 'test.com', null, ['192.168.1.0']); - $frontendMatcher = new RequestMatcher('/index', 'test.com', null, ['192.168.1.0']); - - $scopeMatcher = new ScopeMatcher($backendMatcher, $frontendMatcher); - - $request = new Request([], [], [], [], [], ['HTTP_X-Requested-With' => 'XMLHttpRequest']); - - $requestStack = new RequestStack(); - $requestStack->push($request); - - $urlUtil = $this->createTestInstance([ - 'requestStack' => $requestStack - ]); - $headers = $urlUtil->redirect('http://test.com/test?foo=bar', 307, true, true); - $this->assertNotEmpty($headers); - $this->assertSame(['HTTP/1.1 204 No Content', 'X-Ajax-Location: http://test.com/test?foo=bar'], $headers); - } - - public function createRequestStackMock() - { - $requestStack = new RequestStack(); - $request = new Request(); - $request->attributes->set('_contao_referer_id', 'foobar'); - $requestStack->push($request); - - return $requestStack; - } - - public function createRouterMock() - { - $router = $this->createMock(RouterInterface::class); - $router->method('generate')->with('contao_backend', $this->anything())->willReturnCallback(function ($route, $params = []) { - $url = '/contao'; - - if (!empty($params)) { - $count = 0; - - foreach ($params as $key => $value) { - $url .= (0 === $count ? '?' : '&'); - $url .= $key.'='.$value; - ++$count; - } - } - - return $url; - }); - - return $router; - } - - public function testGetRelativePath() - { - $instance = $this->createTestInstance(); - $this->assertSame('/de', $instance->getRelativePath('https://example.org/de')); - $this->assertSame('/pfad?argument=wert#textanker', $instance->getRelativePath('http://benutzername:passwort@hostname:9090/pfad?argument=wert#textanker')); - $this->assertSame('/path?googleguy=googley', $instance->getRelativePath('//www.example.com/path?googleguy=googley')); - $this->assertSame('/path?test=1&foo=bar&heimrich=hannot', $instance->getRelativePath('//www.example.com/path?test=1&foo=bar&heimrich=hannot')); - $this->assertSame('/mypath/myfile.php', $instance->getRelativePath('foobar.com:80/mypath/myfile.php')); - - $exception = null; - - try { - $instance->getRelativePath('http:///example.com'); - } catch (\Exception $e) { - $exception = $e; - } - $this->assertInstanceOf(InvalidUrlException::class, $exception); - - $instance = $this->createTestInstance(); - $this->assertSame('de', $instance->getRelativePath('https://example.org/de', ['removeLeadingSlash' => true])); - $this->assertSame('pfad?argument=wert#textanker', $instance->getRelativePath('http://benutzername:passwort@hostname:9090/pfad?argument=wert#textanker', ['removeLeadingSlash' => true])); - $this->assertSame('path?googleguy=googley', $instance->getRelativePath('//www.example.com/path?googleguy=googley', ['removeLeadingSlash' => true])); - $this->assertSame('path?test=1&foo=bar&heimrich=hannot', $instance->getRelativePath('//www.example.com/path?test=1&foo=bar&heimrich=hannot', ['removeLeadingSlash' => true])); - $this->assertSame('mypath/myfile.php', $instance->getRelativePath('foobar.com:80/mypath/myfile.php', ['removeLeadingSlash' => true])); - } -} diff --git a/tests/User/UserUtilTest.php b/tests/User/UserUtilTest.php deleted file mode 100644 index a5ebee06..00000000 --- a/tests/User/UserUtilTest.php +++ /dev/null @@ -1,108 +0,0 @@ -createMock(ModelUtil::class); - } - - if (!isset($parameters['framework'])) { - $parameters['framework'] = $this->mockContaoFramework(); - } - - if (!$mockObject) { - return new UserUtil( - $parameters['framework'], - $parameters['modelUtil']); - } - $mockObject->setConstructorArgs([$parameters['framework'], $parameters['modelUtil']]); - - return $mockObject->getMock(); - } - - public function testGetActiveGroups() - { - $parameters['modelUtil'] = $this->createMock(ModelUtil::class); - $parameters['modelUtil']->method('findModelInstanceByPk')->willReturnCallback( - function (string $table, int $id, array $options = []) { - switch ($id) { - case 4: - case 3: - return $this->mockClassWithProperties(UserModel::class, [ - 'groups' => serialize(['2', '5']), - ]); - - case 2: - return $this->mockClassWithProperties(UserModel::class, [ - 'groups' => null, - ]); - - case 1: - default: - return null; - } - } - ); - $groupCollection = $this->createMock(Collection::class); - $parameters['modelUtil']->method('findModelInstancesBy') - ->willReturnOnConsecutiveCalls(null, $groupCollection); - - $instance = $this->getTestInstance($parameters); - - $this->assertNull($instance->getActiveGroups(1)); - $this->assertNull($instance->getActiveGroups(2)); - $this->assertNull($instance->getActiveGroups(3)); - - /** @var Collection $result */ - $result = $instance->getActiveGroups(4); - - $this->assertInstanceOf(Collection::class, $result); - } - - public function testHasActiveGroup() - { - $builder = $this->getMockBuilder(UserUtil::class) - ->setMethods(['getActiveGroups']); - - $instance = $this->getTestInstance([], $builder); - - $instance->method('getActiveGroups')->willReturnCallback(function (int $userId) { - switch ($userId) { - case 2: - return null; - - case 1: - return new Collection([ - $this->mockClassWithProperties(UserGroupModel::class, ['id' => 1]), - $this->mockClassWithProperties(UserGroupModel::class, ['id' => 2]), - ], 'tl_user_group'); - - default: - return null; - } - }); - - $this->assertTrue($instance->hasActiveGroup(1, 1)); - $this->assertFalse($instance->hasActiveGroup(1, 3)); - $this->assertFalse($instance->hasActiveGroup(2, 1)); - $this->assertFalse($instance->hasActiveGroup(3, 1)); - } -} diff --git a/tests/Util/Container/ContainerUtilTest.php b/tests/Util/ContainerUtilTest.php similarity index 88% rename from tests/Util/Container/ContainerUtilTest.php rename to tests/Util/ContainerUtilTest.php index 9606369c..30607e08 100644 --- a/tests/Util/Container/ContainerUtilTest.php +++ b/tests/Util/ContainerUtilTest.php @@ -6,7 +6,7 @@ * @license LGPL-3.0-or-later */ -namespace HeimrichHannot\UtilsBundle\Tests\Util\Container; +namespace HeimrichHannot\UtilsBundle\Tests\Util; use Contao\CoreBundle\ContaoCoreBundle; use Contao\CoreBundle\HttpKernel\Bundle\ContaoModuleBundle; @@ -15,8 +15,9 @@ use Contao\CoreBundle\Security\Authentication\Token\TokenChecker; use Contao\Input; use Contao\TestCase\ContaoTestCase; +use HeimrichHannot\RequestBundle\HeimrichHannotContaoRequestBundle; use HeimrichHannot\UtilsBundle\HeimrichHannotUtilsBundle; -use HeimrichHannot\UtilsBundle\Util\Container\ContainerUtil; +use HeimrichHannot\UtilsBundle\Util\ContainerUtil; use Psr\Log\LogLevel; use Symfony\Bridge\Monolog\Logger; use Symfony\Component\Config\Exception\FileLocatorFileNotFoundException; @@ -39,10 +40,6 @@ public function getTestInstance(array $parameters = []) }); } - if (!isset($parameters['kernelBundles'])) { - $parameters['kernelBundles'] = []; - } - if (!isset($parameters['kernel'])) { $parameters['kernel'] = $this->createMock(KernelInterface::class); } @@ -63,7 +60,6 @@ public function getTestInstance(array $parameters = []) return new ContainerUtil( $parameters['locator'], - $parameters['kernelBundles'], $parameters['kernel'], $parameters['framework'], $parameters['scopeMather'], @@ -264,25 +260,6 @@ public function testGetBundleResourcePath() $this->assertNull($instance->getBundleResourcePath(HeimrichHannotUtilsBundle::class)); } - public function skipTestIsMaintenanceModeActive() - { - $maintenanceDriverMock = $this->mockAdapter(['getDriver', 'isExists']); - $maintenanceDriverMock->method('getDriver')->willReturnSelf(); - $maintenanceDriverMock->method('isExists')->willReturn(false); - $locator = $this->createMock(ServiceLocator::class); - $locator->method('get')->willReturn($maintenanceDriverMock); - $instance = $this->getTestInstance(['locator' => $locator]); - $this->assertFalse($instance->isMaintenanceModeActive()); - - $maintenanceDriverMock = $this->mockAdapter(['getDriver', 'isExists']); - $maintenanceDriverMock->method('getDriver')->willReturnSelf(); - $maintenanceDriverMock->method('isExists')->willReturn(true); - $locator = $this->createMock(ServiceLocator::class); - $locator->method('get')->willReturn($maintenanceDriverMock); - $instance = $this->getTestInstance(['locator' => $locator]); - $this->assertTrue($instance->isMaintenanceModeActive()); - } - public function testIsFrontendCron() { $instance = $this->getTestInstance(); @@ -327,20 +304,6 @@ public function testIsDev() $this->assertFalse($instance->isDev()); } - public function testIsBundleActive() - { - $kernelBundles = [ - 'ContaoCoreBundle' => ContaoCoreBundle::class, - 'HeimrichHannotUtilsBundle' => HeimrichHannotUtilsBundle::class, - 'legacyModule' => ContaoModuleBundle::class, - ]; - - $instance = $this->getTestInstance(['kernelBundles' => $kernelBundles]); - $this->assertTrue($instance->isBundleActive(ContaoCoreBundle::class)); - $this->assertTrue($instance->isBundleActive('legacyModule')); - $this->assertFalse($instance->isBundleActive('haste')); - } - public function testGetBundlePath() { $fileLocator = $this->createMock(FileLocator::class); diff --git a/tests/Util/Data/AnonymizeUtilTest.php b/tests/Util/Data/AnonymizeUtilTest.php new file mode 100644 index 00000000..5e526c95 --- /dev/null +++ b/tests/Util/Data/AnonymizeUtilTest.php @@ -0,0 +1,25 @@ +getTestInstance(); + $this->assertSame('max.mus*******@example.org', $instance->anonymizeEmail('max.mustermann@example.org')); + $this->assertSame('digi****@heimrich-hannot.de', $instance->anonymizeEmail('digitales@heimrich-hannot.de')); + $this->assertSame('dasIstKeinE-Mail', $instance->anonymizeEmail('dasIstKeinE-Mail')); + } +} diff --git a/tests/Util/DatabaseUtil/DatabaseUtilTest.php b/tests/Util/DatabaseUtil/DatabaseUtilTest.php new file mode 100644 index 00000000..fbe9597b --- /dev/null +++ b/tests/Util/DatabaseUtil/DatabaseUtilTest.php @@ -0,0 +1,29 @@ +getTestInstance()->createWhereForSerializedBlob('elements', ['texts', 'headline']); + static::assertInstanceOf(CreateWhereForSerializedBlobResult::class, $result); + static::assertSame('(elements REGEXP (?) OR elements REGEXP (?))', $result->createOrWhere()); + static::assertSame('(elements REGEXP (?) AND elements REGEXP (?))', $result->createAndWhere()); + static::assertSame('(elements REGEXP (:"texts") OR elements REGEXP (:"headline"))', $result->createInlineOrWhere()); + static::assertSame('(elements REGEXP (:"texts") AND elements REGEXP (:"headline"))', $result->createInlineAndWhere()); + static::assertCount(2, $result->values); + } + + +} diff --git a/tests/Util/Dca/DcaUtilTest.php b/tests/Util/Dca/DcaUtilTest.php index 3f2cc113..f804e23f 100644 --- a/tests/Util/Dca/DcaUtilTest.php +++ b/tests/Util/Dca/DcaUtilTest.php @@ -10,7 +10,8 @@ use Contao\Controller; use HeimrichHannot\UtilsBundle\Tests\AbstractUtilsTestCase; -use HeimrichHannot\UtilsBundle\Util\Dca\DcaUtil; +use HeimrichHannot\UtilsBundle\Util\DcaUtil; +use HeimrichHannot\UtilsBundle\Util\DcaUtil\GetDcaFieldsOptions; use PHPUnit\Framework\MockObject\MockBuilder; class DcaUtilTest extends AbstractUtilsTestCase @@ -94,41 +95,45 @@ public function testGetDcaFields() 'title', ], $fields); - $fields = $instance->getDcaFields('table', ['allowedInputTypes' => ['select']]); + $fields = $instance->getDcaFields( + 'table', + GetDcaFieldsOptions::create()->setAllowedInputTypes(['select']) + ); $this->assertSame([], $fields); - $fields = $instance->getDcaFields('table', ['localizeLabels' => true]); + $fields = $instance->getDcaFields( + 'table', + GetDcaFieldsOptions::create()->setLocalizeLabels(true) + ); $this->assertSame([ 'addSubmission' => 'Submission', 'title' => 'Title', ], $fields); - $fields = $instance->getDcaFields('table', ['skipSorting' => true]); + $fields = $instance->getDcaFields( + 'table', + GetDcaFieldsOptions::create()->setSkipSorting(true) + ); $this->assertSame([ 'title', 'addSubmission', ], $fields); - $fields = $instance->getDcaFields('table', ['onlyDatabaseFields' => true]); + $fields = $instance->getDcaFields( + 'table', + GetDcaFieldsOptions::create()->setOnlyDatabaseFields(true) + ); $this->assertSame([ 'title', ], $fields); - $fields = $instance->getDcaFields('table', ['evalConditions' => ['mandatory' => true]]); + $fields = $instance->getDcaFields( + 'table', + GetDcaFieldsOptions::create()->setEvalConditions(['mandatory' => true]) + ); $this->assertSame([ 'title', ], $fields); - - $this->expectWarning(); - $instance->getDcaFields('table', ['allowedInputTypes' => 'checkbox']); - } - - public function testGetDcaFieldsWithWarning() - { - $instance = $this->getTestInstance(); - - $this->expectWarning(); - $instance->getDcaFields('table', ['evalConditions' => 'mandatory']); } } diff --git a/tests/Util/Html/HtmlUtilTest.php b/tests/Util/Html/HtmlUtilTest.php index 7b165361..f9bc2c74 100644 --- a/tests/Util/Html/HtmlUtilTest.php +++ b/tests/Util/Html/HtmlUtilTest.php @@ -9,7 +9,9 @@ namespace HeimrichHannot\UtilsBundle\Tests\Util\Html; use HeimrichHannot\UtilsBundle\Tests\AbstractUtilsTestCase; -use HeimrichHannot\UtilsBundle\Util\Html\HtmlUtil; +use HeimrichHannot\UtilsBundle\Util\HtmlUtil\GenerateDataAttributesStringArrayHandling; +use HeimrichHannot\UtilsBundle\Util\HtmlUtil\GenerateDataAttributesStringOptions; +use HeimrichHannot\UtilsBundle\Util\HtmlUtil; use PHPUnit\Framework\MockObject\MockBuilder; class HtmlUtilTest extends AbstractUtilsTestCase @@ -51,15 +53,24 @@ public function testGenerateDataAttributesString() $this->assertSame( 'data-Foo Bar="1"', - $instance->generateDataAttributesString(['Foo Bar' => '1'], ['normalizeKeys' => false]) + $instance->generateDataAttributesString( + ['Foo Bar' => '1'], + GenerateDataAttributesStringOptions::create()->setNormalizeKeys(false) + ) ); $this->assertSame( 'data-Foo-Bar="1"', - $instance->generateDataAttributesString(['Foo-Bar' => '1'], ['normalizeKeys' => false]) + $instance->generateDataAttributesString( + ['Foo-Bar' => '1'], + GenerateDataAttributesStringOptions::create()->setNormalizeKeys(false) + ) ); $this->assertSame( 'data-FooBar="1"', - $instance->generateDataAttributesString(['FooBar' => '1'], ['normalizeKeys' => false]) + $instance->generateDataAttributesString( + ['FooBar' => '1'], + GenerateDataAttributesStringOptions::create()->setNormalizeKeys(false) + ) ); $this->assertSame( @@ -75,25 +86,25 @@ public function testGenerateDataAttributesString() 'data-foo-bar="Blub"', $instance->generateDataAttributesString(['Foo Bar' => ['Blah' => 'Blub']]) ); - $this->assertSame( - 'data-foo-bar="Blub"', - $instance->generateDataAttributesString(['Foo Bar' => ['Blah' => 'Blub']], ['array_handling' => 'unsupported']) - ); + $this->assertSame( 'data-foo-bar="{"Blah":"Blub"}"', - $instance->generateDataAttributesString(['Foo Bar' => ['Blah' => 'Blub']], ['array_handling' => 'encode']) + $instance->generateDataAttributesString( + ['Foo Bar' => ['Blah' => 'Blub']], + GenerateDataAttributesStringOptions::create()->setArrayHandling(GenerateDataAttributesStringArrayHandling::ENCODE) + ) ); $this->assertSame( 'data-foo-bar data-animal-type="bird" data-editable data-some-strange-attribute="Totally strange" data-perfectly-prepared="sure" data-class="button attention" data-count="5"', $instance->generateDataAttributesString([ - 'data-foo-bar' => true, - 'Animal type' => 'bird', - 'editable' => true, - 'Some Strange Attribute' => 'Totally strange', + 'data-foo-bar' => true, + 'Animal type' => 'bird', + 'editable' => true, + 'Some Strange Attribute' => 'Totally strange', 'data-perfectly-prepared' => 'sure', - 'class' => ['button', 'attention'], - 'Count' => 5, + 'class' => ['button', 'attention'], + 'Count' => 5, ]) ); } diff --git a/tests/Util/Locale/LocaleUtilTest.php b/tests/Util/Locale/LocaleUtilTest.php index d4692abe..4be7ba29 100644 --- a/tests/Util/Locale/LocaleUtilTest.php +++ b/tests/Util/Locale/LocaleUtilTest.php @@ -9,7 +9,7 @@ namespace HeimrichHannot\UtilsBundle\Tests\Util\Locale; use Contao\TestCase\ContaoTestCase; -use HeimrichHannot\UtilsBundle\Util\Locale\LocaleUtil; +use HeimrichHannot\UtilsBundle\Util\LocaleUtil; class LocaleUtilTest extends ContaoTestCase { diff --git a/tests/Util/Model/ModelUtilTest.php b/tests/Util/ModelUtilTest.php similarity index 59% rename from tests/Util/Model/ModelUtilTest.php rename to tests/Util/ModelUtilTest.php index 4399b38a..d59e4899 100644 --- a/tests/Util/Model/ModelUtilTest.php +++ b/tests/Util/ModelUtilTest.php @@ -6,20 +6,39 @@ * @license LGPL-3.0-or-later */ -namespace HeimrichHannot\UtilsBundle\Tests\Util\Model; +namespace HeimrichHannot\UtilsBundle\Tests\Util; use Contao\ContentModel; use Contao\Controller; +use Contao\CoreBundle\Framework\Adapter; use Contao\Model; +use Contao\PageModel; +use Contao\System; +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Schema\AbstractSchemaManager; +use Doctrine\DBAL\Schema\Schema; use HeimrichHannot\UtilsBundle\Tests\AbstractUtilsTestCase; -use HeimrichHannot\UtilsBundle\Util\Model\ModelUtil; +use HeimrichHannot\UtilsBundle\Tests\Util\Model\CfgTagModel; +use HeimrichHannot\UtilsBundle\Util\ModelUtil; use PHPUnit\Framework\MockObject\MockBuilder; +use PHPUnit\Framework\MockObject\MockObject; +use function Clue\StreamFilter\fun; class ModelUtilTest extends AbstractUtilsTestCase { + private Adapter|Model|MockObject $modelAdapter; + + protected function setUp(): void + { + $this->modelAdapter = $this->mockModelAdapter(); + } + + public function getTestInstance(array $parameters = [], ?MockBuilder $mockBuilder = null) { - $contaoFramework = $parameters['framework'] ?? $this->mockContaoFramework(); + $contaoFramework = $parameters['framework'] ?? $this->mockContaoFramework([ + Model::class => $this->modelAdapter, + ]); return new ModelUtil($contaoFramework); } @@ -150,4 +169,92 @@ public function testFindModelInstanceByIdOrAlias() $this->assertSame(5, $instance->findModelInstanceByIdOrAlias('tl_content', 5)->id); $this->assertSame(7, $instance->findModelInstanceByIdOrAlias('tl_content', 'seven')->id); } + + public function testFindParentsRecursively() + { + System::setContainer($this->getContainerWithContaoConfiguration()); + System::getContainer()->setParameter('contao.resources_paths', $this->getFixturesPath().'/contao'); + $connection = $this->createMock(Connection::class); + $connection->method('createSchemaManager')->willReturnCallback(function () { + $schemaManager = $this->createMock(AbstractSchemaManager::class); + $schema = $this->createMock(Schema::class); + $schema->method('getTables')->willReturn([]); + $schemaManager->method('createSchema')->willReturn($schema); + $schemaManager->method('introspectSchema')->willReturn($schema); + return $schemaManager; + }); + System::getContainer()->set('database_connection', $connection); + $pageModel = new PageModel(); + + $pageModel1 = (new PageModel())->setRow(['id' => 1, 'pid' => 0]); + $pageModel2 = (new PageModel())->setRow(['id' => 2, 'pid' => 1]); + $pageModel3 = (new PageModel())->setRow(['id' => 3, 'pid' => 2]); + + $pageModelAdapter = $this->mockAdapter(['findByPk']); + $pageModelAdapter->method('findByPk')->willReturnCallback(function ($id) use ($pageModel1, $pageModel2) { + return match ($id) { + 1 => $pageModel1, + 2 => $pageModel2, + default => null + }; + }); + + $framework = $this->mockContaoFramework([ + PageModel::class => $pageModelAdapter, + Model::class => $this->modelAdapter, + ]); + + $instance = $this->getTestInstance(['framework' => $framework]); + static::assertEmpty($instance->findParentsRecursively($pageModel)); + static::assertEmpty($instance->findParentsRecursively($pageModel1)); + static::assertCount(1, $instance->findParentsRecursively($pageModel2)); + static::assertCount(2, $instance->findParentsRecursively($pageModel3)); + + + + return; + + + + $modelAdapter = $this->mModelockAdapter( + [ + 'getClassFromTable', + ] + ); + $modelAdapter->method('getClassFromTable')->with($this->anything())->willReturnCallback( + function ($table) { + switch ($table) { + case 'tl_content': + return ContentModel::class; + + case 'tl_null_class': + return 'Huh\Null\Class\Nullclass'; + + case 'tl_cfg_tag': + return CfgTagModel::class; + + case 'null': + return null; + + default: + return null; + } + } + ); + $contentModelAdapter = $this->mockAdapter( + [ + 'findByPk', + ] + ); + $contentModelAdapter->method('findByPk')->willReturn($this->getModel(true)); + $contaoFramework = $this->mockContaoFramework([Model::class => $modelAdapter, ContentModel::class => $contentModelAdapter]); + + $util = $this->getTestInstance(['framework' => $contaoFramework]); + + $result = $util->findParentsRecursively('id', 'tl_content', $this->getModel()); + $this->assertInstanceOf(MockObject::class, $result[0]); + + $result = $util->findParentsRecursively('id', 'tl_content', $this->getModel(true)); + $this->assertSame([], $result); + } } diff --git a/tests/Util/Request/RequestUtilTest.php b/tests/Util/Request/RequestUtilTest.php index f42da08b..b9d4b1f6 100644 --- a/tests/Util/Request/RequestUtilTest.php +++ b/tests/Util/Request/RequestUtilTest.php @@ -8,11 +8,12 @@ namespace HeimrichHannot\UtilsBundle\Tests\Util\Request; +use Contao\CoreBundle\Framework\ContaoFramework; use Contao\PageModel; use HeimrichHannot\TestUtilitiesBundle\Mock\ModelMockTrait; use HeimrichHannot\UtilsBundle\Tests\AbstractUtilsTestCase; -use HeimrichHannot\UtilsBundle\Util\Model\ModelUtil; -use HeimrichHannot\UtilsBundle\Util\Request\RequestUtil; +use HeimrichHannot\UtilsBundle\Util\ModelUtil; +use HeimrichHannot\UtilsBundle\Util\RequestUtil; use PHPUnit\Framework\MockObject\MockBuilder; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; @@ -21,135 +22,100 @@ class RequestUtilTest extends AbstractUtilsTestCase { use ModelMockTrait; + /** + * @param array{ + * requestStack?: RequestStack, + * contaoFramework?: ContaoFramework, + * } $parameters + * @param MockBuilder|null $mockBuilder + * @return RequestUtil + */ public function getTestInstance(array $parameters = [], ?MockBuilder $mockBuilder = null) { - $modelUtil = $parameters['modelUtil'] ?? $this->createMock(ModelUtil::class); $requestStack = $parameters['requestStack'] ?? $this->createMock(RequestStack::class); - $kernelPackages = $parameters['kernelPackages'] ?? []; - $contaoFramework = $parameters['contaoFramework'] ?? $this->mockContaoFramework(); + $contaoFramework = $parameters['contaoFramework'] ?? $this->mockContaoFramework([ + PageModel::class => $this->mockAdapter(['findByPk']), + ]); - return new RequestUtil($modelUtil, $requestStack, $kernelPackages, $contaoFramework); + return new RequestUtil($requestStack, $contaoFramework); } public function testGetCurrentPageModel() { unset($GLOBALS['objPage']); - - $instance = $this->getTestInstance(); - $this->assertNull($instance->getCurrentPageModel()); - - $instance = $this->getTestInstance([ - 'kernelPackages' => ['contao/core-bundle' => '4.4.46'], - ]); - $this->assertNull($instance->getCurrentPageModel()); - - $instance = $this->getTestInstance([ - 'kernelPackages' => ['contao/core-bundle' => '4.8.5'], - ]); - $this->assertNull($instance->getCurrentPageModel()); - - $instance = $this->getTestInstance([ - 'kernelPackages' => ['contao/core-bundle' => '4.9.5'], - ]); - $this->assertNull($instance->getCurrentPageModel()); - $requestStack = new RequestStack(); - $requestStack->push(new Request()); - $instance = $this->getTestInstance([ - 'requestStack' => $requestStack, - 'kernelPackages' => ['contao/core-bundle' => '4.9.5'], - ]); + $instance = $this->getTestInstance(['requestStack' => $requestStack]); $this->assertNull($instance->getCurrentPageModel()); $requestStack = new RequestStack(); - $request = new Request([], [], ['pageModel' => 5]); - $requestStack->push($request); - - $instance = $this->getTestInstance([ - 'requestStack' => $requestStack, - 'kernelPackages' => ['contao/core-bundle' => '4.9.5'], - ]); + $requestStack->push(new Request()); + $instance = $this->getTestInstance(['requestStack' => $requestStack]); $this->assertNull($instance->getCurrentPageModel()); $pageModel = $this->mockModelObject(PageModel::class, ['id' => 5]); $requestStack = new RequestStack(); $request = new Request([], [], ['pageModel' => $pageModel]); $requestStack->push($request); - $instance = $this->getTestInstance([ - 'requestStack' => $requestStack, - 'kernelPackages' => ['contao/core-bundle' => '4.9.5'], - ]); - $this->assertSame(5, $instance->getCurrentPageModel()->id); + $instance = $this->getTestInstance(['requestStack' => $requestStack]); + $this->assertSame($pageModel, $instance->getCurrentPageModel()); - $GLOBALS['objPage'] = $pageModel; + $pageModel = $this->mockModelObject(PageModel::class, ['id' => 5]); $requestStack = new RequestStack(); - $request = new Request([], [], ['pageModel' => 5]); + $request = new Request([], [], ['pageModel' => $pageModel->id]); $requestStack->push($request); - - $instance = $this->getTestInstance([ - 'requestStack' => $requestStack, - 'kernelPackages' => ['contao/core-bundle' => '4.9.5'], - ]); - $this->assertSame(5, $instance->getCurrentPageModel()->id); - - $instance = $this->getTestInstance([ - 'requestStack' => $requestStack, - 'kernelPackages' => ['contao/core-bundle' => '4.4.45'], - ]); - $this->assertSame(5, $instance->getCurrentPageModel()->id); + $GLOBALS['objPage'] = $pageModel; + $instance = $this->getTestInstance(['requestStack' => $requestStack]); + $this->assertSame($pageModel, $instance->getCurrentPageModel()); unset($GLOBALS['objPage']); - - $modelUtil = $this->createMock(ModelUtil::class); - $modelUtil->method('findModelInstanceByPk')->willReturn($pageModel); + $pageModelAdapter = $this->mockAdapter(['findByPk']); + $pageModelAdapter->method('findByPk')->willReturnCallback(function ($id) { + return $this->mockModelObject(PageModel::class, ['id' => $id]); + }); + $framework = $this->mockContaoFramework([ + PageModel::class => $pageModelAdapter, + ]); $requestStack = new RequestStack(); $request = new Request([], [], ['pageModel' => 5]); $requestStack->push($request); - $instance = $this->getTestInstance([ - 'modelUtil' => $modelUtil, 'requestStack' => $requestStack, - 'kernelPackages' => ['contao/core-bundle' => '4.9.5'], + 'contaoFramework' => $framework, ]); $this->assertSame(5, $instance->getCurrentPageModel()->id); } public function testGetCurrentRootPageModel() { - $modelUtil = $this->createMock(ModelUtil::class); - $modelUtil->method('findModelInstanceByPk')->willReturn(null); - $requestUtil = $this->getTestInstance([ - 'modelUtil' => $modelUtil, - ]); - $this->assertNull($requestUtil->getCurrentPageModel()); + $instance = $this->getTestInstance(); + $this->assertNull($instance->getCurrentRootPageModel()); $pageModel = $this->mockModelObject(PageModel::class, ['id' => 5, 'rootId' => 3]); - $rootPageModel = $this->mockModelObject(PageModel::class, ['id' => 3, 'rootId' => 3]); - $modelUtil = $this->createMock(ModelUtil::class); - $modelUtil->method('findModelInstanceByPk')->willReturnCallback(function ($table, $id) use ($rootPageModel) { - switch ($id) { - case 3: - return $rootPageModel; - } - - return null; - }); + $pageModel->expects($this->once())->method('loadDetails'); $requestStack = new RequestStack(); $request = new Request([], [], ['pageModel' => $pageModel]); $requestStack->push($request); - $requestUtil = $this->getTestInstance([ + $instance = $this->getTestInstance([ 'requestStack' => $requestStack, - 'kernelPackages' => ['contao/core-bundle' => '4.9.5'], - 'modelUtil' => $modelUtil, ]); + $this->assertNull($instance->getCurrentRootPageModel()); - $this->assertSame(3, $requestUtil->getCurrentRootPageModel()->id); - - unset($GLOBALS['objPage']); - $requestUtil = $this->getTestInstance([ - 'kernelPackages' => ['contao/core-bundle' => '4.4.5'], + $pageModel = $this->mockModelObject(PageModel::class, ['id' => 5, 'rootId' => 3]); + $pageModelAdapter = $this->mockAdapter(['findByPk']); + $pageModelAdapter->method('findByPk')->willReturnCallback(function ($id) { + return $this->mockModelObject(PageModel::class, ['id' => $id]); + }); + $framework = $this->mockContaoFramework([ + PageModel::class => $pageModelAdapter, + ]); + $requestStack = new RequestStack(); + $request = new Request([], [], ['pageModel' => $pageModel]); + $requestStack->push($request); + $instance = $this->getTestInstance([ + 'requestStack' => $requestStack, + 'contaoFramework' => $framework, ]); - $this->assertNull($requestUtil->getCurrentPageModel()); + $this->assertSame(3, $instance->getCurrentRootPageModel()->id); } public function testGetBaseUrl() diff --git a/tests/Util/Request/UrlUtilTest.php b/tests/Util/Request/UrlUtilTest.php index 00121f68..7d9b35c1 100644 --- a/tests/Util/Request/UrlUtilTest.php +++ b/tests/Util/Request/UrlUtilTest.php @@ -10,7 +10,7 @@ use HeimrichHannot\UtilsBundle\Exception\InvalidUrlException; use HeimrichHannot\UtilsBundle\Tests\AbstractUtilsTestCase; -use HeimrichHannot\UtilsBundle\Util\Request\UrlUtil; +use HeimrichHannot\UtilsBundle\Util\UrlUtil; use PHPUnit\Framework\MockObject\MockBuilder; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\RequestStack; diff --git a/tests/Util/Routing/RoutingUtilTest.php b/tests/Util/Routing/RoutingUtilTest.php index 160f196c..5d57ed8f 100644 --- a/tests/Util/Routing/RoutingUtilTest.php +++ b/tests/Util/Routing/RoutingUtilTest.php @@ -10,7 +10,7 @@ use Contao\CoreBundle\Csrf\ContaoCsrfTokenManager; use HeimrichHannot\UtilsBundle\Tests\AbstractUtilsTestCase; -use HeimrichHannot\UtilsBundle\Util\Routing\RoutingUtil; +use HeimrichHannot\UtilsBundle\Util\RoutingUtil; use PHPUnit\Framework\MockObject\MockBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpFoundation\Request; @@ -24,12 +24,12 @@ class RoutingUtilTest extends AbstractUtilsTestCase { public function getTestInstance(array $parameters = [], ?MockBuilder $mockBuilder = null) { - $container = $parameters['container'] ?? $this->getContainerWithContaoConfiguration(); $router = $parameters['router'] ?? $this->createMock(RouterInterface::class); $csrfTokenName = $parameters['csrfTokenName'] ?? 'exampleToken'; $requestStack = $parameters['requestStack'] ?? $this->createMock(RequestStack::class); + $csrfTokenManager = $parameters['csrfTokenManager'] ?? $this->createMock(ContaoCsrfTokenManager::class); - return new RoutingUtil($container, $router, $csrfTokenName, $requestStack); + return new RoutingUtil($csrfTokenManager, $router, $csrfTokenName, $requestStack); } public function testGenerateBackendRoute() @@ -57,9 +57,6 @@ public function testGenerateBackendRoute() $token = $this->createMock(CsrfToken::class); $token->method('getValue')->willReturn('foo-bar'); $tokenManager->method('getToken')->willReturn($token); - $container = $this->getContainerWithContaoConfiguration(); - $container->set(ContaoCsrfTokenManager::class, $tokenManager); - $container->set(CsrfTokenManagerInterface::class, $tokenManager); $requestStack = new RequestStack(); $request = new Request(); @@ -68,32 +65,21 @@ public function testGenerateBackendRoute() $instance = $this->getTestInstance([ 'router' => $router, - 'container' => $container, 'requestStack' => $requestStack, + 'csrfTokenManager' => $tokenManager, ]); $this->assertSame('/contao', $instance->generateBackendRoute([], false, false)); $this->assertSame('/contao?rt=foo-bar', $instance->generateBackendRoute([], true, false)); $this->assertSame('/contao?rt=foo-bar&ref=win-amp', $instance->generateBackendRoute([], true, true)); $this->assertSame('/contao?a=b&rt=foo-bar&ref=win-amp', $instance->generateBackendRoute(['a' => 'b'], true, true)); - - $container = new ContainerBuilder(); - $container->set(CsrfTokenManagerInterface::class, $tokenManager); - - $instance = $this->getTestInstance([ - 'router' => $router, - 'container' => $container, - 'requestStack' => $requestStack, - ]); - $this->assertSame( - '/contao?rt=foo-bar', - $instance->generateBackendRoute([], true, false) + 'https://example.org/contao', + $instance->generateBackendRoute([], false, false, ['absoluteUrl' => true]) ); - $this->assertSame( - 'https://example.org/contao', - $instance->generateBackendRoute([], false, false, 'contao_backend', ['absoluteUrl' => true]) + '/contao_internal', + $instance->generateBackendRoute([], false, false, ['route' => 'contao_internal']) ); } } diff --git a/tests/Util/Type/ArrayUtilTest.php b/tests/Util/Type/ArrayUtilTest.php index a40b0e8a..da878b00 100644 --- a/tests/Util/Type/ArrayUtilTest.php +++ b/tests/Util/Type/ArrayUtilTest.php @@ -9,7 +9,7 @@ namespace HeimrichHannot\UtilsBundle\Tests\Util\Type; use HeimrichHannot\UtilsBundle\Tests\AbstractUtilsTestCase; -use HeimrichHannot\UtilsBundle\Util\Type\ArrayUtil; +use HeimrichHannot\UtilsBundle\Util\ArrayUtil; use PHPUnit\Framework\MockObject\MockBuilder; class ArrayUtilTest extends AbstractUtilsTestCase @@ -35,9 +35,6 @@ public function testInsertBeforeKey(): void $instance::insertBeforeKey($array, ['f', 'h'], 'd', 'D'); $this->assertSame( ['a' => 'A', 'b' => 'B', 'c' => 'C', 'd' => 'D', 'f' => 'F', 'h' => 'H'], $array); - - $this->expectException(\InvalidArgumentException::class); - $instance::insertBeforeKey($array, 3, 'x', 'Y'); } public function testInsertAfterKey() diff --git a/tests/Util/Type/StringUtilTest.php b/tests/Util/Type/StringUtilTest.php index 0c3404a1..c00e97b4 100644 --- a/tests/Util/Type/StringUtilTest.php +++ b/tests/Util/Type/StringUtilTest.php @@ -9,42 +9,13 @@ namespace HeimrichHannot\UtilsBundle\Tests\Util\Type; use Contao\TestCase\ContaoTestCase; -use HeimrichHannot\UtilsBundle\Util\Type\StringUtil; +use HeimrichHannot\UtilsBundle\Util\StringUtil; class StringUtilTest extends ContaoTestCase { public function getTestInstance(array $parameters = []) { - $framework = $this->mockContaoFramework(); - - return new StringUtil($framework); - } - - public function testStartsWith() - { - $instance = $this->getTestInstance(); - $this->assertTrue($instance->startsWith('', '')); - $this->assertTrue($instance->startsWith('bla', '')); - $this->assertTrue($instance->startsWith('heimrichhannot', 'h')); - $this->assertTrue($instance->startsWith('heimrichhannot', 'heimrich')); - $this->assertFalse($instance->startsWith('heimrichhannot', 'hannot')); - $this->assertFalse($instance->startsWith('heimrichhannot', 'foo')); - $this->assertFalse($instance->startsWith('heimrichhannot', 'heimrichhannotutils')); - } - - public function testEndsWith() - { - $instance = $this->getTestInstance(); - $this->assertTrue($instance->endsWith('', '')); - $this->assertTrue($instance->endsWith('bla', '')); - $this->assertTrue($instance->endsWith('heimrichhannot', 't')); - $this->assertTrue($instance->endsWith('heimrichhannot', 'hannot')); - $this->assertFalse($instance->endsWith('heimrichhannot', 'heimrich')); - $this->assertFalse($instance->endsWith('heimrichhannot', 'foo')); - $this->assertFalse($instance->endsWith('heimrichhannot', 'hannotutils')); - $this->assertFalse($instance->endsWith('heimrichhannot', 'heimrichhannotutils')); - $this->assertTrue($instance->endsWith('This is a test string', 'string')); - $this->assertFalse($instance->endsWith('This is a test string', 'ABC')); + return new StringUtil(); } public function testCamelCaseToDashed() diff --git a/tests/Util/Ui/AccordionUtilTest.php b/tests/Util/Ui/AccordionUtilTest.php index 0822229c..0681be3a 100644 --- a/tests/Util/Ui/AccordionUtilTest.php +++ b/tests/Util/Ui/AccordionUtilTest.php @@ -12,7 +12,7 @@ use Contao\Model\Collection; use HeimrichHannot\TestUtilitiesBundle\Mock\ModelMockTrait; use HeimrichHannot\UtilsBundle\Tests\AbstractUtilsTestCase; -use HeimrichHannot\UtilsBundle\Util\Ui\AccordionUtil; +use HeimrichHannot\UtilsBundle\Util\AccordionUtil; use PHPUnit\Framework\MockObject\MockBuilder; class AccordionUtilTest extends AbstractUtilsTestCase diff --git a/tests/Util/User/UserUtilTest.php b/tests/Util/User/UserUtilTest.php deleted file mode 100644 index ff16a629..00000000 --- a/tests/Util/User/UserUtilTest.php +++ /dev/null @@ -1,141 +0,0 @@ -createMock(ModelUtil::class); - $databaseUtil = $parameters['databaseUtil'] ?? $this->createMock(DatabaseUtil::class); - $contaoFramework = $parameters['contaoFramework'] ?? $this->mockContaoFramework(); - - if (!$mockBuilder) { - return new UserUtil($modelUtil, $databaseUtil, $contaoFramework); - } - $mockBuilder->setConstructorArgs([$modelUtil, $databaseUtil, $contaoFramework]); - - return $mockBuilder->getMock(); - } - - public function testGetActiveGroups() - { - $parameters['modelUtil'] = $this->createMock(ModelUtil::class); - $parameters['modelUtil']->method('findModelInstanceByPk')->willReturnCallback( - function (string $table, int $id, array $options = []) { - switch ($id) { - case 4: - case 3: - return $this->mockClassWithProperties(UserModel::class, [ - 'groups' => serialize(['2', '5']), - ]); - - case 2: - return $this->mockClassWithProperties(UserModel::class, [ - 'groups' => null, - ]); - - case 1: - default: - return null; - } - } - ); - $groupCollection = $this->createMock(Collection::class); - $parameters['modelUtil']->method('findModelInstancesBy') - ->willReturnOnConsecutiveCalls(null, $groupCollection); - - $instance = $this->getTestInstance($parameters); - - $this->assertNull($instance->getActiveGroups(1)); - $this->assertNull($instance->getActiveGroups(2)); - $this->assertNull($instance->getActiveGroups(3)); - - /** @var Collection $result */ - $result = $instance->getActiveGroups(4); - - $this->assertInstanceOf(Collection::class, $result); - } - - public function testHasActiveGroup() - { - $builder = $this->getMockBuilder(UserUtil::class) - ->setMethods(['getActiveGroups']); - - $instance = $this->getTestInstance([], $builder); - - $instance->method('getActiveGroups')->willReturnCallback(function (int $userId) { - switch ($userId) { - case 2: - return null; - - case 1: - return new Collection([ - $this->mockClassWithProperties(UserGroupModel::class, ['id' => 1]), - $this->mockClassWithProperties(UserGroupModel::class, ['id' => 2]), - ], 'tl_user_group'); - - default: - return null; - } - }); - - $this->assertTrue($instance->hasActiveGroup(1, 1)); - $this->assertFalse($instance->hasActiveGroup(1, 3)); - $this->assertFalse($instance->hasActiveGroup(2, 1)); - $this->assertFalse($instance->hasActiveGroup(3, 1)); - } - - public function testFindActiveUsersByGroup() - { - $userModelAdapterMock = $this->mockAdapter(['findBy']); - $userModelAdapterMock = $this->mockAdapter(['findBy']); - $userModelAdapterMock->method('findBy')->willReturnCallback(function ($columns, $values, $options) { - $users = []; - $i = 1; - - foreach ($values as $value) { - $users[] = $this->mockModelObject(UserModel::class, ['id' => $i, 'groups' => serialize($value)]); - ++$i; - } - - return new Collection($users, UserModel::getTable()); - }); - - $parameters['contaoFramework'] = $this->mockContaoFramework([ - Model::class => $this->mockModelAdapter(), - UserModel::class => $userModelAdapterMock, - ]); - $parameters['databaseUtil'] = $this->createMock(DatabaseUtil::class); - $parameters['databaseUtil']->method('createWhereForSerializedBlob')->willReturnCallback(function (string $field, array $values) { - return [$field, $values]; - }); - - $instance = $this->getTestInstance($parameters); - $this->assertNull($instance->findActiveUsersByGroup([])); - - $this->assertInstanceOf(Collection::class, $instance->findActiveUsersByGroup([1])); - $this->assertCount(2, $instance->findActiveUsersByGroup([1, 5])); - - $this->assertCount(3, $instance->findActiveUsersByGroup([1, 2, '1 or true', '5'])); - } -} diff --git a/tests/Util/UserUtil/UserUtilTest.php b/tests/Util/UserUtil/UserUtilTest.php new file mode 100644 index 00000000..777d8e23 --- /dev/null +++ b/tests/Util/UserUtil/UserUtilTest.php @@ -0,0 +1,234 @@ +createMock(ModelUtil::class); + $databaseUtil = $parameters['databaseUtil'] ?? $this->createMock(DatabaseUtil::class); + $contaoFramework = $parameters['contaoFramework'] ?? $this->mockContaoFramework(); + + if (!$mockBuilder) { + return new UserUtil($modelUtil, $databaseUtil, $contaoFramework); + } + $mockBuilder->setConstructorArgs([$modelUtil, $databaseUtil, $contaoFramework]); + + return $mockBuilder->getMock(); + } + + public function testGetActiveGroups() + { + // Test no user groups + + $userModel = $this->mockClassWithProperties(UserModel::class); + + $framework = $this->mockContaoFramework([ + Model::class => $this->mockModelAdapter(), + ]); + + $instance = $this->getTestInstance([ + 'contaoFramework' => $framework, + ]); + + $activeGroups = $instance->getActiveGroups($userModel); + self::assertNull($activeGroups); + + $userModel = $this->mockClassWithProperties(UserModel::class, [ + 'groups' => serialize([]), + ]); + + self::assertNull($instance->getActiveGroups($userModel)); + + $userModel = $this->mockClassWithProperties(UserModel::class, [ + 'groups' => serialize(['2', '5']), + ]); + + $groupCollection = new Collection([ + $this->mockClassWithProperties(UserGroupModel::class, ['id' => 2]), + $this->mockClassWithProperties(UserGroupModel::class, ['id' => 5]), + ], UserGroupModel::getTable()); + $modelUtil = $this->createMock(ModelUtil::class); + $modelUtil->method('findModelInstancesBy') + ->with(self::callback(function ($parameter) { + return 'tl_user_group' === $parameter; + })) + ->willReturn($groupCollection); + + $instance = $this->getTestInstance([ + 'modelUtil' => $modelUtil, + 'contaoFramework' => $framework, + ]); + + $activeGroups = $instance->getActiveGroups($userModel); + $this->assertInstanceOf(Collection::class, $activeGroups); + $this->assertCount(2, $activeGroups); + + $memberModel = $this->mockClassWithProperties(MemberModel::class, [ + 'groups' => serialize(['2', '5']), + ]); + + $modelUtil = $this->createMock(ModelUtil::class); + $modelUtil->method('findModelInstancesBy') + ->with(self::callback(function ($parameter) { + return 'tl_member_group' === $parameter; + })) + ->willReturn($groupCollection); + + $instance = $this->getTestInstance([ + 'modelUtil' => $modelUtil, + 'contaoFramework' => $framework, + ]); + + $activeGroups = $instance->getActiveGroups($memberModel); + + $this->assertInstanceOf(Collection::class, $activeGroups); + $this->assertCount(2, $activeGroups); + + +// $parameters['modelUtil'] = $this->createMock(ModelUtil::class); +// $parameters['modelUtil']->method('findModelInstanceByPk')->willReturnCallback( +// function (string $table, int $id, array $options = []) { +// switch ($id) { +// case 4: +// case 3: +// return $this->mockClassWithProperties(UserModel::class, [ +// 'groups' => serialize(['2', '5']), +// ]); +// +// case 2: +// return $this->mockClassWithProperties(UserModel::class, [ +// 'groups' => null, +// ]); +// +// case 1: +// default: +// return null; +// } +// } +// ); +// $groupCollection = $this->createMock(Collection::class); +// $parameters['modelUtil']->method('findModelInstancesBy') +// ->willReturnOnConsecutiveCalls(null, $groupCollection); +// +// $instance = $this->getTestInstance($parameters); +// +// $this->assertNull($instance->getActiveGroups(1)); +// $this->assertNull($instance->getActiveGroups(2)); +// $this->assertNull($instance->getActiveGroups(3)); +// +// /** @var Collection $result */ +// $result = $instance->getActiveGroups(4); +// +// $this->assertInstanceOf(Collection::class, $result); + } + + public function testHasActiveGroup() + { + $userModel = $this->mockClassWithProperties(UserModel::class); + $instance = $this->getTestInstance(); + $this->assertFalse($instance->hasActiveGroup($userModel, 1)); + + $userModel = $this->mockClassWithProperties(UserModel::class, [ + 'groups' => serialize(['2', '5']), + ]); + + $userModelAdapter = $this->mockAdapter(['getTable']); + $userModelAdapter->method('getTable')->willReturn('tl_user'); + + $groupCollection = new Collection([ + $this->mockClassWithProperties(UserGroupModel::class, ['id' => 2]), + $this->mockClassWithProperties(UserGroupModel::class, ['id' => 5]), + ], UserGroupModel::getTable()); + $modelUtil = $this->createMock(ModelUtil::class); + $modelUtil->method('findModelInstancesBy')->willReturn($groupCollection); + + $framework = $this->mockContaoFramework([ + $userModelAdapter::class => $userModelAdapter, + ]); + + $instance = $this->getTestInstance([ + 'modelUtil' => $modelUtil, + 'contaoFramework' => $framework, + ]); + + $this->assertTrue($instance->hasActiveGroup($userModel, 2)); + $this->assertFalse($instance->hasActiveGroup($userModel, 1)); + + +// $builder = $this->getMockBuilder(UserUtil::class) +// ->setMethods(['getActiveGroups']); + +// $instance = $this->getTestInstance([], $builder); +// +// $instance->method('getActiveGroups')->willReturnCallback(function (int $userId) { +// return match ($userId) { +// 1 => new Collection([ +// $this->mockClassWithProperties(UserGroupModel::class, ['id' => 1]), +// $this->mockClassWithProperties(UserGroupModel::class, ['id' => 2]), +// ], 'tl_user_group'), +// default => null, +// }; +// }); +// +// $this->assertTrue($instance->hasActiveGroup(1, 1)); +// $this->assertFalse($instance->hasActiveGroup(1, 3)); +// $this->assertFalse($instance->hasActiveGroup(2, 1)); +// $this->assertFalse($instance->hasActiveGroup(3, 1)); + } + + public function testFindActiveUsersByGroup() + { + $userModelAdapterMock = $this->mockAdapter(['findBy']); + $userModelAdapterMock->method('findBy')->willReturnCallback(function ($columns, $values, $options) { + $users = []; + $i = 1; + + foreach ($values as $value) { + $users[] = $this->mockModelObject(UserModel::class, ['id' => $i, 'groups' => serialize($value)]); + ++$i; + } + + return new Collection($users, UserModel::getTable()); + }); + + $parameters['contaoFramework'] = $this->mockContaoFramework([ + Model::class => $this->mockModelAdapter(), + UserModel::class => $userModelAdapterMock, + ]); + $parameters['databaseUtil'] = $this->createMock(DatabaseUtil::class); + $parameters['databaseUtil']->method('createWhereForSerializedBlob')->willReturnCallback(function (string $field, array $values) { + return new CreateWhereForSerializedBlobResult($field, $values); + }); + + $instance = $this->getTestInstance($parameters); + $this->assertNull($instance->findActiveUsersByGroup([])); + + $this->assertInstanceOf(Collection::class, $instance->findActiveUsersByGroup([1])); + $this->assertCount(2, $instance->findActiveUsersByGroup([1, 5])); + + $this->assertCount(3, $instance->findActiveUsersByGroup([1, 2, '1 or true', '5'])); + } +} diff --git a/tests/Util/UtilsTest.php b/tests/Util/UtilsTest.php index 895c477b..2d4dcb5d 100644 --- a/tests/Util/UtilsTest.php +++ b/tests/Util/UtilsTest.php @@ -9,18 +9,20 @@ namespace HeimrichHannot\UtilsBundle\Tests\Util; use Contao\TestCase\ContaoTestCase; -use HeimrichHannot\UtilsBundle\Util\Container\ContainerUtil; -use HeimrichHannot\UtilsBundle\Util\Dca\DcaUtil; -use HeimrichHannot\UtilsBundle\Util\Html\HtmlUtil; -use HeimrichHannot\UtilsBundle\Util\Locale\LocaleUtil; -use HeimrichHannot\UtilsBundle\Util\Model\ModelUtil; -use HeimrichHannot\UtilsBundle\Util\Request\RequestUtil; -use HeimrichHannot\UtilsBundle\Util\Request\UrlUtil; -use HeimrichHannot\UtilsBundle\Util\Routing\RoutingUtil; -use HeimrichHannot\UtilsBundle\Util\Type\ArrayUtil; -use HeimrichHannot\UtilsBundle\Util\Type\StringUtil; -use HeimrichHannot\UtilsBundle\Util\Ui\AccordionUtil; -use HeimrichHannot\UtilsBundle\Util\User\UserUtil; +use HeimrichHannot\UtilsBundle\Util\ContainerUtil; +use HeimrichHannot\UtilsBundle\Util\AnonymizeUtil; +use HeimrichHannot\UtilsBundle\Util\DatabaseUtil; +use HeimrichHannot\UtilsBundle\Util\DcaUtil; +use HeimrichHannot\UtilsBundle\Util\HtmlUtil; +use HeimrichHannot\UtilsBundle\Util\LocaleUtil; +use HeimrichHannot\UtilsBundle\Util\ModelUtil; +use HeimrichHannot\UtilsBundle\Util\RequestUtil; +use HeimrichHannot\UtilsBundle\Util\UrlUtil; +use HeimrichHannot\UtilsBundle\Util\RoutingUtil; +use HeimrichHannot\UtilsBundle\Util\ArrayUtil; +use HeimrichHannot\UtilsBundle\Util\StringUtil; +use HeimrichHannot\UtilsBundle\Util\AccordionUtil; +use HeimrichHannot\UtilsBundle\Util\UserUtil; use HeimrichHannot\UtilsBundle\Util\Utils; use Symfony\Component\DependencyInjection\ServiceLocator; @@ -35,12 +37,18 @@ public function getTestInstance(array $parameter = []) case AccordionUtil::class: return $this->createMock(AccordionUtil::class); + case AnonymizeUtil::class: + return $this->createMock(AnonymizeUtil::class); + case ArrayUtil::class: return $this->createMock(ArrayUtil::class); case ContainerUtil::class: return $this->createMock(ContainerUtil::class); + case DatabaseUtil::class: + return $this->createMock(DatabaseUtil::class); + case DcaUtil::class: return $this->createMock(DcaUtil::class); @@ -80,6 +88,10 @@ public function testAccordion() { $this->assertInstanceOf(AccordionUtil::class, $this->getTestInstance()->accordion()); } + public function testAnonymize() + { + $this->assertInstanceOf(AnonymizeUtil::class, $this->getTestInstance()->anonymize()); + } public function testArray() { @@ -91,6 +103,11 @@ public function testContainer() $this->assertInstanceOf(ContainerUtil::class, $this->getTestInstance()->container()); } + public function testDatabase() + { + $this->assertInstanceOf(DatabaseUtil::class, $this->getTestInstance()->database()); + } + public function testDca() { $this->assertInstanceOf(DcaUtil::class, $this->getTestInstance()->dca()); diff --git a/tests/bootstrap.php b/tests/bootstrap.php index ac47ee8e..2ca7544e 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -25,10 +25,6 @@ exit(1); } -require 'TestCaseEnvironment.php'; - -require 'Request/StubCurlRequest.php'; - // Handle classes in the global namespace $legacyLoader = function ($class) { if (class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false)) {