Skip to content

Commit 8302bcf

Browse files
committed
latest Kimai API and API token support
1 parent 0bf4db8 commit 8302bcf

14 files changed

+146
-117
lines changed

README.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
# Kimai - Remote Console
22

3-
A PHP application to access your Kimai 2 installation via its API (http).
3+
A PHP application to access your Kimai installation via its JSON API.
44

55
**Requirements**
66

7-
- Kimai > v2.0.20
7+
- Kimai > v2.14.0
88
- PHP 8.1 to 8.4
99
- cURL extension
1010
- json extension
@@ -46,8 +46,7 @@ By default, the configuration file targets the demo installation and will work..
4646
but now it's time to target your own Kimai, so please edit the config file and change the settings:
4747

4848
- `URL`: the Kimai installation URL
49-
- `USERNAME`: the Kimai installation URL
50-
- `API_KEY`: your Kimai API key (can be set when editing your profile)
49+
- `API_TOKEN`: your Kimai API token (can be set when editing your profile)
5150
- `OPTIONS`: an array of request options for CURL (see [guzzle docs](http://docs.guzzlephp.org/en/stable/request-options.html))
5251

5352
FAQ:
@@ -116,7 +115,7 @@ If you want to use the output in a script, instead of manually looking at them,
116115
The following environment variables are supported:
117116

118117
- `KIMAI_MEMORY_LIMIT` - configures the allowed memory limit (eg `128MB`, or `-1` for unlimited) (see [here](https://www.php.net/manual/en/ini.core.php#ini.memory-limit))
119-
- `KIMAI_CONFIG` - path to your configuration file (defaults to: $HOME/.kimai2-console.json)
118+
- `KIMAI_CONFIG` - path to your configuration file (defaults to: `$HOME/.kimai-api.json`)
120119

121120
## FAQ
122121

composer.json

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"ext-json": "*",
1616
"ext-curl": "*",
1717
"ext-mbstring": "*",
18-
"kimai/api-php": "^1.0",
18+
"kimai/api-php": "^2.0",
1919
"symfony/console": "6.*"
2020
},
2121
"require-dev": {
@@ -43,9 +43,7 @@
4343
}
4444
},
4545
"scripts": {
46-
"phpstan": [
47-
"vendor/bin/phpstan analyse src -c phpstan.neon --level=5"
48-
],
46+
"phpstan": "vendor/bin/phpstan analyse src -c phpstan.neon --level=7",
4947
"codestyle": "vendor/bin/php-cs-fixer fix --dry-run --verbose --show-progress=none",
5048
"codestyle-fix": "vendor/bin/php-cs-fixer fix"
5149
}

composer.lock

Lines changed: 13 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Api/Configuration.php

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,32 +12,27 @@
1212
final class Configuration
1313
{
1414
/**
15-
* @var array
15+
* @param array{URL: string, API_TOKEN: string, OPTIONS: array<string, mixed>} $settings
16+
* @throws \Exception
1617
*/
17-
private array $settings;
18-
19-
public function __construct(array $settings)
18+
public function __construct(private readonly array $settings)
2019
{
2120
$this->validate($settings);
22-
23-
$this->settings = $settings;
2421
}
2522

26-
public function getUsername(): string
23+
public function getApiToken(): string
2724
{
28-
return $this->settings['USERNAME'];
29-
}
30-
31-
public function getApiKey(): string
32-
{
33-
return $this->settings['API_KEY'];
25+
return $this->settings['API_TOKEN'];
3426
}
3527

3628
public function getUrl(): string
3729
{
3830
return $this->settings['URL'];
3931
}
4032

33+
/**
34+
* @return array<string, mixed>
35+
*/
4136
public function getCurlOptions(): array
4237
{
4338
if (!\array_key_exists('OPTIONS', $this->settings)) {
@@ -47,39 +42,43 @@ public function getCurlOptions(): array
4742
return $this->settings['OPTIONS'];
4843
}
4944

45+
/**
46+
* @return array<string, string>
47+
*/
5048
public static function getDefaultConfiguration(): array
5149
{
5250
return [
5351
'URL' => 'https://demo.kimai.org/',
54-
'USERNAME' => 'susan_super',
55-
'API_KEY' => 'api_kitten',
52+
'API_TOKEN' => 'token_super',
5653
];
5754
}
5855

5956
public static function getFilename(): string
6057
{
6158
if (false === ($filename = getenv('KIMAI_CONFIG'))) {
62-
$filename = getenv('HOME') . DIRECTORY_SEPARATOR . '.kimai-api.json';
59+
$old = getenv('HOME') . DIRECTORY_SEPARATOR . '.kimai2-console.json';
60+
$new = getenv('HOME') . DIRECTORY_SEPARATOR . '.kimai-api.json';
61+
if (file_exists($old)) {
62+
rename($old, $new);
63+
}
64+
$filename = $new;
6365
}
6466

6567
return $filename;
6668
}
6769

6870
/**
69-
* @param array $settings
70-
* @return bool
71+
* @param array{URL: string, API_TOKEN: string, OPTIONS: null|array<string, mixed>} $settings
7172
* @throws \Exception
7273
*/
73-
private function validate(array $settings)
74+
private function validate(array $settings): bool
7475
{
75-
$required = ['URL', 'USERNAME', 'API_KEY'];
76+
if (!\array_key_exists('URL', $settings) || $settings['URL'] === '') {
77+
throw new \Exception('Missing API URL with the key "URL"');
78+
}
7679

77-
foreach ($required as $key) {
78-
if (!\array_key_exists($key, $settings)) {
79-
throw new \Exception('Missing config: ' . $key);
80-
} elseif (empty($settings[$key])) {
81-
throw new \Exception('Empty config: ' . $key);
82-
}
80+
if (!\array_key_exists('API_TOKEN', $settings) || $settings['API_TOKEN'] === '') {
81+
throw new \Exception('Missing API token with the key "API_TOKEN"');
8382
}
8483

8584
if (\array_key_exists('OPTIONS', $settings) && !\is_array($settings['OPTIONS'])) {

src/Api/Connection.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@ final class Connection
2121
public function __construct(Configuration $configuration)
2222
{
2323
$this->configuration = SwaggerConfiguration::getDefaultConfiguration();
24-
$this->configuration->setApiKey('X-AUTH-TOKEN', $configuration->getApiKey());
25-
$this->configuration->setApiKey('X-AUTH-USER', $configuration->getUsername());
24+
$this->configuration->setAccessToken($configuration->getApiToken());
2625
$this->configuration->setHost(rtrim($configuration->getUrl(), '/'));
2726

2827
$clientOptions = $configuration->getCurlOptions();

src/Application.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222

2323
class Application extends SymfonyApplication
2424
{
25-
private static $logo = ' _ _____ __ __ _ ___
25+
private static string $logo = ' _ _____ __ __ _ ___
2626
| |/ /_ _| \/ | / \ |_ _|
2727
| \' / | || |\/| | / _ \ | |
2828
| . \ | || | | |/ ___ \ | |

src/Command/ActivityListCommand.php

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,15 @@ protected function execute(InputInterface $input, OutputInterface $output): int
5151
$globals = '1';
5252
}
5353

54-
// null parameters are deprecated
55-
$collection = $api->getGetActivities($project, null, $visible, $globals, $order_by, $order, $term);
54+
$collection = $api->getGetActivities(
55+
$project,
56+
null, // @phpstan-ignore argument.type
57+
$visible,
58+
$globals,
59+
$order_by,
60+
$order,
61+
$term
62+
);
5663

5764
$rows = [];
5865
foreach ($collection as $activity) {

src/Command/BaseCommand.php

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,7 @@
2828

2929
abstract class BaseCommand extends Command
3030
{
31-
/**
32-
* @var Connection
33-
*/
34-
private $connection;
31+
private ?Connection $connection = null;
3532

3633
protected function getApi(): DefaultApi
3734
{
@@ -80,8 +77,13 @@ protected function getConnection(): Connection
8077
throw new InvalidConfigurationException('Cannot read configuration: ' . $filename);
8178
}
8279

80+
$configFile = file_get_contents($filename);
81+
if ($configFile === false) {
82+
throw new InvalidConfigurationException('Failed reading configuration: ' . $filename);
83+
}
84+
8385
try {
84-
$result = json_decode(file_get_contents($filename), true);
86+
$result = json_decode($configFile, true);
8587
$config = new Configuration($result);
8688
} catch (\Exception $ex) {
8789
throw new InvalidConfigurationException('Invalid configuration: ' . $ex->getMessage());
@@ -97,6 +99,10 @@ protected function getConnection(): Connection
9799
return $this->connection;
98100
}
99101

102+
/**
103+
* @param array<string> $headers
104+
* @param list<array<int, mixed>> $rows
105+
*/
100106
protected function formatOutput(InputInterface $input, OutputInterface $output, array $headers, array $rows): void
101107
{
102108
$io = new SymfonyStyle($input, $output);

src/Command/ConfigurationCommand.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
namespace KimaiConsole\Command;
1111

1212
use KimaiConsole\Api\Configuration;
13+
use KimaiConsole\Exception\InvalidConfigurationException;
1314
use Symfony\Component\Console\Input\InputInterface;
1415
use Symfony\Component\Console\Output\OutputInterface;
1516
use Symfony\Component\Console\Style\SymfonyStyle;
@@ -34,14 +35,19 @@ protected function execute(InputInterface $input, OutputInterface $output): int
3435
}
3536

3637
if (file_exists($filename)) {
38+
$configFile = file_get_contents($filename);
39+
if ($configFile === false) {
40+
throw new InvalidConfigurationException('Failed reading configuration: ' . $filename);
41+
}
42+
3743
try {
38-
$result = json_decode(file_get_contents($filename), true);
44+
$result = json_decode($configFile, true);
3945
$config = new Configuration($result);
4046

4147
$io->success(
4248
'Configuration file: ' . $filename . PHP_EOL .
4349
'URL: ' . $config->getUrl() . PHP_EOL .
44-
'Username: ' . $config->getUsername() . PHP_EOL
50+
'API Token: ' . $config->getApiToken() . PHP_EOL
4551
);
4652

4753
return 0;

src/Command/ProjectListCommand.php

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,18 @@ protected function execute(InputInterface $input, OutputInterface $output): int
5050
$order_by = 'customer';
5151

5252
// null parameters are deprecated
53-
$collection = $api->getGetProjects($customer, null, $visible, $start, $end, $ignore_dates, $global_activities, $order, $order_by, $term);
53+
$collection = $api->getGetProjects(
54+
$customer,
55+
null, // @phpstan-ignore argument.type
56+
$visible,
57+
$start,
58+
$end,
59+
$ignore_dates,
60+
$global_activities,
61+
$order,
62+
$order_by,
63+
$term
64+
);
5465

5566
$rows = [];
5667
foreach ($collection as $project) {

0 commit comments

Comments
 (0)