Version 4.7.6

This commit is contained in:
Neo committed 2025-12-19 16:05:19 -08:00
1 parent 8ba7d43898
commit 7b7404e741
1085 files changed
+30590 -7837

No files matched your search

@@ -182,7 +182,7 @@ class _ConfigurationError extends \IPS\core\AdminNotification
else
{
\IPS\core\AdminNotification::remove( 'core', 'ConfigurationError', 'failedMail' );
\IPS\Db::i()->update( 'core_mail_error_logs', array( 'mlog_notification_sent' => TRUE ) );
\IPS\Db::i()->update( 'core_mail_error_logs', [ 'mlog_notification_sent' => TRUE ], [ 'mlog_notification_sent=?', 0 ] );
}
/* Username logins enabled */
@@ -195,6 +195,7 @@ class _ConfigurationError extends \IPS\core\AdminNotification
{
\IPS\core\AdminNotification::remove( 'core', 'ConfigurationError', 'usernameLoginEnabled' );
}
}
/**
@@ -628,7 +629,13 @@ class _ConfigurationError extends \IPS\core\AdminNotification
return static::STYLE_INFORMATION;
}
}
if ( mb_substr( $this->extra, 0, 12 ) === 'supportAdmin' or $this->extra === 'recommendations' or $this->extra === 'origTables' or $this->extra === 'marketplaceSetup' )
if( $this->extra === 'recommendations' )
{
return static::STYLE_ERROR;
}
if ( mb_substr( $this->extra, 0, 12 ) === 'supportAdmin' or $this->extra === 'origTables' or $this->extra === 'marketplaceSetup' )
{
return static::STYLE_INFORMATION;
}
@@ -92,7 +92,7 @@ class _License extends \IPS\core\AdminNotification
return \IPS\Member::loggedIn()->language()->addToStack('license_error');
case 'expireSoon':
$licenseKeyData = \IPS\IPS::licenseKey();
return \IPS\Member::loggedIn()->language()->addToStack( 'license_renewal_soon', FALSE, array( 'pluralize' => array( \intval( \IPS\DateTime::create()->diff( \IPS\DateTime::ts( strtotime( $licenseKeyData['expires'] ) ) )->format('%r%a') ) ) ) );
return \IPS\Member::loggedIn()->language()->addToStack( 'license_renewal_soon', FALSE, array( 'pluralize' => array( \intval( \IPS\DateTime::create()->diff( \IPS\DateTime::ts( strtotime( $licenseKeyData['expires'] ), TRUE ) )->format('%r%a') ) ) ) );
case 'expired':
return \IPS\Member::loggedIn()->language()->addToStack('license_expired');
}
@@ -0,0 +1,167 @@
<?php
/**
* @brief ACP Notification Extension
* @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/
* @since 25 Aug 2022
*/
namespace IPS\core\extensions\core\AdminNotifications;
/* 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;
}
/**
* ACP Notification Extension
*/
class _ManualInterventionMessage extends \IPS\core\AdminNotification
{
/**
* @brief Identifier for what to group this notification type with on the settings form
*/
public static $group = 'system';
/**
* @brief Priority 1-5 (1 being highest) for this group compared to others
*/
public static $groupPriority = 2;
/**
* @brief Priority 1-5 (1 being highest) for this notification type compared to others in the same group
*/
public static $itemPriority = 1;
/**
* Title for settings
*
* @return string
*/
public static function settingsTitle()
{
return 'acp_notification_ManualInterventionMessage';
}
/**
* Can a member access this type of notification?
*
* @param \IPS\Member $member The member
* @return bool
*/
public static function permissionCheck( \IPS\Member $member )
{
return \IPS\Member::loggedIn()->hasAcpRestriction( 'core', 'applications', 'app_manage' ) OR \IPS\Member::loggedIn()->hasAcpRestriction( 'core', 'applications', 'plugins_view' );
}
/**
* Is this type of notification ever optional (controls if it will be selectable as "viewable" in settings)
*
* @return string
*/
public static function mayBeOptional()
{
return FALSE;
}
/**
* Is this type of notification might recur (controls what options will be available for the email setting)
*
* @return bool
*/
public static function mayRecur()
{
return FALSE;
}
/**
* Notification Title (full HTML, must be escaped where necessary)
*
* @return string
*/
public function title()
{
return \IPS\Member::loggedIn()->language()->addToStack( 'method_check_fail' );
}
/**
* Notification Body (full HTML, must be escaped where necessary)
*
* @return string
*/
public function body()
{
return \IPS\Member::loggedIn()->language()->addToStack( 'advise_removal_of_php8_incompatible_code' );
}
/**
* Severity
*
* @return string
*/
public function severity()
{
return static::SEVERITY_HIGH;
}
/**
* Dismissible?
*
* @return string
*/
public function dismissible()
{
return static::DISMISSIBLE_UNTIL_RECUR;
}
/**
* Style
*
* @return bool
*/
public function style()
{
return $this->severity() === static::SEVERITY_HIGH ? static::STYLE_ERROR : static::STYLE_WARNING;
}
/**
* Quick link from popup menu
*
* @return \IPS\Http\Url
*/
public function link()
{
return \IPS\Http\Url::internal( 'app=core&module=support&controller=support&do=methodCheck' );
}
/**
* Delete
*
* @return void
*/
public function delete()
{
parent::delete();
}
/**
* Should this notification dismiss itself?
*
* @note This is checked every time the notification shows. Should be lightweight.
* @return bool
*/
public function selfDismiss()
{
if( \IPS\Db::i()->select( 'count(*)', 'core_applications', [ 'app_requires_manual_intervention=?', 1 ] )->first()
OR \IPS\Db::i()->select( 'count(*)', 'core_plugins', [ 'plugin_requires_manual_intervention=?', 1 ] )->first() )
{
return FALSE;
}
return TRUE;
}
}
@@ -64,7 +64,8 @@ class _GoogleMaps
{
$form = new \IPS\Helpers\Form( 'google_maps_enable_apis', 'continue' );
$form->addHeader('google_maps_choose_features');
$form->add( new \IPS\Helpers\Form\YesNo( 'googlemaps', isset( $data['googlemaps'] ) ? $data['googlemaps'] : \IPS\Settings::i()->googlemaps, FALSE, array( 'togglesOn' => array( 'googleApi_jsapi', 'googleApi_staticapi', 'googleApi_geocodeapi' ) ) ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'googlemaps', isset( $data['googlemaps'] ) ? $data['googlemaps'] : \IPS\Settings::i()->googlemaps, FALSE, array( 'togglesOn' => array( 'googleApi_jsapi', 'googleApi_staticapi', 'googleApi_geocodeapi', 'google_maps_enable_apis_google_maps_static_use_embed' ) ) ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'google_maps_static_use_embed', isset( $data['google_maps_static_use_embed'] ) ? $data['google_maps_static_use_embed'] : \IPS\Settings::i()->google_maps_static_use_embed, FALSE ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'googleplacesautocomplete', isset( $data['googleplacesautocomplete'] ) ? $data['googleplacesautocomplete'] : \IPS\Settings::i()->googleplacesautocomplete, FALSE, array( 'togglesOn' => array( 'googleApi_places' ) ) ) );
$form->addHeader('google_maps_enable_apis');
$form->addMessage('google_maps_create_project_message');
@@ -75,13 +76,13 @@ class _GoogleMaps
if ( $values = $form->values() )
{
if ( $values['googlemaps'] or $values['googleplacesautocomplete'] )
if ( $values['googlemaps'] or $values['googleplacesautocomplete'] or $values['google_maps_static_use_embed'] )
{
return $values;
}
else
{
\IPS\Settings::i()->changeValues( array( 'googlemaps' => 0, 'googleplacesautocomplete' => 0 ) );
\IPS\Settings::i()->changeValues( array( 'googlemaps' => 0, 'googleplacesautocomplete' => 0, 'google_maps_static_use_embed' => 0 ) );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal('app=core&module=applications&controller=enhancements'), 'saved' );
}
}
@@ -152,6 +153,7 @@ class _GoogleMaps
$form->saveAsSettings( array(
'googlemaps' => $data['googlemaps'],
'googleplacesautocomplete' => $data['googleplacesautocomplete'],
'google_maps_static_use_embed' => $data['google_maps_static_use_embed'],
'google_maps_api_key' => $values['google_maps_api_key'],
'google_maps_api_key_secret' => $data['googlemaps'] ? $values['google_maps_api_key_secret'] : ''
) );
@@ -1,157 +0,0 @@
<?php
/**
* @brief Community Enhancements: Viglink integration
* @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 20 June 2013
*/
namespace IPS\core\extensions\core\CommunityEnhancements;
/* 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;
}
/**
* Community Enhancements: Viglink
*/
class _Viglink
{
/**
* @brief IPS-provided enhancement?
*/
public $ips = FALSE;
/**
* @brief Enhancement is enabled?
*/
public $enabled = FALSE;
/**
* @brief Enhancement has configuration options?
*/
public $hasOptions = TRUE;
/**
* @brief Icon data
*/
public $icon = "viglink.png";
/**
* Constructor
*
* @return void
*/
public function __construct()
{
$this->enabled = ( \IPS\Settings::i()->viglink_enabled );
}
/**
* Edit
*
* @return void
*/
public function edit()
{
\IPS\Output::i()->sidebar['actions'] = array(
'help' => array(
'title' => 'learn_more',
'icon' => 'question-circle',
'link' => \IPS\Http\Url::ips( 'docs/viglink' ),
'target' => '_blank'
)
);
if ( \IPS\Settings::i()->viglink_api_key )
{
$form = new \IPS\Helpers\Form;
$form->add( new \IPS\Helpers\Form\YesNo( 'viglink_enabled', \IPS\Settings::i()->viglink_enabled, FALSE, array( 'togglesOn' => array( 'viglink_api_key', 'viglink_subid', 'viglink_groups', 'viglink_norewrite' ) ) ) );
if ( !\IPS\Settings::i()->viglink_subid )
{
$form->add( new \IPS\Helpers\Form\Text( 'viglink_api_key', \IPS\Settings::i()->viglink_api_key, FALSE, array(), NULL, NULL, NULL, 'viglink_api_key' ) );
}
$form->add( new \IPS\Helpers\Form\CheckboxSet( 'viglink_groups', ( \IPS\Settings::i()->viglink_groups == 'all' ) ? 'all' : explode( ',', \IPS\Settings::i()->viglink_groups ), FALSE, array( 'options' => \IPS\Member\Group::groups(), 'parse' => 'normal', 'multiple' => true, 'unlimited' => 'all', 'unlimitedLang' => 'all_groups', 'impliedUnlimited' => TRUE ), NULL, NULL, NULL, 'viglink_groups' ) );
$form->add( new \IPS\Helpers\Form\CheckboxSet( 'viglink_norewrite', explode( ',', \IPS\Settings::i()->viglink_norewrite ), FALSE, array( 'options' => \IPS\Member\Group::groups(), 'parse' => 'normal', 'multiple' => true ), NULL, NULL, NULL, 'viglink_norewrite' ) );
if ( $values = $form->values( TRUE ) )
{
if ( !$values['viglink_enabled'] )
{
$values['viglink_api_key'] = '';
}
$form->saveAsSettings( $values );
\IPS\Session::i()->log( 'acplog__enhancements_edited', array( 'enhancements__core_Viglink' => TRUE ) );
\IPS\Output::i()->inlineMessage = \IPS\Member::loggedIn()->language()->addToStack('saved');
}
\IPS\Output::i()->sidebar['actions']['signup'] = array(
'title' => 'viglink_account_type',
'icon' => 'external-link-square',
'link' => \IPS\Http\Url::ips('docs/viglink_account'),
'target' => '_blank'
);
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global' )->block( 'enhancements__core_Viglink', $form );
}
else
{
$form = new \IPS\Helpers\Form;
$form->add( new \IPS\Helpers\Form\Text( 'viglink_api_key', NULL, FALSE, array(), function( $val ){
if( !$val && \IPS\Request::i()->viglink_account_type == 'existing' )
{
throw new \InvalidArgumentException("required");
}
}, NULL, NULL, 'viglink_api_key' ) );
if ( $values = $form->values() )
{
$values['viglink_subid'] = '';
$values['viglink_enabled'] = TRUE;
unset( $values['viglink_account_type'] );
$form->saveAsSettings( $values );
\IPS\Session::i()->log( 'acplog__enhancements_edited', array( 'enhancements__core_Viglink' => TRUE ) );
\IPS\Output::i()->inlineMessage = \IPS\Member::loggedIn()->language()->addToStack('saved');
}
}
\IPS\Output::i()->output = $form;
}
/**
* Enable/Disable
*
* @param $enabled bool Enable/Disable
* @return void
* @throws \DomainException
*/
public function toggle( $enabled )
{
/* If we're disabling, just disable */
if( !$enabled )
{
\IPS\Settings::i()->changeValues( array( 'viglink_enabled' => 0 ) );
}
/* Otherwise if we already have an API key, just toggle on */
if( $enabled && \IPS\Settings::i()->viglink_api_key )
{
\IPS\Settings::i()->changeValues( array( 'viglink_enabled' => 1 ) );
}
/* Otherwise we need to let them enter an API key before we can enable. Throwing an exception causes you to be redirected to the settings page. */
else
{
throw new \DomainException;
}
}
}
@@ -26,21 +26,27 @@ class _Zapier
/**
* Get the permissions needed for the Zapier API key
*
* Since 4.7.1 we don't need to call any other methods while the upgrade, just adjust the permission here, everything else will be handled automatically.
*
* @return array
*/
public static function apiKeyPermissions()
{
$return = array(
'core/clubs/GETindex' => array( 'access' => TRUE ),
'core/clubs/GETitem' => array( 'access' => TRUE ),
'core/groups/GETindex' => array( 'access' => TRUE ),
'core/hello/GETindex' => array( 'access' => TRUE ),
'core/members/GETindex' => array( 'access' => TRUE ),
'core/members/GETitem' => array( 'access' => TRUE ),
'core/members/POSTindex' => array( 'access' => TRUE ),
'core/members/POSTitem' => array( 'access' => TRUE ),
'core/webhooks/POSTindex' => array( 'access' => TRUE ),
'core/webhooks/DELETEitem' => array( 'access' => TRUE ),
'core/clubs/GETindex' => array( 'access' => TRUE ),
'core/clubs/GETitem' => array( 'access' => TRUE ),
'core/groups/GETindex' => array( 'access' => TRUE ),
'core/hello/GETindex' => array( 'access' => TRUE ),
'core/members/GETindex' => array( 'access' => TRUE ),
'core/members/GETitem' => array( 'access' => TRUE ),
'core/members/POSTindex' => array( 'access' => TRUE ),
'core/members/POSTitem_secgroup' => array( 'access' => TRUE ),
'core/members/DELETEitem_secgroup' => array( 'access' => TRUE ),
'core/members/POSTitem' => array( 'access' => TRUE ),
'core/webhooks/POSTindex' => array( 'access' => TRUE ),
'core/webhooks/DELETEitem' => array( 'access' => TRUE ),
'core/promotions/GETindex' => array( 'access' => TRUE ),
'core/promotions/GETitem' => array( 'access' => TRUE ),
);
if ( \IPS\Application::appIsEnabled('forums') )
@@ -309,4 +315,32 @@ class _Zapier
throw new \DomainException( 'zapier_error_api' );
}
}
/**
* Give the Zapier REST Key all required permissions.
*
* @return void
*/
public static function rebuildRESTApiPermissions()
{
/* Rebuild Zapier REST API Key Permissions */
if( \IPS\Settings::i()->zapier_api_key )
{
try
{
$apiKey = \IPS\Api\Key::load( \IPS\Settings::i()->zapier_api_key );
$correctPermissions = json_encode( static::apiKeyPermissions() );
if ( $apiKey->permissions != $correctPermissions )
{
$apiKey->permissions = $correctPermissions;
$apiKey->save();
}
}
catch ( \OutOfRangeException $e )
{
}
}
}
}
@@ -28,6 +28,11 @@ class _Clubs
*/
public $classes = array();
/**
* @brief Can be shown in similar content
*/
public $similarContent = FALSE;
/**
* Constructor
*
@@ -28,6 +28,11 @@ class _Status
*/
public $classes = array();
/**
* @brief Can be shown in similar content
*/
public $similarContent = FALSE;
/**
* Constructor
*
@@ -136,22 +136,24 @@ class _Attachment
if ( isset( static::$loadedExtensions[ $map['location_key'] ] ) )
{
try
if( method_exists( static::$loadedExtensions[$map['location_key']], 'attachmentLookup'))
{
$url = static::$loadedExtensions[ $map['location_key'] ]->attachmentLookup( $map['id1'], $map['id2'], $map['id3'] );
try {
$url = static::$loadedExtensions[$map['location_key']]->attachmentLookup($map['id1'], $map['id2'], $map['id3']);
/* Test url() method to prevent BadMethodCallException from the template below - an attachment may be
located within a Node class that doesn't support urls, such as CMS Blocks. */
/* Test url() method to prevent BadMethodCallException from the template below - an attachment may be
located within a Node class that doesn't support urls, such as CMS Blocks. */
if( $url instanceof \IPS\Content or $url instanceof \IPS\Node\Model )
{
$url->url();
if ($url instanceof \IPS\Content or $url instanceof \IPS\Node\Model){
$url->url();
}
static::$locations[$attachId][] = $url;
} catch (\LogicException $e) {
} catch (\BadMethodCallException $e) {
}
static::$locations[ $attachId ][] = $url;
}
catch ( \LogicException $e ) { }
catch ( \BadMethodCallException $e ){ }
}
}
}
@@ -71,50 +71,60 @@ class _Achievements
/* If all *available* options are selected, we want to choose 'all' for consistency on create vs edit */
$_options = array_keys( $ranks['options'] );
return array(
$return = array(
new \IPS\Helpers\Form\Custom( 'mf_points', array( 0 => isset( $criteria['achievement_points_operator'] ) ? $criteria['achievement_points_operator'] : NULL, 1 => isset( $criteria['achievement_points_value'] ) ? $criteria['achievement_points_value'] : NULL ), FALSE, array(
'getHtml' => function( $element )
{
return \IPS\Theme::i()->getTemplate( 'forms', 'core' )->select( "{$element->name}[0]", $element->value[0], $element->required, array(
'any' => \IPS\Member::loggedIn()->language()->addToStack('any'),
'gt' => \IPS\Member::loggedIn()->language()->addToStack('gt'),
'lt' => \IPS\Member::loggedIn()->language()->addToStack('lt'),
'eq' => \IPS\Member::loggedIn()->language()->addToStack('exactly'),
),
FALSE,
NULL,
FALSE,
array(
'any' => array(),
'gt' => array( 'elNumber_' . $element->name . '-qty' ),
'lt' => array( 'elNumber_' . $element->name . '-qty' ),
'eq' => array( 'elNumber_' . $element->name . '-qty' ),
) )
. ' '
. \IPS\Theme::i()->getTemplate( 'forms', 'core', 'global' )->number( "{$element->name}[1]", $element->value[1], $element->required, NULL, FALSE, NULL, NULL, NULL, 0, NULL, FALSE, NULL, array(), array(), array(), $element->name . '-qty' );
'any' => \IPS\Member::loggedIn()->language()->addToStack('any'),
'gt' => \IPS\Member::loggedIn()->language()->addToStack('gt'),
'lt' => \IPS\Member::loggedIn()->language()->addToStack('lt'),
'eq' => \IPS\Member::loggedIn()->language()->addToStack('exactly'),
),
FALSE,
NULL,
FALSE,
array(
'any' => array(),
'gt' => array( 'elNumber_' . $element->name . '-qty' ),
'lt' => array( 'elNumber_' . $element->name . '-qty' ),
'eq' => array( 'elNumber_' . $element->name . '-qty' ),
) )
. ' '
. \IPS\Theme::i()->getTemplate( 'forms', 'core', 'global' )->number( "{$element->name}[1]", $element->value[1], $element->required, NULL, FALSE, NULL, NULL, NULL, 0, NULL, FALSE, NULL, array(), array(), array(), $element->name . '-qty' );
}
), NULL, NULL, NULL, 'mp_points' ),
new \IPS\Helpers\Form\YesNo( 'mf_badges_choose', ( isset( $criteria['achievement_badges'] ) AND $criteria['achievement_badges'] != 'ignore' ), FALSE, array(
), NULL, NULL, NULL, 'mp_points' )
);
if( \count( $badges['options'] ) )
{
$return[] = new \IPS\Helpers\Form\YesNo( 'mf_badges_choose', ( !empty( $criteria['achievement_badges'] ) AND $criteria['achievement_badges'] !== 'ignore' ), FALSE, array(
'togglesOn' => [ 'mf_badges' ]
), NULL, NULL, NULL, 'mf_badges_choose' ),
new \IPS\Helpers\Form\CheckboxSet( 'mf_badges', ( isset( $criteria['achievement_badges'] ) AND $criteria['achievement_badges'] != 'ignore' ) ? explode( ',', $criteria['achievement_badges'] ) : [], FALSE, array(
), NULL, NULL, NULL, 'mf_badges_choose' );
$return[] = new \IPS\Helpers\Form\CheckboxSet( 'mf_badges', ( isset( $criteria['achievement_badges'] ) AND $criteria['achievement_badges'] != 'ignore' ) ? ( $criteria['achievement_badges'] === 'all' ? 'all' : explode( ',', $criteria['achievement_badges'] ) ) : [], FALSE, array(
'options' => $badges['options'],
'multiple' => TRUE,
'unlimited' => 'all',
'unlimitedLang' => 'mf_achievements_all_badges',
'impliedUnlimited' => TRUE
), NULL, NULL, NULL, 'mf_badges' ),
new \IPS\Helpers\Form\YesNo( 'mf_ranks_choose', ( isset( $criteria['achievement_ranks'] ) AND $criteria['achievement_ranks'] != 'ignore' ), FALSE, array(
), NULL, NULL, NULL, 'mf_badges' );
}
if( \count( $ranks['options'] ) )
{
$return[] = new \IPS\Helpers\Form\YesNo( 'mf_ranks_choose', ( !empty( $criteria['achievement_ranks'] ) AND $criteria['achievement_ranks'] !== 'ignore' ), FALSE, array(
'togglesOn' => [ 'mf_ranks' ]
), NULL, NULL, NULL, 'mf_ranks_choose' ),
new \IPS\Helpers\Form\CheckboxSet( 'mf_ranks', ( isset( $criteria['achievement_ranks'] ) AND $criteria['achievement_ranks'] != 'ignore' ) ? explode( ',', $criteria['achievement_ranks'] ) : [], FALSE, array(
), NULL, NULL, NULL, 'mf_ranks_choose' );
$return[] = new \IPS\Helpers\Form\CheckboxSet( 'mf_ranks', ( isset( $criteria['achievement_ranks'] ) AND $criteria['achievement_ranks'] !== 'ignore' ) ? ( $criteria['achievement_ranks'] === 'all' ? 'all' : explode( ',', $criteria['achievement_ranks'] ) ) : [], FALSE, array(
'options' => $ranks['options'],
'multiple' => TRUE,
'unlimited' => 'all',
'unlimitedLang' => 'mf_achievements_all_ranks',
'impliedUnlimited' => TRUE
), NULL, NULL, NULL, 'mf_ranks' )
);
), NULL, NULL, NULL, 'mf_ranks' );
}
return $return;
}
/**
@@ -129,8 +139,8 @@ class _Achievements
return array(
'achievement_points_operator' => $post['mf_points'][0],
'achievement_points_value' => $post['mf_points'][1],
'achievement_badges' => ( $post['mf_badges_choose'] ) ? implode( ',', $post['mf_badges'] ) : 'ignore',
'achievement_ranks' => ( $post['mf_ranks_choose'] ) ? implode( ',', $post['mf_ranks'] ) : 'ignore'
'achievement_badges' => ( !empty( $post['mf_badges_choose'] ) ) ? ( $post['mf_badges'] == 'all' ? 'all' : implode( ',', $post['mf_badges'] ) ) : 'ignore',
'achievement_ranks' => ( !empty( $post['mf_ranks_choose'] ) ) ? ( $post['mf_ranks'] == 'all' ? 'all' : implode( ',', $post['mf_ranks'] ) ) : 'ignore',
);
}
@@ -161,7 +171,16 @@ class _Achievements
if ( $data['achievement_badges'] != 'ignore' )
{
$badgeSelect = \IPS\Db::i()->select( 'member', 'core_member_badges', \IPS\Db::i()->in( 'badge', explode( ',', $data['achievement_badges'] ) ), NULL, NULL, NULL, NULL, \IPS\Db::SELECT_DISTINCT );
if( $data['achievement_ranks'] == 'all' )
{
$badgeWhere = NULL;
}
else
{
$badgeWhere = \IPS\Db::i()->in( 'badge', explode( ',', $data['achievement_badges'] ) );
}
$badgeSelect = \IPS\Db::i()->select( 'member', 'core_member_badges', $badgeWhere, NULL, NULL, NULL, NULL, \IPS\Db::SELECT_DISTINCT );
$where[] = 'core_members.member_id IN(' . $badgeSelect . ')';
}
@@ -170,7 +189,16 @@ class _Achievements
$ranks = \IPS\core\Achievements\Rank::getStore();
$rankWhere = [];
foreach( explode( ',', $data['achievement_ranks'] ) as $val )
if( $data['achievement_ranks'] == 'all' )
{
$rankIds = array_keys( $ranks );
}
else
{
$rankIds = explode( ',', $data['achievement_ranks'] );
}
foreach( $rankIds as $val )
{
$minPoints = 0;
$maxPoints = 0;
@@ -263,7 +291,16 @@ class _Achievements
$ranks = \IPS\core\Achievements\Rank::getStore();
$pass = FALSE;
foreach( explode( ',', $filters['achievement_ranks'] ) as $val )
if( $filters['achievement_ranks'] == 'all' )
{
$rankIds = array_keys( $ranks );
}
else
{
$rankIds = explode( ',', $filters['achievement_ranks'] );
}
foreach( $rankIds as $val )
{
$minPoints = 0;
$maxPoints = 0;
@@ -90,8 +90,25 @@ class _ProfileFields
}
) );
break;
case 'IPS\Helpers\Form\Checkbox':
$options = array(
'' => 'any_value',
1 => 'checked',
0 => 'unchecked'
);
$helper = new \IPS\Helpers\Form\Select( $langKey, $criteria[ $langKey ] ?? NULL, FALSE, array( 'options' => $options ) );
break;
case 'IPS\Helpers\Form\YesNo':
$options = array(
'' => 'any_value',
1 => 'yes',
0 => 'no'
);
$helper = new \IPS\Helpers\Form\Select( $langKey, $criteria[ $langKey ] ?? NULL, FALSE, array( 'options' => $options ) );
break;
case 'IPS\Helpers\Form\Select':
case 'IPS\Helpers\Form\Radio':
case 'IPS\Helpers\Form\CheckboxSet':
if( $field['pf_multiple'] )
{
$options = array();
@@ -174,7 +191,7 @@ class _ProfileFields
$helper = NULL;
$langKey = "core_pfield_{$id}";
if( !isset( $data[ $langKey ] ) OR !$data[ $langKey ] )
if( empty( $data[ $langKey ] ) )
{
continue;
}
@@ -217,8 +234,13 @@ class _ProfileFields
break;
}
break;
case 'IPS\Helpers\Form\Checkbox':
case 'IPS\Helpers\Form\YesNo':
$where[] = "field_{$id}='" . $data[ $langKey ] . "'";
break;
case 'IPS\Helpers\Form\Select':
case 'IPS\Helpers\Form\Radio':
case 'IPS\Helpers\Form\CheckboxSet':
if ( isset( $field['pf_multiple'] ) AND $field['pf_multiple'] == TRUE )
{
$where[] = \IPS\Db::i()->findInSet( 'field_' . $id, $data[ $langKey ] );
@@ -30,7 +30,12 @@ class _System
*/
public function onCreateAccount( $member )
{
\IPS\Api\Webhook::fire( 'member_create', $member );
\IPS\Api\Webhook::fire( 'member_create', $member, $member->webhookFilters() );
if( $member->id AND $member->completed )
{
\IPS\Api\Webhook::fire( 'member_registration_complete', $member, $member->webhookFilters() );
}
if ( \IPS\Dispatcher::hasInstance() and \IPS\Dispatcher::i()->controllerLocation === 'front' and isset( \IPS\Request::i()->cookie[ 'referred_by' ] ) )
{
@@ -149,7 +154,6 @@ class _System
/* Delete duplicate stuff */
\IPS\Db::i()->delete( 'core_item_markers', array( 'item_member_id=?', $member2->member_id ) );
\IPS\Db::i()->delete( 'core_members_feature_seen', array( 'member_id=?', $member2->member_id ) );
\IPS\Db::i()->delete( 'core_security_answers', array( 'answer_member_id=?', $member2->member_id ) );
\IPS\Db::i()->delete( 'core_sessions', array( 'member_id=?', $member2->member_id ) );
\IPS\Db::i()->delete( 'core_sys_cp_sessions', array( 'session_member_id=?', $member2->member_id ) );
@@ -241,6 +245,8 @@ class _System
/* Rebuild permission array */
$member->rebuildPermissionArray();
\IPS\Api\Webhook::fire( 'member_merged', ['kept' => $member->apiOutput(), 'removed' => $member2->apiOutput()] );
}
/**
@@ -308,7 +314,6 @@ class _System
array( 'core_member_status_updates', 'status_author_id' ),
array( 'core_member_status_replies', 'reply_member_id' ),
array( 'core_login_links', 'token_member' ),
array( 'core_members_feature_seen', 'member_id' ),
array( 'core_saved_charts', 'chart_member' ),
array( 'core_members_known_devices', 'member_id' ),
array( 'core_members_known_ip_addresses', 'member_id' ),
@@ -368,7 +373,7 @@ class _System
$member->deletePhoto();
$member->coverPhoto()->delete();
\IPS\Api\Webhook::fire( 'member_delete', $member );
\IPS\Api\Webhook::fire( 'member_delete', $member, $member->webhookFilters() );
}
/**
@@ -407,6 +412,14 @@ class _System
$member->members_bitoptions['profile_completed'] = 0;
}
foreach( $member::$changesToSkipForMemberEditWebhooks as $fieldToRemove )
{
if( \in_array($fieldToRemove, $changes ) )
{
unset( $changes[$fieldToRemove] );
}
}
if( \count( $changes ) )
{
$params = [
@@ -414,7 +427,7 @@ class _System
'changes' => $changes
];
\IPS\Api\Webhook::fire( 'member_edited', $params );
\IPS\Api\Webhook::fire( 'member_edited', $params, $member->webhookFilters() );
}
}
}
}
@@ -169,17 +169,36 @@ class _FeaturedComments
'note' => isset( $comment['note'] ) ? $comment['note'] : '',
);
$commentIds[] = $comment['comment'];
$memberIds[ $comment['featured_by'] ][] = $comment['comment'];
}
$commentClass = $item::$commentClass;
$commentIdField = $commentClass::$databaseColumnId;
if ( isset( $commentClass::$databaseColumnMap['hidden'] ) )
{
$col = $commentClass::$databaseColumnMap['hidden'];
}
else if ( isset( $commentClass::$databaseColumnMap['approved'] ) )
{
$col = $commentClass::$databaseColumnMap['approved'];
}
$softDeleted = [];
foreach( new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', $commentClass::$databaseTable, array( \IPS\Db::i()->in( $commentClass::$databasePrefix . $commentIdField, $commentIds ) ), $commentClass::$databasePrefix . $commentClass::$databaseColumnMap['date'] . " DESC" ), $commentClass ) AS $row )
{
$comments[ $row->$commentIdField ]['comment'] = $row;
if( $row->$col < 0 )
{
unset( $comments[$row->$commentIdField] );
$softDeleted[] = $row->$commentIdField;
}
else
{
$comments[ $row->$commentIdField ]['comment'] = $row;
}
}
/* And finally, who featured them */
if ( \count( $memberIds ) )
{
@@ -187,11 +206,16 @@ class _FeaturedComments
{
foreach( $memberIds[ $m->member_id ] AS $attach )
{
if( \in_array( $attach, $softDeleted ) )
{
continue;
}
$comments[ $attach ]['featured_by'] = $m;
}
}
}
/* And off we go! */
return $comments;
}
@@ -228,4 +252,4 @@ class _FeaturedComments
return FALSE;
}
}
}
}
@@ -95,7 +95,7 @@ class _Alerts
if ( $values = $form->values() )
{
$alert = \IPS\Core\Alerts\Alert::_createFromForm( $values, $current );
$alert = \IPS\core\Alerts\Alert::_createFromForm( $values, $current );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=modcp&tab=alerts", 'front', 'modcp_alerts' ) );
}
@@ -72,47 +72,16 @@ class _Reports extends \IPS\Content\Controller
* @return void
*/
public function manage()
{
$where = [];
{
/* Make sure we're only loading reports where we have permission to view the content */
$classWhere = [];
$classBinds = [];
$viewPermIds = iterator_to_array( \IPS\Db::i()->select( 'perm_id', 'core_permission_index', \IPS\Db::i()->findInSet( 'perm_view', \IPS\Member::loggedIn()->permissionArray() ) . " OR perm_view='*'" ) );
$permIds = [];
foreach ( array_merge( [ 'IPS\core\Messenger\Conversation', 'IPS\core\Messenger\Message' ], array_values( \IPS\Content::routedClasses( FALSE, TRUE ) ) ) as $class )
{
$workingClass = $class;
if ( isset( $workingClass::$itemClass ) )
{
$workingClass = $workingClass::$itemClass;
}
if ( isset( $workingClass::$containerNodeClass ) )
{
$workingClass = $workingClass::$containerNodeClass;
}
if ( isset( $workingClass::$permissionMap ) and isset( $workingClass::$permissionMap['read'] ) and $workingClass::$permissionMap['read'] !== 'view' )
{
if ( !isset( $permIds[ $workingClass::$permissionMap['read'] ] ) )
{
$permIds[ $workingClass::$permissionMap['read'] ] = iterator_to_array( \IPS\Db::i()->select( 'perm_id', 'core_permission_index', \IPS\Db::i()->findInSet( 'perm_' . $workingClass::$permissionMap['read'], \IPS\Member::loggedIn()->permissionArray() ) . " OR perm_" . $workingClass::$permissionMap['read'] . "='*'" ) );
}
$classWhere[] = "( class='" . str_replace( '\\', '\\\\', $class ) . "' AND ( perm_id IN(" . implode( ',', array_intersect( $viewPermIds, $permIds[ $workingClass::$permissionMap['read'] ] ) ) . ") ) OR perm_id IS NULL )";
}
else
{
$classWhere[] = "( class='" . str_replace( '\\', '\\\\', $class ) . "' AND ( perm_id IN(" . implode( ',', $viewPermIds ) . ") ) OR perm_id IS NULL )";
}
}
$where[] = array( '(' . implode( ' OR ', $classWhere ) . ')' );
$where = [ \IPS\core\Reports\Report::where() ];
/* Make sure we're loading the correct statuses */
if( \IPS\Request::i()->isAjax() and isset( \IPS\Request::i()->overview ) )
{
$where[] = array( "status IN( 1,2 )" );
}
/* Create table */
$table = new \IPS\Helpers\Table\Content( '\IPS\core\Reports\Report', \IPS\Http\Url::internal( 'app=core&module=modcp&controller=modcp&tab=reports', NULL, 'modcp_reports' ), $where );
$table->sortBy = $table->sortBy ?: 'first_report_date';
@@ -121,8 +90,38 @@ class _Reports extends \IPS\Content\Controller
/* Title is a special case in the Report center class that isn't available in the core_rc_index table so attempting to sort on it throws an error and does nothing */
unset( $table->sortOptions['title'] );
$table->filters = array( 'report_status_1' => array( 'status=1' ), 'report_status_2' => array( 'status=2' ), 'report_status_3' => array( 'status=3' ) );
$table->filters = [
'filter_report_status_1' => array( 'status=1' )
];
foreach ( \IPS\Application::allExtensions( 'core', 'ContentRouter') as $app => $classes )
{
$appKey = explode('_', $app)[0];
$apps[ $appKey ] = $classes;
}
foreach( $apps as $appKey => $data )
{
$classes = $data->classes;
foreach( $classes as $class )
{
if ( is_subclass_of( $class, "IPS\Content\Item" ) )
{
$classes[] = $class::$commentClass;
if ( isset( $class::$reviewClass ) )
{
$classes[] = $class::$reviewClass;
}
}
}
$table->filters['filter_report_status_1_' . $appKey ] = [ 'status=1 and ' . \IPS\Db::i()->in( 'class', $classes ) ];
\IPS\Member::loggedIn()->language()->words[ 'filter_report_status_1_' . $appKey ] = \IPS\Member::loggedIn()->language()->addToStack( 'filter_report_status_1_app', NULL, [ 'sprintf' => [ \IPS\Application::applications()[ $appKey ]->_title ] ] );
}
$table->filters['filter_report_status_2'] = [ 'status=2' ];
$table->filters['filter_report_status_3'] = [ 'status=3' ];
$table->title = \IPS\Member::loggedIn()->language()->addToStack( 'report_center_header' );
if ( \IPS\Request::i()->isAjax() and isset( \IPS\Request::i()->overview ) )
@@ -137,7 +136,7 @@ class _Reports extends \IPS\Content\Controller
return \IPS\Theme::i()->getTemplate( 'modcp' )->reportList( (string) $table );
}
}
/**
* View a report
*
@@ -311,7 +311,82 @@ class _MyStuff
'author' => ( \IPS\Member::loggedIn()->group['gbw_view_reps'] ) ? \IPS\Member::load( $first['member_id'] ) : new \IPS\Member
);
}
/**
* Parse notification: parse_rest_new_likes
*
* @param \IPS\Notification\Inline $notification The notification
* @param bool $htmlEscape TRUE to escape HTML in title
* @return array|NULL
* @code
return array(
'title' => "Mark has replied to A Topic", // The notification title
'url' => \IPS\Http\Url::internal( ... ), // The URL the notification should link to
'content' => "Lorem ipsum dolar sit", // [Optional] Any appropriate content. Do not format this like an email where the text
// explains what the notification is about - just include any appropriate content.
// For example, if the notification is about a post, set this as the body of the post.
'author' => \IPS\Member::load( 1 ), // [Optional] The user whose photo should be displayed for this notification
);
* @endcode
*/
public function parse_rest_new_likes( $notification, $htmlEscape=TRUE )
{
$comment = $notification->item;
if ( !$comment )
{
throw new \OutOfRangeException;
}
$item = ( $comment instanceof \IPS\Content\Item ) ? $comment : $comment->item();
$idColumn = $comment::$databaseColumnId;
$between = time();
try
{
/* Is there a newer notification for this item? */
$between = \IPS\Db::i()->select( 'sent_time', 'core_notifications', array( '`member`=? AND item_id=? AND item_class=? AND sent_time>? AND notification_key=?', \IPS\Member::loggedIn()->member_id, $comment->$idColumn, \get_class( $comment ), $notification->sent_time->getTimestamp(), $notification->notification_key ) )->first();
}
catch( \UnderflowException $e ) {}
$likers = \IPS\Db::i()->select( 'DISTINCT member_id, rep_date', 'core_reputation_index', array( 'app=? AND type=? AND rep_date>=? AND rep_date<? AND type_id=?', $comment::$application, $comment::reactionType(), $notification->sent_time->getTimestamp(), $between, $comment->$idColumn ), 'rep_date desc' );
$names = array();
$first = array();
foreach( $likers AS $member )
{
if( empty( $first ) )
{
$first = $member;
}
if ( \count( $names ) > 2 )
{
$names[] = \IPS\Member::loggedIn()->language()->addToStack( 'x_others', FALSE, array( 'pluralize' => array( \count( $likers ) - 3 ) ) );
break;
}
$names[] = \IPS\Member::load( $member['member_id'] )->name;
}
if( empty( $first ) )
{
throw new \OutOfRangeException;
}
return array(
'title' => \IPS\Member::loggedIn()->language()->addToStack( \IPS\Content\Reaction::isLikeMode() ? 'notification__new_likes' : 'notification__new_react', FALSE, array(
'pluralize' => array( \count( $likers ) ),
( $htmlEscape ? 'sprintf' : 'htmlsprintf' ) => array(
\IPS\Member::loggedIn()->language()->formatList( $names ),
mb_strtolower( $comment->indefiniteArticle() )
)
) ) . ' ' . $item->mapped('title'),
'url' => ( $comment instanceof \IPS\Content\Comment ) ? $comment->url('find') : $comment->url(),
'content' => $comment->truncated(),
'author' => \IPS\Member::load( $first['member_id'] )
);
}
/**
* Parse notification for mobile: new_likes
*
@@ -0,0 +1,52 @@
<?php
/**
* @brief Template Plugin
* @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 01 Sep 2022
*/
namespace IPS\core\extensions\core\OutputPlugins;
/* 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;
}
/**
* Template Plugin
*/
class _GraphQl
{
/**
* @brief Can be used when compiling CSS
*/
public static $canBeUsedInCss = FALSE;
/**
* Run the plug-in
*
* @param string $data The initial data from the tag
* @param array $options Array of options
* @return string Code to eval
*/
public static function runPlugin( $data, $options )
{
if( !isset( $options['variables']))
{
$options['variables'] = [];
}
$response = \IPS\Api\GraphQL::execute($data, $options['variables']);
if( isset( $response['data']))
{
return "'" . json_encode($response['data'] ) . "'";
}
}
}
@@ -107,18 +107,30 @@ class _ProfileFields
/* Init Chart */
$pieBarData = array();
$results = array();
$limit = 15;
/* Add Rows */
$select = \IPS\Db::i()->select( 'COUNT(*) as total, field_' . $field['pf_id'], 'core_pfields_content', NULL, NULL, NULL, 'field_' . $field['pf_id'] );
$select = \IPS\Db::i()->select( 'COUNT(*) as total, field_' . $field['pf_id'], 'core_pfields_content', NULL, 'total DESC', NULL, 'field_' . $field['pf_id'] );
$total = 0;
$i = 0;
foreach( $select as $row )
{
if( $row['field_' . $field['pf_id'] ] !== NULL )
{
$total += $row['total'];
$results[ $row['field_' . $field['pf_id'] ] ] = $row;
if( $i <= $limit )
{
$results[ $row['field_' . $field['pf_id'] ] ] = $row;
}
else
{
$otherKey = \IPS\Member::loggedIn()->language()->addToStack('stats_overview_others');
$results[$otherKey] = [ 'total' => 5 ];
}
$i++;
}
}
@@ -165,6 +177,7 @@ class _ProfileFields
elseif( $field['pf_content'] )
{
$options = json_decode( $field['pf_content'], TRUE );
$options = array_keys( $results);
foreach( $options as $k => $v )
{
@@ -0,0 +1,96 @@
<?php
/**
* @brief Background 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 27 Jun 2022
*/
namespace IPS\core\extensions\core\Queue;
/* 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;
}
/**
* Background Task
*/
class _BuildTimeToRank
{
/**
* @brief Number of topics to build per cycle
*/
public $perCycle = \IPS\REBUILD_QUICK;
/**
* Parse data before queuing
*
* @param array $data
* @return array
*/
public function preQueueData( $data )
{
try
{
$data['count'] = \IPS\Db::i()->select( 'COUNT(*)', 'core_points_log', array( 'new_rank IS NOT NULL') )->first();
}
catch( \Exception $ex )
{
throw new \OutOfRangeException;
}
if( $data['count'] == 0 )
{
return null;
}
return $data;
}
/**
* Run Background Task
*
* @param mixed $data Data as it was passed to \IPS\Task::queue()
* @param int $offset Offset
* @return int New offset
* @throws \IPS\Task\Queue\OutOfRangeException Indicates offset doesn't exist and thus task is complete
*/
public function run( $data, $offset )
{
$count = 0;
foreach( \IPS\Db::i()->select( '*', 'core_points_log', array( 'new_rank IS NOT NULL'), 'id ASC', array( $offset, $this->perCycle ) )->join( 'core_members', 'core_members.member_id = core_points_log.member' ) as $log )
{
$count++;
\IPS\Db::i()->update( 'core_points_log', array( 'time_to_new_rank' => $log['datetime'] - $log['joined'] ), array( 'id=?', $log['id'] ) );
$offset = $log['id'];
}
if( !$count )
{
throw new \IPS\Task\Queue\OutOfRangeException;
}
return ( $offset );
}
/**
* Get Progress
*
* @param mixed $data Data as it was passed to \IPS\Task::queue()
* @param int $offset Offset
* @return array( 'text' => 'Doing something...', 'complete' => 50 ) Text explaining task and percentage complete
* @throws \OutOfRangeException Indicates offset doesn't exist and thus task is complete
*/
public function getProgress( $data, $offset )
{
return array( 'text' => \IPS\Member::loggedIn()->language()->addToStack('rankprogression_rebuild'), 'complete' => round( 100 / $data['count'] * $offset, 2 ) );
}
}
@@ -63,10 +63,10 @@ class _Follow
throw new \IPS\Task\Queue\OutOfRangeException;
}
/* Check soft delete */
if( $item->hidden() == -2 )
/* Only send notifications for approved items */
if( $item->hidden() !== 0 )
{
/* Item is pending deletion, do not sent notifications */
/* Item is pending deletion or hidden, do not send notifications */
throw new \IPS\Task\Queue\OutOfRangeException;
}
@@ -0,0 +1,86 @@
<?php
/**
* @brief Background 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 27 Sep 2022
*/
namespace IPS\core\extensions\core\Queue;
/* 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;
}
/**
* Background Task
*/
class _PrunePms
{
/**
* Parse data before queuing
*
* @param array $data
* @return array
*/
public function preQueueData( $data )
{
$rows = new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', 'core_message_topics', array( 'mt_last_post_time < ?', \IPS\DateTime::create()->sub( new \DateInterval( 'P' . \IPS\Settings::i()->prune_pms . 'D' ) )->getTimestamp() ) ), '\IPS\core\Messenger\Conversation');
$data['count'] = $rows->count();
if( $data['count'] == 0 )
{
return null;
}
return $data;
}
/**
* Run Background Task
*
* @param mixed $data Data as it was passed to \IPS\Task::queue()
* @param int $offset Offset
* @return int New offset
* @throws \IPS\Task\Queue\OutOfRangeException Indicates offset doesn't exist and thus task is complete
*/
public function run( &$data, $offset )
{
$rows = new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', 'core_message_topics', array( 'mt_last_post_time < ?', \IPS\DateTime::create()->sub( new \DateInterval( 'P' . \IPS\Settings::i()->prune_pms . 'D' ) )->getTimestamp() ), 'mt_last_post_time ASC', 100 ), '\IPS\core\Messenger\Conversation');
if ( !$rows->count() )
{
throw new \IPS\Task\Queue\OutOfRangeException;
}
foreach( $rows AS $conversation )
{
$conversation->delete();
$offset++;
}
return $offset;
}
/**
* Get Progress
*
* @param mixed $data Data as it was passed to \IPS\Task::queue()
* @param int $offset Offset
* @return array( 'text' => 'Doing something...', 'complete' => 50 ) Text explaining task and percentage complete
* @throws \OutOfRangeException Indicates offset doesn't exist and thus task is complete
*/
public function getProgress( $data, $offset )
{
$text = \IPS\Member::loggedIn()->language()->addToStack('pruning_pms', FALSE, array() );
return array( 'text' => $text, 'complete' => $data['count'] ? ( round( 100 / $data['count'] * $offset, 2 ) ) : 100 );
}
}
@@ -63,7 +63,7 @@ class _RebuildAchievements
}
catch( \Exception $ex )
{
throw new \OutOfRangeException;
return null;
}
if( $data['count'] == 0 )
@@ -104,7 +104,15 @@ class _RebuildAchievements
}
list( $app, $extension ) = explode( '_', $data['extension'] );
$extensionObject = \IPS\Application::load( $app )->extensions( 'core', 'AchievementAction' )[ $extension ];
try
{
$extensionObject = \IPS\Application::load( $app )->extensions( 'core', 'AchievementAction' )[ $extension ];
}
catch( \OutOfRangeException $e )
{
\IPS\Log::debug("App {$app} deleted. Nothing to do.", "achievements");
throw new \IPS\Task\Queue\OutOfRangeException;
}
$where = $process['where'];
if ( \stristr( $process['pkey'], '(' ) )
@@ -0,0 +1,148 @@
<?php
/**
* @brief Background Task: Rebuild Report Index Data
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
* @copyright (c) Invision Power Services, Inc.
* @license https://www.invisioncommunity.com/legal/standards/
* @package Invision Community
* @since 16 Sept 2022
*/
namespace IPS\core\extensions\core\Queue;
/* 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;
}
/**
* Background Task: Rebuild Item Counts (comments, etc)
*/
class _RebuildReportIndexData
{
/**
* @brief Number of content items to index per cycle
*/
public $index = \IPS\REBUILD_QUICK;
/**
* Parse data before queuing
*
* @param array $data
* @return array
*/
public function preQueueData( $data )
{
try
{
$data['count'] = \IPS\Db::i()->select( 'MAX(id)', 'core_rc_index' )->first();
$data['realCount'] = \IPS\Db::i()->select( 'COUNT(*)', 'core_rc_index' )->first();
}
catch( \IPS\Db\Exception $ex )
{
return NULL;
}
catch( \Exception $ex )
{
throw new \OutOfRangeException;
}
if( $data['count'] == 0 )
{
return null;
}
$data['indexed'] = 0;
return $data;
}
/**
* Run Background Task
*
* @param mixed $data Data as it was passed to \IPS\Task::queue()
* @param int $offset Offset
* @return int New offset
* @throws \IPS\Task\Queue\OutOfRangeException Indicates offset doesn't exist and thus task is complete
*/
public function run( &$data, $offset )
{
$last = NULL;
try
{
foreach( \IPS\Db::i()->select( '*', 'core_rc_index', array( 'id > ?', $offset ), 'id ASC', array( 0, $this->index ) ) as $report )
{
$last = $report['id'];
$classname = $report['class'];
$exploded = explode( '\\', $classname );
if ( ! class_exists( $classname ) or ! \IPS\Application::appIsEnabled( $exploded[1] ) )
{
continue;
}
try
{
$content = $classname::load( $report['content_id'] );
$itemId = 0;
$nodeId = 0;
$item = null;
if ( $content instanceof \IPS\Content\Item )
{
$item = $content;
}
else
{
$item = $content->item();
}
$idColumn = $item::$databaseColumnId;
$itemId = $item->$idColumn;
if ( $node = $item->containerWrapper() )
{
$nodeId = $node->_id;
}
\IPS\Db::i()->update( 'core_rc_index', [
'item_id' => $itemId,
'node_id' => $nodeId
],
[
'id=?', $report['id']
] );
}
catch( \Exception $e )
{
continue;
}
}
}
catch( \Exception $e )
{
throw new \IPS\Task\Queue\OutOfRangeException;
}
if( $last === NULL )
{
throw new \IPS\Task\Queue\OutOfRangeException;
}
return $last;
}
/**
* Get Progress
*
* @param mixed $data Data as it was passed to \IPS\Task::queue()
* @param int $offset Offset
* @return array( 'text' => 'Doing something...', 'complete' => 50 ) Text explaining task and percentage complete
*/
public function getProgress( $data, $offset )
{
return array( 'text' => \IPS\Member::loggedIn()->language()->addToStack('rebuilding_report_index_data'), 'complete' => $data['realCount'] ? ( round( 100 / $data['realCount'] * $data['indexed'], 2 ) ) : 100 );
}
}
@@ -169,6 +169,11 @@ class _RebuildSearchIndex
$indexer->index($item);
}
}
catch( \OutOfRangeException $e )
{
/* This can happen if there are older, orphaned posts/comments. Just do nothing here,
don't even log it, because we end up with pages and pages of logs. */
}
catch ( \Exception $e )
{
/* There was an issue indexing the item - skip and log it */