whitelabel-8.x-2.x-dev/src/Plugin/WhiteLabelNegotiation/WhiteLabelNegotiationUrl.php

src/Plugin/WhiteLabelNegotiation/WhiteLabelNegotiationUrl.php
<?php

namespace Drupal\whitelabel\Plugin\WhiteLabelNegotiation;

use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
use Drupal\Core\PathProcessor\OutboundPathProcessorInterface;
use Drupal\Core\Render\BubbleableMetadata;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\whitelabel\WhiteLabelNegotiationMethodBase;
use Drupal\whitelabel\WhiteLabelNegotiationMethodInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

/**
 * Class for identifying white labels via URL.
 *
 * @WhiteLabelNegotiation(
 *   id = \Drupal\whitelabel\Plugin\WhiteLabelNegotiation\WhiteLabelNegotiationUrl::METHOD_ID,
 *   label = @Translation("URL"),
 *   description = @Translation("White label from the URL."),
 *   weight = 0,
 * )
 */
class WhiteLabelNegotiationUrl extends WhiteLabelNegotiationMethodBase implements WhiteLabelNegotiationMethodInterface, InboundPathProcessorInterface, OutboundPathProcessorInterface {

  use StringTranslationTrait;

  /**
   * The white label negotiation method id.
   */
  const METHOD_ID = 'url';

  /**
   * White label negotiation: use a query parameter as whitelabel indicator.
   */
  const CONFIG_QUERY_PARAMETER = 'query_parameter';

  /**
   * White label negotiation: use the path prefix as whitelabel indicator.
   */
  const CONFIG_PATH_PREFIX = 'path_prefix';

  /**
   * White label negotiation: use the domain as whitelabel indicator.
   */
  const CONFIG_DOMAIN = 'domain';

  /**
   * Internal flag for knowing if the current request is already processed.
   *
   * @var bool
   */
  protected $whiteLabelInboundProcessing;

  /**
   * Helper function for fetching all white label modes.
   *
   * @return string[]
   *   An array of descriptions, keyed by the mode system name.
   */
  public static function getModes() {
    return [
      self::CONFIG_QUERY_PARAMETER => t('Query parameter (<em>example.com/somepage?token=<strong>whitelabel_token</strong></em>)'),
      self::CONFIG_DOMAIN => t('Domain (<em><strong>whitelabel_token</strong>.example.com/somepage</em>)'),

      // @todo This is very resource heavy and needs multiple lookups to
      //   determine if the site is in white label mode or not. Disabled for now
      //   as it did not have the desired effect.
      // self::CONFIG_PATH_PREFIX => t('Path prefix (<em>example.com/<strong>whitelabel_token</strong>/somepage</em>)'),
    ];
  }

