bee_hotel-1.x-dev/modules/beehotel_vertical/src/Controller/Vertical.php

modules/beehotel_vertical/src/Controller/Vertical.php
<?php

namespace Drupal\beehotel_vertical\Controller;

use CommerceGuys\Intl\Formatter\CurrencyFormatterInterface;
use Drupal\bee_hotel\Event;
use Drupal\beehotel_utils\BeeHotelUnit;
use Drupal\beehotel_vertical\BeehotelVertical;
use Drupal\Component\Plugin\PluginManagerInterface;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\OpenModalDialogCommand;
use Drupal\Core\Ajax\InvokeCommand;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Datetime\DrupalDateTime;
use Drupal\Core\Datetime\DateFormatterInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormBuilder;
use Drupal\Core\Link;
use Drupal\Core\Render\Markup;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Component\Utility\Unicode;
use Drupal\Core\Url;
use Drupal\user\Entity\Role;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Response;

/**
 * Provides route responses for BeeHotel module.
 */
#[AllowDynamicProperties]
class Vertical extends ControllerBase {

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

  /**
   * The form builder.
   *
   * @var \Drupal\Core\Form\FormBuilder
   */
  protected $formBuilder;

  /**
   * The date formatter service.
   *
   * @var \Drupal\Core\Datetime\DateFormatterInterface
   */
  protected $dateFormatter;

  /**
   * The entity type manager.
   *
   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * The currency formatter.
   *
   * @var \CommerceGuys\Intl\Formatter\CurrencyFormatterInterface
   */
  protected $currencyFormatter;

  /**
   * The config object.
   *
   * @var \Drupal\Core\Config\ConfigFactoryInterface
   */
  protected $config;

  /**
   * The bee hotel event.
   *
   * @var \Drupal\bee_hotel\Event
   */
  protected $event;

  /**
   * The bee hotel unit.
   *
   * @var \Drupal\beehotel_utils\BeeHotelUnit
   */
  protected $beehotelUnit;

  /**
   * The BeeHotel vertical time range.
   *
   * @var \Drupal\beehotel_vertical\BeehotelVertical
   */
  protected $beehotelVertical;

  /**
   * The manager to be used for instantiating plugins.
   *
   * @var \Drupal\Component\Plugin\PluginManagerInterface
   */
  protected $beehotelVerticalManager;

  /**
   * The plugin manager Interaface.
   *
   * @var \Drupal\Component\Plugin\PluginManagerInterface
   */
  protected $pluginManagerInterface;

