Version 5.0.0 beta 1

This commit is contained in:
Neo committed 2025-12-19 16:27:35 -08:00
1 parent 25ddeb65d6
commit 15c7beabc5
6736 files changed
+627902 -497943

No files matched your search

-123
View File
@@ -1,123 +0,0 @@
<?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 ( \class_exists( $classname ) and $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;
}
}
}
-117
View File
@@ -1,117 +0,0 @@
<?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
*
* @param boolean $truncate Truncate the table
* @return void
*/
public function deleteExpired( $truncate=FALSE )
{
try
{
\IPS\Db::i()->delete( 'core_output_cache', ( ! $truncate ? array( 'cache_expire<?', time() ) : NULL ) );
}
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 ){}
}
}
-208
View File
@@ -1,208 +0,0 @@
<?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
*
* @param boolean $truncate Truncate the table
* @return void
*/
public function deleteExpired( $truncate=FALSE )
{
/* 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 );
}
}
-322
View File
@@ -1,322 +0,0 @@
<?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 );
}
}
File diff suppressed because it is too large. Load diff
+654 -596
View File
File diff suppressed because it is too large. Load diff
+9 -6
View File
@@ -11,25 +11,28 @@
namespace IPS\Output\Plugin;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Template Plugin - Address formatting
*/
class _Address
class Address
{
/**
* Run the plug-in
*
* @param string $data The initial data from the tag
* @param array $options Array of options
* @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 )
public static function runPlugin( string $data, array $options ): string
{
if( mb_substr( $data, 0, 1 ) === '\\' OR mb_substr( $data, 0, 1 ) === '$' )
{
+8 -5
View File
@@ -11,21 +11,24 @@
namespace IPS\Output\Plugin;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Template Plugin - Advertisement
*/
class _Advertisement
class Advertisement
{
/**
* @brief Can be used when compiling CSS
*/
public static $canBeUsedInCss = FALSE;
public static bool $canBeUsedInCss = FALSE;
/**
* Run the plug-in
@@ -34,7 +37,7 @@ class _Advertisement
* @param array $options Array of options
* @return string Code to eval
*/
public static function runPlugin( $data, $options )
public static function runPlugin( string $data, array $options ): string
{
return "\IPS\core\Advertisement::loadByLocation( '{$data}' )";
}
+54
View File
@@ -0,0 +1,54 @@
<?php
/**
* @brief Custom Template Plugin - Template
* @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 28 April 2023
*/
namespace IPS\Output\Plugin;
/* To prevent PHP errors (extending class does not exist) revealing path */
use function array_keys;
use function defined;
use function in_array;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Template Plugin - Template
*/
class Customtemplate
{
/**
* @brief Can be used when compiling CSS
*/
public static bool $canBeUsedInCss = FALSE;
/**
* Run the plug-in
*
* @param string $data The initial data from the tag
* @param array $options Array of options
* @return array Code to eval
*/
public static function runPlugin( string $data, array $options ): array
{
$params = ( in_array( 'params', array_keys( $options ) ) ) ? $options['params'] : '';
$hook = ( in_array( 'hook', array_keys( $options ) ) ) ? $options['hook'] : '';
if( mb_strpos( $data, '$' ) === 0 )
{
$data = '{' . $data . '}';
}
return [ 'return' => "\\IPS\\Theme\\CustomTemplate::getCustomTemplatesForHookPoint( \"{$data}\", \"{$hook}\", [ {$params} ] )" ];
}
}
+9 -6
View File
@@ -11,30 +11,33 @@
namespace IPS\Output\Plugin;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Template Plugin - DateTime
*/
class _Datetime
class Datetime
{
/**
* @brief Can be used when compiling CSS
*/
public static $canBeUsedInCss = FALSE;
public static bool $canBeUsedInCss = FALSE;
/**
* Run the plug-in
*
* @param string $data The initial data from the tag
* @param array $options Array of options
* @return string Code to eval
* @return array Code to eval
*/
public static function runPlugin( $data, $options )
public static function runPlugin( string $data, array $options ): array
{
$return = array();
$return['pre'] = '$val = ( ' . $data . ' instanceof \IPS\DateTime ) ? ' . $data . ' : \IPS\DateTime::ts( ' . $data . ' );';
+11 -7
View File
@@ -11,21 +11,25 @@
namespace IPS\Output\Plugin;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\Theme;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Template Plugin - Expression
*/
class _Expression
class Expression
{
/**
* @brief Can be used when compiling CSS
*/
public static $canBeUsedInCss = TRUE;
public static bool $canBeUsedInCss = TRUE;
/**
* Run the plug-in
@@ -34,15 +38,15 @@ class _Expression
* @param array $options Array of options
* @return string Code to eval
*/
public static function runPlugin( $data, $options )
public static function runPlugin( string $data, array $options ): string
{
if( isset( $options['raw'] ) AND $options['raw'] )
{
return \IPS\Theme::expandShortcuts( $data );
return Theme::expandShortcuts( $data );
}
else
{
return 'htmlspecialchars( ' . \IPS\Theme::expandShortcuts( $data ) . ', ENT_QUOTES | ENT_DISALLOWED, \'UTF-8\', FALSE )';
return '\IPS\Theme\Template::htmlspecialchars( ' . Theme::expandShortcuts( $data ) . ', ENT_QUOTES | ENT_DISALLOWED, \'UTF-8\', FALSE )';
}
}
}
+16 -10
View File
@@ -11,21 +11,26 @@
namespace IPS\Output\Plugin;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\File as SystemFile;
use IPS\Request;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Template Plugin - File
*/
class _File
class File
{
/**
* @brief Can be used when compiling CSS
*/
public static $canBeUsedInCss = TRUE;
public static bool $canBeUsedInCss = TRUE;
/**
* Run the plug-in
@@ -34,16 +39,16 @@ class _File
* @param array $options Array of options
* @return string Code to eval
*/
public static function runPlugin( $data, $options )
public static function runPlugin( string $data, array $options ): string
{
$extension = ( isset( $options['extension'] ) ? $options['extension'] : 'core_Attachment' );
$scheme = ( isset( $options['scheme'] ) ? $options['scheme'] : NULL );
$cacheBust = ( isset( $options['cb'] ) ? $options['cb'] : NULL );
$extension = ( $options['extension'] ?? 'core_Attachment' );
$scheme = ( $options['scheme'] ?? NULL );
$cacheBust = ( $options['cb'] ?? NULL );
$schemeString = $cbString = '';
if ( $scheme )
{
$fullScheme = ( \IPS\Request::i()->isSecure() ) ? 'https' : 'http';
$fullScheme = ( Request::i()->isSecure() ) ? 'https' : 'http';
$schemeString = ( $scheme == 'full' ) ? '->setScheme("' . $fullScheme .'")' : '->setScheme(NULL)';
}
@@ -51,7 +56,8 @@ class _File
{
$cbString = "->setQueryString( 'v', " . $cacheBust . ")";
}
if ( $data instanceof \IPS\File )
if ( $data instanceof SystemFile )
{
return "(string) " . $data . "->url" . $schemeString . $cbString;
}
+18 -13
View File
@@ -11,30 +11,35 @@
namespace IPS\Output\Plugin;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\Member;
use function defined;
use function floatval;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Template Plugin - Filesize
*/
class _Filesize
class Filesize
{
/**
* @brief Can be used when compiling CSS
*/
public static $canBeUsedInCss = FALSE;
public static bool $canBeUsedInCss = FALSE;
/**
* Run the plug-in
*
* @param string $data The initial data from the tag
* @param array $options Array of options
* @return array array( 'pre' => Code to eval before 'return', 'return' => Code to eval to return desired value )
* @return string array( 'pre' => Code to eval before 'return', 'return' => Code to eval to return desired value )
*/
public static function runPlugin( $data, $options )
public static function runPlugin( string $data, array $options ): string
{
return '\IPS\Output\Plugin\Filesize::humanReadableFilesize( ' . $data . ( ( isset( $options['decimal'] ) and $options['decimal'] ) ? ', TRUE' : '' ) . ' )';
}
@@ -42,15 +47,15 @@ class _Filesize
/**
* Get human readable filesize (to 2SF)
*
* @param int $sizeInBytes Size in bytes
* @param int|null $sizeInBytes Size in bytes
* @param bool $decimal If TRUE, will calculate based on decimal figures rather than binary
* @param bool $json If TRUE, will format for json
* @param bool $get If TRUE, will get the language string rather than add to stack
* @return string
*/
public static function humanReadableFilesize( $sizeInBytes, $decimal=FALSE, $json=FALSE, $get=FALSE )
public static function humanReadableFilesize( ?int $sizeInBytes, bool $decimal=FALSE, bool $json=FALSE, bool $get=FALSE ) : string
{
$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 )
{
@@ -59,7 +64,7 @@ class _Filesize
{
if ( $get )
{
return sprintf( \IPS\Member::loggedIn()->language()->get( 'filesize_' . $sig ), round( ( $sizeInBytes / $raised ), 2 ) );
return sprintf( Member::loggedIn()->language()->get( 'filesize_' . $sig ), round( ( $sizeInBytes / $raised ), 2 ) );
}
else
{
@@ -70,14 +75,14 @@ class _Filesize
$format['json'] = TRUE;
}
return \IPS\Member::loggedIn()->language()->addToStack( 'filesize_' . $sig, FALSE, $format );
return Member::loggedIn()->language()->addToStack( 'filesize_' . $sig, FALSE, $format );
}
}
}
if ( $get )
{
return sprintf( \IPS\Member::loggedIn()->language()->get( 'filesize_b' ), $sizeInBytes );
return sprintf( Member::loggedIn()->language()->get( 'filesize_b' ), $sizeInBytes );
}
else
{
@@ -88,7 +93,7 @@ class _Filesize
$format['json'] = TRUE;
}
return \IPS\Member::loggedIn()->language()->addToStack( 'filesize_b', FALSE, $format );
return Member::loggedIn()->language()->addToStack( 'filesize_b', FALSE, $format );
}
}
}
+28 -9
View File
@@ -11,21 +11,26 @@
namespace IPS\Output\Plugin;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use function defined;
use function intval;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Template Plugin - Font size
* @note this is still required as the ACP relies on this
*/
class _Fontsize
class Fontsize
{
/**
* @brief Can be used when compiling CSS
*/
public static $canBeUsedInCss = TRUE;
public static bool $canBeUsedInCss = TRUE;
/**
* Run the plug-in
@@ -34,21 +39,35 @@ class _Fontsize
* @param array $options Array of options
* @return string Code to eval
*/
public static function runPlugin( $data, $options )
public static function runPlugin( string $data, array $options ): string
{
$number = 14;
/* Create the array for the ACP based on v4 font-sizes @todo review this! */
$sizes = [
'font_base' => 16,
'font_large' => 18,
'font_medium' => 14,
'font_small' => 13,
'font_x_large' => 20,
'font_x_small' => 12,
'font_2x_large' => 24,
'font_3x_large' => 30,
'font_4x_large' => 36,
'font_size' => 100,
];
// Is this a theme setting or a number?
if( preg_match('/^[0-9]+?$/', $data ) )
{
$number = \intval( $data );
$number = intval( $data );
}
else if ( isset( \IPS\Theme::i()->settings[ 'font_' . $data ] ) )
else if ( isset( $sizes[ 'font_' . $data ] ) )
{
$number = \IPS\Theme::i()->settings[ 'font_' . $data ];
$number = $sizes[ 'font_' . $data ];
}
$scale = isset( \IPS\Theme::i()->settings['font_size'] ) ? \intval( \IPS\Theme::i()->settings['font_size'] ) : 100;
$scale = $sizes['font_size'];
// Should we be scaling?
if( $scale !== 100 && ( !isset( $options['scale'] ) || $options['scale'] !== false ) )
+33 -25
View File
@@ -11,21 +11,29 @@
namespace IPS\Output\Plugin;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\Settings;
use IPS\Theme;
use function defined;
use function str_replace;
use function strlen;
use function substr;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Template Plugin - hextorgb
*/
class _Hextorgb
class Hextorgb
{
/**
* @brief Can be used when compiling CSS
*/
public static $canBeUsedInCss = TRUE;
public static bool $canBeUsedInCss = TRUE;
/**
* Run the plug-in. Take #ffffff and returns 255,255,255, for example
@@ -34,7 +42,7 @@ class _Hextorgb
* @param array $options Array of options
* @return string Code to eval
*/
public static function runPlugin( $data, $options )
public static function runPlugin( string $data, array $options ): string
{
if( $data[0] !== '$' ){
$data = "'" . $data . "'";
@@ -52,62 +60,62 @@ class _Hextorgb
/**
* Convert Hex value to RGB
*
* @param string $data Hex color code
* @param string $opacity Opacity (0-1) or Setting/Theme Setting name
* @param string $data Hex color code
* @param string $opacity Opacity (0-1) or Setting/Theme Setting name
* @return string
*/
public static function convertToRGB( $data, $opacity )
public static function convertToRGB( string $data, string $opacity ): string
{
$output = array();
/* If a theme setting key has been passed in, then use that as the value */
if ( isset( \IPS\Theme::i()->settings[ $data ] ) )
if ( isset( Theme::i()->settings[ $data ] ) )
{
$data = \IPS\Theme::i()->settings[ $data ];
$data = Theme::i()->settings[ $data ];
}
/* If a regular setting key has been passed in, then use that as the value */
if ( isset( \IPS\Settings::i()->$data ) )
if ( isset( Settings::i()->$data ) )
{
$data = \IPS\Settings::i()->$data;
$data = Settings::i()->$data;
}
/* If a theme setting key has been passed in, then use that as the opacity */
if ( isset( \IPS\Theme::i()->settings[ $opacity ] ) )
if ( isset( Theme::i()->settings[ $opacity ] ) )
{
$opacity = \IPS\Theme::i()->settings[ $opacity ];
$opacity = Theme::i()->settings[ $opacity ];
}
/* If a regular setting key has been passed in, then use that as the opacity */
if ( isset( \IPS\Settings::i()->$opacity ) )
if ( isset( Settings::i()->$opacity ) )
{
$opacity = \IPS\Settings::i()->$opacity;
$opacity = Settings::i()->$opacity;
}
/* Basic validation */
if( !preg_match( "/^#?[0-9a-fA-F]+$/", $data ) OR ( \strlen( str_replace( '#', '', $data ) ) !== 3 AND \strlen( str_replace( '#', '', $data ) ) !== 6 ) )
if( !preg_match( "/^#?[0-9a-fA-F]+$/", $data ) OR ( strlen( str_replace( '#', '', $data ) ) !== 3 AND strlen( str_replace( '#', '', $data ) ) !== 6 ) )
{
return "htmlspecialchars( '" . $data . "', ENT_QUOTES | ENT_DISALLOWED, 'UTF-8', FALSE )";
}
$data = str_replace( '#', '', $data );
if ( \strlen( $data ) == 3 )
if ( strlen( $data ) == 3 )
{
$output[] = hexdec( \substr( $data, 0, 1 ) . \substr( $data, 0, 1 ) ); // R
$output[] = hexdec( \substr( $data, 1, 1 ) . \substr( $data, 1, 1 ) ); // G
$output[] = hexdec( \substr( $data, 2, 1 ) . \substr( $data, 2, 1 ) ); // B
$output[] = hexdec( substr( $data, 0, 1 ) . substr( $data, 0, 1 ) ); // R
$output[] = hexdec( substr( $data, 1, 1 ) . substr( $data, 1, 1 ) ); // G
$output[] = hexdec( substr( $data, 2, 1 ) . substr( $data, 2, 1 ) ); // B
}
else
{
$output[] = hexdec( \substr( $data, 0, 2 ) ); // R
$output[] = hexdec( \substr( $data, 2, 2 ) ); // G
$output[] = hexdec( \substr( $data, 4, 2 ) ); // B
$output[] = hexdec( substr( $data, 0, 2 ) ); // R
$output[] = hexdec( substr( $data, 2, 2 ) ); // G
$output[] = hexdec( substr( $data, 4, 2 ) ); // B
}
if( isset( $opacity ) && $opacity !== '1' )
{
return "rgba(" . implode( ',', $output ) . "," . \str_replace( ',', '.', $opacity ) . ")";
return "rgba(" . implode( ',', $output ) . "," . str_replace( ',', '.', $opacity ) . ")";
}
else
{
+8 -5
View File
@@ -11,21 +11,24 @@
namespace IPS\Output\Plugin;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Template Plugin - Include PHP file
*/
class _Insert
class Insert
{
/**
* @brief Can be used when compiling CSS
*/
public static $canBeUsedInCss = FALSE;
public static bool $canBeUsedInCss = FALSE;
/**
* Run the plug-in
@@ -34,7 +37,7 @@ class _Insert
* @param array $options Array of options
* @return string Code to eval
*/
public static function runPlugin( $data, $options )
public static function runPlugin( string $data, array $options ): string
{
return '\IPS\Output::safeInclude( "' . $data . '" )';
}
+16 -9
View File
@@ -11,21 +11,28 @@
namespace IPS\Output\Plugin;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\Theme;
use function count;
use function defined;
use function strpos;
use function substr;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Template Plugin - Lang
*/
class _Lang
class Lang
{
/**
* @brief Can be used when compiling CSS
*/
public static $canBeUsedInCss = FALSE;
public static bool $canBeUsedInCss = FALSE;
/**
* Run the plug-in
@@ -34,17 +41,17 @@ class _Lang
* @param array $options Array of options
* @return array array( 'pre' => Code to eval before 'return', 'return' => Code to eval to return desired value )
*/
public static function runPlugin( $data, $options )
public static function runPlugin( string $data, array $options ): array
{
$return = array();
$return['pre'] = null;
$params = array();
$data = \IPS\Theme::expandShortcuts( $data );
$data = Theme::expandShortcuts( $data );
if( \substr( $data, 0, 1 ) !== '\\' and \strpos( $data, '$' ) !== FALSE )
if( substr( $data, 0, 1 ) !== '\\' and strpos( $data, '$' ) !== FALSE )
{
if ( \strpos( $data, '$' ) === 0 )
if ( strpos( $data, '$' ) === 0 )
{
$data = '{' . $data . '}';
@@ -109,7 +116,7 @@ class _Lang
$vle = 'TRUE';
if( \count( $params ) )
if( count( $params ) )
{
$vle = 'FALSE';
}
+9 -6
View File
@@ -11,21 +11,24 @@
namespace IPS\Output\Plugin;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Template Plugin - Member Data
*/
class _Member
class Member
{
/**
* @brief Can be used when compiling CSS
*/
public static $canBeUsedInCss = FALSE;
public static bool $canBeUsedInCss = FALSE;
/**
* Run the plug-in
@@ -34,14 +37,14 @@ class _Member
* @param array $options Array of options
* @return string Code to eval
*/
public static function runPlugin( $data, $options )
public static function runPlugin( string $data, array $options ): string
{
if ( $data !== 'link()' and isset( $options['group'] ) )
{
$data = "group['{$data}']";
}
$raw = ( isset( $options['raw'] ) AND $options['raw'] ) ? TRUE : FALSE;
$raw = ( isset( $options['raw'] ) AND $options['raw'] );
if ( isset( $options['id'] ) )
{
+8 -5
View File
@@ -11,21 +11,24 @@
namespace IPS\Output\Plugin;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Template Plugin - Number
*/
class _Number
class Number
{
/**
* @brief Can be used when compiling CSS
*/
public static $canBeUsedInCss = FALSE;
public static bool $canBeUsedInCss = FALSE;
/**
* Run the plug-in
@@ -34,7 +37,7 @@ class _Number
* @param array $options Array of options
* @return string Code to eval
*/
public static function runPlugin( $data, $options )
public static function runPlugin( string $data, array $options ): string
{
if ( isset( $options['format'] ) and $options['format'] === 'short' )
{
+12 -8
View File
@@ -11,35 +11,39 @@
namespace IPS\Output\Plugin;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use function defined;
use function in_array;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Template Plugin - Vendor prefix
*/
class _Prefix
class Prefix
{
/**
* @brief Can be used when compiling CSS
*/
public static $canBeUsedInCss = TRUE;
public static bool $canBeUsedInCss = TRUE;
/**
* Defined here so that we can remove names later as browser support for the non-prefixed version improves, without changing our css.
*
* @brief The styles this plugin will prefix
*/
protected static $supportedStyles = array(
protected static array $supportedStyles = array(
'user-select'
);
/**
* @brief Supported vendor prefixes
*/
protected static $prefixes = array( '-webkit-', '-moz-', '-ms-', '-o-', '' );
protected static array $prefixes = array( '-webkit-', '-moz-', '-ms-', '-o-', '' );
/**
* Run the plug-in
@@ -49,9 +53,9 @@ class _Prefix
* @return string Code to eval
* @note Occasionally a style isn't consistent between browsers, so simply adding a prefix isn't always sufficient - you may need other CSS to resolve browser inconsistencies
*/
public static function runPlugin( $data, $options )
public static function runPlugin( string $data, array $options ): string
{
if ( \in_array( $data, static::$supportedStyles ) )
if ( in_array( $data, static::$supportedStyles ) )
{
$output = array();
+10 -7
View File
@@ -11,21 +11,24 @@
namespace IPS\Output\Plugin;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Template Plugin - Request
*/
class _Request
class Request
{
/**
* @brief Can be used when compiling CSS
*/
public static $canBeUsedInCss = FALSE;
public static bool $canBeUsedInCss = FALSE;
/**
* Run the plug-in
@@ -34,15 +37,15 @@ class _Request
* @param array $options Array of options
* @return string Code to eval
*/
public static function runPlugin( $data, $options )
public static function runPlugin( string $data, array $options ): string
{
if( isset( $options['raw'] ) AND $options['raw'] )
{
return "\IPS\Request::i()->$data";
return "isset( \IPS\Widget\Request::i()->$data ) ? \IPS\Widget\Request::i()->$data : NULL";
}
else
{
return "htmlspecialchars( \IPS\Request::i()->$data, ENT_QUOTES | ENT_DISALLOWED, 'UTF-8', FALSE )";
return "isset( \IPS\Widget\Request::i()->$data ) ? htmlspecialchars( \IPS\Widget\Request::i()->$data, ENT_QUOTES | ENT_DISALLOWED, 'UTF-8', FALSE ): NULL";
}
}
}
+40 -10
View File
@@ -11,37 +11,67 @@
namespace IPS\Output\Plugin;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use Exception;
use IPS\Application;
use IPS\Data\Cache;
use IPS\IPS;
use IPS\Member;
use IPS\Theme;
use function array_pop;
use function count;
use function defined;
use function explode;
use function implode;
use function in_array;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Template Plugin - Theme Resource (image, font, theme-specific JS, etc)
*/
class _Resource
class Resource
{
/**
* @brief Can be used when compiling CSS
*/
public static $canBeUsedInCss = TRUE;
public static bool $canBeUsedInCss = TRUE;
/**
* Run the plug-in
*
* @param string $data The initial data from the tag
* @param array $options Array of options
* @param string $context The name of the calling function
* @param string $data The initial data from the tag
* @param array $options Array of options
* @param string $context The name of the calling function
* @return string Code to eval
*/
public static function runPlugin( $data, $options, $context )
public static function runPlugin( string $data, array $options, string $context ): string
{
$exploded = explode( '_', $context );
$app = ( isset( $options['app'] ) ? $options['app'] : ( isset( $exploded[1] ) ? $exploded[1] : '' ) );
$location = ( isset( $options['location'] ) ? $options['location'] : ( isset( $exploded[2] ) ? $exploded[2] : '' ) );
$app = ( $options['app'] ?? ( $exploded[1] ?? 'core' ) );
$location = ( $options['location'] ?? ( $exploded[2] ?? 'front' ) );
$noProtocol = ( isset( $options['noprotocol'] ) ) ? $options['noprotocol'] : "false";
if ( ( ! \IPS\IN_DEV or Application::areWeBuilding() ) and isset( $options['inCss'] ) and $options['inCss'] and $app and in_array( $app, IPS::$ipsApps ) )
{
/* We can use a relative path */
$paths = explode( '/', $data );
$name = array_pop( $paths );
$path = ( count( $paths ) ) ? ( '/' . implode( '/', $paths ) . '/' ) : '/';
$hash = Theme::makeBuiltTemplateLookupHash( $app, $location, $path );
if ( $location === 'interface' )
{
return '"../../applications/' . $app . '/interface/' . $data . '"';
}
return '"../resources/' . $app . '_' . $hash . '_' . $name . '"';
}
return "\\IPS\\Theme::i()->resource( \"{$data}\", \"{$app}\", '{$location}', {$noProtocol} )";
}
}
+8 -5
View File
@@ -11,22 +11,25 @@
namespace IPS\Output\Plugin;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Template Plugin - Setting
*/
class _Setting
class Setting
{
/**
* @brief Can be used when compiling CSS
* @note Using setting in plugins will require a rebuild of the CSS file they're used in when the setting is changed
*/
public static $canBeUsedInCss = TRUE;
public static bool $canBeUsedInCss = TRUE;
/**
* Run the plug-in
@@ -35,7 +38,7 @@ class _Setting
* @param array $options Array of options
* @return string Code to eval
*/
public static function runPlugin( $data, $options )
public static function runPlugin( string $data, array $options ): string
{
if ( isset( $options['escape'] ) )
{
+8 -5
View File
@@ -11,21 +11,24 @@
namespace IPS\Output\Plugin;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Template Plugin - Strip HTML tags
*/
class _Striptags
class Striptags
{
/**
* @brief Can be used when compiling CSS
*/
public static $canBeUsedInCss = FALSE;
public static bool $canBeUsedInCss = FALSE;
/**
* Run the plug-in
@@ -34,7 +37,7 @@ class _Striptags
* @param array $options Array of options
* @return string Code to eval
*/
public static function runPlugin( $data, $options )
public static function runPlugin( string $data, array $options ): string
{
return "strip_tags( $data )";
}
+18 -11
View File
@@ -11,32 +11,39 @@
namespace IPS\Output\Plugin;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\Theme;
use function defined;
use function in_array;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Template Plugin - Template
*/
class _Template
class Template
{
/**
* @brief Can be used when compiling CSS
*/
public static $canBeUsedInCss = FALSE;
public static bool $canBeUsedInCss = FALSE;
/**
* Run the plug-in
*
* @param string $data The initial data from the tag
* @param array $options Array of options
* @return string Code to eval
* @param string $data The initial data from the tag
* @param array $options Array of options
* @param string|null $functionName
* @param string $calledClass
* @return array|string Code to eval
*/
public static function runPlugin( $data, $options, $functionName=NULL, $calledClass='IPS\Theme' )
public static function runPlugin( string $data, array $options, ?string $functionName=NULL, string $calledClass='IPS\Theme' ): array|string
{
$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 . '}';
@@ -63,7 +70,7 @@ class _Template
if ( isset( $options['if'] ) )
{
$it['pre'] = "if ( " . \IPS\Theme::expandShortcuts( $options['if'] ) . " ):\n";
$it['pre'] = "if ( " . Theme::expandShortcuts( $options['if'] ) . " ):\n";
$it['post'] = "\nendif;";
}
+60 -15
View File
@@ -11,49 +11,94 @@
namespace IPS\Output\Plugin;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use InvalidArgumentException;
use IPS\cloud\extensions\core\AccountSettings\Expert;
use IPS\Log;
use IPS\Theme as SystemTheme;
use IPS\Theme\Editor\Setting;
use OutOfRangeException;
use function defined;
use function mb_substr;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Template Plugin - Theme settings
*/
class _Theme
class Theme
{
/**
* @brief Can be used when compiling CSS
*/
public static $canBeUsedInCss = TRUE;
public static bool $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
* @return mixed 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 )
public static function runPlugin( string $data, array $options ): mixed
{
switch( $data )
{
case 'headerHtml':
return \IPS\Theme::i()->getHeaderAndFooter()['header'];
break;
return "\IPS\Theme::i()->getHeaderAndFooter( 'header' )";
case 'footerHtml':
return \IPS\Theme::i()->getHeaderAndFooter()['footer'];
break;
return "\IPS\Theme::i()->getHeaderAndFooter( 'footer' )";
case 'logo_front':
return "\IPS\Theme::i()->logo_front";
break;
case 'logo_height':
return ( isset( \IPS\Theme::i()->logo['front']['height'] ) ) ? '\IPS\Theme::i()->logo[\'front\'][\'height\']' : 100;
break;
return ( isset( SystemTheme::i()->logo['front']['height'] ) ) ? '\IPS\Theme::i()->logo[\'front\'][\'height\']' : 100;
case 'custom_css_for_output':
return "\IPS\Theme::i()->getCustomCssForOutput()";
case 'custom_css':
return "\IPS\Theme::i()->custom_css";
case "scheme":
return "\IPS\Theme::i()->getCurrentCSSScheme()";
case 'id':
return "\IPS\Theme::i()->id";
default:
return ( isset( \IPS\Theme::i()->settings[ $data ] ) ) ? "\IPS\Theme::i()->settings['{$data}']" : '';
break;
try
{
if ( mb_substr( $data, 0, 4 ) === 'css.' or mb_substr( $data, 0, 4 ) === 'var.' )
{
$variable = mb_substr( $data, 4 );
$cssVariables = SystemTheme::i()->getCssVariables();
if( array_key_exists( $variable, $cssVariables ) )
{
return "\IPS\Theme::i()->getParsedCssVariableFromKey( '{$variable}' )";
}
return '';
}
else if ( mb_substr( $data, 0, 5 ) === 'view.' )
{
$variable = mb_substr( $data, 5 );
return "\IPS\Theme::i()->getLayoutValue('{$variable}')";
}
return ( array_key_exists( $data, SystemTheme::i()->settings ) ) ? "\IPS\Theme::i()->settings['{$data}']" : '';
}
catch( InvalidArgumentException $e )
{
Log::log( $e, 'template_error' );
}
return "";
}
}
}
+8 -5
View File
@@ -11,21 +11,24 @@
namespace IPS\Output\Plugin;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Template Plugin - Truncate
*/
class _Truncate
class Truncate
{
/**
* @brief Can be used when compiling CSS
*/
public static $canBeUsedInCss = TRUE;
public static bool $canBeUsedInCss = TRUE;
/**
* Run the plug-in
@@ -34,7 +37,7 @@ class _Truncate
* @param array $options Array of options
* @return string Code to eval
*/
public static function runPlugin( $data, $options )
public static function runPlugin( string $data, array $options ): string
{
if( !$options['length'] )
{
+14 -8
View File
@@ -11,21 +11,27 @@
namespace IPS\Output\Plugin;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\Http\Url as HttpUrl;
use function defined;
use function in_array;
use function substr;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Template Plugin - URL
*/
class _Url
class Url
{
/**
* @brief Can be used when compiling CSS
*/
public static $canBeUsedInCss = TRUE;
public static bool $canBeUsedInCss = TRUE;
/**
* Run the plug-in
@@ -34,7 +40,7 @@ class _Url
* @param array $options Array of options
* @return string Code to eval
*/
public static function runPlugin( $data, $options )
public static function runPlugin( string $data, array $options ): string
{
$csrf = '';
@@ -43,7 +49,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'] ) )
{
@@ -60,7 +66,7 @@ class _Url
if ( !isset( $options['protocol'] ) )
{
$options['protocol'] = \IPS\Http\Url::PROTOCOL_AUTOMATIC;
$options['protocol'] = HttpUrl::PROTOCOL_AUTOMATIC;
}
$fragment = "";
@@ -74,7 +80,7 @@ class _Url
if ( isset( $options['ref'] ) )
{
/* Is this a variable or other dynamic thing? */
if ( \in_array( \substr( $options['ref'], 0, 1 ), array( '(', '$', '\\' ) ) )
if ( in_array( substr( $options['ref'], 0, 1 ), array( '(', '$', '\\' ) ) )
{
$ref = "->addRef(";
$ref .= $options['ref'];
+8 -5
View File
@@ -11,21 +11,24 @@
namespace IPS\Output\Plugin;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Template Plugin - Truncate
*/
class _Wordbreak
class Wordbreak
{
/**
* @brief Can be used when compiling CSS
*/
public static $canBeUsedInCss = TRUE;
public static bool $canBeUsedInCss = TRUE;
/**
* Run the plug-in
@@ -35,7 +38,7 @@ class _Wordbreak
* @param array $options Array of options
* @return string Code to eval
*/
public static function runPlugin( $data, $options )
public static function runPlugin( string $data, array $options ): string
{
return "htmlspecialchars( {$data}, ENT_QUOTES | ENT_DISALLOWED, 'UTF-8', FALSE )";
}
+177 -31
View File
@@ -11,48 +11,61 @@
namespace IPS\Output;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\Dispatcher;
use IPS\Http\Url;
use IPS\Output;
use IPS\Theme;
use function defined;
use function strstr;
use function uniqid;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Output Class
*/
class _System extends \IPS\Output
class System extends Output
{
/**
* Display Error Screen
*
* @param string $message language key for error message
* @param mixed $code Error code
* @param int $httpStatusCode HTTP Status Code
* @param string $adminMessage language key for error message to show to admins
* @param array $httpHeaders Additional HTTP Headers
* @param string $extra Additional information (such as API error)
* @return void
* @param string $message language key for error message
* @param mixed $code Error code
* @param int $httpStatusCode HTTP Status Code
* @param string|null $adminMessage language key for error message to show to admins
* @param array $httpHeaders Additional HTTP Headers
* @param string|null $extra Additional information (such as API error)
* @param int|string|null $faultyAppOrHookId
* @return void
*/
public function error( $message, $code, $httpStatusCode=500, $adminMessage=NULL, $httpHeaders=array(), $extra=NULL )
public function error( string $message, mixed $code, int $httpStatusCode=500, string $adminMessage=NULL, array $httpHeaders=array(), string $extra=NULL, int|string $faultyAppOrHookId=NULL ) : void
{
/* Send output */
$this->sendOutput( \IPS\Theme\System\Theme::i()->getTemplate('global', 'core', 'global')->error( $message ), $httpStatusCode, 'text/html', $httpHeaders );
$this->sendOutput( Theme::i()->getTemplate('global', 'core', 'global')->error( $message ), $httpStatusCode, 'text/html', $httpHeaders );
}
/**
* Send output
*
* @param string $output Content to output
* @param int $httpStatusCode HTTP Status Code
* @param string $contentType HTTP Content-type
* @param array $httpHeaders Additional HTTP Headers
* @param bool $cache Allows the page to be cached - set to FALSE when sending a cached page
* @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
* @param bool $pageIsCached
* @param bool $parseFileObjects
* @param bool $parseEmoji
* @return void
*/
public function sendOutput( $output='', $httpStatusCode=200, $contentType='text/html', $httpHeaders=array(), $cache=TRUE )
public function sendOutput(string $output='', int $httpStatusCode=200, string $contentType='text/html', array $httpHeaders=array(), bool $cacheThisPage=TRUE, bool $pageIsCached=FALSE, bool $parseFileObjects=TRUE, bool $parseEmoji=TRUE ) : void
{
/* Set HTTP status */
if( isset( $_SERVER['SERVER_PROTOCOL'] ) and \strstr( $_SERVER['SERVER_PROTOCOL'], '/1.0' ) !== false )
if( isset( $_SERVER['SERVER_PROTOCOL'] ) and strstr( $_SERVER['SERVER_PROTOCOL'], '/1.0' ) !== false )
{
header( "HTTP/1.0 {$httpStatusCode} " . static::$httpStatuses[ $httpStatusCode ] );
}
@@ -64,7 +77,7 @@ class _System extends \IPS\Output
/* Buffer output */
if ( $output )
{
if( isset( $_SERVER['HTTP_ACCEPT_ENCODING'] ) and \strstr( $_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip' ) !== false and (bool) ini_get('zlib.output_compression') === false )
if( isset( $_SERVER['HTTP_ACCEPT_ENCODING'] ) and strstr( $_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip' ) !== false and (bool) ini_get('zlib.output_compression') === false )
{
ob_start('ob_gzhandler');
}
@@ -73,7 +86,7 @@ class _System extends \IPS\Output
ob_start();
}
print \IPS\Theme\System\Theme::i()->getTemplate('global', 'core', 'global')->globalTemplate( $output, static::i()->title, array( 'app' => \IPS\Dispatcher::i()->application->directory, 'module' => \IPS\Dispatcher::i()->module->key, 'controller' => \IPS\Dispatcher::i()->controller ) );
print Theme::i()->getTemplate('global', 'core', 'global')->globalTemplate( $output, static::i()->title, array( 'app' => Dispatcher::i()->application->directory, 'module' => Dispatcher::i()->module->key, 'controller' => Dispatcher::i()->controller ) );
}
/* Send headers */
@@ -91,23 +104,156 @@ class _System extends \IPS\Output
flush();
exit;
}
/**
* Redirect
*
* @param \IPS\Http\Url $url URL to redirect to
* @param string $message Optional message to display
* @param int $httpStatusCode HTTP Status Code
* @param bool $forceScreen If TRUE, an intermeditate screen will be shown
* @return void
* @param Url|string $url URL to redirect to
* @param string|null $message Optional message to display
* @param int $httpStatusCode HTTP Status Code
* @param bool $forceScreen If TRUE, an intermeditate screen will be shown
* @return void
*/
public function redirect( $url, $message='', $httpStatusCode=303, $forceScreen=FALSE )
public function redirect( Url|string $url, ?string $message='', int $httpStatusCode=301, bool $forceScreen=FALSE ) : void
{
if ( $forceScreen === TRUE )
{
$this->sendOutput( \IPS\Theme\System\Theme::i()->getTemplate( 'global', 'core', 'global' )->redirect( $url, $message ), $httpStatusCode );
$this->sendOutput( Theme::i()->getTemplate( 'global', 'core', 'global' )->redirect( $url, $message ), $httpStatusCode );
}
$this->sendOutput( '', $httpStatusCode, '', array( "Location: {$url}" ) );
}
/**
* Create a HTML file of the MySQL debug backtrace
*
* @param array $debug
* @return string
*/
public static function generateDbLogHtml( array $debug ): string
{
$queryCount = count( $debug ) - 1; # The first query is the connection
$html = <<<HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MySQL Debug Log</title>
<style>
* {
font-family: Arial, sans-serif;
}
table {
max-width: 100%;
width: 100%;
table-layout: fixed;
border-collapse: collapse;
}
th, td {
padding: 10px;
border: 1px solid #ddd;
text-align: left;
}
td.query {
font-family: monospace;
word-wrap: break-word;
font-size: 14px;
text-wrap: wrap;
}
td.details {
width: 10%;
}
tr.collapsible-content {
display: none;
background-color: #e5e5e5;
}
.toggle-button {
cursor: pointer;
color: blue;
}
</style>
<script>
function toggleRow(id) {
const rows = document.querySelectorAll(`[data-row="content-\${id}"]`);
// Loop through each row and toggle visibility
rows.forEach(row => {
if (row.style.display === "none") {
document.querySelector(`td[data-query="\${id}"]`).style.backgroundColor = "lightblue";
// If the row is hidden, show it
row.style.display = "table-row";
} else {
// If the row is visible, hide it
document.querySelector(`td[data-query="\${id}"]`).style.backgroundColor = "transparent";
row.style.display = "none";
}
});
}
</script>
</head>
<body>
<h1>{$queryCount} Queries</h1>
<table>
<thead>
<tr>
<th>Function</th>
<th>File</th>
<th>Line</th>
<th style="width:150px">Details</th>
</tr>
</thead>
<tbody>
HTML;
foreach ($debug as $index => $data)
{
$unique = md5( $data['query'] . uniqid() );
$html .= <<<HTML
<tr>
<td data-query="{$unique}" colspan="3" class="query">{$data['query']}</td>
<td class="details"><span class="toggle-button" onclick="toggleRow('{$unique}')">Expand/Collapse</span></td>
</tr>
HTML;
foreach( $data['backtrace'] as $id => $trace )
{
$trace['file'] = isset( $trace['file'] ) ? str_replace( \IPS\ROOT_PATH, '', $trace['file'] ) : '';
$trace['line'] = $trace['line'] ?? '';
$html .= <<<HTML
<tr data-row="content-{$unique}" style="display: none" class="collapsible-content">
<td>
{$trace['function']}
</td>
<td>
{$trace['file']}
</td>
<td>
{$trace['line']}
</td>
<td>
</td>
</tr>
HTML;
}
}
$html .= <<<HTML
</tbody>
</table>
</body>
</html>
HTML;
return $html;
}
}
+96
View File
@@ -0,0 +1,96 @@
<?php
namespace IPS\Output\UI;
/* To prevent PHP errors (extending class does not exist) revealing path */
use IPS\Content\Comment as BaseComment;
use IPS\Content\Item as BaseItem;
use IPS\Helpers\Form\FormAbstract;
use IPS\Helpers\Menu\MenuItem;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
abstract class Comment
{
/**
* This needs to be declared in any child classes as well
*
* @var ?string
*/
public static ?string $class = NULL;
/**
* Can be used to add additional css classes to the comment
*
* @param BaseComment $comment
* @return string
*/
public function css( BaseComment $comment ): string
{
return '';
}
/**
* Can be used to add additional data attributes to the comment
*
* @param BaseComment $comment
* @return string
*/
public function dataAttributes( BaseComment $comment ): string
{
return '';
}
/**
* Can be used to attach additional content to the author panel
*
* @param BaseComment $comment
* @return string
*/
public function authorPanel( BaseComment $comment ): string
{
return '';
}
/**
* returns additional menu items for the comment menu
*
* @param BaseComment $comment
* @return array<string,MenuItem>
*/
public function menuItems( BaseComment $comment ): array
{
return [];
}
/**
* Add elements to the comment form
*
* @param BaseComment|null $comment
* @param BaseItem $item
* @return array<string,FormAbstract>
*/
public function formElements( ?BaseComment $comment, BaseItem $item ): array
{
return [];
}
/**
* Triggered after the comment form is saved
*
* @param BaseComment $comment
* @param array $values
* @return void
*/
public function formPostSave( BaseComment $comment, array $values ): void
{
}
}
+166
View File
@@ -0,0 +1,166 @@
<?php
namespace IPS\Output\UI;
/* To prevent PHP errors (extending class does not exist) revealing path */
use IPS\Content\Item as BaseItem;
use IPS\Helpers\Badge\Icon;
use IPS\Helpers\Form\FormAbstract;
use IPS\Helpers\Table\Content;
use IPS\Node\Model;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
abstract class Item
{
/**
* This needs to be declared in any child classes as well
*
* @var string|null
*/
public static ?string $class = NULL;
/**
* Can be used to add additional css classes to the item
*
* @param BaseItem $item
* @return string
*/
public function css( BaseItem $item ): string
{
return '';
}
/**
* Can be used to add additional data attributes to the item
*
* @param BaseItem $item
* @return string
*/
public function dataAttributes( BaseItem $item ): string
{
return '';
}
/**
* returns additional menu items for the moderation menu
*
* @param BaseItem $item
* @return array
*/
public function menuItems( BaseItem $item ): array
{
return [];
}
/**
* Returns additional badge icons to display in the header
*
* @param BaseItem $item
* @return array<Icon>
*/
public function badges( BaseItem $item ) : array
{
return [];
}
/**
* Add elements to the item form.
* This method returns all elements that will be added.
* By default, all elements will be added in order at the end of the form.
* To specify placement within the form, @see FormAbstract::setPosition().
*
* @param BaseItem|null $item
* @param Model|null $container
* @return array<string,FormAbstract>
*/
public function formElements( ?BaseItem $item, ?Model $container ): array
{
return [];
}
/**
* Called after the Item form is saved
*
* @param BaseItem $item
* @param array $values
* @return void
*/
public function formPostSave( BaseItem $item, array $values ): void
{
}
/**
* Display content in the contextual sidebar
*
* @param BaseItem $item
* @return string
*/
public function sidebar( BaseItem $item ) : string
{
return "";
}
/**
* Add custom sort options to the content table for this class
* @note $item will ALWAYS be null.
*
* @param BaseItem|null $item
* @param Content $table
* @return array
*/
public function contentTableSortOptions( ?BaseItem $item, Content $table ) : array
{
return [];
}
/**
* Return the sort direction for the selected sort option,
* or NULL to use the default parent method.
*
* @note $item will ALWAYS be null.
*
* @param BaseItem|null $item
* @param Content $table
* @param string $sortBy
* @return string|null
*/
public function contentTableSortDirection( ?BaseItem $item, Content $table, string $sortBy ) : string|null
{
return null;
}
/**
* Add custom filters to the content table for this class
* @note $item will ALWAYS be null.
*
* @param BaseItem|null $item
* @param Content $table
* @return array
*/
public function contentTableFilters( ?BaseItem $item, Content $table ) : array
{
return [];
}
/**
* Add custom conditions to the where clause for a content table.
* @note $item will ALWAYS be null.
*
* @param BaseItem|null $item
* @param Content $table
* @return array
*/
public function contentTableWhere( ?BaseItem $item, Content $table ) : array
{
return [];
}
}
+74
View File
@@ -0,0 +1,74 @@
<?php
namespace IPS\Output\UI;
/* To prevent PHP errors (extending class does not exist) revealing path */
use IPS\Helpers\Menu\MenuItem;
use IPS\Member as MemberClass;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
abstract class MenuExtension
{
/**
* Used to add additional links to the user account menu
*
* @param string $position ( can be content, settings, logout )
* @return array<MenuItem>
*/
public function accountMenu( string $position = 'content' ): array
{
return [];
}
/**
* Used to add items to the mobile navigation Menu
*
* @param string $position (can be content, settings, logout)
* @return array<MenuItem>
*/
public function mobileMenu( string $position = 'content' ) : array
{
return [];
}
/**
* Used to add additional content to userbar
*
* @return string
*/
public function userNav(): string
{
return '';
}
/**
* Used to add content to the mobile navigation header
*
* @param string $position Where the code will be inserted (header/footer)
* @param string $iconLocation Where the main icons are currently located (header/footer)
* @return string
*/
public function mobileNav( string $position, string $iconLocation = 'footer' ) : string
{
return '';
}
/**
* Used to add items to the user profile menu
*
* @param MemberClass $member Member whose profile is being viewed
* @return array<MenuItem>
*/
public function editProfileMenu( MemberClass $member ) : array
{
return [];
}
}
+108
View File
@@ -0,0 +1,108 @@
<?php
namespace IPS\Output\UI;
/* To prevent PHP errors (extending class does not exist) revealing path */
use IPS\Helpers\Form\FormAbstract;
use IPS\Node\Model;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
abstract class Node
{
/**
* This needs to be declared in any child classes as well
*
* @var string|null
*/
public static ?string $class = NULL;
/**
* Can be used to add additional css classes to the node
*
* @param Model $node
* @return array
*/
public function css( Model $node ): array
{
return [];
}
/**
* Can be used to add additional data attributes to the node
*
* @param Model $node
* @return string
*/
public function dataAttributes( Model $node ): string
{
return '';
}
/**
* Can be used to add buttons to the ACP tree row
* and to the view page in the ACP
*
* @param Model $node
* @return array
*/
public function rowButtons( Model $node ): array
{
return [];
}
/**
* Returns additional HTML to be displayed in the ACP tree row
*
* @param Model $node
* @return string
*/
public function rowHtml( Model $node ) : string
{
return '';
}
/**
* Return a badge to be displayed in the ACP tree row
*
* @param Model $node
* @return null|array Null for no badge, or an array of badge data (0 => CSS class type, 1 => language string, 2 => optional raw HTML to show instead of language string)
*/
public function rowBadge( Model $node ) : array|null
{
return null;
}
/**
* Add elements to the Node form
* This method returns all elements that will be added.
* By default, all elements will be added in order at the end of the form.
* To specify placement within the form, @see FormAbstract::setPosition().
*
* @param Model $node
* @return array<string,FormAbstract>
*/
public function formElements( Model $node ): array
{
return [];
}
/**
* Called after the Node form is saved
*
* @param Model $node
* @param array $values
* @return void
*/
public function formPostSave( Model $node, array $values ): void
{
}
}
+18
View File
@@ -0,0 +1,18 @@
<?php
namespace IPS\Output\UI;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
abstract class Review extends Comment
{
}
+163
View File
@@ -0,0 +1,163 @@
<?php
namespace IPS\Output\UI;
/* To prevent PHP errors (extending class does not exist) revealing path */
use IPS\Application;
use IPS\Content;
use IPS\Node\Model;
use IPS\Patterns\Singleton;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
class UiExtension extends Singleton
{
/**
* @brief Singleton Instances
* @note This needs to be declared in any child classes as well, only declaring here for editor code-complete/error-check functionality
*/
protected static ?Singleton $instance = NULL;
/**
* @var array
*/
protected static array $extensions = [];
/**
* Load all available extensions
*
* @param string $type
* @return void
*/
protected function loadExtensions( string $type ) : void
{
if( !isset( static::$extensions[ $type ] ) )
{
static::$extensions[ $type ] = [];
foreach ( Application::allExtensions( 'core', 'UI' . $type, FALSE, 'core' ) as $key => $extension )
{
static::$extensions[$type][ $key ] = $extension;
}
}
}
/**
* Load extensions for this type
*
* @param string $type
* @return array
*/
protected function extensions( string $type ) : array
{
static::loadExtensions( $type );
return static::$extensions[ $type ];
}
/**
* Checks all UI extensions to verify that the classes are supported
* Return a list of all unsupported classes, or NULL if all classes are valid
*
* @param Application $application
* @return array|null
*/
public static function unsupportedExtensions( Application $application ) : ?array
{
$return = [];
foreach( $application->extensions( 'core', 'UINode', false ) as $ext )
{
/* @var Model $nodeClass */
$nodeClass = $ext::$class ?? null;
if( $nodeClass AND !$nodeClass::$canBeExtended )
{
$return[] = $nodeClass;
}
}
return count( $return ) ? $return : null;
}
/**
* Run a UI extension method
*
* @param Content|Model|string $object
* @param string $method
* @param array|null $payload
* @return array
*/
public function run( Content|Model|string $object, string $method, ?array $payload=array() ) : array
{
$className = is_string( $object ) ? $object : get_class( $object );
$baseClass = null;
switch( $className )
{
case '':
default:
return [];
case is_subclass_of( $className, Model::class ):
/* If this node class cannot be extended, just return and do nothing */
if( !$className::$canBeExtended )
{
return [];
}
$type = 'Node';
$baseClass = Model::class;
break;
case is_subclass_of( $className, Content\Item::class ):
$type = 'Item';
$baseClass = Content\Item::class;
break;
case is_subclass_of( $className, Content\Review::class ):
$type = 'Review';
$baseClass = Content\Review::class;
break;
case is_subclass_of( $className, Content\Comment::class ):
$type = 'Comment';
$baseClass = Content\Comment::class;
break;
}
$return = [];
/* If we are working with an object (Item/Comment/Node), put it first in the parameter list */
$params = is_array( $payload ) ? array_values( $payload ) : array();
array_unshift( $params, is_string( $object ) ? null : $object );
foreach( $this->extensions( $type ) as $extension )
{
/* Use subclass to account for classes in Pages */
if( isset( $extension::$class ) AND $className != $extension::$class AND !is_subclass_of( $className, $extension::$class ) )
{
continue;
}
/* Block extension of base classes */
if( $baseClass !== null AND $extension::$class == $baseClass )
{
continue;
}
if( $response = $extension->$method( ...$params ) )
{
if( is_array( $response ) )
{
$return = array_merge( $return, $response );
}
else
{
$return[] = $response;
}
}
}
return $return;
}
}