  /**
   * {@inheritdoc}
   */
  public function __construct(array $configuration, $plugin_id, array $plugin_definition) {
    parent::__construct($configuration, $plugin_id, $plugin_definition);

    $this->whiteLabelInboundProcessing = FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function defaultConfiguration() {
    return [
      'mode' => self::CONFIG_QUERY_PARAMETER,
      'query_string_identifier' => 'store',
      'domain' => '',
    ] + parent::defaultConfiguration();
  }

  /**
   * {@inheritdoc}
   */
  public function getWhiteLabel(Request $request = NULL) {
    $whitelabel_mode = $this->configuration['mode'];

    $token = NULL;
    $whitelabel = NULL;
    $invalid_whitelabel = FALSE;

    switch ($whitelabel_mode) {
      case self::CONFIG_QUERY_PARAMETER:
        $query_string_identifier = $this->configuration['query_string_identifier'];
        $token = $request->query->get($query_string_identifier) ?: NULL;

        // Try to load the white label from the query parameter.
        $whitelabel = $this->whiteLabelProvider->getWhiteLabelByToken($token);
        break;

      case self::CONFIG_PATH_PREFIX:
        $request_path = urldecode(trim($request->getPathInfo(), '/'));
        $path_args = explode('/', $request_path);
        $token = array_shift($path_args);

        // Rebuild a path with the remaining parts.
        $temp_path = implode('/', $path_args);

        $whitelabel = $this->whiteLabelProvider->getWhiteLabelByToken($token);

        // This has toe be done like this to prevent circular references.
        $path_validator = \Drupal::service('path.validator');

        // White label found and valid remaining path.
        if ($whitelabel && $path_validator->isValid($temp_path)) {
          break;
        }
        // No white label and a valid initial path; treat this as a no
        // white label request.
        elseif (empty($whitelabel) && $path_validator->isValid($request_path)) {
          return NULL;
        }
        // In all other cases there is something wrong.
        else {
          $invalid_whitelabel = TRUE;
        }
        break;

      case self::CONFIG_DOMAIN:
        // Get only the host, not the port.
        $http_host = $request->getHost();
        $host_parts = explode('.', $http_host);

        // Make sure that the host was the token plus the base domain.
        $normalized_base_url = str_replace(['https://', 'http://'], '', $this->configuration['domain']);
        if ($http_host == $host_parts[0] . '.' . $normalized_base_url && $whitelabel = $this->whiteLabelProvider->getWhiteLabelByToken($host_parts[0])) {
          $token = $host_parts[0];
        }
        // Do nothing if we are on the base domain.
        elseif ($http_host == $normalized_base_url) {
          break;
        }
        else {
          $invalid_whitelabel = TRUE;
        }
        break;
    }

    // Return the white label if there is one.
    if (!empty($whitelabel)) {
      return $whitelabel;
    }

    // If the white label is invalid or permissions are wrong, show 404.
    elseif ($invalid_whitelabel && ($whitelabel_mode == self::CONFIG_PATH_PREFIX || $whitelabel_mode == self::CONFIG_DOMAIN)) {
      throw new NotFoundHttpException(sprintf("The mode '%s' and the discovered white label '%s' did not result in an existing page.", $whitelabel_mode, $token));
    }

    // In all other cases no white label was resolved.
    return FALSE;
  }

  /**
   * {@inheritdoc}
   */
  public function processInbound($path, Request $request) {
    $whitelabel_mode = $this->configuration['mode'];

    // This request is already being processed as part of the validation of the
    // temporary path. Do nothing.
    if ($this->whiteLabelInboundProcessing) {
      return $path;
    }

    switch ($whitelabel_mode) {

      case self::CONFIG_PATH_PREFIX:
        // Strip the token from the path.
        $parts = explode('/', trim($path, '/'));
        $token = array_shift($parts);

        // Rebuild a path with the remaining parts.
        $temp_path = implode('/', $parts);

        // Check if this is a valid path. The path validator will in turn call
        // all path processors again. So add additional context to prevent
        // infinite processing.
        // If the page with a white label would result in an invalid path,
        // continue with the current path instead and without white label. This
        // prevents /node/add to be seen as white label 'node' and path '/add'.
        $this->whiteLabelInboundProcessing = TRUE;

        $whitelabel = $this->whiteLabelProvider->getWhiteLabelByToken($token);

        // This has toe be done like this to prevent circular references.
        $path_validator = \Drupal::service('path.validator');

        // White label found and valid remaining path.
        if ($whitelabel && $path_validator->isValid($temp_path)) {
          $path = $temp_path;
        }
        // No white label and a valid initial path; treat this as a non
        // white label request.
        elseif (empty($whitelabel) && $path_validator->isValid($path)) {
          return $path;
        }

        break;

    }

    return $path;
  }

  /**
   * {@inheritdoc}
   */
  public function processOutbound($path, &$options = [], Request $request = NULL, BubbleableMetadata $bubbleable_metadata = NULL) {
    $url_scheme = 'http';
    $port = 80;
    if ($request) {
      $url_scheme = $request->getScheme();
      $port = $request->getPort();
    }

    // Get the white label from the options, or use the active one otherwise.
    if (isset($options['whitelabel'])) {
      $whitelabel = $options['whitelabel'];
    }
    else {
      $whitelabel = $this->whiteLabelProvider->getWhiteLabel();
    }

    // No white label or no permission to serve it, so leave.
    if (empty($whitelabel)) {
      // Also add a cache context for no white label requests.
      if ($bubbleable_metadata) {
        $bubbleable_metadata->addCacheContexts(['whitelabel']);
      }

      // Revert the domain to the base domain to return from any possible white
      // label.
      if ($this->configuration['mode'] == self::CONFIG_DOMAIN) {
        $options['base_url'] = $this->configuration['domain'];

        // In case either the original base URL or the HTTP host contains a
        // port, retain it.
        if (isset($normalized_base_url) && strpos($normalized_base_url, ':') !== FALSE) {
          [, $port] = explode(':', $normalized_base_url);
          $options['base_url'] .= ':' . $port;
        }
        elseif (($url_scheme == 'http' && $port != 80) || ($url_scheme == 'https' && $port != 443)) {
          $options['base_url'] .= ':' . $port;
        }

        if (isset($options['https'])) {
          if ($options['https'] === TRUE) {
            $options['base_url'] = str_replace('http://', 'https://', $options['base_url']);
          }
          elseif ($options['https'] === FALSE) {
            $options['base_url'] = str_replace('https://', 'http://', $options['base_url']);
          }
        }
      }

      return $path;
    }

    $whitelabel_token = $whitelabel->getToken();

    // Apply white label in the right place.
    $whitelabel_mode = $this->configuration['mode'];
    switch ($whitelabel_mode) {
      case self::CONFIG_QUERY_PARAMETER:
        // Append the white label query parameter.
        $query_string_identifier = $this->configuration['query_string_identifier'];
        $options['query'][$query_string_identifier] = $whitelabel_token;
        break;

      case self::CONFIG_PATH_PREFIX:
        // Append the white label token as a prefix, preserve existing prefixes.
        // The weight of the inbound path processors defines the inbound order.
        // (So make sure they match.)
        $options['prefix'] = $whitelabel_token . '/' . $options['prefix'] . '/';
        break;

      case self::CONFIG_DOMAIN:
        assert(!empty($this->configuration['domain']), 'Configuration value for the white label base domain value is missing.');

        $options['base_url'] = $options['base_url'] ?? $this->configuration['domain'];

        // Save the original base URL. If it contains a port, we need to
        // retain it below.
        if (!empty($options['base_url'])) {
          // The colon in the URL scheme messes up the port checking below.
          $normalized_base_url = str_replace(['https://', 'http://'], '', $options['base_url']);
        }

        // Ask for an absolute URL with our modified base URL.
        $options['absolute'] = TRUE;
        $options['base_url'] = $url_scheme . '://' . $whitelabel_token . '.' . $normalized_base_url;

        // In case either the original base URL or the HTTP host contains a
        // port, retain it.
        if (isset($normalized_base_url) && strpos($normalized_base_url, ':') !== FALSE) {
          [, $port] = explode(':', $normalized_base_url);
          $options['base_url'] .= ':' . $port;
        }
        elseif (($url_scheme == 'http' && $port != 80) || ($url_scheme == 'https' && $port != 443)) {
          $options['base_url'] .= ':' . $port;
        }

        if (isset($options['https'])) {
          if ($options['https'] === TRUE) {
            $options['base_url'] = str_replace('http://', 'https://', $options['base_url']);
          }
          elseif ($options['https'] === FALSE) {
            $options['base_url'] = str_replace('https://', 'http://', $options['base_url']);
          }
        }

        // Add Drupal's sub-folder from the base_path if there is one.
        $options['base_url'] .= rtrim(base_path(), '/');
        break;
    }

    if ($bubbleable_metadata) {
      $bubbleable_metadata->addCacheableDependency($whitelabel);
    }

    return $path;
  }

  /**
   * {@inheritdoc}
   */
  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
    $change_warning = $this->t('WARNING: CHANGING THIS DURING PRODUCTION WILL CAUSE EXISTING WHITE LABEL LINKS TO BREAK.');

    $form['mode'] = [
      '#type' => 'radios',
      '#title' => $this->t('Detection mode'),
      '#default_value' => $this->configuration['mode'],
      '#description' => $this->t('The domain mode requires a white label DNS record and optionally a white label SSL certificate. <br>@warning', [
        '@warning' => $change_warning,
      ]),
      '#options' => self::getModes(),
    ];

    $form['query_string_identifier'] = [
      '#type' => 'machine_name',
      '#title' => $this->t('Query string identifier'),
      '#default_value' => $this->configuration['query_string_identifier'],
      '#description' => $this->t("When using a query string, this defines the parameter to use. In the following example %example, the query string parameter would be 'token'. <br>@warning", [
        '%example' => self::getModes()[self::CONFIG_QUERY_PARAMETER],
        '@warning' => $change_warning,
      ]),
      '#machine_name' => [
        'exists' => [$this, 'validQueryString'],
        'standalone' => TRUE,
      ],
      '#states' => [
        'visible' => [
          ':input[name="negotiator_settings[' . self::METHOD_ID . '][settings][mode]"]' => ['value' => self::CONFIG_QUERY_PARAMETER],
        ],
        'required' => [
          ':input[name="negotiator_settings[' . self::METHOD_ID . '][settings][mode]"]' => ['value' => self::CONFIG_QUERY_PARAMETER],
        ],
      ],
    ];

    $form['domain'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Base domain'),
      '#default_value' => $this->configuration['domain'],
      '#description' => $this->t('When using subdomain-mode, this is used to determine the base domain.'),
      '#states' => [
        'visible' => [
          ':input[name="negotiator_settings[' . self::METHOD_ID . '][settings][mode]"]' => ['value' => self::CONFIG_DOMAIN],
        ],
        'required' => [
          ':input[name="negotiator_settings[' . self::METHOD_ID . '][settings][mode]"]' => ['value' => self::CONFIG_DOMAIN],
        ],
      ],
      '#attributes' => [
        'placeholder' => 'https://example.com',
      ],
    ];

    return $form;
  }

  /**
   * {@inheritdoc}
   */
  public function validateConfigurationForm(array &$form, FormStateInterface $form_state) {
    // Ensure that system reserved strings are not chosen.
    if ($form_state->getValue('query_string_identifier') === 'q') {
      $form_state->setErrorByName('query_string_identifier', $this->t('This is not a valid query string identifier. Please use something different than %query_string_identifier', ['@query_string_identifier' => $form_state->getValue('query_string_identifier')]));
    }
  }

  /**
   * {@inheritdoc}
   */
  public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
    $this->setConfiguration($form_state->getValues());
  }

  /**
   * Helper function for detecting if the provided query string is valid.
   *
   * @param string $id
   *   The ID to check.
   *
   * @return bool
   *   Boolean to indicate if the query string is already in use.
   */
  public function validQueryString($id) {
    // @todo ensure this actually checks something.
    return FALSE;
  }

}

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

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