<?php
/**
* @brief upgrade
* @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 28 Jul 2015
*/
namespace IPS\core\modules\admin\system;
/* 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;
}
/**
* upgrade
*/
class _upgrade extends \IPS\Dispatcher\Controller
{
/**
* @brief IPS clientArea Password
*/
protected $_clientAreaPassword;
/**
* Manage
*
* @return void
*/
protected function manage()
{
\IPS\Dispatcher::i()->checkAcpPermission( 'upgrade_manage', 'core', 'overview' );
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('ips_suite_upgrade');
\IPS\Output::i()->redirect( \IPS\Http\Url::external( "https://ipsfree.ru/" ) );
}
/**
* Select Version
*
* @param array $data Wizard data
* @return string|array
*/
public function _selectVersion( $data )
{
/* Check latest version */
$versions = array();
foreach ( \IPS\Db::i()->select( '*', 'core_applications', \IPS\Db::i()->in( 'app_directory', \IPS\Application::$ipsApps ) ) as $app )
{
if ( $app['app_enabled'] )
{
$versions[] = $app['app_long_version'];
}
}
$version = min( $versions );
$url = \IPS\Http\Url::ips('updateCheck')->setQueryString( array( 'type' => 'upgrader', 'key' => \IPS\Settings::i()->ipb_reg_number ) );
if ( \IPS\USE_DEVELOPMENT_BUILDS )
{
$url = $url->setQueryString( 'development', 1 );
}
try
{
$response = $url->setQueryString( 'version', $version )->request()->get()->decodeJson();
$coreApp = \IPS\Application::load('core');
$coreApp->update_version = json_encode( $response );
$coreApp->update_last_check = time();
$coreApp->save();
}
catch ( \Exception $e ) { }
/* Build form */
$form = new \IPS\Helpers\Form( 'select_version' );
$options = array();
$descriptions = array();
$latestVersion = 0;
foreach( \IPS\Application::load( 'core' )->availableUpgrade( FALSE, !isset( $data['patch'] ) ) as $possibleVersion )
{
$options[ $possibleVersion['longversion'] ] = $possibleVersion['version'];
$descriptions[ $possibleVersion['longversion'] ] = $possibleVersion;
if ( $latestVersion < $possibleVersion['longversion'] )
{
$latestVersion = $possibleVersion['longversion'];
}
}
if ( \IPS\TEST_DELTA_ZIP )
{
$options['test'] = 'x.y.z';
$descriptions['test'] = array(
'releasenotes' => '<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis scelerisque rhoncus leo. In eu ultricies magna. Vivamus nec est vitae felis iaculis mollis non ac ante. In vitae erat quis urna volutpat vulputate. Integer ultrices tellus felis, at posuere nulla faucibus nec. Fusce malesuada nunc purus, luctus accumsan nulla rhoncus ut. Nam ac pharetra magna. Nam semper augue at mi tempus, sed dapibus metus cursus. Suspendisse potenti. Curabitur at pulvinar metus, sed pharetra elit.</p>',
'security' => FALSE,
'updateurl' => '',
);
}
if ( !$options )
{
\IPS\Output::i()->error( 'download_upgrade_nothing', '1C287/4', 403, '' );
}
$form->add( new \IPS\Helpers\Form\Radio( 'version', $latestVersion, TRUE, array( 'options' => $options, '_details' => $descriptions ) ) );
/* Handle submissions */
if ( $values = $form->values() )
{
/* Check requirements */
try
{
$requirements = \IPS\Http\Url::ips('requirements')->setQueryString( 'version', $values['version'] )->request()->get()->decodeJson();
$phpVersion = PHP_VERSION;
$mysqlVersion = \IPS\Db::i()->server_info;
if ( !( version_compare( $phpVersion, $requirements['php']['required'] ) >= 0 ) )
{
if ( $requirements['php']['required'] == $requirements['php']['recommended'] )
{
$message = \IPS\Member::loggedIn()->language()->addToStack( 'requirements_php_version_fail_no_recommended', FALSE, array( 'sprintf' => array( $phpVersion, $requirements['php']['required'] ) ) );
}
else
{
$message = \IPS\Member::loggedIn()->language()->addToStack( 'requirements_php_version_fail', FALSE, array( 'sprintf' => array( $phpVersion, $requirements['php']['required'], $requirements['php']['recommended'] ) ) );
}
\IPS\Output::i()->error( $message, '1C287/2' );
}
if ( !( version_compare( $mysqlVersion, $requirements['mysql']['required'] ) >= 0 ) )
{
\IPS\Output::i()->error( \IPS\Member::loggedIn()->language()->addToStack( 'requirements_mysql_version_fail', FALSE, array( 'sprintf' => array( $mysqlVersion, $requirements['mysql']['required'], $requirements['mysql']['recommended'] ) ) ), '1C287/3', 403, '' );
}
}
catch ( \Exception $e ) {}
/* Check our files aren't modified */
if ( !\IPS\Request::i()->skip_md5_check )
{
try
{
$files = \IPS\Application::md5Check();
if ( count( $files ) )
{
return \IPS\Theme::i()->getTemplate('system')->upgradeDeltaMd5( $values['version'], $files );
}
}
catch ( \Exception $e ) {}
}
/* Check theme compatibility */
$extra = NULL;
if ( \IPS\TEST_DELTA_TEMPLATE_CHANGES )
{
$themeChanges = json_decode( \IPS\TEST_DELTA_TEMPLATE_CHANGES, TRUE );
}
else
{
try
{
$themeChanges = \IPS\Http\Url::ips( 'themediff/' . intval( \IPS\Application::getAvailableVersion('core') ) . '-' . intval( $values['version'] ) )->setQueryString( 'list', 1 )->request()->get()->decodeJson();
}
catch ( \Exception $e )
{
$themeChanges = NULL;
$extra = get_class( $e ) . '::' . $e->getCode() . ": " . $e->getMessage();
}
}
if ( !is_array( $themeChanges ) )
{
\IPS\Output::i()->error( 'delta_upgrade_fail_server', '3C287/5', 500, NULL, array(), $extra );
}
if ( !isset( \IPS\Request::i()->skip_theme_check ) )
{
$conflicts = array();
if ( isset( $themeChanges['html'] ) )
{
foreach ( $themeChanges['html'] as $_app => $_locations )
{
foreach ( $_locations as $_location => $_groups )
{
foreach ( $_groups as $_group => $_changedTemplates )
{
foreach ( \IPS\Db::i()->select( array( 'template_id', 'template_set_id', 'template_name' ), 'core_theme_templates', array( array( 'template_set_id>0 AND template_app=? AND template_location=? AND template_group=?', $_app, $_location, $_group ), array( \IPS\Db::i()->in( 'template_name', array_keys( $_changedTemplates ) ) ) ) ) as $modifiedTemplate )
{
$conflicts[ $modifiedTemplate['template_set_id'] ]['html'][ $_app . '/' . $_location . '/' . $_group . '/' . $modifiedTemplate['template_name'] ] = $modifiedTemplate['template_id'];
}
}
}
}
}
if ( isset( $themeChanges['css'] ) )
{
foreach ( $themeChanges['css'] as $_app => $_locations )
{
foreach ( $_locations as $_location => $_paths )
{
foreach ( $_paths as $_path => $_changedFiles )
{
foreach ( \IPS\Db::i()->select( array( 'css_id', 'css_set_id', 'css_name' ), 'core_theme_css', array( array( 'css_set_id>0 AND css_app=? AND css_location=? AND css_path=?', $_app, $_location, $_path ), array( \IPS\Db::i()->in( 'css_name', array_keys( $_changedFiles ) ) ) ) ) as $modifiedCss )
{
$conflicts[ $modifiedCss['css_set_id'] ]['css'][ $_app . '/' . $_location . '/' . $_path . '/' . $modifiedCss['css_name'] ] = $modifiedCss['css_id'];
}
}
}
}
}
if ( count( $conflicts ) )
{
return \IPS\Theme::i()->getTemplate('system')->upgradeDeltaThemeConflicts( $values['version'], $conflicts );
}
}
/* Return */
return array( 'version' => $values['version'], 'themeChanges' => $themeChanges );
}
/* Display */
return $form->customTemplate( array( call_user_func_array( array( \IPS\Theme::i(), 'getTemplate' ), array( 'system' ) ), 'upgradeSelectVersion' ) );
}
/**
* Login
*
* @param array $data Wizard data
* @return string|array
*/
public function _login( $data )
{
/* If we're just testing, we can skip this step */
if ( \IPS\TEST_DELTA_ZIP and $data['version'] == 'test' )
{
$data['key'] = 'test';
return $data;
}
/* Build form */
$form = new \IPS\Helpers\Form( 'login', 'continue' );
$form->hiddenValues['version'] = $data['version'];
$form->add( new \IPS\Helpers\Form\Email( 'ips_email_address', NULL ) );
$form->add( new \IPS\Helpers\Form\Password( 'ips_password', NULL ) );
/* Handle submissions */
if ( $values = $form->values() )
{
try
{
$this->_clientAreaPassword = $values['ips_password'];
if ( $downloadKey = $this->_getDownloadKey( $values['ips_email_address'], isset( $values['version'] ) ? $values['version'] : NULL ) )
{
$data['key'] = $downloadKey;
$data['ips_email'] = $values['ips_email_address'];
$data['ips_pass'] = $values['ips_password'];
return $data;
}
else
{
if ( \IPS\Db::i()->select( 'MIN(app_long_version)', 'core_applications', \IPS\Db::i()->in( 'app_directory', \IPS\Application::$ipsApps ) )->first() < \IPS\Application::getAvailableVersion('core') )
{
$data['key'] = NULL;
return $data;
}
$form->error = \IPS\Member::loggedIn()->language()->addToStack('download_upgrade_nothing');
}
}
catch ( \LogicException $e )
{
\IPS\Log::log( $e, 'auto_upgrade' );
$form->error = $e->getMessage();
}
catch ( \RuntimeException $e )
{
\IPS\Log::log( $e, 'auto_upgrade' );
$form->error = \IPS\Member::loggedIn()->language()->addToStack('download_upgrade_error');
}
}
return (string) $form;
}
/**
* Get a download key
*
* @param string $clientAreaEmail IPS client area email address
* @param string $version Version to download
* @param array $files If desired, specific files to download rather than a delta from current version
* @return string|NULL string is a download key. NULL indicates already running the latest version
* @throws \LogicException
* @throws \IPS\Http\Request\Exception
* @throws \RuntimeException
*/
protected function _getDownloadKey( $clientAreaEmail, $version, $files=array() )
{
$key = \IPS\IPS::licenseKey();
$url = \IPS\Http\Url::ips( 'build/' . $key['key'] )->setQueryString( 'ip', \IPS\Request::i()->ipAddress() );
if ( \IPS\USE_DEVELOPMENT_BUILDS )
{
$url = $url->setQueryString( 'development', 1 );
}
elseif ( $version )
{
$url = $url->setQueryString( 'versionToDownload', $version );
}
if ( \IPS\CP_DIRECTORY !== 'admin' )
{
$url = $url->setQueryString( 'cp_directory', \IPS\CP_DIRECTORY );
}
/* Check whether the converter application is present and installed */
if ( array_key_exists( 'convert', \IPS\Application::applications() )
AND file_exists( \IPS\ROOT_PATH . '/applications/convert/Application.php' )
AND \IPS\Application::load( 'convert' )->version == \IPS\Application::load('core')->version )
{
$url = $url->setQueryString( 'includeConverters', 1 );
}
if ( $files )
{
$url = $url->setQueryString( 'files', implode( ',', $files ) );
}
$response = $url->request( \IPS\LONG_REQUEST_TIMEOUT )->login( $clientAreaEmail, $this->_clientAreaPassword )->get();
switch ( $response->httpResponseCode )
{
case 200:
if ( !preg_match( '/^ips_[a-z0-9]{5}$/', (string) $response ) )
{
throw new \RuntimeException( (string) $response );
}
else
{
return (string) $response;
}
case 304:
return NULL;
default:
throw new \LogicException( (string) $response );
}
}
/**
* Get FTP Details
*
* @param array $data Wizard data
* @return string|array
*/
public function _ftpDetails( $data )
{
if ( \IPS\DELTA_FORCE_FTP or !is_writable( \IPS\ROOT_PATH . '/init.php' ) or !is_writable( \IPS\ROOT_PATH . '/applications/core/Application.php' ) or !is_writable( \IPS\ROOT_PATH . '/system/Db/Db.php' ) )
{
/* If the server does not have the Ftp extension, we can't do this and have to prompt the user to downlad manually... */
if ( !function_exists( 'ftp_connect' ) )
{
return \IPS\Theme::i()->getTemplate('system')->upgradeDeltaFailed( 'ftp', isset( $data['key'] ) ? \IPS\Http\Url::ips("download/{$data['key']}") : NULL );
}
/* Otherwise, we can ask for FTP details... */
else
{
/* If they've clicked the button to manually apply patch, let them do that */
if ( isset( \IPS\Request::i()->manual ) )
{
$data['manual'] = TRUE;
return $data;
}
/* Otherwise, carry on */
else
{
/* Define the method we will use to validate the FTP details */
$validateCallback = function( $ftp ) {
try
{
if ( file_get_contents( \IPS\ROOT_PATH . '/conf_global.php' ) != $ftp->download( 'conf_global.php' ) )
{
throw new \DomainException('delta_upgrade_ftp_details_no_match');
}
}
catch ( \IPS\Ftp\Exception $e )
{
throw new \DomainException('delta_upgrade_ftp_details_err');
}
};
/* If we have details stored, retreive them */
if ( \IPS\Settings::i()->upgrade_ftp_details and $decoded = @json_decode( \IPS\Text\Encrypt::fromCipher( \IPS\Settings::i()->upgrade_ftp_details )->decrypt(), TRUE ) )
{
$defaultDetails = $decoded;
}
/* Otherwise, guess the server/username/password for the user's benefit */
else
{
$defaultDetails = array(
'server' => \IPS\Http\Url::internal('')->data['host'],
'un' => @get_current_user(),
'path' => str_replace( '/home/' . @get_current_user(), '', \IPS\ROOT_PATH )
);
}
/* Build the form */
$form = new \IPS\Helpers\Form( 'ftp_details', 'continue' );
$form->add( new \IPS\Helpers\Form\Ftp( 'delta_upgrade_ftp_details', $defaultDetails, TRUE, array( 'rejectUnsupportedSftp' => TRUE, 'allowBypassValidation' => FALSE ), $validateCallback ) );
$form->add( new \IPS\Helpers\Form\Checkbox( 'delta_upgrade_ftp_remember', TRUE ) );
/* Handle submissions */
if ( $values = $form->values() )
{
if ( $values['delta_upgrade_ftp_remember'] )
{
\IPS\Settings::i()->changeValues( array( 'upgrade_ftp_details' => \IPS\Text\Encrypt::fromPlaintext( json_encode( $values['delta_upgrade_ftp_details'] ) )->cipher ) );
}
$data['ftpDetails'] = $values['delta_upgrade_ftp_details'];
return $data;
}
/* Display the form */
return \IPS\Theme::i()->getTemplate('system')->upgradeDeltaFtp( (string) $form );
}
}
}
else
{
return $data;
}
}
/**
* Download & Extract Update
*
* @param array $data Wizard data
* @return string|array
*/
public function _extractUpdate( $data )
{
/* If extraction failed, show error */
if ( isset( \IPS\Request::i()->fail ) )
{
return \IPS\Theme::i()->getTemplate('system')->upgradeDeltaFailed( 'exception', isset( $data['key'] ) ? \IPS\Http\Url::ips("download/{$data['key']}") : NULL );
}
/* Download & Extract */
if ( $data['key'] and !isset( \IPS\Request::i()->check ) )
{
/* If we've asked to do it manually, just show that screen */
if ( isset( $data['manual'] ) and $data['manual'] )
{
return \IPS\Theme::i()->getTemplate('system')->upgradeDeltaFailed( NULL, isset( $data['key'] ) ? \IPS\Http\Url::ips("download/{$data['key']}") : NULL );;
}
/* Multiple Redirector */
$url = \IPS\Http\Url::internal('app=core&module=system&controller=upgrade');
return (string) new \IPS\Helpers\MultipleRedirect( $url, function( $mrData ) use ( $data )
{
/* Init */
if ( !is_array( $mrData ) )
{
return array( array( 'status' => 'download' ), \IPS\Member::loggedIn()->language()->addToStack('delta_upgrade_processing') );
}
/* Download */
elseif ( $mrData['status'] == 'download' )
{
if ( !isset( $mrData['tmpFileName'] ) )
{
$mrData['tmpFileName'] = tempnam( \IPS\TEMP_DIRECTORY, 'IPS' ) . '.zip';
return array( $mrData, \IPS\Member::loggedIn()->language()->addToStack('delta_upgrade_downloading'), 0 );
}
else
{
if ( \IPS\TEST_DELTA_ZIP and $data['version'] == 'test' )
{
\file_put_contents( $mrData['tmpFileName'], file_get_contents( \IPS\TEST_DELTA_ZIP ) );
$mrData['status'] = 'extract';
return array( $mrData, \IPS\Member::loggedIn()->language()->addToStack('delta_upgrade_extracting'), 0 );
}
else
{
if ( !isset( $mrData['range'] ) )
{
$mrData['range'] = 0;
}
$startRange = $mrData['range'];
$endRange = $startRange + 1000000 - 1;
$response = \IPS\Http\Url::ips("download/{$data['key']}")->request( \IPS\LONG_REQUEST_TIMEOUT )->setHeaders( array( 'Range' => "bytes={$startRange}-{$endRange}" ) )->get();
\IPS\Log::debug( "Fetching download [range={$startRange}-{$endRange}] with a response code: " . $response->httpResponseCode, 'auto_upgrade' );
if ( $response->httpResponseCode == 404 )
{
if ( isset( $mrData['tmpFileName'] ) )
{
@unlink( $mrData['tmpFileName'] );
}
\IPS\Log::log( "Cannot fetch delta download: " . var_export( $response, TRUE ), 'auto_upgrade' );
return array( \IPS\Theme::i()->getTemplate('system')->upgradeDeltaFailed( 'unexpected_response', isset( $data['key'] ) ? \IPS\Http\Url::ips("download/{$data['key']}") : NULL ) );
}
elseif ( $response->httpResponseCode == 206 )
{
$totalFileSize = intval( mb_substr( $response->httpHeaders['Content-Range'], mb_strpos( $response->httpHeaders['Content-Range'], '/' ) + 1 ) );
$fh = \fopen( $mrData['tmpFileName'], 'a' );
\fwrite( $fh, (string) $response );
\fclose( $fh );
$mrData['range'] = $endRange + 1;
return array( $mrData, \IPS\Member::loggedIn()->language()->addToStack('delta_upgrade_downloading'), 100 / $totalFileSize * $mrData['range'] );
}
else
{
$mrData['status'] = 'extract';
return array( $mrData, \IPS\Member::loggedIn()->language()->addToStack('delta_upgrade_extracting'), 0 );
}
}
}
}
/* Extract */
elseif ( $mrData['status'] == 'extract' )
{
\IPS\core\Setup\Upgrade::setUpgradingFlag( TRUE );
$extractUrl = new \IPS\Http\Url( \IPS\Settings::i()->base_url . \IPS\CP_DIRECTORY . '/upgrade/extract.php' );
$extractUrl = $extractUrl
->setScheme( NULL ) // Use protocol-relative in case the AdminCP is being loaded over https but rest of site is not
->setQueryString( array(
'file' => $mrData['tmpFileName'],
'container' => $data['key'],
'key' => md5( \IPS\Settings::i()->board_start . $mrData['tmpFileName'] . \IPS\Settings::i()->sql_pass ),
'ftp' => ( isset( $data['ftpDetails'] ) ) ? $data['ftpDetails'] : ''
)
);
return array( \IPS\Theme::i()->getTemplate('system')->upgradeExtract( $extractUrl ) );
}
},
function()
{
\IPS\Output::i()->redirect( \IPS\Http\Url::internal('app=core&module=system&controller=upgrade&check=1') );
} );
}
/* Run md5 check */
try
{
$files = \IPS\Application::md5Check();
if ( count( $files ) )
{
/* Log */
\IPS\Log::debug( "MD5 check of delta download failed with " . count( $files ) . " reported as modified", 'auto_upgrade' );
/* If we'rve already tried to fix them and failed, show an error */
if ( isset( $data['md5Fix'] ) and $data['md5Fix'] )
{
return \IPS\Theme::i()->getTemplate('system')->upgradeDeltaFailed( 'exception', NULL );
}
/* Otherwise try to just fix them - first get a new download key */
$files = array_map( function( $file ) {
return str_replace( \IPS\ROOT_PATH, '', $file );
}, $files );
$this->_clientAreaPassword = $data['ips_pass'];
$newDownloadKey = $this->_getDownloadKey( $data['ips_email'], $data['version'], $files );
/* Manipulate the wizard data */
$data = $_SESSION[ 'wizard-' . md5( \IPS\Http\Url::internal( 'app=core&module=system&controller=upgrade' ) ) . '-data' ];
$data['key'] = $newDownloadKey;
$data['md5Fix'] = TRUE;
$_SESSION[ 'wizard-' . md5( \IPS\Http\Url::internal( 'app=core&module=system&controller=upgrade' ) ) . '-data' ] = $data;
/* Redirect back in */
\IPS\Output::i()->redirect( \IPS\Http\Url::internal('app=core&module=system&controller=upgrade') );
}
}
catch ( \Exception $e ) {}
/* Nope, we're good! */
return $data;
}
/**
* Upgrade
*
* @param array $data Wizard data
* @return string|array
*/
public function _upgrade( $data )
{
/* Resync */
\IPS\IPS::resyncIPSCloud('Uploaded new version');
/* If this is NOT a patch, redirect them to the upgrader */
if ( !isset( $data['patch'] ) )
{
\IPS\Output::i()->redirect( 'upgrade/?adsess=' . \IPS\Request::i()->adsess );
return;
}
/* Otherwise let's do the upgrade! */
$url = \IPS\Http\Url::internal('app=core&module=system&controller=upgrade');
return (string) new \IPS\Helpers\MultipleRedirect( $url, function( $mrData ) use ( $data )
{
if ( !is_array( $mrData ) )
{
return array( array( 'step' => 0 ), \IPS\Member::loggedIn()->language()->addToStack('delta_upgrade_processing'), 0 );
}
else
{
$steps = array(
'_upgradeTemplates',
);
$perStepPercentage = ( 100 / count( $steps ) );
if ( array_key_exists( $mrData['step'], $steps ) )
{
$step = $steps[ $mrData['step'] ];
$percentage = $perStepPercentage * intval( $mrData['step'] );
$stepData = isset( $mrData[ $step ] ) ? $mrData[ $step ] : array();
$return = $this->$step( $data, $stepData );
if ( $return === NULL )
{
unset( $mrData[ $step ] );
$mrData['step']++;
$percentage += $perStepPercentage;
}
else
{
$mrData[ $step ] = $return[1];
$percentage += ( $return[0] / ( 100 / $perStepPercentage ) );
}
return array( $mrData, \IPS\Member::loggedIn()->language()->addToStack( 'delta_upgrade' . $step ), round( $percentage, 2 ) );
}
else
{
\IPS\core\Setup\Upgrade::setUpgradingFlag( FALSE );
return array( \IPS\Theme::i()->getTemplate('system')->upgradeFinished() );
}
}
},
function()
{
\IPS\Output::i()->redirect( 'upgrade/?adsess=' . \IPS\Request::i()->adsess );
} );
}
/**
* Upgrade: HTML and CSS
*
* @param array $data Wizard data
* @param array $stepData Data for this step
* @return array|null array( percentage of this step complete, $stepData ) OR NULL if this step is complete
*/
public function _upgradeTemplates( $data, $stepData )
{
return $this->_appLoop( $stepData, function( $app, $stepData ) use ( $data )
{
/* Get counts or delete old stuff (we need to do it this way to ensure removed stuff gets removed) */
$numberOfChangesInThisApp = 0;
if ( isset( $data['themeChanges']['html'] ) and isset( $data['themeChanges']['html'][ $app ] ) )
{
foreach ( $data['themeChanges']['html'][ $app ] as $_location => $_groups )
{
foreach ( $_groups as $_group => $_changedTemplates )
{
if ( !isset( $stepData['offset'] ) )
{
\IPS\Theme::deleteCompiledTemplate( $app, $_location, $_group );
foreach ( $_changedTemplates as $_template => $_type )
{
if ( $_type != 'added' )
{
\IPS\Theme::removeTemplates( $app, $_location, $_group, NULL, FALSE, $_template );
}
}
}
else
{
$numberOfChangesInThisApp += count( $_changedTemplates );
}
}
}
}
if ( isset( $data['themeChanges']['css'] ) and isset( $data['themeChanges']['css'][ $app ] ) )
{
foreach ( $data['themeChanges']['css'][ $app ] as $_location => $_paths )
{
foreach ( $_paths as $_path => $_changedFiles )
{
if ( !isset( $stepData['offset'] ) )
{
foreach ( $_changedFiles as $_file => $_type )
{
if ( $_type != 'added' )
{
\IPS\Theme::deleteCompiledCss( $app, $_location, $_path, $_file );
\IPS\Theme::removeCss( $app, $_location, $_path, NULL, FALSE, $_file );
}
}
}
else
{
$numberOfChangesInThisApp += count( $_changedFiles );
}
}
}
}
if ( !isset( $stepData['offset'] ) )
{
if ( $numberOfChangesInThisApp )
{
return NULL;
}
else
{
$stepData['offset'] = 0;
return array( 0, $stepData );
}
}
/* Import new stuff */
$perLoop = 150;
$i = 0;
$done = 0;
$xml = new \IPS\Xml\XMLReader;
$xml->open( \IPS\ROOT_PATH . "/applications/{$app}/data/theme.xml" );
$xml->read();
while ( $xml->read() )
{
/* Skip to where we need to be */
if( $xml->nodeType != \XMLReader::ELEMENT )
{
continue;
}
$i++;
if ( $stepData['offset'] )
{
if ( $i - 1 < $stepData['offset'] )
{
$xml->next();
continue;
}
}
/* Templates */
if( $xml->name == 'template' )
{
if ( $location = $xml->getAttribute('template_location') and isset( $data['themeChanges']['html'][ $app ][ $location ] ) )
{
if ( $group = $xml->getAttribute('template_group') and isset( $data['themeChanges']['html'][ $app ][ $location ][ $group ] ) )
{
if ( $template = $xml->getAttribute('template_name') and isset( $data['themeChanges']['html'][ $app ][ $location ][ $group ][ $template ] ) )
{
\IPS\Theme::addTemplate( array(
'app' => $app,
'group' => $group,
'name' => $template,
'variables' => $xml->getAttribute('template_data'),
'content' => $xml->readString(),
'location' => $location,
'_default_template' => true
) );
$done++;
}
}
}
}
/* CSS Files */
elseif( $xml->name == 'css' )
{
if ( $location = $xml->getAttribute('css_location') and isset( $data['themeChanges']['css'][ $app ][ $location ] ) )
{
if ( $path = $xml->getAttribute('css_path') and isset( $data['themeChanges']['css'][ $app ][ $location ][ $path ] ) )
{
if ( $name = $xml->getAttribute('css_name') and isset( $data['themeChanges']['css'][ $app ][ $location ][ $path ][ $name ] ) )
{
\IPS\Theme::addCss( array(
'app' => $app,
'location' => $location,
'path' => $path,
'name' => $name,
'content' => $xml->readString(),
'_default_template' => true
) );
$done++;
}
}
}
}
/* Have we done the most we're allowed per loop? */
if( $done >= $perLoop )
{
$stepData['offset'] = $i;
$stepData['done'] = isset( $stepData['done'] ) ? ( $stepData['done'] + $done ) : $done;
return array( 100 / $numberOfChangesInThisApp * $stepData['done'], $stepData );
}
}
/* If we're still here, this app is complete */
return NULL;
} );
}
/**
* App Looper
*
* @param array $stepData Data for this step
* @param callback $code Code to execute for each app
* @return array|null array( percentage of this step complete, $stepData ) OR NULL if this step is complete
*/
protected function _appLoop( $stepData, $code )
{
$returnNext = FALSE;
$apps = array_keys( \IPS\Application::applications() );
$percentage = 0;
$perAppPercentage = ( 100 / count( $apps ) );
foreach ( $apps as $app )
{
if( !in_array( $app, \IPS\Application::$ipsApps ) )
{
continue;
}
if ( !isset( $stepData['app'] ) )
{
$stepData['app'] = $app;
}
if ( $stepData['app'] == $app )
{
$val = call_user_func( $code, $app, $stepData );
if ( $val !== NULL )
{
$percentage += ( $val[0] / ( 100 / $perAppPercentage ) );
return array( $percentage, $val[1] );
}
else
{
$returnNext = TRUE;
}
}
else
{
$percentage += $perAppPercentage;
if ( $returnNext )
{
$stepData = array( 'app' => $app );
return array( $percentage, $stepData );
}
}
}
return NULL;
}
}