Version 4.7.23
This commit is contained in:
1 parent
7124a02564
commit
25ddeb65d6
1791 files changed
+76990
-44452
No files matched your search
+169
-50
@@ -11,6 +11,10 @@
|
||||
namespace IPS;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
|
||||
use function class_exists;
|
||||
use function time;
|
||||
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
@@ -235,6 +239,11 @@ class _Output
|
||||
* @brief Data which were loaded via the GraphQL framework, but which have to be immediately available, rather then via later AJAX requests
|
||||
*/
|
||||
public $graphData = [];
|
||||
|
||||
/**
|
||||
* @brief A custom cache DateTime object set to UTC
|
||||
*/
|
||||
protected static bool|null|DateTime $cacheDate = NULL;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
@@ -245,6 +254,9 @@ class _Output
|
||||
{
|
||||
if ( \IPS\Dispatcher::hasInstance() and \IPS\Dispatcher::i()->controllerLocation !== 'setup' )
|
||||
{
|
||||
/* Additional security: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Cross-Origin-Opener-Policy */
|
||||
$this->httpHeaders['Cross-Origin-Opener-Policy'] = "same-origin";
|
||||
|
||||
if ( \IPS\Settings::i()->clickjackprevention == 'csp' )
|
||||
{
|
||||
$this->httpHeaders['Content-Security-Policy'] = \IPS\Settings::i()->csp_header;
|
||||
@@ -371,7 +383,7 @@ class _Output
|
||||
/* {location}_{section}.js */
|
||||
else if ( mb_strstr( $file, '_') AND mb_substr( $file, -3 ) === '.js' )
|
||||
{
|
||||
list( $location, $key ) = explode( '_', mb_substr( $file, 0, -3 ) );
|
||||
[ $location, $key ] = explode( '_', mb_substr( $file, 0, -3 ) );
|
||||
|
||||
if ( ( $location == 'front' OR $location == 'admin' OR $location == 'global' ) AND ! empty( $key ) )
|
||||
{
|
||||
@@ -472,6 +484,17 @@ class _Output
|
||||
return $title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store any custom DateTime for cache headers
|
||||
*
|
||||
* @param DateTime|bool $date
|
||||
* @return void
|
||||
*/
|
||||
public static function setCacheTime( \IPS\DateTime|bool $date=false ): void
|
||||
{
|
||||
static::$cacheDate = $date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve cache headers
|
||||
*
|
||||
@@ -481,10 +504,43 @@ class _Output
|
||||
*/
|
||||
public static function getCacheHeaders( int $lastModified, int $cacheSeconds ): array
|
||||
{
|
||||
/* The default is null, so if it is to false from setCacheTime, we should not cache */
|
||||
if ( static::$cacheDate === false )
|
||||
{
|
||||
return static::getNoCacheHeaders();
|
||||
}
|
||||
|
||||
$expires = \IPS\DateTime::ts( ( time() + $cacheSeconds ), TRUE );
|
||||
if ( static::$cacheDate instanceof \DateTime )
|
||||
{
|
||||
$expires = static::$cacheDate;
|
||||
$cacheSeconds = $expires->getTimestamp() - time();
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Set the cache date to the expiration date in case anything intercepts the output process and checks the cacheDate */
|
||||
static::$cacheDate = $expires;
|
||||
}
|
||||
|
||||
if ( \IPS\CIC or \IPS\IN_DEV )
|
||||
{
|
||||
return array(
|
||||
'Date' => \IPS\DateTime::ts( time(), TRUE )->rfc1123(),
|
||||
'Last-Modified' => \IPS\DateTime::ts( $lastModified, TRUE )->rfc1123(),
|
||||
'Expires' => $expires->rfc1123(),
|
||||
'Cache-Control' => implode( ', ', [
|
||||
'max-age=0', // No cache for the browser [https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#response_directives]
|
||||
'public', // Public cache
|
||||
's-maxage=' . $cacheSeconds, // Cache for the CDN [https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#response_directives]
|
||||
'stale-if-error' // Allow the CDN to serve stale content if there is an error
|
||||
] )
|
||||
);
|
||||
}
|
||||
|
||||
return array(
|
||||
'Date' => \IPS\DateTime::ts( time(), TRUE )->rfc1123(),
|
||||
'Last-Modified' => \IPS\DateTime::ts( $lastModified, TRUE )->rfc1123(),
|
||||
'Expires' => \IPS\DateTime::ts( ( time() + $cacheSeconds ), TRUE )->rfc1123(),
|
||||
'Expires' => $expires->rfc1123(),
|
||||
'Cache-Control' => 'no-cache="Set-Cookie", max-age=' . $cacheSeconds . ", public, s-maxage=" . $cacheSeconds . ", stale-while-revalidate, stale-if-error",
|
||||
);
|
||||
}
|
||||
@@ -713,7 +769,7 @@ class _Output
|
||||
|
||||
/* Log */
|
||||
$level = \intval( \substr( $code, 0, 1 ) );
|
||||
if( !\IPS\Session::i()->userAgent->spider )
|
||||
if( !\IPS\Session::i()->userAgent->bot )
|
||||
{
|
||||
if( $code and \IPS\Settings::i()->error_log_level and $level >= \IPS\Settings::i()->error_log_level )
|
||||
{
|
||||
@@ -755,16 +811,6 @@ class _Output
|
||||
}
|
||||
}
|
||||
|
||||
/* Send default Cache Headers if this is a front-end page. */
|
||||
$allowGuestCache = FALSE;
|
||||
if ( !isset( $httpHeaders['Cache-Control'] ) AND ( $httpStatusCode == 404 OR $httpStatusCode == 403 ) )
|
||||
{
|
||||
if ( \IPS\CACHE_PAGE_TIMEOUT AND $this->pageCaching AND \IPS\Dispatcher::hasInstance() AND \IPS\Dispatcher::i()->controllerLocation === 'front' AND !\IPS\Member::loggedIn()->member_id )
|
||||
{
|
||||
$httpHeaders += static::getCacheHeaders( time(), \IPS\CACHE_PAGE_TIMEOUT );
|
||||
}
|
||||
}
|
||||
|
||||
/* Send output */
|
||||
\IPS\Output::i()->sidebar['enabled'] = FALSE;
|
||||
$this->sendOutput( \IPS\Theme::i()->getTemplate( 'global', 'core' )->globalTemplate( $title, \IPS\Theme::i()->getTemplate( 'global', 'core' )->error( $title, $message, $code, $extra, $member, $faulty, $httpStatusCode ), array( 'app' => \IPS\Dispatcher::i()->application ? \IPS\Dispatcher::i()->application->directory : NULL, 'module' => \IPS\Dispatcher::i()->module ? \IPS\Dispatcher::i()->module->key : NULL, 'controller' => \IPS\Dispatcher::i()->controller ) ), $httpStatusCode, 'text/html', $httpHeaders, FALSE, FALSE );
|
||||
@@ -806,6 +852,79 @@ class _Output
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Can this output be cached by CDN?
|
||||
*
|
||||
* @param string $contentType
|
||||
* @param bool $checkCachePageTimeout
|
||||
* @return bool
|
||||
*/
|
||||
public function isCacheable( string $contentType='text/html', bool $checkCachePageTimeout = true ): bool
|
||||
{
|
||||
/* Have we got a value for the default cache timeout? */
|
||||
if ( $checkCachePageTimeout and ! \IPS\CACHE_PAGE_TIMEOUT )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Have we set a per-page cache timeout of false, which means we don't want to cache? */
|
||||
if ( static::$cacheDate === false )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else if ( static::$cacheDate instanceof \IPS\DateTime )
|
||||
{
|
||||
/* Or have we set a specific cache date and it's in the past? */
|
||||
$cacheSeconds = static::$cacheDate->getTimestamp() - time();
|
||||
if ( $cacheSeconds <= 0 )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Are we logged in? */
|
||||
if ( \IPS\Member::loggedIn()->member_id )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Are we using the legacy pageCaching flag? */
|
||||
if ( ! $this->pageCaching )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Do we have a noCache cookie set? */
|
||||
if ( isset( \IPS\Request::i()->cookie['noCache'] ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Do we have a CSRF key? */
|
||||
if ( isset( \IPS\Request::i()->csrfKey ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Check the request method */
|
||||
if ( ! in_array( mb_strtolower( $_SERVER['REQUEST_METHOD'] ), [ 'get', 'head' ] ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! in_array( $contentType, ['text/html', 'text/xml', 'application/manifest+json' ] ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( \IPS\Dispatcher::hasInstance() and class_exists( 'IPS\Dispatcher', FALSE ) and \IPS\Dispatcher::i()->controllerLocation !== 'front' )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Flag to bypass CSRF IN_DEV check
|
||||
*/
|
||||
@@ -862,36 +981,18 @@ class _Output
|
||||
$output = $this->replaceEmojiWithImages( $output );
|
||||
}
|
||||
|
||||
/* Combine some common checks for re-use */
|
||||
$canCache = function( $contentType, $obj ) {
|
||||
if( $obj->pageCaching and
|
||||
( mb_strtolower( $_SERVER['REQUEST_METHOD'] ) == 'get' OR mb_strtolower( $_SERVER['REQUEST_METHOD'] ) == 'head' ) and // Is a HTTP GET/HEAD request (don't cache output for POSTs)
|
||||
( $contentType == 'text/html' or $contentType == 'text/xml' or $contentType == 'application/manifest+json' ) and // Output is HTML, XML or [PWA] application manifest (don't cache JSON output, etc)
|
||||
\IPS\Dispatcher::hasInstance() and class_exists( 'IPS\Dispatcher', FALSE ) and \IPS\Dispatcher::i()->controllerLocation === 'front' // Is a normal, front-end page (necessary to know if user is logged in)
|
||||
)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
};
|
||||
|
||||
if (
|
||||
\IPS\CACHE_PAGE_TIMEOUT and // Page caching is enabled
|
||||
$cacheThisPage and // Some pages can specify not to be cached (for example, when displaying a cached page, you don't want it recached)
|
||||
!isset( \IPS\Request::i()->cookie['noCache'] ) and // A noCache cookie might get set to not cache a particular guest (for example, if they have added items to their cart in the store)
|
||||
$canCache( $contentType, $this ) and // Common checks, see above
|
||||
!isset( \IPS\Request::i()->csrfKey ) and // CSRF key isn't present (which would be like a POST request)
|
||||
!\IPS\Member::loggedIn()->member_id // User is not logged in
|
||||
) {
|
||||
/* Can we cache this page to a CDN? */
|
||||
if( $cacheThisPage and $this->isCacheable( $contentType ) )
|
||||
{
|
||||
/* Add caching headers */
|
||||
if( !isset( $httpHeaders['Cache-Control'] ) )
|
||||
{
|
||||
$httpHeaders += \IPS\Output::getCacheHeaders( time(), \IPS\CACHE_PAGE_TIMEOUT );
|
||||
}
|
||||
}
|
||||
/* Send no-cache headers if we got to this point without any cache-control headers being set, or page caching is forced off */
|
||||
elseif( !isset( $httpHeaders['Cache-Control'] ) OR !$this->pageCaching )
|
||||
elseif( !isset( $httpHeaders['Cache-Control'] ) )
|
||||
{
|
||||
/* Send no-cache headers if we got to this point without any cache-control headers being set, or page caching is forced off */
|
||||
$httpHeaders += \IPS\Output::getNoCacheHeaders();
|
||||
}
|
||||
|
||||
@@ -1292,7 +1393,22 @@ class _Output
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Should we reduce the number of links in the HTML for SEO purposes?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function reduceLinks(): bool
|
||||
{
|
||||
if ( ! \IPS\Member::loggedIn()->member_id and \IPS\Settings::i()->seo_reduce_links )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show Offline
|
||||
*
|
||||
@@ -1462,8 +1578,9 @@ class _Output
|
||||
}
|
||||
else
|
||||
{
|
||||
if( trim( $row['meta_url'], '/' ) == trim( $this->metaTagsUrl, '/' ) and ( ( $row['meta_url'] == '/' and \IPS\Request::i()->url()->data['path'] == $rootPath ) or $row['meta_url'] !== '/' ) )
|
||||
if( trim( $row['meta_url'], '/' ) == trim( $this->metaTagsUrl, '/' ) and ( ( $row['meta_url'] == '/' and rtrim( \IPS\Request::i()->url()->data['path'], '/' ) . '/' == rtrim( $rootPath, '/' ) . '/' ) or $row['meta_url'] !== '/' ) )
|
||||
{
|
||||
|
||||
$_tags = json_decode( $row['meta_tags'], TRUE );
|
||||
|
||||
if ( \is_array( $_tags ) )
|
||||
@@ -1607,28 +1724,30 @@ class _Output
|
||||
|
||||
foreach( $this->breadcrumb as $breadcrumb )
|
||||
{
|
||||
$crumb = [
|
||||
'@type' => "ListItem",
|
||||
'position' => $position,
|
||||
'item' => [
|
||||
'name' => $breadcrumb[1],
|
||||
]
|
||||
];
|
||||
|
||||
if( $breadcrumb[0] )
|
||||
{
|
||||
$elements[] = array(
|
||||
'@type' => "ListItem",
|
||||
'position' => $position,
|
||||
'item' => array(
|
||||
'@id' => (string) $breadcrumb[0],
|
||||
'name' => $breadcrumb[1],
|
||||
)
|
||||
);
|
||||
|
||||
$position++;
|
||||
$crumb['item']['@id'] = (string) $breadcrumb[0];
|
||||
}
|
||||
|
||||
$elements[] = $crumb;
|
||||
$position++;
|
||||
}
|
||||
|
||||
if( \count( $elements ) )
|
||||
{
|
||||
$jsonLd['breadcrumbs'] = array(
|
||||
$jsonLd['breadcrumbs'] = [
|
||||
'@context' => "http://schema.org",
|
||||
'@type' => "BreadcrumbList",
|
||||
'itemListElement' => $elements,
|
||||
);
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user