src/Controller/Rest/CartRestController.php line 34

Open in your IDE?
  1. <?php
  2. namespace App\Controller\Rest;
  3. use App\Controller\Traits\SingleOccurrenceRequestValidatable;
  4. use App\Entity\CartItem;
  5. use App\Entity\Course;
  6. use App\Entity\CourseOccurrence;
  7. use App\Repository\CartItemRepository;
  8. use App\Repository\CourseDataRepository;
  9. use App\Repository\CourseFieldRepository;
  10. use App\Repository\CourseOccurrenceRepository;
  11. use App\Repository\CourseOccurrenceTimeRepository;
  12. use App\Repository\OAuth\AccessTokenRepository;
  13. use App\Repository\OAuth\ClientRepository;
  14. use App\Repository\PersonRepository;
  15. use App\Service\CartService;
  16. use App\Service\ConfigurationService;
  17. use Doctrine\DBAL\Connection;
  18. use Doctrine\Persistence\ManagerRegistry;
  19. use FOS\RestBundle\Controller\Annotations\QueryParam;
  20. use FOS\RestBundle\Controller\Annotations\View;
  21. use FOS\RestBundle\Request\ParamFetcher;
  22. use League\Bundle\OAuth2ServerBundle\Manager\AccessTokenManagerInterface;
  23. use Nelmio\ApiDocBundle\Annotation\Model;
  24. use Swagger\Annotations as SWG;
  25. use Symfony\Component\HttpFoundation\Request;
  26. use Symfony\Component\Routing\Annotation\Route;
  27. use Symfony\Component\Security\Core\Security;
  28. /**
  29.  * @Route("/rest/cart")
  30.  */
  31. class CartRestController extends AbstractRestCartableController
  32. {
  33.     use SingleOccurrenceRequestValidatable;
  34.     private $courseOccurrenceRepo;
  35.     protected $managerRegistry;
  36.     public function __construct(
  37.         ClientRepository $clientRepository,
  38.         AccessTokenManagerInterface $accessTokenManagerInterface,
  39.         AccessTokenRepository $accessTokenRepository,
  40.         Security $security,
  41.         CartService $cartService,
  42.         PersonRepository $personRepo,
  43.         CourseOccurrenceRepository $courseOccurrenceRepo,
  44.         ManagerRegistry $managerRegistry,
  45.     ) {
  46.         parent::__construct($clientRepository$accessTokenManagerInterface$accessTokenRepository$security$managerRegistry$cartService$personRepo);
  47.         $this->courseOccurrenceRepo $courseOccurrenceRepo;
  48.         $this->managerRegistry $managerRegistry;
  49.     }
  50.     /**
  51.      * @Route("/add", name="rest_cart_add", methods="PUT")
  52.      *
  53.      * @SWG\Put(
  54.      *     produces={"application/json"},
  55.      *     consumes={"application/x-www-form-urlencoded"}
  56.      * )
  57.      *
  58.      * @SWG\Parameter(name="courseOccurrence", in="body", required=true, schema=@SWG\Schema(type="integer"))
  59.      * @SWG\Parameter(name="quantity", in="body", required=true, schema=@SWG\Schema(type="integer"))
  60.      * @SWG\Parameter(name="courseOccurrenceTime", in="body", required=false, schema=@SWG\Schema(type="integer"))
  61.      *
  62.      * @SWG\Response(
  63.      *     response=200,
  64.      *     description="Returns json containing a confirmation message",
  65.      *
  66.      *     @SWG\Schema(
  67.      *         type="object",
  68.      *
  69.      *         @SWG\Property(property="success", type="boolean", description="Flag if request was successful"),
  70.      *         @SWG\Property(property="message", type="string", description="Response message."),
  71.      *     )
  72.      * )
  73.      *
  74.      * @SWG\Response(
  75.      *     response=400,
  76.      *     description="Returned if request parameter were invalid."
  77.      * )
  78.      * @SWG\Response(
  79.      *     response=403,
  80.      *     description="Returned if access to course occurrence was denied."
  81.      * )
  82.      * @SWG\Response(
  83.      *     response=404,
  84.      *     description="Returned if course occurrence was not found."
  85.      * )
  86.      *
  87.      * @SWG\Tag(name="rest")
  88.      *
  89.      * @View(serializerGroups={"public"})
  90.      */
  91.     public function add(
  92.         Request $request,
  93.         CourseOccurrenceTimeRepository $courseOccurrenceTimeRepository,
  94.     ) {
  95.         $courseOccurrenceId $request->get('courseOccurrence');
  96.         $quantity $request->get('quantity');
  97.         if (empty($quantity)) {
  98.             throw new \Exception(sprintf('Der Parameter „%s“ fehlt leider.''quantity'));
  99.         }
  100.         $violation $this->validateSingleOccurrenceRequest($courseOccurrenceId$quantity);
  101.         if (null !== $violation) {
  102.             return $violation;
  103.         }
  104.         $courseOccurrence $this->getCourseOccurrence($courseOccurrenceId);
  105.         $courseOccurrenceTimeId null;
  106.         // Validate course template availability
  107.         if ('CourseTemplate' == $courseOccurrence->getCourse()->getCourseNature()) {
  108.             // Obtain course occurrence time id
  109.             $courseOccurrenceTimeId $request->get('courseOccurrenceTime');
  110.             if (empty($courseOccurrenceTimeId)) {
  111.                 throw new \Exception(sprintf('Der Parameter „%s“ fehlt leider.''courseOccurrenceTime'));
  112.             }
  113.             // Fetch time
  114.             $courseOccurrenceTime $courseOccurrenceTimeRepository->find($courseOccurrenceTimeId);
  115.             if (null === $courseOccurrenceTime) {
  116.                 throw $this->createNotFoundException('Der angegebene Zeit-Slot konnte nicht gefunden werden.');
  117.             }
  118.             if ('NotAvailable' == $courseOccurrenceTime->getAvailability()) {
  119.                 throw new \Exception(sprintf('Time is not available.'));
  120.             }
  121.         // Todo check collision with booked courses
  122.         } else {
  123.             $courseOccurrenceTime null;
  124.             if (!$courseOccurrence->isAvailable()) {
  125.                 throw $this->createNotFoundException('Dieser Kurs kann nicht in den Warenkorb gelegt werden.');
  126.             }
  127.         }
  128.         $em $this->managerRegistry->getManager();
  129.         $cart $this->getCart();
  130.         if (!$cart->getId()) {
  131.             $em->flush();
  132.         }
  133.         $cartItem $this->cartService->getCartItemIfExists($courseOccurrence$cartfalse$courseOccurrenceTimeId);
  134.         if ($cartItem) {
  135.             if ('CourseTemplate' != $courseOccurrence->getCourse()->getCourseNature()) {
  136.                 $cartItem->increaseQuantity($quantity);
  137.                 foreach ($cartItem->getMaterialCosts() as $materialCost) {
  138.                     $materialCost->increaseQuantity($quantity);
  139.                 }
  140.             }
  141.         } else {
  142.             $cartItem CartItem::createWithCart($cart$courseOccurrence$quantity$courseOccurrenceTime);
  143.             $em->persist($cartItem);
  144.             if ($courseOccurrence->getMaterialCost() > 0) {
  145.                 $materialCost CartItem::createWithCartItem($cartItem);
  146.                 $em->persist($materialCost);
  147.             }
  148.         }
  149.         $em->flush();
  150.         return [
  151.             'success' => true,
  152.             'message' => $quantity.($quantity ' Kurse' ' Kurs').' in den Warenkorb gelegt.',
  153.         ];
  154.     }
  155.     /**
  156.      * @Route("/set-quantity", name="rest_cart_set-quantity", methods="PUT")
  157.      *
  158.      * @SWG\Put(
  159.      *     produces={"application/json"},
  160.      *     consumes={"application/x-www-form-urlencoded"}
  161.      * )
  162.      *
  163.      * @SWG\Parameter(name="courseOccurrence", in="body", required=true, schema=@SWG\Schema(type="integer"))
  164.      * @SWG\Parameter(name="quantity", in="body", required=true, schema=@SWG\Schema(type="integer"))
  165.      *
  166.      * @SWG\Response(
  167.      *     response=200,
  168.      *     description="Returns json containing a confirmation message"
  169.      * )
  170.      * @SWG\Response(
  171.      *     response=400,
  172.      *     description="Returned if request parameter were invalid."
  173.      * )
  174.      * @SWG\Response(
  175.      *     response=403,
  176.      *     description="Returned if access to course occurrence was denied."
  177.      * )
  178.      * @SWG\Response(
  179.      *     response=404,
  180.      *     description="Returned if course occurrence was not found."
  181.      * )
  182.      *
  183.      * @SWG\Tag(name="rest")
  184.      *
  185.      * @View(serializerGroups={"public"})
  186.      */
  187.     public function setQuantity(Request $request)
  188.     {
  189.         $courseOccurrenceId $request->get('courseOccurrence');
  190.         $quantity $request->get('quantity');
  191.         $violation $this->validateSingleOccurrenceRequest($courseOccurrenceId$quantity);
  192.         if (null !== $violation) {
  193.             return $this->json($violation400);
  194.         }
  195.         $courseOccurrence $this->getCourseOccurrence($courseOccurrenceId);
  196.         $cart $this->getCart();
  197.         $cartItem $this->cartService->getCartItemIfExists($courseOccurrence$cart);
  198.         if (!$cartItem) {
  199.             throw $this->createNotFoundException('The requested item is not in current cart.');
  200.         }
  201.         $message '';
  202.         if ($quantity $courseOccurrence->getFreeSlots()) {
  203.             if (!$courseOccurrence->getReservationAllowed()) {
  204.                 return [
  205.                     'success' => false,
  206.                     'message' => 'Es sind keine freien Plätze mehr verfügbar.',
  207.                 ];
  208.             }
  209.             $message 'Menge im Warenkorb aktualisiert. Es sind nicht mehr genug Plätze verfügbar. Ihre Anmeldung wird auf die Warteliste gestellt.';
  210.         }
  211.         $cartItem->setQuantity($quantity);
  212.         if ($cartItem->getMaterialCosts()) {
  213.             foreach ($cartItem->getMaterialCosts() as $materialCost) {
  214.                 $materialCost->setQuantity($quantity);
  215.             }
  216.         }
  217.         $em $this->managerRegistry->getManager();
  218.         $em->flush();
  219.         return [
  220.             'success' => true,
  221.             'message' => $message $message 'Menge im Warenkorb aktualisiert.',
  222.         ];
  223.     }
  224.     /**
  225.      * @Route("/remove", name="rest_cart_remove", methods="DELETE")
  226.      *
  227.      * @SWG\Delete(
  228.      *     produces={"application/json"},
  229.      *     consumes={"application/x-www-form-urlencoded"}
  230.      * )
  231.      *
  232.      * @SWG\Parameter(name="courseOccurrence", in="body", required=true, schema=@SWG\Schema(type="integer"))
  233.      *
  234.      * @SWG\Response(
  235.      *     response=200,
  236.      *     description="Returns json containing a confirmation message",
  237.      *
  238.      *     @SWG\Schema(
  239.      *         type="object",
  240.      *
  241.      *         @SWG\Property(property="success", type="boolean", description="Flag if request was successful"),
  242.      *         @SWG\Property(property="message", type="string", description="Response message."),
  243.      *     )
  244.      * )
  245.      *
  246.      * @SWG\Response(
  247.      *     response=400,
  248.      *     description="Returned if request parameter were invalid."
  249.      * )
  250.      * @SWG\Response(
  251.      *     response=403,
  252.      *     description="Returned if access to course occurrence was denied."
  253.      * )
  254.      * @SWG\Response(
  255.      *     response=404,
  256.      *     description="Returned if course occurrence was not found."
  257.      * )
  258.      *
  259.      * @SWG\Tag(name="rest")
  260.      *
  261.      * @View(serializerGroups={"public"})
  262.      */
  263.     public function remove(Request $request)
  264.     {
  265.         $courseOccurrenceId $request->get('courseOccurrence');
  266.         if (!is_numeric($courseOccurrenceId)) {
  267.             return $this->json([
  268.                 'success' => false,
  269.                 'message' => 'Es wurden ungültige Werte übergeben',
  270.                 'errors' => ['courseOccurrence' => 'Dieser Wert muss ein Integer sein.'],
  271.             ], 400);
  272.         }
  273.         $courseOccurrence $this->getCourseOccurrence($courseOccurrenceId);
  274.         $cart $this->getCart();
  275.         $em $this->managerRegistry->getManager();
  276.         $cartItem $this->cartService->getCartItemIfExists($courseOccurrence$cart);
  277.         if (!$cartItem) {
  278.             throw $this->createNotFoundException('Der angeforderte Kurs befindet sich nicht im Warenkorb.');
  279.         }
  280.         if ($cartItem->getMaterialCosts()) {
  281.             foreach ($cartItem->getMaterialCosts() as $materialCost) {
  282.                 $em->remove($materialCost);
  283.             }
  284.         }
  285.         $cart->removeItem($cartItem);
  286.         $em->remove($cartItem);
  287.         $em->flush();
  288.         return [
  289.             'success' => true,
  290.             'message' => 'Der Kurs wurde aus dem Warenkorb entfernt.',
  291.         ];
  292.     }
  293.     /**
  294.      * @Route("/show", name="rest_cart_show", methods="GET")
  295.      *
  296.      * @SWG\Get(
  297.      *     produces={"application/json"},
  298.      *
  299.      *     @SWG\Parameter(name="limit", in="query", type="integer"),
  300.      *     @SWG\Parameter(name="offset", in="query", type="integer"),
  301.      * )
  302.      *
  303.      * @QueryParam(
  304.      *     name="limit",
  305.      *     requirements="\d+",
  306.      *     default="100",
  307.      * )
  308.      * @QueryParam(
  309.      *     name="offset",
  310.      *     requirements="\d+",
  311.      *     default="0",
  312.      * )
  313.      * @QueryParam(
  314.      *     name="order",
  315.      *     requirements="asc|desc",
  316.      *     default="asc",
  317.      *     description="Possible values: asc|desc",
  318.      * )
  319.      * @QueryParam(
  320.      *     name="orderby",
  321.      *     requirements="title|price|priceSum|quantity",
  322.      *     default="title",
  323.      *     description="Possible values: title|price|priceSum|quantity",
  324.      * )
  325.      *
  326.      * @SWG\Response(
  327.      *     response=200,
  328.      *     description="Returns cart content in json format.",
  329.      *
  330.      *     @Model(type=Cart::class, groups={"personal"})
  331.      * )
  332.      *
  333.      * @SWG\Tag(name="rest")
  334.      *
  335.      * @View(serializerGroups={"personal"})
  336.      */
  337.     public function show(
  338.         ParamFetcher $paramFetcher,
  339.         CartItemRepository $cartItemRepo,
  340.         Connection $connection,
  341.         CourseDataRepository $courseDataRepository,
  342.         CourseFieldRepository $courseFieldRepository,
  343.         ConfigurationService $configService,
  344.     ) {
  345.         $order $paramFetcher->get('order');
  346.         $orderby $paramFetcher->get('orderby');
  347.         $limit $paramFetcher->get('limit');
  348.         $offset $paramFetcher->get('offset');
  349.         $cart $this->getCart();
  350.         if ($cart->getId()) {
  351.             $cartItems $cartItemRepo->findBy(['cart' => $cart'courseItem' => null], [$orderby => $order], $limit$offset);
  352.             foreach ($cartItems as $cartItem) {
  353.                 $sqlCourse 'SELECT 
  354.                 c.id,
  355.                 c.title,
  356.                 c.description,
  357.                 c.category_id,
  358.                 c.series_id,
  359.                 c.type_id
  360.               FROM 
  361.                 course c
  362.               WHERE 
  363.                 c.id = :courseId';
  364.                 $stmtCourse $connection->prepare($sqlCourse);
  365.                 $stmtCourse->bindValue('courseId'$cartItem->getCourse()->getId());
  366.                 $stmtCourse->executeQuery();
  367.                 $courseData $stmtCourse->fetchAllAssociative();
  368.                 // Falls der Kurs nicht existiert, überspringen
  369.                 if (!$courseData) {
  370.                     continue;
  371.                 }
  372.                 $sql 'SELECT
  373.                 f.*,
  374.                 d.value_text,
  375.                 d.value_integer
  376.             FROM
  377.                 course_field f
  378.             LEFT JOIN
  379.                 course_data d
  380.             ON 
  381.             d.field_id = f.id AND
  382.             d.course_id = :courseId
  383.             WHERE f.certificate = 0';
  384.                 $stmt $connection->prepare($sql);
  385.                 $stmt->bindValue(
  386.                     'courseId',
  387.                     $cartItem->getCourse()->getId()
  388.                 );
  389.                 $stmt->executeQuery();
  390.                 $result $stmt->fetchAll();
  391.                 $fields = [];
  392.                 foreach ($result as $field) {
  393.                     $fields[] = [
  394.                         'id' => $field['id'],
  395.                         'name' => $field['name'],
  396.                         'value' => !empty($field['value_integer']) ? $field['value_integer'] : $field['value_text'],
  397.                     ];
  398.                 }
  399.                 $fields[] = [
  400.                     'id' => 'category_id',
  401.                     'name' => 'Category ID',
  402.                     'value' => $courseData[0]['category_id'],
  403.                 ];
  404.                 $fields[] = [
  405.                     'id' => 'series_id',
  406.                     'name' => 'Series ID',
  407.                     'value' => $courseData[0]['series_id'],
  408.                 ];
  409.                 $fields[] = [
  410.                     'id' => 'type_id',
  411.                     'name' => 'Type ID',
  412.                     'value' => $courseData[0]['type_id'],
  413.                 ];
  414.                 $cartItem->setField($fields);
  415.                 $cartItem->setField($fields); // oder $cartItem->field = 'yes';
  416.             }
  417.             $cart->setItems($cartItems);
  418.         }
  419.         return $cart;
  420.     }
  421.     /**
  422.      * Get course occurrence by id. It will be checked if entity exists and access is allowed.
  423.      * Loads tenant-aware via Course relationship.
  424.      *
  425.      * @return CourseOccurrence|null
  426.      */
  427.     protected function getCourseOccurrence($courseOccurrenceId)
  428.     {
  429.         // Load course occurrence tenant-aware via Course relationship
  430.         $queryBuilder $this->courseOccurrenceRepo->getQueryBuilderByClient($this->getCurrentClient());
  431.         $queryBuilder->andWhere('co.id = :id');
  432.         $queryBuilder->setParameter('id'$courseOccurrenceId);
  433.         
  434.         $courseOccurrence $queryBuilder->getQuery()->getOneOrNullResult();
  435.         
  436.         if (!$courseOccurrence) {
  437.             throw $this->createNotFoundException('The requested course occurrence does not exist or access denied.');
  438.         }
  439.         
  440.         // Security: Check client ownership via CourseOccurrenceVoter
  441.         $this->denyAccessUnlessGranted('view'$courseOccurrence);
  442.         return $courseOccurrence;
  443.     }
  444.     private function createDescription($field$option)
  445.     {
  446.         switch ($option) {
  447.             case 'course':
  448.                 if (!empty($field['certificate'])) {
  449.                     $field['name'] = $this->generateHTMLForDescription(
  450.                         $field['name'],
  451.                         'für den Kurs und das Zertifikat'
  452.                     );
  453.                 } else {
  454.                     $field['name'] = $this->generateHTMLForDescription(
  455.                         $field['name'],
  456.                         'für den Kurs'
  457.                     );
  458.                 }
  459.                 break;
  460.             case 'certificate':
  461.                 $field['name'] = $this->generateHTMLForDescription(
  462.                     $field['name'],
  463.                     'für das Zertifikat'
  464.                 );
  465.                 break;
  466.             default:
  467.                 break;
  468.         }
  469.         return $field;
  470.     }
  471.     private function generateHTMLForDescription($name$text)
  472.     {
  473.         return '<strong>'.$name.'</strong><span style="font-size: 0.7rem"> ('.$text.')</span>';
  474.     }
  475. }