Version 4.3.0

This commit is contained in:
Neo committed 2025-12-19 05:47:12 -08:00
1 parent 96997ddd8e
commit fe1acf984a
1349 files changed
+116159 -67711

No files matched your search

+155 -30
View File
@@ -28,7 +28,6 @@ class _Application extends \IPS\Node\Model
public static $ipsApps = array(
'blog',
'calendar',
'chat',
'cms',
'core',
'downloads',
@@ -114,7 +113,7 @@ class _Application extends \IPS\Node\Model
}
/* Save the new FURL customisation */
\IPS\Db::i()->update( 'core_sys_conf_settings', array( 'conf_value' => json_encode( $furlCustomizations ) ), array( 'conf_key=?', 'furl_configuration' ) );
\IPS\Settings::i()->changeValues( array( 'furl_configuration' => json_encode( $furlCustomizations ) ) );
}
/* Actually update the database */
@@ -123,7 +122,6 @@ class _Application extends \IPS\Node\Model
/* Clear cached data */
unset( \IPS\Data\Store::i()->applications );
unset( \IPS\Data\Store::i()->settings );
unset( \IPS\Data\Store::i()->furl_configuration );
\IPS\Member::clearCreateMenu();
@@ -468,6 +466,37 @@ class _Application extends \IPS\Node\Model
return $update;
}
/**
* The latest new feature ID
*
* @return int|null
*/
public function newFeature()
{
if( $this->update_version )
{
$versions = json_decode( $this->update_version, TRUE );
if ( is_array( $versions ) and !isset( $versions[0] ) and isset( $versions['longversion'] ) )
{
$versions = array( $versions );
}
$latestVersion = NULL;
foreach ( $versions as $data )
{
if( isset( $data['latestNewFeature'] ) AND $data['latestNewFeature'] AND $data['latestNewFeature'] > $latestVersion )
{
$latestVersion = $data['latestNewFeature'];
}
}
return $latestVersion;
}
return NULL;
}
/**
* Is the application up to date with security patches?
*
@@ -493,10 +522,11 @@ class _Application extends \IPS\Node\Model
/**
* MD5 check (returns path to files which do not match)
*
* @retrun array
* @param int|NULL $version Version to check against
* @return array
* @throws \IPS\Http\Request\Exception
*/
public static function md5Check()
public static function md5Check( $version = NULL )
{
/* For Community in the Cloud customers we cannot do this because they have encoded files
and the encoder produces different output each time it runs, so every Cloud customer
@@ -508,7 +538,12 @@ class _Application extends \IPS\Node\Model
}
/* For everyone else, get the correct md5 sums for each file... */
$correctMd5s = \IPS\Http\Url::ips( 'md5' )->request()->get()->decodeJson();
$url = \IPS\Http\Url::ips( 'md5' );
if ( $version !== NULL )
{
$url = $url->setQueryString( 'version', $version );
}
$correctMd5s = $url->request()->get()->decodeJson();
/* And return whichever ones don't match */
$return = array();
@@ -533,11 +568,40 @@ class _Application extends \IPS\Node\Model
continue;
}
/* MD5 check */
if ( !file_exists( \IPS\ROOT_PATH . $file ) or !\IPS\Login::compareHashes( md5( preg_replace( '#\s#', '', utf8_decode( file_get_contents( \IPS\ROOT_PATH . $file ) ) ) ), $md5Hash ) ) // We strip whitespace since FTP in ASCII mode will change the whitespace characters
/* If the file doesn't exist at all, flag it */
if ( !file_exists( \IPS\ROOT_PATH . $file ) )
{
$return[] = \IPS\ROOT_PATH . $file;
}
/* Otherwise, compare the md5 hashes... */
else
{
/* Get the contents. If you can't get the contents, it may be that the file permissions are set wrong. Try to fix. */
$fileContents = @file_get_contents( \IPS\ROOT_PATH . $file );
if ( !$fileContents )
{
@chmod( \IPS\ROOT_PATH . $file, \IPS\FILE_PERMISSION_NO_WRITE );
}
/* If we got the file contents... */
if ( $fileContents )
{
/* Strip whitespace since FTP in ASCII mode will change the whitespace characters */
$fileContents = preg_replace( '#\s#', '', utf8_decode( file_get_contents( \IPS\ROOT_PATH . $file ) ) );
/* Compare */
if ( md5( $fileContents ) != $md5Hash )
{
$return[] = \IPS\ROOT_PATH . $file;
}
}
/* Otherwise, flag it */
else
{
$return[] = \IPS\ROOT_PATH . $file;
}
}
}
return $return;
@@ -989,7 +1053,7 @@ EOF;
{
return TRUE;
}
return $this->enabled and ( !in_array( $this->directory, static::$ipsApps ) or $this->long_version == \IPS\Application::load('core')->long_version );
return $this->enabled and ( !in_array( $this->directory, static::$ipsApps ) or $this->version == \IPS\Application::load('core')->version );
}
/**
@@ -1008,6 +1072,23 @@ EOF;
$this->enabled = $enabled;
$this->save();
\IPS\Plugin\Hook::writeDataFile();
/* Clear templates to rebuild automatically */
\IPS\Theme::deleteCompiledTemplate();
/* Invalidate disk templates */
\IPS\Theme::resetAllCacheKeys();
/* Enable queue task in case there are pending items */
if( $this->enabled )
{
$queueTask = \IPS\Task::load( 'queue', 'key' );
$queueTask->enabled = TRUE;
$queueTask->save();
}
/* Update other app specific task statuses */
\IPS\Db::i()->update( 'core_tasks', array( 'enabled' => (int) $this->enabled ), array( 'app=?', $this->directory ) );
}
/**
@@ -1022,7 +1103,7 @@ EOF;
{
return TRUE;
}
if ( !$this->_enabled and in_array( $this->directory, static::$ipsApps ) and $this->long_version != \IPS\Application::load('core')->long_version )
if ( !$this->_enabled and in_array( $this->directory, static::$ipsApps ) and $this->version != \IPS\Application::load('core')->version )
{
return TRUE;
}
@@ -1249,7 +1330,7 @@ EOF;
/* Clear out member's cached "Create Menu" contents */
\IPS\Member::clearCreateMenu();
unset( \IPS\Data\Store::i()->applications );
unset( \IPS\Data\Store::i()->settings );
\IPS\Settings::i()->clearCache();
}
/**
@@ -1682,7 +1763,7 @@ EOF;
$module->position = ++$position;
$module->area = $area;
$module->default_controller = $data['default_controller'];
$module->default = isset( $data['default'] ) and $data['default'];
$module->default = ( isset( $data['default'] ) and $data['default'] );
$module->save( TRUE );
}
}
@@ -1723,7 +1804,15 @@ EOF;
try
{
$fileSystem = \IPS\Db::i()->select( '*', 'core_file_storage', array( 'method=?', 'FileSystem' ), 'id ASC' )->first();
/* Only check for Amazon when installing an app via the Admin CP on Community in the Cloud. The CiC Installer will handle brand new installs. */
if ( \IPS\CIC AND !$newInstall )
{
$fileSystem = \IPS\Db::i()->select( '*', 'core_file_storage', array( 'method=?', 'Amazon' ), 'id ASC' )->first();
}
else
{
$fileSystem = \IPS\Db::i()->select( '*', 'core_file_storage', array( 'method=?', 'FileSystem' ), 'id ASC' )->first();
}
}
catch( \UnderflowException $ex )
{
@@ -1735,9 +1824,7 @@ EOF;
$settings[ 'filestorage__' . $this->directory . '_' . $key ] = $fileSystem['id'];
}
\IPS\Db::i()->update( 'core_sys_conf_settings', array( 'conf_value' => json_encode( $settings ) ), array( 'conf_key=?', 'upload_settings' ) );
\IPS\Settings::i()->upload_settings = json_encode( $settings );
unset( \IPS\Data\Store::i()->settings );
\IPS\Settings::i()->changeValues( array( 'upload_settings' => json_encode( $settings ) ) );
$inserts = array();
foreach( $this->extensions( 'core', 'Notifications' ) as $key => $class )
@@ -1792,23 +1879,24 @@ EOF;
{
if( file_exists( \IPS\ROOT_PATH . "/applications/{$this->directory}/data/settings.json" ) )
{
$currentDefaults = iterator_to_array( \IPS\Db::i()->select( '*', 'core_sys_conf_settings' )->setKeyField('conf_key')->setValueField('conf_default') );
$currentData = iterator_to_array( \IPS\Db::i()->select( array( 'conf_key', 'conf_default', 'conf_report' ), 'core_sys_conf_settings' )->setKeyField('conf_key') );
$insert = array();
$update = array();
foreach ( json_decode( file_get_contents( \IPS\ROOT_PATH . "/applications/{$this->directory}/data/settings.json" ), TRUE ) as $setting )
{
if ( ! array_key_exists( $setting['key'], $currentDefaults ) )
$report = ( isset( $setting['report'] ) and $setting['report'] != 'none' ) ? $setting['report'] : NULL;
if ( ! array_key_exists( $setting['key'], $currentData ) )
{
$insert[] = array( 'conf_key' => $setting['key'], 'conf_value' => $setting['default'], 'conf_default' => $setting['default'], 'conf_app' => $this->directory );
$insert[] = array( 'conf_key' => $setting['key'], 'conf_value' => $setting['default'], 'conf_default' => $setting['default'], 'conf_app' => $this->directory, 'conf_report' => $report );
}
elseif ( $currentDefaults[ $setting['key'] ] != $setting['default'] )
elseif ( $currentData[ $setting['key'] ]['conf_default'] != $setting['default'] or $currentData[ $setting['key'] ]['conf_report'] != $report )
{
$update[] = array( array( 'conf_default' => $setting['default'] ), array( 'conf_key=?', $setting['key'] ) );
$update[] = array( array( 'conf_default' => $setting['default'], 'conf_report' => $report ), array( 'conf_key=?', $setting['key'] ) );
}
}
if ( !empty( $insert ) )
{
\IPS\Db::i()->insert( 'core_sys_conf_settings', $insert, TRUE );
@@ -1819,7 +1907,7 @@ EOF;
\IPS\Db::i()->update( 'core_sys_conf_settings', $data[0], $data[1] );
}
unset( \IPS\Data\Store::i()->settings );
\IPS\Settings::i()->clearCache();
}
}
@@ -2008,6 +2096,7 @@ EOF;
'template_parent' => 0,
'template_app' => $this->directory,
'template_edited' => 0,
'template_pinned' => 0
);
while ( $xml->read() and $xml->name != 'template' )
@@ -2035,6 +2124,10 @@ EOF;
case 'template_content_plaintext':
$insert['template_content_plaintext'] = $xml->readString();
break;
case 'template_pinned':
$insert['template_pinned'] = $xml->readString();
break;
}
}
@@ -2675,7 +2768,10 @@ EOF;
/* If there were other differences, carry on... */
if ( $differences )
{
{
/* We re-add indexes after changing columns */
$indexesToAdd = array();
/* First check indexes to see if any need to be adjusted */
foreach( $localDefinition['indexes'] as $indexName => $indexData )
{
@@ -2703,7 +2799,7 @@ EOF;
elseif( $thisIndex['indexes'][ $indexName ] !== $localDefinition['indexes'][ $indexName ] )
{
$tableChanges[] = "DROP INDEX `{$db->escape_string( $indexName )}`";
$tableChanges[] = $db->buildIndex( $tableName, $thisIndex['indexes'][ $indexName ] );
$indexesToAdd[] = $db->buildIndex( $tableName, $thisIndex['indexes'][ $indexName ], $tableDefinition );
$dropped[] = $indexName;
if( $tableDefinition['indexes'][ $indexName ]['type'] == 'unique' OR $tableDefinition['indexes'][ $indexName ]['type'] == 'primary' )
@@ -2714,7 +2810,6 @@ EOF;
}
}
}
/* If we are about to adjust the column to not allow NULL values then adjust those values first... */
if( isset( $columnData['allow_null'] ) and $columnData['allow_null'] === FALSE )
@@ -2738,6 +2833,11 @@ EOF;
}
$tableChanges[] = "CHANGE COLUMN `{$db->escape_string( $columnName )}` {$db->compileColumnDefinition( $columnData )}";
if( count( $indexesToAdd ) )
{
$tableChanges = array_merge( $tableChanges, $indexesToAdd );
}
}
}
}
@@ -3458,7 +3558,7 @@ EOF;
$forXml = array();
foreach( $template as $k => $v )
{
if ( in_array( \substr( $k, 9 ), array('app', 'name', 'content_html', 'data', 'content_plaintext' ) ) )
if ( in_array( \substr( $k, 9 ), array('app', 'name', 'content_html', 'data', 'content_plaintext', 'pinned' ) ) )
{
$forXml[ $k ] = $v;
}
@@ -3657,7 +3757,7 @@ EOF;
}
else
{
$member = ( $memberGroups === NULL ) ? \IPS\Member::loggedIn() : $memberGroups;
$member = ( $memberOrGroup === NULL ) ? \IPS\Member::loggedIn() : $memberOrGroup;
$memberGroups = array_merge( array( $member->member_group_id ), array_filter( explode( ',', $member->mgroup_others ) ) );
}
$accessGroups = explode( ',', $this->disabled_groups );
@@ -3783,6 +3883,7 @@ EOF;
\IPS\Db::i()->delete( 'core_follow', array( 'follow_app=?', $this->directory ) );
\IPS\Db::i()->delete( 'core_view_updates', array( "classname LIKE CONCAT( ?, '%' )", "IPS\\\\{$this->directory}" ) );
\IPS\Db::i()->delete( 'core_moderator_logs', array( 'appcomponent=?', $this->directory ) );
\IPS\Db::i()->delete( 'core_member_history', array( 'log_app=?', $this->directory ) );
$classes = array();
foreach( $this->extensions( 'core', 'ContentRouter' ) AS $contentRouter )
@@ -3822,9 +3923,29 @@ EOF;
/* Delete Deletion Log Records */
\IPS\Db::i()->delete( 'core_deletion_log', \IPS\Db::i()->in( 'dellog_content_class', $classes ) );
/* Delete Promoted Content from this app */
\IPS\Db::i()->delete( 'core_social_promote', \IPS\Db::i()->in( 'promote_class', $classes ) );
/* Delete Soft Deletion Log data */
$softDeleteKeys = array();
foreach ( $classes as $class )
{
if ( $class::$hideLogKey )
{
$softDeleteKeys[] = $class::$hideLogKey;
}
}
if ( count( $softDeleteKeys ) )
{
\IPS\Db::i()->delete( 'core_soft_delete_log', \IPS\Db::i()->in( 'sdl_obj_key', $softDeleteKeys ) );
}
}
unset( \IPS\Data\Store::i()->settings );
\IPS\Settings::i()->clearCache();
/* Delete tasks and task logs */
\IPS\Db::i()->delete( 'core_tasks_log', array( 'task IN(?)', \IPS\Db::i()->select( 'id', 'core_tasks', array( 'app=?', $this->directory ) ) ) );
@@ -3865,6 +3986,9 @@ EOF;
\IPS\Theme::removeTemplates( $this->directory, NULL, NULL, NULL, TRUE );
\IPS\Theme::removeCss( $this->directory, NULL, NULL, NULL, TRUE );
\IPS\Theme::removeResources( $this->directory, NULL, NULL, NULL, TRUE );
/* Invalidate disk templates */
\IPS\Theme::resetAllCacheKeys();
/* Delete theme settings */
$valueIds = iterator_to_array( \IPS\Db::i()->select( 'sc_id', 'core_theme_settings_fields', array( array( 'sc_app=?', $this->directory ) ) ) );
@@ -4059,9 +4183,10 @@ EOF;
/* Clear out data store for updated values */
unset( \IPS\Data\Store::i()->modules );
unset( \IPS\Data\Store::i()->applications );
unset( \IPS\Data\Store::i()->settings );
unset( \IPS\Data\Store::i()->widgets );
unset( \IPS\Data\Store::i()->furl_configuration );
\IPS\Settings::i()->clearCache();
}
/**