contacts-8.x-1.x-dev/src/Dashboard.php

src/Dashboard.php
<?php

namespace Drupal\contacts;

use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\DependencyInjection\DependencySerializationTrait;
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
use Drupal\Core\Form\FormBuilder;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\CurrentRouteMatch;
use Drupal\Core\Security\TrustedCallbackInterface;
use Drupal\Core\Url;
use Symfony\Component\HttpFoundation\RequestStack;

/**
 * Helper for Contacts Dashboard.
 */
class Dashboard implements TrustedCallbackInterface {

  use DependencySerializationTrait {
    DependencySerializationTrait::__sleep as traitSleep;
  }

  /**
   * A fast static cache of the service instances, keyed by service name.
   *
   * Values may be FALSE if we are not on the dashboard page.
   *
   * @var array
   */
  protected static $fastStatic;

  /**
   * Indicates we are not on the Contacts Dashboard.
   */
  const MODE_NOT = 'not';

  /**
   * Indicates we are on the Contacts Dashboard via an full page request.
   */
  const MODE_FULL = 'full';

  /**
   * Indicates we are on the Contacts Dashboard via an AJAX request.
   */
  const MODE_AJAX = 'ajax';

  /**
   * The current route match.
   *
   * @var \Drupal\Core\Routing\CurrentRouteMatch
   */
  protected $routeMatch;

  /**
   * The request stack.
   *
   * @var \Symfony\Component\HttpFoundation\RequestStack
   */
  protected $requestStack;

  /**
   * The dashboard full page route name.
   *
   * @var string
   */
  protected $fullRouteName;

  /**
   * The dashboard AJAX page route name.
   *
   * @var string
   */
  protected $ajaxRouteName;

  /**
   * The dashboard mode.
   *
   * @var string
   */
  protected $mode;

  /**
   * Construct the dashboard helper.
   *
   * @param \Drupal\Core\Routing\CurrentRouteMatch $route_match
   *   The route match service.
   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
   *   The request stack.
   * @param string $full_route_name
   *   The route name for the dashboard full page.
   * @param string $ajax_route_name
   *   The route name for the dashboard AJAX page.
   */
  public function __construct(CurrentRouteMatch $route_match, RequestStack $request_stack, $full_route_name, $ajax_route_name) {
    $this->routeMatch = $route_match;
    $this->requestStack = $request_stack;
    $this->fullRouteName = $full_route_name;
    $this->ajaxRouteName = $ajax_route_name;
  }

  /**
   * {@inheritdoc}
   */
  public function __sleep() {
    return array_diff(
      $this->traitSleep(),
      ['mode'],
    );
  }

  /**
   * Get the service from a static cache including an is dashbaord check.
   *
   * @param string $service_name
   *   The service name.
   *
   * @return \Drupal\contacts\Dashboard|null
   *   The dashboard service.
   */
  protected static function getInstance(string $service_name): ?Dashboard {
    // Instantiate our fast static if it's not already set.
    if (!isset(self::$fastStatic[$service_name])) {
      $dashboard = \Drupal::service($service_name);
      self::$fastStatic[$service_name] = $dashboard->isDashboard() ? $dashboard : FALSE;
    }

    // If we have a service, return it.
    return self::$fastStatic[$service_name] ?: NULL;
  }

  /**
   * Get the dashboard mode for a Url or the current request.
   *
   * @param \Drupal\Core\Url|null $url
   *   Optionally a URL to check. Otherwise we use the current request.
   *
   * @return string
   *   One of the self::MODE_* constants.
   */
  public function getDashboardMode(?Url $url = NULL) {
    // If we're not checking a specific URL, see if we can return a cache.
    if (!$url && isset($this->mode)) {
      return $this->mode;
    }

    // Get the route name we're checking.
    if ($url) {
      $route = $url->isRouted() ? $url->getRouteName() : '';

      // If the route is <current>, we can use the cache.
      if ($route == '<current>' || $route == '<none>') {
        return $this->getDashboardMode();
      }
    }
    else {
      $route = $this->routeMatch->getRouteName();
    }

    // Check the mode.
    switch ($route) {
      case $this->fullRouteName:
        $mode = self::MODE_FULL;
        break;

      case $this->ajaxRouteName:
        $mode = self::MODE_AJAX;
        break;

      default:
        $mode = self::MODE_NOT;
        break;
    }

    // If we're checking the current request, cache the value.
    if (!$url) {
      $this->mode = $mode;
    }

    return $mode;
  }

  /**
   * Check if the Url or the current request is the dashboard.
   *
   * @param \Drupal\Core\Url|null $url
   *   Optionally a URL to check. Otherwise we use the current request.
   *
   * @return bool
   *   TRUE if it is the dashboard.
   */
  public function isDashboard(?Url $url = NULL) {
    return $this->getDashboardMode($url) !== self::MODE_NOT;
  }

