1
0
mirror of https://github.com/processwire/processwire.git synced 2025-08-09 16:26:59 +02:00

Update the new path hooks to support $wire->addHook('/foo/{bar}/baz', ...) where {bar} would match any segment and populate it to a $bar variable on the HookEvent. Plus add enforced trailing slash vs. non-trailing slash with redirect, depending on whether your match pattern uses a trailing slash.

This commit is contained in:
Ryan Cramer
2021-03-08 10:10:26 -05:00
parent cb9030a6ff
commit c0ffd35b7d
3 changed files with 92 additions and 22 deletions

View File

@@ -90,14 +90,14 @@ class HookEvent extends WireData {
*
*/
public function arguments($n = null, $value = null) {
if(is_null($n)) return $this->arguments;
if(!is_null($value)) {
if($n === null) return $this->data['arguments'];
if($value !== null) {
$this->setArgument($n, $value);
return $value;
}
if(isset($this->data['arguments'][$n])) return $this->data['arguments'][$n];
if(is_string($n)) return $this->argumentsByName($n);
$arguments = $this->arguments;
return isset($arguments[$n]) ? $arguments[$n] : null;
return null;
}
/**
@@ -119,7 +119,7 @@ class HookEvent extends WireData {
*/
public function argumentsByName($n = '') {
$arguments = $this->arguments();
$arguments = $this->data['arguments'];
if(isset($arguments[$n])) return $arguments[$n];
$names = $this->getArgumentNames();
@@ -165,9 +165,7 @@ class HookEvent extends WireData {
if($n === false) throw new WireException("Unknown argument name: $n");
}
$arguments = $this->arguments;
$arguments[(int)$n] = $value;
$this->set('arguments', $arguments);
$this->data['arguments'][(int)$n] = $value;
return $this;
}

View File

@@ -153,6 +153,14 @@ class WireHooks {
*/
protected $allowPathHooks = true;
/**
* Populated when a path hook requires a redirect
*
* @var string
*
*/
protected $pathHookRedirect = '';
/**
* @var ProcessWire
*
@@ -1067,7 +1075,9 @@ class WireHooks {
$pathHook = $this->pathHooks[$id];
$matchPath = $pathHook['match'];
$requestPath = $arguments[0];
$slashed = substr($requestPath, -1) === '/' && strlen($requestPath) > 1;
$filterFail = false;
$regexDelim = ''; // populated only for user-specified regex
// first pre-filter the requestPath against any words matchPath (filters)
foreach($pathHook['filters'] as $filter) {
@@ -1080,29 +1090,56 @@ class WireHooks {
if(strpos('!@#%', $matchPath[0]) !== false) {
// already in delimited regex format
$regexDelim = $matchPath[0];
} else {
// needs to be in regex format
if(strpos($matchPath, '/') === 0) $matchPath = "^$matchPath";
$matchPath = "!$matchPath$!";
$matchPath = "#$matchPath$#";
}
if(strpos($matchPath, ':') && strpos($matchPath, '(') !== false) {
// named arguments converted to named PCRE capture groups
$matchPath = preg_replace('!\(([-_a-z0-9]+):!i', '(?P<$1>', $matchPath);
// named arguments in format “(name: value)” converted to named PCRE capture groups
$matchPath = preg_replace('#\(([-_a-z0-9]+):#i', '(?P<$1>', $matchPath);
}
if(strpos($matchPath, '{') !== false) {
// named arguments in format “{name}” converted to named PCRE capture groups
// note that the match pattern of any URL segment is assumed for this case
$matchPath = preg_replace('#\{([_a-z][-_a-z0-9]*)\}#i', '(?P<$1>[^/]+)', $matchPath);
}
if(!preg_match($matchPath, $requestPath, $matches)) {
// if match fails, try again with trailing slash state reversed
if(substr($requestPath, -1) === '/') {
$requestPath = rtrim($requestPath, '/');
if($slashed) {
$requestPath2 = rtrim($requestPath, '/');
} else {
$requestPath .= '/';
$requestPath2 = "$requestPath/";
}
if(!preg_match($matchPath, $requestPath, $matches)) return false;
if(!preg_match($matchPath, $requestPath2, $matches)) return false;
}
// check on trailing slash
if(strpos($matchPath, '/?') === false) {
// either slash or no-slash is required, depending on whether match pattern ends with one
$slashRequired = substr(rtrim($pathHook['match'], $regexDelim . '$)+'), -1) === '/';
$this->pathHookRedirect = '';
if($slashRequired && !$slashed) {
// trailing slash required and not present
$this->pathHookRedirect = $requestPath . '/';
return false;
} else if(!$slashRequired && $slashed) {
// lack of trailing slash required and one is present
$this->pathHookRedirect = rtrim($requestPath, '/');
return false;
}
}
// success: at this point the requestPath has matched
$arguments['path'] = $arguments[0];
foreach($matches as $key => $value) {
if($key !== 0) $arguments[$key] = $value;
// populate requested arguments
if($key !== 0) $arguments[$key] = $value;
}
return true;
@@ -1254,6 +1291,17 @@ class WireHooks {
return $this->allowPathHooks;
}
/**
* Return redirect URL required by an applicable path hook, or blank otherwise
*
* @return string
* @since 3.0.173
*
*/
public function getPathHookRedirect() {
return $this->pathHookRedirect;
}
/**
* @return string
*

View File

@@ -196,7 +196,7 @@ class ProcessPageView extends Process {
if(!$this->delayRedirects) {
$this->checkProtocol($page);
if($this->redirectURL) $this->wire()->session->redirect($this->redirectURL);
if($this->redirectURL) $this->redirect($this->redirectURL);
}
$this->wire('page', $page);
@@ -205,7 +205,7 @@ class ProcessPageView extends Process {
if($this->delayRedirects) {
$this->checkProtocol($page);
if($this->redirectURL) $this->wire()->session->redirect($this->redirectURL);
if($this->redirectURL) $this->redirect($this->redirectURL);
}
try {
@@ -285,8 +285,15 @@ class ProcessPageView extends Process {
$this->setResponseType(self::responseTypeNoPage);
}
// did a path hook require a redirect for trailing slash (vs non-trailing slash)?
$redirect = $hooks->getPathHookRedirect();
if($redirect) {
$this->redirect($config->urls->root . ltrim($redirect, '/'));
}
$this->pathHooksReturnValue = false; // no longer applicable
$this->pathHooksReturnValue = false; // no longer applicable once this line reached
$hooks->allowPathHooks(false); // no more path hooks allowed
if($page && $page->id && $page->id !== $page404->id && $page instanceof Page) {
// one of the path hooks set the page
@@ -899,7 +906,7 @@ class ProcessPageView extends Process {
$config = $this->wire()->config;
$disallowIDs = array($config->trashPageID); // don't allow login redirect for these pages
$loginRequestURL = $this->redirectURL;
$loginPageID = $this->wire()->config->loginPageID;
$loginPageID = $config->loginPageID;
$requestPage = $page;
$session = $this->wire()->session;
$input = $this->wire()->input;
@@ -1107,7 +1114,7 @@ class ProcessPageView extends Process {
if(!$page->secureFiles()) {
// if file is not secured, redirect to it
// (potentially deprecated, only necessary for method 2 in checkRequestFile)
$this->wire()->session->redirect($page->filesManager->url() . $basename);
$this->redirect($page->filesManager->url() . $basename);
return;
}
@@ -1199,6 +1206,23 @@ class ProcessPageView extends Process {
$n++;
}
/**
* Perform redirect
*
* @param string $url
* @param bool $permanent
*
*/
protected function redirect($url, $permanent = true) {
$session = $this->wire()->session;
$this->setResponseType(self::responseTypeRedirect);
if($permanent) {
$session->redirect($url);
} else {
$session->location($url);
}
}
/**
* Return the response type for this request, as one of the responseType constants
*