Release 2.0.32 (#4256)

This commit is contained in:
Kevin Papst
2023-08-31 15:33:53 +02:00
committed by GitHub
parent 452de88e18
commit 31348da4c0
42 changed files with 412 additions and 130 deletions

View File

@@ -0,0 +1,93 @@
<?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\API\Model;
use App\Utils\Color;
use JMS\Serializer\Annotation as Serializer;
#[Serializer\ExclusionPolicy('all')]
final class CalendarEvent
{
/**
* Calendar entry title
*/
#[Serializer\Expose]
#[Serializer\Groups(['Default'])]
#[Serializer\Type(name: 'string')]
private string $title; // @phpstan-ignore-line
/**
* Calendar background color
*/
#[Serializer\Expose]
#[Serializer\Groups(['Default'])]
#[Serializer\Type(name: 'string')]
private ?string $color = null; // @phpstan-ignore-line
/**
* Calendar text color
*/
#[Serializer\Expose]
#[Serializer\Groups(['Default'])]
#[Serializer\Type(name: 'string')]
private ?string $textColor = null;
/**
* If this entry is all-day long
*/
#[Serializer\Expose]
#[Serializer\Groups(['Default'])]
#[Serializer\Type(name: 'boolean')]
private bool $allDay = false; // @phpstan-ignore-line
/**
* Calendar entry start date
*/
#[Serializer\Expose]
#[Serializer\Groups(['Default'])]
#[Serializer\Type(name: 'DateTime')]
private \DateTimeInterface $start; // @phpstan-ignore-line
/**
* Calendar entry end date
*/
#[Serializer\Expose]
#[Serializer\Groups(['Default'])]
#[Serializer\Type(name: 'DateTime')]
private \DateTimeInterface $end; // @phpstan-ignore-line
public function setTitle(string $title): void
{
$this->title = $title;
}
public function setStart(\DateTimeInterface $start): void
{
$this->start = $start;
}
public function setEnd(\DateTimeInterface $end): void
{
$this->end = $end;
}
public function setColor(?string $color): void
{
$this->color = $color;
if ($color !== null && $this->textColor === null) {
$this->textColor = (new Color())->getFontContrastColor($color);
}
}
public function setAllDay(bool $allDay): void
{
$this->allDay = $allDay;
}
public function setTextColor(?string $textColor): void
{
$this->textColor = $textColor;
}
}

View File

@@ -14,6 +14,7 @@ use App\Entity\User;
use App\Event\CalendarConfigurationEvent;
use App\Event\CalendarDragAndDropSourceEvent;
use App\Event\CalendarGoogleSourceEvent;
use App\Event\CalendarSourceEvent;
use App\Event\RecentActivityEvent;
use App\Repository\TimesheetRepository;
use App\Utils\Color;
@@ -81,6 +82,23 @@ final class CalendarService
return new Google($apiKey, $sources);
}
/**
* @return array<CalendarSource>
*/
public function getSources(User $user): array
{
$sources = [];
$event = new CalendarSourceEvent($user);
$this->dispatcher->dispatch($event);
foreach ($event->getSources() as $source) {
$sources[] = $source;
}
return $sources;
}
public function getConfiguration(): array
{
$config = [

View File

@@ -0,0 +1,58 @@
<?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\Calendar;
class CalendarSource
{
/** @var array<string, string|bool|int> */
private array $options = [];
public function __construct(private CalendarSourceType $type, private string $id, private string $uri, private ?string $color = null)
{
}
public function getType(): CalendarSourceType
{
return $this->type;
}
public function getTypeName(): string
{
return $this->type->value;
}
public function getId(): string
{
return $this->id;
}
public function getUri(): string
{
return $this->uri;
}
public function getColor(): ?string
{
return $this->color;
}
public function addOption(string $name, int|bool|string $value): void
{
$this->options[$name] = $value;
}
/**
* @return array<string, bool|int|string>
*/
public function getOptions(): array
{
return $this->options;
}
}

View File

@@ -0,0 +1,18 @@
<?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\Calendar;
enum CalendarSourceType: string
{
case GOOGLE = 'google';
case ICAL = 'ical';
case JSON = 'json';
case TIMESHEET = 'timesheet';
}

View File

@@ -9,24 +9,10 @@
namespace App\Calendar;
final class GoogleSource
final class GoogleSource extends CalendarSource
{
public function __construct(private string $id, private string $uri, private ?string $color = null)
public function __construct(string $id, string $uri, ?string $color = null)
{
}
public function getId(): string
{
return $this->id;
}
public function getUri(): string
{
return $this->uri;
}
public function getColor(): ?string
{
return $this->color;
parent::__construct(CalendarSourceType::GOOGLE, $id, $uri, $color);
}
}

View File

@@ -9,8 +9,6 @@
namespace App\Configuration;
use App\Constants;
final class LocaleService
{
public function __construct(private array $languageSettings)
@@ -32,11 +30,6 @@ final class LocaleService
return \in_array($language, $this->getAllLocales());
}
public function getDefaultLocale(): string
{
return Constants::DEFAULT_LOCALE;
}
/**
* Returns the locale specific date format, which should be used in combination with the twig filter "|date".
*

View File

@@ -17,11 +17,11 @@ class Constants
/**
* The current release version
*/
public const VERSION = '2.0.31';
public const VERSION = '2.0.32';
/**
* The current release: major * 10000 + minor * 100 + patch
*/
public const VERSION_ID = 20031;
public const VERSION_ID = 20032;
/**
* The software name
*/
@@ -38,10 +38,6 @@ class Constants
* Homepage, used in multiple views
*/
public const HOMEPAGE = 'https://www.kimai.org';
/**
* Application wide default locale
*/
public const DEFAULT_LOCALE = 'en';
/**
* Default color for Customer, Project and Activity entities
*/

View File

@@ -94,6 +94,7 @@ final class CalendarController extends AbstractController
'config' => $config,
'dragAndDrop' => $dragAndDrop,
'google' => $this->calendarService->getGoogleSources($profile),
'sources' => $this->calendarService->getSources($profile),
'now' => $factory->createDateTime(),
'defaultStartTime' => $defaultStart,
'is_punch_mode' => $isPunchMode,

View File

@@ -46,7 +46,7 @@ final class HomepageController extends AbstractController
// if a user somehow managed to get a wrong locale into hos account (eg. an imported user from Kimai 1)
// make sure that he will still see a beautiful page and not a 404
if (!$service->isKnownLocale($userLanguage)) {
$userLanguage = $service->getDefaultLocale();
$userLanguage = 'en';
}
$routes = [];

View File

@@ -9,7 +9,6 @@
namespace App\Entity;
use App\Constants;
use App\Export\Annotation as Exporter;
use App\Utils\StringHelper;
use App\Validator\Constraints as Constraints;
@@ -55,7 +54,7 @@ class User implements UserInterface, EquatableInterface, ThemeUserInterface, Pas
public const ROLE_SUPER_ADMIN = 'ROLE_SUPER_ADMIN';
public const DEFAULT_ROLE = self::ROLE_USER;
public const DEFAULT_LANGUAGE = Constants::DEFAULT_LOCALE;
public const DEFAULT_LANGUAGE = 'en';
public const DEFAULT_FIRST_WEEKDAY = 'monday';
public const AUTH_INTERNAL = 'kimai';
@@ -169,7 +168,7 @@ class User implements UserInterface, EquatableInterface, ThemeUserInterface, Pas
#[Assert\Email(mode: 'html5', groups: ['Registration', 'UserCreate', 'Profile'])]
private ?string $email = null;
#[ORM\Column(name: 'account', type: 'string', length: 30, nullable: true)]
#[Assert\Length(max: 30, groups: ['Registration', 'UserCreate', 'Profile'])]
#[Assert\Length(max: 30)]
#[Serializer\Expose]
#[Serializer\Groups(['Default'])]
#[Exporter\Expose(label: 'account_number')]
@@ -195,6 +194,7 @@ class User implements UserInterface, EquatableInterface, ThemeUserInterface, Pas
* Random string sent to the user email address in order to verify it.
*/
#[ORM\Column(name: 'confirmation_token', type: 'string', length: 180, unique: true, nullable: true)]
#[Assert\Length(max: 180)]
private ?string $confirmationToken = null;
#[ORM\Column(name: 'password_requested_at', type: 'datetime', nullable: true)]
private ?\DateTime $passwordRequestedAt = null;
@@ -1055,7 +1055,8 @@ class User implements UserInterface, EquatableInterface, ThemeUserInterface, Pas
public function setAccountNumber(?string $accountNumber): void
{
$this->accountNumber = $accountNumber;
// @CloudRequired because SAML mapping could include a longer value
$this->accountNumber = StringHelper::ensureMaxLength($accountNumber, 30);
}
public function isSystemAccount(): bool

View File

@@ -18,7 +18,7 @@ abstract class AbstractMetaDisplayEvent extends Event implements MetaDisplayEven
/**
* @var MetaTableTypeInterface[]
*/
private $fields = [];
private array $fields = [];
public function __construct(private BaseQuery $query, private string $location)
{

View File

@@ -14,16 +14,9 @@ use App\Model\ActivityStatistic;
final class ActivityStatisticEvent extends AbstractActivityEvent
{
private $statistic;
private $begin;
private $end;
public function __construct(Activity $activity, ActivityStatistic $statistic, \DateTime $begin = null, \DateTime $end = null)
public function __construct(Activity $activity, private ActivityStatistic $statistic, private ?\DateTime $begin = null, private ?\DateTime $end = null)
{
parent::__construct($activity);
$this->statistic = $statistic;
$this->begin = $begin;
$this->end = $end;
}
public function getStatistic(): ActivityStatistic

View File

@@ -25,7 +25,7 @@ final class CalendarConfigurationEvent extends Event
return $this->configuration;
}
public function setConfiguration(array $configuration)
public function setConfiguration(array $configuration): void
{
foreach ($configuration as $key => $value) {
if (\array_key_exists($key, $this->configuration)) {

View File

@@ -18,7 +18,7 @@ final class CalendarDragAndDropSourceEvent extends Event
/**
* @var DragAndDropSource[]
*/
private $sources = [];
private array $sources = [];
public function __construct(private User $user, private int $maxEntries)
{

View File

@@ -18,7 +18,7 @@ final class CalendarGoogleSourceEvent extends Event
/**
* @var GoogleSource[]
*/
private $sources = [];
private array $sources = [];
public function __construct(private User $user)
{

View File

@@ -0,0 +1,44 @@
<?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\Event;
use App\Calendar\CalendarSource;
use App\Entity\User;
use Symfony\Contracts\EventDispatcher\Event;
final class CalendarSourceEvent extends Event
{
/**
* @var CalendarSource[]
*/
private array $sources = [];
public function __construct(private User $user)
{
}
public function getUser(): User
{
return $this->user;
}
public function addSource(CalendarSource $source): void
{
$this->sources[] = $source;
}
/**
* @return CalendarSource[]
*/
public function getSources(): array
{
return $this->sources;
}
}

View File

@@ -14,16 +14,9 @@ use App\Model\CustomerStatistic;
final class CustomerStatisticEvent extends AbstractCustomerEvent
{
private $statistic;
private $begin;
private $end;
public function __construct(Customer $customer, CustomerStatistic $statistic, \DateTime $begin = null, \DateTime $end = null)
public function __construct(Customer $customer, private CustomerStatistic $statistic, private ?\DateTime $begin = null, private ?\DateTime $end = null)
{
parent::__construct($customer);
$this->statistic = $statistic;
$this->begin = $begin;
$this->end = $end;
}
public function getStatistic(): CustomerStatistic

View File

@@ -17,7 +17,7 @@ final class DashboardEvent extends Event
/**
* @var array<string>
*/
private $widgets = [];
private array $widgets = [];
public function __construct(private User $user)
{

View File

@@ -14,10 +14,6 @@ use Symfony\Contracts\EventDispatcher\Event;
final class InvoiceDocumentsEvent extends Event
{
/**
* @var array<InvoiceDocument>
*/
private array $documents;
/**
* Maximum amount of allowed invoice documents.
*/
@@ -26,9 +22,8 @@ final class InvoiceDocumentsEvent extends Event
/**
* @param InvoiceDocument[] $documents
*/
public function __construct(array $documents)
public function __construct(private array $documents)
{
$this->documents = $documents;
}
/**

View File

@@ -18,9 +18,9 @@ use Symfony\Contracts\EventDispatcher\Event;
final class PermissionSectionsEvent extends Event
{
/**
* @var array
* @var array<PermissionSectionInterface>
*/
private $sections;
private array $sections = [];
public function addSection(PermissionSectionInterface $section): PermissionSectionsEvent
{

View File

@@ -17,9 +17,9 @@ use Symfony\Contracts\EventDispatcher\Event;
final class PermissionsEvent extends Event
{
/**
* @var array
* @var array<string, array<string>>
*/
private $sections = [];
private array $sections = [];
/**
* @param string $section

View File

@@ -14,16 +14,9 @@ use App\Model\ProjectStatistic;
final class ProjectStatisticEvent extends AbstractProjectEvent
{
private $statistic;
private $begin;
private $end;
public function __construct(Project $project, ProjectStatistic $statistic, \DateTime $begin = null, \DateTime $end = null)
public function __construct(Project $project, private ProjectStatistic $statistic, private ?\DateTime $begin = null, private ?\DateTime $end = null)
{
parent::__construct($project);
$this->statistic = $statistic;
$this->begin = $begin;
$this->end = $end;
}
public function getStatistic(): ProjectStatistic

View File

@@ -13,15 +13,9 @@ use App\Entity\Timesheet;
final class TimesheetDuplicatePostEvent extends AbstractTimesheetEvent
{
/**
* @var Timesheet
*/
private $original;
public function __construct(Timesheet $new, Timesheet $original)
public function __construct(Timesheet $new, private Timesheet $original)
{
parent::__construct($new);
$this->original = $original;
}
public function getOriginalTimesheet(): Timesheet

View File

@@ -13,15 +13,9 @@ use App\Entity\Timesheet;
final class TimesheetDuplicatePreEvent extends AbstractTimesheetEvent
{
/**
* @var Timesheet
*/
private $original;
public function __construct(Timesheet $new, Timesheet $original)
public function __construct(Timesheet $new, private Timesheet $original)
{
parent::__construct($new);
$this->original = $original;
}
public function getOriginalTimesheet(): Timesheet

View File

@@ -13,15 +13,9 @@ use App\Entity\Timesheet;
final class TimesheetRestartPostEvent extends AbstractTimesheetEvent
{
/**
* @var Timesheet
*/
private $original;
public function __construct(Timesheet $new, Timesheet $original)
public function __construct(Timesheet $new, private Timesheet $original)
{
parent::__construct($new);
$this->original = $original;
}
public function getOriginalTimesheet(): Timesheet

View File

@@ -13,15 +13,9 @@ use App\Entity\Timesheet;
final class TimesheetRestartPreEvent extends AbstractTimesheetEvent
{
/**
* @var Timesheet
*/
private $original;
public function __construct(Timesheet $new, Timesheet $original)
public function __construct(Timesheet $new, private Timesheet $original)
{
parent::__construct($new);
$this->original = $original;
}
public function getOriginalTimesheet(): Timesheet

View File

@@ -57,7 +57,7 @@ final class RedirectToLocaleSubscriber implements EventSubscriberInterface
// Add the default locale at the first position of the array, because getPreferredLanguage()
// returns the first element when no appropriate language is found
array_unshift($allLanguages, $this->localeService->getDefaultLocale());
array_unshift($allLanguages, 'en');
$preferredLanguage = $request->getPreferredLanguage(array_unique($allLanguages));

View File

@@ -130,10 +130,14 @@ final class SamlProvider
}
if ($part[0] === '$') {
$key = substr($part, 1);
if (!isset($attributes[$key])) {
if (!\array_key_exists($key, $attributes)) {
throw new \RuntimeException('Missing user attribute: ' . $key);
}
if (!\array_key_exists(0, $attributes[$key])) {
throw new \RuntimeException('Missing user attribute value: ' . $key);
}
$results[] = $attributes[$key][0];
} else {
$results[] = $part;

View File

@@ -43,7 +43,8 @@ final class LocaleFormatExtensions extends AbstractExtension implements LocaleAw
new TwigFilter('day_name', [$this, 'dayName']),
new TwigFilter('date_short', [$this, 'dateShort']),
new TwigFilter('date_time', [$this, 'dateTime']),
new TwigFilter('date_full', [$this, 'dateTime']), // deprecated: needs to be kept for invoice and export templates
// cannot be deleted right now, needs to be kept for invoice and export templates
new TwigFilter('date_full', [$this, 'dateTime'], ['deprecated' => true, 'alternative' => 'date_time']),
new TwigFilter('date_format', [$this, 'dateFormat']),
new TwigFilter('date_weekday', [$this, 'dateWeekday']),
new TwigFilter('time', [$this, 'time']),