vendor/shopware/core/Framework/Api/Controller/InfoController.php line 116

Open in your IDE?
  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\Feature;
  12. use Shopware\Core\Framework\Increment\Exception\IncrementGatewayNotFoundException;
  13. use Shopware\Core\Framework\Increment\IncrementGatewayRegistry;
  14. use Shopware\Core\Framework\Log\Package;
  15. use Shopware\Core\Framework\Plugin;
  16. use Shopware\Core\Framework\Routing\Annotation\Since;
  17. use Shopware\Core\Framework\Routing\Exception\InvalidRequestParameterException;
  18. use Shopware\Core\Kernel;
  19. use Shopware\Core\Maintenance\System\Service\AppUrlVerifier;
  20. use Shopware\Core\PlatformRequest;
  21. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  22. use Symfony\Component\Asset\PackageInterface;
  23. use Symfony\Component\Asset\Packages;
  24. use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
  25. use Symfony\Component\HttpFoundation\JsonResponse;
  26. use Symfony\Component\HttpFoundation\Request;
  27. use Symfony\Component\HttpFoundation\Response;
  28. use Symfony\Component\Routing\Annotation\Route;
  29. /**
  30. * @Route(defaults={"_routeScope"={"api"}})
  31. */
  32. #[Package('core')]
  33. class InfoController extends AbstractController
  34. {
  35. private DefinitionService $definitionService;
  36. private ParameterBagInterface $params;
  37. private Packages $packages;
  38. private Kernel $kernel;
  39. private bool $enableUrlFeature;
  40. /**
  41. * @var array{administration?: string}
  42. */
  43. private array $cspTemplates;
  44. private BusinessEventCollector $eventCollector;
  45. private ?FlowActionCollector $flowActionCollector;
  46. private IncrementGatewayRegistry $incrementGatewayRegistry;
  47. private Connection $connection;
  48. private AppUrlVerifier $appUrlVerifier;
  49. /**
  50. * @param array{administration?: string} $cspTemplates
  51. *
  52. * @internal
  53. */
  54. public function __construct(
  55. DefinitionService $definitionService,
  56. ParameterBagInterface $params,
  57. Kernel $kernel,
  58. Packages $packages,
  59. BusinessEventCollector $eventCollector,
  60. IncrementGatewayRegistry $incrementGatewayRegistry,
  61. Connection $connection,
  62. AppUrlVerifier $appUrlVerifier,
  63. ?FlowActionCollector $flowActionCollector = null,
  64. bool $enableUrlFeature = true,
  65. array $cspTemplates = []
  66. ) {
  67. $this->definitionService = $definitionService;
  68. $this->params = $params;
  69. $this->packages = $packages;
  70. $this->kernel = $kernel;
  71. $this->enableUrlFeature = $enableUrlFeature;
  72. $this->flowActionCollector = $flowActionCollector;
  73. $this->cspTemplates = $cspTemplates;
  74. $this->eventCollector = $eventCollector;
  75. $this->incrementGatewayRegistry = $incrementGatewayRegistry;
  76. $this->connection = $connection;
  77. $this->appUrlVerifier = $appUrlVerifier;
  78. }
  79. /**
  80. * @Since("6.0.0.0")
  81. * @Route("/api/_info/openapi3.json", defaults={"auth_required"="%shopware.api.api_browser.auth_required_str%"}, name="api.info.openapi3", methods={"GET"})
  82. */
  83. public function info(Request $request): JsonResponse
  84. {
  85. $apiType = $request->query->getAlpha('type', DefinitionService::TypeJsonApi);
  86. $apiType = $this->definitionService->toApiType($apiType);
  87. if ($apiType === null) {
  88. throw new InvalidRequestParameterException('type');
  89. }
  90. $data = $this->definitionService->generate(OpenApi3Generator::FORMAT, DefinitionService::API, $apiType);
  91. return new JsonResponse($data);
  92. }
  93. /**
  94. * @Since("6.4.6.0")
  95. * @Route("/api/_info/queue.json", name="api.info.queue", methods={"GET"})
  96. */
  97. public function queue(): JsonResponse
  98. {
  99. try {
  100. $gateway = $this->incrementGatewayRegistry->get(IncrementGatewayRegistry::MESSAGE_QUEUE_POOL);
  101. } catch (IncrementGatewayNotFoundException $exception) {
  102. // In case message_queue pool is disabled
  103. return new JsonResponse([]);
  104. }
  105. // Fetch unlimited message_queue_stats
  106. $entries = $gateway->list('message_queue_stats', -1);
  107. return new JsonResponse(array_map(function (array $entry) {
  108. return [
  109. 'name' => $entry['key'],
  110. 'size' => (int) $entry['count'],
  111. ];
  112. }, array_values($entries)));
  113. }
  114. /**
  115. * @Since("6.0.0.0")
  116. * @Route("/api/_info/open-api-schema.json", defaults={"auth_required"="%shopware.api.api_browser.auth_required_str%"}, name="api.info.open-api-schema", methods={"GET"})
  117. */
  118. public function openApiSchema(): JsonResponse
  119. {
  120. $data = $this->definitionService->getSchema(OpenApi3Generator::FORMAT, DefinitionService::API);
  121. return new JsonResponse($data);
  122. }
  123. /**
  124. * @Since("6.0.0.0")
  125. * @Route("/api/_info/entity-schema.json", name="api.info.entity-schema", methods={"GET"})
  126. */
  127. public function entitySchema(): JsonResponse
  128. {
  129. $data = $this->definitionService->getSchema(EntitySchemaGenerator::FORMAT, DefinitionService::API);
  130. return new JsonResponse($data);
  131. }
  132. /**
  133. * @Since("6.3.2.0")
  134. * @Route("/api/_info/events.json", name="api.info.business-events", methods={"GET"})
  135. */
  136. public function businessEvents(Context $context): JsonResponse
  137. {
  138. $events = $this->eventCollector->collect($context);
  139. return new JsonResponse($events);
  140. }
  141. /**
  142. * @Since("6.0.0.0")
  143. * @Route("/api/_info/swagger.html", defaults={"auth_required"="%shopware.api.api_browser.auth_required_str%"}, name="api.info.swagger", methods={"GET"})
  144. */
  145. public function infoHtml(Request $request): Response
  146. {
  147. $nonce = $request->attributes->get(PlatformRequest::ATTRIBUTE_CSP_NONCE);
  148. $apiType = $request->query->getAlpha('type', DefinitionService::TypeJson);
  149. $response = $this->render(
  150. '@Framework/swagger.html.twig',
  151. [
  152. 'schemaUrl' => 'api.info.openapi3',
  153. 'cspNonce' => $nonce,
  154. 'apiType' => $apiType,
  155. ]
  156. );
  157. $cspTemplate = $this->cspTemplates['administration'] ?? '';
  158. $cspTemplate = trim($cspTemplate);
  159. if ($cspTemplate !== '') {
  160. $csp = str_replace('%nonce%', $nonce, $cspTemplate);
  161. $csp = str_replace(["\n", "\r"], ' ', $csp);
  162. $response->headers->set('Content-Security-Policy', $csp);
  163. }
  164. return $response;
  165. }
  166. /**
  167. * @Since("6.0.0.0")
  168. * @Route("/api/_info/config", name="api.info.config", methods={"GET"})
  169. *
  170. * @deprecated tag:v6.5.0 $context param will be required
  171. * @deprecated tag:v6.5.0 $request param will be required
  172. */
  173. public function config(?Context $context = null, ?Request $request = null): JsonResponse
  174. {
  175. if (!$context) {
  176. Feature::triggerDeprecationOrThrow(
  177. 'v6.5.0.0',
  178. 'First parameter `$context` will be required in method `config()` in `InfoController` in v6.5.0.0'
  179. );
  180. $context = Context::createDefaultContext();
  181. }
  182. $appUrlReachable = true;
  183. if ($request) {
  184. $appUrlReachable = $this->appUrlVerifier->isAppUrlReachable($request);
  185. }
  186. return new JsonResponse([
  187. 'version' => $this->params->get('kernel.shopware_version'),
  188. 'versionRevision' => $this->params->get('kernel.shopware_version_revision'),
  189. 'adminWorker' => [
  190. 'enableAdminWorker' => $this->params->get('shopware.admin_worker.enable_admin_worker'),
  191. 'transports' => $this->params->get('shopware.admin_worker.transports'),
  192. ],
  193. 'bundles' => $this->getBundles($context),
  194. 'settings' => [
  195. 'enableUrlFeature' => $this->enableUrlFeature,
  196. 'appUrlReachable' => $appUrlReachable,
  197. 'appsRequireAppUrl' => $this->appUrlVerifier->hasAppsThatNeedAppUrl($context),
  198. 'private_allowed_extensions' => $this->params->get('shopware.filesystem.private_allowed_extensions'),
  199. ],
  200. ]);
  201. }
  202. /**
  203. * @Since("6.3.5.0")
  204. * @Route("/api/_info/version", name="api.info.shopware.version", methods={"GET"})
  205. * @Route("/api/v1/_info/version", name="api.info.shopware.version_old_version", methods={"GET"})
  206. */
  207. public function infoShopwareVersion(): JsonResponse
  208. {
  209. return new JsonResponse([
  210. 'version' => $this->params->get('kernel.shopware_version'),
  211. ]);
  212. }
  213. /**
  214. * @Since("6.4.5.0")
  215. * @Route("/api/_info/flow-actions.json", name="api.info.actions", methods={"GET"})
  216. */
  217. public function flowActions(Context $context): JsonResponse
  218. {
  219. if (!$this->flowActionCollector) {
  220. return $this->json([]);
  221. }
  222. $events = $this->flowActionCollector->collect($context);
  223. return new JsonResponse($events);
  224. }
  225. /**
  226. * @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[]>}>
  227. */
  228. private function getBundles(Context $context): array
  229. {
  230. $assets = [];
  231. $package = $this->packages->getPackage('asset');
  232. foreach ($this->kernel->getBundles() as $bundle) {
  233. if (!$bundle instanceof Bundle) {
  234. continue;
  235. }
  236. $bundleDirectoryName = preg_replace('/bundle$/', '', mb_strtolower($bundle->getName()));
  237. if ($bundleDirectoryName === null) {
  238. throw new \RuntimeException(sprintf('Unable to generate bundle directory for bundle "%s"', $bundle->getName()));
  239. }
  240. $styles = array_map(static function (string $filename) use ($package, $bundleDirectoryName) {
  241. $url = 'bundles/' . $bundleDirectoryName . '/' . $filename;
  242. return $package->getUrl($url);
  243. }, $this->getAdministrationStyles($bundle));
  244. $scripts = array_map(static function (string $filename) use ($package, $bundleDirectoryName) {
  245. $url = 'bundles/' . $bundleDirectoryName . '/' . $filename;
  246. return $package->getUrl($url);
  247. }, $this->getAdministrationScripts($bundle));
  248. $baseUrl = $this->getBaseUrl($bundle, $package, $bundleDirectoryName);
  249. if (empty($styles) && empty($scripts)) {
  250. if ($baseUrl === null) {
  251. continue;
  252. }
  253. }
  254. $assets[$bundle->getName()] = [
  255. 'css' => $styles,
  256. 'js' => $scripts,
  257. 'baseUrl' => $baseUrl,
  258. 'type' => 'plugin',
  259. ];
  260. }
  261. foreach ($this->getActiveApps() as $app) {
  262. $assets[$app['name']] = [
  263. 'active' => (bool) $app['active'],
  264. 'integrationId' => $app['integrationId'],
  265. 'type' => 'app',
  266. 'baseUrl' => $app['baseUrl'],
  267. 'permissions' => $app['privileges'],
  268. 'version' => $app['version'],
  269. 'name' => $app['name'],
  270. ];
  271. }
  272. return $assets;
  273. }
  274. /**
  275. * @return list<string>
  276. */
  277. private function getAdministrationStyles(Bundle $bundle): array
  278. {
  279. $path = 'administration/css/' . str_replace('_', '-', $bundle->getContainerPrefix()) . '.css';
  280. $bundlePath = $bundle->getPath();
  281. if (!file_exists($bundlePath . '/Resources/public/' . $path)) {
  282. return [];
  283. }
  284. return [$path];
  285. }
  286. /**
  287. * @return list<string>
  288. */
  289. private function getAdministrationScripts(Bundle $bundle): array
  290. {
  291. $path = 'administration/js/' . str_replace('_', '-', $bundle->getContainerPrefix()) . '.js';
  292. $bundlePath = $bundle->getPath();
  293. if (!file_exists($bundlePath . '/Resources/public/' . $path)) {
  294. return [];
  295. }
  296. return [$path];
  297. }
  298. private function getBaseUrl(Bundle $bundle, PackageInterface $package, string $bundleDirectoryName): ?string
  299. {
  300. if (!$bundle instanceof Plugin) {
  301. return null;
  302. }
  303. if ($bundle->getAdminBaseUrl()) {
  304. return $bundle->getAdminBaseUrl();
  305. }
  306. $defaultEntryFile = 'administration/index.html';
  307. $bundlePath = $bundle->getPath();
  308. if (!file_exists($bundlePath . '/Resources/public/' . $defaultEntryFile)) {
  309. return null;
  310. }
  311. $url = 'bundles/' . $bundleDirectoryName . '/' . $defaultEntryFile;
  312. return $package->getUrl($url);
  313. }
  314. /**
  315. * @return list<array{name: string, active: int, integrationId: string, baseUrl: string, version: string, privileges: array<string,list<string>>}>
  316. */
  317. private function getActiveApps(): array
  318. {
  319. /** @var list<array{name: string, active: int, integrationId: string, baseUrl: string, version: string, privileges: ?string}> $apps */
  320. $apps = $this->connection->fetchAllAssociative('SELECT
  321. app.name,
  322. app.active,
  323. LOWER(HEX(app.integration_id)) as integrationId,
  324. app.base_app_url as baseUrl,
  325. app.version,
  326. ar.privileges as privileges
  327. FROM app
  328. LEFT JOIN acl_role ar on app.acl_role_id = ar.id
  329. WHERE app.active = 1 AND app.base_app_url is not null');
  330. return array_map(static function (array $item) {
  331. $privileges = $item['privileges'] ? json_decode($item['privileges'], true, 512, \JSON_THROW_ON_ERROR) : [];
  332. $item['privileges'] = [];
  333. foreach ($privileges as $privilege) {
  334. if (substr_count($privilege, ':') !== 1) {
  335. $item['privileges']['additional'][] = $privilege;
  336. continue;
  337. }
  338. [$entity, $key] = \explode(':', $privilege);
  339. $item['privileges'][$key][] = $entity;
  340. }
  341. return $item;
  342. }, $apps);
  343. }
  344. }