diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index e9bab8dc..00000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,22 +0,0 @@ -## Description - - -## Expected Behavior - - -## Actual Behavior - - -## Possible Fix - - -## Steps to Reproduce -1. -2. -3. -4. - -## Your Environment -* Version: -* Browser Name and version: -* Operating System and version (desktop or mobile): diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index b7353733..97c7c7bf 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,6 +1,6 @@ --- name: Bug report -about: Create a report to help us improve +about: Create a report to help us improve Kimai --- @@ -11,25 +11,24 @@ A clear and concise description of what the bug is. Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' -3. Scroll down to '....' -4. See error +3. See error -**Expected behavior** -A clear and concise description of what you expected to happen. +**Logfile** +``` +Add the last lines from your logfile at var/log/prod.log, around the time when the problem happened. +``` **Screenshots** If applicable, add screenshots to help explain your problem. -**Desktop (please complete the following information):** +**Desktop/Smartphone** +Please complete the following information if this seems to be a frontend issue: + - Device: [e.g. iPhone6s] - OS: [e.g. iOS] - Browser [e.g. chrome, safari] - Version [e.g. 22] -**Smartphone (please complete the following information):** - - Device: [e.g. iPhone6] - - OS: [e.g. iOS8.1] - - Browser [e.g. stock browser, safari] - - Version [e.g. 22] - **Additional context** Add any other context about the problem here. + - Kimai version + - PHP version diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 066b2d92..35f82300 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,6 +1,6 @@ --- name: Feature request -about: Suggest an idea for this project +about: Suggest an idea to make Kimai better --- diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 22829bd2..083082c5 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,5 +1,5 @@ ## Description - +A clear and concise description of what this pull request changes. ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) @@ -7,8 +7,7 @@ - [ ] Breaking change (fix or feature that would cause existing functionality to change) ## Checklist -- [ ] My code follows the code style -- [ ] All files have a license header -- [ ] All methods have a doc header with type declarations +- [ ] I have executed `bin/console kimai:phpcs --fix` to make sure my changes adopt the correct code style - [ ] I have updated the documentation accordingly - [ ] I have added tests to cover my changes +- [ ] I agree that this code is used in Kimai and will be published under the [MIT license](https://github.com/kevinpapst/kimai2/blob/master/LICENSE) diff --git a/.github_changelog_generator b/.github_changelog_generator index c03b7660..f1bc90eb 100644 --- a/.github_changelog_generator +++ b/.github_changelog_generator @@ -1,4 +1,4 @@ unreleased=true -future-release=0.7 +future-release=0.8 exclude-labels=duplicate,question,invalid,wontfix,release enhancement_labels=>enhancement,Enhancement,feature request \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index ce914eaf..4ab92d33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,63 @@ # Change Log +## [0.8](https://github.com/kevinpapst/kimai2/tree/0.8) (2019-02-19) +[Full Changelog](https://github.com/kevinpapst/kimai2/compare/0.7...0.8) + +**Implemented enhancements:** + +- Export other users' timesheets using the API [\#562](https://github.com/kevinpapst/kimai2/issues/562) +- Some importer bugs/improvements [\#553](https://github.com/kevinpapst/kimai2/issues/553) +- Export timesheet only exports entries on the current page [\#534](https://github.com/kevinpapst/kimai2/issues/534) +- On the last activities dropdown show tha last ten DIFFERENT activities [\#533](https://github.com/kevinpapst/kimai2/issues/533) +- Better user expierence on "My Times" [\#526](https://github.com/kevinpapst/kimai2/issues/526) +- Highlight current day in datepickers and not only the selected day [\#522](https://github.com/kevinpapst/kimai2/issues/522) +- Configuration of first page after login [\#501](https://github.com/kevinpapst/kimai2/issues/501) +- Visual Grouping of entry from same day or same week with some stats of that group [\#495](https://github.com/kevinpapst/kimai2/issues/495) +- add currency field to invoice template [\#422](https://github.com/kevinpapst/kimai2/issues/422) + +**Fixed bugs:** + +- Error 404 on 'My times' page 2+ if the time range is changed to something smaller [\#558](https://github.com/kevinpapst/kimai2/issues/558) +- Timezone is not working correctly [\#554](https://github.com/kevinpapst/kimai2/issues/554) +- New time picker not working on languages without updated translation [\#546](https://github.com/kevinpapst/kimai2/issues/546) +- Export timesheet only exports entries on the current page [\#534](https://github.com/kevinpapst/kimai2/issues/534) +- Kimai2 will not allow me to enter times in the future [\#531](https://github.com/kevinpapst/kimai2/issues/531) +- No projects in filter section [\#525](https://github.com/kevinpapst/kimai2/issues/525) +- This value should be greater than or equal to zero [\#511](https://github.com/kevinpapst/kimai2/issues/511) +- Wrong time after migration kimai v1 data [\#507](https://github.com/kevinpapst/kimai2/issues/507) +- SQL-Error for getRecentActivities\(\) caused by GROUP BY [\#488](https://github.com/kevinpapst/kimai2/issues/488) + +**Closed issues:** + +- Feedback for improving installation setup [\#541](https://github.com/kevinpapst/kimai2/issues/541) +- template entry.end\_time shows empty field [\#535](https://github.com/kevinpapst/kimai2/issues/535) +- Global Projects [\#413](https://github.com/kevinpapst/kimai2/issues/413) + +**Merged pull requests:** + +- handle deleted user during import from v1 [\#569](https://github.com/kevinpapst/kimai2/pull/569) ([kevinpapst](https://github.com/kevinpapst)) +- fix pagination in combination with daterange picker [\#568](https://github.com/kevinpapst/kimai2/pull/568) ([kevinpapst](https://github.com/kevinpapst)) +- allow to query other users timesheets via api [\#563](https://github.com/kevinpapst/kimai2/pull/563) ([kevinpapst](https://github.com/kevinpapst)) +- Updated HTML invoice templates [\#560](https://github.com/kevinpapst/kimai2/pull/560) ([kevinpapst](https://github.com/kevinpapst)) +- updated composer packages [\#559](https://github.com/kevinpapst/kimai2/pull/559) ([kevinpapst](https://github.com/kevinpapst)) +- fix timezone problems in timesheet forms [\#555](https://github.com/kevinpapst/kimai2/pull/555) ([kevinpapst](https://github.com/kevinpapst)) +- Daily stats in timesheet [\#552](https://github.com/kevinpapst/kimai2/pull/552) ([kevinpapst](https://github.com/kevinpapst)) +- Added more php requirements to documentation [\#551](https://github.com/kevinpapst/kimai2/pull/551) ([infeeeee](https://github.com/infeeeee)) +- improve recent activities [\#550](https://github.com/kevinpapst/kimai2/pull/550) ([kevinpapst](https://github.com/kevinpapst)) +- scss fixes: year selector width, dropdown menu width, navbar refactoring [\#549](https://github.com/kevinpapst/kimai2/pull/549) ([infeeeee](https://github.com/infeeeee)) +- improved installation docs [\#548](https://github.com/kevinpapst/kimai2/pull/548) ([kevinpapst](https://github.com/kevinpapst)) +- fix daterange-picker for fr, hu and ar [\#547](https://github.com/kevinpapst/kimai2/pull/547) ([kevinpapst](https://github.com/kevinpapst)) +- do not limit users timesheet export page size [\#545](https://github.com/kevinpapst/kimai2/pull/545) ([kevinpapst](https://github.com/kevinpapst)) +- Use Symfony formatter for currency symbol placement [\#542](https://github.com/kevinpapst/kimai2/pull/542) ([sanjitlpatel](https://github.com/sanjitlpatel)) +- Localized date-inputs and daterange-picker [\#540](https://github.com/kevinpapst/kimai2/pull/540) ([kevinpapst](https://github.com/kevinpapst)) +- added export module [\#538](https://github.com/kevinpapst/kimai2/pull/538) ([kevinpapst](https://github.com/kevinpapst)) +- updated documentation [\#536](https://github.com/kevinpapst/kimai2/pull/536) ([kevinpapst](https://github.com/kevinpapst)) +- support remote data in beta-test selectpicker [\#529](https://github.com/kevinpapst/kimai2/pull/529) ([kevinpapst](https://github.com/kevinpapst)) +- Improve allowed running records [\#528](https://github.com/kevinpapst/kimai2/pull/528) ([kevinpapst](https://github.com/kevinpapst)) +- Improve daterangepicker [\#527](https://github.com/kevinpapst/kimai2/pull/527) ([kevinpapst](https://github.com/kevinpapst)) +- added configurable view after login [\#523](https://github.com/kevinpapst/kimai2/pull/523) ([kevinpapst](https://github.com/kevinpapst)) +- convert timesheets to UTC with support for user timezone [\#372](https://github.com/kevinpapst/kimai2/pull/372) ([kevinpapst](https://github.com/kevinpapst)) + ## [0.7](https://github.com/kevinpapst/kimai2/tree/0.7) (2019-01-28) [Full Changelog](https://github.com/kevinpapst/kimai2/compare/0.6.1...0.7) @@ -18,11 +76,10 @@ - Configuration option to disable fixed rate and hourly rate from "edit timesheet" [\#330](https://github.com/kevinpapst/kimai2/issues/330) - Set other users hourly rate [\#303](https://github.com/kevinpapst/kimai2/issues/303) - Feature request - Make "Rate" hideable [\#217](https://github.com/kevinpapst/kimai2/issues/217) -- fixed null project for advanced invoice calculator [\#462](https://github.com/kevinpapst/kimai2/pull/462) ([kevinpapst](https://github.com/kevinpapst)) +- fixed null project for advanced invoice calculator [\#462](https://github.com/kevinpapst/kimai2/pull/462) ([kevinpapst](https://github.com/kevinpapst)) **Fixed bugs:** -- This value should be greater than or equal to zero [\#511](https://github.com/kevinpapst/kimai2/issues/511) - Timesheet Export for Admins [\#503](https://github.com/kevinpapst/kimai2/issues/503) - admin activity: visibility "none" \(no filter\) causes sql-error [\#491](https://github.com/kevinpapst/kimai2/issues/491) - login-screen optimizations [\#483](https://github.com/kevinpapst/kimai2/issues/483) @@ -51,7 +108,7 @@ - pagination without reload while keeping filters applied [\#521](https://github.com/kevinpapst/kimai2/pull/521) ([kevinpapst](https://github.com/kevinpapst)) - go back to calendar after editing and creation of time-records [\#519](https://github.com/kevinpapst/kimai2/pull/519) ([kevinpapst](https://github.com/kevinpapst)) -- fetch toolbar results without page reload [\#518](https://github.com/kevinpapst/kimai2/pull/518) ([kevinpapst](https://github.com/kevinpapst)) +- fetch toolbar results without page reload [\#518](https://github.com/kevinpapst/kimai2/pull/518) ([kevinpapst](https://github.com/kevinpapst)) - Form and theme improvements [\#513](https://github.com/kevinpapst/kimai2/pull/513) ([kevinpapst](https://github.com/kevinpapst)) - validation for future and negative times [\#512](https://github.com/kevinpapst/kimai2/pull/512) ([kevinpapst](https://github.com/kevinpapst)) - alphabetical order for selectboxes [\#510](https://github.com/kevinpapst/kimai2/pull/510) ([kevinpapst](https://github.com/kevinpapst)) diff --git a/UPGRADING.md b/UPGRADING.md index f42a5f2d..8e5edbca 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -5,26 +5,30 @@ Database upgrades are currently ONLY provided for MySQL/MariaDB and SQLite. If you plan on using e.g. PostgreSQL, please read more about the `bin/console doctrine:migrations:diff` and `bin/console doctrine:migrations:migrate` commands and contact us, so we can integrate them into the official releases. -A normal upgrade can be executed with these commands: +Upgrading to the latest available version can be achieved with these commands: ```bash -git pull origin master +git fetch --tags +git checkout 0.8 sudo -u www-data composer install --no-dev --optimize-autoloader sudo -u www-data bin/console cache:clear --env=prod sudo -u www-data bin/console cache:warmup --env=prod bin/console doctrine:migrations:migrate ``` +Be careful when upgrading multiple versions at once: follow each version specific information! + There might be version specific tasks that need to be executed before or after these steps, please see below if your updated version is mentioned below. ## [0.8](https://github.com/kevinpapst/kimai2/releases/tag/0.8) (unreleased) -There was a change introduced regarding the storage of the date-time objects in timesheet records. -Please read this [Pull request](https://github.com/kevinpapst/kimai2/pull/372) carefully before you follow the instructions -to convert the timezones in your existing time records with `bin/console kimai:convert-timezone --help`. +After you followed the normal update and database migration process (see above), you need to execute a bash command (see below) and check if you want to apply changes to your `local.yaml`. -If you don't do that, you will end up with wrong times in your database. Be especially careful, when you previously imported data from Kimai v1. +- An important change was introduced regarding the storage of date-time objects in the database. Please read this [pull request](https://github.com/kevinpapst/kimai2/pull/372) BEFORE you follow the instructions +to convert the timezones in your existing time records with `bin/console kimai:convert-timezone`. Without that, you will end up with wrong times in your database. +- A new boolean setting `kimai.timesheet.rules.allow_future_times` was introduced +- New [permissions](var/docs/permissions.md) are available: `view_export,create_export,edit_export_own_timesheet,edit_export_other_timesheet,system_information` ## [0.7](https://github.com/kevinpapst/kimai2/releases/tag/0.7) (2019-01-28) @@ -68,16 +72,6 @@ sudo composer self-update sudo -u www-data composer update symfony/flex --no-plugins --no-scripts ``` -Then the full update can be executed as usual: - -```bash -git pull origin master -sudo -u www-data composer install --no-dev --optimize-autoloader -sudo -u www-data bin/console cache:clear --env=prod -sudo -u www-data bin/console cache:warmup --env=prod -bin/console doctrine:migrations:migrate -``` - ## [0.3](https://github.com/kevinpapst/kimai2/releases/tag/0.3) (2018-07-22) **Update from 0.2:** diff --git a/assets/js/kimai.js b/assets/js/kimai.js index a7057a99..91f9f866 100644 --- a/assets/js/kimai.js +++ b/assets/js/kimai.js @@ -62,7 +62,6 @@ $(function() { $('input[data-daterangepickerenable="on"]').each(function(index) { var localeFormat = $(this).data('format'); var separator = $(this).data('separator'); - var transToday = $.kimai.settings['today']; var rangesList = {}; rangesList[$.kimai.settings['today']] = [moment(), moment()]; rangesList[$.kimai.settings['yesterday']] = [moment().subtract(1, 'days'), moment().subtract(1, 'days')]; diff --git a/src/API/ActivityController.php b/src/API/ActivityController.php index 600dec2d..3e29d340 100644 --- a/src/API/ActivityController.php +++ b/src/API/ActivityController.php @@ -83,11 +83,11 @@ class ActivityController extends BaseApiController $query->setOrderBy($orderBy); } - if (null !== ($globals = $paramFetcher->get('globals'))) { + if (null !== $paramFetcher->get('globals')) { $query->setGlobalsOnly(true); } - if ('false' === ($globalsFirst = $paramFetcher->get('globalsFirst'))) { + if ('false' === $paramFetcher->get('globalsFirst')) { $query->setOrderGlobalsFirst(false); } diff --git a/src/Command/ConvertTimezoneCommand.php b/src/Command/ConvertTimezoneCommand.php index a23e7efe..e677b4ee 100644 --- a/src/Command/ConvertTimezoneCommand.php +++ b/src/Command/ConvertTimezoneCommand.php @@ -25,6 +25,8 @@ use Symfony\Component\Console\Style\SymfonyStyle; * You should convert all timesheet records that were saved with Kimai 2 directly, but NOT the ones migrated from Kimai v1. * * Please read https://github.com/kevinpapst/kimai2/pull/372 to find out more! + * + * @codeCoverageIgnore */ class ConvertTimezoneCommand extends Command { diff --git a/src/Command/KimaiImporterCommand.php b/src/Command/KimaiImporterCommand.php index c98de081..c71a8980 100644 --- a/src/Command/KimaiImporterCommand.php +++ b/src/Command/KimaiImporterCommand.php @@ -35,6 +35,8 @@ use Symfony\Component\Validator\Validator\ValidatorInterface; /** * Command used to import data from a Kimai v1 installation. * Getting help in improving this script would be fantastic, it currently only handles the most basic use-cases. + * + * @codeCoverageIgnore */ class KimaiImporterCommand extends Command { @@ -962,7 +964,6 @@ class KimaiImporterCommand extends Command // ----------------------- unknown user, damned missing data integrity in Kimai v1 ----------------------- if (!isset($this->users[$oldRecord['userID']])) { - $tempUserName = uniqid(); $tempPassword = uniqid() . uniqid(); diff --git a/src/Constants.php b/src/Constants.php index 13fb31d9..42ac89d3 100644 --- a/src/Constants.php +++ b/src/Constants.php @@ -21,7 +21,7 @@ class Constants /** * The current release version */ - public const VERSION = '0.7'; + public const VERSION = '0.8'; /** * The release name, will only change for new major version */ diff --git a/src/EventSubscriber/TimezoneSubscriber.php b/src/EventSubscriber/TimezoneSubscriber.php index 32db0268..7df32a43 100644 --- a/src/EventSubscriber/TimezoneSubscriber.php +++ b/src/EventSubscriber/TimezoneSubscriber.php @@ -74,5 +74,4 @@ class TimezoneSubscriber implements EventSubscriberInterface return ($user instanceof User); } - } diff --git a/src/Repository/TimesheetRepository.php b/src/Repository/TimesheetRepository.php index c9e52128..e9193a05 100644 --- a/src/Repository/TimesheetRepository.php +++ b/src/Repository/TimesheetRepository.php @@ -9,6 +9,7 @@ namespace App\Repository; +use App\Entity\Activity; use App\Entity\Timesheet; use App\Entity\User; use App\Model\Statistic\Month; @@ -364,8 +365,7 @@ class TimesheetRepository extends AbstractRepository ->setParameter('activity', $query->getActivity()); } - // TODO if activity is an int, this will fail - if (null === $query->getActivity() || null === $query->getActivity()->getProject()) { + if (null === $query->getActivity() || ($query->getActivity() instanceof Activity && null === $query->getActivity()->getProject())) { if (null !== $query->getProject()) { $qb->andWhere('t.project = :project') ->setParameter('project', $query->getProject()); diff --git a/tests/API/ActivityControllerTest.php b/tests/API/ActivityControllerTest.php index 63f9533e..8a7da298 100644 --- a/tests/API/ActivityControllerTest.php +++ b/tests/API/ActivityControllerTest.php @@ -67,7 +67,7 @@ class ActivityControllerTest extends APIControllerBaseTest $this->assertAccessIsGranted($client, $url, 'GET', $parameters); $result = json_decode($client->getResponse()->getContent(), true); - $this->assertInternalType('array', $result); + $this->assertIsArray($result); $this->assertNotEmpty($result); $this->assertEquals(count($expected), count($result)); for ($i = 0; $i < count($result); $i++) { @@ -93,13 +93,32 @@ class ActivityControllerTest extends APIControllerBaseTest yield ['/api/activities', ['project' => '2', 'visible' => VisibilityQuery::SHOW_HIDDEN], [[false], [true, 2]]]; } + public function testGetCollectionWithQuery() + { + $client = $this->getClientForAuthenticatedUser(User::ROLE_USER); + $this->loadActivityTestData($client); + + $query = ['order' => 'ASC', 'orderBy' => 'project', 'globalsFirst' => 'false']; + $client = $this->getClientForAuthenticatedUser(User::ROLE_USER); + $this->assertAccessIsGranted($client, '/api/activities', 'GET', $query); + $result = json_decode($client->getResponse()->getContent(), true); + + $this->assertIsArray($result); + $this->assertNotEmpty($result); + $this->assertEquals(5, count($result)); + $this->assertStructure($result[0], false); + $this->assertEquals(1, $result[2]['project']); + $this->assertEquals(2, $result[3]['project']); + $this->assertEquals(2, $result[4]['project']); + } + public function testGetEntity() { $client = $this->getClientForAuthenticatedUser(User::ROLE_USER); $this->assertAccessIsGranted($client, '/api/activities/1'); $result = json_decode($client->getResponse()->getContent(), true); - $this->assertInternalType('array', $result); + $this->assertIsArray($result); $expectedKeys = ['id', 'name', 'comment', 'visible']; $actual = array_keys($result); diff --git a/tests/API/ApiDocControllerTest.php b/tests/API/ApiDocControllerTest.php index 66a77e63..508c133d 100644 --- a/tests/API/ApiDocControllerTest.php +++ b/tests/API/ApiDocControllerTest.php @@ -35,7 +35,7 @@ class ApiDocControllerTest extends ControllerBaseTest $this->assertAccessIsGranted($client, '/api/doc.json'); $this->assertContains('"title":"Kimai 2 - API Docs"', $client->getResponse()->getContent()); $result = json_decode($client->getResponse()->getContent(), true); - $this->assertInternalType('array', $result); + $this->assertIsArray($result); $this->assertNotEmpty($result); } diff --git a/tests/API/CustomerControllerTest.php b/tests/API/CustomerControllerTest.php index 5080cdbd..62b71bdd 100644 --- a/tests/API/CustomerControllerTest.php +++ b/tests/API/CustomerControllerTest.php @@ -28,7 +28,20 @@ class CustomerControllerTest extends APIControllerBaseTest $this->assertAccessIsGranted($client, '/api/customers'); $result = json_decode($client->getResponse()->getContent(), true); - $this->assertInternalType('array', $result); + $this->assertIsArray($result); + $this->assertNotEmpty($result); + $this->assertEquals(1, count($result)); + $this->assertStructure($result[0], false); + } + + public function testGetCollectionWithQuery() + { + $query = ['order' => 'ASC', 'orderBy' => 'name', 'visible' => 3]; + $client = $this->getClientForAuthenticatedUser(User::ROLE_USER); + $this->assertAccessIsGranted($client, '/api/customers', 'GET', $query); + $result = json_decode($client->getResponse()->getContent(), true); + + $this->assertIsArray($result); $this->assertNotEmpty($result); $this->assertEquals(1, count($result)); $this->assertStructure($result[0], false); @@ -40,7 +53,7 @@ class CustomerControllerTest extends APIControllerBaseTest $this->assertAccessIsGranted($client, '/api/customers/1'); $result = json_decode($client->getResponse()->getContent(), true); - $this->assertInternalType('array', $result); + $this->assertIsArray($result); $this->assertStructure($result, true); } diff --git a/tests/API/HealthcheckControllerTest.php b/tests/API/HealthcheckControllerTest.php index c0a96ef1..bd0910b7 100644 --- a/tests/API/HealthcheckControllerTest.php +++ b/tests/API/HealthcheckControllerTest.php @@ -29,7 +29,7 @@ class HealthcheckControllerTest extends APIControllerBaseTest $this->assertAccessIsGranted($client, '/api/ping'); $result = json_decode($client->getResponse()->getContent(), true); - $this->assertInternalType('array', $result); + $this->assertIsArray($result); $this->assertEquals(['message' => 'pong'], $result); } @@ -39,7 +39,7 @@ class HealthcheckControllerTest extends APIControllerBaseTest $this->assertAccessIsGranted($client, '/api/version'); $result = json_decode($client->getResponse()->getContent(), true); - $this->assertInternalType('array', $result); + $this->assertIsArray($result); $this->assertArrayHasKey('version', $result); $this->assertArrayHasKey('candidate', $result); diff --git a/tests/API/ProjectControllerTest.php b/tests/API/ProjectControllerTest.php index 919874fd..39654f7a 100644 --- a/tests/API/ProjectControllerTest.php +++ b/tests/API/ProjectControllerTest.php @@ -32,7 +32,7 @@ class ProjectControllerTest extends APIControllerBaseTest $this->assertAccessIsGranted($client, '/api/projects'); $result = json_decode($client->getResponse()->getContent(), true); - $this->assertInternalType('array', $result); + $this->assertIsArray($result); $this->assertNotEmpty($result); $this->assertEquals(1, count($result)); $this->assertStructure($result[0], false); @@ -81,7 +81,7 @@ class ProjectControllerTest extends APIControllerBaseTest $this->assertAccessIsGranted($client, $url, 'GET', $parameters); $result = json_decode($client->getResponse()->getContent(), true); - $this->assertInternalType('array', $result); + $this->assertIsArray($result); $this->assertEquals(count($expected), count($result), 'Found wrong amount of projects'); for ($i = 0; $i < count($expected); $i++) { @@ -111,7 +111,7 @@ class ProjectControllerTest extends APIControllerBaseTest $this->assertAccessIsGranted($client, '/api/projects/1'); $result = json_decode($client->getResponse()->getContent(), true); - $this->assertInternalType('array', $result); + $this->assertIsArray($result); $this->assertStructure($result); } diff --git a/tests/API/TimesheetControllerTest.php b/tests/API/TimesheetControllerTest.php index 912e6387..fcd3ecef 100644 --- a/tests/API/TimesheetControllerTest.php +++ b/tests/API/TimesheetControllerTest.php @@ -14,6 +14,7 @@ use App\Entity\Customer; use App\Entity\Project; use App\Entity\User; use App\Tests\DataFixtures\TimesheetFixtures; +use Symfony\Component\HttpFoundation\Response; /** * @coversDefaultClass \App\API\TimesheetController @@ -118,7 +119,7 @@ class TimesheetControllerTest extends APIControllerBaseTest public function testGetCollectionWithQuery() { - $query = ['customer' => 1, 'project' => 1, 'page' => 2, 'size' => 5, 'order' => 'DESC', 'orderBy' => 'rate']; + $query = ['customer' => 1, 'project' => 1, 'activity' => 1, 'page' => 2, 'size' => 5, 'order' => 'DESC', 'orderBy' => 'rate']; $client = $this->getClientForAuthenticatedUser(User::ROLE_USER); $this->assertAccessIsGranted($client, '/api/timesheets', 'GET', $query); $result = json_decode($client->getResponse()->getContent(), true); @@ -218,24 +219,6 @@ class TimesheetControllerTest extends APIControllerBaseTest $this->assertApiCallValidationError($client->getResponse(), ['activity']); } - public function testPostActionWithIdIsNotAllowed() - { - $client = $this->getClientForAuthenticatedUser(User::ROLE_USER); - $data = [ - 'id' => 1, - 'activity' => 1, - 'project' => 1, - 'begin' => (new \DateTime('- 8 hours'))->format('Y-m-d H:m'), - 'end' => (new \DateTime())->format('Y-m-d H:m'), - 'description' => 'foo', - 'fixedRate' => 2016, - 'hourlyRate' => 127 - ]; - $this->request($client, '/api/timesheets', 'POST', [], json_encode($data)); - $this->assertFalse($client->getResponse()->isSuccessful()); - $this->assertEquals(400, $client->getResponse()->getStatusCode()); - } - public function testNotFound() { $this->assertEntityNotFound(User::ROLE_USER, '/api/timesheets/20'); @@ -263,6 +246,38 @@ class TimesheetControllerTest extends APIControllerBaseTest $this->assertEquals(1, $result['exported']); } + public function testPatchActionWithInvalidUser() + { + $client = $this->getClientForAuthenticatedUser(User::ROLE_USER); + $em = $client->getContainer()->get('doctrine.orm.entity_manager'); + + $fixture = new TimesheetFixtures(); + $fixture + ->setFixedRate(true) + ->setHourlyRate(true) + ->setAmount(10) + ->setUser($this->getUserByRole($em, User::ROLE_TEAMLEAD)) + ->setStartDate(new \DateTime('-10 days')) + ->setAllowEmptyDescriptions(false) + ; + $this->importFixture($em, $fixture); + + $data = [ + 'activity' => 1, + 'project' => 1, + 'begin' => (new \DateTime('- 7 hours'))->format('Y-m-d H:m'), + 'end' => (new \DateTime())->format('Y-m-d H:m'), + 'description' => 'foo', + 'exported' => true, + ]; + $this->request($client, '/api/timesheets/15', 'PATCH', [], json_encode($data)); + $response = $client->getResponse(); + $this->assertFalse($response->isSuccessful()); + $this->assertEquals(Response::HTTP_FORBIDDEN, $response->getStatusCode()); + $json = json_decode($response->getContent(), true); + $this->assertEquals('User cannot update timesheet', $json['message']); + } + public function testInvalidPatchAction() { $client = $this->getClientForAuthenticatedUser(User::ROLE_USER); diff --git a/tests/API/UserControllerTest.php b/tests/API/UserControllerTest.php index 429069e9..ee5a929c 100644 --- a/tests/API/UserControllerTest.php +++ b/tests/API/UserControllerTest.php @@ -29,7 +29,7 @@ class UserControllerTest extends APIControllerBaseTest $this->assertAccessIsGranted($client, '/api/users'); $result = json_decode($client->getResponse()->getContent(), true); - $this->assertInternalType('array', $result); + $this->assertIsArray($result); $this->assertNotEmpty($result); $this->assertEquals(6, count($result)); $this->assertStructure($result[0], false); @@ -41,7 +41,7 @@ class UserControllerTest extends APIControllerBaseTest $this->assertAccessIsGranted($client, '/api/users/1'); $result = json_decode($client->getResponse()->getContent(), true); - $this->assertInternalType('array', $result); + $this->assertIsArray($result); $this->assertStructure($result); } diff --git a/tests/Controller/CalendarControllerTest.php b/tests/Controller/CalendarControllerTest.php index cf8ab906..cf96fdca 100644 --- a/tests/Controller/CalendarControllerTest.php +++ b/tests/Controller/CalendarControllerTest.php @@ -48,7 +48,7 @@ class CalendarControllerTest extends ControllerBaseTest $response = $client->getResponse(); $this->assertTrue($response->isSuccessful()); $json = json_decode($response->getContent(), true); - $this->assertInternalType('array', $json); + $this->assertIsArray($json); $this->assertEmpty($json); } @@ -68,11 +68,11 @@ class CalendarControllerTest extends ControllerBaseTest $response = $client->getResponse(); $this->assertTrue($response->isSuccessful()); $json = json_decode($response->getContent(), true); - $this->assertInternalType('array', $json); + $this->assertIsArray($json); $this->assertNotEmpty($json); $this->assertEquals(10, count($json)); foreach ($json as $result) { - $this->assertInternalType('array', $result); + $this->assertIsArray($result); $this->assertCalendarStructure($result); } } diff --git a/tests/DataFixtures/TimesheetFixtures.php b/tests/DataFixtures/TimesheetFixtures.php index e2fb7c59..4dc73c28 100644 --- a/tests/DataFixtures/TimesheetFixtures.php +++ b/tests/DataFixtures/TimesheetFixtures.php @@ -63,6 +63,7 @@ class TimesheetFixtures extends Fixture public function setAllowEmptyDescriptions(bool $allowEmptyDescriptions) { $this->allowEmptyDescriptions = $allowEmptyDescriptions; + return $this; } diff --git a/tests/Invoice/ServiceInvoiceTest.php b/tests/Invoice/ServiceInvoiceTest.php index 63a3dead..80b310f2 100644 --- a/tests/Invoice/ServiceInvoiceTest.php +++ b/tests/Invoice/ServiceInvoiceTest.php @@ -28,13 +28,13 @@ class ServiceInvoiceTest extends TestCase $sut = new ServiceInvoice($repo); $this->assertEmpty($sut->getCalculator()); - $this->assertInternalType('array', $sut->getCalculator()); + $this->assertIsArray($sut->getCalculator()); $this->assertEmpty($sut->getRenderer()); - $this->assertInternalType('array', $sut->getRenderer()); + $this->assertIsArray($sut->getRenderer()); $this->assertEmpty($sut->getNumberGenerator()); - $this->assertInternalType('array', $sut->getNumberGenerator()); + $this->assertIsArray($sut->getNumberGenerator()); $this->assertEmpty($sut->getDocuments()); - $this->assertInternalType('array', $sut->getDocuments()); + $this->assertIsArray($sut->getDocuments()); $this->assertNull($sut->getCalculatorByName('default')); $this->assertNull($sut->getDocumentByName('default')); diff --git a/tests/Model/InvoiceModelTest.php b/tests/Model/InvoiceModelTest.php index 545d7e62..debce854 100644 --- a/tests/Model/InvoiceModelTest.php +++ b/tests/Model/InvoiceModelTest.php @@ -33,7 +33,7 @@ class InvoiceModelTest extends TestCase $this->assertNull($sut->getQuery()); $this->assertNull($sut->getDueDate()); $this->assertEmpty($sut->getEntries()); - $this->assertInternalType('array', $sut->getEntries()); + $this->assertIsArray($sut->getEntries()); $this->assertInstanceOf(\DateTime::class, $sut->getInvoiceDate()); } diff --git a/tests/Model/Statistic/YearTest.php b/tests/Model/Statistic/YearTest.php index def7b064..27b534ef 100644 --- a/tests/Model/Statistic/YearTest.php +++ b/tests/Model/Statistic/YearTest.php @@ -23,7 +23,7 @@ class YearTest extends TestCase $sut = new Year('1999'); $this->assertNull($sut->getMonth('01')); $this->assertEmpty($sut->getMonths()); - $this->assertInternalType('array', $sut->getMonths()); + $this->assertIsArray($sut->getMonths()); $this->assertEquals('1999', $sut->getYear()); } diff --git a/tests/Repository/InvoiceDocumentRepositoryTest.php b/tests/Repository/InvoiceDocumentRepositoryTest.php index 89996de1..402f1ab2 100644 --- a/tests/Repository/InvoiceDocumentRepositoryTest.php +++ b/tests/Repository/InvoiceDocumentRepositoryTest.php @@ -36,7 +36,7 @@ class InvoiceDocumentRepositoryTest extends TestCase { $sut = new InvoiceDocumentRepository([]); $this->assertEmpty($sut->findAll()); - $this->assertInternalType('array', $sut->findAll()); + $this->assertIsArray($sut->findAll()); $this->assertNull($sut->findByName('default')); } diff --git a/tests/Repository/TimesheetRepositoryTest.php b/tests/Repository/TimesheetRepositoryTest.php index a2c90769..964a71c8 100644 --- a/tests/Repository/TimesheetRepositoryTest.php +++ b/tests/Repository/TimesheetRepositoryTest.php @@ -45,7 +45,7 @@ class TimesheetRepositoryTest extends AbstractRepositoryTest $query->setResultType(BaseQuery::RESULT_TYPE_OBJECTS); $result = $repository->findByQuery($query); - $this->assertInternalType('array', $result); + $this->assertIsArray($result); } public function testStoppedEntriesCannotBeStoppedAgain() diff --git a/tests/Twig/ExtensionsTest.php b/tests/Twig/ExtensionsTest.php index b253c34a..9684807b 100644 --- a/tests/Twig/ExtensionsTest.php +++ b/tests/Twig/ExtensionsTest.php @@ -220,7 +220,7 @@ class ExtensionsTest extends TestCase foreach ($icons as $icon) { $result = $sut->icon($icon); $this->assertNotEmpty($result, 'Problem with icon definition: ' . $icon); - $this->assertInternalType('string', $result); + $this->assertIsString($result); } // test fallback will be returned diff --git a/tests/Utils/DurationTest.php b/tests/Utils/DurationTest.php index 3750a0f7..22c185fb 100644 --- a/tests/Utils/DurationTest.php +++ b/tests/Utils/DurationTest.php @@ -20,6 +20,8 @@ class DurationTest extends TestCase public function testFormat() { $sut = new Duration(); + + $this->assertNull($sut->format(null)); $this->assertEquals('02:38', $sut->format(9494)); $this->assertEquals('02:38:14', $sut->format(9494, Duration::FORMAT_WITH_SECONDS)); } @@ -73,7 +75,7 @@ class DurationTest extends TestCase ['13', Duration::FORMAT_COLON], ['13-13', Duration::FORMAT_COLON], ['13.13', Duration::FORMAT_COLON], - [1111, 1111, Duration::FORMAT_NATURAL], + [1111, Duration::FORMAT_NATURAL], // invalid modes [17, 'foo'], diff --git a/tests/Utils/LocaleSettingsTest.php b/tests/Utils/LocaleSettingsTest.php index d774c6cb..fb2bc73c 100644 --- a/tests/Utils/LocaleSettingsTest.php +++ b/tests/Utils/LocaleSettingsTest.php @@ -142,6 +142,7 @@ class LocaleSettingsTest extends TestCase /** * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Unknown locale given: xx */ public function testInvalidLocaleWithGivenLocale() { @@ -197,4 +198,16 @@ class LocaleSettingsTest extends TestCase $this->assertEquals('YYYY-MM-DD HH:mm', $sut->getDateTimePickerFormat()); $this->assertEquals('DD.MM.YYYY HH:mm', $sut->getDateTimePickerFormat('de')); } + + /** + * @expectedException \InvalidArgumentException + * @expectedExceptionMessage Unknown setting for locale en: date_time_picker + */ + public function testUnknownSetting() + { + $sut = $this->getSut('en', ['en' => [ + 'xxx' => 'dd.MM.yyyy HH:mm', + ]]); + $sut->getDateTimePickerFormat('en'); + } } diff --git a/tests/Utils/MarkdownTest.php b/tests/Utils/MarkdownTest.php index 0b85faaf..d65da5ae 100644 --- a/tests/Utils/MarkdownTest.php +++ b/tests/Utils/MarkdownTest.php @@ -14,6 +14,7 @@ use PHPUnit\Framework\TestCase; /** * @covers \App\Utils\Markdown + * @covers \App\Utils\ParsedownExtension */ class MarkdownTest extends TestCase { @@ -51,6 +52,26 @@ sdfsdf [asdfasdf](#test-1) asdfasdf # test aasdfasdf +EOT; + $this->assertEquals($html, $sut->toHtml($markdown)); + } + + public function testDuplicateIds() + { + $sut = new Markdown(); + + $html = <<<'EOT' +

