Version 5.0.6

This commit is contained in:
Neo committed 2025-12-19 18:55:49 -08:00
1 parent a3a4a9ccbd
commit b89858b2ff
1214 files changed
+92828 -14859

No files matched your search

@@ -405,9 +405,6 @@ class themes extends Controller
/* Save snapshot */
Theme::load( Request::i()->id )->saveHistorySnapshot();
/* Wipe clean conflicts */
Db::i()->delete( 'core_theme_conflict', array( 'conflict_set_id=?', Request::i()->id ) );
/* No Name? Then this is a brand-new theme */
if ( empty( $set->name ) )
{
@@ -470,378 +467,363 @@ class themes extends Controller
$xml->read();
while ( $xml->read() )
{
if ( $xml->name === 'header' )
/* If this is a closing tag, OR if the tag is empty, move on */
if( $xml->nodeType == $xml::END_ELEMENT or empty( $xml->readInnerXml() ) )
{
$set->custom_header = $xml->readString();
$set->save();
$xml->next();
continue;
}
elseif ( $xml->name === 'footer' )
$name = $xml->name;
switch( $name )
{
$set->custom_footer = $xml->readString();
$set->save();
$xml->next();
}
elseif ( $xml->name === 'css' )
{
$set->custom_css = $xml->readString();
$set->save();
$xml->next();
}
elseif ( $xml->name === 'layout' )
{
$set->view_options = $xml->readString();
$set->save();
$xml->next();
}
elseif ( $xml->name === 'core_js' )
{
$set->core_js = $xml->readString();
$set->save();
$xml->next();
}
elseif ( $xml->name === 'core_css' )
{
$set->core_css = $xml->readString();
case 'header':
$set->custom_header = $xml->readString();
break;
/* Rebuild any designer core data */
$content = Theme::minifyCss( $set->core_css );
/* Replace any <fileStore.xxx> tags in the CSS */
Output::i()->parseFileObjectUrls( $content );
case 'footer':
$set->custom_footer = $xml->readString();
break;
$set->core_css_filename = File::create( 'core_Theme', 'theme.css', $content );
case 'css':
$set->custom_css = $xml->readString();
break;
$set->save();
$xml->next();
}
elseif( $xml->name === 'editor_data' )
{
$editorData = $xml->readString();
$set->theme_editor_data = !empty( $editorData ) ? json_decode( $editorData, true ) : null;
$set->save();
$xml->next();
}
elseif ( $xml->name === 'css_variables' )
{
$set->css_variables = $xml->readString();
$set->save();
$xml->next();
}
elseif( $xml->name === 'editor_category' )
{
$key = $xml->getAttribute( 'key' );
try
{
$category = Category::load( $key, 'cat_key' );
}
catch( OutOfRangeException )
{
$category = new Category;
$category->key = $key;
$category->set_id = $set->id;
case 'layout':
$set->view_options = $xml->readString();
break;
/* Set the position first, in case there is no parent specified */
$position = (int) Db::i()->select( 'max(cat_position)', 'core_theme_editor_categories', array( 'cat_parent=?', 0 ) )->first();
$category->position = $position + 1;
}
case 'core_js':
$set->core_js = $xml->readString();
break;
$category->app = $xml->getAttribute( 'app' );
case 'core_css':
$set->core_css = $xml->readString();
$xml->read();
while( $xml->read() )
{
switch( $xml->name )
{
case 'name':
$category->name = $xml->readString();
$category->save();
break;
/* Rebuild any designer core data */
$content = Theme::minifyCss( $set->core_css );
/* Replace any <fileStore.xxx> tags in the CSS */
Output::i()->parseFileObjectUrls( $content );
case 'icon':
$category->icon = json_encode( Category::buildIconData( $xml->readString() ) );
$category->save();
break;
$set->core_css_filename = File::create( 'core_Theme', 'theme.css', $content );
break;
case 'parent':
try
{
$parentId = Category::load( $xml->readString(), 'cat_key' )->id;
case 'editor_data':
$editorData = $xml->readString();
$set->theme_editor_data = !empty( $editorData ) ? json_decode( $editorData, true ) : null;
break;
/* Reset the position if we changed the category */
if( $parentId != $category->parent_id )
{
$position = (int) Db::i()->select( 'max(cat_position)', 'core_theme_editor_categories', array( 'cat_parent=?', $category->parent_id ) )->first();
$category->position = $position + 1;
}
case 'css_variables':
$set->css_variables = $xml->readString();
break;
$category->parent_id = $parentId;
$category->save();
}
catch( OutOfRangeException ){}
break;
}
if( $xml->nodeType == $xml::END_ELEMENT )
{
break;
}
$xml->next();
}
}
elseif( $xml->name === 'editor_setting' )
{
$key = $xml->getAttribute( 'key' );
try
{
$setting = Setting::load( $key, 'setting_key' );
}
catch( OutOfRangeException )
{
$setting = new Setting;
$setting->key = $key;
$setting->set_id = $set->id;
}
$setting->app = $xml->getAttribute( 'app' );
$setting->type = $xml->getAttribute( 'type' );
$setting->category_id = Category::load( $xml->getAttribute( 'category' ), 'cat_key' )->id;
if( !$setting->position )
{
$position = (int) Db::i()->select( 'max(setting_position)', 'core_theme_editor_settings', array( 'setting_category_id=?', $setting->category_id ) )->first();
$setting->position = $position + 1;
}
if( $refresh = $xml->getAttribute( 'refresh' ) )
{
$setting->refresh = true;
}
$xml->read();
while( $xml->read() )
{
switch( $xml->name )
{
case 'name':
$setting->name = $xml->readString();
$setting->save();
break;
case 'desc':
$setting->desc = $xml->readString();
$setting->save();
break;
case 'default':
case 'data':
$field = $xml->name;
$value = trim( $xml->readString() );
$setting->$field = $value ?: null;
$setting->save();
break;
}
if( $xml->nodeType == $xml::END_ELEMENT )
{
break;
}
$xml->next();
}
}
elseif ( $xml->name === 'app' )
{
$appKey = $xml->getAttribute('key');
if ( !array_key_exists( $appKey, $data['apps'] ) )
{
case 'editor_category':
$key = $xml->getAttribute( 'key' );
try
{
try
$row = Db::i()->select( '*', 'core_theme_editor_categories', [ 'cat_key=? and cat_set_id=?', $key, $set->id ] )->first();
$category = Category::constructFromData( $row );
}
catch( UnderflowException )
{
$category = new Category;
$category->key = $key;
$category->set_id = $set->id;
/* Set the position first, in case there is no parent specified */
$position = (int) Db::i()->select( 'max(cat_position)', 'core_theme_editor_categories', array( 'cat_parent=?', 0 ) )->first();
$category->position = $position + 1;
}
$category->app = $xml->getAttribute( 'app' );
$xml->read();
while( $xml->read() )
{
if( $xml->nodeType == $xml::END_ELEMENT )
{
$application = Application::load( $appKey );
}
/* Application is out of date */
catch( UnexpectedValueException $e )
{
$xml->next();
continue;
break;
}
if ( $application->enabled )
switch( $xml->name )
{
/* Import */
$xml->read();
while ( $xml->read() )
{
switch ( $xml->name )
case 'name':
$category->name = $xml->readString();
break;
case 'icon':
$category->icon = json_encode( Category::buildIconData( $xml->readString() ) );
break;
case 'parent':
try
{
case 'css':
$location = $xml->getAttribute('css_location');
$path = $xml->getAttribute('css_path');
$name = $xml->getAttribute('css_name');
$parent = Db::i()->select( '*', 'core_theme_editor_categories', [ 'cat_key=? and (cat_set_id=? or cat_set_id=?)', $xml->readString(), $set->id, 0 ], 'cat_set_id desc' )->first();
$parentId = $parent['cat_id'];
/* Keep this */
$_SESSION['theme_import']['css'][] = Db::i()->replace( 'core_theme_css', array(
'css_set_id' => Request::i()->id,
'css_app' => $appKey,
'css_location' => $xml->getAttribute('css_location'),
'css_path' => $xml->getAttribute('css_path'),
'css_name' => $xml->getAttribute('css_name'),
'css_attributes' => $xml->getAttribute('css_attributes'),
'css_content' => $xml->readString(),
'css_version' => $version,
'css_user_edited'=> 0
), true );
/* Reset the position if we changed the category */
if( $parentId != $category->parent )
{
$position = (int) Db::i()->select( 'max(cat_position)', 'core_theme_editor_categories', array( 'cat_parent=?', $category->parent ) )->first();
$category->position = $position + 1;
}
$category->parent = $parentId;
}
catch( UnderflowException ){}
break;
}
$category->save();
$xml->next();
}
break;
case 'editor_setting':
$key = $xml->getAttribute( 'key' );
try
{
$row = Db::i()->select( '*', 'core_theme_editor_settings', [ 'setting_key=? and setting_set_id=?', $key, $set->id ] )->first();
$setting = Setting::constructFromData( $row );
}
catch( UnderflowException )
{
$setting = new Setting;
$setting->key = $key;
$setting->set_id = $set->id;
}
$setting->app = $xml->getAttribute( 'app' );
$setting->type = $xml->getAttribute( 'type' );
/* Figure out the category - it will either be custom to this theme, or it will be in the master */
try
{
$category = Db::i()->select( '*', 'core_theme_editor_categories', [ 'cat_key=? and (cat_set_id=? or cat_set_id=?)', $xml->getAttribute( 'category' ), $set->id, 0 ], 'cat_set_id desc' )->first();
$setting->category_id = $category['cat_id'];
}
catch( UnderflowException )
{
/* This should never happen, but we'll do a fail-safe here so we don't break everything */
$setting->category_id = 0;
}
if( !$setting->position )
{
$position = (int) Db::i()->select( 'max(setting_position)', 'core_theme_editor_settings', array( 'setting_category_id=?', $setting->category_id ) )->first();
$setting->position = $position + 1;
}
$setting->refresh = (bool) $xml->getAttribute( 'refresh' );
$xml->read();
while( $xml->read() )
{
if( $xml->nodeType == $xml::END_ELEMENT )
{
break;
}
switch( $xml->name )
{
case 'name':
$setting->name = $xml->readString();
break;
case 'desc':
$setting->desc = $xml->readString();
break;
case 'default':
case 'data':
$field = $xml->name;
$value = trim( $xml->readString() );
$setting->$field = $value ?: null;
break;
}
$setting->save();
$xml->next();
}
break;
case 'app':
$appKey = $xml->getAttribute('key');
if( Application::appIsEnabled( $appKey ) )
{
/* Import */
$xml->read();
while ( $xml->read() )
{
if( $xml->nodeType == $xml::END_ELEMENT )
{
break;
}
switch ( $xml->name )
{
case 'css':
$location = $xml->getAttribute('css_location');
$path = $xml->getAttribute('css_path');
$name = $xml->getAttribute('css_name');
/* Keep this */
$_SESSION['theme_import']['css'][] = Db::i()->replace( 'core_theme_css', array(
'css_set_id' => Request::i()->id,
'css_app' => $appKey,
'css_location' => $xml->getAttribute('css_location'),
'css_path' => $xml->getAttribute('css_path'),
'css_name' => $xml->getAttribute('css_name'),
'css_attributes' => $xml->getAttribute('css_attributes'),
'css_content' => $xml->readString(),
'css_version' => $version,
'css_user_edited'=> 0
), true );
break;
case 'resource':
/* Theme resources should be raw binary data everywhere (filesystem and DB) except in the theme XML download where they are base64 encoded. */
$content = base64_decode( $xml->readString() );
$name = Theme::makeBuiltTemplateLookupHash( $appKey, $xml->getAttribute('location'), $xml->getAttribute('path') ) . '_' . $xml->getAttribute('name');
$fileName = (string) File::create( 'core_Theme', $name, $content, 'set_resources_' . Request::i()->id, TRUE, NULL, FALSE );
try
case 'resource':
/* Theme resources should be raw binary data everywhere (filesystem and DB) except in the theme XML download where they are base64 encoded. */
$content = base64_decode( $xml->readString() );
$name = Theme::makeBuiltTemplateLookupHash( $appKey, $xml->getAttribute('location'), $xml->getAttribute('path') ) . '_' . $xml->getAttribute('name');
$fileName = (string) File::create( 'core_Theme', $name, $content, 'set_resources_' . Request::i()->id, TRUE, NULL, FALSE );
try
{
$existingImage = Db::i()->select( '*', 'core_theme_resources', array(
'resource_set_id=? AND resource_app=? AND resource_location=? AND resource_path=? AND resource_name=?',
Request::i()->id, $appKey, $xml->getAttribute('location'), $xml->getAttribute('path'), $xml->getAttribute('name')
) )->first();
if ( $existingImage['resource_filename'] )
{
$existingImage = Db::i()->select( '*', 'core_theme_resources', array(
'resource_set_id=? AND resource_app=? AND resource_location=? AND resource_path=? AND resource_name=?',
Request::i()->id, $appKey, $xml->getAttribute('location'), $xml->getAttribute('path'), $xml->getAttribute('name')
) )->first();
if ( $existingImage['resource_filename'] )
try
{
try
{
File::get( 'core_Theme', $existingImage['resource_filename'] )->delete();
}
catch( Exception $e ) { }
File::get( 'core_Theme', $existingImage['resource_filename'] )->delete();
}
Db::i()->delete( 'core_theme_resources', array( 'resource_id=?', $existingImage['resource_id'] ) );
catch( Exception $e ) { }
}
catch( UnderflowException $e ) { }
Db::i()->replace( 'core_theme_resources', array(
'resource_set_id' => Request::i()->id,
'resource_app' => $appKey,
'resource_location' => $xml->getAttribute('location'),
'resource_path' => $xml->getAttribute('path'),
'resource_name' => $xml->getAttribute('name'),
'resource_data' => $content,
'resource_added' => time(),
'resource_filename' => $fileName,
'resource_user_edited' => intval( $xml->getAttribute('user_edited') )
) );
break;
}
if( $xml->nodeType == $xml::END_ELEMENT )
{
Db::i()->delete( 'core_theme_resources', array( 'resource_id=?', $existingImage['resource_id'] ) );
}
catch( UnderflowException $e ) { }
Db::i()->replace( 'core_theme_resources', array(
'resource_set_id' => Request::i()->id,
'resource_app' => $appKey,
'resource_location' => $xml->getAttribute('location'),
'resource_path' => $xml->getAttribute('path'),
'resource_name' => $xml->getAttribute('name'),
'resource_data' => $content,
'resource_added' => time(),
'resource_filename' => $fileName,
'resource_user_edited' => intval( $xml->getAttribute('user_edited') )
) );
break;
}
$xml->next();
}
$xml->next();
}
/* Update set so far so that mappings are saved */
$set->saveSet();
}
catch ( OutOfRangeException $e ) { }
/* Update set so far so that mappings are saved */
$set->saveSet();
/* Done */
$data['apps'][ $appKey ] = TRUE;
return array( $data, Member::loggedIn()->language()->addToStack('processing') );
}
else
{
$xml->next();
}
}
elseif ( $xml->name === 'language' )
{
$xml->read();
while ( $xml->read() )
{
if ( $xml->name == 'word' )
break;
case 'language':
$xml->read();
while ( $xml->read() )
{
$languageIds = $languageIds ?? array_keys( Lang::languages() );
foreach ( $languageIds as $langId )
if ( $xml->name == 'word' )
{
$default = $xml->readString();
$exists = Db::i()->select( 'COUNT(*)', 'core_sys_lang_words', array( array( 'lang_id=? and word_key=? and word_theme=?', $langId, $xml->getAttribute('key'), Request::i()->id ) ) )->first();
$languageIds = $languageIds ?? array_keys( Lang::languages() );
foreach ( $languageIds as $langId )
{
$default = $xml->readString();
$exists = Db::i()->select( 'COUNT(*)', 'core_sys_lang_words', array( array( 'lang_id=? and word_key=? and word_theme=?', $langId, $xml->getAttribute('key'), Request::i()->id ) ) )->first();
if ( $exists )
{
Db::i()->update( 'core_sys_lang_words', array(
'word_default' => $default,
'word_default_version' => $version,
), array( array( 'lang_id=? and word_key=? and word_theme=?', $langId, $xml->getAttribute('key'), Request::i()->id ) ) );
}
else
{
Db::i()->insert( 'core_sys_lang_words', array(
'lang_id' => $langId,
'word_app' => NULL,
'word_plugin' => NULL,
'word_theme' => Request::i()->id,
'word_key' => $xml->getAttribute('key'),
'word_default' => $default,
'word_custom' => NULL,
'word_default_version' => $version,
'word_custom_version' => NULL,
'word_js' => FALSE,
'word_export' => TRUE
) );
if ( $exists )
{
Db::i()->update( 'core_sys_lang_words', array(
'word_default' => $default,
'word_default_version' => $version,
), array( array( 'lang_id=? and word_key=? and word_theme=?', $langId, $xml->getAttribute('key'), Request::i()->id ) ) );
}
else
{
Db::i()->insert( 'core_sys_lang_words', array(
'lang_id' => $langId,
'word_app' => NULL,
'word_plugin' => NULL,
'word_theme' => Request::i()->id,
'word_key' => $xml->getAttribute('key'),
'word_default' => $default,
'word_custom' => NULL,
'word_default_version' => $version,
'word_custom_version' => NULL,
'word_js' => FALSE,
'word_export' => TRUE
) );
}
}
}
$xml->next();
}
}
}
elseif ( $xml->name === 'templates' )
{
$xml->read();
while ( $xml->read() )
{
if ( $xml->name == 'template' )
{
Db::i()->delete( 'core_theme_templates_custom', [ 'template_set_id=? and template_name=? and template_hookpoint=? and template_hookpoint_type=?', Request::i()->id, $xml->getAttribute('name'), $xml->getAttribute('hookpoint'), $xml->getAttribute('hookpoint_type') ] );
Db::i()->insert( 'core_theme_templates_custom', [
'template_set_id' => Request::i()->id,
'template_name' => $xml->getAttribute('name'),
'template_hookpoint' => $xml->getAttribute('hookpoint'),
'template_hookpoint_type' => $xml->getAttribute('hookpoint_type'),
'template_key' => $xml->getAttribute('key'),
'template_version' => $xml->getAttribute('version'),
'template_content' => $xml->readString(),
'template_updated' => time(),
] );
if( $xml->nodeType == $xml::END_ELEMENT )
{
break;
}
$xml->next();
}
}
}
elseif( $xml->name === 'logo' )
{
$type = $xml->getAttribute('type');
$name = $xml->getAttribute('name');
break;
/* Sharer logo and favicons may be present from themes generated in 4.3 and earlier, but are no longer used */
if( $type != 'sharer' AND $type != 'favicon' )
{
if( $set->logo[ $type ]['url'] !== null )
case 'templates':
$xml->read();
while ( $xml->read() )
{
File::get( 'core_Theme', $set->logo[ $type ]['url'] )->delete();
if ( $xml->name == 'template' )
{
Db::i()->delete( 'core_theme_templates_custom', [ 'template_set_id=? and template_name=? and template_hookpoint=? and template_hookpoint_type=?', Request::i()->id, $xml->getAttribute('name'), $xml->getAttribute('hookpoint'), $xml->getAttribute('hookpoint_type') ] );
Db::i()->insert( 'core_theme_templates_custom', [
'template_set_id' => Request::i()->id,
'template_name' => $xml->getAttribute('name'),
'template_hookpoint' => $xml->getAttribute('hookpoint'),
'template_hookpoint_type' => $xml->getAttribute('hookpoint_type'),
'template_key' => $xml->getAttribute('key'),
'template_version' => $xml->getAttribute('version'),
'template_content' => $xml->readString(),
'template_updated' => time(),
] );
if( $xml->nodeType == $xml::END_ELEMENT )
{
break;
}
$xml->next();
}
}
break;
$url = (string) File::create( 'core_Theme', $name, base64_decode( $xml->readString() ) );
case 'logo':
$type = $xml->getAttribute('type');
$name = $xml->getAttribute('name');
$set->saveSet( array( 'logo' => array( $type => array( 'url' => $url, 'height' => $xml->getAttribute('height') ?? 100 ) ) ) );
}
/* Sharer logo and favicons may be present from themes generated in 4.3 and earlier, but are no longer used */
if( $type != 'sharer' AND $type != 'favicon' )
{
if( isset( $set->logo[ $type ]['url'] ) )
{
File::get( 'core_Theme', $set->logo[ $type ]['url'] )->delete();
}
$xml->next();
$url = (string) File::create( 'core_Theme', $name, base64_decode( $xml->readString() ) );
$set->saveSet( array( 'logo' => array( $type => array( 'url' => $url, 'height' => $xml->getAttribute('height') ?? 100 ) ) ) );
}
break;
}
$set->save();
$xml->next();
}
return null;
},
@@ -861,6 +843,7 @@ class themes extends Controller
Store::i()->delete( 'core_theme_import_' . md5_file( Request::i()->file ) );
unset( Store::i()->themes );
unset( Store::i()->custom_templates_rows );
@unlink( Request::i()->file );
@@ -869,16 +852,8 @@ class themes extends Controller
{
$app->installThemeEditorSettings();
}
/* Conflicts to fix? */
if ( Db::i()->select( 'count(*)', 'core_theme_conflict', array( 'conflict_set_id=?', Request::i()->id ) )->first() )
{
Output::i()->redirect( Url::internal( 'app=core&module=customization&controller=themes&do=conflicts&id=' . Request::i()->id ) );
}
else
{
Output::i()->redirect( Url::internal( 'app=core&module=customization&controller=themes' ) );
}
Output::i()->redirect( Url::internal( 'app=core&module=customization&controller=themes' ) );
}
);
}
@@ -1040,9 +1015,12 @@ class themes extends Controller
$xml->text( $file );
$xml->endAttribute();
$xml->startAttribute('height');
$xml->text( $data['height'] );
$xml->endAttribute();
if( isset( $data['height'] ) )
{
$xml->startAttribute('height');
$xml->text( $data['height'] );
$xml->endAttribute();
}
/* Get image data */
$original = File::get( 'core_Theme', $data['url'] );
@@ -1137,7 +1115,7 @@ class themes extends Controller
$xml->endElement();
$xml->startElement( 'data' );
$xml->writeCdata( $setting['setting_data'] );
$xml->writeCdata( $setting['setting_data'] ?? '[]' );
$xml->endElement();
$xml->startElement( 'default' );
@@ -1327,8 +1305,9 @@ class themes extends Controller
$xml->endDocument();
Session::i()->log( 'acplog__theme_exported', array( "core_theme_set_title_{$themeSet->_id}" => TRUE ) );
Output::i()->sendOutput( $xml->outputMemory(), 200, 'application/xml', array( 'Content-Disposition' => Output::getContentDisposition( 'attachment', Member::loggedIn()->language()->get('core_theme_set_title_' . $themeSet->_id ) . " {$version}.xml" ), FALSE, FALSE ) );
$filename = Member::loggedIn()->language()->get('core_theme_set_title_' . $themeSet->_id ) . ( $version ? " {$version}" : "" ) . ".xml";
Output::i()->sendOutput( $xml->outputMemory(), 200, 'application/xml', array( 'Content-Disposition' => Output::getContentDisposition( 'attachment', $filename ), FALSE, FALSE ) );
}
/* Display */
@@ -1491,15 +1470,7 @@ class themes extends Controller
catch( Exception ) { }
}
$functionName = "css_" . uniqid();
Theme::makeProcessFunction( Theme::fixResourceTags( $theme->core_css, 'front' ), $functionName, '', FALSE, TRUE );
$fqFunc = 'IPS\\Theme\\'. $functionName;
$content = Theme::minifyCss( $fqFunc() );
/* Replace any <fileStore.xxx> tags in the CSS */
Output::i()->parseFileObjectUrls( $content );
$theme->core_css_filename = File::create( 'core_Theme', 'theme.css', $content );
$theme->core_css_filename = File::create( 'core_Theme', 'theme.css', Theme::compileCustomCss( $theme->core_css ) );
$theme->save();
if ( Request::i()->isAjax() )