Files
kimai2/src/Configuration/SystemConfiguration.php

588 lines
15 KiB
PHP

<?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\Configuration;
use App\Constants;
final class SystemConfiguration
{
private bool $initialized = false;
public function __construct(private ConfigLoaderInterface $repository, private array $settings = [])
{
}
private function prepare(): void
{
if ($this->initialized) {
return;
}
foreach ($this->repository->getConfigurations() as $key => $value) {
$this->set($key, $value);
}
$this->initialized = true;
}
/**
* Set a new or replace an existing system configuration.
*/
public function set(string $key, mixed $value): void
{
if (\array_key_exists($key, $this->settings)) {
if (\is_bool($this->settings[$key])) {
$value = (bool) $value;
} elseif (\is_int($this->settings[$key])) {
$value = (int) $value;
}
}
$this->settings[$key] = $value;
}
/**
* @param string $key
* @return string|int|bool|float|null
*/
public function find(string $key): string|int|bool|float|null
{
$this->prepare();
if (\array_key_exists($key, $this->settings)) {
return $this->settings[$key];
}
return null;
}
/**
* This method should be avoided if possible, use plain keys instead.
*
* @see https://github.com/divineomega/array_undot
* @param string $key
* @return array
*/
public function findArray(string $key): array
{
$this->prepare();
$result = array_filter($this->settings, function ($settingName) use ($key): bool {
return str_starts_with($settingName, $key);
}, ARRAY_FILTER_USE_KEY);
$replaced = [];
foreach ($result as $settingName => $value) {
if (\is_bool($this->settings[$settingName])) {
$value = (bool) $value;
} elseif (\is_int($this->settings[$settingName])) {
$value = (int) $value;
}
$baseName = str_replace($key . '.', '', $settingName);
$keys = explode('.', $baseName);
$array = &$replaced;
while (\count($keys) > 1) {
$search = array_shift($keys);
if (!\array_key_exists($search, $array) || !\is_array($array[$search])) {
$array[$search] = [];
}
$array = &$array[$search];
}
$array[array_shift($keys)] = $value;
}
return $replaced;
}
public function has(string $key): bool
{
$this->prepare();
if (\array_key_exists($key, $this->settings)) {
return true;
}
$result = array_filter($this->settings, function ($settingName) use ($key): bool {
return str_starts_with($settingName, $key);
}, ARRAY_FILTER_USE_KEY);
return \count($result) > 0;
}
// ========== Array access methods ==========
/**
* @deprecated since 2.0.35
*/
public function offsetExists($offset): bool
{
@trigger_error('The method "SystemConfiguration::offsetExists()" is deprecated, use "has()" instead', E_USER_DEPRECATED);
return $this->has($offset);
}
/**
* @deprecated since 2.0.35
*/
public function offsetGet($offset): mixed
{
@trigger_error('The method "SystemConfiguration::offsetGet()" is deprecated, use "find()" instead', E_USER_DEPRECATED);
return $this->find($offset);
}
/**
* @deprecated since 2.0.35
*/
public function offsetSet(mixed $offset, mixed $value): void
{
@trigger_error('The method "SystemConfiguration::offsetSet()" is deprecated, use "set()" instead', E_USER_DEPRECATED);
$this->set($offset, $value);
}
// ========== Authentication configurations ==========
public function isLoginFormActive(): bool
{
if ($this->isLdapActive()) {
return true;
}
// if SAML is active, the login form can be deactivated
if (!$this->isSamlActive()) {
return true;
}
return (bool) $this->find('user.login');
}
public function isSelfRegistrationActive(): bool
{
if (!$this->isLoginFormActive()) {
return false;
}
return (bool) $this->find('user.registration');
}
public function getAuthenticationTheme(): string
{
return $this->getString('user.theme', 'auto');
}
public function getPasswordResetTokenLifetime(): int
{
return (int) $this->find('user.password_reset_token_ttl');
}
public function getPasswordResetRetryLifetime(): int
{
return (int) $this->find('user.password_reset_retry_ttl');
}
public function isPasswordResetActive(): bool
{
if (!$this->isLoginFormActive()) {
return false;
}
return (bool) $this->find('user.password_reset');
}
public function isSamlActive(): bool
{
return (bool) $this->find('saml.activate');
}
public function getSamlTitle(): string
{
return (string) $this->find('saml.title');
}
public function getSamlProvider(): ?string
{
return $this->find('saml.provider');
}
public function isSamlRolesResetOnLogin(): bool
{
return (bool) $this->find('saml.roles.resetOnLogin');
}
/**
* @return array<int, array<'saml'|'kimai', string>>
*/
public function getSamlRolesMapping(): array
{
return $this->findArray('saml.roles.mapping');
}
/**
* @return array<string, array<mixed>|bool>
*/
public function getSamlConnection(): array
{
return $this->findArray('saml.connection');
}
/**
* @return array<int, array<'saml'|'kimai', string>>
*/
public function getSamlAttributeMapping(): array
{
return $this->findArray('saml.mapping');
}
public function getSamlRolesAttribute(): ?string
{
$attr = $this->find('saml.roles.attribute');
if (empty($attr)) {
return null;
}
return (string) $attr;
}
public function isLdapActive(): bool
{
return (bool) $this->find('ldap.activate');
}
// ========== Calendar configurations ==========
public function getCalendarBusinessTimeBegin(): string
{
return (string) $this->find('calendar.businessHours.begin');
}
public function getCalendarBusinessTimeEnd(): string
{
return (string) $this->find('calendar.businessHours.end');
}
public function getCalendarTimeframeBegin(): string
{
return (string) $this->find('calendar.visibleHours.begin');
}
public function getCalendarTimeframeEnd(): string
{
return (string) $this->find('calendar.visibleHours.end');
}
public function getCalendarDayLimit(): int
{
return (int) $this->find('calendar.day_limit');
}
public function isCalendarShowWeekNumbers(): bool
{
return (bool) $this->find('calendar.week_numbers');
}
public function isCalendarShowWeekends(): bool
{
return (bool) $this->find('calendar.weekends');
}
public function getCalendarGoogleApiKey(): ?string
{
return $this->find('calendar.google.api_key');
}
public function getCalendarGoogleSources(): array
{
return $this->findArray('calendar.google.sources');
}
public function getCalendarSlotDuration(): string
{
return (string) $this->find('calendar.slot_duration');
}
public function getCalendarDragAndDropMaxEntries(): int
{
return (int) $this->find('calendar.dragdrop_amount');
}
public function isCalendarDragAndDropCopyData(): bool
{
return (bool) $this->find('calendar.dragdrop_data');
}
// ========== Customer configurations ==========
public function getCustomerDefaultTimezone(): ?string
{
return $this->find('defaults.customer.timezone');
}
public function getCustomerDefaultCountry(): string
{
return $this->find('defaults.customer.country');
}
// ========== User configurations ==========
public function getUserDefaultTimezone(): ?string
{
return $this->find('defaults.user.timezone');
}
public function getUserDefaultTheme(): string
{
return $this->getString('defaults.user.theme', 'auto');
}
public function getUserDefaultLanguage(): string
{
return $this->getString('defaults.user.language', 'en');
}
public function getDefaultCurrency(): string
{
return $this->getString('defaults.customer.currency', 'EUR');
}
/**
* @deprecated use getDefaultCurrency() instead
*/
public function getCustomerDefaultCurrency(): string
{
return $this->getDefaultCurrency();
}
/**
* @deprecated use getDefaultCurrency() instead
*/
public function getUserDefaultCurrency(): string
{
return $this->getDefaultCurrency();
}
public function isUserWizardActive(): bool
{
return (bool) $this->find('user.wizard');
}
// ========== Timesheet configurations ==========
/*
public function getTimesheetBreakWarningDuration(): int
{
return (int) $this->find('timesheet.rules.break_warning_duration');
}
*/
public function getTimesheetLongRunningDuration(): int
{
return (int) $this->find('timesheet.rules.long_running_duration');
}
public function getTimesheetDefaultBeginTime(): string
{
return (string) $this->find('timesheet.default_begin');
}
public function isTimesheetAllowFutureTimes(): bool
{
return (bool) $this->find('timesheet.rules.allow_future_times');
}
public function isTimesheetAllowZeroDuration(): bool
{
return (bool) $this->find('timesheet.rules.allow_zero_duration');
}
public function isTimesheetAllowOverbookingBudget(): bool
{
return (bool) $this->find('timesheet.rules.allow_overbooking_budget');
}
public function isTimesheetAllowOverlappingRecords(): bool
{
return (bool) $this->find('timesheet.rules.allow_overlapping_records');
}
public function getTimesheetTrackingMode(): string
{
return $this->getString('timesheet.mode', 'default');
}
public function isTimesheetMarkdownEnabled(): bool
{
return (bool) $this->find('timesheet.markdown_content');
}
public function isTimesheetRequiresActivity(): bool
{
return (bool) $this->find('timesheet.rules.require_activity');
}
public function getTimesheetActiveEntriesHardLimit(): int
{
return (int) $this->find('timesheet.active_entries.hard_limit');
}
public function getTimesheetDefaultRoundingDays(): string
{
return (string) $this->find('timesheet.rounding.default.days');
}
public function getTimesheetDefaultRoundingMode(): string
{
return (string) $this->find('timesheet.rounding.default.mode');
}
public function getTimesheetDefaultRoundingBegin(): int
{
return (int) $this->find('timesheet.rounding.default.begin');
}
public function getTimesheetDefaultRoundingEnd(): int
{
return (int) $this->find('timesheet.rounding.default.end');
}
public function getTimesheetDefaultRoundingDuration(): int
{
return (int) $this->find('timesheet.rounding.default.duration');
}
public function getTimesheetIncrementDuration(): int
{
return $this->getIncrement('timesheet.duration_increment', $this->getTimesheetDefaultRoundingDuration(), 0);
}
public function getTimesheetIncrementMinutes(): int
{
return $this->getIncrement('timesheet.time_increment', $this->getTimesheetDefaultRoundingDuration(), 0);
}
public function getQuickEntriesRecentAmount(): int
{
return $this->getIncrement('quick_entry.recent_activities', 5, 0);
}
public function isBreakTimeEnabled(): bool
{
return (bool) $this->find('timesheet.rules.break_time_active');
}
public function getExportTimeout(): int
{
return (int) $this->find('export.timeout');
}
// ========== Company configurations ==========
public function getFinancialYearStart(): ?string
{
$start = $this->find('company.financial_year');
if (empty($start)) {
return null;
}
return (string) $start;
}
// ========== Theme configurations ==========
public function isShowAbout(): bool
{
return (bool) $this->find('theme.show_about');
}
public function isThemeAllowAvatarUrls(): bool
{
return (bool) $this->find('theme.avatar_url');
}
/**
* @return array<string, string>
*/
public function getThemeColors(): array
{
$config = $this->find('theme.color_choices');
if (!\is_string($config) || $config === '') {
return ['Silver' => '#c0c0c0'];
}
$config = explode(',', $config);
$colors = [];
foreach ($config as $item) {
if (empty($item)) {
continue;
}
$item = explode('|', $item);
$key = $item[0];
$value = $key;
if (\count($item) > 1) {
$value = $item[1];
}
if (empty($key)) {
$key = $value;
}
if ($value === Constants::DEFAULT_COLOR) {
continue;
}
$colors[$key] = $value;
}
return array_unique($colors);
}
// ========== Projects ==========
public function isProjectCopyTeamsOnCreate(): bool
{
return $this->find('project.copy_teams_on_create') === true;
}
// ========== Helper functions ==========
private function getIncrement(string $key, int $fallback, int $min = 1): int
{
$config = $this->find($key);
if ($config === null || trim($config) === '') {
return $fallback;
}
$config = (int) $config;
return max($config, $min);
}
private function getString(string $key, string $fallback): string
{
$config = $this->find($key);
if ($config === null) {
return $fallback;
}
$config = (string) $config;
if (trim($config) === '') {
return $fallback;
}
return $config;
}
}