* Invision Power Services * IP.Board v3.3.4 * Skin Caching Functions * Last Updated: $Date: 2012-06-22 07:55:48 -0400 (Fri, 22 Jun 2012) $ * * * Owner: Matt * @author $Author: ips_terabyte $ * @copyright (c) 2001 - 2009 Invision Power Services, Inc. * @license http://www.invisionpower.com/company/standards.php#license * @package IP.Board * @link http://www.invisionpower.com * @since 9th July 2008 * @version $Revision: 10969 $ * */ if ( ! defined( 'IN_IPB' ) ) { print "

Incorrect access

You cannot access this file directly. If you have recently upgraded, make sure you upgraded all the relevant files."; exit(); } /** * Ensure that class functions is loaded */ if ( ! class_exists( 'skinFunctions' ) ) { require_once( dirname( __FILE__ ) . '/skinFunctions.php' );/*noLibHook*/ } class skinCaching extends skinFunctions { /**#@+ * Registry objects * * @access protected * @var object */ protected $registry; protected $DB; protected $settings; protected $request; protected $lang; protected $member; protected $memberData; protected $cache; protected $caches; /**#@-*/ /** * Array of CSS needing to be rebuilt * * @access protected * @var array */ protected $_cssNeedToRebuildArray = array(); /** * Ignore children when recaching ... * @var boolean */ protected $_ignoreChildrenWhenRecaching = false; /** * Recache skin sets on shutdown * @var bool */ protected $_recacheSkinsCache = false; /** * Remember which skins we've already flagged for recache * @var array */ protected $_flaggedForRecache = array(); /** * Constructor * * @access public * @param object Registry object * @return @e void */ public function __construct( ipsRegistry $registry ) { /* Make object */ parent::__construct( $registry ); } /** * Set ignore children when recaching * @param boolean $val */ public function setIgnoreChildrenWhenRecaching( $val ) { $this->_ignoreChildrenWhenRecaching = ( ! empty( $val ) ) ? true : false; } /** * Get ignore children when recaching * @return boolean */ public function getIgnoreChildrenWhenRecaching() { return $this->_ignoreChildrenWhenRecaching; } /** * Rebuild Replacements cache * * @access public * @param int Skin set id to rebuild * @return boolean */ public function rebuildReplacementsCache( $setID ) { //----------------------------------------- // INIT //----------------------------------------- $skinSetData = $this->fetchSkinData( $setID ); $this->_resetErrorHandle(); $this->_resetMessageHandle(); //----------------------------------------- // Push the current skin set ID onto the beginnging //----------------------------------------- if ( is_numeric( $setID ) ) { array_unshift( $skinSetData['_childTree'], $setID ); } else { array_unshift( $skinSetData['_childTree'], 0 ); } //----------------------------------------- // Remove current caches //----------------------------------------- $this->DB->delete( 'skin_cache', 'cache_type=\'replacements\' AND cache_set_id IN (' . implode( ",", $skinSetData['_childTree'] ) . ')' ); //----------------------------------------- // Loop through and rebuild //----------------------------------------- foreach( $skinSetData['_childTree'] as $_setID ) { /* Bit of a fiddle here... */ if ( ! is_numeric( $setID ) AND $_setID == 0 ) { /* We mean a master set, then */ $_setID = $setID; } /* If this is a child skin, just flag for recache */ if ( $_setID != $setID ) { if ( $this->getIgnoreChildrenWhenRecaching() === false ) { $this->flagSetForRecache( $_setID, true ); } continue; } $__replacements = $this->fetchReplacements( $_setID ); $_replacements = array(); /* Strip out unneeded stuff */ foreach( $__replacements as $id => $data ) { $_replacements[ $data['replacement_key'] ] = $data['replacement_content']; } /* Update skin cache */ $this->DB->insert( 'skin_cache', array( 'cache_updated' => time(), 'cache_set_id' => $_setID, 'cache_type' => 'replacements', 'cache_content' => serialize( $_replacements ) ) ); $this->cache->putWithCacheLib( 'Skin_Store_' . $_setID, array(), 1 ); } return TRUE; } /** * Rebuild PHP Templates * * @access public * @param int Skin set ID * @param string [Optional: Rebuild only selected skin group template file] * @return array Messages */ public function rebuildPHPTemplates( $setID, $groupOnly='' ) { //----------------------------------------- // INIT //----------------------------------------- $skinSetData = $this->fetchSkinData( $setID ); $skinSetData['set_image_dir'] = ( $skinSetData['set_image_dir'] ) ? $skinSetData['set_image_dir'] : 'master'; /* These are not always set up correctly */ $this->settings['img_url'] = ( $this->settings['img_url'] ) ? $this->settings['img_url'] : $this->settings['_original_base_url'] . '/' . PUBLIC_DIRECTORY . '/style_images/' . $skinSetData['set_image_dir']; $this->settings['img_url_no_dir'] = ( $this->settings['img_url_no_dir'] ) ? $this->settings['img_url_no_dir'] : $this->settings['_original_base_url'] . '/' . PUBLIC_DIRECTORY . '/style_images/'; /* Remove dead template caches */ $this->removeDeadPHPCaches( $setID ); $this->_resetErrorHandle(); $this->_resetMessageHandle(); //----------------------------------------- // Push the current skin set ID onto the beginnging //----------------------------------------- array_unshift( $skinSetData['_childTree'], $setID ); //----------------------------------------- // Loop through and rebuild //----------------------------------------- foreach( $skinSetData['_childTree'] as $_setID ) { /* If this is a child skin, just flag for recache */ if ( $_setID != $setID ) { if ( $this->getIgnoreChildrenWhenRecaching() === false ) { $this->flagSetForRecache( $_setID, true ); } continue; } //----------------------------------------- // Get template set titles //----------------------------------------- $groupTitles = $this->fetchTemplates( $_setID, 'groupNames' ); $allHooks = $this->fetchSkinHooks(); /* Update DB row */ $this->DB->update( 'skin_collections', array( 'set_updated' => time() ), 'set_id=' . intval( $_setID ) ); foreach ( $groupTitles as $name => $group ) { /* Separated $out into two variables, needed to build the sections independently to fix bug #21574 */ $fileHead = ''; $fileBody = ''; //----------------------------------------- // Skip if we're only updating one group.. //----------------------------------------- if ( $groupOnly != '' ) { if ( $groupOnly != $group['template_group'] ) { continue; } } $fileHead = "class {$group['template_group']}_{$_setID} extends skinMaster{\n\n"; $fileHead .= <<fetchTemplates( $_setID, 'groupTemplates', $group['template_group'] ); foreach ($templates as $func_name => $data ) { $this->registry->templateEngine->setWorkingGroup( $group['template_group'] ); $fileBody .= $this->registry->templateEngine->convertHtmlToPhp( $data['template_name'], $data['template_data'], $data['template_content'] ) ."\n"; } //----------------------------------------- // Function Hooks //------------------------------------------ $funcHooks = '$this->_funcHooks = array();' . "\n"; if( is_array( $this->registry->templateEngine->_hookIds[ $group['template_group'] ] ) ) { foreach( $this->registry->templateEngine->_hookIds[ $group['template_group'] ] as $func_name => $hookIds ) { if( is_array( $hookIds ) && count( $hookIds ) ) { $funcHooks .= "\$this->_funcHooks['{$func_name}'] = array(".implode( ',', $hookIds ).");\n"; } } } $fileHead .= "\n\n{$funcHooks}\n\n}\n\n"; $out = $fileHead . $fileBody . "\n}\n"; /* This would be better off within the templateEngine really cos $out now contains the entire class */ $out = $this->stripUnneededHooks( $out, $allHooks[ $group['template_group'] ] ); //----------------------------------------- // Update DB Cache //----------------------------------------- $this->DB->delete( 'skin_cache', 'cache_set_id=' . $_setID . " AND cache_type='phptemplate' AND cache_value_1='" . $group['template_group'] . "'" ); $this->DB->insert( 'skin_cache', array( 'cache_updated' => time(), 'cache_type' => 'phptemplate', 'cache_set_id' => $_setID, 'cache_key_1' => 'group', 'cache_value_1' => $group['template_group'], 'cache_content' => $out ) ); //----------------------------------------- // Write to the flatfile //----------------------------------------- $start = '<'.'?'."php\n"; $start .= "/*--------------------------------------------------*/\n"; $start .= "/* FILE GENERATED BY INVISION POWER BOARD 3 */\n"; $start .= "/* CACHE FILE: Skin set id: {$_setID} */\n"; $start .= "/* CACHE FILE: Generated: ".gmdate( 'D, d M Y H:i:s \G\M\T' )." */\n"; $start .= "/* DO NOT EDIT DIRECTLY - THE CHANGES WILL NOT BE */\n"; $start .= "/* WRITTEN TO THE DATABASE AUTOMATICALLY */\n"; $start .= "/*--------------------------------------------------*/\n\n"; $end = "\n\n/*--------------------------------------------------*/\n"; $end .= "/* END OF FILE */\n"; $end .= "/*--------------------------------------------------*/\n"; $end .= "\n?".">"; if ( $this->fetchErrorMessages() === FALSE ) { $this->_writePHPTemplate( $_setID, $group['template_group'], $start.$out.$end ); } $this->cache->putWithCacheLib( 'Skin_Store_' . $_setID, array(), 1 ); } $this->_addMessage( sprintf( $this->lang->words['finishedcachinghtml'], $_setID ) ); } /* Rebuild skin cache */ $this->rebuildSkinSetsCache(); return TRUE; } /** * Removes dead PHP cache files * * @access public * @param int Set ID * @return int No. files unlinked */ public function removeDeadPHPCaches( $setID ) { //----------------------------------------- // INIT //----------------------------------------- $phpKeep = array(); $phpThrow = array(); $phpUnlink = array(); //----------------------------------------- // Get skin set data //----------------------------------------- $skinSetData = $this->fetchSkinData( $setID ); $tree = array_merge( $skinSetData['_parentTree'], $skinSetData['_childTree'] ); array_unshift( $tree, $setID ); //----------------------------------------- // Get PHP //----------------------------------------- $this->DB->build( array( 'select' => '*', 'from' => 'skin_collections', 'where' => 'set_id IN (' . implode( ",", $tree ) . ")" ) ); $i = $this->DB->execute(); while( $row = $this->DB->fetch( $i ) ) { $_php = $this->fetchTemplates( $row['set_id'], 'groupNames' ); foreach( $_php as $name => $php ) { $phpKeep[ $row['set_id'] ][ $name ] = $name; } } //----------------------------------------- // Get cached PHP //----------------------------------------- $this->DB->build( array( 'select' => '*', 'from' => 'skin_cache', 'where' => "cache_type='phptemplate' AND cache_set_id IN (" . implode( ",", $tree ) . ")" ) ); $this->DB->execute(); while( $row = $this->DB->fetch() ) { if ( ( is_array( $phpKeep[ $row['cache_set_id'] ] ) ) AND ( ! in_array( $row['cache_value_1'], $phpKeep[ $row['cache_set_id'] ] ) ) ) { $phpThrow[] = $row['cache_id']; } } //----------------------------------------- // Delete... //----------------------------------------- if ( count( $phpThrow ) ) { $this->DB->delete( 'skin_cache', 'cache_id IN (' . implode( ",", $phpThrow ) . ')' ); } //----------------------------------------- // Check flat files... //----------------------------------------- foreach( $phpKeep as $_setID => $data ) { if ( ( $_setID != $setID ) && $this->getIgnoreChildrenWhenRecaching() === true ) { continue; } $_path = IPS_CACHE_PATH . 'cache/skin_cache/cacheid_' . $_setID; if ( is_dir( $_path ) ) { try { foreach( new DirectoryIterator( $_path ) as $file ) { if ( ! $file->isDot() AND ! $file->isDir() ) { $_name = $file->getFileName(); if ( substr( $_name, -4 ) == '.php' ) { $phpName = substr( $_name, 0, -4 ); if ( ( is_array( $phpKeep[ $_setID ] ) ) AND ! in_array( $phpName, $phpKeep[ $_setID ] ) ) { $phpUnlink[] = $_path . '/' . $_name; } } } } } catch ( Exception $e ) {} } } //----------------------------------------- // Unlink... //----------------------------------------- if ( count( $phpUnlink ) ) { foreach( $phpUnlink as $path ) { @unlink( $path ); } } return count( $phpUnlink ); } /** * Removes dead CSS cache files * * @access public * @param int Set ID * @return int No. files unlinked */ public function removeDeadCSSCaches( $setID ) { //----------------------------------------- // INIT //----------------------------------------- $cssKeep = array(); $cssThrow = array(); $cssUnlink = array(); //----------------------------------------- // Get skin set data //----------------------------------------- $skinSetData = $this->fetchSkinData( $setID ); $tree = array_merge( $skinSetData['_parentTree'], $skinSetData['_childTree'] ); array_unshift( $tree, $setID ); //----------------------------------------- // Get CSS //----------------------------------------- $this->DB->build( array( 'select' => '*', 'from' => 'skin_collections', 'where' => 'set_id IN (' . implode( ",", $tree ) . ")" ) ); $i = $this->DB->execute(); while( $row = $this->DB->fetch( $i ) ) { $_css = $this->fetchCSS( $row['set_id'] ); foreach( $_css as $name => $css ) { $cssKeep[ $row['set_id'] ][ $css['css_group'] ] = $css['css_group']; } } //----------------------------------------- // Get cached CSS //----------------------------------------- $this->DB->build( array( 'select' => '*', 'from' => 'skin_cache', 'where' => "cache_type='css' AND cache_set_id IN (" . implode( ",", $tree ) . ")" ) ); $this->DB->execute(); while( $row = $this->DB->fetch() ) { if ( ( is_array( $cssKeep[ $row['cache_set_id'] ] ) ) AND ( ! in_array( $row['cache_value_1'], $cssKeep[ $row['cache_set_id'] ] ) ) ) { $cssThrow[] = $row['cache_id']; } } //----------------------------------------- // Delete... //----------------------------------------- if ( count( $cssThrow ) ) { $this->DB->delete( 'skin_cache', 'cache_id IN (' . implode( ",", $cssThrow ) . ')' ); } //----------------------------------------- // Check flat files... //----------------------------------------- foreach( $cssKeep as $_setID => $data ) { if ( ( $_setID != $setID ) && $this->getIgnoreChildrenWhenRecaching() === true ) { continue; } $_path = DOC_IPS_ROOT_PATH . PUBLIC_DIRECTORY . '/style_css/css_' . $_setID; if ( is_dir( $_path ) ) { try { foreach( new DirectoryIterator( $_path ) as $file ) { if ( ! $file->isDot() AND ! $file->isDir() ) { $_name = $file->getFileName(); if ( substr( $_name, -4 ) == '.css' ) { $cssName = substr( $_name, 0, -4 ); if ( ( is_array( $cssKeep[ $_setID ] ) ) AND ! in_array( $cssName, $cssKeep[ $_setID ] ) ) { $cssUnlink[] = $_path . '/' . $_name; } } } } } catch ( Exception $e ) {} } $this->cache->putWithCacheLib( 'Skin_Store_' . $_setID, array(), 1 ); } //----------------------------------------- // Unlink... //----------------------------------------- if ( count( $cssUnlink ) ) { foreach( $cssUnlink as $path ) { @unlink( $path ); } } return count( $cssUnlink ); } /** * Rebuild CSS cache, skin set cache and writes out any flat files * * @access public * @param int [Optional Skin set id to rebuild. If no value given, all skin sets are rebuilt] * @param boolean Force rebuild regardless of if it requires it * @return @e void */ public function rebuildCSS( $setID, $forceRebuild=FALSE ) { //----------------------------------------- // Rebuild CSS cache //----------------------------------------- $this->rebuildCSSCache( $setID, $forceRebuild ); //----------------------------------------- // Rewrite CSS files //----------------------------------------- $this->rebuildCSSFlatFiles( $setID, $forceRebuild ); } /** * Rebuild CSS flat files * * @access public * @param int [Optional Skin set id to rebuild. If no value given, all skin sets are rebuilt] * @param boolean Force rebuild regardless of if it requires it * @return boolean */ public function rebuildCSSFlatFiles( $setID, $forceRebuild=FALSE ) { $skinSetData = $this->fetchSkinData( $setID ); $this->_resetErrorHandle(); $this->_resetMessageHandle(); //----------------------------------------- // Push the current skin set ID onto the beginnging //----------------------------------------- if ( is_numeric( $setID ) ) { array_unshift( $skinSetData['_childTree'], $setID ); } //----------------------------------------- // Fetch CSS that needs to be rebuilt //----------------------------------------- $needToRebuild = $this->_CSSNeedToRebuild( $skinSetData['_childTree'], $setID, $forceRebuild ); if ( ! count( $needToRebuild ) ) { return TRUE; } //----------------------------------------- // Get the data from the cache... //----------------------------------------- $this->DB->build( array( 'select' => '*', 'from' => 'skin_cache', 'where' => 'cache_type=\'css\' AND cache_set_id IN (' . implode( ",", $skinSetData['_childTree'] ) . ')', 'order' => 'cache_set_id ASC' ) ); $i = $this->DB->execute(); while( $css = $this->DB->fetch( $i ) ) { $_setID = $css['cache_set_id']; $_group = $css['cache_value_1']; $_css = $css['cache_content']; if ( ! count( $needToRebuild[ $_setID ] ) ) { return TRUE; } $_keys = array_keys( $needToRebuild[ $_setID ] ); if ( ! count( $_keys ) ) { return TRUE; } if ( ! in_array( $_group, $_keys ) ) { continue; } /* If this is a child skin, just flag for recache */ if ( $_setID != $setID ) { if ( $this->getIgnoreChildrenWhenRecaching() === false ) { $this->flagSetForRecache( $_setID, true ); } continue; } IPSDebug::addLogMessage( "Attempting to write CSS " . $_setID . ", " . $_group . ".css", 'css' ); $this->_writeCSSFile( $_setID, $_group, $_css ); /* Any problems? */ if ( $this->fetchErrorMessages() !== FALSE ) { /* Flag as using inline */ $this->flagSetAsNonCachedCSS( $_setID ); return FALSE; } IPSDebug::addLogMessage( "Written CSS " . $_setID . ", " . $_group . ".css", 'css' ); } return TRUE; } /** * Rebuild CSS cache * * @access public * @param int Skin set id to rebuild * @param boolean Force rebuild regardless of if it requires it * @return boolean */ public function rebuildCSSCache( $setID, $forceRebuild=FALSE ) { //----------------------------------------- // INIT //----------------------------------------- $cssSkinCollections = array(); $skinSetData = $this->fetchSkinData( $setID ); $cachedCSS = array(); $thisCSS = array(); $needToRebuild = array(); $this->_resetErrorHandle(); $this->_resetMessageHandle(); //----------------------------------------- // Push the current skin set ID onto the beginnging //----------------------------------------- if ( is_numeric( $setID ) ) { array_unshift( $skinSetData['_childTree'], $setID ); } //----------------------------------------- // Fetch CSS that needs to be rebuilt //----------------------------------------- $needToRebuild = $this->_CSSNeedToRebuild( $skinSetData['_childTree'], $setID, $forceRebuild ); IPSDebug::addLogMessage( "Set ID: " . $setID . " - " . serialize( $needToRebuild ), 'css' ); //----------------------------------------- // Loop through and rebuild //----------------------------------------- foreach( array_keys( $needToRebuild ) as $_setID ) { $_css = $this->fetchCSS( $_setID ); $_skinSetData = $this->fetchSkinData( $_setID ); /* If this is a child skin, just flag for recache */ if ( $_setID != $setID ) { if ( $this->getIgnoreChildrenWhenRecaching() === false ) { $this->flagSetForRecache( $_setID, true ); } continue; } foreach( $_css as $name => $css ) { if ( ! in_array( $name, array_keys( $needToRebuild[ $_setID ] ) ) ) { continue; } $this->DB->delete( 'skin_cache', 'cache_type=\'css\' AND cache_set_id=' . $_setID . " AND cache_value_1='" . addslashes( $name ) . "'" ); /* Build skin set row*/ $cssSkinCollections[ $_setID ][ $css['css_position'] . '.' . $css['css_id'] ] = array( 'css_group' => $css['css_group'], 'css_position' => $css['css_position'] ); /* Update skin cache */ $this->DB->insert( 'skin_cache', array( 'cache_updated' => time(), 'cache_set_id' => $_setID, 'cache_type' => 'css', 'cache_key_1' => 'name', 'cache_value_1' => $css['css_group'], 'cache_key_2' => 'position', 'cache_value_2' => $css['css_position'], 'cache_key_3' => 'setBy', 'cache_value_3' => $css['css_set_id'], 'cache_key_4' => 'appInfo', 'cache_value_4' => $css['css_app'] . '-' . $css['css_app_hide'], 'cache_key_5' => 'attributes', 'cache_value_5' => $css['css_attributes'], 'cache_key_6' => 'modules', 'cache_value_6' => $css['css_modules'], 'cache_content' => $this->_CSS_fromDBtoFile( $css['css_content'], $_skinSetData ) //'cache_content' => $css['css_content'] ) ) ; } $this->cache->putWithCacheLib( 'Skin_Store_' . $_setID, array(), 1 ); } //----------------------------------------- // Finally, remove any cached items that no // longer 'exist' (since been deleted, etc) //----------------------------------------- $_css = $this->fetchCSS( $setID ); $_del = array(); /* Get cached items */ $this->DB->build( array( 'select' => '*', 'from' => 'skin_cache', 'where' => 'cache_type=\'css\' AND cache_set_id=' . $setID ) ); $this->DB->execute(); while( $row = $this->DB->fetch() ) { if ( ! $_css[ $row['cache_value_1'] ] ) { $_del[] = $row['cache_value_1']; } } if ( count( $_del ) ) { $this->DB->delete( 'skin_cache', 'cache_type=\'css\' AND cache_set_id=' . $setID . " AND cache_value_1 IN('" . implode( "','", $_del ) . "')" ); } //----------------------------------------- // Update skin sets //----------------------------------------- if ( count( $cssSkinCollections ) ) { foreach( $cssSkinCollections as $setID => $data ) { $this->DB->update( 'skin_collections', array( 'set_css_groups' => serialize( $cssSkinCollections[ $setID ] ) ), 'set_id='. $setID ); $this->cache->putWithCacheLib( 'Skin_Store_' . $setID, array(), 1 ); } } /* Recache skin sets */ $this->rebuildSkinSetsCache(); return TRUE; } /** * Flag skin as not able to serve cached CSS * * @access public * @return boolean */ public function flagSetAsNonCachedCSS( $setId=null ) { /* Update DB row */ if ( $setId !== null ) { $this->DB->update( 'skin_collections', array( 'set_css_inline' => 1 ), 'set_id=' . intval( $setId ) ); } /* Build cache */ $this->rebuildSkinSetsCache(); return TRUE; } /** * Rebuild master replacements from cache/skin_cache/master_skin/_replacements.inc * * @access public * @return bool */ public function rebuildMasterReplacements( $setId=0 ) { /* Set directory */ $setId = is_numeric( $setId ) ? intval( $setId ) : trim( $setId ); $dir = IPS_CACHE_PATH . 'cache/skin_cache/' . $this->remapData['templates'][ $setId ]; $messages = array(); //----------------------------------------- // Master file exists? //----------------------------------------- if ( ! is_file( $dir . '/_replacements.inc' ) ) { return FALSE; } //----------------------------------------- // Remove current CSS //----------------------------------------- if ( ! is_numeric( $setId ) ) { $this->DB->delete( 'skin_replacements', "replacement_master_key='" . $setId . "'" ); } else { $this->DB->delete( 'skin_replacements', "replacement_set_id='" . intval($setId) . "'" ); } //----------------------------------------- // Grab file //----------------------------------------- $replacements = array(); include( $dir . '/_replacements.inc' );/*noLibHook*/ if ( !is_array($replacements) || !count($replacements) ) { return FALSE; } //----------------------------------------- // Get proper set key/id //----------------------------------------- $set_id = ( is_numeric( $setId ) ) ? $setId : $this->fetchSetIdByKey( $setId ); $set_key = ''; if( $this->isMasterSet( $setId ) ) { $set_key = ( is_numeric( $setId ) ) ? $this->fetchSetKeyById( $setId ) : $setId; } //----------------------------------------- // Insert //----------------------------------------- foreach( $replacements as $k => $v ) { $messages[] = $this->lang->words['basicimportedpre'] . $k; $this->DB->insert( 'skin_replacements', array( 'replacement_key' => $k, 'replacement_content' => $v, 'replacement_master_key' => $set_key, 'replacement_set_id' => $set_id, 'replacement_added_to' => 0 ) ); } $this->cache->putWithCacheLib( 'Skin_Store_' . $setId, array(), 1 ); return $messages; } /** * Rebuild master CSS from the .css files in /public/style_css/master_skin/ * * @access public * @param int Skin set ID * @return array Array of messages * Exception Codes: * NOT_MASTER_SKIN: Set ID is not linked to a master skin directory * NO_SUCH_DIR: {master_skin_dir} directory cannot be found */ public function rebuildMasterCSS( $setId=0 ) { /* Set directory */ $setId = is_numeric( $setId ) ? intval( $setId ) : trim( $setId ); $cssDir = DOC_IPS_ROOT_PATH . PUBLIC_DIRECTORY . '/style_css/' . $this->remapData['css'][ $setId ]; $messages = array(); $css = array(); /* Got the master skin dir? */ if ( ! $this->remapData['css'][ $setId ] ) { throw new Exception( "NOT_MASTER_SKIN" ); } if ( ! is_dir( $cssDir ) ) { throw new Exception( "NO_SUCH_DIR" ); } if ( ! is_readable($cssDir) ) { throw new Exception( "CANNOT_READ" ); } /* Remove current CSS... */ if ( is_numeric( $setId ) ) { $this->DB->delete( 'skin_css', 'css_set_id=' . $setId ); } else { $this->DB->delete( 'skin_css', 'css_master_key=\'' . $setId . '\'' ); } /* Fetch all template bits. NOW */ if ( is_numeric( $setId ) AND $setId > 0 ) { $css = $this->fetchCSS( $setId ); } //----------------------------------------- // Set key and ID //----------------------------------------- $set_id = ( is_numeric( $setId ) ) ? $setId : $this->fetchSetIdByKey( $setId ); $set_key = ''; if( $this->isMasterSet( $setId ) ) { $set_key = ( is_numeric( $setId ) ) ? $this->fetchSetKeyById( $setId ) : $setId; } //----------------------------------------- // Recurse over the directory //----------------------------------------- try { foreach( new DirectoryIterator( $cssDir ) as $file ) { if ( ! $file->isDot() AND ! $file->isDir() ) { $_name = $file->getFileName(); if ( substr( $_name, -4 ) == '.css' ) { $cssName = substr( $_name, 0, -4 ); $cssContent = @file_get_contents( $cssDir . '/' . $_name ); $_css_added_to = 0; $cssContent = str_replace( array( "\r\n", "\r" ), "\n", $cssContent ); /* Encode */ $cssContent = IPSText::encodeForXml( $cssContent ); $messages[] = $this->lang->words['basicimportedpre'] . $cssName; $cssData = $this->_CSS_fetchImportBlock( $cssContent ); $cssContent = $this->_CSS_fromFileToDB( $cssContent ); /* 'Master' bit? */ if ( ! $css[ $cssName ] ) { $_css_added_to = $set_id; } $this->DB->insert( 'skin_css', array( 'css_updated' => time(), 'css_set_id' => $set_id, 'css_added_to' => $_css_added_to, 'css_app' => $cssData['css_app'], 'css_app_hide' => intval( $cssData['css_app_hide'] ), 'css_modules' => trim( $cssData['css_modules'] ), 'css_position' => intval( $cssData['css_position'] ), 'css_attributes' => $cssData['css_attributes'], 'css_master_key' => $set_key, 'css_group' => $cssName, 'css_content' => $cssContent ) ); } } } } catch ( Exception $e ) {} /* Recache */ if ( is_numeric( $setId ) AND $setId ) { $this->rebuildCSS( $setId ); $this->removeDeadCSSCaches( $setId ); $this->cache->putWithCacheLib( 'Skin_Store_' . $setId, array(), 1 ); } return $messages; } /** * Rebuilds the master 0 skin from the PHP files in /cache/{master_skin_dir}/ * * @access public * @param int Set ID to rebuild from * @return array Array of messages * * Exception Codes: * NOT_MASTER_SKIN: Set ID is not linked to a master skin directory * NO_SUCH_DIR: {master_skin_dir} directory cannot be found * CANNOT_READ: cannot read the {master_skin_dir} directory * */ public function rebuildMasterFromPHP( $setId=0 ) { //----------------------------------------- // INIT //----------------------------------------- $setId = is_numeric( $setId ) ? intval( $setId ) : trim( $setId ); $script_token = 0; $script_jump = 0; $skin_dir = IPS_CACHE_PATH . "cache/skin_cache/" . $this->remapData['templates'][ $setId ]; $flag = 0; $messages = array(); $templates = array(); /* Got the master skin dir? */ if ( ! $this->remapData['templates'][ $setId ] ) { throw new Exception( "NOT_MASTER_SKIN" ); } if ( ! is_dir( $skin_dir ) ) { throw new Exception( "NO_SUCH_DIR" ); } if ( ! is_readable($skin_dir) ) { throw new Exception( "CANNOT_READ" ); } /* Remove current templates... */ if ( is_numeric( $setId ) ) { $this->DB->delete( 'skin_templates', 'template_set_id=' . $setId ); } else { $this->DB->delete( 'skin_templates', 'template_master_key=\'' . $setId . '\'' ); } /* Fetch all template bits. NOW */ if ( is_numeric( $setId ) AND $setId > 0 ) { $templates = $this->fetchTemplates( $setId, 'allTemplates' ); } //----------------------------------------- // Set key and ID //----------------------------------------- $set_id = ( is_numeric( $setId ) ) ? $setId : $this->fetchSetIdByKey( $setId ); $set_key = ''; if( $this->isMasterSet( $setId ) ) { $set_key = ( is_numeric( $setId ) ) ? $this->fetchSetKeyById( $setId ) : $setId; } /* Loop through the directory */ try { foreach( new DirectoryIterator( $skin_dir ) as $file ) { if ( ! $file->isDot() AND ! $file->isDir() ) { $_name = $file->getFileName(); if ( substr( $_name, -4 ) == '.php' ) { $name = substr( $_name, 0, -4 ); $fdata = @file_get_contents( $skin_dir . "/" . $_name ); if ( ! $fdata ) { $messages[] = sprintf( $this->lang->words['couldnotopenfile'], $_name ); continue; } $fdata = str_replace( "\r", "\n", $fdata ); $fdata = str_replace( "\n\n", "\n", $fdata ); if ( ! preg_match( "/\n/", $fdata ) ) { $messages[] = sprintf( $this->lang->words['couldnotfindlineeol'], $_name ); continue; } $farray = explode( "\n", $fdata ); $functions = array(); $added = 0; $updated = 0; foreach( $farray as $f ) { //----------------------------------------- // Skip javascript functions... //----------------------------------------- if ( stristr( $f, '' ) ) { $script_token = 0; $script_jump = 0; } //----------------------------------------- // If, in the middle? //----------------------------------------- if ( $script_token AND $script_jump == 0 ) { if ( preg_match( '##", $f ) ) { $script_jump = 0; $script_token = 1; } } //----------------------------------------- // NOT IN JS //----------------------------------------- if ( ! $script_token ) { if ( preg_match( '/^function\s*([\w\_]+)\s*\((.*)\)/i', $f, $matches ) ) { $functions[ $matches[1] ] = ''; $config[ $matches[1] ] = $matches[2]; $flag = $matches[1]; continue; } } //----------------------------------------- // ARE IN JS //----------------------------------------- else { # Make JS safe (UBE - Ugly, but effective) $f = preg_replace( '#if\s+?\(#is' , "i~f~(~" , $f ); $f = str_ireplace( 'else' , "e~lse~" , $f ); $f = preg_replace( '#else\s+?if#is', "e~lse~i~f", $f ); } if ( $flag ) { $functions[ $flag ] .= $f."\n"; continue; } } $final = ""; $flag = 0; foreach( $functions as $fname => $ftext ) { preg_match( "#//--starthtml--//(.+?)//--endhtml--//#s", $ftext, $matches ); $content = $this->registry->templateEngine->convertPhpToHtml($matches[1]); //----------------------------------------- // Unconvert JS //----------------------------------------- $content = str_replace( "i~f~(~" , "if (" , $content ); $content = str_replace( "e~lse~" , "else" , $content ); $content = str_replace( "e~lse~i~f", "else if", $content ); /* Encode */ $content = IPSText::encodeForXml( $content ); /* Build array */ $array = array( 'template_set_id' => $set_id, 'template_group' => $name, 'template_content' => $content, 'template_name' => $fname, 'template_data' => trim($config[$fname]), 'template_updated' => time(), 'template_removable' => ( is_numeric( $setId ) AND $setId ) ? 1 : 0, 'template_master_key' => $set_key, 'template_added_to' => $set_id ); if ( ! $setId ) { $added++; /* All new bits go to 'master' skin */ $array['template_set_id'] = 0; $array['template_added_to'] = 0; $array['template_master_key'] = 'root'; $this->DB->insert( 'skin_templates', $array ); } else { /* Compare for changes? */ if ( IPSText::contentToMd5( $templates[ $name ][ strtolower( $fname ) ]['template_content'] ) != IPSText::contentToMd5( $content ) ) { /* It's changed, so update. We create a new row as it might be inherited */ $updated++; $this->DB->insert( 'skin_templates', $array ); } } } $messages[] = sprintf( $this->lang->words['tplbitimported'], $name, $added, $updated ); $functions = array(); } } } } catch ( Exception $e ) {} /* Recache */ if ( is_numeric( $setId ) AND $setId ) { $this->rebuildPHPTemplates( $setId ); $this->removeDeadPHPCaches( $setId ); $this->cache->putWithCacheLib( 'Skin_Store_' . $setId, array(), 1 ); } if ( $set_key == 'mobile' ) { $this->rebuildMobileSkinUserAgentsFromSetDataXml(); } /* Return */ $messages[] = $this->lang->words['cpltrebuildfromcaches']; return $messages; } /** * Flag skin as needing recache * * @param int Set ID * @param bool Skip rebuild skin sets cache * @return boolean */ public function flagSetForRecache( $setId=null, $skipRebuild=false ) { /* If we're already flagged, just return @link http://community.invisionpower.com/tracker/issue-31472-skin-recaching-optimizations */ $_key = md5( $setId . $skipRebuild ); if ( isset($this->_flaggedForRecache[ $_key ]) ) { return true; } $this->_flaggedForRecache[ $_key ] = 1; /* Update DB row */ if ( $setId !== null ) { $this->DB->update( 'skin_collections', array( 'set_updated' => -2 ), 'set_id=' . intval( $setId ) ); } else { $this->DB->update( 'skin_collections', array( 'set_updated' => -2 ) ); } /* Build cache */ if( ! $skipRebuild ) { $this->rebuildSkinSetsCache(); } return TRUE; } /** * Flag skin as needing recache * * @access public * @return boolean */ public function flagSetAsRecaching( $setId=null ) { /* Update DB row */ if ( $setId !== null ) { $this->DB->update( 'skin_collections', array( 'set_updated' => -1 ), 'set_id=' . intval( $setId ) ); } else { $this->DB->update( 'skin_collections', array( 'set_updated' => -1 ) ); } /* Build cache */ $this->rebuildSkinSetsCache(); return TRUE; } /** * We set a flag to rebuild the skin sets cache on shutdown. We do it this way to ensure we only rebuild the cache once per page load. * * @link http://community.invisionpower.com/tracker/issue-31472-skin-recaching-optimizations * @access public * @return boolean */ public function rebuildSkinSetsCache() { if( !$this->_recacheSkinsCache ) { $this->_recacheSkinsCache = true; register_shutdown_function( array( $this, '__rebuildSkinSetsCache' ) ); } } /** * Rebuild skinsets cache in the cache_store table * * @access public * @return boolean */ public function __rebuildSkinSetsCache() { //----------------------------------------- // INIT //----------------------------------------- $cache = array(); //----------------------------------------- // Get skins //----------------------------------------- $this->DB->build( array( 'select' => '*', 'from' => 'skin_collections', 'order' => 'set_order, set_id' ) ); $this->DB->execute(); while( $row = $this->DB->fetch() ) { $cache[ $row['set_id'] ] = $row; } //----------------------------------------- // Save the cache //----------------------------------------- $this->cache->setCache( 'skinsets', $cache, array( 'array' => 1, 'donow' => 1 ) ); return TRUE; } /** * Recache output formats * * @access public * @return boolean */ public function rebuildOutputFormatCaches() { $formats = $this->fetchOutputFormats(); $this->registry->cache()->setCache( 'outputformats', $formats, array( 'donow' => 1, 'array' => 1 ) ); return TRUE; } /** * Recache URL remaps * * @access public * @return boolean */ public function rebuildURLMapCache() { //----------------------------------------- // INIT //----------------------------------------- $cache = array(); $this->DB->build( array( 'select' => '*', 'from' => 'skin_url_mapping' ) ); $this->DB->execute(); while( $r = $this->DB->fetch() ) { $cache[ $r['map_id'] ] = $r; } $this->registry->cache()->setCache( 'skin_remap', $cache, array( 'donow' => 1, 'array' => 1 ) ); return TRUE; } /** * Fetch Directory CSS * Fetchs and parses directory straight from the directory * Used for IN_DEV skins currently. * * @access public * @param string Dir to read from * @return array Array of data */ public function fetchDirectoryCSS( $dir ) { /* Set directory */ $css = array(); $cssDir = DOC_IPS_ROOT_PATH . PUBLIC_DIRECTORY . '/style_css/' . $dir; $messages = array(); if ( ! is_dir( $cssDir ) ) { return $css; } if ( ! is_readable($cssDir) ) { return $css; } //----------------------------------------- // Recurse over the directory //----------------------------------------- try { foreach( new DirectoryIterator( $cssDir ) as $file ) { if ( ! $file->isDot() AND ! $file->isDir() ) { $_name = $file->getFileName(); if ( substr( $_name, -4 ) == '.css' ) { $cssName = substr( $_name, 0, -4 ); $cssContent = @file_get_contents( $cssDir . '/' . $_name ); $cssContent = str_replace( array( "\r\n", "\r" ), "\n", $cssContent ); $messages[] = $this->lang->words['basicimportedpre'] . $cssName; $cssData = $this->_CSS_fetchImportBlock( $cssContent ); $cssContent = $this->_CSS_fromFileToDB( $cssContent ); $css[ $cssName ] = array( 'css_updated' => time(), 'css_set_id' => 0, 'css_added_to' => 0, 'css_app' => $cssData['css_app'], 'css_app_hide' => intval( isset( $cssData['css_app_hide'] ) ? $cssData['css_app_hide'] : 0 ), 'css_position' => intval( isset( $cssData['css_position'] ) ? $cssData['css_position'] : 0 ), 'css_modules' => isset( $cssData['css_modules'] ) ? $cssData['css_modules'] : '', 'css_attributes' => isset( $cssData['css_attributes'] ) ? $cssData['css_attributes'] : '', 'css_group' => $cssName, 'css_content' => $cssContent ); } } } } catch ( Exception $e ) {} return $css; } /** * Write out skin templates in the master format * IN_DEV tool, currently. * * @access public * @param int Skin ID to write out * @param string Directory to write into. Directory must be in /public/style_css/ * @return array Array of messages * Exception Codes: * IN_DEV_OFF IN_DEV constant is not enabled * NO_SUCH_SKIN_SET Skin set of $id does not exist * DIR_DOES_NOT_EXIST Directory does not exist in /cache/skin_cache * CANNOT_WRITE_DIR Directory cannot be written into */ public function writeMasterSkinCss( $id, $dir ) { /* Checkin' */ if ( ! IN_DEV ) { throw new Exception( 'IN_DEV_OFF' ); } if ( ! is_numeric( $id ) ) { $id = $this->fetchSetIdByKey( $id, true ); } $setData = $this->fetchSkinData( $id ); $path = DOC_IPS_ROOT_PATH . PUBLIC_DIRECTORY . '/style_css/' . $dir; $msgs = array(); if ( ! isset( $setData['set_id'] ) ) { throw new Exception( 'NO_SUCH_SKIN_SET' ); } if ( ! is_dir( $path ) ) { throw new Exception( 'DIR_DOES_NOT_EXIST' ); } if ( ! is_writeable( $path ) ) { throw new Exception( 'CANNOT_WRITE_DIR' ); } /* Fetch CSS */ $css = $this->fetchCSS( $id, FALSE ); /* Write it.. */ if ( is_array( $css ) ) { foreach( $css as $group => $data ) { $_block = $this->_masterWrite_createCssBlock( $data ); /** * @link http://community.invisionpower.com/tracker/issue-36016-exported-master-css-files-are-compressed/ */ $setData['set_minify'] = 0; if ( @file_put_contents( $path . '/' . $data['css_group'] . '.css', $_block . $this->_CSS_fromDBtoFile( $data['css_content'], $setData ) ) ) { $msgs[] = $this->lang->words['basicwrittenpre'] . $data['css_group'] . '.css'; } else { $msgs[] = $this->lang->words['basiccouldnotwrite'] . $data['css_group'] . '.css'; } } } return $msgs; } /** * Write out replacements in the master format * IN_DEV tool, currently. * * @access public * @param int Skin ID to write out * @param string Directory to write into. Directory must be in /public/style_css/ * @return array Array of messages * Exception Codes: * IN_DEV_OFF IN_DEV constant is not enabled * NO_SUCH_SKIN_SET Skin set of $id does not exist * DIR_DOES_NOT_EXIST Directory does not exist in /cache/skin_cache * CANNOT_WRITE_DIR Directory cannot be written into */ public function writeMasterSkinReplacements( $id, $dir ) { /* Checkin' */ if ( ! IN_DEV ) { throw new Exception( 'IN_DEV_OFF' ); } if ( ! is_numeric( $id ) ) { $id = $this->fetchSetIdByKey( $id, true ); } $setData = $this->fetchSkinData( $id ); $path = IPS_CACHE_PATH . 'cache/skin_cache/' . $dir; $msgs = array(); if ( ! isset( $setData['set_id'] ) ) { throw new Exception( 'NO_SUCH_SKIN_SET' ); } if ( ! is_dir( $path ) ) { throw new Exception( 'DIR_DOES_NOT_EXIST' ); } if ( ! is_writeable( $path ) ) { throw new Exception( 'CANNOT_WRITE_DIR' ); } /* Fetch Replacements */ $replacements = $this->fetchReplacements( $id ); /* Write it.. */ if ( is_array( $replacements ) ) { $content = "<" . "?php\n\n\$replacements = array(\n\n"; foreach( $replacements as $k => $data ) { $content .= "\n'" . $k . "' => \"" . str_replace( '"', '\\"', $data['replacement_content'] ) . '",'; } $content .= "\n);\n\n?>"; if ( @file_put_contents( $path . '/_replacements.inc', $content ) ) { $msgs[] = $this->lang->words['basicwrittenpre'] . $path . '/_replacements.inc'; } else { $msgs[] = $this->lang->words['basiccouldnotwrite'] . $path . '/_replacements.inc'; } } return $msgs; } /** * Write out skin templates in the master format * IN_DEV tool, currently. * * @access public * @param int Skin ID to write out * @param string Directory to write into. Directory must be in /cache/skin_cache/ * @return array Array of messages * Exception Codes: * IN_DEV_OFF IN_DEV constant is not enabled * NO_SUCH_SKIN_SET Skin set of $id does not exist * DIR_DOES_NOT_EXIST Directory does not exist in /cache/skin_cache * CANNOT_WRITE_DIR Directory cannot be written into */ public function writeMasterSkin( $id, $dir ) { /* Checkin' */ if ( ! IN_DEV ) { throw new Exception( 'IN_DEV_OFF' ); } if ( ! is_numeric( $id ) ) { $id = $this->fetchSetIdByKey( $id, true ); } $setData = $this->fetchSkinData( $id ); $path = IPS_CACHE_PATH . 'cache/skin_cache/' . $dir; $msgs = array(); if ( ! isset( $setData['set_id'] ) ) { throw new Exception( 'NO_SUCH_SKIN_SET' ); } if ( ! is_dir( $path ) ) { throw new Exception( 'DIR_DOES_NOT_EXIST' ); } if ( ! is_writeable( $path ) ) { throw new Exception( 'CANNOT_WRITE_DIR' ); } /* Fetch all template bits. NOW */ $templates = $this->fetchTemplates( $id, 'allTemplates' ); /* Loop through */ foreach( $templates as $group => $data ) { $_file = $group . '.php'; $_content = ''; foreach( $templates[ $group ] as $_name => $_data ) { $_content .= $this->_masterWrite_format( $_data ); } if ( $_content ) { $_content = $this->_masterWrite_wrap( $_content ); if ( ! @file_put_contents( $path . '/' . $_file, $_content ) ) { $msgs[] = $this->lang->words['basiccouldnotwrite'] . $path . '/' . $_file; } else { @chmod( $path . '/' . $_file, IPS_FILE_PERMISSION ); $msgs[] = $this->lang->words['basicwrittenpre'] . $path . '/' . $_file; } } else { $msgs[] = $this->lang->words['basicnothingtowritepre'] . $path . '/' . $_file; } } return $msgs; } /** * Creates CSS master block * * @access protected * @param array Array of CSS data from the DB * @return string */ protected function _masterWrite_createCssBlock( $data ) { $block = array(); if ( isset( $data['css_position'] ) ) { $block[] = "" . $data['css_position'] . ""; } if ( isset( $data['css_app'] ) ) { $block[] = "" . $data['css_app'] . ""; } if ( isset( $data['css_modules'] ) ) { $block[] = "" . $data['css_modules'] . ""; } if ( isset( $data['css_app_hide'] ) ) { $block[] = "" . $data['css_app_hide'] . ""; } if ( isset( $data['css_attributes'] ) ) { $block[] = "" . $data['css_attributes'] . ""; } if ( count( $block ) ) { $_block = implode( "\n", $block ); $content = << DO NOT REMOVE OR ALTER THIS PLEASE. IT IS REMOVED AUTOMATICALLY BY THE IMPORT ROUTINE. $_block */ EOF; return $content; } } /** * Wrap content for a master skin function * * @access protected * @param array Template bit data * @param string Content */ protected function _masterWrite_format( $data ) { $_open = '<< EOF; return $return; } /** * Write a CSS cache file * * @access protected * @param int Skin set ID * @param string Group Name * @param string CSS content * @return @e void */ protected function _writeCSSFile( $setID, $css_group, $css_content ) { //----------------------------------------- // INIT //----------------------------------------- $skinSetData = $this->fetchSkinData( $setID ); $css_content = $this->_CSS_fromDBtoFile( $css_content, $skinSetData ); $return = 0; $good_to_go = 0; if ( ! SAFE_MODE_ON ) { $good_to_go = 1; if ( is_writeable( DOC_IPS_ROOT_PATH . PUBLIC_DIRECTORY . '/style_css/' ) === TRUE ) { $good_to_go = 1; if ( ! is_dir( DOC_IPS_ROOT_PATH . PUBLIC_DIRECTORY . '/style_css/css_' . $setID ) ) { if ( ! @ mkdir( DOC_IPS_ROOT_PATH . PUBLIC_DIRECTORY . '/style_css/css_' . $setID, IPS_FOLDER_PERMISSION ) ) { $this->_addErrorMessage( sprintf( $this->lang->words['couldnotcreatecssdir'], $setID ) ); return $this->_errorMsgs; } else { @file_put_contents( DOC_IPS_ROOT_PATH . PUBLIC_DIRECTORY . '/style_css/css_' . $setID . '/index.html', '' ); @chmod( DOC_IPS_ROOT_PATH . PUBLIC_DIRECTORY . '/style_css/css_'.$setID, IPS_FOLDER_PERMISSION ); $good_to_go = 1; } } else { if ( is_writable( DOC_IPS_ROOT_PATH . PUBLIC_DIRECTORY . '/style_css/css_' . $setID ) !== TRUE ) { $this->_addErrorMessage( sprintf( $this->lang->words['cssdirnotwritaaaable'], $setID ) ); $good_to_go = 0; } else if ( is_file( DOC_IPS_ROOT_PATH . PUBLIC_DIRECTORY . '/style_css/css_'.$setID.'/'.$css_group.'.css' ) ) { if ( is_writable( DOC_IPS_ROOT_PATH . PUBLIC_DIRECTORY . '/style_css/css_'.$setID.'/'.$css_group.'.css' ) !== TRUE ) { $this->_addErrorMessage( sprintf( $this->lang->words['cssfilenotwriteable'], $setID, $css_group ) ); $good_to_go = 0; } else { $good_to_go = 1; } } else { $good_to_go = 1; } } } else { $this->_addErrorMessage( sprintf( $this->lang->words['fatalnotwreite'], DOC_IPS_ROOT_PATH . PUBLIC_DIRECTORY ) ); $good_to_go = 0; } } else { $this->_addErrorMessage( $this->lang->words['safemodeenablednow'] ); $good_to_go = 0; } //----------------------------------------- // Write... //----------------------------------------- if ( $good_to_go ) { if ( $FH = @fopen( DOC_IPS_ROOT_PATH . PUBLIC_DIRECTORY . '/style_css/css_'.$setID.'/'.$css_group.'.css', 'w' ) ) { fwrite( $FH, $css_content, strlen($css_content) ); fclose( $FH ); @chmod( DOC_IPS_ROOT_PATH . PUBLIC_DIRECTORY . '/style_css/css_'.$setID.'/'.$css_group.'.css', IPS_FILE_PERMISSION ); $this->_addMessage( $this->lang->words['basicwrittenpre']. "skin_cache/css_{$setID}/{$css_group}.css" ); } else { $this->_addErrorMessage( sprintf( $this->lang->words['cssfilenotwriteable'], $setID, $css_group ) ); } } } /** * Write a PHP template cache file * * @access protected * @param int Skin set ID * @param string Group Name * @param string Template content * @return array Array of messages */ protected function _writePHPTemplate( $setID, $groupName, $content ) { //----------------------------------------- // INIT //----------------------------------------- $return = 0; $good_to_go = 0; if ( ! SAFE_MODE_ON ) { $good_to_go = 1; if ( IPSLib::isWritable( IPS_CACHE_PATH.'cache/skin_cache/' ) === TRUE ) { $good_to_go = 1; if ( ! is_dir( IPS_CACHE_PATH.'cache/skin_cache/cacheid_'.$setID ) ) { if ( ! @ mkdir( IPS_CACHE_PATH.'cache/skin_cache/cacheid_'.$setID, IPS_FOLDER_PERMISSION ) ) { $this->_addErrorMessage( sprintf( $this->lang->words['fatalnotemplatew'], $setID ) ); return; } else { @file_put_contents( IPS_CACHE_PATH.'cache/skin_cache/cacheid_' . $setID . '/index.html', '' ); @chmod( IPS_CACHE_PATH.'cache/skin_cache/cacheid_'.$setID, IPS_FOLDER_PERMISSION ); $good_to_go = 1; } } else { if ( is_file( IPS_CACHE_PATH.'cache/skin_cache/cacheid_'.$setID.'/'.$groupName.'.php' ) ) { if ( IPSLib::isWritable( IPS_CACHE_PATH.'cache/skin_cache/cacheid_'.$setID.'/'.$groupName.'.php' ) !== TRUE ) { $this->_addErrorMessage( sprintf( $this->lang->words['templatefilenowrite'], $setID, $groupName ) ); $good_to_go = 0; } else { $good_to_go = 1; } } else { $good_to_go = 1; } } } else { $this->_addErrorMessage( sprintf( $this->lang->words['fatalnotemplsafe'], IPS_CACHE_PATH ) ); } } //----------------------------------------- // Write... //----------------------------------------- if ( $good_to_go ) { if ( $FH = @fopen( IPS_CACHE_PATH.'cache/skin_cache/cacheid_'.$setID.'/'.$groupName.'.php', 'w' ) ) { fwrite( $FH, $content, strlen($content) ); fclose( $FH ); @chmod( IPS_CACHE_PATH.'cache/skin_cache/cacheid_'.$setID.'/'.$groupName.'.php', IPS_FILE_PERMISSION ); $this->_addMessage( $this->lang->words['basicwrittenpre'] . "skin_cache/cacheid_{$setID}/{$groupName}.php" ); } else { $this->_addErrorMessage( sprintf( $this->lang->words['templatefilenowrite'], $setID, $groupName ) ); } } } /** * Strip all unneeded hooks * * @access public * @param string Template HTML * @param array Array of hook data * @return string Processed template HTML */ public function stripUnneededHooks( $html, $hooks ) { if ( is_array( $hooks ) and count( $hooks ) ) { /* First, make safe hooks we want to keep */ foreach( $hooks as $hook ) { if ( $hook['_commentTag'] ) { $html = str_replace( '', '{!--hook.' . $hook['_commentTag'] . '--}', $html ); } } /* Remove all hooks */ $html = preg_replace( '##', '', $html ); /* Restore saved hooks */ foreach( $hooks as $hook ) { if ( $hook['_commentTag'] ) { $html = str_replace( '{!--hook.' . $hook['_commentTag'] . '--}', '', $html ); } } } else { /* No hooks, so remove all */ $html = preg_replace( '##', '', $html ); } return $html; } /** * Fetch inline data from a master CSS * * @access protected * @param string Raw CSS * @return array */ protected function _CSS_fetchImportBlock( $cssContent ) { $return = array( 'css_app' => '', 'css_app_hide' => 0 ); /* DO NOT REMOVE OR ALTER THIS PLEASE. IT IS REMOVED AUTOMATICALLY BY THE IMPORT ROUTINE. blog 1 */ /* Fetch the data block */ if ( strstr( $cssContent, '' ) ) { preg_match( '#/\*(.+?)\*/#is', $cssContent, $match ); if ( $match[0] ) { preg_match( "#(.+?)#", $match[0], $cssAppMatch ); $return['css_app'] = trim( $cssAppMatch[1] ); preg_match( "#(.+?)#", $match[0], $cssAppHide ); $return['css_app_hide'] = intval( trim( $cssAppHide[1] ) ); preg_match( "#(.+?)#", $match[0], $cssPosition ); $return['css_position'] = intval( trim( $cssPosition[1] ) ); preg_match( "#(.+?)#", $match[0], $cssAttr ); $return['css_attributes'] = trim( $cssAttr[1] ); preg_match( "#(.+?)#", $match[0], $cssModules ); $return['css_modules'] = isset( $cssModules[1] ) ? trim( $cssModules[1] ) : ''; } } return $return; } /** * Convert CSS from flatfiles to the DB version * * @access protected * @param string Raw CSS * @return string Processed CSS */ protected function _CSS_fromFileToDB( $cssContent ) { /* Fetch the data block */ if ( strstr( $cssContent, '' ) ) { preg_match( '#/\*(.+?)\*/#is', $cssContent, $match ); if ( $match[0] ) { /* And remove it */ $cssContent = trim( str_replace( $match[0], '', $cssContent ) ); } } /* remove phpdoc style comments */ $cssContent = preg_replace( '#/\*\*(\s+?)(.+?)\n(\s+?)?\*/#s', '', $cssContent ); $cssContent = preg_replace( '#url\((\"|\')?.+?style_images/([\d\w]+?)/#i', "url(\\1{style_images_url}/", $cssContent ); $cssContent = preg_replace( '#url\((\"|\')?/style_images/([\d\w]+?)#i' , "url(\\1{style_images_url}/", $cssContent ); return $cssContent; } /** * Convert CSS from DB to the flatfiles version * * @access protected * @param string Raw CSS * @return string Processed CSS */ protected function _CSS_fromDBtoFile( $cssContent, $skinData ) { $skinData['set_image_dir'] = ( $skinData['set_image_dir'] ) ? $skinData['set_image_dir'] : 'master'; /* These are not always set up correctly */ $this->settings['img_url'] = ( $this->settings['img_url'] ) ? $this->settings['img_url'] : $this->settings['_original_base_url'] . '/' . PUBLIC_DIRECTORY . '/style_images/' . $skinData['set_image_dir']; $this->settings['img_url_no_dir'] = ( $this->settings['img_url_no_dir'] ) ? $this->settings['img_url_no_dir'] : $this->settings['_original_base_url'] . '/' . PUBLIC_DIRECTORY . '/style_images/'; //----------------------------------------- // Fix up style images stuff //----------------------------------------- $cssContent = preg_replace( '#([\(|\s*|\'])(style_images/)#', '\\1{style_images_url}', $cssContent ); $cssContent = str_replace( '{style_images_url}', $this->settings['img_url_no_dir'] . $skinData['set_image_dir'], $cssContent ); //----------------------------------------- // And honor image url //----------------------------------------- if ( $this->settings['ipb_img_url'] ) { if ( ! preg_match( "#/$#", $this->settings['ipb_img_url'] ) ) { $this->settings['ipb_img_url'] .= '/'; } $cssContent = preg_replace( '#url\((\'|\")?\.\./\.\./style_images/#', "url(\\1" . $this->settings['ipb_img_url'] . "/style_images/", $cssContent ); } //----------------------------------------- // Minify CSS? //----------------------------------------- if ( $skinData['set_minify'] ) { $cssContent = $this->minify( $cssContent, 'css' ); } return $cssContent; } /** * Determine which CSS files need rebuilding * * @access protected * @param array Array of child skins (including current setID) * @param int Set ID we're recaching * @param boolean Force all css sets to recache * @return array Array of items to rebuild */ protected function _CSSNeedToRebuild( $childTree, $setID, $forceRebuild=FALSE ) { //----------------------------------------- // Already been 'ere? //----------------------------------------- if ( is_array( $this->_cssNeedToRebuildArray[ $setID ] ) ) { return $this->_cssNeedToRebuildArray[ $setID ]; } $cachedCSS = array(); $thisCSS = array(); $needToRebuild = array(); //----------------------------------------- // Fetch cached CSS data - need to add parent tree and select parent css first //----------------------------------------- /* Do we need a parent? */ $setData = $this->fetchSkinData( $setID ); if ( count( $setData['_parentTree'] ) ) { /* Allow parent data to be overwritten by child data */ $this->DB->build( array( 'select' => '*', 'from' => 'skin_cache', 'where' => "cache_type='css' AND cache_set_id IN (" . implode( ',', $setData['_parentTree'] ) . ')' ) ); $this->DB->execute(); while( $row = $this->DB->fetch() ) { $row['cache_content'] = md5( $row['cache_content'] ); $cachedCSS[ $row['cache_set_id'] ][ $row['cache_value_1'] ] = $row; } } $this->DB->build( array( 'select' => '*', 'from' => 'skin_cache', 'where' => "cache_type='css' AND cache_set_id IN (" . implode( ',', $childTree ) . ')' ) ); $this->DB->execute(); while( $row = $this->DB->fetch() ) { $row['cache_content'] = md5( $row['cache_content'] ); $cachedCSS[ $row['cache_set_id'] ][ $row['cache_value_1'] ] = $row; } //----------------------------------------- // Fetch all CSS data for this skin set and master //----------------------------------------- $this->DB->build( array( 'select' => '*', 'from' => 'skin_css', 'where' => "css_set_id IN( 0, " . $setID . ' )' ) ); $this->DB->execute(); while( $row = $this->DB->fetch() ) { $row['css_content'] = md5( $row['css_content'] ); $thisCSS[ $row['css_set_id'] ][ $row['css_group'] ] = $row; } /* Add in parent skins */ if ( count( $setData['_parentTree'] ) ) { $childTree = array_merge( $childTree, $setData['_parentTree'] ); } //----------------------------------------- // First off, any caches missing? //----------------------------------------- foreach( $childTree as $_setID ) { if ( $_setID > 0 AND is_array( $thisCSS[ $_setID ] ) ) { foreach( $thisCSS[ $_setID ] as $group => $data ) { if ( ! is_array( $cachedCSS[ $_setID ][ $group ] ) OR $forceRebuild === TRUE ) { /* Not currently cached. Needs to be recached */ $needToRebuild[ $_setID ][ $group ] = 2; continue; } } } if ( $_setID > 0 ) { /* We have no caches at all... */ if ( is_array( $thisCSS[0] ) ) { foreach( $thisCSS[0] as $group => $data ) { if ( ! is_array( $cachedCSS[ $_setID ][ $group ] ) OR $forceRebuild === TRUE ) { /* Not currently cached. Needs to be recached */ $needToRebuild[ $_setID ][ $group ] = 2; continue; } } } } } //----------------------------------------- // Forcing the issue? //----------------------------------------- if ( $forceRebuild === TRUE ) { $this->_cssNeedToRebuildArray[ $setID ] = $needToRebuild; return $needToRebuild; } //----------------------------------------- // Now check to see if there are any that // inherit directly from any CSS in this set //----------------------------------------- if( is_array($cachedCSS) AND count($cachedCSS) ) { foreach( $cachedCSS as $_setID => $_data ) { foreach( $_data as $group => $cacheData ) { if ( $cacheData['cache_key_3'] != 'setBy' ) { $needToRebuild[ $_setID ][ $group ] = 1; } else if ( $cacheData['cache_set_id'] == $setID ) { /* This CSS is from our skin set, so recache */ $needToRebuild[ $_setID ][ $group ] = 1; } else if ( $cacheData['cache_set_id'] == $_setID ) { /* This CSS is from our child tree, so recache */ $needToRebuild[ $_setID ][ $group ] = 1; } else if ( $cacheData['cache_value_3'] == $setID ) { /* This CSS inherits our skin set, so recache */ $needToRebuild[ $_setID ][ $group ] = 1; } /* Now lets see if there are any parent CSS that need to be added */ if ( count( $setData['_parentTree'] ) ) { foreach( $setData['_parentTree'] as $id ) { if ( $cacheData['cache_key_3'] == 'setBy' && $cacheData['cache_value_3'] == $id ) { /* Add to current set id */ $needToRebuild[ $setID ][ $group ] = 1; } } } } } } /* Finally, we don't want to recache parents so remove them */ if ( count( $setData['_parentTree'] ) ) { foreach( $setData['_parentTree'] as $id ) { if ( is_array( $needToRebuild[ $id ] ) ) { unset( $needToRebuild[ $id ] ); } } } $this->_cssNeedToRebuildArray[ $setID ] = $needToRebuild; return $needToRebuild; } }