test

+

test

+

test

+

test

+EOT; + + $markdown = <<assertEquals($html, $sut->toHtml($markdown)); } diff --git a/var/docs/configurations.md b/var/docs/configurations.md index 1357c079..508f71e7 100644 --- a/var/docs/configurations.md +++ b/var/docs/configurations.md @@ -1,5 +1,18 @@ # Configurations +This is an introduction into the configuration options and files, which are used by Kimai and an explanantion on how to change them. + +Specific configurations are explained in the detailed feature docs: + +- [Timesheet](timesheet.md) +- [Permissions](permissions.md) +- [Invoice](invoices.md) +- [Calendar](calendar.md) +- [Customer](customer.md) +- [Emails](emails.md) +- [Dashboard widgets](dashboard.md) +- [Theme](theme.md) + ## Environment specific settings (.env) The most basic settings, which need always be adjusted are stored in the `.env` file: @@ -19,20 +32,12 @@ Configuration of Kimai is spread in all files in the `config/`directory but main - `config/packages/kimai.yaml` - Kimai specific settings - `config/packages/admin_lte.yaml` - Kimai base theme - `config/packages/fos_user.yaml` - user management and email settings -- `config/packages/local.yaml` - your local configuration settings +- `config/packages/local.yaml` - your configuration settings (file needs to be created by yourself) There are several other configurations that could potentially be interesting for you in [config/packages/*.yaml](../../config/packages/). If you want to adjust a setting from any of these files, use `local.yaml` (see below). -#### Other topics - -- [Theme settings](theme.md) - in `kimai.yaml` and `admin_lte.yaml` -- [Email configuration](emails.md) - in `swiftmailer.yaml` -- [Dashboard widgets](dashboard.md) - in `kimai.yaml` -- [Calendar](calendar.md) - in `kimai.yaml` -- [Invoices](invoices.md) - in `kimai.yaml` - ## Overwriting local configs (local.yaml) You should NOT edit the file `config/packages/kimai.yaml` directly, as it contains default settings and will be overwritten during an update. @@ -71,202 +76,3 @@ bin/console cache:warmup --env=prod Depending on your setup it might be necessary to execute these commands as webserver user, please read the [UPGRADING guide](../../UPGRADING.md) for more details. - -## Security - -Kimai uses the FOSUserBundle for security related tasks like user management. Its configuration can be found in [fos_user.yaml](../../config/packages/fos_user.yaml). - -### User management emails (fos_user.yaml) - -Read more about [email configuration](emails.md). - -### Remember me login (security.yaml) - -The default period for the `Remember me` option can be changed in the config file [security.yaml](../../config/packages/security.yaml). - -### User registration - -If you want your new users to use [email](emails.md) based activation add this to your `local.yaml`: - -```yaml -fos_user: - registration: - confirmation: - enabled: true -``` - -#### Disable user registration - -If you want to disable the user registration, add this your `local.yaml`: -```yaml -kimai: - user: - registration: false -``` - -If you only want to hide the link from the login form but keep the functionality, add this your `local.yaml`: -```yaml -admin_lte: - routes: - adminlte_registration: ~ -``` - -### Password reset - -If you want to configure the behaviour (like the allowed time between multiple retries) then configure the settings: - -- in `config/packages/fos_user.yaml` the key below `fos_user.registration.resetting` (see [documentation](https://symfony.com/doc/current/bundles/FOSUserBundle/configuration_reference.html)) -- the values `retry_ttl` and `token_ttl` are configured in seconds (7220 = 2 hours) - -#### Disable password reset - -If you want to disable the password reset, add this your `local.yaml`: -```yaml -kimai: - user: - password_reset: false -``` - -If you only want to hide the link from the login form but keep the functionality, add this your `local.yaml`: -```yaml -admin_lte: - routes: - adminlte_password_reset: ~ -``` - -## Timesheets (kimai.yaml) - -### Limit active entries - -To limit the amount of active entries each user can have, the configuration `active_entries` can be changed: - -```yaml -kimai: - timesheet: - active_entries: - soft_limit: 1 - hard_limit: 3 -``` - -The `soft_limit` is used as theme setting (formerly "kimai.theme.active_warning") to display a warning if the user has at least X active recordings. - -The `hard_limit` is used to detect how many active records are allowed per user (by default 3 active time-records are allowed). -If `hard_limit` is 1, the active record is automatically stopped when a new one is started. -When `hard_limit` is greater than 1 and as soon as the limit is reached, the user has to manually stop at least one active -entry (an error message is shown, indicating why it is not possible to start another one). - - -### Descriptions with Markdown - -The description for every timesheet entry can be formatted in two different ways, configured with the `markdown_content` setting. - -- `false` - simple newlines in the description box will be displayed in the frontend as well (default) -- `true` - description will be rendered with a markdown engine, supporting simple lists and other HTML content - -Allowing Markdown in timesheet descriptions is beautiful, but also could be a [security risk](https://github.com/erusev/parsedown/blob/master/README.md#security). -Kimai will only apply the markdown in the user timesheet and not in the admin section as additional security measure. - -### Duration only - -Kimai supports two modes for displaying and recording timesheet entries: - -- `begin` and `end` time (default) -- `date` and `duration` (the so called `duration_only` mode) - -When activating the `duration_only` mode all timesheet tables will only display the `date` and `duration` of all records. -In addition, the "edit timesheet" forms will be changed and instead of displaying the `end` date you will see a field for `duration`. -The `start` date is only visible in these forms when editing an active or starting a new record. - -You can activate the `duration_only` mode by switching the configuration key `kimai.timesheet.duration_only` to `true` in the file [kimai.yaml](../../config/packages/kimai.yaml). - -For supported formats while entering the `duration` please see the [timesheet chapter](timesheet.md) - -### Rounding of begin, end and duration for timesheet records - -Rounding rules are used to round the begin & end dates and the duration for timesheet records. - -1. You can define as many rules as you want ("default" is only an example) -2. Every matching rule will be applied, so be careful with overlapping rules -3. The end_date of timesheet records will be used to match the day (think about entries which are recorded overnight) -4. If you set one of "begin", "end", "duration" to 0 no rounding will be applied for that field and the exact time (including seconds) is used for calculation -5. The values of the rules are minutes (not the minute of an hour), so 5 for "begin" means we round down to the previous multiple of five -6. You can define different rules for different days of the week -7. "begin" will always be rounded to the floor (down) and "end" & "duration" to the ceiling (up) -8. Rounding rules will be applied on stopped timesheet records only, so you might see an un-rounded value for the start time and duration until you stop the record - -You can configure your `rounding` rules by changing the configuration file [kimai.yaml](../../config/packages/kimai.yaml). - -#### Examples - -A simple example to always charge at least 1 hour for weekend work (even if you only worked for 5 minutes) could look like this: - -```yaml -kimai: - timesheet: - rounding: - weekend: - days: ['saturday','sunday'] - begin: 1 - end: 1 - duration: 60 -``` - -A rule which is often used is to round up to a mulitple of 10: - -```yaml -kimai: - timesheet: - rounding: - workdays: - days: ['monday','tuesday','wednesday','thursday','friday','saturday','sunday'] - begin: 10 - end: 10 - duration: 0 -``` - -### Hourly rates for timesheet records - -If you want to apply different hourly rates multiplication `factor` for specific weekdays, you can use this `rates` configuration. - -1. You can define as many rules as you want ("workdays" and "weekend" are only examples) -2. Every matching rule will be applied, so be careful with overlapping rules -3. The end_date of timesheet records will be used to match the day (think about entries which are recorded overnight) -4. "days" is an array of weekdays, where the days need to be written in english and in lowercase -5. "factor" will be used as multiplier for the applied hourly rate -6. Rate rules will be applied on stopped timesheet records only, as it can't be calculated before -7. There is no default rule active, by default the users hourly-rate is used for calculation - -You can configure the `hourly_rate` rules by changing the configuration file [kimai.yaml](../../config/packages/kimai.yaml). - -#### Examples - -1. The "workdays" rule will use the default "hourly rate" for each timesheet entry recorded between "monday" to "friday" as a multiplication with 1 will not change the result -2. The "weekend" rule will add 50% to each timesheet entry that will be recorded on "saturdays" or "sundays" - -```yaml -kimai: - timesheet: - rates: - workdays: - days: ['monday','tuesday','wednesday','thursday','friday'] - factor: 1 - weekend: - days: ['saturday','sunday'] - factor: 1.5 -``` - -## Forms (kimai.yaml) - -You can set some defaults for various forms within Kimai, to easify the creation process. - -### Customer creation - -Define the default values for a customer like this: -```yaml -kimai: - defaults: - customer: - timezone: Europe/London - country: GB - currency: GBP -``` \ No newline at end of file diff --git a/var/docs/customer.md b/var/docs/customer.md new file mode 100644 index 00000000..308bb100 --- /dev/null +++ b/var/docs/customer.md @@ -0,0 +1,17 @@ +# Customer + +Customers in Kimai are used to manage project and activities, which are then used for time-records. + +It is very common to have a _customer_ for your own company, to track times for administration and other internal work. + +## Creating customer + +Define the default values for a customer like this: +```yaml +kimai: + defaults: + customer: + timezone: Europe/London + country: GB + currency: GBP +``` \ No newline at end of file diff --git a/var/docs/faq.md b/var/docs/faq.md index 02d74cfe..f9ec7aa3 100644 --- a/var/docs/faq.md +++ b/var/docs/faq.md @@ -2,10 +2,22 @@ ## I have a problem -Please open a new issue at [GitHub](https://github.com/kevinpapst/kimai2/issues/). -Add the last entries from your logfile at `var/log/prod.log`. +Please check your logfile at `var/log/prod.log`. Many problems reveal themselves after checking it. -## I have only FTP available ... +If that doesn't help, open a new issue at [GitHub](https://github.com/kevinpapst/kimai2/issues/) and we try to find a solution. + +## Changed configs/templates do not load + +Kimai is built on top of Symfony, a framework that optimizes its speed by caching most files. +Therefor, if you are running Kimai in `production`, you have to clear the cache before changes will show up: + +```bash +bin/console cache:clear +``` + +See also the [configurations docs](configurations.md). + +## I have only FTP available So you want to install Kimai v2 but have no SSH access to your server? There is a workaround available, read the additional [installation instructions](installation.md). @@ -26,22 +38,17 @@ used by executing `mysql --version` or by checking the server information e.g. w There is a [discussion in the issue tracker](https://github.com/kevinpapst/kimai2/issues/191) about this topic. -There are two solution for this: - -- Update your MariaDB server to at least 10.2.7 -- Switch to SQLite (that can be changed in your `.env` file) - Further readings: - [MariaDB - JSON support was added with 10.2.7](https://mariadb.com/kb/en/library/json-data-type/) - [Using JSON fields with Doctrine ORM on PostgreSQL & MySQL](https://symfony.fi/entry/using-json-fields-with-doctrine-orm-on-postgresql-mysql) -## Why is SQLite not recommended for production usage +## SQLite not recommended for production usage SQLite is a great database engine for testing, but when it comes to production usage it fails due to several reasons: -- It does not support ALTER TABLE commands and makes update procedures very clunky and problematic/errorsome (we still try to support updates, but they are heavy on large databases) -- It does not support FOREIGN KEY constraints out of the box, which can lead to critical bugs when deleting activities/projects/customers +- It does not support ALTER TABLE commands and makes update procedures very clunky and problematic (we still try to support updates, but they are heavy on large databases) +- It does not support FOREIGN KEY constraints [out of the box](https://www.sqlite.org/foreignkeys.html#fk_enable), which can lead to critical bugs when deleting users/activities/projects/customers ## Dotenv::populate() must be an instance of Symfony\\Component\\Dotenv\\void diff --git a/var/docs/installation.md b/var/docs/installation.md index 73ed4b29..53a55ede 100644 --- a/var/docs/installation.md +++ b/var/docs/installation.md @@ -17,7 +17,7 @@ You need to install Git and [Composer](https://getcomposer.org/doc/00-intro.md) First clone this repo: ```bash -git clone https://github.com/kevinpapst/kimai2.git +git clone -b 0.8 --depth 1 https://github.com/kevinpapst/kimai2.git cd kimai2/ ``` diff --git a/var/docs/internal.md b/var/docs/internal.md index f403855a..88ca727c 100644 --- a/var/docs/internal.md +++ b/var/docs/internal.md @@ -7,7 +7,8 @@ Internal documentation for project maintainers - Prepare a GitHub release-draft - Change .github_changelog_generator config accordingly to new release tag (increase future release) - Change version constants in `src/Constants.php` -- Adjust the file `UPGRADING.md` with version specific information +- Adjust the file `UPGRADING.md` with version specific information +- Change version string in git command in `UPGRADING.md` and `var/docs/installation.md` - Create CHANGELOG.md with [github-changelog-generator](https://github.com/github-changelog-generator/github-changelog-generator]) by running `github_changelog_generator kevinpapst/kimai2` - Edit the release-draft and add the "Full changelog" link + everything from CHANGELOG.md related to the new version - Push a release branch and merge it as last PR into master diff --git a/var/docs/timesheet.md b/var/docs/timesheet.md index 75e2a284..add83764 100644 --- a/var/docs/timesheet.md +++ b/var/docs/timesheet.md @@ -11,8 +11,6 @@ You can start new timesheet records like so: - Click the **redo** button from one of the activities in your timesheet - Start a completely new activity, by clicking the big **play** button in the toolbar -The same goes for running time-records: top them with one-click from the timesheet or from the "running entries" dropdown. - ## Stopping records You can stop timesheet records like so: @@ -22,7 +20,24 @@ You can stop timesheet records like so: ## Duration only mode -When the `duration_only` mode is activated, you will only see the `date` and `duration` fields (see [configurations chapter](configurations.md)). +Kimai supports two modes for displaying and recording timesheet entries: + +- `begin` and `end` time (default) +- `date` and `duration` (the so called `duration_only` mode) + +When activating the `duration_only` mode all timesheet tables will only display the `date` and `duration` of all records. +In addition, the "edit timesheet" forms will be changed and instead of displaying the `end` date you will see a field for `duration`. +The `start` date is only visible in these forms when editing an active or starting a new record. + +You can activate the `duration_only` mode by switching the configuration key `kimai.timesheet.duration_only` to `true` in your `local.yaml`: + +```yaml +kimai: + timesheet: + duration_only: true +``` + +### Duration format The `duration` field supports entering data in the following formats: @@ -34,6 +49,78 @@ The `duration` field supports entering data in the following formats: Please note: if time rounding is activated (which is the default behaviour), then your entered seconds might be removed after submitting the form. +## Limit active entries + +To limit the amount of active entries each user can have, the configuration `active_entries` can be changed: + +```yaml +kimai: + timesheet: + active_entries: + soft_limit: 1 + hard_limit: 3 +``` + +The `soft_limit` is used as theme setting (formerly "kimai.theme.active_warning") to display a warning if the user has at least X active recordings. + +The `hard_limit` is used to detect how many active records are allowed per user (by default 3 active time-records are allowed). +If `hard_limit` is 1, the active record is automatically stopped when a new one is started. +When `hard_limit` is greater than 1 and as soon as the limit is reached, the user has to manually stop at least one active +entry (an error message is shown, indicating why it is not possible to start another one). + +## Descriptions with Markdown + +The description for every timesheet entry can be formatted in two different ways, configured with the `markdown_content` setting. + +- `false` - simple newlines in the description box will be displayed in the frontend as well (default) +- `true` - description will be rendered with a markdown engine, supporting simple lists and other HTML content + +Allowing Markdown in timesheet descriptions is beautiful, but also could be a [security risk](https://github.com/erusev/parsedown/blob/master/README.md#security). +Kimai will only apply the markdown in the user timesheet and not in the admin section as additional security measure. + +## Rounding of begin, end and duration for timesheet records + +Rounding rules are used to round the begin & end dates and the duration for timesheet records. + +1. You can define as many rules as you want ("default" is only an example) +2. Every matching rule will be applied, so be careful with overlapping rules +3. The end_date of timesheet records will be used to match the day (think about entries which are recorded overnight) +4. If you set one of "begin", "end", "duration" to 0 no rounding will be applied for that field and the exact time (including seconds) is used for calculation +5. The values of the rules are minutes (not the minute of an hour), so 5 for "begin" means we round down to the previous multiple of five +6. You can define different rules for different days of the week +7. "begin" will always be rounded to the floor (down) and "end" & "duration" to the ceiling (up) +8. Rounding rules will be applied on stopped timesheet records only, so you might see an un-rounded value for the start time and duration until you stop the record + +You can configure your `rounding` rules by changing the configuration file [kimai.yaml](../../config/packages/kimai.yaml). + +### Examples + +A simple example to always charge at least 1 hour for weekend work (even if you only worked for 5 minutes) could look like this: + +```yaml +kimai: + timesheet: + rounding: + weekend: + days: ['saturday','sunday'] + begin: 1 + end: 1 + duration: 60 +``` + +A rule which is often used is to round up to a mulitple of 10: + +```yaml +kimai: + timesheet: + rounding: + workdays: + days: ['monday','tuesday','wednesday','thursday','friday','saturday','sunday'] + begin: 10 + end: 10 + duration: 0 +``` + ## Rate calculation There are two rate types: @@ -63,5 +150,35 @@ The calculation is based on the following formula: - __Fixed rate__: `$fixedRate` - __Hourly rate__: `$hourlyRate * ($durationInSeconds / 3600) * $factor` -Please see also the configuration chapter about [hourly rates for timesheet records](configurations.md) to see how you -can apply configurable multiplying factors based on day and time. +Please see below to see how you can apply configurable multiplying factors based on day and time. + +### Rate multiplier for specific weekdays + +If you want to apply different hourly rates multiplication `factor` for specific weekdays, you can use this `rates` configuration. + +1. You can define as many rules as you want ("workdays" and "weekend" are only examples) +2. Every matching rule will be applied, so be careful with overlapping rules +3. The end_date of timesheet records will be used to match the day (think about entries which are recorded overnight) +4. "days" is an array of weekdays, where the days need to be written in english and in lowercase +5. "factor" will be used as multiplier for the applied hourly rate +6. Rate rules will be applied on stopped timesheet records only, as it can't be calculated before +7. There is no default rule active, by default the users hourly-rate is used for calculation + +You can configure the `hourly_rate` rules by changing the configuration file [kimai.yaml](../../config/packages/kimai.yaml). + +#### Examples + +1. The "workdays" rule will use the default "hourly rate" for each timesheet entry recorded between "monday" to "friday" as a multiplication with 1 will not change the result +2. The "weekend" rule will add 50% to each timesheet entry that will be recorded on "saturdays" or "sundays" + +```yaml +kimai: + timesheet: + rates: + workdays: + days: ['monday','tuesday','wednesday','thursday','friday'] + factor: 1 + weekend: + days: ['saturday','sunday'] + factor: 1.5 +``` diff --git a/var/docs/updates.md b/var/docs/updates.md index d1aec49b..0791ee64 100644 --- a/var/docs/updates.md +++ b/var/docs/updates.md @@ -16,7 +16,8 @@ personal computer instead of a server. Read the [installation docu](installation Change into your Kimai 2 installation directory, then fetch the latest code and install all dependencies: ```bash -git pull origin master +git fetch --tags +git checkout 0.8 sudo -u www-data composer install --no-dev --optimize-autoloader ``` diff --git a/var/docs/users.md b/var/docs/users.md index 5b0d56c3..5bebfbb3 100644 --- a/var/docs/users.md +++ b/var/docs/users.md @@ -21,6 +21,9 @@ The permission system is configurable through a configuration file. You can find - User can login with username or email - If you activate the `Remember me` option, you can use use the most common functions within the next days without a new login +Kimai uses the FOSUserBundle for security related tasks like user management. +Its configuration can be found in [fos_user.yaml](../../config/packages/fos_user.yaml). + ### Remember me login If you have chosen to login with the `Remember me` option, your login will be extended to one week (default value). @@ -34,13 +37,39 @@ If you are an administrator, you will see all your allowed options in the menu, form when you try to access them. This is a security feature to prevent abuse in case you forgot to logout in public environments. -Read the [configurations chapter](configurations.md) if you want to change the value. +The default period for the `Remember me` option can be changed in the config file [security.yaml](../../config/packages/security.yaml). ## User registration User registration with instant approval is activated by default, so users can register and will be able to login and start time-tracking instantly. -Read the [configurations chapter](configurations.md) if you want to disable the registration or enable email verification. +If you want to disable the registration or enable email verification, read further below under _Configurations_. + +### Email activation +If you want your new users to use [email](emails.md) based activation add this to your `local.yaml`: + +```yaml +fos_user: + registration: + confirmation: + enabled: true +``` + +### Disable user registration + +If you want to disable the user registration, add this your `local.yaml`: +```yaml +kimai: + user: + registration: false +``` + +If you only want to hide the link from the login form but keep the functionality, add this your `local.yaml`: +```yaml +admin_lte: + routes: + adminlte_registration: ~ +``` ## Password reset @@ -56,4 +85,19 @@ If you want to configure the behaviour (like the allowed time between multiple r - in `config/packages/fos_user.yaml` the key below `fos_user.registration.resetting` (see [documentation](https://symfony.com/doc/current/bundles/FOSUserBundle/configuration_reference.html)) - the values `retry_ttl` and `token_ttl` are configured in seconds (7220 = 2 hours) -Read the [configurations chapter](configurations.md) if you want to reload the changed configuration files. +### Disable password reset + +If you want to disable the password reset, add this your `local.yaml`: +```yaml +kimai: + user: + password_reset: false +``` + +If you only want to hide the link from the login form but keep the functionality, add this your `local.yaml`: +```yaml +admin_lte: + routes: + adminlte_password_reset: ~ +``` +