src/Controller/PageGeneratorController.php line 44

Open in your IDE?
  1. <?php
  2. namespace App\Controller;
  3. use Twig\Environment;
  4. use Symfony\Component\Routing\Annotation\Route;
  5. use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
  6. use Symfony\Component\HttpFoundation\Request;
  7. use Symfony\Component\HttpFoundation\Response;
  8. use Symfony\Component\HttpFoundation\RedirectResponse;
  9. use Symfony\Component\Finder\Finder;
  10. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  11. use Symfony\Component\EventDispatcher\GenericEvent;
  12. use Symfony\Component\Form\FormInterface;
  13. use Symfony\Component\Security\Core\Security;
  14. use Symfony\Component\Routing\RequestContext;
  15. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  16. use Sylius\Component\Channel\Context\ChannelContextInterface;
  17. use App\Repository\UserRepository;
  18. use App\Service\UserService;
  19. use Vanilla\JsConnect\JsConnect;
  20. use Vanilla\JsConnect\JsConnectJSONP;
  21. final class PageGeneratorController extends AbstractController
  22. {
  23. private const CONTACT_EMAIL = 'info@hephaestos.eu';
  24. /** @var Environment */
  25. private $twig;
  26. /** @var ChannelContextInterface */
  27. private $channelContext;
  28. private Jsconnect $jsconnect;
  29. public function __construct(Environment $twig, Jsconnect $jsconnect, ChannelContextInterface $channelContext)
  30. {
  31. $this->twig = $twig;
  32. $this->jsconnect = $jsconnect;
  33. $this->channelContext = $channelContext;
  34. }
  35. public function menuAction(): Response
  36. {
  37. return $this->render('@SyliusShop/Menu/_pageMenu.html.twig');
  38. }
  39. /**
  40. * Subscription page disabled for the beta-only release.
  41. */
  42. public function subscriptionAction(): Response
  43. {
  44. throw $this->createNotFoundException();
  45. }
  46. /**
  47. * @Route("/beta-testers", name="beta_testers")
  48. */
  49. public function betaTestersAction(Request $request, \Swift_Mailer $mailer): Response
  50. {
  51. $formData = [
  52. 'firstName' => '',
  53. 'lastName' => '',
  54. 'email' => '',
  55. 'company' => '',
  56. 'role' => '',
  57. 'showType' => '',
  58. 'operatingSystem' => '',
  59. 'hardware' => '',
  60. 'features' => '',
  61. 'usage' => '',
  62. 'feedback' => '',
  63. 'availability' => '',
  64. ];
  65. if ($request->isMethod('POST')) {
  66. $formData = array_map(
  67. static fn ($value): string => trim((string) $value),
  68. array_intersect_key($request->request->all(), $formData)
  69. ) + $formData;
  70. $honeypot = trim((string) $request->request->get('website', ''));
  71. $requiredFields = ['firstName', 'lastName', 'email', 'role', 'operatingSystem', 'usage', 'feedback'];
  72. $missingFields = array_filter($requiredFields, static fn (string $field): bool => '' === $formData[$field]);
  73. if ('' !== $honeypot) {
  74. $this->addFlash('success', 'beta.form.success');
  75. return $this->redirectToRoute('beta_testers');
  76. }
  77. if (!$this->isCsrfTokenValid('beta_tester_application', (string) $request->request->get('_token'))) {
  78. $this->addFlash('error', 'beta.form.csrf_error');
  79. } elseif ([] !== $missingFields || false === filter_var($formData['email'], FILTER_VALIDATE_EMAIL)) {
  80. $this->addFlash('error', 'beta.form.error');
  81. } else {
  82. $messageBody = $this->renderView('Email/beta_tester_application.html.twig', [
  83. 'data' => $formData,
  84. ]);
  85. $message = (new \Swift_Message('Nouvelle candidature beta testeur Hephaestos'))
  86. ->setFrom([self::CONTACT_EMAIL => 'Hephaestos'])
  87. ->setTo([self::CONTACT_EMAIL])
  88. ->setReplyTo([$formData['email']])
  89. ->setBody($messageBody, 'text/html');
  90. try {
  91. if (0 === $mailer->send($message)) {
  92. throw new \RuntimeException('Beta email was not accepted by the transport.');
  93. }
  94. } catch (\Throwable $exception) {
  95. $this->addFlash('error', 'beta.form.mail_error');
  96. return $this->redirectToRoute('beta_testers');
  97. }
  98. $this->addFlash('success', 'beta.form.success');
  99. return $this->redirectToRoute('beta_testers');
  100. }
  101. }
  102. return $this->render('page_generator/beta_testers.html.twig', [
  103. 'formData' => $formData,
  104. ]);
  105. }
  106. /**
  107. * @Route("/formation/demande", name="formation_request")
  108. */
  109. public function formationRequestAction(Request $request, \Swift_Mailer $mailer): Response
  110. {
  111. $formData = [
  112. 'firstName' => '',
  113. 'lastName' => '',
  114. 'email' => '',
  115. 'company' => '',
  116. 'subject' => '',
  117. 'needs' => '',
  118. 'availability' => '',
  119. ];
  120. if ($request->isMethod('POST')) {
  121. $formData = array_map(
  122. static fn ($value): string => trim((string) $value),
  123. array_intersect_key($request->request->all(), $formData)
  124. ) + $formData;
  125. $honeypot = trim((string) $request->request->get('website', ''));
  126. $requiredFields = ['firstName', 'lastName', 'email', 'subject', 'needs'];
  127. $missingFields = array_filter($requiredFields, static fn (string $field): bool => '' === $formData[$field]);
  128. if ('' !== $honeypot) {
  129. $this->addFlash('success', 'formation.request.success');
  130. return $this->redirectToRoute('formation_request');
  131. }
  132. if (!$this->isCsrfTokenValid('formation_request', (string) $request->request->get('_token'))) {
  133. $this->addFlash('error', 'formation.request.csrf_error');
  134. } elseif ([] !== $missingFields || false === filter_var($formData['email'], FILTER_VALIDATE_EMAIL)) {
  135. $this->addFlash('error', 'formation.request.error');
  136. } else {
  137. $messageBody = $this->renderView('Email/formation_request.html.twig', [
  138. 'data' => $formData,
  139. ]);
  140. $message = (new \Swift_Message('Nouvelle demande de formation Hephaestos'))
  141. ->setFrom([self::CONTACT_EMAIL => 'Hephaestos'])
  142. ->setTo([self::CONTACT_EMAIL])
  143. ->setReplyTo([$formData['email']])
  144. ->setBody($messageBody, 'text/html');
  145. try {
  146. if (0 === $mailer->send($message)) {
  147. throw new \RuntimeException('Training request email was not accepted by the transport.');
  148. }
  149. } catch (\Throwable $exception) {
  150. $this->addFlash('error', 'formation.request.mail_error');
  151. return $this->redirectToRoute('formation_request');
  152. }
  153. $this->addFlash('success', 'formation.request.success');
  154. return $this->redirectToRoute('formation_request');
  155. }
  156. }
  157. return $this->render('page_generator/formation_request.html.twig', [
  158. 'formData' => $formData,
  159. ]);
  160. }
  161. private function getChannel(): mixed
  162. {
  163. return $this->channelContext->getChannel();
  164. }
  165. /**
  166. * @Route("/old_version", name="old_version")
  167. */
  168. public function oldversionAction(Request $request): Response
  169. {
  170. $finderwindows64 = new Finder();
  171. $windowsfiles64 = $finderwindows64->files()->name('*.zip')->in($this->get('kernel')->getProjectDir() . "/public/versions/old/Windows/x64");
  172. $finderwindows32 = new Finder();
  173. $windowsfiles32 = $finderwindows32->files()->name('*.zip')->in($this->get('kernel')->getProjectDir() . "/public/versions/old/Windows/x86");
  174. $findermac = new Finder();
  175. $macfiles = $findermac->files()->name('*.zip')->in($this->get('kernel')->getProjectDir() . "/public/versions/old/OSX");
  176. return $this->render('page_generator/oldversion.html.twig', array('windowsfiles64' => $windowsfiles64,'windowsfiles32' => $windowsfiles32, 'macfiles' => $macfiles));
  177. }
  178. /**
  179. * @Route("/download", name="download")
  180. public function downloadAction(Request $request): Response
  181. {
  182. return $this->render('page_generator/download.html.twig');
  183. }
  184. */
  185. /**
  186. * @Route("/formation", name="formation")
  187. */
  188. public function formationAction(Request $request): Response
  189. {
  190. $locale = $request->getLocale();
  191. $template = str_starts_with((string) $locale, 'en')
  192. ? 'page_generator/formation.en.html.twig'
  193. : 'page_generator/formation.html.twig';
  194. return $this->render($template);
  195. }
  196. /**
  197. * @Route("/forum", name="forum")
  198. */
  199. public function forumAction(Request $request, UserService $userService): Response
  200. {
  201. $forumRoot = rtrim((string) $this->getParameter('forumdomain'), '/');
  202. $forumIframeUrl = $forumRoot . '/';
  203. $user = $this->getUser();
  204. if ($user === null || !is_object($user) || !method_exists($user, 'isVerified') || !$user->isVerified()) {
  205. return $this->render('page_generator/embedforum.html.twig', [
  206. 'forum_iframe_url' => $forumIframeUrl,
  207. ]);
  208. }
  209. $customer = $userService->getCustomerByUser($user);
  210. $forumName = method_exists($customer, 'getPseudo') ? trim((string) $customer->getPseudo()) : '';
  211. if ('' === $forumName) {
  212. $this->addFlash('error', 'customer.pseudo.required_for_forum');
  213. return $this->redirectToRoute('sylius_shop_account_profile_update');
  214. }
  215. $forumEmail = $this->resolveForumEmail($customer, $user);
  216. $ssoToken = $this->createForumSsoToken([
  217. 'sub' => (string) $user->getId(),
  218. 'name' => $forumName,
  219. 'email' => $forumEmail,
  220. ]);
  221. $forumSsoPath = '/app.php/hephaestos/sso/login';
  222. $forumIframeUrl = $forumRoot . $forumSsoPath . '?token=' . rawurlencode($ssoToken) . '&return=' . rawurlencode('/');
  223. return $this->render('page_generator/embedforum.html.twig', [
  224. 'forum_iframe_url' => $forumIframeUrl,
  225. ]);
  226. }
  227. private function resolveForumEmail(object $customer, object $user): string
  228. {
  229. $candidates = [
  230. method_exists($customer, 'getEmail') ? $customer->getEmail() : null,
  231. method_exists($user, 'getEmail') ? $user->getEmail() : null,
  232. method_exists($user, 'getUsername') ? $user->getUsername() : null,
  233. ];
  234. foreach ($candidates as $candidate) {
  235. $candidate = trim((string) $candidate);
  236. if ('' !== $candidate) {
  237. return $candidate;
  238. }
  239. }
  240. return 'forum-user-' . (method_exists($user, 'getId') ? (string) $user->getId() : 'hephaestos') . '@hephaestos.local';
  241. }
  242. private function resolveForumName(object $customer, object $user): string
  243. {
  244. $candidates = [
  245. method_exists($customer, 'getPseudo') ? $customer->getPseudo() : null,
  246. method_exists($customer, 'getFullName') ? $customer->getFullName() : null,
  247. trim(sprintf(
  248. '%s %s',
  249. method_exists($customer, 'getFirstName') ? (string) $customer->getFirstName() : '',
  250. method_exists($customer, 'getLastName') ? (string) $customer->getLastName() : ''
  251. )),
  252. method_exists($customer, 'getEmail') ? $customer->getEmail() : null,
  253. method_exists($user, 'getEmail') ? $user->getEmail() : null,
  254. method_exists($user, 'getUsername') ? $user->getUsername() : null,
  255. 'user-' . (method_exists($user, 'getId') ? (string) $user->getId() : 'hephaestos'),
  256. ];
  257. foreach ($candidates as $candidate) {
  258. $candidate = trim((string) $candidate);
  259. if ('' !== $candidate) {
  260. return $candidate;
  261. }
  262. }
  263. return 'Hephaestos user';
  264. }
  265. private function createForumSsoToken(array $identity): string
  266. {
  267. $now = time();
  268. $payload = [
  269. 'sub' => (string) ($identity['sub'] ?? ''),
  270. 'name' => (string) ($identity['name'] ?? ''),
  271. 'email' => (string) ($identity['email'] ?? ''),
  272. 'iat' => $now,
  273. 'exp' => $now + 120,
  274. 'nonce' => bin2hex(random_bytes(8)),
  275. ];
  276. $payloadJson = json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
  277. if (false === $payloadJson) {
  278. throw new \RuntimeException('Unable to encode forum SSO payload.');
  279. }
  280. $payloadB64 = rtrim(strtr(base64_encode($payloadJson), '+/', '-_'), '=');
  281. $secret = (string) $this->getParameter('forum_sso_secret');
  282. $signature = hash_hmac('sha256', $payloadB64, $secret);
  283. return $payloadB64 . '.' . $signature;
  284. }
  285. /**
  286. * @Route("/wiki", name="wiki")
  287. */
  288. public function wikiAction(Request $request): Response
  289. {
  290. return $this->render('page_generator/wiki.html.twig');
  291. }
  292. /**
  293. * @Route("/redirectlogin", name="redirectlogin")
  294. */
  295. public function redirectloginAction(Request $request): Response
  296. {
  297. $context = new RequestContext();
  298. $context->fromRequest(Request::createFromGlobals());
  299. $url = $this->generateUrl('sylius_shop_login', [
  300. '_locale' => 'fr',
  301. ], UrlGeneratorInterface::ABSOLUTE_URL);
  302. $js = '<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script><script type="text/javascript">jQuery(document).ready( function() {
  303. top.location = "' . $url . '";});</script>';
  304. $response = new Response();
  305. $response->prepare($request);
  306. $response->setContent($js);
  307. $response->send();
  308. return $this->redirect($url);
  309. //return $this->redirectToRoute('sylius_shop_login', [], 301);
  310. }
  311. /**
  312. * @Route("/redirectlogout", name="redirectlogout")
  313. */
  314. public function redirectlogoutAction(Request $request): Response
  315. {
  316. $context = new RequestContext();
  317. $context->fromRequest(Request::createFromGlobals());
  318. $url = $this->generateUrl('sylius_shop_logout', ['_locale' => 'fr',], UrlGeneratorInterface::ABSOLUTE_URL);
  319. $js = '<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script><script type="text/javascript">jQuery(document).ready( function() {
  320. top.location = "' . $url . '";});</script>';
  321. $response = new Response();
  322. $response->prepare($request);
  323. $response->setContent($js);
  324. $response->send();
  325. }
  326. /**
  327. * @Route("/redirectregister", name="redirectregister")
  328. */
  329. public function redirectregisterAction(Request $request): Response
  330. {
  331. $context = new RequestContext();
  332. $context->fromRequest(Request::createFromGlobals());
  333. $url = $this->generateUrl('sylius_shop_register', ['_locale' => 'fr',], UrlGeneratorInterface::ABSOLUTE_URL);
  334. $js = '<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script><script type="text/javascript">jQuery(document).ready( function() { top.location = "' . $url . '";});</script>';
  335. $response = new Response();
  336. $response->prepare($request);
  337. $response->setContent($js);
  338. $response->send();
  339. // return $this->redirect($url);
  340. //return $this->redirectToRoute('sylius_shop_register', [], 301);
  341. }
  342. /**
  343. * @Route("/electronique", name="electronique")
  344. */
  345. public function electroniqueAction(Request $request): Response
  346. {
  347. $code = array('Board_Taxons','DMX_HF','PWM_DIMMER_Taxon', 'HF_PROJECTOR__Taxon','books');
  348. return $this->render('page_generator/electronique.html.twig', array('code' => $code, 'maxresult' => -1));
  349. }
  350. /**
  351. * @Route("/features", name="features")
  352. */
  353. public function featuresAction(Request $request): Response
  354. {
  355. return $this->render('page_generator/features.html.twig');
  356. }
  357. /**
  358. * @Route("/about", name="about")
  359. */
  360. public function aboutAction(Request $request): Response
  361. {
  362. return $this->render('page_generator/about.html.twig');
  363. }
  364. /**
  365. * @Route("/shipping", name="shipping")
  366. */
  367. public function deliveryAction(Request $request): Response
  368. {
  369. return $this->render('page_generator/shipping.html.twig');
  370. }
  371. /**
  372. * @Route("/FAQ", name="FAQ")
  373. */
  374. public function FAQAction(Request $request): Response
  375. {
  376. return $this->render('page_generator/FAQ.html.twig');
  377. }
  378. /**
  379. * @Route("/privacy", name="privacy")
  380. */
  381. public function privacyAction(Request $request): Response
  382. {
  383. return $this->render('page_generator/privacy.html.twig');
  384. }
  385. /**
  386. * @Route("/return", name="return")
  387. */
  388. public function returnAction(Request $request): Response
  389. {
  390. return $this->render('page_generator/return.html.twig');
  391. }
  392. /**
  393. * @Route("/terms", name="terms")
  394. */
  395. public function termsAction(Request $request): Response
  396. {
  397. return $this->render('page_generator/terms.html.twig');
  398. }
  399. /**
  400. * @Route("/confirmationresend", name="confirmationresend", methods={"POST"})
  401. */
  402. public function resendVerificationTokenfromMailAction(Request $request): Response
  403. {
  404. if (!$this->isCsrfTokenValid('resend_verification_email', (string) $request->request->get('_token'))) {
  405. $this->addTranslatedFlash('error', 'Invalid security token.');
  406. return $this->redirectToRoute('sylius_shop_login');
  407. }
  408. $email = trim((string) $request->request->get('emailverif', ''));
  409. if ('' === $email || false === filter_var($email, FILTER_VALIDATE_EMAIL)) {
  410. // Neutral response to avoid user enumeration.
  411. $this->addTranslatedFlash('success', 'sylius.user.verify_email_request');
  412. return $this->redirectToRoute('sylius_shop_login');
  413. }
  414. $cooldownKey = 'resend_verification_last_' . sha1(strtolower($email) . '|' . (string) $request->getClientIp());
  415. $cooldownSeconds = 60;
  416. if ($request->hasSession()) {
  417. $lastAttemptAt = (int) $request->getSession()->get($cooldownKey, 0);
  418. if ($lastAttemptAt > 0 && (time() - $lastAttemptAt) < $cooldownSeconds) {
  419. $this->addTranslatedFlash('success', 'sylius.user.verify_email_request');
  420. return $this->redirectToRoute('sylius_shop_login');
  421. }
  422. $request->getSession()->set($cooldownKey, time());
  423. }
  424. $customer = $this->container->get('sylius.repository.customer')->findOneBy(['email' => $email]);
  425. $user = null !== $customer ? $customer->getUser() : null;
  426. if (null !== $user && null === $user->getVerifiedAt()) {
  427. $sender = $this->container->get('sylius.email_sender');
  428. $logpath = 'https://www.hephaestos.eu/build/img/baniere1024x768.png';
  429. $localeCode = $this->get('sylius.context.locale')->getLocaleCode();
  430. $channel = $this->container->get('sylius.context.channel')->getChannel();
  431. $sender->send(\Sylius\Bundle\UserBundle\Mailer\Emails::EMAIL_VERIFICATION_TOKEN, [$email], ['user' => $user, 'channel' => $channel, 'localeCode' => $localeCode, 'image_src' => $logpath]);
  432. }
  433. $this->addTranslatedFlash('success', 'sylius.user.verify_email_request');
  434. return $this->redirectToRoute('sylius_shop_login');
  435. }
  436. /**
  437. * {@inheritdoc}
  438. */
  439. protected function addTranslatedFlash(string $type, string $message): void
  440. {
  441. $translator = $this->container->get('translator');
  442. $this->container->get('session')->getFlashBag()->add($type, @$this->translator->trans($message, [], 'flashes'));
  443. }
  444. }