This documentation covers some required steps to migrate your plugin to the upcoming Kimai 2.x.
I started this guide more than 8 months after I started the migration, so there are for sure steps missing. If you encounter any issue, please post them at GitHub and we will adjust this documentation together.
What needs to be done?
var/plugins/
Required code adjustments:
.
anymore (you might need migrations to adapt existing ones, please replace with underscore _
!)date_full
=> date_time
duration_decimal
=> duration(true)
currency
=> currency_name
country
=> country_name
language
=> language_name
createForm()
to createFormForGetRequest()
for toolbar formsactiveEntriesSoftLimit
config/i18n
/api/version
teamlead
and users
- access members
instead/timesheets/
collection - replaced comma separated IDs with arrays[] (customers, projects, activities, tags), throws Exception on unknown IDs/activities/
collection - replaced comma separated IDs with arrays[] (projects), throws Exception on unknown IDs/projects/
collection - replaced comma separated IDs with arrays[] (customers), throws Exception on unknown IDsBelow follows a list of how I did migrate my own plugins.
A big difference between 1.x and 2.x is that Kimai now checks the minimum requirement of a plugin and rejects
reloading the cache if those do not align. The check is done by comparing Constants::VERSION_ID
and the key at extra.key.require
,
which needs to be formatted as integer instead of a string (like 1.20.2
in 1.x releases):
"extra": {
"kimai": {
"require": 20000,
"name": "Some plugin"
}
},
Change includes, mostly from {% embed '@AdminLTE/Widgets/box-widget.html.twig' %}
to {% embed '@theme/embeds/card.html.twig' %}
.
Replaces css classes
no-padding
to p-0
text-left
with text-start
text-right
with text-end
composer require --dev rector/rector
rector.php
at the root of the library with the following contents:<?php
use Rector\Config\RectorConfig;
use Rector\Php80\Rector\Class_\AnnotationToAttributeRector;
use Rector\Php80\ValueObject\AnnotationToAttribute;
return function (RectorConfig $rectorConfig): void {
$rectorConfig->paths([
__DIR__ . '/tests/',
__DIR__ . '/src/',
__DIR__ . '/var/plugins/AuditTrailBundle/',
__DIR__ . '/var/plugins/CustomContentBundle/',
__DIR__ . '/var/plugins/CustomCSSBundle/',
__DIR__ . '/var/plugins/DeductionTimeBundle/',
__DIR__ . '/var/plugins/DemoBundle/',
__DIR__ . '/var/plugins/ExpensesBundle/',
__DIR__ . '/var/plugins/Fail2BanBundle/',
__DIR__ . '/var/plugins/ImportBundle/',
__DIR__ . '/var/plugins/InvoiceBundle/',
__DIR__ . '/var/plugins/KioskBundle/',
__DIR__ . '/var/plugins/LockdownPerUserBundle/',
__DIR__ . '/var/plugins/MetaFieldsBundle/',
__DIR__ . '/var/plugins/TaskManagementBundle/',
__DIR__ . '/var/plugins/TranslationBundle/',
]);
$rectorConfig->ruleWithConfiguration(AnnotationToAttributeRector::class, [
// Annotations from Kimai core
new AnnotationToAttribute('App\\Export\\Annotation\\Expose'),
new AnnotationToAttribute('App\\Export\\Annotation\\Order'),
// For custom APIs
new AnnotationToAttribute('Nelmio\\ApiDocBundle\\Annotation\\Security'),
new AnnotationToAttribute('FOS\\RestBundle\\Controller\\Annotations\\Post'),
new AnnotationToAttribute('FOS\\RestBundle\\Controller\\Annotations\\Get'),
new AnnotationToAttribute('FOS\\RestBundle\\Controller\\Annotations\\Patch'),
new AnnotationToAttribute('FOS\\RestBundle\\Controller\\Annotations\\Delete'),
new AnnotationToAttribute('FOS\\RestBundle\\Controller\\Annotations\\QueryParam'),
new AnnotationToAttribute('FOS\\RestBundle\\Controller\\Annotations\\RequestParam'),
new AnnotationToAttribute('OpenApi\\Annotations\\AdditionalProperties', 'OpenApi\\Attributes\\AdditionalProperties'),
new AnnotationToAttribute('OpenApi\\Annotations\\Attachable', 'OpenApi\\Attributes\\Attachable'),
new AnnotationToAttribute('OpenApi\\Annotations\\Components', 'OpenApi\\Attributes\\Components'),
new AnnotationToAttribute('OpenApi\\Annotations\\Contact', 'OpenApi\\Attributes\\Contact'),
new AnnotationToAttribute('OpenApi\\Annotations\\Delete', 'OpenApi\\Attributes\\Delete'),
new AnnotationToAttribute('OpenApi\\Annotations\\Discriminator', 'OpenApi\\Attributes\\Discriminator'),
new AnnotationToAttribute('OpenApi\\Annotations\\Examples', 'OpenApi\\Attributes\\Examples'),
new AnnotationToAttribute('OpenApi\\Annotations\\ExternalDocumentation', 'OpenApi\\Attributes\\ExternalDocumentation'),
new AnnotationToAttribute('OpenApi\\Annotations\\Flow', 'OpenApi\\Attributes\\Flow'),
new AnnotationToAttribute('OpenApi\\Annotations\\Get', 'OpenApi\\Attributes\\Get'),
new AnnotationToAttribute('OpenApi\\Annotations\\Head', 'OpenApi\\Attributes\\Head'),
new AnnotationToAttribute('OpenApi\\Annotations\\Header', 'OpenApi\\Attributes\\Header'),
new AnnotationToAttribute('OpenApi\\Annotations\\Info', 'OpenApi\\Attributes\\Info'),
new AnnotationToAttribute('OpenApi\\Annotations\\Items', 'OpenApi\\Attributes\\Items'),
new AnnotationToAttribute('OpenApi\\Annotations\\JsonContent', 'OpenApi\\Attributes\\JsonContent'),
new AnnotationToAttribute('OpenApi\\Annotations\\License', 'OpenApi\\Attributes\\License'),
new AnnotationToAttribute('OpenApi\\Annotations\\Link', 'OpenApi\\Attributes\\Link'),
new AnnotationToAttribute('OpenApi\\Annotations\\MediaType', 'OpenApi\\Attributes\\MediaType'),
new AnnotationToAttribute('OpenApi\\Annotations\\OpenApi', 'OpenApi\\Attributes\\OpenApi'),
new AnnotationToAttribute('OpenApi\\Annotations\\Operation', 'OpenApi\\Attributes\\Operation'),
new AnnotationToAttribute('OpenApi\\Annotations\\Options', 'OpenApi\\Attributes\\Options'),
new AnnotationToAttribute('OpenApi\\Annotations\\Parameter', 'OpenApi\\Attributes\\Parameter'),
new AnnotationToAttribute('OpenApi\\Annotations\\Patch', 'OpenApi\\Attributes\\Patch'),
new AnnotationToAttribute('OpenApi\\Annotations\\PatchItem', 'OpenApi\\Attributes\\PatchItem'),
new AnnotationToAttribute('OpenApi\\Annotations\\PathParameter', 'OpenApi\\Attributes\\PathParameter'),
new AnnotationToAttribute('OpenApi\\Annotations\\Post', 'OpenApi\\Attributes\\Post'),
new AnnotationToAttribute('OpenApi\\Annotations\\Property', 'OpenApi\\Attributes\\Property'),
new AnnotationToAttribute('OpenApi\\Annotations\\Put', 'OpenApi\\Attributes\\Put'),
new AnnotationToAttribute('OpenApi\\Annotations\\RequestBody', 'OpenApi\\Attributes\\RequestBody'),
new AnnotationToAttribute('OpenApi\\Annotations\\Response', 'OpenApi\\Attributes\\Response'),
new AnnotationToAttribute('OpenApi\\Annotations\\Schema', 'OpenApi\\Attributes\\Schema'),
new AnnotationToAttribute('OpenApi\\Annotations\\SecurityScheme', 'OpenApi\\Attributes\\SecurityScheme'),
new AnnotationToAttribute('OpenApi\\Annotations\\Server', 'OpenApi\\Attributes\\Server'),
new AnnotationToAttribute('OpenApi\\Annotations\\ServerVariable', 'OpenApi\\Attributes\\ServerVariable'),
new AnnotationToAttribute('OpenApi\\Annotations\\Tag', 'OpenApi\\Attributes\\Tag'),
new AnnotationToAttribute('OpenApi\\Annotations\\Trace', 'OpenApi\\Attributes\\Trace'),
new AnnotationToAttribute('OpenApi\\Annotations\\Xml', 'OpenApi\\Attributes\\Xml'),
new AnnotationToAttribute('OpenApi\\Annotations\\XmlContent', 'OpenApi\\Attributes\\XmlContent'),
]);
$rectorConfig->rules([
\Rector\TypeDeclaration\Rector\ClassMethod\ReturnTypeFromStrictNativeCallRector::class,
\Rector\CodeQuality\Rector\ClassMethod\ReturnTypeFromStrictScalarReturnExprRector::class,
\Rector\TypeDeclaration\Rector\ClassMethod\ReturnTypeFromStrictNewArrayRector::class,
\Rector\TypeDeclaration\Rector\ClassMethod\ReturnTypeFromStrictBoolReturnExprRector::class,
]);
$rectorConfig->sets([
\Rector\Symfony\Set\SymfonySetList::ANNOTATIONS_TO_ATTRIBUTES,
\Rector\Symfony\Set\SensiolabsSetList::FRAMEWORK_EXTRA_61,
\Rector\Symfony\Set\SymfonyLevelSetList::UP_TO_SYMFONY_60,
\Rector\Symfony\Set\JMSSetList::ANNOTATIONS_TO_ATTRIBUTES,
\Rector\Symfony\Set\JMSSetList::REMOVE_JMS_INJECT,
\Rector\Doctrine\Set\DoctrineSetList::ANNOTATIONS_TO_ATTRIBUTES,
\Rector\Doctrine\Set\DoctrineSetList::GEDMO_ANNOTATIONS_TO_ATTRIBUTES,
\Rector\PHPUnit\Set\PHPUnitSetList::PHPUNIT_80,
]);
};
vendor/bin/rector
, which will use the above configurationcomposer remove rector/rector && rm rector.php
composer codestyle-fix
From:
#[OA\Property(type: 'array', new OA\Items(ref: '#/components/schemas/Team'))]
to:
#[OA\Property(type: 'array', items: new OA\Items(ref: '#/components/schemas/Team'))]
Search for #\[Security\("(.*)\n(.*)#\[Security\("
and combine multiple attributes into one with “and” e.g.:
#[Security("is_granted('access', customer)")]
#[Security("is_granted('view_invoice')")]
to:
#[Security("is_granted('access', customer) and is_granted('view_invoice')")]