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