src/Bidcoz/Bundle/FrontendBundle/Controller/Auction/ItemController.php line 53

Open in your IDE?
  1. <?php
  2. namespace Bidcoz\Bundle\FrontendBundle\Controller\Auction;
  3. use Bidcoz\Bundle\CoreBundle\Controller\CoreController;
  4. use Bidcoz\Bundle\CoreBundle\Entity\Auction\Auction;
  5. use Bidcoz\Bundle\CoreBundle\Entity\Auction\FundANeed;
  6. use Bidcoz\Bundle\CoreBundle\Entity\Auction\Item;
  7. use Bidcoz\Bundle\CoreBundle\Entity\Auction\Shop;
  8. use Bidcoz\Bundle\CoreBundle\Entity\Campaign;
  9. use Bidcoz\Bundle\CoreBundle\Entity\Organization;
  10. use Bidcoz\Bundle\CoreBundle\Entity\Proxy\ItemPurchaseProxy;
  11. use Bidcoz\Bundle\FrontendBundle\Form\Type\ItemBidType;
  12. use Bidcoz\Bundle\FrontendBundle\Form\Type\ItemBuyType;
  13. use Bidcoz\Bundle\FrontendBundle\Form\Type\ItemDonationType;
  14. use Sensio\Bundle\FrameworkExtraBundle\Configuration\Cache;
  15. use Sensio\Bundle\FrameworkExtraBundle\Configuration\Entity;
  16. use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
  17. use Symfony\Component\HttpFoundation\Request;
  18. use Symfony\Component\Routing\Annotation\Route;
  19. /**
  20.  * @Route("/{orgSlug}/{campaignSlug}")
  21.  *
  22.  * @IsGranted("VIEW", subject="organization")
  23.  * @IsGranted("FRONT_END", subject="campaign")
  24.  */
  25. class ItemController extends CoreController
  26. {
  27.     /**
  28.      * @Route("/auction/item/{itemId}", name="auction_item", methods={"GET"})
  29.      *
  30.      * @Cache(maxage="0", smaxage="0", expires="now", public="false")
  31.      *
  32.      * @Entity("item", class="Bidcoz\Bundle\CoreBundle\Entity\Auction\Item", options={"id" = "itemId"}, expr="repository.findHydrated(itemId)")
  33.      *
  34.      * @IsGranted("VIEW", subject="item")
  35.      */
  36.     public function viewAuctionItemAction(Organization $organizationCampaign $campaignAuction $auctionItem $item)
  37.     {
  38.         return $this->viewItem($organization$campaign$auction$item);
  39.     }
  40.     /**
  41.      * @Route("/shop/item/{itemId}", name="shop_item", methods={"GET"})
  42.      *
  43.      * @Cache(maxage="0", smaxage="0", expires="now", public="false")
  44.      *
  45.      * @Entity("item", class="Bidcoz\Bundle\CoreBundle\Entity\Auction\Item", options={"id" = "itemId"}, expr="repository.findHydrated(itemId)")
  46.      *
  47.      * @IsGranted("VIEW", subject="item")
  48.      */
  49.     public function viewShopItemAction(Organization $organizationCampaign $campaignShop $shopItem $item)
  50.     {
  51.         return $this->viewItem($organization$campaign$shop$item);
  52.     }
  53.     /**
  54.      * @Route("/fund-a-need/item/{itemId}", name="fund_a_need_item", methods={"GET"})
  55.      *
  56.      * @Cache(maxage="0", smaxage="0", expires="now", public="false")
  57.      *
  58.      * @Entity("item", class="Bidcoz\Bundle\CoreBundle\Entity\Auction\Item", options={"id" = "itemId"}, expr="repository.findHydrated(itemId)")
  59.      *
  60.      * @IsGranted("VIEW", subject="item")
  61.      */
  62.     public function viewFundANeedItemAction(Organization $organizationCampaign $campaign, ?FundANeed $fundANeedItem $item)
  63.     {
  64.         if (!$fundANeed) {
  65.             throw $this->createNotFoundException('Fund-a-Need not found');
  66.         }
  67.         return $this->viewItem($organization$campaign$fundANeed$item);
  68.     }
  69.     /**
  70.      * Lightweight JSON bid status for the item detail page's live poll
  71.      * (auction-item-poll.js). Returns the re-rendered read-only bid-status
  72.      * fragment plus flags so the client can detect a bid change and adapt.
  73.      *
  74.      * @Route("/auction/item/{itemId}/status", name="auction_item_status", methods={"GET"})
  75.      *
  76.      * @Entity("item", class="Bidcoz\Bundle\CoreBundle\Entity\Auction\Item", options={"id" = "itemId"}, expr="repository.findHydrated(itemId)")
  77.      *
  78.      * @IsGranted("VIEW", subject="item")
  79.      */
  80.     public function itemStatusAction(Request $requestOrganization $organizationCampaign $campaignItem $item)
  81.     {
  82.         $user       $this->getUser();
  83.         $biddable   $this->isItemBiddable($item);
  84.         $winningBid $item->getWinningBid();
  85.         $isWinning  $user && $winningBid && $winningBid->getUser() === $user;
  86.         $isOutbid   $user && !$isWinning && $item->hasUserBid($user);
  87.         $bidCount   $item->getActiveBids()->count();
  88.         $html $this->renderView('@BidcozFrontend/Auction/_item_bid_status.html.twig', [
  89.             'item'     => $item,
  90.             'biddable' => $biddable,
  91.         ]);
  92.         // Signature of the state the heavy fragments depend on: the winning bid and
  93.         // the bid count. The client echoes back the signature it last rendered
  94.         // (?signature=); when it matches, skip re-rendering _bid_activity and
  95.         // _outbid_note. The client only consumes those fragments on a real change
  96.         // (see auction-item-poll.js), so near auction close — polling every 1–3s
  97.         // per viewer while the DB is under bid-write load — a hot item no longer
  98.         // does hundreds of full activity-table renders/min that are discarded.
  99.         $signature = ($winningBid $winningBid->getId() : 0) . ':' $bidCount;
  100.         $activityHtml   null;
  101.         $outbidNoteHtml null;
  102.         if ($request->query->get('signature') !== $signature) {
  103.             // Re-rendered bidding activity so the poll can refresh the table live.
  104.             $activityHtml $this->renderView('@BidcozFrontend/Auction/_bid_activity.html.twig', [
  105.                 'item'     => $item,
  106.                 'campaign' => $campaign,
  107.             ]);
  108.             // Re-rendered outbid notice so the poll can update its visibility AND wording
  109.             // (regular vs auto-bidder) + next-increment amount live, without a page reload.
  110.             $outbidNoteHtml $this->renderView('@BidcozFrontend/Auction/_outbid_note.html.twig', [
  111.                 'item' => $item,
  112.             ]);
  113.         }
  114.         return $this->json([
  115.             'html'          => $html,
  116.             'activityHtml'  => $activityHtml,
  117.             'outbidNoteHtml' => $outbidNoteHtml,
  118.             'signature'    => $signature,
  119.             'currentBid'   => $winningBid $winningBid->getAmount() : $item->getMinBid(),
  120.             'bidCount'     => $bidCount,
  121.             'biddable'     => $biddable,
  122.             'isWinning'    => (bool) $isWinning,
  123.             'isOutbid'     => (bool) $isOutbid,
  124.             // True when the user is outbid AND the current winner is an auto-bid proxy raise.
  125.             'outbidByAuto' => (bool) ($isOutbid && $winningBid && $winningBid->isAutoBid()),
  126.         ]);
  127.     }
  128.     private function viewItem(Organization $organizationCampaign $campaignAuction $auctionItem $item)
  129.     {
  130.         [$proxy$bidForm$buyForm$donationForm$showBidForm$showBuyForm$showDonationForm] = $this->getItemVars($item);
  131.         $internalUsers null;
  132.         if ($auction instanceof Shop) {
  133.             $type          'shop';
  134.             $internalUsers $this->getRepository('InternalUser')->findByOrganization($organization);
  135.         } elseif ($auction instanceof FundANeed) {
  136.             $type 'fund-a-need';
  137.         } else {
  138.             $type 'auction';
  139.         }
  140.         $auctionItems $this->getPagination($this->getRepository('Auction\Item')->getAuctionQueryBuilder($auctiontruefalse));
  141.         $params = [
  142.             'organization'      => $organization,
  143.             'campaign'          => $campaign,
  144.             'auction'           => $auction,
  145.             'item'              => $item,
  146.             'bidForm'           => $bidForm->createView(),
  147.             'buyForm'           => $buyForm->createView(),
  148.             'donationForm'      => $donationForm->createView(),
  149.             'showBidForm'       => $showBidForm,
  150.             'showBuyForm'       => $showBuyForm,
  151.             'showDonationForm'  => $showDonationForm,
  152.             'requireCC'         => !$this->isGranted('WITH_CC'$campaign),
  153.             'type'              => $type,
  154.             'auctionItems'      => $auctionItems,
  155.             'internalUsers'     => $internalUsers,
  156.         ];
  157.         return $this->render('@BidcozFrontend/Auction/item.html.twig'$params);
  158.     }
  159.     /**
  160.      * @Route("/auction/item/{itemId}/bid", name="item_bid")
  161.      *
  162.      * @Entity("item", class="Bidcoz\Bundle\CoreBundle\Entity\Auction\Item", options={"id" = "itemId"}, expr="repository.findHydrated(itemId)")
  163.      *
  164.      * @IsGranted("VIEW", subject="item")
  165.      */
  166.     public function bidItemAction(Request $requestOrganization $organizationCampaign $campaignAuction $auctionItem $item)
  167.     {
  168.         $user $this->getUser();
  169.         if (!$user) {
  170.             $this->messageAccessDeniedException('You must login or create an account to place a bid');
  171.         }
  172.         if ('POST' !== $request->getMethod() || !$this->isItemBiddable($item)) {
  173.             return $this->redirectToRoute('auction_item', [
  174.                 'orgSlug'      => $organization->getSlug(),
  175.                 'campaignSlug' => $campaign->getSlug(),
  176.                 'itemId'       => $item->getId(),
  177.             ]);
  178.         }
  179.         [$proxy$bidForm$buyForm$donationForm$showBidForm$showBuyForm$showDonationForm] = $this->getItemVars($item);
  180.         if (!$this->isGranted('WITH_CC'$campaign)) {
  181.             $params = [
  182.                 'organization'      => $organization,
  183.                 'campaign'          => $campaign,
  184.                 'auction'           => $auction,
  185.                 'item'              => $item,
  186.                 'bidForm'           => $bidForm->createView(),
  187.                 'buyForm'           => $buyForm->createView(),
  188.                 'donationForm'      => $donationForm->createView(),
  189.                 'showBidForm'       => $showBidForm,
  190.                 'showBuyForm'       => $showBuyForm,
  191.                 'showDonationForm'  => $showDonationForm,
  192.                 'ccRequiredWarning' => true,
  193.                 'requireCC'         => true,
  194.                 'type'              => 'auction',
  195.             ];
  196.             return $this->render('@BidcozFrontend/Auction/item.html.twig'$params);
  197.         }
  198.         $bidForm->handleRequest($request);
  199.         if ($bidForm->isValid()) {
  200.             try {
  201.                 $this->getBidManager()->createBid($user$proxy);
  202.                 $this->getEntityManager()->flush();
  203.             } catch (\Exception $e) {
  204.                 $this->addFlash('danger'$e->getMessage());
  205.             }
  206.             return $this->redirectToRoute('auction_item', [
  207.                 'orgSlug'      => $organization->getSlug(),
  208.                 'campaignSlug' => $campaign->getSlug(),
  209.                 'itemId'       => $item->getId(),
  210.             ]);
  211.         } else {
  212.             $this->addFlash('warning''There was an error placing your bid, please try again');
  213.         }
  214.         $params = [
  215.             'organization'     => $organization,
  216.             'campaign'         => $campaign,
  217.             'auction'          => $auction,
  218.             'item'             => $item,
  219.             'bidForm'          => $bidForm->createView(),
  220.             'buyForm'          => $buyForm->createView(),
  221.             'donationForm'     => $donationForm->createView(),
  222.             'showBidForm'      => $showBidForm,
  223.             'showBuyForm'      => $showBuyForm,
  224.             'showDonationForm' => $showDonationForm,
  225.             'requireCC'        => false,
  226.             'type'             => 'auction',
  227.         ];
  228.         return $this->render('@BidcozFrontend/Auction/item.html.twig'$params);
  229.     }
  230.     /**
  231.      * @Route("/auction/item/{itemId}/buy", name="auction_item_buy")
  232.      *
  233.      * @Entity("item", class="Bidcoz\Bundle\CoreBundle\Entity\Auction\Item", options={"id" = "itemId"}, expr="repository.findHydrated(itemId)")
  234.      *
  235.      * @IsGranted("VIEW", subject="item")
  236.      */
  237.     public function buyAuctionItemAction(Request $requestOrganization $organizationCampaign $campaignAuction $auctionItem $item)
  238.     {
  239.         return $this->buyItem($request$organization$campaign$auction$item);
  240.     }
  241.     /**
  242.      * @Route("/shop/item/{itemId}/buy", name="shop_item_buy")
  243.      * @Route("/shop/item/{itemId}/donation", name="shop_item_donation")
  244.      *
  245.      * @Entity("item", class="Bidcoz\Bundle\CoreBundle\Entity\Auction\Item", options={"id" = "itemId"}, expr="repository.findHydrated(itemId)")
  246.      *
  247.      * @IsGranted("VIEW", subject="item")
  248.      */
  249.     public function buyShopItemAction(Request $requestOrganization $organizationCampaign $campaignShop $shopItem $item)
  250.     {
  251.         return $this->buyItem($request$organization$campaign$shop$item);
  252.     }
  253.     /**
  254.      * @Route("/fund-a-need/item/{itemId}/buy", name="fund_a_need_item_buy")
  255.      * @Route("/fund-a-need/item/{itemId}/donation", name="fund_a_need_item_donation")
  256.      *
  257.      * @Entity("item", class="Bidcoz\Bundle\CoreBundle\Entity\Auction\Item", options={"id" = "itemId"}, expr="repository.findHydrated(itemId)")
  258.      *
  259.      * @IsGranted("VIEW", subject="item")
  260.      */
  261.     public function buyFundANeedItemAction(Request $requestOrganization $organizationCampaign $campaign, ?FundANeed $fundANeedItem $item)
  262.     {
  263.         if (!$fundANeed) {
  264.             throw $this->createNotFoundException('Fund-a-Need not found');
  265.         }
  266.         return $this->buyItem($request$organization$campaign$fundANeed$item);
  267.     }
  268.     private function buyItem(Request $requestOrganization $organizationCampaign $campaignAuction $auctionItem $item)
  269.     {
  270.         $user $this->getUser();
  271.         if (!$user) {
  272.             $this->messageAccessDeniedException('You must login or create an account to purchase an item');
  273.         }
  274.         if ('POST' !== $request->getMethod() || !($this->isItemBuyitNow($item) || $this->isItemDonation($item))) {
  275.             return $this->redirectToRoute('auction_item', [
  276.                 'orgSlug'      => $organization->getSlug(),
  277.                 'campaignSlug' => $campaign->getSlug(),
  278.                 'itemId'       => $item->getId(),
  279.             ]);
  280.         }
  281.         [$proxy$bidForm$buyForm$donationForm$showBidForm$showBuyForm$showDonationForm] = $this->getItemVars($item);
  282.         if ($auction instanceof Shop) {
  283.             $type 'shop';
  284.         } elseif ($auction instanceof FundANeed) {
  285.             $type 'fund-a-need';
  286.         } else {
  287.             $type 'auction';
  288.         }
  289.         if (!$this->isGranted('WITH_CC'$campaign)) {
  290.             $params = [
  291.                 'organization'      => $organization,
  292.                 'campaign'          => $campaign,
  293.                 'auction'           => $auction,
  294.                 'item'              => $item,
  295.                 'bidForm'           => $bidForm->createView(),
  296.                 'buyForm'           => $buyForm->createView(),
  297.                 'donationForm'      => $donationForm->createView(),
  298.                 'showBidForm'       => $showBidForm,
  299.                 'showBuyForm'       => $showBuyForm,
  300.                 'showDonationForm'  => $showDonationForm,
  301.                 'ccRequiredWarning' => true,
  302.                 'requireCC'         => true,
  303.                 'type'              => $type,
  304.             ];
  305.             return $this->render('@BidcozFrontend/Auction/item.html.twig'$params);
  306.         }
  307.         if ($showBuyForm) {
  308.             $buyForm->handleRequest($request);
  309.             if ($buyForm->isValid()) {
  310.                 try {
  311.                     $holdInCart $campaign->getHoldItemsInCart();
  312.                     $expirationDate $holdInCart
  313.                         null
  314.                         : new \DateTime('+15 minutes');
  315.                     $this->getPurchaseManager()->createBuyItNowItemPurchase($user$proxy$campaign->getHoldItemsInCart(), $expirationDate);
  316.                     if ('auction' == $type) {
  317.                         $this->getWatchListManager()->createWatchListItem($user$proxy->getItem());
  318.                     }
  319.                     $this->getEntityManager()->flush();
  320.                 } catch (\Exception $e) {
  321.                     $this->addFlash('warning'$e->getMessage());
  322.                 }
  323.                 return $this->redirectToRoute('account_campaign_purchases', [
  324.                     'orgSlug'      => $organization->getSlug(),
  325.                     'campaignSlug' => $campaign->getSlug(),
  326.                 ]);
  327.             } else {
  328.                 $this->addFlash('warning''There was an error purchasing this item, please try again');
  329.             }
  330.         } elseif ($showDonationForm) {
  331.             $donationForm->handleRequest($request);
  332.             if ($donationForm->isValid()) {
  333.                 try {
  334.                     $this->getPurchaseManager()->createDonationItemPurchase($user$proxy);
  335.                     $this->getEntityManager()->flush();
  336.                 } catch (\Exception $e) {
  337.                     $this->addFlash('warning'$e->getMessage());
  338.                 }
  339.                 return $this->redirectToRoute('account_campaign_purchases', [
  340.                     'orgSlug'      => $organization->getSlug(),
  341.                     'campaignSlug' => $campaign->getSlug(),
  342.                 ]);
  343.             } else {
  344.                 $this->addFlash('warning''There was an error making this purchase, please try again');
  345.             }
  346.         }
  347.         $params = [
  348.             'organization'     => $organization,
  349.             'campaign'         => $campaign,
  350.             'auction'          => $auction,
  351.             'item'             => $item,
  352.             'bidForm'          => $bidForm->createView(),
  353.             'buyForm'          => $buyForm->createView(),
  354.             'donationForm'     => $donationForm->createView(),
  355.             'showBidForm'      => $showBidForm,
  356.             'showBuyForm'      => $showBuyForm,
  357.             'showDonationForm' => $showDonationForm,
  358.             'requireCC'        => false,
  359.             'type'             => $auction instanceof Shop 'shop' 'auction',
  360.         ];
  361.         return $this->render('@BidcozFrontend/Auction/item.html.twig'$params);
  362.     }
  363.     /**
  364.      * @Route("/auction/item/{itemId}/max-bid", name="item_max_bid")
  365.      *
  366.      * @Entity("item", class="Bidcoz\Bundle\CoreBundle\Entity\Auction\Item", options={"id" = "itemId"}, expr="repository.findHydrated(itemId)")
  367.      *
  368.      * @IsGranted("VIEW", subject="item")
  369.      */
  370.     public function updateMaxBidAction(Request $requestOrganization $organizationCampaign $campaignAuction $auctionItem $item)
  371.     {
  372.         $user $this->getUser();
  373.         if (!$user) {
  374.             $this->messageAccessDeniedException('You must login or create an account to place a bid');
  375.         }
  376.         if ('POST' !== $request->getMethod() || !$this->isItemBiddable($item)) {
  377.             return $this->redirectToRoute('auction_item', [
  378.                 'orgSlug'      => $organization->getSlug(),
  379.                 'campaignSlug' => $campaign->getSlug(),
  380.                 'itemId'       => $item->getId(),
  381.             ]);
  382.         }
  383.         if (!$this->isGranted('WITH_CC'$campaign)) {
  384.             [$proxy$bidForm$buyForm$donationForm$showBidForm$showBuyForm$showDonationForm] = $this->getItemVars($item);
  385.             $params = [
  386.                 'organization'      => $organization,
  387.                 'campaign'          => $campaign,
  388.                 'auction'           => $auction,
  389.                 'item'              => $item,
  390.                 'bidForm'           => $bidForm->createView(),
  391.                 'buyForm'           => $buyForm->createView(),
  392.                 'donationForm'      => $donationForm->createView(),
  393.                 'showBidForm'       => $showBidForm,
  394.                 'showBuyForm'       => $showBuyForm,
  395.                 'showDonationForm'  => $showDonationForm,
  396.                 'ccRequiredWarning' => true,
  397.                 'requireCC'         => true,
  398.                 'type'              => 'auction',
  399.             ];
  400.             return $this->render('@BidcozFrontend/Auction/item.html.twig'$params);
  401.         }
  402.         try {
  403.             $amount    $request->request->get('amount'0);
  404.             $cancelBid = (bool) $request->request->get('_cancel'false);
  405.             $this->getBidManager()->setMaxBid($user$item$amount$cancelBid);
  406.             $this->getEntityManager()->flush();
  407.         } catch (\Exception $e) {
  408.             $this->addFlash('warning'$e->getMessage());
  409.         }
  410.         return $this->redirectToRoute('auction_item', [
  411.             'orgSlug'      => $organization->getSlug(),
  412.             'campaignSlug' => $campaign->getSlug(),
  413.             'itemId'       => $item->getId(),
  414.         ]);
  415.     }
  416.     protected function isItemBiddable(Item $item)
  417.     {
  418.         return $this->getItemManager()->isItemBiddable($item);
  419.     }
  420.     protected function isItemBuyitNow(Item $item)
  421.     {
  422.         return $this->getItemManager()->isItemBuyitNow($item);
  423.     }
  424.     protected function isItemDonation(Item $item)
  425.     {
  426.         return $this->getItemManager()->isItemDonation($item);
  427.     }
  428.     protected function createItemPurchaseProxy(Item $item)
  429.     {
  430.         return new ItemPurchaseProxy($item);
  431.     }
  432.     protected function getBidForm(ItemPurchaseProxy $proxy)
  433.     {
  434.         $item $proxy->getItem();
  435.         if ($item->isAuctionType()) {
  436.             $itemMinimumBid $this->getBidManager()->getItemMinimumBid($item);
  437.             $proxy->setAmount($itemMinimumBid);
  438.         }
  439.         $form $this->createForm(ItemBidType::class, $proxy);
  440.         return $form;
  441.     }
  442.     protected function getBuyForm(ItemPurchaseProxy $proxy)
  443.     {
  444.         $item $proxy->getItem();
  445.         $form $this->createForm(ItemBuyType::class, $proxy, [
  446.             'item' => $item,
  447.         ]);
  448.         if ($item->isFixedPrice()) {
  449.             $form->get('quantity')->setData(1);
  450.         }
  451.         return $form;
  452.     }
  453.     protected function getDonationForm(ItemPurchaseProxy $proxy)
  454.     {
  455.         $item $proxy->getItem();
  456.         return $this->createForm(ItemDonationType::class, $proxy, [
  457.             'item' => $item,
  458.         ]);
  459.     }
  460.     protected function getItemVars(Item $item)
  461.     {
  462.         $proxy            $this->createItemPurchaseProxy($item);
  463.         $bidForm          $this->getBidForm($proxy);
  464.         $buyForm          $this->getBuyForm($proxy);
  465.         $donationForm     $this->getDonationForm($proxy);
  466.         $showBidForm      $this->getItemManager()->isItemBiddable($item);
  467.         $showBuyForm      $this->getItemManager()->isItemBuyitNow($item);
  468.         $showDonationForm $this->getItemManager()->isItemDonation($item);
  469.         return [$proxy$bidForm$buyForm$donationForm$showBidForm$showBuyForm$showDonationForm];
  470.     }
  471. }