SyncService.php
Wed May 18 2022 13:48:20 GMT+0000 (Coordinated Universal Time)
Saved by @igor #drupal #pseudo-field #form-render
<?php namespace Drupal\bb2b_baselinker; use Drupal\commerce_order\Entity\OrderInterface; use Drupal\Core\Asset\AssetCollectionRendererInterface; use Drupal\Core\Asset\AssetResolverInterface; use Drupal\Core\Asset\AttachedAssets; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Database\Connection; use Drupal\Core\Entity\EntityTypeManagerInterface; use Drupal\Core\Extension\ExtensionPathResolver; use Drupal\Core\Extension\InfoParserInterface; use Drupal\Core\Extension\ThemeHandlerInterface; use Drupal\Core\File\FileSystemInterface; use Drupal\Core\File\FileUrlGeneratorInterface; use Drupal\Core\Logger\LoggerChannelFactoryInterface; use Drupal\Core\Queue\QueueFactory; use Drupal\Core\Queue\RequeueException; use Drupal\Core\Render\BubbleableMetadata; use Drupal\Core\Render\RenderContext; use Drupal\Core\Render\RendererInterface; use Drupal\Core\State\StateInterface; use Drupal\Core\Theme\ThemeInitializationInterface; use GuzzleHttp\Client; use GuzzleHttp\Exception\RequestException; use Drupal\Core\StringTranslation\StringTranslationTrait; use GuzzleHttp\RequestOptions; use Mpdf\Mpdf; use Mpdf\Output\Destination; use Symfony\Component\HttpFoundation\RequestStack; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * BaseLinker Sync Service. */ class SyncService { use StringTranslationTrait; /** * Get orders method name. * * @var string */ const GET_ORDERS_METHOD = 'getOrders'; /** * Last order confirmed time key. * * @var string */ const LAST_ORDER_CONFIRMED_TIME = 'bb2b_baselinker.last_order_confirmed_time'; /** * The entity type manager. * * @var \Drupal\Core\Entity\EntityTypeManagerInterface */ protected $entityTypeManager; /** * The config. * * @var \Drupal\Core\Config\ImmutableConfig */ protected $config; /** * The config for athenapdf. * * @var \Drupal\Core\Config\ImmutableConfig */ protected $athenapdfConfig; /** * The logger. * * @var \Drupal\Core\Logger\LoggerChannelInterface */ protected $logger; /** * The http client. * * @var \GuzzleHttp\Client */ protected $httpClient; /** * The order queue. * * @var \Drupal\Core\Queue\QueueFactory */ protected $getOrderQueue; /** * The state. * * @var \Drupal\Core\State\StateInterface */ protected $state; /** * The database connection. * * @var \Drupal\Core\Database\Connection */ protected $database; /** * The renderer. * * @var \Drupal\Core\Render\RendererInterface */ protected $renderer; /** * The theme handler. * * @var \Drupal\Core\Extension\ThemeHandlerInterface */ protected $themeHandler; /** * The extension path resolver. * * @var \Drupal\Core\Extension\ExtensionPathResolver */ protected $extensionPathResolver; /** * The info parser. * * @var \Drupal\Core\Extension\InfoParserInterface */ protected $infoParser; /** * The theme initialization. * * @var \Drupal\Core\Theme\ThemeInitializationInterface */ protected $themeInitialization; /** * The asset resolver. * * @var \Drupal\Core\Asset\AssetResolverInterface */ protected $assetResolver; /** * The CSS collection renderer. * * @var \Drupal\Core\Asset\AssetCollectionRendererInterface */ protected $cssCollectionRenderer; /** * The current request. * * @var \Symfony\Component\HttpFoundation\Request */ protected $request; /** * File system. * * @var \Drupal\Core\File\FileSystemInterface */ protected $fileSystem; /** * The file URL generator. * * @var \Drupal\Core\File\FileUrlGeneratorInterface */ protected $fileUrlGenerator; /** * Constructs a new SyncService object. * * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager * The entity type manager. * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory * The configuration factory. * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory_athenapdf * The configuration factory for athenapdf api. * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory * The logger. * @param \GuzzleHttp\Client $http_client * The http client. * @param \Drupal\Core\Queue\QueueFactory $queue_factory * The queue factory. * @param \Drupal\Core\State\StateInterface $state * The state. * @param \Drupal\Core\Database\Connection $database * The database connection. * @param \Drupal\Core\Render\RendererInterface $renderer * The renderer. * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler * The theme handler. * @param \Drupal\Core\Extension\ExtensionPathResolver $extension_path_resolver * The extension path resolver. * @param \Drupal\Core\Extension\InfoParserInterface $info_parser * The info parser. * @param \Drupal\Core\Theme\ThemeInitializationInterface $theme_initialization * The theme initialization. * @param \Drupal\Core\Asset\AssetResolverInterface $asset_resolver * The asset resolver. * @param \Drupal\Core\Asset\AssetCollectionRendererInterface $css_collection_renderer * The CSS collection renderer. * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack * The request stack. * @param \Drupal\Core\File\FileSystemInterface $fileSystem * File System. * @param \Drupal\Core\File\FileUrlGeneratorInterface $file_url_generator * The file URL generator. */ public function __construct( EntityTypeManagerInterface $entity_type_manager, ConfigFactoryInterface $config_factory, ConfigFactoryInterface $config_factory_athenapdf, LoggerChannelFactoryInterface $logger_factory, Client $http_client, QueueFactory $queue_factory, StateInterface $state, Connection $database, RendererInterface $renderer, ThemeHandlerInterface $theme_handler, ExtensionPathResolver $extension_path_resolver, InfoParserInterface $info_parser, ThemeInitializationInterface $theme_initialization, AssetResolverInterface $asset_resolver, AssetCollectionRendererInterface $css_collection_renderer, RequestStack $request_stack, FileSystemInterface $fileSystem, FileUrlGeneratorInterface $file_url_generator ) { $this->entityTypeManager = $entity_type_manager; $this->config = $config_factory->get('bb2b_baselinker.settings'); $this->athenapdfConfig = $config_factory_athenapdf->get('athenapdf_api.settings'); $this->logger = $logger_factory->get('bb2b_baselinker'); $this->httpClient = $http_client; $this->getOrderQueue = $queue_factory->get('get_orders_from_baselinker'); $this->addProductQueue = $queue_factory->get('bb2b_baselinker_addproduct'); $this->state = $state; $this->database = $database; $this->renderer = $renderer; $this->themeHandler = $theme_handler; $this->extensionPathResolver = $extension_path_resolver; $this->infoParser = $info_parser; $this->themeInitialization = $theme_initialization; $this->assetResolver = $asset_resolver; $this->cssCollectionRenderer = $css_collection_renderer; $this->request = $request_stack->getCurrentRequest(); $this->fileSystem = $fileSystem; $this->fileUrlGenerator = $file_url_generator; } /** * Get orders from BaseLinker API. * * A maximum of 100 orders are returned at a time. */ public function getOrders() { // The buyer's data are deleted after 30 days of shipment. $date_confirmed_from = $this->state ->get(self::LAST_ORDER_CONFIRMED_TIME, strtotime('-30 days')); $parameters = [ 'date_confirmed_from' => $date_confirmed_from, ]; $items = $this->getItems(self::GET_ORDERS_METHOD, $parameters); $api_orders = $items['orders'] ?? []; if ($api_orders && $items['status'] === 'SUCCESS') { foreach ($api_orders as $order) { if ($order['delivery_fullname'] !== '-') { $query = $this->database->select('queue', 'q'); $query->condition('name', 'get_orders_from_baselinker'); $query->condition('data', serialize($order)); $query->fields('q', ['item_id']); // Skip same queue items. if (empty($query->execute()->fetchAll())) { $this->getOrderQueue->createItem($order); } } } } } /** * Sync products to BaseLinker API. */ public function addProduct() { // BaseLinker API credentials. $data = []; $data['token'] = $this->config->get('token'); $data['connected_domain'] = $this->config->get('connected_domain'); // Get products that we want to integrate in BaseLinker. $products = $this->entityTypeManager ->getStorage('commerce_product') ->loadMultiple(); // Send request to BaseLinker. foreach ($products as $product) { $data['product'] = $product; $params = [ 'storage_id' => '4721', 'product_id' => $data['product']->id(), ]; try { $response = $this->httpClient->request('post', $data['connected_domain'], [ 'headers' => [ 'Content-Type' => 'application/x-www-form-urlencoded', 'X-BLToken' => $data['token'], ], 'form_params' => [ 'method' => 'addProduct', 'parameters' => $params, ] ]); } catch (RequeueException $e) { $this->logger->debug('BaseLinker request failed with the message: @message', [ '@message' => $e->getMessage(), ]); } $response = json_decode($response->getStatusCode()); } } /** * Request to the connected domain. * * @param string $method * Which method is used for request. * @param array $parameters * Order parameters for request. * * @return array * Return items. */ public function getItems(string $method, array $parameters = []): array { $token = $this->config->get('token'); $connected_domain = $this->config->get('connected_domain'); $base_parameters = []; if ($method === self::GET_ORDERS_METHOD) { $base_parameters = [ 'get_unconfirmed_orders' => FALSE, ]; } $parameters = array_merge($base_parameters, $parameters); try { $response = $this->httpClient->request('post', $connected_domain, [ 'headers' => [ 'Content-Type' => 'application/x-www-form-urlencoded', 'X-BLToken' => $token, ], 'form_params' => [ 'method' => $method, 'parameters' => json_encode($parameters), ], 'verify' => FALSE, 'http_errors' => FALSE, ]); } catch (RequestException $e) { $this->logger->debug('BaseLinker request failed with the message: @message', [ '@message' => $e->getMessage(), ]); } $this->logger->debug('BaseLinker API response: <pre>@body</pre>', [ '@body' => $response ? $response->getBody()->getContents() : '', ]); $items = json_decode($response->getBody(), TRUE); return $items ?: []; } /** * Generate PDF file. * * @param \Drupal\commerce_order\Entity\OrderInterface $order * Order Interface. * @param bool $is_html * Html. * @param bool $return_with_file * Return with file. * * @return string|\Symfony\Component\HttpFoundation\Response|null * Retrun response. * * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException * @throws \Drupal\Core\Theme\MissingThemeDependencyException * @throws \GuzzleHttp\Exception\GuzzleException */ public function generatePdf(OrderInterface $order, bool $is_html = FALSE, bool $return_with_file = FALSE) { $renderer = $this->renderer; $theme = $this->themeHandler->getDefault(); $theme_path = $this->extensionPathResolver->getPath('theme', $theme); $theme_info = $this->infoParser->parse("$theme_path/$theme.info.yml"); $pdf_title = $this->t('Baselinker'); $applying_guidelines = NULL; $order_items = []; foreach ($order->getItems() as $order_item) { $title = $order_item->get('title')->value; $sku = substr($title, strpos($title, ":") + 1); $shipping = $this->entityTypeManager->getStorage('commerce_shipment') ->load($order->get('shipments')[0]->target_id); // Get asin from product variation. $product = $this->entityTypeManager->getStorage('commerce_product')->load($order_item->getPurchasedEntityId()); if ($product == NULL) { continue; } $order_items[] = [ 'quantity' => $order_item->get('quantity')->value, 'title' => $title, 'price' => $order_item->get('total_price')->number, 'sku' => $sku, 'shipping' => $shipping->get('amount')[0]->number, ]; } $save_order = [ 'delivery_fullname' => $order->getBillingProfile()->get('address')[0]->given_name . ' ' . $order->getBillingProfile()->get('address')->family_name, 'delivery_address' => $order->getBillingProfile()->get('address')->address_line1, 'delivery_postal_code' => $order->getBillingProfile()->get('address')->postal_code, 'delivery_city' => $order->getBillingProfile()->get('address')[0]->locality, 'delivery_country' => $order->getBillingProfile()->get('address')[0]->country_code, 'order_number' => $order->getOrderNumber(), 'date_add' => date('D., d.M.Y', $order->get('created')->value), 'order_items' => $order_items, ]; $currency = $this->entityTypeManager->getStorage('commerce_currency')->load($order->getTotalPrice()->getCurrencyCode()); $build = [ '#theme' => 'belmilb2b_baselinker_pdf', '#order_total_paid' => $order->getTotalPrice()->getNumber(), '#pdf_title' => $pdf_title, '#title' => 'test', '#applying_guidelines' => $applying_guidelines, '#order' => $save_order, '#attached' => [ 'library' => $theme_info['libraries'], ], '#currency' => $currency->getSymbol(), ]; $logo = $this->themeInitialization->getActiveThemeByName($theme) ->getLogo(); if (strpos($logo, '.svg')) { $logo = str_replace('.svg', '.png', $logo); } $build['#site_logo'] = $logo; $context = new RenderContext(); $css_assets = $this->assetResolver->getCssAssets(AttachedAssets::createFromRenderArray($build), TRUE); $rendered_css = $this->cssCollectionRenderer->render($css_assets); /** @var \Drupal\Core\Cache\CacheableDependencyInterface $rendered_css_build */ $rendered_css_build = $this->renderer->executeInRenderContext($context, function () use ($rendered_css, $renderer) { return $renderer->render($rendered_css); }); if (!$context->isEmpty()) { $bubbleable_metadata = $context->pop(); BubbleableMetadata::createFromObject($rendered_css_build) ->merge($bubbleable_metadata); } $build['#rendered_css'] = $rendered_css_build; /** @var \Drupal\Core\Cache\CacheableDependencyInterface $html */ $html = $this->renderer->executeInRenderContext($context, function () use ($build, $renderer) { return $renderer->render($build); }); if (!$context->isEmpty()) { $bubbleable_metadata = $context->pop(); BubbleableMetadata::createFromObject($html) ->merge($bubbleable_metadata); } $host = $this->request->getSchemeAndHttpHost(); if (!$is_html && strpos($host, 'docker') !== FALSE) { $host = 'http://nginx'; } $media_string = '<link rel="stylesheet" media="all" href=""'; $css_string = '<link rel="stylesheet" type="text/css" href=""'; $html = str_replace('<img src=""', '<img src=""' . $host, $html); $html = str_replace($media_string, $media_string . $host, $html); $html = str_replace($css_string, $css_string . $host, $html); if (!$is_html) { $destination_dir = 'public://baselinker_order_pdf'; $destination = $destination_dir . '/baselinker-pdf- ' . $save_order['order_number'] . '.pdf'; $this->fileSystem->prepareDirectory($destination_dir, FileSystemInterface::CREATE_DIRECTORY || FileSystemInterface::MODIFY_PERMISSIONS); $pdf_data = $this->httpClient ->request('POST', $this->athenapdfConfig->get('endpoint') . '?auth=' . $this->athenapdfConfig->get('auth_key') . '&ext=html', [ RequestOptions::MULTIPART => [ [ 'name' => 'file', 'contents' => $html, 'filename' => 'input.html', 'headers' => [ 'Content-Type' => 'text/html; charset=utf-8', ], ], ], RequestOptions::SINK => $destination, ])->getBody()->getContents(); $pdf_file = $this->fileSystem ->saveData($pdf_data, $destination, FileSystemInterface::EXISTS_REPLACE); $order->field_documents->target_id = $pdf_file; $order->save(); if ($return_with_file) { return $pdf_file ? $this->fileUrlGenerator->generateAbsoluteString($pdf_file) : NULL; } else { $this->setPDFTitle($destination, $pdf_title); $response = new Response(); $response->headers->set('Content-Type', 'application/pdf'); $response->headers->set('Content-Disposition', 'inline; filename="' . $pdf_title . '.pdf"'); $response->setContent($pdf_data); return $response; } } else { return new Response($html); } throw new NotFoundHttpException(); } /** * Set PDF title. * * @param string $outputFilePath * Output file path. * @param string $title * Title. */ protected function setPdfTitle(string $outputFilePath, string $title) { try { $mpdf = new Mpdf(['tempDir' => $this->fileSystem->getTempDirectory()]); $page_count = $mpdf->setSourceFile($outputFilePath); for ($i = 1; $i <= $page_count; $i++) { $import_page = $mpdf->ImportPage($i); $mpdf->UseTemplate($import_page); if ($i < $page_count) { $mpdf->AddPage(); } } $mpdf->setTitle($title); $mpdf->Output($outputFilePath, Destination::FILE); } catch (\Exception $e) { // Do nothing. } } }
Comments