Version 4.6.0

This commit is contained in:
Neo committed 2025-12-19 05:55:31 -08:00
1 parent f79dcf067a
commit 517a5e1f70
2036 files changed
+110041 -26162

No files matched your search

+27 -4
View File
@@ -56,9 +56,6 @@ class _Admin extends \IPS\Dispatcher\Standard
{
\IPS\Output::i()->sidebar['appmenu'] = '';
/* Disable sending of referrer to third party sites from AdminCP */
\IPS\Output::i()->sendHeader( "Referrer-Policy: origin-when-cross-origin" );
/* Sync stuff when in developer mode */
if ( \IPS\IN_DEV )
{
@@ -176,7 +173,13 @@ class _Admin extends \IPS\Dispatcher\Standard
{
\IPS\Session::i()->csrfCheck();
}
\IPS\Db::i()->readWriteSeparation = FALSE;
if ( isset( $this->classname::$allowRWSeparation ) and $this->classname::$allowRWSeparation )
{
\IPS\Db::i()->readWriteSeparation = TRUE;
}
/* If we are in recovery mode, but not actually doing the recovery process, or logging in, then we need them to remove the constant */
if ( \IPS\RECOVERY_MODE === TRUE AND !\in_array( $this->controller, array( 'recovery', 'login' ) ) )
{
@@ -413,6 +416,16 @@ class _Admin extends \IPS\Dispatcher\Standard
$_apps = array_keys( $acpTabOrder );
uksort( $this->menu['tabs'], function($a, $b) use ( $_apps )
{
if( !\in_array( $a, $_apps ) )
{
return 1;
}
if( !\in_array( $b, $_apps ) )
{
return -1;
}
return array_search( $a, $_apps ) - array_search( $b, $_apps );
} );
@@ -424,6 +437,16 @@ class _Admin extends \IPS\Dispatcher\Standard
{
uksort( $this->menu['tabs'][ $app ], function($a, $b) use ( $submenu )
{
if( !\in_array( $a, $submenu ) )
{
return 1;
}
if( !\in_array( $b, $submenu ) )
{
return -1;
}
return array_search( $a, $submenu ) - array_search( $b, $submenu );
} );
}
+24 -2
View File
@@ -327,6 +327,9 @@ class _Api extends \IPS\Dispatcher
/* Run it */
$controller = new $class( $this->apiKey, $this->accessToken );
$response = $controller->execute( $pathBits, $shouldLog );
/* Store if we need to mask anything in logs */
$this->parametersToMask = ( $controller->methodCalled AND isset( $controller->parametersToMask[ $controller->methodCalled ] ) ) ? $controller->parametersToMask[ $controller->methodCalled ] : NULL;
/* Send Output */
$output = $response->getOutput();
@@ -345,6 +348,11 @@ class _Api extends \IPS\Dispatcher
$this->_respond( json_encode( array( 'errorCode' => 'EX' . $e->getCode(), 'errorMessage' => \IPS\IN_DEV ? $e->getMessage() : 'UNKNOWN_ERROR' ), JSON_PRETTY_PRINT ), 500 );
}
}
/**
* @brief Parameters to mask in logs per the controller
*/
protected $parametersToMask = NULL;
/**
* Log
@@ -358,12 +366,25 @@ class _Api extends \IPS\Dispatcher
{
try
{
$_requestData = $_REQUEST;
if( $this->parametersToMask AND \count( $this->parametersToMask ) )
{
foreach( $_requestData as $k => $v )
{
if( \in_array( $k, $this->parametersToMask ) )
{
$_requestData[ $k ] = '******';
}
}
}
\IPS\Db::i()->insert( 'core_api_logs', array(
'endpoint' => $this->path,
'method' => $_SERVER['REQUEST_METHOD'],
'api_key' => $this->rawApiKey,
'ip_address' => \IPS\Request::i()->ipAddress(),
'request_data' => json_encode( $_REQUEST, JSON_PRETTY_PRINT ),
'request_data' => json_encode( $_requestData, JSON_PRETTY_PRINT ),
'response_code' => $httpResponseCode,
'response_output' => $response,
'date' => time(),
@@ -387,7 +408,8 @@ class _Api extends \IPS\Dispatcher
*/
protected function _respond( $response, $httpResponseCode, $oauthError=NULL, $log=FALSE )
{
$headers = array();
$headers = ( mb_strtolower( $_SERVER['REQUEST_METHOD'] ) == 'get' ) ? \IPS\Output::getCacheHeaders( time(), 60 ) : \IPS\Output::getNoCacheHeaders();
if ( $this->rawAccessToken and $oauthError )
{
$headers['WWW-Authenticate'] = "Bearer error=\"{$oauthError}\"";
+4 -3
View File
@@ -98,9 +98,10 @@ abstract class _Dispatcher
$url = ( \IPS\Request::i()->isSecure() ? 'https://' : 'http://' ) . $_SERVER['HTTP_HOST'] . rtrim( \dirname( $_SERVER['SCRIPT_NAME'] ), '/' );
header( "HTTP/1.1 307 Temporary Redirect" );
header( "Cache-Control: no-cache, no-store, must-revalidate" );
header( "Pragma: no-cache" );
header( "Expires: 0" );
foreach( \IPS\Output::getNoCacheHeaders() as $headerKey => $headerValue )
{
header( "{$headerKey}: {$headerValue}" );
}
header( "Location: {$url}/" . \IPS\CP_DIRECTORY . "/install/" );
exit;
}
+60 -28
View File
@@ -48,9 +48,10 @@ class _Front extends \IPS\Dispatcher\Standard
}
/* 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" );
foreach( \IPS\Output::getNoCacheHeaders() as $headerKey => $headerValue )
{
header( "{$headerKey}: {$headerValue}" );
}
if ( \IPS\CIC and ! \IPS\Session\Front::loggedIn() and ! \IPS\Session\Front::i()->userAgent->spider )
{
@@ -121,11 +122,7 @@ class _Front extends \IPS\Dispatcher\Standard
}
}
/* 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' ) ) );
}
$this->_setReferralCookie();
/* Enable sidebar by default (controllers can turn it off if needed) */
\IPS\Output::i()->sidebar['enabled'] = ( \IPS\Request::i()->isAjax() ) ? FALSE : TRUE;
@@ -153,7 +150,14 @@ class _Front extends \IPS\Dispatcher\Standard
}
/* Member Ban? */
$ipBanned = \IPS\Request::i()->ipAddressIsBanned();
/* IP Ban check happens only the Login and Register Controller for guests */
$ipBanned = FALSE;
if( \IPS\Member::loggedIn()->member_id OR \in_array( $this->controller, array( 'register', 'login' ) ) )
{
$ipBanned = \IPS\Request::i()->ipAddressIsBanned();
}
if ( $ipBanned or $banEnd = \IPS\Member::loggedIn()->isBanned() )
{
if ( !$ipBanned and !\IPS\Member::loggedIn()->member_id )
@@ -188,7 +192,7 @@ 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', 'embed', 'metatags' );
$legalControllers = array( 'privacy', 'contact', 'terms', 'embed', 'metatags', 'store', 'checkout', 'subscriptions' );
/* 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 ) )
@@ -219,19 +223,9 @@ class _Front extends \IPS\Dispatcher\Standard
}
}
}
/* 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', 'embed' ) ) )
if ( !\in_array( $this->controller, array( 'register', 'login', 'redirect', 'ajax', 'settings', 'embed' ) ) )
{
$haveAcceptableHandlers = FALSE;
$haveConfiguredHandler = FALSE;
@@ -263,6 +257,18 @@ class _Front extends \IPS\Dispatcher\Standard
}
}
}
/* Need to reset password */
if ( $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 );
}
}
}
}
/* Permission Check */
@@ -325,6 +331,20 @@ class _Front extends \IPS\Dispatcher\Standard
}
}
/**
* Set the referral cookie if appropriate
*
* @return void
*/
protected function _setReferralCookie()
{
/* 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' ) ) );
}
}
/**
* Check whether the URL we visited is correct and route appropriately
*
@@ -431,8 +451,23 @@ class _Front extends \IPS\Dispatcher\Standard
protected function checkCached()
{
/* 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 )
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\CACHE_PAGE_TIMEOUT )
{
/* Check whether a 304 Not modified response is appropriate */
if( !empty( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) AND ( new \IPS\DateTime( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) )->getTimestamp() > ( time() - \IPS\CACHE_PAGE_TIMEOUT ) )
{
/* This is intentionally using PHP methods to avoid database connections */
$this->destruct = false;
header('HTTP/1.1 304 Not Modified' );
exit;
}
/* Only check the output cache if it is enabled */
if( empty( \IPS\OUTPUT_CACHE_METHOD ) OR ( !empty( \IPS\OUTPUT_CACHE_METHOD ) AND \IPS\OUTPUT_CACHE_METHOD == 'None' ) )
{
return;
}
/* Which language? */
if ( isset( \IPS\Request::i()->cookie['language'] ) )
{
@@ -464,11 +499,8 @@ class _Front extends \IPS\Dispatcher\Standard
/* 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 );
}
$this->_setReferralCookie();
\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 );
}
+2 -2
View File
@@ -123,7 +123,7 @@ class _GraphQL extends Api
/* Execute! */
$result = \GraphQL\GraphQL::executeQuery(
new \GraphQL\Schema([
new \GraphQL\Type\Schema([
'query' => \IPS\Api\GraphQL\TypeRegistry::query(),
'mutation' => \IPS\Api\GraphQL\TypeRegistry::mutation()
]),
@@ -136,7 +136,7 @@ class _GraphQL extends Api
);
/* Convert result into JSON and send */
$output = $result->toArray( ( \IPS\IN_DEV OR \IPS\DEBUG_GRAPHQL ) ? \GraphQL\Error\Debug::INCLUDE_DEBUG_MESSAGE | \GraphQL\Error\Debug::INCLUDE_TRACE : false );
$output = $result->toArray( ( \IPS\IN_DEV OR \IPS\DEBUG_GRAPHQL ) ? \GraphQL\Error\DebugFlag::INCLUDE_DEBUG_MESSAGE | \GraphQL\Error\DebugFlag::INCLUDE_TRACE : false );
\IPS\Member::loggedIn()->language()->parseOutputForDisplay( $output );
$this->_respond( json_encode( $output, JSON_PRETTY_PRINT ), 200 );
+15 -4
View File
@@ -113,6 +113,11 @@ abstract class _Standard extends \IPS\Dispatcher
parent::finish();
}
/**
* @brief Initialize tasks
*/
protected $runTasks = FALSE;
/**
* Init
*
@@ -121,6 +126,8 @@ abstract class _Standard extends \IPS\Dispatcher
*/
public function init()
{
$this->runTasks = (bool) \IPS\Member::loggedIn()->member_id;
/* Force HTTPs and correct domain (e.g. not "www." if that's not in the base URL) */
if ( mb_strtolower( $_SERVER['REQUEST_METHOD'] ) == 'get' )
{
@@ -297,6 +304,12 @@ abstract class _Standard extends \IPS\Dispatcher
}
}
/**
* @brief Enable destruct method
* @note Disable to avoid an automatic database connection
*/
public $destruct = true;
/**
* Destructor
* Runs tasks
@@ -306,17 +319,15 @@ abstract class _Standard extends \IPS\Dispatcher
public function __destruct()
{
/* If you visit and you are redirected to installer, this code should not run */
if( !file_exists( \IPS\SITE_FILES_PATH . '/conf_global.php' ) )
if( !file_exists( \IPS\SITE_FILES_PATH . '/conf_global.php' ) OR !$this->destruct )
{
return;
}
if ( \IPS\Member::loggedIn()->member_id and \IPS\Settings::i()->task_use_cron == 'normal' and !\IPS\Request::i()->isAjax() )
if ( $this->runTasks and \IPS\Settings::i()->task_use_cron == 'normal' and !\IPS\Request::i()->isAjax() )
{
try
{
\IPS\Db::i()->readWriteSeparation = FALSE;
$task = \IPS\Task::queued();
if ( $task )
{