src/Core/Framework/Api/Controller/InfoController.php line 59

  1. <?php declare(strict_types=1);
  2. namespace Shopware\Core\Framework\Api\Controller;
  3. use Doctrine\DBAL\Connection;
  4. use Shopware\Core\Content\Flow\Api\FlowActionCollector;
  5. use Shopware\Core\Framework\Api\ApiDefinition\DefinitionService;
  6. use Shopware\Core\Framework\Api\ApiDefinition\Generator\EntitySchemaGenerator;
  7. use Shopware\Core\Framework\Api\ApiDefinition\Generator\OpenApi3Generator;
  8. use Shopware\Core\Framework\Bundle;
  9. use Shopware\Core\Framework\Context;
  10. use Shopware\Core\Framework\Event\BusinessEventCollector;
  11. use Shopware\Core\Framework\Increment\Exception\IncrementGatewayNotFoundException;
  12. use Shopware\Core\Framework\Increment\IncrementGatewayRegistry;
  13. use Shopware\Core\Framework\Log\Package;
  14. use Shopware\Core\Framework\Plugin;
  15. use Shopware\Core\Framework\Routing\Exception\InvalidRequestParameterException;
  16. use Shopware\Core\Kernel;
  17. use Shopware\Core\Maintenance\System\Service\AppUrlVerifier;
  18. use Shopware\Core\PlatformRequest;
  19. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  20. use Symfony\Component\Asset\PackageInterface;
  21. use Symfony\Component\Asset\Packages;
  22. use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
  23. use Symfony\Component\HttpFoundation\JsonResponse;
  24. use Symfony\Component\HttpFoundation\Request;
  25. use Symfony\Component\HttpFoundation\Response;
  26. use Symfony\Component\Routing\Annotation\Route;
  27. #[Route(defaults: ['_routeScope' => ['api']])]
  28. #[Package('core')]
  29. class InfoController extends AbstractController
  30. {
  31.     /**
  32.      * @param array{administration?: string} $cspTemplates
  33.      *
  34.      * @internal
  35.      */
  36.     public function __construct(private readonly DefinitionService $definitionService, private readonly ParameterBagInterface $params, private readonly Kernel $kernel, private readonly Packages $packages, private readonly BusinessEventCollector $eventCollector, private readonly IncrementGatewayRegistry $incrementGatewayRegistry, private readonly Connection $connection, private readonly AppUrlVerifier $appUrlVerifier, private readonly ?FlowActionCollector $flowActionCollector null, private readonly bool $enableUrlFeature true, private readonly array $cspTemplates = [])
  37.     {
  38.     }
  39.     #[Route(path'/api/_info/openapi3.json'defaults: ['auth_required' => '%shopware.api.api_browser.auth_required_str%'], name'api.info.openapi3'methods: ['GET'])]
  40.     public function info(Request $request): JsonResponse
  41.     {
  42.         $apiType $request->query->getAlpha('type'DefinitionService::TypeJsonApi);
  43.         $apiType $this->definitionService->toApiType($apiType);
  44.         if ($apiType === null) {
  45.             throw new InvalidRequestParameterException('type');
  46.         }
  47.         $data $this->definitionService->generate(OpenApi3Generator::FORMATDefinitionService::API$apiType);
  48.         return new JsonResponse($data);
  49.     }
  50.     #[Route(path'/api/_info/queue.json'name'api.info.queue'methods: ['GET'])]
  51.     public function queue(): JsonResponse
  52.     {
  53.         try {
  54.             $gateway $this->incrementGatewayRegistry->get(IncrementGatewayRegistry::MESSAGE_QUEUE_POOL);
  55.         } catch (IncrementGatewayNotFoundException) {
  56.             // In case message_queue pool is disabled
  57.             return new JsonResponse([]);
  58.         }
  59.         // Fetch unlimited message_queue_stats
  60.         $entries $gateway->list('message_queue_stats', -1);
  61.         return new JsonResponse(array_map(fn (array $entry) => [
  62.             'name' => $entry['key'],
  63.             'size' => (int) $entry['count'],
  64.         ], array_values($entries)));
  65.     }
  66.     #[Route(path'/api/_info/open-api-schema.json'defaults: ['auth_required' => '%shopware.api.api_browser.auth_required_str%'], name'api.info.open-api-schema'methods: ['GET'])]
  67.     public function openApiSchema(): JsonResponse
  68.     {
  69.         $data $this->definitionService->getSchema(OpenApi3Generator::FORMATDefinitionService::API);
  70.         return new JsonResponse($data);
  71.     }
  72.     #[Route(path'/api/_info/entity-schema.json'name'api.info.entity-schema'methods: ['GET'])]
  73.     public function entitySchema(): JsonResponse
  74.     {
  75.         $data $this->definitionService->getSchema(EntitySchemaGenerator::FORMATDefinitionService::API);
  76.         return new JsonResponse($data);
  77.     }
  78.     #[Route(path'/api/_info/events.json'name'api.info.business-events'methods: ['GET'])]
  79.     public function businessEvents(Context $context): JsonResponse
  80.     {
  81.         $events $this->eventCollector->collect($context);
  82.         return new JsonResponse($events);
  83.     }
  84.     #[Route(path'/api/_info/swagger.html'defaults: ['auth_required' => '%shopware.api.api_browser.auth_required_str%'], name'api.info.swagger'methods: ['GET'])]
  85.     public function infoHtml(Request $request): Response
  86.     {
  87.         $nonce $request->attributes->get(PlatformRequest::ATTRIBUTE_CSP_NONCE);
  88.         $apiType $request->query->getAlpha('type'DefinitionService::TypeJson);
  89.         $response $this->render(
  90.             '@Framework/swagger.html.twig',
  91.             [
  92.                 'schemaUrl' => 'api.info.openapi3',
  93.                 'cspNonce' => $nonce,
  94.                 'apiType' => $apiType,
  95.             ]
  96.         );
  97.         $cspTemplate $this->cspTemplates['administration'] ?? '';
  98.         $cspTemplate trim($cspTemplate);
  99.         if ($cspTemplate !== '') {
  100.             $csp str_replace('%nonce%'$nonce$cspTemplate);
  101.             $csp str_replace(["\n""\r"], ' '$csp);
  102.             $response->headers->set('Content-Security-Policy'$csp);
  103.         }
  104.         return $response;
  105.     }
  106.     #[Route(path'/api/_info/config'name'api.info.config'methods: ['GET'])]
  107.     public function config(Context $contextRequest $request): JsonResponse
  108.     {
  109.         return new JsonResponse([
  110.             'version' => $this->params->get('kernel.shopware_version'),
  111.             'versionRevision' => $this->params->get('kernel.shopware_version_revision'),
  112.             'adminWorker' => [
  113.                 'enableAdminWorker' => $this->params->get('shopware.admin_worker.enable_admin_worker'),
  114.                 'enableQueueStatsWorker' => $this->params->get('shopware.admin_worker.enable_queue_stats_worker'),
  115.                 'enableNotificationWorker' => $this->params->get('shopware.admin_worker.enable_notification_worker'),
  116.                 'transports' => $this->params->get('shopware.admin_worker.transports'),
  117.             ],
  118.             'bundles' => $this->getBundles($context),
  119.             'settings' => [
  120.                 'enableUrlFeature' => $this->enableUrlFeature,
  121.                 'appUrlReachable' => $this->appUrlVerifier->isAppUrlReachable($request),
  122.                 'appsRequireAppUrl' => $this->appUrlVerifier->hasAppsThatNeedAppUrl(),
  123.                 'private_allowed_extensions' => $this->params->get('shopware.filesystem.private_allowed_extensions'),
  124.             ],
  125.         ]);
  126.     }
  127.     #[Route(path'/api/_info/version'name'api.info.shopware.version'methods: ['GET'])]
  128.     #[Route(path'/api/v1/_info/version'name'api.info.shopware.version_old_version'methods: ['GET'])]
  129.     public function infoShopwareVersion(): JsonResponse
  130.     {
  131.         return new JsonResponse([
  132.             'version' => $this->params->get('kernel.shopware_version'),
  133.         ]);
  134.     }
  135.     #[Route(path'/api/_info/flow-actions.json'name'api.info.actions'methods: ['GET'])]
  136.     public function flowActions(Context $context): JsonResponse
  137.     {
  138.         if (!$this->flowActionCollector) {
  139.             return $this->json([]);
  140.         }
  141.         $events $this->flowActionCollector->collect($context);
  142.         return new JsonResponse($events);
  143.     }
  144.     /**
  145.      * @return array<string, array{type: 'plugin', css: string[], js: string[], baseUrl: ?string }|array{type: 'app', name: string, active: bool, integrationId: string, baseUrl: string, version: string, permissions: array<string,string[]>}>
  146.      */
  147.     private function getBundles(Context $context): array
  148.     {
  149.         $assets = [];
  150.         $package $this->packages->getPackage('asset');
  151.         foreach ($this->kernel->getBundles() as $bundle) {
  152.             if (!$bundle instanceof Bundle) {
  153.                 continue;
  154.             }
  155.             $bundleDirectoryName preg_replace('/bundle$/'''mb_strtolower($bundle->getName()));
  156.             if ($bundleDirectoryName === null) {
  157.                 throw new \RuntimeException(sprintf('Unable to generate bundle directory for bundle "%s"'$bundle->getName()));
  158.             }
  159.             $styles array_map(static function (string $filename) use ($package$bundleDirectoryName) {
  160.                 $url 'bundles/' $bundleDirectoryName '/' $filename;
  161.                 return $package->getUrl($url);
  162.             }, $this->getAdministrationStyles($bundle));
  163.             $scripts array_map(static function (string $filename) use ($package$bundleDirectoryName) {
  164.                 $url 'bundles/' $bundleDirectoryName '/' $filename;
  165.                 return $package->getUrl($url);
  166.             }, $this->getAdministrationScripts($bundle));
  167.             $baseUrl $this->getBaseUrl($bundle$package$bundleDirectoryName);
  168.             if (empty($styles) && empty($scripts)) {
  169.                 if ($baseUrl === null) {
  170.                     continue;
  171.                 }
  172.             }
  173.             $assets[$bundle->getName()] = [
  174.                 'css' => $styles,
  175.                 'js' => $scripts,
  176.                 'baseUrl' => $baseUrl,
  177.                 'type' => 'plugin',
  178.             ];
  179.         }
  180.         foreach ($this->getActiveApps() as $app) {
  181.             $assets[$app['name']] = [
  182.                 'active' => (bool) $app['active'],
  183.                 'integrationId' => $app['integrationId'],
  184.                 'type' => 'app',
  185.                 'baseUrl' => $app['baseUrl'],
  186.                 'permissions' => $app['privileges'],
  187.                 'version' => $app['version'],
  188.                 'name' => $app['name'],
  189.             ];
  190.         }
  191.         return $assets;
  192.     }
  193.     /**
  194.      * @return list<string>
  195.      */
  196.     private function getAdministrationStyles(Bundle $bundle): array
  197.     {
  198.         $path 'administration/css/' str_replace('_''-', (string) $bundle->getContainerPrefix()) . '.css';
  199.         $bundlePath $bundle->getPath();
  200.         if (!file_exists($bundlePath '/Resources/public/' $path)) {
  201.             return [];
  202.         }
  203.         return [$path];
  204.     }
  205.     /**
  206.      * @return list<string>
  207.      */
  208.     private function getAdministrationScripts(Bundle $bundle): array
  209.     {
  210.         $path 'administration/js/' str_replace('_''-', (string) $bundle->getContainerPrefix()) . '.js';
  211.         $bundlePath $bundle->getPath();
  212.         if (!file_exists($bundlePath '/Resources/public/' $path)) {
  213.             return [];
  214.         }
  215.         return [$path];
  216.     }
  217.     private function getBaseUrl(Bundle $bundlePackageInterface $packagestring $bundleDirectoryName): ?string
  218.     {
  219.         if (!$bundle instanceof Plugin) {
  220.             return null;
  221.         }
  222.         if ($bundle->getAdminBaseUrl()) {
  223.             return $bundle->getAdminBaseUrl();
  224.         }
  225.         $defaultEntryFile 'administration/index.html';
  226.         $bundlePath $bundle->getPath();
  227.         if (!file_exists($bundlePath '/Resources/public/' $defaultEntryFile)) {
  228.             return null;
  229.         }
  230.         $url 'bundles/' $bundleDirectoryName '/' $defaultEntryFile;
  231.         return $package->getUrl($url);
  232.     }
  233.     /**
  234.      * @return list<array{name: string, active: int, integrationId: string, baseUrl: string, version: string, privileges: array<string,list<string>>}>
  235.      */
  236.     private function getActiveApps(): array
  237.     {
  238.         /** @var list<array{name: string, active: int, integrationId: string, baseUrl: string, version: string, privileges: ?string}> $apps */
  239.         $apps $this->connection->fetchAllAssociative('SELECT
  240.     app.name,
  241.     app.active,
  242.     LOWER(HEX(app.integration_id)) as integrationId,
  243.     app.base_app_url as baseUrl,
  244.     app.version,
  245.     ar.privileges as privileges
  246. FROM app
  247. LEFT JOIN acl_role ar on app.acl_role_id = ar.id
  248. WHERE app.active = 1 AND app.base_app_url is not null');
  249.         return array_map(static function (array $item) {
  250.             $privileges $item['privileges'] ? json_decode((string) $item['privileges'], true512\JSON_THROW_ON_ERROR) : [];
  251.             $item['privileges'] = [];
  252.             foreach ($privileges as $privilege) {
  253.                 if (substr_count($privilege':') !== 1) {
  254.                     $item['privileges']['additional'][] = $privilege;
  255.                     continue;
  256.                 }
  257.                 [$entity$key] = \explode(':'$privilege);
  258.                 $item['privileges'][$key][] = $entity;
  259.             }
  260.             return $item;
  261.         }, $apps);
  262.     }
  263. }