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.cache
|
||||
/composer.lock
|
||||
/phpunit.xml
|
||||
/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",
|
||||
"description": "Symfony bundle to integrate Redmine",
|
||||
"description": "Symfony bundle to integrate the Redmine-API",
|
||||
"license": "MIT",
|
||||
"type": "symfony-bundle",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Marco Grätsch",
|
||||
"email": "magdev3.0@gmail.com"
|
||||
}
|
||||
],
|
||||
"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": {
|
||||
"phpunit/phpunit": "^9.5",
|
||||
"symfony/http-kernel": "^6.2",
|
||||
"symfony/dependency-injection": "^6.2",
|
||||
"symfony/config": "^6.2",
|
||||
"symfony/phpunit-bridge": "^6.0"
|
||||
"phpunit/phpunit": "^9.5 || ^10",
|
||||
"symfony/config": "^6 || ^7",
|
||||
"symfony/dependency-injection": "^6 || ^7",
|
||||
"symfony/http-kernel": "^6 || ^7",
|
||||
"symfony/phpunit-bridge": "^6 || ^7"
|
||||
},
|
||||
"license": "MIT",
|
||||
"minimum-stability": "stable",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Magdev\\RedmineBundle\\": "src/"
|
||||
@@ -22,12 +34,5 @@
|
||||
"psr-4": {
|
||||
"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');
|
||||
$rootNode = $treeBuilder->getRootNode();
|
||||
$rootNode->fixXmlConfig('connections')
|
||||
->children()
|
||||
->arrayNode('connections')
|
||||
->useAttributeAsKey('name')
|
||||
->normalizeKeys(false)
|
||||
->arrayPrototype()
|
||||
->children()
|
||||
$rootNode->children()
|
||||
->scalarNode('url')
|
||||
->info('URL of the Redmine instance')
|
||||
->example('http://your-redmine.com')
|
||||
->cannotBeEmpty()
|
||||
->isRequired()
|
||||
->info('The URL of your Redmine instance')
|
||||
->end()
|
||||
->scalarNode('apikey')
|
||||
->scalarNode('apiKey')
|
||||
->info('API-Key to access Redmine')
|
||||
->example('abcdefghijklmnopqrstuvwxyz')
|
||||
->cannotBeEmpty()
|
||||
->isRequired()
|
||||
->end()
|
||||
->arrayNode('cache')
|
||||
->children()
|
||||
->booleanNode('enabled')
|
||||
->info('Enable/disable cache')
|
||||
->defaultTrue()
|
||||
->isRequired()
|
||||
->end()
|
||||
->scalarNode('ttl')
|
||||
->info('TTL for cached API calls')
|
||||
->example([1800, 'P30M'])
|
||||
->defaultValue(1800)
|
||||
->cannotBeEmpty()
|
||||
->isRequired()
|
||||
->info('Your Redmine API-Key')
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->end()
|
||||
->scalarNode('default_connection')->end()
|
||||
;
|
||||
->end();
|
||||
|
||||
return $treeBuilder;
|
||||
}
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
|
||||
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\Extension\Extension;
|
||||
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
|
||||
|
||||
/**
|
||||
* @author Marco Grätsch <magdev3.0@gmail.com>
|
||||
@@ -15,19 +17,12 @@ final class MagdevRedmineExtension extends Extension
|
||||
{
|
||||
$config = $this->processConfiguration(new Configuration(), $configs);
|
||||
|
||||
if (!isset($config['default_connection'])) {
|
||||
$config['default_connection'] = array_key_first($config['connections']);
|
||||
}
|
||||
$container->setParameter('magdev_redmine.cache.enabled', $config['cache']['enabled']);
|
||||
$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) {
|
||||
$id = sprintf('magdev.redmine.%s', $name);
|
||||
$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);
|
||||
}
|
||||
}
|
||||
$loader = new YamlFileLoader($container, new FileLocator(dirname(__DIR__).'/../config'));
|
||||
$loader->load('services.yaml');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
$config = (new Processor())->processConfiguration(new Configuration(), $configs);
|
||||
$this->assertSame('foo', $config['connections']['default']['token']);
|
||||
$this->assertSame('bar', $config['connections']['default']['baseurl']);
|
||||
$this->assertSame(1800, $config['ttl']);
|
||||
}
|
||||
|
||||
public function configsProvider(): iterable
|
||||
{
|
||||
yield [
|
||||
[
|
||||
yield [[
|
||||
'magdev_redmine' => [
|
||||
'connections' => [
|
||||
'default' => [
|
||||
'token' => 'foo',
|
||||
'baseurl' => 'bar'
|
||||
'ttl' => 1800
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
]];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Magdev\RedmineBundle\Tests\DependencyInjection;
|
||||
|
||||
use Magdev\RedmineBundle\Client\RedmineClient;
|
||||
use Magdev\RedmineBundle\DependencyInjection\MagdevRedmineExtension;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
@@ -17,21 +18,10 @@ class MagdevRedmineExtensionTest extends TestCase
|
||||
|
||||
(new MagdevRedmineExtension())->load([
|
||||
'magdev_redmine' => [
|
||||
'connections' => [
|
||||
'primary' => ['baseurl' => 'http://foo1', 'token' => 'bar1'],
|
||||
'secondary' => ['baseurl' => 'http://foo2', 'token' => 'bar2'],
|
||||
],
|
||||
'ttl' => 1800,
|
||||
],
|
||||
], $container);
|
||||
|
||||
$this->assertTrue($container->has('magdev.redmine.primary'));
|
||||
$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'));
|
||||
#$this->assertInstanceOf(RedmineClient::class, $container->get('redmine.api'));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user