contacts_events-8.x-1.x-dev/modules/printing/src/Controller/TicketPrintingController.php

modules/printing/src/Controller/TicketPrintingController.php
<?php

namespace Drupal\contacts_events_printing\Controller;

use Drupal\commerce_order\Entity\OrderInterface;
use Drupal\Component\Utility\Crypt;
use Drupal\contacts_events\Entity\EventClass;
use Drupal\contacts_events\Entity\EventInterface;
use Drupal\contacts_events\Entity\TicketInterface;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Access\AccessResultInterface;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\PrivateKey;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Site\Settings;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\BinaryFileResponse;

/**
 * Booking checkout process controller.
 */
class TicketPrintingController extends ControllerBase {

  /**
   * The Event Class entity storage.
   *
   * @var \Drupal\Core\Entity\EntityStorageInterface
   */
  protected $classStorage;

  /**
   * An array of class labels.
   *
   * @var string[]
   */
  protected $classLabels;

  /**
   * Private Key service.
   *
   * @var \Drupal\Core\PrivateKey
   */
  protected $privateKey;

  /**
   * Renderer service.
   *
   * @var \Drupal\Core\Render\RendererInterface
   */
  protected $renderer;

  /**
   * Constructs a TicketPrintingController object.
   *
   * @param \Drupal\Core\Entity\EntityStorageInterface $class_storage
   *   The Event Class entity storage class.
   * @param \Drupal\Core\PrivateKey $private_key
   *   Private Key service.
   * @param \Drupal\Core\Render\RendererInterface $renderer
   *   Renderer service.
   */
  public function __construct(EntityStorageInterface $class_storage, PrivateKey $private_key, RendererInterface $renderer) {
    $this->classStorage = $class_storage;
    $this->privateKey = $private_key;
    $this->renderer = $renderer;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('entity_type.manager')->getStorage('contacts_events_class'),
      $container->get('private_key'),
      $container->get('renderer')
    );
  }

  /**
   * Generate a token for access checking to ticket render.
   *
   * This token needs to be reproducible across different session.
   */
  public function generateRenderAccessToken($type, $id): string {
    // Build the value from our arguments.
    $value = 'cbtp:' . $type;
    if ($id) {
      $value .= ':' . $id;
    }

    return Crypt::hmacBase64($value, $this->privateKey->get() . Settings::getHashSalt());
  }

  /**
   * A page for viewing the ticket and printing it if desired.
   */
  public function viewTicket(TicketInterface $contacts_ticket): array {
    $content = [];
    $content['#attached']['library'][] = 'contacts_events_printing/ticket_viewing';
    $content['content'] = $this->buildTicketOutput($contacts_ticket);
    return $content;
  }

  /**
   * A page for rendering the ticket contents for downloading as pdf.
   *
   * This page must be accessible by anonymous users with a valid token.
   */
  public function renderTicket(TicketInterface $contacts_ticket): array {
    $content = [];
    $content['#attached']['library'][] = 'contacts_events_printing/ticket_printing';
    $content['content'] = $this->buildTicketOutput($contacts_ticket);
    return $content;
  }

  /**
   * A page for rendering the ticket contents for downloading as pdf.
   *
   * This page must be accessible by anonymous users with a valid token.
   */
  public function renderBooking(OrderInterface $commerce_order): array {
    $content = [];
    $content['#attached']['library'][] = 'contacts_events_printing/ticket_printing';
    $content['content'] = [];

    // @todo Add a booking summary page.
    // Add each ticket to the booking.
    foreach ($commerce_order->get('order_items') as $order_item_field_item) {
      /** @var \Drupal\commerce_order\Entity\OrderItemInterface $order_item */
      $order_item = $order_item_field_item->entity;

      if ($order_item->bundle() !== 'contacts_ticket') {
        continue;
      }

      $allowed_states = ['paid_in_full'];
      if (!in_array($order_item->get('state')->first()->value, $allowed_states)) {
        continue;
      }

      /** @var \Drupal\contacts_events\Entity\TicketInterface $contacts_ticket */
      $contacts_ticket = $order_item->getPurchasedEntity();
      $content['content'][] = $this->buildTicketOutput($contacts_ticket);
    }

    return $content;
  }

  /**
   * Check event configuration for printing settings.
   *
   * @param \Drupal\contacts_events\Entity\EventInterface $event
   *   The event to be checked.
   *
   * @return \Drupal\Core\Access\AccessResultInterface
   *   Access result.
   */
  public static function isEventPrintingEnabled(EventInterface $event): AccessResultInterface {
    if (!$event->getSetting('printing.enabled')) {
      return AccessResult::forbidden('Printing is not allowed for this event.')
        ->addCacheableDependency($event);
    }

    return AccessResult::neutral();
  }

  /**
   * Access control handler for checking a valid token was supplied.
   */
  public function renderTicketAccess(TicketInterface $contacts_ticket, string $token) {
    /** @var \Drupal\contacts_events\Entity\EventInterface $event */
    $event = $contacts_ticket->getEvent();
    $printing_enabled = static::isEventPrintingEnabled($event);
    if ($printing_enabled->isForbidden()) {
      return $printing_enabled;
    }

    $check = $this->generateRenderAccessToken('ticket', $contacts_ticket->id());
    return AccessResult::allowedIf(hash_equals($check, $token));
  }

  /**
   * Access control handler for checking a valid token was supplied.
   */
  public function renderBookingAccess(OrderInterface $commerce_order, string $token) {
    /** @var \Drupal\contacts_events\Entity\EventInterface $event */
    $event = $commerce_order->get('event')->entity;
    $printing_enabled = static::isEventPrintingEnabled($event);
    if ($printing_enabled->isForbidden()) {
      return $printing_enabled;
    }

    $check = $this->generateRenderAccessToken('booking', $commerce_order->id());
    return AccessResult::allowedIf(hash_equals($check, $token));
  }

  /**
   * Access control handler checking if user can download or print their ticket.
   */
  public function ticketAccess(AccountInterface $account, TicketInterface $contacts_ticket) {
    /** @var \Drupal\contacts_events\Entity\EventInterface $event */
    $event = $contacts_ticket->getEvent();
    $printing_enabled = static::isEventPrintingEnabled($event);
    if ($printing_enabled->isForbidden()) {
      return $printing_enabled;
    }

    $allowed_states = ['paid_in_full'];
    $order_item = $contacts_ticket->getOrderItem();
    if (!in_array($order_item->get('state')->first()->value, $allowed_states)) {
      return AccessResult::forbidden('This ticket is not in a state that can be printed.')->addCacheableDependency($order_item);
    }

    // Allow if we have the manage bookings permission.
    if ($account->hasPermission('can manage bookings for contacts_events')) {
      return AccessResult::allowed()
        ->addCacheContexts(['user.permissions']);
    }

    if ($account->isAuthenticated() && $account->id() == $contacts_ticket->getTicketHolderId()) {
      return AccessResult::allowed()
        ->addCacheContexts(['user']);
    }

    if ($account->isAuthenticated() && $account->id() == $contacts_ticket->getBooking()->getCustomerId()) {
      return AccessResult::allowed()
        ->addCacheContexts(['user']);
    }

    return AccessResult::neutral();
  }

  /**
   * Access control handler checking if user can download/print a whole booking.
   */
  public function bookingAccess(AccountInterface $account, OrderInterface $commerce_order) {
    if ($commerce_order->bundle() !== 'contacts_booking') {
      return AccessResult::forbidden('Printing is not allowed for non booking orders.')->addCacheableDependency($commerce_order);
    }

    /** @var \Drupal\contacts_events\Entity\EventInterface $event */
    $event = $commerce_order->get('event')->entity;
    $printing_enabled = static::isEventPrintingEnabled($event);
    if ($printing_enabled->isForbidden()) {
      return $printing_enabled;
    }

    $disallowed_states = ['draft'];
    if (in_array($commerce_order->get('state')->first()->value, $disallowed_states)) {
      return AccessResult::forbidden('This ticket is not in a state that can be printed.')->addCacheableDependency($commerce_order);
    }

    // Allow if we have the manage bookings permission.
    if ($account->hasPermission('can manage bookings for contacts_events')) {
      return AccessResult::allowed()
        ->addCacheContexts(['user.permissions']);
    }

    if ($account->isAuthenticated() && $account->id() == $commerce_order->getCustomerId()) {
      return AccessResult::allowed()
        ->addCacheContexts(['user']);
    }

    // @todo Check group booking manager.
    return AccessResult::neutral();
  }

  /**
   * Page callback for downloading a "printed" ticket in pdf form.
   */
  public function printTicket(TicketInterface $contacts_ticket) {
    $ticket_id = $contacts_ticket->id();
    $token = $this->generateRenderAccessToken('ticket', $ticket_id);

    $render_path = Url::fromUserInput('/booking/ticket/' . $ticket_id . '/' . $token . '/render', [
      'absolute' => TRUE,
    ])->toString();

    $return = $this->generateDownload($render_path, DRUPAL_ROOT . '/../tickets/ticket-' . $ticket_id . '.pdf', DRUPAL_ROOT . '/../tickets/print_log');

    if (!$return) {
      $this->messenger()->addError($this->t('There was a problem printing ticket %ticket: please contact an administrator.', [
        '%ticket' => $ticket_id,
      ]));

      return [];
    }

    return $return;
  }

  /**
   * Page callback for downloading a "printed" ticket in pdf form.
   */
  public function printBooking(OrderInterface $commerce_order) {
    $order_id = $commerce_order->id();
    $token = $this->generateRenderAccessToken('booking', $order_id);

    $render_path = Url::fromUserInput('/booking/order/' . $order_id . '/' . $token . '/render', [
      'absolute' => TRUE,
    ])->toString();

    $return = $this->generateDownload($render_path, DRUPAL_ROOT . '/../tickets/booking-' . $order_id . '.pdf', DRUPAL_ROOT . '/../tickets/print_log');

    if (!$return) {
      $this->messenger()->addError($this->t('There was a problem printing booking %ticket: please contact an administrator.', [
        '%ticket' => $order_id,
      ]));

      return [];
    }

    return $return;
  }

  /**
   * Builds the render array for the ticket to be shown.
   */
  private function buildTicketOutput(TicketInterface $contacts_ticket): array {
    $booking = $contacts_ticket->getBooking();
    $event = $contacts_ticket->getEvent();

    $given_name = $contacts_ticket->get('name')->first()->given;

    // Check if ticket is full or segment.
    $text_field_name = 'ticket_text';
    if ($contacts_ticket->hasField('segments') && !$contacts_ticket->get('segments')->isEmpty()) {
      $text_field_name = 'ticket_part_text';
    }

    // Make sure the event has the correct text field.
    $letter_text = '';
    if ($event->hasField($text_field_name)) {
      $letter_text = $event->get($text_field_name)->view([
        'label' => 'hidden',
      ]);
      $letter_text = $this->renderer->render($letter_text);
      $letter_text = str_replace('[name:given]', $given_name, $letter_text);
    }

    $address = '';
    if (!$booking->get('shipping_profile')->isEmpty()) {
      $address = $booking->shipping_profile->entity->address->view([
        'label' => 'hidden',
      ]);
    }

    return [
      '#theme' => 'ticket_printing_content',
      '#ticket' => $contacts_ticket,
      '#contact_id' => $contacts_ticket->get('contact')->target_id ?? '',
      '#order_id' => $booking->id(),
      '#ticket_id' => $contacts_ticket->id(),
      '#address' => $address,
      '#first_name' => $contacts_ticket->get('name')->first()->given,
      '#name' => $contacts_ticket->getName(),
      '#event_name' => $event->label(),
      '#event_date' => $event->get('date')->view([
        'label' => 'hidden',
        'type' => 'daterange_custom',
        'settings' => [
          'date_format' => 'l, F d, Y',
          'separator' => 'to',
        ],
      ]),
      '#booking_reference' => $booking->getOrderNumber(),
      '#ticket_class' => $this->getClassLabel($contacts_ticket),
      '#letter_text' => ['#markup' => $letter_text],
    ];
  }

  /**
   * Get the event class label for a ticket.
   *
   * @param \Drupal\contacts_events\Entity\TicketInterface $ticket
   *   The ticket to retrieve for.
   *
   * @return string
   *   The class label.
   */
  protected function getClassLabel(TicketInterface $ticket) {
    if (!isset($this->classLabels)) {
      $this->classLabels = array_map(function (EventClass $class) {
        return $class->label();
      }, $this->classStorage->loadMultiple());
    }
    $class = $ticket->getMappedPrice()['class'];
    return $this->classLabels[$class] ?? '-';
  }

  /**
   * Put the generated pdf file to the browser and exit.
   */
  private function generateDownload($render_path, $file_path, $log_file) {
    // Make sure the directory exists.
    $directory = dirname($file_path);
    if (!is_dir($directory)) {
      mkdir($directory);
    }

    $options = [
      // Quality adjustments.
      '--disable-smart-shrinking',
      '--print-media-type',
      // Disable borders.
      '-T 0',
      '-R 0',
      '-B 0',
      '-L 0',
      // Disable outlines.
      '--no-outline',
      '--outline-depth 0',
    ];
    $options = implode(' ', $options);
    $script = "/usr/local/bin/wkhtmltopdf {$options} \"{$render_path}\" \"{$file_path}\" &> \"{$log_file}\"";
    $this->executePdfScript($script);

    if (!file_exists($file_path)) {
      $message = 'Failed to generate ticket PDF. Render path: %render_path. File path: %file_path. For more information, please see the log file %log_file';
      $vars = [
        '%render_path' => $render_path,
        '%file_path' => $file_path,
        '%log_file' => $log_file,
      ];

      $this->getLogger('contacts_events_printing')->error($message, $vars);
    }
    else {
      return new BinaryFileResponse($file_path, 200, [
        'Content-Description' => 'File Transfer',
        'Content-Type' => 'application/octet-stream',
        'Content-Disposition' => 'attachment; filename=' . basename($file_path),
        'Content-Transfer-Encoding' => 'binary',
        'Expires' => 0,
        'Cache-Control' => 'must-revalidate, post-check=0, pre-check=0',
        'Pragma' => 'public',
        'Content-Length' => filesize($file_path),
      ]);
    }
  }

  /**
   * Executes the pdf generation command and returns the result.
   */
  private function executePdfScript($command, &$output = [], &$retval = NULL) {
    // Store our path statically as it's not prone to changes.
    $path = drupal_static(__FUNCTION__);
    if (!isset($path)) {
      // Attempt a reliable method.
      if (function_exists('posix_getuid')) {
        $info = posix_getpwuid(posix_getuid());
        $path = $info['dir'];
      }
      // Otherwise fall back on something not as reliable.
      else {
        $path = exec('echo $HOME');
      }
    }

    // Correct our path if necessary.
    $orig = getenv('HOME');
    if ($orig != $path) {
      putenv('HOME=' . $path);
    }

    // Pass on to exec().
    $return = exec($command, $output, $retval);

    // Revert if overridden.
    if ($orig != $path) {
      putenv('HOME=' . $orig);
    }

    return $return;
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc