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 }
|