Version 4.5.0

This commit is contained in:
Neo committed 2025-12-19 05:51:58 -08:00
1 parent 1a0c7fd3c2
commit f79dcf067a
3791 files changed
+248032 -76372

No files matched your search

+115 -50
View File
@@ -11,7 +11,7 @@
namespace IPS\Dispatcher;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
@@ -26,11 +26,12 @@ class _Front extends \IPS\Dispatcher\Standard
* Controller Location
*/
public $controllerLocation = 'front';
/**
* Init
*
* @return void
* @return void
* @throws \Exception
*/
public function init()
{
@@ -45,6 +46,11 @@ class _Front extends \IPS\Dispatcher\Standard
{
$protocol = '1.1';
}
/* Don't allow the setup in progress page to be cached, it will only be displayed for a very short period of time */
header( "Cache-Control: no-cache, no-store, must-revalidate" );
header( "Pragma: no-cache" );
header( "Expires: 0" );
if ( \IPS\CIC and ! \IPS\Session\Front::loggedIn() and ! \IPS\Session\Front::i()->userAgent->spider )
{
@@ -114,14 +120,22 @@ class _Front extends \IPS\Dispatcher\Standard
\IPS\Output::i()->error( $e->getMessage(), '2S100/' . $e->getCode(), $e->getCode() === 4 ? 403 : 404, '' );
}
}
/* Set a referral cookie */
if( \IPS\Settings::i()->ref_on and isset( \IPS\Request::i()->_rid ) )
{
\IPS\Request::i()->setCookie( 'referred_by', \intval( \IPS\Request::i()->_rid ), \IPS\DateTime::create()->add( new \DateInterval( 'P1Y' ) ) );
}
/* Enable sidebar by default (controllers can turn it off if needed) */
\IPS\Output::i()->sidebar['enabled'] = ( \IPS\Request::i()->isAjax() ) ? FALSE : TRUE;
/* Add in RSS Feeds */
foreach( \IPS\core\Rss::getAllFeeds() AS $feed_id => $feed )
foreach( \IPS\core\Rss::getStore() AS $feed_id => $feed )
{
if ( $feed->groups == '*' OR \IPS\Member::loggedIn()->inGroup( $feed->groups ) )
$feed = \IPS\core\Rss::constructFromData( $feed );
if ( $feed->_enabled AND ( $feed->groups == '*' OR \IPS\Member::loggedIn()->inGroup( $feed->groups ) ) )
{
\IPS\Output::i()->rssFeeds[ $feed->_title ] = $feed->url();
}
@@ -164,7 +178,7 @@ class _Front extends \IPS\Dispatcher\Standard
{
\IPS\Output::i()->sidebar = FALSE;
\IPS\Output::i()->bodyClasses[] = 'ipsLayout_minimal';
if( !in_array( $this->controller, array( 'contact', 'warnings' ) ) )
if( !\in_array( $this->controller, array( 'contact', 'warnings', 'privacy', 'guidelines', 'metatags' ) ) )
{
\IPS\Output::i()->showBanned();
}
@@ -174,13 +188,13 @@ class _Front extends \IPS\Dispatcher\Standard
/* Do we need more info from the member or do they need to validate? */
/* This controllers should always be accessible, no matter if the member is awaiting validation or needs to set up the email or name */
$legalControllers = array( 'privacy', 'contact', 'terms' );
$legalControllers = array( 'privacy', 'contact', 'terms', 'embed', 'metatags' );
/* Do we need more info from the member or do they need to validate? */
if( \IPS\Member::loggedIn()->member_id and $this->controller !== 'language' and $this->controller !== 'theme' and $this->controller !== 'ajax' and !\in_array( $this->controller, $legalControllers ) )
{
/* Need their name or email... */
if( ( \IPS\Member::loggedIn()->real_name === '' or !\IPS\Member::loggedIn()->email ) and $this->controller !== 'register' )
if( ( \IPS\Member::loggedIn()->real_name === '' or !\IPS\Member::loggedIn()->email ) and $this->controller !== 'register' and $this->controller !== 'login' )
{
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=system&controller=register&do=complete' )->setQueryString( 'ref', base64_encode( \IPS\Request::i()->url() ) ) );
}
@@ -195,18 +209,29 @@ class _Front extends \IPS\Dispatcher\Standard
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=system&controller=register&do=reconfirm', 'front', 'register' )->setQueryString( 'ref', base64_encode( \IPS\Request::i()->url() ) ) );
}
/* Have required profile actions that need completing */
else if ( \IPS\Settings::i()->quick_register AND !\IPS\Member::loggedIn()->members_bitoptions['profile_completed'] AND !in_array( $this->controller, array( 'register', 'login', 'redirect', 'ajax', 'settings' ) ) AND $completion = \IPS\Member::loggedIn()->profileCompletion() AND count( $completion['required'] ) )
else if ( \IPS\Settings::i()->quick_register AND !\IPS\Member::loggedIn()->members_bitoptions['profile_completed'] AND !\in_array( $this->controller, array( 'register', 'login', 'redirect', 'ajax', 'settings', 'checkout', 'pixabay', 'editor' ) ) AND $completion = \IPS\Member::loggedIn()->profileCompletion() AND \count( $completion['required'] ) )
{
foreach( $completion['required'] AS $id => $completed )
{
if ( $completed === FALSE )
{
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=system&controller=register&do=finish", 'front', 'register' ) );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=system&controller=register&do=finish&_new=1", 'front', 'register' )->setQueryString( 'ref', base64_encode( \IPS\Request::i()->url() ) ) );
}
}
}
/* Need to reset password */
elseif ( $this->controller !== 'settings' AND ( !isset( \IPS\Request::i()->area ) OR \IPS\Request::i()->area !== 'password' ) )
{
foreach( \IPS\Login::methods() AS $method )
{
if ( $url = $method->forcePasswordResetUrl( \IPS\Member::loggedIn(), \IPS\Request::i()->url() ) )
{
\IPS\Output::i()->redirect( $url );
}
}
}
/* Need to set up MFA... */
elseif ( !in_array( $this->controller, array( 'register', 'login', 'redirect', 'ajax', 'settings' ) ) )
elseif ( !\in_array( $this->controller, array( 'register', 'login', 'redirect', 'ajax', 'settings', 'embed' ) ) )
{
$haveAcceptableHandlers = FALSE;
$haveConfiguredHandler = FALSE;
@@ -237,17 +262,24 @@ class _Front extends \IPS\Dispatcher\Standard
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=system&controller=settings&do=initialMfa', 'front', 'settings' )->setQueryString( 'ref', base64_encode( \IPS\Request::i()->url() ) ) );
}
}
}
}
}
/* Permission Check */
if ( !\IPS\Member::loggedIn()->canAccessModule( $this->module ) )
try
{
if ( !\IPS\Member::loggedIn()->member_id and isset( \IPS\Request::i()->_mfaLogin ) )
if ( !\IPS\Member::loggedIn()->canAccessModule( $this->module ) )
{
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=system&controller=login", 'front', 'login' )->setQueryString( '_mfaLogin', 1 ) );
if ( !\IPS\Member::loggedIn()->member_id and isset( \IPS\Request::i()->_mfaLogin ) )
{
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=system&controller=login", 'front', 'login' )->setQueryString( '_mfaLogin', 1 ) );
}
\IPS\Output::i()->error( ( \IPS\Member::loggedIn()->member_id ? 'no_module_permission' : 'no_module_permission_guest' ), '2S100/2', 403, 'no_module_permission_admin' );
}
\IPS\Output::i()->error( ( \IPS\Member::loggedIn()->member_id ? 'no_module_permission' : 'no_module_permission_guest' ), '2S100/2', 403, 'no_module_permission_admin' );
}
catch( \InvalidArgumentException $e ) # invalid module
{
\IPS\Output::i()->error( 'requested_route_404', '2S160/5', 404, '' );
}
/* Stuff for output */
@@ -276,7 +308,7 @@ class _Front extends \IPS\Dispatcher\Standard
/* Figure out what the global search is */
foreach ( $this->application->extensions( 'core', 'ContentRouter' ) as $object )
{
if ( count( $object->classes ) === 1 )
if ( \count( $object->classes ) === 1 )
{
$classes = $object->classes;
foreach ( $classes as $class )
@@ -319,18 +351,28 @@ class _Front extends \IPS\Dispatcher\Standard
/* If they are accessing "index.php/whatever", we want "index.php?/whatever */
if ( !( $correctUrl instanceof \IPS\Http\Url ) and $url instanceof \IPS\Http\Url\Internal and mb_strpos( $url->data[ \IPS\Http\Url::COMPONENT_PATH ], '/index.php/' ) !== FALSE )
{
$pathFromBaseUrl = mb_substr( $url->data[ \IPS\Http\Url::COMPONENT_PATH ], mb_strlen( \IPS\Http\Url::internal('')->data[ \IPS\Http\Url::COMPONENT_PATH ] ) );
if ( preg_match( '/index.php\//', $pathFromBaseUrl ) )
$pathFromBaseUrl = ltrim( mb_substr( $url->data[ \IPS\Http\Url::COMPONENT_PATH ], mb_strlen( \IPS\Http\Url::internal('')->data[ \IPS\Http\Url::COMPONENT_PATH ] ) ), '/' );
if ( mb_substr( $pathFromBaseUrl, 0, 10 ) === 'index.php/' )
{
$correctUrl = \IPS\Http\Url\Friendly::friendlyUrlFromComponent( 0, trim( mb_substr( $pathFromBaseUrl, 10 ), '/' ), $url->queryString );
}
}
/* Redirect to the correct URL if we got one */
if ( $correctUrl instanceof \IPS\Http\Url )
{
if( $correctUrl->seoPagination and \in_array( 'page', array_keys( $url->hiddenQueryString ) ) )
{
$correctUrl = $correctUrl->setPage( 'page', $url->hiddenQueryString['page'] );
}
\IPS\Output::i()->redirect( $correctUrl, NULL, 301 );
}
/* Check pagination */
if ( $url instanceof \IPS\Http\Url\Friendly and $url->seoPagination and \in_array( 'page', array_keys( $url->queryString ) ) )
{
\IPS\Output::i()->redirect( $url->setPage( 'page', $url->queryString['page'] )->stripQueryString('page'), NULL, 301 );
}
}
/* If the accessed URL is friendly, set the "real" query string properties */
@@ -346,7 +388,13 @@ class _Front extends \IPS\Dispatcher\Standard
{
$this->_controller = NULL;
}
/* If this is a POST request, and this key has already been populated, do not overwrite it as this allows form input to be ignored and the query string data used */
if ( \IPS\Request::i()->requestMethod() == 'POST' and isset( \IPS\Request::i()->$k ) )
{
continue;
}
\IPS\Request::i()->$k = $v;
}
}
@@ -382,25 +430,6 @@ class _Front extends \IPS\Dispatcher\Standard
*/
protected function checkCached()
{
/* Store page view in Redis */
if ( \IPS\CACHE_METHOD == 'Redis' and \IPS\CIC and !\IPS\Request::i()->isAjax() )
{
try
{
if( \IPS\Redis::i() === NULL )
{
throw new \RedisException( 'Redis instance is NULL', -1 );
}
\IPS\Redis::i()->logPageHit();
}
catch( \RedisException $e )
{
/* Sometimes the connection can be reset - we don't want to let an uncaught exception bubble up in those cases */
\IPS\Log::log( $e, 'redis' );
}
}
/* If this is a guest and there's a full cached page, we can serve that */
if( mb_strtolower( $_SERVER['REQUEST_METHOD'] ) == 'get' and !isset( \IPS\Request::i()->csrfKey ) and !isset( \IPS\Request::i()->_mfaLogin ) and !\IPS\Session\Front::loggedIn() and !isset( \IPS\Request::i()->cookie['noCache'] ) and !\IPS\Request::i()->ipAddressIsBanned() and \IPS\CACHE_PAGE_TIMEOUT )
{
@@ -423,13 +452,25 @@ class _Front extends \IPS\Dispatcher\Standard
/* Which theme? */
$theme = isset( \IPS\Request::i()->cookie['theme'] ) ? \IPS\Request::i()->cookie['theme'] : '';
/* Make sure a session is initiated */
\IPS\Session\Front::i();
/* Get cache */
try
{
$cache = \IPS\Data\Cache::i()->getWithExpire( 'page_' . md5( (int) \IPS\Request::i()->isSecure() . ( !empty( $_SERVER['HTTP_HOST'] ) ? $_SERVER['HTTP_HOST'] : $_SERVER['SERVER_NAME'] ) . '/' . $_SERVER['REQUEST_URI'] ) . "_{$language}_{$theme}", TRUE );
$cache = \IPS\Output\Cache::i()->get( 'page_' . md5( (string) \IPS\Request::i()->url() ) . "_{$language}_{$theme}", TRUE );
$sessionId = isset( \IPS\Request::i()->cookie['IPSSessionFront'] ) ? \IPS\Request::i()->cookie['IPSSessionFront'] : \IPS\Session::i()->id;
\IPS\Output::i()->sendOutput( str_replace( '{{csrfKey}}', md5( '& 0&' . $sessionId ), $cache['output'] ), $cache['code'], $cache['contentType'], $cache['httpHeaders'], FALSE, TRUE );
/* RSS feeds can be shown cached, all other pages contain a cache buster once logged in */
$cache['meta']['httpHeaders'] += \IPS\Output::getCacheHeaders( $cache['meta']['lastUpdated'], \IPS\CACHE_PAGE_TIMEOUT );
if( isset( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) AND $_SERVER['HTTP_IF_MODIFIED_SINCE'] == $cache['meta']['httpHeaders']['Last-Modified'] )
{
\IPS\Output::i()->sendOutput( NULL, 304, $cache['meta']['contentType'], $cache['meta']['httpHeaders'], FALSE, TRUE, FALSE, FALSE );
}
\IPS\Output::i()->sendOutput( str_replace( '{{csrfKey}}', md5( \IPS\SUITE_UNIQUE_KEY . '&& 0&' . $sessionId ), $cache['output'] ), $cache['meta']['code'], $cache['meta']['contentType'], $cache['meta']['httpHeaders'], FALSE, TRUE );
}
catch ( \OutOfRangeException $e ) {}
}
@@ -466,11 +507,11 @@ class _Front extends \IPS\Dispatcher\Standard
{
$widgets = array();
if ( ! isset( \IPS\Output::i()->sidebar['widgets'] ) OR ! is_array( \IPS\Output::i()->sidebar['widgets'] ) )
if ( ! isset( \IPS\Output::i()->sidebar['widgets'] ) OR ! \is_array( \IPS\Output::i()->sidebar['widgets'] ) )
{
\IPS\Output::i()->sidebar['widgets'] = array();
}
try
{
$widgetConfig = \IPS\Db::i()->select( '*', 'core_widget_areas', array( 'app=? AND module=? AND controller=?', $this->application->directory, $this->module->key, $this->controller ) );
@@ -497,7 +538,7 @@ class _Front extends \IPS\Dispatcher\Standard
}
}
if( count( $widgets ) )
if( \count( $widgets ) )
{
if ( ( \IPS\Data\Cache::i() instanceof \IPS\Data\Cache\None ) and ! \IPS\Theme::isUsingTemplateDiskCache() )
{
@@ -514,7 +555,7 @@ class _Front extends \IPS\Dispatcher\Standard
}
}
if( count( $templateLoad ) )
if( \count( $templateLoad ) )
{
\IPS\Data\Store::i()->loadIntoMemory( $templateLoad );
}
@@ -522,6 +563,7 @@ class _Front extends \IPS\Dispatcher\Standard
$widgetObjects = array();
$storeLoad = array();
$googleFonts = array();
foreach ( $widgets as $areaKey => $area )
{
foreach ( $area as $widget )
@@ -540,6 +582,23 @@ class _Front extends \IPS\Dispatcher\Standard
{
$storeLoad[] = $_widget->cacheKey;
}
if ( \in_array( 'IPS\Widget\Builder', class_implements( $_widget ) ) )
{
if ( ! empty( $_widget->configuration['widget_adv__font'] ) and $_widget->configuration['widget_adv__font'] !== 'inherit' )
{
$font = $_widget->configuration['widget_adv__font'];
if ( \mb_substr( $font, -6 ) === ' black' )
{
$fontWeight = 900;
$font = \mb_substr( $font, 0, -6 ) . ':400,900';
}
$googleFonts[ $font ] = $font;
}
}
$widgetObjects[ $areaKey ][] = $_widget;
}
catch ( \Exception $e )
@@ -549,7 +608,12 @@ class _Front extends \IPS\Dispatcher\Standard
}
}
if( ( \IPS\Data\Cache::i() instanceof \IPS\Data\Cache\None ) and count( $storeLoad ) )
if ( \count( $googleFonts ) )
{
\IPS\Output::i()->linkTags['googlefonts'] = array('rel' => 'stylesheet', 'href' => "https://fonts.googleapis.com/css?family=" . implode( "|", array_values( $googleFonts ) ) . "&display=swap");
}
if( ( \IPS\Data\Cache::i() instanceof \IPS\Data\Cache\None ) and \count( $storeLoad ) )
{
\IPS\Data\Store::i()->loadIntoMemory( $storeLoad );
}
@@ -563,10 +627,10 @@ class _Front extends \IPS\Dispatcher\Standard
}
}
}
/* Meta tags */
\IPS\Output::i()->buildMetaTags();
/* Check MFA */
$this->checkMfa();
@@ -617,6 +681,7 @@ class _Front extends \IPS\Dispatcher\Standard
{
\IPS\Output::i()->globalControllers[] = 'core.front.core.app';
\IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js( 'front.js' ) );
\IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js( 'front_core.js', 'core', 'front' ) );
if ( \IPS\Member::loggedIn()->members_bitoptions['bw_using_skin_gen'] AND ( isset( \IPS\Request::i()->cookie['vseThemeId'] ) AND \IPS\Request::i()->cookie['vseThemeId'] ) and \IPS\Member::loggedIn()->isAdmin() and \IPS\Member::loggedIn()->hasAcpRestriction( 'core', 'customization', 'theme_easy_editor' ) )
{
@@ -674,7 +739,7 @@ class _Front extends \IPS\Dispatcher\Standard
{
\IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'styles/query_log.css', 'core', 'front' ) );
}
if ( \IPS\CACHING_LOG )
if ( \IPS\CACHING_LOG or \IPS\REDIS_LOG )
{
\IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'styles/caching_log.css', 'core', 'front' ) );
}