Version 4.3.0
This commit is contained in:
1 parent
96997ddd8e
commit
fe1acf984a
1349 files changed
+116159
-67711
No files matched your search
@@ -374,7 +374,8 @@ class _Javascript extends \IPS\Patterns\ActiveRecord
|
||||
|
||||
if( $xml->name == 'file' )
|
||||
{
|
||||
\IPS\Db::i()->insert( 'core_javascript', array(
|
||||
/* We have a unique key on app, location, path, name so we use replace into to prevent duplicates */
|
||||
\IPS\Db::i()->replace( 'core_javascript', array(
|
||||
'javascript_app' => $xml->getAttribute('javascript_app'),
|
||||
'javascript_key' => '',
|
||||
'javascript_plugin' => '',
|
||||
@@ -868,7 +869,6 @@ class _Javascript extends \IPS\Patterns\ActiveRecord
|
||||
}
|
||||
|
||||
\IPS\Data\Store::i()->javascript_map = $map;
|
||||
\IPS\Theme::unlock( $flagKey );
|
||||
|
||||
/* As the filename has changed, rebuild the JS map */
|
||||
if ( ( $app !== 'global' and ( $location === NULL or in_array( $location, array( 'admin', 'front', 'global' ) ) ) ) )
|
||||
@@ -876,6 +876,8 @@ class _Javascript extends \IPS\Patterns\ActiveRecord
|
||||
\IPS\Output\Javascript::compile( 'global', 'root', 'map.js' );
|
||||
}
|
||||
|
||||
\IPS\Theme::unlock( $flagKey );
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@@ -969,7 +971,7 @@ class _Javascript extends \IPS\Patterns\ActiveRecord
|
||||
|
||||
if ( \IPS\IN_DEV )
|
||||
{
|
||||
foreach ( \IPS\Application::applications() as $app )
|
||||
foreach ( \IPS\Application::enabledApplications() as $app )
|
||||
{
|
||||
if ( file_exists( \IPS\ROOT_PATH . "/applications/{$app->directory}/dev/jslang.php" ) )
|
||||
{
|
||||
@@ -977,7 +979,7 @@ class _Javascript extends \IPS\Patterns\ActiveRecord
|
||||
$_lang = array_merge( $_lang, $lang );
|
||||
}
|
||||
}
|
||||
foreach ( \IPS\Plugin::plugins() as $plugin )
|
||||
foreach ( \IPS\Plugin::enabledPlugins() as $plugin )
|
||||
{
|
||||
if ( file_exists( \IPS\ROOT_PATH . "/plugins/{$plugin->location}/dev/jslang.php" ) )
|
||||
{
|
||||
@@ -1059,8 +1061,17 @@ class _Javascript extends \IPS\Patterns\ActiveRecord
|
||||
|
||||
foreach ( new \GlobIterator( \IPS\ROOT_PATH . '/plugins/*/dev/js/*' ) as $file )
|
||||
{
|
||||
$url = str_replace( \IPS\ROOT_PATH, rtrim( \IPS\Settings::i()->base_url, '/' ), $file );
|
||||
$return[] = str_replace( '\\', '/', $url );
|
||||
try
|
||||
{
|
||||
$plugin = \IPS\Plugin::getPluginFromPath( $file );
|
||||
|
||||
if( $plugin->enabled )
|
||||
{
|
||||
$url = str_replace( \IPS\ROOT_PATH, rtrim( \IPS\Settings::i()->base_url, '/' ), $file );
|
||||
$return[] = str_replace( '\\', '/', $url );
|
||||
}
|
||||
}
|
||||
catch( \OutOfRangeException $e ){}
|
||||
}
|
||||
|
||||
return $return;
|
||||
@@ -1146,7 +1157,7 @@ class _Javascript extends \IPS\Patterns\ActiveRecord
|
||||
{
|
||||
$files = array();
|
||||
|
||||
foreach( \IPS\Application::applications() as $app => $data )
|
||||
foreach( \IPS\Application::enabledApplications() as $app => $data )
|
||||
{
|
||||
$root = \IPS\ROOT_PATH . "/applications/{$app}/dev/js/";
|
||||
|
||||
@@ -1213,11 +1224,17 @@ class _Javascript extends \IPS\Patterns\ActiveRecord
|
||||
*
|
||||
* @param string $app Application
|
||||
* @param string $location Location (front, global, etc)
|
||||
* @return void
|
||||
* @return array
|
||||
*/
|
||||
protected static function _appJs( $app, $location )
|
||||
{
|
||||
$models = array();
|
||||
|
||||
/* Only include if the app is enabled */
|
||||
if( !in_array( $app, array_keys( \IPS\Application::enabledApplications() ) ) )
|
||||
{
|
||||
return $models;
|
||||
}
|
||||
|
||||
if ( \is_dir( \IPS\ROOT_PATH . "/applications/" . $app ) )
|
||||
{
|
||||
|
||||
+157
-13
@@ -370,7 +370,7 @@ class _Output
|
||||
}
|
||||
else
|
||||
{
|
||||
$title = htmlspecialchars( $title, \IPS\HTMLENTITIES, 'UTF-8', FALSE );
|
||||
$title = htmlspecialchars( $title, ENT_DISALLOWED, 'UTF-8', FALSE );
|
||||
}
|
||||
|
||||
if( !\IPS\Settings::i()->site_online )
|
||||
@@ -474,9 +474,19 @@ class _Output
|
||||
}
|
||||
|
||||
/* Still here? */
|
||||
if ( \IPS\Output\Javascript::compile( $app, $location, $file ) === NULL )
|
||||
try
|
||||
{
|
||||
/* Rebuild already in progress */
|
||||
if ( \IPS\Output\Javascript::compile( $app, $location, $file ) === NULL )
|
||||
{
|
||||
/* Rebuild already in progress */
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
catch( \RuntimeException $e )
|
||||
{
|
||||
/* Possibly cannot write file - log but don't show an error as the user can't fix anyways */
|
||||
\IPS\Log::log( $e, 'javascript' );
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -512,16 +522,23 @@ class _Output
|
||||
*/
|
||||
public function error( $message, $code, $httpStatusCode=500, $adminMessage=NULL, $httpHeaders=array(), $extra=NULL, $faultyAppOrHookId=NULL )
|
||||
{
|
||||
/* If we just logged out, we don't want to show a "no permission" error */
|
||||
/* When we log out, the user is taken back to the page they were just on. If this is producing a "no permission" error, redirect them to the index instead */
|
||||
if ( isset( \IPS\Request::i()->_fromLogout ) )
|
||||
{
|
||||
$this->redirect( \IPS\Http\Url::internal('')->stripQueryString()->setQueryString( '_fromLogout', 1 ) );
|
||||
// _fromLogout=1 indicates that they came from log out. To make sure that we don't cause an infinite redirect (which
|
||||
// would happen if guests cannot view the index page) we need to change _fromLogout, but we can't unset it because _fromLogout={anything}
|
||||
// will clear the autosave content on next load (by Javascript), which we need to do on log out for security reasons... so, _fromLogout=2
|
||||
// is used here which will clear the autosave, but *not* redirect them again
|
||||
if ( \IPS\Request::i()->_fromLogout != 2 )
|
||||
{
|
||||
$this->redirect( \IPS\Http\Url::internal('')->stripQueryString()->setQueryString( '_fromLogout', 2 ) );
|
||||
}
|
||||
}
|
||||
|
||||
/* If we just logged in and we need to do MFA, do that */
|
||||
if ( isset( \IPS\Request::i()->_mfaLogin ) )
|
||||
{
|
||||
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=system&controller=login", 'front', 'login', NULL, \IPS\Settings::i()->logins_over_https )->setQueryString( '_mfaLogin', 1 ) );
|
||||
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=system&controller=login", 'front', 'login' )->setQueryString( '_mfaLogin', 1 ) );
|
||||
}
|
||||
|
||||
/* If we're in an external script, just show a simple message */
|
||||
@@ -669,9 +686,10 @@ class _Output
|
||||
* @param bool $cacheThisPage Can/should this page be cached?
|
||||
* @param bool $pageIsCached Is the page from a cache? If TRUE, no language parsing will be done
|
||||
* @param bool $parseFileObjects Should <fileStore.xxx> and <___base_url___> be replaced in the output?
|
||||
* @param bool $parseEmoji Should Emoji be parsed?
|
||||
* @return void
|
||||
*/
|
||||
public function sendOutput( $output='', $httpStatusCode=200, $contentType='text/html', $httpHeaders=array(), $cacheThisPage=TRUE, $pageIsCached=FALSE, $parseFileObjects=TRUE )
|
||||
public function sendOutput( $output='', $httpStatusCode=200, $contentType='text/html', $httpHeaders=array(), $cacheThisPage=TRUE, $pageIsCached=FALSE, $parseFileObjects=TRUE, $parseEmoji=TRUE )
|
||||
{
|
||||
/* Replace language stack keys with actual content */
|
||||
if ( \IPS\Dispatcher::hasInstance() and !in_array( $contentType, array( 'text/javascript', 'text/css', 'application/json' ) ) and $output and !$pageIsCached )
|
||||
@@ -684,6 +702,12 @@ class _Output
|
||||
{
|
||||
$this->parseFileObjectUrls( $output );
|
||||
}
|
||||
|
||||
/* Replace emoji */
|
||||
if ( $output and $parseEmoji and \IPS\Dispatcher::hasInstance() and \IPS\Dispatcher::i()->controllerLocation !== 'setup' )
|
||||
{
|
||||
$output = $this->replaceEmojiWithImages( $output );
|
||||
}
|
||||
|
||||
/* Full page caching for guests */
|
||||
if (
|
||||
@@ -692,7 +716,7 @@ class _Output
|
||||
!isset( \IPS\Request::i()->cookie['noCache'] ) and // A noCache cookie might get set to not cache a particular guest (for example, if they have added items to their cart in the store)
|
||||
mb_strtolower( $_SERVER['REQUEST_METHOD'] ) == 'get' and // Is a HTTP GET request (don't cache output for POSTs)
|
||||
!isset( \IPS\Request::i()->csrfKey ) and // CSRF key isn't present (which would be like a POST request)
|
||||
$contentType == 'text/html' and // Output is HTML (don't cache RSS feeds, JSON output, etc)
|
||||
( $contentType == 'text/html' or $contentType == 'text/xml' ) and // Output is HTML or XML (don't cache JSON output, etc)
|
||||
\IPS\Dispatcher::hasInstance() and class_exists( 'IPS\Dispatcher', FALSE ) and \IPS\Dispatcher::i()->controllerLocation === 'front' and // Is a normal, front-end page (necessary to know if user is logged in)
|
||||
!\IPS\Member::loggedIn()->member_id // User is not logged in
|
||||
) {
|
||||
@@ -719,6 +743,17 @@ class _Output
|
||||
|
||||
/* And run */
|
||||
$cachingLog = \IPS\Data\Cache::i()->log;
|
||||
|
||||
try
|
||||
{
|
||||
if ( \IPS\REDIS_LOG )
|
||||
{
|
||||
$cachingLog = $cachingLog + \IPS\Redis::$log;
|
||||
ksort( $cachingLog );
|
||||
}
|
||||
}
|
||||
catch( \Exception $e ) { }
|
||||
|
||||
$queryLog = \IPS\Db::i()->log;
|
||||
if ( \IPS\QUERY_LOG )
|
||||
{
|
||||
@@ -751,9 +786,7 @@ class _Output
|
||||
$this->sendHeader("Content-Encoding: gzip"); // Tells the server we've alredy encoded so it doesn't need to
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/* Output */
|
||||
print $output;
|
||||
|
||||
@@ -770,7 +803,19 @@ class _Output
|
||||
$size = ob_get_length();
|
||||
$this->sendHeader( "Content-Length: {$size}" ); // Makes sure the connection closes after sending output so that tasks etc aren't holding it open
|
||||
}
|
||||
|
||||
/* Do we want to preload anything? */
|
||||
foreach( $this->linkTags as $tag )
|
||||
{
|
||||
/* We are only doing this for rel=preload, and the 'as' parameter is not optional */
|
||||
if( is_array( $tag ) AND isset( $tag['rel'] ) AND $tag['rel'] == 'preload' AND isset( $tag['as'] ) AND $tag['as'] )
|
||||
{
|
||||
$as = ( isset( $tag['as'] ) ) ? "; as=" . $tag['as'] : '';
|
||||
$this->sendHeader( "Link: " . $tag['href'] . '; rel=preload' . $as );
|
||||
}
|
||||
}
|
||||
|
||||
/* Rest of our HTTP headers */
|
||||
foreach ( $httpHeaders as $key => $header )
|
||||
{
|
||||
$this->sendHeader( $key . ': ' . $header );
|
||||
@@ -849,6 +894,12 @@ class _Output
|
||||
$message = \IPS\Member::loggedIn()->language()->addToStack( $message );
|
||||
\IPS\Member::loggedIn()->language()->parseOutputForDisplay( $message );
|
||||
$_SESSION['inlineMessage'] = $message;
|
||||
session_write_close();
|
||||
}
|
||||
elseif( $this->inlineMessage )
|
||||
{
|
||||
$_SESSION['inlineMessage'] = $this->inlineMessage;
|
||||
session_write_close();
|
||||
}
|
||||
|
||||
/* Send location and no-cache headers to prevent redirects from being cached */
|
||||
@@ -903,6 +954,74 @@ class _Output
|
||||
$output = str_replace( '<___base_url___>', rtrim( \IPS\Settings::i()->base_url, '/' ), $output );
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace emoji unicode with images
|
||||
*
|
||||
* @param string $output The output containing emojis as unicode
|
||||
* @return string
|
||||
*/
|
||||
public function replaceEmojiWithImages( $output )
|
||||
{
|
||||
if ( \IPS\Settings::i()->emoji_style == 'twemoji' or \IPS\Settings::i()->emoji_style == 'emojione' )
|
||||
{
|
||||
return preg_replace_callback( '/<span class="ipsEmoji">(.+?)<\/span>/', function( $matches ) {
|
||||
$hex = bin2hex( mb_convert_encoding( $matches[1], 'UTF-32', 'UTF-8' ) );
|
||||
$hexLength = \strlen( $hex ) / 8;
|
||||
$chunks = array();
|
||||
for ( $i = 0; $i < $hexLength; ++$i )
|
||||
{
|
||||
$tmp = \substr( $hex, $i * 8, 8 );
|
||||
|
||||
$copy = false;
|
||||
$len = \strlen( $tmp );
|
||||
$res = '';
|
||||
for ( $j = 0; $j < $len; ++$j )
|
||||
{
|
||||
$ch = $tmp[ $j ];
|
||||
if ( !$copy )
|
||||
{
|
||||
if ( $ch != '0' )
|
||||
{
|
||||
$copy = true;
|
||||
}
|
||||
else if ( ( $i + 1 ) == $len )
|
||||
{
|
||||
$res = '0';
|
||||
}
|
||||
}
|
||||
if ( $copy )
|
||||
{
|
||||
$res .= $ch;
|
||||
}
|
||||
}
|
||||
|
||||
$chunks[ $i ] = $res;
|
||||
}
|
||||
|
||||
if ( \IPS\Settings::i()->emoji_style == 'twemoji' )
|
||||
{
|
||||
$image = implode( '-', $chunks );
|
||||
|
||||
if ( \strstr( $image, '200d' ) === FALSE or $image === '1f441-fe0f-200d-1f5e8-fe0f' )
|
||||
{
|
||||
$image = str_replace( '-fe0f', '', $image );
|
||||
}
|
||||
if ( in_array( $image, array( '0031-20e3', '0030-20e3', '0032-20e3', '0034-20e3', '0035-20e3', '0036-20e3', '0037-20e3', '0038-20e3', '0033-20e3', '0039-20e3', '0023-20e3', '002a-20e3', '00a9', '00ae' ) ) )
|
||||
{
|
||||
$image = str_replace( '00', '', $image );
|
||||
}
|
||||
|
||||
return '<img src="https://twemoji.maxcdn.com/2/72x72/' . $image . '.png" class="ipsEmoji" alt="' . $matches[1] . '">';
|
||||
}
|
||||
else
|
||||
{
|
||||
return '<img src="https://cdn.jsdelivr.net/emojione/assets/3.1/png/64/' . str_replace( array( '-200d', '-fe0f' ), '', implode( '-', $chunks ) ) . '.png" class="ipsEmoji" alt="' . $matches[1] . '">';
|
||||
}
|
||||
}, $output );
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show Offline
|
||||
*
|
||||
@@ -1183,7 +1302,7 @@ class _Output
|
||||
'position' => $position,
|
||||
'item' => array(
|
||||
'@id' => (string) $breadcrumb[0],
|
||||
'name' => htmlspecialchars( $breadcrumb[1], ENT_QUOTES | \IPS\HTMLENTITIES, 'UTF-8', FALSE ),
|
||||
'name' => $breadcrumb[1],
|
||||
)
|
||||
);
|
||||
|
||||
@@ -1227,10 +1346,35 @@ class _Output
|
||||
return 'expired';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Days remaining before license expires
|
||||
*
|
||||
* @return int|NULL NULL or the number of days remaining
|
||||
*/
|
||||
public function licenseKeyDaysRemaining()
|
||||
{
|
||||
$licenseData = \IPS\IPS::licenseKey();
|
||||
if( !isset( $licenseData['expires'] ) OR !$licenseData['expires'] )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Work out days remaining, we can't use ->days here because it doesn't differentiate between postive and negative */
|
||||
$daysLeft = (int) (new \IPS\DateTime)->diff( \IPS\DateTime::ts( strtotime( $licenseData['expires'] ) ) )->format('%r%a');
|
||||
|
||||
/* Return zero if the days remaining is negative */
|
||||
if( $daysLeft < 0 )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return $daysLeft;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Global search menu options
|
||||
*/
|
||||
|
||||
@@ -42,7 +42,7 @@ class _Expression
|
||||
}
|
||||
else
|
||||
{
|
||||
return 'htmlspecialchars( ' . \IPS\Theme::expandShortcuts( $data ) . ', ENT_QUOTES | \IPS\HTMLENTITIES, \'UTF-8\', FALSE )';
|
||||
return 'htmlspecialchars( ' . \IPS\Theme::expandShortcuts( $data ) . ', ENT_QUOTES | ENT_DISALLOWED, \'UTF-8\', FALSE )';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -74,7 +74,7 @@ class _Hextorgb
|
||||
/* Basic validation */
|
||||
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 | \IPS\HTMLENTITIES, 'UTF-8', FALSE )";
|
||||
return "htmlspecialchars( '" . $data . "', ENT_QUOTES | ENT_DISALLOWED, 'UTF-8', FALSE )";
|
||||
}
|
||||
|
||||
$data = str_replace( '#', '', $data );
|
||||
|
||||
@@ -109,7 +109,7 @@ class _Lang
|
||||
$vle = 'FALSE';
|
||||
}
|
||||
|
||||
$return['return'] = '\IPS\Member::loggedIn()->language()->addToStack( htmlspecialchars( ' . $data . ', \IPS\HTMLENTITIES, \'UTF-8\', FALSE ), ' . $vle . ', array( ' . implode( ", ", $params ) . ' ) )';
|
||||
$return['return'] = '\IPS\Member::loggedIn()->language()->addToStack( htmlspecialchars( ' . $data . ', ENT_DISALLOWED, \'UTF-8\', FALSE ), ' . $vle . ', array( ' . implode( ", ", $params ) . ' ) )';
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ class _Member
|
||||
}
|
||||
else
|
||||
{
|
||||
return "htmlspecialchars( {$member}->$data, ENT_QUOTES | \IPS\HTMLENTITIES, 'UTF-8', FALSE )";
|
||||
return "htmlspecialchars( {$member}->$data, ENT_QUOTES | ENT_DISALLOWED, 'UTF-8', FALSE )";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,7 @@ class _Request
|
||||
}
|
||||
else
|
||||
{
|
||||
return "htmlspecialchars( \IPS\Request::i()->$data, ENT_QUOTES | \IPS\HTMLENTITIES, 'UTF-8', FALSE )";
|
||||
return "htmlspecialchars( \IPS\Request::i()->$data, ENT_QUOTES | ENT_DISALLOWED, 'UTF-8', FALSE )";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ class _Setting
|
||||
{
|
||||
if ( isset( $options['escape'] ) )
|
||||
{
|
||||
return "htmlspecialchars( \IPS\Settings::i()->$data, ENT_QUOTES | \IPS\HTMLENTITIES, 'UTF-8', TRUE )";
|
||||
return "htmlspecialchars( \IPS\Settings::i()->$data, ENT_QUOTES | ENT_DISALLOWED, 'UTF-8', TRUE )";
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -47,6 +47,6 @@ class _Truncate
|
||||
$_argument = ( mb_strpos( $data, '$' ) !== FALSE ) ? $data : '"' . $data . '"';
|
||||
$_length = ( mb_strpos( $options['length'], '$' ) !== FALSE ) ? $options['length'] : '"' . $options['length'] . '"';
|
||||
|
||||
return "htmlspecialchars( mb_substr( html_entity_decode( {$_argument} ), '{$options['start']}', {$_length} ), ENT_QUOTES | \IPS\HTMLENTITIES, 'UTF-8', FALSE ) . ( ( mb_strlen( html_entity_decode( {$_argument} ) ) > {$_length} ) ? '{$options['append']}' : '' )";
|
||||
return "htmlspecialchars( mb_substr( html_entity_decode( {$_argument} ), '{$options['start']}', {$_length} ), ENT_QUOTES | ENT_DISALLOWED, 'UTF-8', FALSE ) . ( ( mb_strlen( html_entity_decode( {$_argument} ) ) > {$_length} ) ? '{$options['append']}' : '' )";
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,6 @@ class _Wordbreak
|
||||
*/
|
||||
public static function runPlugin( $data, $options )
|
||||
{
|
||||
return "\IPS\Lang::wordbreak( htmlspecialchars( {$data}, ENT_QUOTES | \IPS\HTMLENTITIES, 'UTF-8', FALSE ) )";
|
||||
return "\IPS\Lang::wordbreak( htmlspecialchars( {$data}, ENT_QUOTES | ENT_DISALLOWED, 'UTF-8', FALSE ) )";
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user