<?php
namespace App\Controller;
use App\Entity\Production;
use App\Entity\MobileAppPushToken;
use App\Entity\MobileAppTicketShare;
use App\Entity\User as EntityUser;
use App\Form\ProfileForm;
use App\Form\RegisterForm;
use App\Form\SetpassForm;
use App\Repository\MobileAppTicketShareRepository;
use App\Repository\ProductionRepository;
use App\Repository\MobileAppPushTokenRepository;
use App\Security\LocalUserAuthenticator;
use App\Security\TessituraGuardAuthenticator;
use App\Security\MobileAppGuardAuthenticator;
use App\Service\EecmService;
use App\Service\TessituraBundle\AccountService;
use App\Service\TessituraBundle\CartService;
use App\Service\TessituraBundle\CateringServiceException;
use App\Service\TessituraBundle\ConstituentService;
use App\Service\TessituraBundle\CountryService;
use App\Service\TessituraBundle\Item\AccountItemPerformance;
use App\Service\TessituraBundle\TessituraUserProvider;
use App\Service\TessituraCacheService;
use App\Service\TessituraSDK\Entity\Remote\Constituent;
use App\Service\TessituraSDK\Entity\Remote\Performance;
use App\Service\TessituraSDK\Entity\User;
use App\Service\TessituraSDK\Resource\TXN\Performances;
use App\Service\TessituraSDK\TessituraClient;
use App\Service\TessituraSDK\TessituraClientException;
use Jsvrcek\ICS\CalendarExport;
use Jsvrcek\ICS\CalendarStream;
use Jsvrcek\ICS\Model\Calendar;
use Jsvrcek\ICS\Model\CalendarEvent;
use Jsvrcek\ICS\Model\Description\Geo;
use Jsvrcek\ICS\Model\Description\Location;
use Jsvrcek\ICS\Utility\Formatter;
use Psr\Cache\CacheItemPoolInterface;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
use Symfony\Component\Routing\Annotation\Route;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Cache\Adapter\TagAwareAdapterInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Form\FormError;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Guard\GuardAuthenticatorHandler;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use App\Service\TessituraSDK\Resource\Custom;
use App\Util\FirestoreHelper;
use App\Util\TessituraHelper;
use Doctrine\ORM\EntityManagerInterface;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\ClientException;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
class UserController extends AbstractController
{
/**
* @var TessituraClient
*/
protected $client;
/**
* @var User
*/
protected $user;
/**
* @var CacheItemPoolInterface|TagAwareAdapterInterface
*/
protected $cache;
/**
* @var TranslatorInterface
*/
private $translator;
/**
* @var \Doctrine\ORM\EntityManager
*/
private $em;
/**
* @var LoggerInterface
*/
private $logger;
/**
* @var ParameterBagInterface
*/
private $params;
private $tessituraCacheService;
/**
* UserController constructor.
*/
public function __construct(
ContainerInterface $container,
TessituraClient $client,
TranslatorInterface $translator,
ManagerRegistry $doctrine,
LoggerInterface $logger,
ParameterBagInterface $params,
TessituraCacheService $tessituraCacheService,
) {
$this->setContainer($container);
$this->client = $client;
$this->translator = $translator;
$this->em = $doctrine->getManager();
$this->logger = $logger;
$this->params = $params;
$this->cache = $container->get('cache.app');
$this->tessituraCacheService = $tessituraCacheService;
}
/**
* @Route(
* "/{_locale}/register/",
* name="register",
* defaults={
* "_locale": "fi"
* },
* requirements={
* "_locale": "fi|sv|en"
* }
* )
* @param Request $request
* @param RouterInterface $router
* @param TessituraUserProvider $provider
* @param GuardAuthenticatorHandler $handler
* @param CartService $cart
* @param TessituraGuardAuthenticator $authenticator
* @param ConstituentService $constituentService
*
* @return Response
* @throws TessituraClientException * @internal param ConstituentRepository $repository
*/
public function registerAction(
Request $request,
RouterInterface $router,
TessituraUserProvider $provider,
GuardAuthenticatorHandler $handler,
CartService $cart,
// TessituraGuardAuthenticator $authenticator,
ConstituentService $constituentService,
Security $security,
LocalUserAuthenticator $localUserAuthenticator,
UserPasswordHasherInterface $passwordHasher,
) {
/* @var User $user */
$user = $security->getUser();
$tessituraSession = TessituraHelper::getSession(
$request->getSession(),
$provider,
true
);
/**
* Get RegisterForm
*/
$form = $this->createForm(RegisterForm::class, [
// Prefilled values?
]);
/**
* Handle request
*/
$form->handleRequest($request);
if ($form->isSubmitted() && !$form->isValid()) {
$form->addError(new FormError($this->translator->trans("form_general_error")));
} elseif ($form->isSubmitted() && $form->isValid()) {
try {
$data = $form->getData();
if (array_key_exists('source', $_GET)) {
$data['source'] = $_GET['source'];
}
/**
* Try to use the mobile_full created by intlTelInput.js
* This value should contain a valid phone number with a country code
*/
$data['mobile'] = !empty($data['mobile_full']) ? $data['mobile_full'] : $data['mobile'];
$data['mobile_national'] = !empty($data['mobile_national'])
? $data['mobile_national']
: $data['mobile'];
$data['mobile_isocode'] = array_key_exists('mobile_isocode', $data) && !empty($data['mobile_isocode'])
? $data['mobile_isocode']
: 'fi'; // default: finland
$constituent = $constituentService->registerWithSessionKey($tessituraSession, $data);
$user = new \App\Entity\User();
$user->setEmail($constituent->getEmail());
$user->setPassword($passwordHasher->hashPassword($user, $data['password']));
$user->setConstituentId($constituent->getId());
$user->setPhoneNumber($constituent->getPhoneNumbers()[0]->getPhoneNumber());
$user->setPostalCode($constituent->getAddresses()[0]->getPostalCode());
$user->setCreatedAt(new \DateTime());
$user->setUpdatedAt(new \DateTime());
$this->em->persist($user);
$this->em->flush();
/**
* Load new user and login
*/
// login the local user
$handler->authenticateUserAndHandleSuccess(
$user, // Use the newly created local user
$request,
$localUserAuthenticator, // Use LocalUserAuthenticator
'main'
);
/*
$handler->authenticateUserAndHandleSuccess(
$provider->loadUserBySessionKey($tessituraSession),
$request,
$authenticator,
'main' // The firewall
);
*/
if ($cart->getItemCount()) {
$url = $router->generate('checkout');
return new RedirectResponse($url);
} else {
$url = $router->generate('account-me');
return new RedirectResponse($url);
}
} catch (TessituraClientException $exception) {
switch ($exception->getCode()) {
case TessituraClientException::TESSITURA_DUPLICATE_ENTITY:
$form->addError(new FormError($this->translator->trans("user_duplicate_entity")));
break;
default:
throw $exception;
}
}
}
/**
* Render
*/
return $this->render('page/register.html.twig', [
'form' => $form->createView()
]);
}
/**
* @Route(
* "/{_locale}/api/v1/me/",
* name="get-api-me",
* defaults={
* "_locale": "fi"
* },
* requirements={
* "_locale": "fi|sv|en"
* }
* )
* @param Request $request
* @param CartService $cart
* @param ConstituentService $constituentService
* @param Security $security
*
* @return JsonResponse
*/
public function getMeAction(
Request $request,
CartService $cart,
ConstituentService $constituentService,
Security $security,
TessituraUserProvider $userProvider,
TessituraCacheService $tessituraCacheService,
): JsonResponse {
$data = [];
$user = $security->getUser();
if (!$user) {
return new JsonResponse([
'success' => true,
'authenticated' => false,
'message' => 'Not logged in',
]);
}
$item = $tessituraCacheService->get('account_profile_' . $user->getId());
try {
$redirect_to = $request->get('redirect_to');
$login_link_params = !empty($redirect_to) ? ['redirect_to' => rawurlencode($redirect_to)] : [];
$profile_login_url = $request->getSchemeAndHttpHost()
. $this->generateUrl('login', $login_link_params);
$cartView = $this->render('partial/cart-menu-partial.html.twig');
$profileView = $this->render('partial/profile-menu-partial.html.twig', [
'login_link' => $profile_login_url,
]);
$tessituraSession = TessituraHelper::getSession($request->getSession(), $userProvider, true);
if (!$user) {
$data = [
'authenticated' => -1,
'cart' => [
'count' => 0,
'diff' => 0,
'view' => $cartView->getContent(),
'url' => $profile_login_url,
],
'profile' => [
'view' => $profileView->getContent(),
'url' => $profile_login_url,
'initials' => '',
],
'sessionKey' => $tessituraSession,
];
} else {
$tessituraUser = TessituraHelper::loginTessituraUser($user, $request->getSession(), $userProvider);
$constituent = $constituentService->getConstituent($tessituraUser->getConstituentId());
$data = [
'authenticated' => 1,
'cart' => [
'count' => $cart->getItemCount(),
'diff' => $tessituraUser->getExpirationDiff()->i * 60 + $tessituraUser->getExpirationDiff()->s,
'view' => $cartView->getContent(),
'url' => $request->getSchemeAndHttpHost() . $this->generateUrl('cart'),
],
'profile' => [
'view' => $profileView->getContent(),
'url' => $request->getSchemeAndHttpHost() . $this->generateUrl('account-me'),
'initials' => mb_substr($constituent->getFirstName(), 0, 1)
. mb_substr($constituent->getLastName(), 0, 1),
],
'sessionKey' => $tessituraSession,
];
}
return new JsonResponse(array_merge([
'success' => true,
], $data));
} catch (\Exception $exception) {
error_log("getMeAction error: " . print_r($exception->getMessage(), true));
}
return new JsonResponse(array_merge([
'success' => false
], $data));
}
private function getPushTokens(array $constituentIds)
{
/** @var MobileAppPushTokenRepository $repository */
$repository = $this->em->getRepository(MobileAppPushToken::class);
return $repository->findByConstituents($constituentIds);
}
/**
* @Route(
* "/api/v1/authenticated/",
* name="post-authenticated",
* methods={"POST"}
* )
* @param Request $request
* @param TessituraUserProvider $provider
*
* @return Response
*/
public function userIsAuthenticated(Request $request, TessituraUserProvider $provider, Security $security)
{
$data = [];
$user = $security->getUser();
if ($user) {
// this is a local user
$this->logger->debug('API isAuthenticated query success for local user', [
'user' => $user,
]);
return new JsonResponse(array_merge([
'success' => true,
'authenticated' => true
], $data));
}
$requestData = $request->toArray();
$sessionKey = $requestData['sessionKey'];
if (!$sessionKey) {
return new JsonResponse(array_merge([
'success' => false,
'authenticated' => false
], $data));
}
try {
$user = $provider->loadUserBySessionKey($sessionKey);
if ($user->isPseudoFlag()) {
$data = [
'authenticated' => false,
];
} else {
$data = [
'authenticated' => in_array('ROLE_TESSITURA_KNOWN', $user->getRoles()),
];
}
return new JsonResponse(array_merge([
'success' => true
], $data));
} catch (\Exception $exception) {
}
return new JsonResponse(array_merge([
'success' => false,
'authenticated' => false,
], $data));
}
/**
* @Route(
* "/{_locale}/api/v1/user-data/add-favorite/",
* name="post-add-favorite",
* methods={"POST"},
* defaults={
* "_locale": "fi"
* },
* requirements={
* "_locale": "fi|sv|en"
* }
* )
* @param Request $request
*
* @return Response
*/
public function addUserFavorite(
Request $request,
Security $security,
) {
$data = [];
$requestData = $request->toArray();
$favorite = $requestData['favorite'] ?? false;
if (!$favorite) {
return new JsonResponse(array_merge([
'success' => false,
'message' => 'No favorite'
], $data), 500);
}
/** @var TokenInterface $token */
$token = $security->getToken();
if (!$token) {
return new JsonResponse(array_merge([
'success' => false,
'message' => 'No token'
], $data), 500);
}
/** @var User $user */
$user = $token->getUser();
try {
$success = false;
if ($user->isPseudoFlag()) {
$data = [
'authenticated' => false,
'message' => 'Not logged in'
];
return new JsonResponse(array_merge([
'success' => $success
], $data), 403);
} else {
$authenticated = in_array('ROLE_TESSITURA_KNOWN', $user->getRoles());
$status = $authenticated ? 200 : 403;
if ($authenticated) {
$client = new Client();
$environment = $this->params->get('wp.cookie_env');
$payload = [
'json' => [
'user' => md5($user->getConstituentId()),
'environment' => $environment,
'favorite' => (int)$favorite
]
];
try {
$response = $client->post(
$this->params->get('firestore.api.add_favorite'),
$payload
);
$responseStatus = $response->getStatusCode();
$responseBody = \GuzzleHttp\json_decode($response->getBody()->getContents(), true);
$success = true;
} catch (\Exception $exception) {
error_log(print_r($exception->getMessage(), true));
$responseStatus = 500;
$status = 500;
$responseBody = [];
$success = false;
$data = [
'message' => 'Failed to add favorite'
];
}
if ($responseStatus === 200 && $responseBody['success']) {
$data = [
'user' => $responseBody['user'],
'message' => $responseBody['message']
];
}
$data['authenticated'] = $authenticated;
$cid = $user->getConstituentId();
$cacheKeys = [
'tvapp-usercontent-fi-' . $cid,
'tvapp-usercontent-sv-' . $cid,
'tvapp-usercontent-en-' . $cid,
];
$this->cache->invalidateTags($cacheKeys);
return new JsonResponse(array_merge([
'success' => $success
], $data), $status);
}
}
} catch (\Exception $exception) {
}
return new JsonResponse(array_merge([
'success' => false,
'authenticated' => false,
'message' => 'Not logged in (error)',
], $data), 403);
}
/**
* @Route(
* "/{_locale}/api/v1/user-data/remove-favorite/",
* name="post-remove-favorite",
* methods={"POST"},
* defaults={
* "_locale": "fi"
* },
* requirements={
* "_locale": "fi|sv|en"
* }
* )
* @param Request $request
*
* @return Response
*/
public function removeUserFavorite(
Request $request,
Security $security,
): JsonResponse {
$data = [];
$requestData = $request->toArray();
$favorite = $requestData['favorite'] ?? false;
if (!$favorite) {
return new JsonResponse(array_merge([
'success' => false,
'message' => 'No favorite'
], $data), 500);
}
$token = $security->getToken();
if (!$token) {
return new JsonResponse(array_merge([
'success' => false,
'message' => 'No token'
], $data), 500);
}
/** @var User $user */
$user = $token->getUser();
try {
$success = false;
if ($user->isPseudoFlag()) {
$data = [
'authenticated' => false,
'message' => 'Not logged in'
];
return new JsonResponse(array_merge([
'success' => $success
], $data), 403);
} else {
$authenticated = in_array('ROLE_TESSITURA_KNOWN', $user->getRoles());
$status = $authenticated ? 200 : 403;
if ($authenticated) {
$client = new Client();
$environment = $this->params->get('wp.cookie_env');
$payload = [
'json' => [
'user' => md5($user->getConstituentId()),
'environment' => $environment,
'favorite' => (int)$favorite
]
];
try {
$response = $client->delete(
$this->params->get('firestore.api.remove_favorite'),
$payload
);
$responseStatus = $response->getStatusCode();
$responseBody = \GuzzleHttp\json_decode($response->getBody()->getContents(), true);
$success = true;
} catch (\Exception $exception) {
error_log(print_r($exception->getMessage(), true));
$responseStatus = 500;
$status = 500;
$responseBody = [];
$success = false;
$data = [
'message' => 'Failed to remove favorite'
];
}
if ($responseStatus === 200 && $responseBody['success']) {
$data = [
'user' => $responseBody['user'],
'message' => $responseBody['message']
];
}
$data['authenticated'] = $authenticated;
$cid = $user->getConstituentId();
$cacheKeys = [
'tvapp-usercontent-fi-' . $cid,
'tvapp-usercontent-sv-' . $cid,
'tvapp-usercontent-en-' . $cid,
];
$this->cache->invalidateTags($cacheKeys);
return new JsonResponse(array_merge([
'success' => $success
], $data), $status);
}
}
} catch (\Exception $exception) {
}
return new JsonResponse(array_merge([
'success' => false,
'authenticated' => false,
'message' => 'Not logged in (error)',
], $data), 403);
}
/**
* @Route(
* "/{_locale}/api/v1/user-data/update-watch-time/",
* name="post-update-watch-time",
* methods={"POST"},
* defaults={
* "_locale": "fi"
* },
* requirements={
* "_locale": "fi|sv|en"
* }
* )
* @param Request $request
*
* @return Response
*/
public function updateWatchTime(
Request $request,
Security $security,
) {
$data = [];
$requestData = $request->toArray();
$video = $requestData['video'] ?? false;
$progress = $requestData['progress'] ?? false;
$progressPercent = $requestData['progressPercent'] ?? false;
if (!$progress || !$progressPercent || !$video) {
return new JsonResponse(array_merge([
'success' => false,
'message' => 'Bad request'
], $data), 400);
}
$token = $security->getToken();
if (!$token) {
return new JsonResponse(array_merge([
'success' => false,
'message' => 'No token'
], $data), 500);
}
/** @var User $user */
$user = $token->getUser();
try {
$success = false;
if ($user->isPseudoFlag()) {
$data = [
'authenticated' => false,
'message' => 'Not logged in'
];
return new JsonResponse(array_merge([
'success' => $success
], $data), 403);
} else {
$authenticated = in_array('ROLE_TESSITURA_KNOWN', $user->getRoles());
$status = $authenticated ? 200 : 403;
if ($authenticated) {
$client = new Client();
$environment = $this->params->get('wp.cookie_env');
$payload = [
'json' => [
'user' => md5($user->getConstituentId()),
'environment' => $environment,
'video' => (int)$video,
'progress' => (int)$progress,
'progressPercent' => (int)$progressPercent
]
];
try {
$response = $client->post(
$this->params->get('firestore.api.update_watch_time'),
$payload
);
$responseStatus = $response->getStatusCode();
$responseBody = \GuzzleHttp\json_decode($response->getBody()->getContents(), true);
$success = true;
} catch (\Exception $exception) {
error_log(print_r($exception->getMessage(), true));
$responseStatus = 500;
$status = 500;
$responseBody = [];
$success = false;
$data = [
'message' => 'Failed to update watch time'
];
}
if ($responseStatus === 200 && $responseBody['success']) {
$data = [
'user' => $responseBody['user'],
'message' => $responseBody['message']
];
}
$data['authenticated'] = $authenticated;
return new JsonResponse(array_merge([
'success' => $success
], $data), $status);
}
}
} catch (\Exception $exception) {
}
return new JsonResponse(array_merge([
'success' => false,
'authenticated' => false,
'message' => 'Not logged in (error)',
], $data), 403);
}
/**
* @Route(
* "/{_locale}/api/v1/user-data/",
* name="get-user-data",
* methods={"GET"},
* defaults={
* "_locale": "fi"
* },
* requirements={
* "_locale": "fi|sv|en"
* }
* )
* @param Request $request
* @param Security $security
* @param AccountService $accountService
* @param TessituraUserProvider $provider
* @param MobileAppGuardAuthenticator $guardAuthenticator
*
* @return JsonResponse
*/
public function getUserData(
Request $request,
Security $security,
AccountService $accountService,
TessituraUserProvider $provider,
MobileAppGuardAuthenticator $guardAuthenticator
): JsonResponse {
$data = [];
$session = $request->getSession();
if ($request->headers->get('Authorization')) {
// authorization: bearer token
try {
$user = $guardAuthenticator->getUser($request->headers->get('Authorization'), $provider);
} catch (\Exception $exception) {
$statusCode = $exception->getMessage() === 'Token expired' ? 401 : 500;
return new JsonResponse(array_merge([
'success' => false,
'message' => $exception->getMessage(),
], $data), $statusCode);
}
} else {
$user = $security->getUser();
}
if (!$user) {
return new JsonResponse(array_merge([
'success' => false,
'message' => 'No token'
], $data), 500);
}
$fh = new FirestoreHelper($this->params, $user->getConstituentId());
$firestoredata = $fh->getUserData();
if (!$firestoredata) {
return new JsonResponse(array_merge([
'success' => false,
'message' => 'Failed to get user data'
], $data), 500);
}
$data = [
'user' => $firestoredata,
'authenticated' => true,
];
$streams = $this->tessituraCacheService->get('account_streams_' . $user->getId());
if (!$streams) {
$tessituraUser = TessituraHelper::loginTessituraUser($user, $session, $provider);
$streams = array_merge([], $accountService->getPurchasedFeeProducts($tessituraUser->getSessionKey()));
$this->tessituraCacheService->put('account_streams_' . $user->getId(), $streams);
}
// user is authenticated, so return 200 even if firebase user can't be found
return new JsonResponse(array_merge([
'success' => true,
'streams' => $streams,
], $data), 200);
return new JsonResponse(array_merge([
'success' => true,
'authenticated' => false,
'message' => 'Not logged (error)',
], $data), 403);
}
/**
* @Route(
* "/{_locale}/account/profile/",
* name="account-profile",
* defaults={
* "_locale": "fi"
* },
* requirements={
* "_locale": "fi|sv|en"
* }
* )
* @param Request $request
* @param ConstituentService $constituentService
*
* @return Response
* @throws TessituraClientException
* @internal param ConstituentRepository $repository
*/
public function profileAction(
Request $request,
ConstituentService $constituentService,
Security $security,
CountryService $countryService,
TessituraCacheService $tessituraCacheService,
EntityManagerInterface $entityManager,
) {
/** @var \App\Entity\User $user */
$user = $security->getUser();
$item = $tessituraCacheService->get('account_profile_' . $user->getId());
if ($item) {
$formData = json_decode($item, true);
} else {
$constituent = $constituentService->getConstituent();
$defaultPhoneCountryCode = $countryService->idToCode(
$constituent->getDefaultPhoneNumber()->getCountry()->getId()
);
$formData = [
'firstname' => $constituent->getFirstName(),
'lastname' => $constituent->getLastName(),
'email' => $constituent->getEmail(),
'street1' => $constituent->getStreet1(),
'postalcode' => $constituent->getPostalCode(),
'city' => $constituent->getCity(),
'mobile' => $constituent->getMobile(),
'mobile_full' => $constituent->getMobileFull(),
'mobile_isocode' => $defaultPhoneCountryCode,
'opt_in' => $constituent->getOptIn(),
'opt_in_kv' => $constituent->getOptInInterest(),
];
/**
* Check if user has an temporary address and clear these
*/
if ($constituent->getStreet1() === $this->params->get('tessitura.subscribe.default_street1')) {
unset($formData['street1']);
unset($formData['postalcode']);
unset($formData['city']);
unset($formData['mobile']);
unset($formData['mobile_full']);
}
$tessituraCacheService->put('account_profile_' . $user->getId(), json_encode($formData));
}
$form = $this->createForm(ProfileForm::class, $formData);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$tessituraCacheService->delete('account_profile_' . $user->getId());
try {
/** @var array $data */
$data = $form->getData();
$data['mobile'] = !empty($data['mobile_full'])
? $data['mobile_full']
: $data['mobile'];
$data['mobile_national'] = !empty($data['mobile_national'])
? $data['mobile_national']
: $data['mobile'];
$constituentService->update($data);
$user->setEmail($data['email']);
$user->setPhoneNumber($data['mobile']);
$user->setPostalCode($data['postalcode']);
$entityManager->persist($user);
$entityManager->flush();
$this->logger->info('User updated', [
'user' => $user,
'data' => $data
]);
$form->addError(new FormError($this->translator->trans("user_updated")));
} catch (TessituraClientException $exception) {
switch ($exception->getCode()) {
case TessituraClientException::TESSITURA_INVALID_EMAIL_ADDRESS:
$form->addError(new FormError($this->translator->trans("user_invalid_email")));
break;
case TessituraClientException::TESSITURA_INVALID_CREDENTIALS:
$form->addError(new FormError($this->translator->trans("user_invalid_password")));
break;
case TessituraClientException::TESSITURA_DUPLICATE_ENTITY:
$form->addError(new FormError($this->translator->trans("user_duplicate_entity")));
break;
default:
throw $exception;
}
}
}
return $this->render('page/account-profile.html.twig', [
'form' => $form->createView(),
]);
}
/**
* @Route(
* "/{_locale}/account/staff-tickets/",
* name="staff-tickets",
* defaults={
* "_locale": "fi"
* },
* requirements={
* "_locale": "fi|sv|en"
* }
* )
* @param ConstituentService $constituentService
*
* @return Response
* @throws TessituraClientException
* @internal param ConstituentRepository $repository
*/
public function staffTicketsAction(ConstituentService $constituentService)
{
// Not in use since season 24/25 but these might come back at some point
// causes errors in DEV as the procedure isn't available so commented out for now
//
// $constituent = $constituentService->getConstituent();
// $customRequest = Custom::getAll("StaffTicketCodes?customer_no=" . $constituent->getId());
// try {
// $response = $this->client->request($customRequest);
// $staffTickets = [];
// $now = new \DateTime('now');
// if (!empty((array)$response->toArray())) {
// foreach ((array)$response->toArray() as $row) {
// $endTime = new \DateTime();
// $endTime->modify($row['end_dt']);
// if ($now->getTimestamp() <= $endTime->getTimestamp()) {
// $staffTickets[] = [
// 'code' => $row['code'],
// 'active' => is_null($row['sli_no']),
// 'expires' => $endTime->format('d.m.Y')
// ];
// }
// }
// // Show active ones first
// usort($staffTickets, function($a, $b) {
// return $b['active'] <=> $a['active'];
// });
// }
// } catch (TessituraClientException $exception) {
// $staffTickets = [];
// }
return $this->render('page/account-staff-tickets.html.twig', [
'tickets' => [],
]);
}
/**
* @Route(
* "/{_locale}/account/subscribers/",
* name="subscribers",
* defaults={
* "_locale": "fi"
* },
* requirements={
* "_locale": "fi|sv|en"
* }
* )
* @param ConstituentService $constituentService
*
* @return Response
* @throws TessituraClientException
* @internal param ConstituentRepository $repository
*/
public function subscribersAction(ConstituentService $constituentService)
{
return $this->render('page/account-subscribers.html.twig', [
]);
}
/**
* @Route(
* "/{_locale}/login/profile/",
* name="login-profile",
* defaults={
* "_locale": "fi"
* },
* requirements={
* "_locale": "fi|sv|en"
* }
* )
* @param Request $request
* @param ConstituentService $constituentService
* @param CartService $cartService
*
* @return Response
* @throws TessituraClientException * @internal param ConstituentRepository $repository
*/
public function loginProfileAction(
Request $request,
ConstituentService $constituentService,
CartService $cartService,
Security $security,
): Response {
/* why? */
/* $user = $security->getUser(); */
$constituent = $constituentService->getConstituent();
$formData = [
'firstname' => $constituent->getFirstName(),
'lastname' => $constituent->getLastName(),
'email' => $constituent->getEmail(),
'street1' => $constituent->getStreet1(),
'postalcode' => $constituent->getPostalCode(),
'city' => $constituent->getCity(),
'mobile' => $constituent->getMobile(),
'mobile_full' => $constituent->getMobileFull(),
'opt_in' => $constituent->getOptIn(),
'opt_in_kv' => $constituent->getOptInInterest(),
];
/**
* Check if user has an temporary address and clear these
*/
if ($constituent->getStreet1() === $this->params->get('tessitura.subscribe.default_street1')) {
unset($formData['street1']);
unset($formData['postalcode']);
unset($formData['postalcode']);
unset($formData['city']);
unset($formData['mobile']);
unset($formData['mobile_full']);
}
$form = $this->createForm(ProfileForm::class, $formData);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
try {
/** @var array $data */
$data = $form->getData();
$data['mobile'] = !empty($data['mobile_full']) ? $data['mobile_full'] : $data['mobile'];
$data['mobile_national'] = !empty($data['mobile_national'])
? $data['mobile_national']
: $data['mobile'];
$constituentService->update($data);
if ($cartService->getItemCount()) {
return $this->redirectToRoute('checkout');
} else {
return $this->redirectToRoute('account-me');
}
} catch (TessituraClientException $exception) {
switch ($exception->getCode()) {
case TessituraClientException::TESSITURA_INVALID_EMAIL_ADDRESS:
$form->addError(new FormError($this->translator->trans("user_invalid_email")));
break;
case TessituraClientException::TESSITURA_INVALID_CREDENTIALS:
$form->addError(new FormError($this->translator->trans("user_invalid_password")));
break;
case TessituraClientException::TESSITURA_DUPLICATE_ENTITY:
$form->addError(new FormError($this->translator->trans("user_duplicate_entity")));
break;
default:
throw $exception;
}
}
}
return $this->render('page/login-profile.html.twig', [
'form' => $form->createView(),
]);
}
/**
* @Route(
* "/{_locale}/account/password/",
* name="account-password",
* defaults={
* "_locale": "fi"
* },
* requirements={
* "_locale": "fi|sv|en"
* }
* )
* @param Request $request
* @param ConstituentService $constituentService
* @param CartService $cartService
*
* @return Response
* @throws TessituraClientException
*/
public function setPasswordAction(
Request $request,
ConstituentService $constituentService,
CartService $cartService,
Security $security,
TessituraUserProvider $tessituraUserProvider,
UserPasswordHasherInterface $passwordHasher,
EntityManagerInterface $entityManager,
): Response {
/* @var User $user */
$user = $security->getUser();
$tessituraUser = TessituraHelper::loginTessituraUser(
$user,
$request->getSession(),
$tessituraUserProvider
);
$constituent = clone $constituentService->getConstituent();
$defaultWebLogin = $constituent->getDefaultWebLogin();
$email = $defaultWebLogin->getLogin();
$form = $this->createForm(SetpassForm::class, $constituent, [
'ask_current_password' => !$constituent->getIsTemporary()
]);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
try {
/** @var Constituent $data */
$data = $form->getData();
$constituentService->savePassword(
$constituent->getId(),
$tessituraUser,
$data,
$constituentService->getConstituent(),
$email
);
$user->setPassword($passwordHasher->hashPassword($user, $data->getPassword()));
$entityManager->persist($user);
$entityManager->flush();
$form->addError(new FormError($this->translator->trans("user_updated")));
if ($cartService->getItemCount()) {
return $this->redirectToRoute('checkout');
}
if ($constituentService->isInvalid()) {
return $this->redirectToRoute('login-profile');
}
$url = $this->generateUrl('account-me', ['passwd' => 'ok']);
return new RedirectResponse($url);
} catch (TessituraClientException $exception) {
switch ($exception->getCode()) {
case TessituraClientException::TESSITURA_INVALID_CREDENTIALS:
$form->addError(new FormError($this->translator->trans("user_invalid_password")));
break;
default:
throw $exception;
}
}
}
return $this->render('page/account-password.html.twig', [
'form' => $form->createView(),
]);
}
/**
* @Route(
* "/{_locale}/account/order/{orderId}/",
* name="order",
* defaults={
* "_locale": "fi"
* },
* requirements={
* "_locale": "fi|sv|en"
* }
* )
* @param Request $request
* @param AccountService $accountService
* @param $orderId
*
* @return \Symfony\Component\HttpFoundation\Response
*/
public function showOrderAction(
// Request $request,
AccountService $accountService,
EecmService $eecmService,
Security $security,
$orderId,
) {
$user = $security->getUser();
/* Why? */
/* $session = $request->getSession(); */
try {
$order = $accountService->getOrder((int)$orderId);
// Bail if this is not users orders
if ($order->getConstituent()->getId() !== $user->getConstituentId()) {
throw $this->createNotFoundException();
}
} catch (TessituraClientException $exception) {
throw $this->createNotFoundException();
}
$products = $accountService->getProducts($order);
try {
$productsFormatted = $eecmService->formatProducts($products);
$eecmDataLayer = [
'transaction_id' => $order->getId(),
'value' => $accountService->getTotal($order),
'tax' => array_reduce(
$accountService->getVatAmounts($order), // TODO: returns null?
function ($carry, $item) {
$carry += $item;
return $carry;
}
),
'shipping' => 0,
'currency' => 'EUR',
//'coupon' => '', // TODO: how to get?
'items' => $productsFormatted,
];
} catch (\Exception $exception) {
$eecmDataLayer = [];
error_log($exception->getMessage());
$this->logger->warning('Error generating purchase data:' . $exception->getMessage());
}
return $this->render('page/complete.html.twig', [
'order' => $order,
'datalayer' => [
'event' => 'purchase',
'ecommerce' => $eecmDataLayer,
],
]);
}
/**
* @Route(
* "/{_locale}/account/gifts/",
* name="account-gifts",
* defaults={
* "_locale": "fi"
* },
* requirements={
* "_locale": "fi|sv|en"
* }
* )
* @param Request $request
*
* @return Response
* @internal param ConstituentRepository $repository
*/
public function giftsAction(Request $request)
{
return $this->render('page/account-purchases.html.twig', [
]);
}
/**
* @Route(
* "/{_locale}/account/faq/",
* name="account-faq",
* defaults={
* "_locale": "fi"
* },
* requirements={
* "_locale": "fi|sv|en"
* }
* )
* @param Request $request
*
* @return Response
* @internal param ConstituentRepository $repository
*/
public function faqAction(Request $request)
{
return $this->render('page/account-purchases.html.twig', [
]);
}
/**
* @Route(
* "/{_locale}/account/api/v1/ics/",
* name="get-api-ics",
* defaults={
* "_locale": "fi"
* },
* requirements={
* "_locale": "fi|sv|en"
* },
* methods={"GET"}
* )
* @param Request $request
*
* @return \Symfony\Component\HttpFoundation\Response
* @throws TessituraClientException
*/
public function downloadICSAction(Request $request)
{
$performanceId = $request->get('performance');
if (!$performanceId) {
throw new \InvalidArgumentException();
}
/** @var ProductionRepository $productionRepository */
$productionRepository = $this->em->getRepository(Production::class);
/** @var Performance $performance */
$performance = $this->client->request(Performances::get($performanceId))->denormalize(Performance::class);
/** @var Production $production */
$production = $productionRepository->getProduction($performance->getId());
if (!$production->getId()) {
$production->setTitle($performance->getDescription());
}
$duration = $performance->getDuration();
$duration = (int)$duration ? (int)$duration : 3 * 60;
$start = new \DateTime();
$start->setTimestamp($performance->getDate()->getTimestamp());
$end = new \DateTime();
$end->setTimestamp($performance->getDate()->getTimestamp());
$end->add(new \DateInterval('PT' . $duration . 'M'));
$location = new Location();
$location->setName('Suomen kansallisooppera ja -baletti\, Helsinginkatu 58\, 00251 Helsinki\, Finland');
$location->setUri('https://oopperabaletti.fi');
$geo = new Geo();
$geo->setLatitude(60.181608);
$geo->setLongitude(24.929601);
$calendarEvent = new CalendarEvent();
$calendarEvent->setGeo($geo);
$calendarEvent->setUid(md5($performance->getCode()));
$calendarEvent->setLocations([
$location
]);
$calendarEvent->setStart($start);
$calendarEvent->setSummary($production->getTitle());
$calendarEvent->setEnd($end);
$calendarEvent->setDescription($production->getStage());
$calendar = new Calendar();
$calendar->setMethod('REQUEST');
$calendar->setTimezone(new \DateTimeZone('Europe/Helsinki'));
$calendar->setProdId('-//FNOB//Ticketshop');
$calendar->addEvent($calendarEvent);
$calendarExport = new CalendarExport(new CalendarStream(), new Formatter());
$calendarExport->addCalendar($calendar);
$ics = $calendarExport->getStream();
$ics = preg_replace('/^(DTSTART|DTEND)(.*)$/m', '$1;TZID=Europe/Helsinki$2', $ics);
$response = new Response($ics);
$response->headers->set('Content-Type', 'text/calendar');
return $response;
}
/**
* @Route(
* "/{_locale}/account/api/v1/print/",
* name="post-api-print",
* defaults={
* "_locale": "fi"
* },
* requirements={
* "_locale": "fi|sv|en"
* },
* methods={"POST"}
* )
* @param Request $request
* @param AccountService $accountService
* @param ConstituentService $constituentService
*
* @return Response
*/
public function printTicketAction(
Request $request,
AccountService $accountService,
ConstituentService $constituentService,
) {
$primaryEmail = $constituentService->getConstituent()->getDefaultElectronicAddress();
try {
$accountService->reprintTickets(
$request->get('sublineItem'),
$primaryEmail->getId()
);
} catch (TessituraClientException $exception) {
switch ($exception->getCode()) {
case TessituraClientException::TESSITURA_PENDING_PAH_REQUEST:
$message = $this->translator->trans("upcoming_order_ticket_pending_error");
break;
default:
$message = $this->translator->trans("upcoming_order_ticket_error");
break;
}
return new JsonResponse([
'success' => false,
'message' => $message,
'code' => $exception->getCode()
]);
}
return new JsonResponse([
'success' => true,
'message' => $this->translator->trans("upcoming_order_ticket_message") . ' ' . $primaryEmail->getAddress()
]);
}
/**
* @Route(
* "/{_locale}/account/api/v1/show/{subLineId}",
* name="get-api-show",
* defaults={
* "_locale": "fi"
* },
* requirements={
* "_locale": "fi|sv|en",
* "subLineId": "\d+",
* },
* )
* @param Request $request
* @param $subLineId
* @param AccountService $accountService
* @param SerializerInterface $serializer
* @return JsonResponse
*/
public function getShowTicket(
$subLineId,
AccountService $accountService,
SerializerInterface $serializer,
): JsonResponse {
/*
/** @var AccountItemPerformance[] $performances */
try {
$barcode = $accountService->getTicketBarcode($subLineId);
} catch (TessituraClientException $exception) {
throw new NotFoundHttpException('No Barcode found');
}
if (empty($barcode)) {
throw new NotFoundHttpException('No Barcode found');
}
$entityManager = $this->getDoctrine()->getManager();
/** @var MobileAppTicketShareRepository $repository */
$repository = $entityManager->getRepository(MobileAppTicketShare::class);
$shared = $repository->findBySublineId($subLineId);
$sharedData = $serializer->normalize($shared, null, ['groups' => ['json']]);
$barcode = array_merge([
'shared_to' => $sharedData
], $barcode);
return new JsonResponse($barcode);
}
/**
* @Route(
* "/{_locale}/account/api/v1/return/",
* name="post-api-return",
* defaults={
* "_locale": "fi"
* },
* requirements={
* "_locale": "fi|sv|en"
* },
* methods={"POST"}
* )
* @param Request $request
* @param AccountService $accountService
*
* @return Response
*/
public function returnTicketAction(
Request $request,
AccountService $accountService,
// Security $security,
) {
/* Why? */
/* $user = $security->getUser(); */
$returnMessage = $this->translator->trans("upcoming_return_done_message");
try {
$accountService->removeTicket(
$request->get('sublineItem')
);
//return new Response('<html><head></head><body>XXX</body></html>');
} catch (\Exception $exception) {
$this->logger->error("Exception while returning ticket (follows):");
$this->logger->error(print_r($exception, true));
if ($exception instanceof TessituraClientException) {
$this->logger->error('Return: Exception while returning', [$exception]);
$this->cache->invalidateTags([$accountService->getCacheKey()]);
$returnMessage = $this->translator->trans("upcoming_return_error");
return new JsonResponse([
'success' => false,
'message' => $returnMessage,
'code' => $exception->getCode()
]);
} elseif (
$exception instanceof CateringServiceException &&
$exception->getCode() === CateringServiceException::TABLE_ALL_REMOVED
) {
$returnMessage = $this->translator->trans("upcoming_return_done_all_catering_removed_message");
} elseif (
$exception instanceof CateringServiceException &&
$exception->getCode() === CateringServiceException::ITEMS_ALL_REMOVED
) {
$returnMessage = $this->translator->trans("upcoming_return_done_all_catering_bookings_removed_message");
} else {
$this->logger->error("Unknown exception type?");
$this->cache->invalidateTags([$accountService->getCacheKey()]);
throw $exception;
}
}
if ($request->get('context') === 'me') {
$twigResponse = $this->meAction($request);
} elseif ($request->get('context') === 'history') {
$twigResponse = $this->purchasesAction($request);
} else {
$twigResponse = $this->upcomingAction($request);
}
return new JsonResponse([
'success' => true,
'message' => $returnMessage,
'html' => $twigResponse->getContent(),
]);
}
/**
* @Route(
* "/{_locale}/account/api/v1/load-order/",
* name="post-api-load-order",
* defaults={
* "_locale": "fi"
* },
* requirements={
* "_locale": "fi|sv|en"
* },
* methods={"POST"}
* )
* @param Request $request
* @param CartService $cartService
* @param RouterInterface $router
*
* @return Response
*/
public function loadOrderAction(
Request $request,
CartService $cartService,
RouterInterface $router,
$recursion = false
): JsonResponse {
$url = $router->generate('cart');
try {
$cartService->loadOrder($request->get('orderId'));
} catch (TessituraClientException $exception) {
switch ($exception->getCode()) {
case TessituraClientException::TESSITURA_ORDER_LOAD_CART_NOT_EMPTY:
if (!$recursion) {
try {
$cartService->emptyCart();
return $this->loadOrderAction($request, $cartService, $router, true);
} catch (TessituraClientException $exception) {
}
}
$message = $this->translator->trans("order_open_cart_error");
break;
case TessituraClientException::TESSITURA_ORDER_LOCKED:
$message = $this->translator->trans("order_open_locked_error");
break;
default:
$message = $this->translator->trans("order_open_general_error");
break;
}
return new JsonResponse([
'success' => false,
'message' => $message,
]);
}
return new JsonResponse([
'success' => true,
'redirect' => $url
]);
}
/**
* @Route(
* "/{_locale}/account/me/",
* name="account-me",
* defaults={
* "_locale": "fi"
* },
* requirements={
* "_locale": "fi|sv|en"
* }
* )
* @param Request $request
*
* @return \Symfony\Component\HttpFoundation\Response
* @throws TessituraClientException
*/
public function meAction(
Request $request,
Security $security,
TessituraUserProvider $userProvider,
AccountService $accountService,
ConstituentService $constituentService,
) {
/* "New" local user check. If needed, user session is created and logged in to tessitura */
$user = $security->getUser();
$session = $request->getSession();
if (!$session->has('tessitura_user') || !$session->has('tessitura_session')) {
/** @var EntityUser $user */
$sessionKey = $session->has('tessitura_session')
? $session->get('tessitura_session')
: $userProvider->createSessionKey();
if (!$sessionKey) {
throw new \Exception('Failed to create Tessitura session');
}
$tessituraUser = $userProvider->loadUsingConstituentInfo(
$sessionKey,
$user->getConstituentId(),
$user->getPhoneNumber(),
$user->getPostalCode()
);
if (!$tessituraUser) {
throw new \Exception('Failed to load Tessitura user');
}
$session->set('tessitura_user', $tessituraUser);
$session->set('tessitura_session', $sessionKey);
} else {
$tessituraUser = $session->get('tessitura_user');
}
$data = $this->tessituraCacheService->get('account_me_' . $user->getId());
if (!$data) {
$constituent = $constituentService->getConstituent($tessituraUser->getConstituentId());
$data = [
'badge' => $accountService->getConstituencyBadge(),
'firstname' => $constituent->getFirstName(),
'lastname' => $constituent->getLastName(),
'balance' => $accountService->getBalance(true),
'badges' => $accountService->getConstituencyBadges(),
'unpaidorders' => $accountService->getUnpaidOrders(),
'upcoming' => $accountService->getUpcoming(),
'sortupcoming' => $accountService->getSortUpcoming(),
];
$this->tessituraCacheService->put('account_me_' . $user->getId(), json_encode($data));
} else {
$data = json_decode($data, true);
}
return $this->render('page/account-me.html.twig', array_merge($data, [
'user_data_api_url' => $this->generateUrl('get-user-data'),
'wp_host' => $this->params->get('wp.domain')
]));
}
/**
* @Route(
* "/{_locale}/account/purchases/",
* name="account-purchases",
* defaults={
* "_locale": "fi"
* },
* requirements={
* "_locale": "fi|sv|en"
* }
* )
* @param Request $request
*
* @return \Symfony\Component\HttpFoundation\Response
* @throws TessituraClientException
*/
public function purchasesAction(Request $request)
{
return $this->render('page/account-purchases.html.twig', [
]);
}
/**
* @Route(
* "/{_locale}/account/viewing-rights/",
* name="account-viewing-rights",
* defaults={
* "_locale": "fi"
* },
* requirements={
* "_locale": "fi|sv|en"
* }
* )
* @param Request $request
* @param AccountService $accountService
*
* @return \Symfony\Component\HttpFoundation\Response
* @throws TessituraClientException
*/
public function viewingRightsAction(Request $request, AccountService $accountService)
{
return $this->render('page/account-viewing-rights.html.twig', [
'wp_host' => $this->params->get('wp.domain'),
'streams' => array_merge([], array_map(function ($item) {
return $item->stream_id;
}, $accountService->getPurchasedFeeProducts()))
]);
}
/**
* @Route(
* "/{_locale}/account/upcoming/",
* name="account-upcoming",
* defaults={
* "_locale": "fi"
* },
* requirements={
* "_locale": "fi|sv|en"
* }
* )
* @param Request $request
*
* @return \Symfony\Component\HttpFoundation\Response
*/
public function upcomingAction(Request $request)
{
return $this->render('page/account-upcoming.html.twig', [
]);
}
/**
* @Route(
* "/{_locale}/account/upcoming-catering/{performanceId}/",
* name="account-upcoming-catering",
* defaults={
* "_locale": "fi"
* },
* requirements={
* "_locale": "fi|sv|en"
* }
* )
* @param Request $request
*
* @param $performanceId
* @return \Symfony\Component\HttpFoundation\Response
*/
public function upcomingCateringAction(Request $request, $performanceId)
{
return $this->render('page/account-upcoming-catering.html.twig', [
'performance_id' => $performanceId
]);
}
/**
* @Route(
* "/{_locale}/account/favourites/",
* name="account-favourites",
* defaults={
* "_locale": "fi"
* },
* requirements={
* "_locale": "fi|sv|en"
* }
* )
* @param Request $request
*
* @return \Symfony\Component\HttpFoundation\Response
*/
public function favouritesAction(Request $request)
{
return $this->render('page/account-favourites.html.twig', [
]);
}
}