Invision Power Services, Inc. * @copyright (c) Invision Power Services, Inc. * @license https://www.invisioncommunity.com/legal/standards/ * @package Invision Community * @since 16 Apr 2013 */ namespace IPS; /* To prevent PHP errors (extending class does not exist) revealing path */ if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) ) { header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' ); exit; } /** * Skin set */ class _Theme extends \IPS\Node\Model { /** * @brief [ActiveRecord] Multiton Store */ protected static $multitons; /** * @brief [ActiveRecord] Database Table */ public static $databaseTable = 'core_themes'; /** * @brief [ActiveRecord] Database Prefix */ public static $databasePrefix = 'set_'; /** * @brief [ActiveRecord] ID Database Column */ public static $databaseColumnId = 'id'; /** * @brief [ActiveRecord] Database ID Fields */ protected static $databaseIdFields = array('set_id', 'set_key', 'set_marketplace_id'); /** * @brief [ActiveRecord] Multiton Map */ protected static $multitonMap = array(); /** * @brief [Node] Parent Node ID Database Column */ public static $databaseColumnParent = 'parent_id'; /** * @brief [Node] Order Database Column */ public static $databaseColumnOrder = 'order'; /** * @brief [Node] Node Title */ public static $nodeTitle = 'menu__core_customization_themes'; /** * @brief [Node] Show forms modally? */ public static $modalForms = TRUE; /** * @brief [Node] Title prefix. If specified, will look for a language key with "{$key}_title" as the key */ public static $titleLangPrefix = 'core_theme_set_title_'; /** * @brief IN_DEV "theme" */ public static $inDevTheme = NULL; /** * @brief Setup "theme" */ protected static $setupSkin = NULL; /** * @brief Admin "theme" */ public static $adminSkin = NULL; /** * @brief Member's "theme" */ public static $memberTheme = NULL; /** * @brief [SkinSets] Store theme set parent/id relationship for parent/child recursion */ public static $themeSetRelationships = array(); /** * @brief Have fetched all? */ protected static $gotAll = FALSE; /** * @brief [SkinSets] Stores the default theme set id */ public static $defaultFrontendThemeSet = 0; /** * @brief [SkinSets] Templates already loaded and evald via getTemplate() */ public static $calledTemplates = array(); /** * @brief [SkinSets] Some CSS files are built from a directory to save on http requests. They are saved as {$location}_{$folder}.css (so front_responsive.css for example) */ protected static $buildGrouping = array( 'css' => array( 'core' => array( 'global'=> array( 'framework', 'responsive' ), 'front' => array( 'custom' ), 'admin' => array( 'core', 'responsive' ) ) ) ); /** * @brief Return type for getRawTemplates/getRawCss: Return all */ const RETURN_ALL = 1; /** * @brief Return type for getRawTemplates/getRawCss: Return groups and names as a tree */ const RETURN_BIT_NAMES = 2; /** * @brief Return type for getRawTemplates/getRawCss: Return groups and names as a tree with array of data without content */ const RETURN_ALL_NO_CONTENT = 4; /** * @brief Return type for getRawTemplates/getRawCss: Returns bit names as a flat array */ const RETURN_ARRAY_BIT_NAMES = 8; /** * @brief Return type for getRawTemplates/getRawCss: Uses DB if not IN_DEV, otherwise uses disk .phtml look up */ const RETURN_NATIVE = 16; /** * @brief Type for templates */ const TEMPLATES = 1; /** * @brief Type for CSS */ const CSS = 2; /** * @brief Type for Images */ const IMAGES = 4; /** * @brief Bit option for theme settings */ const THEME_KEY_VALUE_PAIRS = 1; /** * @brief Bit option for theme settings */ const THEME_ID_KEY = 2; /** * @brief Bit option for theme settings */ const TEMPLATE_SETTINGS_DEFAULT = 4; /** * @brief Disable the copy button - useful when the forms are very distinctly different */ public $noCopyButton = TRUE; /** * @brief [ActiveRecord] Caches * @note Defined cache keys will be cleared automatically as needed */ protected $caches = array( 'updatecount_themes' ); /** * Get currently logged in member's theme * * @return \IPS\Theme */ public static function i() { if ( \IPS\Dispatcher::hasInstance() AND class_exists( '\IPS\Dispatcher', FALSE ) and \IPS\Dispatcher::i()->controllerLocation === 'setup' ) { if ( static::$setupSkin === NULL ) { static::$setupSkin = new \IPS\Theme\Setup\Theme; } return static::$setupSkin; } else if ( \IPS\RECOVERY_MODE ) { if ( static::$memberTheme === NULL ) { foreach( static::themes() as $theme ) { if ( ! $theme->hasCustomizations() ) { static::$memberTheme = $theme; break; } } } /* Set Admin Theme */ if ( static::$adminSkin === NULL ) { static::$adminSkin = static::master(); } /* Still here because all themes are customized? */ if ( static::$memberTheme === NULL ) { $newTheme = new \IPS\Theme; $newTheme->permissions = \IPS\Member::loggedIn()->member_group_id; $newTheme->save(); $newTheme->installThemeSettings(); $newTheme->copyResourcesFromSet(); \IPS\Lang::saveCustom( 'core', "core_theme_set_title_" . $newTheme->id, "IPS Default" ); static::$memberTheme = $newTheme; } return static::$memberTheme; } else if ( \IPS\Theme::designersModeEnabled() AND \IPS\Dispatcher::hasInstance() AND class_exists( '\IPS\Dispatcher', FALSE ) and \IPS\Dispatcher::i()->controllerLocation === 'front' ) { if ( \IPS\IN_DEV ) { die("Please disable IN_DEV while in Designer's Mode"); } if ( static::$memberTheme === NULL ) { static::themes(); static::$memberTheme = new \IPS\Theme\Advanced\Theme; /* Add in the default theme properties (_data array, etc) */ foreach( static::$multitons[ \IPS\Theme\Advanced\Theme::$currentThemeId ] as $k => $v ) { static::$memberTheme->$k = $v; } } return static::$memberTheme; } else if ( \IPS\IN_DEV ) { if ( static::$inDevTheme === NULL ) { static::$inDevTheme = new \IPS\Theme\Dev\Theme; static::themes(); /* Add in the default theme properties (_data array, etc) */ $default = ( isset( static::$multitons[ \IPS\DEFAULT_THEME_ID ] ) ) ? static::$multitons[ \IPS\DEFAULT_THEME_ID ] : reset( static::$multitons ); foreach( $default as $k => $v ) { static::$inDevTheme->$k = $v; } if ( static::isUsingEasyModeEditor() ) { $setId = \intval( \IPS\Request::i()->cookie['vseThemeId'] ); static::$memberTheme = empty( $setId ) ? static::defaultTheme() : static::load( $setId ); } } return static::$inDevTheme; } else if ( \IPS\Dispatcher::hasInstance() AND class_exists( '\IPS\Dispatcher', FALSE ) and \IPS\Dispatcher::i()->controllerLocation === 'admin' ) { if ( static::$adminSkin === NULL ) { static::$adminSkin = static::master(); } return static::$adminSkin; } else { if ( static::$memberTheme === NULL ) { static::themes(); $setId = NULL; if ( \IPS\Dispatcher::hasInstance() and \IPS\Dispatcher::i()->controllerLocation == 'front' ) { $setId = static::currentThemeId(); if ( $setId ) { if( ! \IPS\Request::i()->isAjax() and \IPS\Session\Front::loggedIn() ) { /* Not an ajax call, so reset theme_id */ $setId = NULL; \IPS\Session\Front::i()->setTheme(0); } else { try { if ( static::load( $setId )->canAccess() !== true ) { $setId = NULL; } } catch ( \OutOfRangeException $ex ) { $setId = NULL; } } } } if ( ! $setId and \IPS\Member::loggedIn()->skin and array_key_exists( \IPS\Member::loggedIn()->skin, static::themes() ) ) { $setId = \IPS\Member::loggedIn()->skin; if ( static::load( $setId )->canAccess() !== true ) { $setId = static::defaultTheme(); /* Restore default theme for member */ \IPS\Member::loggedIn()->skin = $setId; if( \IPS\Member::loggedIn()->member_id ) { \IPS\Member::loggedIn()->save(); } } } else if ( ! $setId ) { $setId = static::defaultTheme(); } if ( static::isUsingEasyModeEditor() ) { $setId = \intval( \IPS\Request::i()->cookie['vseThemeId'] ); if ( ! empty( $setId ) ) { $ok = false; try { $theme = static::load( $setId ); if ( $theme->by_skin_gen ) { $ok = true; } } catch( \OutOfRangeException $ex ) { $ok = false; } if ( $ok !== true ) { /* Update the current member */ \IPS\Member::loggedIn()->members_bitoptions['bw_using_skin_gen'] = 0; \IPS\Member::loggedIn()->save(); \IPS\Request::i()->setCookie( 'vseThemeId', 0 ); $setId = static::defaultTheme(); } } else { $setId = static::defaultTheme(); } } static::$memberTheme = static::load( $setId ); } return static::$memberTheme; } } /** * Get current theme id * * @return int|null */ public static function currentThemeId() { if ( \IPS\Session\Front::loggedIn() ) { return \IPS\Session\Front::i()->getTheme(); } return isset( \IPS\Request::i()->cookie['theme'] ) ? \IPS\Request::i()->cookie['theme'] : NULL; } /** * Themes * * @return array */ public static function themes() { if ( !static::$gotAll ) { static::$gotAll = true; static::$themeSetRelationships = array(); if ( isset( \IPS\Data\Store::i()->themes ) ) { $rows = \IPS\Data\Store::i()->themes; } else { $rows = iterator_to_array( \IPS\Db::i()->select( '*', 'core_themes', NULL, 'set_order' )->setKeyField('set_id') ); \IPS\Data\Store::i()->themes = $rows; } foreach( $rows as $id => $theme ) { if ( $theme['set_is_default'] ) { static::$defaultFrontendThemeSet = $theme['set_id']; } static::$themeSetRelationships[ $theme['set_parent_id'] ][ $theme['set_id'] ] = $theme; static::$multitons[ $theme['set_id'] ] = static::constructFromData( $theme ); } } return static::$multitons; } /** * Returns only the visible themes for the member * * @return array */ public static function getThemesWithAccessPermission() { $visibleThemes = array(); foreach( \IPS\Theme::roots() as $theme ) { if ( $theme->canAccess() ) { $visibleThemes[$theme->id] = $theme; } static::getChildrenWithAccessPermission( $theme, $visibleThemes ); } return $visibleThemes; } /** * Returns only the visible children * @param \IPS\Theme $theme Parent theme * @param Array $visibleThemes Visible themes array passed by reference * * @return void */ public static function getChildrenWithAccessPermission( \IPS\Theme $theme, Array &$visibleThemes ) { foreach ( $theme->children() as $child ) { if( $child->canAccess() ) { $visibleThemes[ $child->id ] = $child; static::getChildrenWithAccessPermission( $child, $visibleThemes ); } } } /** * Fetch the master theme object * * @return \IPS\Theme */ public static function master() { static::themes(); $default = static::$multitons[ static::$defaultFrontendThemeSet ]; $master = static::constructFromData( array_merge( static::$themeSetRelationships[ $default->parent_id ][ $default->id ], array( 'set_id' => 0 ) ) ); $master->_data['settings'] = $default->getThemeSettings( static::TEMPLATE_SETTINGS_DEFAULT | static::THEME_KEY_VALUE_PAIRS ); unset( static::$multitons[ 0 ] ); return $master; } /** * Is designer's mode enabled? * * @return boolean */ public static function designersModeEnabled() { return (boolean) \IPS\Settings::i()->theme_designers_mode; } /** * Default Frontend Skin Set ID * * @return int */ public static function defaultTheme() { if ( !static::$gotAll ) { static::themes(); } return static::$defaultFrontendThemeSet; } /** * Switches the currently initialized theme during execution * * @param int $themeId Id of the theme to switch to * @param boolean $persistent Allow to persist through ajax calls * @return boolean * @note This will not check to ensure the member has permission to view the theme */ public static function switchTheme( $themeId, $persistent=TRUE ) { static::themes(); try { /* Make sure the member theme is set */ \IPS\Theme::i(); $class = static::i(); static::$memberTheme = $class::load( $themeId ); /* Store the ID in sessions so ajax loads correct theme */ if ( $persistent ) { \IPS\Session\Front::i()->setTheme( $themeId ); } /* Flush loaded CSS */ \IPS\Output::i()->cssFiles = array(); \IPS\Dispatcher\Front::baseCss(); /* App CSS */ \IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( \IPS\Dispatcher\Front::i()->application->directory . '.css', \IPS\Dispatcher\Front::i()->application->directory, \IPS\Dispatcher\Front::i()->controllerLocation ) ); if ( \IPS\Theme::i()->settings['responsive'] ) { \IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( \IPS\Dispatcher\Front::i()->application->directory . '_responsive.css', \IPS\Dispatcher\Front::i()->application->directory, \IPS\Dispatcher\Front::i()->controllerLocation ) ); } } catch( \OutOfRangeException $e ) { return FALSE; } } /** * @brief Prevent CSS from being loaded more than once */ protected static $usedCss = array(); /** * Get CSS * This method is used to return the built CSS stored in the file objects system * * @param string $file Filename * @param string|null $app Application * @param string|null $location Location (e.g. 'admin', 'front') * @return array URLs to CSS files */ public function css( $file, $app=NULL, $location=NULL ) { $app = $app ?: \IPS\Dispatcher::i()->application->directory; $location = $location ?: \IPS\Dispatcher::i()->controllerLocation; $paths = explode( '/', $file ); $name = array_pop( $paths ); $path = ( \count( $paths ) ) ? implode( '/', $paths ) : '.'; $cacheKey = $file . ',' . $app . ',' . $location . ',' . $this->_id; if ( isset( static::$usedCss[ $cacheKey ] ) ) { return static::$usedCss[ $cacheKey ]; } if ( $location === 'interface' ) { static::$usedCss[ $cacheKey ] = array( rtrim( \IPS\Http\Url::baseUrl( \IPS\Http\Url::PROTOCOL_RELATIVE ), '/' ) . "/applications/{$app}/interface/{$file}" ); return static::$usedCss[ $cacheKey ]; } $key = static::makeBuiltTemplateLookupHash( $app, $location, $path . '/' . $name ); if ( \in_array( $key, array_keys( $this->css_map ) ) ) { if ( $this->css_map[ $key ] !== null ) { static::$usedCss[ $cacheKey ] = array( \IPS\File::get( 'core_Theme', $this->css_map[ $key ] )->url ); } else { static::$usedCss[ $cacheKey ] = array(); } return static::$usedCss[ $cacheKey ]; } else { /* We're setting up, do nothing to avoid compilation requests when tables are incomplete */ if ( ! isset( \IPS\Settings::i()->setup_in_progress ) OR \IPS\Settings::i()->setup_in_progress ) { /* Do not store a cache here as this will break later when we want CSS */ //return array(); // If we don't let the CSS rebuild here as needed then the AdminCP upgrader can result in a completely unstyled page when interrupted } /* Map doesn't exist, try and create it */ if ( $this->compileCss( $app, $location, $path, $name ) === NULL ) { /* Do not store a cache here as this will break later when we want CSS */ return array(); } /* Still not here? Then add a key but as null to prevent it from attempting to rebuild on every single page * load thus hitting the DB multiple times */ $cssMap = $this->css_map; if ( ! \in_array( $key, array_keys( $this->css_map ) ) ) { $cssMap[ $key ] = null; $this->css_map = $cssMap; $this->save(); } else { static::$usedCss[ $cacheKey ] = array( \IPS\File::get( 'core_Theme', $this->css_map[ $key ] )->url ); return static::$usedCss[ $cacheKey ]; } } return array(); } /** * Get Theme Resource (image, font, theme-specific JS, etc) * * @param string $path Path to resource * @param string|null $app Application key * @param string|null $location Location * @return \IPS\Http\Url|NULL URL to resource */ public function resource( $path, $app=NULL, $location=NULL, $noProtocol=FALSE ) { $app = $app ?: \IPS\Dispatcher::i()->application->directory; $location = $location ?: \IPS\Dispatcher::i()->controllerLocation; $paths = explode( '/', $path ); $name = array_pop( $paths ); $path = ( \count( $paths ) ) ? ( '/' . implode( '/', $paths ) . '/' ) : '/'; $key = static::makeBuiltTemplateLookupHash($app, $location, $path) . '_' .$name; if ( $location === 'interface' ) { return \IPS\Http\Url::internal( "applications/{$app}/interface{$path}{$name}", 'interface', NULL, array(), \IPS\Http\Url::PROTOCOL_RELATIVE ); } if ( \in_array( $key, array_keys( $this->resource_map ) ) ) { if ( $this->resource_map[ $key ] === NULL ) { return NULL; } else { $url = \IPS\File::get( 'core_Theme', $this->resource_map[ $key ] )->url; if( $noProtocol and $url instanceof \IPS\Http\Url ) { $url = $url->setScheme(NULL); } return $url; } } /* Still here? Map doesn't exist, try and create it */ $resourceMap = $this->resource_map; try { /* We're setting up, do nothing to avoid compilation requests when tables are incomplete */ if ( ! isset( \IPS\Settings::i()->setup_in_progress ) OR \IPS\Settings::i()->setup_in_progress ) { return NULL; } $flagKey = 'resource_compiling_' . $this->_id . '_' . md5( $key ); if ( static::checkLock( $flagKey ) ) { return NULL; } static::lock( $flagKey ); $resource = \IPS\Db::i()->select( '*', 'core_theme_resources', array( 'resource_set_id=? AND resource_app=? AND resource_location=? AND resource_path=? AND resource_name=?', $this->id, $app, $location, $path, $name ) )->first(); $resourceMap[ $key ] = (string) \IPS\File::create( 'core_Theme', $key, $resource['resource_data'], 'set_resources_' . $this->id, TRUE, NULL, FALSE ); \IPS\Db::i()->update( 'core_theme_resources', array( 'resource_added' => time(), 'resource_filename' => $resourceMap[ $key ] ), array( 'resource_id=?', $resource['resource_id'] ) ); /* Save map */ $this->resource_map = $resourceMap; $this->save(); static::unlock( $flagKey ); $url = \IPS\File::get( 'core_Theme', $resourceMap[ $key ] )->url; if( $noProtocol and $url instanceof \IPS\Http\Url ) { $url = $url->setScheme(NULL); } return $url; } catch( \UnderflowException $e ) { /* Doesn't exist, add null entry to map to prevent it from being rebuilt on each page load */ $resourceMap[ $key ] = null; /* Save map */ $this->resource_map = $resourceMap; $this->save(); return NULL; } } /** * Get a template * * @param string $group Template Group * @param string $app Application key (NULL for current application) * @param string $location Template Location (NULL for current template location) * @return \IPS\Output\Template * @throws \UnexpectedValueException */ public function getTemplate( $group, $app=NULL, $location=NULL ) { /* Do we have an application? */ if( $app === NULL ) { $app = \IPS\Dispatcher::i()->application->directory; } /* How about a template location? */ if( $location === NULL ) { $location = \IPS\Dispatcher::i()->controllerLocation; } $hashedKey = \strtolower( static::makeBuiltTemplateLookupHash( $app, $location, $group ) . '_' . static::cleanGroupName( $group ) ); $key = 'template_' . $this->id . '_' . $hashedKey; $cachedObject = NULL; /* We cannot use isset( static::$calledTemplates[ $key ] ) here because it fails with NULL while in_array does not */ if ( !\in_array( $hashedKey, array_keys( static::$calledTemplates ) ) ) { /* First, are we on the front end and using a disk cache? */ if ( static::isUsingTemplateDiskCache() and $location != 'admin' ) { $cache = new \IPS\Theme\Cache\Template( $app, $location, $group ); if ( $cache->exists() ) { $cachedObject = $cache->get(); } else { /* If it exists in datastore, use that to write the value */ if ( isset( \IPS\Data\Store::i()->$key ) ) { try { $cache->set( str_replace( 'namespace IPS\Theme;', 'namespace IPS\Theme\Cache;', \IPS\Data\Store::i()->$key ) ); } catch( \RuntimeException $e ) { /* Should we log, ignore or collect failures to notify admin? */ } } else { /* We'll compile it now, but let datastore serve the template this time round */ $this->compileTemplates( $app, $location, $group ); } } } /* If we don't have a compiled template, do that now */ if ( ! $cachedObject and !isset( \IPS\Data\Store::i()->$key ) ) { /* It can take a few seconds for templates to finish compiling if initiated elsewhere, so let's try a few times sleeping 1 second between attempts to give the compilation time to finish */ $attempts = 0; while( $attempts < 6 ) { if ( $attempts === 5 ) { /* Rebuild in progress */ \IPS\Log::debug( "Template store key: {$key} rebuilding and requested again ({$app}, {$location}, {$group})", "template_store_building" ); /* Since we can't do anything else, this ends up just being an uncaught exception - show the error page right away to avoid the unnecessary logging */ \IPS\IPS::genericExceptionPage(); } $built = $this->compileTemplates( $app, $location, $group ); if ( $built === NULL ) { $attempts++; sleep(1); } else { break; } } /* Still no key? */ if ( ! isset( \IPS\Data\Store::i()->$key ) ) { \IPS\Log::log( "Template store key: {$key} missing ({$app}, {$location}, {$group})", "template_store_missing" ); throw new \ErrorException( 'template_store_missing' ); } } /* Load compiled template */ if ( $cachedObject ) { /* Init */ static::$calledTemplates[ $hashedKey ] = new \IPS\Theme\SandboxedTemplate( $cachedObject ); } else { $compiledGroup = \IPS\Data\Store::i()->$key; if ( \IPS\DEBUG_TEMPLATES ) { static::runDebugTemplate( $key, $compiledGroup ); } else { try { if ( @eval( $compiledGroup ) === FALSE ) { throw new \IPS\Theme\TemplateException( 'Invalid Template', 1000, NULL, array( 'group' => $group, 'app' => $app, 'location' => $location ), $this ); } } catch ( \ParseError $e ) { throw new \UnexpectedValueException; } } /* Hooks */ $class = "\\IPS\\Theme\\" . static::overloadHooks( 'class_' . $app . '_' . $location . '_' . $group ); /* Init */ static::$calledTemplates[ $hashedKey ] = new \IPS\Theme\SandboxedTemplate( new $class( $app, $location, $group ) ); } } return static::$calledTemplates[ $hashedKey ]; } /** * Determine the path to load the file from * * @param string $file File path * @return string */ public static function getHookPath( $file ) { $path = \IPS\ROOT_PATH; if ( \IPS\CIC2 AND \IPS\IPS::isThirdParty( $file ) ) { $path = \IPS\SITE_FILES_PATH; } return $path; } /** * Eval hooks into life * * @param string $class Class name * @param string $namespace Namespace for the templates * @return Modified classname */ public static function overloadHooks( $class, $namespace="IPS\\Theme" ) { if ( isset( \IPS\IPS::$hooks[ "\\IPS\\Theme\\{$class}" ] ) AND \IPS\RECOVERY_MODE === FALSE ) { foreach ( \IPS\IPS::$hooks[ "\\IPS\\Theme\\{$class}" ] as $id => $data ) { $path = static::getHookPath( $data['file'] ); if ( file_exists( $path . '/' . $data['file'] ) ) { if ( class_exists( "{$namespace}\\" . $data['class'], FALSE ) ) { $class = $data['class']; continue; } $contents = "namespace {$namespace};\n\n" . str_replace( '_HOOK_CLASS_', $class, file_get_contents( $path . '/' . $data['file'] ) ); try { if( eval( $contents ) !== FALSE ) { $class = $data['class']; } } catch ( \ParseError $e ) { } } } } return $class; } /*! Active Record */ /** * Construct ActiveRecord from database row * * @param array $data Row from database table * @param bool $updateMultitonStoreIfExists Replace current object in multiton store if it already exists there? * @return static */ public static function constructFromData( $data, $updateMultitonStoreIfExists = TRUE ) { $obj = parent::constructFromData( $data, $updateMultitonStoreIfExists ); /* Extra set up */ $logo = json_decode( $obj->logo_data, true ); $obj->_data['logo'] = array( 'front' => null ); if ( \is_array( $logo ) ) { if ( isset( $logo['front'] ) ) { $obj->_data['logo']['front'] = $logo['front']; } } if ( $settings = json_decode( $obj->_data['template_settings'], true ) ) { $obj->_data['settings'] = $settings; } else { /* No settings here */ $obj->_data['settings'] = array(); } if ( ! \is_array( $obj->_data['resource_map'] ) ) { if( $obj->id > 0 ) { if ( $imgMap = json_decode( $obj->_data['resource_map'], true ) ) { $obj->_data['resource_map'] = $imgMap; } else { $obj->_data['resource_map'] = array(); } } else { if( !isset( \IPS\Data\Store::i()->acp_resource_map ) ) { $obj->_data['resource_map'] = []; } else { $obj->_data['resource_map'] = \IPS\Data\Store::i()->acp_resource_map; } } } if ( ! \is_array( $obj->_data['css_map'] ) ) { if ( $obj->id > 0 ) { if ( $cssMap = json_decode( $obj->_data['css_map'], true ) ) { $obj->_data['css_map'] = $cssMap; } else { $obj->_data['css_map'] = array(); } } else { $obj->_data['css_map'] = json_decode( \IPS\Settings::i()->acp_css_map, true ); } } return $obj; } /** * Save resource map * * @param $value array Value to save */ public function set_resource_map( $value ) { if ( \is_array( $value ) ) { $this->_data['resource_map'] = json_encode( $value ); if ( $this->id === 0 ) { \IPS\Data\Store::i()->acp_resource_map = $value; } } } /** * Save CSS map * * @param $value array Value to save */ public function set_css_map( $value ) { if ( \is_array( $value ) ) { $this->_data['css_map'] = json_encode( $value ); if ( $this->id === 0 ) { \IPS\Settings::i()->changeValues( array( 'acp_css_map' => json_encode( $value ) ) ); } } } /** * Add in the header and footer special tags if they are not already present in globalTemplate * * @return void */ public function fixHeaderAndFooterTags() { /* Check the globalTemplate for the special tags we need */ try { $template = \IPS\Db::i()->select( '*', 'core_theme_templates', [ 'template_set_id=? AND template_app=? AND template_group=? AND template_location=? and template_name=?', $this->_id, 'core', 'global', 'front', 'globalTemplate'] )->first(); $update = FALSE; if ( ! \stristr( $template['template_content'], '{theme="headerHtml"}') ) { $template['template_content'] = preg_replace( '#(