Version 4.7.0

This commit is contained in:
Neo committed 2025-12-19 16:00:35 -08:00
1 parent 517a5e1f70
commit 8ba7d43898
1933 files changed
+65613 -11332

No files matched your search

@@ -77,44 +77,26 @@ class _ajax extends \IPS\Dispatcher\Controller
}
else
{
if ( $theme->by_skin_gen )
$cssFiles = [];
/* Remove all but custom/custom.css */
foreach ( $templateNames as $app => $items )
{
/* Remove all but custom/custom.css */
foreach( $templateNames as $app => $items )
foreach ( $templateNames[$app] as $location => $items )
{
foreach( $templateNames[ $app ] as $location => $items )
if ( $location === 'front' )
{
if ( $location === 'front' )
foreach ( $templateNames[$app][$location] as $path => $items )
{
foreach( $templateNames[ $app ][ $location ] as $path => $items )
if ( $path == 'custom' )
{
if ( $path != 'custom' )
{
unset( $templateNames[ $app ][ $location ][ $path ] );
}
$cssFiles[ $app ][ $location ][ $path ] = $items;
}
}
else
{
unset( $templateNames[ $app ][ $location ] );
}
}
}
}
else
{
/* Remove Admin Templates */
foreach( $templateNames as $app => $items )
{
foreach( $templateNames[ $app ] as $location => $items )
{
if ( $location == 'admin' )
{
unset( $templateNames[ $app ][ $location ] );
}
}
}
}
$templateNames = $cssFiles;
}
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'customization' )->templateEditorMenu( $theme, $templateNames, array( 'app' => $t_app, 'location' => $t_location, 'group' => $t_group, 'template' => $t_template, 'type' => $t_type) );
@@ -145,8 +127,8 @@ class _ajax extends \IPS\Dispatcher\Controller
else
{
$templateNames = $skinSet->getRawCss( '', '', '', \IPS\Theme::RETURN_ALL_NO_CONTENT );
$templateBits = $skinSet->getRawCss( $t_app, $t_location, $t_group, \IPS\Theme::RETURN_ALL );
$templateBit = ( ! empty( $t_name ) ) ? $templateBits[ $t_app ][ $t_location ][ $t_group ][ $t_name ] : null;
$templateBits = $skinSet->getRawCss( $t_app, 'front', 'custom', \IPS\Theme::RETURN_ALL );
$templateBit = ( ! empty( $t_name ) ) ? $templateBits[ $t_app ]['front']['custom'][ $t_name ] : null;
$templateBit['css_content'] = htmlentities( $templateBit['css_content'], ENT_DISALLOWED, 'UTF-8', TRUE );
}
@@ -337,7 +319,7 @@ class _ajax extends \IPS\Dispatcher\Controller
continue;
}
}
$return[ $result['css_app'] ][ $result['css_location'] ][ $result['css_path'] ][ $result['css_name'] ] = $result['css_name'];
}
@@ -330,7 +330,8 @@ class _emails extends \IPS\Dispatcher\Controller
}
/* Output */
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('emailtpl_edit');
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('emailtpl_edit', FALSE, array('sprintf' => \IPS\Member::loggedIn()->language()->addToStack( 'emailtpl_' . $template['template_name']) ) );
\IPS\Output::i()->output = $form->customTemplate( array( \IPS\Theme::i()->getTemplate( 'customization', 'core', 'admin' ), 'email' ) );;
}
@@ -58,6 +58,9 @@ class _icons extends \IPS\Dispatcher\Controller
$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' ) ) );
$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' ] ) ) );
/* 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' ) ) );
@@ -134,7 +137,7 @@ class _icons extends \IPS\Dispatcher\Controller
}
/* Homescreen icon is the hardest part, as we need to generate different sizes.. */
$values = static::processApplicationIcon( $values, $homeScreen );
$values = static::processApplicationIcon( $values, $homeScreen, $homeScreenMaskable );
/* The start screen requires more different sizes so we'll rebuild that after submit */
$rebuildStartScreen = FALSE;
@@ -152,14 +155,7 @@ class _icons extends \IPS\Dispatcher\Controller
}
}
if( $rebuildStartScreen )
{
$values = static::processAppleStartupScreen( $values, $apple );
}
else
{
$values['icons_apple_startup'] = json_encode( $apple );
}
$values = static::processAppleStartupScreen( $values, $apple );
/* We need the string value of this uploaded file as well */
$values['icons_mask_icon'] = (string) $values['icons_mask_icon'];
@@ -212,65 +208,85 @@ class _icons extends \IPS\Dispatcher\Controller
/**
* Process application icon
*
* @param array $values Values from form submission
* @param array $homeScreen Existing values, if any
* @param array $values Values from form submission
* @param array $homeScreen Existing values, if any
* @param array $homeScreenMaskable Existing values, if any
* @return array
*/
public static function processApplicationIcon( $values, $homeScreen = array() )
public static function processApplicationIcon( $values, $homeScreen = array(), $homeScreenMaskable = array() )
{
if( $values['icons_homescreen'] )
if( $values['icons_homescreen'] or $values['icons_homescreen_maskable'] )
{
$sizes = array(
'android-chrome-36x36' => array( 36, 36 ),
'android-chrome-48x48' => array( 48, 48 ),
'android-chrome-72x72' => array( 72, 72 ),
'android-chrome-96x96' => array( 96, 96 ),
'android-chrome-144x144' => array( 144, 144 ),
'android-chrome-192x192' => array( 192, 192 ),
'android-chrome-256x256' => array( 256, 256 ),
'android-chrome-384x384' => array( 384, 384 ),
'android-chrome-512x512' => array( 512, 512 ),
'msapplication-square70x70logo' => array( 128, 128 ),
'msapplication-TileImage' => array( 144, 144 ),
'msapplication-square150x150logo' => array( 270, 270 ),
'msapplication-wide310x150logo' => array( 558, 558 ),
'msapplication-square310x310logo' => array( 558, 270 ),
'apple-touch-icon-57x57' => array( 57, 57 ),
'apple-touch-icon-60x60' => array( 60, 60 ),
'apple-touch-icon-72x72' => array( 72, 72 ),
'apple-touch-icon-76x76' => array( 76, 76 ),
'apple-touch-icon-114x114' => array( 114, 114 ),
'apple-touch-icon-120x120' => array( 120, 120 ),
'apple-touch-icon-144x144' => array( 144, 144 ),
'apple-touch-icon-152x152' => array( 152, 152 ),
'apple-touch-icon-180x180' => array( 180, 180 ),
);
$setting = array( 'original' => (string) $values['icons_homescreen'] );
foreach( $sizes as $filename => $_sizes )
foreach( [ 'icons_homescreen', 'icons_homescreen_maskable' ] as $type )
{
try
$setting = [];
if( $values[ $type ] )
{
$image = \IPS\Image::create( $values['icons_homescreen']->contents() );
if( $image::exifSupported() )
{
$image->setExifData( $values['icons_homescreen']->contents() );
}
$image->crop( $_sizes[0], $_sizes[1] );
$setting[ $filename ] = array(
'url' => (string) \IPS\File::create( 'core_Icons', $filename . '.png', (string) $image, NULL, TRUE, NULL, FALSE ),
'width' => $image->width,
'height' => $image->height
$sizes = array(
'android-chrome-36x36' => array(36, 36),
'android-chrome-48x48' => array(48, 48),
'android-chrome-72x72' => array(72, 72),
'android-chrome-96x96' => array(96, 96),
'android-chrome-144x144' => array(144, 144),
'android-chrome-192x192' => array(192, 192),
'android-chrome-256x256' => array(256, 256),
'android-chrome-384x384' => array(384, 384),
'android-chrome-512x512' => array(512, 512),
'msapplication-square70x70logo' => array(128, 128),
'msapplication-TileImage' => array(144, 144),
'msapplication-square150x150logo' => array(270, 270),
'msapplication-wide310x150logo' => array(558, 558),
'msapplication-square310x310logo' => array(558, 270),
'apple-touch-icon-57x57' => array(57, 57),
'apple-touch-icon-60x60' => array(60, 60),
'apple-touch-icon-72x72' => array(72, 72),
'apple-touch-icon-76x76' => array(76, 76),
'apple-touch-icon-114x114' => array(114, 114),
'apple-touch-icon-120x120' => array(120, 120),
'apple-touch-icon-144x144' => array(144, 144),
'apple-touch-icon-152x152' => array(152, 152),
'apple-touch-icon-180x180' => array(180, 180),
);
}
catch ( \Exception $e ) { }
}
$values['icons_homescreen'] = json_encode( $setting );
$setting = array('original' => (string)$values[ $type ]);
foreach ( $sizes as $filename => $_sizes )
{
if ( $type == 'icons_homescreen_maskable' )
{
$filename .= "-masked";
if ( ! \mb_strstr( $filename, 'android-chrome' ) )
{
continue;
}
}
try
{
$image = \IPS\Image::create( $values[ $type ]->contents() );
if ( $image::exifSupported() )
{
$image->setExifData( $values[ $type ]->contents() );
}
$image->crop( $_sizes[0], $_sizes[1] );
$setting[$filename] = array(
'url' => (string)\IPS\File::create( 'core_Icons', $filename . '.png', (string)$image, NULL, TRUE, NULL, FALSE ),
'width' => $image->width,
'height' => $image->height
);
}
catch ( \Exception $e )
{
}
}
}
$values[ $type ] = json_encode( $setting );
}
}
else
{
@@ -284,6 +300,15 @@ class _icons extends \IPS\Dispatcher\Controller
catch( \Exception $e ){}
}
foreach( $homeScreenMaskable as $key => $image )
{
try
{
\IPS\File::get( 'core_Icons', ( $key == 'original' ) ? $image : $image['url'] )->delete();
}
catch( \Exception $e ){}
}
$values['icons_homescreen'] = '';
}
@@ -1141,7 +1141,6 @@ class _themes extends \IPS\Node\Controller
'set_update_check' => $xml->getAttribute('update_check'),
'set_long_version' => ( $xml->getAttribute('long_version') ) ? $xml->getAttribute('long_version') : \IPS\Application::load('core')->long_version,
'set_is_default' => $set->is_default,
'set_is_acp_default' => $set->is_acp_default,
'set_permissions' => implode( ',', $groups )
) );
@@ -1198,7 +1197,19 @@ class _themes extends \IPS\Node\Controller
$xml->read();
while ( $xml->read() )
{
if ( $xml->name === 'app' )
if ( $xml->name === 'header' )
{
$set->custom_header = $xml->readString();
$set->save();
$xml->next();
}
elseif ( $xml->name === 'footer' )
{
$set->custom_footer = $xml->readString();
$set->save();
$xml->next();
}
elseif ( $xml->name === 'app' )
{
$appKey = $xml->getAttribute('key');
if ( !array_key_exists( $appKey, $data['apps'] ) )
@@ -1767,13 +1778,11 @@ class _themes extends \IPS\Node\Controller
{
throw new \InvalidArgumentException( 'form_bad_value' );
}
$path = ( \IPS\Request::i()->theme_css_group_type == 'existing' ) ? \IPS\Request::i()->theme_css_group_existing : \IPS\Request::i()->theme_css_group_new;
/* Make sure key is unique */
try
{
$row = \IPS\Db::i()->select( '*', 'core_theme_css', array( \IPS\Db::i()->in( 'css_set_id', array( 0, \IPS\Request::i()->id ) ) . " AND css_app=? AND css_path=? AND css_location=? AND css_name=?", \IPS\Request::i()->theme_css_app, $path, \IPS\Request::i()->theme_css_location, $val ) )->first();
$row = \IPS\Db::i()->select( '*', 'core_theme_css', array( \IPS\Db::i()->in( 'css_set_id', array( 0, \IPS\Request::i()->id ) ) . " AND css_app=? AND css_path=? AND css_location=? AND css_name=?", 'core', 'custom', 'front', $val ) )->first();
if ( isset( $row['css_id'] ) )
{
@@ -1787,36 +1796,6 @@ class _themes extends \IPS\Node\Controller
return true;
} ) );
/* Locations */
$locations = iterator_to_array( \IPS\Db::i()->select( 'css_location', 'core_theme_css' )->setKeyField( 'css_location' ) );
unset( $locations['admin'] );
/* Groups */
$groups = iterator_to_array( \IPS\Db::i()->select( 'css_path', 'core_theme_css', NULL, 'css_path asc' )->setKeyField( 'css_path' ) );
/* Apps */
$apps = array();
foreach( \IPS\Application::applications() as $key => $data )
{
$apps[ $key ] = $data->_title;
}
$form->add( new \IPS\Helpers\Form\Select( 'theme_css_location', NULL, FALSE, array( 'options' => $locations ), NULL, NULL, NULL, 'theme_css_location' ) );
$form->add( new \IPS\Helpers\Form\Radio( 'theme_css_group_type', 'existing', FALSE, array(
'options' => array( 'existing' => 'theme_css_group_o_existing',
'new' => 'theme_css_group_o_new' ),
'toggles' => array( 'existing' => array( 'group_existing' ),
'new' => array( 'group_new' ) )
) ) );
$form->add( new \IPS\Helpers\Form\Text( 'theme_css_group_new', NULL, FALSE, array( 'regex' => '/^([A-Z0-9_\\-]+?)?$/i' ), NULL, NULL, NULL, 'group_new' ) );
$form->add( new \IPS\Helpers\Form\Select( 'theme_css_group_existing', 'custom', FALSE, array( 'options' => $groups, 'parse' => 'normal' ), NULL, NULL, NULL, 'group_existing' ) );
$form->add( new \IPS\Helpers\Form\Select( 'theme_css_app', NULL, TRUE, array( 'options' => $apps ) ) );
if( ! \IPS\Request::i()->isAjax() )
{
@@ -1826,9 +1805,9 @@ class _themes extends \IPS\Node\Controller
if ( $values = $form->values() )
{
$id = \intval( \IPS\Request::i()->id );
$save = array( 'app' => $values['theme_css_app'],
'location' => $values['theme_css_location'],
'path' => ( $values['theme_css_group_type'] == 'existing' ) ? $values['theme_css_group_existing'] : $values['theme_css_group_new'],
$save = array( 'app' => 'core',
'location' => 'front',
'path' => 'custom',
'name' => $values['theme_css_name'],
'added_to' => $id,
'set_id' => $id,
@@ -1934,7 +1913,7 @@ class _themes extends \IPS\Node\Controller
if ( ! isset( \IPS\Request::i()->rebuildDone ) )
{
$url = \IPS\Http\Url::internal( 'app=core&module=customization&controller=themes&do=devImportBeforeExport&id=' . $id )->csrf();
$form->addMessage( \IPS\Member::loggedIn()->language()->addToStack('theme_export_designer_mode_build_message', NULL, array( 'sprintf' => array( $url ) ) ), 'ipsMessage ipsMessage_info', FALSE );
$form->addMessage( \IPS\Member::loggedIn()->language()->addToStack( 'theme_export_designer_mode_build_message', FALSE, array( 'sprintf' => array( $url ) ) ), 'ipsMessage ipsMessage_info', FALSE );
}
$form->add( new \IPS\Helpers\Form\Text( 'theme_template_export_author_name', ( $storedAuthor !== null ) ? $storedAuthor['name'] : false, false ) );
@@ -1995,7 +1974,15 @@ class _themes extends \IPS\Node\Controller
$xml->startAttribute('update_check');
$xml->text( $updateCheck );
$xml->endAttribute();
$xml->startElement('header');
$xml->writeCData( str_replace( ']]>', ']]]]><![CDATA[>', $themeSet->custom_header ) );
$xml->endElement();
$xml->startElement('footer');
$xml->writeCData( str_replace( ']]>', ']]]]><![CDATA[>', $themeSet->custom_footer ) );
$xml->endElement();
/* Copy logos */
if( $themeSet->logo_data )
{
@@ -2336,7 +2323,7 @@ class _themes extends \IPS\Node\Controller
try
{
$theme = \IPS\Theme::load( \IPS\Request::i()->id );
if ( $theme->is_default or $theme->is_acp_default )
if ( $theme->is_default )
{
\IPS\Output::i()->error( 'cannot_delete_default_theme', '1C163/3', 403, '' );
}
@@ -2877,8 +2864,21 @@ class _themes extends \IPS\Node\Controller
}
else
{
$theme = \IPS\Theme::load( \IPS\Request::i()->id );
$customVersion = \IPS\Db::i()->select( '*', 'core_theme_templates', array( 'template_id=?', (int) \IPS\Request::i()->t_item_id ) )->first();
$rawTemplates = \IPS\Theme::master()->getRawTemplates( $customVersion['template_app'], $customVersion['template_location'], $customVersion['template_group'], \IPS\Theme::RETURN_ALL, TRUE );
$master = \IPS\Theme::master()->getRawTemplates( $customVersion['template_app'], $customVersion['template_location'], $customVersion['template_group'], \IPS\Theme::RETURN_ALL, TRUE );
$rawTemplates = [];
if ( isset( \IPS\Request::i()->type ) and \IPS\Request::i()->type == 'parent' and $theme->parent() )
{
$rawTemplates = $theme->parent()->getRawTemplates( $customVersion['template_app'], $customVersion['template_location'], $customVersion['template_group'], \IPS\Theme::RETURN_ALL, TRUE );
}
if ( ! isset( $rawTemplates[ $customVersion['template_app'] ][ $customVersion['template_location'] ][ $customVersion['template_group'] ][ $customVersion['template_name'] ] ) )
{
$rawTemplates = $master;
}
$original = isset( $rawTemplates[ $customVersion['template_app'] ][ $customVersion['template_location'] ][ $customVersion['template_group'] ][ $customVersion['template_name'] ] ) ? $rawTemplates[ $customVersion['template_app'] ][ $customVersion['template_location'] ][ $customVersion['template_group'] ][ $customVersion['template_name'] ]['template_content'] : '';
}
@@ -2894,119 +2894,21 @@ class _themes extends \IPS\Node\Controller
{
/* Check permission */
\IPS\Dispatcher::i()->checkAcpPermission( 'theme_templates_manage' );
/* Grab groups */
$id = \intval( \IPS\Request::i()->id );
$t_type = \IPS\Request::i()->t_type ?: 'templates';
$t_app = \IPS\Request::i()->t_app ?: 'core';
$t_location = \IPS\Request::i()->t_location ?: ( ( \IPS\Request::i()->t_app ) ? false : 'front' );
$hasHistory = \IPS\Db::i()->select( 'count(*) as cnt', 'core_theme_content_history', array( 'content_set_id=?', $id ) )->setValueField('cnt')->first();
if ( $id === 1 AND \IPS\IN_DEV OR $hasHistory )
{
\IPS\Output::i()->sidebar['actions'] = array(
'diff' => array(
'icon' => 'cog',
'link' => \IPS\Http\Url::internal( 'app=core&module=customization&controller=themes&do=diff&id=' . $id ),
'title' => 'core_theme_set_diff',
)
);
}
if ( $t_type == 'templates' )
{
$t_group = \IPS\Request::i()->t_group ?: ( ( \IPS\Request::i()->t_app ) ? false : 'global' );
$t_template = \IPS\Request::i()->t_name ?: ( ( \IPS\Request::i()->t_app ) ? false : 'globalTemplate' );
}
else
{
$t_group = \IPS\Request::i()->t_group ?: ( ( \IPS\Request::i()->t_app ) ? false : 'custom' );
$t_template = \IPS\Request::i()->t_name ?: ( ( \IPS\Request::i()->t_app ) ? false : 'custom.css' );
}
$groups = array();
$bitNames = array();
$template = array();
$themeSet = \IPS\Theme::load( $id );
if ( $t_type == 'templates' )
{
$templateNames = $themeSet->getRawTemplates( '', '', '', \IPS\Theme::RETURN_ALL_NO_CONTENT );
$templateBits = $themeSet->getRawTemplates( $t_app, $t_location, $t_group, \IPS\Theme::RETURN_ALL );
$templateBit = ( ! empty( $t_template ) AND !empty( $templateBits[ $t_app ][ $t_location ][ $t_group ][ $t_template ] ) ) ? $templateBits[ $t_app ][ $t_location ][ $t_group ][ $t_template ] : null;
$itemId = $templateBit['template_id'];
}
else
{
$templateNames = $themeSet->getRawCss( '', '', '', \IPS\Theme::RETURN_ALL_NO_CONTENT );
$templateBits = $themeSet->getRawCss( $t_app, $t_location, $t_group, \IPS\Theme::RETURN_ALL );
$templateBit = ( ! empty( $t_template ) ) ? $templateBits[ $t_app ][ $t_location ][ $t_group ][ $t_template ] : null;
$itemId = $templateBit['css_id'];
}
if ( $t_type == 'templates' )
{
/* Remove Admin Templates */
foreach( $templateNames as $app => $items )
{
foreach( $templateNames[ $app ] as $location => $items )
{
if ( $location == 'admin' )
{
unset( $templateNames[ $app ][ $location ] );
}
}
}
}
else
{
if ( $themeSet->by_skin_gen )
{
/* Remove all but custom/custom.css */
foreach( $templateNames as $app => $items )
{
foreach( $templateNames[ $app ] as $location => $items )
{
if ( $location === 'front' )
{
foreach( $templateNames[ $app ][ $location ] as $path => $items )
{
if ( $path != 'custom' )
{
unset( $templateNames[ $app ][ $location ][ $path ] );
}
}
}
else
{
unset( $templateNames[ $app ][ $location ] );
}
}
}
}
else
{
/* Remove Admin Templates */
foreach( $templateNames as $app => $items )
{
foreach( $templateNames[ $app ] as $location => $items )
{
if ( $location == 'admin' )
{
unset( $templateNames[ $app ][ $location ] );
}
}
}
}
}
$id = \intval( \IPS\Request::i()->id );
$themeSet = \IPS\Theme::load( $id );
\IPS\Output::i()->sidebar['actions']['advancedmode'] = array(
'title' => ( \IPS\Settings::i()->theme_editing_advanced_mode == 1 ) ? 'theme_advanced_mode_off' : 'theme_advanced_mode_on',
'icon' => 'paint-brush',
'primary' => \IPS\Settings::i()->theme_editing_advanced_mode == 1,
'link' => \IPS\Http\Url::internal( 'app=core&module=customization&controller=themes&do=advancedEditingToggle&from=' . $id ),
'data' => array( 'confirm' => '', 'confirmSubMessage' => ( \IPS\Settings::i()->theme_editing_advanced_mode ) ? \IPS\Member::loggedIn()->language()->addToStack( 'theme_advanced_mode_submessage_off' ) : \IPS\Member::loggedIn()->language()->addToStack( 'theme_advanced_mode_submessage_on' ) )
);
/* Display */
\IPS\Output::i()->responsive = FALSE;
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('theme_editing_title', FALSE, array( 'sprintf' => array( $themeSet->_title ) ) );
\IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js( 'codemirror/diff_match_patch.js', 'core', 'interface' ) );
\IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js( 'codemirror/codemirror.js', 'core', 'interface' ) );
\IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'codemirror/codemirror.css', 'core', 'interface' ) );
\IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'customization/templates.css', 'core', 'admin' ) );
\IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js( 'admin_templates.js', 'core' ) );
@@ -3015,19 +2917,246 @@ class _themes extends \IPS\Node\Controller
/* Display */
\IPS\Output::i()->breadcrumb = array(
array(
\IPS\Http\Url::internal('app=core&module=customization&controller=themes'),
'menu__' . \IPS\Dispatcher::i()->application->directory . '_' . \IPS\Dispatcher::i()->module->key
),
array(
NULL,
\IPS\Member::loggedIn()->language()->addToStack( $name )
)
);
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'customization' )->templateEditor( $themeSet, $templateNames, $templateBit, array( 'app' => $t_app, 'location' => $t_location, 'group' => $t_group, 'template' => $t_template, 'type' => $t_type, 'item_id' => $itemId ) );
array(
\IPS\Http\Url::internal('app=core&module=customization&controller=themes'),
'menu__' . \IPS\Dispatcher::i()->application->directory . '_' . \IPS\Dispatcher::i()->module->key
),
array(
NULL,
\IPS\Member::loggedIn()->language()->addToStack( $name )
)
);
/* Are we using the very lovely simple mode? */
if ( ! \IPS\Settings::i()->theme_editing_advanced_mode )
{
$form = new \IPS\Helpers\Form( 'form', 'theme_simple_save_all' );
$form->hiddenValues['id'] = $id;
$form->class = 'ipsForm_vertical';
$customCss = $themeSet->getRawCss( NULL, 'front', 'custom', \IPS\Theme::RETURN_ALL );
$cssData = NULL;
$itemId = NULL;
$cssFiles = 0;
foreach( $customCss as $app => $data )
{
if ( isset( $customCss[ $app ]['front']['custom'] ) )
{
foreach ( $customCss[ $app ]['front']['custom'] as $name => $data )
{
$cssFiles++;
if ( $name == 'custom.css' )
{
$itemId = $data['css_id'];
$cssData .= "\n" . $data['css_content'];
}
else
{
$cssData .= "\n\n/* ===[Filename:" . $app . '/custom/' . $name . "]=== /*\n\n" . $data['css_content'];
}
}
}
}
$form->addTab('theme_simple_header_tab');
$form->add( new \IPS\Helpers\Form\TextArea( 'theme_simple_header', $themeSet->custom_header, FALSE, [ 'mode' => 'htmlmixed', 'height' => 800 ] ) );
$form->addTab('theme_simple_footer_tab');
$form->add( new \IPS\Helpers\Form\TextArea( 'theme_simple_footer', $themeSet->custom_footer, FALSE, [ 'mode' => 'htmlmixed', 'height' => 800 ] ) );
$form->addTab('theme_simple_css_tab');
if ( $cssFiles > 1 )
{
$form->addMessage( 'theme_simple_lots_of_css_files', 'ipsMessage ipsMessage_warning' );
}
$form->add( new \IPS\Helpers\Form\TextArea( 'theme_simple_css', $cssData, FALSE, [ 'mode' => 'css', 'height' => 800 ] ) );
if ( $values = $form->values() )
{
if ( \IPS\Request::i()->isAjax() )
{
/* Test the syntax */
$errors = [];
foreach( [ 'header', 'footer', 'css' ] as $key )
{
try
{
\IPS\Theme::checkTemplateSyntax( $values['theme_simple_' . $key ] );
}
catch( \LogicException $e )
{
$errors[] = \IPS\Member::loggedIn()->language()->addToStack( 'theme_error_in_template', NULL, [ 'sprintf' => [ \IPS\Member::loggedIn()->language()->get('theme_simple_' . $key . '_tab') ] ] );
}
}
if ( \count( $errors ) )
{
\IPS\Output::i()->error( implode( ". ", $errors ), '3C163/I', 500, '' );
}
}
$themeSet->custom_header = $values['theme_simple_header'];
$themeSet->custom_footer = $values['theme_simple_footer'];
$themeSet->save();
/* Now remove any additional CSS files */
if ( $cssFiles > 1 )
{
foreach( $customCss as $app => $data )
{
if ( isset( $customCss[ $app ]['front']['custom'] ) )
{
foreach ( $customCss[ $app ]['front']['custom'] as $name => $data )
{
if ( ! ( $app == 'core' and $name == 'custom.css' ) )
{
\IPS\Theme::removeCss( $app, 'front', 'custom', NULL, TRUE, $name );
}
}
}
}
}
/* Finally, save the theme */
$themeSet->saveCss( [
'app' => 'core',
'location' => 'front',
'group' => 'custom',
'name' => 'custom.css',
'content' => $values['theme_simple_css'],
'set_id' => $id,
'item_id' => $itemId
] );
/* Remove compiled templates to ensure the globalTemplate gets the latest updates */
\IPS\Theme::deleteCompiledTemplate( 'core', 'front', 'global', $themeSet->_id );
if( \IPS\Request::i()->isAjax() )
{
\IPS\Output::i()->json( ['result' => 'ok' ] );
}
else
{
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=customization&controller=themes&do=templates&id=' . $id ), 'saved' );
}
}
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'customization' )->templateEditorSimple( $themeSet, $form );
}
else
{
\IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js( 'codemirror/diff_match_patch.js', 'core', 'interface' ) );
\IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js( 'codemirror/codemirror.js', 'core', 'interface' ) );
/* Grab groups */
$t_type = \IPS\Request::i()->t_type ?: 'templates';
$t_app = \IPS\Request::i()->t_app ?: 'core';
$t_location = \IPS\Request::i()->t_location ?: ( ( \IPS\Request::i()->t_app ) ? false : 'front' );
$hasHistory = \IPS\Db::i()->select( 'count(*) as cnt', 'core_theme_content_history', array('content_set_id=?', $id) )->setValueField( 'cnt' )->first();
if ( $id === 1 and \IPS\IN_DEV or $hasHistory )
{
\IPS\Output::i()->sidebar['actions']['diff'] = [
'icon' => 'cog',
'link' => \IPS\Http\Url::internal( 'app=core&module=customization&controller=themes&do=diff&id=' . $id ),
'title' => 'core_theme_set_diff'
];
}
if ( $t_type == 'templates' )
{
$t_group = \IPS\Request::i()->t_group ?: ( ( \IPS\Request::i()->t_app ) ? false : 'global' );
$t_template = \IPS\Request::i()->t_name ?: ( ( \IPS\Request::i()->t_app ) ? false : 'globalTemplate' );
}
else
{
$t_group = \IPS\Request::i()->t_group ?: ( ( \IPS\Request::i()->t_app ) ? false : 'custom' );
$t_template = \IPS\Request::i()->t_name ?: ( ( \IPS\Request::i()->t_app ) ? false : 'custom.css' );
}
$groups = array();
$bitNames = array();
$template = array();
if ( $t_type == 'templates' )
{
$templateNames = $themeSet->getRawTemplates( '', '', '', \IPS\Theme::RETURN_ALL_NO_CONTENT );
$templateBits = $themeSet->getRawTemplates( $t_app, $t_location, $t_group, \IPS\Theme::RETURN_ALL );
$templateBit = ( !empty( $t_template ) and !empty( $templateBits[$t_app][$t_location][$t_group][$t_template] ) ) ? $templateBits[$t_app][$t_location][$t_group][$t_template] : null;
$itemId = $templateBit['template_id'];
}
else
{
$templateNames = $themeSet->getRawCss( '', '', '', \IPS\Theme::RETURN_ALL_NO_CONTENT );
$templateBits = $themeSet->getRawCss( $t_app, $t_location, $t_group, \IPS\Theme::RETURN_ALL );
$templateBit = ( !empty( $t_template ) ) ? $templateBits[$t_app][$t_location][$t_group][$t_template] : null;
$itemId = $templateBit['css_id'];
}
if ( $t_type == 'templates' )
{
/* Remove Admin Templates */
foreach ( $templateNames as $app => $items )
{
foreach ( $templateNames[$app] as $location => $items )
{
if ( $location == 'admin' )
{
unset( $templateNames[$app][$location] );
}
}
}
}
else
{
$cssFiles = [];
/* Remove all but custom/custom.css */
foreach ( $templateNames as $app => $items )
{
foreach ( $templateNames[$app] as $location => $items )
{
if ( $location === 'front' )
{
foreach ( $templateNames[$app][$location] as $path => $items )
{
if ( $path == 'custom' )
{
$cssFiles[ $app ][ $location ][ $path ] = $items;
}
}
}
}
}
$templateNames = $cssFiles;
}
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'customization' )->templateEditor( $themeSet, $templateNames, $templateBit, array( 'app' => $t_app, 'location' => $t_location, 'group' => $t_group, 'template' => $t_template, 'type' => $t_type, 'item_id' => $itemId ) );
}
}
/**
* Toggle between the simple and advanced editing modes
*
* @return void
*/
public function advancedEditingToggle()
{
\IPS\Settings::i()->changeValues( [ 'theme_editing_advanced_mode' => ( \IPS\Settings::i()->theme_editing_advanced_mode ) ? 0 : 1 ] );
if ( isset( \IPS\Request::i()->from ) )
{
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=customization&controller=themes&do=templates&id=' . \IPS\Request::i()->from ), 'theme_mode_switched' );
}
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=customization&controller=themes' ), 'theme_mode_switched' );
}
/**
* Set Members
*