@@ -42,7 +42,7 @@ Our roadmap is open for changes and input from the community, please [sent us](i
|
||||
|
||||
There are [further infos about installation](var/docs/installation.md) if you have to use FTP or want to develop with Kimai.
|
||||
|
||||
If you want to install Kimai v2 in your production environment, then SSH into your server and change to your webserevr root.
|
||||
If you want to install Kimai 2 in your production environment, then SSH into your server and change to your webserver root.
|
||||
You need to install Git and [Composer](https://getcomposer.org/doc/00-intro.md) if you haven't already.
|
||||
|
||||
First clone this repo:
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
# Upgrading Kimai 2
|
||||
|
||||
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.
|
||||
|
||||
## [0.3](https://github.com/kevinpapst/kimai2/releases/tag/0.3) (2018-07-22)
|
||||
|
||||
**Update from 0.2:**
|
||||
|
||||
@@ -122,7 +122,7 @@ class ActivityController extends AbstractController
|
||||
|
||||
$this->flashSuccess('action.deleted_successfully');
|
||||
|
||||
return $this->redirectToRoute('admin_activity', ['id' => $activity->getId()]);
|
||||
return $this->redirectToRoute('admin_activity');
|
||||
}
|
||||
|
||||
return $this->render(
|
||||
@@ -160,7 +160,7 @@ class ActivityController extends AbstractController
|
||||
$editForm->get('create_more')->setData(true);
|
||||
$activity = $newActivity;
|
||||
} else {
|
||||
return $this->redirectToRoute('admin_activity', ['id' => $activity->getId()]);
|
||||
return $this->redirectToRoute('admin_activity');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ class CustomerController extends AbstractController
|
||||
|
||||
$this->flashSuccess('action.updated_successfully');
|
||||
|
||||
return $this->redirectToRoute('admin_customer', ['id' => $customer->getId()]);
|
||||
return $this->redirectToRoute('admin_customer');
|
||||
}
|
||||
|
||||
return $this->render('admin/customer_edit.html.twig', [
|
||||
@@ -139,7 +139,7 @@ class CustomerController extends AbstractController
|
||||
|
||||
$this->flashSuccess('action.deleted_successfully');
|
||||
|
||||
return $this->redirectToRoute('admin_customer', ['id' => $customer->getId()]);
|
||||
return $this->redirectToRoute('admin_customer');
|
||||
}
|
||||
|
||||
return $this->render('admin/customer_delete.html.twig', [
|
||||
|
||||
@@ -116,7 +116,7 @@ class ProjectController extends AbstractController
|
||||
|
||||
$this->flashSuccess('action.deleted_successfully');
|
||||
|
||||
return $this->redirectToRoute('admin_project', ['id' => $project->getId()]);
|
||||
return $this->redirectToRoute('admin_project');
|
||||
}
|
||||
|
||||
return $this->render('admin/project_delete.html.twig', [
|
||||
@@ -151,7 +151,7 @@ class ProjectController extends AbstractController
|
||||
$editForm->get('create_more')->setData(true);
|
||||
$project = $newProject;
|
||||
} else {
|
||||
return $this->redirectToRoute('admin_project', ['id' => $project->getId()]);
|
||||
return $this->redirectToRoute('admin_project');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
namespace App\Controller\Admin;
|
||||
|
||||
use App\Controller\AbstractController;
|
||||
use App\Entity\Timesheet;
|
||||
use App\Entity\User;
|
||||
use App\Form\Toolbar\UserToolbarForm;
|
||||
use App\Form\UserCreateType;
|
||||
@@ -19,6 +20,7 @@ use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
|
||||
|
||||
/**
|
||||
* Controller used to manage users in the admin part of the site.
|
||||
@@ -29,6 +31,27 @@ use Symfony\Component\HttpFoundation\Request;
|
||||
*/
|
||||
class UserController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* @var UserPasswordEncoderInterface
|
||||
*/
|
||||
protected $encoder;
|
||||
|
||||
/**
|
||||
* @param UserPasswordEncoderInterface $encoder
|
||||
*/
|
||||
public function __construct(UserPasswordEncoderInterface $encoder)
|
||||
{
|
||||
$this->encoder = $encoder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \App\Repository\UserRepository
|
||||
*/
|
||||
protected function getRepository()
|
||||
{
|
||||
return $this->getDoctrine()->getRepository(User::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/", defaults={"page": 1}, name="admin_user")
|
||||
* @Route("/page/{page}", requirements={"page": "[1-9]\d*"}, name="admin_user_paginated")
|
||||
@@ -47,7 +70,7 @@ class UserController extends AbstractController
|
||||
}
|
||||
|
||||
/* @var $entries Pagerfanta */
|
||||
$entries = $this->getDoctrine()->getRepository(User::class)->findByQuery($query);
|
||||
$entries = $this->getRepository()->findByQuery($query);
|
||||
|
||||
return $this->render('admin/user.html.twig', [
|
||||
'entries' => $entries,
|
||||
@@ -69,8 +92,7 @@ class UserController extends AbstractController
|
||||
$editForm->handleRequest($request);
|
||||
|
||||
if ($editForm->isSubmitted() && $editForm->isValid()) {
|
||||
$password = $this->get('security.password_encoder')
|
||||
->encodePassword($user, $user->getPlainPassword());
|
||||
$password = $this->encoder->encodePassword($user, $user->getPlainPassword());
|
||||
$user->setPassword($password);
|
||||
$user->setEnabled(true);
|
||||
$user->setRoles([User::DEFAULT_ROLE]);
|
||||
@@ -99,6 +121,49 @@ class UserController extends AbstractController
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The route to delete an existing user.
|
||||
*
|
||||
* @Route("/{id}/delete", name="admin_user_delete")
|
||||
* @Method({"GET", "POST"})
|
||||
* @Security("is_granted('delete', userToDelete)")
|
||||
*
|
||||
* @param User $userToDelete
|
||||
* @param Request $request
|
||||
* @return \Symfony\Component\HttpFoundation\RedirectResponse|\Symfony\Component\HttpFoundation\Response
|
||||
* @throws \Doctrine\ORM\NonUniqueResultException
|
||||
*/
|
||||
public function deleteAction(User $userToDelete, Request $request)
|
||||
{
|
||||
$stats = $this->getDoctrine()->getRepository(Timesheet::class)->getUserStatistics($userToDelete);
|
||||
|
||||
$deleteForm = $this->createFormBuilder()
|
||||
->setAction($this->generateUrl('admin_user_delete', ['id' => $userToDelete->getId()]))
|
||||
->setMethod('POST')
|
||||
->getForm();
|
||||
|
||||
$deleteForm->handleRequest($request);
|
||||
|
||||
if (0 == $stats->getRecordsTotal() || ($deleteForm->isSubmitted() && $deleteForm->isValid())) {
|
||||
$entityManager = $this->getDoctrine()->getManager();
|
||||
$entityManager->remove($userToDelete);
|
||||
$entityManager->flush();
|
||||
|
||||
$this->flashSuccess('action.deleted_successfully');
|
||||
|
||||
return $this->redirectToRoute('admin_user');
|
||||
}
|
||||
|
||||
return $this->render(
|
||||
'admin/user_delete.html.twig',
|
||||
[
|
||||
'user' => $userToDelete,
|
||||
'stats' => $stats,
|
||||
'form' => $deleteForm->createView(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param UserQuery $query
|
||||
* @return \Symfony\Component\Form\FormInterface
|
||||
|
||||
@@ -22,6 +22,7 @@ use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
|
||||
use Symfony\Component\Form\Form;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
|
||||
|
||||
/**
|
||||
* User profile controller
|
||||
@@ -31,6 +32,19 @@ use Symfony\Component\HttpFoundation\Request;
|
||||
*/
|
||||
class ProfileController extends AbstractController
|
||||
{
|
||||
/**
|
||||
* @var UserPasswordEncoderInterface
|
||||
*/
|
||||
protected $encoder;
|
||||
|
||||
/**
|
||||
* @param UserPasswordEncoderInterface $encoder
|
||||
*/
|
||||
public function __construct(UserPasswordEncoderInterface $encoder)
|
||||
{
|
||||
$this->encoder = $encoder;
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/{username}", name="user_profile")
|
||||
* @Method("GET")
|
||||
@@ -75,8 +89,7 @@ class ProfileController extends AbstractController
|
||||
$form->handleRequest($request);
|
||||
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
$password = $this->get('security.password_encoder')
|
||||
->encodePassword($profile, $profile->getPlainPassword());
|
||||
$password = $this->encoder->encodePassword($profile, $profile->getPlainPassword());
|
||||
$profile->setPassword($password);
|
||||
|
||||
$entityManager = $this->getDoctrine()->getManager();
|
||||
@@ -147,11 +160,9 @@ class ProfileController extends AbstractController
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($preferences as $preference) {
|
||||
$preference->setUser($profile);
|
||||
$entityManager->persist($preference);
|
||||
$entityManager->flush();
|
||||
}
|
||||
$profile->setPreferences($preferences);
|
||||
$entityManager->persist($profile);
|
||||
$entityManager->flush();
|
||||
|
||||
$this->flashSuccess('action.updated_successfully');
|
||||
|
||||
|
||||
@@ -69,6 +69,14 @@ class Activity
|
||||
*/
|
||||
private $timesheets;
|
||||
|
||||
/**
|
||||
* @return Timesheet[]
|
||||
*/
|
||||
public function getTimesheets(): array
|
||||
{
|
||||
return $this->timesheets;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Project
|
||||
*/
|
||||
|
||||
@@ -64,7 +64,7 @@ class Timesheet
|
||||
* @var User
|
||||
*
|
||||
* @ORM\ManyToOne(targetEntity="App\Entity\User")
|
||||
* @ORM\JoinColumn(name="user", referencedColumnName="id")
|
||||
* @ORM\JoinColumn(name="user", referencedColumnName="id", onDelete="CASCADE")
|
||||
* @Assert\NotNull()
|
||||
*/
|
||||
private $user;
|
||||
|
||||
@@ -191,12 +191,13 @@ class User extends BaseUser implements UserInterface
|
||||
* @param UserPreference[]|Collection<UserPreference> $preferences
|
||||
* @return User
|
||||
*/
|
||||
public function setPreferences(array $preferences)
|
||||
public function setPreferences($preferences)
|
||||
{
|
||||
if (!($preferences instanceof Collection) && is_array($preferences)) {
|
||||
$preferences = new ArrayCollection($preferences);
|
||||
$this->preferences = new ArrayCollection();
|
||||
|
||||
foreach ($preferences as $preference) {
|
||||
$this->addPreference($preference);
|
||||
}
|
||||
$this->preferences = $preferences;
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -238,6 +239,7 @@ class User extends BaseUser implements UserInterface
|
||||
public function addPreference(UserPreference $preference)
|
||||
{
|
||||
$this->preferences->add($preference);
|
||||
$preference->setUser($this);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
92
src/Migrations/Version20180730044139.php
Normal file
92
src/Migrations/Version20180730044139.php
Normal file
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use App\Doctrine\AbstractMigration;
|
||||
use Doctrine\DBAL\Schema\Index;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
|
||||
/**
|
||||
* Add constraints for the "delete user" feature.
|
||||
*/
|
||||
final class Version20180730044139 extends AbstractMigration
|
||||
{
|
||||
/**
|
||||
* @var Index[]
|
||||
*/
|
||||
protected $indexesOld = [];
|
||||
|
||||
/**
|
||||
* @param Schema $schema
|
||||
* @throws \Doctrine\DBAL\DBALException
|
||||
* @throws \Doctrine\DBAL\Migrations\AbortMigrationException
|
||||
*/
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$platform = $this->getPlatform();
|
||||
|
||||
if (!in_array($platform, ['sqlite', 'mysql'])) {
|
||||
$this->abortIf(true, 'Unsupported database platform: ' . $platform);
|
||||
}
|
||||
|
||||
$timesheet = $this->getTableName('timesheet');
|
||||
$user = $this->getTableName('users');
|
||||
$activity = $this->getTableName('activities');
|
||||
|
||||
if ($platform === 'sqlite') {
|
||||
$this->addSql('DROP INDEX IDX_4F60C6B181C06096');
|
||||
$this->addSql('DROP INDEX IDX_4F60C6B18D93D649');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__' . $timesheet . ' AS SELECT id, user, activity_id, start_time, end_time, duration, description, rate FROM ' . $timesheet);
|
||||
$this->addSql('DROP TABLE ' . $timesheet);
|
||||
$this->addSql('CREATE TABLE ' . $timesheet . ' (id INTEGER NOT NULL, user INTEGER DEFAULT NULL, activity_id INTEGER DEFAULT NULL, start_time DATETIME NOT NULL, end_time DATETIME DEFAULT NULL, duration INTEGER DEFAULT NULL, description CLOB DEFAULT NULL COLLATE BINARY, rate NUMERIC(10, 2) NOT NULL, PRIMARY KEY(id), CONSTRAINT FK_4F60C6B18D93D649 FOREIGN KEY (user) REFERENCES ' . $user . ' (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE, CONSTRAINT FK_4F60C6B181C06096 FOREIGN KEY (activity_id) REFERENCES ' . $activity . ' (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE)');
|
||||
$this->addSql('INSERT INTO ' . $timesheet . ' (id, user, activity_id, start_time, end_time, duration, description, rate) SELECT id, user, activity_id, start_time, end_time, duration, description, rate FROM __temp__' . $timesheet);
|
||||
$this->addSql('DROP TABLE __temp__' . $timesheet);
|
||||
$this->addSql('CREATE INDEX IDX_4F60C6B181C06096 ON ' . $timesheet . ' (activity_id)');
|
||||
$this->addSql('CREATE INDEX IDX_4F60C6B18D93D649 ON ' . $timesheet . ' (user)');
|
||||
} else {
|
||||
$this->addSql('ALTER TABLE ' . $timesheet . ' DROP FOREIGN KEY FK_4F60C6B18D93D649');
|
||||
$this->addSql('ALTER TABLE ' . $timesheet . ' ADD CONSTRAINT FK_4F60C6B18D93D649 FOREIGN KEY (user) REFERENCES ' . $user . ' (id) ON DELETE CASCADE');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Schema $schema
|
||||
* @throws \Doctrine\DBAL\DBALException
|
||||
* @throws \Doctrine\DBAL\Migrations\AbortMigrationException
|
||||
*/
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$platform = $this->getPlatform();
|
||||
|
||||
if (!in_array($platform, ['sqlite', 'mysql'])) {
|
||||
$this->abortIf(true, 'Unsupported database platform: ' . $platform);
|
||||
}
|
||||
|
||||
$timesheet = $this->getTableName('timesheet');
|
||||
$user = $this->getTableName('user');
|
||||
|
||||
if ($platform === 'sqlite') {
|
||||
$this->addSql('DROP INDEX IDX_4F60C6B18D93D649');
|
||||
$this->addSql('DROP INDEX IDX_4F60C6B181C06096');
|
||||
$this->addSql('CREATE TEMPORARY TABLE __temp__' . $timesheet . ' AS SELECT id, user, activity_id, start_time, end_time, duration, description, rate FROM ' . $timesheet);
|
||||
$this->addSql('DROP TABLE ' . $timesheet);
|
||||
$this->addSql('CREATE TABLE ' . $timesheet . ' (id INTEGER NOT NULL, user INTEGER DEFAULT NULL, activity_id INTEGER DEFAULT NULL, start_time DATETIME NOT NULL, end_time DATETIME DEFAULT NULL, duration INTEGER DEFAULT NULL, description CLOB DEFAULT NULL, rate NUMERIC(10, 2) NOT NULL, PRIMARY KEY(id))');
|
||||
$this->addSql('INSERT INTO ' . $timesheet . ' (id, user, activity_id, start_time, end_time, duration, description, rate) SELECT id, user, activity_id, start_time, end_time, duration, description, rate FROM __temp__' . $timesheet);
|
||||
$this->addSql('DROP TABLE __temp__' . $timesheet);
|
||||
$this->addSql('CREATE INDEX IDX_4F60C6B18D93D649 ON ' . $timesheet . ' (user)');
|
||||
$this->addSql('CREATE INDEX IDX_4F60C6B181C06096 ON ' . $timesheet . ' (activity_id)');
|
||||
} else {
|
||||
$this->addSql('ALTER TABLE ' . $timesheet . ' DROP FOREIGN KEY FK_4F60C6B18D93D649');
|
||||
$this->addSql('ALTER TABLE ' . $timesheet . ' ADD CONSTRAINT FK_4F60C6B18D93D649 FOREIGN KEY (user) REFERENCES ' . $user . ' (id)');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,10 @@ class TimesheetStatistic
|
||||
* @var \DateTime
|
||||
*/
|
||||
protected $firstEntry;
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $recordsTotal = 0;
|
||||
|
||||
/**
|
||||
* @return int
|
||||
@@ -116,4 +120,23 @@ class TimesheetStatistic
|
||||
{
|
||||
$this->firstEntry = $firstEntry;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getRecordsTotal(): int
|
||||
{
|
||||
return $this->recordsTotal;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $recordsTotal
|
||||
* @return TimesheetStatistic
|
||||
*/
|
||||
public function setRecordsTotal(int $recordsTotal)
|
||||
{
|
||||
$this->recordsTotal = $recordsTotal;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,6 +121,10 @@ class TimesheetRepository extends AbstractRepository
|
||||
->createQuery('SELECT SUM(t.duration) FROM ' . Timesheet::class . ' t WHERE t.user = :user')
|
||||
->setParameter('user', $user)
|
||||
->getSingleScalarResult();
|
||||
$recordsTotal = $this->getEntityManager()
|
||||
->createQuery('SELECT COUNT(t.id) FROM ' . Timesheet::class . ' t WHERE t.user = :user')
|
||||
->setParameter('user', $user)
|
||||
->getSingleScalarResult();
|
||||
$rateTotal = $this->getEntityManager()
|
||||
->createQuery('SELECT SUM(t.rate) FROM ' . Timesheet::class . ' t WHERE t.user = :user')
|
||||
->setParameter('user', $user)
|
||||
@@ -142,6 +146,7 @@ class TimesheetRepository extends AbstractRepository
|
||||
$stats->setAmountThisMonth($amountMonth);
|
||||
$stats->setDurationThisMonth($durationMonth);
|
||||
$stats->setFirstEntry(new DateTime($firstEntry));
|
||||
$stats->setRecordsTotal($recordsTotal);
|
||||
|
||||
return $stats;
|
||||
}
|
||||
@@ -203,6 +208,9 @@ class TimesheetRepository extends AbstractRepository
|
||||
$durationTotal = $this->getEntityManager()
|
||||
->createQuery('SELECT SUM(t.duration) FROM ' . Timesheet::class . ' t')
|
||||
->getSingleScalarResult();
|
||||
$recordsTotal = $this->getEntityManager()
|
||||
->createQuery('SELECT COUNT(t.id) FROM ' . Timesheet::class . ' t')
|
||||
->getSingleScalarResult();
|
||||
$rateTotal = $this->getEntityManager()
|
||||
->createQuery('SELECT SUM(t.rate) FROM ' . Timesheet::class . ' t')
|
||||
->getSingleScalarResult();
|
||||
@@ -228,6 +236,7 @@ class TimesheetRepository extends AbstractRepository
|
||||
$stats->setActiveThisMonth($activeMonth);
|
||||
$stats->setAmountThisMonth($amountMonth);
|
||||
$stats->setDurationThisMonth($durationMonth);
|
||||
$stats->setRecordsTotal($recordsTotal);
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ class UserVoter extends AbstractVoter
|
||||
*/
|
||||
protected function canEditPreferences(User $profile, User $user, TokenInterface $token)
|
||||
{
|
||||
return $profile->getId() == $user->getId();
|
||||
return $profile->getId() === $user->getId();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -107,7 +107,7 @@ class UserVoter extends AbstractVoter
|
||||
return true;
|
||||
}
|
||||
|
||||
return $profile->getId() == $user->getId();
|
||||
return $profile->getId() === $user->getId();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -121,7 +121,7 @@ class UserVoter extends AbstractVoter
|
||||
return true;
|
||||
}
|
||||
|
||||
return $profile->getId() == $user->getId();
|
||||
return $profile->getId() === $user->getId();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -131,7 +131,11 @@ class UserVoter extends AbstractVoter
|
||||
*/
|
||||
protected function canDelete(User $profile, User $user, TokenInterface $token)
|
||||
{
|
||||
return false;
|
||||
if (!$this->canAdminUsers($token)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $profile->getId() !== $user->getId();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -140,6 +144,6 @@ class UserVoter extends AbstractVoter
|
||||
*/
|
||||
protected function canAdminUsers(TokenInterface $token)
|
||||
{
|
||||
return $this->isFullyAuthenticated($token) && $this->hasRole('ROLE_SUPER_ADMIN', $token);
|
||||
return $this->isFullyAuthenticated($token) && $this->hasRole(User::ROLE_SUPER_ADMIN, $token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
{% set actionButtons = {'edit': path('user_profile', {'username' : entry.username})}|merge(actionButtons) %}
|
||||
{% endif %}
|
||||
{% if is_granted('delete', entry) %}
|
||||
{% set actionButtons = actionButtons|merge({'trash': '#'}) %}
|
||||
{% set actionButtons = actionButtons|merge({'trash': path('admin_user_delete', {'id': entry.id})}) %}
|
||||
{% endif %}
|
||||
{{ widgets.button_group(actionButtons) }}
|
||||
</td>
|
||||
|
||||
22
templates/admin/user_delete.html.twig
Normal file
22
templates/admin/user_delete.html.twig
Normal file
@@ -0,0 +1,22 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% import "macros/datatables.html.twig" as tables %}
|
||||
|
||||
{% block page_title %}{{ 'admin_user.title'|trans }}{% endblock %}
|
||||
{% block page_subtitle %}{{ 'admin_user.subtitle'|trans }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
{% set params = {
|
||||
'%user%': '<strong>' ~ widgets.username(user) ~ '</strong>',
|
||||
'%records%': '<strong>' ~ stats.recordsTotal ~ '</strong>',
|
||||
'%duration%': '<strong>' ~ stats.durationTotal|duration ~ '</strong>'
|
||||
} %}
|
||||
|
||||
{{ include('default/_form_delete.html.twig', {
|
||||
'message': "admin_user.delete_confirm"|trans(params)|raw,
|
||||
'form': form,
|
||||
'back': path('admin_activity')
|
||||
}) }}
|
||||
|
||||
{% endblock %}
|
||||
@@ -11,6 +11,8 @@ namespace App\Tests\Controller\Admin;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Tests\Controller\ControllerBaseTest;
|
||||
use App\Tests\DataFixtures\ProjectFixtures;
|
||||
use App\Tests\DataFixtures\TimesheetFixtures;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \App\Controller\Admin\ActivityController
|
||||
@@ -44,7 +46,7 @@ class ActivityControllerTest extends ControllerBaseTest
|
||||
'name' => 'Test 2',
|
||||
]
|
||||
]);
|
||||
$this->assertTrue($client->getResponse()->isRedirect());
|
||||
$this->assertIsRedirect($client, $this->createUrl('/admin/activity/'));
|
||||
$client->followRedirect();
|
||||
$this->assertHasDataTable($client);
|
||||
}
|
||||
@@ -52,14 +54,26 @@ class ActivityControllerTest extends ControllerBaseTest
|
||||
public function testCreateActionWithCreateMore()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
|
||||
$em = $client->getContainer()->get('doctrine.orm.entity_manager');
|
||||
$fixture = new ProjectFixtures();
|
||||
$fixture->setAmount(10);
|
||||
$this->importFixture($em, $fixture);
|
||||
|
||||
$this->assertAccessIsGranted($client, '/admin/activity/create');
|
||||
$form = $client->getCrawler()->filter('form[name=activity_edit_form]')->form();
|
||||
$this->assertTrue($form->has('activity_edit_form[create_more]'));
|
||||
|
||||
/** @var \Symfony\Component\DomCrawler\Field\ChoiceFormField $project */
|
||||
$project = $form->get('activity_edit_form[project]');
|
||||
$options = $project->availableOptionValues();
|
||||
$selectedProject = $options[array_rand($options)];
|
||||
|
||||
$client->submit($form, [
|
||||
'activity_edit_form' => [
|
||||
'name' => 'Test create more',
|
||||
'create_more' => true,
|
||||
// TODO select random project
|
||||
'project' => $selectedProject,
|
||||
]
|
||||
]);
|
||||
$this->assertFalse($client->getResponse()->isRedirect());
|
||||
@@ -67,7 +81,7 @@ class ActivityControllerTest extends ControllerBaseTest
|
||||
$form = $client->getCrawler()->filter('form[name=activity_edit_form]')->form();
|
||||
$this->assertTrue($form->has('activity_edit_form[create_more]'));
|
||||
$this->assertEquals(1, $form->get('activity_edit_form[create_more]')->getValue());
|
||||
// TODO test that project is pre-selected
|
||||
$this->assertEquals($selectedProject, $form->get('activity_edit_form[project]')->getValue());
|
||||
}
|
||||
|
||||
public function testEditAction()
|
||||
@@ -80,7 +94,7 @@ class ActivityControllerTest extends ControllerBaseTest
|
||||
$client->submit($form, [
|
||||
'activity_edit_form' => ['name' => 'Test 2']
|
||||
]);
|
||||
$this->assertTrue($client->getResponse()->isRedirect());
|
||||
$this->assertIsRedirect($client, $this->createUrl('/admin/activity/'));
|
||||
$client->followRedirect();
|
||||
$this->assertHasDataTable($client);
|
||||
$this->request($client, '/admin/activity/1/edit');
|
||||
@@ -88,6 +102,48 @@ class ActivityControllerTest extends ControllerBaseTest
|
||||
$this->assertEquals('Test 2', $editForm->get('activity_edit_form[name]')->getValue());
|
||||
}
|
||||
|
||||
public function testDeleteAction()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$this->request($client, '/admin/activity/1/edit');
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
|
||||
$this->request($client, '/admin/activity/1/delete');
|
||||
$this->assertIsRedirect($client, $this->createUrl('/admin/activity/'));
|
||||
$client->followRedirect();
|
||||
$this->assertHasDataTable($client);
|
||||
$this->assertHasFlashSuccess($client);
|
||||
|
||||
$this->request($client, '/admin/activity/1/edit');
|
||||
$this->assertFalse($client->getResponse()->isSuccessful());
|
||||
}
|
||||
|
||||
public function testDeleteActionWithTimesheetEntries()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
|
||||
$em = $client->getContainer()->get('doctrine.orm.entity_manager');
|
||||
$fixture = new TimesheetFixtures();
|
||||
$fixture->setUser($this->getUserByRole($em, User::ROLE_USER));
|
||||
$fixture->setAmount(10);
|
||||
$this->importFixture($em, $fixture);
|
||||
|
||||
$this->request($client, '/admin/activity/1/delete');
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
|
||||
$form = $client->getCrawler()->filter('form[name=form]')->form();
|
||||
$this->assertStringEndsWith($this->createUrl('/admin/activity/1/delete'), $form->getUri());
|
||||
$client->submit($form);
|
||||
|
||||
$this->assertIsRedirect($client, $this->createUrl('/admin/activity/'));
|
||||
$client->followRedirect();
|
||||
$this->assertHasDataTable($client);
|
||||
$this->assertHasFlashSuccess($client);
|
||||
|
||||
$this->request($client, '/admin/activity/1/edit');
|
||||
$this->assertFalse($client->getResponse()->isSuccessful());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getValidationTestData
|
||||
*/
|
||||
|
||||
@@ -11,10 +11,13 @@ namespace App\Tests\Controller\Admin;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Tests\Controller\ControllerBaseTest;
|
||||
use App\Tests\DataFixtures\CustomerFixtures;
|
||||
use App\Tests\DataFixtures\TimesheetFixtures;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \App\Controller\Admin\CustomerController
|
||||
* @group integration
|
||||
* @group legacy
|
||||
*/
|
||||
class CustomerControllerTest extends ControllerBaseTest
|
||||
{
|
||||
@@ -30,4 +33,127 @@ class CustomerControllerTest extends ControllerBaseTest
|
||||
$this->assertAccessIsGranted($client, '/admin/customer/');
|
||||
$this->assertHasDataTable($client);
|
||||
}
|
||||
|
||||
public function testCreateAction()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$this->assertAccessIsGranted($client, '/admin/customer/create');
|
||||
$form = $client->getCrawler()->filter('form[name=customer_edit_form]')->form();
|
||||
$client->submit($form, [
|
||||
'customer_edit_form' => [
|
||||
'name' => 'Test Customer',
|
||||
]
|
||||
]);
|
||||
$this->assertIsRedirect($client, $this->createUrl('/admin/customer/'));
|
||||
$client->followRedirect();
|
||||
$this->assertHasDataTable($client);
|
||||
$this->assertHasFlashSuccess($client);
|
||||
}
|
||||
|
||||
public function testEditAction()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
$this->assertAccessIsGranted($client, '/admin/customer/1/edit');
|
||||
$form = $client->getCrawler()->filter('form[name=customer_edit_form]')->form();
|
||||
$this->assertFalse($form->has('customer_edit_form[create_more]'));
|
||||
$this->assertEquals('Test', $form->get('customer_edit_form[name]')->getValue());
|
||||
$client->submit($form, [
|
||||
'customer_edit_form' => [
|
||||
'name' => 'Test Customer 2'
|
||||
]
|
||||
]);
|
||||
$this->assertIsRedirect($client, $this->createUrl('/admin/customer/'));
|
||||
$client->followRedirect();
|
||||
$this->assertHasDataTable($client);
|
||||
$this->request($client, '/admin/customer/1/edit');
|
||||
$editForm = $client->getCrawler()->filter('form[name=customer_edit_form]')->form();
|
||||
$this->assertEquals('Test Customer 2', $editForm->get('customer_edit_form[name]')->getValue());
|
||||
}
|
||||
|
||||
public function testDeleteAction()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
|
||||
$em = $client->getContainer()->get('doctrine.orm.entity_manager');
|
||||
$fixture = new CustomerFixtures();
|
||||
$fixture->setAmount(1);
|
||||
$this->importFixture($em, $fixture);
|
||||
|
||||
$this->request($client, '/admin/customer/2/edit');
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
|
||||
$this->request($client, '/admin/customer/2/delete');
|
||||
$this->assertIsRedirect($client, $this->createUrl('/admin/customer/'));
|
||||
|
||||
$client->followRedirect();
|
||||
$this->assertHasDataTable($client);
|
||||
$this->assertHasFlashSuccess($client);
|
||||
|
||||
$this->request($client, '/admin/customer/2/edit');
|
||||
$this->assertFalse($client->getResponse()->isSuccessful());
|
||||
}
|
||||
|
||||
public function testDeleteActionWithTimesheetEntries()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
|
||||
$em = $client->getContainer()->get('doctrine.orm.entity_manager');
|
||||
$fixture = new TimesheetFixtures();
|
||||
$fixture->setUser($this->getUserByRole($em, User::ROLE_USER));
|
||||
$fixture->setAmount(10);
|
||||
$this->importFixture($em, $fixture);
|
||||
|
||||
$this->request($client, '/admin/customer/1/delete');
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
|
||||
$form = $client->getCrawler()->filter('form[name=form]')->form();
|
||||
$this->assertStringEndsWith($this->createUrl('/admin/customer/1/delete'), $form->getUri());
|
||||
$client->submit($form);
|
||||
|
||||
$this->assertIsRedirect($client, $this->createUrl('/admin/customer/'));
|
||||
$client->followRedirect();
|
||||
$this->assertHasDataTable($client);
|
||||
$this->assertHasFlashSuccess($client);
|
||||
|
||||
$this->request($client, '/admin/customer/1/edit');
|
||||
$this->assertFalse($client->getResponse()->isSuccessful());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getValidationTestData
|
||||
*/
|
||||
public function testValidationForCreateAction(array $formData, array $validationFields)
|
||||
{
|
||||
$this->assertFormHasValidationError(
|
||||
User::ROLE_ADMIN,
|
||||
'/admin/customer/create',
|
||||
'form[name=customer_edit_form]',
|
||||
$formData,
|
||||
$validationFields
|
||||
);
|
||||
}
|
||||
|
||||
public function getValidationTestData()
|
||||
{
|
||||
return [
|
||||
[
|
||||
[
|
||||
'customer_edit_form' => [
|
||||
'name' => '',
|
||||
'visible' => 3,
|
||||
'country' => '00', // TODO why does it not fail?
|
||||
'currency' => '00', // TODO why does it not fail?
|
||||
'timezone' => 'XXX'
|
||||
]
|
||||
],
|
||||
[
|
||||
'#customer_edit_form_name',
|
||||
'#customer_edit_form_visible',
|
||||
//'#customer_edit_form_country',
|
||||
//'#customer_edit_form_currency',
|
||||
'#customer_edit_form_timezone',
|
||||
]
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@ namespace App\Tests\Controller\Admin;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Tests\Controller\ControllerBaseTest;
|
||||
use App\Tests\DataFixtures\CustomerFixtures;
|
||||
use App\Tests\DataFixtures\ProjectFixtures;
|
||||
use App\Tests\DataFixtures\TimesheetFixtures;
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \App\Controller\Admin\ProjectController
|
||||
@@ -44,22 +47,35 @@ class ProjectControllerTest extends ControllerBaseTest
|
||||
'name' => 'Test 2',
|
||||
]
|
||||
]);
|
||||
$this->assertTrue($client->getResponse()->isRedirect());
|
||||
$this->assertIsRedirect($client, $this->createUrl('/admin/project/'));
|
||||
$client->followRedirect();
|
||||
$this->assertHasDataTable($client);
|
||||
$this->assertHasFlashSuccess($client);
|
||||
}
|
||||
|
||||
public function testCreateActionWithCreateMore()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
|
||||
$em = $client->getContainer()->get('doctrine.orm.entity_manager');
|
||||
$fixture = new CustomerFixtures();
|
||||
$fixture->setAmount(10);
|
||||
$this->importFixture($em, $fixture);
|
||||
|
||||
$this->assertAccessIsGranted($client, '/admin/project/create');
|
||||
$form = $client->getCrawler()->filter('form[name=project_edit_form]')->form();
|
||||
$this->assertTrue($form->has('project_edit_form[create_more]'));
|
||||
|
||||
/** @var \Symfony\Component\DomCrawler\Field\ChoiceFormField $customer */
|
||||
$customer = $form->get('project_edit_form[customer]');
|
||||
$options = $customer->availableOptionValues();
|
||||
$selectedCustomer = $options[array_rand($options)];
|
||||
|
||||
$client->submit($form, [
|
||||
'project_edit_form' => [
|
||||
'name' => 'Test create more',
|
||||
'create_more' => true,
|
||||
// TODO select random customer
|
||||
'customer' => $selectedCustomer
|
||||
]
|
||||
]);
|
||||
$this->assertFalse($client->getResponse()->isRedirect());
|
||||
@@ -67,7 +83,7 @@ class ProjectControllerTest extends ControllerBaseTest
|
||||
$form = $client->getCrawler()->filter('form[name=project_edit_form]')->form();
|
||||
$this->assertTrue($form->has('project_edit_form[create_more]'));
|
||||
$this->assertEquals(1, $form->get('project_edit_form[create_more]')->getValue());
|
||||
// TODO test that customer is pre-selected
|
||||
$this->assertEquals($selectedCustomer, $form->get('project_edit_form[customer]')->getValue());
|
||||
}
|
||||
|
||||
public function testEditAction()
|
||||
@@ -80,7 +96,7 @@ class ProjectControllerTest extends ControllerBaseTest
|
||||
$client->submit($form, [
|
||||
'project_edit_form' => ['name' => 'Test 2']
|
||||
]);
|
||||
$this->assertTrue($client->getResponse()->isRedirect());
|
||||
$this->assertIsRedirect($client, $this->createUrl('/admin/project/'));
|
||||
$client->followRedirect();
|
||||
$this->assertHasDataTable($client);
|
||||
$this->request($client, '/admin/project/1/edit');
|
||||
@@ -88,6 +104,54 @@ class ProjectControllerTest extends ControllerBaseTest
|
||||
$this->assertEquals('Test 2', $editForm->get('project_edit_form[name]')->getValue());
|
||||
}
|
||||
|
||||
public function testDeleteAction()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
|
||||
$em = $client->getContainer()->get('doctrine.orm.entity_manager');
|
||||
$fixture = new ProjectFixtures();
|
||||
$fixture->setAmount(1);
|
||||
$this->importFixture($em, $fixture);
|
||||
|
||||
$this->request($client, '/admin/project/2/edit');
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
|
||||
$this->request($client, '/admin/project/2/delete');
|
||||
$this->assertIsRedirect($client, $this->createUrl('/admin/project/'));
|
||||
$client->followRedirect();
|
||||
$this->assertHasDataTable($client);
|
||||
$this->assertHasFlashSuccess($client);
|
||||
|
||||
$this->request($client, '/admin/project/2/edit');
|
||||
$this->assertFalse($client->getResponse()->isSuccessful());
|
||||
}
|
||||
|
||||
public function testDeleteActionWithTimesheetEntries()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_ADMIN);
|
||||
|
||||
$em = $client->getContainer()->get('doctrine.orm.entity_manager');
|
||||
$fixture = new TimesheetFixtures();
|
||||
$fixture->setUser($this->getUserByRole($em, User::ROLE_USER));
|
||||
$fixture->setAmount(10);
|
||||
$this->importFixture($em, $fixture);
|
||||
|
||||
$this->request($client, '/admin/project/1/delete');
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
|
||||
$form = $client->getCrawler()->filter('form[name=form]')->form();
|
||||
$this->assertStringEndsWith($this->createUrl('/admin/project/1/delete'), $form->getUri());
|
||||
$client->submit($form);
|
||||
|
||||
$this->assertIsRedirect($client, $this->createUrl('/admin/project/'));
|
||||
$client->followRedirect();
|
||||
$this->assertHasDataTable($client);
|
||||
$this->assertHasFlashSuccess($client);
|
||||
|
||||
$this->request($client, '/admin/project/1/edit');
|
||||
$this->assertFalse($client->getResponse()->isSuccessful());
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider getValidationTestData
|
||||
*/
|
||||
|
||||
@@ -34,6 +34,7 @@ class UserControllerTest extends ControllerBaseTest
|
||||
|
||||
public function testCreateAction()
|
||||
{
|
||||
$username = '亚历山德拉';
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_SUPER_ADMIN);
|
||||
$this->assertAccessIsGranted($client, '/admin/user/create');
|
||||
$form = $client->getCrawler()->filter('form[name=user_create]')->form();
|
||||
@@ -41,15 +42,30 @@ class UserControllerTest extends ControllerBaseTest
|
||||
$this->assertNull($form->get('user_create[create_more]')->getValue());
|
||||
$client->submit($form, [
|
||||
'user_create' => [
|
||||
'username' => 'foobar@example.com',
|
||||
'username' => $username,
|
||||
'alias' => $username,
|
||||
'plainPassword' => ['first' => 'abcdef', 'second' => 'abcdef'],
|
||||
'email' => 'foobar@example.com',
|
||||
'enabled' => 1,
|
||||
]
|
||||
]);
|
||||
$this->assertTrue($client->getResponse()->isRedirect($this->createUrl('/profile/foobar@example.com/edit')));
|
||||
$this->assertIsRedirect($client, $this->createUrl('/profile/' . urlencode($username) . '/edit'));
|
||||
$client->followRedirect();
|
||||
// TODO test that this is the users profile
|
||||
|
||||
$tabs = $client->getCrawler()->filter('div.nav-tabs-custom ul.nav-tabs li');
|
||||
$this->assertEquals(4, $tabs->count());
|
||||
$expectedTabs = ['#charts', '#settings', '#password', '#roles'];
|
||||
$foundTabs = [];
|
||||
foreach ($tabs->filter('a') as $tab) {
|
||||
$name = $tab->getAttribute('href');
|
||||
if (in_array($name, $expectedTabs)) {
|
||||
$foundTabs[] = $name;
|
||||
}
|
||||
}
|
||||
$this->assertEmpty(array_diff($expectedTabs, $foundTabs));
|
||||
|
||||
$form = $client->getCrawler()->filter('form[name=user_edit]')->form();
|
||||
$this->assertEquals($username, $form->get('user_edit[alias]')->getValue());
|
||||
}
|
||||
|
||||
public function testCreateActionWithCreateMore()
|
||||
|
||||
@@ -258,4 +258,28 @@ abstract class ControllerBaseTest extends WebTestCase
|
||||
|
||||
return $em->getRepository(User::class)->findOneBy(['username' => $name]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Client $client
|
||||
*/
|
||||
protected function assertHasFlashSuccess(Client $client)
|
||||
{
|
||||
$node = $client->getCrawler()->filter('div.alert.alert-success.alert-dismissible');
|
||||
$this->assertNotEmpty($node->text());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Client $client
|
||||
* @param string $url
|
||||
*/
|
||||
protected function assertIsRedirect(Client $client, $url = null)
|
||||
{
|
||||
$this->assertTrue($client->getResponse()->isRedirect());
|
||||
if (null === $url) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->assertTrue($client->getResponse()->headers->has('Location'));
|
||||
$this->assertStringEndsWith($url, $client->getResponse()->headers->get('Location'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,18 @@ class ProfileControllerTest extends ControllerBaseTest
|
||||
$client = $this->getClientForAuthenticatedUser();
|
||||
$this->request($client, '/profile/' . UserFixtures::USERNAME_USER);
|
||||
$this->assertTrue($client->getResponse()->isSuccessful());
|
||||
|
||||
$tabs = $client->getCrawler()->filter('div.nav-tabs-custom ul.nav-tabs li');
|
||||
$this->assertEquals(4, $tabs->count());
|
||||
$expectedTabs = ['#charts', '#settings', '#password', '#preferences'];
|
||||
$foundTabs = [];
|
||||
foreach ($tabs->filter('a') as $tab) {
|
||||
$name = $tab->getAttribute('href');
|
||||
if (in_array($name, $expectedTabs)) {
|
||||
$foundTabs[] = $name;
|
||||
}
|
||||
}
|
||||
$this->assertEmpty(array_diff($expectedTabs, $foundTabs));
|
||||
}
|
||||
|
||||
public function testIndexActionWithDifferentUsername()
|
||||
|
||||
88
tests/DataFixtures/ActivityFixtures.php
Normal file
88
tests/DataFixtures/ActivityFixtures.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Tests\DataFixtures;
|
||||
|
||||
use App\Entity\Activity;
|
||||
use App\Entity\Project;
|
||||
use App\Entity\User;
|
||||
use Doctrine\Bundle\FixturesBundle\Fixture;
|
||||
use Doctrine\Common\Persistence\ObjectManager;
|
||||
use Faker\Factory;
|
||||
|
||||
/**
|
||||
* Defines the sample data to load in during controller tests.
|
||||
*/
|
||||
class ActivityFixtures extends Fixture
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $amount = 0;
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getAmount(): int
|
||||
{
|
||||
return $this->amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $amount
|
||||
* @return ActivityFixtures
|
||||
*/
|
||||
public function setAmount(int $amount)
|
||||
{
|
||||
$this->amount = $amount;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function load(ObjectManager $manager)
|
||||
{
|
||||
$projects = $this->getAllProjects($manager);
|
||||
$faker = Factory::create();
|
||||
|
||||
// random amount of timesheet entries for every user
|
||||
for ($i = 0; $i < $this->amount; $i++) {
|
||||
$visible = 0 != $i % 3;
|
||||
$entity = new Activity();
|
||||
$entity
|
||||
->setProject($projects[array_rand($projects)])
|
||||
->setName($faker->bs . ($visible ? '' : ' (x)'))
|
||||
->setComment($faker->text)
|
||||
->setVisible($visible)
|
||||
;
|
||||
|
||||
$manager->persist($entity);
|
||||
}
|
||||
|
||||
$manager->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ObjectManager $manager
|
||||
* @return Project[]
|
||||
*/
|
||||
protected function getAllProjects(ObjectManager $manager)
|
||||
{
|
||||
$all = [];
|
||||
/* @var User[] $entries */
|
||||
$entries = $manager->getRepository(Project::class)->findAll();
|
||||
foreach ($entries as $temp) {
|
||||
$all[$temp->getId()] = $temp;
|
||||
}
|
||||
|
||||
return $all;
|
||||
}
|
||||
}
|
||||
89
tests/DataFixtures/CustomerFixtures.php
Normal file
89
tests/DataFixtures/CustomerFixtures.php
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Tests\DataFixtures;
|
||||
|
||||
use App\Entity\Customer;
|
||||
use App\Entity\User;
|
||||
use Doctrine\Bundle\FixturesBundle\Fixture;
|
||||
use Doctrine\Common\Persistence\ObjectManager;
|
||||
use Faker\Factory;
|
||||
|
||||
/**
|
||||
* Defines the sample data to load in during controller tests.
|
||||
*/
|
||||
class CustomerFixtures extends Fixture
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $amount = 0;
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getAmount(): int
|
||||
{
|
||||
return $this->amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $amount
|
||||
* @return CustomerFixtures
|
||||
*/
|
||||
public function setAmount(int $amount)
|
||||
{
|
||||
$this->amount = $amount;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function load(ObjectManager $manager)
|
||||
{
|
||||
$faker = Factory::create();
|
||||
|
||||
for ($i = 0; $i < $this->amount; $i++) {
|
||||
$visible = 0 != $i % 3;
|
||||
$entity = new Customer();
|
||||
$entity
|
||||
->setCurrency($faker->currencyCode)
|
||||
->setName($faker->company . ($visible ? '' : ' (x)'))
|
||||
->setAddress($faker->address)
|
||||
->setComment($faker->text)
|
||||
->setNumber('C-' . $faker->ean8)
|
||||
->setCountry($faker->countryCode)
|
||||
->setTimezone($faker->timezone)
|
||||
->setVisible($visible)
|
||||
;
|
||||
|
||||
$manager->persist($entity);
|
||||
}
|
||||
|
||||
$manager->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ObjectManager $manager
|
||||
* @return Customer[]
|
||||
*/
|
||||
protected function getAllCustomers(ObjectManager $manager)
|
||||
{
|
||||
$all = [];
|
||||
/* @var User[] $entries */
|
||||
$entries = $manager->getRepository(Customer::class)->findAll();
|
||||
foreach ($entries as $temp) {
|
||||
$all[$temp->getId()] = $temp;
|
||||
}
|
||||
|
||||
return $all;
|
||||
}
|
||||
}
|
||||
88
tests/DataFixtures/ProjectFixtures.php
Normal file
88
tests/DataFixtures/ProjectFixtures.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Kimai time-tracking app.
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace App\Tests\DataFixtures;
|
||||
|
||||
use App\Entity\Customer;
|
||||
use App\Entity\Project;
|
||||
use App\Entity\User;
|
||||
use Doctrine\Bundle\FixturesBundle\Fixture;
|
||||
use Doctrine\Common\Persistence\ObjectManager;
|
||||
use Faker\Factory;
|
||||
|
||||
/**
|
||||
* Defines the sample data to load in during controller tests.
|
||||
*/
|
||||
class ProjectFixtures extends Fixture
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $amount = 0;
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getAmount(): int
|
||||
{
|
||||
return $this->amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $amount
|
||||
* @return ProjectFixtures
|
||||
*/
|
||||
public function setAmount(int $amount)
|
||||
{
|
||||
$this->amount = $amount;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function load(ObjectManager $manager)
|
||||
{
|
||||
$customers = $this->getAllCustomers($manager);
|
||||
$faker = Factory::create();
|
||||
|
||||
for ($i = 0; $i < $this->amount; $i++) {
|
||||
$visible = 0 != $i % 3;
|
||||
$entity = new Project();
|
||||
$entity
|
||||
->setName($faker->catchPhrase . ($visible ? '' : ' (x)'))
|
||||
->setBudget(rand(0, 10000))
|
||||
->setComment($faker->text)
|
||||
->setCustomer($customers[array_rand($customers)])
|
||||
->setVisible($visible)
|
||||
;
|
||||
|
||||
$manager->persist($entity);
|
||||
}
|
||||
|
||||
$manager->flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ObjectManager $manager
|
||||
* @return Customer[]
|
||||
*/
|
||||
protected function getAllCustomers(ObjectManager $manager)
|
||||
{
|
||||
$all = [];
|
||||
/* @var User[] $entries */
|
||||
$entries = $manager->getRepository(Customer::class)->findAll();
|
||||
foreach ($entries as $temp) {
|
||||
$all[$temp->getId()] = $temp;
|
||||
}
|
||||
|
||||
return $all;
|
||||
}
|
||||
}
|
||||
@@ -23,20 +23,21 @@ use Faker\Factory;
|
||||
class TimesheetFixtures extends Fixture
|
||||
{
|
||||
/**
|
||||
* @var
|
||||
* @var User
|
||||
*/
|
||||
protected $user;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $amount = 0;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $running = 0;
|
||||
|
||||
/**
|
||||
* @var Activity[]
|
||||
*/
|
||||
protected $activities = [];
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
@@ -74,16 +75,27 @@ class TimesheetFixtures extends Fixture
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Activity[] $activities
|
||||
*/
|
||||
public function setActivities(array $activities)
|
||||
{
|
||||
$this->activities = $activities;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function load(ObjectManager $manager)
|
||||
{
|
||||
$activities = $this->getAllActivities($manager);
|
||||
$activities = $this->activities;
|
||||
if (empty($activities)) {
|
||||
$activities = $this->getAllActivities($manager);
|
||||
}
|
||||
|
||||
$faker = Factory::create();
|
||||
$user = $this->user;
|
||||
|
||||
// random amount of timesheet entries for every user
|
||||
for ($i = 0; $i < $this->amount; $i++) {
|
||||
$entry = $this->createTimesheetEntry(
|
||||
$user,
|
||||
|
||||
@@ -531,6 +531,13 @@
|
||||
<source>label.roles</source>
|
||||
<target>Rolle</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="admin_user.delete_confirm">
|
||||
<source>admin_user.delete_confirm</source>
|
||||
<target>
|
||||
Momentan existieren für den Benutzer %user% insgesamt %records% Zeiteinträge, welche sich auf eine Gesamtdauer von %duration% belaufen.
|
||||
Diese Zeiteinträge werden ebenfalls mit gelöscht!
|
||||
</target>
|
||||
</trans-unit>
|
||||
|
||||
<!--
|
||||
ROLES
|
||||
|
||||
@@ -539,6 +539,13 @@
|
||||
<source>label.roles</source>
|
||||
<target>Role</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="admin_user.delete_confirm">
|
||||
<source>admin_user.delete_confirm</source>
|
||||
<target>
|
||||
Currently the user %user% has %records% time-records which count up to a total of %duration%.
|
||||
These time-records will be deleted as well!
|
||||
</target>
|
||||
</trans-unit>
|
||||
|
||||
<!--
|
||||
ROLES
|
||||
|
||||
@@ -81,7 +81,7 @@ bin/console doctrine:database:create
|
||||
bin/console doctrine:schema:create
|
||||
```
|
||||
|
||||
Lets bootstrap your environment by executing this commands (which is only available in dev environment):
|
||||
Lets bootstrap your environment by executing this command (which is only available in dev environment):
|
||||
```bash
|
||||
bin/console kimai:reset-dev
|
||||
```
|
||||
@@ -100,15 +100,15 @@ You can now login with these accounts:
|
||||
| susan_super | kitten | Super-Administrator |
|
||||
|
||||
Demo data can always be deleted by dropping the schema and re-creating it.
|
||||
ATTENTION - this will erase all your data:
|
||||
The `kimai:reset-dev` command can always be executed later on to reset your dev database and cache.
|
||||
|
||||
ATTENTION - if you don't want the test data, then erase it and create a empty schema:
|
||||
|
||||
```bash
|
||||
bin/console doctrine:schema:drop --force
|
||||
bin/console doctrine:schema:create
|
||||
```
|
||||
|
||||
The `kimai:reset-dev` command can always be executed later on to reset your dev database and cache.
|
||||
|
||||
There is no need to configure a virtual host in your web server to access the application for testing.
|
||||
Just use the built-in web server for your first tests:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user