  /**
   * Check if the Url or the current request is the full dashboard.
   *
   * @param \Drupal\Core\Url|null $url
   *   Optionally a URL to check. Otherwise we use the current request.
   * @param bool $same_contact_only
   *   Optionally consider a URL to another user to be a full dashboard request.
   *
   * @return bool
   *   TRUE if it is the full dashboard.
   */
  public function isDashboardFull(?Url $url = NULL, $same_contact_only = TRUE) {
    // If:
    // - checking a different URL to the current request
    // - the check should account for the user being different
    // - the user for the current request is different to the URL user
    // Then this is not suitable for an AJAX request.
    if ($url && $same_contact_only && !$this->isCurrentContact($url)) {
      return FALSE;
    }
    return $this->getDashboardMode($url) === self::MODE_FULL;
  }

  /**
   * Check if the Url or the current request is the AJAX dashboard.
   *
   * @param \Drupal\Core\Url|null $url
   *   Optionally a URL to check. Otherwise we use the current request.
   * @param bool $same_user_only
   *   Optionally consider a URL to another user to be an AJAX request.
   *
   * @return bool
   *   TRUE if it is the AJAX dashboard.
   */
  public function isDashboardAjax(?Url $url = NULL, $same_user_only = TRUE) {
    // If:
    // - checking a different URL to the current request
    // - the check should account for the user being different
    // - the user for the current request is different to the URL user
    // Then this is not suitable for an AJAX request.
    if ($url && $same_user_only && !$this->isCurrentContact($url)) {
      return FALSE;
    }
    return $this->getDashboardMode($url) === self::MODE_AJAX;
  }

  /**
   * Check if the Url is for the same user as the current request.
   *
   * @param \Drupal\Core\Url $url
   *   The Url to check.
   *
   * @return bool
   *   TRUE if the Url is for the same user.
   */
  public function isCurrentContact(Url $url) {
    if ($url->isRouted() && $this->isDashboard($url)) {
      $url_route_params = $url->getRouteParameters();
      if (isset($url_route_params['user'])) {
        return $url_route_params['user'] === $this->routeMatch->getRawParameter('user');
      }
    }
    return FALSE;
  }

  /**
   * Get a full page equivalent for the Url or current page.
   *
   * @param \Drupal\Core\Url|null $url
   *   The URL or NULL to use the current page.
   *
   * @return \Drupal\Core\Url
   *   The full page equivalent URL.
   */
  public function getFullUrl(?Url $url = NULL) {
    if ($url) {
      $params = $url->getRouteParameters();
      $options = $url->getOptions();
    }
    else {
      $param_bag = $this->routeMatch->getParameters();
      $params = [
        'user' => $param_bag->get('user')->id(),
        'subpage' => $param_bag->get('subpage'),
      ];
      $options['query'] = $this->requestStack->getCurrentRequest()->query->all();
    }
    unset($options['query']['_wrapper_format']);
    unset($options['query']['_format']);

    return Url::fromRoute($this->fullRouteName, $params, $options);
  }

  /**
   * Get a AJAX page equivalent for the Url or current page.
   *
   * @param \Drupal\Core\Url|null $url
   *   The URL or NULL to use the current page.
   *
   * @return \Drupal\Core\Url
   *   The AJAX page equivalent URL.
   */
  public function getAjaxUrl(?Url $url = NULL) {
    if ($url) {
      $params = $url->getRouteParameters();
      $options = $url->getOptions();
    }
    else {
      $param_bag = $this->routeMatch->getParameters();
      $params = [
        'user' => $param_bag->get('user')->id(),
        'subpage' => $param_bag->get('subpage'),
      ];
      $options = $this->requestStack->getCurrentRequest()->query->all();
    }
    return Url::fromRoute($this->ajaxRouteName, $params, $options);
  }

  /**
   * Fast implementation of hook_link_alter() for dashboard services.
   *
   * Implements a similar pattern to the drupal fast static to optimise the
   * speed of this method and only do processing where required.
   *
   * @param array $variables
   *   The link variables.
   * @param string $service_name
   *   The service name for the dashboard helper.
   */
  public static function fastHookLinkAlter(array &$variables, $service_name) {
    // Escape early if we are explicitly skipping AJAX conversion.
    if (!empty($variables['options']['contacts_no_ajax'])) {
      return;
    }

    if ($dashboard = self::getInstance($service_name)) {
      $dashboard->hookLinkAlter($variables);
    }
  }

