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

@@ -12,30 +12,61 @@
namespace IPS\core\modules\admin\settings;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use Exception;
use IPS\Data\Store;
use IPS\Dispatcher;
use IPS\Dispatcher\Controller;
use IPS\File;
use IPS\Helpers\Form;
use IPS\Helpers\Form\Color;
use IPS\Helpers\Form\Radio;
use IPS\Helpers\Form\Text;
use IPS\Helpers\Form\TextArea;
use IPS\Helpers\Form\Upload;
use IPS\Helpers\Form\YesNo;
use IPS\Helpers\MultipleRedirect;
use IPS\Http\Url;
use IPS\Http\Url\Friendly;
use IPS\Image;
use IPS\Log;
use IPS\Member;
use IPS\Output;
use IPS\Request;
use IPS\Session;
use IPS\Settings;
use IPS\Theme;
use LogicException;
use function count;
use function defined;
use function mb_strstr;
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;
}
/**
* webapp
*/
class _webapp extends \IPS\Dispatcher\Controller
class webapp extends Controller
{
/**
* @brief Has been CSRF-protected
*/
public static $csrfProtected = TRUE;
public static bool $csrfProtected = TRUE;
/**
* Execute
*
* @return void
*/
public function execute()
public function execute() : void
{
\IPS\Dispatcher::i()->checkAcpPermission( 'webapp_manage' );
Dispatcher::i()->checkAcpPermission( 'webapp_manage' );
parent::execute();
}
@@ -44,74 +75,78 @@ class _webapp extends \IPS\Dispatcher\Controller
*
* @return void
*/
protected function manage()
protected function manage() : void
{
$form = new \IPS\Helpers\Form;
$form = new Form;
$form->addTab( 'webapp_tab_icons' );
/* Homescreen icons - we accept one upload and create the images we need */
$homeScreen = json_decode( \IPS\Settings::i()->icons_homescreen, TRUE ) ?? array();
$form->add( new \IPS\Helpers\Form\Upload( 'icons_homescreen', ( isset( $homeScreen['original'] ) ) ? \IPS\File::get( 'core_Icons', $homeScreen['original'] ) : NULL, FALSE, array( 'image' => true, 'storageExtension' => 'core_Icons' ) ) );
$homeScreen = json_decode( Settings::i()->icons_homescreen, TRUE ) ?? array();
$form->add( new Upload( 'icons_homescreen', ( isset( $homeScreen['original'] ) ) ? File::get( 'core_Icons', $homeScreen['original'] ) : NULL, FALSE, array( 'image' => true, 'storageExtension' => 'core_Icons' ) ) );
$homeScreenMaskable = json_decode( \IPS\Settings::i()->icons_homescreen_maskable, TRUE ) ?? array();
$form->add( new \IPS\Helpers\Form\Upload( 'icons_homescreen_maskable', ( isset( $homeScreenMaskable['original'] ) ) ? \IPS\File::get( 'core_Icons', $homeScreenMaskable['original'] ) : NULL, FALSE, array( 'image' => true, 'storageExtension' => 'core_Icons', 'allowedFileTypes' => ['png', 'webp' ] ) ) );
$homeScreenMaskable = json_decode( Settings::i()->icons_homescreen_maskable, TRUE ) ?? array();
$form->add( new Upload( 'icons_homescreen_maskable', ( isset( $homeScreenMaskable['original'] ) ) ? File::get( 'core_Icons', $homeScreenMaskable['original'] ) : NULL, FALSE, array( 'image' => true, 'storageExtension' => 'core_Icons', 'allowedFileTypes' => ['png', 'webp' ] ) ) );
/* Apple startup screen logo - we accept one upload and create the images we need */
$apple = json_decode( \IPS\Settings::i()->icons_apple_startup, TRUE ) ?? array();
$form->add( new \IPS\Helpers\Form\Upload( 'icons_apple_startup', ( isset( $apple['original'] ) ) ? \IPS\File::get( 'core_Icons', $apple['original'] ) : NULL, FALSE, array( 'image' => true, 'storageExtension' => 'core_Icons' ) ) );
$apple = json_decode( Settings::i()->icons_apple_startup, TRUE ) ?? array();
$form->add( new Upload( 'icons_apple_startup', ( isset( $apple['original'] ) ) ? File::get( 'core_Icons', $apple['original'] ) : NULL, FALSE, array( 'image' => true, 'storageExtension' => 'core_Icons' ) ) );
/* Safari pinned tabs icon and highlight color */
$form->add( new \IPS\Helpers\Form\Upload( 'icons_mask_icon', \IPS\Settings::i()->icons_mask_icon ? \IPS\File::get( 'core_Icons', \IPS\Settings::i()->icons_mask_icon ) : NULL, FALSE, array( 'allowedFileTypes' => array( 'svg' ), 'storageExtension' => 'core_Icons', 'obscure' => FALSE ) ) );
$form->add( new \IPS\Helpers\Form\Color( 'icons_mask_color', \IPS\Settings::i()->icons_mask_color, FALSE ) );
$form->add( new Upload( 'icons_mask_icon', Settings::i()->icons_mask_icon ? File::get( 'core_Icons', Settings::i()->icons_mask_icon ) : NULL, FALSE, array( 'allowedFileTypes' => array( 'svg' ), 'storageExtension' => 'core_Icons', 'obscure' => FALSE ) ) );
$form->add( new Color( 'icons_mask_color', Settings::i()->icons_mask_color, FALSE ) );
$form->addTab( 'webapp_tab_manifest' );
/* And finally, additional manifest and livetile details */
$manifestDetails = json_decode( \IPS\Settings::i()->manifest_details, TRUE );
$manifestDetails = json_decode( Settings::i()->manifest_details, TRUE );
if ( isset( $manifestDetails['cache_key'] ) )
{
unset( $manifestDetails['cache_key'] );
}
$form->add( new \IPS\Helpers\Form\YesNo( 'configure_manifest', \count( $manifestDetails ) > 0, FALSE, array(
$form->add( new YesNo( 'configure_manifest', count( $manifestDetails ) > 0, FALSE, array(
'togglesOn' => array( 'manifest_shortname', 'manifest_fullname', 'manifest_description', 'manifest_defaultapp', 'manifest_themecolor', 'manifest_bgcolor', 'manifest_display', 'manifest_custom_url_toggle' ),
) ) );
$form->add( new \IPS\Helpers\Form\Text( 'manifest_shortname', ( isset( $manifestDetails['short_name'] ) ) ? $manifestDetails['short_name'] : '', FALSE, array(), NULL, NULL, NULL, 'manifest_shortname' ) );
$form->add( new \IPS\Helpers\Form\Text( 'manifest_fullname', ( isset( $manifestDetails['name'] ) ) ? $manifestDetails['name'] : '', FALSE, array(), NULL, NULL, NULL, 'manifest_fullname' ) );
$form->add( new \IPS\Helpers\Form\TextArea( 'manifest_description', ( isset( $manifestDetails['description'] ) ) ? $manifestDetails['description'] : '', FALSE, array(), NULL, NULL, NULL, 'manifest_description' ) );
$form->add( new Text( 'manifest_shortname', ( isset( $manifestDetails['short_name'] ) ) ? $manifestDetails['short_name'] : '', FALSE, array(), NULL, NULL, NULL, 'manifest_shortname' ) );
$form->add( new Text( 'manifest_fullname', ( isset( $manifestDetails['name'] ) ) ? $manifestDetails['name'] : '', FALSE, array(), NULL, NULL, NULL, 'manifest_fullname' ) );
$form->add( new TextArea( 'manifest_description', ( isset( $manifestDetails['description'] ) ) ? $manifestDetails['description'] : '', FALSE, array(), NULL, NULL, NULL, 'manifest_description' ) );
$formStartUrl = '';
if( isset( $manifestDetails['start_url'] ) )
{
$formStartUrl = str_replace( 'index.php?/', '', \IPS\Http\Url\Friendly::fixComponentPath( $manifestDetails['start_url'] ) );
$formStartUrl = str_replace( 'index.php?/', '', Friendly::fixComponentPath( $manifestDetails['start_url'] ) );
}
$form->add( new \IPS\Helpers\Form\YesNo( 'manifest_custom_url_toggle', ( isset( $manifestDetails['start_url'] ) and empty( $formStartUrl ) ? FALSE : TRUE ), FALSE, array(
$form->add( new YesNo( 'manifest_custom_url_toggle', ( isset( $manifestDetails['start_url'] ) and !empty( $formStartUrl ) ), FALSE, array(
'togglesOn' => array( 'manifest_short_url' ),
), NULL, NULL, NULL, 'manifest_custom_url_toggle' ) );
$form->add( new \IPS\Helpers\Form\Text( 'manifest_short_url', $formStartUrl, FALSE, array(), function( $val )
$form->add( new Text( 'manifest_short_url', $formStartUrl, FALSE, array(), function( $val )
{
if ( $val and \IPS\Request::i()->manifest_custom_url_toggle_checkbox )
if ( $val and Request::i()->manifest_custom_url_toggle_checkbox )
{
if ( mb_substr( $val, -1 ) !== '/' )
{
$val .= '/';
}
$response = \IPS\Http\Url::external( \IPS\Http\Url::baseUrl() . ( \IPS\Settings::i()->htaccess_mod_rewrite ? $val : 'index.php?/' . $val ) )->request( NULL, NULL, FALSE )->get();
if ( $response->httpResponseCode != 200 and $response->httpResponseCode != 303 and ( \IPS\Settings::i()->site_online OR $response->httpResponseCode != 503 ) )
$response = Url::external( Url::baseUrl() . ( Settings::i()->htaccess_mod_rewrite ? $val : 'index.php?/' . $val ) )->request( NULL, NULL, FALSE )->get();
if ( $response->httpResponseCode != 200 and $response->httpResponseCode != 303 and ( Settings::i()->site_online OR $response->httpResponseCode != 503 ) )
{
throw new \LogicException( 'pwa_start_url_incorrect' );
throw new LogicException( 'pwa_start_url_incorrect' );
}
}
}, \IPS\Http\Url::baseUrl() . ( !\IPS\Settings::i()->htaccess_mod_rewrite ? 'index.php?/' : '' ), NULL, 'manifest_short_url' ) );
}, Url::baseUrl() . ( !Settings::i()->htaccess_mod_rewrite ? 'index.php?/' : '' ), NULL, 'manifest_short_url' ) );
$form->add( new \IPS\Helpers\Form\Color( 'manifest_themecolor', ( isset( $manifestDetails['theme_color'] ) ) ? $manifestDetails['theme_color'] : NULL, FALSE, array(), NULL, NULL, NULL, 'manifest_themecolor' ) );
$form->add( new \IPS\Helpers\Form\Color( 'manifest_bgcolor', ( isset( $manifestDetails['background_color'] ) ) ? $manifestDetails['background_color'] : NULL, FALSE, array(), NULL, NULL, NULL, 'manifest_bgcolor' ) );
$form->add( new \IPS\Helpers\Form\Radio( 'manifest_display', ( isset( $manifestDetails['display'] ) ) ? $manifestDetails['display'] : 'standalone', FALSE, array( 'options' => array( 'fullscreen' => 'manifest_fullscreen', 'standalone' => 'manifest_standalone', 'minimal-ui' => 'manifest_minimalui', 'browser' => 'manifest_browser' ) ), NULL, NULL, NULL, 'manifest_display' ) );
$form->add( new Color( 'manifest_themecolor', ( isset( $manifestDetails['theme_color'] ) ) ? $manifestDetails['theme_color'] : NULL, FALSE, array( 'allowNoneLanguage' => 'manifest_themecolor_none', 'allowNone' => true ), NULL, NULL, NULL, 'manifest_themecolor' ) );
$form->add( new Color( 'manifest_bgcolor', ( isset( $manifestDetails['background_color'] ) ) ? $manifestDetails['background_color'] : NULL, FALSE, array(), NULL, NULL, NULL, 'manifest_bgcolor' ) );
$form->add( new Radio( 'manifest_display', ( isset( $manifestDetails['display'] ) ) ? $manifestDetails['display'] : 'standalone', FALSE, array( 'options' => array( 'fullscreen' => 'manifest_fullscreen', 'standalone' => 'manifest_standalone', 'minimal-ui' => 'manifest_minimalui', 'browser' => 'manifest_browser' ) ), NULL, NULL, NULL, 'manifest_display' ) );
if( $values = $form->values() )
{
$path = \IPS\Http\Url::createFromString( \IPS\Http\Url::baseUrl() )->data[ \IPS\Http\Url::COMPONENT_PATH ];
$path = Url::createFromString( Url::baseUrl() )->data[ Url::COMPONENT_PATH ];
$startUrl = $path ?? '';
if ( $values['manifest_custom_url_toggle'] !== FALSE and ! empty( $values['manifest_short_url'] ) )
@@ -120,7 +155,7 @@ class _webapp extends \IPS\Dispatcher\Controller
if( !empty( $path ) )
{
$startUrl = '/' . trim( $path . ( !\IPS\Settings::i()->htaccess_mod_rewrite ? 'index.php?/' : '' ) . ltrim( $values['manifest_short_url'], '/' ), '/' ) . '/';
$startUrl = '/' . trim( $path . ( !Settings::i()->htaccess_mod_rewrite ? 'index.php?/' : '' ) . ltrim( $values['manifest_short_url'], '/' ), '/' ) . '/';
}
}
@@ -169,28 +204,25 @@ class _webapp extends \IPS\Dispatcher\Controller
/* Save the settings */
$form->saveAsSettings( $values );
/* Clear guest page caches */
\IPS\Data\Cache::i()->clearAll();
/* Clear manifest and ie browser data stores */
unset( \IPS\Data\Store::i()->manifest, \IPS\Data\Store::i()->iebrowserconfig );
unset( Store::i()->manifest, Store::i()->iebrowserconfig );
/* And log */
\IPS\Session::i()->log( 'acplogs__webapp' );
Session::i()->log( 'acplogs__webapp' );
/* And Redirect */
if( $rebuildStartScreen === TRUE )
{
\IPS\Output::i()->redirect( $this->url->setQueryString( 'do', 'buildStartupScreenImages' ), 'saved' );
Output::i()->redirect( $this->url->setQueryString( 'do', 'buildStartupScreenImages' ), 'saved' );
}
else
{
\IPS\Output::i()->redirect( $this->url, 'saved' );
Output::i()->redirect( $this->url, 'saved' );
}
}
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('menu__core_settings_webapp');
\IPS\Output::i()->output .= \IPS\Theme::i()->getTemplate( 'global' )->block( 'menu__core_settings_webapp', $form );
Output::i()->title = Member::loggedIn()->language()->addToStack('menu__core_settings_webapp');
Output::i()->output .= Theme::i()->getTemplate( 'global' )->block( 'menu__core_settings_webapp', $form );
}
/**
@@ -201,7 +233,7 @@ class _webapp extends \IPS\Dispatcher\Controller
* @param array $homeScreenMaskable Existing values, if any
* @return array
*/
public static function processApplicationIcon( $values, $homeScreen = array(), $homeScreenMaskable = array() )
public static function processApplicationIcon( array $values, array $homeScreen = array(), array $homeScreenMaskable = array() ) : array
{
if( ( isset( $values['icons_homescreen'] ) AND $values['icons_homescreen'] ) or ( isset( $values['icons_homescreen_maskable' ] ) AND $values['icons_homescreen_maskable'] ) )
{
@@ -244,14 +276,14 @@ class _webapp extends \IPS\Dispatcher\Controller
{
$filename .= "-masked";
if ( ! \mb_strstr( $filename, 'android-chrome' ) )
if ( ! mb_strstr( $filename, 'android-chrome' ) )
{
continue;
}
}
try
{
$image = \IPS\Image::create( $values[ $type ]->contents() );
$image = Image::create( $values[ $type ]->contents() );
if ( $image::exifSupported() )
{
@@ -261,12 +293,12 @@ class _webapp extends \IPS\Dispatcher\Controller
$image->crop( $_sizes[0], $_sizes[1] );
$setting[$filename] = array(
'url' => (string)\IPS\File::create( 'core_Icons', $filename . '.png', (string)$image, NULL, TRUE, NULL, FALSE ),
'url' => (string)File::create( 'core_Icons', $filename . '.png', (string)$image, NULL, TRUE, NULL, FALSE ),
'width' => $image->width,
'height' => $image->height
);
}
catch ( \Exception $e )
catch ( Exception $e )
{
}
}
@@ -283,18 +315,18 @@ class _webapp extends \IPS\Dispatcher\Controller
{
try
{
\IPS\File::get( 'core_Icons', ( $key == 'original' ) ? $image : $image['url'] )->delete();
File::get( 'core_Icons', ( $key == 'original' ) ? $image : $image['url'] )->delete();
}
catch( \Exception $e ){}
catch( Exception $e ){}
}
foreach( $homeScreenMaskable as $key => $image )
{
try
{
\IPS\File::get( 'core_Icons', ( $key == 'original' ) ? $image : $image['url'] )->delete();
File::get( 'core_Icons', ( $key == 'original' ) ? $image : $image['url'] )->delete();
}
catch( \Exception $e ){}
catch( Exception $e ){}
}
$values['icons_homescreen'] = '';
@@ -315,7 +347,7 @@ class _webapp extends \IPS\Dispatcher\Controller
* @param array $apple Existing values, if any
* @return array
*/
public static function processAppleStartupScreen( $values, $apple = array() )
public static function processAppleStartupScreen( array $values, array $apple = array() ) : array
{
if( $values['icons_apple_startup'] )
{
@@ -328,9 +360,9 @@ class _webapp extends \IPS\Dispatcher\Controller
{
try
{
\IPS\File::get( 'core_Icons', ( $key == 'original' ) ? $image : $image['url'] )->delete();
File::get( 'core_Icons', ( $key == 'original' ) ? $image : $image['url'] )->delete();
}
catch( \Exception $e ){}
catch( Exception $e ){}
}
$values['icons_apple_startup'] = '';
@@ -342,21 +374,19 @@ class _webapp extends \IPS\Dispatcher\Controller
/**
* Process Apple startup screen images
*
* @param array $values Values from form submission
* @param array $apple Existing values, if any
* @return array
* @return array|null
*/
public function buildStartupScreenImages()
public function buildStartupScreenImages() : ?array
{
$self = $this;
$multiRedirect = new \IPS\Helpers\MultipleRedirect(
$multiRedirect = new MultipleRedirect(
$this->url->setQueryString('do', 'buildStartupScreenImages'),
function( $data )
{
/* Get the necessary data */
$manifestDetails = json_decode( \IPS\Settings::i()->manifest_details, TRUE );
$setting = json_decode( \IPS\Settings::i()->icons_apple_startup, TRUE ) ?? array();
$manifestDetails = json_decode( Settings::i()->manifest_details, TRUE );
$setting = json_decode( Settings::i()->icons_apple_startup, TRUE ) ?? array();
if( isset( $setting['original'] ) )
{
@@ -393,24 +423,24 @@ class _webapp extends \IPS\Dispatcher\Controller
'apple-startup-1080x2340' => array( 360, 780, 3, 'portrait' ),
);
$file = \IPS\File::get( 'core_Icons', $setting['original'] );
$file = File::get( 'core_Icons', $setting['original'] );
$originalContents = $file->contents();
$backgroundColor = ( isset( $manifestDetails['background_color'] ) ) ? str_replace( '#', '', $manifestDetails['background_color'] ) : 'FFFFFF';
$rgb = array();
if ( \strlen( $backgroundColor ) == 3 )
if ( strlen( $backgroundColor ) == 3 )
{
$rgb[] = hexdec( \substr( $backgroundColor, 0, 1 ) . \substr( $backgroundColor, 0, 1 ) ); // R
$rgb[] = hexdec( \substr( $backgroundColor, 1, 1 ) . \substr( $backgroundColor, 1, 1 ) ); // G
$rgb[] = hexdec( \substr( $backgroundColor, 2, 1 ) . \substr( $backgroundColor, 2, 1 ) ); // B
$rgb[] = hexdec( substr( $backgroundColor, 0, 1 ) . substr( $backgroundColor, 0, 1 ) ); // R
$rgb[] = hexdec( substr( $backgroundColor, 1, 1 ) . substr( $backgroundColor, 1, 1 ) ); // G
$rgb[] = hexdec( substr( $backgroundColor, 2, 1 ) . substr( $backgroundColor, 2, 1 ) ); // B
}
else
{
$rgb[] = hexdec( \substr( $backgroundColor, 0, 2 ) ); // R
$rgb[] = hexdec( \substr( $backgroundColor, 2, 2 ) ); // G
$rgb[] = hexdec( \substr( $backgroundColor, 4, 2 ) ); // B
$rgb[] = hexdec( substr( $backgroundColor, 0, 2 ) ); // R
$rgb[] = hexdec( substr( $backgroundColor, 2, 2 ) ); // G
$rgb[] = hexdec( substr( $backgroundColor, 4, 2 ) ); // B
}
$did = 0;
@@ -429,8 +459,8 @@ class _webapp extends \IPS\Dispatcher\Controller
$width = ( $_sizes[3] == 'landscape' ) ? $_sizes[1] * $_sizes[2] : $_sizes[0] * $_sizes[2];
$height = ( $_sizes[3] == 'portrait' ) ? $_sizes[1] * $_sizes[2] : $_sizes[0] * $_sizes[2];
$logoImage = \IPS\Image::create( $originalContents );
$canvas = \IPS\Image::newImageCanvas( $width, $height, $rgb );
$logoImage = Image::create( $originalContents );
$canvas = Image::newImageCanvas( $width, $height, $rgb );
$logoImage->resizeToMax( $width - static::MINIMUM_STARTUP_IMAGE_PADDING, $height - static::MINIMUM_STARTUP_IMAGE_PADDING );
@@ -445,43 +475,44 @@ class _webapp extends \IPS\Dispatcher\Controller
}
$setting[ $filename ] = array(
'url' => (string) \IPS\File::create( 'core_Icons', $filename . '.png', (string) $canvas, NULL, TRUE, NULL, FALSE ),
'url' => (string) File::create( 'core_Icons', $filename . '.png', (string) $canvas, NULL, TRUE, NULL, FALSE ),
'width' => $canvas->width,
'height' => $canvas->height,
'density' => $_sizes[2],
'orientation' => $_sizes[3],
);
}
catch ( \Exception $e )
catch ( Exception $e )
{
\IPS\Log::log( $e, 'apple-startup-image' );
Log::log( $e, 'apple-startup-image' );
}
break;
}
/* Are we done? */
if( $did == \count( $sizes ) )
if( $did == count( $sizes ) )
{
return NULL;
}
\IPS\Settings::i()->changeValues( array( 'icons_apple_startup' => json_encode( $setting ) ) );
Settings::i()->changeValues( array( 'icons_apple_startup' => json_encode( $setting ) ) );
}
return array( $data, \IPS\Member::loggedIn()->language()->addToStack('build_start_images_title'), ( $did ) ? round( ( 100 / \count( $sizes ) * $did ), 2 ) : 0 );
return array( $data, Member::loggedIn()->language()->addToStack('build_start_images_title'), ( $did ) ? round( ( 100 / count( $sizes ) * $did ), 2 ) : 0 );
},
function() use( $self )
{
$manifest = json_decode( \IPS\Settings::i()->manifest_details, TRUE );
$manifest = json_decode( Settings::i()->manifest_details, TRUE );
$manifest['cache_key'] = time();
\IPS\Settings::i()->changeValues( [ 'manifest_details' => json_encode( $manifest ) ] );
\IPS\Output::i()->redirect( $self->url, 'completed' );
Settings::i()->changeValues( [ 'manifest_details' => json_encode( $manifest ) ] );
Output::i()->redirect( $self->url, 'completed' );
}
);
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('build_start_images_title');
\IPS\Output::i()->output = $multiRedirect;
Output::i()->title = Member::loggedIn()->language()->addToStack('build_start_images_title');
Output::i()->output = $multiRedirect;
return null;
}
}