<?php
/**
* @brief Initiates Invision Community constants, autoloader and exception handler
* @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 18 Feb 2013
*/
namespace IPS;
/**
* Class to contain Invision Community autoloader and exception handler
*/
class IPS
{
/**
* @brief Classes that have hooks on
*/
public static $hooks = array();
/**
* @brief Unique key for this suite (used in http requests to defy browser caching)
*/
public static $suiteUniqueKey = NULL;
/**
* @brief Developer Code to be added to all namespaces
*/
private static $inDevCode = '';
/**
* @brief Namespaces developer code has been imported to
*/
private static $inDevCodeImportedTo = array();
/**
* @brief Vendors to use PSR-0 autoloader for
*/
public static $PSR0Namespaces = array();
/**
* Get default constants
*
* @return array
*/
public static function defaultConstants()
{
return array(
'CIC' => isset( $_SERVER['IPS_CIC'] ),
'CP_DIRECTORY' => 'admin',
'IN_DEV' => FALSE,
'DEV_USE_WHOOPS' => TRUE,
'DEV_USE_FURL_CACHE' => FALSE,
'DEV_USE_MENU_CACHE' => FALSE,
'DEV_SHOW_SEARCH_KEYWORD_PROMPTS' => TRUE,
'DEBUG_JS' => FALSE,
'DEV_DEBUG_JS' => TRUE,
'DEV_DEBUG_CSS' => FALSE,
'DEBUG_TEMPLATES' => FALSE,
'IPS_FOLDER_PERMISSION' => 0777,
'FOLDER_PERMISSION_NO_WRITE' => 0755,
'IPS_FILE_PERMISSION' => 0666,
'FILE_PERMISSION_NO_WRITE' => 0644,
'ROOT_PATH' => __DIR__,
'NO_WRITES' => ( isset( $_SERVER['IPS_CIC'] ) and !isset( $_SERVER['IPS_CIC_ACP'] ) ),
'DEBUG_LOG' => FALSE,
'LOG_FALLBACK_DIR' => '{root}/uploads/logs',
'STORE_METHOD' => ( isset( $_SERVER['IPS_CIC'] ) ? 'Database' : 'FileSystem' ),
'STORE_CONFIG' => '{"path":"{root}/datastore"}',
'CACHE_METHOD' => isset( $_SERVER['IPS_CLOUD_REDIS425'] ) ? 'Redis' : 'None',
'CACHE_CONFIG' => isset( $_SERVER['IPS_CLOUD_REDIS425'] ) ? '{ "server": "' . $_SERVER['IPS_CLOUD_REDIS425'] . '", "port": 6379 }' : '{}',
'CACHE_PAGE_TIMEOUT' => ( isset( $_SERVER['IPS_CIC'] ) ? 300 : 30 ),
'TEST_CACHING' => FALSE,
'EMAIL_DEBUG_PATH' => NULL,
'BULK_MAILS_PER_CYCLE' => 50,
'JAVA_PATH' => "",
'ERROR_PAGE' => 'error.php',
'UPGRADING_PAGE' => ( defined( 'CP_DIRECTORY' ) ) ? CP_DIRECTORY . '/upgrade/upgrading.html' : 'admin/upgrade/upgrading.html', # defined call here just as a sanity check
'QUERY_LOG' => FALSE,
'CACHING_LOG' => FALSE,
'ENFORCE_ACCESS' => FALSE,
'THUMBNAIL_SIZE' => '500x500',
'PHOTO_THUMBNAIL_SIZE' => 240, // The max we display is 120x120, so this allows for double size for high dpi screens
'COOKIE_DOMAIN' => NULL,
'COOKIE_PREFIX' => 'ips4_',
'COOKIE_PATH' => NULL,
'COOKIE_BYPASS_SSLONLY' => FALSE,
'CONNECT_NOSYNC_NAMES' => FALSE,
'BYPASS_CURL' => FALSE,
'FORCE_CURL' => FALSE,
'NEXUS_TEST_GATEWAYS' => FALSE,
'NEXUS_LKEY_API_DISABLE' => TRUE,
'NEXUS_LKEY_API_CHECK_IP' => TRUE,
'NEXUS_LKEY_API_ALLOW_IP_OVERRIDE' => FALSE,
'UPGRADE_MANUAL_THRESHOLD' => 250000,
'SUITE_UNIQUE_KEY' => ( isset( $_SERVER['IPS_CIC'] ) and preg_match( '/^\/var\/www\/html\/(.+?)$/i', __DIR__, $matches ) ) ? str_replace( '/', '', $matches[1] ) : mb_substr( md5( '13_mafia' . '$Rev: 3023$'), 10, 10 ),
'CACHEBUST_KEY' => mb_substr( md5( '13_mafia' . '$Rev: 3023$'), 10, 10 ), // This looks unnecessary but SUITE_UNIQUE_KEY can be set to a constant constant in constants.php whereas we need a version specific constant for cache busting.
'TEXT_ENCRYPTION_KEY' => NULL,
'CONNECT_MASTER_KEY' => NULL,
'USE_DEVELOPMENT_BUILDS' => FALSE,
'DEV_WHOOPS_EDITOR' => NULL,
'DEFAULT_REQUEST_TIMEOUT' => 10, // In seconds - default for most external connections
'LONG_REQUEST_TIMEOUT' => 30, // In seconds - used for specific API-based calls where we expect a slightly longer response time
'TEMP_DIRECTORY' => sys_get_temp_dir(),
'TEST_DELTA_ZIP' => '',
'TEST_DELTA_TEMPLATE_CHANGES' => '',
'DELTA_FORCE_FTP' => FALSE,
'BYPASS_ACP_IP_CHECK' => FALSE,
'RECOVERY_MODE' => FALSE,
'DEV_FORCE_MFA' => FALSE,
'DISABLE_MFA' => FALSE,
'BOT_SEARCH_FLOOD_SECONDS' => 30,
'READ_WRITE_SEPARATION' => TRUE,
'USE_MYSQL_SEARCH_BASIC_MODE_THRESHOLD' => 0,
'UPGRADE_MD5_CHECK' => TRUE,
'BYPASS_UPGRADER_LOGIN' => FALSE,
'OAUTH_REQUIRES_HTTPS' => TRUE,
'REDIS_LOG' => FALSE,
'REDIS_ENABLED' => 0,//isset( $_SERVER['IPS_CLOUD_REDIS425'] ),
'REDIS_ENCRYPT' => TRUE,
'NOTIFICATIONS_PER_BATCH' => 30,
'SHOW_ACP_LINK' => TRUE,
'REPORT_EXCEPTIONS' => FALSE,
'DEFAULT_THEME_ID' => 1,
'HTMLENTITIES' => ENT_DISALLOWED, // @deprecated and will be removed in 4.4
);
}
/**
* Initiate Invision Community constants, autoloader and exception handler
*
* @return void
*/
public static function init()
{
/* Set timezone */
date_default_timezone_set( 'UTC' );
/* Set default MB internal encoding */
mb_internal_encoding('UTF-8');
/* Define the IN_IPB constant - this needs to be in the global namespace for backwards compatibility */
define( 'IN_IPB', TRUE );
/* Load constants.php */
if( file_exists( __DIR__ . '/constants.php' ) )
{
@include_once( __DIR__ . '/constants.php' );
}
/* Import and set defaults */
$defaultConstants = static::defaultConstants();
foreach ( $defaultConstants as $k => $v )
{
if( defined( $k ) )
{
define( 'IPS\\' . $k, constant( $k ) );
}
else
{
define( 'IPS\\' . $k, $v );
}
}
/* If they have customized the ACP directory but it doesn't exist, throw an error */
if( !is_dir( ROOT_PATH . '/' . CP_DIRECTORY ) AND CP_DIRECTORY != $defaultConstants['CP_DIRECTORY'] )
{
die( "You have defined a custom ACP directory (CP_DIRECTORY) in constants.php, however it is not valid. Please remove or correct this constant definition." );
}
/* Load developer code */
if( IN_DEV and file_exists( ROOT_PATH . '/dev/function_overrides.php' ) )
{
self::$inDevCode = file_get_contents( ROOT_PATH . '/dev/function_overrides.php' );
}
/* Set autoloader */
spl_autoload_register( '\IPS\IPS::autoloader', true, true );
/* Set error handlers */
if ( \IPS\IN_DEV AND \IPS\DEV_USE_WHOOPS and file_exists( ROOT_PATH . '/dev/Whoops/Run.php' ) )
{
self::$PSR0Namespaces['Whoops'] = ROOT_PATH . '/dev/Whoops';
$whoops = new \Whoops\Run;
$handler = new \Whoops\Handler\PrettyPageHandler;
if ( \IPS\DEV_WHOOPS_EDITOR )
{
$handler->setEditor( \IPS\DEV_WHOOPS_EDITOR );
}
$whoops->pushHandler( $handler );
$whoops->register();
}
else
{
set_error_handler( '\IPS\IPS::errorHandler' );
set_exception_handler( '\IPS\IPS::exceptionHandler' );
}
/* Init hooks */
if ( file_exists( \IPS\ROOT_PATH . '/plugins/hooks.php' ) )
{
if ( !( self::$hooks = require( \IPS\ROOT_PATH . '/plugins/hooks.php' ) ) )
{
self::$hooks = array();
}
}
}
/**
* Autoloader
*
* @param string $classname Class to load
* @return void
*/
public static function autoloader( $classname )
{
/* Separate by namespace */
$bits = explode( '\\', ltrim( $classname, '\\' ) );
/* If this doesn't belong to us, try a PSR-0 loader or ignore it */
$vendorName = array_shift( $bits );
if( $vendorName !== 'IPS' )
{
if ( isset( self::$PSR0Namespaces[ $vendorName ] ) )
{
@include_once( self::$PSR0Namespaces[ $vendorName ] . DIRECTORY_SEPARATOR . implode( DIRECTORY_SEPARATOR, $bits ) . '.php' );
}
return;
}
/* Work out what namespace we're in */
$class = array_pop( $bits );
$namespace = empty( $bits ) ? 'IPS' : ( 'IPS\\' . implode( '\\', $bits ) );
$inDevCode = '';
/* We only need to load the file if we don't have the underscore-prefixed one */
if( !class_exists( "{$namespace}\\_{$class}", FALSE ) )
{
/* Locate file */
$path = ROOT_PATH . '/';
$sourcesDirSet = FALSE;
foreach ( array_merge( $bits, array( $class ) ) as $i => $bit )
{
if( preg_match( "/^[a-z0-9]/", $bit ) )
{
if( $i === 0 )
{
$path .= 'applications/';
}
else
{
$sourcesDirSet = TRUE;
}
}
elseif ( $i === 3 and $bit === 'Upgrade' )
{
$bit = mb_strtolower( $bit );
}
elseif( $sourcesDirSet === FALSE )
{
if( $i === 0 )
{
$path .= 'system/';
}
elseif ( $i === 1 and $bit === 'Application' )
{
// do nothing
}
else
{
$path .= 'sources/';
}
$sourcesDirSet = TRUE;
}
$path .= "{$bit}/";
}
/* Load it */
$path = \substr( $path, 0, -1 ) . '.php';
if( !file_exists( $path ) )
{
$path = \substr( $path, 0, -4 ) . \substr( $path, \strrpos( $path, '/' ) );
if ( !file_exists( $path ) )
{
return FALSE;
}
}
require_once( $path );
/* Is it an interface? */
if ( interface_exists( "{$namespace}\\{$class}", FALSE ) )
{
return;
}
/* Is it a trait? The function_exists call is just so PHP 5.3 doesn't throw a fatal error (even though it's required, we want it to fail gracefully) */
if ( function_exists('trait_exists') and trait_exists( "{$namespace}\\{$class}", FALSE ) )
{
return;
}
/* Doesn't exist? */
if( !class_exists( "{$namespace}\\_{$class}", FALSE ) )
{
trigger_error( "Class {$classname} could not be loaded. Ensure it has been properly prefixed with an underscore and is in the correct namespace.", E_USER_ERROR );
}
/* Stuff for developer mode */
if( IN_DEV )
{
$reflection = new \ReflectionClass( "{$namespace}\\_{$class}" );
/* Import our code to override forbidden functions */
if( !in_array( \strtolower( $namespace ), self::$inDevCodeImportedTo ) )
{
$inDevCode = self::$inDevCode;
self::$inDevCodeImportedTo[] = \strtolower( $namespace );
}
/* Any classes which extend a core PHP class are exempt from our rules */
$extendsCorePhpClass = FALSE;
for ( $workingClass = $reflection; $parent = $workingClass->getParentClass(); $workingClass = $parent )
{
if ( \substr( $parent->getNamespaceName(), 0, 3 ) !== 'IPS' )
{
$extendsCorePhpClass = TRUE;
break;
}
}
if ( !$extendsCorePhpClass )
{
/* Make sure it's name follows our standards */
if( !preg_match( '/^_[A-Z0-9]+$/i', $reflection->getShortName() ) )
{
trigger_error( "{$classname} does not follow our naming conventions. Please rename using only alphabetic characters and PascalCase. (PHP Coding Standards: Classes.5)", E_USER_ERROR );
}
/* Loop methods */
$hasNonAbstract = FALSE;
$hasNonStatic = FALSE;
foreach ( $reflection->getMethods() as $method )
{
if ( \substr( $method->getDeclaringClass()->getName(), 0, 3 ) === 'IPS' )
{
/* Make sure it's not private */
if( $method->isPrivate() )
{
trigger_error( "{$classname}::{$method->name} is declared as private. In order to ensure that hooks are able to work freely, please use protected instead. (PHP Coding Standards: Functions and Methods.4)", E_USER_ERROR );
}
/* We need to know for later if we have non-abstract methods */
if( !$method->isAbstract() )
{
$hasNonAbstract = TRUE;
}
/* We need to know for later if we have non-static methods */
if( !$method->isStatic() )
{
$hasNonStatic = TRUE;
}
/* Make sure the name follows our conventions */
if(
!preg_match( '/^_?[a-z][A-Za-z0-9]*$/', $method->name ) // Normal pattern most methods should match
and
!preg_match( '/^get_/i', $method->name ) // get_* is allowed
and
!preg_match( '/^set_/i', $method->name ) // set_* is allowed
and
!preg_match( '/^parse_/i', $method->name ) // parse_* is allowed
and
!preg_match( '/^setBitwise_/i', $method->name ) // set_Bitiwse_* is allowed
and
!preg_match( '/^(GET|POST|PUT|DELETE)[a-zA-Z_]+$/', $method->name ) // API methods have a specific naming format
and
!in_array( $method->name, array( // PHP's magic methods are allowed (except __sleep and __wakeup as we don't allow serializing)
'__construct',
'__destruct',
'__call',
'__callStatic',
'__get',
'__set',
'__isset',
'__unset',
'__toString',
'__invoke',
'__set_state',
'__clone',
'__debugInfo',
) )
) {
trigger_error( "{$classname}::{$method->name} does not follow our naming conventions. Please rename using only alphabetic characters and camelCase. (PHP Coding Standards: Functions and Methods.1-3)", E_USER_ERROR );
}
}
}
/* Loop properties */
foreach ( $reflection->getProperties() as $property )
{
$hasNonAbstract = TRUE;
/* Make sure it's not private */
if( $property->isPrivate() )
{
trigger_error( "{$classname}::\${$property->name} is declared as private. In order to ensure that hooks are able to work freely, please use protected instead. (PHP Coding Standards: Properties and Variables.3)", E_USER_ERROR );
}
/* Make sure the name follows our conventions */
if( !preg_match( '/^_?[a-z][A-Za-z]*$/', $property->name ) )
{
trigger_error( "{$classname}::\${$property->name} does not follow our naming conventions. Please rename using only alphabetic characters and camelCase. (PHP Coding Standards: Properties and Variables.1-2)", E_USER_ERROR );
}
}
/* Check an interface wouldn't be more appropriate */
if( !$hasNonAbstract )
{
trigger_error( "You do not have any non-abstract methods in {$classname}. Please use an interface instead. (PHP Coding Standards: Classes.7)", E_USER_ERROR );
}
/* Check we have at least one non-static method (unless this class is abstract or has a parent) */
elseif( !$reflection->isAbstract() and $reflection->getParentClass() === FALSE and !$hasNonStatic and $reflection->getNamespacename() !== 'IPS\Output\Plugin' and !in_array( 'extensions', $bits ) and !in_array( 'templateplugins', $bits ) )
{
trigger_error( "You do not have any methods in {$classname} which are not static. Please refactor. (PHP Coding Standards: Functions and Methods.6)", E_USER_ERROR );
}
}
}
}
/* Monkey Patch */
self::monkeyPatch( $namespace, $class, $inDevCode );
}
/**
* Monkey Patch
*
* @param string $namespace The namespace
* @param string $finalClass The final class name we want to be able to use (without namespace)
* @param string $extraCode Any additonal code to import before the class is defined
* @return null
*/
public static function monkeyPatch( $namespace, $finalClass, $extraCode = '' )
{
$realClass = "_{$finalClass}";
if( isset( self::$hooks[ "\\{$namespace}\\{$finalClass}" ] ) AND \IPS\RECOVERY_MODE === FALSE )
{
foreach ( self::$hooks[ "\\{$namespace}\\{$finalClass}" ] as $id => $data )
{
if ( file_exists( ROOT_PATH . '/' . $data['file'] ) )
{
$contents = "namespace {$namespace}; ". str_replace( '_HOOK_CLASS_', $realClass, file_get_contents( ROOT_PATH . '/' . $data['file'] ) );
try
{
if( @eval( $contents ) !== FALSE )
{
$realClass = $data['class'];
}
}
catch ( \ParseError $e )
{
/* Show the error if we have development mode enabled */
if( \IPS\IN_DEV )
{
throw $e;
}
}
}
}
}
$reflection = new \ReflectionClass( "{$namespace}\\_{$finalClass}" );
if( eval( "namespace {$namespace}; ". $extraCode . ( $reflection->isAbstract() ? 'abstract' : '' )." class {$finalClass} extends {$realClass} {}" ) === FALSE )
{
trigger_error( "There was an error initiating the class {$namespace}\\{$finalClass}.", E_USER_ERROR );
}
}
/**
* Error Handler
*
* @param int $errno Error number
* @param string $errstr Error message
* @param string $errfile File
* @param int $errline Line
* @param array $trace Backtrace
* @return void
*/
public static function errorHandler( $errno, $errstr, $errfile, $errline, $trace=NULL )
{
/* We don't care about these in production */
if ( in_array( $errno, array( E_WARNING, E_NOTICE, E_STRICT, E_DEPRECATED ) ) )
{
return;
}
/* This means the error suppressor was used, so we should ignore any non-fatal errors */
if ( error_reporting() === 0 )
{
return false;
}
throw new \ErrorException( $errstr, $errno, 0, $errfile, $errline );
}
/**
* Exception Handler
*
* @param \Throwable $exception Exception class
* @return void
*/
public static function exceptionHandler( $exception )
{
/* Should we show the exception message? */
$showMessage = ( \IPS\Dispatcher::hasInstance() AND \IPS\Dispatcher::i()->controllerLocation == 'admin' );
if ( method_exists( $exception, 'isServerError' ) and $exception->isServerError() )
{
$showMessage = TRUE;
}
/* Work out what we'll log - exception classes can provide extra data */
$log = '';
if ( method_exists( $exception, 'extraLogData' ) )
{
$log .= $exception->extraLogData() . "\n";
}
$log .= get_class( $exception ) . ": " . $exception->getMessage() . " (" . $exception->getCode() . ")\n" . $exception->getTraceAsString();
/* Log it */
\IPS\Log::log( $log, 'uncaught_exception' );
/* Report it */
try
{
if (
!\IPS\IN_DEV
and \IPS\REPORT_EXCEPTIONS === TRUE
and \IPS\Settings::i()->diagnostics_reporting
and !\IPS\Settings::i()->theme_designers_mode
and !self::exceptionWasThrownByThirdParty( $exception )
and ( !method_exists( $exception, 'isThirdPartyError' ) or !$exception->isThirdPartyError() )
and ( !method_exists( $exception, 'isServerError' ) or !$exception->isServerError() )
and !( $exception instanceof \ParseError )
)
{
self::reportExceptionToIPS( $exception );
}
}
catch ( \Exception $e ) { }
/* Try to display a friendly error page */
try
{
/* If we couldn't connect to the database, don't bother trying to show the friendly page because nope */
if( $exception instanceof \IPS\Db\Exception AND $exception->getCode() === 0 )
{
throw new \RuntimeException;
}
/* If we're in the installer/upgrader, show the raw message */
$message = 'generic_error';
if( \IPS\Dispatcher::hasInstance() AND \IPS\Dispatcher::i()->controllerLocation == 'setup' )
{
$message = $exception->getMessage();
}
$faultyAppOrHookId = static::exceptionWasThrownByThirdParty( $exception );
/* Output */
\IPS\Output::i()->error( $message, "EX{$exception->getCode()}", 500, NULL, array(), $log, $faultyAppOrHookId );
}
/* And if *that* fails, show our generic page */
catch ( \Exception $e )
{
static::genericExceptionPage( $showMessage ? $exception->getMessage() : NULL );
}
catch ( \Throwable $e )
{
static::genericExceptionPage( $showMessage ? $exception->getMessage() : NULL );
}
exit;
}
/**
* Should a given exception be reported to IPS? Filter out 3rd party etc.
*
* @param \Throwable $exception The exception
* @return void
*/
final public static function reportExceptionToIPS( $exception )
{
$response = \IPS\Http\Url::external('https://invisionpowerdiagnostics.com')->request()->post( array(
'version' => \IPS\Application::getAvailableVersion('core'),
'class' => get_class( $exception ),
'message' => $exception->getMessage(),
'code' => $exception->getCode(),
'file' => str_replace( \IPS\ROOT_PATH, '', $exception->getFile() ),
'line' => $exception->getLine(),
'backtrace' => str_replace( \IPS\ROOT_PATH, '', $exception->getTraceAsString() )
) );
if ( $response->httpResponseCode == 410 )
{
\IPS\Settings::i()->changeValues( array( 'diagnostics_reporting' => 0 ) );
}
}
/**
* Generic exception page
*
* @param \Exception $exception Exception class
* @return void
* @note Abstracted so Theme can call this if templates are in the process of building
*/
public static function genericExceptionPage( $message = NULL )
{
if( isset( $_SERVER['SERVER_PROTOCOL'] ) and \strstr( $_SERVER['SERVER_PROTOCOL'], '/1.0' ) !== false )
{
header( "HTTP/1.0 500 Internal Server Error" );
}
else
{
header( "HTTP/1.1 500 Internal Server Error" );
}
require \IPS\ROOT_PATH . '/' . \IPS\ERROR_PAGE;
exit;
}
/**
* Small utility function to check if a class has a trait as PHP doesn't have an operator
* for this and the monkey patching means we can't use class_uses() directly
*
* @param string|object $class The class
* @param string $trait Trait name to look for
* @return bool
*/
public static function classUsesTrait( $class, $trait )
{
do
{
if ( in_array( $trait, class_uses( $class ) ) )
{
return TRUE;
}
}
while( $class = get_parent_class( $class ) );
return FALSE;
}
/**
* Get license key data
*
* @param bool $forceRefresh If TRUE, will get data from server
* @return array|NULL
*/
public static function licenseKey( $forceRefresh = FALSE )
{
/* We haven't license key saved in settings? Saving... */
if ( !\IPS\Settings::i()->ipb_reg_number ) {
\IPS\Db::i()->update( 'core_sys_conf_settings', array( 'conf_value' => 'LICENSE KEY GOES HERE!-123456789' ), array( 'conf_key=?', 'ipb_reg_number' ) );
\IPS\Settings::i()->ipb_reg_number = 'LICENSE KEY GOES HERE!-123456789';
}
$response = array(
'key' => \IPS\Settings::i()->ipb_reg_number, //IPS Key
'active' => \IPS\Settings::i()->ipb_license_active, //License Active?
'cloud' => \IPS\Settings::i()->ipb_license_cloud, //We are "cloud" clients?
'url' => \IPS\Settings::i()->ipb_license_url, //Forum URL
'test_url' => \IPS\Settings::i()->ipb_license_test_url, //Test URL
'expires' => \IPS\Settings::i()->ipb_license_expires, //When our license will expire?
'products' => array( //Array of components. Can we use...
'forums' => \IPS\Settings::i()->ipb_license_product_forums, //...IP.Board // Forums?
'calendar' => \IPS\Settings::i()->ipb_license_product_calendar, //...IP.Calendar // Calendar?
'blog' => \IPS\Settings::i()->ipb_license_product_blog, //...IP.Blogs // Blogs?
'gallery' => \IPS\Settings::i()->ipb_license_product_gallery, //...IP.Gallery // Gallery?
'downloads' => \IPS\Settings::i()->ipb_license_product_downloads, //...IP.Downloads // Downloads?
'cms' => \IPS\Settings::i()->ipb_license_product_cms, //...IP.Content // Pages?
'nexus' => \IPS\Settings::i()->ipb_license_product_nexus, //...IP.Nexus // Commerce?
'spam' => FALSE, //...IPS Spam Service? No! Hardcoded to prevent requests to IPS servers.
'copyright' => \IPS\Settings::i()->ipb_license_product_copyright, //...remove copyright function?
),
'chat_limit' => \IPS\Settings::i()->ipb_license_chat_limit, //How many users can use IP.Chat?
'support' => \IPS\Settings::i()->ipb_license_support, //Can we use Support?
);
$cached = NULL;
if ( isset( \IPS\Data\Store::i()->license_data ) ) //License data exists in cache?
{
$cached = \IPS\Data\Store::i()->license_data;
/* Keep license data updated in cache store */
if ( $cached['fetched'] < ( time() - 1814400 ) )
{
/* Data older, than 21 days. Updating... */
unset( \IPS\Data\Store::i()->license_data );
\IPS\Data\Store::i()->license_data = array( //Add information to cache...
'fetched' => time(),
'data' => $response,
);
return $response;
} else {
return $cached['data'];
}
}
else
{
/* Cached license data is missing? Creating... */
\IPS\Data\Store::i()->license_data = array( //Add information to cache...
'fetched' => time(),
'data' => $response,
);
return $response;
}
}
/**
* Check license key
*
* @param string The license key
* @param string The site URL
* @return void
* @throws \DomainException
*/
public static function checkLicenseKey( $val, $url )
{
//Do Nothing
}
/**
* Was the exception thrown by a third party app/plugin?
*
* @param \Throwable $exception The exception
* @return string|int|NULL string = application directory, int = hook id, null means that it was probably caused by an application
*/
public static function exceptionWasThrownByThirdParty( $exception )
{
$trace = $exception->getTraceAsString();
/* Did it happen in a hook? */
if ( preg_match( '/init\.php\(\d*\) : eval\(\)\'d code$/', $exception->getFile() ) )
{
/* Did it happen inside a plugin hook? */
if ( preg_match( '/hook(\d+)/', $trace, $matches ) )
{
/* Return the hook id, the error method will fetch the plugin name */
return $matches[1];
}
/* Did it happen inside an applications hook? */
else if ( preg_match_all( '/([a-zA-Z]+)_hook/', $trace, $matches ) )
{
foreach ( $matches[1] as $appKey )
{
if ( !in_array( $appKey, \IPS\Application::$ipsApps ) )
{
return $appKey;
}
}
}
return NULL;
}
/* Exception was thrown by 'normal code', check if it's from a third-party app */
else
{
foreach ( explode( "\n", $trace ) as $line )
{
if ( preg_match( '/' . preg_quote( DIRECTORY_SEPARATOR, '/' ) . 'applications' . preg_quote( DIRECTORY_SEPARATOR, '/' ) . '([a-zA-Z]+)/', str_replace( \IPS\ROOT_PATH, '', $line ), $matches ) )
{
if ( !in_array( $matches[1], \IPS\Application::$ipsApps ) )
{
return $matches[1];
}
else
{
return NULL;
}
}
}
}
/* Still here? Probably system */
return NULL;
}
/**
* Resync IPS Cloud File System
* Must be called when writing any files to disk on IPS Community in the Cloud
*
* @param string $reason Reason
* @return void
*/
public static function resyncIPSCloud( $reason = NULL )
{
if ( \IPS\CIC )
{
if ( preg_match( '/^\/var\/www\/html\/(.+?)(?:\/|$)/i', \IPS\ROOT_PATH, $matches ) )
{
try
{
\IPS\Http\Url::external('http://ips-cic-fileupdate.invisioncic.com/')
->setQueryString( array( 'user' => $matches[1], 'reason' => $reason ) )
->request()
->post();
}
catch ( \Exception $e ) { }
}
}
}
}
/* Init */
IPS::init();
/* Custom mb_ucfirst() function - eval'd so we can put into global namespace */
eval( '
function mb_ucfirst()
{
$text = func_get_arg( 0 );
return mb_strtoupper( mb_substr( $text, 0, 1 ) ) . mb_substr( $text, 1 );
}
');