random improvements (#5506)
* fix order of destroying form elements * API documentation - fixes #1949 * make command path independent * use PhpSubprocess to execute migrations * allow to configure optional SAML attributes * fix deprecation * unify wording of exported state - fixes #5392
This commit is contained in:
@@ -40,16 +40,18 @@ export default class KimaiTimesheetForm extends KimaiFormPlugin {
|
|||||||
|
|
||||||
if (this._beginTime !== undefined) {
|
if (this._beginTime !== undefined) {
|
||||||
this._beginTime.removeEventListener('change', this._beginListener);
|
this._beginTime.removeEventListener('change', this._beginListener);
|
||||||
delete this._beginTime;
|
delete this._beginListener;
|
||||||
this._beginTime.removeEventListener('blur', this._beginBlurListener);
|
this._beginTime.removeEventListener('blur', this._beginBlurListener);
|
||||||
delete this._beginBlurListener;
|
delete this._beginBlurListener;
|
||||||
|
delete this._beginTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._endTime !== undefined) {
|
if (this._endTime !== undefined) {
|
||||||
this._endTime.removeEventListener('change', this._endListener);
|
this._endTime.removeEventListener('change', this._endListener);
|
||||||
delete this._endTime;
|
delete this._endListener;
|
||||||
this._endTime.removeEventListener('blur', this._endBlurListener);
|
this._endTime.removeEventListener('blur', this._endBlurListener);
|
||||||
delete this._endBlurListener;
|
delete this._endBlurListener;
|
||||||
|
delete this._endTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._duration !== undefined) {
|
if (this._duration !== undefined) {
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ parameters:
|
|||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Cannot call method find\\(\\) on Symfony\\\\Component\\\\Console\\\\Application\\|null\\.$#"
|
message: "#^Cannot call method find\\(\\) on Symfony\\\\Component\\\\Console\\\\Application\\|null\\.$#"
|
||||||
count: 2
|
count: 1
|
||||||
path: src/Command/AbstractBundleInstallerCommand.php
|
path: src/Command/AbstractBundleInstallerCommand.php
|
||||||
|
|
||||||
-
|
-
|
||||||
@@ -423,11 +423,6 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: src/Command/ReloadCommand.php
|
path: src/Command/ReloadCommand.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Cannot call method find\\(\\) on Symfony\\\\Component\\\\Console\\\\Application\\|null\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: src/Command/ResetDevelopmentCommand.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Access to an undefined property SimpleXMLElement\\|false\\:\\:\\$file\\.$#"
|
message: "#^Access to an undefined property SimpleXMLElement\\|false\\:\\:\\$file\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
2
public/build/app.f98f56cc.js
Normal file
2
public/build/app.f98f56cc.js
Normal file
File diff suppressed because one or more lines are too long
@@ -3,7 +3,7 @@
|
|||||||
"app": {
|
"app": {
|
||||||
"js": [
|
"js": [
|
||||||
"/build/runtime.6c399d29.js",
|
"/build/runtime.6c399d29.js",
|
||||||
"/build/app.4f0fd3e5.js"
|
"/build/app.f98f56cc.js"
|
||||||
],
|
],
|
||||||
"css": [
|
"css": [
|
||||||
"/build/app.0416ea92.css"
|
"/build/app.0416ea92.css"
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
},
|
},
|
||||||
"integrity": {
|
"integrity": {
|
||||||
"/build/runtime.6c399d29.js": "sha384-/rm616f12czi8l/27GvWXtb3g608vJZf2XTUKxqCRI4tsa2vUHP+BW90edTok5zC",
|
"/build/runtime.6c399d29.js": "sha384-/rm616f12czi8l/27GvWXtb3g608vJZf2XTUKxqCRI4tsa2vUHP+BW90edTok5zC",
|
||||||
"/build/app.4f0fd3e5.js": "sha384-p1bPGT8nZ43+h+77R5zqIOwLx0RIESeYGq9x/3FxDd5V43nw2aF6263pbj9oa57M",
|
"/build/app.f98f56cc.js": "sha384-YWrfaZO6uvTBk94Q/zlqG56vaP68xEWYD1YunerAX1rMJcRMFSzi8fp7TVGeJm78",
|
||||||
"/build/app.0416ea92.css": "sha384-JAIO6+B/vmV8IlpsmQ+zkfO4JsvdKTMsZZaDg6EMlZxkMZ5K3cpEK0mo24D/Wgkh",
|
"/build/app.0416ea92.css": "sha384-JAIO6+B/vmV8IlpsmQ+zkfO4JsvdKTMsZZaDg6EMlZxkMZ5K3cpEK0mo24D/Wgkh",
|
||||||
"/build/app-rtl.7a875ca7.js": "sha384-T7gLI61h9dGeMgzo63vKu4GiDOeLPct9zSUHrceNbhSwIdUmSSNoZ1+d7fKhJJ4/",
|
"/build/app-rtl.7a875ca7.js": "sha384-T7gLI61h9dGeMgzo63vKu4GiDOeLPct9zSUHrceNbhSwIdUmSSNoZ1+d7fKhJJ4/",
|
||||||
"/build/app-rtl.0848906b.css": "sha384-O26Xw3P/NSea5iT6lt5v2RaZ6+jP06hf9vYD2pUJbkFSCLGU1iGXnobP25dWjUs/",
|
"/build/app-rtl.0848906b.css": "sha384-O26Xw3P/NSea5iT6lt5v2RaZ6+jP06hf9vYD2pUJbkFSCLGU1iGXnobP25dWjUs/",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"build/app.css": "/build/app.0416ea92.css",
|
"build/app.css": "/build/app.0416ea92.css",
|
||||||
"build/app.js": "/build/app.4f0fd3e5.js",
|
"build/app.js": "/build/app.f98f56cc.js",
|
||||||
"build/app-rtl.css": "/build/app-rtl.0848906b.css",
|
"build/app-rtl.css": "/build/app-rtl.0848906b.css",
|
||||||
"build/app-rtl.js": "/build/app-rtl.7a875ca7.js",
|
"build/app-rtl.js": "/build/app-rtl.7a875ca7.js",
|
||||||
"build/export-pdf.css": "/build/export-pdf.d8a6c23b.css",
|
"build/export-pdf.css": "/build/export-pdf.d8a6c23b.css",
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ use Symfony\Component\Console\Input\InputOption;
|
|||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
use Symfony\Component\Filesystem\Exception\FileNotFoundException;
|
use Symfony\Component\Filesystem\Exception\FileNotFoundException;
|
||||||
|
use Symfony\Component\Process\PhpSubprocess;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extend this class if you have a plugin that requires installation steps.
|
* Extend this class if you have a plugin that requires installation steps.
|
||||||
@@ -36,8 +37,7 @@ abstract class AbstractBundleInstallerCommand extends Command
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If your bundle ships assets, that need to be available in the public/ directory,
|
* Return <true> if your bundle ships assets for the public/ directory.
|
||||||
* then overwrite this method and return: <true>.
|
|
||||||
*/
|
*/
|
||||||
protected function hasAssets(): bool
|
protected function hasAssets(): bool
|
||||||
{
|
{
|
||||||
@@ -189,13 +189,28 @@ abstract class AbstractBundleInstallerCommand extends Command
|
|||||||
// prevent windows from breaking
|
// prevent windows from breaking
|
||||||
$config = str_replace('/', DIRECTORY_SEPARATOR, $config);
|
$config = str_replace('/', DIRECTORY_SEPARATOR, $config);
|
||||||
|
|
||||||
$command = $this->getApplication()->find('doctrine:migrations:migrate');
|
$process = new PhpSubprocess(
|
||||||
$cmdInput = new ArrayInput(['--allow-no-migration' => true, '--configuration' => $config]);
|
[
|
||||||
$cmdInput->setInteractive(false);
|
'bin/console',
|
||||||
if (0 !== $command->run($cmdInput, $output)) {
|
'doctrine:migrations:migrate',
|
||||||
|
'--allow-no-migration',
|
||||||
|
'--no-interaction',
|
||||||
|
'--configuration=' . $config
|
||||||
|
],
|
||||||
|
$this->getRootDirectory()
|
||||||
|
);
|
||||||
|
$process->run();
|
||||||
|
|
||||||
|
if (!$process->isSuccessful()) {
|
||||||
|
$io->error('Failed to install bundle database: ' . PHP_EOL . $config);
|
||||||
|
$io->error($process->getErrorOutput());
|
||||||
throw new \Exception('Problem occurred while executing migrations.');
|
throw new \Exception('Problem occurred while executing migrations.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($io->isVerbose()) {
|
||||||
|
$io->write($process->getOutput());
|
||||||
|
}
|
||||||
|
|
||||||
$io->writeln('');
|
$io->writeln('');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ abstract class AbstractResetCommand extends Command
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$command = $this->getApplication()->find('doctrine:query:sql');
|
$command = $this->getApplication()->find('dbal:run-sql');
|
||||||
$command->run(new ArrayInput(['sql' => 'DROP TABLE IF EXISTS migration_versions']), $output);
|
$command->run(new ArrayInput(['sql' => 'DROP TABLE IF EXISTS migration_versions']), $output);
|
||||||
} catch (Exception $ex) {
|
} catch (Exception $ex) {
|
||||||
$io->error('Failed to drop migration_versions table: ' . $ex->getMessage());
|
$io->error('Failed to drop migration_versions table: ' . $ex->getMessage());
|
||||||
@@ -125,7 +125,7 @@ abstract class AbstractResetCommand extends Command
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$command = $this->getApplication()->find('doctrine:query:sql');
|
$command = $this->getApplication()->find('dbal:run-sql');
|
||||||
$command->run(new ArrayInput(['sql' => 'DROP TABLE IF EXISTS kimai2_sessions']), $output);
|
$command->run(new ArrayInput(['sql' => 'DROP TABLE IF EXISTS kimai2_sessions']), $output);
|
||||||
} catch (Exception $ex) {
|
} catch (Exception $ex) {
|
||||||
$io->error('Failed to drop kimai2_sessions table: ' . $ex->getMessage());
|
$io->error('Failed to drop kimai2_sessions table: ' . $ex->getMessage());
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ final class PluginCommand extends Command
|
|||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private readonly PluginManager $pluginManager,
|
private readonly PluginManager $pluginManager,
|
||||||
private readonly PackageManager $packageManager
|
private readonly PackageManager $packageManager,
|
||||||
|
private readonly string $projectDirectory
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
parent::__construct();
|
parent::__construct();
|
||||||
@@ -69,17 +70,16 @@ final class PluginCommand extends Command
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// using getApplication()->find('doctrine:migrations:migrate') does NOT work here
|
$process = new PhpSubprocess(
|
||||||
// because the Doctrine command can only be executed once
|
[
|
||||||
// if run more than once it fails with a "Container is frozen" exception
|
'bin/console',
|
||||||
|
'doctrine:migrations:migrate',
|
||||||
$process = new PhpSubprocess([
|
'--allow-no-migration',
|
||||||
'bin/console',
|
'--no-interaction',
|
||||||
'doctrine:migrations:migrate',
|
'--configuration=' . $config
|
||||||
'--allow-no-migration',
|
],
|
||||||
'--no-interaction',
|
$this->projectDirectory
|
||||||
'--configuration=' . $config
|
);
|
||||||
]);
|
|
||||||
$process->run();
|
$process->run();
|
||||||
|
|
||||||
if (!$process->isSuccessful()) {
|
if (!$process->isSuccessful()) {
|
||||||
|
|||||||
@@ -10,9 +10,10 @@
|
|||||||
namespace App\Command;
|
namespace App\Command;
|
||||||
|
|
||||||
use Symfony\Component\Console\Attribute\AsCommand;
|
use Symfony\Component\Console\Attribute\AsCommand;
|
||||||
use Symfony\Component\Console\Input\ArrayInput;
|
|
||||||
use Symfony\Component\Console\Input\InputInterface;
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
use Symfony\Component\Console\Output\OutputInterface;
|
use Symfony\Component\Console\Output\OutputInterface;
|
||||||
|
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||||
|
use Symfony\Component\Process\PhpSubprocess;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Command used to execute all the basic application bootstrapping AFTER "composer install" was executed.
|
* Command used to execute all the basic application bootstrapping AFTER "composer install" was executed.
|
||||||
@@ -24,16 +25,36 @@ use Symfony\Component\Console\Output\OutputInterface;
|
|||||||
#[AsCommand(name: 'kimai:reset:dev', description: 'Resets the "development" environment')]
|
#[AsCommand(name: 'kimai:reset:dev', description: 'Resets the "development" environment')]
|
||||||
final class ResetDevelopmentCommand extends AbstractResetCommand
|
final class ResetDevelopmentCommand extends AbstractResetCommand
|
||||||
{
|
{
|
||||||
public function __construct(string $kernelEnvironment)
|
public function __construct(string $kernelEnvironment, private readonly string $projectDirectory)
|
||||||
{
|
{
|
||||||
parent::__construct($kernelEnvironment);
|
parent::__construct($kernelEnvironment);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function loadData(InputInterface $input, OutputInterface $output): void
|
protected function loadData(InputInterface $input, OutputInterface $output): void
|
||||||
{
|
{
|
||||||
$command = $this->getApplication()->find('doctrine:fixtures:load');
|
$io = new SymfonyStyle($input, $output);
|
||||||
$cmdInput = new ArrayInput([]);
|
|
||||||
$cmdInput->setInteractive(false);
|
$io->writeln('Importing fixtures, this will take a while ... please be patient ...');
|
||||||
$command->run($cmdInput, $output);
|
|
||||||
|
$process = new PhpSubprocess(
|
||||||
|
[
|
||||||
|
'bin/console',
|
||||||
|
'doctrine:fixtures:load',
|
||||||
|
'--no-interaction',
|
||||||
|
],
|
||||||
|
$this->projectDirectory
|
||||||
|
);
|
||||||
|
$process->run();
|
||||||
|
|
||||||
|
if (!$process->isSuccessful()) {
|
||||||
|
$io->error('Failed to load fixtures');
|
||||||
|
$io->error($process->getErrorOutput());
|
||||||
|
} else {
|
||||||
|
if ($io->isVerbose()) {
|
||||||
|
$io->write($process->getOutput());
|
||||||
|
} else {
|
||||||
|
$io->success('Fixtures loaded');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,11 @@ final class TimesheetApiEditForm extends TimesheetEditForm
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$builder->add('billable', BillableType::class);
|
$builder->add('billable', BillableType::class, [
|
||||||
|
'documentation' => [
|
||||||
|
'description' => 'If true, this timesheet will be flagged as billable'
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
$builder->addEventListener(
|
$builder->addEventListener(
|
||||||
FormEvents::PRE_SUBMIT,
|
FormEvents::PRE_SUBMIT,
|
||||||
@@ -56,6 +60,7 @@ final class TimesheetApiEditForm extends TimesheetEditForm
|
|||||||
$builder->remove('metaFields');
|
$builder->remove('metaFields');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO this is only a quick fix, see bugs reports
|
||||||
if ($builder->has('duration')) {
|
if ($builder->has('duration')) {
|
||||||
$builder->remove('duration');
|
$builder->remove('duration');
|
||||||
}
|
}
|
||||||
@@ -64,11 +69,6 @@ final class TimesheetApiEditForm extends TimesheetEditForm
|
|||||||
$builder->get('user')->setRequired(false);
|
$builder->get('user')->setRequired(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO this is only a quick fix, see bugs reports
|
|
||||||
if ($builder->has('duration')) {
|
|
||||||
$builder->remove('duration');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($builder->has('tags')) {
|
if ($builder->has('tags')) {
|
||||||
$builder->remove('tags');
|
$builder->remove('tags');
|
||||||
// @deprecated for BC reasons here, arrays will be supported in 2.0
|
// @deprecated for BC reasons here, arrays will be supported in 2.0
|
||||||
@@ -82,6 +82,10 @@ final class TimesheetApiEditForm extends TimesheetEditForm
|
|||||||
{
|
{
|
||||||
$builder->add('begin', DateTimeApiType::class, array_merge($dateTimeOptions, [
|
$builder->add('begin', DateTimeApiType::class, array_merge($dateTimeOptions, [
|
||||||
'label' => 'begin',
|
'label' => 'begin',
|
||||||
|
'required' => false,
|
||||||
|
'documentation' => [
|
||||||
|
'description' => 'If no begin date-time is set, the users current timestamp will be used'
|
||||||
|
]
|
||||||
]));
|
]));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,6 +94,9 @@ final class TimesheetApiEditForm extends TimesheetEditForm
|
|||||||
$builder->add('end', DateTimeApiType::class, array_merge($dateTimeOptions, [
|
$builder->add('end', DateTimeApiType::class, array_merge($dateTimeOptions, [
|
||||||
'label' => 'end',
|
'label' => 'end',
|
||||||
'required' => false,
|
'required' => false,
|
||||||
|
'documentation' => [
|
||||||
|
'description' => 'If no end date-time is set, the timesheet will be running'
|
||||||
|
]
|
||||||
]));
|
]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -183,8 +183,8 @@ final class TimesheetMultiUpdate extends AbstractType
|
|||||||
'label' => 'mark_as_exported',
|
'label' => 'mark_as_exported',
|
||||||
'required' => false,
|
'required' => false,
|
||||||
'choices' => [
|
'choices' => [
|
||||||
'entryState.exported' => true,
|
'yes' => true,
|
||||||
'entryState.not_exported' => false
|
'no' => false
|
||||||
]
|
]
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,10 @@ class TimesheetEditForm extends AbstractType
|
|||||||
{
|
{
|
||||||
use FormTrait;
|
use FormTrait;
|
||||||
|
|
||||||
public function __construct(private CustomerRepository $customers, private SystemConfiguration $systemConfiguration)
|
public function __construct(
|
||||||
|
private readonly CustomerRepository $customers,
|
||||||
|
private readonly SystemConfiguration $systemConfiguration
|
||||||
|
)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -400,14 +403,17 @@ class TimesheetEditForm extends AbstractType
|
|||||||
}
|
}
|
||||||
|
|
||||||
$builder->add('exported', YesNoType::class, [
|
$builder->add('exported', YesNoType::class, [
|
||||||
'label' => 'exported'
|
'label' => 'exported',
|
||||||
|
'documentation' => [
|
||||||
|
'description' => 'If true, this timesheet will be flagged as being exported'
|
||||||
|
]
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function addBillable(FormBuilderInterface $builder, array $options): void
|
protected function addBillable(FormBuilderInterface $builder, array $options): void
|
||||||
{
|
{
|
||||||
if ($options['include_billable']) {
|
if ($options['include_billable']) {
|
||||||
$builder->add('billableMode', TimesheetBillableType::class, []);
|
$builder->add('billableMode', TimesheetBillableType::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
$builder->addModelTransformer(new CallbackTransformer(
|
$builder->addModelTransformer(new CallbackTransformer(
|
||||||
|
|||||||
@@ -97,6 +97,9 @@ final class SamlProvider
|
|||||||
$field = $mapping['kimai'];
|
$field = $mapping['kimai'];
|
||||||
$attribute = $mapping['saml'];
|
$attribute = $mapping['saml'];
|
||||||
$value = $this->getPropertyValue($token, $attribute);
|
$value = $this->getPropertyValue($token, $attribute);
|
||||||
|
if ($value === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
$setter = 'set' . ucfirst($field);
|
$setter = 'set' . ucfirst($field);
|
||||||
if (method_exists($user, $setter)) {
|
if (method_exists($user, $setter)) {
|
||||||
$user->$setter($value);
|
$user->$setter($value);
|
||||||
@@ -117,7 +120,7 @@ final class SamlProvider
|
|||||||
$user->setAuth(User::AUTH_SAML);
|
$user->setAuth(User::AUTH_SAML);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getPropertyValue(SamlLoginAttributes $token, $attribute): string
|
private function getPropertyValue(SamlLoginAttributes $token, $attribute): ?string
|
||||||
{
|
{
|
||||||
$results = [];
|
$results = [];
|
||||||
$attributes = $token->getAttributes();
|
$attributes = $token->getAttributes();
|
||||||
@@ -129,11 +132,16 @@ final class SamlProvider
|
|||||||
}
|
}
|
||||||
if ($part[0] === '$') {
|
if ($part[0] === '$') {
|
||||||
$key = substr($part, 1);
|
$key = substr($part, 1);
|
||||||
if (!\array_key_exists($key, $attributes)) {
|
$optional = false;
|
||||||
|
if ($key[0] === '$') {
|
||||||
|
$key = substr($key, 1);
|
||||||
|
$optional = true;
|
||||||
|
}
|
||||||
|
if (!$optional && !\array_key_exists($key, $attributes)) {
|
||||||
throw new \RuntimeException('Missing SAML attribute in response: ' . $key);
|
throw new \RuntimeException('Missing SAML attribute in response: ' . $key);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (\is_array($attributes[$key]) && isset($attributes[$key][0])) {
|
if (\array_key_exists($key, $attributes) && \is_array($attributes[$key]) && isset($attributes[$key][0])) {
|
||||||
$results[] = $attributes[$key][0];
|
$results[] = $attributes[$key][0];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -145,6 +153,6 @@ final class SamlProvider
|
|||||||
return implode(' ', $results);
|
return implode(' ', $results);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $attribute;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -275,6 +275,10 @@
|
|||||||
{
|
{
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
.items th.column-exported
|
||||||
|
{
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
.table>thead:first-child>tr:first-child>th {
|
.table>thead:first-child>tr:first-child>th {
|
||||||
padding-right: 5px;
|
padding-right: 5px;
|
||||||
}
|
}
|
||||||
@@ -668,12 +672,8 @@
|
|||||||
{{ entry.description|desc2html }}
|
{{ entry.description|desc2html }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td class="column-exported" {% if not columns.exported %}style="display: none"{% endif %}>
|
<td class="column-exported text-center" {% if not columns.exported %}style="display: none"{% endif %}>
|
||||||
{% if entry.exported %}
|
{{ widgets.label_boolean(entry.exported) }}
|
||||||
{{ 'entryState.exported'|trans }}
|
|
||||||
{% else %}
|
|
||||||
{{ 'entryState.not_exported'|trans }}
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
</td>
|
||||||
<td class="column-tags" {% if not columns.tags %}style="display: none"{% endif %}>
|
<td class="column-tags" {% if not columns.tags %}style="display: none"{% endif %}>
|
||||||
{% if entry.tags is defined and entry.tags is not empty %}
|
{% if entry.tags is defined and entry.tags is not empty %}
|
||||||
|
|||||||
@@ -40,9 +40,10 @@ class PluginCommandTest extends KernelTestCase
|
|||||||
|
|
||||||
private function getCommandTester(array $plugins, array $options = []): CommandTester
|
private function getCommandTester(array $plugins, array $options = []): CommandTester
|
||||||
{
|
{
|
||||||
|
$projectDirectory = __DIR__ . '/../../';
|
||||||
$kernel = self::bootKernel();
|
$kernel = self::bootKernel();
|
||||||
$this->application = new Application($kernel);
|
$this->application = new Application($kernel);
|
||||||
$this->application->add(new PluginCommand(new PluginManager($plugins), new PackageManager(__DIR__ . '/../../')));
|
$this->application->add(new PluginCommand(new PluginManager($plugins), new PackageManager($projectDirectory), $projectDirectory));
|
||||||
|
|
||||||
$command = $this->application->find('kimai:plugins');
|
$command = $this->application->find('kimai:plugins');
|
||||||
$commandTester = new CommandTester($command);
|
$commandTester = new CommandTester($command);
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class ResetDevelopmentCommandTest extends KernelTestCase
|
|||||||
{
|
{
|
||||||
$kernel = self::bootKernel();
|
$kernel = self::bootKernel();
|
||||||
$application = new Application($kernel);
|
$application = new Application($kernel);
|
||||||
$application->add(new ResetDevelopmentCommand('dev'));
|
$application->add(new ResetDevelopmentCommand('dev', __DIR__ . '/../../'));
|
||||||
|
|
||||||
self::assertTrue($application->has('kimai:reset:dev'));
|
self::assertTrue($application->has('kimai:reset:dev'));
|
||||||
$command = $application->find('kimai:reset:dev');
|
$command = $application->find('kimai:reset:dev');
|
||||||
@@ -32,7 +32,7 @@ class ResetDevelopmentCommandTest extends KernelTestCase
|
|||||||
|
|
||||||
public function testCommandNameIsNotEnabledInProd(): void
|
public function testCommandNameIsNotEnabledInProd(): void
|
||||||
{
|
{
|
||||||
$sut = new ResetDevelopmentCommand('prod');
|
$sut = new ResetDevelopmentCommand('prod', __DIR__ . '/../../');
|
||||||
self::assertFalse($sut->isEnabled());
|
self::assertFalse($sut->isEnabled());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -122,4 +122,36 @@ class SamlProviderTest extends TestCase
|
|||||||
$sut = $this->getSamlProvider(null, $user);
|
$sut = $this->getSamlProvider(null, $user);
|
||||||
$sut->findUser($token);
|
$sut->findUser($token);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testAuthenticateNotThrowsOnOptionalAttribute(): void
|
||||||
|
{
|
||||||
|
$mapping = [
|
||||||
|
'mapping' => [
|
||||||
|
['saml' => '$Chicken', 'kimai' => 'alias'],
|
||||||
|
['saml' => '$$Email', 'kimai' => 'title'],
|
||||||
|
],
|
||||||
|
'roles' => [
|
||||||
|
'attribute' => '',
|
||||||
|
'mapping' => []
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
$user = new User();
|
||||||
|
$user->setAuth(User::AUTH_SAML);
|
||||||
|
$user->setUserIdentifier('foo1@example.com');
|
||||||
|
$user->setTitle('I will not be overwritten');
|
||||||
|
|
||||||
|
$token = new SamlLoginAttributes();
|
||||||
|
$token->setUserIdentifier($user->getUserIdentifier());
|
||||||
|
$token->setAttributes([
|
||||||
|
'Chicken' => ['foo@example.com'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$sut = $this->getSamlProvider($mapping, $user);
|
||||||
|
$tokenUser = $sut->findUser($token);
|
||||||
|
self::assertSame($user, $tokenUser);
|
||||||
|
self::assertTrue($tokenUser->isSamlUser());
|
||||||
|
self::assertEquals('foo@example.com', $tokenUser->getAlias());
|
||||||
|
self::assertEquals('I will not be overwritten', $tokenUser->getTitle());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user