  /**
   * Constructs a new Vertical object.
   *
   * @param \Drupal\Core\Render\RendererInterface $renderer
   *   The renderer service.
   * @param \Drupal\Core\Form\FormBuilder $form_builder
   *   The form builder.
   * @param \Drupal\Core\Datetime\DateFormatterInterface $date_formatter
   *   The date formatter service.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager.
   * @param \CommerceGuys\Intl\Formatter\CurrencyFormatterInterface $currency_formatter
   *   The currency formatter.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
   *   The config factory service.
   * @param Drupal\bee_hotel\Event $beehotel_event
   *   The Bee Hotel Event util.
   * @param \Drupal\beehotel_utils\BeeHotelUnit $bee_hotel_unit
   *   The BeeHotel Unit Utility.
   * @param \Drupal\beehotel_vertical\BeehotelVertical $beehotel_vertical
   *   The BeeHotel Vertical Class for features.
   * @param \Drupal\Component\Plugin\PluginManagerInterface $beehotelVerticalManager
   *   The BeeHotel Vertical manager.
   */
  public function __construct(RendererInterface $renderer, FormBuilder $form_builder, DateFormatterInterface $date_formatter, EntityTypeManagerInterface $entity_type_manager, CurrencyFormatterInterface $currency_formatter, ConfigFactoryInterface $config_factory, Event $beehotel_event, BeeHotelUnit $bee_hotel_unit, BeehotelVertical $beehotel_vertical, PluginManagerInterface $beehotelVerticalManager) {
    $this->renderer = $renderer;
    $this->formBuilder = $form_builder;
    $this->dateFormatter = $date_formatter;
    $this->entityTypeManager = $entity_type_manager;
    $this->currencyFormatter = $currency_formatter;
    $this->config = $config_factory;
    $this->event = $beehotel_event;
    $this->beehotelUnit = $bee_hotel_unit;
    $this->beehotelVertical = $beehotel_vertical;
    $this->pluginManagerInterface = $beehotelVerticalManager;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('renderer'),
      $container->get('form_builder'),
      $container->get('date.formatter'),
      $container->get('entity_type.manager'),
      $container->get('commerce_price.currency_formatter'),
      $container->get('config.factory'),
      $container->get('bee_hotel.event'),
      $container->get('beehotel_utils.beehotelunit'),
      $container->get('beehotel_vertical.beehotelvertical'),
      $container->get('plugin.manager.beehotel.pricealterator'),
    );
  }

  /**
   * Produce the table.
   */
  public function result($data) {
    $data['rows'] = $this->rows($data);
    $data['header'] = $this->header($data);

    $output['table'] = [
      '#type' => 'table',
      '#attributes' => [
        'class' => ['vertical-table'],
      ],
      '#prefix' => $this->displayTimeRangeForm(),
      '#header' => $data['header'],
      '#rows' => $data['rows'],
      '#empty' => $this->t('Your table is empty'),
    ];
    return $this->renderer->render($output);
  }

  /**
   * Produce the table header.
   */
  private function header_OLD($data) {
    $data['units'] = $this->beehotelUnit->getBeeHotelUnits($options = []);
    $header = [];
    $url = Url::fromRoute('beehotel_vertical.admin_settings', []);
    $link = Link::fromTextAndUrl($this->t('Day'), $url);
    $link = $link->toRenderable();
    $link['#attributes'] = ['class' => ['button', 'gear']];

    $header[] = Markup::create("<h3 class='settings'>" . $this->renderer->render($link) . "</strong>");

    if (!empty($data['units'])) {
      foreach ($data['units'] as $unit) {
        $url = Url::fromRoute('entity.node.edit_form', ['node' => $unit->Id()]);
        $unit_link = Link::fromTextAndUrl($unit->GetTitle(), $url);
        $unit_link = $unit_link->toRenderable();
        $unit_link['#attributes'] = ['class' => ['button', 'link']];
        $header[] = Markup::create("<h3 class='unit'>" . $this->renderer->render($unit_link) . "</h3>");
      }
    }
    $data['header'] = $header;
    return $data['header'];
  }

  /**
  * Produce the table header.
  */
  private function header($data) {
    $data['units'] = $this->beehotelUnit->getBeeHotelUnits($options = []);
    $header = [];

    $url = Url::fromRoute('beehotel_vertical.admin_settings', []);
    $link = Link::fromTextAndUrl($this->t('Day'), $url);
    $link = $link->toRenderable();
    $link['#attributes'] = ['class' => ['button', 'gear']];

    $header[] = Markup::create("<h3 class='settings'>" . $this->renderer->render($link) . "</h3>");

    $config = $this->config('beehotel_vertical.settings');
    $header_fields = $config->get('vertical.header_fields') ?: ['title'];
    $header_format = $config->get('vertical.header_format') ?: '[node:title]';

    if (!empty($data['units'])) {
      foreach ($data['units'] as $unit) {
        $header_text = $this->generateUnitHeader($unit, $header_format, $header_fields);

        $url = Url::fromRoute('entity.node.edit_form', ['node' => $unit->Id()]);

        $unit_link = [
          '#type' => 'link',
          '#title' => Markup::create($header_text),
          '#url' => $url,
          '#attributes' => ['class' => ['button', 'link']],
        ];

        $rendered_link = $this->renderer->render($unit_link);
        $header[] = Markup::create("<h3 class='unit'>" . $rendered_link . "</h3>");
      }
    }

    $data['header'] = $header;
    return $data['header'];
  }


  private function generateUnitHeader($unit, $header_format, $header_fields) {
    $header_text = $header_format;

    foreach ($header_fields as $field_name) {
      if ($field_name && $field_name !== '0') {
        $token = '[node:' . $field_name . ']';
        $value = $this->getFieldValue($unit, $field_name);

        if (!empty($value)) {
          $css_class = str_replace('_', '-', $field_name);
          $wrapped_value = '<span class="header-field header-field--' . $css_class . '">' . $value . '</span>';
          $header_text = str_replace($token, $wrapped_value, $header_text);
        } else {
          $header_text = str_replace($token, '', $header_text);
        }
      }
    }

    return trim($header_text) ?: $unit->getTitle(); // Fallback al titolo se vuoto
  }

  private function getFieldValue($node, $field_name) {
    if ($field_name == 'title') {
      return $node->getTitle();
    }

    if (!$node->hasField($field_name) || $node->get($field_name)->isEmpty()) {
      return '';
    }

    $field = $node->get($field_name);
    $field_type = $field->getFieldDefinition()->getType();

    switch ($field_type) {
      case 'entity_reference':
        $entity = $field->entity;
        return $entity ? $entity->label() : '';

      case 'datetime':
      case 'date':
        return $field->date ? $field->date->format('d/m/Y') : '';

      case 'boolean':
        return $field->value ? $this->t('Yes') : $this->t('No');

      case 'list_string':
        $value = $field->value;
        $allowed_values = $field->getFieldDefinition()->getSetting('allowed_values');
        return $allowed_values[$value] ?? $value;

      case 'text_with_summary':
      case 'text_long':
        return strip_tags($field->value);

      default:
        return $field->value;
    }
  }

  /**
   * Add result rows to data.
   *
   * @param array $data
   *   An array with report related data.
   */
  private function rows(array $data) {

    $data['rows'] = $tmp = [];
    $data['system_email'] = $this->config->get('system.site')->get('mail');

    $config = $this->config->get('beehotel_vertical.settings');
    $timejump = $config->get('vertical.timejump');
    $data['rowsnumber'] = $this->rowsNumber();

    $data['units'] = $this->beehotelUnit->getBeeHotelUnits($options = []);

    // Produce a row for every requested day.
    for ($i = 0; $i <= $data['rowsnumber']; $i++) {
      $data['day']['timestamp'] = (time() - $timejump) + ($i * (60 * 60 * 24));

      // @todo move this into a twig template
      $data['day']['formatted'] =
        "<span class='day-of-the-week'>" .
          $this->dateFormatter->format($data['day']['timestamp'], 'custom', 'D') . "
          </span>" .
        "<span class='day-month'>" .
         $this->dateFormatter->format($data['day']['timestamp'], 'custom', 'd M') . ",
        </span>" .
        "<span class='year'>" .
         $this->dateFormatter->format($data['day']['timestamp'], 'custom', 'Y') . "
        </span>";

      $data['day']['formatted'] =
        "<span class='day-of-the-week'>" .
          $this->dateFormatter->format($data['day']['timestamp'], 'custom', 'D') . "</span>" .
        "<span class='day-number'>" .
         $this->dateFormatter->format($data['day']['timestamp'], 'custom', 'd') . "</span>" .
        "<span class='month'>" .
         $this->dateFormatter->format($data['day']['timestamp'], 'custom', 'M') . "</span>";

      $data['day']['d'] = $this->dateFormatter->format($data['day']['timestamp'], 'custom', 'd');
      $data['day']['day'] = $this->dateFormatter->format($data['day']['timestamp'], 'custom', 'j');
      $data['day']['month'] = $this->dateFormatter->format($data['day']['timestamp'], 'custom', 'n');
      $data['day']['m'] = $this->dateFormatter->format($data['day']['timestamp'], 'custom', 'm');
      $data['day']['year'] = $this->dateFormatter->format($data['day']['timestamp'], 'custom', 'Y');

      // ISO 8601.
      $data['day']['today']['ISO8601'] = $data['day']['year'] . "-" . $data['day']['m'] . "-" . $data['day']['d'];
      $data['day']['day_of_elaboration']['ISO8601'] = $this->dateFormatter->format(time(), 'custom', 'Y-m-d');
      $data['day']['daybefore']['ISO8601'] = date('Y-m-d', strtotime("-1 day", strtotime($data['day']['today']['ISO8601'])));

      $elab_day = "";
      if ($data['day']['today']['ISO8601'] == $data['day']['day_of_elaboration']['ISO8601']) {
        $elab_day = "<span class='elab-day'>*</span>";
      }

      // Produce columns.
      $columns = [];

      // More this into day column.
      $this->getSeason($data);

      if (!isset($tmp['last_season']) || $tmp['last_season'] != $data['season']) {
        $tmp['season_label'] = $this->t("%label season", ['%label' => $data['season']]);
      }
      else {
        $tmp['season_label'] = "";
      }

      $col1 = [
        '#theme' => 'vertical_table_tr_td_col1',
        '#season' => $tmp['season_label'],
        '#season_class' => strtolower($data['season']),
        '#day' => ['#markup' => $data['day']['formatted']],
        '#elab_day' => ['#markup' => $elab_day],
      ];
      $columns[] = $this->renderer->render($col1);

      // Produce a column per unit.
      if (!empty($data['units'])) {
        foreach ($data['units'] as $unit_id => $unit) {
          $data['unit']['node'] = $unit;
          $data['unit']['nid'] = $unit_id;
          $data['unit']['bid'] = $unit->get("field_availability_daily")->target_id;
          $data = $this->cellContent($data);
          $columns[] = $this->renderer->render($data['cellcontent']);
        }
        $data['rows'][] = $columns;
      }

      $tmp['last_season'] = $data['season'];

    }

    return $data['rows'];
  }

  /**
   * Get time range for current report.
   */
  public function requestedRange() {

    $beeHotelUtil = \Drupal::service('beehotel_utils.beehotel');

    $data = [];

    $data['session'] = $beeHotelUtil->getSession();

    $data['tmp']['a'] = $data['session']->get('beehotel_requested_range');
    $data['tmp']['b'] = $this->defaultTimeRange();
    $data['range'] = $data['session']->get('beehotel_requested_range') ?? $this->defaultTimeRange();
    return $data['range'];
  }

  /**
   * Get rows number for current report.
   *
   * @return array
   *   Data array is enriched with rows number.
   */
  private function rowsNumber() {
    $data = [];
    $data['range'] = $this->RequestedRange();
    $data['ranges'] = $this->beehotelVertical->timeRanges();
    $data['rows'] = $data['ranges'][$data['range']]['rows'];
    return $data['rows'];
  }

  /**
   * Produce cell content(html + data)
   */
  private function cellContent($data) {
    $data = $this->event->typeofOccupacy($data);
    $data['cellcontent'] = $this->verticalTableTrTd($data, $options = []);
    return $data;
  }

  /**
   * Collect order data.
   */
  private function getOrderData($data, $options) {

    $tmp = [];
    $tmp['day'] = $options['day'];
    $tmp['unit'] = $data['unit']['bid'];
    $tmp['currentday_order_id'] = $this->getCurrentDayOrderId($data);
    $tmp['daybefore_order_id'] = $this->getDayBeforeOrderId($data, $tmp['unit']);
    $order = [];
    $order['total_price__number'] = 0;

    if (
      !empty($data['occupancy'][$tmp['day']][$tmp['unit']])
      && !empty($data['occupancy'][$tmp['day']][$tmp['unit']]['order'])
    ) {

      $order['order_id'] = $data['occupancy'][$tmp['day']][$tmp['unit']]['order']->order_id;
      $order['mail'] = $data['occupancy'][$tmp['day']][$tmp['unit']]['order']->mail;
      $order['total'] = $data['occupancy'][$tmp['day']][$tmp['unit']]['order']->total_price__number;
      $order['order_number'] = $data['occupancy'][$tmp['day']][$tmp['unit']]['order']->order_number;
      $order['order_balance'] = $this->getOrderBalance($order['order_id'], FALSE);

      // @todo Improve i18.
      $order['order_balance'] = number_format($order['order_balance'], 2, ',', ' ');

      // No text for same order on following days.
      $order['show_text'] = FALSE;

      if (isset($data['occupancy'][$tmp['day']][$tmp['unit']]['order'])) {
        if ($data['occupancy'][$tmp['day']][$tmp['unit']]['order']->object->billing_profile->entity) {
          $address = $data['occupancy'][$tmp['day']][$tmp['unit']]['order']->object->billing_profile->entity->address->getValue()[0];
          $order['name'] = $address['given_name'] ?? '';
          $order['surname'] = $address['surname'] ?? '';
        }
      }

      if ($tmp['currentday_order_id'] != $tmp['daybefore_order_id']) {
        $order['show_text'] = TRUE;
      }
      return $order;
    }
  }

  /**
   * Get the balance.
   */
  private function getOrderBalance($order_id, $formatted) {

    $order = $this->entityTypeManager
      ->getStorage('commerce_order')
      ->loadByProperties(['order_id' => $order_id]);

    $balance = $order[$order_id]->getBalance();

    if ($formatted == FALSE) {
      return $balance->getNumber();
    }
    else {
      return $this->currencyFormatter->format($balance->getNumber(), $balance->getCurrencyCode());
    }
  }

  /**
   * Theme cell content.
   */
  private function verticalTableTrTd($data, $options) {

    $beeHotelUtil = \Drupal::service('beehotel_utils.beehotel');
    $data['session'] = $beeHotelUtil->getSession();
    $beehotel_data = $data['session']->get('beehotel_data');

    // A.
    $a_attributes = $this->verticalTableTrTdAttributes($data, $options = ['div' => "a"]);
    $a_class = implode(" ", $a_attributes['class']);
    $a_content = $this->verticalOrderItem($data, $options = ['div' => "a"]);

    // Spacer.
    $spacer_attributes = $this->verticalTableTrTdAttributes($data, $options = ['div' => "spacer"]);
    $spacer_class = implode(" ", $spacer_attributes['class']);

    // B.
    $b_attributes = $this->verticalTableTrTdAttributes($data, $options = ['div' => "b"]);
    $b_class = implode(" ", $b_attributes['class']);
    $b_content = $this->verticalTableTrTdOrderOrState($data, $options = ['div' => "b"]);

    $td_classes = ['vertical-tr-td'];

    if (isset($data['tmp']['order']->is_blocking) && $data['tmp']['order']->is_blocking != 1) {
      $td_classes[] = 'not-blocking';
    }

    if (isset($beehotel_data) && isset($beehotel_data['not_blocking_order_events_by_date_nid'])) {

      $not_blocking_order_events_by_date_nid = $beehotel_data['not_blocking_order_events_by_date_nid'];

      if (isset($not_blocking_order_events_by_date_nid[$data['day']['daybefore']['ISO8601']][$data['unit']['nid']])) {
        $td_classes[] = 'not-blocking';
      }
    }

    if (isset($data['tmp']['order']->bat_event)) {
      $data['current_order_event_id'] = $data['tmp']['order']->bat_event->get('id')->value;
      if (isset($beehotel_data['not-blocking-order-events'][$data['current_order_event_id']])) {
        $td_classes[] = 'not-blocking';
      }
    }

    $output = [
      '#theme' => 'vertical_table_tr_td',
      '#a_class' => $a_class,
      '#a_content' => $a_content,
      '#b_class' => $b_class,
      '#b_content' => $b_content,
      '#td_classes' => $td_classes,
    ];

    // what's this?
    if (isset($a_content['#order_id']) && (int) $a_content['#order_id'] > 0) {
      $output['#spacer_class'] = $spacer_class;
    }
    return $output;
  }

  /**
   * What to expose in that cell.
   */
  private function verticalTableTrTdOrderOrState($data, $options = ['div' => "b"]) {
    $content = $this->verticalOrderItem($data, $options = ['div' => "b"]);

    if (!isset($content)) {
      $content = $this->verticalTableTrTdState($data, $options);
      // return "";
      return $content;
    }
    return $content;
  }

  /**
  * Generates price markup for vertical table cells with currency formatting.
  *
  * This function retrieves the price for a specific unit on a given day and
  * season, formats it with the currency symbol from the store, and displays
  * it with the currency symbol at the beginning and decimal parts as superscript.
  *
  * @param array $data
  *   An associative array containing:
  *   - 'unit': Unit data with 'bid' and 'nid'.
  *   - 'day': Day data with 'timestamp'.
  *   - 'season': Season identifier.
  * @param array $options
  *   Optional array of formatting options (currently unused but kept for
  *   compatibility with calling patterns).
  *
  * @return array
  *   A render array with '#markup' containing the formatted price HTML.
  *   Includes appropriate cache metadata for currency and store context.
  */
  private function verticalTableTrTdPrice(array $data, array $options): array {
    // Initialize the price render array.
    $price = [];

    // Retrieve the unit data using the business ID.
    $unit = $this->beehotelUnit->getUnitFromBid($data['unit']['bid']);

    // Get the price configuration settings.
    $config = $this->config->get('beehotel_pricealterator.settings');

    // Build the unique key for price lookup based on node, day, and season.
    $node_id = $data['unit']['nid'];
    $day = strtolower(date('D', (int) $data['day']['timestamp']));
    $season = $data['season'];
    $key = $node_id . "_" . $day . "_" . $season;

    // Check if the price key exists and has a value.
    if (isset($key) && ($price_value = $config->get($key))) {
      // Get the store from the unit to determine the currency.
      $store = $unit['store'];
      $currency_code = $store->getDefaultCurrencyCode();

      // Get the currency symbol using Commerce's currency formatter service.
      $currency_formatter = \Drupal::service('commerce_price.currency_formatter');
      // $symbol = $currency_formatter->getSymbol($currency_code, 'narrow');

      $currency = \Drupal\commerce_price\Entity\Currency::load($currency_code);

      if ($currency) {
          $symbol = $currency->getSymbol();
          // Se il simbolo è troppo lungo (es: "USD"), usa il codice
          if (mb_strlen($symbol) > 3) {
              $symbol = $currency_code;
          }
      } else {
          $symbol = $currency_code;
      }

      // Fallback to standard symbol if narrow symbol is not available.
      if (empty($symbol)) {
        $symbol = $currency_formatter->getSymbol($currency_code);
      }

      // Final fallback to currency code if no symbol is found.
      if (empty($symbol)) {
        $symbol = $currency_code;
      }

      // Format the price value to ensure exactly two decimal places.
      $formatted_price = number_format((float) $price_value, 2, '.', '');
      list($integer_part, $decimal_part) = explode('.', $formatted_price);

      // Only show decimal superscript if decimal part is not "00".
      $decimal_markup = '';
      if ($decimal_part !== '00') {
        $decimal_markup = '<sup class="price__decimal">'
          . htmlspecialchars($decimal_part, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8')
          . '</sup>';
      }

      // Build the HTML markup for the formatted price.
      $price_markup = sprintf(
        '<div class="price price--formatted" data-currency="%s" data-value="%s">
          <span class="price__currency">%s</span>
          <span class="price__integer">%s</span>
          %s
        </div>',
        htmlspecialchars($currency_code, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'),
        htmlspecialchars($price_value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'),
        htmlspecialchars($symbol, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'),
        htmlspecialchars($integer_part, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'),
        $decimal_markup
      );

      $price['#markup'] = $price_markup;

      // Add cache contexts for currency and language interface.
      $price['#cache']['contexts'][] = 'commerce_currency';
      $price['#cache']['contexts'][] = 'languages:language_interface';

      // Add store-specific cache tags for proper cache invalidation.
      if ($store) {
        $price['#cache']['tags'] = $store->getCacheTags();
      }
    }
    else {
      // Display a placeholder when no price is available.
      $price['#markup'] = '<div class="price price--empty" title="Price not available">-</div>';
      $price['#attributes']['class'][] = 'no-price';
    }

    return $price;
  }

  /**
   * Expose that cell state.
   */
  private function verticalTableTrTdState($data, $options) {

    /*
     * State of the day
     *
     * from default values at /admin/bat/events/state.
     * 1 Available #42f649 AV Not blocking Availability Daily.
     * 2 Not available #f04b4b N/A  Blocking Availability Daily.
     * 3 Booked #4b3cea BOOK Blocking Availability Daily.
     *
     */

    $data['has_full_access'] = \Drupal::currentUser()->hasPermission('beehotel_vertical_access_full_vertical');

    if ($data['has_full_access'] != TRUE) {
      return;
    }

    // Full content below.
    $card = $this->verticalTableTrTdCard($data, $options);

    if (isset($card['#id'])) {
      $build[]['link'] = [
        '#type' => 'link',
        '#title' => ['#markup' => $card['#markup']],
        '#attached' => ['library' => ['core/drupal.ajax']],
        '#attributes' => ['class' => ['use-ajax', 'state-link']],
        '#url' => Url::fromRoute('beehotel_vertical.ajax_link_callback', [
          'nojs' => 'nojs',
          'card_id' => $card['#id'],
        ]),
      ];
    }

    /*
     * Price of the day.
     *
     * Get today base price from weekly table
     */
    $price = $this->verticalTableTrTdPrice($data, $options);
    $url_object = Url::fromRoute('beehotel_pricealterator.mandatory.basepricetable', ['node' => $data['unit']['nid']]);


    // dump ($price);exit;

    $build[]['link'] = [
      '#type' => 'link',
      '#title' => ['#markup' => $price['#markup']],
      '#attributes' => ['class' => ['price-link']],
      '#url' => $url_object,
    ];

    return $build;
  }

  /**
   * Produce a flip card.
   *
   * With no reservation, we produce a flippable card to swap availability.
   */
  private function verticalTableTrTdCard($data, $options) {

    $state = $card = [];
    $state['id'] = $this->event->getNightState($data);
    $data['event']['id'] = $this->event->getNightEvent($data);
    $data['event']['lenght'] = "";
    if (isset($data['event']['id'])) {
      $data['event']['object'] = bat_event_load($data['event']['id'], $reset = FALSE);
      $data['event']['lenght'] = $this->event->getEventLength($data['event']['object'], ['output' => "timestamp"]);
    }

    if (isset($state['id'])) {
      /* Events with no reservation longer than
       * one day not supported by VertiCal UI.
       */
      if ($data['event']['lenght'] == 86400 || $data['event']['lenght'] == "") {

        if ($state['id'] == 1) {
          $state['card']['front']['label'] = $this->t("AV");
          $state['card']['front']['css'] = "green";
          $state['card']['back']['label'] = $this->t("NO");
          $state['card']['back']['css'] = "red";
        }
        else {
          $state['card']['back']['label'] = $this->t("AV");
          $state['card']['back']['css'] = "green";
          $state['card']['front']['label'] = $this->t("NO");
          $state['card']['front']['css'] = "red";
        }
      }
    }
    else {

      // ND = not defined.
      // @todo: replace "ND" with "NO" and "grey" with "green" after first click.
      $state['card']['front']['label'] = $this->t("ND");
      $state['card']['front']['css'] = "grey";
      $state['card']['back']['label'] = $this->t("AV");
      $state['card']['back']['css'] = "green";

    }

    $card['#id'] = implode("-", [
      "card",
      $data['unit']['bid'],
      $data['day']['year'],
      $data['day']['month'],
      $data['day']['day'],
    ]);

    $card['#markup'] = "";
    if (isset($state['card'])) {
      $card['#markup'] = "<div class=\"state state-card\">
        <div class=\"card\" id=\"" . $card['#id'] . "\">
          <div class=\"card__face card__face--front " . $state['card']['front']['css'] . "\">" . $state['card']['front']['label'] . "</div>
          <div class=\"card__face card__face--back " . $state['card']['back']['css'] . "\">" . $state['card']['back']['label'] . " </div>
        </div>
      </div>";
    }

    return $card;
  }

  /**
   * Callback for link example.
   *
   * Takes different logic paths based on whether Javascript was enabled.
   * If $type == 'ajax', it tells this function that ajax.js has rewritten
   * the URL and thus we are doing an AJAX and can return an array of commands.
   *
   * @param string $nojs
   *   Either 'ajax' or 'nojs. Type is simply the normal URL argument to this
   *   URL.
   * @param string $card_id
   *   Unique ID for cards.
   *
   * @return string|array
   *   If $type == 'ajax', returns an array of AJAX Commands.
   *   Otherwise, just returns the content, which will end up being a page.
   */
  public function ajaxLinkCallback($nojs = 'nojs', $card_id = "") {

    // Determine whether the request is coming from AJAX or not.
    if ($nojs == 'ajax') {
      // $output = $this->t("This is some content delivered via AJAX");
      $response = new AjaxResponse();

      // Create a $data array to feed the set function.
      $tmp = explode("-", $card_id);
      $data['day'] = [
        'year' => $tmp[2],
        'month' => $tmp[3],
        'day' => $tmp[4],
        'm' => str_pad($tmp[3], 2, '0', STR_PAD_LEFT),
        'd' => str_pad($tmp[4], 2, '0', STR_PAD_LEFT),
      ];

      $data['start_date'] = new DrupalDateTime(
        $data['day']['year'] . '-' . $data['day']['m'] . '-' . $data['day']['d'] . ' 00:00:00', 'UTC');

      $data['unit'] = ['bid' => $tmp[1]];

      $data['event_state'] = $this->event->getNightState($data);

      if ($this->event->getNightState($data) == 1) {
        $data['new_state'] = 2;
      }
      else {
        $data['new_state'] = 1;
      }

      $event = bat_event_create(['type' => 'availability_daily']);
      $event_dates = [
        'value' => $data['start_date']->format('Y-m-d\TH:i:00'),
        'end_value' => $data['start_date']->modify('+1 day')->format('Y-m-d\TH:i:00'),
      ];
      $event->set('event_dates', $event_dates);
      $event->set('event_state_reference', $data['new_state']);
      $event->set('event_bat_unit_reference', $data['unit']['bid']);
      $event->save();

      // A jQuery selector.
      $selector = "#" . $card_id;

      // The name of a jQuery method to invoke.
      $method = 'toggleClass';

      // (Optional) An array of arguments to pass to the method.
      $arguments = ['is-flipped'];
      $response->addCommand(new InvokeCommand($selector, $method, $arguments));

      return $response;
    }
    $response = new Response($this->t("Delivering via page load."));
    return $response;
  }

  /**
   * Extra info about cell content (maybe events).
   */
  private function verticalOrderItemExtra($data, $options) {
    $output = NULL;
    $output = $this->verticalOrderItemLastComment($data, $options);
    return $output;
  }

  /**
   * Get the string of the last comment for a given order.
   */
  private function verticalOrderItemLastComment($data, $options) {}

  /**
   * Build up order info.
   *
   *  Returns null when no reservation is fund.
   */
  private function verticalOrderItem($data, $opt) {

    $beeHotelUtil = \Drupal::service('beehotel_utils.beehotel');

    $order = [];
    $config = $this->config->get('beehotel_vertical.settings');

    $tmp = [];
    $tmp['unit'] = $data['unit']['bid'];
    $tmp['currentday_order_id'] = $this->getCurrentDayOrderId($data);
    $tmp['daybefore_order_id'] = $this->getDayBeforeOrderId($data, $tmp['unit']);

    if ($opt['div'] == "a") {
      $options = [
        'day' => $data['day']['daybefore']['ISO8601'],
        'div' => $opt['div'],
      ];
    }
    elseif ($opt['div'] == "b") {
      $options = [
        'day' => $data['day']['today']['ISO8601'],
        'div' => $opt['div'],
      ];
    }
    $order = $this->getOrderData($data, $options);
    $mail = $order['mail'] ?? '';
    $total = $order['total_price__number'] ?? 0;

    if ($order) {

      if ($config->get('vertical.warning.money') == 1) {
        $this->messenger()->addWarning($this->t('IMPORTANT: You have money to collect'));
      }

      $items = $this->entityTypeManager->getStorage('commerce_payment')->loadByProperties(['order_id' => $order['order_id']]);

      $payments = [];
      foreach ($items as $item) {
        if ($item->get('state')->value == "completed") {
          $payments[] = [
            'amount' => number_format($item->get('amount')->number, 2, ',', ' '),
            'payment_id' => $item->get('payment_id')->value,
            'date' => $this->dateFormatter->format($item->get('completed')->value, 'custom', 'd/m/Y'),
          ];
        }
      }

      // @todo add extra info/notes
      $order['extra'] = $this->verticalOrderItemExtra($data, $options);

      $order['classes'] = [
        'vertical-order-item',
      ];

      if (isset($data['tmp']['order']->is_blocking) && $data['tmp']['order']->is_blocking != 1) {
        $order['classes'] = 'not-blocking';
        $order['extra'] = $this->t('No-Blocking');
        $data['not_blocking_order_events_by_date_nid'][$data['day']['day_of_elaboration']['ISO8601']][$data['unit']['nid']] = $data['tmp']['event_id'];
        $beeHotelUtil->storeInSession($data);
      }

      $data['current_user'] = \Drupal::currentUser();
      $data['roles'] = $data['current_user']->getRoles();

      $data['entities'] = Role::loadMultiple($data['roles']);
      $data['role_permissions'] = [];
      foreach ($data['roles'] as $rid) {
        $data['role_permissions'][$rid] = $data['entities'][$rid]?->getPermissions();
      }

      $data['has_full_access'] = \Drupal::currentUser()->hasPermission('beehotel_vertical_access_full_vertical');

      if ($data['has_full_access'] != TRUE) {
        $order['show_text'] = "";
      }

      return [
        '#theme' => 'vertical_order_item',
        '#balance' => $order['order_balance'],
        '#classes' => $order['classes'],
        '#comments' => $data['tmp']['order']->last_comment ?? "",
        '#extra' => $order['extra'],
        '#show_text' => $order['show_text'],
        '#mail' => Unicode::truncate($mail, 8, FALSE, TRUE) ,
        '#name' => $order['name'] ?? '',
        '#surname' => $order['surname'] ?? '',
        '#order_id' => $order['order_id'] ?? '' ,
        '#order_number' => $order['order_number'] ?? '',
        '#payments' => $payments ?? '',
        '#total' => number_format($total),
      ];
    }
  }

  /**
   * Build up atributes for table cell.
   */
  private function verticalTableTrTdAttributes($data, $options) {

    $attributes = ['class' => []];
    $tmp['unit'] = $data['unit']['bid'];

    $tmp['currentday_order_id'] = $this->getCurrentDayOrderId($data);
    $tmp['daybefore_order_id'] = $this->getDayBeforeOrderId($data, $tmp['unit']);

    if (empty($data['occupancy'][$data['day']['daybefore']['ISO8601']])) {
      $data['first_row_of_the_table'] = TRUE;
    }

    // A.
    if ($options['div'] == "a") {

      // A1. Same reservation today and yestarday falls  into A2/A3.
      // A2. Guest is checking out today.
      if (!empty($tmp['daybefore_order_id']) && $tmp['currentday_order_id'] != $tmp['daybefore_order_id']) {
        $attributes['class'][] = 'reservation';
        $attributes['class'][] = 'checkout';
      }

      // A3. Today's reservation is the same as yestarday.
      if (!empty($tmp['daybefore_order_id']) && $tmp['currentday_order_id'] == $tmp['daybefore_order_id']) {
        $attributes['class'][] = 'reservation';
        $attributes['class'][] = 'same-as-daybefore';
      }

      // @todo A4. Show Checkout on the first day in table.
    }

    // Spacer.
    if ($options['div'] == "spacer") {
      /*
       * Spacer 1. We have some reservation today and today reservation
       * is not checking in.
       */
      if (!empty($data['occupancy']['current']) &&
          !empty($data['occupancy']['current']['order'])  &&
          $data['occupancy']['current']['order']->checkin != $data['day']['today']['ISO8601'] &&
          $data['occupancy']['current']['order']->checkout != $data['day']['today']['ISO8601']) {
        $attributes['class'][] = 'occupied';
      }

      if (isset($data['occupancy']['current']['event']['id'])) {
        $attributes['class'][] = 'no-display';
      }

    }

    // B.
    if ($options['div'] == "b") {

      // B1. We have a reservation today.
      if (!empty($data['occupancy']['current']) && !empty($data['occupancy']['current']['order'])) {

        $attributes['class'][] = 'reservation';

        // B1.1. Today reservation is checkin today.
        if ($data['occupancy']['current']['order']->checkin == $data['day']['today']['ISO8601']) {
          $attributes['class'][] = 'checkin';
        }
      }

    }
    return $attributes;
  }

  /**
   * Add order id to data.
   */
  private function getCurrentDayOrderId($data) {
    if (!empty($data)) {
      if (!empty($data['occupancy'])) {
        if (!empty($data['day']['today']['ISO8601'])) {
          if (!empty($data['occupancy'][$data['day']['today']['ISO8601']])) {
            if (!empty($data['unit']['bid'])) {
              if (!empty($data['occupancy'][$data['day']['today']['ISO8601']][$data['unit']['bid']]['order'])) {
                if (!empty($data['occupancy'][$data['day']['today']['ISO8601']][$data['unit']['bid']]['order'])) {
                  return $data['occupancy'][$data['day']['today']['ISO8601']][$data['unit']['bid']]['order']->order_id;
                }
              }
            }
          }
        }
      }
    }
  }

  /**
   * Get Day before.
   */
  private function getDayBeforeOrderId($data) {
    if (!empty($data)) {
      if (!empty($data['occupancy'])) {
        if (!empty($data['day']['daybefore']['ISO8601'])) {
          if (!empty($data['occupancy'][$data['day']['daybefore']['ISO8601']])) {
            if (!empty($data['unit']['bid'])) {
              if (!empty($data['occupancy'][$data['day']['daybefore']['ISO8601']][$data['unit']['bid']]['order'])) {
                $res = $data['occupancy'][$data['day']['daybefore']['ISO8601']][$data['unit']['bid']]['order']->order_id;
                return $res;
              }
            }
          }
        }
      }
    }
  }

  /**
   * TimeMachine data to step back in time.
   */
  public function page() {
    $result = $this->result($data = []);
    return [
      '#type' => 'markup',
      '#markup' => $result,
    ];
  }

  /**
   * Display a form.
   */
  private function displayTimeRangeForm() {
    $data = [];
    $data['class'] = "\Drupal\beehotel_vertical\Form\TimeRangeForm";
    $form = $this->formBuilder->getForm($data['class']);
    $htmlForm = $this->renderer->render($form);
    return $htmlForm;
  }

  /**
   * Give a default value.
   */
  private function defaultTimeRange() {
    return "cw";
  }

  /**
   * Callback for opening the modal form.
   */
  public function editState() {
    $response = new AjaxResponse();
    // Get the modal form using the form builder.
    $modal_form = $this->formBuilder->getForm('Drupal\beehotel_vertical\Form\BeeHotelVerticalEditEvent');
    // Add an AJAX command to open a modal dialog with the form as the content.
    $response->addCommand(new OpenModalDialogCommand('Edit Event', $modal_form, ['width' => '555']));
    return $response;
  }

  /**
   * Checks access for a specific request.
   *
   * @param \Drupal\Core\Session\AccountInterface $account
   *   Run access checks for this account.
   *
   * @return \Drupal\Core\Access\AccessResultInterface
   *   The access result.
   */
  public function access(AccountInterface $account) {

    $d = [];

    $d['access_vertical_basic'] = $account->hasPermission('access_vertical_basic');
    $d['access_vertical_full'] = $account->hasPermission('access_vertical_full');
    $d['access_vertical'] = NULL;

    if ($d['access_vertical_basic'] || $d['access_vertical_full']) {
      $d['access_vertical'] = TRUE;
    }

    return AccessResult::allowedIf($d['access_vertical']);

  }

  /**
   * Get data set as Season.
   *
   *  @todo move this into some Util class.
   */
  private function getSeason(&$data) {
    $timestamp = $data['day']['timestamp'];
    $plugin_id = "GetSeason";
    $getSeason = $this->pluginManagerInterface->createInstance($plugin_id, []);
    $getSeason->getThisDaySeasonFromInput($timestamp, $data);
  }

}

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

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