Version 5.0.0 release
This commit is contained in:
1 parent
90e8c7e903
commit
a3a4a9ccbd
651 files changed
+7275
-9481
No files matched your search
@@ -781,6 +781,9 @@ class ajax extends Controller
|
||||
/* Don't cache the CSRF key */
|
||||
Output::setCacheTime( false );
|
||||
|
||||
/* Restrict endpoint to our origin JS */
|
||||
Output::i()->httpHeaders['Access-Control-Allow-Origin'] = Url::internal('')->data[ Url::COMPONENT_SCHEME ] . '://' . Url::internal('')->data[ Url::COMPONENT_HOST ];
|
||||
|
||||
if ( isset( Request::i()->path ) )
|
||||
{
|
||||
$baseUrlData = parse_url( Url::baseUrl() );
|
||||
@@ -811,7 +814,8 @@ class ajax extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
Output::i()->json( [ 'key' => Session::i()->csrfKey ] );
|
||||
|
||||
Output::i()->json( [ 'key' => Session::i()->csrfKey, 'expiry' => time() + 500 ] );
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -19,10 +19,13 @@ use IPS\Member;
|
||||
use IPS\Output;
|
||||
use IPS\Settings;
|
||||
use IPS\Theme;
|
||||
use IPS\Xml\DOMDocument;
|
||||
use function count;
|
||||
use function defined;
|
||||
use const IPS\DEBUG_JS;
|
||||
use const IPS\DEV_DEBUG_JS;
|
||||
use const IPS\IN_DEV;
|
||||
use const IPS\ROOT_PATH;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
@@ -43,7 +46,6 @@ class serviceworker extends Controller
|
||||
protected function manage() : void
|
||||
{
|
||||
$cachedUrls = array();
|
||||
$cachedUrls[] = (string) Url::internal("app=core&module=system&controller=offline", "front", "user_offline");
|
||||
|
||||
$notificationIcon = NULL;
|
||||
|
||||
@@ -63,31 +65,57 @@ class serviceworker extends Controller
|
||||
}
|
||||
|
||||
/* VARIABLES TO PASS THROUGH TO JS */
|
||||
$DEBUG = ( ( \IPS\IN_DEV and DEV_DEBUG_JS ) or DEBUG_JS ) ? 'true' : 'false'; // Weird casting is intentional.
|
||||
$BASE_URL = Settings::i()->base_url;
|
||||
$CACHED_ASSETS = json_encode( $cachedUrls, JSON_UNESCAPED_SLASHES );
|
||||
$OFFLINE_URL = (string) Url::internal("app=core&module=system&controller=offline", "front", "user_offline");
|
||||
$CACHE_VERSION = Theme::i()->cssCacheBustKey();
|
||||
$NOTIFICATION_ICON = $notificationIcon ? "\"{$notificationIcon}\"" : 'null'; // Weird casting is intentional. 'null' will become literal null in JS file.
|
||||
$DEFAULT_NOTIFICATION_TITLE = Member::loggedIn()->language()->addToStack('default_notification_title');
|
||||
$DEFAULT_NOTIFICATION_BODY = Member::loggedIn()->language()->addToStack('default_notification_body');
|
||||
$variables = [
|
||||
"DEBUG" => boolval( ( IN_DEV and DEV_DEBUG_JS ) or DEBUG_JS ),
|
||||
"BASE_URL" => Settings::i()->base_url,
|
||||
"CACHED_ASSETS" => $cachedUrls,
|
||||
"CACHE_NAME" => "invision-community-{$CACHE_VERSION}",
|
||||
"CACHE_VERSION" => $CACHE_VERSION,
|
||||
"NOTIFICATION_ICON" => $notificationIcon ?: null,
|
||||
"DEFAULT_NOTIFICATION_TITLE" => Member::loggedIn()->language()->addToStack( 'default_notification_title'),
|
||||
"DEFAULT_NOTIFICATION_BODY" => Member::loggedIn()->language()->addToStack( 'default_notification_body'),
|
||||
"OFFLINE_PAGE" => $this->buildCollapsedOfflinePage(),
|
||||
];
|
||||
|
||||
$output = <<<JAVASCRIPT
|
||||
"use strict";
|
||||
const DEBUG = {$DEBUG};
|
||||
const BASE_URL = "{$BASE_URL}";
|
||||
const CACHED_ASSETS = {$CACHED_ASSETS};
|
||||
const CACHE_NAME = 'invision-community-{$CACHE_VERSION}';
|
||||
const OFFLINE_URL = "{$OFFLINE_URL}";
|
||||
const NOTIFICATION_ICON = {$NOTIFICATION_ICON};
|
||||
const DEFAULT_NOTIFICATION_TITLE = "{$DEFAULT_NOTIFICATION_TITLE}";
|
||||
const DEFAULT_NOTIFICATION_BODY = "{$DEFAULT_NOTIFICATION_BODY}";
|
||||
$variables["OFFLINE_PAGE_SIZE"] = \strlen( $variables["OFFLINE_PAGE"] );
|
||||
|
||||
$output = "\"use strict;\"\n\n";
|
||||
foreach ( $variables as $var => $value )
|
||||
{
|
||||
$toEncode = json_encode( $value, JSON_UNESCAPED_SLASHES );
|
||||
$output .= <<<JAVASCRIPT
|
||||
const {$var} = {$toEncode};
|
||||
|
||||
JAVASCRIPT;
|
||||
|
||||
}
|
||||
|
||||
|
||||
Member::loggedIn()->language()->parseOutputForDisplay( $output );
|
||||
$output .= file_get_contents( \IPS\ROOT_PATH . '/applications/core/interface/js/serviceWorker.js' );
|
||||
$cacheHeaders = !\IPS\IN_DEV ? Output::getCacheHeaders(time(), 86400) : array();
|
||||
$output .= file_get_contents( ROOT_PATH . '/applications/core/interface/js/serviceWorker.js' );
|
||||
$cacheHeaders = !IN_DEV ? Output::getCacheHeaders(time(), 86400) : array();
|
||||
Output::i()->sendOutput($output, 200, 'text/javascript', $cacheHeaders);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return template for offline page
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function buildCollapsedOfflinePage() : string
|
||||
{
|
||||
$html = Theme::i()->getTemplate( 'global', 'core', 'global' )->offline();
|
||||
Member::loggedIn()->language()->parseOutputForDisplay( $html );
|
||||
|
||||
$doc = new DOMDocument( "2.0", "utf-8" );
|
||||
$doc->preserveWhiteSpace = false;
|
||||
$doc->loadHTML( $html, LIBXML_NOBLANKS );
|
||||
$doc->formatOutput = true;
|
||||
$compact = $doc->saveHTML();
|
||||
|
||||
// We don't need these segments of whitespace. HTML entities can be used if it's absolutely necessary
|
||||
return preg_replace( "/\s+/", " ", $compact );
|
||||
}
|
||||
}
|
||||
@@ -132,6 +132,53 @@ class themeeditor extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
/* Handle image settings (except logos) */
|
||||
foreach( Db::i()->select( 'setting_key', 'core_theme_editor_settings', [
|
||||
[ 'setting_type=?', Theme\Editor\Setting::SETTING_IMAGE ],
|
||||
//[ Db::i()->in( 'setting_key', [ 'set__logo-light', 'set__logo-dark', 'set__mobile-logo-light', 'set__mobile-logo-dark' ], true ) ]
|
||||
] ) as $key )
|
||||
{
|
||||
/* Did we delete the file? */
|
||||
if( isset( Request::i()->{ 'delete__' . $key } ) and empty( Request::i()->$key ) )
|
||||
{
|
||||
$newValue = '';
|
||||
}
|
||||
elseif ( isset( $_FILES[ $key ] ) and $_FILES[ $key ]['tmp_name'] )
|
||||
{
|
||||
$imageData = $this->_handleImageUpload( $key, false );
|
||||
|
||||
/* If we specifically set the value to null, then we will use the default value */
|
||||
$newValue = isset( $imageData['fullUrl'] ) ? (string) $imageData['fullUrl'] : null;
|
||||
}
|
||||
else
|
||||
{
|
||||
$newValue = $changed[ $key ] ?? null;
|
||||
}
|
||||
|
||||
$cssVariables[ $key ] = $newValue;
|
||||
|
||||
/* Only delete the file if it was not the default! */
|
||||
if( empty( $newValue ) and !empty( $changed[ $key ] ) )
|
||||
{
|
||||
File::get( 'core_Theme', $changed[$key] )->delete();
|
||||
}
|
||||
|
||||
if ( $newValue !== null and $newValue !== $rootCssVariables[ $key ] )
|
||||
{
|
||||
$changed[ $key ] = $newValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* If we previously changed the value and we changed it back, clear it */
|
||||
if( array_key_exists( $key, $changed ) )
|
||||
{
|
||||
unset( $changed[ $key ] );
|
||||
}
|
||||
|
||||
$unchanged[ $key ] = $rootCssVariables[ $key ];
|
||||
}
|
||||
}
|
||||
|
||||
/* Save the css */
|
||||
Theme::i()->setCssVariables( $changed );
|
||||
|
||||
@@ -173,64 +220,9 @@ class themeeditor extends Controller
|
||||
File::get( 'core_Theme', Theme::i()->logo[ $logoKey ]['url'] )->delete();
|
||||
$data['logo'][$logoKey] = [];
|
||||
}
|
||||
else
|
||||
elseif ( isset( $_FILES[ 'set__' . $type ] ) and $_FILES[ 'set__' . $type ]['tmp_name'] )
|
||||
{
|
||||
if ( isset( $_FILES['set__' . $type] ) and $_FILES['set__' . $type]['tmp_name'] )
|
||||
{
|
||||
$file = $_FILES['set__' . $type];
|
||||
$fileExt = strtolower( pathinfo( $file['name'], PATHINFO_EXTENSION ) );
|
||||
$imageAttributes = getimagesize( $file['tmp_name'] );
|
||||
$logoHeightSetting = 'set__i-logo--he';
|
||||
if ( $fileExt === 'svg' or in_array( $imageAttributes[2], [IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG, IMAGETYPE_WEBP, IMAGETYPE_AVIF] ) )
|
||||
{
|
||||
/* So we can use the File::create() obfuscation, but it will not re-add the file extension back on for SVG, so... */
|
||||
$baseName = pathinfo( $file['name'], PATHINFO_FILENAME );
|
||||
$extension = pathinfo( $file['name'], PATHINFO_EXTENSION );
|
||||
$randomKey = substr(bin2hex( random_bytes(3) ), 0, 6); // Produces a 6-character long key
|
||||
$uniqueFilename = "{$baseName}_{$randomKey}.{$extension}";
|
||||
|
||||
try
|
||||
{
|
||||
if ( $fileExt !== 'svg' )
|
||||
{
|
||||
$image = Image::create( file_get_contents( $file['tmp_name'] ), false );
|
||||
$width = $image->width;
|
||||
$height = $image->height;
|
||||
}
|
||||
else
|
||||
{
|
||||
$image = file_get_contents( $file['tmp_name'] );
|
||||
$width = null;
|
||||
$height = null;
|
||||
|
||||
try
|
||||
{
|
||||
/* I tried many fancy methods including XML parsing but they all failed, but regex will always work becaue it is the best thing in the world */
|
||||
preg_match( '#<image(?:.+?)width="(\d+)"\s+height="(\d+)"#i', $image, $matches );
|
||||
if ( isset( $matches[1] ) and isset( $matches[2] ) )
|
||||
{
|
||||
$width = $matches[1];
|
||||
$height = $matches[2];
|
||||
}
|
||||
}
|
||||
catch ( Exception $e ) { }
|
||||
}
|
||||
|
||||
$file = File::create( 'core_Theme', $uniqueFilename, $image, null, true, null, false );
|
||||
|
||||
$data['logo'][$logoKey] = [
|
||||
'url' => (string)$file,
|
||||
'setting_height' => isset( Request::i()->$logoHeightSetting ) ? Request::i()->$logoHeightSetting : 100,
|
||||
'img_width' => $width,
|
||||
'img_height' => $height,
|
||||
];
|
||||
}
|
||||
catch ( RuntimeException $e )
|
||||
{
|
||||
$data['logo'][$logoKey] = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
$data['logo'][ $logoKey ] = $this->_handleImageUpload( 'set__' . $type );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,6 +273,73 @@ class themeeditor extends Controller
|
||||
Output::i()->redirect( Url::internal( "" ), ( \IPS\IN_DEV and ! Settings::i()->theme_designer_mode ) ? 'theme_editor_saved_indev' : 'theme_editor_saved' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle uploads in image settings
|
||||
*
|
||||
* @param string $key
|
||||
* @param bool $isLogo
|
||||
* @return array
|
||||
*/
|
||||
protected function _handleImageUpload( string $key, bool $isLogo=true ) : array
|
||||
{
|
||||
if ( isset( $_FILES[ $key ] ) and $_FILES[ $key ]['tmp_name'] )
|
||||
{
|
||||
$file = $_FILES[ $key ];
|
||||
$fileExt = strtolower( pathinfo( $file['name'], PATHINFO_EXTENSION ) );
|
||||
$imageAttributes = getimagesize( $file['tmp_name'] );
|
||||
$logoHeightSetting = 'set__i-logo--he';
|
||||
if ( $fileExt === 'svg' or in_array( $imageAttributes[2], [IMAGETYPE_GIF, IMAGETYPE_JPEG, IMAGETYPE_PNG, IMAGETYPE_WEBP, IMAGETYPE_AVIF] ) )
|
||||
{
|
||||
/* So we can use the File::create() obfuscation, but it will not re-add the file extension back on for SVG, so... */
|
||||
$baseName = pathinfo( $file['name'], PATHINFO_FILENAME );
|
||||
$extension = pathinfo( $file['name'], PATHINFO_EXTENSION );
|
||||
$randomKey = substr(bin2hex( random_bytes(3) ), 0, 6); // Produces a 6-character long key
|
||||
$uniqueFilename = "{$baseName}_{$randomKey}.{$extension}";
|
||||
|
||||
try
|
||||
{
|
||||
if ( $fileExt !== 'svg' )
|
||||
{
|
||||
$image = Image::create( file_get_contents( $file['tmp_name'] ), false );
|
||||
$width = $image->width;
|
||||
$height = $image->height;
|
||||
}
|
||||
else
|
||||
{
|
||||
$image = file_get_contents( $file['tmp_name'] );
|
||||
$width = null;
|
||||
$height = null;
|
||||
|
||||
try
|
||||
{
|
||||
/* I tried many fancy methods including XML parsing, but they all failed, but regex will always work becaue it is the best thing in the world */
|
||||
preg_match( '#<image(?:.+?)width="(\d+)"\s+height="(\d+)"#i', $image, $matches );
|
||||
if ( isset( $matches[1] ) and isset( $matches[2] ) )
|
||||
{
|
||||
$width = $matches[1];
|
||||
$height = $matches[2];
|
||||
}
|
||||
}
|
||||
catch ( Exception $e ) { }
|
||||
}
|
||||
|
||||
$file = File::create( 'core_Theme', $uniqueFilename, $image, null, true, null, false );
|
||||
|
||||
return [
|
||||
'url' => (string) $file,
|
||||
'fullUrl' => (string) $file->url,
|
||||
'setting_height' => ( isset( Request::i()->$logoHeightSetting ) and $isLogo ) ? Request::i()->$logoHeightSetting : 100,
|
||||
'img_width' => $width,
|
||||
'img_height' => $height,
|
||||
];
|
||||
}
|
||||
catch ( RuntimeException $e ){}
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the theme editor
|
||||
*
|
||||
@@ -296,4 +355,23 @@ class themeeditor extends Controller
|
||||
|
||||
Output::i()->redirect( $redirect );
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse Custom CSS so that we can properly handle any
|
||||
* resource tags, etc
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function customCss() : void
|
||||
{
|
||||
$functionName = "css_" . uniqid();
|
||||
Theme::makeProcessFunction( Theme::fixResourceTags( (string) Request::i()->content, 'front' ), $functionName, '', FALSE, TRUE );
|
||||
|
||||
$fqFunc = 'IPS\\Theme\\'. $functionName;
|
||||
$content = Theme::minifyCss( $fqFunc() );
|
||||
|
||||
/* Replace any <fileStore.xxx> tags in the CSS */
|
||||
Output::i()->parseFileObjectUrls( $content );
|
||||
Output::i()->json( [ 'content' => $content ] );
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user