<?php
namespace Boldr\Shop\CloudPrntReceiptPrinterBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\{ Request, Response, BinaryFileResponse };
use Boldr\Shop\ReceiptPrinterBundle\Printer\PrintJobManager;
use Boldr\Shop\CloudPrntReceiptPrinterBundle\Entity\CloudPrntPrinterConfiguration;
use Boldr\Shop\ReceiptPrinterBundle\Entity\{ Receipt };
use mikehaertl\wkhtmlto\Image;
use Boldr\Shop\CpUtilBinLocator\CpUtilBinLocator;
use Imagick;
use ImagickException;
use Boldr\Shop\ShopBundle\Presenter\Order\OrderPresenter;
use Symfony\Component\HttpKernel\Config\FileLocator;
use Psr\Log\LoggerInterface;
class CloudPrntController extends AbstractController
{
public static function getSubscribedServices()
{
return array_merge(parent::getSubscribedServices(), [
'print_job_manager' => PrintJobManager::class,
'order_presenter' => OrderPresenter::class,
'file_locator' => FileLocator::class,
'logger' => LoggerInterface::class,
]);
}
/** @Route("/boldr-api/cloudprnt/{secret}", name="cloudprnt_receipt_printer_print") */
public function cloudprnt(Request $request, string $secret)
{
$printerConfigurations = $this->getDoctrine()->getRepository(CloudPrntPrinterConfiguration::class)->findBySecret($secret);
if (count($printerConfigurations) === 0)
throw $this->createNotFoundException();
$printers = array_map(fn($printerConfiguration) => $printerConfiguration->getPrinter(), $printerConfigurations);
if ($request->getMethod() == 'POST')
{
return $this->poll($request, $printers);
}
elseif ($request->getMethod() == 'GET')
{
return $this->print($request, $printers);
}
elseif ($request->getMethod() == 'DELETE')
{
return $this->completed($request, $printers);
}
}
private function poll(Request $request, array $printers): Response
{
$printJobManager = $this->get('print_job_manager');
$printJobs = $printJobManager->getPrintJobs($printers, ['unknown', 'queued']);
$data = [];
// Ignore jobs if printingInProgress
if (!$request->request->get('printingInProgress', false))
{
if (count($printJobs) == 0)
{
$data['jobReady'] = false;
}
else
{
$receipt = $printJobs[0];
$data = [
'jobReady' => true,
'jobToken' => (string) $receipt->getId(),
'mediaTypes' => ['application/vnd.star.raster', 'application/vnd.star.line', 'application/vnd.star.starprnt', 'image/png', 'image/jpeg', 'application/vnd.star.starprntcore']
];
}
}
return $this->json($data);
}
private function getPrintJobByToken(array $printers, string $jobToken)
{
$printJobManager = $this->get('print_job_manager');
$receipt = $printJobManager->getPrintJob($printers, $jobToken);
if ($receipt === null)
// || $receipt->getState() == 'printed')
return null;
return $receipt;
}
private function print(Request $request, array $printers): Response
{
$token = $request->query->get('token');
if ($token === null)
{
return $this->json(['error' => 'missing_token_parameter']);
}
$printJob = $this->getPrintJobByToken($printers, $token);
if ($printJob === null)
throw $this->createNotFoundException();
$printer = $printJob->getPrinter();
$mediaType = $request->query->get('type');
$printJob->setState(Receipt::STATE_PRINTING);
$this->getDoctrine()->getManager()->flush();
$template = $printer->getConfiguration()->getTemplate();
$orders = $printJob->getOrders();
$customer = $orders[0]->getCustomer();
$state = $orders[0]->getState();
$currency = $orders[0]->getCurrency();
$megaOrder = clone $orders[0];
$invoiceItems = $megaOrder->getInvoiceItems();
$orderItems = $megaOrder->getItems();
foreach ($orders as $i => $order)
{
if ($i !== 0)
{
foreach ($order->getItems() as $orderItem)
{
$orderItems->add($orderItem);
$invoiceItem = $orderItem->getInvoiceItem();
$invoiceItems->add($invoiceItem);
}
foreach ($order->getInvoiceItems() as $invoiceItem)
{
if (!$invoiceItems->contains($invoiceItem))
{
$invoiceItems->add($invoiceItem);
}
}
}
}
$orderView = $this->get('order_presenter')->createOrderView($megaOrder, $printer->getLocale());
// Separate items by category
$orderItemViewsByCategoryId = [];
if ($printer->getSeparateItemsByCategory())
{
$printerCategoryIds = array_map(fn($category) => $category->getId(), $printer->getCategories());
foreach ($orderView->getItems() as $orderItemView)
{
$orderItemViewCategoryIds = array_map(fn($categoryView) => $categoryView->getId(), $orderItemView->getProduct()->getCategories());
$inAny = false;
foreach ($printerCategoryIds as $id)
{
if (in_array($id, $orderItemViewCategoryIds))
{
$orderItemViewsByCategoryId[$id][] = $orderItemView;
$inAny = true;
break;
}
}
if (!$inAny && !$printer->getRestrictItemsByCategory())
{
$orderItemViewsByCategoryId[null][] = $orderItemView;
}
}
}
// Generate receipt view
$templateData = $this->getParameter('boldr_cloudprnt_receipt_printer.receipt_templates')[$template];
$html = $this->renderView($templateData['template'], [
'order' => $orderView,
'logoUrl' => null,
'separateItemsByCategory' => $printer->getSeparateItemsByCategory(),
'categoryTranslations' => array_map(fn($category) => $category->getTranslations()->get($printer->getLocale()), $printer->getCategories()),
'orderItemsByCategoryId' => $orderItemViewsByCategoryId,
]);
// Render to image
$stylesheet = substr($templateData['stylesheet'], 0, 1) == '@'
? $this->get('file_locator')->locate($templateData['stylesheet'])
: $this->getParameter('kernel.project_dir') .'/'. $templateData['stylesheet'];
$wkhtmltoimage = new Image([
'user-style-sheet' => $stylesheet,
'width' => '518',
'encoding' => 'utf-8'
]);
$wkhtmltoimage->setPage($html);
try
{
$imagick = new Imagick;
// $imagick->readImageBlob('');
$imagick->readImageBlob($wkhtmltoimage->toString());
$imagick->setImageType(Imagick::IMGTYPE_GRAYSCALE);
$imagick->setImageFormat('png8');
$imagick->stripImage();
$bytes = (string) $imagick;
}
catch (ImagickException $ex)
{
$this->container->get('logger')->critical('printing failed for receipt token '. $token .', requeued: '. $ex);
// If image generation failed for some reason, e.g. empty image string passed,
// re-queue the receipt to be printed another time.
$printJob->setState(Receipt::STATE_QUEUED);
$this->getDoctrine()->getManager()->flush();
return new Response('', 500);
}
if (!in_array($mediaType, $request->getAcceptableContentTypes()))
{
$response = new Response($bytes);
$response->headers->set('Content-Type', 'image/png');
return $response;
}
$tmpInput = tempnam(sys_get_temp_dir(), 'bcpi');
rename($tmpInput, $tmpInput.'.png');
$tmpInput .= '.png';
file_put_contents($tmpInput, $bytes);
// Capture cputil output
$tmpOutput = tempnam(sys_get_temp_dir(), 'bcpo');
$this->runCpUtilCommand('thermal3 dither buzzer-start 1 scale-to-fit decode '.$mediaType.' '.$tmpInput.' '.$tmpOutput);
$response = new BinaryFileResponse($tmpOutput);
$response->headers->set('Content-Type', $mediaType);
$response->headers->set('X-Star-Buzzerstartpattern', '1');
return $response;
}
private function runCpUtilCommand(string $command)
{
$cpUtilPath = CpUtilBinLocator::getCpUtilBin();
system($cpUtilPath .' '. $command);
}
private function completed(Request $request, array $printers): Response
{
$printJob = $this->getPrintJobByToken($printers, $request->query->get('token'));
if ($printJob === null)
return $this->createNotFoundException();
$code = $request->query->get('code');
if ($code)
{
if ($code >= 400 && $code < 600)
{
$orderNumbers = $printJob->getOrders()->map(fn($order) => $order->getNumber() ?? ('ID#'. $order->getId()))->toArray();
//$this->get('logger')->critical('Print job "'.$printJob->getId().'" (order(s) '. join(', ', $orderNumbers) .') return status '. $code .', retrying.');
return $this->json([]);
}
}
$printJob->setState(Receipt::STATE_PRINTED);
$this->getDoctrine()->getManager()->flush();
return $this->json([]);
}
public function handleClientAction(Request $request)
{
$clientActions = $request->request->get('clientAction');
foreach ($clientActions as $clientAction)
{
switch ($clientAction['request'])
{
}
}
}
}