Skip to content
Permalink
Browse files
version 1.16.2 (#2942)
* bump version
* include calendar week in week chooser
* table names in SQL
* show save flash message
* prevent migration warning
* drop default value to prevent error when server version is not set
* csrf token for duplicate actions
* updated translations
  • Loading branch information
kevinpapst committed Nov 18, 2021
1 parent c858edf commit b28e9c120c87222e21a238f1b03a609d6a5d506e
Showing with 291 additions and 154 deletions.
  1. +2 −2 src/Constants.php
  2. +12 −2 src/Controller/ProjectController.php
  3. +12 −2 src/Controller/TeamController.php
  4. +3 −3 src/Controller/TimesheetAbstractController.php
  5. +15 −5 src/Controller/TimesheetController.php
  6. +15 −5 src/Controller/TimesheetTeamController.php
  7. +1 −1 src/EventSubscriber/Actions/AbstractTimesheetSubscriber.php
  8. +1 −1 src/EventSubscriber/Actions/ProjectSubscriber.php
  9. +1 −1 src/EventSubscriber/Actions/TeamSubscriber.php
  10. +12 −20 src/Migrations/Version20180701120000.php
  11. +27 −31 src/Migrations/Version20180715160326.php
  12. +4 −11 src/Migrations/Version20180730044139.php
  13. +3 −7 src/Migrations/Version20180924111853.php
  14. +18 −28 src/Migrations/Version20181031220003.php
  15. +8 −19 src/Migrations/Version20190305152308.php
  16. +1 −1 src/Migrations/Version20210802152814.php
  17. +2 −0 src/Migrations/Version20211008092010.php
  18. +2 −4 templates/form/kimai-theme.html.twig
  19. +1 −1 templates/project/actions.html.twig
  20. +1 −1 templates/team/actions.html.twig
  21. +1 −1 templates/timesheet-team/actions.html.twig
  22. +1 −1 templates/timesheet/actions.html.twig
  23. +10 −0 tests/Controller/ControllerBaseTest.php
  24. +7 −0 tests/Controller/DoctorControllerTest.php
  25. +22 −1 tests/Controller/ProjectControllerTest.php
  26. +10 −1 tests/Controller/TeamControllerTest.php
  27. +32 −1 tests/Controller/TimesheetControllerTest.php
  28. +32 −1 tests/Controller/TimesheetTeamControllerTest.php
  29. +2 −2 translations/about.ja.xlf
  30. +5 −1 translations/flashmessages.el.xlf
  31. +4 −0 translations/flashmessages.fr.xlf
  32. +4 −0 translations/flashmessages.he.xlf
  33. +4 −0 translations/flashmessages.pt.xlf
  34. +4 −0 translations/flashmessages.pt_BR.xlf
  35. +4 −0 translations/flashmessages.tr.xlf
  36. +4 −0 translations/messages.de.xlf
  37. +4 −0 translations/messages.en.xlf
@@ -17,11 +17,11 @@ class Constants
/**
* The current release version
*/
public const VERSION = '1.16.0';
public const VERSION = '1.16.2';
/**
* The current release: major * 10000 + minor * 100 + patch
*/
public const VERSION_ID = 11600;
public const VERSION_ID = 11602;
/**
* The current release status, either "stable" or "dev"
*/
@@ -421,13 +421,23 @@ public function editAction(Project $project, Request $request)
}

/**
* @Route(path="/{id}/duplicate", name="admin_project_duplicate", methods={"GET", "POST"})
* @Route(path="/{id}/duplicate/{token}", name="admin_project_duplicate", methods={"GET", "POST"})
* @Security("is_granted('edit', project)")
*/
public function duplicateAction(Project $project, Request $request, ProjectDuplicationService $projectDuplicationService)
public function duplicateAction(Project $project, string $token, ProjectDuplicationService $projectDuplicationService, CsrfTokenManagerInterface $csrfTokenManager)
{
if (!$csrfTokenManager->isTokenValid(new CsrfToken('project.duplicate', $token))) {
$this->flashError('action.csrf.error');

return $this->redirectToRoute('project_details', ['id' => $project->getId()]);
}

$csrfTokenManager->refreshToken($token);

$newProject = $projectDuplicationService->duplicate($project, $project->getName() . ' [COPY]');

$this->flashSuccess('action.update.success');

return $this->redirectToRoute('project_details', ['id' => $newProject->getId()]);
}

@@ -22,6 +22,8 @@
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;

