<?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'] ) );
						
						if ( \IPS\DEBUG_TEMPLATES )
						{
							static::runDebugTemplate( str_replace( '/', '_', $data['file'] ), $contents );
						}
						else
						{
							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 )
					{
						if ( $file->getFilename() != 'index.html' )
						{
							$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
	 * @param	bool		$noProtocol	Return URL without a protocol (protocol-relative)
	 * @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;

								/* Enforce \n line endings */
								if( mb_strtolower( mb_substr( PHP_OS, 0, 3 ) ) === 'win' )
								{
									$html = str_replace( "\r\n", "\n", $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	$currentCss	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
				);

				/* Enforce \n line endings */
				if( mb_strtolower( mb_substr( PHP_OS, 0, 3 ) ) === 'win' )
				{
					$css = str_replace( "\r\n", "\n", $css );
				}
							
				/* 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, $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() )
			{
				/* When we are building, removeResources() has already taken care of this */
				if( $id !== 0 )
				{
					\IPS\Theme::deleteCompiledResources( $dir->getFilename(), null, null, null, $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 ), TRUE );
				
				if ( $id !== 0 AND ( !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\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	Location of path to create (e.g. admin, front)
	 * @param	string	$path		Path to create
	 * @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\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, json_encode( $json, JSON_PRETTY_PRINT ) ) === 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;
	}
}