Version 4.7.0

This commit is contained in:
Neo committed 2025-12-19 16:00:35 -08:00
1 parent 517a5e1f70
commit 8ba7d43898
1933 files changed
+65613 -11332

No files matched your search

@@ -42,7 +42,7 @@ class _Comment extends \IPS\core\Achievements\Actions\AbstractContentAchievement
{
return FALSE;
}
if ( isset( $filters['milestone'] ) )
{
$count = 0;
@@ -50,14 +50,32 @@ class _Comment extends \IPS\core\Achievements\Actions\AbstractContentAchievement
$itemClassesToDeduct = [];
if ( isset( $filters['type'] ) )
{
if ( isset( $filters[ 'nodes_' . str_replace( '\\', '-', $filters['type'] ) ] ) )
$item = $extra->item();
if ( isset( $filters[ 'nodes_' . str_replace( '\\', '-', \get_class( $item ) ) ] ) )
{
$class = $filters['type'];
$where = [];
$where[] = [ $class::$databasePrefix . $class::$databaseColumnMap['author'] . '=?', $subject->member_id ];
$where[] = [ \IPS\Db::i()->in( $class::$databasePrefix . $class::$databaseColumnMap['container'] ), $filters[ 'nodes_' . str_replace( '\\', '-', $filters['type'] ) ] ];
$where = [ [ $class::$databasePrefix . $class::$databaseColumnMap['author'] . '=?', $subject->member_id ] ];
if ( isset( $extra::$databaseColumnMap['first'] ) )
{
$where[] = [ $class::$databasePrefix . $class::$databaseColumnMap['first'] . '=0' ];
}
if ( isset( $class::$databaseColumnMap['approved'] ) )
{
$where[] = [ $class::$databasePrefix . $class::$databaseColumnMap['approved'] . '=?', 1 ];
}
elseif ( isset( $class::$databaseColumnMap['hidden'] ) )
{
$where[] = [ $class::$databasePrefix . $class::$databaseColumnMap['hidden'] . '=?', 0 ];
}
$where[] = [ \IPS\Db::i()->in( $item::$databaseTable . '.' . $item::$databasePrefix . $item::$databaseColumnMap['container'] , $filters[ 'nodes_' . str_replace( '\\', '-', \get_class( $item ) ) ] ) ];
$count += \IPS\Db::i()->select( 'COUNT(*)', $class::$databaseTable, $where )->first();
$count += \IPS\Db::i()->select( 'COUNT(*)', $class::$databaseTable, $where )
->join( $item::$databaseTable, $item::$databasePrefix . $item::$databaseColumnId . '=' . $class::$databasePrefix . $class::$databaseColumnMap['item'] )
->first();
}
else
{
@@ -88,27 +106,30 @@ class _Comment extends \IPS\core\Achievements\Actions\AbstractContentAchievement
return TRUE;
}
/**
* Get the labels for the people this action might give awards to
*
* @param array|NULL $filters Current filter values
*
* @return array
*/
public function awardOptions(): array
public function awardOptions( ?array $filters ): array
{
return [
'subject' => 'achievement_filter_Comment_author',
'other' => 'achievement_filter_Comment_item_author'
];
}
/**
* Get the "other" people we need to award =stuff to
*
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
* @param array|NULL $filters Current filter values
* @return array
*/
public function awardOther( $extra = NULL ): array
public function awardOther( $extra = NULL, ?array $filters = NULL ): array
{
return [ $extra->item()->author() ];
}
@@ -127,6 +148,11 @@ class _Comment extends \IPS\core\Achievements\Actions\AbstractContentAchievement
$sprintf = [];
try
{
if( !\class_exists( $exploded[0] ) )
{
throw new \OutOfRangeException;
}
$comment = $exploded[0]::load( $exploded[1] );
$item = $comment->item();
$sprintf = [ 'htmlsprintf' => [
@@ -91,7 +91,7 @@ class _ContentPromotion extends \IPS\core\Achievements\Actions\AbstractContentAc
if ( isset( $filters['milestone'] ) )
{
if ( ( $this->getQuery( 'COUNT(*)', $filters, $filters['promotype'], $subject )->first() + 1 ) < $filters['milestone'] )
if ( ( $this->getQuery( 'COUNT(*)', $filters, $extra['promotype'], $subject )->first() + 1 ) < $filters['milestone'] )
{
return FALSE;
}
@@ -248,7 +248,7 @@ class _ContentPromotion extends \IPS\core\Achievements\Actions\AbstractContentAc
break;
case 'core_social_promote':
$promote = \IPS\core\Promote::load( $row['promote_id'] );
$promote->author()->achievementAction( 'core', 'ContentPromotion', [
$promote->object()->author()->achievementAction( 'core', 'ContentPromotion', [
'content' => $promote->object(),
'promotype' => 'promote'
] );
@@ -267,20 +267,24 @@ class _ContentPromotion extends \IPS\core\Achievements\Actions\AbstractContentAc
* @param null|boolean $order
* @param null $limit
* @return \IPS\Db\Select
* @throws \InvalidArgumentException
*/
protected function getQuery( $select, $filters, $type, $subject=NULL, $lastId=NULL, $time=NULL, $order=NULL, $limit=NULL )
{
$itemClass = $filters['type'];
if ( isset( $itemClass::$databaseColumnMap['hidden'] ) )
if( isset( $filters['type'] ) )
{
$subWhere = [ [ $itemClass::$databaseColumnMap['hidden'] . '=0' ] ];
}
elseif ( isset( $itemClass::$databaseColumnMap['approved'] ) )
{
$subWhere = [ [ $itemClass::$databaseColumnMap['approved'] . '=1' ] ];
}
$itemClass = $filters['type'];
if ( isset( $itemClass::$databaseColumnMap['hidden'] ) )
{
$subWhere = [ [ $itemClass::$databaseColumnMap['hidden'] . '=0' ] ];
}
elseif ( isset( $itemClass::$databaseColumnMap['approved'] ) )
{
$subWhere = [ [ $itemClass::$databaseColumnMap['approved'] . '=1' ] ];
}
}
/* Recommended (but really called featured in the code just to confuse me) */
if ( $type === 'recommend' )
{
@@ -324,7 +328,7 @@ class _ContentPromotion extends \IPS\core\Achievements\Actions\AbstractContentAc
$where[] = [ 'meta_item_id IN(?)', \IPS\Db::i()->select( $itemClass::$databaseColumnId, $itemClass::$databasePrefix . $itemClass::$databaseTable, $subWhere ) ];
}
$query = \IPS\Db::i()->select( $select, 'core_content_meta', $where );
return \IPS\Db::i()->select( $select, 'core_content_meta', $where );
}
else if ( $type === 'feature' )
{
@@ -362,7 +366,7 @@ class _ContentPromotion extends \IPS\core\Achievements\Actions\AbstractContentAc
$where[] = [ 'feature_content_id IN(?)', \IPS\Db::i()->select( $itemClass::$databaseColumnId, $itemClass::$databasePrefix . $itemClass::$databaseTable, $subWhere ) ];
}
$query = \IPS\Db::i()->select( $select, 'core_content_featured', $where );
return \IPS\Db::i()->select( $select, 'core_content_featured', $where );
}
else if ( $type === 'promote' )
{
@@ -400,9 +404,9 @@ class _ContentPromotion extends \IPS\core\Achievements\Actions\AbstractContentAc
$where[] = [ 'promote_class_id IN(?)', \IPS\Db::i()->select( $itemClass::$databaseColumnId, $itemClass::$databasePrefix . $itemClass::$databaseTable, $subWhere ) ];
}
$query = \IPS\Db::i()->select( $select, 'core_social_promote', $where, $order, $limit );
return \IPS\Db::i()->select( $select, 'core_social_promote', $where, $order, $limit );
}
return $query;
throw new \InvalidArgumentException;
}
}
@@ -80,9 +80,11 @@ class _FollowContentItem extends \IPS\core\Achievements\Actions\AbstractContentA
/**
* Get the labels for the people this action might give awards to
*
* @param array|NULL $filters Current filter values
*
* @return array
*/
public function awardOptions(): array
public function awardOptions( ?array $filters ): array
{
return [
'subject' => 'achievement_filter_FollowContentItem_follower',
@@ -93,10 +95,11 @@ class _FollowContentItem extends \IPS\core\Achievements\Actions\AbstractContentA
/**
* Get the "other" people we need to award =stuff to
*
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
* @param array|NULL $filters Current filter values
* @return array
*/
public function awardOther( $extra = NULL ): array
public function awardOther( $extra = NULL, ?array $filters = NULL ): array
{
return [ $extra['author'] ];
}
@@ -199,7 +202,7 @@ class _FollowContentItem extends \IPS\core\Achievements\Actions\AbstractContentA
{
$className = 'IPS\\' . $row['follow_app'] . '\\' . mb_ucfirst( $row['follow_area'] );
if ( \in_array( 'IPS\Content\Item', class_parents( $className ) ) )
if ( class_exists( $className ) AND \in_array( 'IPS\Content\Item', class_parents( $className ) ) )
{
$item = $className::load( $row['follow_rel_id'] );
\IPS\Member::load( $row['follow_member_id'] )->achievementAction( 'core', 'FollowContentItem', [
@@ -74,7 +74,7 @@ class _FollowMember extends \IPS\core\Achievements\Actions\AbstractAchievementAc
if ( isset( $filters['milestone'] ) )
{
$where = [];
$where[] = [ 'follow_app=? and follow_area=? and follow_member_id=?', 'core', 'member', $subject->member_id ];
$where[] = [ 'follow_app=? and follow_area=? and follow_rel_id=?', 'core', 'member', $subject->member_id ];
$count = \IPS\Db::i()->select( 'COUNT(*)', 'core_follow', $where )->first();
@@ -90,9 +90,11 @@ class _FollowMember extends \IPS\core\Achievements\Actions\AbstractAchievementAc
/**
* Get the labels for the people this action might give awards to
*
* @param array|NULL $filters Current filter values
*
* @return array
*/
public function awardOptions(): array
public function awardOptions( ?array $filters ): array
{
return [
'subject' => 'achievement_filter_FollowMember_receiver',
@@ -103,10 +105,11 @@ class _FollowMember extends \IPS\core\Achievements\Actions\AbstractAchievementAc
/**
* Get the "other" people we need to award =stuff to
*
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
* @param array|NULL $filters Current filter values
* @return array
*/
public function awardOther( $extra = NULL ): array
public function awardOther( $extra = NULL, ?array $filters = NULL ): array
{
return [ $extra['giver'] ];
}
@@ -127,7 +127,7 @@ class _FollowNode extends \IPS\core\Achievements\Actions\AbstractNodeAchievement
public static function rebuildRow( $row, $data )
{
$class = 'IPS\\' . $row['follow_app'] . '\\' . mb_ucfirst( $row['follow_area'] );
if ( \in_array( 'IPS\Node\Model', class_parents( $class ) ) )
if ( class_exists( $class ) AND \in_array( 'IPS\Node\Model', class_parents( $class ) ) )
{
\IPS\Member::load( $row['follow_member_id'] )->achievementAction( 'core', 'FollowNode', $class::load( $row['follow_rel_id'] ) );
}
@@ -96,27 +96,30 @@ class _JoinClub extends \IPS\core\Achievements\Actions\AbstractAchievementAction
return TRUE;
}
/**
* Get the labels for the people this action might give awards to
*
* @param array|NULL $filters Current filter values
*
* @return array
*/
public function awardOptions(): array
public function awardOptions( ?array $filters ): array
{
return [
'subject' => 'achievement_filter_JoinClub_joiner',
'other' => 'achievement_filter_JoinClub_owner'
];
}
/**
* Get the "other" people we need to award =stuff to
*
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
* @param array|NULL $filters Current filter values
* @return array
*/
public function awardOther( $extra = NULL ): array
public function awardOther( $extra = NULL, ?array $filters = NULL ): array
{
return [ $extra->owner ];
}
@@ -98,6 +98,11 @@ class _NewContentItem extends \IPS\core\Achievements\Actions\AbstractContentAchi
$sprintf = [];
try
{
if( !\class_exists( $exploded[0] ) )
{
throw new \OutOfRangeException;
}
$item = $exploded[0]::load( $exploded[1] );
$sprintf = [ 'htmlsprintf' => [
\IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->basicUrl( $item->url(), TRUE, $item->mapped('title') ?: $item->indefiniteArticle(), FALSE )
@@ -40,6 +40,16 @@ class _Reaction extends \IPS\core\Achievements\Actions\AbstractContentAchievemen
], NULL, \IPS\Member::loggedIn()->language()->addToStack( 'achievement_subfilter_Reaction_reaction_prefix' ) );
$return['reactions'] = $reactionFilter;
$return['milestone'] = new \IPS\Helpers\Form\Custom( 'achievement_filter_Reaction_nth', ( $filters and isset( $filters['milestone'] ) and $filters['milestone'] ) ? $filters['milestone'] : [], FALSE, array( 'getHtml' => function( $element )
{
/* 4.6.0 - 4.6.3 */
if ( \is_integer( $element->value ) )
{
$element->value = ['receiver', $element->value];
}
return \IPS\Theme::i()->getTemplate( 'achievements' )->milestoneWithSubjectSwitch( $element->name, $element->value );
} ), NULL, NULL, NULL, 'achievement_filter_Reaction_nth' );
return $return;
}
@@ -56,6 +66,7 @@ class _Reaction extends \IPS\core\Achievements\Actions\AbstractContentAchievemen
{
$return['reactions'] = array_keys( $values['achievement_filter_Reaction_reaction'] );
}
return $return;
}
@@ -77,7 +88,7 @@ class _Reaction extends \IPS\core\Achievements\Actions\AbstractContentAchievemen
{
return FALSE;
}
if ( isset( $filters['reactions'] ) )
{
if ( !\in_array( $extra['reaction']->id, $filters['reactions'] ) )
@@ -85,12 +96,26 @@ class _Reaction extends \IPS\core\Achievements\Actions\AbstractContentAchievemen
return FALSE;
}
}
if ( isset( $filters['milestone'] ) )
{
$query = $this->getQuery( 'COUNT(*)', [ [ 'member_id=?', $subject->member_id ] ], NULL, $filters );
if ( ( $query->first() + 1 ) < $filters['milestone'] )
if ( isset( $filters['milestone'] ) and \is_array( $filters['milestone'] ) )
{
$field = ( $filters['milestone'][0] == 'giver' ) ? 'member_id' : 'member_received';
$member = ( $filters['milestone'][0] == 'giver' ) ? $extra['giver'] : $subject;
$milestone = $filters['milestone'][1];
}
else
{
/* 4.6.0 - 4.6.3 just had a milestone for member_received */
$field = 'member_received';
$milestone = $filters['milestone'];
$member = $subject;
}
$query = $this->getQuery( 'COUNT(*)', [ [ $field . '=?', $member->member_id ] ], NULL, $filters );
if ( ( $query->first() + 1 ) < $milestone )
{
return FALSE;
}
@@ -111,27 +136,30 @@ class _Reaction extends \IPS\core\Achievements\Actions\AbstractContentAchievemen
{
return \get_class( $extra['content'] ) . ':' . $extra['content']->{$extra['content']::$databaseColumnId} . ':' . $extra['giver']->member_id;
}
/**
* Get the labels for the people this action might give awards to
*
* @param array|NULL $filters Current filter values
*
* @return array
*/
public function awardOptions(): array
public function awardOptions( ?array $filters ): array
{
return [
'subject' => 'achievement_filter_Reaction_receiver',
'other' => 'achievement_filter_Reaction_giver'
'subject' => 'achievement_filter_Reaction_receiver',
'other' => 'achievement_filter_Reaction_giver'
];
}
/**
* Get the "other" people we need to award =stuff to
*
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
* @param array|NULL $filters Current filter values
* @return array
*/
public function awardOther( $extra = NULL ): array
public function awardOther( $extra = NULL, ?array $filters = NULL ): array
{
return [ $extra['giver'] ];
}
@@ -181,9 +209,19 @@ class _Reaction extends \IPS\core\Achievements\Actions\AbstractContentAchievemen
$conditions = [];
if ( isset( $rule->filters['milestone'] ) )
{
if ( \is_integer( $rule->filters['milestone'] ) )
{
/* 4.6.0 - 4.6.3 */
$milestone = $rule->filters['milestone'];
}
else
{
$milestone = $rule->filters['milestone'][1];
}
$conditions[] = \IPS\Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone', FALSE, [
'htmlsprintf' => [
\IPS\Theme::i()->getTemplate( 'achievements' )->ruleDescriptionBadge( 'milestone', \IPS\Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone_nth', FALSE, [ 'pluralize' => [ $rule->filters['milestone'] ] ] ) )
\IPS\Theme::i()->getTemplate( 'achievements' )->ruleDescriptionBadge( 'milestone', \IPS\Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone_nth', FALSE, [ 'pluralize' => [ $milestone ] ] ) )
],
'sprintf' => \IPS\Member::loggedIn()->language()->addToStack('AchievementAction__Reaction_title_generic')
] );
@@ -219,7 +257,9 @@ class _Reaction extends \IPS\core\Achievements\Actions\AbstractContentAchievemen
}
return \IPS\Theme::i()->getTemplate( 'achievements' )->ruleDescription(
\IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__Reaction_title' ),
( ( isset( $rule->filters['milestone'] ) and ( ( \is_array( $rule->filters['milestone'] ) and $rule->filters['milestone'][0] == 'giver' ) or \is_numeric( $rule->filters['milestone'] ) ) ) or ! isset( $rule->filters['milestone']) )
? \IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__Reaction_title' )
: \IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__Reaction_title_received' ),
$conditions
);
}
@@ -254,9 +294,27 @@ class _Reaction extends \IPS\core\Achievements\Actions\AbstractContentAchievemen
return;
}
$object = $row['rep_class']::load( $row['item_id'] );
$object = $row['rep_class']::load( $row['type_id'] );
$reaction = \IPS\Content\Reaction::load( $row['reaction'] );
/* Give points */
static::getAuthor( $object )->achievementAction( 'core', 'Reaction', [
'giver' => \IPS\Member::load( $row['member_id'] ),
'content' => $object,
'reaction' => $reaction
] );
}
/**
* Get the author of the content / node
*
* @param $object
* @return \IPS\Member|null
*/
public static function getAuthor( $object ): ?\IPS\Member
{
$owner = NULL;
/* Figure out the owner of this - if it is content, it will be the author. If it is a node, then it will be the person who created it */
if ( $object instanceof \IPS\Content )
{
@@ -267,12 +325,7 @@ class _Reaction extends \IPS\core\Achievements\Actions\AbstractContentAchievemen
$owner = $object->owner();
}
/* Give points */
$owner->achievementAction( 'core', 'Reaction', [
'giver' => \IPS\Member::load( $row['member_id'] ),
'content' => $object,
'reaction' => $reaction
] );
return $owner;
}
/**
@@ -91,27 +91,30 @@ class _Review extends \IPS\core\Achievements\Actions\AbstractContentAchievementA
return TRUE;
}
/**
* Get the labels for the people this action might give awards to
*
* @param array|NULL $filters Current filter values
*
* @return array
*/
public function awardOptions(): array
public function awardOptions( ?array $filters ): array
{
return [
'subject' => 'achievement_filter_Review_author',
'other' => 'achievement_filter_Review_item_author'
];
}
/**
* Get the "other" people we need to award =stuff to
*
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
* @param array|NULL $filters Current filter values
* @return array
*/
public function awardOther( $extra = NULL ): array
public function awardOther( $extra = NULL, ?array $filters = NULL ): array
{
return [ $extra->item()->author() ];
}
@@ -126,7 +126,8 @@ class _SessionStartDaily extends \IPS\core\Achievements\Actions\AbstractAchievem
*/
public function logRow( string $identifier, array $actor ): string
{
return \IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__SessionStartDaily_log', FALSE, array( 'sprintf' => ( new \IPS\DateTime( $identifier ) )->dayAndMonth() ) );
list( $id, $time ) = explode( '.', $identifier );
return \IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__SessionStartDaily_log', FALSE, array( 'sprintf' => ( new \IPS\DateTime( $time ) )->dayAndMonth() ) );
}
/**
@@ -86,28 +86,30 @@ class _VotePoll extends \IPS\core\Achievements\Actions\AbstractAchievementAction
return TRUE;
}
/**
* Get the labels for the people this action might give awards to
*
* @param array|NULL $filters Current filter values
*
* @return array
*/
public function awardOptions(): array
public function awardOptions( ?array $filters ): array
{
return [
'subject' => 'achievement_filter_VotePoll_voter',
'other' => 'achievement_filter_VotePoll_author'
];
}
/**
* Get the "other" people we need to award =stuff to
*
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
* @param array|NULL $filters Current filter values
* @return array
*/
public function awardOther( $extra = NULL ): array
public function awardOther( $extra = NULL, ?array $filters = NULL ): array
{
return [ $extra->author() ];
}
@@ -108,7 +108,8 @@ class _Bulletin extends \IPS\core\AdminNotification
{
try
{
$bulletin = \IPS\Http\Url::ips("bulletin/{$data['id']}")->request()->get()->decodeJson();
$response = \IPS\Http\Url::ips("bulletin/{$data['id']}")->request()->get();
$bulletin = $response->decodeJson();
if ( isset( $bulletin['title'] ) )
{
$data = array(
@@ -128,6 +129,12 @@ class _Bulletin extends \IPS\core\AdminNotification
}
else
{
if( (int) $response->httpResponseCode === 410 )
{
\IPS\Db::i()->delete( 'core_ips_bulletins', [ 'id=?', $this->extra ] );
$this->delete();
}
throw new \DomainException;
}
}
@@ -238,11 +245,7 @@ class _Bulletin extends \IPS\core\AdminNotification
return !@eval( $this->data()['conditions'] );
}
catch ( \Exception $e )
{
return FALSE;
}
catch ( \Throwable $e )
catch ( \Throwable | \Exception $e )
{
return FALSE;
}
@@ -548,6 +548,10 @@ class _ConfigurationError extends \IPS\core\AdminNotification
}
elseif ( $this->extra === 'cacheBroken' )
{
if( \IPS\CIC )
{
return \IPS\Member::loggedIn()->language()->addToStack('dashboard_invalid_cachesetup_desc_cic');
}
return \IPS\Member::loggedIn()->language()->addToStack('dashboard_invalid_cachesetup_desc');
}
elseif ( $this->extra === 'cicEmailQuota' )
@@ -60,6 +60,11 @@ class _NewVersion extends \IPS\core\AdminNotification
*/
public static function permissionCheck( \IPS\Member $member )
{
if ( \IPS\CIC AND \IPS\IPS::isManaged() )
{
return FALSE;
}
return $member->hasAcpRestriction( 'core', 'overview', 'upgrade_manage' );
}
@@ -163,4 +168,15 @@ class _NewVersion extends \IPS\core\AdminNotification
{
return \IPS\Http\Url::internal( 'app=core&module=system&controller=upgrade&_new=1', 'admin' );
}
/**
* Should this notification dismiss itself?
*
* @note This is checked every time the notification shows. Should be lightweight.
* @return bool
*/
public function selfDismiss()
{
return empty( $this->_details['version'] );
}
}
@@ -0,0 +1,127 @@
<?php
/**
* @brief Community Enhancements
* @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 2021
*/
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 Enhancement
*/
class _GoogleAnalytics
{
/**
* @brief Enhancement is enabled?
*/
public $enabled = FALSE;
/**
* @brief IPS-provided enhancement?
*/
public $ips = FALSE;
/**
* @brief Enhancement has configuration options?
*/
public $hasOptions = TRUE;
/**
* @brief Icon data
*/
public $icon = "google_analytics.png";
/**
* Constructor
*
* @return void
*/
public function __construct()
{
$this->enabled = ( \IPS\Settings::i()->ga_enabled and \IPS\Settings::i()->ga_code );
}
/**
* Edit
*
* @return void
*/
public function edit()
{
$validation = function( $val ) {
if ( $val and !\IPS\Request::i()->ga_code )
{
throw new \DomainException('ga_code_required');
}
};
$form = new \IPS\Helpers\Form;
$form->add( new \IPS\Helpers\Form\YesNo( 'ga_enabled', \IPS\Settings::i()->ga_enabled, FALSE, array(), $validation ) );
$form->add( new \IPS\Helpers\Form\Codemirror( 'ga_code', \IPS\Settings::i()->ga_code, FALSE, array( 'height' => 150, 'mode' => 'javascript' ), NULL, NULL, NULL, 'ga_code' ) );
if ( $form->values() )
{
try
{
$form->saveAsSettings();
\IPS\Output::i()->inlineMessage = \IPS\Member::loggedIn()->language()->addToStack('saved');
}
catch ( \LogicException $e )
{
$form->error = $e->getMessage();
}
}
\IPS\Output::i()->sidebar['actions'] = array(
'help' => array(
'title' => 'learn_more',
'icon' => 'question-circle',
'link' => \IPS\Http\Url::ips( 'docs/googleanalytics' ), //@todo
'target' => '_blank'
),
);
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global' )->block( 'enhancements__core_GoogleAnalytics', $form );
}
/**
* Enable/Disable
*
* @param $enabled bool Enable/Disable
* @return void
* @throws \LogicException
*/
public function toggle( $enabled )
{
if ( $enabled )
{
if ( \IPS\Settings::i()->ga_code )
{
\IPS\Settings::i()->changeValues( array( 'ga_enabled' => 1 ) );
}
else
{
throw new \DomainException;
}
}
else
{
\IPS\Settings::i()->changeValues( array( 'ga_enabled' => 0 ) );
}
}
}
@@ -0,0 +1,133 @@
<?php
/**
* @brief Community Enhancements
* @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 2021
*/
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 Enhancement
*/
class _GoogleTagManager
{
/**
* @brief Enhancement is enabled?
*/
public $enabled = FALSE;
/**
* @brief IPS-provided enhancement?
*/
public $ips = FALSE;
/**
* @brief Enhancement has configuration options?
*/
public $hasOptions = TRUE;
/**
* @brief Icon data
*/
public $icon = "google_tag_manager.png";
/**
* Constructor
*
* @return void
*/
public function __construct()
{
$this->enabled = ( \IPS\Settings::i()->googletag_enabled and \IPS\Settings::i()->googletag_head_code and \IPS\Settings::i()->googletag_noscript_code );
}
/**
* Edit
*
* @return void
*/
public function edit()
{
$validation = function( $val ) {
if ( $val and ( !\IPS\Request::i()->googletag_head_code or !\IPS\Request::i()->googletag_noscript_code ) )
{
throw new \DomainException('googletag_code_required');
}
};
$form = new \IPS\Helpers\Form;
$form->add( new \IPS\Helpers\Form\YesNo( 'googletag_enabled', \IPS\Settings::i()->googletag_enabled, FALSE, array(), $validation ) );
if ( \IPS\Settings::i()->core_datalayer_enabled )
{
$form->add( new \IPS\Helpers\Form\YesNo( 'core_datalayer_use_gtm', \IPS\Settings::i()->core_datalayer_use_gtm, FALSE ) );
$form->add( new \IPS\Helpers\Form\Text( 'core_datalayer_gtmkey', \IPS\Settings::i()->core_datalayer_gtmkey, FALSE ) );
}
$form->add( new \IPS\Helpers\Form\Codemirror( 'googletag_head_code', \IPS\Settings::i()->googletag_head_code, FALSE, array( 'height' => 150, 'mode' => 'javascript' ), NULL, NULL, NULL, 'googletag_head_code' ) );
$form->add( new \IPS\Helpers\Form\Codemirror( 'googletag_noscript_code', \IPS\Settings::i()->googletag_noscript_code, FALSE, array( 'height' => 150, 'mode' => 'javascript' ), NULL, NULL, NULL, 'googletag_noscript_code' ) );
if ( $form->values() )
{
try
{
$form->saveAsSettings();
\IPS\Output::i()->inlineMessage = \IPS\Member::loggedIn()->language()->addToStack('saved');
}
catch ( \LogicException $e )
{
$form->error = $e->getMessage();
}
}
\IPS\Output::i()->sidebar['actions'] = array(
'help' => array(
'title' => 'learn_more',
'icon' => 'question-circle',
'link' => \IPS\Http\Url::ips( 'docs/googletagmanager' ),
'target' => '_blank'
),
);
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global' )->block( 'enhancements__core_GoogleTagManager', $form );
}
/**
* Enable/Disable
*
* @param $enabled bool Enable/Disable
* @return void
* @throws \LogicException
*/
public function toggle( $enabled )
{
if ( $enabled )
{
if ( \IPS\Settings::i()->googletag_head_code and \IPS\Settings::i()->googletag_noscript_code )
{
\IPS\Settings::i()->changeValues( array( 'googletag_enabled' => 1 ) );
}
else
{
throw new \DomainException;
}
}
else
{
\IPS\Settings::i()->changeValues( array( 'googletag_enabled' => 0 ) );
}
}
}
@@ -0,0 +1,79 @@
<?php
/**
* @brief Community Enhancements
* @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 11 Jan 2022
*/
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 Enhancement
*/
class _IndexNow
{
/**
* @brief Enhancement is enabled?
*/
public $enabled = FALSE;
/**
* @brief IPS-provided enhancement?
*/
public $ips = FALSE;
/**
* @brief Enhancement has configuration options?
*/
public $hasOptions = FALSE;
/**
* @brief Icon data
*/
public $icon = "";
/**
* Constructor
*
* @return void
*/
public function __construct()
{
$this->enabled = \IPS\Settings::i()->indexnow_enabled;
}
/**
* Enable/Disable
*
* @param $enabled bool Enable/Disable
* @return void
* @throws \LogicException
*/
public function toggle( $enabled )
{
/* If we're disabling, just disable */
if( !$enabled )
{
\IPS\Settings::i()->changeValues( array( 'indexnow_enabled' => 0 ) );
\IPS\Settings::i()->changeValues( array( 'indexnow_key' => '' ) );
}
else
{
$key = \IPS\Login::generateRandomString();
\IPS\Settings::i()->changeValues( array( 'indexnow_enabled' => 1 ) );
\IPS\Settings::i()->changeValues( array( 'indexnow_key' => $key ) );
}
}
}
@@ -0,0 +1,127 @@
<?php
/**
* @brief Community Enhancements
* @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 2021
*/
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 Enhancement
*/
class _Matomo
{
/**
* @brief Enhancement is enabled?
*/
public $enabled = FALSE;
/**
* @brief IPS-provided enhancement?
*/
public $ips = FALSE;
/**
* @brief Enhancement has configuration options?
*/
public $hasOptions = TRUE;
/**
* @brief Icon data
*/
public $icon = "matomo.png";
/**
* Constructor
*
* @return void
*/
public function __construct()
{
$this->enabled = ( \IPS\Settings::i()->matomo_enabled and \IPS\Settings::i()->matomo_code );
}
/**
* Edit
*
* @return void
*/
public function edit()
{
$validation = function( $val ) {
if ( $val and !\IPS\Request::i()->matomo_code )
{
throw new \DomainException('matomo_code_required');
}
};
$form = new \IPS\Helpers\Form;
$form->add( new \IPS\Helpers\Form\YesNo( 'matomo_enabled', \IPS\Settings::i()->matomo_enabled, FALSE, array(), $validation ) );
$form->add( new \IPS\Helpers\Form\Codemirror( 'matomo_code', \IPS\Settings::i()->matomo_code, FALSE, array( 'height' => 150, 'mode' => 'javascript' ), NULL, NULL, NULL, 'matomo_code' ) );
if ( $form->values() )
{
try
{
$form->saveAsSettings();
\IPS\Output::i()->inlineMessage = \IPS\Member::loggedIn()->language()->addToStack('saved');
}
catch ( \LogicException $e )
{
$form->error = $e->getMessage();
}
}
\IPS\Output::i()->sidebar['actions'] = array(
'help' => array(
'title' => 'learn_more',
'icon' => 'question-circle',
'link' => \IPS\Http\Url::ips( 'docs/matomo' ),
'target' => '_blank'
),
);
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global' )->block( 'enhancements__core_Matomo', $form );
}
/**
* Enable/Disable
*
* @param $enabled bool Enable/Disable
* @return void
* @throws \LogicException
*/
public function toggle( $enabled )
{
if ( $enabled )
{
if ( \IPS\Settings::i()->matomo_code )
{
\IPS\Settings::i()->changeValues( array( 'matomo_enabled' => 1 ) );
}
else
{
throw new \DomainException;
}
}
else
{
\IPS\Settings::i()->changeValues( array( 'matomo_enabled' => 0 ) );
}
}
}
@@ -106,20 +106,7 @@ class _Viglink
}
else
{
$json = $this->_makeApiCall();
$form = new \IPS\Helpers\Form;
$form->attributes['data-controller'] = 'core.admin.core.viglink';
$form->attributes['data-viglinkUrl'] = $json['URL'];
$form->add( new \IPS\Helpers\Form\Radio( 'viglink_account_type', 'create', FALSE, array(
'options' => array(
'create' => 'viglink_account_type_create',
'existing' => 'viglink_account_existing'
),
'toggles' => array(
'existing' => array( 'viglink_api_key' ),
),
) ) );
$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' )
{
@@ -129,16 +116,7 @@ class _Viglink
if ( $values = $form->values() )
{
if ( $values['viglink_account_type'] == 'create' )
{
$values['viglink_api_key'] = $json['API_KEY'];
$values['viglink_subid'] = $json['SUBID'];
}
else
{
$values['viglink_subid'] = '';
}
$values['viglink_subid'] = '';
$values['viglink_enabled'] = TRUE;
unset( $values['viglink_account_type'] );
$form->saveAsSettings( $values );
@@ -150,45 +128,6 @@ class _Viglink
\IPS\Output::i()->output = $form;
}
/**
* Make call to IPS to get API keys
*
* @return array
*/
protected function _makeApiCall()
{
$subId = NULL;
if ( \IPS\Settings::i()->ipb_reg_number )
{
$exploded = explode( '-', \IPS\Settings::i()->ipb_reg_number );
if ( isset( $exploded[3] ) )
{
$subId = $exploded[3];
}
}
if ( $subId === NULL )
{
$subId = \IPS\Settings::i()->board_url;
if ( \strlen( $subId ) > 32 )
{
$subId = str_replace( array( 'http://', 'https://' ), '', $subId );
}
if ( \strlen( $subId ) > 32 )
{
$subId = md5( $subId );
}
}
try
{
return \IPS\Http\Url::ips( "viglink/{$subId}" )->request()->get()->decodeJson();
}
catch ( \IPS\Http\Url\Exception $e )
{
\IPS\Output::i()->error( 'viglink_error', '5S144/2', 500, '' );
}
}
/**
* Enable/Disable
*
@@ -28,14 +28,17 @@ class _Zapier
*
* @return array
*/
protected static function apiKeyPermissions()
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 ),
);
@@ -163,7 +166,7 @@ class _Zapier
*/
public static function isAvailable()
{
return \IPS\CIC;
return TRUE;
}
/**
@@ -275,19 +278,22 @@ class _Zapier
*/
protected function testSettings()
{
if ( \IPS\Request::i()->isCgi() )
{
throw new \DomainException( 'zapier_error_cgi' );
}
if ( !\IPS\Settings::i()->use_friendly_urls or !\IPS\Settings::i()->htaccess_mod_rewrite )
{
throw new \DomainException( 'zapier_error_friendly_urls' );
}
$url = \IPS\Http\Url::external( rtrim( \IPS\Settings::i()->base_url, '/' ) . '/api/core/hello' );
try
{
$response = $url->request()->login( 'test', '' )->get()->decodeJson();
if ( \IPS\Request::i()->isCgi() )
{
$response = $url->setQueryString( 'key', 'test' )->request()->get()->decodeJson();
}
else
{
$response = $url->request()->login( 'test', '' )->get()->decodeJson();
}
if ( isset( $response['errorMessage'] ) AND $response['errorMessage'] == 'IP_ADDRESS_BANNED' )
{
throw new \Exception;
@@ -36,7 +36,7 @@ class _Status
*/
public function __construct( $member = NULL )
{
if ( \IPS\Settings::i()->profile_comments and ( $member === NULL or $member->canAccessModule( \IPS\Application\Module::get( 'core', 'status', 'front' ) ) ) )
if ( \IPS\Settings::i()->profile_comments and ( $member === NULL or ( $member->canAccessModule( \IPS\Application\Module::get( 'core', 'members', 'front' ) ) AND $member->canAccessModule( \IPS\Application\Module::get( 'core', 'status', 'front' ) ) ) ) )
{
$this->classes[] = 'IPS\core\Statuses\Status';
}
@@ -116,6 +116,10 @@ class _Admin
case 'forumsSavedAction':
return \IPS\Http\Url::internal( 'app=forums&module=forums&controller=savedActions&do=form&id=' . $id1, 'admin' );
break;
case 'editor_stored_replies':
return \IPS\Http\Url::internal( 'app=core&module=editor&controller=storedreplies&do=form&id=' . $id1, 'admin' );
break;
}
return NULL;
@@ -0,0 +1,208 @@
<?php
/**
* @brief Editor Extension: Alerts
* @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 12 May 2022
*/
namespace IPS\core\extensions\core\EditorLocations;
/* 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;
}
/**
* Editor Extension: Alerts
*/
class _Alert
{
/**
* Can we use HTML in this editor?
*
* @param \IPS\Member $member The member
* @param \IPS\Helpers\Form\Editor $field The editor field
* @return bool|null NULL will cause the default value (based on the member's permissions) to be used, and is recommended in most cases. A boolean value will override that.
*/
public function canUseHtml( $member, $field )
{
return NULL;
}
/**
* Can we use attachments in this editor?
*
* @param \IPS\Member $member The member
* @param \IPS\Helpers\Form\Editor $field The editor field
* @return bool|null NULL will cause the default value (based on the member's permissions) to be used, and is recommended in most cases. A boolean value will override that.
*/
public function canAttach( $member, $field )
{
return NULL;
}
/**
* Permission check for attachments
*
* @param \IPS\Member $member The member
* @param int|null $id1 Primary ID
* @param int|null $id2 Secondary ID
* @param string|null $id3 Arbitrary data
* @param array $attachment The attachment data
* @param bool $viewOnly If true, just check if the user can see the attachment rather than download it
* @return bool
*/
public function attachmentPermissionCheck( $member, $id1, $id2, $id3, $attachment, $viewOnly=FALSE )
{
return \IPS\core\Alerts\Alert::load( $id1 )->canView( $member );
}
/**
* Attachment lookup
*
* @param int|null $id1 Primary ID
* @param int|null $id2 Secondary ID
* @param string|null $id3 Arbitrary data
* @return \IPS\Http\Url|\IPS\Content|\IPS\Node\Model
* @throws \LogicException
*/
public function attachmentLookup( $id1, $id2, $id3 )
{
return \IPS\core\Alerts\Alert::load( $id1 );
}
/**
* Rebuild attachment images in non-content item areas
*
* @param int|null $offset Offset to start from
* @param int|null $max Maximum to parse
* @return int Number completed
* @note This method is optional and will only be called if it exists
*/
public function rebuildAttachmentImages( $offset, $max )
{
return $this->performRebuild( $offset, $max, array( 'IPS\Text\Parser', 'rebuildAttachmentUrls' ) );
}
/**
* Rebuild content post-upgrade
*
* @param int|null $offset Offset to start from
* @param int|null $max Maximum to parse
* @return int Number completed
* @note This method is optional and will only be called if it exists
*/
public function rebuildContent( $offset, $max )
{
return $this->performRebuild( $offset, $max, array( 'IPS\Text\LegacyParser', 'parseStatic' ) );
}
/**
* @brief Use the cached image URL instead of the original URL
*/
protected $proxyUrl = FALSE;
/**
* Rebuild content to add or remove image proxy
*
* @param int|null $offset Offset to start from
* @param int|null $max Maximum to parse
* @param bool $proxyUrl Use the cached image URL instead of the original URL
* @return int Number completed
* @note This method is optional and will only be called if it exists
*/
public function rebuildImageProxy( $offset, $max, $proxyUrl = FALSE )
{
$this->proxyUrl = $proxyUrl;
return $this->performRebuild( $offset, $max, 'parseImageProxy' );
}
/**
* @brief Store lazy loading status ( true = enabled )
*/
protected $_lazyLoadStatus = null;
/**
* Rebuild content to add or remove lazy loading
*
* @param int|null $offset Offset to start from
* @param int|null $max Maximum to parse
* @param bool $status Enable/Disable lazy loading
* @return int Number completed
* @note This method is optional and will only be called if it exists
*/
public function rebuildLazyLoad( $offset, $max, $status=TRUE )
{
$this->_lazyLoadStatus = $status;
return $this->performRebuild( $offset, $max, 'parseLazyLoad' );
}
/**
* Perform rebuild - abstracted as the call for rebuildContent() and rebuildAttachmentImages() is nearly identical
*
* @param int|null $offset Offset to start from
* @param int|null $max Maximum to parse
* @param callable $callback Method to call to rebuild content
* @return int Number completed
*/
protected function performRebuild( $offset, $max, $callback )
{
$did = 0;
foreach( \IPS\Db::i()->select( '*', 'core_alerts', "alert_content IS NOT NULL", 'alert_id ASC', array( $offset, $max ) ) as $alert )
{
$did++;
/* Update */
try
{
if( $callback == 'parseImageProxy' )
{
$rebuilt = \IPS\Text\Parser::removeImageProxy( $alert['alert_content'], $this->proxyUrl );
}
elseif( $callback == 'parseLazyLoad' )
{
$rebuilt = \IPS\Text\Parser::parseLazyLoad( $alert['alert_content'], $this->_lazyLoadStatus );
}
else
{
$rebuilt = $callback( $alert['alert_content'], \IPS\Member::load( $alert['alert_member_id'] ), FALSE, 'core_Alert' );
}
}
catch( \InvalidArgumentException $e )
{
if( $callback[1] == 'parseStatic' AND $e->getcode() == 103014 )
{
$rebuilt = preg_replace( "#\[/?([^\]]+?)\]#", '', $alert['alert_content'] );
}
else
{
throw $e;
}
}
if( $rebuilt !== FALSE )
{
\IPS\Db::i()->update( 'core_alerts', array( 'alert_content' => $rebuilt ), array( 'alert_id=?', $alert['alert_id'] ) );
}
}
return $did;
}
/**
* Total content count to be used in progress indicator
*
* @return int Total Count
*/
public function contentCount()
{
return \IPS\Db::i()->select( 'COUNT(*) as count', 'core_alerts', "alert_content IS NOT NULL" )->first();
}
}
@@ -44,6 +44,37 @@ class _Members
{
return NULL;
}
/**
* Can whatever is posted in this editor be moderated?
* If this returns TRUE, we must ensure the content is ran through word, link and image filters
*
* @param \IPS\Member $member The member
* @param \IPS\Helpers\Form\Editor $field The editor field
* @return bool
*/
public function canBeModerated( $member, $field )
{
/* Creating/editing an status update */
if ( preg_match( '/^status(\d+)?(?:_\d+)?$/', $field->options['autoSaveKey'] ) )
{
return TRUE;
}
/* Creating/editing a reply to a status update */
elseif ( preg_match( '/^(?:reply|editComment)\-core\/members-(\d+)$/', $field->options['autoSaveKey'] ) )
{
return TRUE;
}
/* Unknown */
else
{
if ( \IPS\IN_DEV )
{
throw new \RuntimeException( 'Unknown canBeModerated: ' . $field->options['autoSaveKey'] );
}
return FALSE;
}
}
/**
* Permission check for attachments
@@ -0,0 +1,120 @@
<?php
/**
* @brief File Storage Extension: Ranks
* @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 25 Mar 2022
*/
namespace IPS\core\extensions\core\FileStorage;
/* 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;
}
/**
* File Storage Extension: Ranks
*/
class _Ranks
{
/**
* Count stored files
*
* @return int
*/
public function count()
{
return \IPS\Db::i()->select( 'COUNT(*)', 'core_member_ranks', 'icon IS NOT NULL' )->first();
}
/**
* Move stored files
*
* @param int $offset This will be sent starting with 0, increasing to get all files stored by this extension
* @param int $storageConfiguration New storage configuration ID
* @param int|NULL $oldConfiguration Old storage configuration ID
* @throws \UnderflowException When file record doesn't exist. Indicating there are no more files to move
* @return void|int An offset integer to use on the next cycle, or nothing
*/
public function move( $offset, $storageConfiguration, $oldConfiguration=NULL )
{
$rankData = \IPS\Db::i()->select( '*', 'core_member_ranks', 'icon IS NOT NULL', 'id', array( $offset, 1 ) )->first();
try
{
$file = \IPS\File::get( $oldConfiguration ?: 'core_Ranks', $rankData['icon'] )->move( $storageConfiguration );
if ( (string) $file != $rankData['icon'] )
{
\IPS\Db::i()->update( 'core_member_ranks', array( 'icon' => (string) $file ), array( 'id=?', $rankData['id'] ) );
}
}
catch( \Exception $e )
{
/* Any issues are logged */
}
}
/**
* Fix all URLs
*
* @param int $offset This will be sent starting with 0, increasing to get all files stored by this extension
* @return void
*/
public function fixUrls( $offset )
{
$rankData = \IPS\Db::i()->select( '*', 'core_member_ranks', 'icon IS NOT NULL', 'id', array( $offset, 1 ) )->first();
try
{
if ( $new = \IPS\File::repairUrl( $rankData['icon'] ) )
{
\IPS\Db::i()->update( 'core_member_ranks', [ 'icon' => $new ], array( 'id=?', $rankData['id'] ) );
}
}
catch( \Exception $e ) { }
}
/**
* Check if a file is valid
*
* @param string $file The file path to check
* @return bool
*/
public function isValidFile( $file )
{
try
{
$rankData = \IPS\Db::i()->select( '*', 'core_member_ranks', array( 'icon=?', (string) $file ) )->first();
return TRUE;
}
catch ( \UnderflowException $e )
{
return FALSE;
}
}
/**
* Delete all stored files
*
* @return void
*/
public function delete()
{
foreach( \IPS\Db::i()->select( '*', 'core_member_ranks', 'icon IS NOT NULL' ) as $rankData )
{
try
{
\IPS\File::get( 'core_member_ranks', $rankData['icon'] )->delete();
}
catch( \Exception $e ){}
}
}
}
@@ -54,7 +54,7 @@ class _Reaction
/* Any issues are logged */
}
if ( $url != $reaction['reaction_icon'] )
if ( !empty( $url ) AND $url != $reaction['reaction_icon'] )
{
\IPS\Db::i()->update( 'core_reactions', array( 'reaction_icon' => $url ), array( 'reaction_id=?', $reaction['reaction_id'] ) );
unset( \IPS\Data\Store::i()->reactions );
@@ -180,6 +180,10 @@ class _Content
return \IPS\Theme::i()->getTemplate( 'members' )->moderationLimits( $element->name, $element->value );
} ), NULL, NULL, NULL, 'g_mod_post_unit' ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'g_bypass_badwords', $group->g_bypass_badwords ) );
if ( \IPS\Settings::i()->ips_imagescanner_enable )
{
\IPS\Member::loggedIn()->language()->words['g_bypass_badwords'] = \IPS\Member::loggedIn()->language()->addToStack('g_bypass_badwords_images');
}
/* Promotion */
if( $group->g_id != \IPS\Settings::i()->guest_group )
@@ -45,7 +45,7 @@ class _GroupSettings
}
catch ( \Exception $e ) { }
}
$form->add( new \IPS\Helpers\Form\Upload( 'g_icon', $icon, FALSE, array( 'storageExtension' => 'core_Theme', 'allowedFileTypes' => array( 'gif', 'png', 'jpeg', 'jpg' ) ) ) );
$form->add( new \IPS\Helpers\Form\Upload( 'g_icon', $icon, FALSE, array( 'storageExtension' => 'core_Theme', 'allowedFileTypes' => \IPS\Image::supportedExtensions() ) ) );
/* Prefix/Suffix */
$form->add( new \IPS\Helpers\Form\Custom( 'g_prefixsuffix', array( 'prefix' => $group->prefix, 'suffix' => $group->suffix ), FALSE, array(
@@ -171,7 +171,7 @@ class _GroupSettings
/* Search */
if ( $group->canAccessModule( \IPS\Application\Module::get( 'core', 'search', 'front' ) ) )
{
$form->add( new \IPS\Helpers\Form\Interval( 'g_search_flood', $group->g_search_flood, FALSE, array( 'valueAs' => \IPS\Helpers\Form\Interval::SECONDS, 'unlimited' => 0 ) ) );
$form->add( new \IPS\Helpers\Form\Interval( 'g_search_flood', $group->g_id ? $group->g_search_flood : 0, FALSE, array( 'valueAs' => \IPS\Helpers\Form\Interval::SECONDS, 'unlimited' => 0 ) ) );
}
/* Privacy */
@@ -380,4 +380,19 @@ class _GroupSettings
$newGroup->save();
}
}
/**
* Can this group be deleted?
*
* @param \IPS\Member\Group $group The group
* @return void
*/
public function canDelete( $group ) : bool
{
if ( \in_array( $group->g_id, array( \IPS\Settings::i()->guest_group, \IPS\Settings::i()->member_group, \IPS\Settings::i()->admin_group ) ) )
{
return FALSE;
}
return TRUE;
}
}
@@ -59,25 +59,44 @@ class _Content extends \IPS\Content\ExtensionGenerator
public function findByIp( $ip, \IPS\Http\Url $baseUrl = NULL )
{
$class = $this->class;
if( !isset( $class::$databaseColumnMap['ip_address'] ) OR !$class::$databaseColumnMap['ip_address'] )
{
return NULL;
}
if ( ! \IPS\Application::appIsEnabled( $class::$application ) )
{
return NULL;
}
$where = array( "{$class::$databasePrefix}{$class::$databaseColumnMap['ip_address']} LIKE ?" , $ip );
/* Don't need Posts Before Registration */
if ( isset( $class::$databaseColumnMap['hidden'] ) )
{
$where[0] .= " AND {$class::$databasePrefix}{$class::$databaseColumnMap['hidden']} <> -3";
}
if ( isset( $class::$databaseColumnMap['approved'] ) )
{
$where[0] .= " AND {$class::$databasePrefix}{$class::$databaseColumnMap['approved']} <> -3";
}
/* Does the class have any filters? */
if( method_exists( $class,'findByIPWhere') )
{
$where[0] .= $class::findByIPWhere();
}
/* Return count */
if ( $baseUrl === NULL )
{
return \IPS\Db::i()->select( 'COUNT(*)', $class::$databaseTable, array( "{$class::$databasePrefix}{$class::$databaseColumnMap['ip_address']} LIKE ?", $ip ) )->first();
return \IPS\Db::i()->select( 'COUNT(*)', $class::$databaseTable, $where )->first();
}
/* Init Table */
$table = new \IPS\Helpers\Table\Db( $class::$databaseTable, $baseUrl, array( "{$class::$databasePrefix}{$class::$databaseColumnMap['ip_address']} LIKE ?", $ip ) );
$table = new \IPS\Helpers\Table\Db( $class::$databaseTable, $baseUrl, $where );
$table->tableTemplate = array( \IPS\Theme::i()->getTemplate( 'tables', 'core', 'admin' ), 'table' );
$table->rowsTemplate = array( \IPS\Theme::i()->getTemplate( 'tables', 'core', 'admin' ), 'rows' );
@@ -103,6 +103,22 @@ class _BasicInformation extends \IPS\core\MemberACPProfile\Block
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('member_export_pi_title') )
);
}
/* Data Layer PII Option */
if (
\IPS\Settings::i()->core_datalayer_enabled AND
\IPS\Settings::i()->core_datalayer_include_pii AND
\IPS\Settings::i()->core_datalayer_member_pii_choice AND
( ( $this->member->isAdmin() AND \IPS\Member::loggedIn()->hasAcpRestriction('core', 'members', 'member_edit_admin' ) ) OR \IPS\Member::loggedIn()->hasAcpRestriction( 'core', 'members', 'member_edit' ) )
)
{
$enabled = $this->member->datalayer_collect_pii;
$accountActions[] = array(
'title' => $enabled ? 'datalayer_omit_member_pii' : 'datalayer_collect_member_pii',
'icon' => 'id-card',
'link' => \IPS\Http\Url::internal( "app=core&module=members&controller=members&do=toggleDataLayerPii&id={$this->member->member_id}" ),
);
}
$activeSubscription = FALSE;
if ( \IPS\Application::appIsEnabled('nexus') and \IPS\Settings::i()->nexus_subs_enabled ) // I know... this should really be a hook... I won't tell if you won't
@@ -41,14 +41,6 @@ class _OAuth extends \IPS\core\MemberACPProfile\Block
$client = \IPS\Api\OAuthClient::load( $token['client_id'] );
$title = $client->_title;
if ( $count === 1 and $token['issue_user_agent'] )
{
$agent = \IPS\Http\UserAgent::parse( $token['issue_user_agent'] );
if ( $agent->ipsApp )
{
$title = \IPS\Member::loggedIn()->language()->addToStack( 'mobile_app_user_agent', FALSE, array( 'sprintf' => array( $agent->browserVersion, $agent->platform ) ) );
}
}
$tokens[] = array(
'title' => $title,
@@ -46,11 +46,11 @@ class _Points extends \IPS\core\MemberACPProfile\Block
'icon' => 'pencil',
'link' => $this->member->acpUrl()->setQueryString('do', 'points')->csrf(),
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('acp_profile_points_manage_title' ) ),
'title' => \IPS\Member::loggedIn()->language()->addToStack('acp_profile_edit_points', NULL, [ 'sprintf' => $this->member->name ] ),
'title' => \IPS\Member::loggedIn()->language()->addToStack( 'acp_profile_edit_points', FALSE, [ 'sprintf' => $this->member->name ] ),
);
}
$table = new \IPS\Helpers\Table\Db( 'core_points_log', $this->member->acpUrl()->setQueryString( array( 'do' => 'editBlock', 'block' => \get_class( $this ) ) ), [ [ 'member=?', $this->member->member_id ] ] );
$table = new \IPS\Helpers\Table\Db( 'core_points_log', $this->member->acpUrl()->setQueryString( array( 'do' => 'editBlock', 'block' => \get_class( $this ) ) ), [ [ '`member`=?', $this->member->member_id ] ] );
$table->joins[] = [
'select' => 'action,identifier',
'from' => 'core_achievements_log',
@@ -75,8 +75,14 @@ class _Points extends \IPS\core\MemberACPProfile\Block
if ( isset( $exploded[1] ) )
{
$extension = \IPS\Application::load( $exploded[0] )->extensions( 'core', 'AchievementAction' )[$exploded[1]];
return $extension->logRow( $row['identifier'], explode( ',', $row['actor'] ) );
try{
$extension = \IPS\Application::load( $exploded[0] )->extensions( 'core', 'AchievementAction' )[$exploded[1]];
return $extension->logRow( $row['identifier'], explode( ',', $row['actor'] ) );
}
catch( \OutOfRangeException $e )
{
return \IPS\Member::loggedIn()->language()->addToStack( 'acp_points_rule_deleted');
}
}
else if ( isset( $row['rules'] ) and empty( $row['rules'] ) )
{
@@ -30,6 +30,11 @@ class _Referrals extends \IPS\core\MemberACPProfile\Block
*/
public function output( $edit = FALSE )
{
if( !\IPS\Settings::i()->ref_on )
{
return "";
}
$url = $this->member->acpUrl()->setQueryString( array( 'do' => 'editBlock', 'block' => \get_class( $this ) ) );
$referCount = \IPS\Db::i()->select( 'COUNT(*)', 'core_referrals', array( 'referred_by=?', $this->member->member_id ) )->first();
$referrals = new \IPS\Helpers\Table\Db( 'core_referrals', $url, array( 'referred_by=?', $this->member->member_id ) );
@@ -0,0 +1,362 @@
<?php
/**
* @brief Member filter extension: Achievements
* @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 14 Sept 2021
*/
namespace IPS\core\extensions\core\MemberFilter;
/* 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;
}
/**
* @brief Member filter: Achievements
*/
class _Achievements
{
/**
* Determine if the filter is available in a given area
*
* @param string $area Area to check
* @return bool
*/
public function availableIn( $area )
{
return \in_array( $area, array( 'bulkmail', 'group_promotions', 'automatic_moderation' ) );
}
/**
* Get Setting Field
*
* @param mixed $criteria Value returned from the save() method
* @return array Array of form elements
*/
public function getSettingField( $criteria )
{
/* Get our badges */
$badges['options'] = [];
foreach( \IPS\core\Achievements\Badge::roots() as $badge )
{
$badges['options'][ $badge->id ] = $badge->_title;
}
/* If all *available* options are selected, we want to choose 'all' for consistency on create vs edit */
$_options = array_keys( $badges['options'] );
if( isset( $criteria['achievement_badges'] ) )
{
if( ! \count( array_diff( $_options, explode( ',', $criteria['achievement_badges'] ) ) ) )
{
$criteria['achievement_badges'] = 'all';
}
}
/* Get our ranks */
$ranks['options'] = [];
foreach( \IPS\core\Achievements\Rank::roots() as $rank )
{
$ranks['options'][ $rank->id ] = $rank->_title;
}
/* If all *available* options are selected, we want to choose 'all' for consistency on create vs edit */
$_options = array_keys( $ranks['options'] );
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' );
}
), NULL, NULL, NULL, 'mp_points' ),
new \IPS\Helpers\Form\YesNo( 'mf_badges_choose', ( isset( $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(
'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(
'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(
'options' => $ranks['options'],
'multiple' => TRUE,
'unlimited' => 'all',
'unlimitedLang' => 'mf_achievements_all_ranks',
'impliedUnlimited' => TRUE
), NULL, NULL, NULL, 'mf_ranks' )
);
}
/**
* Save the filter data
*
* @param array $post Form values
* @return mixed False, or an array of data to use later when filtering the members
* @throws \LogicException
*/
public function save( $post )
{
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'
);
}
/**
* Get where clause to add to the member retrieval database query
*
* @param mixed $data The array returned from the save() method
* @return array|NULL Where clause - must be a single array( "clause" )
*/
public function getQueryWhereClause( $data )
{
$where = [];
if ( $data['achievement_points_operator'] and $data['achievement_points_value'] )
{
switch ( $data['achievement_points_operator'] )
{
case 'gt':
$where[] = "achievements_points > " . (int) $data['achievement_points_value'];
break;
case 'lt':
$where[] = "achievements_points < " . (int) $data['achievement_points_value'];
break;
case 'eq':
$where[] = "achievements_points = " . (int) $data['achievement_points_value'];
break;
}
}
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 );
$where[] = 'core_members.member_id IN(' . $badgeSelect . ')';
}
if ( $data['achievement_ranks'] != 'ignore' )
{
$ranks = \IPS\core\Achievements\Rank::getStore();
$rankWhere = [];
foreach( explode( ',', $data['achievement_ranks'] ) as $val )
{
$minPoints = 0;
$maxPoints = 0;
$minSet = FALSE;
foreach ( $ranks as $rank )
{
if ( !$minSet and $rank->id == $val )
{
$minPoints = $rank->points;
$minSet = TRUE;
}
if ( $minSet and $rank->points > $minPoints )
{
$maxPoints = $rank->points;
break;
}
}
if ( $minPoints and $maxPoints )
{
$rankWhere[] = '( achievements_points BETWEEN ' . $minPoints . ' AND ' . ( $maxPoints - 1 ) . ' )';
}
elseif ( $minPoints )
{
$rankWhere[] = '( achievements_points >= ' . $minPoints . ' )';
}
elseif ( $maxPoints )
{
$rankWhere[] = '( achievements_points < ' . $maxPoints . ' )';
}
}
if ( \count( $rankWhere ) )
{
$where[] = '(' . implode( ' OR ', $rankWhere ) . ')';
}
}
return \count( $where ) ? '(' . implode( ' AND ', $where ) . ')' : NULL;
}
/**
* Determine if a member matches specified filters
*
* @note This is only necessary if availableIn() includes group_promotions
* @param \IPS\Member $member Member object to check
* @param array $filters Previously defined filters
* @param object|NULL $object Calling class
* @return bool
*/
public function matches( \IPS\Member $member, $filters, $object=NULL )
{
if ( ( isset( $filters['achievement_points_operator'] ) and ( $filters['achievement_points_operator'] ) and $filters['achievement_points_operator'] != 'any' ) and ( isset( $filters['achievement_points_value'] ) ) )
{
$pass = FALSE;
switch ( $filters['achievement_points_operator'] )
{
case 'gt':
$pass = (bool) ( $member->achievements_points > (int) $filters['achievement_points_value'] );
break;
case 'lt':
$pass = (bool) ( $member->achievements_points < (int) $filters['achievement_points_value'] );
break;
case 'eq':
$pass = (bool) ( $member->achievements_points = (int) $filters['achievement_points_value'] );
break;
}
if ( $pass === FALSE )
{
/* They don't make it this far */
return FALSE;
}
}
/* Lets check badges */
if ( isset( $filters['achievement_badges'] ) and $filters['achievement_badges'] != 'ignore' )
{
if ( ! \IPS\Db::i()->select( 'COUNT(*)', 'core_member_badges', [ 'member=? AND ' . \IPS\Db::i()->in( 'badge', explode( ',', $filters['achievement_badges'] ) ), $member->member_id ] )->first() )
{
/* Did not find a badge, so they failed the match */
return FALSE;
}
}
/* Let's check ranks */
if ( isset( $filters['achievement_ranks'] ) and $filters['achievement_ranks'] != 'ignore' )
{
$ranks = \IPS\core\Achievements\Rank::getStore();
$pass = FALSE;
foreach( explode( ',', $filters['achievement_ranks'] ) as $val )
{
$minPoints = 0;
$maxPoints = 0;
$minSet = FALSE;
foreach ( $ranks as $rank )
{
if ( !$minSet and $rank->id == $val )
{
$minPoints = $rank->points;
$minSet = TRUE;
}
if ( $minSet and $rank->points > $minPoints )
{
$maxPoints = $rank->points;
break;
}
}
if ( $minPoints and $maxPoints )
{
$pass = (bool) $member->achievements_points >= $minPoints AND $member->achievements_points <= ($maxPoints - 1);
}
elseif ( $minPoints )
{
$pass = (bool) $member->achievements_points >= $minPoints;
}
elseif ( $maxPoints )
{
$pass = (bool) $member->achievements_points < $minPoints;
}
}
if ( $pass === FALSE )
{
/* They don't make it this far */
return FALSE;
}
}
/* If we are still here, then there wasn't an appropriate operator (maybe they selected 'any') so return true */
return TRUE;
}
/**
* Return a lovely human description for this rule if used
*
* @param mixed $filters The array returned from the save() method
* @return string|NULL
*/
public function getDescription( $filters )
{
$message = [];
if ( ! empty( $filters['achievement_points_value'] ) and $filters['achievement_points_value'] > 0 )
{
switch ( $filters['achievement_points_operator'] )
{
case 'gt':
$message[] = \IPS\Member::loggedIn()->language()->addToStack( 'member_filter_core_cheev_points_gt_desc', FALSE, array( 'sprintf' => array( $filters['achievement_points_value'] ) ) );
break;
case 'lt':
$message[] = \IPS\Member::loggedIn()->language()->addToStack( 'member_filter_core_cheev_points_lt_desc', FALSE, array( 'sprintf' => array( $filters['achievement_points_value'] ) ) );
break;
case 'eq':
$message[] = \IPS\Member::loggedIn()->language()->addToStack( 'member_filter_core_cheev_points_eq_desc', FALSE, array( 'sprintf' => array( $filters['achievement_points_value'] ) ) );
break;
}
}
if ( $filters['achievement_badges'] != 'ignore' )
{
$count = \count( explode( ',', $filters['achievement_badges'] ) );
if( $count )
{
$message[] = \IPS\Member::loggedIn()->language()->addToStack( 'member_filter_core_cheev_desc', FALSE, [
'sprintf' => [
\IPS\Member::loggedIn()->language()->addToStack( 'recognize_badges_pluralize', FALSE, ['sprintf' => [$count], 'pluralize' => [$count]] )
]] );
}
}
if ( $filters['achievement_ranks'] != 'ignore' )
{
$count = \count( explode( ',', $filters['achievement_badges'] ) );
if ( $count )
{
$message[] = \IPS\Member::loggedIn()->language()->addToStack( 'member_filter_core_cheev_desc', FALSE, [
'sprintf' => [
\IPS\Member::loggedIn()->language()->addToStack( 'achievement_ranks_pluralize', FALSE, ['sprintf' => [$count], 'pluralize' => [$count]] )
]] );
}
}
return \count( $message ) ? implode( ', ', $message ) : NULL;
}
}
@@ -0,0 +1,116 @@
<?php
/**
* @brief Member Filter 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/
* @package Invision Community
* @since 22 Sep 2021
*/
namespace IPS\core\extensions\core\MemberFilter;
/* 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;
}
/**
* Member Filter Extension
*/
class _Banned
{
/**
* Determine if the filter is available in a given area
*
* @param string $area Area to check
* @return bool
*/
public function availableIn( $area )
{
return \in_array( $area, array( 'group_promotions' ) );
}
/**
* Get Setting Field
*
* @param mixed $criteria Value returned from the save() method
* @return array Array of form elements
*/
public function getSettingField( $criteria )
{
$options = array( 'any' => 'any', 'banned' => 'mf_banned_banned', 'notbanned' => 'mf_banned_not_banned' );
return array(
new \IPS\Helpers\Form\Radio( 'mf_banned', isset( $criteria['banned'] ) ? $criteria['banned'] : 'any', FALSE, array( 'options' => $options ) ),
);
}
/**
* Determine if a member matches specified filters
*
* @note This is only necessary if availableIn() includes group_promotions
* @param \IPS\Member $member Member object to check
* @param array $filters Previously defined filters
* @param object|NULL $object Calling class
* @return bool
*/
public function matches( \IPS\Member $member, $filters, $object=NULL )
{
if( !isset( $filters['banned'] ) )
{
return TRUE;
}
switch ( $filters['banned'] )
{
case 'banned':
return ( (int) $member->temp_ban !== 0 );
break;
case 'notbanned':
return empty( $member->temp_ban );
break;
}
return FALSE;
}
/**
* Save the filter data
*
* @param array $post Form values
* @return mixed False, or an array of data to use later when filtering the members
* @throws \LogicException
*/
public function save( $post )
{
return ( isset( $post['mf_banned'] ) and \in_array( $post['mf_banned'], array( 'banned', 'notbanned' ) ) ) ? array( 'banned' => $post['mf_banned'] ) : FALSE;
}
/**
* Get where clause to add to the member retrieval database query
*
* @param mixed $data The array returned from the save() method
* @return array|NULL Where clause - must be a single array( "clause" )
*/
public function getQueryWhereClause( $data )
{
if ( isset( $data['banned'] ) )
{
switch ( $data['banned'] )
{
case 'banned':
return array( "temp_ban<>0" );
break;
case 'notbanned':
return array( "(temp_ban IS NULL OR temp_ban=0)" );
break;
}
}
return NULL;
}
}
@@ -153,13 +153,13 @@ class _Content
switch ( $filters['content_count_operator'] )
{
case 'gt':
return \IPS\Member::loggedIn()->language()->addToStack( 'member_filter_core_content_gt_desc', NULL, array( 'sprintf' => array( $filters['content_count_score'] ) ) );
return \IPS\Member::loggedIn()->language()->addToStack( 'member_filter_core_content_gt_desc', FALSE, array( 'sprintf' => array( $filters['content_count_score'] ) ) );
break;
case 'lt':
return \IPS\Member::loggedIn()->language()->addToStack( 'member_filter_core_content_lt_desc', NULL, array( 'sprintf' => array( $filters['content_count_score'] ) ) );
return \IPS\Member::loggedIn()->language()->addToStack( 'member_filter_core_content_lt_desc', FALSE, array( 'sprintf' => array( $filters['content_count_score'] ) ) );
break;
case 'eq':
return \IPS\Member::loggedIn()->language()->addToStack( 'member_filter_core_content_eq_desc', NULL, array( 'sprintf' => array( $filters['content_count_score'] ) ) );
return \IPS\Member::loggedIn()->language()->addToStack( 'member_filter_core_content_eq_desc', FALSE, array( 'sprintf' => array( $filters['content_count_score'] ) ) );
break;
}
}
@@ -94,7 +94,7 @@ class _Group
if ( \count( $humanGroups ) )
{
return \IPS\Member::loggedIn()->language()->addToStack( 'member_filter_core_group_desc', NULL, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->formatList( $humanGroups ) ) ) );
return \IPS\Member::loggedIn()->language()->addToStack( 'member_filter_core_group_desc', FALSE, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->formatList( $humanGroups ) ) ) );
}
}
@@ -246,16 +246,16 @@ class _Joined
if ( $start and $end )
{
return \IPS\Member::loggedIn()->language()->addToStack( 'member_filter_core_joined_range_desc', NULL, array( 'sprintf' => array( $start->localeDate(), $end->localeDate() ) ) );
return \IPS\Member::loggedIn()->language()->addToStack( 'member_filter_core_joined_range_desc', FALSE, array( 'sprintf' => array( $start->localeDate(), $end->localeDate() ) ) );
}
}
elseif( !empty( $filters['days'] ) AND (int) $filters['days'] )
{
return \IPS\Member::loggedIn()->language()->addToStack( 'member_filter_core_joined_days_desc', NULL, array( 'sprintf' => array( $filters['days'] ) ) );
return \IPS\Member::loggedIn()->language()->addToStack( 'member_filter_core_joined_days_desc', FALSE, array( 'sprintf' => array( $filters['days'] ) ) );
}
elseif( !empty( $filters['days_lt'] ) AND (int) $filters['days_lt'] )
{
return \IPS\Member::loggedIn()->language()->addToStack( 'member_filter_core_joined_days_lt_desc', NULL, array( 'sprintf' => array( $filters['days_lt'] ) ) );
return \IPS\Member::loggedIn()->language()->addToStack( 'member_filter_core_joined_days_lt_desc', FALSE, array( 'sprintf' => array( $filters['days_lt'] ) ) );
}
return NULL;
@@ -260,16 +260,16 @@ class _Lastpost
if ( $start and $end )
{
return \IPS\Member::loggedIn()->language()->addToStack( 'member_filter_core_lastpost_range_desc', NULL, array( 'sprintf' => array( $start->localeDate(), $end->localeDate() ) ) );
return \IPS\Member::loggedIn()->language()->addToStack( 'member_filter_core_lastpost_range_desc', FALSE, array( 'sprintf' => array( $start->localeDate(), $end->localeDate() ) ) );
}
}
elseif( !empty( $filters['days'] ) AND (int) $filters['days'] )
{
return \IPS\Member::loggedIn()->language()->addToStack( 'member_filter_core_lastpost_days_desc', NULL, array( 'sprintf' => array( $filters['days'] ) ) );
return \IPS\Member::loggedIn()->language()->addToStack( 'member_filter_core_lastpost_days_desc', FALSE, array( 'sprintf' => array( $filters['days'] ) ) );
}
elseif( !empty( $filters['days_lt'] ) AND (int) $filters['days_lt'] )
{
return \IPS\Member::loggedIn()->language()->addToStack( 'member_filter_core_lastpost_days_lt_desc', NULL, array( 'sprintf' => array( $filters['days_lt'] ) ) );
return \IPS\Member::loggedIn()->language()->addToStack( 'member_filter_core_lastpost_days_lt_desc', FALSE, array( 'sprintf' => array( $filters['days_lt'] ) ) );
}
return NULL;
@@ -245,16 +245,16 @@ class _Lastvisit
if ( $start and $end )
{
return \IPS\Member::loggedIn()->language()->addToStack( 'member_filter_core_lastvisit_range_desc', NULL, array( 'sprintf' => array( $start->localeDate(), $end->localeDate() ) ) );
return \IPS\Member::loggedIn()->language()->addToStack( 'member_filter_core_lastvisit_range_desc', FALSE, array( 'sprintf' => array( $start->localeDate(), $end->localeDate() ) ) );
}
}
elseif( !empty( $filters['days'] ) AND (int) $filters['days'] )
{
return \IPS\Member::loggedIn()->language()->addToStack( 'member_filter_core_lastvisit_days_desc', NULL, array( 'sprintf' => array( $filters['days'] ) ) );
return \IPS\Member::loggedIn()->language()->addToStack( 'member_filter_core_lastvisit_days_desc', FALSE, array( 'sprintf' => array( $filters['days'] ) ) );
}
elseif( !empty( $filters['days_lt'] ) AND (int) $filters['days_lt'] )
{
return \IPS\Member::loggedIn()->language()->addToStack( 'member_filter_core_lastvisit_days_lt_desc', NULL, array( 'sprintf' => array( $filters['days_lt'] ) ) );
return \IPS\Member::loggedIn()->language()->addToStack( 'member_filter_core_lastvisit_days_lt_desc', FALSE, array( 'sprintf' => array( $filters['days_lt'] ) ) );
}
return NULL;
@@ -296,14 +296,9 @@ class _ProfileFields
}
break;
case 'IPS\Helpers\Form\Date':
if( !$profileFieldData['core_pfieldgroups_' . $group ][ $langKey ] )
{
return FALSE;
}
$start = NULL;
$end = NULL;
if ( $filters[ $langKey ]['start'] )
{
$start = ( new \IPS\DateTime( $filters[ $langKey ]['start'] ) )->getTimestamp();
@@ -313,6 +308,11 @@ class _ProfileFields
$end = ( new \IPS\DateTime( $filters[ $langKey ]['end'] ) )->getTimestamp();
}
if( ( $start OR $end ) AND !$profileFieldData['core_pfieldgroups_' . $group ][ $langKey ] )
{
return FALSE;
}
if( ( $start AND $start > $profileFieldData['core_pfieldgroups_' . $group ][ $langKey ] ) OR ( $end AND $end < $profileFieldData['core_pfieldgroups_' . $group ][ $langKey ] ) )
{
return FALSE;
@@ -153,13 +153,13 @@ class _Reputation
switch ( $filters['reputation_operator'] )
{
case 'gt':
return \IPS\Member::loggedIn()->language()->addToStack( 'member_filter_core_reputation_gt_desc', NULL, array( 'sprintf' => array( $filters['reputation_score'] ) ) );
return \IPS\Member::loggedIn()->language()->addToStack( 'member_filter_core_reputation_gt_desc', FALSE, array( 'sprintf' => array( $filters['reputation_score'] ) ) );
break;
case 'lt':
return \IPS\Member::loggedIn()->language()->addToStack( 'member_filter_core_reputation_lt_desc', NULL, array( 'sprintf' => array( $filters['reputation_score'] ) ) );
return \IPS\Member::loggedIn()->language()->addToStack( 'member_filter_core_reputation_lt_desc', FALSE, array( 'sprintf' => array( $filters['reputation_score'] ) ) );
break;
case 'eq':
return \IPS\Member::loggedIn()->language()->addToStack( 'member_filter_core_reputation_eq_desc', NULL, array( 'sprintf' => array( $filters['reputation_score'] ) ) );
return \IPS\Member::loggedIn()->language()->addToStack( 'member_filter_core_reputation_eq_desc', FALSE, array( 'sprintf' => array( $filters['reputation_score'] ) ) );
break;
}
}
@@ -0,0 +1,87 @@
<?php
/**
* @brief Member Filter 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/
* @package Invision Community
* @since 22 Sep 2021
*/
namespace IPS\core\extensions\core\MemberFilter;
/* 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;
}
/**
* Member Filter Extension
*/
class _Spammer
{
/**
* Determine if the filter is available in a given area
*
* @param string $area Area to check
* @return bool
*/
public function availableIn( $area )
{
return \in_array( $area, array( 'group_promotions' ) );
}
/**
* Get Setting Field
*
* @param mixed $criteria Value returned from the save() method
* @return array Array of form elements
*/
public function getSettingField( $criteria )
{
$options = array( 'any' => 'any', 'spammer' => 'mf_spammer_spammer', 'notspammer' => 'mf_spammer_not_spammer' );
return array(
new \IPS\Helpers\Form\Radio( 'mf_spammer', isset( $criteria['spammer'] ) ? $criteria['spammer'] : 'any', FALSE, array( 'options' => $options ) ),
);
}
/**
* Save the filter data
*
* @param array $post Form values
* @return mixed False, or an array of data to use later when filtering the members
* @throws \LogicException
*/
public function save( $post )
{
return ( isset( $post['mf_spammer'] ) and \in_array( $post['mf_spammer'], array( 'spammer', 'notspammer' ) ) ) ? array( 'spammer' => $post['mf_spammer'] ) : FALSE;
}
/**
* Get where clause to add to the member retrieval database query
*
* @param mixed $data The array returned from the save() method
* @return array|NULL Where clause - must be a single array( "clause" )
*/
public function getQueryWhereClause( $data )
{
if ( isset( $data['spammer'] ) )
{
switch ( $data['spammer'] )
{
case 'spammer':
return array( \IPS\Db::i()->bitwiseWhere( \IPS\Member::$bitOptions['members_bitoptions'], 'bw_is_spammer' ));
break;
case 'notspammer':
return array( \IPS\Db::i()->bitwiseWhere( \IPS\Member::$bitOptions['members_bitoptions'], 'bw_is_spammer', FALSE ) );
break;
}
}
return NULL;
}
}
@@ -126,7 +126,7 @@ class _Staff
$not = TRUE;
}
return array( "(" . \IPS\Db::i()->in( 'member_id', $members, $not ) . " OR " . \IPS\Db::i()->in( 'member_group_id', $groups, $not ) . " OR " . \IPS\Db::i()->findInSet( 'mgroup_others', $groups, $not ) . ")" );
return array( "(" . \IPS\Db::i()->in( 'core_members.member_id', $members, $not ) . " OR " . \IPS\Db::i()->in( 'member_group_id', $groups, $not ) . " OR " . \IPS\Db::i()->findInSet( 'mgroup_others', $groups, $not ) . ")" );
}
return NULL;
}
@@ -0,0 +1,105 @@
<?php
/**
* @brief Member Filter 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/
* @package Invision Community
* @since 22 Sep 2021
*/
namespace IPS\core\extensions\core\MemberFilter;
/* 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;
}
/**
* Member Filter Extension
*/
class _Validating
{
/**
* Determine if the filter is available in a given area
*
* @param string $area Area to check
* @return bool
*/
public function availableIn( $area )
{
return \in_array( $area, array( 'bulkmail' ) );
}
/**
* Get Setting Field
*
* @param mixed $criteria Value returned from the save() method
* @return array Array of form elements
*/
public function getSettingField( $criteria )
{
$options = array( 'any' => 'any', 'validating' => 'mf_validating_validating', 'notvalidating' => 'mf_validating_not_validating' );
return array(
new \IPS\Helpers\Form\Radio( 'mf_validating', isset( $criteria['validating'] ) ? $criteria['validating'] : 'any', FALSE, array( 'options' => $options ) ),
);
}
/**
* Save the filter data
*
* @param array $post Form values
* @return mixed False, or an array of data to use later when filtering the members
* @throws \LogicException
*/
public function save( $post )
{
return ( isset( $post['mf_validating'] ) and \in_array( $post['mf_validating'], array( 'validating', 'notvalidating' ) ) ) ? array( 'validating' => $post['mf_validating'] ) : FALSE;
}
/**
* Get where clause to add to the member retrieval database query
*
* @param mixed $data The array returned from the save() method
* @return array|NULL Where clause - must be a single array( "clause" )
*/
public function getQueryWhereClause( $data )
{
if ( isset( $data['validating'] ) )
{
switch ( $data['validating'] )
{
case 'validating':
return array( "( v.lost_pass=0 AND v.forgot_security=0 AND v.vid IS NOT NULL )" );
break;
case 'notvalidating':
return array( "( v.vid IS NULL )" );
break;
}
}
return NULL;
}
/**
* Callback for member retrieval database query
* Can be used to set joins
*
* @param mixed $data The array returned from the save() method
* @param \IPS\Db\Query $query The query
* @return void
*/
public function queryCallback( $data, &$query )
{
if( isset( $data['validating'] ) )
{
$query->join( [ 'core_validating', 'v' ], "core_members.member_id=v.member_id" );
}
return NULL;
}
}
@@ -45,7 +45,8 @@ class _Core
'admin_mails',
'terms_acceptance',
'points',
'badges'
'badges',
'club_membership'
);
}
@@ -548,7 +549,7 @@ class _Core
foreach ( \IPS\Application::allExtensions( 'core', 'MemberRestrictions', TRUE, 'core', 'Content', FALSE ) as $class )
{
foreach ( $class::changesForHistory( $jsonValue['restrictions'] ) as $v )
foreach ( $class::changesForHistory( $jsonValue['restrictions'], $row ) as $v )
{
$changes[] = $v;
}
@@ -745,8 +746,24 @@ class _Core
}
break;
case 'club_membership':
if( isset( $jsonValue['club_id'] ))
{
try
{
$club = \IPS\Member\Club::load( $jsonValue['club_id'] );
switch( $jsonValue['type'] )
{
case \IPS\Member\Club::STATUS_BANNED:
return \IPS\Member::loggedIn()->language()->addToStack( 'history_removed_from_club', FALSE, array('htmlsprintf' => array( $club->name, $byStaff ) ) );
case \IPS\Member\Club::STATUS_MEMBER:
return \IPS\Member::loggedIn()->language()->addToStack( 'history_added_to_club', FALSE, array('htmlsprintf' => array( $club->name, $byStaff ) ) );
}
}
catch ( \Exception $e ) { }
}
}
$member->logHistory( 'core', 'badges', [ 'action' => 'manual', 'id' => $values['acp_manual_badge']->_id ] );
return $value;
}
@@ -86,12 +86,13 @@ class _Content extends \IPS\core\MemberACPProfile\Restriction
* Get details of a change to show on history
*
* @param array $changes Changes as set in save()
* @param array $row Row of data from member history table.
* @return array
*/
public static function changesForHistory( $changes )
public static function changesForHistory( array $changes, array $row ): array
{
$return = array();
foreach ( array( 'modq' => 'mod_posts', 'nopost' => 'restrict_post', 'banned' => 'temp_ban' ) as $k => $v )
{
if ( isset( $changes[ $v ] ) )
@@ -101,7 +102,7 @@ class _Content extends \IPS\core\MemberACPProfile\Restriction
$c = \IPS\Member::loggedIn()->language()->addToStack( 'moderation_' . $k );
if ( $changes[ $v ]['new'] != -1 )
{
$interval = \IPS\DateTime::formatInterval( ( \is_numeric( $changes[ $v ]['new'] ) ) ? \IPS\DateTime::create()->diff( \IPS\DateTime::ts( $changes[ $v ]['new'] ) ) : new \DateInterval( $changes[ $v ]['new'] ), 2 );
$interval = \IPS\DateTime::formatInterval( ( \is_numeric( $changes[ $v ]['new'] ) ) ? \IPS\DateTime::ts( $row['log_date'] )->diff( \IPS\DateTime::ts( $changes[ $v ]['new'] ) ) : new \DateInterval( $changes[ $v ]['new'] ), 2 );
$c = \IPS\Member::loggedIn()->language()->addToStack( 'history_received_warning_penalty_time', FALSE, array( 'sprintf' => array( $c, $interval ) ) );
}
}
@@ -91,14 +91,15 @@ class _Statuses extends \IPS\core\MemberACPProfile\Restriction
return $return;
}
/**
* Get details of a change to show on history
*
* @param array $changes Changes as set in save()
* @param array $row Row of data from member history table.
* @return array
*/
public static function changesForHistory( $changes )
public static function changesForHistory( array $changes, array $row ): array
{
if ( isset( $changes['bw_no_status_update'] ) )
{
@@ -93,21 +93,22 @@ class _Tags extends \IPS\core\MemberACPProfile\Restriction
{
$return[] = 'restriction_no_tagging';
}
if ( !$this->member->group['bw_disable_prefixes'] and $this->member->members_bitoptions['bw_disable_prefixes'] )
if ( !$this->member->group['gbw_disable_prefixes'] and $this->member->members_bitoptions['bw_disable_prefixes'] )
{
$return[] = 'restriction_no_prefixes';
}
return $return;
}
/**
* Get details of a change to show on history
*
* @param array $changes Changes as set in save()
* @param array $row Row of data from member history table.
* @return array
*/
public static function changesForHistory( $changes )
public static function changesForHistory( array $changes, array $row ): array
{
$return = array();
@@ -110,7 +110,11 @@ class _System
array( 'core_rss_import', 'rss_import_member' ),
array( 'core_anonymous_posts', 'anonymous_member_id' ),
array( 'core_member_badges', 'member' ),
array( 'core_notifications_pwa_keys', 'member' )
array( 'core_notifications_pwa_keys', 'member' ),
array( 'core_members_logins', 'member_id' ),
array( 'core_stream_subscriptions', 'member_id' ),
array( 'core_alerts', 'alert_member_id' ),
array( 'core_alerts', 'alert_recipient_user' ),
) as $toMerge
)
{
@@ -139,7 +143,10 @@ class _System
/* Solved */
\IPS\Db::i()->update( 'core_solved_index', array( 'member_id' => $member->member_id ), array( 'member_id=?', $member2->member_id ), array(), NULL, \IPS\Db::IGNORE );
/* Achievements */
\IPS\Db::i()->update( 'core_achievements_log_milestones', array( 'milestone_member_id' => $member->member_id ), array( 'milestone_member_id=?', $member2->member_id ), array(), NULL, \IPS\Db::IGNORE );
/* 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 ) );
@@ -155,6 +162,7 @@ class _System
\IPS\Db::i()->delete( 'core_acp_notifcations_dismissals', array( '`member`=?', $member2->member_id ) );
\IPS\Db::i()->delete( 'core_acp_notifications_preferences', array( '`member`=?', $member2->member_id ) );
\IPS\Db::i()->delete( 'core_marketplace_tokens', array( 'id=?', $member2->member_id ) );
\IPS\Db::i()->delete( 'core_achievements_log_milestones', array( 'milestone_member_id=?', $member2->member_id ) );
/* Update PM recipient counts */
foreach( \IPS\Db::i()->select( 'map_topic_id', 'core_message_topic_user_map', array( 'map_user_id=?', $member->member_id ) ) as $topicId )
@@ -250,7 +258,10 @@ class _System
\IPS\Db::i()->delete( 'core_notifications', array( 'item_class=? AND item_id=?', 'IPS\\core\\Statuses\\Reply', $reply ) );
/* Remove item from deletion log */
\IPS\Db::i()->delete( 'core_deletion_log', array( "dellog_content_class=? AND dellog_content_id=?", "IPS\\core\\Statuses\\Reply", $reply ) );
\IPS\Db::i()->delete( 'core_deletion_log', array( "dellog_content_class=? AND dellog_content_id=?", "IPS\\core\\Statuses\\Reply", $reply ) );
/* Remove reports */
\IPS\Db::i()->delete( 'core_rc_index', array( "class=? AND content_id=?", "IPS\\core\\Statuses\\Reply", $reply ) );
}
\IPS\Content\Search\Index::i()->removeClassFromSearchIndex( 'IPS\core\Statuses\Reply', NULL, $member->member_id );
@@ -261,6 +272,9 @@ class _System
/* Remove item from deletion log */
\IPS\Db::i()->delete( 'core_deletion_log', array( "dellog_content_class=? AND dellog_content_id=?", "IPS\\core\\Statuses\\Status", $status ) );
/* Remove reports */
\IPS\Db::i()->delete( 'core_rc_index', array( "class=? AND content_id=?", "IPS\\core\\Statuses\\Reply", $status ) );
}
\IPS\Content\Search\Index::i()->removeClassFromSearchIndex( 'IPS\core\Statuses\Status', NULL, $member->member_id );
@@ -306,7 +320,11 @@ class _System
array( 'core_anonymous_posts', 'anonymous_member_id' ),
array( 'core_marketplace_tokens', 'id' ),
array( 'core_member_badges', 'member' ),
array( 'core_notifications_pwa_keys', 'member' )
array( 'core_notifications_pwa_keys', 'member' ),
array( 'core_achievements_log_milestones', 'milestone_member_id' ),
array( 'core_members_logins', 'member_id' ),
array( 'core_stream_subscriptions', 'member_id' ),
array( 'core_alerts_seen', 'seen_member_id' )
) as $toDelete )
{
\IPS\Db::i()->delete( $toDelete[0], array( '`' . $toDelete[1] . '`=?', $member->member_id ) );
@@ -349,6 +367,8 @@ class _System
$member->deletePhoto();
$member->coverPhoto()->delete();
\IPS\Api\Webhook::fire( 'member_delete', $member );
}
/**
@@ -386,5 +406,15 @@ class _System
/* Reset profile completion flag to see if there are any items to complete now permissions are elevated */
$member->members_bitoptions['profile_completed'] = 0;
}
if( \count( $changes ) )
{
$params = [
'member' => $member->apiOutput(),
'changes' => $changes
];
\IPS\Api\Webhook::fire( 'member_edited', $params );
}
}
}
@@ -1,157 +0,0 @@
<?php
/**
* @brief Mobile Navigation Extension: Custom Item
* @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
* @subpackage Core
* @since 12 Jun 2019
*/
namespace IPS\core\extensions\core\MobileNavigation;
/* 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;
}
/**
* Mobile Navigation Extension: Custom Item
*/
class _CustomItem extends \IPS\core\MobileNavigation\MobileNavigationAbstract
{
/**
* Get Type Title which will display in the AdminCP Menu Manager
*
* @return string
*/
public static function typeTitle()
{
return \IPS\Member::loggedIn()->language()->addToStack('mobilenavigation_CustomItem');
}
/**
* Allow multiple instances?
*
* @return bool
*/
public static function allowMultiple()
{
return TRUE;
}
/**
* Get configuration fields
*
* @param array $existingConfiguration The existing configuration, if editing an existing item
* @param int $id The ID number of the existing item, if editing
* @return array
*/
public static function configuration( $existingConfiguration, $id = NULL )
{
$currentUrl = NULL;
if ( isset( $existingConfiguration['menu_custom_item_url'] ) )
{
if ( isset( $existingConfiguration['internal'] ) )
{
$currentUrl = (string) \IPS\Http\Url::internal( $existingConfiguration['menu_custom_item_url'], 'front', $existingConfiguration['internal'], isset( $existingConfiguration['seoTitles'] ) ? $existingConfiguration['seoTitles'] : array() );
}
else
{
$currentUrl = $existingConfiguration['menu_custom_item_url'];
}
}
return array(
new \IPS\Helpers\Form\Translatable( 'menu_custom_item_link', NULL, NULL, array( 'app' => 'core', 'key' => $id ? "mobilemenu_item_{$id}" : NULL ), function( $val )
{
if ( !trim( $val[ \IPS\Lang::defaultLanguage() ] ) )
{
throw new \InvalidArgumentException('form_required');
}
} ),
new \IPS\Helpers\Form\Url( 'menu_custom_item_url', $currentUrl, NULL, array(), function( $val )
{
if ( isset( \IPS\Request::i()->menu_manager_extension ) and \IPS\Request::i()->menu_manager_extension === 'core_CustomItem' and empty( $val ) )
{
throw new \InvalidArgumentException('form_required');
}
} )
);
}
/**
* Parse configuration fields
*
* @param array $configuration The values received from the form
* @param int $id The ID number of the existing item, if editing
* @return array
*/
public static function parseConfiguration( $configuration, $id )
{
$baseUrl = \IPS\Http\Url::internal('', 'front');
if ( $configuration['menu_custom_item_url'] instanceof \IPS\Http\Url\Friendly )
{
$configuration['internal'] = $configuration['menu_custom_item_url']->seoTemplate;
$configuration['seoTitles'] = $configuration['menu_custom_item_url']->seoTitles;
$configuration['menu_custom_item_url'] = http_build_query( $configuration['menu_custom_item_url']->hiddenQueryString + $configuration['menu_custom_item_url']->queryString, '', '&' );
}
else
{
$configuration['menu_custom_item_url'] = (string) $configuration['menu_custom_item_url'];
}
\IPS\Lang::saveCustom( 'core', "mobilemenu_item_{$id}", $configuration['menu_custom_item_link'] );
unset( $configuration['menu_custom_item_link'] );
return $configuration;
}
/**
* Permissions can be inherited?
*
* @return bool
*/
public static function permissionsCanInherit()
{
return FALSE;
}
/**
* Get Title
*
* @return string
*/
public function title()
{
return \IPS\Member::loggedIn()->language()->addToStack( "mobilemenu_item_{$this->id}" );
}
/**
* Get Link
*
* @return \IPS\Http\Url
*/
public function link()
{
if ( isset( $this->configuration['menu_custom_item_url'] ) and ( $this->configuration['menu_custom_item_url'] or isset( $this->configuration['internal'] ) ) )
{
if ( isset( $this->configuration['internal'] ) )
{
return \IPS\Http\Url::internal( $this->configuration['menu_custom_item_url'], 'front', $this->configuration['internal'], isset( $this->configuration['seoTitles'] ) ? $this->configuration['seoTitles'] : array() );
}
else
{
return \IPS\Http\Url::external( $this->configuration['menu_custom_item_url'] );
}
}
else
{
return \IPS\Http\Url::internal('');
}
}
}
@@ -0,0 +1,217 @@
<?php
/**
* @brief Moderator Control Panel Extension: Alerts
* @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 12 May 2022
*/
namespace IPS\core\extensions\core\ModCp;
/* 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;
}
/**
* @brief Moderator Control Panel Extension: Alerts
*/
class _Alerts
{
/**
* Returns the primary tab key for the navigation bar
*
* @return string|null
*/
public function getTab()
{
/* Check Permissions */
if ( ! \IPS\Member::loggedIn()->modPermission('can_manage_alerts') )
{
return null;
}
return 'alerts';
}
/**
* Manage
*
* @return void
*/
public function manage()
{
/* Check Permissions */
if ( ! \IPS\Member::loggedIn()->modPermission('can_manage_alerts') )
{
\IPS\Output::i()->error( 'no_module_permission', '2C427/1', 403, '' );
}
$table = new \IPS\Helpers\Table\Content( '\IPS\core\Alerts\Alert', \IPS\Http\Url::internal( 'app=core&module=modcp&controller=modcp&tab=alerts' ) );
$table->rowsTemplate = array( \IPS\Theme::i()->getTemplate( 'modcp', 'core', 'front' ), 'alertRow' );
$table->include = array( 'alert_title' );
$table->mainColumn = 'alert_title';
$table->sortBy = 'alert_id';
$table->sortDirection = 'desc';
$table->sortOptions = array( 'alert_id' );
$table->title = \IPS\Member::loggedIn()->language()->addToStack( 'modcp_alerts' );
/* Filters */
$table->filters = array(
'alert_filter_active' => [ 'alert_enabled=1' ],
'alert_filter_inactive' => [ 'alert_enabled=0' ],
'alert_filter_viewed' => [ 'alert_viewed > 0 ' ],
'alert_filter_not_viewed' => [ 'alert_viewed = 0 ' ],
'alert_filter_groups' => [ "alert_recipient_type='group'"],
'alert_filter_user' => [ "alert_recipient_type='user'"]
);
\IPS\Output::i()->sidebar['enabled'] = FALSE;
\IPS\Output::i()->breadcrumb[] = array( NULL, \IPS\Member::loggedIn()->language()->addToStack( 'modcp_alerts' ) );
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack( 'modcp_alerts' );
return \IPS\Theme::i()->getTemplate( 'modcp' )->alerts( (string) $table );
}
/**
* Add/Edit Alert
*
* @return void
*/
public function create()
{
$current = NULL;
if ( \IPS\Request::i()->id )
{
$current = \IPS\core\Alerts\Alert::load( \IPS\Request::i()->id );
}
$form = \IPS\core\Alerts\Alert::form( $current );
$form->class = 'ipsForm_vertical';
if ( $values = $form->values() )
{
$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' ) );
}
if ( !\is_null( $current ) )
{
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack( 'edit_alert' );
}
else
{
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack( 'add_alert' );
}
\IPS\Output::i()->breadcrumb[] = array( \IPS\Http\Url::internal( 'app=core&module=modcp&controller=modcp&tab=alerts' ), \IPS\Member::loggedIn()->language()->addToStack( 'modcp_alerts' ) );
\IPS\Output::i()->breadcrumb[] = array( NULL, ( !\is_null( $current ) ) ? \IPS\Member::loggedIn()->language()->addToStack( 'edit_alert' ) : \IPS\Member::loggedIn()->language()->addToStack( 'add_alert' ) );
return $form->customTemplate( array( \IPS\Theme::i()->getTemplate( 'forms', 'core' ), 'popupTemplate' ) );
}
/**
* Delete Alert
*
* @return void
*/
public function delete()
{
if ( !\IPS\Member::loggedIn()->modPermission( 'can_manage_alerts' ) )
{
\IPS\Output::i()->error( 'no_module_permission', '2C427/2', 403, '' );
}
\IPS\Session::i()->csrfCheck();
try
{
$alert = \IPS\core\Alerts\Alert::load( \IPS\Request::i()->id );
}
catch( \OutOfRangeException $e )
{
\IPS\Output::i()->error( 'node_error', '2C427/3', 404, '' );
}
$alert->delete();
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=modcp&tab=alerts", 'front', 'modcp_alerts' ) );
}
/**
* Change Author
*
* @return void
*/
public function changeAuthor()
{
if ( !\IPS\Member::loggedIn()->modPermission( 'can_manage_alerts' ) )
{
\IPS\Output::i()->error( 'no_module_permission', '2C427/4', 403, '' );
}
try
{
$alert = \IPS\core\Alerts\Alert::load( \IPS\Request::i()->id );
}
catch( \OutOfRangeException $e )
{
\IPS\Output::i()->error( 'node_error', '2C427/5', 404, '' );
}
/* Build form */
$form = new \IPS\Helpers\Form;
$form->add( new \IPS\Helpers\Form\Member( 'author', $alert->author(), TRUE ) );
$form->class .= 'ipsForm_vertical';
/* Handle submissions */
if ( $values = $form->values() )
{
$alert->changeAuthor( $values['author'] );
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=modcp&tab=alerts", 'front', 'modcp_alerts' ) );
}
/* Display form */
\IPS\Output::i()->output = $form->customTemplate( array( \IPS\Theme::i()->getTemplate( 'forms', 'core' ), 'popupTemplate' ) );
}
/**
* Change Alert Status
*
* @return void
*/
public function status()
{
if ( !\IPS\Member::loggedIn()->modPermission( 'can_manage_alerts' ) )
{
\IPS\Output::i()->error( 'no_module_permission', '2C427/6', 403, '' );
}
\IPS\Session::i()->csrfCheck();
try
{
$alert = \IPS\core\Alerts\Alert::load( \IPS\Request::i()->id );
}
catch( \OutOfRangeException $e )
{
\IPS\Output::i()->error( 'node_error', '2C427/7', 404, '' );
}
$alert->enabled = ( $alert->enabled === 1 ? 0 : 1 );
$alert->save();
if ( \IPS\Request::i()->isAjax() )
{
\IPS\Output::i()->json( array( 'OK' ) );
}
else
{
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=modcp&tab=alerts", 'front', 'modcp_alerts' ) );
}
}
}
@@ -93,6 +93,7 @@ class _Content
$table = new \IPS\Helpers\Table\Content( $currentClass, \IPS\Http\Url::internal( "app=core&module=modcp&controller=modcp&tab=hidden&type={$currentType}", 'front', 'modcp_content' ), array( $where ) );
$table->title = \IPS\Member::loggedIn()->language()->addToStack( 'modcp_hidden' );
$table->classes = array( 'ipsStream' );
return \IPS\Theme::i()->getTemplate( 'modcp' )->tableWrapper( (string) $table );
}
@@ -85,11 +85,12 @@ class _Content
$return['can_manage_announcements'] = 'YesNo';
$return['can_see_poll_voters'] = 'YesNo';
$return['can_edit_poll_votes'] = 'YesNo';
$return['can_close_polls'] = 'YesNo';
$return['can_manage_deleted_content'] = 'YesNo';
$return['can_view_anonymous_posters'] = 'YesNo';
$return['can_manage_alerts'] = 'YesNo';
return $return;
}
@@ -31,18 +31,25 @@ class _Achievements
public static function configurationOptions( \IPS\Member $member = NULL )
{
// Return an array defining each notification type, including the form elements necessary to allow its configuration
return array(
'core_Achievements' => array(
'type' => 'standard',
'notificationTypes' => array( 'new_rank', 'new_badge', 'new_recognize' ),
'title' => 'notifications__core_Achievements',
'showTitle' => FALSE,
'description' => 'notifications__core_Achievements_desc',
'default' => array( 'inline' ),
'disabled' => array()
)
);
if ( \IPS\Settings::i()->achievements_enabled )
{
return array(
'core_Achievements' => array(
'type' => 'standard',
'notificationTypes' => array( 'new_rank', 'new_badge', 'new_recognize' ),
'title' => 'notifications__core_Achievements',
'showTitle' => FALSE,
'description' => 'notifications__core_Achievements_desc',
'default' => array( 'inline' ),
'disabled' => array()
)
);
}
else
{
return [];
}
}
/**
@@ -87,13 +94,13 @@ class _Achievements
}
else
{
$title = \IPS\Member::loggedIn()->rank['title'];
$title = $notification->member->rank['title'];
}
return array(
'title' => \IPS\Member::loggedIn()->language()->addToStack( 'notification__new_rank', FALSE, array( 'sprintf' => array( $title ) ) ),
'url' => \IPS\Member::loggedIn()->url(),
'author' => \IPS\Member::loggedIn()
'title' => $notification->member->language()->addToStack( 'notification__new_rank', FALSE, array( 'sprintf' => array( $title ) ) ),
'url' => $notification->member->url(),
'author' => $notification->member
);
}
@@ -104,7 +111,7 @@ class _Achievements
* @param \IPS\Member $member The member that earned the rank
* @return array
*/
public static function parse_mobile_new_rank( \IPS\Lang $language, \IPS\Member $member )
public static function parse_mobile_new_rank( \IPS\Lang $language, \IPS\core\Achievements\Rank $rank, \IPS\Member $member )
{
return array(
'body' => $language->addToStack( 'notification__new_rank', FALSE, array( 'sprintf' => array( $member->rank['title'] ) ) ),
@@ -149,8 +156,8 @@ class _Achievements
try
{
$badge = \IPS\core\Achievements\Badge::load( $badgeId );
$content = \IPS\Member::loggedIn()->language()->addToStack( 'notification__new_badge_content', NULL, [ 'sprintf' => [ $badge->_title ] ] );
$title = \IPS\Member::loggedIn()->language()->addToStack( 'notification__new_badge', NULL, [ 'sprintf' => [ $badge->_title ] ] );
$content = \IPS\Member::loggedIn()->language()->addToStack( 'notification__new_badge_content', FALSE, [ 'sprintf' => [ $badge->_title ] ] );
$title = \IPS\Member::loggedIn()->language()->addToStack( 'notification__new_badge', FALSE, [ 'sprintf' => [ $badge->_title ] ] );
}
catch( \Exception $e )
{
@@ -165,9 +172,9 @@ class _Achievements
return array(
'title' => $title,
'url' => (string) \IPS\Http\Url::internal( "app=core&module=members&controller=profile&do=badges&id=" . \IPS\Member::loggedIn()->member_id, 'front', 'profile_badges', \IPS\Member::loggedIn()->members_seo_name ),
'url' => (string) \IPS\Http\Url::internal( "app=core&module=members&controller=profile&do=badges&id=" . $notification->member->member_id, 'front', 'profile_badges', $notification->member->members_seo_name ),
'content' => $content,
'author' => \IPS\Member::loggedIn()
'author' => $notification->member
);
}
@@ -182,7 +189,7 @@ class _Achievements
public static function parse_mobile_new_badge( \IPS\Lang $language, \IPS\Member $member, \IPS\core\Achievements\Badge $badge )
{
return array(
'body' => $language->addToStack( 'notification__new_badge', NULL, [ 'sprintf' => [ $badge->_title ] ] ),
'body' => $language->addToStack( 'notification__new_badge', FALSE, [ 'sprintf' => [ $badge->_title ] ] ),
'data' => array(
'url' => (string) \IPS\Http\Url::internal( "app=core&module=members&controller=profile&do=badges&id={$member->member_id}", 'front', 'profile_badges', $member->members_seo_name ),
'author' => $member,
@@ -42,7 +42,7 @@ class _Content
$autoFollow[] = 'comments';
}
$autoFollowField = new \IPS\Helpers\Form\CheckboxSet( 'auto_track', $autoFollow, FALSE, array( 'options' => array( 'content' => 'auto_track_content', 'comments' => 'auto_track_comments' ), 'multiple' => TRUE ) );
$autoFollowField = new \IPS\Helpers\Form\CheckboxSet( 'auto_track', $autoFollow, FALSE, array( 'options' => array( 'content' => 'auto_track_content', 'comments' => 'auto_track_comments' ), 'multiple' => TRUE, 'showAllNone' => FALSE ) );
}
else
{
@@ -55,7 +55,7 @@ class _Content
$autoFollow[] = 'comments';
}
$autoFollowField = new \IPS\Helpers\Form\CheckboxSet( 'auto_follow_defaults', $autoFollow, FALSE, array( 'options' => array( 'content' => 'auto_follow_new_content', 'comments' => 'auto_follow_replied_to' ), 'multiple' => TRUE ) );
$autoFollowField = new \IPS\Helpers\Form\CheckboxSet( 'auto_follow_defaults', $autoFollow, FALSE, array( 'options' => array( 'content' => 'auto_follow_new_content', 'comments' => 'auto_follow_replied_to' ), 'multiple' => TRUE, 'showAllNone' => FALSE ) );
}
return array(
@@ -445,12 +445,13 @@ class _Content
$names = array();
foreach ( $commenters as $member )
{
$name = ( $item->isAnonymous() ) ? \IPS\Member::loggedIn()->language()->addToStack( 'post_anonymously_placename' ) : \IPS\Member::load( $member )->name;
if ( \count( $names ) > 2 )
{
$names[] = \IPS\Member::loggedIn()->language()->addToStack( 'x_others', FALSE, array( 'pluralize' => array( \count( $commenters ) - 3 ) ) );
break;
}
$names[] = \IPS\Member::load( $member )->name;
$names[] = $name;
}
$comment = $commentClass::loadAndCheckPerms( $notification->item_sub_id );
@@ -35,7 +35,7 @@ class _Messenger
$module = \IPS\Application\Module::get( 'core', 'messaging', 'front' );
if ( $module->_enabled )
{
if ( !$member or ( $member->canAccessModule( $module ) and !$member->members_disable_pm ) )
if ( !$member or ( $member->canAccessModule( $module ) and $member->members_disable_pm != 2 ) )
{
$return['core_Messenger'] = array(
'type' => 'standard',
@@ -528,7 +528,7 @@ class _Moderation
return array(
'title' => $language->addToStack( 'notification__unapproved_content_title', FALSE, array( 'htmlsprintf' => array(
( $content instanceof \IPS\Content\Item ) ? $content->definiteArticle( $language ) : $language->get( $content::$title . '_lc' ),
( $content instanceof \IPS\Content ) ? $content->definiteArticle( $language ) : $language->get( $content::$title . '_lc' ),
) ) ),
'body' => $language->addToStack( 'notification__unapproved_content', FALSE, array( 'htmlsprintf' => array(
$content->author()->name,
@@ -524,7 +524,7 @@ class _MyStuff
)
) ),
'data' => array(
'url' => (string) $content->url('find'),
'url' => (string) ( $content instanceof \IPS\Content\Comment ) ? $content->url('find') : $content->url(),
'author' => $content->author()
),
'channelId' => 'my-content',
@@ -602,7 +602,7 @@ class _MyStuff
)
) ),
'data' => array(
'url' => (string) $content->url('find'),
'url' => (string) ( $content instanceof \IPS\Content\Comment ) ? $content->url('find') : $content->url(),
'author' => $content->author()
),
'channelId' => 'my-content',
@@ -670,8 +670,15 @@ class _MyStuff
) ),
'data' => array(
'url' => (string) $content->url(),
'author' => $content->author()
'author' => $content->author(),
'grouped' => $language->addToStack( 'notification__approved_content_grouped', FALSE, array(
'htmlsprintf' => array(
mb_strtolower( $content->definiteArticle( $language, TRUE ) )
)
) ),
'groupedTitle' => $language->addToStack( 'notification__approved_content_title', FALSE )
),
'tag' => md5( 'approved' . \get_class( $item ) . $item::$title ), // Group notification by type
'channelId' => 'my-content',
);
}
@@ -0,0 +1,77 @@
<?php
/**
* @brief Overview statistics extension: DeletedContent
* @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 23 Sep 2021
*/
namespace IPS\core\extensions\core\OverviewStatistics;
/* 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;
}
/**
* @brief Overview statistics extension: DeletedContent
*/
class _DeletedContent
{
/**
* @brief Which statistics page (activity or user)
*/
public $page = 'activity';
/**
* Return the sub-block keys
*
* @note This is designed to allow one class to support multiple blocks, for instance using the ContentRouter to generate blocks.
* @return array
*/
public function getBlocks()
{
return array( 'deletedcontent' );
}
/**
* Return block details (title and description)
*
* @param string|NULL $subBlock The subblock we are loading as returned by getBlocks()
* @return array
*/
public function getBlockDetails( $subBlock = NULL )
{
/* Description can be null and will not be shown if so */
return array( 'app' => 'core', 'title' => 'stats_deletedcontent_percent', 'description' => 'stats_percentagedeleted_desc', 'refresh' => 60, 'form' => false );
}
/**
* Return the block HTML to show
*
* @param array|NULL $dateRange NULL for all time, or an array with 'start' and 'end' \IPS\DateTime objects to restrict to
* @param string|NULL $subBlock The subblock we are loading as returned by getBlocks()
* @return string
*/
public function getBlock( $dateRange = NULL, $subBlock = NULL )
{
if( !\IPS\Settings::i()->dellog_retention_period )
{
$total = $deleted = $value = 0;
}
else
{
$total = \IPS\Db::i()->select( 'COUNT(*)', 'core_search_index', array( 'index_date_created > ?', time() - \IPS\Settings::i()->dellog_retention_period ) )->first();
$deleted = \IPS\Db::i()->select( 'COUNT(*)', 'core_deletion_log', array() )->first();
$value = $total ? round( $deleted / $total * 100, 2 ) : 0;
}
return \IPS\Theme::i()->getTemplate( 'activitystats', 'core' )->deletedPercentage( $value, $total, $deleted );
}
}
@@ -147,7 +147,7 @@ class _ProfileFields
$pieBarData[] = array(
'name' => \IPS\Member::loggedIn()->language()->addToStack('stats_pfields__yes'),
'value' => isset( $results[1] ) ? $results[1]['total'] : 0,
'percentage' => ( isset( $results[1] ) AND $results[0]['total'] > 0 ) ? round( ( $results[1]['total'] / $total ) * 100, 2 ) : 0
'percentage' => ( isset( $results[1] ) AND $results[1]['total'] > 0 ) ? round( ( $results[1]['total'] / $total ) * 100, 2 ) : 0
);
}
elseif( $field['pf_type'] == 'Rating' )
@@ -112,8 +112,7 @@ class _ProfileFields
foreach( $step->subcompletion_act as $item )
{
$fieldId = \substr( $item, 12 );
foreach( $member->profileFields( \IPS\core\ProfileFields\Field::PROFILE_COMPLETION ) AS $group => $field )
foreach( $member->profileFields( \IPS\core\ProfileFields\Field::PROFILE_COMPLETION, TRUE ) AS $group => $field )
{
foreach( $field AS $key => $value )
{
@@ -51,7 +51,8 @@ class _Bulkmail
/* Reduce the maximum number of emails to be sent for bulk mail to 500 to prevent member specific tag timeouts */
$mailPerGo = min( $classToUse::MAX_EMAILS_PER_GO, 500 );
$existingvalue = \IPS\Db::i()->readWriteSeparation;
\IPS\Db::i()->readWriteSeparation = FALSE;
$results = $mail->getQuery( array( $offset, $mailPerGo ) );
if ( !\count( $results ) )
@@ -59,10 +60,10 @@ class _Bulkmail
$mail->active = 0;
$mail->offset = 0;
$mail->save();
\IPS\Db::i()->readWriteSeparation = $existingvalue;
throw new \IPS\Task\Queue\OutOfRangeException;
}
/* Convert $results into an array with replacement tags */
$recipients = array();
foreach ( $results as $memberData )
@@ -102,7 +103,7 @@ class _Bulkmail
}
/* Send it */
$email = \IPS\Email::buildFromContent( $mail->subject, $content, NULL, \IPS\Email::TYPE_BULK, TRUE, 'bulk_mail' )
$email = \IPS\Email::buildFromContent( $mail->subject, $content, NULL, \IPS\Email::TYPE_BULK, \IPS\Email::WRAPPER_USE, 'bulk_mail' )
->setUnsubscribe( 'core', 'unsubscribeBulk' );
$sent = 0;
foreach ( $recipients as $languageId => $_recipients )
@@ -114,7 +115,7 @@ class _Bulkmail
$mail->offset = ( $mail->offset + $mailPerGo );
$mail->sentto = ( $mail->sentto + $sent );
$mail->save();
\IPS\Db::i()->readWriteSeparation = $existingvalue;
return $offset + $mailPerGo;
}
catch( \OutOfRangeException $e )
@@ -76,8 +76,15 @@ class _DeleteOrMoveContent
$moveTo = NULL;
if ( isset( $data['moveTo'] ) )
{
$moveToClass = isset( $data['moveToClass'] ) ? $data['moveToClass'] : $classname;
$moveTo = $moveToClass::load( $data['moveTo'] );
$moveToClass = $data['moveToClass'] ?? $classname;
try
{
$moveTo = $moveToClass::load( $data['moveTo'] );
}
catch( \OutOfRangeException $e )
{
$moveTo = NULL;
}
}
$return = $node->massMoveorDelete( $moveTo, $data );
@@ -70,10 +70,15 @@ class _Follow
throw new \IPS\Task\Queue\OutOfRangeException;
}
$existingvalue = \IPS\Db::i()->readWriteSeparation;
\IPS\Db::i()->readWriteSeparation = FALSE;
$sentTo = isset( $data['sentTo'] ) ? $data['sentTo'] : array();
$newOffset = $item->sendNotificationsBatch( $offset, $sentTo, isset( $data['extra'] ) ? $data['extra'] : NULL );
$data['sentTo'] = $sentTo;
\IPS\Db::i()->readWriteSeparation = $existingvalue;
if( $newOffset === NULL )
{
throw new \IPS\Task\Queue\OutOfRangeException;
@@ -0,0 +1,80 @@
<?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 11 Jan 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 _IndexNow
{
/**
* Parse data before queuing
*
* @param array $data
* @return array
*/
public function preQueueData( $data )
{
if( !\IPS\core\IndexNow::i()->isEnabled() )
{
return NULL;
}
if( isset( $data ['urls'] ) AND \count($data ['urls'] ) )
{
return $data;
}
/* We don't have any URLS to submit, so just return NULL to complete the task */
return NULL;
}
/**
* 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 )
{
\IPS\core\IndexNow::i()->send( $data['urls'] );
throw new \IPS\Task\Queue\OutOfRangeException;
}
/**
* 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 )
{
if( isset( $data['type']))
{
return array( 'text' => \IPS\Member::loggedIn()->language()->addToStack( 'indexnow_s_submitting', FALSE, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->addToStack( $data['type'], FALSE ), ) ) ), 'complete' => 0 );
}
else
{
return array( 'text' => \IPS\Member::loggedIn()->language()->addToStack( 'indexnow_data_submitting' ), 'complete' => 0 );
}
}
}
@@ -48,19 +48,22 @@ class _MemberContent
return NULL;
}
/* Get count */
try
/* Get count - only do this if we're deleting member content, not guest content */
if ( $data['member_id'] )
{
$data['count'] = $classname::db()->select( 'COUNT(*)', $classname::$databaseTable, static::_getWhere( $data ) )->first();
}
catch( \IPS\Db\Exception $e )
{
return NULL;
}
try
{
$data['count'] = $classname::db()->select( 'COUNT(*)', $classname::$databaseTable, static::_getWhere( $data ) )->first();
}
catch( \IPS\Db\Exception $e )
{
return NULL;
}
if ( !$data['count'] )
{
return NULL;
if ( !$data['count'] )
{
return NULL;
}
}
return $data;
@@ -84,7 +87,9 @@ class _MemberContent
}
$select = $classname::db()->select( '*', $classname::$databaseTable, static::_getWhere( $data ), $classname::$databasePrefix.$classname::$databaseColumnId, array( 0, \IPS\REBUILD_NORMAL ) );
/* Keep track of how many we did since we cannot rely on the count for guests */
$did = 0;
foreach ( new \IPS\Patterns\ActiveRecordIterator( $select, $classname ) as $item )
{
/* If this is the first comment on an item where a first comment is required (e.g. posts) do nothing, as when we get to the item, that will handle it */
@@ -137,18 +142,24 @@ class _MemberContent
}
break;
}
$did++;
}
catch( \OutOfRangeException $e )
{
$did++;
}
catch( \ErrorException $e )
{
$did++;
}
}
if( $offset + \IPS\REBUILD_NORMAL >= $data['count'] )
if ( !$did )
{
throw new \IPS\Task\Queue\OutOfRangeException;
}
else if( isset( $data['count'] ) AND ( $offset + \IPS\REBUILD_NORMAL >= $data['count'] ) )
{
throw new \IPS\Task\Queue\OutOfRangeException;
}
@@ -186,7 +197,14 @@ class _MemberContent
$text = \IPS\Member::loggedIn()->language()->addToStack( 'backgroundQueue_membercontent_' . $data['action'], FALSE, $sprintf );
return array( 'text' => $text, 'complete' => $data['count'] ? ( round( 100 / $data['count'] * $offset, 2 ) ) : 100 );
if ( !$data['member_id'] )
{
return array( 'text' => $text, 'complete' => NULL );
}
else
{
return array( 'text' => $text, 'complete' => $data['count'] ? ( round( 100 / $data['count'] * $offset, 2 ) ) : 100 );
}
}
/**
@@ -209,7 +209,7 @@ class _RebuildAchievements
public function postComplete( $data, $processed = TRUE )
{
/* The core_queue row is deleted before this is run */
if ( ! \IPS\Db::i()->select( 'COUNT(*)', 'core_queue', [ [ '`key`=?', 'RebuildAchievements' ] ] )->first() )
if ( ! \IPS\Db::i()->select( 'COUNT(*)', 'core_queue', [ [ '`key`=?', 'RebuildAchievements' ] ], NULL, NULL, NULL, NULL, \IPS\Db::SELECT_FROM_WRITE_SERVER )->first() )
{
/* Rebuilding is complete! */
\IPS\Settings::i()->changeValues( array( 'achievements_rebuilding' => 0, 'achievements_last_rebuilt' => time() ) );
@@ -127,6 +127,9 @@ class _RebuildSearchIndex
*/
public function run( &$data, $offset )
{
/* We want to allow read/write separation in this task */
\IPS\Db::i()->readWriteSeparation = TRUE;
$classname = $data['class'];
$exploded = explode( '\\', $classname );
if ( !class_exists( $classname ) or !\IPS\Application::appIsEnabled( $exploded[1] ) )
@@ -186,20 +189,32 @@ class _RebuildSearchIndex
}
catch( \OutOfRangeException $e )
{
/* Turn off read/write separation before returning */
\IPS\Db::i()->readWriteSeparation = FALSE;
/* Something has gone wrong with iterator attempting to use constructFromData */
throw new \IPS\Task\Queue\OutOfRangeException;
}
}
catch( \IPS\Db\Exception $e )
{
/* Turn off read/write separation before returning */
\IPS\Db::i()->readWriteSeparation = FALSE;
/* Something has gone wrong with the query, like the table not existing */
throw new \IPS\Task\Queue\OutOfRangeException;
}
if( $indexed === NULL )
{
/* Turn off read/write separation before returning */
\IPS\Db::i()->readWriteSeparation = FALSE;
throw new \IPS\Task\Queue\OutOfRangeException;
}
/* Turn off read/write separation before returning */
\IPS\Db::i()->readWriteSeparation = FALSE;
/* Return the number indexed so far, so that the rebuild progress bar text makes sense */
return $data['indexed'];
@@ -1,82 +0,0 @@
<?php
/**
* @brief Support Applications in sitemaps
* @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 17 Mar 2020
*/
namespace IPS\core\extensions\core\Sitemap;
/* 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;
}
/**
* Support Applications in sitemaps
*/
class _Applications
{
/**
* @brief Recommended Settings
*/
public $recommendedSettings = array();
/**
* Add settings for ACP configuration to the form
*
* @return array
*/
public function settings()
{
return array();
}
/**
* Save settings for ACP configuration
*
* @param array $values Values
* @return void
*/
public function saveSettings( $values )
{
}
/**
* Get the sitemap filename(s)
*
* @return array
*/
public function getFilenames()
{
return array( 'sitemap_' . mb_strtolower('Applications') );
}
/**
* Generate the sitemap
*
* @param string $filename The sitemap file to build (should be one returned from getFilenames())
* @param \IPS\Sitemap $sitemap Sitemap object reference
* @return void
*/
public function generateSitemap( $filename, $sitemap )
{
$links = array();
foreach( \IPS\Application::enabledApplications() as $app )
{
foreach( $app->sitemapLinks() as $link )
{
$links[] = array( 'url' => $link );
}
}
/* Build the file */
$sitemap->buildSitemapFile( $filename, $links );
}
}
@@ -163,6 +163,12 @@ class _Content extends \IPS\Content\ExtensionGenerator
/* Check that guests can access the content at all */
try
{
$app = \IPS\Application::load( $class::$application );
if ( !$app->canAccess( new \IPS\Member ) )
{
throw new \OutOfRangeException;
}
$module = \IPS\Application\Module::get( $class::$application, $class::$module, 'front' );
if ( !$module->can( 'view', new \IPS\Member ) )
{
@@ -1,199 +0,0 @@
<?php
/**
* @brief Support profiles in sitemaps
* @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 29 Aug 2013
*/
namespace IPS\core\extensions\core\Sitemap;
/* 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;
}
/**
* Support profiles in sitemaps
*/
class _Profiles
{
/**
* @brief Recommended Settings
*/
public $recommendedSettings = array(
'sitemap_profiles_include' => true,
'sitemap_profiles_count' => -1,
'sitemap_profiles_priority' => 0.6,
'sitemap_profiles_content' => 10,
);
/**
* Settings for ACP configuration to the form
*
* @return array
*/
public function settings()
{
return array(
'sitemap_profiles_include' => new \IPS\Helpers\Form\YesNo( "sitemap_profiles_include", \IPS\Settings::i()->sitemap_profiles_count != 0, FALSE, array( 'togglesOn' => array( "sitemap_profiles_count", "sitemap_profiles_priority", "sitemap_profiles_content" ) ), NULL, NULL, NULL, "sitemap_profiles_include" ),
'sitemap_profiles_count' => new \IPS\Helpers\Form\Number( 'sitemap_profiles_count', \IPS\Settings::i()->sitemap_profiles_count, FALSE, array( 'min' => '-1', 'unlimited' => '-1' ), NULL, NULL, NULL, 'sitemap_profiles_count' ),
'sitemap_profiles_content' => new \IPS\Helpers\Form\Number( 'sitemap_profiles_content', \IPS\Settings::i()->sitemap_profiles_content, FALSE, array( 'min' => '-1', 'unlimited' => '-1', 'unlimitedLang' => 'sitemap_profiles_content_unlimited' ), NULL, NULL, \IPS\Member::loggedIn()->language()->addToStack('sitemap_profiles_content_suffix'), 'sitemap_profiles_content' ),
'sitemap_profiles_priority' => new \IPS\Helpers\Form\Select( 'sitemap_profiles_priority', \IPS\Settings::i()->sitemap_profiles_priority, FALSE, array( 'options' => \IPS\Sitemap::$priorities, 'unlimited' => '-1', 'unlimitedLang' => 'sitemap_dont_include' ), NULL, NULL, NULL, 'sitemap_profiles_priority' )
);
}
/**
* Save settings for ACP configuration
*
* @param array $values Values
* @return void
*/
public function saveSettings( $values )
{
if ( $values['sitemap_configuration_info'] )
{
\IPS\Settings::i()->changeValues( array( 'sitemap_profiles_count' => $this->recommendedSettings['sitemap_profiles_count'], 'sitemap_profiles_content' => $this->recommendedSettings['sitemap_profiles_content'], 'sitemap_profiles_priority' => $this->recommendedSettings['sitemap_profiles_priority'] ) );
}
else
{
\IPS\Settings::i()->changeValues( array( 'sitemap_profiles_count' => $values['sitemap_profiles_include'] ? $values['sitemap_profiles_count'] : 0, 'sitemap_profiles_content' => $values['sitemap_profiles_content'], 'sitemap_profiles_priority' => $values['sitemap_profiles_priority'] ) );
}
}
/**
* Get the sitemap filename(s)
*
* @return array
*/
public function getFilenames()
{
/* First, we need to make sure we are actually including profiles in the sitemap */
if ( \IPS\Settings::i()->sitemap_profiles_count == 0 )
{
return array();
}
/* Then, make sure the module is enabled. */
$profileModule = \IPS\Application\Module::get( 'core', 'members', 'front' );
if( !$profileModule->visible )
{
return array();
}
/* And that Guests can view the profile module */
if( !$profileModule->can( 'view', new \IPS\Member ) )
{
return array();
}
$where = NULL;
if ( \IPS\Settings::i()->sitemap_profiles_content > 0 )
{
$where = array( array( 'member_posts >= ?', \IPS\Settings::i()->sitemap_profiles_content ) );
}
/* Get a count of how many files we'll be generating */
$count = \IPS\Db::i()->select( 'COUNT(*)', 'core_members', $where )->first();
$count = ( $count > \IPS\Sitemap::MAX_PER_FILE ) ? ceil( $count / \IPS\Sitemap::MAX_PER_FILE ) : 1;
/* If we are not storing all profiles, limit the count */
if ( \IPS\Settings::i()->sitemap_profiles_count > -1 AND $count > \IPS\Settings::i()->sitemap_profiles_count )
{
$count = \IPS\Settings::i()->sitemap_profiles_count;
}
/* Generate the file names */
$files = array();
for( $i=1; $i <= $count; $i++ )
{
$files[] = "sitemap_profiles_" . $i;
}
/* Return */
return $files;
}
/**
* Generate the sitemap
*
* @param string $filename The sitemap file to build (should be one returned from getFilenames())
* @param \IPS\Sitemap $sitemap Sitemap object reference
* @return void
*/
public function generateSitemap( $filename, $sitemap )
{
/* Which file are we building? */
$_info = explode( '_', $filename );
$index = array_pop( $_info ) - 1;
$lastId = 0;
$entries = array();
$start = \IPS\Sitemap::MAX_PER_FILE * $index;
$limit = \IPS\Sitemap::MAX_PER_FILE;
/* Have we already maxed out? We shouldn't really hit this because getFilenames() already factors in the max, but best to check */
if( \IPS\Settings::i()->sitemap_profiles_count > -1 AND \IPS\Settings::i()->sitemap_profiles_count < $start )
{
return;
}
/* Do we need less than 10k? */
if( \IPS\Settings::i()->sitemap_profiles_count > -1 AND \IPS\Settings::i()->sitemap_profiles_count < $start + $limit )
{
$limit = \IPS\Settings::i()->sitemap_profiles_count - $start;
}
/* Create limit clause */
$limitClause = array( $start, $limit );
/* Generate WHERE clause */
$where = array();
if ( \IPS\Settings::i()->sitemap_profiles_content > 0 )
{
$where[] = array( 'member_posts >= ?', \IPS\Settings::i()->sitemap_profiles_content );
}
/* Try to fetch the highest ID built in the last sitemap, if it exists */
try
{
$lastId = \IPS\Db::i()->select( 'last_id', 'core_sitemap', array( array( 'sitemap=?', implode( '_', $_info ) . '_' . ( $index ) ) ) )->first();
if( $lastId > 0 )
{
$where[] = array( 'core_members.member_id > ?', $lastId );
$limitClause = $limit;
}
}
catch( \UnderflowException $e ){}
/* Retrieve the members */
foreach( \IPS\Db::i()->select( 'members_seo_name, name, member_id', 'core_members', $where, 'member_id', $limitClause ) as $row )
{
$row['members_seo_name'] = $row['members_seo_name'] ?: \IPS\Http\Url\Friendly::seoTitle( $row['name'] );
$entry = array(
'url' => \IPS\Http\Url::internal( "app=core&module=members&controller=profile&id={$row['member_id']}", 'front', 'profile', $row['members_seo_name'] )
);
if( \IPS\Settings::i()->sitemap_profiles_priority > 0 )
{
$entry['priority'] = \IPS\Settings::i()->sitemap_profiles_priority;
}
$entries[] = $entry;
$lastId = $row['member_id'];
}
/* Build the file */
$sitemap->buildSitemapFile( $filename, $entries, $lastId );
return $lastId;
}
}