From e846da366156fa2663a0ea2ee2d3bd7c432bfc36 Mon Sep 17 00:00:00 2001 From: Kevin Papst Date: Wed, 19 Jun 2019 18:54:13 +0200 Subject: [PATCH 1/6] added custom field docu and examples --- _documentation/developers.md | 174 ++++++++++++++++++++++++++++++++--- 1 file changed, 161 insertions(+), 13 deletions(-) diff --git a/_documentation/developers.md b/_documentation/developers.md index ce55b6ce2..6ab19f5c4 100644 --- a/_documentation/developers.md +++ b/_documentation/developers.md @@ -43,10 +43,7 @@ To rebuild all assets you have to execute: yarn run prod ``` -You can find more information at: - -- https://symfony.com/doc/current/frontend/encore/installation.html -- https://symfony.com/doc/current/frontend.html +You can find more information [here](https://symfony.com/doc/current/frontend/encore/installation.html) and [here](https://symfony.com/doc/current/frontend.html). ## local.yaml @@ -69,30 +66,26 @@ composer kimai:tests-unit composer kimai:tests-integration ``` -Or you simply run all tests with: -```bash -composer kimai:tests -vendor/bin/phpunit -``` +Or you simply run all tests with one of: +- `composer kimai:tests` +- `vendor/bin/phpunit` ## Static code analysis via PHPStan Besides automated tests Kimai relies on PHPStan to detect code problems. -You can run the checks before CI process kicks in via: - ```bash composer kimai:phpstan ``` ## Coding styles -You can run the code formatter with the built-in command like that: +You can run the code sniffer with the built-in command like that: ```bash composer kimai:codestyle ``` -You can also automatically fix the violations by running: +And you can also automatically fix the violations by running: ```bash composer kimai:codestyle-fix @@ -271,3 +264,158 @@ You can apply several rules in your config file [local.yaml]({% link _documentat The configuration for "rounding rules" can be fetched from the container parameter `kimai.timesheet.rounding`. The configuration for "hourly-rates multiplication factors" can be fetched from the container parameter `kimai.timesheet.rates`. + +## Adding custom fields +{% include new_since.html version="1.0" %} + +Kimai supports custom fields for literally every object: +- `User` via `UserPreference` +- `Timesheet` via `TimesheetMeta` +- `Customer` via `CustomerMeta` +- `Project` via `ProjectMeta` +- `Activity` via `ActivityMeta` + +Using the fields for internal reasons (eg. importing and linking to IDs of external apps) is simple. +You can add these fields programmatically at any time: +```php +$externalId = (new TimesheetMeta())->setName('externalID')->setValue(1); +$timesheet = new Timesheet(); +$timesheet->setMetaField($externalId); +``` + +But what if you want the field to be editable by users? + +Well, this is possible through the registration via an EventSubscriber, where you add your custom fields. + +### UserPreference + +Adding a new user preference is simple as that: + +```php +use App\Entity\UserPreference; +use App\Event\UserPreferenceEvent; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Form\Extension\Core\Type\CheckboxType; + +class UserProfileSubscriber implements EventSubscriberInterface +{ + public static function getSubscribedEvents(): array + { + return [ + UserPreferenceEvent::CONFIGURE => ['loadUserPreferences', 200] + ]; + } + + public function loadUserPreferences(UserPreferenceEvent $event) + { + if (null === ($user = $event->getUser())) { + return; + } + + // You attach every field to the event and all the heavy lifting is done by Kimai. + // The value is the default as long as the user has not yet updated his preferences, + // otherwise it will be overwritten with the users choice, stored in the database. + $event->addUserPreference( + (new UserPreference()) + ->setName('fooooo-bar') + ->setValue(false) + ->setType(CheckboxType::class) + ); + } +} +``` + +### Custom field for other entities + +The following example adds a custom fields to each entity type: +- `Timesheet` via `TimesheetMeta` +- `Customer` via `CustomerMeta` +- `Project` via `ProjectMeta` +- `Activity` via `ActivityMeta` + +This example might seem a little awkward first, as I wanted to add only one example for all +possible entity types and that definitely doesn't make the code prettier ;-) +But I hope you get the point and see in `prepareEntity` what needs to be done to setup new +custom fields, which can be edited by the user. + +```php +use App\Entity\ActivityMeta; +use App\Entity\CustomerMeta; +use App\Entity\EntityWithMetaFields; +use App\Entity\MetaTableTypeInterface; +use App\Entity\ProjectMeta; +use App\Entity\TimesheetMeta; +use App\Event\ActivityMetaDefinitionEvent; +use App\Event\CustomerMetaDefinitionEvent; +use App\Event\ProjectMetaDefinitionEvent; +use App\Event\TimesheetMetaDefinitionEvent; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Validator\Constraints\Length; + +class MetaFieldSubscriber implements EventSubscriberInterface +{ + public static function getSubscribedEvents(): array + { + return [ + TimesheetMetaDefinitionEvent::class => ['loadTimesheetMeta', 200], + CustomerMetaDefinitionEvent::class => ['loadCustomerMeta', 200], + ProjectMetaDefinitionEvent::class => ['loadProjectMeta', 200], + ActivityMetaDefinitionEvent::class => ['loadActivityMeta', 200], + ]; + } + + public function loadTimesheetMeta(TimesheetMetaDefinitionEvent $event) + { + $this->prepareEntity($event->getEntity(), new TimesheetMeta()); + } + + public function loadCustomerMeta(CustomerMetaDefinitionEvent $event) + { + $this->prepareEntity($event->getEntity(), new CustomerMeta()); + } + + public function loadProjectMeta(ProjectMetaDefinitionEvent $event) + { + $this->prepareEntity($event->getEntity(), new ProjectMeta()); + } + + public function loadActivityMeta(ActivityMetaDefinitionEvent $event) + { + $this->prepareEntity($event->getEntity(), new ActivityMeta()); + } + + private function prepareLocationField(MetaTableTypeInterface $entity) + { + return $entity + ->setName('location') + ->setType(TextType::class) + ->addConstraint(new Length(['max' => 100])) + ->setIsPublicVisible(false); + } + + private function prepareEntity(EntityWithMetaFields $entity, MetaTableTypeInterface $meta) + { + $definition = $this->prepareLocationField($meta); + $current = $entity->getMetaField($definition->getName()); + + // Compared with UserPreferences, the handling of custom fields needs a little bit + // setup logic. That allows you, to create arbitrary complicated field setups, + // eg. adding fields only to timesheets for a certain customer. + + // If the field is not yet existing, you can simply attach the new definition + if (null === $current) { + $entity->setMetaField($definition); + return; + } + + // But if the field already exist in the entity, you have to merge + // the form definition with the existing field data + $current + ->setType($definition->getType()) + ->setConstraints($definition->getConstraints()) + ->setIsRequired($definition->isRequired()) + ->setIsPublicVisible($definition->isPublicVisible()); + } +} +``` From 8344b0cd26f8151ca3d418b0e364341b573c2453 Mon Sep 17 00:00:00 2001 From: Kevin Papst Date: Thu, 20 Jun 2019 13:11:48 +0200 Subject: [PATCH 2/6] latest API --- _documentation/developers.md | 32 +++++--------------------------- 1 file changed, 5 insertions(+), 27 deletions(-) diff --git a/_documentation/developers.md b/_documentation/developers.md index 6ab19f5c4..ef9bdc851 100644 --- a/_documentation/developers.md +++ b/_documentation/developers.md @@ -385,37 +385,15 @@ class MetaFieldSubscriber implements EventSubscriberInterface $this->prepareEntity($event->getEntity(), new ActivityMeta()); } - private function prepareLocationField(MetaTableTypeInterface $entity) + private function prepareEntity(EntityWithMetaFields $entity, MetaTableTypeInterface $definition) { - return $entity - ->setName('location') + $definition + ->setName('location2') ->setType(TextType::class) - ->addConstraint(new Length(['max' => 100])) + ->addConstraint(new Length(['max' => 255])) ->setIsPublicVisible(false); - } - - private function prepareEntity(EntityWithMetaFields $entity, MetaTableTypeInterface $meta) - { - $definition = $this->prepareLocationField($meta); - $current = $entity->getMetaField($definition->getName()); - - // Compared with UserPreferences, the handling of custom fields needs a little bit - // setup logic. That allows you, to create arbitrary complicated field setups, - // eg. adding fields only to timesheets for a certain customer. - - // If the field is not yet existing, you can simply attach the new definition - if (null === $current) { - $entity->setMetaField($definition); - return; - } - // But if the field already exist in the entity, you have to merge - // the form definition with the existing field data - $current - ->setType($definition->getType()) - ->setConstraints($definition->getConstraints()) - ->setIsRequired($definition->isRequired()) - ->setIsPublicVisible($definition->isPublicVisible()); + $entity->setMetaField($definition); } } ``` From 6664f087b17b58c21521e774c60faa02a44362ca Mon Sep 17 00:00:00 2001 From: Kevin Papst Date: Sat, 22 Jun 2019 14:52:14 +0200 Subject: [PATCH 3/6] new page for meta fields --- _data/menu-documentation.yml | 2 +- _documentation/developers.md | 144 ++-------------------------------- _documentation/meta-fields.md | 101 ++++++++++++++++++++++++ 3 files changed, 110 insertions(+), 137 deletions(-) create mode 100644 _documentation/meta-fields.md diff --git a/_data/menu-documentation.yml b/_data/menu-documentation.yml index 9d59f1562..f8aa79b37 100644 --- a/_data/menu-documentation.yml +++ b/_data/menu-documentation.yml @@ -1,7 +1,7 @@ - title: Administration pages: [installation, updates, docker, backups] - title: User manual - pages: [timesheet, invoices, export, users, customer, project, activity, tags] + pages: [timesheet, invoices, export, users, customer, project, activity, tags, meta-fields] - title: Configuration pages: [configurations, user-preferences, calendar, dashboard, emails, permissions, ldap] - title: Developer diff --git a/_documentation/developers.md b/_documentation/developers.md index ef9bdc851..531be465b 100644 --- a/_documentation/developers.md +++ b/_documentation/developers.md @@ -209,14 +209,14 @@ In the config `kimai.invoice.documents`, you can add a list of directories with ### Adding invoice calculator An invoice calculator is a class implementing `App\Invoice\CalculatorInterface` and it is responsible for calculating -invoice rates, taxes and taking care of all timesheet entries that should be displayed. +invoice rates, taxes and taking care to aggregate all timesheet entries that should be displayed. Every invoice calculator class will be automatically available, after refreshing the application cache with `bin/console cache:clear`. This "magic" happens in the [InvoiceServiceCompilerPass]({{ site.kimai_v2_file }}/src/DependencyInjection/Compiler/InvoiceServiceCompilerPass.php), which finds the classes by the interface `CalculatorInterface`. The ID of the calculator must be unique, please prefix it with your vendor or bundle name and make sure it only contains -character as it will be stored in a database column. +alpha-numeric characters, as it will be stored in a database column. Translations are stored in the `invoice-calculator.xx.xliff`. @@ -230,7 +230,7 @@ This "magic" happens in the [InvoiceServiceCompilerPass]({{ site.kimai_v2_file } which finds the classes by the interface `NumberGeneratorInterface`. The ID of the number generator must be unique, please prefix it with your vendor or bundle name and make sure it only contains -character as it will be stored in a database column. +alpha-numeric characters, as it will be stored in a database column. Translations are stored in the `invoice-numbergenerator.xx.xliff`. @@ -245,10 +245,7 @@ which finds the classes by the interface `RendererInterface`. ## Adding export renderer -An export renderer is a class implementing `App\Export\RendererInterface` and it is responsible to convert ar array of `Timesheet` objects -into a downloadable/printable document. - -Every export renderer class will be automatically available when refreshing the application cache by the [ExportServiceCompilerPass]({{ site.kimai_v2_file }}/src/DependencyInjection/Compiler/ExportServiceCompilerPass.php): +See [export]({% link _documentation/export.md %}) documentation. ## Adding timesheet calculator @@ -265,135 +262,10 @@ The configuration for "rounding rules" can be fetched from the container paramet The configuration for "hourly-rates multiplication factors" can be fetched from the container parameter `kimai.timesheet.rates`. -## Adding custom fields -{% include new_since.html version="1.0" %} - -Kimai supports custom fields for literally every object: -- `User` via `UserPreference` -- `Timesheet` via `TimesheetMeta` -- `Customer` via `CustomerMeta` -- `Project` via `ProjectMeta` -- `Activity` via `ActivityMeta` - -Using the fields for internal reasons (eg. importing and linking to IDs of external apps) is simple. -You can add these fields programmatically at any time: -```php -$externalId = (new TimesheetMeta())->setName('externalID')->setValue(1); -$timesheet = new Timesheet(); -$timesheet->setMetaField($externalId); -``` - -But what if you want the field to be editable by users? - -Well, this is possible through the registration via an EventSubscriber, where you add your custom fields. - -### UserPreference - -Adding a new user preference is simple as that: - -```php -use App\Entity\UserPreference; -use App\Event\UserPreferenceEvent; -use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\Form\Extension\Core\Type\CheckboxType; - -class UserProfileSubscriber implements EventSubscriberInterface -{ - public static function getSubscribedEvents(): array - { - return [ - UserPreferenceEvent::CONFIGURE => ['loadUserPreferences', 200] - ]; - } - - public function loadUserPreferences(UserPreferenceEvent $event) - { - if (null === ($user = $event->getUser())) { - return; - } - - // You attach every field to the event and all the heavy lifting is done by Kimai. - // The value is the default as long as the user has not yet updated his preferences, - // otherwise it will be overwritten with the users choice, stored in the database. - $event->addUserPreference( - (new UserPreference()) - ->setName('fooooo-bar') - ->setValue(false) - ->setType(CheckboxType::class) - ); - } -} -``` - -### Custom field for other entities - -The following example adds a custom fields to each entity type: -- `Timesheet` via `TimesheetMeta` -- `Customer` via `CustomerMeta` -- `Project` via `ProjectMeta` -- `Activity` via `ActivityMeta` +## Adding custom fields (meta fields) -This example might seem a little awkward first, as I wanted to add only one example for all -possible entity types and that definitely doesn't make the code prettier ;-) -But I hope you get the point and see in `prepareEntity` what needs to be done to setup new -custom fields, which can be edited by the user. +See [meta fields]({% link _documentation/meta-fields.md %}) documentation. -```php -use App\Entity\ActivityMeta; -use App\Entity\CustomerMeta; -use App\Entity\EntityWithMetaFields; -use App\Entity\MetaTableTypeInterface; -use App\Entity\ProjectMeta; -use App\Entity\TimesheetMeta; -use App\Event\ActivityMetaDefinitionEvent; -use App\Event\CustomerMetaDefinitionEvent; -use App\Event\ProjectMetaDefinitionEvent; -use App\Event\TimesheetMetaDefinitionEvent; -use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\Form\Extension\Core\Type\TextType; -use Symfony\Component\Validator\Constraints\Length; - -class MetaFieldSubscriber implements EventSubscriberInterface -{ - public static function getSubscribedEvents(): array - { - return [ - TimesheetMetaDefinitionEvent::class => ['loadTimesheetMeta', 200], - CustomerMetaDefinitionEvent::class => ['loadCustomerMeta', 200], - ProjectMetaDefinitionEvent::class => ['loadProjectMeta', 200], - ActivityMetaDefinitionEvent::class => ['loadActivityMeta', 200], - ]; - } - - public function loadTimesheetMeta(TimesheetMetaDefinitionEvent $event) - { - $this->prepareEntity($event->getEntity(), new TimesheetMeta()); - } - - public function loadCustomerMeta(CustomerMetaDefinitionEvent $event) - { - $this->prepareEntity($event->getEntity(), new CustomerMeta()); - } +## Adding UserPreference - public function loadProjectMeta(ProjectMetaDefinitionEvent $event) - { - $this->prepareEntity($event->getEntity(), new ProjectMeta()); - } - - public function loadActivityMeta(ActivityMetaDefinitionEvent $event) - { - $this->prepareEntity($event->getEntity(), new ActivityMeta()); - } - - private function prepareEntity(EntityWithMetaFields $entity, MetaTableTypeInterface $definition) - { - $definition - ->setName('location2') - ->setType(TextType::class) - ->addConstraint(new Length(['max' => 255])) - ->setIsPublicVisible(false); - - $entity->setMetaField($definition); - } -} -``` +See [user preferences]({% link _documentation/user-preferences.md %}) documentation. diff --git a/_documentation/meta-fields.md b/_documentation/meta-fields.md new file mode 100644 index 000000000..a6bc1ba9e --- /dev/null +++ b/_documentation/meta-fields.md @@ -0,0 +1,101 @@ +--- +title: Custom fields +description: Use your own custom/meta fields +toc: true +since_version: 1.0 +--- + +Kimai supports custom fields for the following object types: + +- `User` via `UserPreference` (see [user preferences]({% link _documentation/user-preferences.md %})) +- `Timesheet` via `TimesheetMeta` +- `Customer` via `CustomerMeta` +- `Project` via `ProjectMeta` +- `Activity` via `ActivityMeta` + +## Custom fields + +Using the fields for internal reasons (eg. importing and linking to IDs of external apps) is simple. +You can add these fields programmatically at any time: +```php +$externalId = (new TimesheetMeta())->setName('externalID')->setValue(1); +$timesheet = new Timesheet(); +$timesheet->setMetaField($externalId); +``` + +But what if you want the field to be editable by users? + +Well, this is possible through the registration via an EventSubscriber, where you add your custom fields. + +## Add editable custom fields + +The following example adds a custom fields to each entity types "edit" and "create" forms: +- `Timesheet` via `TimesheetMeta` +- `Customer` via `CustomerMeta` +- `Project` via `ProjectMeta` +- `Activity` via `ActivityMeta` + +This example might seem a little awkward first, as I wanted to add only one example for all +possible entity types and that definitely doesn't make the code prettier ;-) +But I hope you get the point and see in `prepareEntity` what needs to be done to setup new +custom fields, which can be edited by the user. + +```php +use App\Entity\ActivityMeta; +use App\Entity\CustomerMeta; +use App\Entity\EntityWithMetaFields; +use App\Entity\MetaTableTypeInterface; +use App\Entity\ProjectMeta; +use App\Entity\TimesheetMeta; +use App\Event\ActivityMetaDefinitionEvent; +use App\Event\CustomerMetaDefinitionEvent; +use App\Event\ProjectMetaDefinitionEvent; +use App\Event\TimesheetMetaDefinitionEvent; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Form\Extension\Core\Type\TextType; +use Symfony\Component\Validator\Constraints\Length; + +class MetaFieldSubscriber implements EventSubscriberInterface +{ + public static function getSubscribedEvents(): array + { + return [ + TimesheetMetaDefinitionEvent::class => ['loadTimesheetMeta', 200], + CustomerMetaDefinitionEvent::class => ['loadCustomerMeta', 200], + ProjectMetaDefinitionEvent::class => ['loadProjectMeta', 200], + ActivityMetaDefinitionEvent::class => ['loadActivityMeta', 200], + ]; + } + + public function loadTimesheetMeta(TimesheetMetaDefinitionEvent $event) + { + $this->prepareEntity($event->getEntity(), new TimesheetMeta()); + } + + public function loadCustomerMeta(CustomerMetaDefinitionEvent $event) + { + $this->prepareEntity($event->getEntity(), new CustomerMeta()); + } + + public function loadProjectMeta(ProjectMetaDefinitionEvent $event) + { + $this->prepareEntity($event->getEntity(), new ProjectMeta()); + } + + public function loadActivityMeta(ActivityMetaDefinitionEvent $event) + { + $this->prepareEntity($event->getEntity(), new ActivityMeta()); + } + + private function prepareEntity(EntityWithMetaFields $entity, MetaTableTypeInterface $definition) + { + $definition + ->setName('Location') + ->setType(TextType::class) + ->addConstraint(new Length(['max' => 255])) + ->setIsVisible(true); + + $entity->setMetaField($definition); + } +} +``` From ed10839467a6fee4991576cfea39456a29984fed Mon Sep 17 00:00:00 2001 From: Kevin Papst Date: Sat, 22 Jun 2019 14:52:42 +0200 Subject: [PATCH 4/6] added example for export renderer --- _documentation/export.md | 52 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/_documentation/export.md b/_documentation/export.md index 513e48878..cd58482cf 100644 --- a/_documentation/export.md +++ b/_documentation/export.md @@ -2,6 +2,7 @@ title: Export description: Export your timesheet data with Kimai into several different formats since_version: 0.8 +toc: true --- The export module allows you to export filtered timesheet data into several formats. @@ -26,3 +27,54 @@ So all customer, projects, activities, all hourly rates, the personal time worke Exported records cannot be edited or deleted any longer. For further information read the [timesheet documentation]({% link _documentation/timesheet.md %}). + +## Adding export renderer + +An export renderer is a class implementing `App\Export\RendererInterface` and it is responsible to convert an array of +`Timesheet` objects into a downloadable/printable document. + +Every export renderer class will be automatically available when refreshing the application cache, thanks to the +[ExportServiceCompilerPass]({{ site.kimai_v2_file }}/src/DependencyInjection/Compiler/ExportServiceCompilerPass.php). + +Each renderer is represented by a "button" below the datatable on the export screen. + +A simple example, which only shows the IDs of the included timesheet records could look like this: + +```php +use App\Entity\Timesheet; +use App\Export\RendererInterface; +use App\Repository\Query\TimesheetQuery; +use Symfony\Component\HttpFoundation\Response; + +final class TimesheetIdRenderer implements RendererInterface +{ + public function render(array $timesheets, TimesheetQuery $query): Response + { + $ids = array_map(function(Timesheet $timesheet) { + return $timesheet->getId(); + }, $timesheets); + + $response = new Response(); + $response->setContent(sprintf('Included IDs: %s', implode(', ', $ids))); + + return $response; + } + + public function getId(): string + { + return 'ext_array_dump'; + } + + public function getIcon(): string + { + return 'fas fa-file-code'; + } + + public function getTitle(): string + { + return 'Show IDs'; + } +} +``` + +All you need to do is to register it as a service in the Symfony DI container. From eea555342fa231ecfee420afd6bf78a50e2c6c47 Mon Sep 17 00:00:00 2001 From: Kevin Papst Date: Sat, 22 Jun 2019 14:53:07 +0200 Subject: [PATCH 5/6] added example for user preference --- _documentation/timesheet.md | 4 ++-- _documentation/user-preferences.md | 38 ++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/_documentation/timesheet.md b/_documentation/timesheet.md index 12335e2c1..4f18379a0 100644 --- a/_documentation/timesheet.md +++ b/_documentation/timesheet.md @@ -246,5 +246,5 @@ kimai: Exported records will be locked to prevent manipulation of cleared data. -There is the permission `edit_exported_timesheet` which allows to edit and delete these locked entries nevertheless, -which by default is given to users with `ROLE_ADMIN` and `ROLE_SUPER_ADMIN`. +The [permission]({% link _documentation/permissions.md %}) `edit_exported_timesheet` does allow to edit and delete these +locked entries nevertheless, which by default is given to users with `ROLE_ADMIN` and `ROLE_SUPER_ADMIN`. diff --git a/_documentation/user-preferences.md b/_documentation/user-preferences.md index afd414ccd..a4b5b34fd 100644 --- a/_documentation/user-preferences.md +++ b/_documentation/user-preferences.md @@ -71,3 +71,41 @@ on mobile devices will be replaced by a link to the calendar. ## Show daily statistics in timesheet If activated, the personal timesheet visually groups and shows statistics for all records within one day. + +## Adding new UserPreference + +Developers can register new user preferences from within [their plugin]({% link _documentation/plugins.md %}) as easy as that: + +```php +use App\Entity\UserPreference; +use App\Event\UserPreferenceEvent; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\Form\Extension\Core\Type\CheckboxType; + +class UserProfileSubscriber implements EventSubscriberInterface +{ + public static function getSubscribedEvents(): array + { + return [ + UserPreferenceEvent::CONFIGURE => ['loadUserPreferences', 200] + ]; + } + + public function loadUserPreferences(UserPreferenceEvent $event) + { + if (null === ($user = $event->getUser())) { + return; + } + + // You attach every field to the event and all the heavy lifting is done by Kimai. + // The value is the default as long as the user has not yet updated his preferences, + // otherwise it will be overwritten with the users choice, stored in the database. + $event->addUserPreference( + (new UserPreference()) + ->setName('fooooo-bar') + ->setValue(false) + ->setType(CheckboxType::class) + ); + } +} +``` From f30257f5afd5d2b880e9aed9ccda881db903bd5f Mon Sep 17 00:00:00 2001 From: Kevin Papst Date: Sun, 23 Jun 2019 13:12:47 +0200 Subject: [PATCH 6/6] added invoice and visibility docu --- _documentation/invoices.md | 12 ++++++++++++ _documentation/meta-fields.md | 15 +++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/_documentation/invoices.md b/_documentation/invoices.md index 1ddecfaea..cbd277438 100644 --- a/_documentation/invoices.md +++ b/_documentation/invoices.md @@ -147,6 +147,7 @@ If a customer was selected the following values exist as well: | ${customer.country} | The customer country | | ${customer.homepage} | The customer homepage | | ${customer.comment} | The customer comment | +| ${customer.meta.X} | The customer [meta field]({% link _documentation/meta-fields.md %}) named `X` (if visible) | If a project was selected the following values exist as well: @@ -156,6 +157,16 @@ If a project was selected the following values exist as well: | ${project.name} | The project name | | ${project.comment} | The project name | | ${project.order_number} | The project order number | +| ${project.meta.X} | The project [meta field]({% link _documentation/meta-fields.md %}) named `X` (if visible) | + +If an activity was selected the following values exist as well: + +| Key | Description | +|---|---| +| ${activity.id} | The activity ID | +| ${activity.name} | The activity name | +| ${activity.comment} | The activity name | +| ${activity.meta.X} | The activity [meta field]({% link _documentation/meta-fields.md %}) named `X` (if visible) | ### Timesheet entry variables @@ -187,6 +198,7 @@ For each timesheet entry you can use the variables from the following table. | ${entry.project_id} | Project ID | 10 | | ${entry.customer} | Customer name | Acme Studios | | ${entry.customer_id} | Customer ID | 3 | +| ${entry.meta.X} | The [meta field]({% link _documentation/meta-fields.md %}) named `X` (if visible) | ## Configure search path diff --git a/_documentation/meta-fields.md b/_documentation/meta-fields.md index a6bc1ba9e..fd7d0afe2 100644 --- a/_documentation/meta-fields.md +++ b/_documentation/meta-fields.md @@ -99,3 +99,18 @@ class MetaFieldSubscriber implements EventSubscriberInterface } } ``` + +## Visibility + +Each meta field has its own visibility, which determines whether the field will be exposed +in the following places: + +- Export +- Invoice +- API + +The default visibility is `false` (hidden). If you want to use the meta fields value +in your invoices, then you have to set its visibility to true (see EventSubscriber example above). + +Be aware: the visibility is stored with the meta field, so changing its value via the EventSubscriber +does NOT change the visibility of already saved meta fields, just for new ones.