Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
0.00% |
0 / 1 |
|
0.00% |
0 / 9 |
CRAP | |
0.00% |
0 / 107 |
LanguageNegotiationContentEntity | |
0.00% |
0 / 1 |
|
0.00% |
0 / 9 |
1806 | |
0.00% |
0 / 107 |
__construct | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 3 |
|||
create | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
getLangcode | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 4 |
|||
processOutbound | |
0.00% |
0 / 1 |
182 | |
0.00% |
0 / 28 |
|||
getLanguageSwitchLinks | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 15 |
|||
hasLowerLanguageNegotiationWeight | |
0.00% |
0 / 1 |
110 | |
0.00% |
0 / 23 |
|||
meetsContentEntityRoutesCondition | |
0.00% |
0 / 1 |
42 | |
0.00% |
0 / 13 |
|||
getContentEntityTypeIdForCurrentRequest | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 7 |
|||
getContentEntityPaths | |
0.00% |
0 / 1 |
20 | |
0.00% |
0 / 12 |
<?php | |
/** | |
* @file | |
* Contains \Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationContentEntity. | |
*/ | |
namespace Drupal\language\Plugin\LanguageNegotiation; | |
use Drupal\Component\Utility\UrlHelper; | |
use Drupal\Core\Entity\ContentEntityInterface; | |
use Drupal\Core\Entity\EntityManagerInterface; | |
use Drupal\Core\PathProcessor\OutboundPathProcessorInterface; | |
use Drupal\Core\Plugin\ContainerFactoryPluginInterface; | |
use Drupal\Core\Render\BubbleableMetadata; | |
use Drupal\Core\Url; | |
use Drupal\language\LanguageNegotiationMethodBase; | |
use Drupal\language\LanguageSwitcherInterface; | |
use Symfony\Cmf\Component\Routing\RouteObjectInterface; | |
use Symfony\Component\DependencyInjection\ContainerInterface; | |
use Symfony\Component\HttpFoundation\Request; | |
use Symfony\Component\Routing\Route; | |
/** | |
* Class for identifying the content translation language. | |
* | |
* @LanguageNegotiation( | |
* id = Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationContentEntity::METHOD_ID, | |
* types = {Drupal\Core\Language\LanguageInterface::TYPE_CONTENT}, | |
* weight = -9, | |
* name = @Translation("Content language"), | |
* description = @Translation("Determines the content language from a request parameter."), | |
* ) | |
*/ | |
class LanguageNegotiationContentEntity extends LanguageNegotiationMethodBase implements OutboundPathProcessorInterface, LanguageSwitcherInterface, ContainerFactoryPluginInterface { | |
/** | |
* The language negotiation method ID. | |
*/ | |
const METHOD_ID = 'language-content-entity'; | |
/** | |
* The query string parameter. | |
*/ | |
const QUERY_PARAMETER = 'language_content_entity'; | |
/** | |
* A list of all the link paths of enabled content entities. | |
* | |
* @var array | |
*/ | |
protected $contentEntityPaths; | |
/** | |
* Static cache for the language negotiation order check. | |
* | |
* @see \Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationContentEntity::hasLowerLanguageNegotiationWeight() | |
* | |
* @var bool | |
*/ | |
protected $hasLowerLanguageNegotiationWeightResult; | |
/** | |
* Static cache of outbound route paths per request. | |
* | |
* @var \SplObjectStorage | |
*/ | |
protected $paths; | |
/** | |
* The entity manager. | |
* | |
* @var \Drupal\Core\Entity\EntityManagerInterface | |
*/ | |
protected $entityManager; | |
/** | |
* Constructs a new LanguageNegotiationContentEntity instance. | |
* | |
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager | |
* The entity manager. | |
*/ | |
public function __construct(EntityManagerInterface $entity_manager) { | |
$this->entityManager = $entity_manager; | |
$this->paths = new \SplObjectStorage(); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { | |
return new static($container->get('entity.manager')); | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function getLangcode(Request $request = NULL) { | |
$langcode = $request->get(static::QUERY_PARAMETER); | |
$language_enabled = array_key_exists($langcode, $this->languageManager->getLanguages()); | |
return $language_enabled ? $langcode : NULL; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function processOutbound($path, &$options = [], Request $request = NULL, BubbleableMetadata $bubbleable_metadata = NULL) { | |
// If appropriate, process outbound to add a query parameter to the url and | |
// remove the language option, so that url negotiator does not rewrite the | |
// url. | |
// First, check if processing conditions are met. | |
if (!($request && !empty($options['route']) && $this->hasLowerLanguageNegotiationWeight() && $this->meetsContentEntityRoutesCondition($options['route'], $request))) { | |
return $path; | |
} | |
if (isset($options['language']) || $langcode = $this->getLangcode($request)) { | |
// If the language option is set, unset it, so that the url language | |
// negotiator does not rewrite the url. | |
if (isset($options['language'])) { | |
$langcode = $options['language']->getId(); | |
unset($options['language']); | |
} | |
if (isset($options['query']) && is_string($options['query'])) { | |
$query = []; | |
parse_str($options['query'], $query); | |
$options['query'] = $query; | |
} | |
else { | |
$options['query'] = []; | |
} | |
if (!isset($options['query'][static::QUERY_PARAMETER])) { | |
$query_addon = [static::QUERY_PARAMETER => $langcode]; | |
$options['query'] += $query_addon; | |
// @todo Remove this once https://www.drupal.org/node/2507005 lands. | |
$path .= (strpos($path, '?') !== FALSE ? '&' : '?') . UrlHelper::buildQuery($query_addon); | |
} | |
if ($bubbleable_metadata) { | |
// Cached URLs that have been processed by this outbound path | |
// processor must be: | |
$bubbleable_metadata | |
// - varied by the content language query parameter. | |
->addCacheContexts(['url.query_args:' . static::QUERY_PARAMETER]); | |
} | |
} | |
return $path; | |
} | |
/** | |
* {@inheritdoc} | |
*/ | |
public function getLanguageSwitchLinks(Request $request, $type, Url $url) { | |
$links = []; | |
$query = []; | |
parse_str($request->getQueryString(), $query); | |
foreach ($this->languageManager->getNativeLanguages() as $language) { | |
$langcode = $language->getId(); | |
$query[static::QUERY_PARAMETER] = $langcode; | |
$links[$langcode] = [ | |
'url' => $url, | |
'title' => $language->getName(), | |
'attributes' => ['class' => ['language-link']], | |
'query' => $query, | |
]; | |
} | |
return $links; | |
} | |
/** | |
* Determines if content entity language negotiator has higher priority. | |
* | |
* The content entity language negotiator having higher priority than the url | |
* language negotiator, is a criteria in | |
* \Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationContentEntity::processOutbound(). | |
* | |
* @return bool | |
* TRUE if the content entity language negotiator has higher priority than | |
* the url language negotiator, FALSE otherwise. | |
*/ | |
protected function hasLowerLanguageNegotiationWeight() { | |
if (!isset($this->hasLowerLanguageNegotiationWeightResult)) { | |
// Only run if the LanguageNegotiationContentEntity outbound function is | |
// being executed before the outbound function of LanguageNegotiationUrl. | |
$content_method_weights = $this->config->get('language.types')->get('negotiation.language_content.enabled') ?: []; | |
// Check if the content language is configured to be dependent on the | |
// url negotiator directly or indirectly over the interface negotiator. | |
if (isset($content_method_weights[LanguageNegotiationUrl::METHOD_ID]) && ($content_method_weights[static::METHOD_ID] > $content_method_weights[LanguageNegotiationUrl::METHOD_ID])) { | |
$this->hasLowerLanguageNegotiationWeightResult = FALSE; | |
} | |
else { | |
$check_interface_method = FALSE; | |
if (isset($content_method_weights[LanguageNegotiationUI::METHOD_ID])) { | |
$interface_method_weights = $this->config->get('language.types')->get('negotiation.language_interface.enabled') ?: []; | |
$check_interface_method = isset($interface_method_weights[LanguageNegotiationUrl::METHOD_ID]); | |
} | |
if ($check_interface_method) { | |
$max_weight = $content_method_weights[LanguageNegotiationUI::METHOD_ID]; | |
$max_weight = isset($content_method_weights[LanguageNegotiationUrl::METHOD_ID]) ? max($max_weight, $content_method_weights[LanguageNegotiationUrl::METHOD_ID]) : $max_weight; | |
} | |
else { | |
$max_weight = isset($content_method_weights[LanguageNegotiationUrl::METHOD_ID]) ? $content_method_weights[LanguageNegotiationUrl::METHOD_ID] : PHP_INT_MAX; | |
} | |
$this->hasLowerLanguageNegotiationWeightResult = $content_method_weights[static::METHOD_ID] < $max_weight; | |
} | |
} | |
return $this->hasLowerLanguageNegotiationWeightResult; | |
} | |
/** | |
* Determines if content entity route condition is met. | |
* | |
* Requirements: currently being on an content entity route and processing | |
* outbound url pointing to the same content entity. | |
* | |
* @param \Symfony\Component\Routing\Route $outbound_route | |
* The route object for the current outbound url being processed. | |
* @param \Symfony\Component\HttpFoundation\Request $request | |
* The HttpRequest object representing the current request. | |
* | |
* @return bool | |
* TRUE if the content entity route condition is met, FALSE otherwise. | |
*/ | |
protected function meetsContentEntityRoutesCondition(Route $outbound_route, Request $request) { | |
$outbound_path_pattern = $outbound_route->getPath(); | |
$storage = isset($this->paths[$request]) ? $this->paths[$request] : []; | |
if (!isset($storage[$outbound_path_pattern])) { | |
$storage[$outbound_path_pattern] = FALSE; | |
// Check if the outbound route points to the current entity. | |
if ($content_entity_type_id_for_current_route = $this->getContentEntityTypeIdForCurrentRequest($request)) { | |
if (!empty($this->getContentEntityPaths()[$outbound_path_pattern]) && $content_entity_type_id_for_current_route == $this->getContentEntityPaths()[$outbound_path_pattern]) { | |
$storage[$outbound_path_pattern] = TRUE; | |
} | |
} | |
$this->paths[$request] = $storage; | |
} | |
return $storage[$outbound_path_pattern]; | |
} | |
/** | |
* Returns the content entity type ID from the current request for the route. | |
* | |
* @param \Symfony\Component\HttpFoundation\Request $request | |
* The HttpRequest object representing the current request. | |
* | |
* @return string | |
* The entity type ID for the route from the request. | |
*/ | |
protected function getContentEntityTypeIdForCurrentRequest(Request $request) { | |
$content_entity_type_id_for_current_route = ''; | |
if ($current_route = $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT)) { | |
$current_route_path = $current_route->getPath(); | |
$content_entity_type_id_for_current_route = isset($this->getContentEntityPaths()[$current_route_path]) ? $this->getContentEntityPaths()[$current_route_path] : ''; | |
} | |
return $content_entity_type_id_for_current_route; | |
} | |
/** | |
* Returns the paths for the link templates of all content entities. | |
* | |
* @return array | |
* An array of all content entity type IDs, keyed by the corresponding link | |
* template paths. | |
*/ | |
protected function getContentEntityPaths() { | |
if (!isset($this->contentEntityPaths)) { | |
$this->contentEntityPaths = []; | |
$entity_types = $this->entityManager->getDefinitions(); | |
foreach ($entity_types as $entity_type_id => $entity_type) { | |
if ($entity_type->isSubclassOf(ContentEntityInterface::class)) { | |
$entity_paths = array_fill_keys($entity_type->getLinkTemplates(), $entity_type_id); | |
$this->contentEntityPaths = array_merge($this->contentEntityPaths, $entity_paths); | |
} | |
} | |
} | |
return $this->contentEntityPaths; | |
} | |
} |