Invision Power Services, Inc.
* @copyright (c) Invision Power Services, Inc.
* @license https://www.invisioncommunity.com/legal/standards/
* @package Invision Community
* @since 17 Oct 2013
*/
namespace IPS\Output;
/* To prevent PHP errors (extending class does not exist) revealing path */
use IPS\Db;
use IPS\Dispatcher;
use IPS\Http\Url;
use IPS\Output;
use IPS\Theme;
use function defined;
use function strstr;
use function uniqid;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Output Class
*/
class System extends 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|null $adminMessage language key for error message to show to admins
* @param array $httpHeaders Additional HTTP Headers
* @param string|null $extra Additional information (such as API error)
* @param int|string|null $faultyAppOrHookId
* @return void
*/
public function error( string $message, mixed $code, int $httpStatusCode=500, string $adminMessage=NULL, array $httpHeaders=array(), string $extra=NULL, int|string $faultyAppOrHookId=NULL ) : void
{
/* Send output */
$this->sendOutput( Theme::i()->getTemplate('global', 'core', 'global')->error( $message ), $httpStatusCode, 'text/html', $httpHeaders );
}
/**
* Send output
*
* @param string $output Content to output
* @param int $httpStatusCode HTTP Status Code
* @param string $contentType HTTP Content-type
* @param array $httpHeaders Additional HTTP Headers
* @param bool $cacheThisPage
* @param bool $pageIsCached
* @param bool $parseFileObjects
* @param bool $parseEmoji
* @return void
*/
public function sendOutput(string $output='', int $httpStatusCode=200, string $contentType='text/html', array $httpHeaders=array(), bool $cacheThisPage=TRUE, bool $pageIsCached=FALSE, bool $parseFileObjects=TRUE, bool $parseEmoji=TRUE ) : void
{
/* Set HTTP status */
if( isset( $_SERVER['SERVER_PROTOCOL'] ) and strstr( $_SERVER['SERVER_PROTOCOL'], '/1.0' ) !== false )
{
header( "HTTP/1.0 {$httpStatusCode} " . static::$httpStatuses[ $httpStatusCode ] );
}
else
{
header( "HTTP/1.1 {$httpStatusCode} " . static::$httpStatuses[ $httpStatusCode ] );
}
/* Buffer output */
if ( $output )
{
if( isset( $_SERVER['HTTP_ACCEPT_ENCODING'] ) and strstr( $_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip' ) !== false and (bool) ini_get('zlib.output_compression') === false )
{
ob_start('ob_gzhandler');
}
else
{
ob_start();
}
print Theme::i()->getTemplate('global', 'core', 'global')->globalTemplate( $output, static::i()->title, array( 'app' => Dispatcher::i()->application->directory, 'module' => Dispatcher::i()->module->key, 'controller' => Dispatcher::i()->controller ) );
}
/* Send headers */
$size = ob_get_length();
header( "Content-type: {$contentType};charset=UTF-8" );
header( "Content-Length: {$size}" );
foreach ( $httpHeaders as $header )
{
header( $header );
}
header( "Connection: close" );
/* Flush and exit */
ob_end_flush();
flush();
exit;
}
/**
* Redirect
*
* @param Url|string $url URL to redirect to
* @param string|null $message Optional message to display
* @param int $httpStatusCode HTTP Status Code
* @param bool $forceScreen If TRUE, an intermeditate screen will be shown
* @return void
*/
public function redirect( Url|string $url, ?string $message='', int $httpStatusCode=301, bool $forceScreen=FALSE ) : void
{
if ( $forceScreen === TRUE )
{
$this->sendOutput( Theme::i()->getTemplate( 'global', 'core', 'global' )->redirect( $url, $message ), $httpStatusCode );
}
$this->sendOutput( '', $httpStatusCode, '', array( "Location: {$url}" ) );
}
/**
* Create a HTML file of the MySQL debug backtrace
*
* @param array $debug
* @return string
*/
public static function generateDbLogHtml( array $debug ): string
{
$queryCount = count( $debug ) - 1; # The first query is the connection
$html = <<
MySQL Debug Log
{$queryCount} Queries
| Function |
File |
Line |
Details |
HTML;
foreach ($debug as $index => $data)
{
$unique = md5( $data['query'] . uniqid() );
$explain = null;
$explainHtml = '';
/* If this is a select query, we can get the explain */
if ( stripos( $data['query'], 'SELECT' ) !== false )
{
if( $result = Db::i()->query( "EXPLAIN " . $data['query'] ) )
{
$explain = $result->fetch_assoc();
}
}
if ( $explain )
{
$explainHtml = '';
foreach( array_keys( $explain ) as $header )
{
$explainHtml .= "| {$header} | ";
}
$explainHtml .= '
';
foreach( $explain as $key => $value )
{
$explainHtml .= "| {$value} | ";
}
$explainHtml .= '
';
}
$html .= <<
{$data['query']} |
Expand/Collapse |
HTML;
foreach( $data['backtrace'] as $id => $trace )
{
$trace['file'] = isset( $trace['file'] ) ? str_replace( \IPS\ROOT_PATH, '', $trace['file'] ) : '';
$trace['line'] = $trace['line'] ?? '';
$html .= <<
{$trace['function']}
|
{$trace['file']}
|
{$trace['line']}
|
|
HTML;
}
$html .= <<
{$explainHtml}
|
HTML;
}
$html .= <<
HTML;
return $html;
}
}