contacts_events-8.x-1.x-dev/modules/village_allocation/src/Plugin/rest/resource/VillageAllocationBookingsResource.php

modules/village_allocation/src/Plugin/rest/resource/VillageAllocationBookingsResource.php
<?php

namespace Drupal\village_allocation\Plugin\rest\resource;

use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Render\RenderContext;
use Drupal\Core\Render\RendererInterface;
use Drupal\facets\FacetManager\DefaultFacetManager;
use Drupal\rest\Plugin\ResourceBase;
use Drupal\rest\ResourceResponse;
use Drupal\search_api\Utility\QueryHelper as SearchApiQueryHelper;
use Drupal\views\ResultRow;
use Drupal\views\Views;
use Drupal\village_allocation\VillageAllocationGroup;
use Drupal\village_allocation\VillageAllocationQueries;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;

/**
 * Provides bookings list.
 *
 * @RestResource(
 *   id = "village_allocation_bookings",
 *   label = @Translation("Village Allocation Bookings"),
 *   uri_paths = {
 *     "canonical" = "/admin/village_allocation/bookings/{event_id}"
 *   }
 * )
 */
class VillageAllocationBookingsResource extends ResourceBase {

  const VIEW_ID = 'camping_groups_indexed_';
  const VIEW_DISPLAY_ID = 'block_1';

  /**
   * Queries.
   *
   * @var \Drupal\village_allocation\VillageAllocationQueries
   */
  protected $queries;

  /**
   * Facets.
   *
   * @var \Drupal\facets\FacetManager\DefaultFacetManager
   */
  protected $facetManager;

  /**
   * Search API query tool.
   *
   * @var \Drupal\search_api\Utility\QueryHelper
   */
  protected $searchApiQuery;

  /**
   * Database connection.
   *
   * @var \Drupal\Core\Database\Connection
   */
  protected $db;

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

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

