[+FEATURE] Backport CommandController Implementation
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Classes / MVC / Web / Routing / UriBuilder.php
1 <?php
2 /* *
3 * This script is part of the TYPO3 project - inspiring people to share! *
4 * *
5 * TYPO3 is free software; you can redistribute it and/or modify it under *
6 * the terms of the GNU General Public License version 2 as published by *
7 * the Free Software Foundation. *
8 * *
9 * This script is distributed in the hope that it will be useful, but *
10 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHAN- *
11 * TABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General *
12 * Public License for more details. *
13 * */
14
15 /**
16 * An URI Builder
17 *
18 * @package Extbase
19 * @subpackage MVC\Web\Routing
20 * @version $Id$
21 * @api
22 */
23 class Tx_Extbase_MVC_Web_Routing_UriBuilder {
24
25 /**
26 * @var Tx_Extbase_Configuration_ConfigurationManagerInterface
27 */
28 protected $configurationManager;
29
30 /**
31 * @var Tx_Extbase_Service_ExtensionService
32 */
33 protected $extensionService;
34
35 /**
36 * An instance of tslib_cObj
37 *
38 * @var tslib_cObj
39 */
40 protected $contentObject;
41
42 /**
43 * @var Tx_Extbase_MVC_Web_Request
44 */
45 protected $request;
46
47 /**
48 * @var array
49 */
50 protected $arguments = array();
51
52 /**
53 * Arguments which have been used for building the last URI
54 * @var array
55 */
56 protected $lastArguments = array();
57
58 /**
59 * @var string
60 */
61 protected $section = '';
62
63 /**
64 * @var boolean
65 */
66 protected $createAbsoluteUri = FALSE;
67
68 /**
69 * @var string
70 */
71 protected $absoluteUriScheme = NULL;
72
73 /**
74 * @var boolean
75 */
76 protected $addQueryString = FALSE;
77
78 /**
79 * @var array
80 */
81 protected $argumentsToBeExcludedFromQueryString = array();
82
83 /**
84 * @var boolean
85 */
86 protected $linkAccessRestrictedPages = FALSE;
87
88 /**
89 * @var integer
90 */
91 protected $targetPageUid = NULL;
92
93 /**
94 * @var integer
95 */
96 protected $targetPageType = 0;
97
98 /**
99 * @var boolean
100 */
101 protected $noCache = FALSE;
102
103 /**
104 * @var boolean
105 */
106 protected $useCacheHash = TRUE;
107
108 /**
109 * @var string
110 */
111 protected $format = '';
112
113 /**
114 * @var string
115 */
116 protected $argumentPrefix = NULL;
117
118 /**
119 * @param Tx_Extbase_Configuration_ConfigurationManagerInterface $configurationManager
120 * @return void
121 */
122 public function injectConfigurationManager(Tx_Extbase_Configuration_ConfigurationManagerInterface $configurationManager) {
123 $this->configurationManager = $configurationManager;
124 }
125
126 /**
127 * @param Tx_Extbase_Service_ExtensionService $extensionService
128 * @return void
129 */
130 public function injectExtensionService(Tx_Extbase_Service_ExtensionService $extensionService) {
131 $this->extensionService = $extensionService;
132 }
133
134 /**
135 * Life-cycle method that is called by the DI container as soon as this object is completely built
136 *
137 * @return void
138 */
139 public function initializeObject() {
140 $this->contentObject = $this->configurationManager->getContentObject();
141 }
142
143 /**
144 * Sets the current request
145 *
146 * @param Tx_Extbase_MVC_Request $request
147 * @return Tx_Extbase_MVC_Web_Routing_UriBuilder the current UriBuilder to allow method chaining
148 */
149 public function setRequest(Tx_Extbase_MVC_Request $request) {
150 $this->request = $request;
151 return $this;
152 }
153
154 /**
155 * @return Tx_Extbase_MVC_Web_Request
156 */
157 public function getRequest() {
158 return $this->request;
159 }
160
161 /**
162 * Additional query parameters.
163 * If you want to "prefix" arguments, you can pass in multidimensional arrays:
164 * array('prefix1' => array('foo' => 'bar')) gets "&prefix1[foo]=bar"
165 *
166 * @param array $arguments
167 * @return Tx_Extbase_MVC_Web_Routing_UriBuilder the current UriBuilder to allow method chaining
168 * @api
169 */
170 public function setArguments(array $arguments) {
171 $this->arguments = $arguments;
172 return $this;
173 }
174
175 /**
176 * @return array
177 * @api
178 */
179 public function getArguments() {
180 return $this->arguments;
181 }
182
183 /**
184 * If specified, adds a given HTML anchor to the URI (#...)
185 *
186 * @param string $section
187 * @return Tx_Extbase_MVC_Web_Routing_UriBuilder the current UriBuilder to allow method chaining
188 * @api
189 */
190 public function setSection($section) {
191 $this->section = $section;
192 return $this;
193 }
194
195 /**
196 * @return string
197 * @api
198 */
199 public function getSection() {
200 return $this->section;
201 }
202
203 /**
204 * Specifies the format of the target (e.g. "html" or "xml")
205 *
206 * @param string $format
207 * @return Tx_Extbase_MVC_Web_Routing_UriBuilder the current UriBuilder to allow method chaining
208 * @api
209 */
210 public function setFormat($format) {
211 $this->format = $format;
212 return $this;
213 }
214
215 /**
216 * @return string
217 * @api
218 */
219 public function getFormat() {
220 return $this->format;
221 }
222
223 /**
224 * If set, the URI is prepended with the current base URI. Defaults to FALSE.
225 *
226 * @param boolean $createAbsoluteUri
227 * @return Tx_Extbase_MVC_Web_Routing_UriBuilder the current UriBuilder to allow method chaining
228 * @api
229 */
230 public function setCreateAbsoluteUri($createAbsoluteUri) {
231 $this->createAbsoluteUri = $createAbsoluteUri;
232 return $this;
233 }
234
235 /**
236 * @return boolean
237 * @api
238 */
239 public function getCreateAbsoluteUri() {
240 return $this->createAbsoluteUri;
241 }
242
243 /**
244 * @return string
245 */
246 public function getAbsoluteUriScheme() {
247 return $this->absoluteUriScheme;
248 }
249
250 /**
251 * Sets the scheme that should be used for absolute URIs in FE mode
252 *
253 * @param string $absoluteUriScheme the scheme to be used for absolute URIs
254 * @return Tx_Extbase_MVC_Web_Routing_UriBuilder the current UriBuilder to allow method chaining
255 */
256 public function setAbsoluteUriScheme($absoluteUriScheme) {
257 $this->absoluteUriScheme = $absoluteUriScheme;
258 return $this;
259 }
260
261 /**
262 * If set, the current query parameters will be merged with $this->arguments. Defaults to FALSE.
263 *
264 * @param boolean $addQueryString
265 * @return Tx_Extbase_MVC_Web_Routing_UriBuilder the current UriBuilder to allow method chaining
266 * @api
267 * @see TSref/typolink.addQueryString
268 */
269 public function setAddQueryString($addQueryString) {
270 $this->addQueryString = (boolean)$addQueryString;
271 return $this;
272 }
273
274 /**
275 * @return boolean
276 * @api
277 */
278 public function getAddQueryString() {
279 return $this->addQueryString;
280 }
281
282 /**
283 * A list of arguments to be excluded from the query parameters
284 * Only active if addQueryString is set
285 *
286 * @param array $argumentsToBeExcludedFromQueryString
287 * @return Tx_Extbase_MVC_Web_Routing_UriBuilder the current UriBuilder to allow method chaining
288 * @api
289 * @see TSref/typolink.addQueryString.exclude
290 * @see setAddQueryString()
291 */
292 public function setArgumentsToBeExcludedFromQueryString(array $argumentsToBeExcludedFromQueryString) {
293 $this->argumentsToBeExcludedFromQueryString = $argumentsToBeExcludedFromQueryString;
294 return $this;
295 }
296
297 /**
298 * @return array
299 * @api
300 */
301 public function getArgumentsToBeExcludedFromQueryString() {
302 return $this->argumentsToBeExcludedFromQueryString;
303 }
304
305 /**
306 * Specifies the prefix to be used for all arguments.
307 *
308 * @param string $argumentPrefix
309 * @return Tx_Extbase_MVC_Web_Routing_UriBuilder the current UriBuilder to allow method chaining
310 */
311 public function setArgumentPrefix($argumentPrefix) {
312 $this->argumentPrefix = (string)$argumentPrefix;
313 return $this;
314 }
315
316 /**
317 * @return string
318 */
319 public function getArgumentPrefix() {
320 return $this->argumentPrefix;
321 }
322
323 /**
324 * If set, URIs for pages without access permissions will be created
325 *
326 * @param boolean $linkAccessRestrictedPages
327 * @return Tx_Extbase_MVC_Web_Routing_UriBuilder the current UriBuilder to allow method chaining
328 * @api
329 */
330 public function setLinkAccessRestrictedPages($linkAccessRestrictedPages) {
331 $this->linkAccessRestrictedPages = (boolean)$linkAccessRestrictedPages;
332 return $this;
333 }
334
335 /**
336 * @return boolean
337 * @api
338 */
339 public function getLinkAccessRestrictedPages() {
340 return $this->linkAccessRestrictedPages;
341 }
342
343 /**
344 * Uid of the target page
345 *
346 * @param integer $pageUid
347 * @return Tx_Extbase_MVC_Web_Routing_UriBuilder the current UriBuilder to allow method chaining
348 * @api
349 */
350 public function setTargetPageUid($targetPageUid) {
351 $this->targetPageUid = $targetPageUid;
352 return $this;
353 }
354
355 /**
356 * returns $this->targetPageUid.
357 *
358 * @return integer
359 * @api
360 */
361 public function getTargetPageUid() {
362 return $this->targetPageUid;
363 }
364
365 /**
366 * Sets the page type of the target URI. Defaults to 0
367 *
368 * @param integer $pageType
369 * @return Tx_Extbase_MVC_Web_Routing_UriBuilder the current UriBuilder to allow method chaining
370 * @api
371 */
372 public function setTargetPageType($targetPageType) {
373 $this->targetPageType = (integer)$targetPageType;
374 return $this;
375 }
376
377 /**
378 * @return integer
379 * @api
380 */
381 public function getTargetPageType() {
382 return $this->targetPageType;
383 }
384
385 /**
386 * by default FALSE; if TRUE, &no_cache=1 will be appended to the URI
387 * This overrules the useCacheHash setting
388 *
389 * @param boolean $noCache
390 * @return Tx_Extbase_MVC_Web_Routing_UriBuilder the current UriBuilder to allow method chaining
391 * @api
392 */
393 public function setNoCache($noCache) {
394 $this->noCache = (boolean)$noCache;
395 return $this;
396 }
397
398 /**
399 * @return boolean
400 * @api
401 */
402 public function getNoCache() {
403 return $this->noCache;
404 }
405
406 /**
407 * by default TRUE; if FALSE, no cHash parameter will be appended to the URI
408 * If noCache is set, this setting will be ignored.
409 *
410 * @param boolean $useCacheHash
411 * @return Tx_Extbase_MVC_Web_Routing_UriBuilder the current UriBuilder to allow method chaining
412 * @api
413 */
414 public function setUseCacheHash($useCacheHash) {
415 $this->useCacheHash = (boolean)$useCacheHash;
416 return $this;
417 }
418
419 /**
420 * @return boolean
421 * @api
422 */
423 public function getUseCacheHash() {
424 return $this->useCacheHash;
425 }
426
427 /**
428 * Returns the arguments being used for the last URI being built.
429 * This is only set after build() / uriFor() has been called.
430 *
431 * @return array The last arguments
432 * @author Sebastian Kurf├╝rst <sebastian@typo3.org>
433 */
434 public function getLastArguments() {
435 return $this->lastArguments;
436 }
437
438 /**
439 * Resets all UriBuilder options to their default value
440 *
441 * @return Tx_Extbase_MVC_Web_Routing_UriBuilder the current UriBuilder to allow method chaining
442 * @api
443 */
444 public function reset() {
445 $this->arguments = array();
446 $this->section = '';
447 $this->format = '';
448 $this->createAbsoluteUri = FALSE;
449 $this->addQueryString = FALSE;
450 $this->argumentsToBeExcludedFromQueryString = array();
451 $this->linkAccessRestrictedPages = FALSE;
452 $this->targetPageUid = NULL;
453 $this->targetPageType = 0;
454 $this->noCache = FALSE;
455 $this->useCacheHash = TRUE;
456 $this->argumentPrefix = NULL;
457
458 return $this;
459 }
460
461 /**
462 * Creates an URI used for linking to an Extbase action.
463 * Works in Frontend and Backend mode of TYPO3.
464 *
465 * @param string $actionName Name of the action to be called
466 * @param array $controllerArguments Additional query parameters. Will be "namespaced" and merged with $this->arguments.
467 * @param string $controllerName Name of the target controller. If not set, current ControllerName is used.
468 * @param string $extensionName Name of the target extension, without underscores. If not set, current ExtensionName is used.
469 * @param string $pluginName Name of the target plugin. If not set, current PluginName is used.
470 * @return string the rendered URI
471 * @api
472 * @see build()
473 */
474 public function uriFor($actionName = NULL, $controllerArguments = array(), $controllerName = NULL, $extensionName = NULL, $pluginName = NULL) {
475 if ($actionName !== NULL) {
476 $controllerArguments['action'] = $actionName;
477 }
478 if ($controllerName !== NULL) {
479 $controllerArguments['controller'] = $controllerName;
480 } else {
481 $controllerArguments['controller'] = $this->request->getControllerName();
482 }
483 if ($extensionName === NULL) {
484 $extensionName = $this->request->getControllerExtensionName();
485 }
486 if ($pluginName === NULL && TYPO3_MODE === 'FE') {
487 $pluginName = $this->extensionService->getPluginNameByAction($extensionName, $controllerArguments['controller'], $controllerArguments['action']);
488 }
489 if ($pluginName === NULL) {
490 $pluginName = $this->request->getPluginName();
491 }
492 if (TYPO3_MODE === 'FE' && $this->configurationManager->isFeatureEnabled('skipDefaultArguments')) {
493 $controllerArguments = $this->removeDefaultControllerAndAction($controllerArguments, $extensionName, $pluginName);
494 }
495 if ($this->targetPageUid === NULL && TYPO3_MODE === 'FE') {
496 $this->targetPageUid = $this->extensionService->getTargetPidByPlugin($extensionName, $pluginName);
497 }
498 if ($this->format !== '') {
499 $controllerArguments['format'] = $this->format;
500 }
501 if ($this->argumentPrefix !== NULL) {
502 $prefixedControllerArguments = array($this->argumentPrefix => $controllerArguments);
503 } else {
504 $pluginNamespace = $this->extensionService->getPluginNamespace($extensionName, $pluginName);
505 $prefixedControllerArguments = array($pluginNamespace => $controllerArguments);
506 }
507 $this->arguments = t3lib_div::array_merge_recursive_overrule($this->arguments, $prefixedControllerArguments);
508
509 return $this->build();
510 }
511
512 /**
513 * This removes controller and/or action arguments from given controllerArguments
514 * if they are equal to the default controller/action of the target plugin.
515 * Note: This is only active in FE mode and if feature "skipDefaultArguments" is enabled
516 * @see Tx_Extbase_Configuration_ConfigurationManagerInterface::isFeatureEnabled()
517 *
518 * @param array $controllerArguments the current controller arguments to be modified
519 * @param string $extensionName target extension name
520 * @param string $pluginName target plugin name
521 * @return array
522 */
523 protected function removeDefaultControllerAndAction(array $controllerArguments, $extensionName, $pluginName) {
524 $defaultControllerName = $this->extensionService->getDefaultControllerNameByPlugin($extensionName, $pluginName);
525 if (isset($controllerArguments['action'])) {
526 $defaultActionName = $this->extensionService->getDefaultActionNameByPluginAndController($extensionName, $pluginName, $controllerArguments['controller']);
527 if ($controllerArguments['action'] === $defaultActionName) {
528 unset($controllerArguments['action']);
529 }
530 }
531 if ($controllerArguments['controller'] === $defaultControllerName) {
532 unset($controllerArguments['controller']);
533 }
534 return $controllerArguments;
535 }
536
537 /**
538 * Builds the URI
539 * Depending on the current context this calls buildBackendUri() or buildFrontendUri()
540 *
541 * @return string The URI
542 * @api
543 * @see buildBackendUri()
544 * @see buildFrontendUri()
545 */
546 public function build() {
547 if (TYPO3_MODE === 'BE') {
548 return $this->buildBackendUri();
549 } else {
550 return $this->buildFrontendUri();
551 }
552 }
553
554 /**
555 * Builds the URI, backend flavour
556 * The resulting URI is relative and starts with "mod.php".
557 * The settings pageUid, pageType, noCache, useCacheHash & linkAccessRestrictedPages
558 * will be ignored in the backend.
559 *
560 * @return string The URI
561 */
562 public function buildBackendUri() {
563 if ($this->addQueryString === TRUE) {
564 $arguments = t3lib_div::_GET();
565 foreach($this->argumentsToBeExcludedFromQueryString as $argumentToBeExcluded) {
566 unset($arguments[$argumentToBeExcluded]);
567 }
568 } else {
569 $arguments = array(
570 'M' => t3lib_div::_GET('M'),
571 'id' => t3lib_div::_GET('id')
572 );
573 }
574 $arguments = t3lib_div::array_merge_recursive_overrule($arguments, $this->arguments);
575 $arguments = $this->convertDomainObjectsToIdentityArrays($arguments);
576 $this->lastArguments = $arguments;
577 $uri = 'mod.php?' . http_build_query($arguments, NULL, '&');
578 if ($this->section !== '') {
579 $uri .= '#' . $this->section;
580 }
581 if ($this->createAbsoluteUri === TRUE) {
582 $uri = $this->request->getBaseUri() . $uri;
583 }
584 return $uri;
585 }
586
587 /**
588 * Builds the URI, frontend flavour
589 *
590 * @return string The URI
591 * @see buildTypolinkConfiguration()
592 */
593 public function buildFrontendUri() {
594 $typolinkConfiguration = $this->buildTypolinkConfiguration();
595
596 if ($this->createAbsoluteUri === TRUE) {
597 $typolinkConfiguration['forceAbsoluteUrl'] = TRUE;
598 if ($this->absoluteUriScheme !== NULL) {
599 $typolinkConfiguration['forceAbsoluteUrl.']['scheme'] = $this->absoluteUriScheme;
600 }
601 }
602
603 $uri = $this->contentObject->typoLink_URL($typolinkConfiguration);
604 return $uri;
605 }
606
607
608 /**
609 * Builds a TypoLink configuration array from the current settings
610 *
611 * @return array typolink configuration array
612 * @see TSref/typolink
613 */
614 protected function buildTypolinkConfiguration() {
615 $typolinkConfiguration = array();
616
617 $typolinkConfiguration['parameter'] = $this->targetPageUid !== NULL ? $this->targetPageUid : $GLOBALS['TSFE']->id;
618 if ($this->targetPageType !== 0) {
619 $typolinkConfiguration['parameter'] .= ',' . $this->targetPageType;
620 }
621
622 if (count($this->arguments) > 0) {
623 $arguments = $this->convertDomainObjectsToIdentityArrays($this->arguments);
624 $this->lastArguments = $arguments;
625 $typolinkConfiguration['additionalParams'] = t3lib_div::implodeArrayForUrl(NULL, $arguments);
626 }
627
628 if ($this->addQueryString === TRUE) {
629 $typolinkConfiguration['addQueryString'] = 1;
630 if (count($this->argumentsToBeExcludedFromQueryString) > 0) {
631 $typolinkConfiguration['addQueryString.'] = array(
632 'exclude' => implode(',', $this->argumentsToBeExcludedFromQueryString)
633 );
634 }
635 // TODO: Support for __hmac and addQueryString!
636 }
637
638 if ($this->noCache === TRUE) {
639 $typolinkConfiguration['no_cache'] = 1;
640 } elseif ($this->useCacheHash) {
641 $typolinkConfiguration['useCacheHash'] = 1;
642 }
643
644 if ($this->section !== '') {
645 $typolinkConfiguration['section'] = $this->section;
646 }
647
648 if ($this->linkAccessRestrictedPages === TRUE) {
649 $typolinkConfiguration['linkAccessRestrictedPages'] = 1;
650 }
651
652 return $typolinkConfiguration;
653 }
654
655 /**
656 * Recursively iterates through the specified arguments and turns instances of type Tx_Extbase_DomainObject_AbstractEntity
657 * into an arrays containing the uid of the domain object.
658 *
659 * @param array $arguments The arguments to be iterated
660 * @return array The modified arguments array
661 */
662 protected function convertDomainObjectsToIdentityArrays(array $arguments) {
663 foreach ($arguments as $argumentKey => $argumentValue) {
664 // if we have a LazyLoadingProxy here, make sure to get the real instance for further processing
665 if ($argumentValue instanceof Tx_Extbase_Persistence_LazyLoadingProxy) {
666 $argumentValue = $argumentValue->_loadRealInstance();
667 // also update the value in the arguments array, because the lazyLoaded object could be
668 // hidden and thus the $argumentValue would be NULL.
669 $arguments[$argumentKey] = $argumentValue;
670 }
671 if ($argumentValue instanceof Tx_Extbase_DomainObject_AbstractDomainObject) {
672 if ($argumentValue->getUid() !== NULL) {
673 $arguments[$argumentKey] = $argumentValue->getUid();
674 } elseif ($argumentValue instanceof Tx_Extbase_DomainObject_AbstractValueObject) {
675 $arguments[$argumentKey] = $this->convertTransientObjectToArray($argumentValue);
676 } else {
677 throw new Tx_Extbase_MVC_Exception_InvalidArgumentValue('Could not serialize Domain Object ' . get_class($argumentValue) . '. It is neither an Entity with identity properties set, nor a Value Object.', 1260881688);
678 }
679 } elseif (is_array($argumentValue)) {
680 $arguments[$argumentKey] = $this->convertDomainObjectsToIdentityArrays($argumentValue);
681 }
682 }
683 return $arguments;
684 }
685
686 /**
687 * Converts a given object recursively into an array.
688 *
689 * @param Tx_Extbase_DomainObject_AbstractDomainObject $object
690 * @return void
691 */
692 // TODO Refactore this into convertDomainObjectsToIdentityArrays()
693 public function convertTransientObjectToArray(Tx_Extbase_DomainObject_AbstractDomainObject $object) {
694 $result = array();
695 foreach ($object->_getProperties() as $propertyName => $propertyValue) {
696 if ($propertyValue instanceof Tx_Extbase_DomainObject_AbstractDomainObject) {
697 if ($propertyValue->getUid() !== NULL) {
698 $result[$propertyName] = $propertyValue->getUid();
699 } else {
700 $result[$propertyName] = $this->convertTransientObjectToArray($propertyValue);
701 }
702 } elseif (is_array($propertyValue)) {
703 $result[$propertyName] = $this->convertDomainObjectsToIdentityArrays($propertyValue);
704 } else {
705 $result[$propertyName] = $propertyValue;
706 }
707 }
708 return $result;
709 }
710
711 }
712 ?>