From 3a4aa5a0012dd0f79caeb947803fe8adc8509dc1 Mon Sep 17 00:00:00 2001 From: Kevin Papst Date: Mon, 8 Apr 2019 18:38:56 +0200 Subject: [PATCH] added plugin screen (#671) --- UPGRADING.md | 7 +- assets/js/kimai.js | 4 +- composer.json | 7 +- composer.lock | 459 +++++++++--------- config/packages/doctrine.yaml | 46 +- config/packages/kimai.yaml | 2 +- config/packages/security.yaml | 4 + config/services.yaml | 12 +- src/Controller/AboutController.php | 38 +- src/Controller/PluginController.php | 51 ++ .../Compiler/DoctrineCompilerPass.php | 7 +- .../Compiler/ExportServiceCompilerPass.php | 41 -- src/Doctrine/AbstractMigration.php | 3 +- src/Doctrine/TablePrefixSubscriber.php | 56 --- src/Entity/Activity.php | 2 +- src/Entity/Configuration.php | 3 +- src/Entity/Customer.php | 2 +- src/Entity/InvoiceTemplate.php | 5 +- src/Entity/Project.php | 2 +- src/Entity/Timesheet.php | 3 +- src/Entity/User.php | 5 +- src/Entity/UserPreference.php | 5 +- src/Event/ConfigureMainMenuEvent.php | 35 +- src/Event/ThemeEvent.php | 4 + src/EventSubscriber/MenuBuilderSubscriber.php | 30 +- src/EventSubscriber/MenuSubscriber.php | 60 ++- src/Export/Renderer/HtmlRenderer.php | 13 +- src/Export/Renderer/PDFRenderer.php | 15 +- src/Export/ServiceExport.php | 10 + src/Invoice/Renderer/TwigRenderer.php | 7 +- src/Kernel.php | 11 +- src/Plugin/Plugin.php | 83 ++++ src/Plugin/PluginInterface.php | 23 + src/Plugin/PluginManager.php | 111 +++++ src/Plugin/PluginMetadata.php | 106 ++++ src/Twig/Extensions.php | 2 + src/Voter/TimesheetVoter.php | 6 +- src/Voter/UserVoter.php | 4 +- symfony.lock | 6 +- templates/about/system.html.twig | 60 ++- templates/base.html.twig | 8 + templates/macros/actions.html.twig | 22 + templates/macros/widgets.html.twig | 5 + templates/plugin/index.html.twig | 58 +++ tests/API/ConfigurationControllerTest.php | 4 +- tests/Controller/AboutControllerTest.php | 4 +- tests/Controller/CalendarControllerTest.php | 1 + tests/Controller/ControllerBaseTest.php | 17 + tests/Controller/ExportControllerTest.php | 1 - tests/Controller/PluginControllerTest.php | 48 ++ tests/Export/Renderer/HtmlRendererTest.php | 9 +- tests/Export/Renderer/PdfRendererTest.php | 9 +- tests/Export/ServiceExportTest.php | 9 +- tests/Invoice/Renderer/TwigRendererTest.php | 5 +- tests/Invoice/ServiceInvoiceTest.php | 3 +- tests/Plugin/Fixtures/TestPlugin.php | 31 ++ tests/Plugin/Fixtures/composer.json | 29 ++ tests/Plugin/PluginManagerTest.php | 78 +++ tests/Plugin/PluginMetadataTest.php | 42 ++ tests/Plugin/PluginTest.php | 48 ++ tests/Twig/ExtensionsTest.php | 3 +- translations/about.de.xliff | 8 + translations/about.en.xliff | 8 + translations/messages.de.xliff | 8 + translations/messages.en.xliff | 8 + translations/plugins.de.xliff | 31 ++ translations/plugins.en.xliff | 31 ++ webpack.config.js | 4 +- 68 files changed, 1392 insertions(+), 490 deletions(-) create mode 100644 src/Controller/PluginController.php delete mode 100644 src/DependencyInjection/Compiler/ExportServiceCompilerPass.php delete mode 100644 src/Doctrine/TablePrefixSubscriber.php create mode 100644 src/Plugin/Plugin.php create mode 100644 src/Plugin/PluginInterface.php create mode 100644 src/Plugin/PluginManager.php create mode 100644 src/Plugin/PluginMetadata.php create mode 100644 templates/plugin/index.html.twig create mode 100644 tests/Controller/PluginControllerTest.php create mode 100644 tests/Plugin/Fixtures/TestPlugin.php create mode 100644 tests/Plugin/Fixtures/composer.json create mode 100644 tests/Plugin/PluginManagerTest.php create mode 100644 tests/Plugin/PluginMetadataTest.php create mode 100644 tests/Plugin/PluginTest.php create mode 100644 translations/plugins.de.xliff create mode 100644 translations/plugins.en.xliff diff --git a/UPGRADING.md b/UPGRADING.md index 290f40d8..47ba2b7a 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -27,8 +27,13 @@ Remember to execute the necessary timezone conversion script, if you haven't upd **BC BREAKS** - in an ongoing effort to simplify future installation and upgrade processes the `.env` variable `DATABASE_PREFIX` was removed. The table prefix is now hardcoded to `kimai2_`. If you used another prefix, you have to rename your tables manually -before starting the update process. You can also delete the row `DATABASE_PREFIX` from your `.env` file. +before starting the update process. And delete the row `DATABASE_PREFIX` from your `.env` file. - API: DateTime objects will be returned including timezone identifier (previously 2019-03-02 14:23 - now 2019-03-02T14:23:00+00:00) +- Plugin mechanism changed: existing Plugins have to be deleted or updated + +**Check if you want to apply changes to your `local.yaml`:** + +- New permissions are available: `system_configuration`, `system_actions`, `plugins` **Check if you want to apply changes to your `local.yaml`:** diff --git a/assets/js/kimai.js b/assets/js/kimai.js index a47d6fa3..a21293ae 100644 --- a/assets/js/kimai.js +++ b/assets/js/kimai.js @@ -1,6 +1,8 @@ -/* +/*! * This file is part of the Kimai time-tracking app. * + * Main JS application file for Kimai 2. This file should be included in all pages. + * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ diff --git a/composer.json b/composer.json index e4b7c9c9..dde844ab 100644 --- a/composer.json +++ b/composer.json @@ -3,6 +3,12 @@ "license": "MIT", "type": "project", "description": "Kimai Time-Tracking 2", + "authors": [ + { + "name": "Kevin Papst", + "homepage": "https://www.kevinpapst.de" + } + ], "require": { "php": "^7.1.3", "ext-gd": "*", @@ -56,7 +62,6 @@ "require-dev": { "friendsofphp/php-cs-fixer": "^2.10", "phpunit/phpunit": "^7.0", - "squizlabs/php_codesniffer": "^3.2", "symfony/browser-kit": "^4.0", "symfony/css-selector": "^4.0", "symfony/phpunit-bridge": "^4.0", diff --git a/composer.lock b/composer.lock index faa0a363..3cab6424 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": "89fb63af4a70745a81b734b4a3efb494", + "content-hash": "677f829fb26c710d70a2baf67c5f1487", "packages": [ { "name": "beberlei/DoctrineExtensions", @@ -106,16 +106,16 @@ }, { "name": "dama/doctrine-test-bundle", - "version": "v5.0.2", + "version": "v5.0.3", "source": { "type": "git", "url": "https://github.com/dmaicher/doctrine-test-bundle.git", - "reference": "f166fed1c902807db361c125798741e4a78a06ce" + "reference": "29882b0d1a815f4819126ef714931bb24a31cbaa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dmaicher/doctrine-test-bundle/zipball/f166fed1c902807db361c125798741e4a78a06ce", - "reference": "f166fed1c902807db361c125798741e4a78a06ce", + "url": "https://api.github.com/repos/dmaicher/doctrine-test-bundle/zipball/29882b0d1a815f4819126ef714931bb24a31cbaa", + "reference": "29882b0d1a815f4819126ef714931bb24a31cbaa", "shasum": "" }, "require": { @@ -125,8 +125,7 @@ "symfony/framework-bundle": "~2.7|~3.0|~4.0" }, "require-dev": { - "doctrine/common": "~2.10", - "phpunit/phpunit": "~6.0|~7.0", + "phpunit/phpunit": "~6.0|~7.0|~8.0", "symfony/phpunit-bridge": "~2.8|~3.0|~4.0", "symfony/yaml": "~2.8|~3.0|~4.0" }, @@ -162,20 +161,20 @@ "symfony 2", "tests" ], - "time": "2018-12-09T10:45:39+00:00" + "time": "2019-03-22T10:34:17+00:00" }, { "name": "doctrine/annotations", - "version": "v1.6.0", + "version": "v1.6.1", "source": { "type": "git", "url": "https://github.com/doctrine/annotations.git", - "reference": "c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5" + "reference": "53120e0eb10355388d6ccbe462f1fea34ddadb24" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5", - "reference": "c7f2050c68a9ab0bdb0f98567ec08d80ea7d24d5", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/53120e0eb10355388d6ccbe462f1fea34ddadb24", + "reference": "53120e0eb10355388d6ccbe462f1fea34ddadb24", "shasum": "" }, "require": { @@ -230,7 +229,7 @@ "docblock", "parser" ], - "time": "2017-12-06T07:11:42+00:00" + "time": "2019-03-25T19:12:02+00:00" }, { "name": "doctrine/cache", @@ -309,34 +308,36 @@ }, { "name": "doctrine/collections", - "version": "v1.5.0", + "version": "v1.6.1", "source": { "type": "git", "url": "https://github.com/doctrine/collections.git", - "reference": "a01ee38fcd999f34d9bfbcee59dbda5105449cbf" + "reference": "d2ae4ef05e25197343b6a39bae1d3c427a2f6956" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/collections/zipball/a01ee38fcd999f34d9bfbcee59dbda5105449cbf", - "reference": "a01ee38fcd999f34d9bfbcee59dbda5105449cbf", + "url": "https://api.github.com/repos/doctrine/collections/zipball/d2ae4ef05e25197343b6a39bae1d3c427a2f6956", + "reference": "d2ae4ef05e25197343b6a39bae1d3c427a2f6956", "shasum": "" }, "require": { - "php": "^7.1" + "php": "^7.1.3" }, "require-dev": { - "doctrine/coding-standard": "~0.1@dev", - "phpunit/phpunit": "^5.7" + "doctrine/coding-standard": "^6.0", + "phpstan/phpstan-shim": "^0.9.2", + "phpunit/phpunit": "^7.0", + "vimeo/psalm": "^3.2.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.3.x-dev" + "dev-master": "1.6.x-dev" } }, "autoload": { - "psr-0": { - "Doctrine\\Common\\Collections\\": "lib/" + "psr-4": { + "Doctrine\\Common\\Collections\\": "lib/Doctrine/Common/Collections" } }, "notification-url": "https://packagist.org/downloads/", @@ -365,14 +366,15 @@ "email": "schmittjoh@gmail.com" } ], - "description": "Collections Abstraction library", - "homepage": "http://www.doctrine-project.org", + "description": "PHP Doctrine Collections library that adds additional functionality on top of PHP arrays.", + "homepage": "https://www.doctrine-project.org/projects/collections.html", "keywords": [ "array", "collections", - "iterator" + "iterators", + "php" ], - "time": "2017-07-22T10:37:32+00:00" + "time": "2019-03-25T19:03:48+00:00" }, { "name": "doctrine/common", @@ -1044,27 +1046,29 @@ }, { "name": "doctrine/instantiator", - "version": "1.1.0", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda" + "reference": "a2c590166b2133a4633738648b6b064edae0814a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", - "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/a2c590166b2133a4633738648b6b064edae0814a", + "reference": "a2c590166b2133a4633738648b6b064edae0814a", "shasum": "" }, "require": { "php": "^7.1" }, "require-dev": { - "athletic/athletic": "~0.1.8", + "doctrine/coding-standard": "^6.0", "ext-pdo": "*", "ext-phar": "*", - "phpunit/phpunit": "^6.2.3", - "squizlabs/php_codesniffer": "^3.0.2" + "phpbench/phpbench": "^0.13", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-shim": "^0.11", + "phpunit/phpunit": "^7.0" }, "type": "library", "extra": { @@ -1089,12 +1093,12 @@ } ], "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://github.com/doctrine/instantiator", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", "keywords": [ "constructor", "instantiate" ], - "time": "2017-07-22T11:58:36+00:00" + "time": "2019-03-17T17:37:11+00:00" }, { "name": "doctrine/lexer", @@ -1580,16 +1584,16 @@ }, { "name": "erusev/parsedown", - "version": "1.7.1", + "version": "v1.7.2", "source": { "type": "git", "url": "https://github.com/erusev/parsedown.git", - "reference": "92e9c27ba0e74b8b028b111d1b6f956a15c01fc1" + "reference": "d60bcdc46978357759ecb13cb4b078da783f8faf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/erusev/parsedown/zipball/92e9c27ba0e74b8b028b111d1b6f956a15c01fc1", - "reference": "92e9c27ba0e74b8b028b111d1b6f956a15c01fc1", + "url": "https://api.github.com/repos/erusev/parsedown/zipball/d60bcdc46978357759ecb13cb4b078da783f8faf", + "reference": "d60bcdc46978357759ecb13cb4b078da783f8faf", "shasum": "" }, "require": { @@ -1622,7 +1626,7 @@ "markdown", "parser" ], - "time": "2018-03-08T01:11:30+00:00" + "time": "2019-03-17T17:19:46+00:00" }, { "name": "exsyst/swagger", @@ -1894,16 +1898,16 @@ }, { "name": "gedmo/doctrine-extensions", - "version": "v2.4.36", + "version": "v2.4.37", "source": { "type": "git", "url": "https://github.com/Atlantic18/DoctrineExtensions.git", - "reference": "87c78ff9fd4b90460386f753d95622f6fbbfcb27" + "reference": "5dd471f656e46d815f063bf3f12c667649ec7ffb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Atlantic18/DoctrineExtensions/zipball/87c78ff9fd4b90460386f753d95622f6fbbfcb27", - "reference": "87c78ff9fd4b90460386f753d95622f6fbbfcb27", + "url": "https://api.github.com/repos/Atlantic18/DoctrineExtensions/zipball/5dd471f656e46d815f063bf3f12c667649ec7ffb", + "reference": "5dd471f656e46d815f063bf3f12c667649ec7ffb", "shasum": "" }, "require": { @@ -1971,7 +1975,7 @@ "tree", "uploadable" ], - "time": "2018-07-26T12:16:35+00:00" + "time": "2019-03-17T18:16:12+00:00" }, { "name": "jdorn/sql-formatter", @@ -2199,16 +2203,16 @@ }, { "name": "jms/serializer-bundle", - "version": "2.4.3", + "version": "2.4.4", "source": { "type": "git", "url": "https://github.com/schmittjoh/JMSSerializerBundle.git", - "reference": "e5231652ce24b8673b2ac70bc76b0cabaf35deea" + "reference": "92ee808c64c1c180775a0e57d00e3be0674668fb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/schmittjoh/JMSSerializerBundle/zipball/e5231652ce24b8673b2ac70bc76b0cabaf35deea", - "reference": "e5231652ce24b8673b2ac70bc76b0cabaf35deea", + "url": "https://api.github.com/repos/schmittjoh/JMSSerializerBundle/zipball/92ee808c64c1c180775a0e57d00e3be0674668fb", + "reference": "92ee808c64c1c180775a0e57d00e3be0674668fb", "shasum": "" }, "require": { @@ -2269,20 +2273,20 @@ "serialization", "xml" ], - "time": "2018-12-09T07:52:19+00:00" + "time": "2019-03-30T10:26:09+00:00" }, { "name": "kevinpapst/adminlte-bundle", - "version": "2.3", + "version": "2.4", "source": { "type": "git", "url": "https://github.com/kevinpapst/AdminLTEBundle.git", - "reference": "0d095f9ee7ec1450100f65ea392b938296e15863" + "reference": "1450cecfe26bf84f062ed0eb3655903a3710d6a3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/kevinpapst/AdminLTEBundle/zipball/0d095f9ee7ec1450100f65ea392b938296e15863", - "reference": "0d095f9ee7ec1450100f65ea392b938296e15863", + "url": "https://api.github.com/repos/kevinpapst/AdminLTEBundle/zipball/1450cecfe26bf84f062ed0eb3655903a3710d6a3", + "reference": "1450cecfe26bf84f062ed0eb3655903a3710d6a3", "shasum": "" }, "require": { @@ -2330,7 +2334,7 @@ } ], "description": "Admin theme bundle for Symfony 4 based on AdminLTE 2.4.8 with FOSUserBundle support", - "time": "2019-03-05T20:06:11+00:00" + "time": "2019-03-19T22:28:15+00:00" }, { "name": "kimai/kimai2-composer", @@ -2745,16 +2749,16 @@ }, { "name": "nelmio/api-doc-bundle", - "version": "v3.3.1", + "version": "v3.4.0", "source": { "type": "git", "url": "https://github.com/nelmio/NelmioApiDocBundle.git", - "reference": "1b104bbf748f2af0496897c98d5ded3ac545ed43" + "reference": "279d20be784b9879ad0a12859efa3a301404f06c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nelmio/NelmioApiDocBundle/zipball/1b104bbf748f2af0496897c98d5ded3ac545ed43", - "reference": "1b104bbf748f2af0496897c98d5ded3ac545ed43", + "url": "https://api.github.com/repos/nelmio/NelmioApiDocBundle/zipball/279d20be784b9879ad0a12859efa3a301404f06c", + "reference": "279d20be784b9879ad0a12859efa3a301404f06c", "shasum": "" }, "require": { @@ -2771,7 +2775,7 @@ "doctrine/annotations": "^1.2", "doctrine/common": "^2.4", "friendsofsymfony/rest-bundle": "^2.0", - "jms/serializer-bundle": "^2.0", + "jms/serializer-bundle": "^2.0|^3.0", "sensio/framework-extra-bundle": "^3.0", "symfony/asset": "^3.4|^4.0", "symfony/browser-kit": "^3.4|^4.0", @@ -2786,7 +2790,7 @@ "symfony/templating": "^3.4|^4.0", "symfony/twig-bundle": "^3.4|^4.0", "symfony/validator": "^3.4|^4.0", - "willdurand/hateoas-bundle": "^1.0" + "willdurand/hateoas-bundle": "^1.0|^2.0" }, "suggest": { "api-platform/core": "For using an API oriented framework.", @@ -2824,7 +2828,7 @@ "documentation", "rest" ], - "time": "2019-01-25T17:25:46+00:00" + "time": "2019-02-18T16:27:10+00:00" }, { "name": "nelmio/cors-bundle", @@ -2886,16 +2890,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.2.0", + "version": "v4.2.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "594bcae1fc0bccd3993d2f0d61a018e26ac2865a" + "reference": "5221f49a608808c1e4d436df32884cbc1b821ac0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/594bcae1fc0bccd3993d2f0d61a018e26ac2865a", - "reference": "594bcae1fc0bccd3993d2f0d61a018e26ac2865a", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/5221f49a608808c1e4d436df32884cbc1b821ac0", + "reference": "5221f49a608808c1e4d436df32884cbc1b821ac0", "shasum": "" }, "require": { @@ -2933,20 +2937,20 @@ "parser", "php" ], - "time": "2019-01-12T16:31:37+00:00" + "time": "2019-02-16T20:54:15+00:00" }, { "name": "ocramius/package-versions", - "version": "1.3.0", + "version": "1.4.0", "source": { "type": "git", "url": "https://github.com/Ocramius/PackageVersions.git", - "reference": "4489d5002c49d55576fa0ba786f42dbb009be46f" + "reference": "a4d4b60d0e60da2487bd21a2c6ac089f85570dbb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Ocramius/PackageVersions/zipball/4489d5002c49d55576fa0ba786f42dbb009be46f", - "reference": "4489d5002c49d55576fa0ba786f42dbb009be46f", + "url": "https://api.github.com/repos/Ocramius/PackageVersions/zipball/a4d4b60d0e60da2487bd21a2c6ac089f85570dbb", + "reference": "a4d4b60d0e60da2487bd21a2c6ac089f85570dbb", "shasum": "" }, "require": { @@ -2955,6 +2959,7 @@ }, "require-dev": { "composer/composer": "^1.6.3", + "doctrine/coding-standard": "^5.0.1", "ext-zip": "*", "infection/infection": "^0.7.1", "phpunit/phpunit": "^7.0.0" @@ -2982,7 +2987,7 @@ } ], "description": "Composer plugin that provides efficient querying for installed package versions (no runtime IO)", - "time": "2018-02-05T13:05:30+00:00" + "time": "2019-02-21T12:16:21+00:00" }, { "name": "ocramius/proxy-manager", @@ -3055,20 +3060,20 @@ }, { "name": "pagerfanta/pagerfanta", - "version": "v2.0.1", + "version": "v2.1.1", "source": { "type": "git", "url": "https://github.com/whiteoctober/Pagerfanta.git", - "reference": "15770d9d7f6e8e07af568aed104a51f869591e73" + "reference": "8276a078563e719be16d12480dbd28e47ac094fa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/whiteoctober/Pagerfanta/zipball/15770d9d7f6e8e07af568aed104a51f869591e73", - "reference": "15770d9d7f6e8e07af568aed104a51f869591e73", + "url": "https://api.github.com/repos/whiteoctober/Pagerfanta/zipball/8276a078563e719be16d12480dbd28e47ac094fa", + "reference": "8276a078563e719be16d12480dbd28e47ac094fa", "shasum": "" }, "require": { - "php": ">=7.0" + "php": "^7.0" }, "require-dev": { "doctrine/orm": "~2.3", @@ -3077,7 +3082,7 @@ "jmikola/geojson": "~1.0", "mandango/mandango": "~1.0@dev", "mandango/mondator": "~1.0@dev", - "phpunit/phpunit": "^5.7|^6", + "phpunit/phpunit": "^6.5", "propel/propel": "~2.0@dev", "propel/propel1": "~1.6", "ruflin/elastica": "~1.3", @@ -3099,8 +3104,8 @@ } }, "autoload": { - "psr-0": { - "Pagerfanta\\": "src/" + "psr-4": { + "Pagerfanta\\": "src/Pagerfanta/" } }, "notification-url": "https://packagist.org/downloads/", @@ -3113,14 +3118,14 @@ "email": "pablodip@gmail.com" } ], - "description": "Pagination for PHP 5.3", + "description": "Pagination for PHP", "keywords": [ "page", "pagination", "paginator", "paging" ], - "time": "2018-05-17T09:23:52+00:00" + "time": "2019-03-11T08:58:08+00:00" }, { "name": "paragonie/random_compat", @@ -4028,25 +4033,28 @@ }, { "name": "swiftmailer/swiftmailer", - "version": "v6.1.3", + "version": "v6.2.0", "source": { "type": "git", "url": "https://github.com/swiftmailer/swiftmailer.git", - "reference": "8ddcb66ac10c392d3beb54829eef8ac1438595f4" + "reference": "6fa3232ff9d3f8237c0fae4b7ff05e1baa4cd707" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/8ddcb66ac10c392d3beb54829eef8ac1438595f4", - "reference": "8ddcb66ac10c392d3beb54829eef8ac1438595f4", + "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/6fa3232ff9d3f8237c0fae4b7ff05e1baa4cd707", + "reference": "6fa3232ff9d3f8237c0fae4b7ff05e1baa4cd707", "shasum": "" }, "require": { "egulias/email-validator": "~2.0", - "php": ">=7.0.0" + "php": ">=7.0.0", + "symfony/polyfill-iconv": "^1.0", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" }, "require-dev": { "mockery/mockery": "~0.9.1", - "symfony/phpunit-bridge": "~3.3@dev" + "symfony/phpunit-bridge": "^3.4.19|^4.1.8" }, "suggest": { "ext-intl": "Needed to support internationalized email addresses", @@ -4055,7 +4063,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "6.1-dev" + "dev-master": "6.2-dev" } }, "autoload": { @@ -4083,7 +4091,7 @@ "mail", "mailer" ], - "time": "2018-09-11T07:12:52+00:00" + "time": "2019-03-10T07:52:41+00:00" }, { "name": "symfony/asset", @@ -4917,16 +4925,16 @@ }, { "name": "symfony/flex", - "version": "v1.1.8", + "version": "v1.2.0", "source": { "type": "git", "url": "https://github.com/symfony/flex.git", - "reference": "955774ecf07b10230bb5b44e150ba078b45f68fa" + "reference": "7f04fb50c5da6d020e4df743b4c764c188bb24bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/flex/zipball/955774ecf07b10230bb5b44e150ba078b45f68fa", - "reference": "955774ecf07b10230bb5b44e150ba078b45f68fa", + "url": "https://api.github.com/repos/symfony/flex/zipball/7f04fb50c5da6d020e4df743b4c764c188bb24bf", + "reference": "7f04fb50c5da6d020e4df743b4c764c188bb24bf", "shasum": "" }, "require": { @@ -4935,12 +4943,14 @@ }, "require-dev": { "composer/composer": "^1.0.2", - "symfony/phpunit-bridge": "^3.2.8" + "symfony/dotenv": "^3.4|^4.0", + "symfony/phpunit-bridge": "^3.4.19|^4.1.8", + "symfony/process": "^2.7|^3.0|^4.0" }, "type": "composer-plugin", "extra": { "branch-alias": { - "dev-master": "1.1-dev" + "dev-master": "1.2-dev" }, "class": "Symfony\\Flex\\Flex" }, @@ -4960,7 +4970,7 @@ } ], "description": "Composer plugin for Symfony", - "time": "2018-11-15T06:11:38+00:00" + "time": "2019-02-21T11:31:25+00:00" }, { "name": "symfony/form", @@ -5436,16 +5446,16 @@ }, { "name": "symfony/maker-bundle", - "version": "v1.11.3", + "version": "v1.11.5", "source": { "type": "git", "url": "https://github.com/symfony/maker-bundle.git", - "reference": "08fabae78050d2fe71341d0edc3872bddf03482f" + "reference": "186ffc80b8f363e04ea3c2ebf49e1263a84b4e39" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/maker-bundle/zipball/08fabae78050d2fe71341d0edc3872bddf03482f", - "reference": "08fabae78050d2fe71341d0edc3872bddf03482f", + "url": "https://api.github.com/repos/symfony/maker-bundle/zipball/186ffc80b8f363e04ea3c2ebf49e1263a84b4e39", + "reference": "186ffc80b8f363e04ea3c2ebf49e1263a84b4e39", "shasum": "" }, "require": { @@ -5498,7 +5508,7 @@ "scaffold", "scaffolding" ], - "time": "2019-01-17T02:02:27+00:00" + "time": "2019-03-11T15:55:09+00:00" }, { "name": "symfony/monolog-bridge", @@ -5714,16 +5724,16 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.10.0", + "version": "v1.11.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "e3d826245268269cd66f8326bd8bc066687b4a19" + "reference": "82ebae02209c21113908c229e9883c419720738a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19", - "reference": "e3d826245268269cd66f8326bd8bc066687b4a19", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a", + "reference": "82ebae02209c21113908c229e9883c419720738a", "shasum": "" }, "require": { @@ -5735,7 +5745,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.11-dev" } }, "autoload": { @@ -5768,20 +5778,20 @@ "polyfill", "portable" ], - "time": "2018-08-06T14:22:27+00:00" + "time": "2019-02-06T07:57:58+00:00" }, { "name": "symfony/polyfill-intl-icu", - "version": "v1.10.0", + "version": "v1.11.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-icu.git", - "reference": "f22a90256d577c7ef7efad8df1f0201663d57644" + "reference": "999878a3a09d73cae157b0cf89bb6fb2cc073057" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/f22a90256d577c7ef7efad8df1f0201663d57644", - "reference": "f22a90256d577c7ef7efad8df1f0201663d57644", + "url": "https://api.github.com/repos/symfony/polyfill-intl-icu/zipball/999878a3a09d73cae157b0cf89bb6fb2cc073057", + "reference": "999878a3a09d73cae157b0cf89bb6fb2cc073057", "shasum": "" }, "require": { @@ -5826,20 +5836,82 @@ "portable", "shim" ], - "time": "2018-08-06T14:22:27+00:00" + "time": "2019-01-07T19:39:47+00:00" }, { - "name": "symfony/polyfill-mbstring", - "version": "v1.10.0", + "name": "symfony/polyfill-intl-idn", + "version": "v1.11.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "c79c051f5b3a46be09205c73b80b346e4153e494" + "url": "https://github.com/symfony/polyfill-intl-idn.git", + "reference": "c766e95bec706cdd89903b1eda8afab7d7a6b7af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/c79c051f5b3a46be09205c73b80b346e4153e494", - "reference": "c79c051f5b3a46be09205c73b80b346e4153e494", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/c766e95bec706cdd89903b1eda8afab7d7a6b7af", + "reference": "c766e95bec706cdd89903b1eda8afab7d7a6b7af", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/polyfill-mbstring": "^1.3", + "symfony/polyfill-php72": "^1.9" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.9-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Intl\\Idn\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + }, + { + "name": "Laurent Bassin", + "email": "laurent@bassin.info" + } + ], + "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "idn", + "intl", + "polyfill", + "portable", + "shim" + ], + "time": "2019-03-04T13:44:35+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.11.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "fe5e94c604826c35a32fa832f35bd036b6799609" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fe5e94c604826c35a32fa832f35bd036b6799609", + "reference": "fe5e94c604826c35a32fa832f35bd036b6799609", "shasum": "" }, "require": { @@ -5851,7 +5923,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.11-dev" } }, "autoload": { @@ -5885,20 +5957,20 @@ "portable", "shim" ], - "time": "2018-09-21T13:07:52+00:00" + "time": "2019-02-06T07:57:58+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.10.0", + "version": "v1.11.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "9050816e2ca34a8e916c3a0ae8b9c2fccf68b631" + "reference": "ab50dcf166d5f577978419edd37aa2bb8eabce0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/9050816e2ca34a8e916c3a0ae8b9c2fccf68b631", - "reference": "9050816e2ca34a8e916c3a0ae8b9c2fccf68b631", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/ab50dcf166d5f577978419edd37aa2bb8eabce0c", + "reference": "ab50dcf166d5f577978419edd37aa2bb8eabce0c", "shasum": "" }, "require": { @@ -5907,7 +5979,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.11-dev" } }, "autoload": { @@ -5940,7 +6012,7 @@ "portable", "shim" ], - "time": "2018-09-21T13:07:52+00:00" + "time": "2019-02-06T07:57:58+00:00" }, { "name": "symfony/process", @@ -7282,16 +7354,16 @@ }, { "name": "twig/twig", - "version": "v2.6.2", + "version": "v2.7.4", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "7d7342c8a4059fefb9b8d07db0cc14007021f9b7" + "reference": "ed9c49220e09bfaeb1ba4d48077c08a7b09908dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/7d7342c8a4059fefb9b8d07db0cc14007021f9b7", - "reference": "7d7342c8a4059fefb9b8d07db0cc14007021f9b7", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/ed9c49220e09bfaeb1ba4d48077c08a7b09908dd", + "reference": "ed9c49220e09bfaeb1ba4d48077c08a7b09908dd", "shasum": "" }, "require": { @@ -7307,7 +7379,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.6-dev" + "dev-master": "2.7-dev" } }, "autoload": { @@ -7345,7 +7417,7 @@ "keywords": [ "templating" ], - "time": "2019-01-14T15:00:48+00:00" + "time": "2019-03-23T14:28:58+00:00" }, { "name": "webmozart/assert", @@ -7400,16 +7472,16 @@ }, { "name": "white-october/pagerfanta-bundle", - "version": "v1.2.3", + "version": "v1.2.4", "source": { "type": "git", "url": "https://github.com/whiteoctober/WhiteOctoberPagerfantaBundle.git", - "reference": "3a2f39fecde9b8aefe7c9cbd0baf06bc72ef3e5e" + "reference": "19bf14bf8c72e4205c9ca97028436701fe3991a6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/whiteoctober/WhiteOctoberPagerfantaBundle/zipball/3a2f39fecde9b8aefe7c9cbd0baf06bc72ef3e5e", - "reference": "3a2f39fecde9b8aefe7c9cbd0baf06bc72ef3e5e", + "url": "https://api.github.com/repos/whiteoctober/WhiteOctoberPagerfantaBundle/zipball/19bf14bf8c72e4205c9ca97028436701fe3991a6", + "reference": "19bf14bf8c72e4205c9ca97028436701fe3991a6", "shasum": "" }, "require": { @@ -7454,7 +7526,7 @@ "page", "paging" ], - "time": "2018-12-05T16:58:18+00:00" + "time": "2019-02-14T08:42:52+00:00" }, { "name": "willdurand/jsonp-callback-validator", @@ -7766,16 +7838,16 @@ "packages-dev": [ { "name": "composer/semver", - "version": "1.4.2", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "c7cb9a2095a074d131b65a8a0cd294479d785573" + "reference": "46d9139568ccb8d9e7cdd4539cab7347568a5e2e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/c7cb9a2095a074d131b65a8a0cd294479d785573", - "reference": "c7cb9a2095a074d131b65a8a0cd294479d785573", + "url": "https://api.github.com/repos/composer/semver/zipball/46d9139568ccb8d9e7cdd4539cab7347568a5e2e", + "reference": "46d9139568ccb8d9e7cdd4539cab7347568a5e2e", "shasum": "" }, "require": { @@ -7824,7 +7896,7 @@ "validation", "versioning" ], - "time": "2016-08-30T16:08:34+00:00" + "time": "2019-03-19T17:25:45+00:00" }, { "name": "composer/xdebug-handler", @@ -7872,16 +7944,16 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v2.14.1", + "version": "v2.14.2", "source": { "type": "git", "url": "https://github.com/FriendsOfPHP/PHP-CS-Fixer.git", - "reference": "2e82abd1322897eb8aacb861e5ff551af3888349" + "reference": "ff401e58261ffc5934a58f795b3f95b355e276cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/2e82abd1322897eb8aacb861e5ff551af3888349", - "reference": "2e82abd1322897eb8aacb861e5ff551af3888349", + "url": "https://api.github.com/repos/FriendsOfPHP/PHP-CS-Fixer/zipball/ff401e58261ffc5934a58f795b3f95b355e276cb", + "reference": "ff401e58261ffc5934a58f795b3f95b355e276cb", "shasum": "" }, "require": { @@ -7902,9 +7974,6 @@ "symfony/process": "^3.0 || ^4.0", "symfony/stopwatch": "^3.0 || ^4.0" }, - "conflict": { - "hhvm": "*" - }, "require-dev": { "johnkary/phpunit-speedtrap": "^1.1 || ^2.0 || ^3.0", "justinrainbow/json-schema": "^5.0", @@ -7959,7 +8028,7 @@ } ], "description": "A tool to automatically fix PHP code style", - "time": "2019-02-10T22:52:33+00:00" + "time": "2019-02-17T17:44:13+00:00" }, { "name": "phar-io/manifest", @@ -8333,16 +8402,16 @@ }, { "name": "phpunit/php-timer", - "version": "2.0.0", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "8b8454ea6958c3dee38453d3bd571e023108c91f" + "reference": "8b389aebe1b8b0578430bda0c7c95a829608e059" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/8b8454ea6958c3dee38453d3bd571e023108c91f", - "reference": "8b8454ea6958c3dee38453d3bd571e023108c91f", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/8b389aebe1b8b0578430bda0c7c95a829608e059", + "reference": "8b389aebe1b8b0578430bda0c7c95a829608e059", "shasum": "" }, "require": { @@ -8354,7 +8423,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "2.0-dev" + "dev-master": "2.1-dev" } }, "autoload": { @@ -8378,7 +8447,7 @@ "keywords": [ "timer" ], - "time": "2018-02-01T13:07:23+00:00" + "time": "2019-02-20T10:12:59+00:00" }, { "name": "phpunit/php-token-stream", @@ -8431,16 +8500,16 @@ }, { "name": "phpunit/phpunit", - "version": "7.5.4", + "version": "7.5.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "2896657da5fb237bc316bdfc18c2650efeee0dc0" + "reference": "c29c0525cf4572c11efe1db49a8b8aee9dfac58a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/2896657da5fb237bc316bdfc18c2650efeee0dc0", - "reference": "2896657da5fb237bc316bdfc18c2650efeee0dc0", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c29c0525cf4572c11efe1db49a8b8aee9dfac58a", + "reference": "c29c0525cf4572c11efe1db49a8b8aee9dfac58a", "shasum": "" }, "require": { @@ -8458,7 +8527,7 @@ "phpunit/php-code-coverage": "^6.0.7", "phpunit/php-file-iterator": "^2.0.1", "phpunit/php-text-template": "^1.2.1", - "phpunit/php-timer": "^2.0", + "phpunit/php-timer": "^2.1", "sebastian/comparator": "^3.0", "sebastian/diff": "^3.0", "sebastian/environment": "^4.0", @@ -8511,7 +8580,7 @@ "testing", "xunit" ], - "time": "2019-02-07T14:15:04+00:00" + "time": "2019-03-26T13:23:54+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -9079,57 +9148,6 @@ "homepage": "https://github.com/sebastianbergmann/version", "time": "2016-10-03T07:35:21+00:00" }, - { - "name": "squizlabs/php_codesniffer", - "version": "3.4.0", - "source": { - "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "379deb987e26c7cd103a7b387aea178baec96e48" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/379deb987e26c7cd103a7b387aea178baec96e48", - "reference": "379deb987e26c7cd103a7b387aea178baec96e48", - "shasum": "" - }, - "require": { - "ext-simplexml": "*", - "ext-tokenizer": "*", - "ext-xmlwriter": "*", - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" - }, - "bin": [ - "bin/phpcs", - "bin/phpcbf" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Greg Sherwood", - "role": "lead" - } - ], - "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "http://www.squizlabs.com/php-codesniffer", - "keywords": [ - "phpcs", - "standards" - ], - "time": "2018-12-19T23:57:18+00:00" - }, { "name": "symfony/browser-kit", "version": "v4.1.11", @@ -9299,16 +9317,16 @@ }, { "name": "symfony/phpunit-bridge", - "version": "v4.2.3", + "version": "v4.2.4", "source": { "type": "git", "url": "https://github.com/symfony/phpunit-bridge.git", - "reference": "4ea7d80a7512ddc41d5af598978edcd395140edc" + "reference": "2cc651a38fcb831a405c14fcb76fcb00320e7ee8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/4ea7d80a7512ddc41d5af598978edcd395140edc", - "reference": "4ea7d80a7512ddc41d5af598978edcd395140edc", + "url": "https://api.github.com/repos/symfony/phpunit-bridge/zipball/2cc651a38fcb831a405c14fcb76fcb00320e7ee8", + "reference": "2cc651a38fcb831a405c14fcb76fcb00320e7ee8", "shasum": "" }, "require": { @@ -9318,7 +9336,6 @@ "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0" }, "suggest": { - "ext-zip": "Zip support is required when using bin/simple-phpunit", "symfony/debug": "For tracking deprecated interfaces usages at runtime with DebugClassLoader" }, "bin": [ @@ -9361,7 +9378,7 @@ ], "description": "Symfony PHPUnit Bridge", "homepage": "https://symfony.com", - "time": "2019-01-24T21:39:51+00:00" + "time": "2019-02-18T06:49:49+00:00" }, { "name": "symfony/thanks", diff --git a/config/packages/doctrine.yaml b/config/packages/doctrine.yaml index 18c49567..60809917 100644 --- a/config/packages/doctrine.yaml +++ b/config/packages/doctrine.yaml @@ -7,32 +7,30 @@ parameters: doctrine: dbal: - # configure these for your database server - driver: 'pdo_mysql' - server_version: '5.7' - charset: utf8mb4 - default_table_options: - charset: utf8mb4 - collate: utf8mb4_unicode_ci - - # With Symfony 3.3, remove the `resolve:` prefix - url: '%env(resolve:DATABASE_URL)%' + default_connection: default + connections: + default: + url: '%env(resolve:DATABASE_URL)%' + driver: 'pdo_mysql' + server_version: '5.7' + charset: utf8mb4 + default_table_options: + charset: utf8mb4 + collate: utf8mb4_unicode_ci types: datetime: App\Doctrine\UTCDateTimeType orm: auto_generate_proxy_classes: '%kernel.debug%' - naming_strategy: doctrine.orm.naming_strategy.underscore - auto_mapping: true - mappings: - App: - is_bundle: false - type: annotation - dir: '%kernel.project_dir%/src/Entity' - prefix: 'App\Entity' - alias: Kimai - loggable: - type: annotation - alias: Gedmo - prefix: Gedmo\Loggable\Entity - dir: "%kernel.project_dir%/vendor/gedmo/doctrine-extensions/lib/Gedmo/Loggable/Entity" + default_entity_manager: default + entity_managers: + default: + connection: default + naming_strategy: doctrine.orm.naming_strategy.underscore + auto_mapping: true + mappings: + App: + type: annotation + dir: '%kernel.project_dir%/src/Entity' + prefix: 'App\Entity' + alias: Kimai diff --git a/config/packages/kimai.yaml b/config/packages/kimai.yaml index 095775a0..26b1b1a1 100644 --- a/config/packages/kimai.yaml +++ b/config/packages/kimai.yaml @@ -97,7 +97,7 @@ kimai: ROLE_USER: [] ROLE_TEAMLEAD: [view_invoice_template,create_invoice_template,edit_invoice_template,view_rate_own_timesheet,view_rate_other_timesheet,hourly-rate_own_profile] ROLE_ADMIN: [hourly-rate_own_profile] - ROLE_SUPER_ADMIN: [hourly-rate_own_profile,hourly-rate_other_profile,delete_own_profile,roles_own_profile,system_information,system_configuration] + ROLE_SUPER_ADMIN: [hourly-rate_own_profile,hourly-rate_other_profile,delete_own_profile,roles_own_profile,system_information,system_actions,system_configuration,plugins] # -------------------------------------------------------------------------------- # Language specific settings, like the date formats diff --git a/config/packages/security.yaml b/config/packages/security.yaml index c5ee3f24..a4622ab6 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -36,6 +36,10 @@ security: path: fos_user_security_logout target: homepage + access_decision_manager: + strategy: unanimous + allow_if_all_abstain: false + role_hierarchy: ROLE_USER: ROLE_CUSTOMER ROLE_TEAMLEAD: ROLE_USER diff --git a/config/services.yaml b/config/services.yaml index ab3b07cc..aad45e20 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -64,16 +64,16 @@ services: App\Utils\MPdfConverter: arguments: ['%kernel.cache_dir%'] + App\Plugin\PluginManager: + arguments: [!tagged kimai.plugin] + + App\Export\ServiceExport: + arguments: [!tagged export.renderer] + # ================================================================================ # DATABASE # ================================================================================ - # service that prefixes every database table - App\Doctrine\TablePrefixSubscriber: - class: App\Doctrine\TablePrefixSubscriber - tags: - - { name: doctrine.event_subscriber } - # updates timesheet records and apply configured rate & rounding rules App\Doctrine\TimesheetSubscriber: class: App\Doctrine\TimesheetSubscriber diff --git a/src/Controller/AboutController.php b/src/Controller/AboutController.php index b7c76a9b..136b4fde 100644 --- a/src/Controller/AboutController.php +++ b/src/Controller/AboutController.php @@ -11,6 +11,10 @@ namespace App\Controller; use App\Constants; use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security; +use Symfony\Bundle\FrameworkBundle\Console\Application; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Output\BufferedOutput; +use Symfony\Component\HttpKernel\KernelInterface; use Symfony\Component\Routing\Annotation\Route; /** @@ -40,6 +44,11 @@ class AboutController extends AbstractController * @return \Symfony\Component\HttpFoundation\Response */ public function indexAction() + { + return $this->getAboutView(); + } + + protected function getAboutView(array $additional = []) { $phpInfo = $this->getPhpInfo(); unset($phpInfo[0]); @@ -71,7 +80,8 @@ class AboutController extends AbstractController } } - return $this->render('about/system.html.twig', [ + return $this->render('about/system.html.twig', array_merge( + [ 'modules' => get_loaded_extensions(), 'dotenv' => [ 'APP_ENV' => getenv('APP_ENV'), @@ -81,7 +91,9 @@ class AboutController extends AbstractController 'info' => $phpInfo, 'settings' => $settings, 'license' => $this->getLicense(), - ]); + ], + $additional + )); } /** @@ -141,4 +153,26 @@ class AboutController extends AbstractController return $phpinfo['phpinfo']; } + + /** + * @Route(path="/flush-cache", name="system_flush_cache", methods={"GET"}) + * + * @Security("is_granted('system_actions')") + */ + public function rebuildContainer(KernelInterface $kernel) + { + $application = new Application($kernel); + $application->setAutoExit(false); + + $input = new ArrayInput([ + 'command' => 'cache:clear', + '--env' => $kernel->getEnvironment(), + '-n', + ]); + + $output = new BufferedOutput(); + $application->run($input, $output); + + return $this->getAboutView(['content_action' => $output->fetch()]); + } } diff --git a/src/Controller/PluginController.php b/src/Controller/PluginController.php new file mode 100644 index 00000000..3aac1f6c --- /dev/null +++ b/src/Controller/PluginController.php @@ -0,0 +1,51 @@ +plugins = $manager; + } + + /** + * @Route(path="/", name="plugins", methods={"GET"}) + * + * @return \Symfony\Component\HttpFoundation\Response + */ + public function indexAction() + { + $plugins = $this->plugins->getPlugins(); + foreach ($this->plugins->getPlugins() as $plugin) { + $this->plugins->loadMetadata($plugin); + } + + return $this->render('plugin/index.html.twig', [ + 'plugins' => $plugins, + ]); + } +} diff --git a/src/DependencyInjection/Compiler/DoctrineCompilerPass.php b/src/DependencyInjection/Compiler/DoctrineCompilerPass.php index b148eddd..57614e2d 100644 --- a/src/DependencyInjection/Compiler/DoctrineCompilerPass.php +++ b/src/DependencyInjection/Compiler/DoctrineCompilerPass.php @@ -27,11 +27,10 @@ class DoctrineCompilerPass implements CompilerPassInterface ]; /** - * @param ContainerBuilder $container * @return array|false|null|string * @throws \Exception */ - protected function findEngine(ContainerBuilder $container) + protected function findEngine() { $engine = null; @@ -47,7 +46,7 @@ class DoctrineCompilerPass implements CompilerPassInterface if (null === $engine) { throw new \Exception( 'Could not detect database engine. Please set the environment config DATABASE_ENGINE ' . - 'to one of: "' . implode(', ', $this->allowedEngines) . '" in your .env file: DATABASE_ENGINE=sqlite' + 'to one of: "' . implode(', ', $this->allowedEngines) . '" in your .env file, e.g. DATABASE_ENGINE=sqlite' ); } @@ -68,7 +67,7 @@ class DoctrineCompilerPass implements CompilerPassInterface */ protected function getConfigFile(ContainerBuilder $container) { - $engine = $this->findEngine($container); + $engine = $this->findEngine(); $configDir = realpath( $container->getParameter('kernel.project_dir') . '/vendor/beberlei/DoctrineExtensions/config/' diff --git a/src/DependencyInjection/Compiler/ExportServiceCompilerPass.php b/src/DependencyInjection/Compiler/ExportServiceCompilerPass.php deleted file mode 100644 index 46204dd2..00000000 --- a/src/DependencyInjection/Compiler/ExportServiceCompilerPass.php +++ /dev/null @@ -1,41 +0,0 @@ -has(ServiceExport::class)) { - return; - } - - $definition = $container->findDefinition(ServiceExport::class); - - $taggedRenderer = $container->findTaggedServiceIds(Kernel::TAG_EXPORT_RENDERER); - foreach ($taggedRenderer as $id => $tags) { - $definition->addMethodCall('addRenderer', [new Reference($id)]); - } - } -} diff --git a/src/Doctrine/AbstractMigration.php b/src/Doctrine/AbstractMigration.php index 171a7867..fa8f877c 100644 --- a/src/Doctrine/AbstractMigration.php +++ b/src/Doctrine/AbstractMigration.php @@ -45,10 +45,11 @@ abstract class AbstractMigration extends BaseAbstractMigration implements Contai /** * @param string $name * @return string + * @deprecated since 0.9 */ protected function getTableName($name) { - return TablePrefixSubscriber::PREFIX . $name; + return 'kimai2_' . $name; } /** diff --git a/src/Doctrine/TablePrefixSubscriber.php b/src/Doctrine/TablePrefixSubscriber.php deleted file mode 100644 index bb7e9e93..00000000 --- a/src/Doctrine/TablePrefixSubscriber.php +++ /dev/null @@ -1,56 +0,0 @@ -getClassMetadata(); - if ($classMetadata->isInheritanceTypeSingleTable() && !$classMetadata->isRootEntity()) { - // if we are in an inheritance hierarchy, only apply this once - return; - } - - $classMetadata->setPrimaryTable(['name' => self::PREFIX . $classMetadata->getTableName()]); - - foreach ($classMetadata->getAssociationMappings() as $fieldName => $mapping) { - if (\Doctrine\ORM\Mapping\ClassMetadataInfo::MANY_TO_MANY == $mapping['type'] - // Check if "joinTable" exists: - // it can be null if this field is the reverse side of a ManyToMany relationship - && array_key_exists('name', $classMetadata->associationMappings[$fieldName]['joinTable'])) { - $mappedTableName = $classMetadata->associationMappings[$fieldName]['joinTable']['name']; - $classMetadata->associationMappings[$fieldName]['joinTable']['name'] = self::PREFIX . $mappedTableName; - } - } - } -} diff --git a/src/Entity/Activity.php b/src/Entity/Activity.php index 7999dc7c..0edca238 100644 --- a/src/Entity/Activity.php +++ b/src/Entity/Activity.php @@ -13,7 +13,7 @@ use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; /** - * @ORM\Table(name="activities") + * @ORM\Table(name="kimai2_activities") * @ORM\Entity(repositoryClass="App\Repository\ActivityRepository") */ class Activity diff --git a/src/Entity/Configuration.php b/src/Entity/Configuration.php index 20573052..d7f5109a 100644 --- a/src/Entity/Configuration.php +++ b/src/Entity/Configuration.php @@ -15,8 +15,7 @@ use Symfony\Component\Validator\Constraints as Assert; /** * @ORM\Entity(repositoryClass="App\Repository\ConfigurationRepository") - * @ORM\Table( - * name="configuration", + * @ORM\Table(name="kimai2_configuration", * uniqueConstraints={ * @ORM\UniqueConstraint(columns={"name"}) * } diff --git a/src/Entity/Customer.php b/src/Entity/Customer.php index ddd9e985..993bb5a5 100644 --- a/src/Entity/Customer.php +++ b/src/Entity/Customer.php @@ -13,7 +13,7 @@ use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; /** - * @ORM\Table(name="customers") + * @ORM\Table(name="kimai2_customers") * @ORM\Entity(repositoryClass="App\Repository\CustomerRepository") */ class Customer diff --git a/src/Entity/InvoiceTemplate.php b/src/Entity/InvoiceTemplate.php index 3506b0fe..4685e8ed 100644 --- a/src/Entity/InvoiceTemplate.php +++ b/src/Entity/InvoiceTemplate.php @@ -13,11 +13,8 @@ use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; /** - * InvoiceTemplate - * * @ORM\Entity(repositoryClass="App\Repository\InvoiceTemplateRepository") - * @ORM\Table( - * name="invoice_templates", + * @ORM\Table(name="kimai2_invoice_templates", * uniqueConstraints={ * @ORM\UniqueConstraint(columns={"name"}) * } diff --git a/src/Entity/Project.php b/src/Entity/Project.php index f1ca59cc..a7bf75c3 100644 --- a/src/Entity/Project.php +++ b/src/Entity/Project.php @@ -13,7 +13,7 @@ use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; /** - * @ORM\Table(name="projects") + * @ORM\Table(name="kimai2_projects") * @ORM\Entity(repositoryClass="App\Repository\ProjectRepository") */ class Project diff --git a/src/Entity/Timesheet.php b/src/Entity/Timesheet.php index e0b9a0f7..91574964 100644 --- a/src/Entity/Timesheet.php +++ b/src/Entity/Timesheet.php @@ -13,8 +13,7 @@ use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; /** - * @ORM\Table( - * name="timesheet", + * @ORM\Table(name="kimai2_timesheet", * indexes={ * @ORM\Index(columns={"user"}), * @ORM\Index(columns={"activity_id"}) diff --git a/src/Entity/User.php b/src/Entity/User.php index babe4539..c12a2eca 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -18,11 +18,8 @@ use Symfony\Component\Security\Core\User\UserInterface; use Symfony\Component\Validator\Constraints as Assert; /** - * Application main User entity. - * * @ORM\Entity(repositoryClass="App\Repository\UserRepository") - * @ORM\Table( - * name="users", + * @ORM\Table(name="kimai2_users", * uniqueConstraints={ * @ORM\UniqueConstraint(columns={"username"}), * @ORM\UniqueConstraint(columns={"email"}) diff --git a/src/Entity/UserPreference.php b/src/Entity/UserPreference.php index b4551385..1893f2a6 100644 --- a/src/Entity/UserPreference.php +++ b/src/Entity/UserPreference.php @@ -16,11 +16,8 @@ use Symfony\Component\Validator\Constraint; use Symfony\Component\Validator\Constraints as Assert; /** - * UserPreference - * * @ORM\Entity() - * @ORM\Table( - * name="user_preferences", + * @ORM\Table(name="kimai2_user_preferences", * uniqueConstraints={ * @ORM\UniqueConstraint(columns={"user_id", "name"}) * } diff --git a/src/Event/ConfigureMainMenuEvent.php b/src/Event/ConfigureMainMenuEvent.php index 5604f266..b34e56c4 100644 --- a/src/Event/ConfigureMainMenuEvent.php +++ b/src/Event/ConfigureMainMenuEvent.php @@ -10,6 +10,7 @@ namespace App\Event; use KevinPapst\AdminLTEBundle\Event\SidebarMenuEvent; +use KevinPapst\AdminLTEBundle\Model\MenuItemModel; use Symfony\Component\EventDispatcher\Event; use Symfony\Component\HttpFoundation\Request; @@ -28,17 +29,27 @@ class ConfigureMainMenuEvent extends Event * @var SidebarMenuEvent */ private $event; + /** + * @var MenuItemModel + */ + private $admin; + /** + * @var MenuItemModel + */ + private $system; /** * @param Request $request * @param SidebarMenuEvent $event + * @param MenuItemModel $admin + * @param MenuItemModel $system */ - public function __construct( - Request $request, - SidebarMenuEvent $event - ) { + public function __construct(Request $request, SidebarMenuEvent $event, MenuItemModel $admin, MenuItemModel $system) + { $this->request = $request; $this->event = $event; + $this->admin = $admin; + $this->system = $system; } /** @@ -56,4 +67,20 @@ class ConfigureMainMenuEvent extends Event { return $this->event; } + + /** + * @return MenuItemModel + */ + public function getAdminMenu() + { + return $this->admin; + } + + /** + * @return MenuItemModel + */ + public function getSystemMenu() + { + return $this->system; + } } diff --git a/src/Event/ThemeEvent.php b/src/Event/ThemeEvent.php index 6286f198..3aaa2335 100644 --- a/src/Event/ThemeEvent.php +++ b/src/Event/ThemeEvent.php @@ -17,6 +17,10 @@ class ThemeEvent extends Event public const JAVASCRIPT = 'app.theme.javascript'; public const STYLESHEET = 'app.theme.css'; public const HTML_HEAD = 'app.theme.html_head'; + public const CONTENT_BEFORE = 'app.theme.content_before'; + public const CONTENT_START = 'app.theme.content_start'; + public const CONTENT_END = 'app.theme.content_end'; + public const CONTENT_AFTER = 'app.theme.content_after'; /** * @var User diff --git a/src/EventSubscriber/MenuBuilderSubscriber.php b/src/EventSubscriber/MenuBuilderSubscriber.php index 0d4b067c..85d7e8db 100644 --- a/src/EventSubscriber/MenuBuilderSubscriber.php +++ b/src/EventSubscriber/MenuBuilderSubscriber.php @@ -66,26 +66,36 @@ class MenuBuilderSubscriber implements EventSubscriberInterface new MenuItemModel('dashboard', 'menu.homepage', 'dashboard', [], 'fas fa-tachometer-alt') ); - $this->eventDispatcher->dispatch( - ConfigureMainMenuEvent::CONFIGURE, - new ConfigureMainMenuEvent( - $request, - $event - ) + $menuEvent = new ConfigureMainMenuEvent( + $request, + $event, + new MenuItemModel('admin', 'menu.admin', ''), + new MenuItemModel('system', 'menu.system', '') ); - $admin = new MenuItemModel('admin', 'menu.admin', '', [], 'fas fa-wrench'); + $this->eventDispatcher->dispatch(ConfigureMainMenuEvent::CONFIGURE, $menuEvent); + // @deprecated since 0.9, will be removed with 1.0 $this->eventDispatcher->dispatch( ConfigureAdminMenuEvent::CONFIGURE, new ConfigureAdminMenuEvent( $request, - $admin + $menuEvent->getAdminMenu() ) ); - if ($admin->hasChildren()) { - $event->addItem($admin); + if ($menuEvent->getAdminMenu()->hasChildren()) { + $event->addItem(new MenuItemModel('admin', 'menu.admin', '')); + foreach ($menuEvent->getAdminMenu()->getChildren() as $child) { + $event->addItem($child); + } + } + + if ($menuEvent->getSystemMenu()->hasChildren()) { + $event->addItem(new MenuItemModel('system', 'menu.system', '')); + foreach ($menuEvent->getSystemMenu()->getChildren() as $child) { + $event->addItem($child); + } } $this->activateByRoute( diff --git a/src/EventSubscriber/MenuSubscriber.php b/src/EventSubscriber/MenuSubscriber.php index 041a2d2d..13f8d8ce 100644 --- a/src/EventSubscriber/MenuSubscriber.php +++ b/src/EventSubscriber/MenuSubscriber.php @@ -9,15 +9,14 @@ namespace App\EventSubscriber; -use App\Event\ConfigureAdminMenuEvent; use App\Event\ConfigureMainMenuEvent; +use KevinPapst\AdminLTEBundle\Event\SidebarMenuEvent; use KevinPapst\AdminLTEBundle\Model\MenuItemModel; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface; /** - * Menu event subscriber for timesheet, customer, projects, activities. - * This is a sample implementation for developer who want to add new navigation entries in their bundles. + * Menu event subscriber is creating the Kimai default menu structure. */ class MenuSubscriber implements EventSubscriberInterface { @@ -42,7 +41,6 @@ class MenuSubscriber implements EventSubscriberInterface { return [ ConfigureMainMenuEvent::CONFIGURE => ['onMainMenuConfigure', 100], - ConfigureAdminMenuEvent::CONFIGURE => ['onAdminMenuConfigure', 100], ]; } @@ -57,7 +55,17 @@ class MenuSubscriber implements EventSubscriberInterface return; } - $menu = $event->getMenu(); + $this->configureMainMenu($event->getMenu()); + $this->configureAdminMenu($event->getAdminMenu()); + $this->configureSystemMenu($event->getSystemMenu()); + } + + /** + * @param SidebarMenuEvent $menu + */ + protected function configureMainMenu(SidebarMenuEvent $menu) + { + $auth = $this->security; if ($auth->isGranted('view_own_timesheet')) { $menu->addItem( @@ -67,7 +75,7 @@ class MenuSubscriber implements EventSubscriberInterface if ($auth->isGranted('view_invoice')) { $menu->addItem( - new MenuItemModel('invoice', 'menu.invoice', 'invoice', [], 'fas fa-file-invoice') + new MenuItemModel('invoice', 'menu.invoice', 'invoice', [], 'far fa-file-alt') ); } @@ -79,33 +87,21 @@ class MenuSubscriber implements EventSubscriberInterface } /** - * @param \App\Event\ConfigureAdminMenuEvent $event + * @param MenuItemModel $menu */ - public function onAdminMenuConfigure(ConfigureAdminMenuEvent $event) + protected function configureAdminMenu(MenuItemModel $menu) { $auth = $this->security; - if (!$auth->isGranted('IS_AUTHENTICATED_REMEMBERED')) { - return; - } - - $menu = $event->getAdminMenu(); - if ($auth->isGranted('view_other_timesheet')) { $menu->addChild( - new MenuItemModel('timesheet_admin', 'menu.admin_timesheet', 'admin_timesheet', [], 'far fa-clock') - ); - } - - if ($auth->isGranted('view_user')) { - $menu->addChild( - new MenuItemModel('user_admin', 'menu.admin_user', 'admin_user', [], 'fas fa-user') + new MenuItemModel('timesheet_admin', 'menu.admin_timesheet', 'admin_timesheet', [], 'fas fa-user-clock') ); } if ($auth->isGranted('view_customer')) { $menu->addChild( - new MenuItemModel('customer_admin', 'menu.admin_customer', 'admin_customer', [], 'fas fa-users') + new MenuItemModel('customer_admin', 'menu.admin_customer', 'admin_customer', [], 'fas fa-user-tie') ); } @@ -120,6 +116,26 @@ class MenuSubscriber implements EventSubscriberInterface new MenuItemModel('activity_admin', 'menu.admin_activity', 'admin_activity', [], 'fas fa-tasks') ); } + } + + /** + * @param MenuItemModel $menu + */ + protected function configureSystemMenu(MenuItemModel $menu) + { + $auth = $this->security; + + if ($auth->isGranted('view_user')) { + $menu->addChild( + new MenuItemModel('user_admin', 'menu.admin_user', 'admin_user', [], 'fas fa-users') + ); + } + + if ($auth->isGranted('plugins')) { + $menu->addChild( + new MenuItemModel('plugins', 'menu.plugin', 'plugins', [], 'fas fa-plug') + ); + } if ($auth->isGranted('system_configuration')) { $menu->addChild( diff --git a/src/Export/Renderer/HtmlRenderer.php b/src/Export/Renderer/HtmlRenderer.php index 7e00e71b..ec55c707 100644 --- a/src/Export/Renderer/HtmlRenderer.php +++ b/src/Export/Renderer/HtmlRenderer.php @@ -13,20 +13,21 @@ use App\Entity\Timesheet; use App\Export\RendererInterface; use App\Repository\Query\TimesheetQuery; use Symfony\Component\HttpFoundation\Response; +use Twig\Environment; class HtmlRenderer implements RendererInterface { use RendererTrait; /** - * @var \Twig_Environment + * @var Environment */ protected $twig; /** - * @param \Twig_Environment $twig + * @param Environment $twig */ - public function __construct(\Twig_Environment $twig) + public function __construct(Environment $twig) { $this->twig = $twig; } @@ -35,9 +36,9 @@ class HtmlRenderer implements RendererInterface * @param Timesheet[] $timesheets * @param TimesheetQuery $query * @return Response - * @throws \Twig_Error_Loader - * @throws \Twig_Error_Runtime - * @throws \Twig_Error_Syntax + * @throws \Twig\Error\LoaderError + * @throws \Twig\Error\RuntimeError + * @throws \Twig\Error\SyntaxError */ public function render(array $timesheets, TimesheetQuery $query): Response { diff --git a/src/Export/Renderer/PDFRenderer.php b/src/Export/Renderer/PDFRenderer.php index 92707911..c401e85e 100644 --- a/src/Export/Renderer/PDFRenderer.php +++ b/src/Export/Renderer/PDFRenderer.php @@ -16,13 +16,14 @@ use App\Timesheet\UserDateTimeFactory; use App\Utils\HtmlToPdfConverter; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\ResponseHeaderBag; +use Twig\Environment; class PDFRenderer implements RendererInterface { use RendererTrait; /** - * @var \Twig_Environment + * @var Environment */ protected $twig; /** @@ -35,10 +36,11 @@ class PDFRenderer implements RendererInterface protected $converter; /** - * @param \Twig_Environment $twig + * @param Environment $twig * @param UserDateTimeFactory $dateTime + * @param HtmlToPdfConverter $converter */ - public function __construct(\Twig_Environment $twig, UserDateTimeFactory $dateTime, HtmlToPdfConverter $converter) + public function __construct(Environment $twig, UserDateTimeFactory $dateTime, HtmlToPdfConverter $converter) { $this->twig = $twig; $this->dateTime = $dateTime; @@ -49,10 +51,9 @@ class PDFRenderer implements RendererInterface * @param Timesheet[] $timesheets * @param TimesheetQuery $query * @return Response - * @throws \Mpdf\MpdfException - * @throws \Twig_Error_Loader - * @throws \Twig_Error_Runtime - * @throws \Twig_Error_Syntax + * @throws \Twig\Error\LoaderError + * @throws \Twig\Error\RuntimeError + * @throws \Twig\Error\SyntaxError */ public function render(array $timesheets, TimesheetQuery $query): Response { diff --git a/src/Export/ServiceExport.php b/src/Export/ServiceExport.php index 4bf3942d..cbba6564 100644 --- a/src/Export/ServiceExport.php +++ b/src/Export/ServiceExport.php @@ -16,6 +16,16 @@ class ServiceExport */ protected $renderer = []; + /** + * @param RendererInterface[] $renderer + */ + public function __construct(iterable $renderer) + { + foreach ($renderer as $render) { + $this->addRenderer($render); + } + } + /** * @param RendererInterface $renderer * @return $this diff --git a/src/Invoice/Renderer/TwigRenderer.php b/src/Invoice/Renderer/TwigRenderer.php index 4adc5d84..72f674fb 100644 --- a/src/Invoice/Renderer/TwigRenderer.php +++ b/src/Invoice/Renderer/TwigRenderer.php @@ -13,18 +13,19 @@ use App\Entity\InvoiceDocument; use App\Invoice\RendererInterface; use App\Model\InvoiceModel; use Symfony\Component\HttpFoundation\Response; +use Twig\Environment; class TwigRenderer implements RendererInterface { /** - * @var \Twig_Environment + * @var Environment */ protected $twig; /** - * @param \Twig_Environment $twig + * @param Environment $twig */ - public function __construct(\Twig_Environment $twig) + public function __construct(Environment $twig) { $this->twig = $twig; } diff --git a/src/Kernel.php b/src/Kernel.php index d75968f8..bcc48c9a 100644 --- a/src/Kernel.php +++ b/src/Kernel.php @@ -11,13 +11,13 @@ namespace App; use App\DependencyInjection\AppExtension; use App\DependencyInjection\Compiler\DoctrineCompilerPass; -use App\DependencyInjection\Compiler\ExportServiceCompilerPass; use App\DependencyInjection\Compiler\InvoiceServiceCompilerPass; use App\DependencyInjection\Compiler\TwigContextCompilerPass; use App\Export\RendererInterface as ExportRendererInterface; use App\Invoice\CalculatorInterface as InvoiceCalculator; use App\Invoice\NumberGeneratorInterface; use App\Invoice\RendererInterface as InvoiceRendererInterface; +use App\Plugin\PluginInterface; use App\Timesheet\CalculatorInterface as TimesheetCalculator; use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; use Symfony\Component\Config\Loader\LoaderInterface; @@ -34,6 +34,7 @@ class Kernel extends BaseKernel public const CONFIG_EXTS = '.{php,xml,yaml,yml}'; + public const TAG_PLUGIN = 'kimai.plugin'; public const TAG_EXPORT_RENDERER = 'export.renderer'; public const TAG_INVOICE_RENDERER = 'invoice.renderer'; public const TAG_INVOICE_NUMBER_GENERATOR = 'invoice.number_generator'; @@ -56,6 +57,7 @@ class Kernel extends BaseKernel $container->registerForAutoconfiguration(InvoiceRendererInterface::class)->addTag(self::TAG_INVOICE_RENDERER); $container->registerForAutoconfiguration(NumberGeneratorInterface::class)->addTag(self::TAG_INVOICE_NUMBER_GENERATOR); $container->registerForAutoconfiguration(InvoiceCalculator::class)->addTag(self::TAG_INVOICE_CALCULATOR); + $container->registerForAutoconfiguration(PluginInterface::class)->addTag(self::TAG_PLUGIN); } public function registerBundles() @@ -67,6 +69,12 @@ class Kernel extends BaseKernel } } + // do not load Kimai plugin in test environment, they may alter the default behaviour and create + // false-negatives in integration/system tests + if ('test' === $this->environment) { + return; + } + $pluginsDir = $this->getProjectDir() . '/var/plugins'; if (!file_exists($pluginsDir)) { return; @@ -110,7 +118,6 @@ class Kernel extends BaseKernel $container->addCompilerPass(new DoctrineCompilerPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -1000); $container->addCompilerPass(new TwigContextCompilerPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -1000); $container->addCompilerPass(new InvoiceServiceCompilerPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -1000); - $container->addCompilerPass(new ExportServiceCompilerPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -1000); } protected function configureRoutes(RouteCollectionBuilder $routes) diff --git a/src/Plugin/Plugin.php b/src/Plugin/Plugin.php new file mode 100644 index 00000000..e445461b --- /dev/null +++ b/src/Plugin/Plugin.php @@ -0,0 +1,83 @@ +metadata; + } + + /** + * @param PluginMetadata $metadata + * @return Plugin + */ + public function setMetadata(PluginMetadata $metadata) + { + $this->metadata = $metadata; + + return $this; + } + + /** + * @return string + */ + public function getPath(): ?string + { + return $this->path; + } + + /** + * @param string $path + * @return Plugin + */ + public function setPath(string $path) + { + $this->path = $path; + + return $this; + } + + /** + * @return string + */ + public function getName(): ?string + { + return $this->name; + } + + /** + * @param string $name + * @return Plugin + */ + public function setName(string $name) + { + $this->name = $name; + + return $this; + } +} diff --git a/src/Plugin/PluginInterface.php b/src/Plugin/PluginInterface.php new file mode 100644 index 00000000..ce8ec791 --- /dev/null +++ b/src/Plugin/PluginInterface.php @@ -0,0 +1,23 @@ +addPlugin($plugin); + } + } + + /** + * @param PluginInterface $plugin + * @throws \Exception + */ + public function addPlugin(PluginInterface $plugin) + { + if (isset($this->plugins[$plugin->getName()])) { + return; + } + + $this->plugins[$plugin->getName()] = $this->createPlugin($plugin); + } + + /** + * @return Plugin[] + */ + public function getPlugins() + { + return $this->plugins; + } + + /** + * @param string $name + * @return Plugin|null + */ + public function getPlugin(string $name): ?Plugin + { + if (!isset($this->plugins[$name])) { + return null; + } + + return $this->plugins[$name]; + } + + /** + * @param PluginInterface $bundle + * @return Plugin + */ + protected function createPlugin(PluginInterface $bundle) + { + $plugin = new Plugin(); + $plugin + ->setName($bundle->getName()) + ->setPath($bundle->getPath()) + ->setMetadata(new PluginMetadata()) + ; + + return $plugin; + } + + /** + * Call this method and pass a plugin, to set its metdata. + * This is not pre-filled by default, as it would mean to parse several composer.json on each request. + * + * @param Plugin $plugin + */ + public function loadMetadata(Plugin $plugin) + { + $composer = $plugin->getPath() . '/composer.json'; + if (!file_exists($composer) || !is_readable($composer)) { + return; + } + + $json = json_decode(file_get_contents($composer), true); + + $reqVersion = $json['extra']['kimai']['require'] ?? 'unknown'; + $version = $json['extra']['kimai']['version'] ?? 'unknown'; + $description = $json['description'] ?? ''; + + $homepage = $json['homepage'] ?? Constants::HOMEPAGE . '/store/'; + + $plugin + ->getMetadata() + ->setHomepage($homepage) + ->setKimaiVersion($reqVersion) + ->setVersion($version) + ->setDescription($description) + ; + } +} diff --git a/src/Plugin/PluginMetadata.php b/src/Plugin/PluginMetadata.php new file mode 100644 index 00000000..d268f3b2 --- /dev/null +++ b/src/Plugin/PluginMetadata.php @@ -0,0 +1,106 @@ +description; + } + + /** + * @param string $description + * @return PluginMetadata + */ + public function setDescription(string $description) + { + $this->description = $description; + + return $this; + } + + /** + * @return string + */ + public function getVersion(): ?string + { + return $this->version; + } + + /** + * @param string $version + * @return PluginMetadata + */ + public function setVersion(string $version) + { + $this->version = $version; + + return $this; + } + + /** + * @return string + */ + public function getKimaiVersion(): ?string + { + return $this->kimaiVersion; + } + + /** + * @param string $kimaiVersion + * @return PluginMetadata + */ + public function setKimaiVersion(string $kimaiVersion) + { + $this->kimaiVersion = $kimaiVersion; + + return $this; + } + + /** + * @return string + */ + public function getHomepage(): ?string + { + return $this->homepage; + } + + /** + * @param string $homepage + * @return PluginMetadata + */ + public function setHomepage(string $homepage) + { + $this->homepage = $homepage; + + return $this; + } +} diff --git a/src/Twig/Extensions.php b/src/Twig/Extensions.php index 92683b8d..8cd52f9e 100644 --- a/src/Twig/Extensions.php +++ b/src/Twig/Extensions.php @@ -101,6 +101,8 @@ class Extensions extends AbstractExtension 'on' => 'fas fa-toggle-on', 'off' => 'fas fa-toggle-off', 'audit' => 'fas fa-history', + 'home' => 'fas fa-home', + 'shop' => 'fas fa-shopping-cart', ]; /** diff --git a/src/Voter/TimesheetVoter.php b/src/Voter/TimesheetVoter.php index 1eb411af..8e61ca97 100644 --- a/src/Voter/TimesheetVoter.php +++ b/src/Voter/TimesheetVoter.php @@ -77,7 +77,7 @@ class TimesheetVoter extends AbstractVoter switch ($attribute) { case self::START: - if (!$this->canStart($subject, $user, $token)) { + if (!$this->canStart($subject)) { return false; } $permission .= $attribute; @@ -113,11 +113,9 @@ class TimesheetVoter extends AbstractVoter /** * @param Timesheet $timesheet - * @param User $user - * @param TokenInterface $token * @return bool */ - protected function canStart(Timesheet $timesheet, User $user, TokenInterface $token) + protected function canStart(Timesheet $timesheet) { // possible improvements for the future: // we could check the amount of active entries (maybe slow) diff --git a/src/Voter/UserVoter.php b/src/Voter/UserVoter.php index 35f389da..452da437 100644 --- a/src/Voter/UserVoter.php +++ b/src/Voter/UserVoter.php @@ -74,7 +74,7 @@ class UserVoter extends AbstractVoter switch ($attribute) { // special case for the UserController case self::DELETE: - if (!$this->canDelete($subject, $user, $token)) { + if (!$this->canDelete($subject, $user)) { return false; } @@ -114,7 +114,7 @@ class UserVoter extends AbstractVoter * @param User $user * @return bool */ - protected function canDelete(User $profile, User $user, TokenInterface $token) + protected function canDelete(User $profile, User $user) { return $profile->getId() !== $user->getId(); } diff --git a/symfony.lock b/symfony.lock index d3b9a3b8..36ca50c9 100644 --- a/symfony.lock +++ b/symfony.lock @@ -338,9 +338,6 @@ "setasign/fpdi": { "version": "1.6.2" }, - "squizlabs/php_codesniffer": { - "version": "3.2.2" - }, "swiftmailer/swiftmailer": { "version": "v6.0.2" }, @@ -482,6 +479,9 @@ "symfony/polyfill-intl-icu": { "version": "v1.6.0" }, + "symfony/polyfill-intl-idn": { + "version": "v1.11.0" + }, "symfony/polyfill-mbstring": { "version": "v1.6.0" }, diff --git a/templates/about/system.html.twig b/templates/about/system.html.twig index 0a664385..62a0e013 100644 --- a/templates/about/system.html.twig +++ b/templates/about/system.html.twig @@ -1,38 +1,57 @@ {% extends 'base.html.twig' %} +{% import "macros/widgets.html.twig" as widgets %} {% block page_title %}{{ 'about.title'|trans({}, 'about') }}{% endblock %} {% block page_subtitle %}{{ 'about.subtitle'|trans({}, 'about') }}{% endblock %} {% block main %} + + {{ widgets.callout('info', 'Thank you for using Kimai ' ~ constant('App\\Constants::VERSION') ~ ' ' ~ constant('App\\Constants::STATUS') ~ ' - ' ~ constant('App\\Constants::NAME'), 'Kimai ' ~ constant('App\\Constants::VERSION'), 'fas fa-thumbs-up') }} +