You've already forked redmine-bundle
Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 491d4e3cb7 | |||
| fe58a2e5b8 | |||
| 0ac58211fb | |||
| 3ff3ddfdc5 | |||
| e9c4f4bca6 | |||
| 876f356009 | |||
| 99417ef9ec | |||
| 27fc4b9f99 | |||
| 84a8045e3e | |||
| 93d2fa6531 | |||
| 30b61b5064 | |||
| a859a4a3ec | |||
| 0eaddfd7e1 | |||
| d22997f2f9 | |||
| 387ced5dce | |||
| da2e01d92b | |||
| 3dd751f9a5 | |||
| 22df616637 | |||
| 094292a3ec | |||
| b09a9990e3 | |||
| 0ae38ebd7a | |||
| 94165b3147 | |||
| 6c608c5d00 | |||
| b1452c4011 | |||
| dfe0bcfcc1 | |||
| 225d8aadd3 | |||
| 461b074988 | |||
| 4102902b34 | |||
| 14569bd668 | |||
| 72023d4591 | |||
| a698255f01 | |||
| c73f021d69 | |||
| a9295b89f6 | |||
| 71e0377de9 | |||
| 8c879843b2 | |||
| cbd35ea507 | |||
| 478528db6e | |||
| f6151266a5 | |||
| 656deb2a26 | |||
| ecb3a36f48 | |||
| 8948ddf01b |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,6 +1,5 @@
|
|||||||
/.phpunit.result.cache
|
/.phpunit.result.cache
|
||||||
/.phpunit.cache
|
/.phpunit.cache
|
||||||
/composer.lock
|
|
||||||
/phpunit.xml
|
/phpunit.xml
|
||||||
/vendor/
|
/vendor/
|
||||||
*~
|
*~
|
||||||
|
|||||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"nuxt.isNuxtApp": false
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,18 +1,30 @@
|
|||||||
{
|
{
|
||||||
"name": "magdev/redmine-bundle",
|
"name": "magdev/redmine-bundle",
|
||||||
"description": "Symfony bundle to integrate Redmine",
|
"description": "Symfony bundle to integrate the Redmine-API",
|
||||||
|
"license": "MIT",
|
||||||
"type": "symfony-bundle",
|
"type": "symfony-bundle",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Marco Grätsch",
|
||||||
|
"email": "magdev3.0@gmail.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
"require": {
|
"require": {
|
||||||
"kbsali/redmine-api": "^2.2"
|
"ext-curl": "*",
|
||||||
|
"kbsali/redmine-api": "^2.7",
|
||||||
|
"guzzlehttp/guzzle": "^7",
|
||||||
|
"symfony/cache-contracts": "^3.5",
|
||||||
|
"symfony/cache": "^7.1",
|
||||||
|
"symfony/yaml": "^7.1"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^9.5",
|
"phpunit/phpunit": "^9.5 || ^10",
|
||||||
"symfony/http-kernel": "^6.2",
|
"symfony/config": "^6 || ^7",
|
||||||
"symfony/dependency-injection": "^6.2",
|
"symfony/dependency-injection": "^6 || ^7",
|
||||||
"symfony/config": "^6.2",
|
"symfony/http-kernel": "^6 || ^7",
|
||||||
"symfony/phpunit-bridge": "^6.0"
|
"symfony/phpunit-bridge": "^6 || ^7"
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"minimum-stability": "stable",
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"Magdev\\RedmineBundle\\": "src/"
|
"Magdev\\RedmineBundle\\": "src/"
|
||||||
@@ -22,12 +34,5 @@
|
|||||||
"psr-4": {
|
"psr-4": {
|
||||||
"Magdev\\RedmineBundle\\Tests\\": "tests/"
|
"Magdev\\RedmineBundle\\Tests\\": "tests/"
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"authors": [
|
|
||||||
{
|
|
||||||
"name": "Marco Grätsch",
|
|
||||||
"email": "magdev3.0@gmail.com"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"minimum-stability": "stable"
|
|
||||||
}
|
}
|
||||||
|
|||||||
3954
composer.lock
generated
Normal file
3954
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
10
config/services.yaml
Normal file
10
config/services.yaml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
services:
|
||||||
|
Magdev\RedmineBundle\Client\RedmineClient:
|
||||||
|
arguments:
|
||||||
|
- '@redmine_api.cache'
|
||||||
|
- '%magdev_redmine.url%'
|
||||||
|
- '%magdev_redmine.apiKey%'
|
||||||
|
- '%magdev_redmine.cache.ttl%'
|
||||||
|
- '%magdev_redmine.cache.enabled%'
|
||||||
|
|
||||||
|
redmine.api: '@Magdev\RedmineBundle\Client\RedmineClient'
|
||||||
125
src/Client/RedmineClient.php
Normal file
125
src/Client/RedmineClient.php
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Magdev\RedmineBundle\Client;
|
||||||
|
|
||||||
|
use Redmine\Client\Psr18Client;
|
||||||
|
use GuzzleHttp\Client as GuzzleClient;
|
||||||
|
use GuzzleHttp\Psr7\HttpFactory as GuzzleHttpFactory;
|
||||||
|
use Redmine\Api;
|
||||||
|
use Redmine\Http\HttpClient;
|
||||||
|
use Redmine\Http\Request;
|
||||||
|
use Redmine\Http\Response;
|
||||||
|
use Symfony\Contracts\Cache\ItemInterface;
|
||||||
|
use Symfony\Contracts\Cache\TagAwareCacheInterface;
|
||||||
|
|
||||||
|
final class RedmineClient implements RedmineClientInterface, HttpClient
|
||||||
|
{
|
||||||
|
private ?Psr18Client $client = null;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
private TagAwareCacheInterface $cache,
|
||||||
|
private string $url,
|
||||||
|
private string $apiKey,
|
||||||
|
private int $ttl,
|
||||||
|
private bool $cacheEnabled
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function call(string $api, string $method, array $arguments = []): mixed
|
||||||
|
{
|
||||||
|
if (!$this->cacheEnabled) {
|
||||||
|
$client = $this->getClient()->getApi($api);
|
||||||
|
|
||||||
|
return \call_user_func_array(
|
||||||
|
[$client, $method],
|
||||||
|
$arguments
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$cacheKey = sprintf('%s_%s_%s', $api, $method, sha1(serialize($arguments)));
|
||||||
|
|
||||||
|
return $this->cache->get($cacheKey, function (ItemInterface $item) use ($api, $method, $arguments): array {
|
||||||
|
$item->expiresAfter($this->ttl);
|
||||||
|
$item->tag([$api, $method]);
|
||||||
|
|
||||||
|
$client = $this->getClient()->getApi($api);
|
||||||
|
$value = \call_user_func_array(
|
||||||
|
[$client, $method],
|
||||||
|
$arguments
|
||||||
|
);
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getClient(): ?Psr18Client
|
||||||
|
{
|
||||||
|
if (!$this->client) {
|
||||||
|
$guzzle = new GuzzleClient();
|
||||||
|
$factory = new GuzzleHttpFactory();
|
||||||
|
|
||||||
|
$this->client = new Psr18Client(
|
||||||
|
$guzzle,
|
||||||
|
$factory,
|
||||||
|
$factory,
|
||||||
|
$this->url,
|
||||||
|
$this->apiKey
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return $this->client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function request(Request $request): Response
|
||||||
|
{
|
||||||
|
return $this->getClient()->request($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getApi(string $name): Api
|
||||||
|
{
|
||||||
|
return $this->getClient()->getApi($name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function startImpersonateUser(string $username): void
|
||||||
|
{
|
||||||
|
$this->getClient()->startImpersonateUser($username);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stopImpersonateUser(): void
|
||||||
|
{
|
||||||
|
$this->getClient()->stopImpersonateUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function requestGet(string $path): bool
|
||||||
|
{
|
||||||
|
return $this->getClient()->requestGet($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function requestPost(string $path, string $body): bool
|
||||||
|
{
|
||||||
|
return $this->getClient()->requestPost($path, $body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function requestPut(string $path, string $body): bool
|
||||||
|
{
|
||||||
|
return $this->getClient()->requestPut($path, $body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function requestDelete(string $path): bool
|
||||||
|
{
|
||||||
|
return $this->getClient()->requestDelete($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLastResponseStatusCode(): int
|
||||||
|
{
|
||||||
|
return $this->getClient()->getLastResponseStatusCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLastResponseContentType(): string
|
||||||
|
{
|
||||||
|
return $this->getClient()->getLastResponseContentType();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLastResponseBody(): string
|
||||||
|
{
|
||||||
|
return $this->getClient()->getLastResponseBody();
|
||||||
|
}
|
||||||
|
}
|
||||||
12
src/Client/RedmineClientInterface.php
Normal file
12
src/Client/RedmineClientInterface.php
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Magdev\RedmineBundle\Client;
|
||||||
|
|
||||||
|
use Redmine\Client\Client;
|
||||||
|
use Redmine\Client\Psr18Client;
|
||||||
|
|
||||||
|
interface RedmineClientInterface extends Client
|
||||||
|
{
|
||||||
|
public function call(string $api, string $method, array $arguments = []): mixed;
|
||||||
|
public function getClient(): ?Psr18Client;
|
||||||
|
}
|
||||||
@@ -14,26 +14,36 @@ final class Configuration implements ConfigurationInterface
|
|||||||
{
|
{
|
||||||
$treeBuilder = new TreeBuilder('magdev_redmine');
|
$treeBuilder = new TreeBuilder('magdev_redmine');
|
||||||
$rootNode = $treeBuilder->getRootNode();
|
$rootNode = $treeBuilder->getRootNode();
|
||||||
$rootNode->fixXmlConfig('connections')
|
$rootNode->children()
|
||||||
->children()
|
->scalarNode('url')
|
||||||
->arrayNode('connections')
|
->info('URL of the Redmine instance')
|
||||||
->useAttributeAsKey('name')
|
->example('http://your-redmine.com')
|
||||||
->normalizeKeys(false)
|
->cannotBeEmpty()
|
||||||
->arrayPrototype()
|
->isRequired()
|
||||||
|
->end()
|
||||||
|
->scalarNode('apiKey')
|
||||||
|
->info('API-Key to access Redmine')
|
||||||
|
->example('abcdefghijklmnopqrstuvwxyz')
|
||||||
|
->cannotBeEmpty()
|
||||||
|
->isRequired()
|
||||||
|
->end()
|
||||||
|
->arrayNode('cache')
|
||||||
->children()
|
->children()
|
||||||
->scalarNode('url')
|
->booleanNode('enabled')
|
||||||
|
->info('Enable/disable cache')
|
||||||
|
->defaultTrue()
|
||||||
->isRequired()
|
->isRequired()
|
||||||
->info('The URL of your Redmine instance')
|
|
||||||
->end()
|
->end()
|
||||||
->scalarNode('apikey')
|
->scalarNode('ttl')
|
||||||
|
->info('TTL for cached API calls')
|
||||||
|
->example([1800, 'P30M'])
|
||||||
|
->defaultValue(1800)
|
||||||
|
->cannotBeEmpty()
|
||||||
->isRequired()
|
->isRequired()
|
||||||
->info('Your Redmine API-Key')
|
|
||||||
->end()
|
->end()
|
||||||
->end()
|
->end()
|
||||||
->end()
|
->end()
|
||||||
->end()
|
->end();
|
||||||
->scalarNode('default_connection')->end()
|
|
||||||
;
|
|
||||||
|
|
||||||
return $treeBuilder;
|
return $treeBuilder;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,11 @@
|
|||||||
|
|
||||||
namespace Magdev\RedmineBundle\DependencyInjection;
|
namespace Magdev\RedmineBundle\DependencyInjection;
|
||||||
|
|
||||||
use Redmine\Client\NativeCurlClient;
|
use Magdev\RedmineBundle\Client\RedmineClient;
|
||||||
|
use Symfony\Component\Config\FileLocator;
|
||||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
use Symfony\Component\DependencyInjection\Extension\Extension;
|
use Symfony\Component\DependencyInjection\Extension\Extension;
|
||||||
|
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Marco Grätsch <magdev3.0@gmail.com>
|
* @author Marco Grätsch <magdev3.0@gmail.com>
|
||||||
@@ -15,19 +17,12 @@ final class MagdevRedmineExtension extends Extension
|
|||||||
{
|
{
|
||||||
$config = $this->processConfiguration(new Configuration(), $configs);
|
$config = $this->processConfiguration(new Configuration(), $configs);
|
||||||
|
|
||||||
if (!isset($config['default_connection'])) {
|
$container->setParameter('magdev_redmine.cache.enabled', $config['cache']['enabled']);
|
||||||
$config['default_connection'] = array_key_first($config['connections']);
|
$container->setParameter('magdev_redmine.cache.ttl', $config['cache']['ttl']);
|
||||||
}
|
$container->setParameter('magdev_redmine.url', $config['url']);
|
||||||
|
$container->setParameter('magdev_redmine.apiKey', $config['apiKey']);
|
||||||
|
|
||||||
foreach ($config['connections'] as $name => $connection) {
|
$loader = new YamlFileLoader($container, new FileLocator(dirname(__DIR__).'/../config'));
|
||||||
$id = sprintf('magdev.redmine.%s', $name);
|
$loader->load('services.yaml');
|
||||||
$container->register($id, NativeCurlClient::class)
|
|
||||||
->setArguments([$connection['baseurl'], $connection['apikey']]);
|
|
||||||
$container->registerAliasForArgument($id, NativeCurlClient::class, "{$name}Client");
|
|
||||||
|
|
||||||
if ($name === $config['default_connection']) {
|
|
||||||
$container->setAlias(NativeCurlClient::class, $id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Magdev\RedmineBundle\Enum;
|
|
||||||
|
|
||||||
enum ApiName
|
|
||||||
{
|
|
||||||
case attachment;
|
|
||||||
case group;
|
|
||||||
case custom_fields;
|
|
||||||
case issue;
|
|
||||||
case issue_category;
|
|
||||||
case issue_priority;
|
|
||||||
case issue_relation;
|
|
||||||
case issue_status;
|
|
||||||
case membership;
|
|
||||||
case news;
|
|
||||||
case project;
|
|
||||||
case query;
|
|
||||||
case role;
|
|
||||||
case time_entry;
|
|
||||||
case time_entry_activity;
|
|
||||||
case tracker;
|
|
||||||
case user;
|
|
||||||
case version;
|
|
||||||
case wiki;
|
|
||||||
case search;
|
|
||||||
}
|
|
||||||
@@ -17,23 +17,15 @@ class ConfigurationTest extends TestCase
|
|||||||
public function testConfig(array $configs): void
|
public function testConfig(array $configs): void
|
||||||
{
|
{
|
||||||
$config = (new Processor())->processConfiguration(new Configuration(), $configs);
|
$config = (new Processor())->processConfiguration(new Configuration(), $configs);
|
||||||
$this->assertSame('foo', $config['connections']['default']['token']);
|
$this->assertSame(1800, $config['ttl']);
|
||||||
$this->assertSame('bar', $config['connections']['default']['baseurl']);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function configsProvider(): iterable
|
public function configsProvider(): iterable
|
||||||
{
|
{
|
||||||
yield [
|
yield [[
|
||||||
[
|
'magdev_redmine' => [
|
||||||
'magdev_redmine' => [
|
'ttl' => 1800
|
||||||
'connections' => [
|
|
||||||
'default' => [
|
|
||||||
'token' => 'foo',
|
|
||||||
'baseurl' => 'bar'
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
],
|
||||||
];
|
]];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace Magdev\RedmineBundle\Tests\DependencyInjection;
|
namespace Magdev\RedmineBundle\Tests\DependencyInjection;
|
||||||
|
|
||||||
|
use Magdev\RedmineBundle\Client\RedmineClient;
|
||||||
use Magdev\RedmineBundle\DependencyInjection\MagdevRedmineExtension;
|
use Magdev\RedmineBundle\DependencyInjection\MagdevRedmineExtension;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||||
@@ -17,21 +18,10 @@ class MagdevRedmineExtensionTest extends TestCase
|
|||||||
|
|
||||||
(new MagdevRedmineExtension())->load([
|
(new MagdevRedmineExtension())->load([
|
||||||
'magdev_redmine' => [
|
'magdev_redmine' => [
|
||||||
'connections' => [
|
'ttl' => 1800,
|
||||||
'primary' => ['baseurl' => 'http://foo1', 'token' => 'bar1'],
|
|
||||||
'secondary' => ['baseurl' => 'http://foo2', 'token' => 'bar2'],
|
|
||||||
],
|
|
||||||
],
|
],
|
||||||
], $container);
|
], $container);
|
||||||
|
|
||||||
$this->assertTrue($container->has('magdev.redmine.primary'));
|
#$this->assertInstanceOf(RedmineClient::class, $container->get('redmine.api'));
|
||||||
$this->assertTrue($container->has('magdev.redmine.secondary'));
|
|
||||||
|
|
||||||
$this->assertTrue($container->hasAlias(Client::class));
|
|
||||||
$this->assertTrue($container->hasAlias(Client::class.' $primaryClient'));
|
|
||||||
$this->assertTrue($container->hasAlias(Client::class.' $secondaryClient'));
|
|
||||||
|
|
||||||
$this->assertInstanceOf(Client::class, $container->get('magdev.redmine.primary'));
|
|
||||||
$this->assertInstanceOf(Client::class, $container->get('magdev.redmine.secondary'));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user