/**
* @Route(path="/admin/teams")
@@ -81,11 +83,19 @@ public function createTeam(Request $request)
}

/**
* @Route(path="/{id}/duplicate", name="team_duplicate", methods={"GET", "POST"})
* @Route(path="/{id}/duplicate/{token}", name="team_duplicate", methods={"GET", "POST"})
* @Security("is_granted('edit', team) and is_granted('create_team')")
*/
public function duplicateTeam(Team $team, Request $request)
public function duplicateTeam(Team $team, string $token, CsrfTokenManagerInterface $csrfTokenManager)
{
if (!$csrfTokenManager->isTokenValid(new CsrfToken('team.duplicate', $token))) {
$this->flashError('action.csrf.error');

return $this->redirectToRoute('admin_team_edit', ['id' => $team->getId()]);
}

$csrfTokenManager->refreshToken($token);

$newTeam = clone $team;
$newTeam->setName($team->getName() . ' [COPY]');

@@ -211,14 +211,14 @@ protected function create(Request $request, string $renderTemplate, ProjectRepos
]);
}

protected function duplicate(Timesheet $timesheet, Request $request, string $renderTemplate): Response
protected function duplicate(Timesheet $timesheet, Request $request, string $renderTemplate, string $token): Response
{
$copyTimesheet = clone $timesheet;

$event = new TimesheetMetaDefinitionEvent($copyTimesheet);
$this->dispatcher->dispatch($event);

$form = $this->getDuplicateForm($copyTimesheet, $timesheet);
$form = $this->getDuplicateForm($copyTimesheet, $timesheet, $token);
$form->handleRequest($request);

if ($form->isSubmitted() && $form->isValid()) {
@@ -612,7 +612,7 @@ protected function createDefaultQuery(string $suffix = 'Listing'): TimesheetQuer
return $query;
}

abstract protected function getDuplicateForm(Timesheet $entry, Timesheet $original): FormInterface;
abstract protected function getDuplicateForm(Timesheet $entry, Timesheet $original, string $token): FormInterface;

abstract protected function getCreateForm(Timesheet $entry): FormInterface;
}
@@ -21,6 +21,8 @@
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;

/**
* @Route(path="/timesheet")
@@ -60,12 +62,20 @@ public function editAction(Timesheet $entry, Request $request): Response
}

/**
* @Route(path="/{id}/duplicate", name="timesheet_duplicate", methods={"GET", "POST"})
* @Route(path="/{id}/duplicate/{token}", name="timesheet_duplicate", methods={"GET", "POST"})
* @Security("is_granted('duplicate', entry)")
*/
public function duplicateAction(Timesheet $entry, Request $request): Response
public function duplicateAction(Timesheet $entry, Request $request, string $token, CsrfTokenManagerInterface $csrfTokenManager): Response
{
return $this->duplicate($entry, $request, 'timesheet/edit.html.twig');
if (!$csrfTokenManager->isTokenValid(new CsrfToken('timesheet.duplicate', $token))) {
$this->flashError('action.csrf.error');

return $this->redirectToRoute('timesheet');
}

$csrfTokenManager->refreshToken($token);

return $this->duplicate($entry, $request, 'timesheet/edit.html.twig', $token);
}

/**
@@ -100,8 +110,8 @@ protected function getCreateForm(Timesheet $entry): FormInterface
return $this->generateCreateForm($entry, TimesheetEditForm::class, $this->generateUrl('timesheet_create'));
}

protected function getDuplicateForm(Timesheet $entry, Timesheet $original): FormInterface
protected function getDuplicateForm(Timesheet $entry, Timesheet $original, string $token): FormInterface
{
return $this->generateCreateForm($entry, TimesheetEditForm::class, $this->generateUrl('timesheet_duplicate', ['id' => $original->getId()]));
return $this->generateCreateForm($entry, TimesheetEditForm::class, $this->generateUrl('timesheet_duplicate', ['id' => $original->getId(), 'token' => $token]));
}
}
@@ -28,6 +28,8 @@
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Csrf\CsrfToken;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;

/**
* @Route(path="/team/timesheet")
@@ -71,12 +73,20 @@ public function editAction(Timesheet $entry, Request $request): Response
}

/**
* @Route(path="/{id}/duplicate", name="admin_timesheet_duplicate", methods={"GET", "POST"})
* @Route(path="/{id}/duplicate/{token}", name="admin_timesheet_duplicate", methods={"GET", "POST"})
* @Security("is_granted('duplicate', entry)")
*/
public function duplicateAction(Timesheet $entry, Request $request): Response
public function duplicateAction(Timesheet $entry, Request $request, string $token, CsrfTokenManagerInterface $csrfTokenManager): Response
{
return $this->duplicate($entry, $request, 'timesheet-team/edit.html.twig');
if (!$csrfTokenManager->isTokenValid(new CsrfToken('admin_timesheet.duplicate', $token))) {
$this->flashError('action.csrf.error');

return $this->redirectToRoute('admin_timesheet');
}

$csrfTokenManager->refreshToken($token);

return $this->duplicate($entry, $request, 'timesheet-team/edit.html.twig', $token);
}

