/**
* Request/URL factory.
* @param Component base
* @param string destination in format "[//] [[[module:]presenter:]action | signal! | this] [#fragment]"
* @param array array of arguments
* @param string forward|redirect|link
* @return string URL
* @throws InvalidLinkException
* @internal
*/
protected function createRequest($component, $destination, array $args, $mode)
{
// note: createRequest supposes that saveState(), run() & tryCall() behaviour is final
$this->lastCreatedRequest = $this->lastCreatedRequestFlag = NULL;
// PARSE DESTINATION
// 1) fragment
$a = strpos($destination, '#');
if ($a === FALSE) {
$fragment = '';
} else {
$fragment = substr($destination, $a);
$destination = substr($destination, 0, $a);
}
// 2) ?query syntax
$a = strpos($destination, '?');
if ($a !== FALSE) {
parse_str(substr($destination, $a + 1), $args);
$destination = substr($destination, 0, $a);
}
// 3) URL scheme
$a = strpos($destination, '//');
if ($a === FALSE) {
$scheme = FALSE;
} else {
$scheme = substr($destination, 0, $a);
$destination = substr($destination, $a + 2);
}
// 4) signal or empty
if (!$component instanceof self || substr($destination, -1) === '!') {
list($cname, $signal) = Helpers::splitName(rtrim($destination, '!'));
if ($cname !== '') {
$component = $component->getComponent(strtr($cname, ':', '-'));
}
if ($signal === '') {
throw new InvalidLinkException('Signal must be non-empty string.');
}
$destination = 'this';
}
if ($destination == NULL) {
// intentionally ==
throw new InvalidLinkException('Destination must be non-empty string.');
}
// 5) presenter: action
$current = FALSE;
list($presenter, $action) = Helpers::splitName($destination);
if ($presenter === '') {
$action = $destination === 'this' ? $this->action : $action;
$presenter = $this->getName();
$presenterClass = get_class($this);
} else {
if ($presenter[0] === ':') {
// absolute
$presenter = substr($presenter, 1);
if (!$presenter) {
throw new InvalidLinkException("Missing presenter name in '{$destination}'.");
}
} else {
// relative
list($module, , $sep) = Helpers::splitName($this->getName());
$presenter = $module . $sep . $presenter;
}
if (!$this->presenterFactory) {
throw new Nette\InvalidStateException('Unable to create link to other presenter, service PresenterFactory has not been set.');
}
try {
$presenterClass = $this->presenterFactory->getPresenterClass($presenter);
} catch (Application\InvalidPresenterException $e) {
throw new InvalidLinkException($e->getMessage(), NULL, $e);
}
}
// PROCESS SIGNAL ARGUMENTS
if (isset($signal)) {
// $component must be IStatePersistent
$reflection = new ComponentReflection(get_class($component));
if ($signal === 'this') {
// means "no signal"
$signal = '';
if (array_key_exists(0, $args)) {
throw new InvalidLinkException("Unable to pass parameters to 'this!' signal.");
}
} elseif (strpos($signal, self::NAME_SEPARATOR) === FALSE) {
// counterpart of signalReceived() & tryCall()
$method = $component->formatSignalMethod($signal);
if (!$reflection->hasCallableMethod($method)) {
throw new InvalidLinkException("Unknown signal '{$signal}', missing handler {$reflection->getName()}::{$method}()");
}
// convert indexed parameters to named
self::argsToParams(get_class($component), $method, $args, [], $missing);
}
// counterpart of IStatePersistent
if ($args && array_intersect_key($args, $reflection->getPersistentParams())) {
$component->saveState($args);
}
if ($args && $component !== $this) {
$prefix = $component->getUniqueId() . self::NAME_SEPARATOR;
foreach ($args as $key => $val) {
unset($args[$key]);
$args[$prefix . $key] = $val;
}
}
}
// PROCESS ARGUMENTS
if (is_subclass_of($presenterClass, __CLASS__)) {
if ($action === '') {
$action = self::DEFAULT_ACTION;
}
$current = ($action === '*' || strcasecmp($action, $this->action) === 0) && $presenterClass === get_class($this);
$reflection = new ComponentReflection($presenterClass);
// counterpart of run() & tryCall()
$method = $presenterClass::formatActionMethod($action);
if (!$reflection->hasCallableMethod($method)) {
$method = $presenterClass::formatRenderMethod($action);
if (!$reflection->hasCallableMethod($method)) {
$method = NULL;
}
}
// convert indexed parameters to named
if ($method === NULL) {
if (array_key_exists(0, $args)) {
throw new InvalidLinkException("Unable to pass parameters to action '{$presenter}:{$action}', missing corresponding method.");
}
} else {
self::argsToParams($presenterClass, $method, $args, $destination === 'this' ? $this->params : [], $missing);
}
// counterpart of IStatePersistent
if ($args && array_intersect_key($args, $reflection->getPersistentParams())) {
$this->saveState($args, $reflection);
}
if ($mode === 'redirect') {
$this->saveGlobalState();
}
$globalState = $this->getGlobalState($destination === 'this' ? NULL : $presenterClass);
if ($current && $args) {
$tmp = $globalState + $this->params;
foreach ($args as $key => $val) {
if (http_build_query([$val]) !== (isset($tmp[$key]) ? http_build_query([$tmp[$key]]) : '')) {
$current = FALSE;
break;
}
}
}
$args += $globalState;
}
if ($mode !== 'test' && !empty($missing)) {
foreach ($missing as $rp) {
if (!array_key_exists($rp->getName(), $args)) {
throw new InvalidLinkException("Missing parameter \${$rp->getName()} required by {$rp->getDeclaringClass()->getName()}::{$rp->getDeclaringFunction()->getName()}()");
}
}
}
// ADD ACTION & SIGNAL & FLASH
if ($action) {
$args[self::ACTION_KEY] = $action;
}
if (!empty($signal)) {
$args[self::SIGNAL_KEY] = $component->getParameterId($signal);
$current = $current && $args[self::SIGNAL_KEY] === $this->getParameter(self::SIGNAL_KEY);
}
if (($mode === 'redirect' || $mode === 'forward') && $this->hasFlashSession()) {
$args[self::FLASH_KEY] = $this->getFlashKey();
}
$this->lastCreatedRequest = new Application\Request($presenter, Application\Request::FORWARD, $args, [], []);
$this->lastCreatedRequestFlag = ['current' => $current];
if ($mode === 'forward' || $mode === 'test') {
return;
}
// CONSTRUCT URL
if ($this->refUrlCache === NULL) {
$this->refUrlCache = new Http\Url($this->httpRequest->getUrl());
$this->refUrlCache->setPath($this->httpRequest->getUrl()->getScriptPath());
}
if (!$this->router) {
throw new Nette\InvalidStateException('Unable to generate URL, service Router has not been set.');
}
$url = $this->router->constructUrl($this->lastCreatedRequest, $this->refUrlCache);
if ($url === NULL) {
unset($args[self::ACTION_KEY]);
$params = urldecode(http_build_query($args, NULL, ', '));
throw new InvalidLinkException("No route for {$presenter}:{$action}({$params})");
}
// make URL relative if possible
if ($mode === 'link' && $scheme === FALSE && !$this->absoluteUrls) {
$hostUrl = $this->refUrlCache->getHostUrl() . '/';
if (strncmp($url, $hostUrl, strlen($hostUrl)) === 0) {
$url = substr($url, strlen($hostUrl) - 1);
}
}
return $url . $fragment;
}