711 lines
20 KiB
Text
711 lines
20 KiB
Text
|
<?php namespace ProcessWire;
|
||
|
|
||
|
/**
|
||
|
* ProcessWire Page View Process
|
||
|
*
|
||
|
* Enables viewing or Processes, one of the core components in connecting ProcessWire to HTTP.
|
||
|
*
|
||
|
* For more details about how Process modules work, please see:
|
||
|
* /wire/core/Process.php
|
||
|
*
|
||
|
* ProcessWire 3.x, Copyright 2023 by Ryan Cramer
|
||
|
* https://processwire.com
|
||
|
*
|
||
|
* @method string execute($internal = true)
|
||
|
* @method string executeExternal()
|
||
|
* @method ready(array $data = array())
|
||
|
* @method finished(array $data = array())
|
||
|
* @method failed(\Exception $e, $reason = '', $page = null, $url = '')
|
||
|
* @method sendFile($page, $basename)
|
||
|
* @method string pageNotFound($page, $url, $triggerReady = false, $reason = '', \Exception $e = null)
|
||
|
* @method string|bool|array|Page pathHooks($path, $out)
|
||
|
* @method void userNotAllowed(User $user, $page, PagesRequest $request)
|
||
|
*
|
||
|
*/
|
||
|
class ProcessPageView extends Process {
|
||
|
|
||
|
public static function getModuleInfo() {
|
||
|
return array(
|
||
|
'title' => __('Page View', __FILE__), // getModuleInfo title
|
||
|
'summary' => __('All page views are routed through this Process', __FILE__), // getModuleInfo summary
|
||
|
'version' => 106,
|
||
|
'permanent' => true,
|
||
|
'permission' => 'page-view',
|
||
|
);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Response types
|
||
|
*
|
||
|
*/
|
||
|
const responseTypeError = 0;
|
||
|
const responseTypeNormal = 1;
|
||
|
const responseTypeAjax = 2;
|
||
|
const responseTypeFile = 4;
|
||
|
const responseTypeRedirect = 8;
|
||
|
const responseTypeExternal = 16;
|
||
|
const responseTypeNoPage = 32;
|
||
|
const responseTypePathHook = 64;
|
||
|
|
||
|
/**
|
||
|
* Response type (see response type codes above)
|
||
|
*
|
||
|
*/
|
||
|
protected $responseType = 1;
|
||
|
|
||
|
/**
|
||
|
* True if any redirects should be delayed until after API ready() has been issued
|
||
|
*
|
||
|
*/
|
||
|
protected $delayRedirects = false;
|
||
|
|
||
|
/**
|
||
|
* @var Page|null
|
||
|
*
|
||
|
*/
|
||
|
protected $http404Page = null;
|
||
|
|
||
|
/**
|
||
|
* Return value from first iteration of pathHooks() method (when applicable)
|
||
|
*
|
||
|
* @var mixed
|
||
|
*
|
||
|
*/
|
||
|
protected $pathHooksReturnValue = false;
|
||
|
|
||
|
/**
|
||
|
* Construct
|
||
|
*
|
||
|
*/
|
||
|
public function __construct() {} // no parent call intentional
|
||
|
|
||
|
/**
|
||
|
* Init
|
||
|
*
|
||
|
*/
|
||
|
public function init() {} // no parent call intentional
|
||
|
|
||
|
/**
|
||
|
* Retrieve a page, check access, and render
|
||
|
*
|
||
|
* @param bool $internal True if request should be internally processed. False if PW is bootstrapped externally.
|
||
|
* @return string Output of request
|
||
|
*
|
||
|
*/
|
||
|
public function ___execute($internal = true) {
|
||
|
|
||
|
if(!$internal) return $this->executeExternal();
|
||
|
|
||
|
$this->responseType = self::responseTypeNormal;
|
||
|
$config = $this->wire()->config;
|
||
|
$pages = $this->wire()->pages;
|
||
|
$request = $pages->request();
|
||
|
$timerKey = $config->debug ? 'ProcessPageView.getPage()' : '';
|
||
|
|
||
|
if($config->usePoweredBy !== null) header('X-Powered-By:' . ($config->usePoweredBy ? ' ProcessWire CMS' : ''));
|
||
|
|
||
|
$pages->setOutputFormatting(true);
|
||
|
|
||
|
if($timerKey) Debug::timer($timerKey);
|
||
|
$page = $this->getPage();
|
||
|
if($timerKey) Debug::saveTimer($timerKey, ($page && $page->id ? $page->path : ''));
|
||
|
|
||
|
if($page && $page->id) {
|
||
|
return $this->renderPage($page, $request);
|
||
|
} else {
|
||
|
return $this->renderNoPage($request);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get requested page
|
||
|
*
|
||
|
* @return NullPage|Page
|
||
|
* @throws WireException
|
||
|
*
|
||
|
*/
|
||
|
public function getPage() {
|
||
|
return $this->wire()->pages->request()->getPage();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Render Page
|
||
|
*
|
||
|
* @param Page $page
|
||
|
* @param PagesRequest $request
|
||
|
* @return bool|mixed|string
|
||
|
* @throws WireException
|
||
|
* @since 3.0.173
|
||
|
*
|
||
|
*/
|
||
|
protected function renderPage(Page $page, PagesRequest $request) {
|
||
|
|
||
|
$config = $this->wire()->config;
|
||
|
$user = $this->wire()->user;
|
||
|
|
||
|
$page->of(true);
|
||
|
$originalPage = $page;
|
||
|
$page = $request->getPageForUser($page, $user);
|
||
|
$code = $request->getResponseCode();
|
||
|
|
||
|
if($code == 401 || $code == 403) {
|
||
|
$this->userNotAllowed($user, $originalPage, $request);
|
||
|
}
|
||
|
|
||
|
if(!$page || !$page->id || $originalPage->id == $config->http404PageID) {
|
||
|
$this->checkForRedirect($request);
|
||
|
$s = 'access not allowed';
|
||
|
$e = new Wire404Exception($s, Wire404Exception::codePermission);
|
||
|
return $this->pageNotFound($originalPage, $request->getRequestPath(), true, $s, $e);
|
||
|
}
|
||
|
|
||
|
if(!$this->delayRedirects) $this->checkForRedirect($request);
|
||
|
|
||
|
$this->wire('page', $page);
|
||
|
$this->ready();
|
||
|
$page = $this->wire()->page; // in case anything changed it
|
||
|
|
||
|
if($this->delayRedirects) {
|
||
|
if($page !== $originalPage) $request->checkScheme($page);
|
||
|
$this->checkForRedirect($request);
|
||
|
}
|
||
|
|
||
|
try {
|
||
|
$file = $request->getFile();
|
||
|
if($file) {
|
||
|
$this->responseType = self::responseTypeFile;
|
||
|
$this->wire()->setStatus(ProcessWire::statusDownload, array('downloadFile' => $file));
|
||
|
$this->sendFile($page, $file);
|
||
|
|
||
|
} else {
|
||
|
$contentType = $this->contentTypeHeader($page, true);
|
||
|
$this->wire()->setStatus(ProcessWire::statusRender, array('contentType' => $contentType));
|
||
|
if($config->ajax) $this->responseType = self::responseTypeAjax;
|
||
|
return $page->render();
|
||
|
}
|
||
|
|
||
|
} catch(Wire404Exception $e) {
|
||
|
// 404 exception thrown during page render
|
||
|
TemplateFile::clearAll();
|
||
|
return $this->renderNoPage($request, array(
|
||
|
'reason404' => '404 thrown during page render',
|
||
|
'exception404' => $e,
|
||
|
'page' => $page,
|
||
|
'ready' => true, // let it know ready state already executed
|
||
|
));
|
||
|
|
||
|
} catch(\Exception $e) {
|
||
|
// other exception thrown during page render (re-throw non 404 exceptions)
|
||
|
$this->responseType = self::responseTypeError;
|
||
|
$this->failed($e, "Thrown during page render", $page);
|
||
|
throw $e;
|
||
|
}
|
||
|
|
||
|
return '';
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Render when no page mapped to request URL
|
||
|
*
|
||
|
* @param PagesRequest $request
|
||
|
* @param array $options
|
||
|
* @return array|bool|false|string
|
||
|
* @throws WireException
|
||
|
* @since 3.0.173
|
||
|
*
|
||
|
*/
|
||
|
protected function renderNoPage(PagesRequest $request, array $options = array()) {
|
||
|
|
||
|
$defaults = array(
|
||
|
'allow404' => true, // allow 404 to be thrown?
|
||
|
'reason404' => 'Requested URL did not resolve to a Page',
|
||
|
'exception404' => null,
|
||
|
'ready' => false, // are we executing from the API ready state?
|
||
|
'page' => $this->http404Page(), // potential Page object (default is 404 page)
|
||
|
);
|
||
|
|
||
|
$options = count($options) ? array_merge($defaults, $options) : $defaults;
|
||
|
|
||
|
$config = $this->wire()->config;
|
||
|
$hooks = $this->wire()->hooks;
|
||
|
$input = $this->wire()->input;
|
||
|
$pages = $this->wire()->pages;
|
||
|
|
||
|
$requestPath = $request->getRequestPath();
|
||
|
$pageNumPrefix = $request->getPageNumPrefix();
|
||
|
$pageNumSegment = '';
|
||
|
$setPageNum = 0;
|
||
|
$page = null;
|
||
|
$out = false;
|
||
|
|
||
|
$this->setResponseType(self::responseTypeNoPage);
|
||
|
|
||
|
if($pageNumPrefix !== null) {
|
||
|
// request may have a pagination segment
|
||
|
$pageNumSegment = $this->renderNoPagePagination($requestPath, $pageNumPrefix, $request->getPageNum());
|
||
|
$setPageNum = $input->pageNum();
|
||
|
}
|
||
|
|
||
|
if(!$options['ready']) $this->wire('page', $options['page']);
|
||
|
|
||
|
// run up to 2 times, once before ready state and once after
|
||
|
for($n = 1; $n <= 2; $n++) {
|
||
|
|
||
|
// only run once if already in ready state
|
||
|
if($options['ready']) $n = 2;
|
||
|
|
||
|
// call ready() on 2nd iteration only, allows for ready hooks to set $page
|
||
|
if($n === 2 && !$options['ready']) $this->ready();
|
||
|
|
||
|
if(!$hooks->hasPathHooks()) continue;
|
||
|
|
||
|
$this->setResponseType(self::responseTypePathHook);
|
||
|
|
||
|
try {
|
||
|
$out = $this->pathHooks($requestPath, $out);
|
||
|
} catch(Wire404Exception $e) {
|
||
|
$out = false;
|
||
|
}
|
||
|
|
||
|
// allow for pathHooks() $event->return to persist between init and ready states
|
||
|
// this makes it possible for ready() call to examine $event->return from init() call
|
||
|
// in case it wants to concatenate it or something
|
||
|
if($n === 1) $this->pathHooksReturnValue = $out;
|
||
|
|
||
|
if($out instanceof Page) {
|
||
|
// hook returned Page object to set as page to render
|
||
|
$page = $out;
|
||
|
$out = true;
|
||
|
} else {
|
||
|
// check if hooks changed $page API variable instead
|
||
|
$page = $this->wire()->page;
|
||
|
}
|
||
|
|
||
|
// first hook that determines the $page wins
|
||
|
if($page && $page->id && $page->id !== $options['page']->id) break;
|
||
|
|
||
|
$this->setResponseType(self::responseTypeNoPage);
|
||
|
}
|
||
|
|
||
|
// did a path hook require a redirect for trailing slash (vs non-trailing slash)?
|
||
|
$redirect = $hooks->getPathHookRedirect();
|
||
|
if($redirect) {
|
||
|
// path hook suggests a redirect for proper URL format
|
||
|
$url = $config->urls->root . ltrim($redirect, '/');
|
||
|
// if present, add pagination segment back into URL
|
||
|
if($pageNumSegment) $url = rtrim($url, '/') . "/$pageNumSegment";
|
||
|
$this->redirect($url);
|
||
|
}
|
||
|
|
||
|
$this->pathHooksReturnValue = false; // no longer applicable once this line reached
|
||
|
$hooks->allowPathHooks(false); // no more path hooks allowed
|
||
|
|
||
|
if($page instanceof Page && $page->id && $page->id !== $options['page']->id) {
|
||
|
// one of the path hooks set the page
|
||
|
$this->wire('page', $page);
|
||
|
return $this->renderPage($page, $request);
|
||
|
}
|
||
|
|
||
|
if($out === false) {
|
||
|
// hook failed to handle request
|
||
|
if($setPageNum > 1) $input->setPageNum(1);
|
||
|
if($options['allow404']) {
|
||
|
$page = $options['page'];
|
||
|
// hooks to pageNotFound() method may expect NullPage rather than 404 page
|
||
|
if($page->id == $config->http404PageID) $page = $pages->newNullPage();
|
||
|
$out = $this->pageNotFound($page, $requestPath, false, $options['reason404'], $options['exception404']);
|
||
|
} else {
|
||
|
$out = false;
|
||
|
}
|
||
|
|
||
|
} else if($out === true) {
|
||
|
// hook silently handled the request
|
||
|
$out = '';
|
||
|
|
||
|
} else if(is_array($out)) {
|
||
|
// hook returned array to convert to JSON
|
||
|
$jsonFlags = JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES;
|
||
|
$contentTypes = $config->contentTypes;
|
||
|
if(isset($contentTypes['json'])) header("Content-Type: $contentTypes[json]");
|
||
|
$out = json_encode($out, $jsonFlags);
|
||
|
}
|
||
|
|
||
|
return $out;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Check for pagination in a no-page request (helper to renderNoPage method)
|
||
|
*
|
||
|
* - Updates given request path to remove pagination segment.
|
||
|
* - Returns found pagination segment or blank if none.
|
||
|
* - Redirects to non-slash version if: pagination segment found with trailing slash,
|
||
|
* no $page API var was present, or $page present but does not allow slash.
|
||
|
*
|
||
|
* @param string $requestPath
|
||
|
* @param string|null $pageNumPrefix
|
||
|
* @param int $pageNum
|
||
|
* @return string Return found pageNum segment or blank if none
|
||
|
*
|
||
|
*/
|
||
|
protected function renderNoPagePagination(&$requestPath, $pageNumPrefix, $pageNum) {
|
||
|
|
||
|
$config = $this->wire()->config;
|
||
|
|
||
|
if($pageNum < 1 || $pageNumPrefix === null) return '';
|
||
|
|
||
|
// there is a pagination segment present in the request path
|
||
|
$slash = substr($requestPath, -1) === '/' ? '/' : '';
|
||
|
$requestPath = rtrim($requestPath, '/');
|
||
|
$pageNumSegment = $pageNumPrefix . $pageNum;
|
||
|
|
||
|
if(substr($requestPath, -1 * strlen($pageNumSegment)) === $pageNumSegment) {
|
||
|
// remove pagination segment from request path
|
||
|
$requestPath = substr($requestPath, 0, -1 * strlen($pageNumSegment));
|
||
|
$setPageNum = (int) $pageNum;
|
||
|
if($setPageNum === 1) {
|
||
|
// disallow specific "/page1" in URL as it is implied by the lack of pagination segment
|
||
|
$this->redirect($config->urls->root . ltrim($requestPath, '/'));
|
||
|
} else if($slash) {
|
||
|
// a trailing slash is present after the pageNum i.e. /page9/
|
||
|
$page = $this->wire()->page;
|
||
|
// a $page API var will be present if a 404 was manually thrown from a template file
|
||
|
// but it likely won't be present if we are leading to a path hook
|
||
|
if(!$page || !$page->id || !$page->template || !$page->template->allowPageNum) $page = null;
|
||
|
if($page && ((int) $page->template->slashPageNum) > -1) {
|
||
|
// $page API var present and trailing slash is okay
|
||
|
} else {
|
||
|
// no $page API var present or trailing slash on pageNum disallowed
|
||
|
// enforce no trailing slashes for pagination numbers
|
||
|
$this->redirect($config->urls->root . ltrim($requestPath, '/') . $pageNumSegment);
|
||
|
}
|
||
|
}
|
||
|
$this->wire()->input->setPageNum($pageNum);
|
||
|
|
||
|
} else {
|
||
|
// not a pagination segment
|
||
|
// add the slash back to restore requestPath
|
||
|
$requestPath .= $slash;
|
||
|
$pageNumSegment = '';
|
||
|
}
|
||
|
|
||
|
return $pageNumSegment;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Called when a 401 unauthorized or 403 forbidden request
|
||
|
*
|
||
|
* #pw-hooker
|
||
|
*
|
||
|
* @param User $user
|
||
|
* @param Page|NullPage|null $page
|
||
|
* @param PagesRequest $request
|
||
|
* @since 3.0.186
|
||
|
*
|
||
|
*/
|
||
|
protected function ___userNotAllowed(User $user, $page, PagesRequest $request) {
|
||
|
|
||
|
$input = $this->wire()->input;
|
||
|
$config = $this->wire()->config;
|
||
|
$session = $this->wire()->session;
|
||
|
|
||
|
if(!$session || !$page || !$page->id) return;
|
||
|
if($user->isLoggedin()) return;
|
||
|
|
||
|
$loginRequestURL = $request->getRedirectUrl();
|
||
|
$ns = 'ProcessPageView'; // session namespace
|
||
|
|
||
|
if(empty($loginRequestURL)) {
|
||
|
$loginRequestURL = $session->getFor($ns, 'loginRequestURL');
|
||
|
}
|
||
|
|
||
|
if(!empty($loginRequestURL)) return;
|
||
|
if($page->id == $config->loginPageID) return;
|
||
|
if($input->get('loggedout')) return;
|
||
|
|
||
|
$loginRequestURL = $input->url(array('page' => $page));
|
||
|
|
||
|
if(!empty($_GET)) {
|
||
|
$queryString = $input->queryStringClean(array(
|
||
|
'maxItems' => 10,
|
||
|
'maxLength' => 500,
|
||
|
'maxNameLength' => 20,
|
||
|
'maxValueLength' => 200,
|
||
|
'sanitizeName' => 'fieldName',
|
||
|
'sanitizeValue' => 'name',
|
||
|
'entityEncode' => false,
|
||
|
));
|
||
|
if(strlen($queryString)) $loginRequestURL .= "?$queryString";
|
||
|
}
|
||
|
|
||
|
$session->setFor($ns, 'loginRequestPageID', $page->id);
|
||
|
$session->setFor($ns, 'loginRequestURL', $loginRequestURL);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Check request for redirect and apply it when appropriate
|
||
|
*
|
||
|
* @param PagesRequest $request
|
||
|
*
|
||
|
*/
|
||
|
protected function checkForRedirect(PagesRequest $request) {
|
||
|
$redirectUrl = $request->getRedirectUrl();
|
||
|
if($redirectUrl) $this->redirect($redirectUrl, $request->getRedirectType());
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Get and optionally send the content-type header
|
||
|
*
|
||
|
* @param Page $page
|
||
|
* @param bool $send
|
||
|
* @return string
|
||
|
*
|
||
|
*/
|
||
|
protected function contentTypeHeader(Page $page, $send = false) {
|
||
|
|
||
|
$config = $this->wire()->config;
|
||
|
$contentType = $page->template->contentType;
|
||
|
|
||
|
if(!$contentType) return '';
|
||
|
|
||
|
if(strpos($contentType, '/') === false) {
|
||
|
if(isset($config->contentTypes[$contentType])) {
|
||
|
$contentType = $config->contentTypes[$contentType];
|
||
|
} else {
|
||
|
$contentType = '';
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if($contentType && $send) header("Content-Type: $contentType");
|
||
|
|
||
|
return $contentType;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Method executed when externally bootstrapped
|
||
|
*
|
||
|
* @return string blank string
|
||
|
*
|
||
|
*/
|
||
|
public function ___executeExternal() {
|
||
|
$this->setResponseType(self::responseTypeExternal);
|
||
|
$config = $this->wire()->config;
|
||
|
$config->external = true;
|
||
|
if($config->externalPageID) {
|
||
|
$page = $this->wire()->pages->get((int) $config->externalPageID);
|
||
|
} else {
|
||
|
$page = $this->wire()->pages->newNullPage();
|
||
|
}
|
||
|
$this->wire('page', $page);
|
||
|
$this->ready();
|
||
|
$this->wire()->setStatus(ProcessWire::statusRender, array('contentType' => 'external'));
|
||
|
return '';
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Hook called when the $page API var is ready, and before the $page is rendered.
|
||
|
*
|
||
|
* @param array $data
|
||
|
*
|
||
|
*/
|
||
|
public function ___ready(array $data = array()) {
|
||
|
$this->wire()->setStatus(ProcessWire::statusReady, $data);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Hook called with the pageview has been finished and output has been sent. Note this is called in /index.php.
|
||
|
*
|
||
|
* @param array $data
|
||
|
*
|
||
|
*/
|
||
|
public function ___finished(array $data = array()) {
|
||
|
$this->wire()->setStatus(ProcessWire::statusFinished, $data);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Hook called when the pageview failed to finish due to an Exception or Error.
|
||
|
*
|
||
|
* Sends a copy of the throwable that occurred.
|
||
|
*
|
||
|
* @param \Throwable $e Exception or Error
|
||
|
* @param string $reason
|
||
|
* @param Page|null $page
|
||
|
* @param string $url
|
||
|
*
|
||
|
*/
|
||
|
public function ___failed($e, $reason = '', $page = null, $url = '') {
|
||
|
$this->wire()->setStatusFailed($e, $reason, $page, $url);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Passthru a file for a non-public page
|
||
|
*
|
||
|
* If the page is public, then it just does a 301 redirect to the file.
|
||
|
*
|
||
|
* @param Page $page
|
||
|
* @param string $basename
|
||
|
* @param array $options
|
||
|
* @throws Wire404Exception
|
||
|
*
|
||
|
*/
|
||
|
protected function ___sendFile($page, $basename, array $options = array()) {
|
||
|
|
||
|
$err = 'File not found';
|
||
|
|
||
|
if(!$page->hasFilesPath()) {
|
||
|
throw new Wire404Exception($err, Wire404Exception::codeFile);
|
||
|
}
|
||
|
|
||
|
$filename = $page->filesPath() . $basename;
|
||
|
|
||
|
if(!file_exists($filename)) {
|
||
|
throw new Wire404Exception($err, Wire404Exception::codeFile);
|
||
|
}
|
||
|
|
||
|
if(!$page->secureFiles()) {
|
||
|
// if file is not secured, redirect to it
|
||
|
// (potentially deprecated, only necessary for method 2 in checkRequestFile)
|
||
|
$this->redirect($page->filesManager->url() . $basename);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// options for WireHttp::sendFile
|
||
|
$defaults = array('exit' => false, 'limitPath' => $page->filesPath());
|
||
|
$options = array_merge($defaults, $options);
|
||
|
|
||
|
$this->wire()->files->send($filename, $options);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Called when a page is not found, sends 404 header, and displays the configured 404 page instead.
|
||
|
*
|
||
|
* Method is hookable, for instance if you wanted to log 404s. When hooking this method note that it
|
||
|
* must be hooked sometime before the ready state.
|
||
|
*
|
||
|
* @param Page|null $page Page that was found if applicable (like if user didn't have permission or $page's template threw the 404). If not applicable then NULL will be given instead.
|
||
|
* @param string $url The URL that the request originated from (like $_SERVER['REQUEST_URI'] but already sanitized)
|
||
|
* @param bool $triggerReady Whether or not the ready() hook should be triggered (default=false)
|
||
|
* @param string $reason Reason why 404 occurred, for debugging purposes (en text)
|
||
|
* @param WireException|Wire404Exception $exception Exception that was thrown or that indicates details of error
|
||
|
* @throws WireException
|
||
|
* @return string
|
||
|
*/
|
||
|
protected function ___pageNotFound($page, $url, $triggerReady = false, $reason = '', $exception = null) {
|
||
|
|
||
|
if(!$exception) {
|
||
|
// create exception but do not throw
|
||
|
$exception = new Wire404Exception($reason, Wire404Exception::codeNonexist);
|
||
|
}
|
||
|
|
||
|
$this->failed($exception, $reason, $page, $url);
|
||
|
$this->responseType = self::responseTypeError;
|
||
|
$this->header404();
|
||
|
|
||
|
$page = $this->http404Page();
|
||
|
if($page->id) {
|
||
|
$this->wire('page', $page);
|
||
|
if($triggerReady) $this->ready();
|
||
|
return $page->render();
|
||
|
} else {
|
||
|
return "404 page not found";
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Handler for path hooks
|
||
|
*
|
||
|
* No need to hook this method directly, instead use a path hook.
|
||
|
*
|
||
|
* #pw-internal
|
||
|
*
|
||
|
* @param string $path
|
||
|
* @param bool|string|array|Page Output so far, or false if none
|
||
|
* @return bool|string|array
|
||
|
* Return false if path cannot be handled
|
||
|
* Return true if path handled silently
|
||
|
* Return string for output to send
|
||
|
* Return array for JSON output to send
|
||
|
* Return Page object to make it the page that is rendered
|
||
|
*
|
||
|
*/
|
||
|
protected function ___pathHooks($path, $out) {
|
||
|
if($path && $out) {} // ignore
|
||
|
return $this->pathHooksReturnValue;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* @return NullPage|Page
|
||
|
*
|
||
|
*/
|
||
|
protected function http404Page() {
|
||
|
if($this->http404Page) return $this->http404Page;
|
||
|
$config = $this->config;
|
||
|
$pages = $this->wire()->pages;
|
||
|
$this->http404Page = $config->http404PageID ? $pages->get($config->http404PageID) : $pages->newNullPage();
|
||
|
return $this->http404Page;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Send a 404 header, but not more than once per request
|
||
|
*
|
||
|
*/
|
||
|
protected function header404() {
|
||
|
static $n = 0;
|
||
|
if($n) return;
|
||
|
$http = new WireHttp();
|
||
|
$this->wire($http);
|
||
|
$http->sendStatusHeader(404);
|
||
|
$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 === true || $permanent === 301) {
|
||
|
$session->redirect($url);
|
||
|
} else {
|
||
|
$session->location($url);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Return the response type for this request, as one of the responseType constants
|
||
|
*
|
||
|
* @return int
|
||
|
*
|
||
|
*/
|
||
|
public function getResponseType() {
|
||
|
return $this->responseType;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set the response type for this request, see responseType constants in this class
|
||
|
*
|
||
|
* @param int $responseType
|
||
|
*
|
||
|
*/
|
||
|
public function setResponseType($responseType) {
|
||
|
$this->responseType = (int) $responseType;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set whether any redirects should be performed after the API ready() call
|
||
|
*
|
||
|
* This is used by LanguageSupportPageNames to delay redirects until after http/https schema is determined.
|
||
|
*
|
||
|
* @param bool $delayRedirects
|
||
|
*
|
||
|
*/
|
||
|
public function setDelayRedirects($delayRedirects) {
|
||
|
$this->delayRedirects = $delayRedirects ? true : false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|