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
@@ -21,10 +21,8 @@ use IPS\Helpers\Form;
|
||||
use IPS\Helpers\Form\Icon;
|
||||
use IPS\Helpers\Form\Node;
|
||||
use IPS\Helpers\Form\Text;
|
||||
use IPS\Helpers\Form\Translatable;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Lang;
|
||||
use IPS\Member;
|
||||
use IPS\Node\Model;
|
||||
use IPS\Request;
|
||||
use IPS\Settings;
|
||||
@@ -74,11 +72,6 @@ class Category extends Model
|
||||
*/
|
||||
public static ?string $databaseColumnParent = 'parent';
|
||||
|
||||
/**
|
||||
* @brief [Node] Title prefix. If specified, will look for a language key with "{$titleLangPrefix}_{$id}" as the key
|
||||
*/
|
||||
public static ?string $titleLangPrefix = 'themeeditor_cat_';
|
||||
|
||||
/**
|
||||
* @brief [Node] Show forms modally?
|
||||
*/
|
||||
@@ -106,7 +99,7 @@ class Category extends Model
|
||||
*/
|
||||
protected function get__title(): string
|
||||
{
|
||||
return Member::loggedIn()->language()->addToStack( static::$titleLangPrefix . $this->key, NULL, array( 'escape' => TRUE ) );
|
||||
return $this->name ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -119,7 +112,7 @@ class Category extends Model
|
||||
*/
|
||||
public function getTitleForLanguage( Lang $language, array $options=array() ): string
|
||||
{
|
||||
return $language->addToStack( static::$titleLangPrefix . $this->key, NULL, $options );
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -129,7 +122,7 @@ class Category extends Model
|
||||
*/
|
||||
protected function get__titleLanguageKey(): ?string
|
||||
{
|
||||
return static::$titleLangPrefix . $this->key;
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -316,10 +309,7 @@ class Category extends Model
|
||||
*/
|
||||
public function form( Form &$form ) : void
|
||||
{
|
||||
$form->add( new Translatable( 'themeeditor_cat_name', null, true, array(
|
||||
'app' => 'core',
|
||||
'key' => ( $this->id ? 'themeeditor_cat_' . $this->key : null )
|
||||
) ) );
|
||||
$form->add( new Text( 'themeeditor_cat_name', $this->name, true ) );
|
||||
|
||||
$form->add( new Text( 'themeeditor_cat_key', $this->key, true, array(
|
||||
'regex' => '/[A-Z]+/i',
|
||||
@@ -375,12 +365,6 @@ class Category extends Model
|
||||
*/
|
||||
public function formatFormValues( array $values ): array
|
||||
{
|
||||
if( !$this->id )
|
||||
{
|
||||
$this->key = $values['themeeditor_cat_key'];
|
||||
$this->save();
|
||||
}
|
||||
|
||||
$icon = $values['themeeditor_cat_icon'][0];
|
||||
if( $values['themeeditor_cat_icon_extra'] )
|
||||
{
|
||||
@@ -388,6 +372,7 @@ class Category extends Model
|
||||
}
|
||||
|
||||
$return = [
|
||||
'cat_name' => $values['themeeditor_cat_name'],
|
||||
'cat_key' => $values['themeeditor_cat_key'],
|
||||
'cat_icon' => json_encode( $icon ),
|
||||
'cat_parent' => ( $values['themeeditor_cat_parent'] instanceof Model ? $values['themeeditor_cat_parent']->id : 0 ),
|
||||
@@ -399,8 +384,6 @@ class Category extends Model
|
||||
$return['cat_app'] = $values['themeeditor_cat_app']->directory;
|
||||
}
|
||||
|
||||
Lang::saveCustom( 'core', static::$titleLangPrefix . $values['themeeditor_cat_key'], $values['themeeditor_cat_name'] );
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
@@ -527,15 +510,4 @@ class Category extends Model
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* [ActiveRecord] Delete Record
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete(): void
|
||||
{
|
||||
parent::delete();
|
||||
Lang::deleteCustom( 'core', static::$titleLangPrefix . $this->key );
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,6 @@ use IPS\Helpers\Form\Node;
|
||||
use IPS\Helpers\Form\Number;
|
||||
use IPS\Helpers\Form\Select;
|
||||
use IPS\Helpers\Form\Text;
|
||||
use IPS\Helpers\Form\Translatable;
|
||||
use IPS\Helpers\Form\YesNo;
|
||||
use IPS\Http\Url;
|
||||
use IPS\IPS;
|
||||
@@ -90,16 +89,6 @@ class Setting extends Model
|
||||
*/
|
||||
public static ?string $databaseColumnOrder = 'position';
|
||||
|
||||
/**
|
||||
* @brief [Node] Title prefix. If specified, will look for a language key with "{$titleLangPrefix}_{$id}" as the key
|
||||
*/
|
||||
public static ?string $titleLangPrefix = 'themeeditor_var_';
|
||||
|
||||
/**
|
||||
* @brief [Node] Description suffix. If specified, will look for a language key with "{$titleLangPrefix}_{$id}_{$descriptionLangSuffix}" as the key
|
||||
*/
|
||||
public static ?string $descriptionLangSuffix = '_desc';
|
||||
|
||||
/**
|
||||
* @brief [Node] Parent Node ID Database Column
|
||||
*/
|
||||
@@ -134,7 +123,7 @@ class Setting extends Model
|
||||
*/
|
||||
protected function get__title(): string
|
||||
{
|
||||
return Member::loggedIn()->language()->addToStack( static::$titleLangPrefix . $this->key, NULL, array( 'escape' => TRUE ) );
|
||||
return $this->name ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -147,7 +136,7 @@ class Setting extends Model
|
||||
*/
|
||||
public function getTitleForLanguage( Lang $language, array $options=array() ): string
|
||||
{
|
||||
return $language->addToStack( static::$titleLangPrefix . $this->key, NULL, $options );
|
||||
return $this->_title;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -157,7 +146,7 @@ class Setting extends Model
|
||||
*/
|
||||
protected function get__titleLanguageKey(): ?string
|
||||
{
|
||||
return static::$titleLangPrefix . $this->key;
|
||||
return $this->_title;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -167,7 +156,7 @@ class Setting extends Model
|
||||
*/
|
||||
protected function get_description(): ?string
|
||||
{
|
||||
return Member::loggedIn()->language()->addToStack( static::$titleLangPrefix . $this->key . static::$descriptionLangSuffix );
|
||||
return $this->desc;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -237,27 +226,6 @@ class Setting extends Model
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function get_name() : string
|
||||
{
|
||||
return Member::loggedIn()->language()->addToStack( 'themeeditor_var_' . $this->key );
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function get_desc() : string
|
||||
{
|
||||
if( Member::loggedIn()->language()->checkKeyExists( 'themeeditor_var_' . $this->key . '_desc' ) )
|
||||
{
|
||||
return Member::loggedIn()->language()->addToStack( 'themeeditor_var_' . $this->key . '_desc' );
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
@@ -427,10 +395,6 @@ class Setting extends Model
|
||||
|
||||
switch( $this->type )
|
||||
{
|
||||
case static::SETTING_IMAGE:
|
||||
$value = 'url("' . $value . '")';
|
||||
break;
|
||||
|
||||
case static::SETTING_TEXT:
|
||||
$value = strip_tags( $value );
|
||||
$value = str_replace( array( '/*', ';' ), '', $value );
|
||||
@@ -495,16 +459,8 @@ class Setting extends Model
|
||||
*/
|
||||
public function form( Form &$form ) : void
|
||||
{
|
||||
$form->add( new Translatable( 'themeeditor_setting_name', null, true, array(
|
||||
'app' => 'core',
|
||||
'key' => ( $this->id ? 'themeeditor_var_' . $this->key : null )
|
||||
) ) );
|
||||
|
||||
$form->add( new Translatable( 'themeeditor_setting_desc', null, false, array(
|
||||
'app' => 'core',
|
||||
'key' => ( $this->id ? 'themeeditor_var_' . $this->key . '_desc' : null )
|
||||
) ) );
|
||||
|
||||
$form->add( new Text( 'themeeditor_setting_name', $this->name, true ) );
|
||||
$form->add( new Text( 'themeeditor_setting_desc', $this->desc, false ) );
|
||||
$form->add( new Text( 'themeeditor_setting_key', $this->key, true, array(
|
||||
'regex' => '/[A-Z]+/i',
|
||||
'maxLength' => 150
|
||||
@@ -606,6 +562,8 @@ class Setting extends Model
|
||||
}
|
||||
|
||||
$return = [
|
||||
'setting_name' => $values['themeeditor_setting_name'],
|
||||
'setting_desc' => $values['themeeditor_setting_desc'],
|
||||
'setting_key' => $values['themeeditor_setting_key'],
|
||||
'setting_category_id' => ( $values['themeeditor_setting_category'] instanceof Category ) ? $values['themeeditor_setting_category']->id : 0,
|
||||
'setting_type' => $values['themeeditor_setting_type'],
|
||||
@@ -651,22 +609,6 @@ class Setting extends Model
|
||||
$return['setting_app'] = $values['themeeditor_setting_app']->directory;
|
||||
}
|
||||
|
||||
Lang::saveCustom( 'core', static::$titleLangPrefix . $values['themeeditor_setting_key'], $values['themeeditor_setting_name'] );
|
||||
Lang::saveCustom( 'core', static::$titleLangPrefix . $values['themeeditor_setting_key'] . '_desc', $values['themeeditor_setting_desc'] );
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* [ActiveRecord] Delete Record
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete(): void
|
||||
{
|
||||
parent::delete();
|
||||
|
||||
Lang::deleteCustom( 'core', static::$titleLangPrefix . $this->key );
|
||||
Lang::deleteCustom( 'core', static::$titleLangPrefix . $this->key . '_desc' );
|
||||
}
|
||||
}
|
||||
+119
-38
@@ -35,6 +35,7 @@ use IPS\Member\Group;
|
||||
use IPS\Node\Model;
|
||||
use IPS\Patterns\ActiveRecord;
|
||||
use IPS\Patterns\ActiveRecordIterator;
|
||||
use IPS\Theme\Editor\Category;
|
||||
use IPS\Theme\Editor\Setting;
|
||||
use IPS\Theme\SandboxedTemplate;
|
||||
use IPS\Theme\Dev\Theme as DevTheme;
|
||||
@@ -755,7 +756,6 @@ class Theme extends Model
|
||||
/* Are these core IPS apps? If so, we store these in the static directory and won't be in the resource map */
|
||||
if( in_array( $app, IPS::$ipsApps ) and str_replace( '/', '', $path ) !== 'custom' )
|
||||
{
|
||||
|
||||
$url = Url::internal( 'static/resources/' . $app . '_' . static::makeBuiltTemplateLookupHash($app, $location, $path) . '_' .$name, 'static' );
|
||||
|
||||
if( $noProtocol )
|
||||
@@ -766,7 +766,6 @@ class Theme extends Model
|
||||
return $url;
|
||||
}
|
||||
|
||||
|
||||
if ( in_array( $key, array_keys( $this->resource_map ) ) )
|
||||
{
|
||||
if ( $this->resource_map[ $key ] === NULL )
|
||||
@@ -777,7 +776,7 @@ class Theme extends Model
|
||||
{
|
||||
$url = File::get( 'core_Theme', $this->resource_map[ $key ] )->url;
|
||||
|
||||
if( $noProtocol and $url instanceof Url)
|
||||
if( $noProtocol and $url instanceof Url )
|
||||
{
|
||||
$url = $url->setScheme(NULL);
|
||||
}
|
||||
@@ -816,7 +815,7 @@ class Theme extends Model
|
||||
|
||||
static::unlock( $flagKey );
|
||||
|
||||
if( in_array( $app, IPS::$ipsApps ) )
|
||||
if( in_array( $app, IPS::$ipsApps ) and $resource['resource_path'] !== '/custom/' )
|
||||
{
|
||||
$url = rtrim( Settings::i()->base_url, '/' ) . '/' . $resourceMap[ $key ];
|
||||
}
|
||||
@@ -1011,33 +1010,6 @@ class Theme extends Model
|
||||
}
|
||||
}
|
||||
|
||||
if ( $editorData = json_decode( $obj->_data['theme_editor_data'], true ) )
|
||||
{
|
||||
$obj->_data['theme_editor_data'] = $editorData;
|
||||
|
||||
if ( ! isset( $obj->_data['theme_editor_data']['header'] ) )
|
||||
{
|
||||
$obj->_data['theme_editor_data']['header'] = [
|
||||
'logo' => 4,
|
||||
'navigation' => 5,
|
||||
'user' => 6,
|
||||
'breadcrumb' => 7,
|
||||
'search' => 9,
|
||||
];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Set up defaults */
|
||||
$obj->_data['theme_editor_data'] = [ 'header' => [
|
||||
'logo' => 4,
|
||||
'navigation' => 5,
|
||||
'user' => 6,
|
||||
'breadcrumb' => 7,
|
||||
'search' => 9,
|
||||
] ];
|
||||
}
|
||||
|
||||
if ( ! is_array( $obj->_data['resource_map'] ) )
|
||||
{
|
||||
if( $obj->id > 0 )
|
||||
@@ -1142,6 +1114,24 @@ class Theme extends Model
|
||||
*/
|
||||
public function getCustomCssForOutput(): string
|
||||
{
|
||||
/* Use pending changes, if we have any */
|
||||
if ( Member::loggedIn()->isEditingTheme() and isset( Request::i()->cookie['theme_editor_vars'] ) and $cookie = json_decode( Request::i()->cookie['theme_editor_vars'], true ) )
|
||||
{
|
||||
if( array_key_exists( 'custom', $cookie ) )
|
||||
{
|
||||
/* Compile the pending CSS but don't store it */
|
||||
$functionName = "css_" . uniqid();
|
||||
static::makeProcessFunction( static::fixResourceTags( (string) $cookie['custom'], 'front' ), $functionName, '', FALSE, TRUE );
|
||||
|
||||
$fqFunc = 'IPS\\Theme\\'. $functionName;
|
||||
$content = static::minifyCss( $fqFunc() );
|
||||
|
||||
/* Replace any <fileStore.xxx> tags in the CSS */
|
||||
Output::i()->parseFileObjectUrls( $content );
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
|
||||
$key = $this->_id . '_theme_editor_custom_css';
|
||||
if ( ! isset( Store::i()->$key ) )
|
||||
{
|
||||
@@ -1307,6 +1297,43 @@ class Theme extends Model
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the default so that we can determine if this is customized or not
|
||||
*
|
||||
* @var array[]
|
||||
*/
|
||||
public static array $defaultThemeEditorData = [
|
||||
'header' => [
|
||||
'logo' => 4,
|
||||
'navigation' => 5,
|
||||
'user' => 6,
|
||||
'breadcrumb' => 7,
|
||||
'search' => 9,
|
||||
]
|
||||
];
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function get_theme_editor_data() : array
|
||||
{
|
||||
$editorData = isset( $this->_data['theme_editor_data'] ) ? json_decode( $this->_data['theme_editor_data'], true ) : [];
|
||||
if( !isset( $editorData['header'] ) )
|
||||
{
|
||||
$editorData['header'] = static::$defaultThemeEditorData['header'];
|
||||
}
|
||||
return $editorData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $val
|
||||
* @return void
|
||||
*/
|
||||
public function set_theme_editor_data( mixed $val ) : void
|
||||
{
|
||||
$this->_data['theme_editor_data'] = is_array( $val ) ? json_encode( $val ) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* [Node] Return the custom badge for each row
|
||||
*
|
||||
@@ -1440,6 +1467,9 @@ class Theme extends Model
|
||||
/* Copy logos */
|
||||
$this->logo_data = NULL;
|
||||
$this->copyLogosFromSet( $originalId );
|
||||
|
||||
/* Copy custom settings and categories */
|
||||
$this->copyEditorSettings( $originalId );
|
||||
|
||||
Session::i()->log( 'acplogs__themeset_created', array( sprintf( Member::loggedIn()->language()->get( 'theme_clone_copy_of' ), Member::loggedIn()->language()->get( 'core_theme_set_title_' . $originalId ) ) => FALSE ) );
|
||||
}
|
||||
@@ -2053,6 +2083,46 @@ class Theme extends Model
|
||||
$this->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the editor settings and categories from the specified theme
|
||||
*
|
||||
* @param int $id
|
||||
* @return void
|
||||
*/
|
||||
public function copyEditorSettings( int $id ) : void
|
||||
{
|
||||
$categoryMapping = [];
|
||||
foreach( new ActiveRecordIterator(
|
||||
Db::i()->select( '*', Category::$databaseTable, [ 'cat_set_id=?', $id ], 'cat_parent, cat_position' ),
|
||||
Category::class
|
||||
) as $cat )
|
||||
{
|
||||
/* Copy the category and change the theme ID */
|
||||
$newCategory = clone $cat;
|
||||
$newCategory->set_id = $this->_id;
|
||||
if( $newCategory->parent and array_key_exists( $cat->parent, $categoryMapping ) )
|
||||
{
|
||||
$newCategory->parent = $categoryMapping[ $cat->parent ];
|
||||
}
|
||||
$newCategory->save();
|
||||
|
||||
/* Map the category IDs, we'll need them later */
|
||||
$categoryMapping[ $cat->id ] = $newCategory->id;
|
||||
}
|
||||
|
||||
/* Now loop through the settings */
|
||||
foreach( new ActiveRecordIterator(
|
||||
Db::i()->select( '*', Setting::$databaseTable, [ 'setting_set_id=?', $id ], 'setting_position' ),
|
||||
Setting::class
|
||||
) as $setting )
|
||||
{
|
||||
$newSetting = clone $setting;
|
||||
$newSetting->set_id = $this->_id;
|
||||
$newSetting->category_id = $categoryMapping[ $newSetting->category_id ] ?? 0;
|
||||
$newSetting->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Install the theme editor settings for this theme
|
||||
*
|
||||
@@ -2742,7 +2812,10 @@ EOF;
|
||||
{
|
||||
foreach( $cookie as $group => $vars )
|
||||
{
|
||||
$customValues = array_merge( $customValues, $vars );
|
||||
if( is_array( $vars ) )
|
||||
{
|
||||
$customValues = array_merge( $customValues, $vars );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2839,7 +2912,17 @@ EOF;
|
||||
/* Parse the value based on the setting type */
|
||||
try
|
||||
{
|
||||
$value = Setting::load( $key, 'setting_key' )->parsedValue( $value );
|
||||
$setting = Setting::load( $key, 'setting_key' );
|
||||
$value = $setting->parsedValue( $value );
|
||||
if( $setting->type == Setting::SETTING_IMAGE )
|
||||
{
|
||||
if( empty( $value ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = "url('" . $value . "')";
|
||||
}
|
||||
}
|
||||
catch( OutOfRangeException ){}
|
||||
|
||||
@@ -3894,9 +3977,7 @@ EOF;
|
||||
if( $functionName == 'theme_core_front_global_footer' or ( $functionName == 'footer' and $app == 'core' and $location == 'front' and $group == 'global' ) )
|
||||
{
|
||||
$content = $content . "\n<p class='ipsCopyright'>
|
||||
<span class='ipsCopyright__user'>{lang=\"copyright_line_value\"}</span>
|
||||
{{if !\$licenseData = \IPS\IPS::licenseKey() or !isset(\$licenseData['products']['copyright']) or !\$licenseData['products']['copyright']}}<a rel='nofollow' title='Invision Community' href='https://www.invisioncommunity.com/'>Powered by <span translate='no'>Invision Community</span></a>{{endif}}
|
||||
</p>";
|
||||
<span class='ipsCopyright__user'>{lang=\"copyright_line_value\"}</span></p>";
|
||||
}
|
||||
|
||||
/* Expand hook points */
|
||||
@@ -4607,7 +4688,7 @@ PHP;
|
||||
$this->custom_css = '';
|
||||
$this->css_variables = null;
|
||||
$this->view_options = null;
|
||||
$this->theme_editor_data = json_encode( $data );
|
||||
$this->theme_editor_data = $data;
|
||||
$this->save();
|
||||
}
|
||||
|
||||
@@ -4746,7 +4827,7 @@ PHP;
|
||||
*/
|
||||
public function isCustomized(): bool
|
||||
{
|
||||
if ( is_array( $this->set_theme_editor_data ) and count( $this->set_theme_editor_data ) )
|
||||
if( $this->theme_editor_data !== static::$defaultThemeEditorData )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user