  /**
   * Service implementation of hook_link_alter().
   *
   * @param array $variables
   *   The link variables.
   */
  public function hookLinkAlter(array &$variables) {
    /** @var \Drupal\Core\Url $url */
    $url = &$variables['url'];

    // Change links within the dashboard.
    if ($this->isDashboardFull($url)) {
      $variables['options']['attributes'] += [
        'data-ajax-progress' => 'fullscreen',
        'data-ajax-url' => $this->getAjaxUrl($url)->toString(),
      ];
      if (!isset($variables['options']['attributes']['class']) || !in_array('use-ajax', $variables['options']['attributes']['class'])) {
        $variables['options']['attributes']['class'][] = 'use-ajax';
      }
    }

    // Update links indicated to use a modal. Check the target as a workaround
    // for views, which doesn't allow more specific options to be set.
    $use_modal = !empty($variables['options']['contacts_modal'])
      || ($variables['options']['attributes']['target'] ?? NULL) == '_contacts_modal';
    if ($use_modal) {
      unset($variables['options']['attributes']['target']);
      $variables['options']['attributes'] += [
        'data-ajax-progress' => 'fullscreen',
        'data-dialog-type' => 'modal',
      ];
      $variables['options']['attributes']['class'][] = 'use-ajax';

      // Set a redirect URL if there isn't one.
      if (empty($variables['options']['query']['destination'])) {
        $variables['options']['query']['destination'] = $this->getFullUrl()->toString();
      }
    }

    // Update destinations on links to always use the non-AJAX links. We only
    // need to do this if we're currently on the AJAX dashboard.
    if (isset($variables['options']['query']['destination']) && $this->isDashboardAjax()) {
      $destination = Url::fromUserInput($variables['options']['query']['destination']);
      if ($this->isDashboardAjax($destination)) {
        $variables['options']['query']['destination'] = $this->getFullUrl($destination)->toString();
      }
    }

  }

  /**
   * Fast implementation of hook_form_alter().
   *
   * @param array $form
   *   The form array.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   * @param string $form_id
   *   The form ID.
   * @param string $service_name
   *   The service name for the dashboard helper.
   */
  public static function fastHookFormAlter(array &$form, FormStateInterface $form_state, string $form_id, string $service_name): void {
    if (!self::isDefaultFormAction($form)) {
      return;
    }

    if ($dashboard = self::getInstance($service_name)) {
      $dashboard->hookFormAlter($form, $form_state, $form_id, TRUE);
    }
  }

  /**
   * Implementation of hook_form_alter().
   *
   * @param array $form
   *   The form array.
   * @param \Drupal\Core\Form\FormStateInterface $form_state
   *   The form state.
   * @param string $form_id
   *   The form ID.
   * @param bool $default_checked
   *   Whether the is default check has already been run to avoid doing it
   *   again.
   */
  public function hookFormAlter(array &$form, FormStateInterface $form_state, string $form_id, bool $default_checked = FALSE): void {
    if (!$default_checked && !self::isDefaultFormAction($form)) {
      return;
    }

    // If we are in an AJAX request, explicitly set the action to the non AJAX
    // variation.
    if ($this->isDashboardAjax()) {
      $form['#attached']['placeholders'][$form['#action']]['#lazy_builder'][0] = [
        $this,
        'buildFormAction',
      ];
    }
  }

  /**
   * Check whether a form is using the default form action (the current page).
   *
   * @param array $form
   *   The form array.
   *
   * @return bool
   *   Whether the default form action is being used.
   */
  public static function isDefaultFormAction(array $form): bool {
    // Check whether the action is the default placeholder.
    if (!is_string($form['#action'])) {
      return FALSE;
    }
    if (!isset($form['#attached']['placeholders'][$form['#action']])) {
      return FALSE;
    }
    return $form['#attached']['placeholders'][$form['#action']]['#lazy_builder'][0] === 'form_builder:renderPlaceholderFormAction';
  }

  /**
   * Builds the $form['#action'].
   *
   * @return array
   *   The form action render array.
   */
  public function buildFormAction() {
    $url = $this->getFullUrl();
    $uri = $url->toString();

    // Prevent cross site requests via the Form API by using an absolute URL
    // when the request uri starts with multiple slashes..
    if (strpos($uri, '//') === 0) {
      $uri = $url->setAbsolute()->toString();
    }

    // @todo Remove this parsing once these are removed from the request in
    //   https://www.drupal.org/node/2504709.
    $parsed = UrlHelper::parse($uri);
    unset($parsed['query'][FormBuilder::AJAX_FORM_REQUEST], $parsed['query'][MainContentViewSubscriber::WRAPPER_FORMAT]);
    $action = $parsed['path'] . ($parsed['query'] ? ('?' . UrlHelper::buildQuery($parsed['query'])) : '');

    return [
      '#type' => 'markup',
      '#markup' => UrlHelper::filterBadProtocol($action),
      '#cache' => ['contexts' => ['url.path', 'url.query_args']],
    ];
  }

  /**
   * {@inheritdoc}
   */
  public static function trustedCallbacks() {
    return ['buildFormAction'];
  }

}

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

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