annotate core/modules/language/src/Plugin/LanguageNegotiation/LanguageNegotiationContentEntity.php @ 19:fa3358dc1485 tip

Add ndrum files
author Chris Cannam
date Wed, 28 Aug 2019 13:14:47 +0100
parents 4c8ae668cc8c
children
rev   line source
Chris@0 1 <?php
Chris@0 2
Chris@0 3 namespace Drupal\language\Plugin\LanguageNegotiation;
Chris@0 4
Chris@0 5 use Drupal\Core\Entity\ContentEntityInterface;
Chris@0 6 use Drupal\Core\Entity\EntityManagerInterface;
Chris@0 7 use Drupal\Core\PathProcessor\OutboundPathProcessorInterface;
Chris@0 8 use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
Chris@0 9 use Drupal\Core\Render\BubbleableMetadata;
Chris@0 10 use Drupal\Core\Url;
Chris@0 11 use Drupal\language\LanguageNegotiationMethodBase;
Chris@0 12 use Drupal\language\LanguageSwitcherInterface;
Chris@0 13 use Symfony\Cmf\Component\Routing\RouteObjectInterface;
Chris@0 14 use Symfony\Component\DependencyInjection\ContainerInterface;
Chris@0 15 use Symfony\Component\HttpFoundation\Request;
Chris@0 16 use Symfony\Component\Routing\Route;
Chris@0 17
Chris@0 18 /**
Chris@0 19 * Class for identifying the content translation language.
Chris@0 20 *
Chris@0 21 * @LanguageNegotiation(
Chris@0 22 * id = Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationContentEntity::METHOD_ID,
Chris@0 23 * types = {Drupal\Core\Language\LanguageInterface::TYPE_CONTENT},
Chris@0 24 * weight = -9,
Chris@0 25 * name = @Translation("Content language"),
Chris@0 26 * description = @Translation("Determines the content language from a request parameter."),
Chris@0 27 * )
Chris@0 28 */
Chris@0 29 class LanguageNegotiationContentEntity extends LanguageNegotiationMethodBase implements OutboundPathProcessorInterface, LanguageSwitcherInterface, ContainerFactoryPluginInterface {
Chris@0 30
Chris@0 31 /**
Chris@0 32 * The language negotiation method ID.
Chris@0 33 */
Chris@0 34 const METHOD_ID = 'language-content-entity';
Chris@0 35
Chris@0 36 /**
Chris@0 37 * The query string parameter.
Chris@0 38 */
Chris@0 39 const QUERY_PARAMETER = 'language_content_entity';
Chris@0 40
Chris@0 41 /**
Chris@0 42 * A list of all the link paths of enabled content entities.
Chris@0 43 *
Chris@0 44 * @var array
Chris@0 45 */
Chris@0 46 protected $contentEntityPaths;
Chris@0 47
Chris@0 48 /**
Chris@0 49 * Static cache for the language negotiation order check.
Chris@0 50 *
Chris@0 51 * @see \Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationContentEntity::hasLowerLanguageNegotiationWeight()
Chris@0 52 *
Chris@0 53 * @var bool
Chris@0 54 */
Chris@0 55 protected $hasLowerLanguageNegotiationWeightResult;
Chris@0 56
Chris@0 57 /**
Chris@0 58 * Static cache of outbound route paths per request.
Chris@0 59 *
Chris@0 60 * @var \SplObjectStorage
Chris@0 61 */
Chris@0 62 protected $paths;
Chris@0 63
Chris@0 64 /**
Chris@0 65 * The entity manager.
Chris@0 66 *
Chris@0 67 * @var \Drupal\Core\Entity\EntityManagerInterface
Chris@0 68 */
Chris@0 69 protected $entityManager;
Chris@0 70
Chris@0 71 /**
Chris@0 72 * Constructs a new LanguageNegotiationContentEntity instance.
Chris@0 73 *
Chris@0 74 * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
Chris@0 75 * The entity manager.
Chris@0 76 */
Chris@0 77 public function __construct(EntityManagerInterface $entity_manager) {
Chris@0 78 $this->entityManager = $entity_manager;
Chris@0 79 $this->paths = new \SplObjectStorage();
Chris@0 80 }
Chris@0 81
Chris@0 82 /**
Chris@0 83 * {@inheritdoc}
Chris@0 84 */
Chris@0 85 public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
Chris@0 86 return new static($container->get('entity.manager'));
Chris@0 87 }
Chris@0 88
Chris@0 89 /**
Chris@0 90 * {@inheritdoc}
Chris@0 91 */
Chris@0 92 public function getLangcode(Request $request = NULL) {
Chris@0 93 $langcode = $request->query->get(static::QUERY_PARAMETER);
Chris@0 94
Chris@0 95 $language_enabled = array_key_exists($langcode, $this->languageManager->getLanguages());
Chris@0 96 return $language_enabled ? $langcode : NULL;
Chris@0 97 }
Chris@0 98
Chris@0 99 /**
Chris@0 100 * {@inheritdoc}
Chris@0 101 */
Chris@0 102 public function processOutbound($path, &$options = [], Request $request = NULL, BubbleableMetadata $bubbleable_metadata = NULL) {
Chris@0 103 // If appropriate, process outbound to add a query parameter to the url and
Chris@0 104 // remove the language option, so that url negotiator does not rewrite the
Chris@0 105 // url.
Chris@0 106
Chris@0 107 // First, check if processing conditions are met.
Chris@0 108 if (!($request && !empty($options['route']) && $this->hasLowerLanguageNegotiationWeight() && $this->meetsContentEntityRoutesCondition($options['route'], $request))) {
Chris@0 109 return $path;
Chris@0 110 }
Chris@0 111
Chris@0 112 if (isset($options['language']) || $langcode = $this->getLangcode($request)) {
Chris@0 113 // If the language option is set, unset it, so that the url language
Chris@0 114 // negotiator does not rewrite the url.
Chris@0 115 if (isset($options['language'])) {
Chris@0 116 $langcode = $options['language']->getId();
Chris@0 117 unset($options['language']);
Chris@0 118 }
Chris@0 119
Chris@0 120 if (!isset($options['query'][static::QUERY_PARAMETER])) {
Chris@0 121 $options['query'][static::QUERY_PARAMETER] = $langcode;
Chris@0 122 }
Chris@0 123
Chris@0 124 if ($bubbleable_metadata) {
Chris@0 125 // Cached URLs that have been processed by this outbound path
Chris@0 126 // processor must be:
Chris@0 127 $bubbleable_metadata
Chris@0 128 // - varied by the content language query parameter.
Chris@0 129 ->addCacheContexts(['url.query_args:' . static::QUERY_PARAMETER]);
Chris@0 130 }
Chris@0 131 }
Chris@0 132
Chris@0 133 return $path;
Chris@0 134 }
Chris@0 135
Chris@0 136 /**
Chris@0 137 * {@inheritdoc}
Chris@0 138 */
Chris@0 139 public function getLanguageSwitchLinks(Request $request, $type, Url $url) {
Chris@0 140 $links = [];
Chris@0 141 $query = [];
Chris@0 142 parse_str($request->getQueryString(), $query);
Chris@0 143
Chris@0 144 foreach ($this->languageManager->getNativeLanguages() as $language) {
Chris@0 145 $langcode = $language->getId();
Chris@0 146 $query[static::QUERY_PARAMETER] = $langcode;
Chris@0 147 $links[$langcode] = [
Chris@0 148 'url' => $url,
Chris@0 149 'title' => $language->getName(),
Chris@0 150 'attributes' => ['class' => ['language-link']],
Chris@0 151 'query' => $query,
Chris@0 152 ];
Chris@0 153 }
Chris@0 154
Chris@0 155 return $links;
Chris@0 156 }
Chris@0 157
Chris@0 158 /**
Chris@0 159 * Determines if content entity language negotiator has higher priority.
Chris@0 160 *
Chris@0 161 * The content entity language negotiator having higher priority than the url
Chris@0 162 * language negotiator, is a criteria in
Chris@0 163 * \Drupal\language\Plugin\LanguageNegotiation\LanguageNegotiationContentEntity::processOutbound().
Chris@0 164 *
Chris@0 165 * @return bool
Chris@0 166 * TRUE if the the content entity language negotiator has higher priority
Chris@0 167 * than the url language negotiator, FALSE otherwise.
Chris@0 168 */
Chris@0 169 protected function hasLowerLanguageNegotiationWeight() {
Chris@0 170 if (!isset($this->hasLowerLanguageNegotiationWeightResult)) {
Chris@0 171 // Only run if the LanguageNegotiationContentEntity outbound function is
Chris@0 172 // being executed before the outbound function of LanguageNegotiationUrl.
Chris@0 173 $content_method_weights = $this->config->get('language.types')->get('negotiation.language_content.enabled') ?: [];
Chris@0 174
Chris@0 175 // Check if the content language is configured to be dependent on the
Chris@0 176 // url negotiator directly or indirectly over the interface negotiator.
Chris@0 177 if (isset($content_method_weights[LanguageNegotiationUrl::METHOD_ID]) && ($content_method_weights[static::METHOD_ID] > $content_method_weights[LanguageNegotiationUrl::METHOD_ID])) {
Chris@0 178 $this->hasLowerLanguageNegotiationWeightResult = FALSE;
Chris@0 179 }
Chris@0 180 else {
Chris@0 181 $check_interface_method = FALSE;
Chris@0 182 if (isset($content_method_weights[LanguageNegotiationUI::METHOD_ID])) {
Chris@0 183 $interface_method_weights = $this->config->get('language.types')->get('negotiation.language_interface.enabled') ?: [];
Chris@0 184 $check_interface_method = isset($interface_method_weights[LanguageNegotiationUrl::METHOD_ID]);
Chris@0 185 }
Chris@0 186 if ($check_interface_method) {
Chris@0 187 $max_weight = $content_method_weights[LanguageNegotiationUI::METHOD_ID];
Chris@0 188 $max_weight = isset($content_method_weights[LanguageNegotiationUrl::METHOD_ID]) ? max($max_weight, $content_method_weights[LanguageNegotiationUrl::METHOD_ID]) : $max_weight;
Chris@0 189 }
Chris@0 190 else {
Chris@0 191 $max_weight = isset($content_method_weights[LanguageNegotiationUrl::METHOD_ID]) ? $content_method_weights[LanguageNegotiationUrl::METHOD_ID] : PHP_INT_MAX;
Chris@0 192 }
Chris@0 193
Chris@0 194 $this->hasLowerLanguageNegotiationWeightResult = $content_method_weights[static::METHOD_ID] < $max_weight;
Chris@0 195 }
Chris@0 196 }
Chris@0 197
Chris@0 198 return $this->hasLowerLanguageNegotiationWeightResult;
Chris@0 199 }
Chris@0 200
Chris@0 201 /**
Chris@0 202 * Determines if content entity route condition is met.
Chris@0 203 *
Chris@0 204 * Requirements: currently being on an content entity route and processing
Chris@0 205 * outbound url pointing to the same content entity.
Chris@0 206 *
Chris@0 207 * @param \Symfony\Component\Routing\Route $outbound_route
Chris@0 208 * The route object for the current outbound url being processed.
Chris@0 209 * @param \Symfony\Component\HttpFoundation\Request $request
Chris@0 210 * The HttpRequest object representing the current request.
Chris@0 211 *
Chris@0 212 * @return bool
Chris@0 213 * TRUE if the content entity route condition is met, FALSE otherwise.
Chris@0 214 */
Chris@0 215 protected function meetsContentEntityRoutesCondition(Route $outbound_route, Request $request) {
Chris@0 216 $outbound_path_pattern = $outbound_route->getPath();
Chris@0 217 $storage = isset($this->paths[$request]) ? $this->paths[$request] : [];
Chris@0 218 if (!isset($storage[$outbound_path_pattern])) {
Chris@0 219 $storage[$outbound_path_pattern] = FALSE;
Chris@0 220
Chris@0 221 // Check if the outbound route points to the current entity.
Chris@0 222 if ($content_entity_type_id_for_current_route = $this->getContentEntityTypeIdForCurrentRequest($request)) {
Chris@0 223 if (!empty($this->getContentEntityPaths()[$outbound_path_pattern]) && $content_entity_type_id_for_current_route == $this->getContentEntityPaths()[$outbound_path_pattern]) {
Chris@0 224 $storage[$outbound_path_pattern] = TRUE;
Chris@0 225 }
Chris@0 226 }
Chris@0 227
Chris@0 228 $this->paths[$request] = $storage;
Chris@0 229 }
Chris@0 230
Chris@0 231 return $storage[$outbound_path_pattern];
Chris@0 232 }
Chris@0 233
Chris@0 234 /**
Chris@0 235 * Returns the content entity type ID from the current request for the route.
Chris@0 236 *
Chris@0 237 * @param \Symfony\Component\HttpFoundation\Request $request
Chris@0 238 * The HttpRequest object representing the current request.
Chris@0 239 *
Chris@0 240 * @return string
Chris@0 241 * The entity type ID for the route from the request.
Chris@0 242 */
Chris@0 243 protected function getContentEntityTypeIdForCurrentRequest(Request $request) {
Chris@0 244 $content_entity_type_id_for_current_route = '';
Chris@0 245
Chris@0 246 if ($current_route = $request->attributes->get(RouteObjectInterface::ROUTE_OBJECT)) {
Chris@0 247 $current_route_path = $current_route->getPath();
Chris@0 248 $content_entity_type_id_for_current_route = isset($this->getContentEntityPaths()[$current_route_path]) ? $this->getContentEntityPaths()[$current_route_path] : '';
Chris@0 249 }
Chris@0 250
Chris@0 251 return $content_entity_type_id_for_current_route;
Chris@0 252 }
Chris@0 253
Chris@0 254 /**
Chris@0 255 * Returns the paths for the link templates of all content entities.
Chris@0 256 *
Chris@0 257 * @return array
Chris@0 258 * An array of all content entity type IDs, keyed by the corresponding link
Chris@0 259 * template paths.
Chris@0 260 */
Chris@0 261 protected function getContentEntityPaths() {
Chris@0 262 if (!isset($this->contentEntityPaths)) {
Chris@0 263 $this->contentEntityPaths = [];
Chris@0 264 $entity_types = $this->entityManager->getDefinitions();
Chris@0 265 foreach ($entity_types as $entity_type_id => $entity_type) {
Chris@0 266 if ($entity_type->entityClassImplements(ContentEntityInterface::class)) {
Chris@0 267 $entity_paths = array_fill_keys($entity_type->getLinkTemplates(), $entity_type_id);
Chris@0 268 $this->contentEntityPaths = array_merge($this->contentEntityPaths, $entity_paths);
Chris@0 269 }
Chris@0 270 }
Chris@0 271 }
Chris@0 272
Chris@0 273 return $this->contentEntityPaths;
Chris@0 274 }
Chris@0 275
Chris@0 276 }