Version 4.1.19

This commit is contained in:
Neo committed 2025-12-19 05:38:56 -08:00
1 parent 28bd025b35
commit 2bd5025018
2674 files changed
+233518 -77766

No files matched your search

+148 -55
View File
@@ -2,9 +2,9 @@
/**
* @brief Output Class
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @copyright (c) 2001 - 2016 Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @package IPS Community Suite
* @since 18 Feb 2013
* @version SVN_VERSION_NUMBER
*/
@@ -27,7 +27,7 @@ class _Output
* @brief HTTP Statuses
* @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
*/
protected static $httpStatuses = array( 100 => 'Continue', 101 => 'Switching Protocols', 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 307 => 'Temporary Redirect', 400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Timeout', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Long', 415 => 'Unsupported Media Type', 416 => 'Requested Range Not Satisfiable', 417 => 'Expectation Failed', 429 => 'Too Many Requests', 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Timeout', 505 => 'HTTP Version Not Supported' );
public static $httpStatuses = array( 100 => 'Continue', 101 => 'Switching Protocols', 200 => 'OK', 201 => 'Created', 202 => 'Accepted', 203 => 'Non-Authoritative Information', 204 => 'No Content', 205 => 'Reset Content', 206 => 'Partial Content', 300 => 'Multiple Choices', 301 => 'Moved Permanently', 302 => 'Found', 303 => 'See Other', 304 => 'Not Modified', 305 => 'Use Proxy', 307 => 'Temporary Redirect', 400 => 'Bad Request', 401 => 'Unauthorized', 402 => 'Payment Required', 403 => 'Forbidden', 404 => 'Not Found', 405 => 'Method Not Allowed', 406 => 'Not Acceptable', 407 => 'Proxy Authentication Required', 408 => 'Request Timeout', 409 => 'Conflict', 410 => 'Gone', 411 => 'Length Required', 412 => 'Precondition Failed', 413 => 'Request Entity Too Large', 414 => 'Request-URI Too Long', 415 => 'Unsupported Media Type', 416 => 'Requested Range Not Satisfiable', 417 => 'Expectation Failed', 429 => 'Too Many Requests', 500 => 'Internal Server Error', 501 => 'Not Implemented', 502 => 'Bad Gateway', 503 => 'Service Unavailable', 504 => 'Gateway Timeout', 505 => 'HTTP Version Not Supported' );
/**
* @brief Singleton Instance
@@ -90,8 +90,11 @@ class _Output
/* Inline Message */
if( isset( $_SESSION['inlineMessage'] ) )
{
static::$instance->inlineMessage = $_SESSION['inlineMessage'];
$_SESSION['inlineMessage'] = NULL;
if( !\IPS\Request::i()->isAjax() )
{
static::$instance->inlineMessage = $_SESSION['inlineMessage'];
$_SESSION['inlineMessage'] = NULL;
}
}
return static::$instance;
@@ -108,6 +111,11 @@ class _Output
* @brief Stored Page Title
*/
public $title = '';
/**
* @brief Should the title show in the header (ACP only)?
*/
public $showTitle = TRUE;
/**
* @brief Stored Content to output
@@ -221,7 +229,7 @@ class _Output
if ( $location === 'interface' AND mb_substr( $file, -3 ) === '.js' )
{
/* @see http://community.invisionpower.com/4bugtrack/some-js-files-get-loaded-with-a-double-in-the-url-r3558/ */
return array( rtrim( \IPS\Http\Url::baseUrl( \IPS\Http\Url::PROTOCOL_RELATIVE ), '/' ) . "/applications/{$app}/interface/{$file}?v=" . \IPS\SUITE_UNIQUE_KEY );
return array( rtrim( \IPS\Http\Url::baseUrl( \IPS\Http\Url::PROTOCOL_RELATIVE ), '/' ) . "/applications/{$app}/interface/{$file}?v=" . ( defined( \IPS\CACHEBUST_KEY ) ? \IPS\CACHEBUST_KEY : time() ) );
}
elseif ( \IPS\IN_DEV )
{
@@ -310,7 +318,10 @@ class _Output
{
if ( $app === null or $app === 'global' )
{
\IPS\File::getClass('core_Theme')->deleteContainer( 'javascript_global' );
try
{
\IPS\File::getClass('core_Theme')->deleteContainer( 'javascript_global' );
} catch( \Exception $e ) { }
unset( $javascriptObjects['global'] );
}
@@ -319,7 +330,10 @@ class _Output
{
if ( $app === null or $app === $key )
{
\IPS\File::getClass('core_Theme')->deleteContainer( 'javascript_' . $key );
try
{
\IPS\File::getClass('core_Theme')->deleteContainer( 'javascript_' . $key );
} catch( \Exception $e ) { }
unset( $javascriptObjects[ $key ] );
}
@@ -356,7 +370,11 @@ class _Output
{
$title = $this->metaTagsTitle;
}
else
{
$title = htmlspecialchars( $title, \IPS\HTMLENTITIES, 'UTF-8', FALSE );
}
if( !\IPS\Settings::i()->site_online )
{
$title = sprintf( \IPS\Member::loggedIn()->language()->get( 'offline_title_wrap' ), $title );
@@ -487,15 +505,15 @@ class _Output
/**
* Display Error Screen
*
* @param string $message language key for error message
* @param mixed $code Error code
* @param int $httpStatusCode HTTP Status Code
* @param string $adminMessage language key for error message to show to admins
* @param array $httpHeaders Additional HTTP Headers
* @param string $extra Additional information (such as API error)
* @return void
* @param string $message language key for error message
* @param mixed $code Error code
* @param int $httpStatusCode HTTP Status Code
* @param string $adminMessage language key for error message to show to admins
* @param array $httpHeaders Additional HTTP Headers
* @param string $extra Additional information (such backtrace or API error) which will be shown to admins
* @param int|string|NULL $faultyAppOrHookId The 3rd party application or the hook id, which caused this error, NULL if it was a core application
*/
public function error( $message, $code, $httpStatusCode=500, $adminMessage=NULL, $httpHeaders=array(), $extra=NULL )
public function error( $message, $code, $httpStatusCode=500, $adminMessage=NULL, $httpHeaders=array(), $extra=NULL, $faultyAppOrHookId=NULL )
{
/* If we just logged out, we don't want to show a "no permission" error */
if ( isset( \IPS\Request::i()->_fromLogout ) )
@@ -516,26 +534,29 @@ class _Output
$title = "{$httpStatusCode}_error_title";
$title = \IPS\Member::loggedIn()->language()->checkKeyExists( $title ) ? \IPS\Member::loggedIn()->language()->addToStack( $title ) : \IPS\Member::loggedIn()->language()->addToStack( 'error_title' );
/* If we're in developer mode, show a backtrace */
$backtrace = '';
if( \IPS\IN_DEV and !$extra )
{
ob_start();
$backtrace = print_r( debug_backtrace( 0 ), TRUE );
}
else
{
$backtrace = $extra;
}
/* If we're in setup, just display it */
if ( \IPS\Dispatcher::i()->controllerLocation === 'setup' )
{
$this->sendOutput( \IPS\Theme::i()->getTemplate( 'global', 'core' )->globalTemplate( $title, \IPS\Theme::i()->getTemplate( 'global', 'core' )->error( $title, $message, $code, $backtrace ) ), $httpStatusCode, 'text/html', $httpHeaders, FALSE );
$this->sendOutput( \IPS\Theme::i()->getTemplate( 'global', 'core' )->globalTemplate( $title, \IPS\Theme::i()->getTemplate( 'global', 'core' )->error( $title, $message, $code, $extra ) ), $httpStatusCode, 'text/html', $httpHeaders, FALSE );
}
/* Are we an administrator logged in as a member? */
$member = \IPS\Member::loggedIn();
if ( isset( $_SESSION['logged_in_as_key'] ) )
{
try
{
$_member = \IPS\Member::load( $_SESSION['logged_in_from']['id'] );
if ( $_member->member_id == $_SESSION['logged_in_from']['id'] )
{
$member = $_member;
}
}
catch ( \OutOfRangeException $e ) { }
}
/* Which message are we showing? */
if( \IPS\Member::loggedIn()->isAdmin() and $adminMessage )
if( $member->isAdmin() and $adminMessage )
{
$message = $adminMessage;
}
@@ -565,7 +586,7 @@ class _Output
if( \IPS\Settings::i()->error_notify_level and $level >= \IPS\Settings::i()->error_notify_level )
{
\IPS\Email::buildFromTemplate( 'core', 'error_log', array( $code, $message ) )->send( \IPS\Settings::i()->email_in );
\IPS\Email::buildFromTemplate( 'core', 'error_log', array( $code, $message ), \IPS\Email::TYPE_TRANSACTIONAL )->send( \IPS\Settings::i()->email_in );
}
}
@@ -574,10 +595,29 @@ class _Output
{
$this->json( $message, $httpStatusCode );
}
$faulty = '';
/* Try to find the breaking hook */
if ( $faultyAppOrHookId )
{
if ( is_numeric( $faultyAppOrHookId ) )
{
$hookSource = \IPS\Db::i()->select( 'plugin', 'core_hooks', array( 'id=?', $faultyAppOrHookId ) )->first();
$plugin = \IPS\Plugin::load( $hookSource );
$faulty = \IPS\Member::loggedIn()->language()->addToStack( 'faulty_plugin', FALSE, array( 'sprintf' => array( $plugin->name, \IPS\Http\Url::internal('app=core&module=applications&controller=plugins', 'admin' ) ) ) );
}
else
{
$app = \IPS\Application::load( $faultyAppOrHookId );
$faulty = \IPS\Member::loggedIn()->language()->addToStack( 'faulty_app', FALSE, array( 'sprintf' => array( $app->_title, \IPS\Http\Url::internal( 'app=core&module=applications&controller=applications', 'admin' ) ) ) );
}
}
/* 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, $backtrace ), 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 );
$this->sendOutput( \IPS\Theme::i()->getTemplate( 'global', 'core' )->globalTemplate( $title, \IPS\Theme::i()->getTemplate( 'global', 'core' )->error( $title, $message, $code, $extra, $member, $faulty ), 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 );
}
/**
@@ -608,11 +648,11 @@ class _Output
/* Set HTTP status */
if( isset( $_SERVER['SERVER_PROTOCOL'] ) and \strstr( $_SERVER['SERVER_PROTOCOL'], '/1.0' ) !== false )
{
$this->sendHeader( "HTTP/1.0 {$httpStatusCode} " . self::$httpStatuses[ $httpStatusCode ] );
$this->sendHeader( "HTTP/1.0 {$httpStatusCode} " . static::$httpStatuses[ $httpStatusCode ] );
}
else
{
$this->sendHeader( "HTTP/1.1 {$httpStatusCode} " . self::$httpStatuses[ $httpStatusCode ] );
$this->sendHeader( "HTTP/1.1 {$httpStatusCode} " . static::$httpStatuses[ $httpStatusCode ] );
}
}
@@ -648,11 +688,12 @@ class _Output
$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)
mb_strtolower( $_SERVER['REQUEST_METHOD'] ) == 'get' and // Is a HTTP GET request (don't cache output for POSTs)
!isset( \IPS\Request::i()->csrfKey ) and // CSRF key isn't present (which would be like a POST request)
$contentType == 'text/html' and // Output is HTML (don't cache RSS feeds, JSON output, etc)
\IPS\Dispatcher::hasInstance() and class_exists( 'IPS\Dispatcher', FALSE ) and \IPS\Dispatcher::i()->controllerLocation === 'front' and // Is a normal, front-end page (necessary to know if user is logged in)
!\IPS\Member::loggedIn()->member_id // User is not logged in
) {
\IPS\Data\Cache::i()->storeWithExpire( 'page_' . md5( ( !empty( $_SERVER['HTTP_HOST'] ) ? $_SERVER['HTTP_HOST'] : $_SERVER['SERVER_NAME'] ) . '/' . $_SERVER['REQUEST_URI'] ) . '_' . \IPS\Member::loggedIn()->language()->id . '_' . \IPS\Theme::i()->id, array(
\IPS\Data\Cache::i()->storeWithExpire( 'page_' . md5( (int) \IPS\Request::i()->isSecure() . ( !empty( $_SERVER['HTTP_HOST'] ) ? $_SERVER['HTTP_HOST'] : $_SERVER['SERVER_NAME'] ) . '/' . $_SERVER['REQUEST_URI'] ) . '_' . \IPS\Member::loggedIn()->language()->id . '_' . \IPS\Theme::i()->id, array(
'output' => str_replace( \IPS\Session::i()->csrfKey, '{{csrfKey}}', $output ),
'code' => $httpStatusCode,
'contentType' => $contentType,
@@ -662,7 +703,9 @@ class _Output
}
/* Query Log (has to be done after parseOutputForDisplay because runs queries and after page caching so the log isn't misleading) */
if ( \IPS\QUERY_LOG or \IPS\CACHING_LOG )
$contentTypeTypes = explode( '/', $contentType );
if ( ( \IPS\QUERY_LOG or \IPS\CACHING_LOG ) and in_array( $contentType, array( 'text/html', 'application/json' ) ) )
{
/* Close the session and run tasks now so we can see those queries */
session_write_close();
@@ -683,10 +726,16 @@ class _Output
$output = str_replace( '<!--ipsCachingLog-->', \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' )->cachingLog( $cachingLog ), $output );
}
}
/* Remove anything from the output buffer that should not be there as it can confuse content-length */
@ob_end_clean();
/* Trim any blank spaces before the beginning of output */
$output = ltrim( $output );
/* Set HTTP status */
$this->sendStatusCodeHeader( $httpStatusCode );
/* Start buffering */
ob_start();
@@ -700,15 +749,13 @@ class _Output
}
}
/* Output */
print $output;
/* Update advertisement impression counts, if appropriate */
if( count( \IPS\core\Advertisement::$advertisementIds ) )
{
\IPS\Db::i()->update( 'core_advertisements', "ad_impressions=ad_impressions+1", "ad_id IN(" . implode( ',', \IPS\core\Advertisement::$advertisementIds ) . ")" );
unset( \IPS\Data\Cache::i()->advertisements );
}
\IPS\core\Advertisement::updateImpressions();
/* Send headers */
$this->sendHeader( "Content-type: {$contentType};charset=UTF-8" );
@@ -757,7 +804,7 @@ class _Output
public function json( $data, $httpStatusCode=200 )
{
\IPS\Member::loggedIn()->language()->parseOutputForDisplay( $data );
return $this->sendOutput( json_encode( \IPS\Member::loggedIn()->language()->stripVLETags( $data ) ), $httpStatusCode, 'application/json' );
return $this->sendOutput( json_encode( \IPS\Member::loggedIn()->language()->stripVLETags( $data ) ), $httpStatusCode, 'application/json', $this->httpHeaders );
}
/**
@@ -821,7 +868,7 @@ class _Output
*/
public function parseFileObjectUrls( &$output )
{
if ( mb_stristr( $output, '<fileStore.' ) )
if ( \stristr( $output, '<fileStore.' ) )
{
preg_match_all( '#<fileStore.([\d\w\_]+?)>#', $output, $matches, PREG_SET_ORDER );
@@ -867,10 +914,57 @@ class _Output
$this->title = \IPS\Settings::i()->board_name;
\IPS\Output::i()->sidebar['enabled'] = FALSE;
\IPS\Dispatcher\Front::i()->checkMfa();
$this->sendOutput( \IPS\Theme::i()->getTemplate( 'global', 'core' )->globalTemplate( $this->title, $this->output, array( 'app' => \IPS\Dispatcher::i()->application->directory, 'module' => \IPS\Dispatcher::i()->module->key, 'controller' => \IPS\Dispatcher::i()->controller ) ), 503 );
}
/**
* Show Banned
*
* @return void
*/
public function showBanned()
{
$ipBanned = \IPS\Request::i()->ipAddressIsBanned();
$banEnd = \IPS\Member::loggedIn()->isBanned();
$message = 'member_banned';
if ( !$ipBanned and $banEnd instanceof \IPS\DateTime )
{
$message = \IPS\Member::loggedIn()->language()->addToStack( 'member_banned_temp', FALSE, array( 'htmlsprintf' => array( $banEnd->html() ) ) );
}
$member = \IPS\Member::loggedIn();
$warnings = NULL;
if( $member->member_id )
{
try
{
$warningCount = \IPS\Db::i()->select( 'COUNT(*)', 'core_members_warn_logs', array( 'wl_member = ?', $member->member_id ) )->first();
if( $warningCount )
{
$warnings = new \IPS\Helpers\Table\Content( 'IPS\core\Warnings\Warning', \IPS\Http\Url::internal( "app=core&module=system&controller=warnings&id={$member->member_id}", 'front', 'warn_list', $member->members_seo_name ), array( array( 'wl_member=?', $member->member_id ) ) );
$warnings->rowsTemplate = array( \IPS\Theme::i()->getTemplate( 'system', 'core', 'front' ), 'warningRow' );
}
}
catch ( \UnderflowException $e ){}
}
$this->bodyClasses[] = 'ipsLayout_minimal';
$this->bodyClasses[] = 'ipsLayout_minimalNoHome';
$this->output = \IPS\Theme::i()->getTemplate( 'system', 'core' )->banned( $message, $warnings, $banEnd );
$this->title = \IPS\Settings::i()->board_name;
\IPS\Output::i()->sidebar['enabled'] = FALSE;
$this->sendOutput( \IPS\Theme::i()->getTemplate( 'global', 'core' )->globalTemplate( $this->title, $this->output, array( 'app' => \IPS\Dispatcher::i()->application->directory, 'module' => \IPS\Dispatcher::i()->module->key, 'controller' => \IPS\Dispatcher::i()->controller ) ), 403, 'text/html', array(), FALSE );
}
/**
* Checks and rebuilds JS map if it is broken
*
@@ -972,8 +1066,7 @@ class _Output
}
}
$baseUrl = parse_url( \IPS\Settings::i()->base_url );
$lookFor = '//' . $baseUrl['host'];
$baseUrl = parse_url( \IPS\Settings::i()->base_url );
foreach( $this->metaTags as $name => $value )
{
@@ -984,16 +1077,16 @@ class _Output
foreach( $value as $tag )
{
if ( mb_substr( $tag, 0, mb_strlen( $lookFor ) ) === $lookFor )
if ( mb_substr( $tag, 0, 2 ) === '//' )
{
/* Try to preserve http vs https */
if( isset( $baseUrl['scheme'] ) )
{
$tag = str_replace( $lookFor, $baseUrl['scheme'] . '://' . $baseUrl['host'], $tag );
$tag = str_replace( '//', $baseUrl['scheme'] . '://', $tag );
}
else
{
$tag = str_replace( $lookFor, 'http://' . $baseUrl['host'], $tag );
$tag = str_replace( '//', 'http://', $tag );
}
$this->metaTags[ $name ] = $tag;
@@ -1020,7 +1113,7 @@ class _Output
{
return 'none';
}
elseif ( ( ( $licenseKey['expires'] and strtotime( $licenseKey['expires'] ) < time() ) or ! isset( $licenseKey['active'] ) or !$licenseKey['active'] ) and \IPS\Member::loggedIn()->hasAcpRestriction( 'core', 'settings', 'licensekey_manage' ) )
elseif ( ( ( isset( $licenseKey['expires'] ) and strtotime( $licenseKey['expires'] ) < time() ) or ! isset( $licenseKey['active'] ) or !$licenseKey['active'] ) and \IPS\Member::loggedIn()->hasAcpRestriction( 'core', 'settings', 'licensekey_manage' ) )
{
return 'expired';
}
@@ -1057,7 +1150,7 @@ class _Output
{
if( is_subclass_of( $class, 'IPS\Content\Searchable' ) )
{
if ( $class::$includeInSiteSearch )
if ( $class::includeInSiteSearch() )
{
$type = mb_strtolower( str_replace( '\\', '_', mb_substr( $class, 4 ) ) );
$this->globalSearchMenuOptions[ $type ] = $type . '_pl';