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 `
'.$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: - * - *@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): ?>
-
-
-