<?php
/**
* @brief IN_DEV Skin Set
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
* @copyright (c) Invision Power Services, Inc.
* @license https://www.invisioncommunity.com/legal/standards/
* @package Invision Community
* @since 16 Apr 2013
*/
namespace IPS\Theme\Dev;
/* 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;
}
/**
* IN_DEV Skin set
*/
class _Theme extends \IPS\Theme
{
/**
* @brief Template Classes
*/
protected $templates;
/**
* @brief Stored plugins
*/
protected static $plugins = array();
/**
* Get raw templates. Raw means HTML logic and variables are still in {{format}}
*
* @param string|array $app Template app (e.g. core, forum)
* @param string|array $location Template location (e.g. admin,global,front)
* @param string|array $group Template group (e.g. login, share)
* @param int|constant $returnType Determines the content returned
* @param boolean $returnThisSetOnly Returns rows unique to this set only
* @return array
*/
public function getRawTemplates( $app=array(), $location=array(), $group=array(), $returnType=null, $returnThisSetOnly=false )
{
$returnType = ( $returnType === null ) ? self::RETURN_ALL : $returnType;
$app = ( is_string( $app ) AND $app != '' ) ? array( $app ) : $app;
$location = ( is_string( $location ) AND $location != '' ) ? array( $location ) : $location;
$group = ( is_string( $group ) AND $group != '' ) ? array( $group ) : $group;
$where = array();
$templates = array();
if ( ! ( $returnType & static::RETURN_NATIVE ) )
{
return parent::getRawTemplates( $app, $location, $group, $returnType, $returnThisSetOnly );
}
$fixedLocations = array( 'admin', 'front', 'global' );
$results = array();
foreach( \IPS\Application::applications() as $appDir => $application )
{
if ( $app === NULL or ( in_array( $appDir, $app ) ) )
{
foreach( $fixedLocations as $_location )
{
if ( $location === NULL or ( in_array( $_location, $location ) ) ) # location?
{
foreach( new \DirectoryIterator( static::_getHtmlPath( $appDir, $_location ) ) as $file )
{
if ( $file->isDir() AND mb_substr( $file->getFilename(), 0, 1 ) !== '.' )
{
if ( $group === NULL or ( in_array( $file->getFilename(), $group ) ) )
{
foreach( new \DirectoryIterator( static::_getHtmlPath( $appDir, $_location, $file->getFilename() ) ) as $template )
{
if ( ! $template->isDir() AND mb_substr( $template->getFilename(), -6 ) === '.phtml' )
{
$results[] = str_replace( ".phtml", "", $template->getFilename() );
}
}
}
}
}
}
}
}
}
return array_unique( $results );
}
/**
* 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\Theme\Template
*/
public function getTemplate( $group, $app=NULL, $location=NULL )
{
/* Do we have an application? */
if( $app === NULL )
{
if ( !\IPS\Dispatcher::hasInstance() )
{
throw new \RuntimeException('NO_APP');
}
$app = \IPS\Dispatcher::i()->application->directory;
}
/* How about a template location? */
if( $location === NULL )
{
if ( !\IPS\Dispatcher::hasInstance() )
{
throw new \RuntimeException('NO_LOCATION');
}
$location = \IPS\Dispatcher::i()->controllerLocation;
}
/* Get template */
if ( !isset( $this->templates[ $app ][ $location ][ $group ] ) )
{
$class = 'Template';
if ( isset( \IPS\IPS::$hooks[ "\IPS\Theme\class_{$app}_{$location}_{$group}" ] ) AND \IPS\RECOVERY_MODE === FALSE )
{
foreach ( \IPS\IPS::$hooks[ "\IPS\Theme\class_{$app}_{$location}_{$group}" ] as $id => $data )
{
if( file_exists( \IPS\ROOT_PATH . '/' . $data['file'] ) )
{
$contents = "namespace " . rtrim( static::_getTemplateNamespace(), '\\' ) . ";\n\n" . str_replace( '_HOOK_CLASS_', $class, file_get_contents( \IPS\ROOT_PATH . '/' . $data['file'] ) );
eval( $contents );
$class = $data['class'];
}
}
}
$class = static::_getTemplateNamespace() . $class;
$this->templates[ $app ][ $location ][ $group ] = new $class( $app, $location, $group );
}
return $this->templates[ $app ][ $location ][ $group ];
}
/**
* Get CSS
*
* @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\Request::i()->app;
$location = $location ?: \IPS\Dispatcher::i()->controllerLocation;
if ( $location === 'interface' )
{
$path = \IPS\ROOT_PATH . "/applications/{$app}/interface/{$file}";
}
else
{
$path = static::_getCssPath($app, $location, $file);
}
if ( isset( self::$buildGrouping['css'][ $app ][ $location ] ) AND is_array( self::$buildGrouping['css'][ $app ][ $location ] ) )
{
foreach( self::$buildGrouping['css'][ $app ][ $location ] as $buildPath )
{
if ( mb_substr( $file, 0, -4 ) == $buildPath )
{
$path = static::_getCssPath($app, $location, $buildPath);
$file = $buildPath;
}
}
}
$return = array();
if ( is_dir( $path ) )
{
$bits = explode( '/', $path );
$fileName = array_pop( $bits ) . '.css';
/* Load css/location/folderName/folderName.css first */
if ( is_file( $path . '/' . $fileName ) )
{
$return[] = str_replace( array( 'http://', 'https://' ), '//', \IPS\Settings::i()->base_url ) . "applications/core/interface/css/css.php?css=" . ( str_replace( \IPS\ROOT_PATH . '/', '', static::_getCssPath( $app, $location, $file ) ) ) . $fileName;
}
$csses = array();
foreach ( new \DirectoryIterator( $path ) as $f )
{
if ( !$f->isDot() and mb_substr( $f, -4 ) === '.css' and $f->getFileName() != $fileName )
{
$csses[] = ( str_replace( \IPS\ROOT_PATH . '/', '', static::_getCssPath( $app, $location, $file ) ) ) . $f;
}
}
sort( $csses );
if ( count( $csses ) )
{
if( \IPS\DEV_DEBUG_CSS )
{
foreach( $csses as $cssFile )
{
$return[] = str_replace( array( 'http://', 'https://' ), '//', \IPS\Settings::i()->base_url ) . "applications/core/interface/css/css.php?css=" . $cssFile;
}
}
else
{
$return[] = str_replace( array( 'http://', 'https://' ), '//', \IPS\Settings::i()->base_url ) . "applications/core/interface/css/css.php?css=" . implode( ',', $csses );
}
}
if ( $file === 'custom' and $app === 'core' )
{
foreach ( \IPS\Plugin::enabledPlugins() as $plugin )
{
foreach ( new \GlobIterator( \IPS\ROOT_PATH . '/plugins/' . $plugin->location . '/dev/css/*' ) as $file )
{
$return[] = str_replace( array( 'http://', 'https://' ), '//', \IPS\Settings::i()->base_url ) . "applications/core/interface/css/css.php?css=" . 'plugins/' . $plugin->location . '/dev/css/' . $file->getFilename();
}
}
}
}
elseif ( file_exists( $path ) )
{
$return[] = str_replace( \IPS\ROOT_PATH . '/', '', str_replace( array( 'http://', 'https://' ), '//', \IPS\Settings::i()->base_url ) . "applications/core/interface/css/css.php?css=" . $path );
}
return $return;
}
/**
* Get JS
*
* @param string $file Filename
* @param string|null $app Application
* @param string|null $location Location (e.g. 'admin', 'front')
* @return array URL to JS files
*/
public function js( $file, $app=NULL, $location=NULL )
{
$app = $app ?: \IPS\Request::i()->app;
$location = $location ?: \IPS\Dispatcher::i()->controllerLocation;
$return = array();
if ( $app === 'core' and $location === 'global' and $file === 'plugins' )
{
foreach ( new \GlobIterator( \IPS\ROOT_PATH . '/plugins/*/dev/js/*' ) as $file )
{
try
{
$plugin = \IPS\Plugin::getPluginFromPath( $file );
if( $plugin->enabled )
{
$url = str_replace( \IPS\ROOT_PATH, rtrim( \IPS\Settings::i()->base_url, '/' ), $file );
$return[] = str_replace( '\\', '/', $url );
}
}
catch( \OutOfRangeException $e ){}
}
}
else
{
if ( $location === 'interface' )
{
$path = \IPS\ROOT_PATH . "/applications/{$app}/interface/{$file}";
}
else
{
$path = \IPS\ROOT_PATH . "/applications/{$app}/dev/js/{$location}/{$file}";
}
if ( is_dir( $path ) )
{
$bits = explode( '/', $path );
$fileName = 'ips.' . array_pop( $bits ) . '.js';
if ( is_file( $path .'/' . $fileName ) )
{
$return[] = \IPS\Settings::i()->base_url . "/applications/{$app}/dev/js/{$location}/{$file}/{$fileName}";
}
foreach ( new \DirectoryIterator( $path ) as $f )
{
if ( !$f->isDot() and mb_substr( $f, -3 ) === '.js' and $f->getFileName() != $fileName )
{
$return[] = \IPS\Settings::i()->base_url . "/applications/{$app}/dev/js/{$location}/{$file}/{$f}";
}
}
}
else
{
$return[] = str_replace( \IPS\ROOT_PATH, \IPS\Settings::i()->base_url, $path );
}
}
return $return;
}
/**
* Get Theme Resource (resource, 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 )
{
$baseUrl = \IPS\Settings::i()->base_url;
$app = $app ?: \IPS\Dispatcher::i()->application->directory;
$location = $location ?: \IPS\Dispatcher::i()->controllerLocation;
if ( $location === 'interface' )
{
return \IPS\Http\Url::internal( "applications/{$app}/interface/{$path}", 'interface', NULL, array(), \IPS\Http\Url::PROTOCOL_RELATIVE );
}
$url = \IPS\Http\Url::createFromString( $baseUrl . str_replace( '\\', '/', str_replace( \IPS\ROOT_PATH . '/', '', static::_getResourcePath( $app, $location, $path ) ) ), false );
if( $noProtocol and $url instanceof \IPS\Http\Url )
{
$url = $url->setScheme(NULL);
}
return $url;
}
/**
* (re)import HTML templates into the template DB
*
* @param string $app Application Key
* @param int $id Theme Set Id (0 if IN_DEV and not in advanced theming mode)
*
* @return void
*/
public static function importDevHtml( $app, $id )
{
/* Clear out existing template bits */
\IPS\Db::i()->delete( 'core_theme_templates', array( 'template_app=? AND ( template_set_id=? OR ( template_set_id=0 AND template_added_to=? ) )', $app, $id, $id ) );
$themeLocations = \IPS\Application::load( $app )->themeLocations;
/* Get existing template bits to see if we need to import */
if ( $id > 0 )
{
$currentTemplates = \IPS\Theme::load( $id )->getRawTemplates( $app );
}
$path = static::_getHtmlPath( $app );
if ( is_dir( $path ) )
{
foreach( new \DirectoryIterator( $path ) as $location )
{
if ( $location->isDot() || mb_substr( $location->getFilename(), 0, 1 ) === '.' )
{
continue;
}
if ( $location->isDir() )
{
if ( ! in_array( $location->getFilename(), $themeLocations ) )
{
continue;
}
foreach( new \DirectoryIterator( $path . $location->getFilename() ) as $group )
{
if ( $group->isDot() || mb_substr( $group->getFilename(), 0, 1 ) === '.' )
{
continue;
}
if ( $group->isDir() )
{
foreach( new \DirectoryIterator( $path . $location->getFilename() . '/' . $group->getFilename() ) as $file )
{
if ( $file->isDot() || mb_substr( $file->getFilename(), -6 ) !== '.phtml')
{
continue;
}
/* Get the content */
$html = file_get_contents( $path . $location->getFilename() . '/' . $group->getFilename() . '/' . $file->getFilename() );
$params = array();
/* Parse the header tag */
preg_match( '/^<ips:template parameters="(.+?)?"(.+?)?\/>(\r\n?|\n)/', $html, $params );
/* Strip it */
$html = ( isset($params[0]) ) ? str_replace( $params[0], '', $html ) : $html;
$version = \IPS\Application::load( $app )->long_version;
$save = array(
'set_id' => $id,
'added_to' => 0,
'user_added' => 0,
'user_edited' => 0
);
/* If we're syncing designer mode, check for actual changes */
$name = preg_replace( '/[^a-zA-Z0-9_]/', '', str_replace( '.phtml', '', $file->getFilename() ) );
/* Prevent '.phtml' files creating empty template rows in the database */
if ( empty( $name ) )
{
continue;
}
if ( $id > 0 )
{
if ( isset( $currentTemplates[ $app ][ $location->getFilename() ][ $group->getFilename() ][ $name ] ) )
{
if( \IPS\Login::compareHashes( md5( trim( $html ) ), md5( trim( $currentTemplates[ $app ][ $location->getFilename() ][ $group->getFilename() ][ $name ]['template_content'] ) ) ) )
{
/* No change */
continue;
}
else
{
/* It has changed */
$save['user_edited'] = $version;
}
}
else
{
/* New template bit */
$save['added_to'] = $id;
$save['set_id'] = 0;
$save['user_added'] = 1;
}
}
\IPS\Db::i()->replace( 'core_theme_templates', array( 'template_set_id' => $save['set_id'],
'template_app' => $app,
'template_added_to' => $save['added_to'],
'template_location' => $location->getFilename(),
'template_group' => $group->getFilename(),
'template_name' => $name,
'template_data' => ( isset( $params[1] ) ) ? $params[1] : '',
'template_content' => $html,
'template_updated' => time(),
'template_user_added' => $save['user_added'],
'template_user_edited' => $save['user_edited'],
'template_version' => ( isset( $master ) AND isset( $master[ $key ] ) ) ? $master[ $key ]['template_version'] : NULL,
'template_removable' => $id ) );
}
}
}
}
}
}
}
/**
* (re)import CSS into the CSS DB
*
* @param string $app Application Key
* @param int $id Theme Set Id (0 if IN_DEV and not in advanced theming mode)
*
* @return void
*/
public static function importDevCss( $app, $id )
{
/* Clear out existing template bits */
\IPS\Db::i()->delete( 'core_theme_css', array( 'css_app=? AND ( css_set_id=? OR ( css_added_to=0 AND css_set_id=? ) ) ', $app, $id, $id ) );
$master = array();
/* Get existing template bits to see if we need to import */
$currentCss = NULL;
if ( $id > 0 )
{
$currentCss = \IPS\Theme::load( $id )->getRawCss( $app );
}
$path = static::_getCssPath($app);
if ( is_dir( $path ) )
{
foreach( new \DirectoryIterator( $path ) as $location )
{
if ( $location->isDot() OR mb_substr( $location->getFilename(), 0, 1 ) === '.' )
{
continue;
}
if ( $location->isDir() )
{
static::_importDevCss( $app, $id, $currentCss, $location->getFilename() );
}
}
}
}
/**
* (re)import CSS into the CSS DB (Iterable)
*
* @param string $app Application Key
* @param int $id Theme set ID
* @param array $master Master CSS bits
* @param string $location Location Folder Name
* @param string $path Path
* @return void
*/
protected static function _importDevCss( $app, $id, $currentCss, $location, $path='/' )
{
$root = static::_getCssPath( $app, $location );
foreach( new \DirectoryIterator( $root . $path ) as $file )
{
if ( $file->isDot() OR mb_substr( $file->getFilename(), 0, 1 ) === '.' OR $file == 'index.html' )
{
continue;
}
if ( $file->isDir() )
{
static::_importDevCss( $app, $id, $currentCss, $location, $path . $file->getFilename() . '/' );
}
else
{
if ( mb_substr( $file->getFilename(), -4 ) !== '.css' )
{
continue;
}
/* Get the content */
$css = file_get_contents( $root . $path . $file->getFilename() );
/* Parse the header tag */
preg_match( '#^/\*<ips:css([^>]+?)>\*/\n#', $css, $params );
/* Strip it */
if ( count( $params ) AND ! empty( $params[0] ) )
{
$css = str_replace( $params[0], '', $css );
}
$cssModule = '';
$cssApp = '';
$cssPos = 0;
$cssHidden = 0;
/* Tidy params */
if ( count( $params ) AND ! empty( $params[1] ) )
{
preg_match_all( '#([\d\w]+?)=\"([^"]+?)"#i', $params[1], $items, PREG_SET_ORDER );
foreach( $items as $id => $attr )
{
switch( trim( $attr[1] ) )
{
case 'module':
$cssModule = trim( $attr[2] );
break;
case 'app':
$cssApp = trim( $attr[2] );
break;
case 'position':
$cssPos = intval( $attr[2] );
break;
case 'hidden':
$cssHidden = intval( $attr[2] );
break;
}
}
}
$trimmedPath = trim( $path, '/' );
$finalPath = ( ( ! empty( $trimmedPath ) ) ? $trimmedPath : '.' );
$version = \IPS\Application::load( $app )->long_version;
$save = array(
'set_id' => $id,
'added_to' => 0,
'user_edited' => 0
);
/* If we're syncing designer mode, check for actual changes */
if ( $id > 0 )
{
$css = str_replace( '/* No Content */', '', $css );
if ( isset( $currentCss[ $app ][ $location ][ $finalPath ][ $file->getFilename() ] ) )
{
if( \IPS\Login::compareHashes( md5( trim( $css ) ), md5( trim( $currentCss[ $app ][ $location ][ $finalPath ][ $file->getFilename() ]['css_content'] ) ) ) )
{
/* No change */
continue;
}
else
{
/* It has changed */
$save['user_edited'] = $version;
$save['added_to'] = $id;
$save['set_id'] = $id;
}
}
else
{
/* New template bit */
$save['added_to'] = $id;
$save['set_id'] = $id;
}
}
\IPS\Db::i()->insert( 'core_theme_css', array( 'css_set_id' => $save['set_id'],
'css_app' => $app,
'css_added_to' => $save['added_to'],
'css_location' => $location,
'css_path' => $finalPath,
'css_name' => $file->getFilename(),
'css_attributes' => '',
'css_content' => $css,
'css_modules' => $cssModule,
'css_position' => $cssPos,
'css_user_edited' => $save['user_edited'],
'css_updated' => time(),
'css_hidden' => $cssHidden ) );
}
}
}
/**
* Build Resourcess ready for non IN_DEV use
* @param string|array $app App (e.g. core, forum)
* @param int $id Theme Set Id (0 if IN_DEV and not in advanced theming mode)
*
* @return void
*/
public static function importDevResources( $app=NULL, $id )
{
foreach( new \DirectoryIterator( \IPS\ROOT_PATH . '/applications/' ) as $dir )
{
if ( $dir->isDot() || mb_substr( $dir->getFilename(), 0, 1 ) === '.' || $dir == 'index.html')
{
continue;
}
if ( $app === null OR $app == $dir->getFilename() )
{
\IPS\Theme::deleteCompiledResources( $dir->getFilename(), null, null, null, ( $id === 0 ? \IPS\DEFAULT_THEME_ID : $id ) );
if ( $id )
{
foreach( \IPS\Db::i()->select( '*', 'core_theme_resources', array( 'resource_set_id=? and resource_plugin IS NOT NULL', $id ) ) as $resource )
{
static::$plugins[ $resource['resource_name'] ] = $resource['resource_plugin'];
}
}
\IPS\Db::i()->delete( 'core_theme_resources', array( 'resource_app=? AND resource_set_id=?', $dir->getFilename(), $id ) );
$path = static::_getResourcePath( $dir->getFilename() );
if ( is_dir( $path ) )
{
foreach( new \DirectoryIterator( $path ) as $location )
{
if ( $location->isDot() || mb_substr( $location->getFilename(), 0, 1 ) === '.' )
{
continue;
}
if ( $location->isDir() )
{
static::_importDevResources( $dir->getFilename(), $id, $location->getFilename() );
}
}
}
}
}
}
/**
* Build Resources ready for non IN_DEV use (Iterable)
* Theme resources should be raw binary data everywhere (filesystem and DB) except in the theme XML download where they are base64 encoded.
*
* @param string $app Application Key
* @param int $id Theme Set Id
* @param string $location Location Folder Name
* @param string $path Path
*
* @return void
*/
public static function _importDevResources( $app, $id, $location, $path='/' )
{
$root = static::_getResourcePath($app, $location);
$master = array();
$plugins = array();
if ( $id )
{
foreach( \IPS\Db::i()->select( '*', 'core_theme_resources', array( 'resource_set_id=0 and resource_app=? and resource_location=? and resource_path=?', $app, $location, $path ) ) as $resource )
{
$master[ $resource['resource_name'] ] = md5( $resource['resource_data'] );
}
}
foreach( new \DirectoryIterator( $root . $path ) as $file )
{
if ( $file->isDot() || mb_substr( $file->getFilename(), 0, 1 ) === '.' || $file == 'index.html' )
{
continue;
}
if ( $file->isDir() )
{
static::_importDevResources( $app, $id, $location, $path . $file->getFilename() . '/' );
}
else
{
/* files larger than 1.5mb don't base64_encode() as they may not get stored in the resources_data column */
if ( filesize( $root . $path . $file->getFilename() ) > ( 1.5 * 1024 * 1024 ) )
{
\IPS\Log::log( $root . $path . $file->getFilename() . " too large to import", 'designers_mode_import' );
continue;
}
$content = file_get_contents( $root . $path . $file->getFilename() );
if ( ! base64_encode( $content ) )
{
\IPS\Log::log( $root . $path . $file->getFilename() . " could not be saved correctly", 'designers_mode_import' );
continue;
}
$custom = 0;
$name = self::makeBuiltTemplateLookupHash($app, $location, $path) . '_' . $file->getFilename();
$fileName = (string) \IPS\File::create( 'core_Theme', $name, $content, 'set_resources_' . ( $id == 0 ? \IPS\DEFAULT_THEME_ID : $id ) );
if ( ! isset( $master[ $file->getFilename() ] ) or !\IPS\Login::compareHashes( md5( $content ), $master[ $file->getFilename() ] ) )
{
$custom = 1;
}
\IPS\Db::i()->insert( 'core_theme_resources', array(
'resource_set_id' => ( $id == 0 ? \IPS\DEFAULT_THEME_ID : $id ),
'resource_app' => $app,
'resource_location' => $location,
'resource_path' => $path,
'resource_name' => $file->getFilename(),
'resource_added' => time(),
'resource_data' => $content,
'resource_filename' => $fileName,
'resource_plugin' => ( ( $app == 'core' and $location == 'global' and $path == '/plugins/' ) and isset( static::$plugins[ $file->getFilename() ] ) ) ? static::$plugins[ $file->getFilename() ] : NULL,
'resource_user_edited' => $custom
) );
/* Store in master table */
if ( $id == 0 )
{
\IPS\Db::i()->insert( 'core_theme_resources', array(
'resource_set_id' => 0,
'resource_app' => $app,
'resource_location' => $location,
'resource_path' => $path,
'resource_name' => $file->getFilename(),
'resource_added' => time(),
'resource_data' => $content,
'resource_filename' => ''
) );
}
}
}
}
/**
* Writes the /application/{app}/dev/{container}/ directory
*
* @param string $app Application Directory
* @param string $container Container directory (e.g. html/css/resources)
* @return string Path created
* @throws \RuntimeException
*/
protected static function _writeThemeContainerDirectory( $app, $container )
{
$dirToWrite = \IPS\ROOT_PATH . "/applications/" . $app . '/dev/' . $container;
if ( ! is_dir( $dirToWrite ) )
{
if ( ! @mkdir( $dirToWrite ) )
{
throw new \RuntimeException('core_theme_dev_cannot_make_dir,' . $dirToWrite);
}
else
{
@chmod( $dirToWrite, IPS_FOLDER_PERMISSION );
}
}
/* Check its writeable */
if ( ! is_writeable( $dirToWrite ) )
{
throw new \RuntimeException('core_theme_dev_not_writeable,' . $dirToWrite);
}
/* Make sure root directory is CHMOD correctly */
@chmod( \IPS\ROOT_PATH . "/applications/" . $app . '/dev', 0777 );
return $dirToWrite;
}
/**
* Writes the /application/{app}/dev/{container}/{path} directory
*
* @param string $app Application Directory
* @param string $container Path to create (e.g. admin, front)
* @return string Path created
* @throws \RuntimeException
*/
protected static function _writeThemePathDirectory( $app, $container, $path )
{
$dirToWrite = \IPS\ROOT_PATH . "/applications/" . $app . '/dev/' . $container . '/' . $path;
if ( ! is_dir( $dirToWrite ) )
{
if ( ! @mkdir( $dirToWrite ) )
{
throw new \RuntimeException('core_theme_dev_cannot_make_dir,' . $dirToWrite);
}
else
{
@chmod( $dirToWrite, IPS_FOLDER_PERMISSION );
}
}
/* Check its writeable */
if ( ! is_writeable( $dirToWrite ) )
{
throw new \RuntimeException('core_theme_dev_not_writeable,' . $dirToWrite);
}
return $dirToWrite;
}
/**
* Write skin resources
* Theme resources should be raw binary data everywhere (filesystem and DB) except in the theme XML download where they are base64 encoded.
*
* @param string $app Application Directory
* @return void
* @throws \RuntimeException
*/
public static function exportResources( $app )
{
try
{
self::_writeThemeContainerDirectory( $app, 'img' );
}
catch( \RuntimeException $e )
{
throw new \RuntimeException( $e->getMessage() );
}
foreach( \IPS\Db::i()->select( '*', 'core_theme_resources', array( 'resource_app=? AND resource_set_id=?', $app, \IPS\DEFAULT_THEME_ID ) )->setKeyField('resource_id') as $resourceId => $resource )
{
try
{
$pathToWrite = self::_writeThemePathDirectory( $app, 'img', $resource['resource_location'] );
}
catch( \RuntimeException $e )
{
throw new \RuntimeException( $e->getMessage() );
}
if ( $resource['resource_path'] != '/' )
{
$_path = '';
foreach( explode( '/', trim( $resource['resource_path'], '/' ) ) as $dir )
{
$_path .= '/' . trim( $dir, '/' );
try
{
$pathToWrite = self::_writeThemePathDirectory( $app, 'img', $resource['resource_location'] . $_path );
}
catch( \RuntimeException $e )
{
throw new \RuntimeException( $e->getMessage() );
}
}
}
try
{
if ( ! @\file_put_contents( $pathToWrite . '/' . $resource['resource_name'], $resource['resource_data'] ) )
{
throw new \RuntimeException('core_theme_dev_cannot_write_resource,' . $pathToWrite . '/' . $resource['resource_name']);
}
else
{
@chmod( $pathToWrite . '/' . $resource['resource_name'], 0777 );
}
}
catch( \InvalidArgumentException $e )
{
}
}
}
/**
* Write CSS into the appropriate theme directory as plain text CSS ({resource="foo.png"} intact)
*
* @param string $app Application Directory
* @return void
* @throws \RuntimeException
*/
public static function exportCss( $app )
{
try
{
self::_writeThemeContainerDirectory( $app, 'css' );
}
catch( \RuntimeException $e )
{
throw new \RuntimeException( $e->getMessage() );
}
$css = static::master()->getRawCss();
foreach( $css as $appDir => $data )
{
foreach( $css[ $appDir ] as $location => $data )
{
try
{
$pathToWrite = self::_writeThemePathDirectory( $app, 'css', $location );
}
catch( \RuntimeException $e )
{
throw new \RuntimeException( $e->getMessage() );
}
foreach( $css[ $appDir ][ $location ] as $path => $data )
{
if ( $path != '.' )
{
$_path = $path;
if ( \strstr( $path, '/' ) )
{
$_path = '';
foreach( explode( '/', $path ) as $dir )
{
$_path .= '/' . trim( $dir, '/' );
try
{
$pathToWrite = self::_writeThemePathDirectory( $app, 'css', $location . $_path );
}
catch( \RuntimeException $e )
{
throw new \RuntimeException( $e->getMessage() );
}
}
}
else
{
try
{
$pathToWrite = self::_writeThemePathDirectory( $app, 'css', $location . '/' . $path );
}
catch( \RuntimeException $e )
{
throw new \RuntimeException( $e->getMessage() );
}
}
}
foreach( $css[ $appDir ][ $location ][ $path ] as $name => $data )
{
$params = array();
$write = '';
if ( $data['css_hidden'] )
{
$params[] = 'hidden="1"';
}
if ( count( $params ) )
{
$write .= '/*<ips:css ' . implode( ' ', $params ) . ' />*/' . "\n";
}
$write .= ( empty( $data['css_content'] ) ) ? '/* No Content */' : $data['css_content'];
if ( ! @\file_put_contents( $pathToWrite . '/' . $data['css_name'], $write ) )
{
throw new \RuntimeException('core_theme_dev_cannot_write_css,' . $pathToWrite . '/' . $data['css_name']);
}
else
{
@chmod( $pathToWrite . '/' . $data['css_name'], 0777 );
}
}
}
}
}
}
/**
* Write templates into the appropriate theme directory as plain text templates ({{logic}} intact)
*
* @param string $app Application Directory
* @return void
* @throws \RuntimeException
*/
public static function exportTemplates( $app )
{
try
{
self::_writeThemeContainerDirectory( $app, 'html' );
}
catch( \RuntimeException $e )
{
throw new \RuntimeException( $e->getMessage() );
}
$templates = static::master()->getRawTemplates();
foreach( $templates as $appDir => $data )
{
foreach( $templates[ $app ] as $location => $data )
{
try
{
self::_writeThemePathDirectory( $app, 'html', $location );
}
catch( \RuntimeException $e )
{
throw new \RuntimeException( $e->getMessage() );
}
foreach( $templates[ $app ][ $location ] as $group => $data )
{
try
{
$pathToWrite = self::_writeThemePathDirectory( $app, 'html', $location . '/' . $group );
}
catch( \RuntimeException $e )
{
throw new \RuntimeException( $e->getMessage() );
}
foreach( $templates[ $app ][ $location ][ $group ] as $name => $data )
{
$write = '<ips:template parameters="' . $data['template_data'] . '" />' . "\n";
$write .= $data['template_content'];
if ( ! @\file_put_contents( $pathToWrite . '/' . $data['template_name'] . '.phtml', $write ) )
{
throw new \RuntimeException('core_theme_dev_cannot_write_template,' . $pathToWrite . '/' . $data['css_name']);
}
else
{
@chmod( $pathToWrite . '/' . $data['template_name'] . '.phtml', 0777 );
}
}
}
}
}
}
/**
* Write JSON file so dev installs are synced
*
* @return void
*/
public static function writeThemeSettingsToDisk()
{
$data = array();
foreach (
\IPS\Db::i()->select(
'sv.*, core_theme_settings_fields.*',
'core_theme_settings_fields',
array( 'sc_set_id=?', \IPS\DEFAULT_THEME_ID )
)->join(
array('core_theme_settings_values', 'sv'),
'sv.sv_id=core_theme_settings_fields.sc_id'
)->setKeyField('sc_key') as $row
)
{
$row['sc_default'] = ( $row['sv_value'] ) ? $row['sv_value'] : $row['sc_default'];
unset( $row['sc_id'], $row['sc_set_id'], $row['sc_updated'], $row['sv_id'], $row['sv_value'] );
$data[ $row['sc_app'] ][] = $row;
}
foreach( $data as $app => $json )
{
$file = \IPS\ROOT_PATH . "/applications/{$app}/data/themesettings.json";
if( @\file_put_contents( $file, version_compare( PHP_VERSION, '5.4.0' ) >= 0 ? json_encode( $json, JSON_PRETTY_PRINT ) : json_encode( $json ) ) === FALSE )
{
\IPS\Output::i()->error( 'dev_could_not_write_data', '1C103/4', 403, '' );
}
}
}
/**
* Remove an entire theme directory
*
* @param string $dir Path
* @return void
*/
public static function removeThemeDirectory( $dir )
{
if ( is_dir( $dir ) )
{
foreach ( new \DirectoryIterator( $dir ) as $f )
{
if ( !$f->isDot() )
{
if ( $f->isDir() )
{
static::removeThemeDirectory( $f->getPathname() );
}
else
{
@unlink( $f->getPathname() );
}
}
}
$handle = opendir( $dir );
closedir( $handle );
rmdir( $dir );
}
}
/**
* Returns the namespace for the template class
* @return string
*/
protected static function _getTemplateNamespace()
{
return 'IPS\\Theme\\Dev\\';
}
/**
* Returns the path for the IN_DEV .phtml files
* @param string $app Application Key
* @param string|null $location Location
* @param string|null $path Path or Filename
* @return string
*/
protected static function _getHtmlPath( $app, $location=null, $path=null )
{
return rtrim( \IPS\ROOT_PATH . "/applications/{$app}/dev/html/{$location}/{$path}", '/' ) . '/';
}
/**
* Returns the path for the IN_DEV CSS file
* @param string $app Application Key
* @param string|null $location Location
* @param string|null $path Path or Filename
* @return string
*/
protected static function _getCssPath( $app, $location=null, $path=null )
{
return rtrim( \IPS\ROOT_PATH . "/applications/{$app}/dev/css/{$location}/{$path}", '/' ) . ( \stristr( $path, '.css' ) ? '' : '/' );
}
/**
* Returns the path for the IN_DEV resource files
* @param string $app Application Key
* @param string|null $location Location
* @param string|null $path Path or Filename
* @return string
*/
protected static function _getResourcePath( $app, $location=null, $path=null )
{
if ( $app == 'core' and $location == 'global' and mb_substr( $path, 0, mb_strpos( $path, '/' ) ) === 'plugins' )
{
foreach ( new \GlobIterator( \IPS\ROOT_PATH . '/plugins/*/dev/resources/' . mb_substr( $path, mb_strpos( $path, '/' ) + 1 ) ) as $file )
{
return $file->getPathName();
}
}
return rtrim( \IPS\ROOT_PATH . "/applications/{$app}/dev/resources/{$location}/{$path}", '/' ) . ( ( \stristr( $path, '.' ) || \stristr( $path, '{' ) ) ? '' : '/' );
}
/**
* Basic debugging output functionality
*
* @param \Exception $e Exception that we are outputting
* @return void
*/
public static function varDumpException( $e )
{
echo '<pre>';
var_dump( $e );
exit;
}
}