diff --git a/.github/workflows/pull-request-from-branch-check.yaml b/.github/workflows/pull-request-from-branch-check.yaml index 77d1970f..401de613 100644 --- a/.github/workflows/pull-request-from-branch-check.yaml +++ b/.github/workflows/pull-request-from-branch-check.yaml @@ -1,18 +1,18 @@ name: Main Branch Protection -on: - pull_request: - branches: - - main +#on: +# pull_request: +# branches: +# - main -jobs: - check-branch: - runs-on: ubuntu-latest - steps: - - name: Check branch - run: | - if [[ ${GITHUB_HEAD_REF} != development ]] && [[ ${GITHUB_HEAD_REF} != documentation ]] && ! [[ ${GITHUB_HEAD_REF} =~ ^hotfix/ ]]; - then - echo "Error: Pull request must come from 'development', 'documentation' or 'hotfix/' branch" - exit 1 - fi +#jobs: +# check-branch: +# runs-on: ubuntu-latest +# steps: +# - name: Check branch +# run: | +# if [[ ${GITHUB_HEAD_REF} != development ]] && [[ ${GITHUB_HEAD_REF} != documentation ]] && ! [[ ${GITHUB_HEAD_REF} =~ ^hotfix/ ]]; +# then +# echo "Error: Pull request must come from 'development', 'documentation' or 'hotfix/' branch" +# exit 1 +# fi diff --git a/appinfo/info.xml b/appinfo/info.xml index 260f2fe6..f04f76a4 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -8,12 +8,12 @@ The OpenConnector Nextcloud app provides a ESB-framework to work together in an (open) data ecosystem -- 📲 Synchronize your data sources +- 📲 Synchronize your data sources - 📰 Send cloud events - 🆓 Map and translate API calls ]]> - 0.1.16 + 0.1.26 agpl integration Conduction @@ -27,12 +27,16 @@ The OpenConnector Nextcloud app provides a ESB-framework to work together in an pgsql sqlite mysql - + curl + + OCA\OpenConnector\Cron\LogCleanUpTask + + openconnector diff --git a/composer.json b/composer.json index 612df13f..b20d0b5a 100644 --- a/composer.json +++ b/composer.json @@ -34,6 +34,8 @@ "bamarni/composer-bin-plugin": "^1.8", "elasticsearch/elasticsearch": "^v8.14.0", "guzzlehttp/guzzle": "^7.0", + "jwadhams/json-logic-php": "^1.5", + "symfony/console": "^5.4", "symfony/uid": "^6.4", "symfony/yaml": "^6.4", "twig/twig": "^3.14", diff --git a/composer.lock b/composer.lock index 68c791f4..94d4d6dd 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "804adee469d5f3c9b6bd2879a48fe872", + "content-hash": "675849f1df3dec3e4818f1d9daa65c84", "packages": [ { "name": "adbario/php-dot-notation", @@ -237,16 +237,16 @@ }, { "name": "elasticsearch/elasticsearch", - "version": "v8.15.0", + "version": "v8.16.0", "source": { "type": "git", "url": "https://github.com/elastic/elasticsearch-php.git", - "reference": "34c2444fa8d4c3e6c8b009bd8dea90bca007203b" + "reference": "ab0fdb43f9e69f0d0539028d8b0b56cdf3328d85" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/elastic/elasticsearch-php/zipball/34c2444fa8d4c3e6c8b009bd8dea90bca007203b", - "reference": "34c2444fa8d4c3e6c8b009bd8dea90bca007203b", + "url": "https://api.github.com/repos/elastic/elasticsearch-php/zipball/ab0fdb43f9e69f0d0539028d8b0b56cdf3328d85", + "reference": "ab0fdb43f9e69f0d0539028d8b0b56cdf3328d85", "shasum": "" }, "require": { @@ -289,9 +289,9 @@ ], "support": { "issues": "https://github.com/elastic/elasticsearch-php/issues", - "source": "https://github.com/elastic/elasticsearch-php/tree/v8.15.0" + "source": "https://github.com/elastic/elasticsearch-php/tree/v8.16.0" }, - "time": "2024-08-14T14:32:50+00:00" + "time": "2024-11-14T22:23:33+00:00" }, { "name": "guzzlehttp/guzzle", @@ -618,6 +618,49 @@ ], "time": "2024-07-18T11:15:46+00:00" }, + { + "name": "jwadhams/json-logic-php", + "version": "1.5.1", + "source": { + "type": "git", + "url": "https://github.com/jwadhams/json-logic-php.git", + "reference": "060aab5ad36ae1fdd74d3006131b197ca777fa48" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jwadhams/json-logic-php/zipball/060aab5ad36ae1fdd74d3006131b197ca777fa48", + "reference": "060aab5ad36ae1fdd74d3006131b197ca777fa48", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.3.3" + }, + "type": "library", + "autoload": { + "psr-0": { + "JWadhams": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jeremy Wadhams", + "email": "jwadhams@dealerinspire.com" + } + ], + "description": "Build rules with complex comparisons and boolean operators, serialized as JSON, and execute them in PHP", + "support": { + "issues": "https://github.com/jwadhams/json-logic-php/issues", + "source": "https://github.com/jwadhams/json-logic-php/tree/1.5.1" + }, + "time": "2024-07-09T15:20:54+00:00" + }, { "name": "open-telemetry/api", "version": "1.1.1", @@ -1736,16 +1779,16 @@ }, { "name": "symfony/config", - "version": "v6.4.8", + "version": "v6.4.14", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "12e7e52515ce37191b193cf3365903c4f3951e35" + "reference": "4e55e7e4ffddd343671ea972216d4509f46c22ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/12e7e52515ce37191b193cf3365903c4f3951e35", - "reference": "12e7e52515ce37191b193cf3365903c4f3951e35", + "url": "https://api.github.com/repos/symfony/config/zipball/4e55e7e4ffddd343671ea972216d4509f46c22ef", + "reference": "4e55e7e4ffddd343671ea972216d4509f46c22ef", "shasum": "" }, "require": { @@ -1791,7 +1834,7 @@ "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/config/tree/v6.4.8" + "source": "https://github.com/symfony/config/tree/v6.4.14" }, "funding": [ { @@ -1807,51 +1850,56 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-11-04T11:33:53+00:00" }, { "name": "symfony/console", - "version": "v6.4.12", + "version": "v5.4.47", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "72d080eb9edf80e36c19be61f72c98ed8273b765" + "reference": "c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/72d080eb9edf80e36c19be61f72c98ed8273b765", - "reference": "72d080eb9edf80e36c19be61f72c98ed8273b765", + "url": "https://api.github.com/repos/symfony/console/zipball/c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed", + "reference": "c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.5|^3", + "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", "symfony/polyfill-mbstring": "~1.0", - "symfony/service-contracts": "^2.5|^3", - "symfony/string": "^5.4|^6.0|^7.0" + "symfony/polyfill-php73": "^1.9", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.1|^2|^3", + "symfony/string": "^5.1|^6.0" }, "conflict": { - "symfony/dependency-injection": "<5.4", - "symfony/dotenv": "<5.4", - "symfony/event-dispatcher": "<5.4", - "symfony/lock": "<5.4", - "symfony/process": "<5.4" + "psr/log": ">=3", + "symfony/dependency-injection": "<4.4", + "symfony/dotenv": "<5.1", + "symfony/event-dispatcher": "<4.4", + "symfony/lock": "<4.4", + "symfony/process": "<4.4" }, "provide": { - "psr/log-implementation": "1.0|2.0|3.0" + "psr/log-implementation": "1.0|2.0" }, "require-dev": { - "psr/log": "^1|^2|^3", - "symfony/config": "^5.4|^6.0|^7.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/event-dispatcher": "^5.4|^6.0|^7.0", - "symfony/http-foundation": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/lock": "^5.4|^6.0|^7.0", - "symfony/messenger": "^5.4|^6.0|^7.0", - "symfony/process": "^5.4|^6.0|^7.0", - "symfony/stopwatch": "^5.4|^6.0|^7.0", - "symfony/var-dumper": "^5.4|^6.0|^7.0" + "psr/log": "^1|^2", + "symfony/config": "^4.4|^5.0|^6.0", + "symfony/dependency-injection": "^4.4|^5.0|^6.0", + "symfony/event-dispatcher": "^4.4|^5.0|^6.0", + "symfony/lock": "^4.4|^5.0|^6.0", + "symfony/process": "^4.4|^5.0|^6.0", + "symfony/var-dumper": "^4.4|^5.0|^6.0" + }, + "suggest": { + "psr/log": "For using the console logger", + "symfony/event-dispatcher": "", + "symfony/lock": "", + "symfony/process": "" }, "type": "library", "autoload": { @@ -1885,7 +1933,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.4.12" + "source": "https://github.com/symfony/console/tree/v5.4.47" }, "funding": [ { @@ -1901,20 +1949,20 @@ "type": "tidelift" } ], - "time": "2024-09-20T08:15:52+00:00" + "time": "2024-11-06T11:30:55+00:00" }, { "name": "symfony/dependency-injection", - "version": "v6.4.12", + "version": "v6.4.15", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "cfb9d34a1cdd4911bc737a5358fd1cf8ebfb536e" + "reference": "70ab1f65a4516ef741e519ea938e6aa465e6aa36" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/cfb9d34a1cdd4911bc737a5358fd1cf8ebfb536e", - "reference": "cfb9d34a1cdd4911bc737a5358fd1cf8ebfb536e", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/70ab1f65a4516ef741e519ea938e6aa465e6aa36", + "reference": "70ab1f65a4516ef741e519ea938e6aa465e6aa36", "shasum": "" }, "require": { @@ -1966,7 +2014,7 @@ "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v6.4.12" + "source": "https://github.com/symfony/dependency-injection/tree/v6.4.15" }, "funding": [ { @@ -1982,7 +2030,7 @@ "type": "tidelift" } ], - "time": "2024-09-20T08:18:25+00:00" + "time": "2024-11-09T06:56:25+00:00" }, { "name": "symfony/deprecation-contracts", @@ -2053,16 +2101,16 @@ }, { "name": "symfony/error-handler", - "version": "v6.4.10", + "version": "v6.4.14", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "231f1b2ee80f72daa1972f7340297d67439224f0" + "reference": "9e024324511eeb00983ee76b9aedc3e6ecd993d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/231f1b2ee80f72daa1972f7340297d67439224f0", - "reference": "231f1b2ee80f72daa1972f7340297d67439224f0", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/9e024324511eeb00983ee76b9aedc3e6ecd993d9", + "reference": "9e024324511eeb00983ee76b9aedc3e6ecd993d9", "shasum": "" }, "require": { @@ -2108,7 +2156,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v6.4.10" + "source": "https://github.com/symfony/error-handler/tree/v6.4.14" }, "funding": [ { @@ -2124,20 +2172,20 @@ "type": "tidelift" } ], - "time": "2024-07-26T12:30:32+00:00" + "time": "2024-11-05T15:34:40+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v6.4.8", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "8d7507f02b06e06815e56bb39aa0128e3806208b" + "reference": "0ffc48080ab3e9132ea74ef4e09d8dcf26bf897e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/8d7507f02b06e06815e56bb39aa0128e3806208b", - "reference": "8d7507f02b06e06815e56bb39aa0128e3806208b", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/0ffc48080ab3e9132ea74ef4e09d8dcf26bf897e", + "reference": "0ffc48080ab3e9132ea74ef4e09d8dcf26bf897e", "shasum": "" }, "require": { @@ -2188,7 +2236,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v6.4.8" + "source": "https://github.com/symfony/event-dispatcher/tree/v6.4.13" }, "funding": [ { @@ -2204,7 +2252,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:49:08+00:00" + "time": "2024-09-25T14:18:03+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -2284,16 +2332,16 @@ }, { "name": "symfony/filesystem", - "version": "v6.4.12", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "f810e3cbdf7fdc35983968523d09f349fa9ada12" + "reference": "4856c9cf585d5a0313d8d35afd681a526f038dd3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/f810e3cbdf7fdc35983968523d09f349fa9ada12", - "reference": "f810e3cbdf7fdc35983968523d09f349fa9ada12", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/4856c9cf585d5a0313d8d35afd681a526f038dd3", + "reference": "4856c9cf585d5a0313d8d35afd681a526f038dd3", "shasum": "" }, "require": { @@ -2330,7 +2378,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v6.4.12" + "source": "https://github.com/symfony/filesystem/tree/v6.4.13" }, "funding": [ { @@ -2346,20 +2394,20 @@ "type": "tidelift" } ], - "time": "2024-09-16T16:01:33+00:00" + "time": "2024-10-25T15:07:50+00:00" }, { "name": "symfony/http-client", - "version": "v6.4.12", + "version": "v6.4.15", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "fbebfcce21084d3e91ea987ae5bdd8c71ff0fd56" + "reference": "cb4073c905cd12b8496d24ac428a9228c1750670" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/fbebfcce21084d3e91ea987ae5bdd8c71ff0fd56", - "reference": "fbebfcce21084d3e91ea987ae5bdd8c71ff0fd56", + "url": "https://api.github.com/repos/symfony/http-client/zipball/cb4073c905cd12b8496d24ac428a9228c1750670", + "reference": "cb4073c905cd12b8496d24ac428a9228c1750670", "shasum": "" }, "require": { @@ -2423,7 +2471,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v6.4.12" + "source": "https://github.com/symfony/http-client/tree/v6.4.15" }, "funding": [ { @@ -2439,7 +2487,7 @@ "type": "tidelift" } ], - "time": "2024-09-20T08:21:33+00:00" + "time": "2024-11-13T13:40:18+00:00" }, { "name": "symfony/http-client-contracts", @@ -2521,16 +2569,16 @@ }, { "name": "symfony/http-foundation", - "version": "v6.4.12", + "version": "v6.4.15", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "133ac043875f59c26c55e79cf074562127cce4d2" + "reference": "9b3165eb2f04aeaa1a5a2cfef73e63fe3b22dff6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/133ac043875f59c26c55e79cf074562127cce4d2", - "reference": "133ac043875f59c26c55e79cf074562127cce4d2", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/9b3165eb2f04aeaa1a5a2cfef73e63fe3b22dff6", + "reference": "9b3165eb2f04aeaa1a5a2cfef73e63fe3b22dff6", "shasum": "" }, "require": { @@ -2540,12 +2588,12 @@ "symfony/polyfill-php83": "^1.27" }, "conflict": { - "symfony/cache": "<6.3" + "symfony/cache": "<6.4.12|>=7.0,<7.1.5" }, "require-dev": { "doctrine/dbal": "^2.13.1|^3|^4", "predis/predis": "^1.1|^2.0", - "symfony/cache": "^6.3|^7.0", + "symfony/cache": "^6.4.12|^7.1.5", "symfony/dependency-injection": "^5.4|^6.0|^7.0", "symfony/expression-language": "^5.4|^6.0|^7.0", "symfony/http-kernel": "^5.4.12|^6.0.12|^6.1.4|^7.0", @@ -2578,7 +2626,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v6.4.12" + "source": "https://github.com/symfony/http-foundation/tree/v6.4.15" }, "funding": [ { @@ -2594,20 +2642,20 @@ "type": "tidelift" } ], - "time": "2024-09-20T08:18:25+00:00" + "time": "2024-11-08T16:09:24+00:00" }, { "name": "symfony/http-kernel", - "version": "v6.4.12", + "version": "v6.4.15", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "96df83d51b5f78804f70c093b97310794fd6257b" + "reference": "b002a5b3947653c5aee3adac2a024ea615fd3ff5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/96df83d51b5f78804f70c093b97310794fd6257b", - "reference": "96df83d51b5f78804f70c093b97310794fd6257b", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/b002a5b3947653c5aee3adac2a024ea615fd3ff5", + "reference": "b002a5b3947653c5aee3adac2a024ea615fd3ff5", "shasum": "" }, "require": { @@ -2692,7 +2740,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v6.4.12" + "source": "https://github.com/symfony/http-kernel/tree/v6.4.15" }, "funding": [ { @@ -2708,7 +2756,7 @@ "type": "tidelift" } ], - "time": "2024-09-21T06:02:57+00:00" + "time": "2024-11-13T13:57:37+00:00" }, { "name": "symfony/polyfill-ctype", @@ -3028,6 +3076,162 @@ ], "time": "2024-09-09T11:45:10+00:00" }, + { + "name": "symfony/polyfill-php73", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "reference": "0f68c03565dcaaf25a890667542e8bd75fe7e5bb", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.31.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, { "name": "symfony/polyfill-php81", "version": "v1.31.0", @@ -3420,16 +3624,16 @@ }, { "name": "symfony/string", - "version": "v6.4.12", + "version": "v6.4.15", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "f8a1ccebd0997e16112dfecfd74220b78e5b284b" + "reference": "73a5e66ea2e1677c98d4449177c5a9cf9d8b4c6f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/f8a1ccebd0997e16112dfecfd74220b78e5b284b", - "reference": "f8a1ccebd0997e16112dfecfd74220b78e5b284b", + "url": "https://api.github.com/repos/symfony/string/zipball/73a5e66ea2e1677c98d4449177c5a9cf9d8b4c6f", + "reference": "73a5e66ea2e1677c98d4449177c5a9cf9d8b4c6f", "shasum": "" }, "require": { @@ -3486,7 +3690,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.4.12" + "source": "https://github.com/symfony/string/tree/v6.4.15" }, "funding": [ { @@ -3502,20 +3706,20 @@ "type": "tidelift" } ], - "time": "2024-09-20T08:15:52+00:00" + "time": "2024-11-13T13:31:12+00:00" }, { "name": "symfony/uid", - "version": "v6.4.12", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/uid.git", - "reference": "2f16054e0a9b194b8ca581d4a64eee3f7d4a9d4d" + "reference": "18eb207f0436a993fffbdd811b5b8fa35fa5e007" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/2f16054e0a9b194b8ca581d4a64eee3f7d4a9d4d", - "reference": "2f16054e0a9b194b8ca581d4a64eee3f7d4a9d4d", + "url": "https://api.github.com/repos/symfony/uid/zipball/18eb207f0436a993fffbdd811b5b8fa35fa5e007", + "reference": "18eb207f0436a993fffbdd811b5b8fa35fa5e007", "shasum": "" }, "require": { @@ -3560,7 +3764,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/uid/tree/v6.4.12" + "source": "https://github.com/symfony/uid/tree/v6.4.13" }, "funding": [ { @@ -3576,20 +3780,20 @@ "type": "tidelift" } ], - "time": "2024-09-20T08:32:26+00:00" + "time": "2024-09-25T14:18:03+00:00" }, { "name": "symfony/var-dumper", - "version": "v6.4.11", + "version": "v6.4.15", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "ee14c8254a480913268b1e3b1cba8045ed122694" + "reference": "38254d5a5ac2e61f2b52f9caf54e7aa3c9d36b80" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/ee14c8254a480913268b1e3b1cba8045ed122694", - "reference": "ee14c8254a480913268b1e3b1cba8045ed122694", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/38254d5a5ac2e61f2b52f9caf54e7aa3c9d36b80", + "reference": "38254d5a5ac2e61f2b52f9caf54e7aa3c9d36b80", "shasum": "" }, "require": { @@ -3645,7 +3849,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.4.11" + "source": "https://github.com/symfony/var-dumper/tree/v6.4.15" }, "funding": [ { @@ -3661,20 +3865,20 @@ "type": "tidelift" } ], - "time": "2024-08-30T16:03:21+00:00" + "time": "2024-11-08T15:28:48+00:00" }, { "name": "symfony/var-exporter", - "version": "v6.4.9", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "f9a060622e0d93777b7f8687ec4860191e16802e" + "reference": "0f605f72a363f8743001038a176eeb2a11223b51" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/f9a060622e0d93777b7f8687ec4860191e16802e", - "reference": "f9a060622e0d93777b7f8687ec4860191e16802e", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/0f605f72a363f8743001038a176eeb2a11223b51", + "reference": "0f605f72a363f8743001038a176eeb2a11223b51", "shasum": "" }, "require": { @@ -3722,7 +3926,7 @@ "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v6.4.9" + "source": "https://github.com/symfony/var-exporter/tree/v6.4.13" }, "funding": [ { @@ -3738,20 +3942,20 @@ "type": "tidelift" } ], - "time": "2024-06-24T15:53:56+00:00" + "time": "2024-09-25T14:18:03+00:00" }, { "name": "symfony/yaml", - "version": "v6.4.12", + "version": "v6.4.13", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "762ee56b2649659380e0ef4d592d807bc17b7971" + "reference": "e99b4e94d124b29ee4cf3140e1b537d2dad8cec9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/762ee56b2649659380e0ef4d592d807bc17b7971", - "reference": "762ee56b2649659380e0ef4d592d807bc17b7971", + "url": "https://api.github.com/repos/symfony/yaml/zipball/e99b4e94d124b29ee4cf3140e1b537d2dad8cec9", + "reference": "e99b4e94d124b29ee4cf3140e1b537d2dad8cec9", "shasum": "" }, "require": { @@ -3794,7 +3998,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v6.4.12" + "source": "https://github.com/symfony/yaml/tree/v6.4.13" }, "funding": [ { @@ -3810,20 +4014,20 @@ "type": "tidelift" } ], - "time": "2024-09-17T12:47:12+00:00" + "time": "2024-09-25T14:18:03+00:00" }, { "name": "twig/twig", - "version": "v3.14.0", + "version": "v3.15.0", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "126b2c97818dbff0cdf3fbfc881aedb3d40aae72" + "reference": "2d5b3964cc21d0188633d7ddce732dc8e874db02" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/126b2c97818dbff0cdf3fbfc881aedb3d40aae72", - "reference": "126b2c97818dbff0cdf3fbfc881aedb3d40aae72", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/2d5b3964cc21d0188633d7ddce732dc8e874db02", + "reference": "2d5b3964cc21d0188633d7ddce732dc8e874db02", "shasum": "" }, "require": { @@ -3877,7 +4081,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.14.0" + "source": "https://github.com/twigphp/Twig/tree/v3.15.0" }, "funding": [ { @@ -3889,7 +4093,7 @@ "type": "tidelift" } ], - "time": "2024-09-09T17:55:12+00:00" + "time": "2024-11-17T15:59:19+00:00" }, { "name": "web-token/jwt-framework", @@ -4112,12 +4316,12 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "51e3fa290bca57eca7ba6c261b8a1278eb13a98a" + "reference": "b33a18b5d222c63472a4b41f6fa3e15e591c9595" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/51e3fa290bca57eca7ba6c261b8a1278eb13a98a", - "reference": "51e3fa290bca57eca7ba6c261b8a1278eb13a98a", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/b33a18b5d222c63472a4b41f6fa3e15e591c9595", + "reference": "b33a18b5d222c63472a4b41f6fa3e15e591c9595", "shasum": "" }, "conflict": { @@ -4125,7 +4329,7 @@ "admidio/admidio": "<4.3.12", "adodb/adodb-php": "<=5.20.20|>=5.21,<=5.21.3", "aheinze/cockpit": "<2.2", - "aimeos/ai-admin-graphql": ">=2022.04.1,<2022.10.10|>=2023.04.1,<2023.10.6|>=2024.04.1,<2024.04.6", + "aimeos/ai-admin-graphql": ">=2022.04.1,<2022.10.10|>=2023.04.1,<2023.10.6|>=2024.04.1,<2024.07.2", "aimeos/ai-admin-jsonadm": "<2020.10.13|>=2021.04.1,<2021.10.6|>=2022.04.1,<2022.10.3|>=2023.04.1,<2023.10.4|==2024.04.1", "aimeos/ai-client-html": ">=2020.04.1,<2020.10.27|>=2021.04.1,<2021.10.22|>=2022.04.1,<2022.10.13|>=2023.04.1,<2023.10.15|>=2024.04.1,<2024.04.7", "aimeos/ai-controller-frontend": "<2020.10.15|>=2021.04.1,<2021.10.8|>=2022.04.1,<2022.10.8|>=2023.04.1,<2023.10.9|==2024.04.1", @@ -4137,6 +4341,7 @@ "alextselegidis/easyappointments": "<1.5", "alterphp/easyadmin-extension-bundle": ">=1.2,<1.2.11|>=1.3,<1.3.1", "amazing/media2click": ">=1,<1.3.3", + "ameos/ameos_tarteaucitron": "<1.2.23", "amphp/artax": "<1.0.6|>=2,<2.0.6", "amphp/http": "<=1.7.2|>=2,<=2.1", "amphp/http-client": ">=4,<4.4", @@ -4162,6 +4367,7 @@ "azuracast/azuracast": "<0.18.3", "backdrop/backdrop": "<1.27.3|>=1.28,<1.28.2", "backpack/crud": "<3.4.9", + "backpack/filemanager": "<2.0.2|>=3,<3.0.9", "bacula-web/bacula-web": "<8.0.0.0-RC2-dev", "badaso/core": "<2.7", "bagisto/bagisto": "<2.1", @@ -4169,7 +4375,7 @@ "barrelstrength/sprout-forms": "<3.9", "barryvdh/laravel-translation-manager": "<0.6.2", "barzahlen/barzahlen-php": "<2.0.1", - "baserproject/basercms": "<5.0.9", + "baserproject/basercms": "<=5.1.1", "bassjobsen/bootstrap-3-typeahead": ">4.0.2", "bbpress/bbpress": "<2.6.5", "bcosca/fatfree": "<3.7.2", @@ -4225,7 +4431,7 @@ "contao/managed-edition": "<=1.5", "corveda/phpsandbox": "<1.3.5", "cosenary/instagram": "<=2.3", - "craftcms/cms": "<4.6.2|>=5,<=5.2.2", + "craftcms/cms": "<=4.12.6.1|>=5,<=5.4.7.1", "croogo/croogo": "<4", "cuyz/valinor": "<0.12", "czim/file-handling": "<1.5|>=2,<2.3", @@ -4294,13 +4500,14 @@ "ezsystems/ezpublish-legacy": "<=2017.12.7.3|>=2018.6,<=2019.03.5.1", "ezsystems/platform-ui-assets-bundle": ">=4.2,<4.2.3", "ezsystems/repository-forms": ">=2.3,<2.3.2.1-dev|>=2.5,<2.5.15", - "ezyang/htmlpurifier": "<4.1.1", + "ezyang/htmlpurifier": "<=4.2", "facade/ignition": "<1.16.15|>=2,<2.4.2|>=2.5,<2.5.2", "facturascripts/facturascripts": "<=2022.08", "fastly/magento2": "<1.2.26", "feehi/cms": "<=2.1.1", "feehi/feehicms": "<=2.1.1", "fenom/fenom": "<=2.12.1", + "filament/actions": ">=3.2,<3.2.123", "filament/infolists": ">=3,<3.2.115", "filament/tables": ">=3,<3.2.115", "filegator/filegator": "<7.8", @@ -4434,8 +4641,9 @@ "lara-zeus/artemis": ">=1,<=1.0.6", "lara-zeus/dynamic-dashboard": ">=3,<=3.0.1", "laravel/fortify": "<1.11.1", - "laravel/framework": "<6.20.44|>=7,<7.30.6|>=8,<8.75", + "laravel/framework": "<6.20.45|>=7,<7.30.7|>=8,<8.83.28|>=9,<9.52.17|>=10,<10.48.23|>=11,<11.31", "laravel/laravel": ">=5.4,<5.4.22", + "laravel/reverb": "<1.4", "laravel/socialite": ">=1,<2.0.10", "latte/latte": "<2.10.8", "lavalite/cms": "<=9|==10.1", @@ -4454,6 +4662,7 @@ "lms/routes": "<2.1.1", "localizationteam/l10nmgr": "<7.4|>=8,<8.7|>=9,<9.2", "luyadev/yii-helpers": "<1.2.1", + "maestroerror/php-heic-to-jpg": "<1.0.5", "magento/community-edition": "<2.4.5|==2.4.5|>=2.4.5.0-patch1,<2.4.5.0-patch10|==2.4.6|>=2.4.6.0-patch1,<2.4.6.0-patch8|>=2.4.7.0-beta1,<2.4.7.0-patch3", "magento/core": "<=1.9.4.5", "magento/magento1ce": "<1.9.4.3-dev", @@ -4467,9 +4676,10 @@ "matyhtf/framework": "<3.0.6", "mautic/core": "<4.4.13|>=5,<5.1.1", "mautic/core-lib": ">=1.0.0.0-beta,<4.4.13|>=5.0.0.0-alpha,<5.1.1", + "maximebf/debugbar": "<1.19", "mdanter/ecc": "<2", "mediawiki/cargo": "<3.6.1", - "mediawiki/core": "<1.36.2", + "mediawiki/core": "<1.39.5|==1.40", "mediawiki/matomo": "<2.4.3", "mediawiki/semantic-media-wiki": "<4.0.2", "melisplatform/melis-asset-manager": "<5.0.1", @@ -4489,7 +4699,7 @@ "mojo42/jirafeau": "<4.4", "mongodb/mongodb": ">=1,<1.9.2", "monolog/monolog": ">=1.8,<1.12", - "moodle/moodle": "<4.3.5|>=4.4.0.0-beta,<4.4.1", + "moodle/moodle": "<4.3.8|>=4.4,<4.4.4", "mos/cimage": "<0.7.19", "movim/moxl": ">=0.8,<=0.10", "movingbytes/social-network": "<=1.2.1", @@ -4537,7 +4747,7 @@ "openmage/magento-lts": "<20.10.1", "opensolutions/vimbadmin": "<=3.0.15", "opensource-workshop/connect-cms": "<1.7.2|>=2,<2.3.2", - "orchid/platform": ">=9,<9.4.4|>=14.0.0.0-alpha4,<14.5", + "orchid/platform": ">=8,<14.43", "oro/calendar-bundle": ">=4.2,<=4.2.6|>=5,<=5.0.6|>=5.1,<5.1.1", "oro/commerce": ">=4.1,<5.0.11|>=5.1,<5.1.1", "oro/crm": ">=1.7,<1.7.4|>=3.1,<4.1.17|>=4.2,<4.2.7", @@ -4568,7 +4778,7 @@ "phenx/php-svg-lib": "<0.5.2", "php-censor/php-censor": "<2.0.13|>=2.1,<2.1.5", "php-mod/curl": "<2.3.2", - "phpbb/phpbb": "<3.2.10|>=3.3,<3.3.1", + "phpbb/phpbb": "<3.3.11", "phpems/phpems": ">=6,<=6.1.3", "phpfastcache/phpfastcache": "<6.1.5|>=7,<7.1.2|>=8,<8.0.7", "phpmailer/phpmailer": "<6.5", @@ -4576,8 +4786,8 @@ "phpmyadmin/phpmyadmin": "<5.2.1", "phpmyfaq/phpmyfaq": "<3.2.5|==3.2.5", "phpoffice/common": "<0.2.9", - "phpoffice/phpexcel": "<1.8", - "phpoffice/phpspreadsheet": "<1.29.2|>=2,<2.1.1|>=2.2,<2.3", + "phpoffice/phpexcel": "<1.8.1", + "phpoffice/phpspreadsheet": "<1.29.4|>=2,<2.1.3|>=2.2,<2.3.2|>=3.3,<3.4", "phpseclib/phpseclib": "<2.0.47|>=3,<3.0.36", "phpservermon/phpservermon": "<3.6", "phpsysinfo/phpsysinfo": "<3.4.3", @@ -4614,7 +4824,7 @@ "processwire/processwire": "<=3.0.229", "propel/propel": ">=2.0.0.0-alpha1,<=2.0.0.0-alpha7", "propel/propel1": ">=1,<=1.7.1", - "pterodactyl/panel": "<1.11.6", + "pterodactyl/panel": "<1.11.8", "ptheofan/yii2-statemachine": ">=2.0.0.0-RC1-dev,<=2", "ptrofimov/beanstalk_console": "<1.7.14", "pubnub/pubnub": "<6.1", @@ -4631,7 +4841,7 @@ "rap2hpoutre/laravel-log-viewer": "<0.13", "react/http": ">=0.7,<1.9", "really-simple-plugins/complianz-gdpr": "<6.4.2", - "redaxo/source": "<=5.17.1", + "redaxo/source": "<5.18", "remdex/livehelperchat": "<4.29", "reportico-web/reportico": "<=8.1", "rhukster/dom-sanitizer": "<1.0.7", @@ -4687,7 +4897,7 @@ "slim/slim": "<2.6", "slub/slub-events": "<3.0.3", "smarty/smarty": "<4.5.3|>=5,<5.1.1", - "snipe/snipe-it": "<7.0.10", + "snipe/snipe-it": "<=7.0.13", "socalnick/scn-social-auth": "<1.15.2", "socialiteproviders/steam": "<1.1", "spatie/browsershot": "<3.57.4", @@ -4698,7 +4908,7 @@ "squizlabs/php_codesniffer": ">=1,<2.8.1|>=3,<3.0.1", "ssddanbrown/bookstack": "<24.05.1", "starcitizentools/citizen-skin": ">=2.6.3,<2.31", - "statamic/cms": "<4.46|>=5.3,<5.6.2", + "statamic/cms": "<=5.16", "stormpath/sdk": "<9.9.99", "studio-42/elfinder": "<=2.1.64", "studiomitte/friendlycaptcha": "<0.1.4", @@ -4727,7 +4937,8 @@ "symfony/error-handler": ">=4.4,<4.4.4|>=5,<5.0.4", "symfony/form": ">=2.3,<2.3.35|>=2.4,<2.6.12|>=2.7,<2.7.50|>=2.8,<2.8.49|>=3,<3.4.20|>=4,<4.0.15|>=4.1,<4.1.9|>=4.2,<4.2.1", "symfony/framework-bundle": ">=2,<2.3.18|>=2.4,<2.4.8|>=2.5,<2.5.2|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7|>=5.3.14,<5.3.15|>=5.4.3,<5.4.4|>=6.0.3,<6.0.4", - "symfony/http-foundation": ">=2,<2.8.52|>=3,<3.4.35|>=4,<4.2.12|>=4.3,<4.3.8|>=4.4,<4.4.7|>=5,<5.0.7", + "symfony/http-client": ">=4.3,<5.4.47|>=6,<6.4.15|>=7,<7.1.8", + "symfony/http-foundation": "<5.4.46|>=6,<6.4.14|>=7,<7.1.7", "symfony/http-kernel": ">=2,<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.2.6", "symfony/intl": ">=2.7,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13", "symfony/maker-bundle": ">=1.27,<1.29.2|>=1.30,<1.31.1", @@ -4735,20 +4946,22 @@ "symfony/phpunit-bridge": ">=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", "symfony/polyfill": ">=1,<1.10", "symfony/polyfill-php55": ">=1,<1.10", + "symfony/process": "<5.4.46|>=6,<6.4.14|>=7,<7.1.7", "symfony/proxy-manager-bridge": ">=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", "symfony/routing": ">=2,<2.0.19", + "symfony/runtime": ">=5.3,<5.4.46|>=6,<6.4.14|>=7,<7.1.7", "symfony/security": ">=2,<2.7.51|>=2.8,<3.4.49|>=4,<4.4.24|>=5,<5.2.8", - "symfony/security-bundle": ">=2,<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.2.6", + "symfony/security-bundle": ">=2,<4.4.50|>=5,<5.4.20|>=6,<6.0.20|>=6.1,<6.1.12|>=6.2,<6.4.10|>=7,<7.0.10|>=7.1,<7.1.3", "symfony/security-core": ">=2.4,<2.6.13|>=2.7,<2.7.9|>=2.7.30,<2.7.32|>=2.8,<3.4.49|>=4,<4.4.24|>=5,<5.2.9", "symfony/security-csrf": ">=2.4,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", "symfony/security-guard": ">=2.8,<3.4.48|>=4,<4.4.23|>=5,<5.2.8", - "symfony/security-http": ">=2.3,<2.3.41|>=2.4,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.2.12|>=4.3,<4.3.8|>=4.4,<4.4.7|>=5,<5.0.7|>=5.1,<5.2.8|>=5.3,<5.3.2|>=5.4,<5.4.31|>=6,<6.3.8", + "symfony/security-http": ">=2.3,<2.3.41|>=2.4,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.2.12|>=4.3,<4.3.8|>=4.4,<4.4.7|>=5,<5.0.7|>=5.1,<5.2.8|>=5.3,<5.4.47|>=6,<6.4.15|>=7,<7.1.8", "symfony/serializer": ">=2,<2.0.11|>=4.1,<4.4.35|>=5,<5.3.12", - "symfony/symfony": ">=2,<4.4.51|>=5,<5.4.31|>=6,<6.3.8", + "symfony/symfony": "<5.4.47|>=6,<6.4.15|>=7,<7.1.8", "symfony/translation": ">=2,<2.0.17", "symfony/twig-bridge": ">=2,<4.4.51|>=5,<5.4.31|>=6,<6.3.8", "symfony/ux-autocomplete": "<2.11.2", - "symfony/validator": ">=2,<2.0.24|>=2.1,<2.1.12|>=2.2,<2.2.5|>=2.3,<2.3.3", + "symfony/validator": "<5.4.43|>=6,<6.4.11|>=7,<7.1.4", "symfony/var-exporter": ">=4.2,<4.2.12|>=4.3,<4.3.8", "symfony/web-profiler-bundle": ">=2,<2.3.19|>=2.4,<2.4.9|>=2.5,<2.5.4", "symfony/webhook": ">=6.3,<6.3.8", @@ -4774,14 +4987,14 @@ "tobiasbg/tablepress": "<=2.0.0.0-RC1", "topthink/framework": "<6.0.17|>=6.1,<=8.0.4", "topthink/think": "<=6.1.1", - "topthink/thinkphp": "<=3.2.3", + "topthink/thinkphp": "<=3.2.3|>=6.1.3,<=8.0.4", "torrentpier/torrentpier": "<=2.4.3", "tpwd/ke_search": "<4.0.3|>=4.1,<4.6.6|>=5,<5.0.2", "tribalsystems/zenario": "<=9.7.61188", "truckersmp/phpwhois": "<=4.3.1", "ttskch/pagination-service-provider": "<1", "twbs/bootstrap": "<=3.4.1|>=4,<=4.6.2", - "twig/twig": "<1.44.8|>=2,<2.16.1|>=3,<3.11.1|>=3.12,<3.14", + "twig/twig": "<3.11.2|>=3.12,<3.14.1", "typo3/cms": "<9.5.29|>=10,<10.4.35|>=11,<11.5.23|>=12,<12.2", "typo3/cms-backend": "<4.1.14|>=4.2,<4.2.15|>=4.3,<4.3.7|>=4.4,<4.4.4|>=7,<=7.6.50|>=8,<=8.7.39|>=9,<=9.5.24|>=10,<10.4.46|>=11,<11.5.40|>=12,<12.4.21|>=13,<13.3.1", "typo3/cms-core": "<=8.7.56|>=9,<=9.5.47|>=10,<=10.4.44|>=11,<=11.5.36|>=12,<=12.4.14|>=13,<=13.1", @@ -4800,6 +5013,7 @@ "ua-parser/uap-php": "<3.8", "uasoft-indonesia/badaso": "<=2.9.7", "unisharp/laravel-filemanager": "<2.6.4", + "unopim/unopim": "<0.1.5", "userfrosting/userfrosting": ">=0.3.1,<4.6.3", "usmanhalalit/pixie": "<1.0.3|>=2,<2.0.2", "uvdesk/community-skeleton": "<=1.1.1", @@ -4845,7 +5059,7 @@ "xataface/xataface": "<3", "xpressengine/xpressengine": "<3.0.15", "yab/quarx": "<2.4.5", - "yeswiki/yeswiki": "<4.1", + "yeswiki/yeswiki": "<=4.4.4", "yetiforce/yetiforce-crm": "<=6.4", "yidashi/yii2cmf": "<=2", "yii2mod/yii2-cms": "<1.9.2", @@ -4936,7 +5150,7 @@ "type": "tidelift" } ], - "time": "2024-10-21T20:05:20+00:00" + "time": "2024-11-19T21:04:39+00:00" } ], "aliases": [], @@ -4950,7 +5164,7 @@ "platform": { "php": "^8.1" }, - "platform-dev": [], + "platform-dev": {}, "platform-overrides": { "php": "8.1" }, diff --git a/configurations/README.md b/configurations/README.md new file mode 100644 index 00000000..ac30f4ff --- /dev/null +++ b/configurations/README.md @@ -0,0 +1,5 @@ +You can add folders here for certain synchronizations flows. + +These json files are meant to be copy/pasted into a Postman request that creates that specific type of object. + +Reminder to always leave out sensitive information like authorization keys et cetera. \ No newline at end of file diff --git a/configurations/sharepoint-woo/mappings/sharepoint-woo-verzoek-to-publications.json b/configurations/sharepoint-woo/mappings/sharepoint-woo-verzoek-to-publications.json new file mode 100644 index 00000000..a6cdbade --- /dev/null +++ b/configurations/sharepoint-woo/mappings/sharepoint-woo-verzoek-to-publications.json @@ -0,0 +1,30 @@ +{ + "name": "Sharepoint Woo verzoek to Publication", + "description": "", + "version": "0.0.1", + "mapping": { + "title": "d.woo_x005f_titel", + "description": "d.woo_x005f_beschrijving", + "summary": "d.woo_x005f_samenvatting", + "category": "d.woo_x005f_categorie", + "published": "{% if d['woo_x005f_publicatiedatum']|default %}{{ d['woo_x005f_publicatiedatum']|date('Y-m-d') }}{% endif %}", + "modified": "d.vti_x005f_nexttolasttimemodified", + "attachments": "[ {% for file in fileUrls %} { {% if file['Name']|default %}\"title\": \"{{ file['Name'] }}\",{% endif %}{% if file['d']['document_x005f_label']|default %}\"labels\": [\"{{ file['d']['document_x005f_label'] }}\"],{% endif %}{% if file['__metadata']['uri']|default %}\"downloadUrl\": {\"accessUrl\": \"{{ file['__metadata']['uri']~'/$value' }}\", \"source\": \"1\"}{% endif %} }{{ loop.last ? '' : ',' }} {% endfor %} ]", + "status": "Concept", + "catalog": "", + "publicationType": "" + }, + "unset": [], + "cast": { + "title": "unsetIfValue==d.woo_x005f_titel", + "description": "unsetIfValue==d.woo_x005f_beschrijving", + "summary": "unsetIfValue==d.woo_x005f_samenvatting", + "category": "unsetIfValue==d.woo_x005f_categorie", + "published": "unsetIfValue==", + "modified": "unsetIfValue==d.vti_x005f_nexttolasttimemodified", + "attachments": "jsonToArray", + "publicationType": "unsetIfValue==", + "catalog": "unsetIfValue==" + }, + "passThrough": false +} diff --git a/configurations/sharepoint-woo/mappings/xxllnc-suite-to-publications.json b/configurations/sharepoint-woo/mappings/xxllnc-suite-to-publications.json new file mode 100644 index 00000000..744823b1 --- /dev/null +++ b/configurations/sharepoint-woo/mappings/xxllnc-suite-to-publications.json @@ -0,0 +1,26 @@ +{ + "name": "Xxllnc suite to Publication", + "version": "0.0.1", + "mapping": { + "title": "omschrijving", + "summary": "zaaktypeomschrijving", + "description": "zaaktypeomschrijving", + "category": "{% if zaaktypecode|default %}{% set wooVerzoekenEnBesluiten = ['LP00000431', 'B1873', 'cherry'] %}{% set klachtoordelen = ['LP00000091', 'LP00001132', 'LP00000121', 'B0757', 'LP00000832', 'LP00001096'] %}{% if zaaktypecode in wooVerzoekenEnBesluiten %}{{ 'Woo-verzoeken en -besluiten' }}{% elseif zaaktypecode in klachtoordelen %}{{ 'Klachtoordelen' }}{% endif %}{% endif %}", + "published": "startdatum", + "modified": "{{ 'now'|date('H:i:sTm-d-Y') }}", + "attachments": "[{% if files|default %}{% for file in files %} { {% if file['titel']|default %}\"title\": \"{{ file['titel'] }}\",{% endif %}\"labels\": [\"{{ 'Informatieverzoek' }}\"],{% if file['formaat']|default %}\"extension\": \"{{ file['formaat']|split('/')|last }}\",\"type\": \"{{ file['formaat'] }}\",{% endif %}{% if file['inhoud']|default and file['formaat']|default %}\"accessUrl\": \"data:{{ file['formaat'] }};base64,{{ file.inhoud }}\"{% endif %} }{{ loop.last ? '' : ',' }} {% endfor %}{% endif %}]", + "status": "Concept" + }, + "unset": [ + "" + ], + "cast": { + "title": "unsetIfValue==omschrijving", + "summary": "unsetIfValue==zaaktypeomschrijving", + "description": "unsetIfValue==zaaktypeomschrijving", + "category": "unsetIfValue==", + "published": "unsetIfValue==startdatum", + "attachments": "jsonToArray" + }, + "passThrough": false +} \ No newline at end of file diff --git a/configurations/sharepoint-woo/sources/sharepoint.json b/configurations/sharepoint-woo/sources/sharepoint.json new file mode 100644 index 00000000..ddfcdbd6 --- /dev/null +++ b/configurations/sharepoint-woo/sources/sharepoint.json @@ -0,0 +1,34 @@ +{ + "name": "Sharepoint Woo API", + "description": "Sharepoint Woo API", + "reference": null, + "version": "0.0.1", + "location": "", + "isEnabled": true, + "type": "api", + "authorizationHeader": "Authorization", + "auth": "none", + "authorizationPassthroughMethod": "header", + "authenticationConfig": [], + "configuration": { + "headers": { + "Authorization": "Bearer {{ oauthToken(source) }}", + "Accept": "application/json;odata=verbose" + }, + "authentication": { + "grant_type": "", + "scope": "", + "authentication": "body", + "client_id": "", + "client_secret": "", + "client_assertion_type": "", + "private_key": "", + "x5t": "", + "payload": "", + "tokenUrl": "" + } + }, + "logRetention": 3600, + "errorRetention": 86400, + "test": false +} \ No newline at end of file diff --git a/configurations/sharepoint-woo/sources/xxllnc-suite.json b/configurations/sharepoint-woo/sources/xxllnc-suite.json new file mode 100644 index 00000000..ba40f107 --- /dev/null +++ b/configurations/sharepoint-woo/sources/xxllnc-suite.json @@ -0,0 +1,20 @@ +{ + "name": "Xxllnc suite API", + "description": "Xxllnc suite API", + "reference": null, + "version": "0.0.0", + "location": "", + "isEnabled": true, + "type": "api", + "authorizationHeader": null, + "auth": "none", + "authorizationPassthroughMethod": "header", + "authenticationConfig": [], + "configuration": { + "auth.0": "username", + "auth.1": "password" + }, + "logRetention": 3600, + "errorRetention": 86400, + "test": false +} \ No newline at end of file diff --git a/configurations/sharepoint-woo/synchronizations/sharepoint-convenanten-to-publications.json b/configurations/sharepoint-woo/synchronizations/sharepoint-convenanten-to-publications.json new file mode 100644 index 00000000..539175ce --- /dev/null +++ b/configurations/sharepoint-woo/synchronizations/sharepoint-convenanten-to-publications.json @@ -0,0 +1,26 @@ +{ + "name": "Sharepoint Convenanten to Publications", + "description": "", + "version": "0.0.1", + "sourceId": "1", + "sourceType": "api", + "sourceHash": "", + "sourceTargetMapping": "1", + "sourceConfig": { + "idPosition": "UniqueId", + "resultsPosition": "d.results", + "endpoint": "\/Web\/GetFolderByServerRelativePath(decodedurl='\/WOO\/Convenanten')\/folders", + "extraDataConfigs.0.dynamicEndpointLocation": "Properties.__deferred.uri", + "extraDataConfigs.0.mergeExtraData": "true", + "extraDataConfigs.1.dynamicEndpointLocation": "Files.__deferred.uri", + "extraDataConfigs.1.mergeExtraData": "true", + "extraDataConfigs.1.keyToSetExtraData": "fileUrls", + "extraDataConfigs.1.resultsLocation": "d.results", + "extraDataConfigs.1.extraDataConfigPerResult.dynamicEndpointLocation": "Properties.__deferred.uri", + "extraDataConfigs.1.extraDataConfigPerResult.mergeExtraData": "true", + "headers": [], + "query": [] + }, + "targetId": "1/1", + "targetType": "register/schema" +} \ No newline at end of file diff --git a/configurations/sharepoint-woo/synchronizations/sharepoint-woo-verzoeken-to-publications.json b/configurations/sharepoint-woo/synchronizations/sharepoint-woo-verzoeken-to-publications.json new file mode 100644 index 00000000..88568b92 --- /dev/null +++ b/configurations/sharepoint-woo/synchronizations/sharepoint-woo-verzoeken-to-publications.json @@ -0,0 +1,26 @@ +{ + "name": "Sharepoint Woo-verzoeken en -besluiten to Publications", + "description": "", + "version": "0.0.1", + "sourceId": "1", + "sourceType": "api", + "sourceHash": "", + "sourceTargetMapping": "1", + "sourceConfig": { + "idPosition": "UniqueId", + "resultsPosition": "d.results", + "endpoint": "/Web/GetFolderByServerRelativePath(decodedurl='/WOO/Woo-verzoeken en -besluiten')/folders", + "extraDataConfigs.0.dynamicEndpointLocation": "Properties.__deferred.uri", + "extraDataConfigs.0.mergeExtraData": "true", + "extraDataConfigs.1.dynamicEndpointLocation": "Files.__deferred.uri", + "extraDataConfigs.1.mergeExtraData": "true", + "extraDataConfigs.1.keyToSetExtraData": "fileUrls", + "extraDataConfigs.1.resultsLocation": "d.results", + "extraDataConfigs.1.extraDataConfigPerResult.dynamicEndpointLocation": "Properties.__deferred.uri", + "extraDataConfigs.1.extraDataConfigPerResult.mergeExtraData": "true", + "headers": [], + "query": [] + }, + "targetId": "1/1", + "targetType": "register/schema" +} \ No newline at end of file diff --git a/configurations/sharepoint-woo/synchronizations/xxllnc-suite-to-publications.json b/configurations/sharepoint-woo/synchronizations/xxllnc-suite-to-publications.json new file mode 100644 index 00000000..91922ef1 --- /dev/null +++ b/configurations/sharepoint-woo/synchronizations/xxllnc-suite-to-publications.json @@ -0,0 +1,25 @@ +{ + "name": "Xxllnc suite to Publications", + "description": "", + "version": "0.0.1", + "sourceId": "1", + "sourceType": "api", + "sourceHash": "", + "sourceTargetMapping": "1", + "sourceConfig": { + "idPosition": "identificatie", + "resultsPosition": "results", + "endpoint": "\/tlb\/zaaksysteem\/api\/v1\/zaken", + "headers": [], + "query.startdatum__gte": "2024-08-01", + "query.einddatum__lt": "2025-01-01", + "usesPagination": "false", + "extraDataConfigs.0.staticEndpoint": "/tlb/zaaksysteem/api/v1/zaken/{{ originId }}/informatieobjecten", + "extraDataConfigs.0.mergeExtraData": "true", + "extraDataConfigs.0.keyToSetExtraData": "files", + "extraDataConfigs.0.resultsLocation": "results", + "extraDataConfigs.0.extraDataConfigPerResult.staticEndpoint": "/tlb/zaaksysteem/api/v1/informatieobjecten/{{ originId }}" + }, + "targetId": "1/1", + "targetType": "register/schema" +} \ No newline at end of file diff --git a/lib/Controller/JobsController.php b/lib/Controller/JobsController.php index 4ffe28b8..201657ae 100644 --- a/lib/Controller/JobsController.php +++ b/lib/Controller/JobsController.php @@ -236,13 +236,18 @@ public function run(int $id): JSONResponse } try { - $this->jobList->getById($job->getJobListId())->start($this->jobList); - $lastLog = $this->jobLogMapper->getLastCallLog(); - if ($lastLog !== null) { - return new JSONResponse(data: $lastLog, statusCode: 200); - } - - return new JSONResponse(data: ['error' => 'No job log could be found, job did not went succesfully or failed to log anything'], statusCode: 500); + $job = $this->jobList->getById($job->getJobListId()); + if ($job !== null) { + $job->setArgument(['jobId' => $id, 'forceRun' => true]); + $job->start($this->jobList); + + $lastLog = $this->jobLogMapper->getLastCallLog(); + if ($lastLog !== null && ($lastLog->getJobId() === null || (int) $lastLog->getJobId() === $id)) { + return new JSONResponse(data: $lastLog, statusCode: 200); + } + } + + return new JSONResponse(data: ['error' => 'No job log could be found, job did not go successfully or failed to log anything'], statusCode: 500); } catch (Exception $exception) { return new JSONResponse(data: ['error' => $exception->getMessage()], statusCode: 400); } diff --git a/lib/Controller/SynchronizationsController.php b/lib/Controller/SynchronizationsController.php index 9ceb1e9b..85b5171d 100644 --- a/lib/Controller/SynchronizationsController.php +++ b/lib/Controller/SynchronizationsController.php @@ -2,6 +2,7 @@ namespace OCA\OpenConnector\Controller; +use GuzzleHttp\Exception\GuzzleException; use OCA\OpenConnector\Service\ObjectService; use OCA\OpenConnector\Service\SearchService; use OCA\OpenConnector\Service\SynchronizationService; @@ -15,6 +16,8 @@ use OCP\IRequest; use Exception; use OCP\AppFramework\Db\DoesNotExistException; +use Psr\Container\ContainerExceptionInterface; +use Psr\Container\NotFoundExceptionInterface; class SynchronizationsController extends Controller { @@ -213,33 +216,36 @@ public function logs(int $id): JSONResponse } } - /** - * Tests a synchronization - * - * This method tests a synchronization without persisting anything to the database. - * - * @NoAdminRequired - * @NoCSRFRequired - * - * @param int $id The ID of the synchronization - * - * @return JSONResponse A JSON response containing the test results - * - * @example - * Request: - * empty POST - * - * Response: - * { - * "resultObject": { - * "fullName": "John Doe", - * "userAge": 30, - * "contactEmail": "john@example.com" - * }, - * "isValid": true, - * "validationErrors": [] - * } - */ + /** + * Tests a synchronization + * + * This method tests a synchronization without persisting anything to the database. + * + * @NoAdminRequired + * @NoCSRFRequired + * + * @param int $id The ID of the synchronization + * + * @return JSONResponse A JSON response containing the test results + * @throws GuzzleException + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + * + * @example + * Request: + * empty POST + * + * Response: + * { + * "resultObject": { + * "fullName": "John Doe", + * "userAge": 30, + * "contactEmail": "john@example.com" + * }, + * "isValid": true, + * "validationErrors": [] + * } + */ public function test(int $id): JSONResponse { try { @@ -263,4 +269,4 @@ public function test(int $id): JSONResponse return new JSONResponse($resultFromTest, 200); } -} \ No newline at end of file +} diff --git a/lib/Cron/ActionTask.php b/lib/Cron/ActionTask.php index a5b3a54d..ec412b38 100644 --- a/lib/Cron/ActionTask.php +++ b/lib/Cron/ActionTask.php @@ -8,6 +8,8 @@ use OCP\BackgroundJob\TimedJob; use OCP\AppFramework\Utility\ITimeFactory; use OCP\BackgroundJob\IJobList; +use OCP\IUserManager; +use OCP\IUserSession; use Psr\Container\ContainerInterface; use Symfony\Component\Uid\Uuid; use DateInterval; @@ -30,7 +32,9 @@ public function __construct( JobMapper $jobMapper, JobLogMapper $jobLogMapper, IJobList $jobList, - ContainerInterface $containerInterface + ContainerInterface $containerInterface, + private IUserSession $userSession, + private IUserManager $userManager, ) { parent::__construct($time); $this->jobMapper = $jobMapper; @@ -59,27 +63,53 @@ public function __construct( public function run($argument) { // if we do not have a job id then everything is wrong - if (isset($arguments['jobId']) === true && is_int($argument['jobId']) === true) { - return; + if (isset($argument['jobId']) === false || is_int($argument['jobId']) === false) { + return $this->jobLogMapper->createFromArray([ + 'jobId' => 'null', + 'level' => 'ERROR', + 'message' => "Couldn't find a jobId in the action argument" + ]); } - // Let's get the job, the user might have deleted it in the mean time + // Let's get the job, the user might have deleted it in the meantime try { $job = $this->jobMapper->find($argument['jobId']); } catch (Exception $e) { - return; + return $this->jobLogMapper->createFromArray([ + 'jobId' => $argument['jobId'], + 'level' => 'ERROR', + 'message' => "Couldn't find a Job with this jobId, message: ".$e->getMessage() + ]); } + $forceRun = false; + $stackTrace = []; + if (isset($argument['forceRun']) === true && $argument['forceRun'] === true) { + $forceRun = true; + $stackTrace[] = 'Doing a force run for this job, ignoring "enabled" & "nextRun" check...'; + } + // If the job is not enabled, we don't need to do anything - if ($job->getIsEnabled() === false) { - return; + if ($forceRun === false && $job->getIsEnabled() === false) { + return $this->jobLogMapper->createForJob($job, [ + 'level' => 'WARNING', + 'message' => 'This job is disabled' + ]); } - // if the next run is in the the future, we don't need to do anything - if ($job->getNextRun() !== null && $job->getNextRun() > new DateTime()) { - return; + // if the next run is in the future, we don't need to do anything + if ($forceRun === false && $job->getNextRun() !== null && $job->getNextRun() > new DateTime()) { + return $this->jobLogMapper->createForJob($job, [ + 'level' => 'WARNING', + 'message' => 'Next Run is still in the future for this job' + ]); } + if (empty($job->getUserId()) === false && $this->userSession->getUser() === null) { + $user = $this->userManager->get($job->getUserId()); + $this->userSession->setUser($user); + } + $time_start = microtime(true); $action = $this->containerInterface->get($job->getJobClass()); @@ -93,24 +123,23 @@ public function run($argument) $executionTime = ( $time_end - $time_start ) * 1000; // deal with single run - if ($job->isSingleRun() === true) { + if ($forceRun === false && $job->isSingleRun() === true) { $job->setIsEnabled(false); } - // Update the job - $job->setLastRun(new DateTime()); - $job->setNextRun(new DateTime()); - $this->jobMapper->update($job); + if ($forceRun === false) { + $job->setLastRun(new DateTime()); + $nextRun = new DateTime('now + '.$job->getInterval().' seconds'); + $nextRun->setTime(hour: $nextRun->format('H'), minute: $nextRun->format('i')); + $job->setNextRun($nextRun); + $this->jobMapper->update($job); + } // Log the job - $jobLog = $this->jobLogMapper->createFromArray([ - 'jobId' => $job->getId(), - 'jobClass' => $job->getJobClass(), - 'jobListId' => $job->getJobListId(), - 'arguments' => $job->getArguments(), - 'lastRun' => $job->getLastRun(), - 'nextRun' => $job->getNextRun(), + $jobLog = $this->jobLogMapper->createForJob($job, [ + 'level' => 'INFO', + 'message' => 'Succes', 'executionTime' => $executionTime ]); @@ -123,10 +152,14 @@ public function run($argument) $jobLog->setMessage($result['message']); } if (isset($result['stackTrace']) === true) { - $jobLog->setStackTrace($result['stackTrace']); + $stackTrace = array_merge($stackTrace, $result['stackTrace']); } } + $jobLog->setStackTrace($stackTrace); + + $this->jobLogMapper->update(entity: $jobLog); + // Let's report back about what we have just done return $jobLog; } diff --git a/lib/Cron/LogCleanUpTask.php b/lib/Cron/LogCleanUpTask.php index 0af048ad..0aa7cf32 100644 --- a/lib/Cron/LogCleanUpTask.php +++ b/lib/Cron/LogCleanUpTask.php @@ -3,13 +3,35 @@ namespace OCA\OpenConnector\Cron; use OCA\OpenConnector\Db\CallLogMapper; +use OCA\OpenConnector\Db\JobLogMapper; +use OCA\OpenConnector\Db\JobMapper; +use OCP\BackgroundJob\IJobList; use OCP\BackgroundJob\TimedJob; use OCP\AppFramework\Utility\ITimeFactory; +use OCP\IUserManager; +use OCP\IUserSession; -class LogCleanUpTask +class LogCleanUpTask extends TimedJob { - public function doCron(array $arguments, CallLogMapper $callLogMapper){ - $callLogMapper = new ClearLogs(); + public function __construct( + ITimeFactory $time, + private readonly CallLogMapper $callLogMapper, + ) { + parent::__construct($time); + + // Run every 5 minutes + $this->setInterval(300); + + // Delay until low-load time + //$this->setTimeSensitivity(\OCP\BackgroundJob\IJob::TIME_SENSITIVE); + // Or $this->setTimeSensitivity(\OCP\BackgroundJob\IJob::TIME_INSENSITIVE); + + // Only run one instance of this job at a time + $this->setAllowParallelRuns(false); + } + + public function run(mixed $argument){ + $this->callLogMapper->clearLogs(); } } diff --git a/lib/Db/CallLogMapper.php b/lib/Db/CallLogMapper.php index 49cac829..615b5bb8 100644 --- a/lib/Db/CallLogMapper.php +++ b/lib/Db/CallLogMapper.php @@ -93,7 +93,7 @@ public function clearLogs(): bool // Build the delete query $qb->delete('openconnector_call_logs') - ->where($qb->expr()->lt('expired', $qb->createFunction('NOW()'))); + ->where($qb->expr()->lt('expires', $qb->createFunction('NOW()'))); // Execute the query and get the number of affected rows $result = $qb->execute(); diff --git a/lib/Db/JobLogMapper.php b/lib/Db/JobLogMapper.php index cc0dc683..cea21ca0 100644 --- a/lib/Db/JobLogMapper.php +++ b/lib/Db/JobLogMapper.php @@ -56,8 +56,28 @@ public function findAll(?int $limit = null, ?int $offset = null, ?array $filters return $this->findEntities($qb); } + public function createForJob(Job $job, array $object): JobLog + { + $jobObject = [ + 'jobId' => $job->getId(), + 'jobClass' => $job->getJobClass(), + 'jobListId' => $job->getJobListId(), + 'arguments' => $job->getArguments(), + 'lastRun' => $job->getLastRun(), + 'nextRun' => $job->getNextRun(), + ]; + + $object = array_merge($jobObject, $object); + + return $this->createFromArray($object); + } + public function createFromArray(array $object): JobLog { + if (isset($object['executionTime']) === false) { + $object['executionTime'] = 0; + } + $obj = new JobLog(); $obj->hydrate($object); // Set uuid diff --git a/lib/Db/Source.php b/lib/Db/Source.php index 85270d64..a1952937 100644 --- a/lib/Db/Source.php +++ b/lib/Db/Source.php @@ -39,12 +39,16 @@ class Source extends Entity implements JsonSerializable protected ?string $status = null; protected ?int $logRetention = 3600; // seconds to save all logs protected ?int $errorRetention = 86400; // seconds to save error logs + protected ?int $objectCount = null; + protected ?bool $test = null; + protected ?int $rateLimitLimit = null; // Indicates the total number of allowed requests within a specific time period. + protected ?int $rateLimitRemaining = null; // Specifies how many requests are still allowed within the current limit. + protected ?int $rateLimitReset = null; // A Unix Time Stamp that indicates when the rate limit will be reset. + protected ?int $rateLimitWindow = null; // Indicates how many seconds the client must wait before making new requests. protected ?DateTime $lastCall = null; protected ?DateTime $lastSync = null; - protected ?int $objectCount = null; protected ?DateTime $dateCreated = null; protected ?DateTime $dateModified = null; - protected ?bool $test = null; public function __construct() { $this->addType('uuid', 'string'); @@ -78,12 +82,16 @@ public function __construct() { $this->addType('status', 'string'); $this->addType('logRetention', 'integer'); $this->addType('errorRetention', 'integer'); + $this->addType('objectCount', 'integer'); + $this->addType('test', 'boolean'); + $this->addType('rateLimitLimit', 'integer'); + $this->addType('rateLimitRemaining', 'integer'); + $this->addType('rateLimitReset', 'integer'); + $this->addType('rateLimitWindow', 'integer'); $this->addType('lastCall', 'datetime'); $this->addType('lastSync', 'datetime'); - $this->addType('objectCount', 'integer'); $this->addType('dateCreated', 'datetime'); $this->addType('dateModified', 'datetime'); - $this->addType('test', 'boolean'); } public function getJsonFields(): array @@ -151,12 +159,16 @@ public function jsonSerialize(): array 'status' => $this->status, 'logRetention' => $this->logRetention, 'errorRetention' => $this->errorRetention, + 'objectCount' => $this->objectCount, + 'test' => $this->test, + 'rateLimitLimit' => $this->rateLimitLimit, + 'rateLimitRemaining' => $this->rateLimitRemaining, + 'rateLimitReset' => $this->rateLimitReset, + 'rateLimitWindow' => $this->rateLimitWindow, 'lastCall' => $this->lastCall, 'lastSync' => $this->lastSync, - 'objectCount' => $this->objectCount, 'dateCreated' => isset($this->dateCreated) ? $this->dateCreated->format('c') : null, 'dateModified' => isset($this->dateModified) ? $this->dateModified->format('c') : null, - 'test' => $this->test ]; } } diff --git a/lib/Db/Synchronization.php b/lib/Db/Synchronization.php index 0460859c..473524b0 100644 --- a/lib/Db/Synchronization.php +++ b/lib/Db/Synchronization.php @@ -34,6 +34,9 @@ class Synchronization extends Entity implements JsonSerializable protected ?DateTime $created = null; // The date and time the synchronization was created protected ?DateTime $updated = null; // The date and time the synchronization was updated + protected array $conditions = []; + protected array $followUps = []; + public function __construct() { $this->addType('uuid', 'string'); @@ -58,8 +61,25 @@ public function __construct() { $this->addType('targetLastSynced', 'datetime'); $this->addType('created', 'datetime'); $this->addType('updated', 'datetime'); + $this->addType(fieldName:'conditions', type: 'json'); + $this->addType(fieldName:'followUps', type: 'json'); } + /** + * Checks through sourceConfig if the source of this sync uses pagination + * + * @return bool true if its uses pagination + */ + public function usesPagination(): bool + { + if (isset($this->sourceConfig['usesPagination']) === true && ($this->sourceConfig['usesPagination'] === false || $this->sourceConfig['usesPagination'] === 'false')) { + return false; + } + + // By default sources use basic pagination. + return true; + } + public function getJsonFields(): array { return array_keys( @@ -115,7 +135,9 @@ public function jsonSerialize(): array 'targetLastChecked' => isset($this->targetLastChecked) === true ? $this->targetLastChecked->format('c') : null, 'targetLastSynced' => isset($this->targetLastSynced) === true ? $this->targetLastSynced->format('c') : null, 'created' => isset($this->created) === true ? $this->created->format('c') : null, - 'updated' => isset($this->updated) === true ? $this->updated->format('c') : null + 'updated' => isset($this->updated) === true ? $this->updated->format('c') : null, + 'conditions' => $this->conditions, + 'followUps' => $this->followUps, ]; } } diff --git a/lib/Migration/Version1Date20241111144800.php b/lib/Migration/Version1Date20241111144800.php index 9eb4651d..10a6768c 100644 --- a/lib/Migration/Version1Date20241111144800.php +++ b/lib/Migration/Version1Date20241111144800.php @@ -101,7 +101,7 @@ public function postSchemaChange(IOutput $output, Closure $schemaClosure, array && $table->hasColumn('source_id') === true && $table->hasColumn('source_hash') === true ) { $this->connection->executeQuery(" - UPDATE openconnector_synchronization_contracts + UPDATE oc_openconnector_synchronization_contracts SET origin_id = source_id, origin_hash = source_hash WHERE source_id IS NOT NULL "); diff --git a/lib/Migration/Version1Date20241121160300.php b/lib/Migration/Version1Date20241121160300.php new file mode 100644 index 00000000..daef153b --- /dev/null +++ b/lib/Migration/Version1Date20241121160300.php @@ -0,0 +1,84 @@ +getTable('openconnector_sources'); + + if ($table->hasColumn('rate_limit_limit') === false) { + $table->addColumn('rate_limit_limit', Types::INTEGER, [ + 'notnull' => false, + 'default' => null + ]); + } + + if ($table->hasColumn('rate_limit_remaining') === false) { + $table->addColumn('rate_limit_remaining', Types::INTEGER, [ + 'notnull' => false, + 'default' => null + ]); + } + + if ($table->hasColumn('rate_limit_reset') === false) { + $table->addColumn('rate_limit_reset', Types::INTEGER, [ + 'notnull' => false, + 'default' => null + ]); + } + + if ($table->hasColumn('rate_limit_window') === false) { + $table->addColumn('rate_limit_window', Types::INTEGER, [ + 'notnull' => false, + 'default' => null + ]); + } + + return $schema; + } + + /** + * @param IOutput $output + * @param Closure(): ISchemaWrapper $schemaClosure + * @param array $options + */ + public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void { + } +} diff --git a/lib/Migration/Version1Date20241126074122.php b/lib/Migration/Version1Date20241126074122.php new file mode 100644 index 00000000..71edeb90 --- /dev/null +++ b/lib/Migration/Version1Date20241126074122.php @@ -0,0 +1,70 @@ +hasTable(tableName: 'openconnector_synchronizations') === true) { + $table = $schema->getTable(tableName: 'openconnector_synchronizations'); + if ($table->hasColumn(name: 'conditions') === false) { + $table->addColumn(name: 'conditions', typeName: Types::JSON) + ->setDefault(default: '{}') + ->setNotnull(notnull:false); + } + if ($table->hasColumn(name: 'follow_ups') === false) { + + $table->addColumn(name: 'follow_ups', typeName: Types::JSON) + ->setDefault(default: '{}') + ->setNotnull(notnull:false); + } + } + + return $schema; + } + + /** + * @param IOutput $output + * @param Closure(): ISchemaWrapper $schemaClosure + * @param array $options + */ + public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void { + } +} diff --git a/lib/Migration/Version1Date20241206095007.php b/lib/Migration/Version1Date20241206095007.php new file mode 100644 index 00000000..d26a0c8d --- /dev/null +++ b/lib/Migration/Version1Date20241206095007.php @@ -0,0 +1,66 @@ +hasTable('openconnector_sources') === true) { + $table = $schema->getTable('openconnector_sources'); + + if ($table->hasColumn('logRetention') === true) { + $table->dropColumn('logRetention'); + $table->addColumn('log_retention', Types::INTEGER)->setNotnull(false)->setDefault(3600); + } + if ($table->hasColumn('errorRetention') === true) { + $table->dropColumn('errorRetention'); + $table->addColumn('error_retention', Types::INTEGER)->setNotnull(false)->setDefault(86400); + } + } + + return $schema; + } + + /** + * @param IOutput $output + * @param Closure(): ISchemaWrapper $schemaClosure + * @param array $options + */ + public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options): void { + } +} diff --git a/lib/Service/AuthenticationService.php b/lib/Service/AuthenticationService.php index b59e41b4..f6048746 100644 --- a/lib/Service/AuthenticationService.php +++ b/lib/Service/AuthenticationService.php @@ -192,6 +192,35 @@ public function fetchOAuthTokens (array $configuration): string return $result['access_token']; } + /** + * Fetch an access token from the DeCOS non-implementation of OAuth 2.0 + * + * @param array $configuration The configuration of the source. + * + * @return string The access token + * + * @throws \GuzzleHttp\Exception\GuzzleException + */ + public function fetchDecosToken(array $configuration): string + { + $url = $configuration['tokenUrl']; + $tokenLocation = $configuration['tokenLocation']; + unset($configuration['tokenUrl']); + + $callConfig['json'] = $configuration; + + $client = new Client(); + $response = $client->post(uri: $url, options: $callConfig); + + $result = json_decode(json: $response->getBody()->getContents(), associative: true); + + if (isset($tokenLocation) === true) { + return $result[$tokenLocation]; + } + + return $result['token']; + } + /** * Get RSA key for RS and PS (asymmetrical) encryption. * @@ -201,7 +230,7 @@ public function fetchOAuthTokens (array $configuration): string private function getRSJWK(array $configuration): ?JWK { $stamp = microtime().getmypid(); - $filename = "privatekey-$stamp"; + $filename = "/var/tmp/privatekey-$stamp"; file_put_contents($filename, base64_decode($configuration['secret'])); $jwk = null; try { diff --git a/lib/Service/CallService.php b/lib/Service/CallService.php index 4a86c601..8db6434c 100644 --- a/lib/Service/CallService.php +++ b/lib/Service/CallService.php @@ -19,6 +19,7 @@ use OCA\OpenConnector\Db\CallLogMapper; use OCA\OpenConnector\Twig\AuthenticationExtension; use OCA\OpenConnector\Twig\AuthenticationRuntimeLoader; +use Symfony\Component\HttpKernel\Exception\TooManyRequestsHttpException; use Symfony\Component\Uid\Uuid; use Twig\Environment; use Twig\Error\LoaderError; @@ -42,11 +43,13 @@ class CallService * The constructor sets al needed variables. * * @param CallLogMapper $callLogMapper + * @param SourceMapper $sourceMapper * @param ArrayLoader $loader * @param AuthenticationService $authenticationService */ public function __construct( private readonly CallLogMapper $callLogMapper, + private readonly SourceMapper $sourceMapper, ArrayLoader $loader, AuthenticationService $authenticationService ) @@ -111,6 +114,8 @@ private function renderConfiguration(array $configuration, Source $source): arra * * @return CallLog * @throws GuzzleException + * @throws LoaderError + * @throws SyntaxError * @throws \OCP\DB\Exception */ public function call( @@ -133,7 +138,7 @@ public function call( $callLog->setStatusCode(409); $callLog->setStatusMessage("This source is not enabled"); $callLog->setCreated(new \DateTime()); - $callLog->setUpdated(new \DateTime()); + $callLog->setExpires(new \DateTime('now + '.$source->getErrorRetention().' seconds')); $this->callLogMapper->insert($callLog); @@ -148,7 +153,34 @@ public function call( $callLog->setStatusCode(409); $callLog->setStatusMessage("This source has no location"); $callLog->setCreated(new \DateTime()); - $callLog->setUpdated(new \DateTime()); + $callLog->setExpires(new \DateTime('now + '.$source->getErrorRetention().' seconds')); + + $this->callLogMapper->insert($callLog); + + return $callLog; + } + + // Check if Source has a RateLimit and if we need to reset RateLimit-Reset and RateLimit-Remaining. + if ($this->source->getRateLimitReset() !== null + && $this->source->getRateLimitRemaining() !== null + && $this->source->getRateLimitReset() <= time() + ) { + $this->source->setRateLimitReset(null); + $this->source->setRateLimitRemaining(null); + + $this->sourceMapper->update($source); + } + + // Check if RateLimit-Remaining is set on this source and if limit has been reached. + if ($this->source->getRateLimitRemaining() !== null && $this->source->getRateLimitRemaining() <= 0) { + // Create and save the CallLog + $callLog = new CallLog(); + $callLog->setUuid(Uuid::v4()); + $callLog->setSourceId($this->source->getId()); + $callLog->setStatusCode(429); // + $callLog->setStatusMessage("The rate limit for this source has been exceeded. Try again later."); + $callLog->setCreated(new \DateTime()); + $callLog->setExpires(new \DateTime('now + '.$source->getErrorRetention().' seconds')); $this->callLogMapper->insert($callLog); @@ -211,6 +243,7 @@ public function call( if ($asynchronous === false) { $response = $this->client->request($method, $url, $config); } else { + // @todo: we want to get rate limit headers from async calls as well return $this->client->requestAsync($method, $url, $config); } } catch (GuzzleHttp\Exception\BadResponseException $e) { @@ -219,6 +252,8 @@ public function call( $time_end = microtime(true); + $body = $response->getBody()->getContents(); + // Let create the data array $data = [ 'request' => [ @@ -233,10 +268,14 @@ public function call( 'size' => $response->getBody()->getSize(), 'remoteIp' => $response->getHeaderLine('X-Real-IP') ?: $response->getHeaderLine('X-Forwarded-For') ?: null, 'headers' => $response->getHeaders(), - 'body' => $response->getBody()->getContents(), + 'body' => mb_check_encoding(value: $body, encoding: 'UTF-8') !== false ? $body : base64_encode($body), + 'encoding' => mb_check_encoding(value: $body, encoding: 'UTF-8') !== false ? 'UTF-8' : 'base64', ] ]; + // Update Rate Limit info for the source with the rate limit headers if present or if configured in the source. + $data['response']['headers'] = $this->sourceRateLimit($source, $data['response']['headers']); + // Create and save the CallLog $callLog = new CallLog(); $callLog->setUuid(Uuid::v4()); @@ -246,12 +285,76 @@ public function call( $callLog->setRequest($data['request']); $callLog->setResponse($data['response']); $callLog->setCreated(new \DateTime()); + $callLog->setExpires(new \DateTime('now + '.($data['response']['statusCode'] < 400 ? $source->getLogRetention() : $source->getErrorRetention()).' seconds')); $this->callLogMapper->insert($callLog); return $callLog; } + /** + * Update the source with rate limit info if any of the rate limit headers are found. Else checks if config on the + * source has been set for Rate Limit. And update the response headers with this Rate Limit info. + * + * @param Source $source The source to update. + * @param array $headers The response headers to check for Rate Limit headers. + * + * @return array The updated response headers. + * @throws \OCP\DB\Exception + */ + private function sourceRateLimit(Source $source, array $headers): array + { + // Check if RateLimit-Reset is present in response headers. If so, save it in the source. + if (isset($headers['X-RateLimit-Reset']) === true) { + $source->setRateLimitReset($headers['X-RateLimit-Reset']); + } + + // If RateLimit-Reset not in headers and source->RateLimit-Reset === null. But source->RateLimit-Window is set. + if (isset($headers['X-RateLimit-Reset']) === false + && $source->getRateLimitReset() === null + && $source->getRateLimitWindow() !== null + ) { + // Set new RateLimit-Reset time on the source. + $rateLimitReset = time() + $source->getRateLimitWindow(); + $source->setRateLimitReset($rateLimitReset); + } + + // Check if RateLimit-Limit is present in response headers. If so, save it in the source. + if (isset($headers['X-RateLimit-Limit']) === true) { + $source->setRateLimitLimit($headers['X-RateLimit-Limit']); + } + + // Check if RateLimit-Remaining is present in response headers. If so, save it in the source. + if (isset($headers['X-RateLimit-Remaining']) === true) { + $source->setRateLimitRemaining($headers['X-RateLimit-Remaining']); + } + + // If RateLimit-Remaining not in headers and source->RateLimit-Limit is set, update source->RateLimit-Remaining. + if (isset($headers['X-RateLimit-Remaining']) === false && $source->getRateLimitLimit() !== null) { + $rateLimitRemaining = $source->getRateLimitRemaining(); + if ($rateLimitRemaining === null) { + // Re-set the RateLimit-Remaining on the source. + $rateLimitRemaining = $source->getRateLimitLimit(); + } + $source->setRateLimitRemaining($rateLimitRemaining - 1); + } + + $this->sourceMapper->update($source); + + if ($source->getRateLimitLimit() !== null || $source->getRateLimitWindow() !== null) { + $headers = array_merge($headers, [ + 'X-RateLimit-Limit' => [(string) $source->getRateLimitLimit()], + 'X-RateLimit-Remaining' => [(string) $source->getRateLimitRemaining()], + 'X-RateLimit-Reset' => [(string) $source->getRateLimitReset()], + 'X-RateLimit-Used' => ["1"], + 'X-RateLimit-Window' => [(string) $source->getRateLimitWindow()], + ]); + ksort($headers); + } + + return $headers; + } + /** * Uses Adbar Dot to place the values of keys with a dot in it in the $config array * to the correct position in the then updated multidimensional $config array. @@ -260,7 +363,7 @@ public function call( * * @return array The updated config array. */ - private function applyConfigDot(array $config): array + public function applyConfigDot(array $config): array { $dotConfig = new Dot($config); $unsetKeys = []; diff --git a/lib/Service/JobService.php b/lib/Service/JobService.php index 81856460..e5da76a5 100644 --- a/lib/Service/JobService.php +++ b/lib/Service/JobService.php @@ -51,10 +51,10 @@ public function scheduleJob(Job $job): Job $arguments['jobId'] = $job->getId(); if (!$job->getScheduleAfter()) { - $iJob = $this->jobList->add($this->actionTask::class, $arguments); + $this->jobList->add($this->actionTask::class, $arguments); } else { $runAfter = $job->getScheduleAfter()->getTimestamp(); - $iJob = $this->jobList->scheduleAfter($this->actionTask::class, $runAfter, $arguments); + $this->jobList->scheduleAfter($this->actionTask::class, $runAfter, $arguments); } // Set the job list id diff --git a/lib/Service/SynchronizationService.php b/lib/Service/SynchronizationService.php index 37fcb836..3f51accd 100644 --- a/lib/Service/SynchronizationService.php +++ b/lib/Service/SynchronizationService.php @@ -4,6 +4,7 @@ use Exception; use GuzzleHttp\Exception\GuzzleException; +use JWadhams\JsonLogic; use OCA\OpenConnector\Db\CallLog; use OCA\OpenConnector\Db\Mapping; use OCA\OpenConnector\Db\Source; @@ -29,6 +30,7 @@ use DateTime; use OCA\OpenConnector\Db\MappingMapper; use OCP\AppFramework\Http\NotFoundResponse; +use Twig\Environment; use Twig\Error\LoaderError; use Twig\Error\SyntaxError; @@ -45,6 +47,12 @@ class SynchronizationService private ObjectService $objectService; private Source $source; + const EXTRA_DATA_CONFIGS_LOCATION = 'extraDataConfigs'; + const EXTRA_DATA_DYNAMIC_ENDPOINT_LOCATION = 'dynamicEndpointLocation'; + const EXTRA_DATA_STATIC_ENDPOINT_LOCATION = 'staticEndpoint'; + const KEY_FOR_EXTRA_DATA_LOCATION = 'keyToSetExtraData'; + const MERGE_EXTRA_DATA_OBJECT_LOCATION = 'mergeExtraData'; + public function __construct( CallService $callService, @@ -54,7 +62,7 @@ public function __construct( MappingMapper $mappingMapper, SynchronizationMapper $synchronizationMapper, SynchronizationContractMapper $synchronizationContractMapper, - SynchronizationContractLogMapper $synchronizationContractLogMapper + SynchronizationContractLogMapper $synchronizationContractLogMapper, ) { $this->callService = $callService; $this->mappingService = $mappingService; @@ -102,13 +110,11 @@ public function synchronize(Synchronization $synchronization, ?bool $isTest = fa $synchronizationContract = $this->synchronizationContractMapper->createFromArray([ 'synchronizationId' => $synchronization->getId(), 'originId' => $originId, - 'originHash' => md5(serialize($object)) ]); } else { $synchronizationContract = new SynchronizationContract(); $synchronizationContract->setSynchronizationId($synchronization->getId()); $synchronizationContract->setOriginId($originId); - $synchronizationContract->setOriginHash(md5(serialize($object))); } $synchronizationContract = $this->synchronizeContract(synchronizationContract: $synchronizationContract, synchronization: $synchronization, object: $object, isTest: $isTest); @@ -135,6 +141,11 @@ public function synchronize(Synchronization $synchronization, ?bool $isTest = fa $this->synchronizationContractMapper->update($synchronizationContract); } + foreach ($synchronization->getFollowUps() as $followUp) { + $followUpSynchronization = $this->synchronizationMapper->find($followUp); + $this->synchronize($followUpSynchronization, $isTest); + } + return $objectList; } @@ -174,6 +185,116 @@ private function getOriginId(Synchronization $synchronization, array $object): i return $originId; } + /** + * Fetch an object from a specific endpoint. + * + * @param Synchronization $synchronization The synchronization containing the source. + * @param string $endpoint The endpoint to request to fetch the desired object. + * + * @return array The resulting object. + * + * @throws GuzzleException + * @throws \OCP\DB\Exception + */ + public function getObjectFromSource(Synchronization $synchronization, string $endpoint): array + { + $source = $this->sourceMapper->find(id: $synchronization->getSourceId()); + + // Lets get the source config + $sourceConfig = $synchronization->getSourceConfig(); + $headers = $sourceConfig['headers'] ?? []; + $query = $sourceConfig['query'] ?? []; + $config = [ + 'headers' => $headers, + 'query' => $query, + ]; + + if (str_starts_with($endpoint, $source->getLocation()) === true) { + $endpoint = str_replace(search: $source->getLocation(), replace: '', subject: $endpoint); + } + + // Make the initial API call + // @TODO: method is now fixed to GET, but could end up in configuration. + $response = $this->callService->call(source: $source, endpoint: $endpoint, config: $config)->getResponse(); + + return json_decode($response['body'], true); + } + + /** + * Fetches additional data for a given object based on the synchronization configuration. + * + * This method retrieves extra data using either a dynamically determined endpoint from the object + * or a statically defined endpoint in the configuration. The extra data can be merged with the original + * object or returned as-is, based on the provided configuration. + * + * @param Synchronization $synchronization The synchronization instance containing configuration details. + * @param array $extraDataConfig The configuration array specifying how to retrieve and handle the extra data: + * - EXTRA_DATA_DYNAMIC_ENDPOINT_LOCATION: The key to retrieve the dynamic endpoint from the object. + * - EXTRA_DATA_STATIC_ENDPOINT_LOCATION: The statically defined endpoint. + * - KEY_FOR_EXTRA_DATA_LOCATION: The key under which the extra data should be returned. + * - MERGE_EXTRA_DATA_OBJECT_LOCATION: Boolean flag indicating whether to merge the extra data with the object. + * @param array $object The original object for which extra data needs to be fetched. + * + * @return array The original object merged with the extra data, or the extra data itself based on the configuration. + * + * @throws Exception If both dynamic and static endpoint configurations are missing or the endpoint cannot be determined. + */ + private function fetchExtraDataForObject(Synchronization $synchronization, array $extraDataConfig, array $object) + { + if (isset($extraDataConfig[$this::EXTRA_DATA_DYNAMIC_ENDPOINT_LOCATION]) === false && isset($extraDataConfig[$this::EXTRA_DATA_STATIC_ENDPOINT_LOCATION]) === false) { + return $object; + } + + // Get endpoint from earlier fetched object. + if (isset($extraDataConfig[$this::EXTRA_DATA_DYNAMIC_ENDPOINT_LOCATION]) === true) { + $dotObject = new Dot($object); + $endpoint = $dotObject->get($extraDataConfig[$this::EXTRA_DATA_DYNAMIC_ENDPOINT_LOCATION] ?? null); + } + + // Get endpoint static defined in config. + if (isset($extraDataConfig[$this::EXTRA_DATA_STATIC_ENDPOINT_LOCATION]) === true) { + $endpoint = $extraDataConfig[$this::EXTRA_DATA_STATIC_ENDPOINT_LOCATION]; + $endpoint = str_replace(search: '{{ originId }}', replace: $this->getOriginId($synchronization, $object), subject: $endpoint); + $endpoint = str_replace(search: '{{originId}}', replace: $this->getOriginId($synchronization, $object), subject: $endpoint); + } + + if (!$endpoint) { + throw new Exception( + sprintf( + 'Could not get static or dynamic endpoint, object: %s', + json_encode($object) + ) + ); + } + + $extraData = $this->getObjectFromSource($synchronization, $endpoint); + + // Temporary fix, + if (isset($extraDataConfig['extraDataConfigPerResult']) === true) { + $dotObject = new Dot($extraData); + $results = $dotObject->get($extraDataConfig['resultsLocation']); + + foreach ($results as $key => $result) { + $results[$key] = $this->fetchExtraDataForObject($synchronization, $extraDataConfig['extraDataConfigPerResult'], $result); + } + + $extraData = $results; + } + + // Set new key if configured. + if (isset($extraDataConfig[$this::KEY_FOR_EXTRA_DATA_LOCATION]) === true) { + $extraData = [$extraDataConfig[$this::KEY_FOR_EXTRA_DATA_LOCATION] => $extraData]; + } + + // Merge with earlier fetchde object if configured. + if (isset($extraDataConfig[$this::MERGE_EXTRA_DATA_OBJECT_LOCATION]) === true && ($extraDataConfig[$this::MERGE_EXTRA_DATA_OBJECT_LOCATION] === true || $extraDataConfig[$this::MERGE_EXTRA_DATA_OBJECT_LOCATION] === 'true')) { + return array_merge($object, $extraData); + } + + return $extraData; + } + + /** * Synchronize a contract * @@ -190,20 +311,40 @@ private function getOriginId(Synchronization $synchronization, array $object): i */ public function synchronizeContract(SynchronizationContract $synchronizationContract, Synchronization $synchronization = null, array $object = [], ?bool $isTest = false): SynchronizationContract|Exception|array { + $sourceConfig = $this->callService->applyConfigDot($synchronization->getSourceConfig()); + + // Check if extra data needs to be fetched + if (isset($sourceConfig[$this::EXTRA_DATA_CONFIGS_LOCATION]) === true) { + foreach ($sourceConfig[$this::EXTRA_DATA_CONFIGS_LOCATION] as $extraDataConfig) { + $object = array_merge($object, $this->fetchExtraDataForObject($synchronization, $extraDataConfig, $object)); + } + } + + // @TODO: This should be unset through pre-mapping + if(isset($object['d']['vti_x005f_dirlateststamp']) === true) { + unset($object['d']['vti_x005f_dirlateststamp']); + } + + // Let create a source hash for the object $originHash = md5(serialize($object)); - $synchronizationContract->setSourceLastChecked(new DateTime()); // Let's prevent pointless updates @todo account for omnidirectional sync, unless the config has been updated since last check then we do want to rebuild and check if the tagert object has changed if ($originHash === $synchronizationContract->getOriginHash() && $synchronization->getUpdated() < $synchronizationContract->getSourceLastChecked()) { // The object has not changed and the config has not been updated since last check - // return $synchronizationContract; - // @todo: somehow this always returns true, so we never do the updateTarget + return $synchronizationContract; } - + // The object has changed, oke let do mappig and bla die bla $synchronizationContract->setOriginHash($originHash); $synchronizationContract->setSourceLastChanged(new DateTime()); + $synchronizationContract->setSourceLastChecked(new DateTime()); + + // Check if object adheres to conditions. + // Take note, JsonLogic::apply() returns a range of return types, so checking it with '=== false' or '!== true' does not work properly. + if ($synchronization->getConditions() !== [] && !JsonLogic::apply($synchronization->getConditions(), $object)) { + return $synchronizationContract; + } // If no source target mapping is defined, use original object if (empty($synchronization->getSourceTargetMapping()) === true) { @@ -223,7 +364,6 @@ public function synchronizeContract(SynchronizationContract $synchronizationCont } } - // set the target hash $targetHash = md5(serialize($targetObject)); $synchronizationContract->setTargetHash($targetHash); @@ -373,7 +513,7 @@ public function getAllObjectsFromApi(Synchronization $synchronization, ?bool $is $source = $this->sourceMapper->find(id: $synchronization->getSourceId()); // Lets get the source config - $sourceConfig = $synchronization->getSourceConfig(); + $sourceConfig = $this->callService->applyConfigDot($synchronization->getSourceConfig()); $endpoint = $sourceConfig['endpoint'] ?? ''; $headers = $sourceConfig['headers'] ?? []; $query = $sourceConfig['query'] ?? []; @@ -383,6 +523,7 @@ public function getAllObjectsFromApi(Synchronization $synchronization, ?bool $is ]; // Make the initial API call + // @TODO: method is now fixed to GET, but could end up in configuration. $response = $this->callService->call(source: $source, endpoint: $endpoint, method: 'GET', config: $config)->getResponse(); $lastHash = md5($response['body']); $body = json_decode($response['body'], true); @@ -413,7 +554,7 @@ public function getAllObjectsFromApi(Synchronization $synchronization, ?bool $is $objects = array_merge($objects, $this->getAllObjectsFromArray($body, $synchronization)); } - if ($useNextEndpoint === false) { + if ($useNextEndpoint === false && $synchronization->usesPagination() === true) { do { $config = $this->getNextPage(config: $config, sourceConfig: $sourceConfig, currentPage: $currentPage); $response = $this->callService->call(source: $source, endpoint: $endpoint, method: 'GET', config: $config)->getResponse(); diff --git a/lib/Settings/OpenConnectorAdmin.php b/lib/Settings/OpenConnectorAdmin.php index 564615c6..4e35c3fa 100644 --- a/lib/Settings/OpenConnectorAdmin.php +++ b/lib/Settings/OpenConnectorAdmin.php @@ -7,37 +7,37 @@ use OCP\Settings\ISettings; class OpenConnectorAdmin implements ISettings { - private IL10N $l; - private IConfig $config; + private IL10N $l; + private IConfig $config; - public function __construct(IConfig $config, IL10N $l) { - $this->config = $config; - $this->l = $l; - } + public function __construct(IConfig $config, IL10N $l) { + $this->config = $config; + $this->l = $l; + } - /** - * @return TemplateResponse - */ - public function getForm() { - $parameters = [ - 'mySetting' => $this->config->getSystemValue('open_connector_setting', true), - ]; + /** + * @return TemplateResponse + */ + public function getForm() { + $parameters = [ + 'mySetting' => $this->config->getSystemValue('open_connector_setting', true), + ]; - return new TemplateResponse('openconnector', 'settings/admin', $parameters, ''); - } + return new TemplateResponse('openconnector', 'settings/admin', $parameters, ''); + } - public function getSection() { - return 'openconnector'; // Name of the previously created section. - } + public function getSection() { + return 'openconnector'; // Name of the previously created section. + } - /** - * @return int whether the form should be rather on the top or bottom of - * the admin section. The forms are arranged in ascending order of the - * priority values. It is required to return a value between 0 and 100. - * - * E.g.: 70 - */ - public function getPriority() { - return 10; - } -} \ No newline at end of file + /** + * @return int whether the form should be rather on the top or bottom of + * the admin section. The forms are arranged in ascending order of the + * priority values. It is required to return a value between 0 and 100. + * + * E.g.: 70 + */ + public function getPriority() { + return 10; + } +} diff --git a/lib/Twig/AuthenticationExtension.php b/lib/Twig/AuthenticationExtension.php index fed2dfb5..a4cc8ec4 100644 --- a/lib/Twig/AuthenticationExtension.php +++ b/lib/Twig/AuthenticationExtension.php @@ -11,6 +11,7 @@ public function getFunctions(): array { return [ new TwigFunction(name: 'oauthToken', callable: [AuthenticationRuntime::class, 'oauthToken']), + new TwigFunction(name: 'decosToken', callable: [AuthenticationRuntime::class, 'decosToken']), new TwigFunction(name: 'jwtToken', callable: [AuthenticationRuntime::class, 'jwtToken']), ]; //return parent::getFunctions(); // TODO: Change the autogenerated stub diff --git a/lib/Twig/AuthenticationRuntime.php b/lib/Twig/AuthenticationRuntime.php index 1655229e..7534bcf9 100644 --- a/lib/Twig/AuthenticationRuntime.php +++ b/lib/Twig/AuthenticationRuntime.php @@ -37,6 +37,25 @@ public function oauthToken(Source $source): string ); } + /** + * Add a decos non-oauth token to the configuration. + * + * @param Source $source + * @return string + * + * @throws GuzzleException + */ + public function decosToken(Source $source): string + { + $configuration = new Dot($source->getConfiguration(), true); + + $authConfig = $configuration->get('authentication'); + + return $this->authService->fetchDecosToken( + configuration: $authConfig + ); + } + /** * Add a jwt token to the configuration. * diff --git a/package-lock.json b/package-lock.json index 32efce01..637d0838 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "AGPL-3.0-or-later", "dependencies": { + "@codemirror/lang-json": "^6.0.1", "@fortawesome/fontawesome-svg-core": "^6.5.2", "@fortawesome/free-solid-svg-icons": "^6.5.2", "@mdi/js": "^7.4.47", @@ -32,6 +33,7 @@ "style-loader": "^3.3.3", "vue": "^2.7.14", "vue-apexcharts": "^1.6.2", + "vue-codemirror6": "^1.3.8", "vue-loader": "^15.11.1 <16.0.0", "vue-loading-overlay": "^3.4.3", "vue-material-design-icons": "^5.3.0", @@ -1800,6 +1802,91 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "node_modules/@codemirror/autocomplete": { + "version": "6.18.3", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.3.tgz", + "integrity": "sha512-1dNIOmiM0z4BIBwxmxEfA1yoxh1MF/6KPBbh20a5vphGV0ictKlgQsbJs6D6SkR6iJpGbpwRsa6PFMNlg9T9pQ==", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0" + }, + "peerDependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@codemirror/commands": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.7.1.tgz", + "integrity": "sha512-llTrboQYw5H4THfhN4U3qCnSZ1SOJ60ohhz+SzU0ADGtwlc533DtklQP0vSFaQuCPDn3BPpOd1GbbnUtwNjsrw==", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.4.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" + } + }, + "node_modules/@codemirror/lang-json": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.1.tgz", + "integrity": "sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/json": "^1.0.0" + } + }, + "node_modules/@codemirror/language": { + "version": "6.10.4", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.4.tgz", + "integrity": "sha512-qjt7Wn/nxGuI278GYVlqE5V93Xn8ZQwzqZtgS0FaWr7K2yWgd5/FlBNqNi4jtUvBVvWJzAGfnggIlpyjTOaF4A==", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.1.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/lint": { + "version": "6.8.3", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.3.tgz", + "integrity": "sha512-GSGfKxCo867P7EX1k2LoCrjuQFeqVgPGRRsSl4J4c0KMkD+k1y6WYvTQkzv0iZ8JhLJDujEvlnMchv4CZQLh3Q==", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.35.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/search": { + "version": "6.5.8", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.8.tgz", + "integrity": "sha512-PoWtZvo7c1XFeZWmmyaOp2G0XVbOnm+fJzvghqGAktBW3cufwJUWvSCcNG0ppXiBEM05mZu6RhMtXPv2hpllig==", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/state": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.4.1.tgz", + "integrity": "sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==" + }, + "node_modules/@codemirror/view": { + "version": "6.35.0", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.35.0.tgz", + "integrity": "sha512-I0tYy63q5XkaWsJ8QRv5h6ves7kvtrBWjBcnf/bzohFJQc5c14a1AQRdE8QpPF9eMp5Mq2FMm59TCj1gDfE7kw==", + "dependencies": { + "@codemirror/state": "^6.4.0", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, "node_modules/@csstools/css-parser-algorithms": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.7.1.tgz", @@ -3056,6 +3143,37 @@ "dev": true, "peer": true }, + "node_modules/@lezer/common": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz", + "integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==" + }, + "node_modules/@lezer/highlight": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz", + "integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lezer/json": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.2.tgz", + "integrity": "sha512-xHT2P4S5eeCYECyKNPhr4cbEL9tc8w83SPwRC373o9uEdrvGKTZoJVAGxpOsZckMlEh9W23Pc72ew918RWQOBQ==", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz", + "integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, "node_modules/@linusborg/vue-simple-portal": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/@linusborg/vue-simple-portal/-/vue-simple-portal-0.1.5.tgz", @@ -7240,6 +7358,20 @@ "node": ">= 0.12.0" } }, + "node_modules/codemirror": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.1.tgz", + "integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/commands": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/search": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + } + }, "node_modules/collapse-white-space": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", @@ -7729,6 +7861,11 @@ "node": ">=8" } }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==" + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -19357,6 +19494,11 @@ "webpack": "^5.0.0" } }, + "node_modules/style-mod": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz", + "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==" + }, "node_modules/style-search": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/style-search/-/style-search-0.1.0.tgz", @@ -21293,6 +21435,52 @@ "apexcharts": "^3.26.0" } }, + "node_modules/vue-codemirror6": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/vue-codemirror6/-/vue-codemirror6-1.3.8.tgz", + "integrity": "sha512-pCOzKzBBSFKi/SjUg+XGranV1vt+8S22z56BES/OeZtmyuj2M0CE0aczYS8qbTWNnKcuJcI5FRDHzVXy2v2Htg==", + "dependencies": { + "@codemirror/commands": "^6.7.1", + "@codemirror/language": "^6.10.3", + "@codemirror/lint": "^6.8.3", + "@codemirror/state": "^6.4.1", + "@codemirror/view": "^6.35.0", + "codemirror": "^6.0.1", + "style-mod": "^4.1.2", + "vue-demi": "latest" + }, + "engines": { + "pnpm": ">=9.14.2" + }, + "peerDependencies": { + "vue": "^2.7.14 || ^3.4" + } + }, + "node_modules/vue-codemirror6/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, "node_modules/vue-color": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/vue-color/-/vue-color-2.8.1.tgz", @@ -21526,6 +21714,11 @@ "vue": "^2.5.0" } }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==" + }, "node_modules/w3c-xmlserializer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", @@ -23395,6 +23588,85 @@ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "dev": true }, + "@codemirror/autocomplete": { + "version": "6.18.3", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.18.3.tgz", + "integrity": "sha512-1dNIOmiM0z4BIBwxmxEfA1yoxh1MF/6KPBbh20a5vphGV0ictKlgQsbJs6D6SkR6iJpGbpwRsa6PFMNlg9T9pQ==", + "requires": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0" + } + }, + "@codemirror/commands": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.7.1.tgz", + "integrity": "sha512-llTrboQYw5H4THfhN4U3qCnSZ1SOJ60ohhz+SzU0ADGtwlc533DtklQP0vSFaQuCPDn3BPpOd1GbbnUtwNjsrw==", + "requires": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.4.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" + } + }, + "@codemirror/lang-json": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.1.tgz", + "integrity": "sha512-+T1flHdgpqDDlJZ2Lkil/rLiRy684WMLc74xUnjJH48GQdfJo/pudlTRreZmKwzP8/tGdKf83wlbAdOCzlJOGQ==", + "requires": { + "@codemirror/language": "^6.0.0", + "@lezer/json": "^1.0.0" + } + }, + "@codemirror/language": { + "version": "6.10.4", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.4.tgz", + "integrity": "sha512-qjt7Wn/nxGuI278GYVlqE5V93Xn8ZQwzqZtgS0FaWr7K2yWgd5/FlBNqNi4jtUvBVvWJzAGfnggIlpyjTOaF4A==", + "requires": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.1.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "@codemirror/lint": { + "version": "6.8.3", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.3.tgz", + "integrity": "sha512-GSGfKxCo867P7EX1k2LoCrjuQFeqVgPGRRsSl4J4c0KMkD+k1y6WYvTQkzv0iZ8JhLJDujEvlnMchv4CZQLh3Q==", + "requires": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.35.0", + "crelt": "^1.0.5" + } + }, + "@codemirror/search": { + "version": "6.5.8", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.5.8.tgz", + "integrity": "sha512-PoWtZvo7c1XFeZWmmyaOp2G0XVbOnm+fJzvghqGAktBW3cufwJUWvSCcNG0ppXiBEM05mZu6RhMtXPv2hpllig==", + "requires": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "crelt": "^1.0.5" + } + }, + "@codemirror/state": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.4.1.tgz", + "integrity": "sha512-QkEyUiLhsJoZkbumGZlswmAhA7CBU02Wrz7zvH4SrcifbsqwlXShVXg65f3v/ts57W3dqyamEriMhij1Z3Zz4A==" + }, + "@codemirror/view": { + "version": "6.35.0", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.35.0.tgz", + "integrity": "sha512-I0tYy63q5XkaWsJ8QRv5h6ves7kvtrBWjBcnf/bzohFJQc5c14a1AQRdE8QpPF9eMp5Mq2FMm59TCj1gDfE7kw==", + "requires": { + "@codemirror/state": "^6.4.0", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, "@csstools/css-parser-algorithms": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-2.7.1.tgz", @@ -24299,6 +24571,37 @@ "dev": true, "peer": true }, + "@lezer/common": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.2.3.tgz", + "integrity": "sha512-w7ojc8ejBqr2REPsWxJjrMFsA/ysDCFICn8zEOR9mrqzOu2amhITYuLD8ag6XZf0CFXDrhKqw7+tW8cX66NaDA==" + }, + "@lezer/highlight": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.1.tgz", + "integrity": "sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==", + "requires": { + "@lezer/common": "^1.0.0" + } + }, + "@lezer/json": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.2.tgz", + "integrity": "sha512-xHT2P4S5eeCYECyKNPhr4cbEL9tc8w83SPwRC373o9uEdrvGKTZoJVAGxpOsZckMlEh9W23Pc72ew918RWQOBQ==", + "requires": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "@lezer/lr": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.2.tgz", + "integrity": "sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==", + "requires": { + "@lezer/common": "^1.0.0" + } + }, "@linusborg/vue-simple-portal": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/@linusborg/vue-simple-portal/-/vue-simple-portal-0.1.5.tgz", @@ -27512,6 +27815,20 @@ "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", "dev": true }, + "codemirror": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-6.0.1.tgz", + "integrity": "sha512-J8j+nZ+CdWmIeFIGXEFbFPtpiYacFMDR8GlHK3IyHQJMCaVRfGx9NT+Hxivv1ckLWPvNdZqndbr/7lVhrf/Svg==", + "requires": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/commands": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/search": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0" + } + }, "collapse-white-space": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", @@ -27913,6 +28230,11 @@ } } }, + "crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==" + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -36389,6 +36711,11 @@ "integrity": "sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==", "requires": {} }, + "style-mod": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz", + "integrity": "sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==" + }, "style-search": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/style-search/-/style-search-0.1.0.tgz", @@ -37779,6 +38106,29 @@ "integrity": "sha512-9HS3scJwWgKjmkcWIf+ndNDR0WytUJD8Ju0V2ZYcjYtlTLwJAf2SKUlBZaQTkDmwje/zMgulvZRi+MXmi+WkKw==", "requires": {} }, + "vue-codemirror6": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/vue-codemirror6/-/vue-codemirror6-1.3.8.tgz", + "integrity": "sha512-pCOzKzBBSFKi/SjUg+XGranV1vt+8S22z56BES/OeZtmyuj2M0CE0aczYS8qbTWNnKcuJcI5FRDHzVXy2v2Htg==", + "requires": { + "@codemirror/commands": "^6.7.1", + "@codemirror/language": "^6.10.3", + "@codemirror/lint": "^6.8.3", + "@codemirror/state": "^6.4.1", + "@codemirror/view": "^6.35.0", + "codemirror": "^6.0.1", + "style-mod": "^4.1.2", + "vue-demi": "latest" + }, + "dependencies": { + "vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "requires": {} + } + } + }, "vue-color": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/vue-color/-/vue-color-2.8.1.tgz", @@ -37948,6 +38298,11 @@ "date-format-parse": "^0.2.7" } }, + "w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==" + }, "w3c-xmlserializer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", diff --git a/package.json b/package.json index 8c004797..4354ebec 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "extends @nextcloud/browserslist-config" ], "dependencies": { + "@codemirror/lang-json": "^6.0.1", "@fortawesome/fontawesome-svg-core": "^6.5.2", "@fortawesome/free-solid-svg-icons": "^6.5.2", "@mdi/js": "^7.4.47", @@ -43,6 +44,7 @@ "style-loader": "^3.3.3", "vue": "^2.7.14", "vue-apexcharts": "^1.6.2", + "vue-codemirror6": "^1.3.8", "vue-loader": "^15.11.1 <16.0.0", "vue-loading-overlay": "^3.4.3", "vue-material-design-icons": "^5.3.0", diff --git a/src/modals/Job/EditJob.vue b/src/modals/Job/EditJob.vue index a16c8cde..6713307b 100644 --- a/src/modals/Job/EditJob.vue +++ b/src/modals/Job/EditJob.vue @@ -28,10 +28,12 @@ import { jobStore, navigationStore } from '../../store/store.js' label="Description" :value.sync="jobItem.description" /> - + option.label === jobStore.jobItem.jobClass) + activeJobClass && (this.classOptions.value = activeJobClass) + this.jobItem = { ...jobStore.jobItem, name: jobStore.jobItem.name || '', @@ -221,11 +234,15 @@ export default { errorRetention: '86400', userId: '', } + this.classOptions.value = this.classOptions.options[0] }, async editJob() { this.loading = true try { - await jobStore.saveJob({ ...this.jobItem }) + await jobStore.saveJob({ + ...this.jobItem, + jobClass: this.classOptions.value.label, + }) // Close modal or show success message this.success = true this.loading = false @@ -246,4 +263,8 @@ export default { grid-template-columns: repeat(2, 1fr); gap: 10px; } + +.jobClassSelect { + width: 100%; +} diff --git a/src/modals/Synchronization/TestSynchronization.vue b/src/modals/Synchronization/TestSynchronization.vue index 7e83dde4..e3c8e40e 100644 --- a/src/modals/Synchronization/TestSynchronization.vue +++ b/src/modals/Synchronization/TestSynchronization.vue @@ -1,5 +1,6 @@