Release 2.26 (#5189)
* bring back deprecated methods * bump packages * fix SAML redirect * config flag for break times * use class constant instead of string in attributes * throw if all tags were not found - fixes #4792
This commit is contained in:
463
composer.lock
generated
463
composer.lock
generated
File diff suppressed because it is too large
Load Diff
15
phpstan.neon
15
phpstan.neon
@@ -697,11 +697,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Controller/ActivityController.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$returnTo of method OneLogin\\\\Saml2\\\\Auth\\:\\:login\\(\\) expects string\\|null, mixed given\\.$#"
|
||||
count: 1
|
||||
path: src/Controller/Auth/SamlController.php
|
||||
|
||||
-
|
||||
message: "#^Cannot access offset 'user' on mixed\\.$#"
|
||||
count: 1
|
||||
@@ -3872,11 +3867,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Repository/TimesheetRepository.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Repository\\\\TimesheetRepository\\:\\:getRawData\\(\\) should return array but returns mixed\\.$#"
|
||||
count: 1
|
||||
path: src/Repository/TimesheetRepository.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$amountThisMonth of method App\\\\Model\\\\TimesheetStatistic\\:\\:setAmountThisMonth\\(\\) expects float\\|int, mixed given\\.$#"
|
||||
count: 1
|
||||
@@ -3972,11 +3962,6 @@ parameters:
|
||||
count: 1
|
||||
path: src/Saml/Security/SamlAuthenticationFailureHandler.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Saml\\\\Security\\\\SamlAuthenticationSuccessHandler\\:\\:determineTargetUrl\\(\\) should return string but returns mixed\\.$#"
|
||||
count: 1
|
||||
path: src/Saml/Security/SamlAuthenticationSuccessHandler.php
|
||||
|
||||
-
|
||||
message: "#^Property App\\\\Saml\\\\Security\\\\SamlAuthenticationSuccessHandler\\:\\:\\$defaultOptions has no type specified\\.$#"
|
||||
count: 1
|
||||
|
||||
@@ -37,6 +37,7 @@ use Symfony\Component\ExpressionLanguage\Expression;
|
||||
use Symfony\Component\Form\FormError;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Security\Http\Attribute\IsGranted;
|
||||
use Symfony\Component\Validator\Constraints;
|
||||
@@ -181,8 +182,11 @@ final class TimesheetController extends BaseApiController
|
||||
/** @var array<string> $tags */
|
||||
$tags = $paramFetcher->get('tags');
|
||||
if (\is_array($tags) && \count($tags) > 0) {
|
||||
$tags = $this->tagRepository->findTagsByName($tags, true);
|
||||
foreach ($tags as $tag) {
|
||||
$tagsByName = $this->tagRepository->findTagsByName($tags, true);
|
||||
if (\count($tagsByName) === 0) {
|
||||
throw new BadRequestHttpException('Given tags were not found');
|
||||
}
|
||||
foreach ($tagsByName as $tag) {
|
||||
$query->addTag($tag);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ use Symfony\Component\Intl\Locales;
|
||||
*
|
||||
* @codeCoverageIgnore
|
||||
*/
|
||||
#[AsCommand(name: 'kimai:reset:locales')]
|
||||
#[AsCommand(name: 'kimai:reset:locales', description: 'Regenerate the locale definition file')]
|
||||
final class RegenerateLocalesCommand extends Command
|
||||
{
|
||||
/**
|
||||
@@ -66,11 +66,6 @@ final class RegenerateLocalesCommand extends Command
|
||||
return $this->kernelEnvironment !== 'prod';
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setDescription('Regenerate the locale definition file');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
|
||||
@@ -17,11 +17,11 @@ class Constants
|
||||
/**
|
||||
* The current release version
|
||||
*/
|
||||
public const VERSION = '2.25.0';
|
||||
public const VERSION = '2.26.0';
|
||||
/**
|
||||
* The current release: major * 10000 + minor * 100 + patch
|
||||
*/
|
||||
public const VERSION_ID = 22500;
|
||||
public const VERSION_ID = 22600;
|
||||
/**
|
||||
* The software name
|
||||
*/
|
||||
|
||||
@@ -12,18 +12,24 @@ namespace App\Controller\Auth;
|
||||
use App\Configuration\SamlConfigurationInterface;
|
||||
use App\Saml\SamlAuthFactory;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
use Symfony\Component\Security\Http\SecurityRequestAttributes;
|
||||
use Symfony\Component\Security\Http\Util\TargetPathTrait;
|
||||
|
||||
#[Route(path: '/saml')]
|
||||
final class SamlController extends AbstractController
|
||||
{
|
||||
use TargetPathTrait;
|
||||
|
||||
public function __construct(
|
||||
private readonly SamlAuthFactory $authFactory,
|
||||
private readonly SamlConfigurationInterface $samlConfiguration
|
||||
private readonly SamlConfigurationInterface $samlConfiguration,
|
||||
private readonly Security $security,
|
||||
)
|
||||
{
|
||||
}
|
||||
@@ -54,8 +60,12 @@ final class SamlController extends AbstractController
|
||||
throw new \RuntimeException($error);
|
||||
}
|
||||
|
||||
// this does set headers and exit as $stay is not set to true
|
||||
$redirectTarget = $session->get('_security.main.target_path');
|
||||
$firewallName = $this->security->getFirewallConfig($request)?->getName();
|
||||
if ($firewallName === null || $firewallName === '') {
|
||||
throw new ServiceUnavailableHttpException(message: 'Unknown firewall.');
|
||||
}
|
||||
|
||||
$redirectTarget = $this->getTargetPath($session, $firewallName);
|
||||
if ($redirectTarget === null || $redirectTarget === '') {
|
||||
$redirectTarget = $this->generateUrl('homepage', [], UrlGeneratorInterface::ABSOLUTE_URL);
|
||||
}
|
||||
|
||||
@@ -253,7 +253,6 @@ final class DoctorController extends AbstractController
|
||||
'max_execution_time',
|
||||
'date.timezone',
|
||||
'allow_url_fopen',
|
||||
'allow_url_include',
|
||||
'default_charset',
|
||||
'default_mimetype',
|
||||
'display_errors',
|
||||
|
||||
@@ -331,6 +331,9 @@ final class Configuration implements ConfigurationInterface
|
||||
->booleanNode('require_activity')
|
||||
->defaultTrue()
|
||||
->end()
|
||||
->booleanNode('break_time_active')
|
||||
->defaultFalse()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
|
||||
@@ -9,12 +9,13 @@
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\AccessTokenRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
#[ORM\Table(name: 'kimai2_access_token')]
|
||||
#[ORM\Entity(repositoryClass: 'App\Repository\AccessTokenRepository')]
|
||||
#[ORM\Entity(repositoryClass: AccessTokenRepository::class)]
|
||||
#[ORM\UniqueConstraint(columns: ['token'])]
|
||||
#[ORM\ChangeTrackingPolicy('DEFERRED_EXPLICIT')]
|
||||
#[UniqueEntity(fields: ['token'])]
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace App\Entity;
|
||||
use App\Doctrine\Behavior\CreatedAt;
|
||||
use App\Doctrine\Behavior\CreatedTrait;
|
||||
use App\Export\Annotation as Exporter;
|
||||
use App\Repository\ActivityRepository;
|
||||
use App\Validator\Constraints as Constraints;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
@@ -24,7 +25,7 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
#[ORM\Index(columns: ['visible', 'project_id'])]
|
||||
#[ORM\Index(columns: ['visible', 'project_id', 'name'])]
|
||||
#[ORM\Index(columns: ['visible', 'name'])]
|
||||
#[ORM\Entity(repositoryClass: 'App\Repository\ActivityRepository')]
|
||||
#[ORM\Entity(repositoryClass: ActivityRepository::class)]
|
||||
#[ORM\ChangeTrackingPolicy('DEFERRED_EXPLICIT')]
|
||||
#[Serializer\ExclusionPolicy('all')]
|
||||
#[Serializer\VirtualProperty('ProjectName', exp: 'object.getProject() === null ? null : object.getProject().getName()', options: [new Serializer\SerializedName('parentTitle'), new Serializer\Type(name: 'string'), new Serializer\Groups(['Activity'])])]
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\ActivityRateRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use JMS\Serializer\Annotation as Serializer;
|
||||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||
@@ -16,7 +17,7 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
#[ORM\Table(name: 'kimai2_activities_rates')]
|
||||
#[ORM\UniqueConstraint(columns: ['user_id', 'activity_id'])]
|
||||
#[ORM\Entity(repositoryClass: 'App\Repository\ActivityRateRepository')]
|
||||
#[ORM\Entity(repositoryClass: ActivityRateRepository::class)]
|
||||
#[ORM\ChangeTrackingPolicy('DEFERRED_EXPLICIT')]
|
||||
#[UniqueEntity(['user', 'activity'], ignoreNull: false)]
|
||||
#[Serializer\ExclusionPolicy('all')]
|
||||
|
||||
@@ -9,13 +9,14 @@
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\BookmarkRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
#[ORM\Table(name: 'kimai2_bookmarks')]
|
||||
#[ORM\UniqueConstraint(columns: ['user_id', 'name'])]
|
||||
#[ORM\Entity(repositoryClass: 'App\Repository\BookmarkRepository')]
|
||||
#[ORM\Entity(repositoryClass: BookmarkRepository::class)]
|
||||
#[ORM\ChangeTrackingPolicy('DEFERRED_EXPLICIT')]
|
||||
#[UniqueEntity(fields: ['user', 'name'])]
|
||||
class Bookmark
|
||||
|
||||
@@ -9,13 +9,14 @@
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\ConfigurationRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
#[ORM\Table(name: 'kimai2_configuration')]
|
||||
#[ORM\UniqueConstraint(columns: ['name'])]
|
||||
#[ORM\Entity(repositoryClass: 'App\Repository\ConfigurationRepository')]
|
||||
#[ORM\Entity(repositoryClass: ConfigurationRepository::class)]
|
||||
#[ORM\ChangeTrackingPolicy('DEFERRED_EXPLICIT')]
|
||||
#[UniqueEntity('name')]
|
||||
class Configuration
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace App\Entity;
|
||||
use App\Doctrine\Behavior\CreatedAt;
|
||||
use App\Doctrine\Behavior\CreatedTrait;
|
||||
use App\Export\Annotation as Exporter;
|
||||
use App\Repository\CustomerRepository;
|
||||
use App\Validator\Constraints as Constraints;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
@@ -22,7 +23,7 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
#[ORM\Table(name: 'kimai2_customers')]
|
||||
#[ORM\Index(columns: ['visible'])]
|
||||
#[ORM\Entity(repositoryClass: 'App\Repository\CustomerRepository')]
|
||||
#[ORM\Entity(repositoryClass: CustomerRepository::class)]
|
||||
#[ORM\ChangeTrackingPolicy('DEFERRED_EXPLICIT')]
|
||||
#[Serializer\ExclusionPolicy('all')]
|
||||
#[Exporter\Order(['id', 'name', 'company', 'number', 'vatId', 'address', 'contact', 'email', 'phone', 'mobile', 'fax', 'homepage', 'country', 'currency', 'timezone', 'budget', 'timeBudget', 'budgetType', 'color', 'visible', 'comment', 'billable'])]
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\CustomerRateRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use JMS\Serializer\Annotation as Serializer;
|
||||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||
@@ -16,7 +17,7 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
#[ORM\Table(name: 'kimai2_customers_rates')]
|
||||
#[ORM\UniqueConstraint(columns: ['user_id', 'customer_id'])]
|
||||
#[ORM\Entity(repositoryClass: 'App\Repository\CustomerRateRepository')]
|
||||
#[ORM\Entity(repositoryClass: CustomerRateRepository::class)]
|
||||
#[ORM\ChangeTrackingPolicy('DEFERRED_EXPLICIT')]
|
||||
#[UniqueEntity(['user', 'customer'], ignoreNull: false)]
|
||||
#[Serializer\ExclusionPolicy('all')]
|
||||
|
||||
@@ -11,17 +11,19 @@ namespace App\Entity;
|
||||
|
||||
use App\Export\Annotation as Exporter;
|
||||
use App\Invoice\InvoiceModel;
|
||||
use App\Repository\InvoiceRepository;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use JMS\Serializer\Annotation as Serializer;
|
||||
use OpenApi\Attributes as OA;
|
||||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
#[ORM\Table(name: 'kimai2_invoices')]
|
||||
#[ORM\UniqueConstraint(columns: ['invoice_number'])]
|
||||
#[ORM\UniqueConstraint(columns: ['invoice_filename'])]
|
||||
#[ORM\Entity(repositoryClass: 'App\Repository\InvoiceRepository')]
|
||||
#[ORM\Entity(repositoryClass: InvoiceRepository::class)]
|
||||
#[ORM\ChangeTrackingPolicy('DEFERRED_EXPLICIT')]
|
||||
#[UniqueEntity('invoiceNumber')]
|
||||
#[UniqueEntity('invoiceFilename')]
|
||||
@@ -65,12 +67,14 @@ class Invoice implements EntityWithMetaFields
|
||||
#[Assert\NotNull]
|
||||
#[Serializer\Expose]
|
||||
#[Serializer\Groups(['Default'])]
|
||||
#[OA\Property(ref: '#/components/schemas/Customer')]
|
||||
private ?Customer $customer = null;
|
||||
#[ORM\ManyToOne(targetEntity: User::class)]
|
||||
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
|
||||
#[Assert\NotNull]
|
||||
#[Serializer\Expose]
|
||||
#[Serializer\Groups(['Default'])]
|
||||
#[OA\Property(ref: '#/components/schemas/User')]
|
||||
private ?User $user = null;
|
||||
#[ORM\Column(name: 'created_at', type: 'datetime', nullable: false)]
|
||||
#[Assert\NotNull]
|
||||
|
||||
@@ -9,13 +9,14 @@
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\InvoiceTemplateRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
#[ORM\Table(name: 'kimai2_invoice_templates')]
|
||||
#[ORM\UniqueConstraint(columns: ['name'])]
|
||||
#[ORM\Entity(repositoryClass: 'App\Repository\InvoiceTemplateRepository')]
|
||||
#[ORM\Entity(repositoryClass: InvoiceTemplateRepository::class)]
|
||||
#[ORM\ChangeTrackingPolicy('DEFERRED_EXPLICIT')]
|
||||
#[UniqueEntity('name')]
|
||||
class InvoiceTemplate
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace App\Entity;
|
||||
use App\Doctrine\Behavior\CreatedAt;
|
||||
use App\Doctrine\Behavior\CreatedTrait;
|
||||
use App\Export\Annotation as Exporter;
|
||||
use App\Repository\ProjectRepository;
|
||||
use App\Validator\Constraints as Constraints;
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\Common\Collections\Collection;
|
||||
@@ -23,7 +24,7 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
#[ORM\Table(name: 'kimai2_projects')]
|
||||
#[ORM\Index(columns: ['customer_id', 'visible', 'name'])]
|
||||
#[ORM\Index(columns: ['customer_id', 'visible', 'id'])]
|
||||
#[ORM\Entity(repositoryClass: 'App\Repository\ProjectRepository')]
|
||||
#[ORM\Entity(repositoryClass: ProjectRepository::class)]
|
||||
#[ORM\ChangeTrackingPolicy('DEFERRED_EXPLICIT')]
|
||||
#[Serializer\ExclusionPolicy('all')]
|
||||
#[Serializer\VirtualProperty('CustomerName', exp: 'object.getCustomer() === null ? null : object.getCustomer().getName()', options: [new Serializer\SerializedName('parentTitle'), new Serializer\Type(name: 'string'), new Serializer\Groups(['Project'])])]
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\ProjectRateRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use JMS\Serializer\Annotation as Serializer;
|
||||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||
@@ -16,7 +17,7 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
#[ORM\Table(name: 'kimai2_projects_rates')]
|
||||
#[ORM\UniqueConstraint(columns: ['user_id', 'project_id'])]
|
||||
#[ORM\Entity(repositoryClass: 'App\Repository\ProjectRateRepository')]
|
||||
#[ORM\Entity(repositoryClass: ProjectRateRepository::class)]
|
||||
#[ORM\ChangeTrackingPolicy('DEFERRED_EXPLICIT')]
|
||||
#[UniqueEntity(['user', 'project'], ignoreNull: false)]
|
||||
#[Serializer\ExclusionPolicy('all')]
|
||||
|
||||
@@ -9,13 +9,14 @@
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\RoleRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
#[ORM\Table(name: 'kimai2_roles')]
|
||||
#[ORM\UniqueConstraint(name: 'roles_name', columns: ['name'])]
|
||||
#[ORM\Entity(repositoryClass: 'App\Repository\RoleRepository')]
|
||||
#[ORM\Entity(repositoryClass: RoleRepository::class)]
|
||||
#[ORM\ChangeTrackingPolicy('DEFERRED_EXPLICIT')]
|
||||
#[UniqueEntity('name')]
|
||||
class Role
|
||||
|
||||
@@ -9,13 +9,14 @@
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\RolePermissionRepository;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||
use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
#[ORM\Table(name: 'kimai2_roles_permissions')]
|
||||
#[ORM\UniqueConstraint(name: 'role_permission', columns: ['role_id', 'permission'])]
|
||||
#[ORM\Entity(repositoryClass: 'App\Repository\RolePermissionRepository')]
|
||||
#[ORM\Entity(repositoryClass: RolePermissionRepository::class)]
|
||||
#[ORM\ChangeTrackingPolicy('DEFERRED_EXPLICIT')]
|
||||
#[UniqueEntity(['role', 'permission'])]
|
||||
class RolePermission
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\TagRepository;
|
||||
use App\Utils\Color;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use JMS\Serializer\Annotation as Serializer;
|
||||
@@ -17,7 +18,7 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
|
||||
#[ORM\Table(name: 'kimai2_tags')]
|
||||
#[ORM\UniqueConstraint(columns: ['name'])]
|
||||
#[ORM\Entity(repositoryClass: 'App\Repository\TagRepository')]
|
||||
#[ORM\Entity(repositoryClass: TagRepository::class)]
|
||||
#[ORM\ChangeTrackingPolicy('DEFERRED_EXPLICIT')]
|
||||
#[UniqueEntity('name')]
|
||||
#[Serializer\ExclusionPolicy('all')]
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace App\Entity;
|
||||
|
||||
use App\Doctrine\Behavior\ModifiedAt;
|
||||
use App\Doctrine\Behavior\ModifiedTrait;
|
||||
use App\Repository\TimesheetRepository;
|
||||
use App\Validator\Constraints as Constraints;
|
||||
use DateTime;
|
||||
use DateTimeZone;
|
||||
@@ -37,7 +38,7 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
#[ORM\Index(columns: ['end_time', 'user', 'start_time'], name: 'IDX_TIMESHEET_TICKTAC')]
|
||||
#[ORM\Index(columns: ['user', 'project_id', 'activity_id'], name: 'IDX_TIMESHEET_RECENT_ACTIVITIES')]
|
||||
#[ORM\Index(columns: ['user', 'id', 'duration'], name: 'IDX_TIMESHEET_RESULT_STATS')]
|
||||
#[ORM\Entity(repositoryClass: 'App\Repository\TimesheetRepository')]
|
||||
#[ORM\Entity(repositoryClass: TimesheetRepository::class)]
|
||||
#[ORM\ChangeTrackingPolicy('DEFERRED_EXPLICIT')]
|
||||
#[ORM\HasLifecycleCallbacks]
|
||||
#[Serializer\ExclusionPolicy('all')]
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Export\Annotation as Exporter;
|
||||
use App\Repository\UserRepository;
|
||||
use App\Utils\StringHelper;
|
||||
use App\Validator\Constraints as Constraints;
|
||||
use App\WorkingTime\Mode\WorkingTimeModeNone;
|
||||
@@ -33,7 +34,7 @@ use Symfony\Component\Validator\Constraints as Assert;
|
||||
#[ORM\Table(name: 'kimai2_users')]
|
||||
#[ORM\UniqueConstraint(columns: ['username'])]
|
||||
#[ORM\UniqueConstraint(columns: ['email'])]
|
||||
#[ORM\Entity(repositoryClass: 'App\Repository\UserRepository')]
|
||||
#[ORM\Entity(repositoryClass: UserRepository::class)]
|
||||
#[ORM\ChangeTrackingPolicy('DEFERRED_EXPLICIT')]
|
||||
#[UniqueEntity('username')]
|
||||
#[UniqueEntity('email')]
|
||||
|
||||
@@ -66,6 +66,35 @@ class WorkingTimeRepository extends EntityRepository
|
||||
return $qb->getQuery()->getResult();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 2.25.0 - kept for BC with old plugin versions
|
||||
*/
|
||||
public function getLatestApproval(User $user): ?WorkingTime
|
||||
{
|
||||
$qb = $this->createQueryBuilder('w');
|
||||
$qb->select('MAX(DATE(w.date))')
|
||||
->where($qb->expr()->eq('w.user', ':user'))
|
||||
->setParameter('user', $user->getId())
|
||||
->andWhere($qb->expr()->isNotNull('w.approvedAt'))
|
||||
;
|
||||
|
||||
$date = $qb->getQuery()->getSingleScalarResult();
|
||||
|
||||
if ($date === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$qb = $this->createQueryBuilder('w');
|
||||
$qb->select('w')
|
||||
->where($qb->expr()->eq('w.user', ':user'))
|
||||
->setParameter('user', $user->getId())
|
||||
->andWhere($qb->expr()->eq('DATE(w.date)', 'DATE(:date)'))
|
||||
->setParameter('date', $date)
|
||||
;
|
||||
|
||||
return $qb->getQuery()->getOneOrNullResult();
|
||||
}
|
||||
|
||||
public function getLatestApprovalDate(User $user): ?\DateTimeInterface
|
||||
{
|
||||
$qb = $this->createQueryBuilder('w');
|
||||
|
||||
@@ -50,6 +50,10 @@ class SamlAuthenticator extends AbstractAuthenticator
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$request->isMethod(Request::METHOD_POST)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$this->httpUtils->checkRequestPath($request, $this->options['check_path'])) {
|
||||
return false;
|
||||
}
|
||||
@@ -81,7 +85,7 @@ class SamlAuthenticator extends AbstractAuthenticator
|
||||
|
||||
// file_put_contents(__DIR__ . '/../../var/log/saml.xml', $oneLoginAuth->getLastResponseXML());
|
||||
|
||||
if ($oneLoginAuth->getErrors()) {
|
||||
if (\count($oneLoginAuth->getErrors()) > 0) {
|
||||
throw new AuthenticationException($oneLoginAuth->getLastErrorReason());
|
||||
}
|
||||
|
||||
|
||||
@@ -24,16 +24,13 @@ final class SamlAuthenticationSuccessHandler extends DefaultAuthenticationSucces
|
||||
|
||||
protected function determineTargetUrl(Request $request): string
|
||||
{
|
||||
if ($this->options['always_use_default_target_path']) {
|
||||
return $this->options['default_target_path'];
|
||||
}
|
||||
|
||||
$relayState = $request->get('RelayState');
|
||||
$loginUrl = $this->httpUtils->generateUri($request, $this->options['login_path']);
|
||||
|
||||
if ($relayState !== null && $relayState !== '' && $relayState !== $loginUrl) {
|
||||
if (\is_scalar($relayState)) {
|
||||
$relayState = (string) $relayState;
|
||||
if ($relayState !== $this->httpUtils->generateUri($request, (string) $this->options['login_path'])) {
|
||||
return $relayState;
|
||||
}
|
||||
}
|
||||
|
||||
return parent::determineTargetUrl($request);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
|
||||
|
||||
final class TwoFactorCondition implements TwoFactorConditionInterface
|
||||
{
|
||||
public function __construct(private AuthorizationCheckerInterface $authorizationChecker)
|
||||
public function __construct(private readonly AuthorizationCheckerInterface $authorizationChecker)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -88,13 +88,12 @@ final class DataTable implements \Countable, \IteratorAggregate
|
||||
|
||||
/**
|
||||
* @param FormInterface<MultiUpdateTableDTO>|null $batchForm
|
||||
* @return void
|
||||
*/
|
||||
public function setBatchForm(?FormInterface $batchForm): void
|
||||
{
|
||||
$this->batchForm = $batchForm;
|
||||
|
||||
if (!\array_key_exists('id', $this->columns)) {
|
||||
if ($batchForm !== null && !\array_key_exists('id', $this->columns)) {
|
||||
$this->addColumn('id', [
|
||||
'class' => 'alwaysVisible multiCheckbox',
|
||||
'orderBy' => false,
|
||||
|
||||
@@ -32,6 +32,8 @@ final class WorkingTimeService
|
||||
{
|
||||
private const LATEST_APPROVAL_PREF = '_latest_approval';
|
||||
private const LATEST_APPROVAL_FORMAT = 'Y-m-d H:i:s';
|
||||
/** @var array<string, WorkingTime|null> */
|
||||
private array $latestApprovals = [];
|
||||
|
||||
public function __construct(
|
||||
private readonly TimesheetRepository $timesheetRepository,
|
||||
@@ -58,6 +60,24 @@ final class WorkingTimeService
|
||||
return $yearPerUserSummary;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated since 2.25.0 - kept for BC with old plugin versions
|
||||
*/
|
||||
public function getLatestApproval(User $user): ?WorkingTime
|
||||
{
|
||||
if ($user->getId() === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$key = 'u_' . $user->getId();
|
||||
|
||||
if (!\array_key_exists($key, $this->latestApprovals)) {
|
||||
$this->latestApprovals[$key] = $this->workingTimeRepository->getLatestApproval($user);
|
||||
}
|
||||
|
||||
return $this->latestApprovals[$key];
|
||||
}
|
||||
|
||||
public function getLatestApprovalDate(User $user): ?\DateTimeInterface
|
||||
{
|
||||
if ($user->getId() === null) {
|
||||
|
||||
@@ -188,6 +188,14 @@ abstract class APIControllerBaseTest extends ControllerBaseTest
|
||||
]);
|
||||
}
|
||||
|
||||
protected function assertBadRequestResponse(Response $response): void
|
||||
{
|
||||
$this->assertApiException($response, [
|
||||
'code' => Response::HTTP_BAD_REQUEST,
|
||||
'message' => 'Bad Request'
|
||||
]);
|
||||
}
|
||||
|
||||
protected function assertApiAccessDenied(HttpKernelBrowser $client, string $url, string $message = 'Forbidden'): void
|
||||
{
|
||||
$this->request($client, $url);
|
||||
|
||||
@@ -1173,18 +1173,9 @@ class TimesheetControllerTest extends APIControllerBaseTest
|
||||
$this->assertIsArray($result[0]);
|
||||
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
|
||||
|
||||
$query = ['tags' => ['Nothing-2-see', 'here']];
|
||||
$this->assertAccessIsGranted($client, '/api/timesheets', 'GET', $query);
|
||||
|
||||
$content = $client->getResponse()->getContent();
|
||||
self::assertIsString($content);
|
||||
$result = json_decode($content, true);
|
||||
|
||||
self::assertIsArray($result);
|
||||
self::assertNotEmpty($result);
|
||||
self::assertEquals(20, \count($result));
|
||||
$this->assertIsArray($result[0]);
|
||||
self::assertApiResponseTypeStructure('TimesheetCollection', $result[0]);
|
||||
$query = ['tags' => ['Nothing-2-see', 'not-existing-here']];
|
||||
$this->request($client, '/api/timesheets', 'GET', $query);
|
||||
$this->assertBadRequestResponse($client->getResponse());
|
||||
}
|
||||
|
||||
public function testRestartAction(): void
|
||||
|
||||
@@ -10,11 +10,13 @@
|
||||
namespace App\Tests\Controller\Auth;
|
||||
|
||||
use App\Configuration\SamlConfiguration;
|
||||
use App\Configuration\SamlConfigurationInterface;
|
||||
use App\Configuration\SystemConfiguration;
|
||||
use App\Controller\Auth\SamlController;
|
||||
use App\Saml\SamlAuthFactory;
|
||||
use App\Tests\Configuration\TestConfigLoader;
|
||||
use App\Tests\Mocks\Saml\SamlAuthFactoryFactory;
|
||||
use App\Tests\Mocks\SecurityFactory;
|
||||
use App\Tests\Mocks\SystemConfigurationFactory;
|
||||
use OneLogin\Saml2\Auth;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
@@ -28,19 +30,14 @@ use Symfony\Component\Security\Http\SecurityRequestAttributes;
|
||||
*/
|
||||
class SamlControllerTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @param array $settings
|
||||
* @param array $loaderSettings
|
||||
* @return SystemConfiguration
|
||||
*/
|
||||
protected function getSystemConfigurationMock(array $settings, array $loaderSettings = [])
|
||||
protected function getSystemConfigurationMock(array $settings, array $loaderSettings = []): SystemConfiguration
|
||||
{
|
||||
$loader = new TestConfigLoader($loaderSettings);
|
||||
|
||||
return SystemConfigurationFactory::create($loader, $settings);
|
||||
}
|
||||
|
||||
protected function getDefaultSettings(bool $activated = true)
|
||||
protected function getDefaultSettings(bool $activated = true): array
|
||||
{
|
||||
return [
|
||||
'saml' => [
|
||||
@@ -66,10 +63,15 @@ class SamlControllerTest extends TestCase
|
||||
|
||||
$factory = $this->getMockBuilder(SamlAuthFactory::class)->disableOriginalConstructor()->getMock();
|
||||
|
||||
$sut = new SamlController($factory, $this->getSamlConfiguration());
|
||||
$sut = $this->getSut($factory, $this->getSamlConfiguration());
|
||||
$sut->assertionConsumerServiceAction();
|
||||
}
|
||||
|
||||
public function getSut(SamlAuthFactory $authFactory, SamlConfigurationInterface $samlConfiguration): SamlController
|
||||
{
|
||||
return new SamlController($authFactory, $samlConfiguration, (new SecurityFactory($this))->create());
|
||||
}
|
||||
|
||||
public function testMetadataAction(): void
|
||||
{
|
||||
$expectedXmlString = <<<EOD
|
||||
@@ -101,7 +103,7 @@ class SamlControllerTest extends TestCase
|
||||
$factory = $this->getMockBuilder(SamlAuthFactory::class)->disableOriginalConstructor()->getMock();
|
||||
$factory->expects($this->once())->method('create')->willReturn($oauth);
|
||||
|
||||
$sut = new SamlController($factory, $this->getSamlConfiguration());
|
||||
$sut = $this->getSut($factory, $this->getSamlConfiguration());
|
||||
$result = $sut->metadataAction();
|
||||
|
||||
self::assertEquals('xml', $result->headers->get('Content-Type'));
|
||||
@@ -129,7 +131,7 @@ class SamlControllerTest extends TestCase
|
||||
|
||||
$factory = $this->getMockBuilder(SamlAuthFactory::class)->disableOriginalConstructor()->getMock();
|
||||
|
||||
$sut = new SamlController($factory, $this->getSamlConfiguration());
|
||||
$sut = $this->getSut($factory, $this->getSamlConfiguration());
|
||||
$sut->loginAction($request);
|
||||
}
|
||||
|
||||
@@ -140,7 +142,7 @@ class SamlControllerTest extends TestCase
|
||||
|
||||
$factory = $this->getMockBuilder(SamlAuthFactory::class)->disableOriginalConstructor()->getMock();
|
||||
|
||||
$sut = new SamlController($factory, $this->getSamlConfiguration(false));
|
||||
$sut = $this->getSut($factory, $this->getSamlConfiguration(false));
|
||||
$sut->loginAction(new Request());
|
||||
}
|
||||
|
||||
@@ -151,7 +153,7 @@ class SamlControllerTest extends TestCase
|
||||
|
||||
$factory = $this->getMockBuilder(SamlAuthFactory::class)->disableOriginalConstructor()->getMock();
|
||||
|
||||
$sut = new SamlController($factory, $this->getSamlConfiguration(false));
|
||||
$sut = $this->getSut($factory, $this->getSamlConfiguration(false));
|
||||
$sut->metadataAction();
|
||||
}
|
||||
|
||||
@@ -162,7 +164,7 @@ class SamlControllerTest extends TestCase
|
||||
|
||||
$factory = $this->getMockBuilder(SamlAuthFactory::class)->disableOriginalConstructor()->getMock();
|
||||
|
||||
$sut = new SamlController($factory, $this->getSamlConfiguration(false));
|
||||
$sut = $this->getSut($factory, $this->getSamlConfiguration(false));
|
||||
$sut->logoutAction();
|
||||
}
|
||||
|
||||
@@ -173,7 +175,7 @@ class SamlControllerTest extends TestCase
|
||||
|
||||
$factory = $this->getMockBuilder(SamlAuthFactory::class)->disableOriginalConstructor()->getMock();
|
||||
|
||||
$sut = new SamlController($factory, $this->getSamlConfiguration(false));
|
||||
$sut = $this->getSut($factory, $this->getSamlConfiguration(false));
|
||||
$sut->assertionConsumerServiceAction();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,6 +290,7 @@ class ConfigurationTest extends TestCase
|
||||
'break_warning_duration' => 0,
|
||||
'long_running_duration' => 0,
|
||||
'require_activity' => true,
|
||||
'break_time_active' => false,
|
||||
],
|
||||
'duration_increment' => 15,
|
||||
'time_increment' => 15,
|
||||
|
||||
31
tests/Mocks/SecurityFactory.php
Normal file
31
tests/Mocks/SecurityFactory.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?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\Mocks;
|
||||
|
||||
use App\Entity\User;
|
||||
use Symfony\Bundle\SecurityBundle\Security;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
|
||||
|
||||
class SecurityFactory extends AbstractMockFactory
|
||||
{
|
||||
public function create(): Security
|
||||
{
|
||||
$interface = $this->createMock(TokenInterface::class);
|
||||
$interface->method('getUser')->willReturn(new User());
|
||||
$storage = $this->createMock(TokenStorageInterface::class);
|
||||
$storage->method('getToken')->willReturn($interface);
|
||||
$container = $this->createMock(ContainerInterface::class);
|
||||
$container->method('get')->willReturn($storage);
|
||||
|
||||
return new Security($container);
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ use App\Saml\Security\SamlAuthenticationSuccessHandler;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\HttpFoundation\RedirectResponse;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
use Symfony\Component\Security\Http\HttpUtils;
|
||||
|
||||
/**
|
||||
@@ -22,19 +23,9 @@ use Symfony\Component\Security\Http\HttpUtils;
|
||||
*/
|
||||
class SamlAuthenticationSuccessHandlerTest extends TestCase
|
||||
{
|
||||
public function testWithAlwaysUseDefaultTargetPath(): void
|
||||
{
|
||||
$httpUtils = new HttpUtils($this->getUrlGenerator());
|
||||
$handler = new SamlAuthenticationSuccessHandler($httpUtils, ['always_use_default_target_path' => true]);
|
||||
$defaultTargetPath = $httpUtils->generateUri($this->getRequest('/sso/login'), $this->getOption($handler, 'default_target_path', '/'));
|
||||
$response = $handler->onAuthenticationSuccess($this->getRequest('/login', 'http://localhost/relayed'), $this->getSamlToken());
|
||||
$this->assertInstanceOf(RedirectResponse::class, $response);
|
||||
$this->assertTrue($response->isRedirect($defaultTargetPath));
|
||||
}
|
||||
|
||||
public function testRelayState(): void
|
||||
{
|
||||
$handler = new SamlAuthenticationSuccessHandler(new HttpUtils($this->getUrlGenerator()), ['always_use_default_target_path' => false]);
|
||||
$handler = new SamlAuthenticationSuccessHandler(new HttpUtils($this->getUrlGenerator()));
|
||||
$response = $handler->onAuthenticationSuccess($this->getRequest('/sso/login', 'http://localhost/relayed'), $this->getSamlToken());
|
||||
$this->assertInstanceOf(RedirectResponse::class, $response);
|
||||
$this->assertTrue($response->isRedirect('http://localhost/relayed'));
|
||||
@@ -43,8 +34,8 @@ class SamlAuthenticationSuccessHandlerTest extends TestCase
|
||||
public function testWithoutRelayState(): void
|
||||
{
|
||||
$httpUtils = new HttpUtils($this->getUrlGenerator());
|
||||
$handler = new SamlAuthenticationSuccessHandler($httpUtils, ['always_use_default_target_path' => false]);
|
||||
$defaultTargetPath = $httpUtils->generateUri($this->getRequest('/sso/login'), $this->getOption($handler, 'default_target_path', '/'));
|
||||
$handler = new SamlAuthenticationSuccessHandler($httpUtils);
|
||||
$defaultTargetPath = $httpUtils->generateUri($this->getRequest('/sso/login'), '/');
|
||||
$response = $handler->onAuthenticationSuccess($this->getRequest(), $this->getSamlToken());
|
||||
$this->assertInstanceOf(RedirectResponse::class, $response);
|
||||
$this->assertTrue($response->isRedirect($defaultTargetPath));
|
||||
@@ -53,16 +44,16 @@ class SamlAuthenticationSuccessHandlerTest extends TestCase
|
||||
public function testRelayStateLoop(): void
|
||||
{
|
||||
$httpUtils = new HttpUtils($this->getUrlGenerator());
|
||||
$handler = new SamlAuthenticationSuccessHandler($httpUtils, ['always_use_default_target_path' => false]);
|
||||
$loginPath = $httpUtils->generateUri($this->getRequest('/sso/login'), $this->getOption($handler, 'login_path', '/login'));
|
||||
$handler = new SamlAuthenticationSuccessHandler($httpUtils);
|
||||
$loginPath = $httpUtils->generateUri($this->getRequest('/sso/login'), '/login');
|
||||
$response = $handler->onAuthenticationSuccess($this->getRequest($loginPath), $this->getSamlToken());
|
||||
$this->assertInstanceOf(RedirectResponse::class, $response);
|
||||
$this->assertTrue(!$response->isRedirect($loginPath));
|
||||
}
|
||||
|
||||
private function getUrlGenerator()
|
||||
private function getUrlGenerator(): UrlGeneratorInterface
|
||||
{
|
||||
$urlGenerator = $this->getMockBuilder('Symfony\Component\Routing\Generator\UrlGeneratorInterface')->getMock();
|
||||
$urlGenerator = $this->getMockBuilder(UrlGeneratorInterface::class)->getMock();
|
||||
$urlGenerator
|
||||
->expects($this->any())
|
||||
->method('generate')
|
||||
@@ -74,7 +65,7 @@ class SamlAuthenticationSuccessHandlerTest extends TestCase
|
||||
return $urlGenerator;
|
||||
}
|
||||
|
||||
private function getRequest($path = '/', $relayState = null)
|
||||
private function getRequest(string $path = '/', ?string $relayState = null): Request
|
||||
{
|
||||
$params = [];
|
||||
if (null !== $relayState) {
|
||||
@@ -84,7 +75,7 @@ class SamlAuthenticationSuccessHandlerTest extends TestCase
|
||||
return Request::create($path, 'get', $params);
|
||||
}
|
||||
|
||||
private function getSamlToken()
|
||||
private function getSamlToken(): SamlToken
|
||||
{
|
||||
$user = new User();
|
||||
$user->setUserIdentifier('admin');
|
||||
@@ -94,17 +85,4 @@ class SamlAuthenticationSuccessHandlerTest extends TestCase
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
private function getOption($handler, $name, $default = null)
|
||||
{
|
||||
$reflection = new \ReflectionObject($handler);
|
||||
$options = $reflection->getProperty('options');
|
||||
$options->setAccessible(true);
|
||||
$arr = $options->getValue($handler);
|
||||
if (!\is_array($arr) || !isset($arr[$name])) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
return $arr[$name];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -599,11 +599,6 @@ parameters:
|
||||
count: 2
|
||||
path: Controller/Auth/SamlControllerTest.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Tests\\\\Controller\\\\Auth\\\\SamlControllerTest\\:\\:getDefaultSettings\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: Controller/Auth/SamlControllerTest.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Tests\\\\Controller\\\\Auth\\\\SamlControllerTest\\:\\:getSystemConfigurationMock\\(\\) has parameter \\$loaderSettings with no value type specified in iterable type array\\.$#"
|
||||
count: 1
|
||||
@@ -2364,51 +2359,6 @@ parameters:
|
||||
count: 1
|
||||
path: Saml/SamlProviderTest.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Tests\\\\Saml\\\\Security\\\\SamlAuthenticationSuccessHandlerTest\\:\\:getOption\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: Saml/Security/SamlAuthenticationSuccessHandlerTest.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Tests\\\\Saml\\\\Security\\\\SamlAuthenticationSuccessHandlerTest\\:\\:getOption\\(\\) has parameter \\$default with no type specified\\.$#"
|
||||
count: 1
|
||||
path: Saml/Security/SamlAuthenticationSuccessHandlerTest.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Tests\\\\Saml\\\\Security\\\\SamlAuthenticationSuccessHandlerTest\\:\\:getOption\\(\\) has parameter \\$handler with no type specified\\.$#"
|
||||
count: 1
|
||||
path: Saml/Security/SamlAuthenticationSuccessHandlerTest.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Tests\\\\Saml\\\\Security\\\\SamlAuthenticationSuccessHandlerTest\\:\\:getOption\\(\\) has parameter \\$name with no type specified\\.$#"
|
||||
count: 1
|
||||
path: Saml/Security/SamlAuthenticationSuccessHandlerTest.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Tests\\\\Saml\\\\Security\\\\SamlAuthenticationSuccessHandlerTest\\:\\:getRequest\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: Saml/Security/SamlAuthenticationSuccessHandlerTest.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Tests\\\\Saml\\\\Security\\\\SamlAuthenticationSuccessHandlerTest\\:\\:getRequest\\(\\) has parameter \\$path with no type specified\\.$#"
|
||||
count: 1
|
||||
path: Saml/Security/SamlAuthenticationSuccessHandlerTest.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Tests\\\\Saml\\\\Security\\\\SamlAuthenticationSuccessHandlerTest\\:\\:getRequest\\(\\) has parameter \\$relayState with no type specified\\.$#"
|
||||
count: 1
|
||||
path: Saml/Security/SamlAuthenticationSuccessHandlerTest.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Tests\\\\Saml\\\\Security\\\\SamlAuthenticationSuccessHandlerTest\\:\\:getSamlToken\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: Saml/Security/SamlAuthenticationSuccessHandlerTest.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Tests\\\\Saml\\\\Security\\\\SamlAuthenticationSuccessHandlerTest\\:\\:getUrlGenerator\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
path: Saml/Security/SamlAuthenticationSuccessHandlerTest.php
|
||||
|
||||
-
|
||||
message: "#^Method App\\\\Tests\\\\Timesheet\\\\Calculator\\\\BillableCalculatorTest\\:\\:getTestData\\(\\) has no return type specified\\.$#"
|
||||
count: 1
|
||||
|
||||
@@ -1930,6 +1930,14 @@
|
||||
<source>import</source>
|
||||
<target>Import</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="EiWubBr" resname="sessions">
|
||||
<source>Sessions</source>
|
||||
<target>Sitzungen</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="f0kKHXR" resname="sessions_logout">
|
||||
<source>Log out of all devices</source>
|
||||
<target>Von allen Geräten abmelden</target>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
|
||||
@@ -1930,6 +1930,14 @@
|
||||
<source>import</source>
|
||||
<target>Import</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="EiWubBr" resname="sessions">
|
||||
<source>Sessions</source>
|
||||
<target>Sessions</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="f0kKHXR" resname="sessions_logout">
|
||||
<source>Log out of all devices</source>
|
||||
<target>Log out of all devices</target>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
||||
|
||||
Reference in New Issue
Block a user