Version 4.5.0
This commit is contained in:
1 parent
1a0c7fd3c2
commit
f79dcf067a
3791 files changed
+248032
-76372
No files matched your search
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Abstract output caching class
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 12 November 2018
|
||||
*/
|
||||
|
||||
namespace IPS\Output;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract Output Caching Class
|
||||
*/
|
||||
abstract class _Cache
|
||||
{
|
||||
/**
|
||||
* @brief Instance
|
||||
*/
|
||||
protected static $instance;
|
||||
|
||||
/**
|
||||
* @brief Caches already retrieved this instance
|
||||
*/
|
||||
protected $cache = array();
|
||||
|
||||
/**
|
||||
* @brief Log
|
||||
*/
|
||||
public $log = array();
|
||||
|
||||
/**
|
||||
* Get instance
|
||||
*
|
||||
* @return \IPS\Data\Cache
|
||||
*/
|
||||
public static function i()
|
||||
{
|
||||
if( static::$instance === NULL )
|
||||
{
|
||||
$classname = 'IPS\Output\Cache\\' . \IPS\OUTPUT_CACHE_METHOD;
|
||||
|
||||
if ( $classname::supported() )
|
||||
{
|
||||
try
|
||||
{
|
||||
static::$instance = new $classname();
|
||||
}
|
||||
catch( \IPS\Data\Cache\Exception $e )
|
||||
{
|
||||
static::$instance = new \IPS\Output\Cache\Database();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
static::$instance = new \IPS\Output\Cache\Database();
|
||||
}
|
||||
}
|
||||
|
||||
return static::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store value
|
||||
*
|
||||
* @param string $key Key
|
||||
* @param mixed $value Value
|
||||
* @param mixed $meta Meta data (contentType, headers, etc)
|
||||
* @param \IPS\DateTime $expire Expiration if using database
|
||||
* @return bool
|
||||
*/
|
||||
public function set( $key, $value, $meta, \IPS\DateTime $expire )
|
||||
{
|
||||
if ( \IPS\CACHING_LOG )
|
||||
{
|
||||
$this->log[ microtime(true) ] = array( 'set', $key, json_encode( $value, JSON_PRETTY_PRINT ), var_export( debug_backtrace( DEBUG_BACKTRACE_IGNORE_ARGS ), TRUE ) );
|
||||
}
|
||||
|
||||
if ( $this->_set( $key, $value, $meta, $expire ) )
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get value
|
||||
*
|
||||
* @param string $key Key
|
||||
* @return mixed
|
||||
* @throws \OutOfRangeException
|
||||
*/
|
||||
public function get( $key )
|
||||
{
|
||||
$data = $this->_get( $key );
|
||||
|
||||
if( \is_array( $data ) and \count( $data ) and isset( $data['output'] ) and isset( $data['expires'] ) )
|
||||
{
|
||||
/* Is it expired? */
|
||||
if( $data['expires'] AND time() < $data['expires'] )
|
||||
{
|
||||
return $data;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new \OutOfRangeException;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new \OutOfRangeException;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Database Output Cache Storage Class
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 17 November 2018
|
||||
*/
|
||||
|
||||
namespace IPS\Output\Cache;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Database Output Cache Storage Class
|
||||
*/
|
||||
class _Database extends \IPS\Output\Cache
|
||||
{
|
||||
/**
|
||||
* Server supports this method?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function supported()
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract Method: Get
|
||||
*
|
||||
* @param string $key Key
|
||||
* @return array
|
||||
*/
|
||||
protected function _get( $key )
|
||||
{
|
||||
try
|
||||
{
|
||||
$data = \IPS\Db::i()->select( '*', 'core_output_cache', array( 'cache_key=? AND cache_expire>?', $key, time() ) )->first();
|
||||
$meta = array();
|
||||
|
||||
if ( ! empty( $data['cache_meta'] ) )
|
||||
{
|
||||
$meta = json_decode( $data['cache_meta'], TRUE );
|
||||
}
|
||||
return array( 'output' => $data['cache_value'], 'meta' => $meta, 'expires' => $data['cache_expire'] );
|
||||
}
|
||||
catch ( \UnderflowException $e )
|
||||
{
|
||||
throw new \OutOfRangeException;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store value using cache method if available or falling back to the database
|
||||
*
|
||||
* @param string $key Key
|
||||
* @param mixed $value Value
|
||||
* @param mixed $meta Meta data (contentType, headers, etc)
|
||||
* @param \IPS\DateTime $expire Expiration if using database
|
||||
* @return bool
|
||||
*/
|
||||
protected function _set( $key, $value, $meta, \IPS\DateTime $expire )
|
||||
{
|
||||
try
|
||||
{
|
||||
\IPS\Db::i()->replace( 'core_output_cache', array(
|
||||
'cache_key' => $key,
|
||||
'cache_value' => $value,
|
||||
'cache_meta' => json_encode( $meta ),
|
||||
'cache_expire' => $expire->getTimestamp()
|
||||
) );
|
||||
}
|
||||
catch( \IPS\Db\Exception $e )
|
||||
{
|
||||
\IPS\Log::debug( $e, 'guest_cache_fail' );
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all expired caches
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function deleteExpired()
|
||||
{
|
||||
try
|
||||
{
|
||||
\IPS\Db::i()->delete( 'core_output_cache', array( 'cache_expire<?', time() ) );
|
||||
}
|
||||
catch ( \IPS\Db\Exception $e ) { }
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract Method: Clear All Caches
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clearAll()
|
||||
{
|
||||
try
|
||||
{
|
||||
\IPS\Db::i()->delete( 'core_output_cache' );
|
||||
}
|
||||
catch ( \IPS\Db\Exception $e ){}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Redis Cache Class
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 12 November 2018
|
||||
*/
|
||||
|
||||
namespace IPS\Output\Cache;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redis Cache Class
|
||||
*/
|
||||
class _Redis extends \IPS\Output\Cache
|
||||
{
|
||||
/**
|
||||
* Turn on debugging which logs stats about cache hits and misses in Redis
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $debug = FALSE;
|
||||
|
||||
/**
|
||||
* Server supports this method?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function supported()
|
||||
{
|
||||
if ( class_exists('Redis') )
|
||||
{
|
||||
try
|
||||
{
|
||||
$connection = \IPS\Redis::i()->connection('read');
|
||||
|
||||
if( !$connection )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
catch( \RedisException $e )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Redis key
|
||||
*/
|
||||
protected $_redisKey;
|
||||
|
||||
/**
|
||||
* Get random string used in the keys to identify this site compared to other sites
|
||||
*
|
||||
* @return string|FALSE Value from the _datastore; FALSE if key doesn't exist
|
||||
*/
|
||||
protected function _getRedisKey()
|
||||
{
|
||||
if ( !$this->_redisKey )
|
||||
{
|
||||
if ( !( $this->_redisKey = \IPS\Redis::i()->get( 'redisKeyOutputCache' ) ) )
|
||||
{
|
||||
$this->_redisKey = md5( mt_rand() ) . '_pg_';
|
||||
\IPS\Redis::i()->setex( 'redisKeyOutputCache', 604800, $this->_redisKey );
|
||||
}
|
||||
}
|
||||
|
||||
return $this->_redisKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract Method: Get
|
||||
*
|
||||
* @param string $key
|
||||
* @return array|FALSE Value from the _datastore; FALSE if key doesn't exist
|
||||
*/
|
||||
protected function _get( $key )
|
||||
{
|
||||
try
|
||||
{
|
||||
$value = \IPS\Redis::i()->get( $this->_getRedisKey() . '_' . $key );
|
||||
|
||||
if ( ! $value )
|
||||
{
|
||||
if ( $this->debug )
|
||||
{
|
||||
\IPS\Redis::i()->zRemRangeByScore('guest_page_failures_key_gone', 0, time() - \IPS\CACHE_PAGE_TIMEOUT );
|
||||
\IPS\Redis::i()->zAdd( 'guest_page_failures_key_gone', time(), md5(microtime()), \IPS\CACHE_PAGE_TIMEOUT );
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if ( ! $decrypted = \IPS\Text\Encrypt::fromTag( $value )->decrypt() )
|
||||
{
|
||||
if ( $this->debug )
|
||||
{
|
||||
\IPS\Redis::i()->zRemRangeByScore('guest_page_failures_decrypt', 0, time() - \IPS\CACHE_PAGE_TIMEOUT );
|
||||
\IPS\Redis::i()->zAdd( 'guest_page_failures_decrypt', time(), md5(microtime()), \IPS\CACHE_PAGE_TIMEOUT );
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if ( ! $unzipped = @gzdecode( $decrypted ) )
|
||||
{
|
||||
if ( $this->debug )
|
||||
{
|
||||
\IPS\Redis::i()->zRemRangeByScore('guest_page_failures_gz', 0, time() - \IPS\CACHE_PAGE_TIMEOUT );
|
||||
\IPS\Redis::i()->zAdd( 'guest_page_failures_gz', time(), md5(microtime()), \IPS\CACHE_PAGE_TIMEOUT );
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if ( ! $data = json_decode( $unzipped, TRUE ) )
|
||||
{
|
||||
if ( $this->debug )
|
||||
{
|
||||
\IPS\Redis::i()->zRemRangeByScore('guest_page_failures_json_decode', 0, time() - \IPS\CACHE_PAGE_TIMEOUT );
|
||||
\IPS\Redis::i()->zAdd( 'guest_page_failures_json_decode', time(), md5(microtime()), \IPS\CACHE_PAGE_TIMEOUT );
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if ( $this->debug )
|
||||
{
|
||||
\IPS\Redis::i()->zRemRangeByScore('guest_page_hits', 0, time() - \IPS\CACHE_PAGE_TIMEOUT );
|
||||
\IPS\Redis::i()->zAdd( 'guest_page_hits', time(), md5(microtime()), \IPS\CACHE_PAGE_TIMEOUT );
|
||||
}
|
||||
|
||||
return array( 'output' => $data['value'], 'meta' => $data['meta'], 'expires' => $data['expires'] );
|
||||
}
|
||||
catch( \RedisException $e )
|
||||
{
|
||||
\IPS\Redis::i()->resetConnection( $e );
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store value using cache method if available or falling back to the database
|
||||
*
|
||||
* @param string $key Key
|
||||
* @param mixed $value Value
|
||||
* @param mixed $meta Meta data (contentType, headers, etc)
|
||||
* @param \IPS\DateTime $expire Expiration if using database
|
||||
* @return bool
|
||||
*/
|
||||
protected function _set( $key, $value, $meta, \IPS\DateTime $expire )
|
||||
{
|
||||
try
|
||||
{
|
||||
if ( $this->debug )
|
||||
{
|
||||
\IPS\Redis::i()->zRemRangeByScore('guest_page_created', 0, time() - \IPS\CACHE_PAGE_TIMEOUT );
|
||||
\IPS\Redis::i()->zAdd( 'guest_page_created', time(), md5(microtime()), \IPS\CACHE_PAGE_TIMEOUT );
|
||||
}
|
||||
|
||||
/* json_encode > gzencode > crypt */
|
||||
return (bool) \IPS\Redis::i()->setex( $this->_getRedisKey() . '_' . $key, $expire->getTimestamp() - time(), \IPS\Text\Encrypt::fromPlaintext( gzencode( json_encode( array( 'value' => $value, 'meta' => $meta, 'expires' => $expire->getTimestamp() ) ) ) )->tag() );
|
||||
}
|
||||
catch( \RedisException $e )
|
||||
{
|
||||
\IPS\Redis::i()->resetConnection( $e );
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all expired caches
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function deleteExpired()
|
||||
{
|
||||
/* Redis handles this for us automatically */
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract Method: Clear All Caches
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clearAll()
|
||||
{
|
||||
$this->_redisKey = md5( mt_rand() ) . '_pg_';
|
||||
\IPS\Redis::i()->setex( 'redisKeyOutputCache', 604800, $this->_redisKey );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,322 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Database Output Cache Storage Class
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 17 November 2018
|
||||
*/
|
||||
|
||||
namespace IPS\Output\Cache;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remote Database Output Cache Storage Class
|
||||
*/
|
||||
class _RemoteDatabase extends \IPS\Output\Cache
|
||||
{
|
||||
/**
|
||||
* @brief Unpacked config
|
||||
*/
|
||||
protected static $config = NULL;
|
||||
|
||||
/**
|
||||
* Server supports this method?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function supported()
|
||||
{
|
||||
static::getConfig();
|
||||
return ( static::$config === NULL ) ? FALSE : TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpack the config
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected static function getConfig()
|
||||
{
|
||||
if ( \defined('\IPS\OUTPUT_CACHE_METHOD_CONFIG') and \IPS\OUTPUT_CACHE_METHOD_CONFIG !== NULL )
|
||||
{
|
||||
static::$config = json_decode( \IPS\OUTPUT_CACHE_METHOD_CONFIG, TRUE );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief [ActiveRecord] Database Connection
|
||||
* @return \IPS\Db
|
||||
*/
|
||||
public static function db()
|
||||
{
|
||||
return \IPS\Db::i( 'remoteOutputCache', array(
|
||||
'sql_host' => static::$config['sql_host'],
|
||||
'sql_user' => static::$config['sql_user'],
|
||||
'sql_pass' => static::$config['sql_pass'],
|
||||
'sql_database' => static::$config['sql_database'],
|
||||
'sql_port' => static::$config['sql_port'],
|
||||
'sql_socket' => static::$config['sql_socket'],
|
||||
'sql_tbl_prefix'=> ( ! empty( static::$config['sql_tbl_prefix'] ) ) ? static::$config['sql_tbl_prefix'] : '',
|
||||
'sql_utf8mb4' => TRUE
|
||||
) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the remote DB connection is closed
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
static::db()->close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix any SQL issues
|
||||
*
|
||||
* @param \Exception $exception The exception object
|
||||
* @return boolean
|
||||
*/
|
||||
protected static function handleException( $exception )
|
||||
{
|
||||
if ( $exception->getCode() == 1017 or $exception->getCode() == 1146 )
|
||||
{
|
||||
/* Table does not exist */
|
||||
static::db()->createTable( static::getSchema() );
|
||||
return TRUE;
|
||||
}
|
||||
else if ( $exception->getCode() == 1054 )
|
||||
{
|
||||
/* Column doesn't exist, so drop table and recreate */
|
||||
static::db()->dropTable( 'core_output_cache' );
|
||||
static::db()->createTable( static::getSchema() );
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Other errors we can't auto fix */
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract Method: Get
|
||||
*
|
||||
* @param string $key Key
|
||||
* @return array
|
||||
* @throws \OutOfRangeException
|
||||
*/
|
||||
protected function _get( $key )
|
||||
{
|
||||
try
|
||||
{
|
||||
$data = static::db()->select( '*', 'core_output_cache', array( 'cache_key=?', $key,) )->first();
|
||||
$meta = array();
|
||||
|
||||
if ( ! empty( $data['cache_meta'] ) )
|
||||
{
|
||||
$meta = json_decode( $data['cache_meta'], TRUE );
|
||||
}
|
||||
|
||||
if ( $data['cache_expire'] <= time() )
|
||||
{
|
||||
throw new \UnderflowException;
|
||||
}
|
||||
|
||||
return array( 'output' => $data['cache_value'], 'meta' => $meta, 'expires' => $data['cache_expire'] );
|
||||
}
|
||||
catch ( \UnderflowException $e )
|
||||
{
|
||||
throw new \OutOfRangeException;
|
||||
}
|
||||
catch ( \IPS\Db\Exception $e )
|
||||
{
|
||||
if ( static::handleException( $e ) === TRUE )
|
||||
{
|
||||
return $this->_get( $key );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store value using cache method if available or falling back to the database
|
||||
*
|
||||
* @param string $key Key
|
||||
* @param mixed $value Value
|
||||
* @param mixed $meta Meta data (contentType, headers, etc)
|
||||
* @param \IPS\DateTime $expire Expiration if using database
|
||||
* @return bool
|
||||
*/
|
||||
protected function _set( $key, $value, $meta, \IPS\DateTime $expire )
|
||||
{
|
||||
try
|
||||
{
|
||||
static::db()->replace( 'core_output_cache', array(
|
||||
'cache_key' => $key,
|
||||
'cache_value' => $value,
|
||||
'cache_meta' => json_encode( $meta ),
|
||||
'cache_expire' => $expire->getTimestamp()
|
||||
) );
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
catch ( \IPS\Db\Exception $e )
|
||||
{
|
||||
if ( static::handleException( $e ) === TRUE )
|
||||
{
|
||||
$this->_set( $key, $value, $meta, $expire );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all expired caches
|
||||
*
|
||||
* @param boolean $truncate Truncate the table
|
||||
* @return void
|
||||
*/
|
||||
public function deleteExpired( $truncate=FALSE )
|
||||
{
|
||||
try
|
||||
{
|
||||
static::db()->delete( 'core_output_cache', ( ! $truncate ? array( 'cache_expire<?', time() ) : NULL ) );
|
||||
}
|
||||
catch ( \IPS\Db\Exception $e )
|
||||
{
|
||||
if ( static::handleException( $e ) === TRUE )
|
||||
{
|
||||
$this->deleteExpired( $truncate );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract Method: Clear All Caches
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function clearAll()
|
||||
{
|
||||
try
|
||||
{
|
||||
static::db()->delete( 'core_output_cache' );
|
||||
}
|
||||
catch ( \IPS\Db\Exception $e )
|
||||
{
|
||||
if ( static::handleException( $e ) === TRUE )
|
||||
{
|
||||
$this->clearAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop the tables
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function recreateTable()
|
||||
{
|
||||
try
|
||||
{
|
||||
static::db()->dropTable( 'core_output_cache' );
|
||||
static::db()->createTable( static::getSchema() );
|
||||
}
|
||||
catch ( \Exception $e ) { }
|
||||
}
|
||||
|
||||
/**
|
||||
* Get schema
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected static function getSchema()
|
||||
{
|
||||
$schema = <<<EOF
|
||||
{
|
||||
"name": "core_output_cache",
|
||||
"columns": {
|
||||
"cache_key": {
|
||||
"allow_null": false,
|
||||
"auto_increment": false,
|
||||
"comment": "The key",
|
||||
"decimals": null,
|
||||
"default": "",
|
||||
"length": 100,
|
||||
"name": "cache_key",
|
||||
"type": "VARCHAR",
|
||||
"unsigned": false,
|
||||
"values": []
|
||||
},
|
||||
"cache_value": {
|
||||
"allow_null": false,
|
||||
"auto_increment": false,
|
||||
"comment": "The output HTML",
|
||||
"decimals": null,
|
||||
"default": "",
|
||||
"length": 0,
|
||||
"name": "cache_value",
|
||||
"type": "LONGTEXT",
|
||||
"unsigned": false,
|
||||
"values": []
|
||||
},
|
||||
"cache_meta": {
|
||||
"allow_null": false,
|
||||
"auto_increment": false,
|
||||
"comment": "JSON headers and meta data",
|
||||
"decimals": null,
|
||||
"default": "",
|
||||
"length": 0,
|
||||
"name": "cache_meta",
|
||||
"type": "MEDIUMTEXT",
|
||||
"unsigned": false,
|
||||
"values": []
|
||||
},
|
||||
"cache_expire": {
|
||||
"allow_null": false,
|
||||
"auto_increment": false,
|
||||
"comment": "Unix timestamp of when the cache expires",
|
||||
"decimals": null,
|
||||
"default": "",
|
||||
"length": null,
|
||||
"name": "cache_expire",
|
||||
"type": "INT",
|
||||
"unsigned": false,
|
||||
"values": []
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"PRIMARY": {
|
||||
"type": "primary",
|
||||
"name": "PRIMARY",
|
||||
"length": [
|
||||
null
|
||||
],
|
||||
"columns": [
|
||||
"cache_key"
|
||||
]
|
||||
},
|
||||
"cache_expire": {
|
||||
"type": "key",
|
||||
"name": "cache_expire",
|
||||
"length": [
|
||||
null
|
||||
],
|
||||
"columns": [
|
||||
"cache_expire"
|
||||
]
|
||||
}
|
||||
},
|
||||
"collation": "utf8mb4_unicode_ci",
|
||||
"engine": "InnoDB"
|
||||
}
|
||||
EOF;
|
||||
return json_decode( $schema, TRUE );
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace IPS\Output;
|
||||
|
||||
/* 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;
|
||||
@@ -66,11 +66,11 @@ class _Javascript extends \IPS\Patterns\ActiveRecord
|
||||
{
|
||||
$key = md5( $app . '-' . $location . '-' . $path . '-' . $name );
|
||||
|
||||
if ( !in_array( $key, static::$foundJsObjects ) )
|
||||
if ( !\in_array( $key, static::$foundJsObjects ) )
|
||||
{
|
||||
$where = array( 'javascript_app=?', 'javascript_location=?', 'javascript_path=?', 'javascript_name=?' );
|
||||
|
||||
if ( ! is_string( $app ) )
|
||||
if ( !\is_string( $app ) )
|
||||
{
|
||||
$where[] = 'javascript_plugin=?';
|
||||
$bindings = array( 'core', 'plugins', '/', $name, $app );
|
||||
@@ -101,13 +101,13 @@ class _Javascript extends \IPS\Patterns\ActiveRecord
|
||||
*/
|
||||
protected function setAppOrPluginProperties()
|
||||
{
|
||||
if ( ( $this->app AND ! is_string( $this->app ) ) OR ( $this->plugin ) )
|
||||
if ( ( $this->app AND !\is_string( $this->app ) ) OR ( $this->plugin ) )
|
||||
{
|
||||
$this->app = 'core';
|
||||
$this->location = 'plugins';
|
||||
$this->path = '/';
|
||||
$this->type = 'plugin';
|
||||
$this->plugin = ( ! is_string( $this->app ) ) ? $this->app : $this->plugin;
|
||||
$this->plugin = ( !\is_string( $this->app ) ) ? $this->app : $this->plugin;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,10 +214,12 @@ class _Javascript extends \IPS\Patterns\ActiveRecord
|
||||
/**
|
||||
* Create an XML document
|
||||
*
|
||||
* @param string $app Application
|
||||
* @return XML Object
|
||||
* @param string $app Application
|
||||
* @param array $current Details about current javascript.xml file. Used if $changes is desired to be tracked
|
||||
* @param array $changes If set, will set details of any changes by reference
|
||||
* @return object
|
||||
*/
|
||||
public static function createXml( $app )
|
||||
public static function createXml( $app, $current = array(), &$changes = NULL )
|
||||
{
|
||||
static::importDev($app);
|
||||
|
||||
@@ -243,11 +245,12 @@ class _Javascript extends \IPS\Patterns\ActiveRecord
|
||||
{
|
||||
/* Initiate the <template> tag */
|
||||
$xml->startElement('file');
|
||||
|
||||
$attributes = array();
|
||||
foreach( $js as $k => $v )
|
||||
{
|
||||
if ( in_array( \substr( $k, 11 ), array('app', 'location', 'path', 'name', 'type', 'version', 'position' ) ) )
|
||||
if ( \in_array( \substr( $k, 11 ), array('app', 'location', 'path', 'name', 'type', 'version', 'position' ) ) )
|
||||
{
|
||||
$attributes[ $k ] = $v;
|
||||
$xml->startAttribute( $k );
|
||||
$xml->text( $v );
|
||||
$xml->endAttribute();
|
||||
@@ -266,16 +269,33 @@ class _Javascript extends \IPS\Patterns\ActiveRecord
|
||||
|
||||
/* Close the <template> tag */
|
||||
$xml->endElement();
|
||||
|
||||
/* Note it */
|
||||
$k = "{$attributes['javascript_app']}/{$attributes['javascript_location']}/" . ( trim( $attributes['javascript_path'] ) ? "{$attributes['javascript_path']}/" : '' ) . "{$attributes['javascript_name']}";
|
||||
|
||||
if( $changes !== NULL )
|
||||
{
|
||||
if ( !isset( $current['files'][ $k ] ) )
|
||||
{
|
||||
$changes['files']['added'][] = $k;
|
||||
}
|
||||
elseif ( $current['files'][ $k ] != $js['javascript_content'] )
|
||||
{
|
||||
$changes['files']['edited'][] = $k;
|
||||
}
|
||||
}
|
||||
|
||||
unset( $current['files'][ $k ] );
|
||||
}
|
||||
|
||||
if( count( static::$_orders ) )
|
||||
if( \count( static::$_orders ) )
|
||||
{
|
||||
foreach( static::$_orders as $_app => $orderArray )
|
||||
{
|
||||
foreach( $orderArray as $order )
|
||||
{
|
||||
$xml->startElement('order');
|
||||
|
||||
|
||||
$xml->startAttribute( 'app' );
|
||||
$xml->text( $_app );
|
||||
$xml->endAttribute();
|
||||
@@ -287,6 +307,23 @@ class _Javascript extends \IPS\Patterns\ActiveRecord
|
||||
$xml->text( $order['contents'] );
|
||||
|
||||
$xml->endElement();
|
||||
|
||||
/* Note it */
|
||||
$k = "{$_app}/{$order['path']}";
|
||||
|
||||
if( $changes !== NULL )
|
||||
{
|
||||
if ( !isset( $current['orders'][ $k ] ) )
|
||||
{
|
||||
$changes['orders']['added'][] = $k;
|
||||
}
|
||||
elseif ( $current['orders'][ $k ] != $order['contents'] )
|
||||
{
|
||||
$changes['orders']['edited'][] = $k;
|
||||
}
|
||||
}
|
||||
|
||||
unset( $current['orders'][ $k ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -294,6 +331,12 @@ class _Javascript extends \IPS\Patterns\ActiveRecord
|
||||
/* Finish */
|
||||
$xml->endDocument();
|
||||
|
||||
if( $changes !== NULL )
|
||||
{
|
||||
$changes['files']['removed'] = array_keys( $current['files'] );
|
||||
$changes['orders']['removed'] = array_keys( $current['orders'] );
|
||||
}
|
||||
|
||||
return $xml;
|
||||
}
|
||||
|
||||
@@ -695,8 +738,8 @@ class _Javascript extends \IPS\Patterns\ActiveRecord
|
||||
|
||||
\IPS\Theme::lock( $flagKey );
|
||||
|
||||
$map = ( isset( \IPS\Data\Store::i()->javascript_map ) and is_array( \IPS\Data\Store::i()->javascript_map ) ) ? \IPS\Data\Store::i()->javascript_map : array();
|
||||
$files = ( isset( \IPS\Data\Store::i()->javascript_file_map ) and is_array( \IPS\Data\Store::i()->javascript_file_map ) ) ? \IPS\Data\Store::i()->javascript_file_map : array();
|
||||
$map = ( isset( \IPS\Data\Store::i()->javascript_map ) and \is_array( \IPS\Data\Store::i()->javascript_map ) ) ? \IPS\Data\Store::i()->javascript_map : array();
|
||||
$files = ( isset( \IPS\Data\Store::i()->javascript_file_map ) and \is_array( \IPS\Data\Store::i()->javascript_file_map ) ) ? \IPS\Data\Store::i()->javascript_file_map : array();
|
||||
|
||||
if ( $location === null and $file === null )
|
||||
{
|
||||
@@ -715,7 +758,7 @@ class _Javascript extends \IPS\Patterns\ActiveRecord
|
||||
|
||||
if ( mb_substr( $file, 0, 8 ) === 'js_lang_' )
|
||||
{
|
||||
$langId = intval( mb_substr( $file, 8, -3 ) );
|
||||
$langId = \intval( mb_substr( $file, 8, -3 ) );
|
||||
|
||||
if ( $langId > 0 )
|
||||
{
|
||||
@@ -785,10 +828,10 @@ class _Javascript extends \IPS\Patterns\ActiveRecord
|
||||
|
||||
foreach ( \IPS\Db::i()->select( '*', 'core_plugins', 'plugin_enabled=1' ) as $row )
|
||||
{
|
||||
$ids[] = intval( $row['plugin_id'] );
|
||||
$ids[] = \intval( $row['plugin_id'] );
|
||||
}
|
||||
|
||||
if ( count( $ids ) )
|
||||
if ( \count( $ids ) )
|
||||
{
|
||||
$obj = static::_writeJavascriptFromResultset( \IPS\Db::i()->select( '*', 'core_javascript', array( array( 'javascript_app=? AND javascript_location=? AND javascript_type=? AND javascript_plugin IN(' . implode( ',', array_values( $ids ) ) . ')', 'core', 'plugins', 'plugin' ) ), 'javascript_app, javascript_location, javascript_position' )->setKeyField('javascript_id'), 'plugins.js', 'core', 'plugins' );
|
||||
$map[ $app ][ md5( 'core-plugins-plugins.js' ) ] = $obj ? (string) $obj : null;
|
||||
@@ -852,7 +895,7 @@ class _Javascript extends \IPS\Patterns\ActiveRecord
|
||||
}
|
||||
|
||||
/* Template only? */
|
||||
if ( count( $templateOnlyKeys ) AND in_array( $key, $templateOnlyKeys ) )
|
||||
if ( \count( $templateOnlyKeys ) AND \in_array( $key, $templateOnlyKeys ) )
|
||||
{
|
||||
foreach( $templates[ $key ] as $tmpl )
|
||||
{
|
||||
@@ -869,9 +912,10 @@ class _Javascript extends \IPS\Patterns\ActiveRecord
|
||||
}
|
||||
|
||||
\IPS\Data\Store::i()->javascript_map = $map;
|
||||
|
||||
\IPS\Settings::i()->changeValues( array( 'javascript_updated' => time() ) );
|
||||
|
||||
/* As the filename has changed, rebuild the JS map */
|
||||
if ( ( $app !== 'global' and ( $location === NULL or in_array( $location, array( 'admin', 'front', 'global' ) ) ) ) )
|
||||
if ( ( $app !== 'global' and ( $location === NULL or \in_array( $location, array( 'admin', 'front', 'global' ) ) ) ) )
|
||||
{
|
||||
\IPS\Output\Javascript::compile( 'global', 'root', 'map.js' );
|
||||
}
|
||||
@@ -893,14 +937,14 @@ class _Javascript extends \IPS\Patterns\ActiveRecord
|
||||
protected static function _writeJavascriptFromResultset( $files, $fileName, $app, $location )
|
||||
{
|
||||
$content = array();
|
||||
$jsMap = ( isset( \IPS\Data\Store::i()->javascript_map ) and is_array( \IPS\Data\Store::i()->javascript_map ) ) ? \IPS\Data\Store::i()->javascript_map : array();
|
||||
$jsMap = ( isset( \IPS\Data\Store::i()->javascript_map ) and \is_array( \IPS\Data\Store::i()->javascript_map ) ) ? \IPS\Data\Store::i()->javascript_map : array();
|
||||
|
||||
/* Try and remove any existing files */
|
||||
try
|
||||
{
|
||||
$md5 = md5( $app . '-' . $location . '-' . $fileName );
|
||||
|
||||
if ( isset( $jsMap[ $app ] ) and in_array( $md5, array_keys( $jsMap[ $app ] ) ) )
|
||||
if ( isset( $jsMap[ $app ] ) and \in_array( $md5, array_keys( $jsMap[ $app ] ) ) )
|
||||
{
|
||||
\IPS\File::get( 'core_Theme', $jsMap[ $app ][ $md5 ] )->delete();
|
||||
}
|
||||
@@ -908,7 +952,7 @@ class _Javascript extends \IPS\Patterns\ActiveRecord
|
||||
catch ( \InvalidArgumentException $e ) { }
|
||||
|
||||
|
||||
if ( ! count( $files ) )
|
||||
if ( ! \count( $files ) )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
@@ -951,7 +995,7 @@ class _Javascript extends \IPS\Patterns\ActiveRecord
|
||||
*/
|
||||
protected static function _writeJavascript( $content, $fileName, $app, $location )
|
||||
{
|
||||
return \IPS\File::create( 'core_Theme', $location . '_' . $fileName, $content, 'javascript_' . $app, TRUE );
|
||||
return \IPS\File::create( 'core_Theme', $location . '_' . $fileName, $content, 'javascript_' . $app, TRUE, NULL, FALSE );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1024,7 +1068,7 @@ class _Javascript extends \IPS\Patterns\ActiveRecord
|
||||
}
|
||||
}
|
||||
|
||||
$json = version_compare( PHP_VERSION, '5.4.0' ) >= 0 ? json_encode( $map, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ) : str_replace( '\\/', '/', json_encode( $map ) );
|
||||
$json = json_encode( $map, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES );
|
||||
|
||||
return 'var ipsJavascriptMap = ' . $json . ';';
|
||||
}
|
||||
@@ -1085,7 +1129,7 @@ class _Javascript extends \IPS\Patterns\ActiveRecord
|
||||
return array();
|
||||
}
|
||||
|
||||
if ( in_array( $file, \IPS\Output::$globalJavascript ) )
|
||||
if ( \in_array( $file, \IPS\Output::$globalJavascript ) )
|
||||
{
|
||||
$app = 'global';
|
||||
$location = '/';
|
||||
@@ -1093,7 +1137,7 @@ class _Javascript extends \IPS\Patterns\ActiveRecord
|
||||
|
||||
if ( mb_substr( $file, 0, 8 ) === 'js_lang_' )
|
||||
{
|
||||
return array( \IPS\Http\Url::baseUrl() . "/applications/core/interface/js/jslang.php?langId=" . intval( mb_substr( $file, 8, -3 ) ) );
|
||||
return array( \IPS\Http\Url::baseUrl() . "/applications/core/interface/js/jslang.php?langId=" . \intval( mb_substr( $file, 8, -3 ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1126,7 +1170,7 @@ class _Javascript extends \IPS\Patterns\ActiveRecord
|
||||
{
|
||||
$return = array();
|
||||
|
||||
if ( in_array( $file, \IPS\Output::$globalJavascript ) )
|
||||
if ( \in_array( $file, \IPS\Output::$globalJavascript ) )
|
||||
{
|
||||
return static::_directoryJs( \IPS\ROOT_PATH . "/dev/js/" . mb_substr( $file, 0, -3 ) );
|
||||
}
|
||||
@@ -1191,10 +1235,20 @@ class _Javascript extends \IPS\Patterns\ActiveRecord
|
||||
}
|
||||
}
|
||||
|
||||
$json = version_compare( PHP_VERSION, '5.4.0' ) >= 0 ? json_encode( $files, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ) : str_replace( '\\/', '/', json_encode( $files ) );
|
||||
$json = json_encode( $files, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES );
|
||||
return 'var ipsJavascriptMap = ' . $json . ';';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the cache bust key for javascript
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function javascriptCacheBustKey()
|
||||
{
|
||||
return \IPS\CACHEBUST_KEY . \IPS\Settings::i()->javascript_updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the component parts from a URL
|
||||
*
|
||||
@@ -1231,7 +1285,7 @@ class _Javascript extends \IPS\Patterns\ActiveRecord
|
||||
$models = array();
|
||||
|
||||
/* Only include if the app is enabled */
|
||||
if( !in_array( $app, array_keys( \IPS\Application::enabledApplications() ) ) )
|
||||
if( !\in_array( $app, array_keys( \IPS\Application::enabledApplications() ) ) )
|
||||
{
|
||||
return $models;
|
||||
}
|
||||
@@ -1257,6 +1311,7 @@ class _Javascript extends \IPS\Patterns\ActiveRecord
|
||||
* @param string $key Controller Key (messages, reports, etc)
|
||||
* @param string $location Location (front, admin)
|
||||
* @param string $app Application
|
||||
* @return array
|
||||
*/
|
||||
protected static function _sectionJs( $key, $location, $app )
|
||||
{
|
||||
@@ -1314,7 +1369,7 @@ class _Javascript extends \IPS\Patterns\ActiveRecord
|
||||
}
|
||||
}
|
||||
|
||||
if ( count( $contents ) )
|
||||
if ( \count( $contents ) )
|
||||
{
|
||||
/* Check to see if 'ips.{dir}.js' exists and if so, put that first */
|
||||
$parentDir = preg_replace( '#^(.*)/([^\/]+?)$#', '\2', $path );
|
||||
@@ -1363,12 +1418,12 @@ class _Javascript extends \IPS\Patterns\ActiveRecord
|
||||
}
|
||||
}
|
||||
|
||||
if ( count( $reordered ) )
|
||||
if ( \count( $reordered ) )
|
||||
{
|
||||
/* Add in items not specified in the order */
|
||||
$diff = array_diff( $return, $reordered );
|
||||
|
||||
if ( count( $diff ) )
|
||||
if ( \count( $diff ) )
|
||||
{
|
||||
foreach( $diff as $url )
|
||||
{
|
||||
|
||||
+361
-125
@@ -11,7 +11,7 @@
|
||||
namespace IPS;
|
||||
|
||||
/* 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;
|
||||
@@ -24,7 +24,7 @@ class _Output
|
||||
{
|
||||
/**
|
||||
* @brief HTTP Statuses
|
||||
* @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
|
||||
* @see <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html">RFC 2616</a>
|
||||
*/
|
||||
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' );
|
||||
|
||||
@@ -43,7 +43,7 @@ class _Output
|
||||
*/
|
||||
protected static $javascriptObjects = null;
|
||||
|
||||
/**
|
||||
/**
|
||||
* @brief File object classes
|
||||
*/
|
||||
protected static $fileObjectClasses = array();
|
||||
@@ -52,9 +52,19 @@ class _Output
|
||||
* @brief Meta tags for the current page
|
||||
*/
|
||||
public $metaTags = array();
|
||||
|
||||
/**
|
||||
* @brief Custom meta tags for the current page
|
||||
*/
|
||||
public $customMetaTags = array();
|
||||
|
||||
/**
|
||||
* @brief Automatic meta tags for the current page
|
||||
*/
|
||||
public $autoMetaTags = array();
|
||||
|
||||
/**
|
||||
* @brief Other <link rel=""> tags
|
||||
* @brief Other `<link rel="">` tags
|
||||
*/
|
||||
public $linkTags = array();
|
||||
|
||||
@@ -82,17 +92,17 @@ class _Output
|
||||
{
|
||||
if( static::$instance === NULL )
|
||||
{
|
||||
$classname = get_called_class();
|
||||
$classname = \get_called_class();
|
||||
static::$instance = new $classname;
|
||||
}
|
||||
|
||||
/* Inline Message */
|
||||
if( isset( $_SESSION['inlineMessage'] ) )
|
||||
if( $message = static::getInlineMessage() )
|
||||
{
|
||||
if( !\IPS\Request::i()->isAjax() )
|
||||
{
|
||||
static::$instance->inlineMessage = $_SESSION['inlineMessage'];
|
||||
$_SESSION['inlineMessage'] = NULL;
|
||||
static::$instance->inlineMessage = $message;
|
||||
static::setInlineMessage();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,6 +121,11 @@ class _Output
|
||||
*/
|
||||
public $title = '';
|
||||
|
||||
/**
|
||||
* @brief Default page title (may differ from $title if the meta tag editor was used)
|
||||
*/
|
||||
public $defaultPageTitle = '';
|
||||
|
||||
/**
|
||||
* @brief Should the title show in the header (ACP only)?
|
||||
*/
|
||||
@@ -142,7 +157,7 @@ class _Output
|
||||
public $jsVars = array();
|
||||
|
||||
/**
|
||||
* @brief Other raw JS - this is included inside an existing <script> tag already, so you should omit wrapping tags
|
||||
* @brief Other raw JS - this is included inside an existing `<script>` tag already, so you should omit wrapping tags
|
||||
*/
|
||||
public $headJs = '';
|
||||
|
||||
@@ -152,7 +167,7 @@ class _Output
|
||||
public $headCss = '';
|
||||
|
||||
/**
|
||||
* @brief Anything set in this property will be output right before </body> - useful for certain third party scripts that need to be output at end of page
|
||||
* @brief Anything set in this property will be output right before `</body>` - useful for certain third party scripts that need to be output at end of page
|
||||
*/
|
||||
public $endBodyCode = '';
|
||||
|
||||
@@ -197,7 +212,7 @@ class _Output
|
||||
public $editUrl = NULL;
|
||||
|
||||
/**
|
||||
* @brief <base target="">
|
||||
* @brief `<base target="">`
|
||||
*/
|
||||
public $base = NULL;
|
||||
|
||||
@@ -211,15 +226,36 @@ class _Output
|
||||
*/
|
||||
public $pageCaching = TRUE;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
if ( \IPS\Dispatcher::hasInstance() and \IPS\Dispatcher::i()->controllerLocation !== 'setup' )
|
||||
{
|
||||
if ( \IPS\Settings::i()->clickjackprevention == 'csp' )
|
||||
{
|
||||
$this->httpHeaders['Content-Security-Policy'] = \IPS\Settings::i()->csp_header;
|
||||
$this->httpHeaders['X-Content-Security-Policy'] = \IPS\Settings::i()->csp_header; // This is just for IE11
|
||||
}
|
||||
elseif ( \IPS\Settings::i()->clickjackprevention != 'none' )
|
||||
{
|
||||
$this->httpHeaders['X-Frame-Options'] = 'sameorigin';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a JS bundle
|
||||
*
|
||||
* JS Bundle Cheatsheet
|
||||
* library.js (this is jQuery, mustache, underscore, jstz, etc)
|
||||
* framework.js (this is ui/*, utils/*, ips.model.js, ips.controller.js and the editor controllers)
|
||||
* admin.js or front.js (these are controllers, templates and models which are used everywhere for that location)
|
||||
* app.js (this is all models for a single application)
|
||||
* {location}_{section}.js (this is all controllers and templates for this section called ad-hoc when needed)
|
||||
* @par JS Bundle Cheatsheet
|
||||
* @li library.js (this is jQuery, mustache, underscore, jstz, etc)
|
||||
* @li framework.js (this is ui/, utils/*, ips.model.js, ips.controller.js and the editor controllers)
|
||||
* @li admin.js or front.js (these are controllers, templates and models which are used everywhere for that location)
|
||||
* @li app.js (this is all models for a single application)
|
||||
* @li {location}_{section}.js (this is all controllers and templates for this section called ad-hoc when needed)
|
||||
*
|
||||
* @param string $file Filename
|
||||
* @param string|null $app Application
|
||||
@@ -232,7 +268,7 @@ class _Output
|
||||
|
||||
if ( $location === 'interface' AND mb_substr( $file, -3 ) === '.js' )
|
||||
{
|
||||
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() ) );
|
||||
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 )
|
||||
{
|
||||
@@ -240,14 +276,14 @@ class _Output
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( class_exists( 'IPS\Dispatcher', FALSE ) and \IPS\Dispatcher::i()->controllerLocation === 'setup' )
|
||||
if ( class_exists( 'IPS\Dispatcher', FALSE ) and ( !\IPS\Dispatcher::hasInstance() OR \IPS\Dispatcher::i()->controllerLocation === 'setup' ) )
|
||||
{
|
||||
return array();
|
||||
}
|
||||
|
||||
if ( $app === null OR $app === 'global' )
|
||||
{
|
||||
if ( in_array( $file, static::$globalJavascript ) )
|
||||
if ( \in_array( $file, static::$globalJavascript ) )
|
||||
{
|
||||
/* Global bundle (admin.js, front.js, library.js, framework.js, map.js) */
|
||||
return array( static::_getJavascriptFileObject( 'global', 'root', $file )->url );
|
||||
@@ -347,7 +383,7 @@ class _Output
|
||||
{
|
||||
$key = md5( $app .'-' . $location . '-' . $file );
|
||||
|
||||
if ( isset( $javascriptObjects[ $app ] ) and is_array( $javascriptObjects[ $app ] ) and in_array( $key, array_keys( $javascriptObjects[ $app ] ) ) )
|
||||
if ( isset( $javascriptObjects[ $app ] ) and \is_array( $javascriptObjects[ $app ] ) and \in_array( $key, array_keys( $javascriptObjects[ $app ] ) ) )
|
||||
{
|
||||
if ( $javascriptObjects[ $app ][ $key ] !== NULL )
|
||||
{
|
||||
@@ -357,7 +393,9 @@ class _Output
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
\IPS\Settings::i()->changeValues( array( 'javascript_updated' => time() ) );
|
||||
|
||||
\IPS\Data\Store::i()->javascript_map = $javascriptObjects;
|
||||
}
|
||||
|
||||
@@ -460,7 +498,7 @@ class _Output
|
||||
|
||||
$javascriptObjects = ( isset( \IPS\Data\Store::i()->javascript_map ) ) ? \IPS\Data\Store::i()->javascript_map : array();
|
||||
|
||||
if ( isset( $javascriptObjects[ $app ] ) and in_array( $key, array_keys( $javascriptObjects[ $app ] ) ) )
|
||||
if ( isset( $javascriptObjects[ $app ] ) and \in_array( $key, array_keys( $javascriptObjects[ $app ] ) ) )
|
||||
{
|
||||
if ( $javascriptObjects[ $app ][ $key ] === NULL )
|
||||
{
|
||||
@@ -499,7 +537,7 @@ class _Output
|
||||
$javascriptObjects = ( isset( \IPS\Data\Store::i()->javascript_map ) ) ? \IPS\Data\Store::i()->javascript_map : array();
|
||||
|
||||
/* Test again */
|
||||
if ( isset( $javascriptObjects[ $app ] ) and in_array( $key, array_keys( $javascriptObjects[ $app ] ) ) and $javascriptObjects[ $app ][ $key ] )
|
||||
if ( isset( $javascriptObjects[ $app ] ) and \in_array( $key, array_keys( $javascriptObjects[ $app ] ) ) and $javascriptObjects[ $app ][ $key ] )
|
||||
{
|
||||
return \IPS\File::get( 'core_Theme', $javascriptObjects[ $app ][ $key ] );
|
||||
}
|
||||
@@ -545,6 +583,12 @@ class _Output
|
||||
{
|
||||
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=system&controller=login", 'front', 'login' )->setQueryString( '_mfaLogin', 1 ) );
|
||||
}
|
||||
|
||||
/* Do not show advertisements that shouldn't display on non-content pages on error pages */
|
||||
if( \IPS\Dispatcher::i()->dispatcherController )
|
||||
{
|
||||
\IPS\Dispatcher::i()->dispatcherController->isContentPage = FALSE;
|
||||
}
|
||||
|
||||
/* If we're in an external script, just show a simple message */
|
||||
if ( !\IPS\Dispatcher::hasInstance() )
|
||||
@@ -594,7 +638,7 @@ class _Output
|
||||
\IPS\Member::loggedIn()->language()->parseOutputForDisplay( $message );
|
||||
|
||||
/* Log */
|
||||
$level = intval( \substr( $code, 0, 1 ) );
|
||||
$level = \intval( \substr( $code, 0, 1 ) );
|
||||
if( !\IPS\Session::i()->userAgent->spider )
|
||||
{
|
||||
if( $code and \IPS\Settings::i()->error_log_level and $level >= \IPS\Settings::i()->error_log_level )
|
||||
@@ -607,11 +651,8 @@ class _Output
|
||||
'log_ip_address' => \IPS\Request::i()->ipAddress(),
|
||||
'log_request_uri' => $_SERVER['REQUEST_URI'],
|
||||
) );
|
||||
}
|
||||
|
||||
if( \IPS\Settings::i()->error_notify_level and $level >= \IPS\Settings::i()->error_notify_level )
|
||||
{
|
||||
\IPS\Email::buildFromTemplate( 'core', 'error_log', array( $code, $message ), \IPS\Email::TYPE_TRANSACTIONAL )->send( \IPS\Settings::i()->email_in );
|
||||
\IPS\core\AdminNotification::send( 'core', 'Error', NULL, TRUE, array( $code, $message ) );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -627,7 +668,7 @@ class _Output
|
||||
/* Try to find the breaking hook */
|
||||
if ( $faultyAppOrHookId )
|
||||
{
|
||||
if ( is_numeric( $faultyAppOrHookId ) )
|
||||
if ( \is_numeric( $faultyAppOrHookId ) )
|
||||
{
|
||||
$hookSource = \IPS\Db::i()->select( 'plugin', 'core_hooks', array( 'id=?', $faultyAppOrHookId ) )->first();
|
||||
$plugin = \IPS\Plugin::load( $hookSource );
|
||||
@@ -639,10 +680,13 @@ class _Output
|
||||
$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' ) ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Do not cache error pages
|
||||
$allowGuestCache = FALSE;
|
||||
|
||||
/* 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 ), 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, $allowGuestCache, FALSE );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -684,20 +728,21 @@ class _Output
|
||||
/**
|
||||
* 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 Can/should this page be cached?
|
||||
* @param bool $pageIsCached Is the page from a cache? If TRUE, no language parsing will be done
|
||||
* @param bool $parseFileObjects Should <fileStore.xxx> and <___base_url___> be replaced in the output?
|
||||
* @param bool $parseEmoji Should Emoji be parsed?
|
||||
* @return void
|
||||
* @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 Can/should this page be cached?
|
||||
* @param bool $pageIsCached Is the page from a cache? If TRUE, no language parsing will be done
|
||||
* @param bool $parseFileObjects Should `<fileStore.xxx>` and `<___base_url___>` be replaced in the output?
|
||||
* @param bool $parseEmoji Should Emoji be parsed?
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function sendOutput( $output='', $httpStatusCode=200, $contentType='text/html', $httpHeaders=array(), $cacheThisPage=TRUE, $pageIsCached=FALSE, $parseFileObjects=TRUE, $parseEmoji=TRUE )
|
||||
{
|
||||
/* Replace language stack keys with actual content */
|
||||
if ( \IPS\Dispatcher::hasInstance() and !in_array( $contentType, array( 'text/javascript', 'text/css', 'application/json' ) ) and $output and !$pageIsCached )
|
||||
if ( \IPS\Dispatcher::hasInstance() and !\in_array( $contentType, array( 'text/javascript', 'text/css', 'application/json' ) ) and $output and !$pageIsCached )
|
||||
{
|
||||
\IPS\Member::loggedIn()->language()->parseOutputForDisplay( $output );
|
||||
}
|
||||
@@ -707,39 +752,65 @@ class _Output
|
||||
{
|
||||
$this->parseFileObjectUrls( $output );
|
||||
}
|
||||
|
||||
|
||||
/* Replace emoji */
|
||||
if ( $output and $parseEmoji and \IPS\Dispatcher::hasInstance() and \IPS\Dispatcher::i()->controllerLocation !== 'setup' )
|
||||
{
|
||||
$output = $this->replaceEmojiWithImages( $output );
|
||||
}
|
||||
|
||||
/* Do we want to preload anything? Only send preload headers for full page requests with a text/html response that have output. */
|
||||
if( !$pageIsCached AND !\IPS\IN_DEV AND $output AND !\IPS\Request::i()->isAjax() AND $contentType == 'text/html' )
|
||||
{
|
||||
if( $urls = $this->getPreloadUrls() AND \count( $urls ) )
|
||||
{
|
||||
/* Send a link header for preloading */
|
||||
//$httpHeaders['Link'] = implode( ', ', $urls );
|
||||
}
|
||||
}
|
||||
|
||||
/* Full page caching for guests */
|
||||
if (
|
||||
\IPS\CACHE_PAGE_TIMEOUT and $this->pageCaching 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)
|
||||
mb_strtolower( $_SERVER['REQUEST_METHOD'] ) == 'get' and // Is a HTTP GET request (don't cache output for POSTs)
|
||||
( 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)
|
||||
!isset( \IPS\Request::i()->csrfKey ) and // CSRF key isn't present (which would be like a POST request)
|
||||
( $contentType == 'text/html' or $contentType == 'text/xml' ) and // Output is HTML or XML (don't cache JSON output, etc)
|
||||
( $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' 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
|
||||
) {
|
||||
$theme = isset( \IPS\Request::i()->cookie['theme'] ) ? \IPS\Request::i()->cookie['theme'] : '';
|
||||
|
||||
\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 . '_' . $theme, array(
|
||||
'output' => str_replace( \IPS\Session::i()->csrfKey, '{{csrfKey}}', $output ),
|
||||
'code' => $httpStatusCode,
|
||||
'contentType' => $contentType,
|
||||
'httpHeaders' => array_merge( $httpHeaders, array( 'X-IPS-Cached-Response' => \IPS\DateTime::create()->rfc1123() ) ),
|
||||
'lastUpdated' => time()
|
||||
), \IPS\DateTime::create()->add( new\DateInterval( 'PT' . \IPS\CACHE_PAGE_TIMEOUT . 'S' ) ), TRUE );
|
||||
\IPS\Output\Cache::i()->set( 'page_' . md5( (string) \IPS\Request::i()->url() ) . '_' . \IPS\Member::loggedIn()->language()->id . '_' . $theme,
|
||||
str_replace( \IPS\Session::i()->csrfKey, '{{csrfKey}}', $output ),
|
||||
array(
|
||||
'code' => $httpStatusCode,
|
||||
'contentType' => $contentType,
|
||||
'httpHeaders' => array_merge( $httpHeaders, array( 'X-IPS-Cached-Response' => \IPS\DateTime::create()->rfc1123() ) ),
|
||||
'lastUpdated' => time()
|
||||
), \IPS\DateTime::create()->add( new\DateInterval( 'PT' . \IPS\CACHE_PAGE_TIMEOUT . 'S' ) ), TRUE );
|
||||
|
||||
/* Add caching headers */
|
||||
if( !isset( $httpHeaders['Cache-Control'] ) )
|
||||
{
|
||||
$httpHeaders += \IPS\Output::getCacheHeaders( time(), \IPS\CACHE_PAGE_TIMEOUT );
|
||||
}
|
||||
}
|
||||
|
||||
/* Include headers set in constructor, intentionally after guest caching. */
|
||||
$httpHeaders = $this->httpHeaders + $httpHeaders;
|
||||
|
||||
/* We will only push resources (http/2) on the first visit, i.e. if the session cookie is not present yet */
|
||||
$location = ( \IPS\Dispatcher::hasInstance() ) ? mb_ucfirst( \IPS\Dispatcher::i()->controllerLocation ) : 'Front';
|
||||
|
||||
if( isset( \IPS\Request::i()->cookie['IPSSession' . $location ] ) AND \IPS\Request::i()->cookie['IPSSession' . $location ] AND isset( $httpHeaders['Link'] ) )
|
||||
{
|
||||
unset( $httpHeaders['Link'] );
|
||||
}
|
||||
|
||||
/* Query Log (has to be done after parseOutputForDisplay because runs queries and after page caching so the log isn't misleading) */
|
||||
$contentTypeTypes = explode( '/', $contentType );
|
||||
|
||||
if ( ( \IPS\QUERY_LOG or \IPS\CACHING_LOG ) and in_array( $contentType, array( 'text/html', 'application/json' ) ) )
|
||||
if ( $output and ( \IPS\QUERY_LOG or \IPS\CACHING_LOG or \IPS\REDIS_LOG ) and \in_array( $contentType, array( 'text/html', 'application/json' ) ) and ( \IPS\Dispatcher::hasInstance() and \IPS\Dispatcher::i()->controllerLocation !== 'setup' ) )
|
||||
{
|
||||
/* Close the session and run tasks now so we can see those queries */
|
||||
session_write_close();
|
||||
@@ -766,7 +837,7 @@ class _Output
|
||||
{
|
||||
$output = str_replace( '<!--ipsQueryLog-->', \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' )->queryLog( $queryLog ), $output );
|
||||
}
|
||||
if ( \IPS\CACHING_LOG )
|
||||
if ( \IPS\CACHING_LOG or \IPS\REDIS_LOG )
|
||||
{
|
||||
$output = str_replace( '<!--ipsCachingLog-->', \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' )->cachingLog( $cachingLog ), $output );
|
||||
}
|
||||
@@ -784,15 +855,41 @@ class _Output
|
||||
/* Start buffering */
|
||||
ob_start();
|
||||
|
||||
/* If the browser supports gzip, gzip the content - we do this ourselves so that we can send Content-Length even with mod_gzip */
|
||||
if ( isset( $_SERVER['HTTP_ACCEPT_ENCODING'] ) and \strpos( $_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip' ) !== false )
|
||||
/* Generated by a logged in user? */
|
||||
if( \IPS\Dispatcher::hasInstance() )
|
||||
{
|
||||
if ( function_exists( 'gzencode' ) and (bool) ini_get('zlib.output_compression') === false )
|
||||
$this->sendHeader( "X-IPS-LoggedIn: " . ( ( \IPS\Member::loggedIn()->member_id ) ? 1 : 0 ) );
|
||||
}
|
||||
|
||||
$vary = array( 'cookie' );
|
||||
|
||||
/* Can we compress the content? */
|
||||
if ( $output and isset( $_SERVER['HTTP_ACCEPT_ENCODING'] ) )
|
||||
{
|
||||
/* If php zlib.output_compression is on, don't do anything since PHP will */
|
||||
if( (bool) ini_get('zlib.output_compression') === false )
|
||||
{
|
||||
$output = gzencode( $output ); // mod_gzip will encode pages, but we want to encode ourselves so that Content-Length is correct
|
||||
$this->sendHeader("Content-Encoding: gzip"); // Tells the server we've alredy encoded so it doesn't need to
|
||||
/* Try brotli first - support will be rare, but preferred if it is available */
|
||||
if ( \strpos( $_SERVER['HTTP_ACCEPT_ENCODING'], 'br' ) !== false and \function_exists( 'brotli_compress' ) )
|
||||
{
|
||||
$output = brotli_compress( $output );
|
||||
$this->sendHeader( "Content-Encoding: br" ); // Tells the server we've alredy encoded so it doesn't need to
|
||||
$vary[] = "Accept-Encoding"; // Tells proxy caches that the response varies depending upon encoding
|
||||
}
|
||||
/* If the browser supports gzip, gzip the content - we do this ourselves so that we can send Content-Length even with mod_gzip */
|
||||
elseif ( \strpos( $_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip' ) !== false and \function_exists( 'gzencode' ) )
|
||||
{
|
||||
$output = gzencode( $output ); // mod_gzip will encode pages, but we want to encode ourselves so that Content-Length is correct
|
||||
$this->sendHeader( "Content-Encoding: gzip" ); // Tells the server we've alredy encoded so it doesn't need to
|
||||
$vary[] = "Accept-Encoding"; // Tells proxy caches that the response varies depending upon encoding
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( \count( $vary ) )
|
||||
{
|
||||
$this->sendHeader( "Vary: " . implode( ", ", $vary ) );
|
||||
}
|
||||
|
||||
/* Output */
|
||||
print $output;
|
||||
@@ -810,17 +907,6 @@ class _Output
|
||||
$size = ob_get_length();
|
||||
$this->sendHeader( "Content-Length: {$size}" ); // Makes sure the connection closes after sending output so that tasks etc aren't holding it open
|
||||
}
|
||||
|
||||
/* Do we want to preload anything? */
|
||||
foreach( $this->linkTags as $tag )
|
||||
{
|
||||
/* We are only doing this for rel=preload, and the 'as' parameter is not optional */
|
||||
if( is_array( $tag ) AND isset( $tag['rel'] ) AND $tag['rel'] == 'preload' AND isset( $tag['as'] ) AND $tag['as'] )
|
||||
{
|
||||
$as = ( isset( $tag['as'] ) ) ? "; as=" . $tag['as'] : '';
|
||||
$this->sendHeader( "Link: " . $tag['href'] . '; rel=preload' . $as );
|
||||
}
|
||||
}
|
||||
|
||||
/* Rest of our HTTP headers */
|
||||
foreach ( $httpHeaders as $key => $header )
|
||||
@@ -839,21 +925,98 @@ class _Output
|
||||
@ob_end_flush();
|
||||
@flush();
|
||||
|
||||
/* Log headers if we are set to do so */
|
||||
if( \IPS\DEV_LOG_HEADERS === TRUE )
|
||||
{
|
||||
$this->_logHeadersSent();
|
||||
}
|
||||
|
||||
/* If using PHP-FPM, close the request so that __destruct tasks are run after data is flushed to the browser
|
||||
@see http://www.php.net/manual/en/function.fastcgi-finish-request.php */
|
||||
if( function_exists( 'fastcgi_finish_request' ) )
|
||||
if( \function_exists( 'fastcgi_finish_request' ) )
|
||||
{
|
||||
fastcgi_finish_request();
|
||||
}
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs the headers that have been sent, if we are able to do so
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function _logHeadersSent()
|
||||
{
|
||||
$headers = NULL;
|
||||
|
||||
if( \function_exists('headers_list') )
|
||||
{
|
||||
$headers = headers_list();
|
||||
}
|
||||
elseif( \function_exists('apache_response_headers') )
|
||||
{
|
||||
$headers = apache_response_headers();
|
||||
}
|
||||
elseif( \function_exists('xdebug_get_headers') )
|
||||
{
|
||||
$headers = xdebug_get_headers();
|
||||
}
|
||||
|
||||
if( $headers !== NULL )
|
||||
{
|
||||
\IPS\Log::log( $headers, 'httpHeaders' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the URLs to preload (via Link: HTTP header)
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getPreloadUrls()
|
||||
{
|
||||
/* http/2 push resources */
|
||||
$preload = array();
|
||||
|
||||
foreach( $this->linkTags as $tag )
|
||||
{
|
||||
/* We are only doing this for rel=preload, and the 'as' parameter is not optional. Preloading fonts currently does not work as expected in Chrome, resulting in the font file downloading twice, so we will skip fonts. */
|
||||
if( \is_array( $tag ) AND isset( $tag['rel'] ) AND $tag['rel'] == 'preload' AND isset( $tag['as'] ) AND $tag['as'] AND $tag['as'] != 'font' )
|
||||
{
|
||||
$preload[] = $tag['href'] . '; rel=preload; as=' . $tag['as'];
|
||||
}
|
||||
}
|
||||
|
||||
$cssFilesToCheck = ( \IPS\Dispatcher::hasInstance() AND \IPS\Dispatcher::i()->controllerLocation == 'setup' ) ? \IPS\Output::i()->cssFiles : array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'custom.css', 'core', 'front' ) );
|
||||
|
||||
foreach( array_unique( $cssFilesToCheck, SORT_STRING ) as $css )
|
||||
{
|
||||
$url = \IPS\Http\Url::external( $css )->setQueryString( 'v', \IPS\CACHEBUST_KEY );
|
||||
$preload[] = (string) $url . '; rel=preload; as=style';
|
||||
}
|
||||
|
||||
foreach( array_unique( array_filter( array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->jsFilesAsync ) ), SORT_STRING ) as $js )
|
||||
{
|
||||
$url = \IPS\Http\Url::external( $js );
|
||||
|
||||
if( $url->data['host'] == parse_url( \IPS\Settings::i()->base_url, PHP_URL_HOST ) )
|
||||
{
|
||||
$url = $url->setQueryString( 'v', \IPS\CACHEBUST_KEY );
|
||||
}
|
||||
|
||||
$preload[] = (string) $url . '; rel=preload; as=script';
|
||||
}
|
||||
|
||||
/* Only include the first 30 entries if there are a lot */
|
||||
return \array_slice( $preload, 0, 30 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Send JSON output
|
||||
*
|
||||
* @param string $data Data to be JSON-encoded
|
||||
* @param int $httpStatusCode HTTP Status Code
|
||||
* @param string|array $data Data to be JSON-encoded
|
||||
* @param int $httpStatusCode HTTP Status Code
|
||||
* @return void
|
||||
*/
|
||||
public function json( $data, $httpStatusCode=200 )
|
||||
@@ -900,12 +1063,12 @@ class _Output
|
||||
{
|
||||
$message = \IPS\Member::loggedIn()->language()->addToStack( $message );
|
||||
\IPS\Member::loggedIn()->language()->parseOutputForDisplay( $message );
|
||||
$_SESSION['inlineMessage'] = $message;
|
||||
static::setInlineMessage( $message );
|
||||
session_write_close();
|
||||
}
|
||||
elseif( $this->inlineMessage )
|
||||
{
|
||||
$_SESSION['inlineMessage'] = $this->inlineMessage;
|
||||
static::setInlineMessage( $this->inlineMessage );
|
||||
session_write_close();
|
||||
}
|
||||
|
||||
@@ -969,7 +1132,7 @@ class _Output
|
||||
*/
|
||||
public function replaceEmojiWithImages( $output )
|
||||
{
|
||||
if ( \IPS\Settings::i()->emoji_style == 'twemoji' or \IPS\Settings::i()->emoji_style == 'emojione' )
|
||||
if ( \IPS\Settings::i()->emoji_style == 'twemoji' )
|
||||
{
|
||||
return preg_replace_callback( '/<span class="ipsEmoji">(.+?)<\/span>/', function( $matches ) {
|
||||
$hex = bin2hex( mb_convert_encoding( $matches[1], 'UTF-32', 'UTF-8' ) );
|
||||
@@ -1004,26 +1167,19 @@ class _Output
|
||||
|
||||
$chunks[ $i ] = $res;
|
||||
}
|
||||
|
||||
if ( \IPS\Settings::i()->emoji_style == 'twemoji' )
|
||||
{
|
||||
$image = implode( '-', $chunks );
|
||||
|
||||
if ( \strstr( $image, '200d' ) === FALSE or $image === '1f441-fe0f-200d-1f5e8-fe0f' )
|
||||
{
|
||||
$image = str_replace( '-fe0f', '', $image );
|
||||
}
|
||||
if ( in_array( $image, array( '0031-20e3', '0030-20e3', '0032-20e3', '0034-20e3', '0035-20e3', '0036-20e3', '0037-20e3', '0038-20e3', '0033-20e3', '0039-20e3', '0023-20e3', '002a-20e3', '00a9', '00ae' ) ) )
|
||||
{
|
||||
$image = str_replace( '00', '', $image );
|
||||
}
|
||||
|
||||
return '<img src="https://twemoji.maxcdn.com/2/72x72/' . $image . '.png" class="ipsEmoji" alt="' . $matches[1] . '">';
|
||||
}
|
||||
else
|
||||
|
||||
$image = implode( '-', $chunks );
|
||||
|
||||
if ( \strstr( $image, '200d' ) === FALSE or $image === '1f441-fe0f-200d-1f5e8-fe0f' )
|
||||
{
|
||||
return '<img src="https://cdn.jsdelivr.net/emojione/assets/3.1/png/64/' . str_replace( array( '-200d', '-fe0f' ), '', implode( '-', $chunks ) ) . '.png" class="ipsEmoji" alt="' . $matches[1] . '">';
|
||||
$image = str_replace( '-fe0f', '', $image );
|
||||
}
|
||||
if ( \in_array( $image, array( '0031-20e3', '0030-20e3', '0032-20e3', '0034-20e3', '0035-20e3', '0036-20e3', '0037-20e3', '0038-20e3', '0033-20e3', '0039-20e3', '0023-20e3', '002a-20e3', '00a9', '00ae' ) ) )
|
||||
{
|
||||
$image = str_replace( '00', '', $image );
|
||||
}
|
||||
|
||||
return '<img src="https://twemoji.maxcdn.com/2/72x72/' . $image . '.png" class="ipsEmoji" alt="' . $matches[1] . '">';
|
||||
}, $output );
|
||||
}
|
||||
return $output;
|
||||
@@ -1104,7 +1260,7 @@ class _Output
|
||||
{
|
||||
$javascriptObjects = ( isset( \IPS\Data\Store::i()->javascript_map ) ) ? \IPS\Data\Store::i()->javascript_map : array();
|
||||
|
||||
if ( ! is_array( $javascriptObjects ) OR ! count( $javascriptObjects ) OR ! isset( $javascriptObjects[ $app ] ) )
|
||||
if ( ! \is_array( $javascriptObjects ) OR ! \count( $javascriptObjects ) OR ! isset( $javascriptObjects[ $app ] ) )
|
||||
{
|
||||
/* Map is broken or missing, recompile all JS */
|
||||
\IPS\Output\Javascript::compile( $app );
|
||||
@@ -1132,6 +1288,8 @@ class _Output
|
||||
{
|
||||
$this->title .= ' - ' . \IPS\Settings::i()->board_name;
|
||||
}
|
||||
|
||||
$this->defaultPageTitle = $this->title;
|
||||
|
||||
/* Add Admin-specified ones */
|
||||
if( !$this->metaTagsUrl )
|
||||
@@ -1148,8 +1306,11 @@ class _Output
|
||||
\IPS\Data\Store::i()->metaTags = $rows;
|
||||
}
|
||||
|
||||
if( is_array( $rows ) )
|
||||
if( \is_array( $rows ) )
|
||||
{
|
||||
/* We duplicate these so we can know what is generated automatically for the live meta tag editor */
|
||||
$this->autoMetaTags = $this->metaTags;
|
||||
|
||||
foreach ( $rows as $row )
|
||||
{
|
||||
if( \strpos( $row['meta_url'], '*' ) !== FALSE )
|
||||
@@ -1158,11 +1319,20 @@ class _Output
|
||||
{
|
||||
$_tags = json_decode( $row['meta_tags'], TRUE );
|
||||
|
||||
if( is_array( $_tags ) )
|
||||
if( \is_array( $_tags ) )
|
||||
{
|
||||
foreach( $_tags as $_tagName => $_tagContent )
|
||||
{
|
||||
$this->metaTags[ $_tagName ] = $_tagContent;
|
||||
if( $_tagContent === NULL )
|
||||
{
|
||||
unset( $this->metaTags[ $_tagName ] );
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->metaTags[ $_tagName ] = $_tagContent;
|
||||
}
|
||||
|
||||
$this->customMetaTags[ $_tagName ] = $_tagContent;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1176,15 +1346,25 @@ class _Output
|
||||
}
|
||||
else
|
||||
{
|
||||
if( trim( $row['meta_url'], '/' ) == trim( $this->metaTagsUrl, '/' ) )
|
||||
$rootPath = \IPS\Http\Url::external( \IPS\Settings::i()->base_url )->data['path'];
|
||||
if( trim( $row['meta_url'], '/' ) == trim( $this->metaTagsUrl, '/' ) and ( ( $row['meta_url'] == '/' and \IPS\Request::i()->url()->data['path'] == $rootPath ) or $row['meta_url'] !== '/' ) )
|
||||
{
|
||||
$_tags = json_decode( $row['meta_tags'], TRUE );
|
||||
|
||||
if ( is_array( $_tags ) )
|
||||
if ( \is_array( $_tags ) )
|
||||
{
|
||||
foreach( $_tags as $_tagName => $_tagContent )
|
||||
{
|
||||
$this->metaTags[ $_tagName ] = $_tagContent;
|
||||
if( $_tagContent === NULL )
|
||||
{
|
||||
unset( $this->metaTags[ $_tagName ] );
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->metaTags[ $_tagName ] = $_tagContent;
|
||||
}
|
||||
|
||||
$this->customMetaTags[ $_tagName ] = $_tagContent;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1204,7 +1384,7 @@ class _Output
|
||||
|
||||
foreach( $this->metaTags as $name => $value )
|
||||
{
|
||||
if ( ! is_array( $value ) )
|
||||
if ( ! \is_array( $value ) )
|
||||
{
|
||||
$value = array( $value );
|
||||
}
|
||||
@@ -1232,7 +1412,10 @@ class _Output
|
||||
$jsonLd = array(
|
||||
'website' => array(
|
||||
'@context' => "http://www.schema.org",
|
||||
'publisher' => \IPS\Settings::i()->base_url . '#organization',
|
||||
'@type' => "WebSite",
|
||||
'@id' => \IPS\Settings::i()->base_url . '#website',
|
||||
'mainEntityOfPage' => \IPS\Settings::i()->base_url,
|
||||
'name' => \IPS\Settings::i()->board_name,
|
||||
'url' => \IPS\Settings::i()->base_url,
|
||||
'potentialAction' => array(
|
||||
@@ -1245,17 +1428,23 @@ class _Output
|
||||
'organization' => array(
|
||||
'@context' => "http://www.schema.org",
|
||||
'@type' => "Organization",
|
||||
'@id' => \IPS\Settings::i()->base_url . '#organization',
|
||||
'mainEntityOfPage' => \IPS\Settings::i()->base_url,
|
||||
'name' => \IPS\Settings::i()->board_name,
|
||||
'url' => \IPS\Settings::i()->base_url,
|
||||
)
|
||||
);
|
||||
|
||||
if( \IPS\Theme::i()->logo_front )
|
||||
if( $logo = \IPS\Theme::i()->logo_front )
|
||||
{
|
||||
$jsonLd['organization']['logo'] = (string) \IPS\Theme::i()->logo_front;
|
||||
$jsonLd['organization']['logo'] = array(
|
||||
'@type' => 'ImageObject',
|
||||
'@id' => \IPS\Settings::i()->base_url . '#logo',
|
||||
'url' => (string) $logo
|
||||
);
|
||||
}
|
||||
|
||||
if( \IPS\Settings::i()->site_social_profiles AND $links = json_decode( \IPS\Settings::i()->site_social_profiles, TRUE ) AND count( $links ) )
|
||||
if( \IPS\Settings::i()->site_social_profiles AND $links = json_decode( \IPS\Settings::i()->site_social_profiles, TRUE ) AND \count( $links ) )
|
||||
{
|
||||
if( !isset( $jsonLd['organization']['sameAs'] ) )
|
||||
{
|
||||
@@ -1270,14 +1459,17 @@ class _Output
|
||||
|
||||
if( \IPS\Settings::i()->site_address AND $address = \IPS\GeoLocation::buildFromJson( \IPS\Settings::i()->site_address ) )
|
||||
{
|
||||
$jsonLd['organization']['address'] = array(
|
||||
'@type' => 'PostalAddress',
|
||||
'streetAddress' => implode( ', ', $address->addressLines ),
|
||||
'addressLocality' => $address->city,
|
||||
'addressRegion' => $address->region,
|
||||
'postalCode' => $address->postalCode,
|
||||
'addressCountry' => $address->country,
|
||||
);
|
||||
if ( ! empty( $address->country ) )
|
||||
{
|
||||
$jsonLd['organization']['address'] = array(
|
||||
'@type' => 'PostalAddress',
|
||||
'streetAddress' => implode( ', ', $address->addressLines ),
|
||||
'addressLocality' => $address->city,
|
||||
'addressRegion' => $address->region,
|
||||
'postalCode' => $address->postalCode,
|
||||
'addressCountry' => $address->country,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
foreach( \IPS\Lang::getEnabledLanguages() as $language )
|
||||
@@ -1290,21 +1482,17 @@ class _Output
|
||||
}
|
||||
|
||||
/* Add breadcrumbs */
|
||||
if( count( $this->breadcrumb ) )
|
||||
if( \count( $this->breadcrumb ) )
|
||||
{
|
||||
$jsonLd['breadcrumbs'] = array(
|
||||
'@context' => "http://schema.org",
|
||||
'@type' => "BreadcrumbList",
|
||||
'itemListElement' => array(),
|
||||
);
|
||||
|
||||
$position = 1;
|
||||
$elements = [];
|
||||
|
||||
foreach( $this->breadcrumb as $breadcrumb )
|
||||
{
|
||||
if( $breadcrumb[0] )
|
||||
{
|
||||
$jsonLd['breadcrumbs']['itemListElement'][] = array(
|
||||
$elements[] = array(
|
||||
'@type' => "ListItem",
|
||||
'position' => $position,
|
||||
'item' => array(
|
||||
@@ -1316,6 +1504,15 @@ class _Output
|
||||
$position++;
|
||||
}
|
||||
}
|
||||
|
||||
if( \count( $elements ) )
|
||||
{
|
||||
$jsonLd['breadcrumbs'] = array(
|
||||
'@context' => "http://schema.org",
|
||||
'@type' => "BreadcrumbList",
|
||||
'itemListElement' => $elements,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if( \IPS\Member::loggedIn()->canUseContactUs() )
|
||||
@@ -1329,11 +1526,12 @@ class _Output
|
||||
|
||||
$this->jsonLd = array_merge( $this->jsonLd, $jsonLd );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* License Warning
|
||||
*
|
||||
* @return string|NULL "none" = no license key. "expired" = license key expired. NULL = no error
|
||||
* @return string|NULL "none" = no license key. "expired" = license key expired. NULL = no error
|
||||
* @deprecated to be removed in 4.5
|
||||
*/
|
||||
public function licenseKeyWarning()
|
||||
{
|
||||
@@ -1361,6 +1559,7 @@ class _Output
|
||||
* Days remaining before license expires
|
||||
*
|
||||
* @return int|NULL NULL or the number of days remaining
|
||||
* @deprecated to be removed in 4.5
|
||||
*/
|
||||
public function licenseKeyDaysRemaining()
|
||||
{
|
||||
@@ -1381,7 +1580,7 @@ class _Output
|
||||
|
||||
return $daysLeft;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Global search menu options
|
||||
*/
|
||||
@@ -1443,4 +1642,41 @@ class _Output
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get any inline message
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function getInlineMessage()
|
||||
{
|
||||
if ( ( \IPS\Dispatcher::hasInstance() and \IPS\Dispatcher::i()->controllerLocation == 'front' ) and ( \IPS\Session\Front::loggedIn() or isset( \IPS\Request::i()->cookie['noCache'] ) ) ) # Don't attempt to initiate a full member object here
|
||||
{
|
||||
return ( ( isset( $_SESSION['inlineMessage'] ) and ! empty( $_SESSION['inlineMessage'] ) ) ? $_SESSION['inlineMessage'] : NULL );
|
||||
}
|
||||
else if ( isset( \IPS\Request::i()->cookie['inlineMessage'] ) )
|
||||
{
|
||||
return \IPS\Request::i()->cookie['inlineMessage'];
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an inline message
|
||||
*
|
||||
* @param string $message The message
|
||||
* @return void
|
||||
*/
|
||||
protected static function setInlineMessage( $message=NULL )
|
||||
{
|
||||
if ( ( \IPS\Dispatcher::hasInstance() and \IPS\Dispatcher::i()->controllerLocation == 'front' ) and ( \IPS\Session\Front::loggedIn() or isset( \IPS\Request::i()->cookie['noCache'] ) ) ) # Don't attempt to initiate a full member object here
|
||||
{
|
||||
$_SESSION['inlineMessage'] = $message;
|
||||
}
|
||||
else
|
||||
{
|
||||
\IPS\Request::i()->setCookie( 'inlineMessage', $message );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace IPS\Output\Plugin;
|
||||
|
||||
/* 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;
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace IPS\Output\Plugin;
|
||||
|
||||
/* 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;
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace IPS\Output\Plugin;
|
||||
|
||||
/* 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;
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace IPS\Output\Plugin;
|
||||
|
||||
/* 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;
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace IPS\Output\Plugin;
|
||||
|
||||
/* 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;
|
||||
@@ -37,19 +37,27 @@ class _File
|
||||
public static function runPlugin( $data, $options )
|
||||
{
|
||||
$extension = ( isset( $options['extension'] ) ? $options['extension'] : 'core_Attachment' );
|
||||
|
||||
$scheme = ( isset( $options['scheme'] ) ? $options['scheme'] : NULL );
|
||||
$schemeString = '';
|
||||
|
||||
if ( $scheme )
|
||||
{
|
||||
$fullScheme = ( \IPS\Request::i()->isSecure() ) ? 'https' : 'http';
|
||||
$schemeString = ( $scheme == 'full' ) ? '->setScheme("' . $fullScheme .'")' : '->setScheme(NULL)';
|
||||
}
|
||||
|
||||
if ( $data instanceof \IPS\File )
|
||||
{
|
||||
return "(string) " . $data . "->url";
|
||||
return "(string) " . $data . "->url" . $schemeString;
|
||||
}
|
||||
|
||||
if ( mb_substr( $extension, 0, 1 ) === '$' )
|
||||
{
|
||||
return "\\IPS\\File::get( {$extension}, " . $data . " )->url";
|
||||
return "\\IPS\\File::get( {$extension}, " . $data . " )->url" . $schemeString;
|
||||
}
|
||||
else
|
||||
{
|
||||
return "\\IPS\\File::get( \"{$extension}\", " . $data . " )->url";
|
||||
return "\\IPS\\File::get( \"{$extension}\", " . $data . " )->url" . $schemeString;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace IPS\Output\Plugin;
|
||||
|
||||
/* 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;
|
||||
@@ -50,7 +50,7 @@ class _Filesize
|
||||
*/
|
||||
public static function humanReadableFilesize( $sizeInBytes, $decimal=FALSE, $json=FALSE, $get=FALSE )
|
||||
{
|
||||
$sizeInBytes = floatval( $sizeInBytes );
|
||||
$sizeInBytes = \floatval( $sizeInBytes );
|
||||
|
||||
foreach ( array( 'Y' => 80, 'Z' => 70, 'E' => 60, 'P' => 50, 'T' => 40, 'G' => 30, 'M' => 20, 'k' => 10 ) as $sig => $pow )
|
||||
{
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Template Plugin - Font size
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 9 Apr 2020
|
||||
*/
|
||||
|
||||
namespace IPS\Output\Plugin;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Template Plugin - Font size
|
||||
*/
|
||||
class _Fontsize
|
||||
{
|
||||
/**
|
||||
* @brief Can be used when compiling CSS
|
||||
*/
|
||||
public static $canBeUsedInCss = TRUE;
|
||||
|
||||
/**
|
||||
* Run the plug-in
|
||||
*
|
||||
* @param string $data The initial data from the tag
|
||||
* @param array $options Array of options
|
||||
* @return string Code to eval
|
||||
*/
|
||||
public static function runPlugin( $data, $options )
|
||||
{
|
||||
$number = 14;
|
||||
|
||||
// Is this a theme setting or a number?
|
||||
if( preg_match('/^[0-9]+?$/', $data ) )
|
||||
{
|
||||
$number = \intval( $data );
|
||||
}
|
||||
else if ( isset( \IPS\Theme::i()->settings[ 'font_' . $data ] ) )
|
||||
{
|
||||
$number = \IPS\Theme::i()->settings[ 'font_' . $data ];
|
||||
}
|
||||
|
||||
$scale = isset( \IPS\Theme::i()->settings['font_size'] ) ? \intval( \IPS\Theme::i()->settings['font_size'] ) : 100;
|
||||
|
||||
// Should we be scaling?
|
||||
if( $scale !== 100 && ( !isset( $options['scale'] ) || $options['scale'] !== false ) )
|
||||
{
|
||||
$number = round( $scale * ( $number / 100 ), 1 );
|
||||
}
|
||||
|
||||
// e.g. 100 * 0.14 = 14px
|
||||
return '"' . $number . 'px"';
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace IPS\Output\Plugin;
|
||||
|
||||
/* 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;
|
||||
@@ -43,6 +43,13 @@ class _Hextorgb
|
||||
return '\IPS\Output\Plugin\Hextorgb::convertToRGB(' . $data . ', ' . ( isset( $options['opacity'] ) ? $options['opacity'] : '1' ) . ')';
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Hex value to RGB
|
||||
*
|
||||
* @param string $data Hex color code
|
||||
* @param float $opacity Opacity (0-1)
|
||||
* @return string
|
||||
*/
|
||||
public static function convertToRGB( $data, $opacity )
|
||||
{
|
||||
$output = array();
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace IPS\Output\Plugin;
|
||||
|
||||
/* 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;
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace IPS\Output\Plugin;
|
||||
|
||||
/* 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;
|
||||
@@ -59,13 +59,13 @@ class _Lang
|
||||
|
||||
if ( isset( $options['list'] ) )
|
||||
{
|
||||
$return['pre'] .= '$sprintf = \IPS\Member::loggedIn()->language()->formatList(' . $options['list'] . ');';
|
||||
$return['pre'] .= '$sprintf = array( \IPS\Member::loggedIn()->language()->formatList(' . $options['list'] . ') );';
|
||||
$params[]= '\'sprintf\' => $sprintf';
|
||||
}
|
||||
|
||||
if ( isset( $options['htmllist'] ) )
|
||||
{
|
||||
$return['pre'] .= '$htmlsprintf = \IPS\Member::loggedIn()->language()->formatList(' . $options['htmllist'] . ');';
|
||||
$return['pre'] .= '$htmlsprintf = array( \IPS\Member::loggedIn()->language()->formatList(' . $options['htmllist'] . ') );';
|
||||
$params[]= '\'htmlsprintf\' => $htmlsprintf';
|
||||
}
|
||||
|
||||
@@ -85,6 +85,11 @@ class _Lang
|
||||
{
|
||||
$return['pre'] .= '$pluralize = array( ' . $options['pluralize'] . ' ); ';
|
||||
$params[] = '\'pluralize\' => $pluralize';
|
||||
|
||||
if ( isset( $options['format'] ) )
|
||||
{
|
||||
$params[] = '\'format\' => ' . "'" . $options['format'] . "'";
|
||||
}
|
||||
}
|
||||
|
||||
if( isset( $options['wordbreak'] ) )
|
||||
@@ -104,7 +109,7 @@ class _Lang
|
||||
|
||||
$vle = 'TRUE';
|
||||
|
||||
if( count( $params ) )
|
||||
if( \count( $params ) )
|
||||
{
|
||||
$vle = 'FALSE';
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace IPS\Output\Plugin;
|
||||
|
||||
/* 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;
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace IPS\Output\Plugin;
|
||||
|
||||
/* 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;
|
||||
@@ -36,6 +36,15 @@ class _Number
|
||||
*/
|
||||
public static function runPlugin( $data, $options )
|
||||
{
|
||||
if ( isset( $options['format'] ) and $options['format'] === 'short' )
|
||||
{
|
||||
return "\IPS\Member::loggedIn()->language()->formatNumberShort( {$data} )";
|
||||
}
|
||||
elseif ( isset( $options['decimals'] ) )
|
||||
{
|
||||
return "\IPS\Member::loggedIn()->language()->formatNumber( {$data}, {$options['decimals']} )";
|
||||
}
|
||||
|
||||
return "\IPS\Member::loggedIn()->language()->formatNumber( {$data} )";
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace IPS\Output\Plugin;
|
||||
|
||||
/* 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;
|
||||
@@ -33,7 +33,7 @@ class _Prefix
|
||||
* @brief The styles this plugin will prefix
|
||||
*/
|
||||
protected static $supportedStyles = array(
|
||||
'transition', 'transform', 'animation', 'animation-name', 'animation-duration', 'animation-fill-mode', 'animation-timing-function', 'user-select', 'box-sizing', 'background-size'
|
||||
'user-select'
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -51,7 +51,7 @@ class _Prefix
|
||||
*/
|
||||
public static function runPlugin( $data, $options )
|
||||
{
|
||||
if ( in_array( $data, static::$supportedStyles ) )
|
||||
if ( \in_array( $data, static::$supportedStyles ) )
|
||||
{
|
||||
$output = array();
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace IPS\Output\Plugin;
|
||||
|
||||
/* 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;
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace IPS\Output\Plugin;
|
||||
|
||||
/* 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;
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace IPS\Output\Plugin;
|
||||
|
||||
/* 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;
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace IPS\Output\Plugin;
|
||||
|
||||
/* 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;
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace IPS\Output\Plugin;
|
||||
|
||||
/* 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;
|
||||
@@ -36,7 +36,7 @@ class _Template
|
||||
*/
|
||||
public static function runPlugin( $data, $options, $functionName=NULL, $calledClass='IPS\Theme' )
|
||||
{
|
||||
$params = ( in_array( 'params', array_keys( $options ) ) ) ? $options['params'] : '';
|
||||
$params = ( \in_array( 'params', array_keys( $options ) ) ) ? $options['params'] : '';
|
||||
if( mb_strpos( $data, '$' ) === 0 )
|
||||
{
|
||||
$data = '{' . $data . '}';
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace IPS\Output\Plugin;
|
||||
|
||||
/* 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;
|
||||
@@ -33,6 +33,7 @@ class _Theme
|
||||
* @param string $data The initial data from the tag
|
||||
* @param array $options Array of options
|
||||
* @return string Code to eval
|
||||
* @note Using this plugin to call the sharer logos or favicon is deprecated and will be removed in a future version
|
||||
*/
|
||||
public static function runPlugin( $data, $options )
|
||||
{
|
||||
@@ -47,6 +48,9 @@ class _Theme
|
||||
case 'logo_favicon':
|
||||
return "\IPS\Theme::i()->logo_favicon";
|
||||
break;
|
||||
case 'logo_height':
|
||||
return ( isset( \IPS\Theme::i()->logo['front']['height'] ) ) ? '\IPS\Theme::i()->logo[\'front\'][\'height\']' : 100;
|
||||
break;
|
||||
default:
|
||||
return ( isset( \IPS\Theme::i()->settings[ $data ] ) ) ? "\IPS\Theme::i()->settings['{$data}']" : '';
|
||||
break;
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace IPS\Output\Plugin;
|
||||
|
||||
/* 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;
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace IPS\Output\Plugin;
|
||||
|
||||
/* 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;
|
||||
@@ -43,7 +43,7 @@ class _Url
|
||||
$csrf = ' . "&csrfKey=" . \IPS\Session::i()->csrfKey';
|
||||
}
|
||||
|
||||
$location = ( in_array( 'base', array_keys( $options ) ) ) ? '"' . $options['base'] . '"' : 'null';
|
||||
$location = ( \in_array( 'base', array_keys( $options ) ) ) ? '"' . $options['base'] . '"' : 'null';
|
||||
|
||||
if ( !isset( $options['seoTemplate'] ) )
|
||||
{
|
||||
@@ -70,11 +70,28 @@ class _Url
|
||||
$fragment = "->setFragment(\"{$options['fragment']}\")";
|
||||
}
|
||||
|
||||
$ref = '';
|
||||
if ( isset( $options['ref'] ) )
|
||||
{
|
||||
/* Is this a variable or other dynamic thing? */
|
||||
if ( \in_array( \substr( $options['ref'], 0, 1 ), array( '(', '$', '\\' ) ) )
|
||||
{
|
||||
$ref = "->addRef(";
|
||||
$ref .= $options['ref'];
|
||||
$ref .= ")";
|
||||
}
|
||||
else
|
||||
{
|
||||
/* It's not - just a normal string */
|
||||
$ref = "->addRef(\"{$options['ref']}\")";
|
||||
}
|
||||
}
|
||||
|
||||
$_data = mb_substr( $data, 0, 1 ) == '$' ? $data : "\"$data\"";
|
||||
|
||||
if ( isset( $options['plain'] ) )
|
||||
{
|
||||
$url = "\IPS\Http\Url::internal( {$_data}{$csrf}, {$location}, \"{$options['seoTemplate']}\", {$options['seoTitles']}, {$options['protocol']} )".$fragment;
|
||||
$url = "\IPS\Http\Url::internal( {$_data}{$csrf}, {$location}, \"{$options['seoTemplate']}\", {$options['seoTitles']}, {$options['protocol']} )".$fragment.$ref;
|
||||
}
|
||||
elseif ( isset( $options['ips'] ) )
|
||||
{
|
||||
@@ -82,7 +99,7 @@ class _Url
|
||||
}
|
||||
else
|
||||
{
|
||||
$url = "str_replace( '&', '&', \IPS\Http\Url::internal( {$_data}{$csrf}, {$location}, \"{$options['seoTemplate']}\", {$options['seoTitles']}, {$options['protocol']} ){$fragment} )";
|
||||
$url = "htmlspecialchars( \IPS\Http\Url::internal( {$_data}{$csrf}, {$location}, \"{$options['seoTemplate']}\", {$options['seoTitles']}, {$options['protocol']} ){$fragment}{$ref}, ENT_QUOTES | ENT_DISALLOWED, 'UTF-8', TRUE )";
|
||||
}
|
||||
|
||||
if( isset( $options['noprotocol'] ) AND $options['noprotocol'] )
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace IPS\Output\Plugin;
|
||||
|
||||
/* 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;
|
||||
@@ -30,13 +30,13 @@ class _Wordbreak
|
||||
/**
|
||||
* Run the plug-in
|
||||
*
|
||||
* @deprecated \IPS\Lang::wordbreak() is deprecated but we left this here to prevent breaking anything
|
||||
* @deprecated This will be removed in a future release.
|
||||
* @param string $data The initial data from the tag
|
||||
* @param array $options Array of options
|
||||
* @return string Code to eval
|
||||
*/
|
||||
public static function runPlugin( $data, $options )
|
||||
{
|
||||
return "\IPS\Lang::wordbreak( htmlspecialchars( {$data}, ENT_QUOTES | ENT_DISALLOWED, 'UTF-8', FALSE ) )";
|
||||
return "htmlspecialchars( {$data}, ENT_QUOTES | ENT_DISALLOWED, 'UTF-8', FALSE )";
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace IPS\Output;
|
||||
|
||||
/* 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;
|
||||
|
||||
Reference in new issue
Block a user