Version 4.7.23

This commit is contained in:
Neo committed 2025-12-19 16:21:27 -08:00
1 parent 7124a02564
commit 25ddeb65d6
1791 files changed
+76990 -44452

No files matched your search

+31 -15
View File
@@ -10,6 +10,9 @@
namespace IPS\core\tasks;
use IPS\Application;
use function implode;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
@@ -31,7 +34,13 @@ class _cleanup extends \IPS\Task
public function execute()
{
/* Delete old failed guest logins for accounts that do not exist */
unset( \IPS\Data\Store::i()->failedLogins );
if( $maxCutoff = (int) \IPS\Settings::i()->bruteforce_global_period )
{
\IPS\Db::i()->delete( 'core_login_failures', [
'login_date<? AND login_ip_address IS NOT NULL AND login_email IS NOT NULL',
( new \IPS\DateTime )->sub( new \DateInterval( 'PT' . $maxCutoff . 'M' ) )->sub( new \DateInterval( 'PT24H' ) )->getTimestamp()
] );
}
/* Delete old password / security answer reset requests */
\IPS\Db::i()->delete( 'core_validating', array( '( lost_pass=1 OR forgot_security=1 ) AND email_sent < ?', \IPS\DateTime::create()->sub( new \DateInterval( 'P1D' ) )->getTimestamp() ) );
@@ -146,20 +155,38 @@ class _cleanup extends \IPS\Task
{
\IPS\Db::i()->delete( 'core_statistics', array( "type IN('emails_sent','email_views','email_clicks') AND time < ?", \IPS\DateTime::create()->sub( new \DateInterval( 'P' . \IPS\Settings::i()->prune_log_emailstats . 'D' ) )->getTimestamp() ) );
}
/* ...and guest details stored with reports */
if( (int) \IPS\Settings::i()->report_guest_details_store_days > 0 )
{
\IPS\Db::i()->update( 'core_rc_reports', [ 'guest_name' => null, 'guest_email' => null ], array( 'date_reported < ?', \IPS\DateTime::create()->sub( new \DateInterval( 'P' . \IPS\Settings::i()->report_guest_details_store_days . 'D' ) )->getTimestamp() ) );
}
/* ...and geoip cache */
\IPS\Db::i()->delete( 'core_geoip_cache', array( 'date < ?', \IPS\DateTime::create()->sub( new \DateInterval( 'P7D' ) )->getTimestamp() ) );
/* ...and API logs */
$successfulCodes = [ 200, 201, 202, 203, 204, 205, 206, 207, 208, 226 ];
if( \IPS\Settings::i()->api_log_prune )
{
\IPS\Db::i()->delete( 'core_api_logs', array( 'date < ?', \IPS\DateTime::create()->sub( new \DateInterval( 'P' . \IPS\Settings::i()->api_log_prune . 'D' ) )->getTimestamp() ) );
\IPS\Db::i()->delete( 'core_api_logs', array( 'date < ? and response_code IN(' . implode( ',', $successfulCodes ) . ')', \IPS\DateTime::create()->sub( new \DateInterval( 'P' . \IPS\Settings::i()->api_log_prune . 'D' ) )->getTimestamp() ) );
}
/* ...and member history */
if( \IPS\Settings::i()->api_log_prune_failures )
{
\IPS\Db::i()->delete( 'core_api_logs', array( 'date < ? and response_code NOT IN(' . implode( ',', $successfulCodes ) . ')', \IPS\DateTime::create()->sub( new \DateInterval( 'P' . \IPS\Settings::i()->api_log_prune_failures . 'D' ) )->getTimestamp() ) );
}
/* ...and non-nexus member history */
if( \IPS\Settings::i()->prune_member_history AND !\in_array( 'core_member_history', $currentlyPruning ) )
{
\IPS\Db::i()->delete( 'core_member_history', array( 'log_date < ?', \IPS\DateTime::create()->sub( new \DateInterval( 'P' . \IPS\Settings::i()->prune_member_history . 'D' ) )->getTimestamp() ) );
\IPS\Db::i()->delete( 'core_member_history', array( 'log_date < ? and log_app != ?', \IPS\DateTime::create()->sub( new \DateInterval( 'P' . \IPS\Settings::i()->prune_member_history . 'D' ) )->getTimestamp(), 'nexus' ) );
}
/* ...and Nexus member history */
if( Application::appIsEnabled('nexus') and \IPS\Settings::i()->nexus_prune_history AND !\in_array( 'core_member_history', $currentlyPruning ) )
{
\IPS\Db::i()->delete( 'core_member_history', array( 'log_date < ? and log_app = ?', \IPS\DateTime::create()->sub( new \DateInterval( 'P' . \IPS\Settings::i()->nexus_prune_history . 'D' ) )->getTimestamp(), 'nexus' ) );
}
/* ...and promote response logs */
@@ -200,15 +227,6 @@ class _cleanup extends \IPS\Task
}
}
/* Delete old follows */
if ( \IPS\Settings::i()->prune_follows AND !\in_array( 'core_follow', $currentlyPruning ) )
{
\IPS\Db::i()->delete( 'core_follow', array( 'follow_app!=? AND follow_area!=? AND follow_member_id IN(?)', 'core', 'member', \IPS\Db::i()->select( 'member_id', 'core_members', array( 'last_activity < ?', \IPS\DateTime::create()->sub( new \DateInterval( 'P' . \IPS\Settings::i()->prune_follows . 'D' ) )->getTimestamp() ) ) ) );
/* And clear the cache so it can rebuild */
\IPS\Db::i()->delete( 'core_follow_count_cache' );
}
/* Delete old item markers */
if ( \IPS\Settings::i()->prune_item_markers AND !\in_array( 'core_item_markers', $currentlyPruning ) )
{
@@ -227,8 +245,6 @@ class _cleanup extends \IPS\Task
\IPS\Db::i()->delete( 'core_members_known_devices', array( 'last_seen < ?', \IPS\DateTime::create()->sub( new \DateInterval( 'P' . \IPS\Settings::i()->prune_known_devices . 'D' ) )->getTimestamp() ) );
}
\IPS\Db::i()->delete( 'core_item_member_map', array( 'map_latest_date < ?', \IPS\DateTime::create()->sub( new \DateInterval( 'P9M' ) )->getTimestamp() ) );
/* Delete moved links */
if ( \IPS\Settings::i()->topic_redirect_prune )
{
+113
View File
@@ -0,0 +1,113 @@
<?php
/**
* @brief nodeRecounts Task
* @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 13 Sep 2024
*/
namespace IPS\core\tasks;
use IPS\Data\Store;
use IPS\Node\Model;
use IPS\Redis;
use function array_key_exists;
use function is_array;
/* 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;
}
/**
* nodeRecounts Task
*/
class _nodeRecounts extends \IPS\Task
{
/**
* Execute
*
* If ran successfully, should return anything worth logging. Only log something
* worth mentioning (don't log "task ran successfully"). Return NULL (actual NULL, not '' or 0) to not log (which will be most cases).
* If an error occurs which means the task could not finish running, throw an \IPS\Task\Exception - do not log an error as a normal log.
* Tasks should execute within the time of a normal HTTP request.
*
* @return mixed Message to log or NULL
* @throws \IPS\Task\Exception
*/
public function execute(): mixed
{
$allKeys = [];
try
{
if( ! Redis::isEnabled() )
{
throw new \BadMethodCallException;
}
$allKeys = Redis::i()->hGetAll('nodeSyncTimes');
}
catch( \BadMethodCallException | \RedisException )
{
try
{
$key = 'nodeSyncTimes';
$allKeys = Store::i()->$key;
}
catch( \OutOfRangeException ){}
}
if ( ! is_array( $allKeys ) or ! count( $allKeys ))
{
return null;
}
/* Now sort the keys by value, which is the timestamp */
asort($allKeys);
/* Now get the top 50 keys, which will the oldest timestamps */
$allKeys = array_slice( $allKeys, 0, 50 );
foreach( $allKeys as $classAndId => $time )
{
$classAndIdArray = explode('_', $classAndId);
$class = $classAndIdArray[0];
$id = $classAndIdArray[1];
try
{
/* @var $class Model */
$node = $class::load($id);
/* Recount items, comments, reviews and the last comment data */
$node->runScheduledRebuild();
}
catch( \Exception $e )
{
continue;
}
/* Now remove the key */
$node->clearUpdateTime();
}
return null;
}
/**
* Cleanup
*
* If your task takes longer than 15 minutes to run, this method
* will be called before execute(). Use it to clean up anything which
* may not have been done
*
* @return void
*/
public function cleanup()
{
}
}
@@ -88,7 +88,7 @@ class _postBeforeRegisterFollowup extends \IPS\Task
{
$language = \IPS\Lang::load( \IPS\Lang::defaultLanguage() );
}
$email = \IPS\Email::buildFromTemplate( 'core', 'postBeforeRegisterFollowup', array( $content, $userData['secret'] ), \IPS\Email::TYPE_TRANSACTIONAL );
$email = \IPS\Email::buildFromTemplate( 'core', 'postBeforeRegisterFollowup', array( $content, $userData['secret'] ), \IPS\Email::TYPE_BULK );
$email->language = $language;
$email->setUnsubscribe( 'core', 'unsubscribeNotNeeded' );
$email->send( $userData['email'] );
+3 -3
View File
@@ -18,7 +18,7 @@ if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
}
/**
* s3_delete Task
* s3Delete Task
*/
class _s3Delete extends \IPS\Task
{
@@ -45,7 +45,7 @@ class _s3Delete extends \IPS\Task
{
/* Class is not Amazon, so just stop here and truncate the table as there's nothing we can really do now. */
\IPS\Db::i()->delete( 'core_s3_deletions' );
\IPS\Db::i()->update( 'core_tasks', array( 'enabled' => 0 ), array( '`key`=?', 's3_delete' ) );
\IPS\Db::i()->update( 'core_tasks', array( 'enabled' => 0 ), array( '`key`=?', 's3Delete' ) );
return NULL;
}
@@ -94,7 +94,7 @@ class _s3Delete extends \IPS\Task
catch( \UnderflowException $ex )
{
/* Nothing to do, so switch off the task */
\IPS\Db::i()->update( 'core_tasks', array( 'enabled' => 0 ), array( '`key`=?', 's3_delete' ) );
\IPS\Db::i()->update( 'core_tasks', array( 'enabled' => 0 ), array( '`key`=?', 's3Delete' ) );
}
return NULL;
+4 -21
View File
@@ -40,29 +40,12 @@ class _unlockmembers extends \IPS\Task
return NULL;
}
/* Clear out old data */
\IPS\Db::i()->delete( 'core_login_failures', [ 'login_date<? AND login_ip_address IS NOT NULL AND login_member_id IS NOT NULL', ( new \IPS\DateTime() )->sub( new \DateInterval( 'PT' . \IPS\Settings::i()->ipb_bruteforce_period . 'M' ) )->getTimestamp() ] );
foreach ( new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', 'core_members', array( 'failed_login_count >= ?', \IPS\Settings::i()->ipb_bruteforce_attempts ) ), 'IPS\Member') AS $member )
{
$failedLogins = $member->failed_logins;
if ( \is_array( $failedLogins ) )
{
foreach ( $failedLogins as $ipAddress => $times )
{
foreach ( $times as $k => $v )
{
if ( $v < \IPS\DateTime::create()->sub( new \DateInterval( 'PT' . \IPS\Settings::i()->ipb_bruteforce_period . 'M' ) )->getTimestamp() )
{
unset( $failedLogins[ $ipAddress ][ $k ] );
}
}
}
$member->failed_logins = $failedLogins;
}
else
{
$member->failed_logins = array();
}
$member->save();
$member->recountFailedLogins();
}
return NULL;
+7 -81
View File
@@ -26,11 +26,6 @@ class _updatecheck extends \IPS\Task
* @brief Type to send to update server
*/
public $type = 'task';
/**
* @brief Cache marketplace controller
*/
protected $_marketplaceController = null;
/**
* Execute
@@ -145,19 +140,18 @@ class _updatecheck extends \IPS\Task
catch( \RuntimeException $e ){ }
$updateChecked = [];
$marketplaceUpdates = [];
$fails = [];
$fiveMinutesAgo = time() - 300;
$this->runUntilTimeout( function() use ( &$updateChecked, &$marketplaceUpdates, &$fails, $version, $fiveMinutesAgo ) {
$this->runUntilTimeout( function() use ( &$updateChecked, &$fails, $version, $fiveMinutesAgo ) {
try
{
$row = \IPS\Db::i()->union(
array(
\IPS\Db::i()->select( "'core_applications' AS `table`, app_directory AS `id`, app_update_check AS `url`, app_update_last_check AS `last`, app_long_version AS `current`, app_marketplace_id as `marketplace_id`", 'core_applications', "app_update_last_check<{$fiveMinutesAgo} AND ( ( app_update_check<>'' AND app_update_check IS NOT NULL ) OR app_marketplace_id IS NOT NULL )" ),
\IPS\Db::i()->select( "'core_plugins' AS `table`, plugin_id AS id, plugin_update_check as url, plugin_update_check_last AS last, plugin_version_long AS `current`, plugin_marketplace_id as `marketplace_id`", 'core_plugins', "plugin_update_check_last<{$fiveMinutesAgo} AND ( (plugin_update_check<>'' AND plugin_update_check IS NOT NULL ) OR plugin_marketplace_id IS NOT NULL )" ),
\IPS\Db::i()->select( "'core_themes' AS `table`, set_id AS `id`, set_update_check AS `url`, set_update_last_check AS `last`, set_long_version AS `current`, set_marketplace_id as `marketplace_id`", 'core_themes', "set_update_last_check<{$fiveMinutesAgo} AND ( (set_update_check<>'' AND set_update_check IS NOT NULL ) OR set_marketplace_id IS NOT NULL )" ),
\IPS\Db::i()->select( "'core_sys_lang' AS `table`, lang_id AS `id`, `lang_update_url` AS `url`, `lang_update_check` AS `last`, `lang_version_long` AS `current`, `lang_marketplace_id` as `marketplace_id`", "core_sys_lang", "lang_update_check<{$fiveMinutesAgo} AND ( (lang_update_url<>'' AND lang_update_url IS NOT NULL ) OR lang_marketplace_id IS NOT NULL )" )
\IPS\Db::i()->select( "'core_applications' AS `table`, app_directory AS `id`, app_update_check AS `url`, app_update_last_check AS `last`, app_long_version AS `current`", 'core_applications', "app_update_last_check<{$fiveMinutesAgo} AND ( app_update_check<>'' AND app_update_check IS NOT NULL )" ),
\IPS\Db::i()->select( "'core_plugins' AS `table`, plugin_id AS id, plugin_update_check as url, plugin_update_check_last AS last, plugin_version_long AS `current`", 'core_plugins', "plugin_update_check_last<{$fiveMinutesAgo} AND ( plugin_update_check<>'' AND plugin_update_check IS NOT NULL )" ),
\IPS\Db::i()->select( "'core_themes' AS `table`, set_id AS `id`, set_update_check AS `url`, set_update_last_check AS `last`, set_long_version AS `current`", 'core_themes', "set_update_last_check<{$fiveMinutesAgo} AND ( set_update_check<>'' AND set_update_check IS NOT NULL )" ),
\IPS\Db::i()->select( "'core_sys_lang' AS `table`, lang_id AS `id`, `lang_update_url` AS `url`, `lang_update_check` AS `last`, `lang_version_long` AS `current`", "core_sys_lang", "lang_update_check<{$fiveMinutesAgo} AND ( lang_update_url<>'' AND lang_update_url IS NOT NULL )" )
),
'last ASC',
1
@@ -216,32 +210,8 @@ class _updatecheck extends \IPS\Task
try
{
/* Query the marketplace */
if( $row['marketplace_id'] )
{
$marketplaceUpdates[ $row['marketplace_id'] ] = [
'table' => $row['table'],
'current' => $row['current'],
'dataColumn' => $dataColumn,
'timeColumn' => $timeColumn,
'idColumn' => $idColumn,
'id' => $row['id'],
'marketplace_id'=> $row['marketplace_id']
];
/* Update the timestamp so we can deal with other resources */
\IPS\Db::i()->update( $row['table'], array(
$timeColumn => time()
), array( "{$idColumn}=?", $row['id'] ) );
return TRUE;
}
/* Query the applications update URL */
else
{
$url = \IPS\Http\Url::external( $row['url'] )->setQueryString( array( 'version' => $row['current'], 'ips_version' => $version ) );
$response = $url->request()->get()->decodeJson();
}
$url = \IPS\Http\Url::external( $row['url'] )->setQueryString( array( 'version' => $row['current'], 'ips_version' => $version ) );
$response = $url->request()->get()->decodeJson();
/* Unset the object so it isn't present for next update check. */
unset( $object );
@@ -278,50 +248,6 @@ class _updatecheck extends \IPS\Task
return TRUE;
});
/* Check Marketplace updates in bulk */
if( \count( $marketplaceUpdates ) )
{
try
{
$marketplaceController = new \IPS\core\modules\admin\marketplace\marketplace;
$response = $marketplaceController->_updateCheck( array_column( $marketplaceUpdates, 'current', 'marketplace_id' ) );
foreach( $marketplaceUpdates as $resourceId => $resourceData )
{
$releaseData = $response[ $resourceId ] ?? NULL;
if( $releaseData !== NULL )
{
if( $resourceData['table'] == 'core_applications' )
{
$object = \IPS\Application::load( $resourceData['id'] );
}
elseif( $resourceData['table'] == 'core_plugins' )
{
$object = \IPS\Plugin::load( $resourceData['id'] );
}
/* Use a local update URL */
$releaseData['updateurl'] = (string) \IPS\Http\Url::internal("app=core&module=marketplace&controller=marketplace&do=viewFile&id={$resourceId}", 'admin' );
if( isset( $object ) AND isset( $releaseData['disableresource'] ) AND $releaseData['disableresource'] === TRUE )
{
$object->_enabled = FALSE;
$object->save();
}
}
\IPS\Db::i()->update( $resourceData['table'], array(
$resourceData['dataColumn'] => json_encode( $releaseData ),
$resourceData['timeColumn'] => time()
), array( "{$resourceData['idColumn']}=?", $resourceData['id'] ) );
}
}
catch( \RuntimeException | \UnexpectedValueException $e )
{
$fails[] = '';
}
}
/* Reset Menu Cache */
foreach( $updateChecked as $type )
{
+2 -1
View File
@@ -40,6 +40,7 @@ class _viewupdates extends \IPS\Task
try
{
/* @note SELECT_FROM_WRITE_SERVER added in 72724569ca77f6686f6be0861ce673f793a862e8 to avoid a race condition that caused high view counts */
$database = \IPS\Db::i()->select( 'classname, id, count(*) AS count', 'core_view_updates', NULL, NULL, 20, array( 'classname', 'id' ), NULL, \IPS\Db::SELECT_FROM_WRITE_SERVER );
/* Database results first */
@@ -68,7 +69,7 @@ class _viewupdates extends \IPS\Task
{
$redis = \IPS\Redis::i()->zRevRangeByScore( 'topic_views', '+inf', '-inf', array('withscores' => TRUE, 'limit' => array( 0, 20 ) ) );
if( \is_array( $redis ) )
if( \is_array( $redis ) AND \count( $redis ) )
{
foreach ( $redis as $data => $count )
{
+1
View File
@@ -43,6 +43,7 @@ class _webhooks extends \IPS\Task
/* Try and get a queue item */
try
{
/* @note SELECT_FROM_WRITE_SERVER race condition in tasks */
$fire = \IPS\Db::i()->select( '*', 'core_api_webhook_fires', array( "status='pending'" ), '`time` ASC', 1, NULL, NULL, \IPS\Db::SELECT_FROM_WRITE_SERVER )->first();
try
@@ -1,18 +1,16 @@
<?php
/**
* @brief clearUnconfirmed Task
* @brief weeklyCleanup Task
* @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 21 Aug 2023
{subpackage}
* @since 27 Nov 2023
*/
namespace IPS\core\tasks;
use IPS\DateTime;
use IPS\Db;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
@@ -21,9 +19,9 @@ if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
}
/**
* clearUnconfirmed Task
* weeklyCleanup Task
*/
class _clearUnconfirmed extends \IPS\Task
class _weeklyCleanup extends \IPS\Task
{
/**
* Execute
@@ -38,9 +36,24 @@ class _clearUnconfirmed extends \IPS\Task
*/
public function execute()
{
Db::i()->delete( 'core_hive_subscribers', [ 'subscribe_confirmed=? AND subscribe_date<=?', 0, DateTime::create()->sub( new \DateInterval( 'P1M' ) )->getTimestamp() ] );
/* If we are currently pruning any large tables via a bg task, find out so we don't try to prune them normally here as well. The bg task should finish first. */
$currentlyPruning = array();
return NULL;
foreach( \IPS\Db::i()->select( '*', 'core_queue', array( '`key`=?', 'PruneLargeTable' ) ) as $pruneTask )
{
$data = json_decode( $pruneTask['data'], true );
$currentlyPruning[] = $data['table'];
}
/* Delete old follows */
if ( \IPS\Settings::i()->prune_follows AND !\in_array( 'core_follow', $currentlyPruning ) )
{
\IPS\Db::i()->delete( 'core_follow', array( 'follow_app!=? AND follow_area!=? AND follow_member_id IN(?)', 'core', 'member', \IPS\Db::i()->select( 'member_id', 'core_members', array( 'last_activity < ?', \IPS\DateTime::create()->sub( new \DateInterval( 'P' . \IPS\Settings::i()->prune_follows . 'D' ) )->getTimestamp() ) ) ) );
/* And clear the cache so it can rebuild */
\IPS\Db::i()->delete( 'core_follow_count_cache' );
}
}
/**