  /**
   * {@inheritdoc}
   */
  public function __construct(array $configuration, $plugin_id, $plugin_definition, array $serializer_formats, LoggerInterface $logger, VillageAllocationQueries $queries, DefaultFacetManager $facet_manager, SearchApiQueryHelper $search_api_query, Connection $db, RendererInterface $renderer, EntityTypeManagerInterface $entity_type_manager) {
    parent::__construct($configuration, $plugin_id, $plugin_definition, $serializer_formats, $logger);
    $this->queries = $queries;
    $this->facetManager = $facet_manager;
    $this->searchApiQuery = $search_api_query;
    $this->db = $db;
    $this->renderer = $renderer;
    $this->entityTypeManager = $entity_type_manager;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->getParameter('serializer.formats'),
      $container->get('logger.factory')->get('rest'),
      $container->get('village_allocation.queries'),
      $container->get('facets.manager'),
      $container->get('search_api.query_helper'),
      $container->get('database'),
      $container->get('renderer'),
      $container->get('entity_type.manager')
    );
  }

  /**
   * Request handler for GETs.
   *
   * @param \Symfony\Component\HttpFoundation\Request $request
   *   Current HTTP request.
   * @param string $event_id
   *   Event ID.
   *
   * @return \Drupal\rest\ResourceResponse
   *   The resource for returning to the client. This will be a JSON object
   *   with the following keys:
   *   - count: total number of bookings
   *   - groups: camping groups
   *   - facets: information on the solr facets that can be used for filtering.
   */
  public function get(Request $request, $event_id) {
    $village_filter = $request->query->get('village', [])['value'];
    $village_operator = $request->query->get('village_op');

    $village_groups = $this->getGroupsFromSolrSearch($count);
    $groups = $this->getVillageAllocationGroups($event_id, $village_filter, $village_operator, $village_groups);
    $facets = $this->getFacets();

    // Get the array representation of each group.
    $groups = array_map(function ($group) {
      return $group->toArray();
    }, $groups);

    // Only want the array values, so the serializer gives us a JS array
    // rather than a JS object.
    $groups = array_values($groups);

    $response = [
      'count' => $count ?? 0,
      'page_size' => $this->getPageSize(),
      'groups' => $groups,
      'facets' => $facets,
    ];

    // Don't want the response cached.
    return (new ResourceResponse($response))
      ->addCacheableDependency([
        '#cache' => ['max-age' => 0],
      ]);
  }

  /**
   * Gets the village allocation groups.
   *
   * @param string $event_id
   *   Event ID.
   * @param int $village
   *   ID of the village to limit to. This could be 0 to indicate unallocated.
   * @param string $village_operator
   *   Operator to use for the village filter. May be "=" or ">" etc.
   * @param \Drupal\contacts_events_villages\Entity\VillageGroup[] $groups
   *   Group IDs to filter the bookings. Used by the solr facets.
   *
   * @return \Drupal\village_allocation\VillageAllocationGroup[]
   *   Array of village allocation groups.
   */
  protected function getVillageAllocationGroups($event_id, $village, $village_operator, array $groups) {
    // Get the bookings within the groups we've found.
    $bookings = $this->queries->getBookingsInManualAllocationGroups($event_id, $village, $village_operator, array_keys($groups));
    $booking_ids_with_village_hosts = $this->queries->getBookingIdsWithVillageHosts($event_id);

    // Preload all the booking entities based on the ids.
    /** @var \Drupal\commerce_order\Entity\Order[] $orders */
    $orders = $this->entityTypeManager->getStorage('commerce_order')->loadMultiple(array_keys($bookings));

    // With group bookings enabled, we want to aggregate all the bookings by
    // their Group Booking, so that only the Group Bookings appear in the
    // Allocation Groups.
    foreach ($bookings as $id => $booking) {
      // If the ID of this order is the same as the group booking ID, then this
      // is the Group Booking. Alternatively if there's no Group Booking ID
      // treat this as a Group Booking too.
      $is_group_booking = $booking->group_booking_id == $booking->order_id || $booking->group_booking_id == NULL;

      if (!$is_group_booking && isset($bookings[$booking->group_booking_id])) {
        // This is not a group booking. Add it to the parent group booking
        // then remove it from the list.
        $bookings[$booking->group_booking_id]->childBookings[$id] = $booking;
        unset($bookings[$id]);
      }
    }

    /** @var \Drupal\village_allocation\VillageAllocationGroup[] $allocation_groups */
    $allocation_groups = [];

    // Create the groups.
    foreach ($groups as $group) {
      $group_id = $group->id();
      $group_name = $group->getName() ?? 'Unknown';
      $allocation_groups[$group_id] = new VillageAllocationGroup($group_id, $group_name);
    }

    // Add bookings to their corresponding Village Groups.
    foreach ($bookings as $booking) {
      $order = $orders[$booking->order_id];
      $group_id = $booking->group_id ?? 0;
      $is_village_host_in_booking = in_array($order->id(), $booking_ids_with_village_hosts);
      $pitches = floatval($booking->pitches) ?? 0;
      $child_bookings = [];

      // If this is a group booking, also add in the child pitches.
      if (isset($booking->childBookings)) {
        foreach ($booking->childBookings as $child_booking) {
          $child_order = $orders[$child_booking->order_id];
          $child_pitches = floatval($child_booking->pitches) ?? 0;
          $pitches += $child_pitches;

          // Take into account if the village host is in a child booking.
          if (in_array($child_order->id(), $booking_ids_with_village_hosts)) {
            $is_village_host_in_booking = TRUE;
          }

          $child_bookings[] = [
            'order' => $child_order,
            'pitches' => $child_pitches,
          ];
        }
      }

      $allocation_groups[$group_id]->addBooking($order, $pitches, $is_village_host_in_booking, $child_bookings);
    }

    $this->calculateVillagesAllocated($allocation_groups);

    return $allocation_groups;
  }

  /**
   * Calculates villages allocated and applies the results to the groups.
   *
   * Note that this does not use the village IDs returned by the bookings query
   * as the bookings query may have filtered out certain bookings (depending on
   * the village filter). So we use a separate query to get the villages of all
   * bookings in the groups, regardless of village filter.
   *
   * @param \Drupal\village_allocation\VillageAllocationGroup[] $groups
   *   Array of village allocation groups to process.
   */
  protected function calculateVillagesAllocated(array &$groups) {
    if (!count($groups)) {
      return;
    }

    $group_ids = array_keys($groups);

    $q = $this->db->select('commerce_order', 'o');
    $q->leftJoin('commerce_order__village_group', 'vg', 'o.order_id = vg.entity_id');
    $q->innerJoin('commerce_order__village', 'v', 'o.order_id = v.entity_id');
    $q->addField('v', 'village_target_id', 'village_id');
    $q->addField('vg', 'village_group_target_id', 'group_id');
    $q->condition('vg.village_group_target_id', $group_ids, 'IN');
    $q->condition('o.state', 'draft', '<>');
    $q->distinct();

    $results = $q->execute()->fetchAll();

    foreach ($results as $result) {
      $groups[$result->group_id ?? 0]->addVillage($result->village_id);
    }
  }

  /**
   * Gets facets that can be used on the village allocation page.
   *
   * @return array
   *   Array of facets containing elements id, name and items. Each item is
   *   an array with elements name, count (int), showCount (bool),
   *   filter (string) and id.
   */
  protected function getFacets() {
    // The ID of our facet source based on the view that indexes camping
    // groups.
    $facet_source_id = 'search_api:views_block__' . self::VIEW_ID . '__' . self::VIEW_DISPLAY_ID;
    $facets = $this->facetManager->getFacetsByFacetSourceId($facet_source_id);
    $facets_for_serialization = [];

    foreach ($facets as $facet) {
      // There isn't really a good api to get the facet info without having
      // to re-implement a lot of what the facet manager does. Instead, let the
      // facet manager build its render array, and extract the facet info from
      // this to build our json. This causes the solr query to be run
      // (and the results cached) so we don't need to explicitly run it again
      // later.
      $build = $this->facetManager->build($facet);

      $facet_json = [
        'id' => $facet->id(),
        'name' => $facet->getName(),
        'items' => [],
      ];

      foreach ($build[0]['#items'] as $item) {
        if ($item['#type'] == 'link') {
          $facet_json['items'][] = [
            'name' => $item['#title']['#value'],
            'count' => $item['#title']['#count'],
            'showCount' => $item['#title']['#show_count'],
            // Extract the filter querystring from the url.
            'filter' => $item['#url']->getOption('query')['f'][0],
            'id' => $item['#attributes']['data-drupal-facet-item-id'],
          ];
        }
      }

      $facets_for_serialization[] = $facet_json;
    }

    return $facets_for_serialization;
  }

  /**
   * Gets the village group IDs returned by the solr search.
   *
   * @param int $count
   *   Output parameter - total results.
   *
   * @return \Drupal\contacts_events_villages\Entity\VillageGroup[]
   *   Array of groups.
   */
  private function getGroupsFromSolrSearch(&$count) {
    // Need to execute the view inside in a rendering context
    // otherwise Drupal contains about leaked cache metadata.
    // This is only necessary because of the exposed filters on the view.
    $view = $this->renderer->executeInRenderContext(new RenderContext(), function () {
      $view = Views::getView(self::VIEW_ID);
      $view->setDisplay(self::VIEW_DISPLAY_ID);
      $view->execute();
      return $view;
    });

    // The solr results don't give us back entities (or even entity ids)
    // but instead we get an array of searchApi items. We can infer the IDs of
    // the VA groups from the this.
    // The ids of the search api results will be in the format:
    // entity:c_events_village_group/3:en
    // So we can infer the id (3) by splitting at the / character and then
    // stripping out the colon and everything after.
    $entity_ids = array_map(function (ResultRow $row) {
      /** @var \Drupal\search_api\Item\Item $item */
      $item = $row->_item;
      $id = explode('/', $item->getId());
      // Take everything to the right of the slash (eg 3:en)
      $id = $id[1];
      $id = explode(':', $id);
      // Everything left of the colon (eg 3).
      return $id[0];
    }, $view->result);

    // Now we can load the groups with these IDs.
    $groups = $this->entityTypeManager->getStorage('c_events_village_group')
      ->loadMultiple($entity_ids);

    // We also need to know the total number of groups that match the search
    // before paging was applied, so we can build the pager.
    // This is not available on the view but is available on the cached solr
    // results. Calling getResults with the same query ID will return a cached
    // copy, and will not execute the query again.
    $solr_search_id = 'views_block:' . self::VIEW_ID . '__' . self::VIEW_DISPLAY_ID;
    $solr_results = $this->searchApiQuery->getResults($solr_search_id);
    $count = $solr_results->getResultCount();

    return $groups;
  }

  /**
   * Gets the page size.
   *
   * @return int
   *   Page size.
   */
  private function getPageSize() {
    /** @var \Drupal\views\Entity\View $view */
    $view = $this->entityTypeManager->getStorage('view')
      ->load(self::VIEW_ID);
    // Pager is stored on the default display, not the block display.
    $display = $view->getDisplay('default');
    return $display['display_options']['pager']['options']['items_per_page'];
  }

}

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

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