graphql_core_schema-1.0.x-dev/src/Plugin/GraphQL/DataProducer/Route.php
src/Plugin/GraphQL/DataProducer/Route.php
<?php
declare(strict_types=1);
namespace Drupal\graphql_core_schema\Plugin\GraphQL\DataProducer;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Path\PathValidatorInterface;
use Drupal\Core\PathProcessor\InboundPathProcessorInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Url;
use Drupal\graphql\GraphQL\Execution\FieldContext;
use Drupal\graphql\Plugin\GraphQL\DataProducer\DataProducerPluginBase;
use Drupal\graphql_core_schema\GraphQL\Buffers\SubRequestBuffer;
use Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationUrl;
use GraphQL\Deferred;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
use Drupal\Core\Session\AccountProxyInterface;
/**
* Returns the URL of the given path.
*
* @DataProducer(
* id = "get_route",
* name = @Translation("Load route"),
* description = @Translation("Loads a route."),
* produces = @ContextDefinition("any",
* label = @Translation("Route")
* ),
* consumes = {
* "path" = @ContextDefinition("string",
* label = @Translation("Path")
* )
* }
* )
*/
class Route extends DataProducerPluginBase implements ContainerFactoryPluginInterface {
/**
* The language negotiator service.
*
* @var \Drupal\language\LanguageNegotiator
*/
protected $languageNegotiator;
/**
* The redirect repository.
*
* @var \Drupal\redirect\RedirectRepository
*/
protected $redirectRepository;
/**
* The redirect entity repository.
*
* @var \Drupal\domain_path_redirect\DomainPathRedirectRepository
*/
protected $domainPathRedirectRepository;
/**
* Domain negotiator.
*
* @var \Drupal\domain\DomainNegotiator
*/
protected $domainNegotiator;
/**
* The redirect 404 storage.
*
* @var \Drupal\redirect_404\RedirectNotFoundStorageInterface
*/
protected $redirectNotFoundStorage;
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container->get('path.validator'),
$container->get('language_negotiator', ContainerInterface::NULL_ON_INVALID_REFERENCE),
$container->get('language_manager'),
$container->get('redirect.repository', ContainerInterface::NULL_ON_INVALID_REFERENCE),
$container->get('path_processor_manager'),
$container->get('domain_path_redirect.repository', ContainerInterface::NULL_ON_INVALID_REFERENCE),
$container->get('domain.negotiator', ContainerInterface::NULL_ON_INVALID_REFERENCE),
$container->get('entity_type.manager'),
$container->get('redirect.not_found_storage', ContainerInterface::NULL_ON_INVALID_REFERENCE),
$container->get('config.factory'),
$container->get('graphql_core_schema.buffer.subrequest'),
$container->get('current_user'),
);
}
public function __construct(
array $configuration,
$pluginId,
$pluginDefinition,
protected PathValidatorInterface $pathValidator,
$languageNegotiator,
protected LanguageManagerInterface $languageManager,
$redirectRepository,
protected InboundPathProcessorInterface $pathProcessor,
$domain_path_redirect_repository,
$domain_negotiator,
protected EntityTypeManagerInterface $entityTypeManager,
$redirect_not_found_storage,
protected ConfigFactoryInterface $configFactory,
protected SubRequestBuffer $subRequestBuffer,
protected AccountProxyInterface $currentUser,
) {
parent::__construct($configuration, $pluginId, $pluginDefinition);
$this->redirectRepository = $redirectRepository;
$this->languageNegotiator = $languageNegotiator;
$this->domainPathRedirectRepository = $domain_path_redirect_repository;
$this->domainNegotiator = $domain_negotiator;
$this->redirectNotFoundStorage = $redirect_not_found_storage;
}
/**
* {@inheritdoc}
*/
public function resolve($value, FieldContext $field) {
// @todo This entire method needs to be refactored and made more
// configurable/extendable by users of the module.
$currentLanguage = $this->languageManager->getCurrentLanguage()->getId();
$field->setContextValue('language', $currentLanguage);
$request = Request::create($value);
$path = $this->pathProcessor->processInbound($value, $request);
if ($this->domainPathRedirectRepository) {
$domain = $this->domainNegotiator ? $this->domainNegotiator->getActiveId() : NULL;
$field->addCacheContexts(['domain']);
// Check for domain_path_redirect.
$domain_redirect_path = trim($path, '/');
if ($redirect = $this->domainPathRedirectRepository->findMatchingRedirect($domain_redirect_path, $domain, [], 'und')) {
return $redirect;
}
}
if ($this->redirectRepository) {
$query_params = $request->query->all() ?: [];
if ($query_params) {
// Strip everything after the query string.
$path = explode('?', $path)[0];
}
// Remove any trailing slashes.
$path = rtrim($path, '/');
// Check for regular redirects.
if ($redirect = $this->redirectRepository->findMatchingRedirect($path, $query_params, $currentLanguage)) {
// Pass the query string to the resolver.
if ($query_params) {
$redirect->queryParams = $request->getQueryString();
}
return $redirect;
}
}
// Get the URL if valid.
$url = $this->pathValidator->getUrlIfValidWithoutAccessCheck($value);
if ($url) {
// If the URL is external, we stop here for security reasons.
// Similar to https://www.drupal.org/sa-core-2020-003
// This prevents the possibility of an open redirect. Example:
// https://example.ddev.site//https://malicous-redirect.com/
// will not be redirected to https://malicous-redirect.com/
if ($url->isExternal()) {
return NULL;
}
$resolver = $this->subRequestBuffer->add($url, function (Url $url) use ($value, $request, $currentLanguage, $path, $field) {
$negotiatedLangcode = $currentLanguage;
if ($this->languageNegotiator && $this->languageNegotiator->isNegotiationMethodEnabled('language-url')) {
$currentUser = $this->currentUser;
$this->languageNegotiator->setCurrentUser($currentUser);
// Determine the language from the provided url string.
$negotiator = $this->languageNegotiator->getNegotiationMethodInstance('language-url');
$negotiatedUrlLangcode = $negotiator->getLangcode($request);
if ($negotiatedUrlLangcode) {
$negotiatedLangcode = $negotiatedUrlLangcode;
}
}
// Check URL access.
$target_url = $url->toString(TRUE)->getGeneratedUrl();
// If language detection is domain based, remove domain from $target_url
if ($this->languageNegotiator) {
$lang_n_config = $this->configFactory->get('language.negotiation');
if ($lang_n_config->get('url.source') == LanguageNegotiationUrl::CONFIG_DOMAIN) {
$lang_domain = $lang_n_config->get('url.domains.' . $negotiatedLangcode);
$target_url = str_replace(['http://', 'https://'], '', $target_url);
$target_url = str_replace($lang_domain, '', $target_url);
}
}
// Check if $target_url is absolute. This is needed for domain_source module.
// '/my-url' is replaced by domain_source with 'http://example.com/my-url'.
$isExternal = UrlHelper::isExternal($target_url);
// Check if the URL has an alias and should be redirected.
if (!$isExternal && $value !== $target_url && $this->entityTypeManager->hasDefinition('redirect')) {
$redirectStorage = $this->entityTypeManager->getStorage('redirect');
/** @var \Drupal\redirect\Entity\Redirect $redirect */
$redirect = $redirectStorage->create();
$redirect->setRedirect($target_url);
$redirect->setSource($path);
$redirect->setLanguage($negotiatedLangcode);
$redirect->setStatusCode(301);
return $redirect;
}
$access = $url->access(NULL, TRUE);
$field->addCacheableDependency($access);
if ($access->isAllowed()) {
$negotiatedLanguage = $this->languageManager->getLanguage($negotiatedLangcode);
$url->setOption('language', $negotiatedLanguage);
$field->setContextValue('language', $negotiatedLangcode);
return $url;
}
else {
if ($this->entityTypeManager->hasDefinition('redirect')) {
$url = Url::fromUserInput('/user/login?destination=' . $url->toString());
// The URL exists but the user has no access.
$redirectStorage = $this->entityTypeManager->getStorage('redirect');
/** @var \Drupal\redirect\Entity\Redirect $redirect */
$redirect = $redirectStorage->create();
$redirect->setRedirect($url->toString());
$redirect->setSource($path);
$redirect->setLanguage($negotiatedLangcode);
$redirect->setStatusCode(403);
return $redirect;
}
}
});
return new Deferred($resolver);
}
$field->addCacheTags(['4xx-response']);
if ($this->redirectNotFoundStorage) {
$this->redirectNotFoundStorage->logRequest($path, $currentLanguage);
}
return NULL;
}
}
