Skip to content

Commit 9ac78b5

Browse files
feat(plugin): create TestPluginLoaderAction command and update plugin entity definitions
1 parent bd472c2 commit 9ac78b5

File tree

5 files changed

+222
-48
lines changed

5 files changed

+222
-48
lines changed

src/Action/LoadPluginAction.php

Lines changed: 175 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -7,62 +7,194 @@
77
use Composer\Semver\Semver;
88
use NuonicPluginInstaller\Service\IndexFileServiceInterface;
99
use NuonicPluginInstaller\Struct\PackageIndexEntry;
10+
use Shopware\Core\Framework\Adapter\Cache\CacheValueCompressor;
1011
use Shopware\Core\Framework\Context;
1112
use Shopware\Core\Framework\DataAbstractionLayer\EntityRepository;
1213
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
1314
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
1415
use Shopware\Core\Framework\Uuid\Uuid;
16+
use Symfony\Component\Yaml\Yaml;
17+
use Symfony\Contracts\Cache\CacheInterface;
18+
use Symfony\Contracts\Cache\ItemInterface;
19+
use Symfony\Contracts\HttpClient\HttpClientInterface;
1520

1621
readonly class LoadPluginAction
1722
{
18-
public function __construct(
19-
private EntityRepository $availableOpensourcePluginRepository,
20-
private IndexFileServiceInterface $indexFileService,
21-
) {
22-
}
23+
public function __construct(
24+
private readonly EntityRepository $availableOpensourcePluginRepository,
25+
private readonly IndexFileServiceInterface $indexFileService,
26+
private HttpClientInterface $httpClient,
27+
private readonly CacheInterface $cache,
28+
private readonly EntityRepository $languageRepository,
29+
private readonly string $shopwareVersion,
30+
) {
31+
// This is a constructor
32+
}
33+
34+
public function execute(string|PackageIndexEntry $packageInformation): void
35+
{
36+
if (!$packageInformation instanceof PackageIndexEntry) {
37+
$packageInformation = $this->indexFileService->getPackageInformation($packageInformation);
38+
}
39+
40+
if (null === $packageInformation) {
41+
return;
42+
}
43+
44+
$repositoryUrl = $packageInformation->repositoryUrl;
45+
$ref = $packageInformation->ref;
46+
47+
$packagistData = $this->getPackagistData($ref);
48+
49+
$version = $this->findSuitableVersion($packagistData);
50+
51+
if ($version === null) {
52+
return;
53+
}
54+
55+
$githubUrl = \str_replace('.git', '', $version['source']['url']);
56+
$githubUrl = \str_replace('https://github.com/', 'https://raw.githubusercontent.com/', $githubUrl . '/refs/heads/main/');
57+
$mainExtensionYmlUrl = $githubUrl . '.shopware-extension.yml';
58+
59+
$extensionYmlUrl = null;
60+
61+
$isComposer = false;
62+
63+
$mainExtensionYmlResponse = $this->httpClient->request('GET', $mainExtensionYmlUrl);
64+
if ($mainExtensionYmlResponse->getStatusCode() === 200) {
65+
$extensionYmlUrl = $mainExtensionYmlUrl;
66+
} else {
67+
$isComposer = true;
68+
}
69+
70+
$pluginData = [
71+
'packageName' => $packageInformation->packageName,
72+
'manufacturer' => \implode(', ', \array_column($version['authors'], 'name')),
73+
'manufacturerLink' => $version['extra']['manufacturerLink']['en-GB'],
74+
'license' => $version['license'][0],
75+
'link' => $repositoryUrl,
76+
'availableVersion' => $version['version']
77+
];
78+
79+
80+
if (!$isComposer) {
81+
$extensionYmlResponse = $this->httpClient->request('GET', $extensionYmlUrl);
82+
if ($extensionYmlResponse->getStatusCode() === 200) {
83+
$extensionYmlData = Yaml::parse($extensionYmlResponse->getContent());
84+
if (isset($extensionYmlData['store'])) {
85+
if (isset($extensionYmlData['store']['icon'])) {
86+
$pluginData['icon'] = $githubUrl . '/' . $extensionYmlData['store']['icon'];
87+
} else {
88+
$pluginData['icon'] = $githubUrl . '/src/Resources/config/plugin.png';
89+
}
90+
foreach ($extensionYmlData['store']['images'] as $images) {
91+
$pluginData['images'][] = $githubUrl . '/' . $images['file'];
92+
}
93+
if (isset($extensionYmlData['store']['description']['en'])) {
94+
$pluginData['description']['en-GB'] = $extensionYmlData['store']['description']['en'];
95+
}
96+
if (isset($extensionYmlData['store']['description']['de'])) {
97+
$pluginData['description']['de-DE'] = $extensionYmlData['store']['description']['de'];
98+
}
99+
100+
// TODO Load from markdown files
101+
102+
// TODO load images from folder
103+
104+
} else {
105+
$pluginData['icon'] = $githubUrl . '/src/Resources/config/plugin.png';
106+
$isComposer = true;
107+
}
108+
}
109+
}
110+
111+
if ($isComposer) {
112+
$pluginData['description']['en-GB'] = isset($version['extra']['description']['en-GB']) ? $version['extra']['description']['en-GB'] : $packageInformation->packageName;
113+
$pluginData['description']['de-DE'] = isset($version['extra']['description']['de-DE']) ? $version['extra']['description']['de-DE'] : $packageInformation->packageName;
114+
}
115+
116+
$pluginData['name']['de-DE'] = isset($version['extra']['label']['de-DE']) ? $version['extra']['label']['de-DE'] : $packageInformation->packageName;
117+
$pluginData['name']['en-GB'] = isset($version['extra']['label']['en-GB']) ? $version['extra']['label']['en-GB'] : $packageInformation->packageName;
118+
119+
$langIdDe = $this->loadLanguageId('de-DE');
120+
$langIdEn = $this->loadLanguageId('en-GB');
121+
122+
$pluginData['translations'] = [
123+
['languageId' => $langIdDe, 'name' => $pluginData['name']['de-DE'], 'description' => $pluginData['description']['de-DE']],
124+
['languageId' => $langIdEn, 'name' => $pluginData['name']['en-GB'], 'description' => $pluginData['description']['en-GB']],
125+
];
126+
127+
unset($pluginData['description']);
128+
unset($pluginData['name']);
129+
130+
/** @var \NuonicPluginInstaller\Core\Framework\Plugin\AvailableOpensourcePlugin\AvailableOpensourcePluginEntity|null */
131+
$plugin = $this->availableOpensourcePluginRepository->search(
132+
(new Criteria())->addFilter(new EqualsFilter('packageName', $packageInformation->packageName)),
133+
Context::createDefaultContext()
134+
)->first();
135+
136+
$id = $plugin?->getId() ?? Uuid::randomHex();
23137

24-
public function execute(string|PackageIndexEntry $packageInformation): void
25-
{
26-
if (!$packageInformation instanceof PackageIndexEntry) {
27-
$packageInformation = $this->indexFileService->getPackageInformation($packageInformation);
28-
}
138+
$pluginData['id'] = $id;
139+
$pluginData['isInstalled'] = $plugin ? $plugin->isInstalled() : false;
140+
141+
$this->availableOpensourcePluginRepository->upsert([$pluginData], Context::createDefaultContext());
142+
}
29143

30-
if (null === $packageInformation) {
31-
return;
32-
}
33144

34-
$repositoryUrl = $packageInformation->repositoryUrl;
35-
$ref = $packageInformation->ref;
145+
private function findSuitableVersion(array $packagistData): ?array
146+
{
147+
$versions = $packagistData['package']['versions'];
148+
\ksort($versions, SORT_NATURAL);
149+
150+
$versions = \array_reverse($versions, true);
151+
152+
foreach ($versions as $version => $versionData) {
153+
if (\str_contains($version, 'dev') || \str_contains($version, 'alpha') || \str_contains($version, 'beta') || \str_contains($version, 'rc') || \str_contains($version, 'main')) {
154+
continue;
155+
}
156+
157+
if ($this->checkVersionConstraint($versionData['require']['shopware/core'], $this->shopwareVersion)) {
158+
return $versionData;
159+
}
160+
}
161+
162+
return null;
163+
}
164+
165+
private function getPackagistData(string $ref)
166+
{
167+
$response = $this->httpClient->request('GET', $ref);
168+
169+
if ($response->getStatusCode() !== 200) {
170+
return null;
171+
}
172+
173+
$packagistData = json_decode($response->getContent(), true);
174+
return $packagistData;
175+
}
176+
177+
private function checkVersionConstraint(string $constraint, string $version): bool
178+
{
179+
return Semver::satisfies($version, $constraint);
180+
}
181+
182+
private function loadLanguageId(string $locale)
183+
{
184+
$key = 'nuonic-plugin-locale-' . $locale;
185+
$context = Context::createDefaultContext();
186+
187+
$value = $this->cache->get($key, function (ItemInterface $item) use ($locale, $context) {
36188

37-
$packagistData = $this->getPackagistData($ref);
189+
$languageCriteria = new Criteria();
190+
$languageCriteria->addFilter(new EqualsFilter('locale.code', $locale));
191+
$languageCriteria->setLimit(1);
38192

39-
$version = $this->findSuitableVersion($packagistData);
193+
$languageId = $this->languageRepository->searchIds($languageCriteria, $context)->firstId();
40194

41-
$plugin = $this->availableOpensourcePluginRepository->search(
42-
(new Criteria())->addFilter(new EqualsFilter('packageName', $packageInformation->packageName)),
43-
Context::createDefaultContext()
44-
)->first();
195+
return CacheValueCompressor::compress($languageId);
196+
});
45197

46-
$id = $plugin?->getId() ?? Uuid::randomHex();
47-
}
48-
49-
private function findSuitableVersion(array $packagistData): string
50-
{
51-
$test = 1;
52-
53-
return '';
54-
}
55-
56-
private function getPackagistData(string $ref)
57-
{
58-
$packagistData = file_get_contents($ref);
59-
$packagistData = json_decode($packagistData, true);
60-
61-
return $packagistData;
62-
}
63-
64-
private function checkVersionConstraint(string $constraint, string $version): bool
65-
{
66-
return Semver::satisfies($version, $constraint);
67-
}
198+
return CacheValueCompressor::uncompress($value);
199+
}
68200
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace NuonicPluginInstaller\Command;
6+
7+
use NuonicPluginInstaller\Action\LoadPluginAction;
8+
use Symfony\Component\Console\Command\Command;
9+
use Symfony\Component\Console\Input\InputInterface;
10+
use Symfony\Component\Console\Output\OutputInterface;
11+
use Symfony\Component\Console\Attribute\AsCommand;
12+
use Symfony\Component\Console\Input\InputOption;
13+
14+
// Command name
15+
#[AsCommand(name: 'nuonic:plugin-installer:plugin:load')]
16+
class TestPluginLoaderAction extends Command
17+
{
18+
public function __construct(
19+
private LoadPluginAction $loadPluginAction,
20+
) {
21+
parent::__construct();
22+
}
23+
// Provides a description, printed out in bin/console
24+
protected function configure(): void
25+
{
26+
$this->setDescription('Tests LoadPluginAction');
27+
$this->addOption('packageName', 'p', InputOption::VALUE_REQUIRED, 'Package name to load');
28+
}
29+
30+
// Actual code executed in the command
31+
protected function execute(InputInterface $input, OutputInterface $output): int
32+
{
33+
$this->loadPluginAction->execute($input->getOption('packageName'));
34+
return Command::SUCCESS;
35+
}
36+
}

src/Core/Framework/Plugin/AvailableOpensourcePlugin/Aggregate/AvailableOpensourcePluginTranslation/AvailableOpensourcePluginTranslationDefinition.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use NuonicPluginInstaller\Core\Framework\Plugin\AvailableOpensourcePlugin\AvailableOpensourcePluginDefinition;
88
use Shopware\Core\Framework\DataAbstractionLayer\EntityTranslationDefinition;
99
use Shopware\Core\Framework\DataAbstractionLayer\Field\Flag\Required;
10+
use Shopware\Core\Framework\DataAbstractionLayer\Field\LongTextField;
1011
use Shopware\Core\Framework\DataAbstractionLayer\Field\StringField;
1112
use Shopware\Core\Framework\DataAbstractionLayer\FieldCollection;
1213

@@ -33,7 +34,7 @@ protected function defineFields(): FieldCollection
3334
{
3435
return new FieldCollection([
3536
(new StringField('name', 'name'))->addFlags(new Required()),
36-
(new StringField('description', 'description'))->addFlags(new Required()),
37+
(new LongTextField('description', 'description'))->addFlags(new Required()),
3738
]);
3839
}
3940
}

