Release 2.24 (#5097)
This commit is contained in:
@@ -26,7 +26,7 @@ use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
*/
|
||||
abstract class AbstractResetCommand extends Command
|
||||
{
|
||||
public function __construct(private string $kernelEnvironment)
|
||||
public function __construct(private readonly string $kernelEnvironment)
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
@@ -25,12 +25,13 @@ use Symfony\Component\Console\Input\ArrayInput;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||
|
||||
/**
|
||||
* Command used to execute all the basic application bootstrapping AFTER "composer install" was executed.
|
||||
*
|
||||
* This command is NOT used during runtime and only meant for developers and the CI processes for quality management.
|
||||
* This is one of the cases where I don't feel like it is necessary to add tests, so lets "cheat" with:
|
||||
* This is one of the cases where it is necessary to add tests:
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
#[AsCommand(name: 'kimai:reset:test', description: 'Resets the "test" environment')]
|
||||
@@ -38,6 +39,7 @@ final class ResetTestCommand extends AbstractResetCommand
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly UserPasswordHasherInterface $passwordHasher,
|
||||
string $kernelEnvironment
|
||||
)
|
||||
{
|
||||
@@ -80,227 +82,127 @@ final class ResetTestCommand extends AbstractResetCommand
|
||||
$project->setCustomer($customer);
|
||||
$this->entityManager->persist($project);
|
||||
|
||||
$user1 = new User();
|
||||
$user1->setPreferenceValue(UserPreference::HOURLY_RATE, 53);
|
||||
$user1->setAlias('Clara Haynes');
|
||||
$user1->setRegisteredAt(new \DateTime('2018-02-06 23:28:57'));
|
||||
$user1->setTitle('CFO');
|
||||
$user1->setAvatar('https://www.gravatar.com/avatar/00000000000000000000000000000000?d=monsterid&f=y');
|
||||
$user1->setEnabled(true);
|
||||
$user1->setRoles(['ROLE_CUSTOMER']);
|
||||
$user1->setUserIdentifier('clara_customer');
|
||||
$user1->setEmail('clara_customer@example.com');
|
||||
$token1 = new AccessToken($user1, UserFixtures::DEFAULT_API_TOKEN . '_customer');
|
||||
$token1->setName('Test fixture');
|
||||
|
||||
$user2 = new User();
|
||||
$user2->setPreferenceValue(UserPreference::HOURLY_RATE, 82);
|
||||
$user2->setAlias('John Doe');
|
||||
$user2->setRegisteredAt(new \DateTime('2018-02-06 23:28:57'));
|
||||
$user2->setTitle('Developer');
|
||||
$user2->setAvatar('https://www.gravatar.com/avatar/00000000000000000000000000000000?d=retro&f=y');
|
||||
$user2->setEnabled(true);
|
||||
$user2->setRoles(['ROLE_USER']);
|
||||
$user2->setUserIdentifier('john_user');
|
||||
$user2->setEmail('john_user@example.com');
|
||||
$token2 = new AccessToken($user2, UserFixtures::DEFAULT_API_TOKEN . '_user');
|
||||
$token2->setName('Test fixture');
|
||||
|
||||
$user3 = new User();
|
||||
$user3->setPreferenceValue(UserPreference::HOURLY_RATE, 35);
|
||||
$user3->setAlias('Chris Deactive');
|
||||
$user3->setRegisteredAt(new \DateTime('2018-02-06 23:28:57'));
|
||||
$user3->setTitle('Developer (left company)');
|
||||
$user3->setAvatar('https://www.gravatar.com/avatar/00000000000000000000000000000000?d=retro&f=y');
|
||||
$user3->setEnabled(false);
|
||||
$user3->setRoles(['ROLE_USER']);
|
||||
$user3->setUserIdentifier('chris_user');
|
||||
$user3->setEmail('chris_user@example.com');
|
||||
$token3 = new AccessToken($user3, UserFixtures::DEFAULT_API_TOKEN . '_inactive');
|
||||
$token3->setName('Test fixture');
|
||||
|
||||
$user4 = new User();
|
||||
$user4->setPreferenceValue(UserPreference::HOURLY_RATE, 35);
|
||||
$user4->setAlias('Tony Maier');
|
||||
$user4->setRegisteredAt(new \DateTime('2018-02-06 23:28:57'));
|
||||
$user4->setTitle('Head of Development');
|
||||
$user4->setAvatar('https://en.gravatar.com/userimage/3533186/bf2163b1dd23f3107a028af0195624e9.jpeg');
|
||||
$user4->setEnabled(true);
|
||||
$user4->setRoles(['ROLE_TEAMLEAD']);
|
||||
$user4->setUserIdentifier('tony_teamlead');
|
||||
$user4->setEmail('tony_teamlead@example.com');
|
||||
$token4 = new AccessToken($user4, UserFixtures::DEFAULT_API_TOKEN . '_teamlead');
|
||||
$token4->setName('Test fixture');
|
||||
|
||||
$user5 = new User();
|
||||
$user5->setPreferenceValue(UserPreference::HOURLY_RATE, 81);
|
||||
$user5->setAlias('Anna Smith');
|
||||
$user5->setRegisteredAt(new \DateTime('2018-02-06 23:28:57'));
|
||||
$user5->setTitle('Administrator');
|
||||
$user5->setEnabled(true);
|
||||
$user5->setRoles(['ROLE_ADMIN']);
|
||||
$user5->setUserIdentifier('anna_admin');
|
||||
$user5->setEmail('anna_admin@example.com');
|
||||
$token5 = new AccessToken($user5, UserFixtures::DEFAULT_API_TOKEN . '_admin');
|
||||
$token5->setName('Test fixture');
|
||||
|
||||
$user6 = new User();
|
||||
$user6->setPreferenceValue(UserPreference::HOURLY_RATE, 46);
|
||||
$user6->setRegisteredAt(new \DateTime('2018-02-06 23:28:57'));
|
||||
$user6->setTitle('Super Administrator');
|
||||
$user6->setAvatar('/bundles/avanzuadmintheme/img/avatar.png');
|
||||
$user6->setEnabled(true);
|
||||
$user6->setRoles(['ROLE_SUPER_ADMIN']);
|
||||
$user6->setUserIdentifier('susan_super');
|
||||
$user6->setEmail('susan_super@example.com');
|
||||
$token6 = new AccessToken($user6, UserFixtures::DEFAULT_API_TOKEN . '_super');
|
||||
$token6->setName('Test fixture');
|
||||
|
||||
$user7 = new User();
|
||||
$user7->setAlias('Test User 1');
|
||||
$user7->setTitle('Quality Tester 1');
|
||||
$user7->setEnabled(true);
|
||||
$user7->setRoles(['ROLE_USER']);
|
||||
$user7->setUserIdentifier('test_user_1');
|
||||
$user7->setEmail('test_user_1@example.com');
|
||||
$token7 = new AccessToken($user7, UserFixtures::DEFAULT_API_TOKEN . '_qa1');
|
||||
$token7->setName('Test fixture');
|
||||
|
||||
$user8 = new User();
|
||||
$user8->setAlias('Test User 2');
|
||||
$user8->setTitle('Quality Tester 2');
|
||||
$user8->setEnabled(true);
|
||||
$user8->setRoles(['ROLE_USER']);
|
||||
$user8->setUserIdentifier('test_user_2');
|
||||
$user8->setEmail('test_user_2@example.com');
|
||||
$token8 = new AccessToken($user8, UserFixtures::DEFAULT_API_TOKEN . '_qa2');
|
||||
$token8->setName('Test fixture');
|
||||
|
||||
/** @var array<int, array{0: User, 1: AccessToken}> $users */
|
||||
$users = [
|
||||
// 0=id, 1=hourly rate, 2=Alias, 3=registration date, 4=title, 5=avatar, 6=enabled, 7=password, 8=roles, 9=username, 10=username canonical, 11=email, 12=email canonical, 13=salt, 14=last login, 15=confirmation token, 16=password requested at, 17=api_token
|
||||
[
|
||||
1,
|
||||
53,
|
||||
'Clara Haynes',
|
||||
'2018-02-06 23:28:57',
|
||||
'CFO',
|
||||
'https://www.gravatar.com/avatar/00000000000000000000000000000000?d=monsterid&f=y',
|
||||
1,
|
||||
'$2y$04$kKBYJ8sKCOhhakCjm9sCp.TQdwLTS1FPkPiWn2KBmaCA7xFL0NA42',
|
||||
['ROLE_CUSTOMER'],
|
||||
'clara_customer',
|
||||
'clara_customer',
|
||||
'clara_customer@example.com',
|
||||
'clara_customer@example.com',
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
'$2y$13$X8/msijlFUgvRaiGLCJP/ep2hRyjpd.TSNz3cuutZLp05FpuBsYfO',
|
||||
UserFixtures::DEFAULT_API_TOKEN . '_customer',
|
||||
],
|
||||
[
|
||||
2,
|
||||
82,
|
||||
'John Doe',
|
||||
'2018-02-06 23:28:57',
|
||||
'Developer',
|
||||
'https://www.gravatar.com/avatar/00000000000000000000000000000000?d=retro&f=y',
|
||||
1,
|
||||
'$2y$04$36P/xyhP6FbnfFYbXy7V0.ioSe8HjMlJQFYnlIzz2T6Agfi8ob6jK',
|
||||
[],
|
||||
'john_user',
|
||||
'john_user',
|
||||
'john_user@example.com',
|
||||
'john_user@example.com',
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
'$2y$13$X8/msijlFUgvRaiGLCJP/ep2hRyjpd.TSNz3cuutZLp05FpuBsYfO',
|
||||
UserFixtures::DEFAULT_API_TOKEN . '_user',
|
||||
],
|
||||
[
|
||||
3,
|
||||
35,
|
||||
'Chris Deactive',
|
||||
'2018-02-06 23:28:57',
|
||||
'Developer (left company)',
|
||||
'https://www.gravatar.com/avatar/00000000000000000000000000000000?d=retro&f=y',
|
||||
0,
|
||||
'$2y$04$MLtQBZ9JLzWu1Y01QnNjsuoLm8qC9XRkpUywf6DIbpd9OAL1mEcCi',
|
||||
[],
|
||||
'chris_user',
|
||||
'chris_user',
|
||||
'chris_user@example.com',
|
||||
'chris_user@example.com',
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
'$2y$13$X8/msijlFUgvRaiGLCJP/ep2hRyjpd.TSNz3cuutZLp05FpuBsYfO',
|
||||
UserFixtures::DEFAULT_API_TOKEN . '_inactive',
|
||||
],
|
||||
[
|
||||
4,
|
||||
35,
|
||||
'Tony Maier',
|
||||
'2018-02-06 23:28:57',
|
||||
'Head of Development',
|
||||
'https://en.gravatar.com/userimage/3533186/bf2163b1dd23f3107a028af0195624e9.jpeg',
|
||||
1,
|
||||
'$2y$04$rqxiiExfUVzIYRVL2x4JJumQWNPIG6PazXwrSJm/VQFEesR08Uj5i',
|
||||
['ROLE_TEAMLEAD'],
|
||||
'tony_teamlead',
|
||||
'tony_teamlead',
|
||||
'tony_teamlead@example.com',
|
||||
'tony_teamlead@example.com',
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
'$2y$13$X8/msijlFUgvRaiGLCJP/ep2hRyjpd.TSNz3cuutZLp05FpuBsYfO',
|
||||
UserFixtures::DEFAULT_API_TOKEN . '_teamlead',
|
||||
],
|
||||
[
|
||||
5,
|
||||
81,
|
||||
'Anna Smith',
|
||||
'2018-02-06 23:28:57',
|
||||
'Administrator',
|
||||
null,
|
||||
1,
|
||||
'$2y$04$ct/rVb.naDzYZECnvfTJ2uns/zPHv8.8KcunhTjYFwWQeg1dywI8G',
|
||||
['ROLE_ADMIN'],
|
||||
'anna_admin',
|
||||
'anna_admin',
|
||||
'anna_admin@example.com',
|
||||
'anna_admin@example.com',
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
'$2y$13$X8/msijlFUgvRaiGLCJP/ep2hRyjpd.TSNz3cuutZLp05FpuBsYfO',
|
||||
UserFixtures::DEFAULT_API_TOKEN . '_admin',
|
||||
],
|
||||
[
|
||||
6,
|
||||
46,
|
||||
null,
|
||||
'2018-02-06 23:28:57',
|
||||
'Super Administrator',
|
||||
'/bundles/avanzuadmintheme/img/avatar.png',
|
||||
1,
|
||||
'$2y$04$kuhEEPw/CBMYc3x7SOv27eC1hQSmrtFvgJI2ULRuJeddAVDyrPKJ2',
|
||||
['ROLE_SUPER_ADMIN'],
|
||||
'susan_super',
|
||||
'susan_super',
|
||||
'susan_super@example.com',
|
||||
'susan_super@example.com',
|
||||
null,
|
||||
'2020-04-14 09:50:38',
|
||||
null,
|
||||
null,
|
||||
'$2y$13$X8/msijlFUgvRaiGLCJP/ep2hRyjpd.TSNz3cuutZLp05FpuBsYfO',
|
||||
UserFixtures::DEFAULT_API_TOKEN . '_super',
|
||||
],
|
||||
[
|
||||
7,
|
||||
null,
|
||||
'Test User 1',
|
||||
null,
|
||||
'Quality Tester 1',
|
||||
null,
|
||||
1,
|
||||
'$2y$04$kuhEEPw/CBMYc3x7SOv27eC1hQSmrtFvgJI2ULRuJeddAVDyrPKJ2',
|
||||
[],
|
||||
'test_user_1',
|
||||
'test_user_1',
|
||||
'test_user_1@example.com',
|
||||
'test_user_1@example.com',
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
'$2y$13$X8/msijlFUgvRaiGLCJP/ep2hRyjpd.TSNz3cuutZLp05FpuBsYfO',
|
||||
UserFixtures::DEFAULT_API_TOKEN . '_qa1',
|
||||
],
|
||||
[
|
||||
8,
|
||||
null,
|
||||
'Test User 2',
|
||||
null,
|
||||
'Quality Tester 2',
|
||||
null,
|
||||
1,
|
||||
'$2y$04$kuhEEPw/CBMYc3x7SOv27eC1hQSmrtFvgJI2ULRuJeddAVDyrPKJ2',
|
||||
[],
|
||||
'test_user_2',
|
||||
'test_user_2',
|
||||
'test_user_2@example.com',
|
||||
'test_user_2@example.com',
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
'$2y$13$X8/msijlFUgvRaiGLCJP/ep2hRyjpd.TSNz3cuutZLp05FpuBsYfO',
|
||||
UserFixtures::DEFAULT_API_TOKEN . '_qa2',
|
||||
],
|
||||
[$user1, $token1],
|
||||
[$user2, $token2],
|
||||
[$user3, $token3],
|
||||
[$user4, $token4],
|
||||
[$user5, $token5],
|
||||
[$user6, $token6],
|
||||
[$user7, $token7],
|
||||
[$user8, $token8],
|
||||
];
|
||||
|
||||
$userEntities = [];
|
||||
foreach ($users as $userConf) {
|
||||
$user = new User();
|
||||
foreach ($users as $items) {
|
||||
$user = $items[0];
|
||||
$user->setPassword($this->passwordHasher->hashPassword($user, UserFixtures::DEFAULT_PASSWORD));
|
||||
$user->setApiToken($this->passwordHasher->hashPassword($user, UserFixtures::DEFAULT_API_TOKEN));
|
||||
foreach (User::WIZARDS as $wizard) {
|
||||
$user->setWizardAsSeen($wizard);
|
||||
}
|
||||
if ($userConf[1] !== null) {
|
||||
$user->setPreferenceValue(UserPreference::HOURLY_RATE, $userConf[1]);
|
||||
}
|
||||
if ($userConf[2] !== null) {
|
||||
$user->setAlias($userConf[2]);
|
||||
}
|
||||
if ($userConf[3] !== null) {
|
||||
$user->setRegisteredAt(new \DateTime($userConf[3]));
|
||||
}
|
||||
if ($userConf[4] !== null) {
|
||||
$user->setTitle($userConf[4]);
|
||||
}
|
||||
if ($userConf[5] !== null) {
|
||||
$user->setAvatar($userConf[5]);
|
||||
}
|
||||
if ($userConf[6] !== null) {
|
||||
$user->setEnabled((bool) $userConf[6]);
|
||||
}
|
||||
$user->setPassword($userConf[7]);
|
||||
if ($userConf[8] !== null && !empty($userConf[8])) {
|
||||
$user->setRoles($userConf[8]);
|
||||
} else {
|
||||
$user->setRoles(['ROLE_USER']);
|
||||
}
|
||||
$user->setUserIdentifier($userConf[9]);
|
||||
if ($userConf[10] !== null) {
|
||||
// removed field: UsernameCanonical
|
||||
}
|
||||
if ($userConf[11] !== null) {
|
||||
$user->setEmail($userConf[11]);
|
||||
}
|
||||
if ($userConf[12] !== null) {
|
||||
// removed field: EmailCanonical
|
||||
}
|
||||
if ($userConf[17] !== null) {
|
||||
$user->setApiToken($userConf[17]);
|
||||
}
|
||||
$this->entityManager->persist($user);
|
||||
|
||||
$accessToken = new AccessToken($user, $userConf[18]);
|
||||
$accessToken->setName('Test fixture');
|
||||
$accessToken = $items[1];
|
||||
$this->entityManager->persist($accessToken);
|
||||
|
||||
$this->entityManager->persist($user);
|
||||
$userEntities[] = $user;
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ final class UserLoginLinkCommand extends Command
|
||||
parent::__construct();
|
||||
$this->addArgument('email', InputArgument::REQUIRED, 'The email of the user');
|
||||
$this->addOption('password-reset', null, InputOption::VALUE_NONE, 'Whether the user needs to reset the password afterwards');
|
||||
$this->addOption('all-auth', null, InputOption::VALUE_NONE, 'Ignore that the user is using an external authentication system');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
@@ -63,7 +64,7 @@ final class UserLoginLinkCommand extends Command
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
if (!$user->isInternalUser()) {
|
||||
if (!$user->isInternalUser() && !$input->getOption('all-auth')) {
|
||||
$io->error('User does not use internal login');
|
||||
|
||||
return Command::FAILURE;
|
||||
|
||||
@@ -33,10 +33,7 @@ final class SystemConfiguration
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an array item to a given value using "dot" notation.
|
||||
* If no key is given to the method, the entire array will be replaced.
|
||||
*
|
||||
* @internal
|
||||
* Set a new or replace an existing system configuration.
|
||||
*/
|
||||
public function set(string $key, mixed $value): void
|
||||
{
|
||||
@@ -456,6 +453,11 @@ final class SystemConfiguration
|
||||
return $this->getIncrement('quick_entry.recent_activities', 5, 0);
|
||||
}
|
||||
|
||||
public function isBreakTimeEnabled(): bool
|
||||
{
|
||||
return (bool) $this->find('timesheet.rules.break_time_active');
|
||||
}
|
||||
|
||||
// ========== Company configurations ==========
|
||||
|
||||
public function getFinancialYearStart(): ?string
|
||||
|
||||
@@ -17,11 +17,11 @@ class Constants
|
||||
/**
|
||||
* The current release version
|
||||
*/
|
||||
public const VERSION = '2.23.0';
|
||||
public const VERSION = '2.24.0';
|
||||
/**
|
||||
* The current release: major * 10000 + minor * 100 + patch
|
||||
*/
|
||||
public const VERSION_ID = 22300;
|
||||
public const VERSION_ID = 22400;
|
||||
/**
|
||||
* The software name
|
||||
*/
|
||||
|
||||
@@ -85,6 +85,10 @@ abstract class TimesheetAbstractController extends AbstractController
|
||||
$table->addColumn('endtime', ['class' => 'd-none d-sm-table-cell text-center text-nowrap', 'orderBy' => 'end']);
|
||||
}
|
||||
|
||||
if ($this->configuration->isBreakTimeEnabled()) {
|
||||
$table->addColumn('break', ['class' => 'text-end text-nowrap']);
|
||||
}
|
||||
|
||||
$table->addColumn('duration', ['class' => 'text-end text-nowrap']);
|
||||
|
||||
if ($canSeeRate) {
|
||||
|
||||
@@ -29,8 +29,8 @@ use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||
*/
|
||||
final class UserFixtures extends Fixture implements FixtureGroupInterface
|
||||
{
|
||||
public const DEFAULT_PASSWORD = 'kitten';
|
||||
public const DEFAULT_API_TOKEN = 'api_kitten';
|
||||
public const DEFAULT_PASSWORD = 'password';
|
||||
public const DEFAULT_API_TOKEN = 'token';
|
||||
public const DEFAULT_AVATAR = 'https://www.gravatar.com/avatar/00000000000000000000000000000000?d=retro&f=y';
|
||||
|
||||
public const USERNAME_USER = 'john_user';
|
||||
@@ -43,7 +43,7 @@ final class UserFixtures extends Fixture implements FixtureGroupInterface
|
||||
public const MIN_RATE = 30;
|
||||
public const MAX_RATE = 120;
|
||||
|
||||
public function __construct(private UserPasswordHasherInterface $passwordHasher)
|
||||
public function __construct(private readonly UserPasswordHasherInterface $passwordHasher)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -65,32 +65,8 @@ final class UserFixtures extends Fixture implements FixtureGroupInterface
|
||||
{
|
||||
$allUsers = $this->getUserDefinition();
|
||||
foreach ($allUsers as $userData) {
|
||||
$user = new User();
|
||||
$user->setAlias($userData[0]);
|
||||
$user->setTitle($userData[1]);
|
||||
$user->setUserIdentifier($userData[2]);
|
||||
$user->setEmail($userData[3]);
|
||||
$user->setRoles([$userData[4]]);
|
||||
$user->setAvatar($userData[5]);
|
||||
$user->setEnabled($userData[6]);
|
||||
$user->setPassword($this->passwordHasher->hashPassword($user, $userData[8]));
|
||||
$user->setApiToken($this->passwordHasher->hashPassword($user, $userData[9]));
|
||||
$manager->persist($user);
|
||||
|
||||
$prefs = $this->getUserPreferences($user, $userData[7]);
|
||||
$user->setPreferences($prefs);
|
||||
// better to be able to test the wizard in demo installations
|
||||
/*
|
||||
foreach (User::WIZARDS as $wizard) {
|
||||
$user->setWizardAsSeen($wizard);
|
||||
}
|
||||
*/
|
||||
$manager->persist($prefs[0]);
|
||||
$manager->persist($prefs[1]);
|
||||
|
||||
$accessToken = new AccessToken($user, $userData[10]);
|
||||
$accessToken->setName('Test fixture');
|
||||
$manager->persist($accessToken);
|
||||
$manager->persist($userData[0]);
|
||||
$manager->persist($userData[1]);
|
||||
}
|
||||
|
||||
$manager->flush();
|
||||
@@ -100,7 +76,7 @@ final class UserFixtures extends Fixture implements FixtureGroupInterface
|
||||
/**
|
||||
* @param User $user
|
||||
* @param string|null $timezone
|
||||
* @return array
|
||||
* @return array<UserPreference>
|
||||
*/
|
||||
private function getUserPreferences(User $user, string $timezone = null): array
|
||||
{
|
||||
@@ -162,141 +138,158 @@ final class UserFixtures extends Fixture implements FixtureGroupInterface
|
||||
$manager->clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Do NOT set wizard as seen here, because they should be visible in the demo installation.
|
||||
*
|
||||
* @return array<int, array{0: User, 1: AccessToken}>
|
||||
*/
|
||||
private function getUserDefinition(): array
|
||||
{
|
||||
// alias = $userData[0]
|
||||
// title = $userData[1]
|
||||
// username = $userData[2]
|
||||
// email = $userData[3]
|
||||
// roles = [$userData[4]]
|
||||
// avatar = $userData[5]
|
||||
// enabled = $userData[6]
|
||||
// timezone = $userData[7]
|
||||
// password = $userData[8]
|
||||
// api old = $userData[9]
|
||||
// api new = $userData[10]
|
||||
$all = [];
|
||||
|
||||
return [
|
||||
[
|
||||
'John Doe',
|
||||
'Developer',
|
||||
self::USERNAME_USER,
|
||||
'john_user@example.com',
|
||||
User::ROLE_USER,
|
||||
self::DEFAULT_AVATAR,
|
||||
true,
|
||||
'America/Vancouver',
|
||||
self::DEFAULT_PASSWORD,
|
||||
self::DEFAULT_API_TOKEN,
|
||||
self::DEFAULT_API_TOKEN . '_john',
|
||||
],
|
||||
[
|
||||
'John Doe',
|
||||
'Developer',
|
||||
'user',
|
||||
'user@example.com',
|
||||
User::ROLE_USER,
|
||||
self::DEFAULT_AVATAR,
|
||||
true,
|
||||
'America/Vancouver',
|
||||
'password',
|
||||
'password',
|
||||
self::DEFAULT_API_TOKEN . '_user',
|
||||
],
|
||||
// inactive user to test login
|
||||
[
|
||||
'Chris Deactive',
|
||||
'Developer (left company)',
|
||||
'chris_user',
|
||||
'chris_user@example.com',
|
||||
User::ROLE_USER,
|
||||
self::DEFAULT_AVATAR,
|
||||
false,
|
||||
'Australia/Sydney',
|
||||
self::DEFAULT_PASSWORD,
|
||||
self::DEFAULT_API_TOKEN,
|
||||
self::DEFAULT_API_TOKEN . '_inactive',
|
||||
],
|
||||
[
|
||||
'Tony Maier',
|
||||
'Head of Sales',
|
||||
self::USERNAME_TEAMLEAD,
|
||||
'tony_teamlead@example.com',
|
||||
User::ROLE_TEAMLEAD,
|
||||
'https://en.gravatar.com/userimage/3533186/bf2163b1dd23f3107a028af0195624e9.jpeg',
|
||||
true,
|
||||
'Asia/Bangkok',
|
||||
self::DEFAULT_PASSWORD,
|
||||
self::DEFAULT_API_TOKEN,
|
||||
self::DEFAULT_API_TOKEN . '_teamlead',
|
||||
],
|
||||
[
|
||||
'Tony Maier',
|
||||
'Head of Sales',
|
||||
'teamlead',
|
||||
'teamlead@example.com',
|
||||
User::ROLE_TEAMLEAD,
|
||||
'https://en.gravatar.com/userimage/3533186/bf2163b1dd23f3107a028af0195624e9.jpeg',
|
||||
true,
|
||||
'Asia/Bangkok',
|
||||
'password',
|
||||
'password',
|
||||
self::DEFAULT_API_TOKEN . '_tony',
|
||||
],
|
||||
// no avatar to test default image macro
|
||||
[
|
||||
'Anna Smith',
|
||||
'Administrator',
|
||||
self::USERNAME_ADMIN,
|
||||
'anna_admin@example.com',
|
||||
User::ROLE_ADMIN,
|
||||
null,
|
||||
true,
|
||||
'Europe/London',
|
||||
self::DEFAULT_PASSWORD,
|
||||
self::DEFAULT_API_TOKEN,
|
||||
self::DEFAULT_API_TOKEN . '_anna',
|
||||
],
|
||||
[
|
||||
'Anna Smith',
|
||||
'Administrator',
|
||||
'administrator',
|
||||
'administrator@example.com',
|
||||
User::ROLE_ADMIN,
|
||||
null,
|
||||
true,
|
||||
'Europe/London',
|
||||
'password',
|
||||
'password',
|
||||
self::DEFAULT_API_TOKEN . '_admin',
|
||||
],
|
||||
// no alias to test twig username macro
|
||||
[
|
||||
null,
|
||||
'Super Administrator',
|
||||
self::USERNAME_SUPER_ADMIN,
|
||||
'susan_super@example.com',
|
||||
User::ROLE_SUPER_ADMIN,
|
||||
'/touch-icon-192x192.png',
|
||||
true,
|
||||
'Europe/Berlin',
|
||||
self::DEFAULT_PASSWORD,
|
||||
self::DEFAULT_API_TOKEN,
|
||||
self::DEFAULT_API_TOKEN . '_susan',
|
||||
],
|
||||
[
|
||||
null,
|
||||
'Super Administrator',
|
||||
'super_admin',
|
||||
'super_admin@example.com',
|
||||
User::ROLE_SUPER_ADMIN,
|
||||
'/touch-icon-192x192.png',
|
||||
true,
|
||||
'Europe/Berlin',
|
||||
'password',
|
||||
'password',
|
||||
self::DEFAULT_API_TOKEN . '_super',
|
||||
],
|
||||
];
|
||||
$user = new User();
|
||||
$user->setAlias('John Doe');
|
||||
$user->setTitle('Developer');
|
||||
$user->setUserIdentifier(self::USERNAME_USER);
|
||||
$user->setEmail('john_user@example.com');
|
||||
$user->setRoles([User::ROLE_USER]);
|
||||
$user->setAvatar(self::DEFAULT_AVATAR);
|
||||
$user->setEnabled(true);
|
||||
$prefs = $this->getUserPreferences($user, 'America/Vancouver');
|
||||
$user->setPreferences($prefs);
|
||||
$user->setPassword($this->passwordHasher->hashPassword($user, self::DEFAULT_PASSWORD));
|
||||
$user->setApiToken($this->passwordHasher->hashPassword($user, self::DEFAULT_API_TOKEN));
|
||||
$token = new AccessToken($user, self::DEFAULT_API_TOKEN . '_john');
|
||||
$token->setName('User fixture');
|
||||
$all[] = [$user, $token];
|
||||
|
||||
$user = new User();
|
||||
$user->setAlias('John Doe');
|
||||
$user->setTitle('Developer');
|
||||
$user->setUserIdentifier('user');
|
||||
$user->setEmail('user@example.com');
|
||||
$user->setRoles([User::ROLE_USER]);
|
||||
$user->setAvatar(self::DEFAULT_AVATAR);
|
||||
$user->setEnabled(true);
|
||||
$prefs = $this->getUserPreferences($user, 'America/Vancouver');
|
||||
$user->setPreferences($prefs);
|
||||
$user->setPassword($this->passwordHasher->hashPassword($user, self::DEFAULT_PASSWORD));
|
||||
$user->setApiToken($this->passwordHasher->hashPassword($user, self::DEFAULT_API_TOKEN));
|
||||
$token = new AccessToken($user, self::DEFAULT_API_TOKEN . '_user');
|
||||
$token->setName('User fixture');
|
||||
$all[] = [$user, $token];
|
||||
|
||||
// inactive user to test login
|
||||
$user = new User();
|
||||
$user->setAlias('Chris Deactive');
|
||||
$user->setTitle('Developer (left company)');
|
||||
$user->setUserIdentifier('chris_user');
|
||||
$user->setEmail('chris_user@example.com');
|
||||
$user->setRoles([User::ROLE_USER]);
|
||||
$user->setAvatar(self::DEFAULT_AVATAR);
|
||||
$user->setEnabled(false);
|
||||
$prefs = $this->getUserPreferences($user, 'Australia/Sydney');
|
||||
$user->setPreferences($prefs);
|
||||
$user->setPassword($this->passwordHasher->hashPassword($user, self::DEFAULT_PASSWORD));
|
||||
$user->setApiToken($this->passwordHasher->hashPassword($user, self::DEFAULT_API_TOKEN));
|
||||
$token = new AccessToken($user, self::DEFAULT_API_TOKEN . '_inactive');
|
||||
$token->setName('User fixture');
|
||||
$all[] = [$user, $token];
|
||||
|
||||
$user = new User();
|
||||
$user->setAlias('Tony Maier');
|
||||
$user->setTitle('Head of Sales');
|
||||
$user->setUserIdentifier(self::USERNAME_TEAMLEAD);
|
||||
$user->setEmail('tony_teamlead@example.com');
|
||||
$user->setRoles([User::ROLE_TEAMLEAD]);
|
||||
$user->setAvatar('https://en.gravatar.com/userimage/3533186/bf2163b1dd23f3107a028af0195624e9.jpeg');
|
||||
$user->setEnabled(true);
|
||||
$prefs = $this->getUserPreferences($user, 'Asia/Bangkok');
|
||||
$user->setPreferences($prefs);
|
||||
$user->setPassword($this->passwordHasher->hashPassword($user, self::DEFAULT_PASSWORD));
|
||||
$user->setApiToken($this->passwordHasher->hashPassword($user, self::DEFAULT_API_TOKEN));
|
||||
$token = new AccessToken($user, self::DEFAULT_API_TOKEN . '_teamlead');
|
||||
$token->setName('User fixture');
|
||||
$all[] = [$user, $token];
|
||||
|
||||
$user = new User();
|
||||
$user->setAlias('Tony Maier');
|
||||
$user->setTitle('Head of Sales');
|
||||
$user->setUserIdentifier('teamlead');
|
||||
$user->setEmail('teamlead@example.com');
|
||||
$user->setRoles([User::ROLE_TEAMLEAD]);
|
||||
$user->setAvatar('https://en.gravatar.com/userimage/3533186/bf2163b1dd23f3107a028af0195624e9.jpeg');
|
||||
$user->setEnabled(true);
|
||||
$prefs = $this->getUserPreferences($user, 'Asia/Bangkok');
|
||||
$user->setPreferences($prefs);
|
||||
$user->setPassword($this->passwordHasher->hashPassword($user, self::DEFAULT_PASSWORD));
|
||||
$user->setApiToken($this->passwordHasher->hashPassword($user, self::DEFAULT_API_TOKEN));
|
||||
$token = new AccessToken($user, self::DEFAULT_API_TOKEN . '_tony');
|
||||
$token->setName('User fixture');
|
||||
$all[] = [$user, $token];
|
||||
|
||||
// no avatar to test default image macro
|
||||
$user = new User();
|
||||
$user->setAlias('Anna Smith');
|
||||
$user->setTitle('Administrator');
|
||||
$user->setUserIdentifier(self::USERNAME_ADMIN);
|
||||
$user->setEmail('anna_admin@example.com');
|
||||
$user->setRoles([User::ROLE_ADMIN]);
|
||||
$user->setEnabled(true);
|
||||
$prefs = $this->getUserPreferences($user, 'Europe/London');
|
||||
$user->setPreferences($prefs);
|
||||
$user->setPassword($this->passwordHasher->hashPassword($user, self::DEFAULT_PASSWORD));
|
||||
$user->setApiToken($this->passwordHasher->hashPassword($user, self::DEFAULT_API_TOKEN));
|
||||
$token = new AccessToken($user, self::DEFAULT_API_TOKEN . '_anna');
|
||||
$token->setName('User fixture');
|
||||
$all[] = [$user, $token];
|
||||
|
||||
$user = new User();
|
||||
$user->setAlias('Anna Smith');
|
||||
$user->setTitle('Administrator');
|
||||
$user->setUserIdentifier('administrator');
|
||||
$user->setEmail('administrator@example.com');
|
||||
$user->setRoles([User::ROLE_ADMIN]);
|
||||
$user->setEnabled(true);
|
||||
$prefs = $this->getUserPreferences($user, 'Europe/London');
|
||||
$user->setPreferences($prefs);
|
||||
$user->setPassword($this->passwordHasher->hashPassword($user, self::DEFAULT_PASSWORD));
|
||||
$user->setApiToken($this->passwordHasher->hashPassword($user, self::DEFAULT_API_TOKEN));
|
||||
$token = new AccessToken($user, self::DEFAULT_API_TOKEN . '_admin');
|
||||
$token->setName('User fixture');
|
||||
$all[] = [$user, $token];
|
||||
|
||||
// no alias to test twig username macro
|
||||
$user = new User();
|
||||
$user->setTitle('Super Administrator');
|
||||
$user->setUserIdentifier(self::USERNAME_SUPER_ADMIN);
|
||||
$user->setEmail('susan_super@example.com');
|
||||
$user->setRoles([User::ROLE_SUPER_ADMIN]);
|
||||
$user->setAvatar('/touch-icon-192x192.png');
|
||||
$user->setEnabled(true);
|
||||
$prefs = $this->getUserPreferences($user, 'Europe/Berlin');
|
||||
$user->setPreferences($prefs);
|
||||
$user->setPassword($this->passwordHasher->hashPassword($user, self::DEFAULT_PASSWORD));
|
||||
$user->setApiToken($this->passwordHasher->hashPassword($user, self::DEFAULT_API_TOKEN));
|
||||
$token = new AccessToken($user, self::DEFAULT_API_TOKEN . '_susan');
|
||||
$token->setName('User fixture');
|
||||
$all[] = [$user, $token];
|
||||
|
||||
$user = new User();
|
||||
$user->setTitle('Super Administrator');
|
||||
$user->setUserIdentifier('super_admin');
|
||||
$user->setEmail('super_admin@example.com');
|
||||
$user->setRoles([User::ROLE_SUPER_ADMIN]);
|
||||
$user->setAvatar('/touch-icon-192x192.png');
|
||||
$user->setEnabled(true);
|
||||
$prefs = $this->getUserPreferences($user, 'Europe/Berlin');
|
||||
$user->setPreferences($prefs);
|
||||
$user->setPassword($this->passwordHasher->hashPassword($user, self::DEFAULT_PASSWORD));
|
||||
$user->setApiToken($this->passwordHasher->hashPassword($user, self::DEFAULT_API_TOKEN));
|
||||
$token = new AccessToken($user, self::DEFAULT_API_TOKEN . '_super');
|
||||
$token->setName('User fixture');
|
||||
$all[] = [$user, $token];
|
||||
|
||||
return $all;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1276,7 +1276,17 @@ class User implements UserInterface, EquatableInterface, ThemeUserInterface, Pas
|
||||
|
||||
public function getWorkStartingDay(): ?\DateTimeInterface
|
||||
{
|
||||
$date = $this->getPreferenceValue(UserPreference::WORK_STARTING_DAY);
|
||||
return $this->getPreferenceDate('work_start_day');
|
||||
}
|
||||
|
||||
public function setWorkStartingDay(?\DateTimeInterface $date): void
|
||||
{
|
||||
$this->setPreferenceValue('work_start_day', $date?->format('Y-m-d'));
|
||||
}
|
||||
|
||||
private function getPreferenceDate(string $prefName): ?\DateTimeInterface
|
||||
{
|
||||
$date = $this->getPreferenceValue($prefName);
|
||||
|
||||
if ($date === null) {
|
||||
return null;
|
||||
@@ -1290,9 +1300,14 @@ class User implements UserInterface, EquatableInterface, ThemeUserInterface, Pas
|
||||
return ($date instanceof \DateTimeInterface) ? $date : null;
|
||||
}
|
||||
|
||||
public function setWorkStartingDay(?\DateTimeInterface $date): void
|
||||
public function getLastWorkingDay(): ?\DateTimeInterface
|
||||
{
|
||||
$this->setPreferenceValue(UserPreference::WORK_STARTING_DAY, $date?->format('Y-m-d'));
|
||||
return $this->getPreferenceDate('work_last_day');
|
||||
}
|
||||
|
||||
public function setLastWorkingDay(?\DateTimeInterface $date): void
|
||||
{
|
||||
$this->setPreferenceValue('work_last_day', $date?->format('Y-m-d'));
|
||||
}
|
||||
|
||||
public function getPublicHolidayGroup(): null|string
|
||||
|
||||
@@ -47,7 +47,6 @@ class UserPreference
|
||||
public const WORK_HOURS_SATURDAY = WorkingTimeCalculatorDay::WORK_HOURS_SATURDAY;
|
||||
/** @deprecated since 2.22*/
|
||||
public const WORK_HOURS_SUNDAY = WorkingTimeCalculatorDay::WORK_HOURS_SUNDAY;
|
||||
public const WORK_STARTING_DAY = 'work_start_day';
|
||||
public const PUBLIC_HOLIDAY_GROUP = 'public_holiday_group';
|
||||
public const HOLIDAYS_PER_YEAR = 'holidays';
|
||||
public const WORK_CONTRACT_TYPE = 'work_contract_type';
|
||||
|
||||
@@ -26,7 +26,7 @@ use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
*/
|
||||
final class ActivityType extends AbstractType
|
||||
{
|
||||
public function __construct(private ActivityHelper $activityHelper, private ProjectHelper $projectHelper)
|
||||
public function __construct(private readonly ActivityHelper $activityHelper, private readonly ProjectHelper $projectHelper)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -35,10 +35,10 @@ final class ActivityType extends AbstractType
|
||||
return $this->activityHelper->getChoiceLabel($activity);
|
||||
}
|
||||
|
||||
public function groupBy(Activity $activity, $key, $index): ?string
|
||||
public function groupBy(Activity $activity, $key, $index): string
|
||||
{
|
||||
if (null === $activity->getProject()) {
|
||||
return null;
|
||||
return '';
|
||||
}
|
||||
|
||||
return $this->projectHelper->getChoiceLabel($activity->getProject());
|
||||
|
||||
@@ -102,6 +102,7 @@ final class WorkingTimeService
|
||||
|
||||
$stats = null;
|
||||
$firstDay = $user->getWorkStartingDay();
|
||||
$lastDay = $user->getLastWorkingDay();
|
||||
$calculator = $this->getContractMode($user)->getCalculator($user);
|
||||
|
||||
foreach ($year->getMonths() as $month) {
|
||||
@@ -119,7 +120,7 @@ final class WorkingTimeService
|
||||
$dayDate = $day->getDay();
|
||||
$result = new WorkingTime($user, $dayDate);
|
||||
|
||||
if ($firstDay === null || $firstDay <= $dayDate) {
|
||||
if (($firstDay === null || $firstDay <= $dayDate) && ($lastDay === null || $lastDay >= $dayDate)) {
|
||||
$result->setExpectedTime($calculator->getWorkHoursForDay($dayDate));
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user