/**
@@ -195,9 +205,9 @@ protected function getCreateForm(Timesheet $entry): FormInterface
return $this->generateCreateForm($entry, TimesheetAdminEditForm::class, $this->generateUrl('admin_timesheet_create'));
}

protected function getDuplicateForm(Timesheet $entry, Timesheet $original): FormInterface
protected function getDuplicateForm(Timesheet $entry, Timesheet $original, string $token): FormInterface
{
return $this->generateCreateForm($entry, TimesheetAdminEditForm::class, $this->generateUrl('admin_timesheet_duplicate', ['id' => $original->getId()]));
return $this->generateCreateForm($entry, TimesheetAdminEditForm::class, $this->generateUrl('admin_timesheet_duplicate', ['id' => $original->getId(), 'token' => $token]));
}

protected function getPermissionEditExport(): string
@@ -39,7 +39,7 @@ protected function timesheetActions(PageActionsEvent $event, string $routeEdit,

if ($this->isGranted('duplicate', $timesheet)) {
$class = $event->isView('edit') ? '' : 'modal-ajax-form';
$event->addAction('copy', ['url' => $this->path($routeDuplicate, ['id' => $timesheet->getId()]), 'class' => $class]);
$event->addAction('copy', ['url' => $this->path($routeDuplicate, ['id' => $timesheet->getId(), 'token' => $payload['token']]), 'class' => $class]);
}

if ($event->countActions() > 0) {
@@ -73,7 +73,7 @@ public function onActions(PageActionsEvent $event): void
if ($this->isGranted('edit', $project) && $this->isGranted('create_project')) {
$event->addAction(
'copy',
['url' => $this->path('admin_project_duplicate', ['id' => $project->getId()])]
['url' => $this->path('admin_project_duplicate', ['id' => $project->getId(), 'token' => $payload['token']])]
);
}

@@ -34,7 +34,7 @@ public function onActions(PageActionsEvent $event): void
$event->addAction('edit', ['url' => $this->path('admin_team_edit', ['id' => $team->getId()])]);

if ($this->isGranted('create_team')) {
$event->addAction('copy', ['url' => $this->path('team_duplicate', ['id' => $team->getId()])]);
$event->addAction('copy', ['url' => $this->path('team_duplicate', ['id' => $team->getId(), 'token' => $payload['token']])]);
}
}

@@ -22,26 +22,18 @@ final class Version20180701120000 extends AbstractMigration
{
public function up(Schema $schema): void
{
$users = 'kimai2_users';
$userPreferences = 'kimai2_user_preferences';
$customers = 'kimai2_customers';
$projects = 'kimai2_projects';
$activities = 'kimai2_activities';
$timesheets = 'kimai2_timesheet';
$invoiceTemplates = 'kimai2_invoice_templates';

$this->addSql('CREATE TABLE ' . $users . ' (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(60) NOT NULL, mail VARCHAR(160) NOT NULL, password VARCHAR(254) DEFAULT NULL, alias VARCHAR(60) DEFAULT NULL, active TINYINT(1) NOT NULL, registration_date DATETIME DEFAULT NULL, title VARCHAR(50) DEFAULT NULL, avatar VARCHAR(255) DEFAULT NULL, roles LONGTEXT NOT NULL COMMENT \'(DC2Type:array)\', UNIQUE INDEX UNIQ_B9AC5BCE5E237E06 (name), UNIQUE INDEX UNIQ_B9AC5BCE5126AC48 (mail), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB');
$this->addSql('CREATE TABLE ' . $userPreferences . ' (id INT AUTO_INCREMENT NOT NULL, user_id INT DEFAULT NULL, name VARCHAR(50) NOT NULL, value VARCHAR(255) DEFAULT NULL, INDEX IDX_8D08F631A76ED395 (user_id), UNIQUE INDEX UNIQ_8D08F631A76ED3955E237E06 (user_id, name), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB');
$this->addSql('CREATE TABLE ' . $customers . ' (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(150) NOT NULL, number VARCHAR(50) DEFAULT NULL, comment TEXT DEFAULT NULL, visible TINYINT(1) NOT NULL, company VARCHAR(255) DEFAULT NULL, contact VARCHAR(255) DEFAULT NULL, address TEXT DEFAULT NULL, country VARCHAR(2) NOT NULL, currency VARCHAR(3) NOT NULL, phone VARCHAR(255) DEFAULT NULL, fax VARCHAR(255) DEFAULT NULL, mobile VARCHAR(255) DEFAULT NULL, mail VARCHAR(255) DEFAULT NULL, homepage VARCHAR(255) DEFAULT NULL, timezone VARCHAR(255) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB');
$this->addSql('CREATE TABLE ' . $projects . ' (id INT AUTO_INCREMENT NOT NULL, customer_id INT DEFAULT NULL, name VARCHAR(150) NOT NULL, order_number TINYTEXT DEFAULT NULL, comment TEXT DEFAULT NULL, visible TINYINT(1) NOT NULL, budget NUMERIC(10, 2) NOT NULL, INDEX IDX_407F12069395C3F3 (customer_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB');
$this->addSql('CREATE TABLE ' . $activities . ' (id INT AUTO_INCREMENT NOT NULL, project_id INT DEFAULT NULL, name VARCHAR(150) NOT NULL, comment TEXT DEFAULT NULL, visible TINYINT(1) NOT NULL, INDEX IDX_8811FE1C166D1F9C (project_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB');
$this->addSql('CREATE TABLE ' . $timesheets . ' (id INT AUTO_INCREMENT NOT NULL, user INT DEFAULT NULL, activity_id INT DEFAULT NULL, start_time DATETIME NOT NULL, end_time DATETIME DEFAULT NULL, duration INT DEFAULT NULL, description TEXT DEFAULT NULL, rate NUMERIC(10, 2) NOT NULL, INDEX IDX_4F60C6B18D93D649 (user), INDEX IDX_4F60C6B181C06096 (activity_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB');
$this->addSql('CREATE TABLE ' . $invoiceTemplates . ' (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(60) NOT NULL, title VARCHAR(255) NOT NULL, company VARCHAR(255) NOT NULL, address TEXT DEFAULT NULL, due_days INT NOT NULL, vat INT DEFAULT NULL, calculator VARCHAR(20) NOT NULL, number_generator VARCHAR(20) NOT NULL, renderer VARCHAR(20) NOT NULL, payment_terms TEXT DEFAULT NULL, UNIQUE INDEX UNIQ_1626CFE95E237E06 (name), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB');
$this->addSql('ALTER TABLE ' . $userPreferences . ' ADD CONSTRAINT FK_8D08F631A76ED395 FOREIGN KEY (user_id) REFERENCES ' . $users . ' (id) ON DELETE CASCADE');
$this->addSql('ALTER TABLE ' . $projects . ' ADD CONSTRAINT FK_407F12069395C3F3 FOREIGN KEY (customer_id) REFERENCES ' . $customers . ' (id) ON DELETE CASCADE');
$this->addSql('ALTER TABLE ' . $activities . ' ADD CONSTRAINT FK_8811FE1C166D1F9C FOREIGN KEY (project_id) REFERENCES ' . $projects . ' (id) ON DELETE CASCADE');
$this->addSql('ALTER TABLE ' . $timesheets . ' ADD CONSTRAINT FK_4F60C6B18D93D649 FOREIGN KEY (user) REFERENCES ' . $users . ' (id)');
$this->addSql('ALTER TABLE ' . $timesheets . ' ADD CONSTRAINT FK_4F60C6B181C06096 FOREIGN KEY (activity_id) REFERENCES ' . $activities . ' (id) ON DELETE CASCADE');
$this->addSql('CREATE TABLE kimai2_users (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(60) NOT NULL, mail VARCHAR(160) NOT NULL, password VARCHAR(254) DEFAULT NULL, alias VARCHAR(60) DEFAULT NULL, active TINYINT(1) NOT NULL, registration_date DATETIME DEFAULT NULL, title VARCHAR(50) DEFAULT NULL, avatar VARCHAR(255) DEFAULT NULL, roles LONGTEXT NOT NULL COMMENT \'(DC2Type:array)\', UNIQUE INDEX UNIQ_B9AC5BCE5E237E06 (name), UNIQUE INDEX UNIQ_B9AC5BCE5126AC48 (mail), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB');
$this->addSql('CREATE TABLE kimai2_user_preferences (id INT AUTO_INCREMENT NOT NULL, user_id INT DEFAULT NULL, name VARCHAR(50) NOT NULL, value VARCHAR(255) DEFAULT NULL, INDEX IDX_8D08F631A76ED395 (user_id), UNIQUE INDEX UNIQ_8D08F631A76ED3955E237E06 (user_id, name), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB');
$this->addSql('CREATE TABLE kimai2_customers (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(150) NOT NULL, number VARCHAR(50) DEFAULT NULL, comment TEXT DEFAULT NULL, visible TINYINT(1) NOT NULL, company VARCHAR(255) DEFAULT NULL, contact VARCHAR(255) DEFAULT NULL, address TEXT DEFAULT NULL, country VARCHAR(2) NOT NULL, currency VARCHAR(3) NOT NULL, phone VARCHAR(255) DEFAULT NULL, fax VARCHAR(255) DEFAULT NULL, mobile VARCHAR(255) DEFAULT NULL, mail VARCHAR(255) DEFAULT NULL, homepage VARCHAR(255) DEFAULT NULL, timezone VARCHAR(255) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB');
$this->addSql('CREATE TABLE kimai2_projects (id INT AUTO_INCREMENT NOT NULL, customer_id INT DEFAULT NULL, name VARCHAR(150) NOT NULL, order_number TINYTEXT DEFAULT NULL, comment TEXT DEFAULT NULL, visible TINYINT(1) NOT NULL, budget NUMERIC(10, 2) NOT NULL, INDEX IDX_407F12069395C3F3 (customer_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB');
$this->addSql('CREATE TABLE kimai2_activities (id INT AUTO_INCREMENT NOT NULL, project_id INT DEFAULT NULL, name VARCHAR(150) NOT NULL, comment TEXT DEFAULT NULL, visible TINYINT(1) NOT NULL, INDEX IDX_8811FE1C166D1F9C (project_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB');
$this->addSql('CREATE TABLE kimai2_timesheet (id INT AUTO_INCREMENT NOT NULL, user INT DEFAULT NULL, activity_id INT DEFAULT NULL, start_time DATETIME NOT NULL, end_time DATETIME DEFAULT NULL, duration INT DEFAULT NULL, description TEXT DEFAULT NULL, rate NUMERIC(10, 2) NOT NULL, INDEX IDX_4F60C6B18D93D649 (user), INDEX IDX_4F60C6B181C06096 (activity_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB');
$this->addSql('CREATE TABLE kimai2_invoice_templates (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(60) NOT NULL, title VARCHAR(255) NOT NULL, company VARCHAR(255) NOT NULL, address TEXT DEFAULT NULL, due_days INT NOT NULL, vat INT DEFAULT NULL, calculator VARCHAR(20) NOT NULL, number_generator VARCHAR(20) NOT NULL, renderer VARCHAR(20) NOT NULL, payment_terms TEXT DEFAULT NULL, UNIQUE INDEX UNIQ_1626CFE95E237E06 (name), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ENGINE = InnoDB');
$this->addSql('ALTER TABLE kimai2_user_preferences ADD CONSTRAINT FK_8D08F631A76ED395 FOREIGN KEY (user_id) REFERENCES kimai2_users (id) ON DELETE CASCADE');
$this->addSql('ALTER TABLE kimai2_projects ADD CONSTRAINT FK_407F12069395C3F3 FOREIGN KEY (customer_id) REFERENCES kimai2_customers (id) ON DELETE CASCADE');
$this->addSql('ALTER TABLE kimai2_activities ADD CONSTRAINT FK_8811FE1C166D1F9C FOREIGN KEY (project_id) REFERENCES kimai2_projects (id) ON DELETE CASCADE');
$this->addSql('ALTER TABLE kimai2_timesheet ADD CONSTRAINT FK_4F60C6B18D93D649 FOREIGN KEY (user) REFERENCES kimai2_users (id)');
$this->addSql('ALTER TABLE kimai2_timesheet ADD CONSTRAINT FK_4F60C6B181C06096 FOREIGN KEY (activity_id) REFERENCES kimai2_activities (id) ON DELETE CASCADE');
}

public function down(Schema $schema): void

0 comments on commit b28e9c1

Please sign in to comment.