src/Migration/Migration1741374590CreateEntities.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@ public function update(Connection $connection): void
2222
$connection->executeStatement('
2323
CREATE TABLE IF NOT EXISTS `nuonic_available_opensource_plugin` (
2424
`id` BINARY(16) NOT NULL,
25-
`name` VARCHAR(255) NOT NULL,
26-
`description` LONGTEXT NOT NULL,
2725
`manufacturer` VARCHAR(255) NOT NULL,
2826
`manufacturer_link` VARCHAR(255) NOT NULL,
2927
`icon` VARCHAR(255),

src/Resources/config/services.xml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
<?xml version="1.0" ?>
22

33
<container xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4-
xmlns="http://symfony.com/schema/dic/services"
5-
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
4+
xmlns="http://symfony.com/schema/dic/services" xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
65
<services>
76
<service id="NuonicPluginInstaller\Config\PluginConfigService">
87
<argument type="service" id="Shopware\Core\System\SystemConfig\SystemConfigService" />
@@ -24,6 +23,10 @@
2423
<service id="NuonicPluginInstaller\Action\LoadPluginAction">
2524
<argument type="service" id="nuonic_available_opensource_plugin.repository" />
2625
<argument type="service" id="NuonicPluginInstaller\Service\IndexFileServiceInterface" />
26+
<argument type="service" id="http_client" />
27+
<argument type="service" id="cache.app"/>
28+
<argument type="service" id="language.repository"/>
29+
<argument>%kernel.shopware_version%</argument>
2730
</service>
2831

2932
<service id="NuonicPluginInstaller\Service\IndexFileServiceInterface" class="NuonicPluginInstaller\Service\IndexFileService">
@@ -64,5 +67,9 @@
6467
<argument type="service" id="NuonicPluginInstaller\Action\LoadPluginAction" />
6568
<tag name="messenger.message_handler" />
6669
</service>
70+
<service id="NuonicPluginInstaller\Command\TestPluginLoaderAction">
71+
<argument type="service" id="NuonicPluginInstaller\Action\LoadPluginAction" />
72+
<tag name="console.command" />
73+
</service>
6774
</services>
6875
</container>

0 commit comments

Comments
 (0)