Version 5.0.13
This commit is contained in:
1 parent
b89858b2ff
commit
5efe744c5f
1407 files changed
+69690
-30177
No files matched your search
@@ -11,6 +11,7 @@ namespace IPS\core\extensions\core\AchievementAction;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
|
||||
use IPS\Application;
|
||||
use IPS\core\Achievements\Actions\ContentAchievementActionAbstract;
|
||||
use IPS\core\Achievements\Rule;
|
||||
use IPS\Db;
|
||||
@@ -145,6 +146,52 @@ class Comment extends ContentAchievementActionAbstract
|
||||
{
|
||||
return [ $extra->item()->author() ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the member has already completed this rule.
|
||||
* Used for retroactive rule completion.
|
||||
* So far, this is only used in Quests, but may be used elsewhere at a later point.
|
||||
*
|
||||
* @param Member $member
|
||||
* @param array $filters
|
||||
* @return bool
|
||||
*/
|
||||
public function isRuleCompleted( Member $member, array $filters ) : bool
|
||||
{
|
||||
$total = 0;
|
||||
foreach( Application::allExtensions( 'core', 'ContentRouter', false ) as $extension )
|
||||
{
|
||||
foreach( $extension->classes as $itemClass )
|
||||
{
|
||||
if( !isset( $itemClass::$commentClass ) or ( !empty( $filters['type'] ) and $filters['type'] != $itemClass::$commentClass ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
/* @var \IPS\Content\Comment $class */
|
||||
$class = $itemClass::$commentClass;
|
||||
|
||||
/* @var array $databaseColumnMap */
|
||||
$where = [
|
||||
[ $class::$databasePrefix . $class::$databaseColumnMap['author'] . '=?', $member->member_id ]
|
||||
];
|
||||
|
||||
if( isset( $class::$databaseColumnMap['approved'] ) )
|
||||
{
|
||||
$where[] = [ $class::$databasePrefix . $class::$databaseColumnMap['approved'] . '=?', 1 ];
|
||||
}
|
||||
|
||||
$total += (int) Db::i()->select( 'count(*)', $class::$databaseTable, $where )->first();
|
||||
}
|
||||
}
|
||||
|
||||
if( !empty( $filters['milestone'] ) )
|
||||
{
|
||||
return $total >= $filters['milestone'];
|
||||
}
|
||||
|
||||
return $total > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a description for this action to show in the log
|
||||
@@ -194,7 +241,7 @@ class Comment extends ContentAchievementActionAbstract
|
||||
{
|
||||
$conditions[] = Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone', FALSE, [
|
||||
'htmlsprintf' => [
|
||||
Theme::i()->getTemplate( 'achievements' )->ruleDescriptionBadge( 'milestone', Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone_nth', FALSE, [ 'pluralize' => [ $rule->filters['milestone'] ] ] ) )
|
||||
Theme::i()->getTemplate( 'achievements', 'core' )->ruleDescriptionBadge( 'milestone', Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone_nth', FALSE, [ 'pluralize' => [ $rule->filters['milestone'] ] ] ) )
|
||||
],
|
||||
'sprintf' => [ $type ? Member::loggedIn()->language()->addToStack( $type::$title ) : Member::loggedIn()->language()->addToStack('AchievementAction__Comment_title_generic') ]
|
||||
] );
|
||||
@@ -203,8 +250,12 @@ class Comment extends ContentAchievementActionAbstract
|
||||
{
|
||||
$conditions[] = $nodeCondition;
|
||||
}
|
||||
if( $questCondition = $this->_questFilterDescription( $rule ) )
|
||||
{
|
||||
$conditions[] = $questCondition;
|
||||
}
|
||||
|
||||
return Theme::i()->getTemplate( 'achievements' )->ruleDescription(
|
||||
return Theme::i()->getTemplate( 'achievements', 'core' )->ruleDescription(
|
||||
$type ? Member::loggedIn()->language()->addToStack( 'AchievementAction__NewContentItem_title_t', FALSE, [ 'sprintf' => [ Member::loggedIn()->language()->addToStack( $type::$title ) ] ] ) : Member::loggedIn()->language()->addToStack( 'AchievementAction__Comment_title' ),
|
||||
$conditions
|
||||
);
|
||||
|
||||
@@ -169,7 +169,7 @@ class ContentPromotion extends ContentAchievementActionAbstract
|
||||
{
|
||||
$conditions[] = Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone', FALSE, [
|
||||
'htmlsprintf' => [
|
||||
Theme::i()->getTemplate( 'achievements' )->ruleDescriptionBadge( 'milestone', Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone_nth', FALSE, [ 'pluralize' => [ $rule->filters['milestone'] ] ] ) )
|
||||
Theme::i()->getTemplate( 'achievements', 'core' )->ruleDescriptionBadge( 'milestone', Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone_nth', FALSE, [ 'pluralize' => [ $rule->filters['milestone'] ] ] ) )
|
||||
],
|
||||
'sprintf' => Member::loggedIn()->language()->addToStack('AchievementAction__ContentPromotion_title_generic')
|
||||
] );
|
||||
@@ -189,7 +189,7 @@ class ContentPromotion extends ContentAchievementActionAbstract
|
||||
{
|
||||
$conditions[] = Member::loggedIn()->language()->addToStack( 'achievements_title_filter_type', FALSE, [
|
||||
'htmlsprintf' => [
|
||||
Theme::i()->getTemplate( 'achievements' )->ruleDescriptionBadge( 'other',
|
||||
Theme::i()->getTemplate( 'achievements', 'core' )->ruleDescriptionBadge( 'other',
|
||||
count( $promoTypes ) === 1 ? $promoTypes[0] : Member::loggedIn()->language()->addToStack( 'AchievementAction__ContentPromotion_types', FALSE, [ 'sprintf' => [
|
||||
count( $promoTypes ),
|
||||
] ] ),
|
||||
@@ -203,8 +203,12 @@ class ContentPromotion extends ContentAchievementActionAbstract
|
||||
{
|
||||
$conditions[] = $nodeCondition;
|
||||
}
|
||||
if( $questCondition = $this->_questFilterDescription( $rule ) )
|
||||
{
|
||||
$conditions[] = $questCondition;
|
||||
}
|
||||
|
||||
return Theme::i()->getTemplate( 'achievements' )->ruleDescription(
|
||||
return Theme::i()->getTemplate( 'achievements', 'core' )->ruleDescription(
|
||||
Member::loggedIn()->language()->addToStack( 'AchievementAction__ContentPromotion_title' ),
|
||||
$conditions
|
||||
);
|
||||
|
||||
@@ -120,6 +120,56 @@ class FollowContentItem extends ContentAchievementActionAbstract
|
||||
{
|
||||
return [ $extra['author'] ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the member has already completed this rule.
|
||||
* Used for retroactive rule completion.
|
||||
* So far, this is only used in Quests, but may be used elsewhere at a later point.
|
||||
*
|
||||
* @param Member $member
|
||||
* @param array $filters
|
||||
* @return bool
|
||||
*/
|
||||
public function isRuleCompleted( Member $member, array $filters ) : bool
|
||||
{
|
||||
$where = [
|
||||
[ 'follow_member_id=?', $member->member_id ],
|
||||
[ 'follow_area !=?', 'member' ]
|
||||
];
|
||||
|
||||
$totalItemsFollowed = 0;
|
||||
$matchesFilters = empty( $filters['nodes'] );
|
||||
foreach( Db::i()->select( '*', 'core_follow', $where, 'follow_added' ) as $row )
|
||||
{
|
||||
$class = 'IPS\\' . $row['follow_app'] . '\\' . \ucfirst( $row['follow_area'] );
|
||||
if ( class_exists( $class ) AND in_array( 'IPS\Content\Item', class_parents( $class ) ) )
|
||||
{
|
||||
$totalItemsFollowed++;
|
||||
if( !empty( $filters['nodes'] ) and in_array( $row['follow_rel_id'], $filters['nodes'] ) )
|
||||
{
|
||||
$matchesFilters = true;
|
||||
}
|
||||
|
||||
/* Check if we hit the conditions yet */
|
||||
if( $matchesFilters )
|
||||
{
|
||||
if( !empty( $filters['milestone'] ) )
|
||||
{
|
||||
if( $totalItemsFollowed >= $filters['milestone'] )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get identifier to prevent the member being awarded points for the same action twice
|
||||
@@ -178,7 +228,7 @@ class FollowContentItem extends ContentAchievementActionAbstract
|
||||
{
|
||||
$conditions[] = Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone', FALSE, [
|
||||
'htmlsprintf' => [
|
||||
Theme::i()->getTemplate( 'achievements' )->ruleDescriptionBadge( 'milestone', Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone_nth', FALSE, [ 'pluralize' => [ $rule->filters['milestone'] ] ] ) )
|
||||
Theme::i()->getTemplate( 'achievements', 'core' )->ruleDescriptionBadge( 'milestone', Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone_nth', FALSE, [ 'pluralize' => [ $rule->filters['milestone'] ] ] ) )
|
||||
],
|
||||
'sprintf' => [ $type ? Member::loggedIn()->language()->addToStack( $type::$title, FALSE, [ 'strtolower' => TRUE ] ) : Member::loggedIn()->language()->addToStack('AchievementAction__NewContentItem_title_generic') ]
|
||||
] );
|
||||
@@ -187,8 +237,12 @@ class FollowContentItem extends ContentAchievementActionAbstract
|
||||
{
|
||||
$conditions[] = $nodeCondition;
|
||||
}
|
||||
if( $questCondition = $this->_questFilterDescription( $rule ) )
|
||||
{
|
||||
$conditions[] = $questCondition;
|
||||
}
|
||||
|
||||
return Theme::i()->getTemplate( 'achievements' )->ruleDescription(
|
||||
return Theme::i()->getTemplate( 'achievements', 'core' )->ruleDescription(
|
||||
$type ? Member::loggedIn()->language()->addToStack( 'AchievementAction__FollowContentItem_title_t', FALSE, [ 'sprintf' => [ Member::loggedIn()->language()->addToStack( $type::$title ) ] ] ) : Member::loggedIn()->language()->addToStack( 'AchievementAction__FollowContentItem_title' ),
|
||||
$conditions
|
||||
);
|
||||
|
||||
@@ -14,9 +14,11 @@ namespace IPS\core\extensions\core\AchievementAction;
|
||||
use IPS\core\Achievements\Actions\AchievementActionAbstract;
|
||||
use IPS\core\Achievements\Rule;
|
||||
use IPS\Db;
|
||||
use IPS\Helpers\Form\Member as FormMember;
|
||||
use IPS\Helpers\Form\Number;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Member;
|
||||
use IPS\Patterns\ActiveRecordIterator;
|
||||
use IPS\Theme;
|
||||
use OutOfRangeException;
|
||||
use function defined;
|
||||
@@ -42,13 +44,29 @@ class FollowMember extends AchievementActionAbstract
|
||||
*/
|
||||
public function filters( ?array $filters, Url $url ): array
|
||||
{
|
||||
$nthFilter = new Number( 'achievement_filter_FollowMember_nth', ( $filters and isset( $filters['milestone'] ) and $filters['milestone'] ) ? $filters['milestone'] : 0, FALSE, [], NULL, Member::loggedIn()->language()->addToStack('achievement_filter_nth_their'), Member::loggedIn()->language()->addToStack('achievement_filter_FollowMember_nth_suffix') );
|
||||
$nthFilter->label = Member::loggedIn()->language()->addToStack('achievement_filter_NewClub_nth');
|
||||
$return = parent::filters( $filters, $url );
|
||||
|
||||
$return = array();
|
||||
$nthFilter = new Number( 'achievement_filter_FollowMember_nth', ( $filters and isset( $filters['milestone'] ) and $filters['milestone'] ) ? $filters['milestone'] : 0, FALSE, [], NULL, Member::loggedIn()->language()->addToStack('achievement_filter_nth_their'), Member::loggedIn()->language()->addToStack('achievement_filter_FollowMember_nth_suffix') );
|
||||
$nthFilter->label = Member::loggedIn()->language()->addToStack('achievement_filter_FollowMember_nth');
|
||||
|
||||
$return['milestone'] = $nthFilter;
|
||||
|
||||
if( isset( $filters['members'] ) )
|
||||
{
|
||||
$selected = iterator_to_array(
|
||||
new ActiveRecordIterator(
|
||||
Db::i()->select( '*', 'core_members', [ Db::i()->in( 'member_id', $filters['members'] ) ] ),
|
||||
'IPS\Member'
|
||||
)
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
$selected = null;
|
||||
}
|
||||
|
||||
$return['members'] = new FormMember( 'achievement_filter_FollowMember_member', $selected, false, [ 'multiple' => null ], null, Member::loggedIn()->language()->addToStack( 'achievement_filter_FollowMember_member_prefix' ), null, 'achievement_filter_FollowMember_member' );
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
@@ -60,12 +78,18 @@ class FollowMember extends AchievementActionAbstract
|
||||
*/
|
||||
public function formatFilterValues( array $values ): array
|
||||
{
|
||||
$return = [];
|
||||
$return = parent::formatFilterValues( $values );
|
||||
|
||||
if ( isset( $values['achievement_filter_FollowMember_nth'] ) )
|
||||
{
|
||||
$return['milestone'] = $values['achievement_filter_FollowMember_nth'];
|
||||
}
|
||||
|
||||
if( isset( $values['achievement_filter_FollowMember_member'] ) )
|
||||
{
|
||||
$return['members'] = array_keys( $values['achievement_filter_FollowMember_member'] );
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
@@ -96,6 +120,14 @@ class FollowMember extends AchievementActionAbstract
|
||||
}
|
||||
}
|
||||
|
||||
if( isset( $filters['members'] ) )
|
||||
{
|
||||
if( !in_array( $subject->member_id, $filters['members'] ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@@ -125,6 +157,44 @@ class FollowMember extends AchievementActionAbstract
|
||||
{
|
||||
return [ $extra['giver'] ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the member has already completed this rule.
|
||||
* Used for retroactive rule completion.
|
||||
* So far, this is only used in Quests, but may be used elsewhere at a later point.
|
||||
*
|
||||
* @param Member $member
|
||||
* @param array $filters
|
||||
* @return bool
|
||||
*/
|
||||
public function isRuleCompleted( Member $member, array $filters ) : bool
|
||||
{
|
||||
/* Get all followed members first, because we need to take milestones into account */
|
||||
$members = iterator_to_array(
|
||||
Db::i()->select( 'follow_rel_id', 'core_follow', [ 'follow_app=? and follow_area=? and follow_member_id=?', 'core', 'member', $member->member_id ] )
|
||||
);
|
||||
|
||||
if( !count( $members ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if( !empty( $filters['members'] ) )
|
||||
{
|
||||
$match = array_intersect( $filters['members'], $members );
|
||||
if( !count( $match ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if( !empty( $filters['milestone'] ) )
|
||||
{
|
||||
return count( $members ) >= $filters['milestone'];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get identifier to prevent the member being awarded points for the same action twice
|
||||
@@ -186,13 +256,28 @@ class FollowMember extends AchievementActionAbstract
|
||||
{
|
||||
$conditions[] = Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone', FALSE, [
|
||||
'htmlsprintf' => [
|
||||
Theme::i()->getTemplate( 'achievements' )->ruleDescriptionBadge( 'milestone', Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone_nth', FALSE, [ 'pluralize' => [ $rule->filters['milestone'] ] ] ) )
|
||||
Theme::i()->getTemplate( 'achievements', 'core' )->ruleDescriptionBadge( 'milestone', Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone_nth', FALSE, [ 'pluralize' => [ $rule->filters['milestone'] ] ] ) )
|
||||
],
|
||||
'sprintf' => Member::loggedIn()->language()->addToStack('AchievementAction__FollowMember_title_generic')
|
||||
] );
|
||||
}
|
||||
|
||||
return Theme::i()->getTemplate( 'achievements' )->ruleDescription(
|
||||
if( isset( $rule->filters['members'] ) )
|
||||
{
|
||||
$memberNames = iterator_to_array( Db::i()->select( 'name', 'core_members', Db::i()->in( 'member_id', $rule->filters['members'] ), 'name' ) );
|
||||
$conditions[] = Member::loggedIn()->language()->addToStack( 'achievements_title_filter_members', false, [
|
||||
'htmlsprintf' => [
|
||||
Theme::i()->getTemplate( 'achievements', 'core' )->ruleDescriptionBadge( 'clubs', Member::loggedIn()->language()->addToStack( 'achievement_filter_FollowMember_member_title_pl', false, [ 'pluralize' => [ count( $memberNames ) ] ] ), $memberNames )
|
||||
]
|
||||
] );
|
||||
}
|
||||
|
||||
if( $questCondition = $this->_questFilterDescription( $rule ) )
|
||||
{
|
||||
$conditions[] = $questCondition;
|
||||
}
|
||||
|
||||
return Theme::i()->getTemplate( 'achievements', 'core' )->ruleDescription(
|
||||
Member::loggedIn()->language()->addToStack( 'AchievementAction__FollowMember_title' ),
|
||||
$conditions
|
||||
);
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace IPS\core\extensions\core\AchievementAction;
|
||||
|
||||
use IPS\core\Achievements\Actions\NodeAchievementActionAbstract;
|
||||
use IPS\core\Achievements\Rule;
|
||||
use IPS\Db;
|
||||
use IPS\Http\Url;
|
||||
use IPS\IPS;
|
||||
use IPS\Member;
|
||||
@@ -55,6 +56,56 @@ class FollowNode extends NodeAchievementActionAbstract
|
||||
return $filters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the member has already completed this rule.
|
||||
* Used for retroactive rule completion.
|
||||
* So far, this is only used in Quests, but may be used elsewhere at a later point.
|
||||
*
|
||||
* @param Member $member
|
||||
* @param array $filters
|
||||
* @return bool
|
||||
*/
|
||||
public function isRuleCompleted( Member $member, array $filters ) : bool
|
||||
{
|
||||
$where = [
|
||||
[ 'follow_member_id=?', $member->member_id ],
|
||||
[ 'follow_area !=?', 'member' ]
|
||||
];
|
||||
|
||||
$totalNodesFollowed = 0;
|
||||
$matchesFilters = empty( $filters['nodes'] );
|
||||
foreach( Db::i()->select( '*', 'core_follow', $where, 'follow_added' ) as $row )
|
||||
{
|
||||
$class = 'IPS\\' . $row['follow_app'] . '\\' . \ucfirst( $row['follow_area'] );
|
||||
if ( class_exists( $class ) AND \in_array( 'IPS\Node\Model', class_parents( $class ) ) )
|
||||
{
|
||||
$totalNodesFollowed++;
|
||||
if( !empty( $filters['nodes'] ) and in_array( $row['follow_rel_id'], $filters['nodes'] ) )
|
||||
{
|
||||
$matchesFilters = true;
|
||||
}
|
||||
|
||||
/* Check if we hit the conditions yet */
|
||||
if( $matchesFilters )
|
||||
{
|
||||
if( !empty( $filters['milestone'] ) )
|
||||
{
|
||||
if( $totalNodesFollowed >= $filters['milestone'] )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a description for this action to show in the log
|
||||
*
|
||||
@@ -97,7 +148,7 @@ class FollowNode extends NodeAchievementActionAbstract
|
||||
{
|
||||
$conditions[] = Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone', FALSE, [
|
||||
'htmlsprintf' => [
|
||||
Theme::i()->getTemplate( 'achievements' )->ruleDescriptionBadge( 'milestone', Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone_nth', FALSE, [ 'pluralize' => [ $rule->filters['milestone'] ] ] ) )
|
||||
Theme::i()->getTemplate( 'achievements', 'core' )->ruleDescriptionBadge( 'milestone', Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone_nth', FALSE, [ 'pluralize' => [ $rule->filters['milestone'] ] ] ) )
|
||||
],
|
||||
'sprintf' => [ $type ? Member::loggedIn()->language()->addToStack( $type::fullyQualifiedType(), FALSE, [ 'strtolower' => TRUE ] ) : Member::loggedIn()->language()->addToStack('AchievementAction__NewContentItem_title_generic') ]
|
||||
] );
|
||||
@@ -106,8 +157,12 @@ class FollowNode extends NodeAchievementActionAbstract
|
||||
{
|
||||
$conditions[] = $nodeCondition;
|
||||
}
|
||||
if( $questCondition = $this->_questFilterDescription( $rule ) )
|
||||
{
|
||||
$conditions[] = $questCondition;
|
||||
}
|
||||
|
||||
return Theme::i()->getTemplate( 'achievements' )->ruleDescription(
|
||||
return Theme::i()->getTemplate( 'achievements', 'core' )->ruleDescription(
|
||||
$type ? Member::loggedIn()->language()->addToStack( 'AchievementAction__FollowNode_title_t', FALSE, [ 'sprintf' => [ Member::loggedIn()->language()->addToStack( $type::fullyQualifiedType() ) ] ] ) : Member::loggedIn()->language()->addToStack( 'AchievementAction__FollowNode_title' ),
|
||||
$conditions
|
||||
);
|
||||
|
||||
@@ -150,7 +150,7 @@ class Highlight extends ContentAchievementActionAbstract // NOTE: Other classes
|
||||
{
|
||||
$conditions[] = Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone', FALSE, [
|
||||
'htmlsprintf' => [
|
||||
Theme::i()->getTemplate( 'achievements' )->ruleDescriptionBadge( 'milestone', Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone_nth', FALSE, [ 'pluralize' => [ $rule->filters['milestone'] ] ] ) )
|
||||
Theme::i()->getTemplate( 'achievements', 'core' )->ruleDescriptionBadge( 'milestone', Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone_nth', FALSE, [ 'pluralize' => [ $rule->filters['milestone'] ] ] ) )
|
||||
],
|
||||
'sprintf' => Member::loggedIn()->language()->addToStack('AchievementAction__Highlight_title')
|
||||
] );
|
||||
@@ -161,7 +161,12 @@ class Highlight extends ContentAchievementActionAbstract // NOTE: Other classes
|
||||
$conditions[] = $nodeCondition;
|
||||
}
|
||||
|
||||
return Theme::i()->getTemplate( 'achievements' )->ruleDescription( Member::loggedIn()->language()->addToStack( 'AchievementAction__Highlight_title' ), $conditions );
|
||||
if( $questCondition = $this->_questFilterDescription( $rule ) )
|
||||
{
|
||||
$conditions[] = $questCondition;
|
||||
}
|
||||
|
||||
return Theme::i()->getTemplate( 'achievements', 'core' )->ruleDescription( Member::loggedIn()->language()->addToStack( 'AchievementAction__Highlight_title' ), $conditions );
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,6 +15,7 @@ use IPS\core\Achievements\Actions\AchievementActionAbstract;
|
||||
use IPS\core\Achievements\Rule;
|
||||
use IPS\Db;
|
||||
use IPS\Helpers\Form\Number;
|
||||
use IPS\Helpers\Form\Select;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Member;
|
||||
use IPS\Member\Club;
|
||||
@@ -53,12 +54,23 @@ class JoinClub extends AchievementActionAbstract // NOTE: Other classes exist to
|
||||
*/
|
||||
public function filters( ?array $filters, Url $url ): array
|
||||
{
|
||||
$return = parent::filters( $filters, $url );
|
||||
|
||||
$nthFilter = new Number( 'achievement_filter_club_joined_nth', ( $filters and isset( $filters['milestone'] ) and $filters['milestone'] ) ? $filters['milestone'] : 0, FALSE, [], NULL, Member::loggedIn()->language()->addToStack('achievement_filter_nth_their'), Member::loggedIn()->language()->addToStack('achievement_filter_JoinClub_nth_suffix') );
|
||||
$nthFilter->label = Member::loggedIn()->language()->addToStack('achievement_filter_JoinClub_nth');
|
||||
|
||||
$return = array();
|
||||
|
||||
$return['milestone'] = $nthFilter;
|
||||
|
||||
$clubs = iterator_to_array(
|
||||
Db::i()->select( 'id,name', 'core_clubs', [ '`type`!=?', Club::TYPE_PUBLIC ], 'name' )
|
||||
->setKeyField( 'id' )
|
||||
->setValueField( 'name' )
|
||||
);
|
||||
$return['clubs'] = new Select( 'achievement_filter_JoinClub_club', ( $filters and isset( $filters['clubs'] ) and $filters['clubs'] ) ? $filters['clubs'] : NULL, FALSE, [
|
||||
'options' => $clubs,
|
||||
'multiple' => true,
|
||||
'noDefault' => true
|
||||
], NULL, Member::loggedIn()->language()->addToStack('achievement_filter_JoinClub_club_prefix' ), null, 'achievement_filter_JoinClub_club' );
|
||||
|
||||
return $return;
|
||||
}
|
||||
@@ -71,12 +83,18 @@ class JoinClub extends AchievementActionAbstract // NOTE: Other classes exist to
|
||||
*/
|
||||
public function formatFilterValues( array $values ): array
|
||||
{
|
||||
$return = [];
|
||||
$return = parent::formatFilterValues( $values );
|
||||
|
||||
if ( isset( $values['achievement_filter_club_joined_nth'] ) )
|
||||
{
|
||||
$return['milestone'] = $values['achievement_filter_club_joined_nth'];
|
||||
}
|
||||
|
||||
if( isset( $values['achievement_filter_JoinClub_club'] ) )
|
||||
{
|
||||
$return['clubs'] = $values['achievement_filter_JoinClub_club'];
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
@@ -106,6 +124,14 @@ class JoinClub extends AchievementActionAbstract // NOTE: Other classes exist to
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
if( isset( $filters['clubs'] ) and $extra instanceof Club )
|
||||
{
|
||||
if( !in_array( $extra->id, $filters['clubs'] ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
@@ -136,6 +162,53 @@ class JoinClub extends AchievementActionAbstract // NOTE: Other classes exist to
|
||||
{
|
||||
return [ $extra->owner ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the member has already completed this rule.
|
||||
* Used for retroactive rule completion.
|
||||
* So far, this is only used in Quests, but may be used elsewhere at a later point.
|
||||
*
|
||||
* @param Member $member
|
||||
* @param array $filters
|
||||
* @return bool
|
||||
*/
|
||||
public function isRuleCompleted( Member $member, array $filters ) : bool
|
||||
{
|
||||
if( !Settings::i()->clubs )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$where = [
|
||||
[ 'member_id=?', $member->member_id ],
|
||||
[ Db::i()->in( 'status', [ Club::STATUS_EXPIRED, Club::STATUS_EXPIRED_MODERATOR, Club::STATUS_MODERATOR, Club::STATUS_MEMBER, Club::STATUS_LEADER ] ) ]
|
||||
];
|
||||
|
||||
$clubsJoined = iterator_to_array(
|
||||
Db::i()->select( 'club_id', 'core_clubs_memberships', $where )
|
||||
);
|
||||
|
||||
if( !count( $clubsJoined ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if( !empty( $filters['clubs'] ) )
|
||||
{
|
||||
$match = array_intersect( $filters['clubs'], $clubsJoined );
|
||||
if( !count( $match ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if( !empty( $filters['milestone'] ) )
|
||||
{
|
||||
return count( $clubsJoined ) >= $filters['milestone'];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get identifier to prevent the member being awarded points for the same action twice
|
||||
@@ -178,13 +251,28 @@ class JoinClub extends AchievementActionAbstract // NOTE: Other classes exist to
|
||||
{
|
||||
$conditions[] = Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone', FALSE, [
|
||||
'htmlsprintf' => [
|
||||
Theme::i()->getTemplate( 'achievements' )->ruleDescriptionBadge( 'milestone', Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone_nth', FALSE, [ 'pluralize' => [ $rule->filters['milestone'] ] ] ) )
|
||||
Theme::i()->getTemplate( 'achievements', 'core' )->ruleDescriptionBadge( 'milestone', Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone_nth', FALSE, [ 'pluralize' => [ $rule->filters['milestone'] ] ] ) )
|
||||
],
|
||||
'sprintf' => [ Member::loggedIn()->language()->addToStack('AchievementAction__JoinClub_title_generic') ]
|
||||
] );
|
||||
}
|
||||
|
||||
return Theme::i()->getTemplate( 'achievements' )->ruleDescription(
|
||||
if( isset( $rule->filters['clubs'] ) )
|
||||
{
|
||||
$clubNames = iterator_to_array( Db::i()->select( 'name', 'core_clubs', Db::i()->in( 'id', $rule->filters['clubs'] ), 'name' ) );
|
||||
$conditions[] = Member::loggedIn()->language()->addToStack( 'achievements_title_filter_clubs', false, [
|
||||
'htmlsprintf' => [
|
||||
Theme::i()->getTemplate( 'achievements', 'core' )->ruleDescriptionBadge( 'clubs', Member::loggedIn()->language()->addToStack( 'achievement_filter_JoinClub_club_title_pl', false, [ 'pluralize' => [ count( $clubNames ) ] ] ), $clubNames )
|
||||
]
|
||||
] );
|
||||
}
|
||||
|
||||
if( $questCondition = $this->_questFilterDescription( $rule ) )
|
||||
{
|
||||
$conditions[] = $questCondition;
|
||||
}
|
||||
|
||||
return Theme::i()->getTemplate( 'achievements', 'core' )->ruleDescription(
|
||||
Member::loggedIn()->language()->addToStack( 'AchievementAction__JoinClub_title' ),
|
||||
$conditions
|
||||
);
|
||||
|
||||
@@ -52,11 +52,11 @@ class NewClub extends AchievementActionAbstract // NOTE: Other classes exist to
|
||||
*/
|
||||
public function filters( ?array $filters, Url $url ): array
|
||||
{
|
||||
$return = parent::filters( $filters, $url );
|
||||
|
||||
$nthFilter = new Number( 'achievement_filter_club_nth', ( $filters and isset( $filters['milestone'] ) and $filters['milestone'] ) ? $filters['milestone'] : 0, FALSE, [], NULL, Member::loggedIn()->language()->addToStack('achievement_filter_nth_their'), Member::loggedIn()->language()->addToStack('achievement_filter_NewClub_nth_suffix') );
|
||||
$nthFilter->label = Member::loggedIn()->language()->addToStack('achievement_filter_NewClub_nth');
|
||||
|
||||
$return = array();
|
||||
|
||||
$return['milestone'] = $nthFilter;
|
||||
|
||||
return $return;
|
||||
@@ -70,7 +70,7 @@ class NewClub extends AchievementActionAbstract // NOTE: Other classes exist to
|
||||
*/
|
||||
public function formatFilterValues( array $values ): array
|
||||
{
|
||||
$return = [];
|
||||
$return = parent::formatFilterValues( $values );
|
||||
|
||||
if ( isset( $values['achievement_filter_club_nth'] ) )
|
||||
{
|
||||
@@ -121,6 +121,31 @@ class NewClub extends AchievementActionAbstract // NOTE: Other classes exist to
|
||||
{
|
||||
return (string) $extra->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the member has already completed this rule.
|
||||
* Used for retroactive rule completion.
|
||||
* So far, this is only used in Quests, but may be used elsewhere at a later point.
|
||||
*
|
||||
* @param Member $member
|
||||
* @param array $filters
|
||||
* @return bool
|
||||
*/
|
||||
public function isRuleCompleted( Member $member, array $filters ) : bool
|
||||
{
|
||||
if( !Settings::i()->clubs )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
$total = Db::i()->select( 'count(*)', 'core_clubs', [ 'owner=? and approved=?', $member->member_id, 1 ] )->first();
|
||||
if( !empty( $filters['milestone'] ) )
|
||||
{
|
||||
return $total >= $filters['milestone'];
|
||||
}
|
||||
|
||||
return $total > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a description for this action to show in the log
|
||||
@@ -150,13 +175,18 @@ class NewClub extends AchievementActionAbstract // NOTE: Other classes exist to
|
||||
{
|
||||
$conditions[] = Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone', FALSE, [
|
||||
'htmlsprintf' => [
|
||||
Theme::i()->getTemplate( 'achievements' )->ruleDescriptionBadge( 'milestone', Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone_nth', FALSE, [ 'pluralize' => [ $rule->filters['milestone'] ] ] ) )
|
||||
Theme::i()->getTemplate( 'achievements', 'core' )->ruleDescriptionBadge( 'milestone', Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone_nth', FALSE, [ 'pluralize' => [ $rule->filters['milestone'] ] ] ) )
|
||||
],
|
||||
'sprintf' => [ Member::loggedIn()->language()->addToStack('AchievementAction__NewClub_title_generic') ]
|
||||
] );
|
||||
}
|
||||
|
||||
return Theme::i()->getTemplate( 'achievements' )->ruleDescription(
|
||||
if( $questCondition = $this->_questFilterDescription( $rule ) )
|
||||
{
|
||||
$conditions[] = $questCondition;
|
||||
}
|
||||
|
||||
return Theme::i()->getTemplate( 'achievements', 'core' )->ruleDescription(
|
||||
Member::loggedIn()->language()->addToStack( 'AchievementAction__NewClub_title' ),
|
||||
$conditions
|
||||
);
|
||||
|
||||
@@ -11,6 +11,8 @@ namespace IPS\core\extensions\core\AchievementAction;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
|
||||
use IPS\Application;
|
||||
use IPS\Content\Item;
|
||||
use IPS\core\Achievements\Actions\ContentAchievementActionAbstract;
|
||||
use IPS\core\Achievements\Rule;
|
||||
use IPS\Db;
|
||||
@@ -95,6 +97,67 @@ class NewContentItem extends ContentAchievementActionAbstract
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the member has already completed this rule.
|
||||
* Used for retroactive rule completion.
|
||||
* So far, this is only used in Quests, but may be used elsewhere at a later point.
|
||||
*
|
||||
* @param Member $member
|
||||
* @param array $filters
|
||||
* @return bool
|
||||
*/
|
||||
public function isRuleCompleted( Member $member, array $filters ) : bool
|
||||
{
|
||||
$total = 0;
|
||||
foreach( Application::allExtensions( 'core', 'ContentRouter', false ) as $extension )
|
||||
{
|
||||
foreach( $extension->classes as $class )
|
||||
{
|
||||
if( !empty( $filters['type'] ) and $filters['type'] != $class )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if( !isset( $class::$databaseColumnMap['author'] ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
/* @var Item $class */
|
||||
$where = [
|
||||
[ $class::$databasePrefix . $class::$databaseColumnMap['author'] . '=?', $member->member_id ]
|
||||
];
|
||||
|
||||
if( isset( $class::$databaseColumnMap['approved'] ) )
|
||||
{
|
||||
$where[] = [ $class::$databasePrefix . $class::$databaseColumnMap['approved'] . '=?', 1 ];
|
||||
}
|
||||
elseif( isset( $class::$databaseColumnMap['hidden'] ) )
|
||||
{
|
||||
$where[] = [ $class::$databasePrefix . $class::$databaseColumnMap['hidden'] . '=?', 0 ];
|
||||
}
|
||||
|
||||
if( !empty( $filters['type'] ) )
|
||||
{
|
||||
$key = 'nodes_' . str_replace( '\\', '-', $filters['type'] );
|
||||
if( !empty( $filters[ $key ] ) and isset( $class::$databaseColumnMap['container'] ) )
|
||||
{
|
||||
$where[] = [ Db::i()->in( $class::$databasePrefix . $class::$databaseColumnMap['container'], $filters[ $key ] ) ];
|
||||
}
|
||||
}
|
||||
|
||||
$total += (int) Db::i()->select( 'count(*)', $class::$databaseTable, $where )->first();
|
||||
}
|
||||
}
|
||||
|
||||
if( !empty( $filters['milestone'] ) )
|
||||
{
|
||||
return $total >= $filters['milestone'];
|
||||
}
|
||||
|
||||
return $total > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a description for this action to show in the log
|
||||
@@ -143,7 +206,7 @@ class NewContentItem extends ContentAchievementActionAbstract
|
||||
{
|
||||
$conditions[] = Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone', FALSE, [
|
||||
'htmlsprintf' => [
|
||||
Theme::i()->getTemplate( 'achievements' )->ruleDescriptionBadge( 'milestone', Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone_nth', FALSE, [ 'pluralize' => [ $rule->filters['milestone'] ] ] ) )
|
||||
Theme::i()->getTemplate( 'achievements', 'core' )->ruleDescriptionBadge( 'milestone', Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone_nth', FALSE, [ 'pluralize' => [ $rule->filters['milestone'] ] ] ) )
|
||||
],
|
||||
'sprintf' => [ $type ? Member::loggedIn()->language()->addToStack( $type::$title ) : Member::loggedIn()->language()->addToStack('AchievementAction__NewContentItem_title_generic') ]
|
||||
] );
|
||||
@@ -152,8 +215,12 @@ class NewContentItem extends ContentAchievementActionAbstract
|
||||
{
|
||||
$conditions[] = $nodeCondition;
|
||||
}
|
||||
if( $questCondition = $this->_questFilterDescription( $rule ) )
|
||||
{
|
||||
$conditions[] = $questCondition;
|
||||
}
|
||||
|
||||
return Theme::i()->getTemplate( 'achievements' )->ruleDescription(
|
||||
return Theme::i()->getTemplate( 'achievements', 'core' )->ruleDescription(
|
||||
$type ? Member::loggedIn()->language()->addToStack( 'AchievementAction__NewContentItem_title_t', FALSE, [ 'sprintf' => [ Member::loggedIn()->language()->addToStack( $type::$title ) ] ] ) : Member::loggedIn()->language()->addToStack( 'AchievementAction__NewContentItem_title' ),
|
||||
$conditions
|
||||
);
|
||||
|
||||
@@ -41,11 +41,11 @@ class NewPoll extends AchievementActionAbstract // NOTE: Other classes exist to
|
||||
*/
|
||||
public function filters( ?array $filters, Url $url ): array
|
||||
{
|
||||
$return = parent::filters( $filters, $url );
|
||||
|
||||
$nthFilter = new Number( 'achievement_filter_poll_nth', ( $filters and isset( $filters['milestone'] ) and $filters['milestone'] ) ? $filters['milestone'] : 0, FALSE, [], NULL, Member::loggedIn()->language()->addToStack('achievement_filter_nth_their'), Member::loggedIn()->language()->addToStack('achievement_filter_NewPoll_nth_suffix') );
|
||||
$nthFilter->label = Member::loggedIn()->language()->addToStack('achievement_filter_NewPoll_nth');
|
||||
|
||||
$return = array();
|
||||
|
||||
$return['milestone'] = $nthFilter;
|
||||
|
||||
return $return;
|
||||
@@ -59,7 +59,7 @@ class NewPoll extends AchievementActionAbstract // NOTE: Other classes exist to
|
||||
*/
|
||||
public function formatFilterValues( array $values ): array
|
||||
{
|
||||
$return = [];
|
||||
$return = parent::formatFilterValues( $values );
|
||||
|
||||
if ( isset( $values['achievement_filter_poll_nth'] ) )
|
||||
{
|
||||
@@ -97,6 +97,27 @@ class NewPoll extends AchievementActionAbstract // NOTE: Other classes exist to
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the member has already completed this rule.
|
||||
* Used for retroactive rule completion.
|
||||
* So far, this is only used in Quests, but may be used elsewhere at a later point.
|
||||
*
|
||||
* @param Member $member
|
||||
* @param array $filters
|
||||
* @return bool
|
||||
*/
|
||||
public function isRuleCompleted( Member $member, array $filters ) : bool
|
||||
{
|
||||
$total = (int) Db::i()->select( 'count(*)', 'core_polls', [ 'starter_id=?', $member->member_id ] )->first();
|
||||
|
||||
if( !empty( $filters['milestone'] ) )
|
||||
{
|
||||
return $total >= $filters['milestone'];
|
||||
}
|
||||
|
||||
return $total > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get identifier to prevent the member being awarded points for the same action twice
|
||||
@@ -136,13 +157,18 @@ class NewPoll extends AchievementActionAbstract // NOTE: Other classes exist to
|
||||
{
|
||||
$conditions[] = Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone', FALSE, [
|
||||
'htmlsprintf' => [
|
||||
Theme::i()->getTemplate( 'achievements' )->ruleDescriptionBadge( 'milestone', Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone_nth', FALSE, [ 'pluralize' => [ $rule->filters['milestone'] ] ] ) )
|
||||
Theme::i()->getTemplate( 'achievements', 'core' )->ruleDescriptionBadge( 'milestone', Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone_nth', FALSE, [ 'pluralize' => [ $rule->filters['milestone'] ] ] ) )
|
||||
],
|
||||
'sprintf' => [ Member::loggedIn()->language()->addToStack('AchievementAction__NewPoll_title_generic') ]
|
||||
] );
|
||||
}
|
||||
|
||||
return Theme::i()->getTemplate( 'achievements' )->ruleDescription(
|
||||
if( $questCondition = $this->_questFilterDescription( $rule ) )
|
||||
{
|
||||
$conditions[] = $questCondition;
|
||||
}
|
||||
|
||||
return Theme::i()->getTemplate( 'achievements', 'core' )->ruleDescription(
|
||||
Member::loggedIn()->language()->addToStack( 'AchievementAction__NewPoll_title' ),
|
||||
$conditions
|
||||
);
|
||||
|
||||
@@ -78,7 +78,7 @@ class Reaction extends ContentAchievementActionAbstract
|
||||
{
|
||||
$element->value = ['receiver', $element->value];
|
||||
}
|
||||
return Theme::i()->getTemplate( 'achievements' )->milestoneWithSubjectSwitch( $element->name, $element->value );
|
||||
return Theme::i()->getTemplate( 'achievements', 'core' )->milestoneWithSubjectSwitch( $element->name, $element->value );
|
||||
} ), NULL, NULL, NULL, 'achievement_filter_Reaction_nth' );
|
||||
|
||||
return $return;
|
||||
@@ -154,6 +154,46 @@ class Reaction extends ContentAchievementActionAbstract
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the member has already completed this rule.
|
||||
* Used for retroactive rule completion.
|
||||
* So far, this is only used in Quests, but may be used elsewhere at a later point.
|
||||
*
|
||||
* @param Member $member
|
||||
* @param array $filters
|
||||
* @return bool
|
||||
*/
|
||||
public function isRuleCompleted( Member $member, array $filters ) : bool
|
||||
{
|
||||
$where = [];
|
||||
|
||||
if( !empty( $filters['milestone'] ) )
|
||||
{
|
||||
$milestone = is_array( $filters['milestone'] ) ? $filters['milestone'][1] : $filters['milestone'];
|
||||
if( is_array( $filters['milestone'] ) and $filters['milestone'][0] == 'receiver' )
|
||||
{
|
||||
$where[] = [ 'member_received=?', $member->member_id ];
|
||||
}
|
||||
else
|
||||
{
|
||||
$where[] = [ 'member_id=?', $member->member_id ];
|
||||
}
|
||||
}
|
||||
|
||||
if( !empty( $filters['reactions'] ) )
|
||||
{
|
||||
$where[] = [ Db::i()->in( 'reaction', $filters['reactions'] ) ];
|
||||
}
|
||||
|
||||
$total = (int) Db::i()->select( 'count(*)', 'core_reputation_index', $where )->first();
|
||||
if( isset( $milestone ) )
|
||||
{
|
||||
return $total >= $milestone;
|
||||
}
|
||||
|
||||
return $total > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get identifier to prevent the member being awarded points for the same action twice
|
||||
@@ -255,7 +295,7 @@ class Reaction extends ContentAchievementActionAbstract
|
||||
|
||||
$conditions[] = Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone', FALSE, [
|
||||
'htmlsprintf' => [
|
||||
Theme::i()->getTemplate( 'achievements' )->ruleDescriptionBadge( 'milestone', Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone_nth', FALSE, [ 'pluralize' => [ $milestone ] ] ) )
|
||||
Theme::i()->getTemplate( 'achievements', 'core' )->ruleDescriptionBadge( 'milestone', Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone_nth', FALSE, [ 'pluralize' => [ $milestone ] ] ) )
|
||||
],
|
||||
'sprintf' => Member::loggedIn()->language()->addToStack('AchievementAction__Reaction_title_generic')
|
||||
] );
|
||||
@@ -275,7 +315,7 @@ class Reaction extends ContentAchievementActionAbstract
|
||||
{
|
||||
$conditions[] = Member::loggedIn()->language()->addToStack( 'achievements_title_filter_type', FALSE, [
|
||||
'htmlsprintf' => [
|
||||
Theme::i()->getTemplate( 'achievements' )->ruleDescriptionBadge( 'other',
|
||||
Theme::i()->getTemplate( 'achievements', 'core' )->ruleDescriptionBadge( 'other',
|
||||
count( $reactionNames ) === 1 ? $reactionNames[0] : Member::loggedIn()->language()->addToStack( 'AchievementAction__Reaction_types', FALSE, [ 'sprintf' => [
|
||||
count( $reactionNames ),
|
||||
] ] ),
|
||||
@@ -289,8 +329,12 @@ class Reaction extends ContentAchievementActionAbstract
|
||||
{
|
||||
$conditions[] = $nodeCondition;
|
||||
}
|
||||
if( $questCondition = $this->_questFilterDescription( $rule ) )
|
||||
{
|
||||
$conditions[] = $questCondition;
|
||||
}
|
||||
|
||||
return Theme::i()->getTemplate( 'achievements' )->ruleDescription(
|
||||
return Theme::i()->getTemplate( 'achievements', 'core' )->ruleDescription(
|
||||
( ( 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']) )
|
||||
? Member::loggedIn()->language()->addToStack( 'AchievementAction__Reaction_title' )
|
||||
: Member::loggedIn()->language()->addToStack( 'AchievementAction__Reaction_title_received' ),
|
||||
|
||||
@@ -134,6 +134,52 @@ class Review extends ContentAchievementActionAbstract
|
||||
{
|
||||
return [ $extra->item()->author() ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the member has already completed this rule.
|
||||
* Used for retroactive rule completion.
|
||||
* So far, this is only used in Quests, but may be used elsewhere at a later point.
|
||||
*
|
||||
* @param Member $member
|
||||
* @param array $filters
|
||||
* @return bool
|
||||
*/
|
||||
public function isRuleCompleted( Member $member, array $filters ) : bool
|
||||
{
|
||||
$total = 0;
|
||||
foreach( Application::allExtensions( 'core', 'ContentRouter', false ) as $extension )
|
||||
{
|
||||
foreach( $extension->classes as $itemClass )
|
||||
{
|
||||
if( !isset( $itemClass::$reviewClass ) or ( !empty( $filters['type'] ) and $filters['type'] != $itemClass::$reviewClass ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
/* @var \IPS\Content\Review $class */
|
||||
$class = $itemClass::$reviewClass;
|
||||
|
||||
/* @var array $databaseColumnMap */
|
||||
$where = [
|
||||
[ $class::$databasePrefix . $class::$databaseColumnMap['author'] . '=?', $member->member_id ]
|
||||
];
|
||||
|
||||
if( isset( $class::$databaseColumnMap['approved'] ) )
|
||||
{
|
||||
$where[] = [ $class::$databasePrefix . $class::$databaseColumnMap['approved'] . '=?', 1 ];
|
||||
}
|
||||
|
||||
$total += (int) Db::i()->select( 'count(*)', $class::$databaseTable, $where )->first();
|
||||
}
|
||||
}
|
||||
|
||||
if( !empty( $filters['milestone'] ) )
|
||||
{
|
||||
return $total >= $filters['milestone'];
|
||||
}
|
||||
|
||||
return $total > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a description for this action to show in the log
|
||||
@@ -178,7 +224,7 @@ class Review extends ContentAchievementActionAbstract
|
||||
{
|
||||
$conditions[] = Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone', FALSE, [
|
||||
'htmlsprintf' => [
|
||||
Theme::i()->getTemplate( 'achievements' )->ruleDescriptionBadge( 'milestone', Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone_nth', FALSE, [ 'pluralize' => [ $rule->filters['milestone'] ] ] ) )
|
||||
Theme::i()->getTemplate( 'achievements', 'core' )->ruleDescriptionBadge( 'milestone', Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone_nth', FALSE, [ 'pluralize' => [ $rule->filters['milestone'] ] ] ) )
|
||||
],
|
||||
'sprintf' => [ $type ? Member::loggedIn()->language()->addToStack( $type::$title, FALSE, [ 'strtolower' => TRUE ] ) : Member::loggedIn()->language()->addToStack('AchievementAction__Review_title_generic') ]
|
||||
] );
|
||||
@@ -187,8 +233,12 @@ class Review extends ContentAchievementActionAbstract
|
||||
{
|
||||
$conditions[] = $nodeCondition;
|
||||
}
|
||||
if( $questCondition = $this->_questFilterDescription( $rule ) )
|
||||
{
|
||||
$conditions[] = $questCondition;
|
||||
}
|
||||
|
||||
return Theme::i()->getTemplate( 'achievements' )->ruleDescription(
|
||||
return Theme::i()->getTemplate( 'achievements', 'core' )->ruleDescription(
|
||||
$type ? Member::loggedIn()->language()->addToStack( 'AchievementAction__NewContentItem_title_t', FALSE, [ 'sprintf' => [ Member::loggedIn()->language()->addToStack( $type::$title ) ] ] ) : Member::loggedIn()->language()->addToStack( 'AchievementAction__Review_title' ),
|
||||
$conditions
|
||||
);
|
||||
|
||||
@@ -44,6 +44,7 @@ class SessionStartDaily extends AchievementActionAbstract
|
||||
*/
|
||||
public function filters( ?array $filters, Url $url ): array
|
||||
{
|
||||
$return = parent::filters( $filters, $url );
|
||||
$return['milestone_registered'] = new Interval( 'achievement_filter_SessionStartDaily', ( $filters and isset( $filters['milestone_registered'] ) and $filters['milestone_registered'] ) ? $filters['milestone_registered'] : 0, FALSE, [ 'valueAs' => Interval::DAYS, 'min' => NULL ], NULL, Member::loggedIn()->language()->addToStack('achievement_filter_SessionStartDaily_prefix') );
|
||||
$return['milestone_concurrent'] = new Number( 'achievement_filter_SessionStartDaily_concurrent', ( $filters and isset( $filters['milestone_concurrent'] ) and $filters['milestone_concurrent'] ) ? $filters['milestone_concurrent'] : 0, FALSE, [], NULL, Member::loggedIn()->language()->addToStack('achievement_filter_SessionStartDaily_concurrent_prefix'), Member::loggedIn()->language()->addToStack('achievement_filter_SessionStartDaily_concurrent_suffix') );
|
||||
|
||||
@@ -58,7 +59,7 @@ class SessionStartDaily extends AchievementActionAbstract
|
||||
*/
|
||||
public function formatFilterValues( array $values ): array
|
||||
{
|
||||
$return = [];
|
||||
$return = parent::formatFilterValues( $values );
|
||||
if ( isset( $values['achievement_filter_SessionStartDaily'] ) )
|
||||
{
|
||||
$return['milestone_registered'] = $values['achievement_filter_SessionStartDaily'];
|
||||
@@ -157,7 +158,7 @@ class SessionStartDaily extends AchievementActionAbstract
|
||||
{
|
||||
$conditions[] = Member::loggedIn()->language()->addToStack( 'AchievementAction__SessionStartDaily_milestone', FALSE, [
|
||||
'htmlsprintf' => [
|
||||
Theme::i()->getTemplate( 'achievements' )->ruleDescriptionBadge( 'milestone_registered', Member::loggedIn()->language()->addToStack( 'f_days', FALSE, [ 'pluralize' => [ $rule->filters['milestone_registered'] ] ] ) )
|
||||
Theme::i()->getTemplate( 'achievements', 'core' )->ruleDescriptionBadge( 'milestone_registered', Member::loggedIn()->language()->addToStack( 'f_days', FALSE, [ 'pluralize' => [ $rule->filters['milestone_registered'] ] ] ) )
|
||||
],
|
||||
] );
|
||||
}
|
||||
@@ -165,11 +166,16 @@ class SessionStartDaily extends AchievementActionAbstract
|
||||
{
|
||||
$conditions[] = Member::loggedIn()->language()->addToStack( 'AchievementAction__SessionStartDaily_concurrent_milestone', FALSE, [
|
||||
'htmlsprintf' => [
|
||||
Theme::i()->getTemplate( 'achievements' )->ruleDescriptionBadge( 'milestone', Member::loggedIn()->language()->addToStack( 'f_days', FALSE, [ 'pluralize' => [ $rule->filters['milestone_concurrent'] ] ] ) )
|
||||
Theme::i()->getTemplate( 'achievements', 'core' )->ruleDescriptionBadge( 'milestone', Member::loggedIn()->language()->addToStack( 'f_days', FALSE, [ 'pluralize' => [ $rule->filters['milestone_concurrent'] ] ] ) )
|
||||
],
|
||||
] );
|
||||
}
|
||||
|
||||
if( $questCondition = $this->_questFilterDescription( $rule ) )
|
||||
{
|
||||
$conditions[] = $questCondition;
|
||||
}
|
||||
|
||||
return Theme::i()->getTemplate( 'achievements', 'core', 'admin' )->ruleDescription(
|
||||
Member::loggedIn()->language()->addToStack( 'AchievementAction__SessionStartDaily_title' ),
|
||||
$conditions
|
||||
|
||||
@@ -41,11 +41,10 @@ class VotePoll extends AchievementActionAbstract // NOTE: Other classes exist to
|
||||
*/
|
||||
public function filters( ?array $filters, Url $url ): array
|
||||
{
|
||||
$return = parent::filters( $filters, $url );
|
||||
$nthFilter = new Number( 'achievement_filter_poll_vote_nth', ( $filters and isset( $filters['milestone'] ) and $filters['milestone'] ) ? $filters['milestone'] : 0, FALSE, [], NULL, Member::loggedIn()->language()->addToStack('achievement_filter_nth_their'), Member::loggedIn()->language()->addToStack('achievement_filter_VotePoll_nth_suffix') );
|
||||
$nthFilter->label = Member::loggedIn()->language()->addToStack('achievement_filter_NewPoll_nth');
|
||||
|
||||
$return = array();
|
||||
|
||||
$return['milestone'] = $nthFilter;
|
||||
|
||||
return $return;
|
||||
@@ -59,7 +58,7 @@ class VotePoll extends AchievementActionAbstract // NOTE: Other classes exist to
|
||||
*/
|
||||
public function formatFilterValues( array $values ): array
|
||||
{
|
||||
$return = [];
|
||||
$return = parent::formatFilterValues( $values );
|
||||
|
||||
if ( isset( $values['achievement_filter_poll_vote_nth'] ) )
|
||||
{
|
||||
@@ -124,6 +123,27 @@ class VotePoll extends AchievementActionAbstract // NOTE: Other classes exist to
|
||||
{
|
||||
return [ $extra->author() ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the member has already completed this rule.
|
||||
* Used for retroactive rule completion.
|
||||
* So far, this is only used in Quests, but may be used elsewhere at a later point.
|
||||
*
|
||||
* @param Member $member
|
||||
* @param array $filters
|
||||
* @return bool
|
||||
*/
|
||||
public function isRuleCompleted( Member $member, array $filters ) : bool
|
||||
{
|
||||
$total = (int)Db::i()->select( 'count(*)', 'core_voters', [ 'member_id=?', $member->member_id ] )->first();
|
||||
|
||||
if( !empty( $filters['milestone'] ) )
|
||||
{
|
||||
return $total >= $filters['milestone'];
|
||||
}
|
||||
|
||||
return $total > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get identifier to prevent the member being awarded points for the same action twice
|
||||
@@ -163,13 +183,18 @@ class VotePoll extends AchievementActionAbstract // NOTE: Other classes exist to
|
||||
{
|
||||
$conditions[] = Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone', FALSE, [
|
||||
'htmlsprintf' => [
|
||||
Theme::i()->getTemplate( 'achievements' )->ruleDescriptionBadge( 'milestone', Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone_nth', FALSE, [ 'pluralize' => [ $rule->filters['milestone'] ] ] ) )
|
||||
Theme::i()->getTemplate( 'achievements', 'core' )->ruleDescriptionBadge( 'milestone', Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone_nth', FALSE, [ 'pluralize' => [ $rule->filters['milestone'] ] ] ) )
|
||||
],
|
||||
'sprintf' => [ Member::loggedIn()->language()->addToStack('AchievementAction__VotePoll_title_generic') ]
|
||||
] );
|
||||
}
|
||||
|
||||
return Theme::i()->getTemplate( 'achievements' )->ruleDescription(
|
||||
if( $questCondition = $this->_questFilterDescription( $rule ) )
|
||||
{
|
||||
$conditions[] = $questCondition;
|
||||
}
|
||||
|
||||
return Theme::i()->getTemplate( 'achievements', 'core' )->ruleDescription(
|
||||
Member::loggedIn()->language()->addToStack( 'AchievementAction__VotePoll_title' ),
|
||||
$conditions
|
||||
);
|
||||
|
||||
@@ -1,173 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief ACP Notification Extension
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
|
||||
* @since 25 Aug 2022
|
||||
*/
|
||||
|
||||
namespace IPS\core\extensions\core\AdminNotifications;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
|
||||
use IPS\core\AdminNotification;
|
||||
use IPS\Db;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Member;
|
||||
use function defined;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* ACP Notification Extension
|
||||
*/
|
||||
class ManualInterventionMessage extends AdminNotification
|
||||
{
|
||||
/**
|
||||
* @brief Identifier for what to group this notification type with on the settings form
|
||||
*/
|
||||
public static string $group = 'system';
|
||||
|
||||
/**
|
||||
* @brief Priority 1-5 (1 being highest) for this group compared to others
|
||||
*/
|
||||
public static int $groupPriority = 2;
|
||||
|
||||
/**
|
||||
* @brief Priority 1-5 (1 being highest) for this notification type compared to others in the same group
|
||||
*/
|
||||
public static int $itemPriority = 1;
|
||||
|
||||
/**
|
||||
* Title for settings
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function settingsTitle(): string
|
||||
{
|
||||
return 'acp_notification_ManualInterventionMessage';
|
||||
}
|
||||
|
||||
/**
|
||||
* Can a member access this type of notification?
|
||||
*
|
||||
* @param Member $member The member
|
||||
* @return bool
|
||||
*/
|
||||
public static function permissionCheck( Member $member ): bool
|
||||
{
|
||||
return Member::loggedIn()->hasAcpRestriction( 'core', 'applications', 'app_manage' ) OR Member::loggedIn()->hasAcpRestriction( 'core', 'applications', 'plugins_view' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this type of notification ever optional (controls if it will be selectable as "viewable" in settings)
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function mayBeOptional(): bool
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this type of notification might recur (controls what options will be available for the email setting)
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function mayRecur(): bool
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notification Title (full HTML, must be escaped where necessary)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function title(): string
|
||||
{
|
||||
return Member::loggedIn()->language()->addToStack( 'method_check_fail' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Notification Body (full HTML, must be escaped where necessary)
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function body(): ?string
|
||||
{
|
||||
return Member::loggedIn()->language()->addToStack( 'advise_removal_of_php8_incompatible_code' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Severity
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function severity(): string
|
||||
{
|
||||
return static::SEVERITY_HIGH;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismissible?
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function dismissible(): string
|
||||
{
|
||||
return static::DISMISSIBLE_UNTIL_RECUR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Style
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function style(): string
|
||||
{
|
||||
return $this->severity() === static::SEVERITY_HIGH ? static::STYLE_ERROR : static::STYLE_WARNING;
|
||||
}
|
||||
|
||||
/**
|
||||
* Quick link from popup menu
|
||||
*
|
||||
* @return Url|null
|
||||
*/
|
||||
public function link(): Url|null
|
||||
{
|
||||
return Url::internal( 'app=core&module=support&controller=support&do=methodCheck' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete(): void
|
||||
{
|
||||
parent::delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Should this notification dismiss itself?
|
||||
*
|
||||
* @note This is checked every time the notification shows. Should be lightweight.
|
||||
* @return bool
|
||||
*/
|
||||
public function selfDismiss(): bool
|
||||
{
|
||||
if( Db::i()->select( 'count(*)', 'core_applications', [ 'app_requires_manual_intervention=?', 1 ] )->first() )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ namespace IPS\core\extensions\core\CommunityEnhancements;
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
|
||||
use DomainException;
|
||||
use IPS\core\DataLayer;
|
||||
use IPS\Extensions\CommunityEnhancementsAbstract;
|
||||
use IPS\Helpers\Form;
|
||||
use IPS\Helpers\Form\Codemirror;
|
||||
@@ -87,7 +88,7 @@ class GoogleTagManager extends CommunityEnhancementsAbstract
|
||||
$form = new Form;
|
||||
|
||||
$form->add( new YesNo( 'googletag_enabled', Settings::i()->googletag_enabled, FALSE, array(), $validation ) );
|
||||
if ( Settings::i()->core_datalayer_enabled )
|
||||
if ( DataLayer::enabled() )
|
||||
{
|
||||
$form->add( new YesNo( 'core_datalayer_use_gtm', Settings::i()->core_datalayer_use_gtm, FALSE ) );
|
||||
$form->add( new Text( 'core_datalayer_gtmkey', Settings::i()->core_datalayer_gtmkey, FALSE ) );
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
<?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 12 Aug 2025
|
||||
*/
|
||||
|
||||
namespace IPS\core\extensions\core\CommunityEnhancements;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
|
||||
use DomainException;
|
||||
use IPS\core\Mailchimp as MailchimpClass;
|
||||
use IPS\Extensions\CommunityEnhancementsAbstract;
|
||||
use IPS\Helpers\Form;
|
||||
use IPS\Helpers\Form\Select;
|
||||
use IPS\Helpers\Form\Text;
|
||||
use IPS\Helpers\Wizard;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Member as MemberClass;
|
||||
use IPS\Output;
|
||||
use IPS\Request;
|
||||
use IPS\Settings;
|
||||
use LogicException;
|
||||
use function defined;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Community Enhancement
|
||||
*/
|
||||
class Mailchimp extends CommunityEnhancementsAbstract
|
||||
{
|
||||
/**
|
||||
* @brief Enhancement is enabled?
|
||||
*/
|
||||
public bool $enabled = FALSE;
|
||||
|
||||
/**
|
||||
* @brief IPS-provided enhancement?
|
||||
*/
|
||||
public bool $ips = FALSE;
|
||||
|
||||
/**
|
||||
* @brief Enhancement has configuration options?
|
||||
*/
|
||||
public bool $hasOptions = TRUE;
|
||||
|
||||
/**
|
||||
* @brief Icon data
|
||||
*/
|
||||
public string $icon = "mailchimp.png";
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->enabled = ( Settings::i()->mailchimp_enabled and Settings::i()->mailchimp_api_key and Settings::i()->mailchimp_server_prefix );
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function edit() : void
|
||||
{
|
||||
$steps = [];
|
||||
if( empty( Settings::i()->mailchimp_api_key ) or empty( Settings::i()->mailchimp_server_prefix ) or isset( Request::i()->change ) )
|
||||
{
|
||||
$steps[ 'mailchimp__apiKey' ] = function()
|
||||
{
|
||||
$form = new Form( 'mailchimpApiForm', 'continue' );
|
||||
$form->add( new Text( 'mailchimp_api_key', Settings::i()->mailchimp_api_key, true ) );
|
||||
$form->add( new Text( 'mailchimp_server_prefix', Settings::i()->mailchimp_server_prefix, true ) );
|
||||
if( $values = $form->values() )
|
||||
{
|
||||
try
|
||||
{
|
||||
if ( preg_match('#https?://(us\d+)\.admin\.mailchimp\.com#i', $values['mailchimp_server_prefix'], $matches ) )
|
||||
{
|
||||
/* A common mistake is to enter the full URL, so we extract the prefix */
|
||||
$values['mailchimp_server_prefix'] = strtolower( $matches[1] );
|
||||
}
|
||||
|
||||
MailchimpClass::i()->test( $values['mailchimp_api_key'], $values['mailchimp_server_prefix'] );
|
||||
$form->saveAsSettings( $values );
|
||||
return $values;
|
||||
}
|
||||
catch( DomainException $e )
|
||||
{
|
||||
$form->error = $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
return (string) $form;
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
Output::i()->sidebar['actions']['api'] = [
|
||||
'title' => 'mailchimp_change_key',
|
||||
'link' => Url::internal( "app=core&module=applications&controller=enhancements&do=edit&id=core_Mailchimp&change=1" ),
|
||||
'icon' => 'cog'
|
||||
];
|
||||
}
|
||||
|
||||
$steps['mailchimp__list'] = function()
|
||||
{
|
||||
$list = Settings::i()->mailchimp_lists ? json_decode( Settings::i()->mailchimp_lists, true ) : [];
|
||||
$form = new Form( 'mailchimpList', 'save' );
|
||||
|
||||
$lists = MailchimpClass::i()->getLists();
|
||||
$form->add( new Select( 'mailchimp_lists', $list['id'] ?? null, true, [
|
||||
'options' => $lists
|
||||
] ) );
|
||||
if( $values = $form->values() )
|
||||
{
|
||||
$form->saveAsSettings( [
|
||||
'mailchimp_lists' => json_encode( [ 'id' => $values['mailchimp_lists'], 'name' => $lists[ $values['mailchimp_lists'] ] ] ),
|
||||
'mailchimp_enabled' => true
|
||||
]);
|
||||
|
||||
Output::i()->redirect( Url::internal( "app=core&module=applications&controller=enhancements" ), MemberClass::loggedIn()->language()->addToStack( 'saved' ) );
|
||||
}
|
||||
|
||||
return (string) $form;
|
||||
};
|
||||
|
||||
Output::i()->output = new Wizard( $steps, Url::internal( "app=core&module=applications&controller=enhancements&do=edit&id=core_Mailchimp" ), count( $steps ) > 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable/Disable
|
||||
*
|
||||
* @param $enabled bool Enable/Disable
|
||||
* @return void
|
||||
* @throws LogicException
|
||||
*/
|
||||
public function toggle( bool $enabled ) : void
|
||||
{
|
||||
/* If we're disabling, just disable */
|
||||
if( !$enabled )
|
||||
{
|
||||
Settings::i()->changeValues( array( 'mailchimp_enabled' => 0 ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
if( Settings::i()->mailchimp_api_key and Settings::i()->mailchimp_server_prefix )
|
||||
{
|
||||
Settings::i()->changeValues( array( 'mailchimp_enabled' => 1 ) );
|
||||
|
||||
/* Test the connection, this will throw a Domain exception if it fails, which will take us back to the settings page */
|
||||
MailchimpClass::i()->test();
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Otherwise we need to let them enter an API key before we can enable. Throwing an exception causes you to be redirected to the settings page. */
|
||||
throw new DomainException;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@ use IPS\Theme;
|
||||
use LogicException;
|
||||
use OutOfRangeException;
|
||||
use function defined;
|
||||
use const IPS\CIC;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
@@ -65,6 +66,7 @@ class Zapier extends CommunityEnhancementsAbstract
|
||||
'core/webhooks/DELETEitem' => array( 'access' => TRUE ),
|
||||
'core/promotions/GETindex' => array( 'access' => TRUE ),
|
||||
'core/promotions/GETitem' => array( 'access' => TRUE ),
|
||||
'core/content/GETitem' => array( 'access' => TRUE ),
|
||||
);
|
||||
|
||||
if ( Application::appIsEnabled('forums') )
|
||||
@@ -159,6 +161,13 @@ class Zapier extends CommunityEnhancementsAbstract
|
||||
$return['cms/reviews/GETitem'] = array( 'access' => TRUE );
|
||||
$return['cms/reviews/POSTindex'] = array( 'access' => TRUE );
|
||||
}
|
||||
|
||||
if ( Application::appIsEnabled('courses') )
|
||||
{
|
||||
$return['courses/courses/GETindex'] = array( 'access' => TRUE );
|
||||
$return['courses/courses/GETitem'] = array( 'access' => TRUE );
|
||||
$return['courses/courses/POSTitem'] = array( 'access' => TRUE );
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
@@ -302,6 +311,10 @@ class Zapier extends CommunityEnhancementsAbstract
|
||||
*/
|
||||
protected function testSettings() : void
|
||||
{
|
||||
if( CIC )
|
||||
{
|
||||
return;
|
||||
}
|
||||
if ( !Settings::i()->use_friendly_urls or !Settings::i()->htaccess_mod_rewrite )
|
||||
{
|
||||
throw new DomainException( 'zapier_error_friendly_urls' );
|
||||
|
||||
@@ -33,7 +33,7 @@ class Clubs extends ContentRouterAbstract
|
||||
/**
|
||||
* @brief Classes of embeddable content
|
||||
*/
|
||||
protected array $embeddableContent = array();
|
||||
public array $embeddableContent = array();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
||||
@@ -26,6 +26,7 @@ use IPS\Settings;
|
||||
use IPS\Text\Parser;
|
||||
use LogicException;
|
||||
use function defined;
|
||||
use function is_array;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
@@ -179,7 +180,7 @@ class Admin extends EditorLocationsAbstract
|
||||
}
|
||||
catch( InvalidArgumentException $e )
|
||||
{
|
||||
if( $callback[1] == 'parseStatic' AND $e->getcode() == 103014 )
|
||||
if( is_array( $callback ) and $callback[1] == 'parseStatic' AND $e->getcode() == 103014 )
|
||||
{
|
||||
$rebuilt = preg_replace( "#\[/?([^\]]+?)\]#", '', $word['word_custom'] );
|
||||
}
|
||||
@@ -206,7 +207,7 @@ class Admin extends EditorLocationsAbstract
|
||||
}
|
||||
catch( InvalidArgumentException $e )
|
||||
{
|
||||
if( $callback[1] == 'parseStatic' AND $e->getcode() == 103014 )
|
||||
if( is_array( $callback ) and $callback[1] == 'parseStatic' AND $e->getcode() == 103014 )
|
||||
{
|
||||
$newMessage = preg_replace( "#\[/?([^\]]+?)\]#", '', Settings::i()->site_offline_message );
|
||||
}
|
||||
@@ -234,7 +235,7 @@ class Admin extends EditorLocationsAbstract
|
||||
}
|
||||
catch( InvalidArgumentException $e )
|
||||
{
|
||||
if( $callback[1] == 'parseStatic' AND $e->getcode() == 103014 )
|
||||
if( is_array( $callback ) and $callback[1] == 'parseStatic' AND $e->getcode() == 103014 )
|
||||
{
|
||||
$rebuilt = preg_replace( "#\[/?([^\]]+?)\]#", '', $application['app_disabled_message'] );
|
||||
}
|
||||
@@ -265,7 +266,7 @@ class Admin extends EditorLocationsAbstract
|
||||
}
|
||||
catch( InvalidArgumentException $e )
|
||||
{
|
||||
if( $callback[1] == 'parseStatic' AND $e->getcode() == 103014 )
|
||||
if( is_array( $callback ) and $callback[1] == 'parseStatic' AND $e->getcode() == 103014 )
|
||||
{
|
||||
$rebuilt = preg_replace( "#\[/?([^\]]+?)\]#", '', $mmod['topic_reply_content'] );
|
||||
}
|
||||
@@ -298,7 +299,7 @@ class Admin extends EditorLocationsAbstract
|
||||
}
|
||||
catch( InvalidArgumentException $e )
|
||||
{
|
||||
if( $callback[1] == 'parseStatic' AND $e->getcode() == 103014 )
|
||||
if( is_array( $callback ) and $callback[1] == 'parseStatic' AND $e->getcode() == 103014 )
|
||||
{
|
||||
$rebuilt = preg_replace( "#\[/?([^\]]+?)\]#", '', $mail['mail_content'] );
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ class Alert extends EditorLocationsAbstract
|
||||
}
|
||||
catch( InvalidArgumentException $e )
|
||||
{
|
||||
if( $callback[1] == 'parseStatic' AND $e->getcode() == 103014 )
|
||||
if( is_array( $callback ) and $callback[1] == 'parseStatic' AND $e->getcode() == 103014 )
|
||||
{
|
||||
$rebuilt = preg_replace( "#\[/?([^\]]+?)\]#", '', $alert['alert_content'] );
|
||||
}
|
||||
|
||||
@@ -141,7 +141,7 @@ class Announcement extends EditorLocationsAbstract
|
||||
/* Update */
|
||||
try
|
||||
{
|
||||
if( $callback[1] === 'parseStatic' )
|
||||
if( is_array( $callback ) and $callback[1] === 'parseStatic' )
|
||||
{
|
||||
$rebuilt = $callback( $announcement['announce_content'], Member::load( $announcement['announce_member_id'] ), FALSE, 'core_Announcement' );
|
||||
}
|
||||
@@ -152,7 +152,7 @@ class Announcement extends EditorLocationsAbstract
|
||||
}
|
||||
catch( InvalidArgumentException $e )
|
||||
{
|
||||
if( $callback[1] == 'parseStatic' AND $e->getcode() == 103014 )
|
||||
if( is_array( $callback ) and $callback[1] == 'parseStatic' AND $e->getcode() == 103014 )
|
||||
{
|
||||
$rebuilt = preg_replace( "#\[/?([^\]]+?)\]#", '', $announcement['announce_content'] );
|
||||
}
|
||||
|
||||
@@ -176,7 +176,7 @@ class Clubs extends EditorLocationsAbstract
|
||||
}
|
||||
catch( InvalidArgumentException $e )
|
||||
{
|
||||
if( $callback[1] == 'parseStatic' AND $e->getcode() == 103014 )
|
||||
if( is_array( $callback ) and $callback[1] == 'parseStatic' AND $e->getcode() == 103014 )
|
||||
{
|
||||
$rebuilt = preg_replace( "#\[/?([^\]]+?)\]#", '', $clubFieldValues[ $fieldId ] );
|
||||
}
|
||||
|
||||
@@ -205,7 +205,7 @@ class CustomField extends EditorLocationsAbstract
|
||||
}
|
||||
catch( InvalidArgumentException $e )
|
||||
{
|
||||
if( $callback[1] == 'parseStatic' AND $e->getcode() == 103014 )
|
||||
if( is_array( $callback ) and $callback[1] == 'parseStatic' AND $e->getcode() == 103014 )
|
||||
{
|
||||
$rebuilt = preg_replace( "#\[/?([^\]]+?)\]#", '', $member[ $fieldId ] );
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace IPS\core\extensions\core\EditorLocations;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
|
||||
use Exception;
|
||||
use IPS\Content;
|
||||
use IPS\core\Messenger\Conversation;
|
||||
use IPS\core\Messenger\Message;
|
||||
@@ -83,7 +84,14 @@ class Messaging extends EditorLocationsAbstract
|
||||
*/
|
||||
public function attachmentLookup( ?int $id1=NULL, ?int $id2=NULL, ?string $id3=NULL ): Model|Content|Url|Member|null
|
||||
{
|
||||
return Message::load( $id2 );
|
||||
try
|
||||
{
|
||||
return Message::load( $id2 );
|
||||
}
|
||||
catch( Exception $e )
|
||||
{
|
||||
throw new LogicException;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace IPS\core\extensions\core\EditorLocations;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
|
||||
use Exception;
|
||||
use IPS\Content;
|
||||
use IPS\Db;
|
||||
use IPS\Extensions\EditorLocationsAbstract;
|
||||
@@ -89,7 +90,14 @@ class Meta extends EditorLocationsAbstract
|
||||
*/
|
||||
public function attachmentLookup( int $id1=NULL, int $id2=NULL, string $id3=NULL ): Model|Content|Url|Member|null
|
||||
{
|
||||
$data = Db::i()->select( '*', 'core_content_meta', array( "meta_id=? AND meta_type=?", $id1, $id3 ) )->first();
|
||||
return $data['meta_class']::load( $data['meta_item_id'] );
|
||||
try
|
||||
{
|
||||
$data = Db::i()->select( '*', 'core_content_meta', ["meta_id=? AND meta_type=?", $id1, $id3] )->first();
|
||||
return $data[ 'meta_class' ]::load( $data[ 'meta_item_id' ] );
|
||||
}
|
||||
catch( Exception $e )
|
||||
{
|
||||
throw new LogicException;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -167,7 +167,7 @@ class Modcp extends EditorLocationsAbstract
|
||||
}
|
||||
catch( InvalidArgumentException $e )
|
||||
{
|
||||
if( $callback[1] == 'parseStatic' AND $e->getcode() == 103014 )
|
||||
if( is_array( $callback ) and $callback[1] == 'parseStatic' AND $e->getcode() == 103014 )
|
||||
{
|
||||
$update[ $k ] = preg_replace( "#\[/?([^\]]+?)\]#", '', $log[ $k ] );
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ class Reports extends EditorLocationsAbstract
|
||||
}
|
||||
catch( InvalidArgumentException $e )
|
||||
{
|
||||
if( $callback[1] == 'parseStatic' AND $e->getcode() == 103014 )
|
||||
if( is_array( $callback ) and $callback[1] == 'parseStatic' AND $e->getcode() == 103014 )
|
||||
{
|
||||
$update['report'] = preg_replace( "#\[/?([^\]]+?)\]#", '', $report['report'] );
|
||||
}
|
||||
@@ -195,7 +195,7 @@ class Reports extends EditorLocationsAbstract
|
||||
}
|
||||
catch( InvalidArgumentException $e )
|
||||
{
|
||||
if( $callback[1] == 'parseStatic' AND $e->getcode() == 103014 )
|
||||
if( is_array( $callback ) and $callback[1] == 'parseStatic' AND $e->getcode() == 103014 )
|
||||
{
|
||||
$updateComment = preg_replace( "#\[/?([^\]]+?)\]#", '', $comment['comment'] );
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@ class Signatures extends EditorLocationsAbstract
|
||||
}
|
||||
catch( InvalidArgumentException $e )
|
||||
{
|
||||
if( $callback == 'parseStatic' AND $e->getcode() == 103014 )
|
||||
if( is_array( $callback ) and $callback[1] == 'parseStatic' AND $e->getcode() == 103014 )
|
||||
{
|
||||
$rebuilt = preg_replace( "#\[/?([^\]]+?)\]#", '', $member['signature'] );
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ class Staffdirectory extends EditorLocationsAbstract
|
||||
}
|
||||
catch( InvalidArgumentException $e )
|
||||
{
|
||||
if( $callback[1] == 'parseStatic' AND $e->getcode() == 103014 )
|
||||
if( is_array( $callback ) and $callback[1] == 'parseStatic' AND $e->getcode() == 103014 )
|
||||
{
|
||||
$rebuilt = preg_replace( "#\[/?([^\]]+?)\]#", '', $word['word_custom'] );
|
||||
}
|
||||
|
||||
@@ -199,7 +199,7 @@ class Widget extends EditorLocationsAbstract
|
||||
/* Rebuild language bits */
|
||||
foreach( Db::i()->select( '*', 'core_sys_lang_words', array( 'word_key=?', $string ), 'word_id ASC', array( $offset, $max ) ) as $word )
|
||||
{
|
||||
$rebuilt = $this->_rebuildWidgetContent( $word['word_custom'], $callback );
|
||||
$rebuilt = $this->_rebuildWidgetContent( $word['word_custom'] ?? $word['word_default'], $callback );
|
||||
Db::i()->update( 'core_sys_lang_words', array( 'word_custom' => $rebuilt ), array( 'word_id=?', $word['word_id'] ) );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ class CustomBadges extends FileStorageAbstract
|
||||
Db::i()->update( 'core_custom_badges', [ 'file' => (string) $file ], [ 'id=?', $row['id'] ] );
|
||||
}
|
||||
}
|
||||
catch( OutOfRangeException ){}
|
||||
catch( \Exception ){}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -14,10 +14,12 @@ namespace IPS\core\extensions\core\FrontNavigation;
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
|
||||
use InvalidArgumentException;
|
||||
use IPS\Application;
|
||||
use IPS\core\FrontNavigation\FrontNavigationAbstract;
|
||||
use IPS\Helpers\Form\Translatable;
|
||||
use IPS\Helpers\Form\YesNo;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Http\Url\Exception as UrlException;
|
||||
use IPS\Http\Url\Friendly;
|
||||
use IPS\Lang;
|
||||
use IPS\Member;
|
||||
@@ -55,6 +57,27 @@ class CustomItem extends FrontNavigationAbstract
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Can the currently logged in user see this menu item?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function canView() : bool
|
||||
{
|
||||
/* This might link to an internal app that is disabled, so let's try to figure out where we are */
|
||||
if( isset( $this->configuration['menu_custom_item_url'] ) )
|
||||
{
|
||||
/* See if we can parse this out to find an application */
|
||||
parse_str( $this->configuration['menu_custom_item_url'], $queryString );
|
||||
if( isset( $queryString['app'] ) and !Application::appIsEnabled( $queryString['app'] ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return parent::canView();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get configuration fields
|
||||
@@ -159,7 +182,18 @@ class CustomItem extends FrontNavigationAbstract
|
||||
{
|
||||
if ( isset( $this->configuration['internal'] ) )
|
||||
{
|
||||
return Url::internal( urldecode( $this->configuration['menu_custom_item_url'] ), 'front', $this->configuration['internal'], $this->configuration['seoTitles'] ?? array() );
|
||||
try
|
||||
{
|
||||
return Url::internal( urldecode( $this->configuration['menu_custom_item_url'] ), 'front', $this->configuration['internal'], $this->configuration['seoTitles'] ?? array() );
|
||||
}
|
||||
catch( UrlException $e )
|
||||
{
|
||||
/* We shouldn't get this far, because the canView method should stop it, but just in case */
|
||||
if( $e->getMessage() == "INVALID_SEO_TEMPLATE" )
|
||||
{
|
||||
return '#';
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -17,10 +17,13 @@ use IPS\Content\Tag;
|
||||
use IPS\core\FrontNavigation\FrontNavigationAbstract;
|
||||
use IPS\Db;
|
||||
use IPS\Helpers\Form\Select;
|
||||
use IPS\Helpers\Form\Text;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Member;
|
||||
use IPS\Request;
|
||||
use IPS\Settings;
|
||||
use OutOfRangeException;
|
||||
use function array_merge;
|
||||
use function defined;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
@@ -150,14 +153,34 @@ class Tags extends FrontNavigationAbstract
|
||||
*/
|
||||
public static function configuration( array $existingConfiguration, ?int $id = NULL ): array
|
||||
{
|
||||
$tags = iterator_to_array(
|
||||
Db::i()->select( 'tag_id, tag_text', 'core_tags_data', null, 'tag_text' )
|
||||
->setKeyField( 'tag_id' )
|
||||
->setValueField( 'tag_text' )
|
||||
);
|
||||
$existingValue = null;
|
||||
/* We have an ID, but the auto complete needs text */
|
||||
if ( ! empty( $existingConfiguration['id'] ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
$tag = Tag::load( $existingConfiguration['id'] );
|
||||
$existingValue = $tag->text;
|
||||
}
|
||||
catch ( \OutOfRangeException )
|
||||
{
|
||||
/* Can't find it */
|
||||
}
|
||||
}
|
||||
|
||||
$options = array(
|
||||
'autocomplete' => array(
|
||||
'unique' => true,
|
||||
'source' => Tag::getStore(),
|
||||
'freeChoice' => false,
|
||||
'minimized' => true,
|
||||
'maxItems' => 1,
|
||||
'alphabetical' => true,
|
||||
'disallowedCharacters' => array( '#' ), // @todo Pending \IPS\Http\Url rework, hashes cannot be used in URLs
|
||||
) );
|
||||
|
||||
return [
|
||||
new Select( 'tag', $existingConfiguration['id'] ?? null, null, [ 'options' => [ '' => ''] + $tags ] )
|
||||
new Text( 'tag', $existingValue, false, $options )
|
||||
];
|
||||
}
|
||||
|
||||
@@ -170,8 +193,21 @@ class Tags extends FrontNavigationAbstract
|
||||
*/
|
||||
public static function parseConfiguration( array $configuration, int $id ): array
|
||||
{
|
||||
return [
|
||||
'id' => $configuration['tag'] ?: null
|
||||
];
|
||||
/* We get the name from autocomplete, but we need the ID, so look that up */
|
||||
try
|
||||
{
|
||||
$tag = Tag::load( $configuration['tag'], 'tag_text' );
|
||||
|
||||
return [
|
||||
'id' => $tag->id
|
||||
];
|
||||
}
|
||||
catch( \OutOfRangeException $e )
|
||||
{
|
||||
/* No idea */
|
||||
return [
|
||||
'id' => null
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -269,6 +269,12 @@ class Content extends GroupFormAbstract
|
||||
$group->g_poll_results = (int) $values['g_poll_results'];
|
||||
}
|
||||
|
||||
/* If some settings are disabled, override toggled off settings with default values */
|
||||
if( !$values['g_attach'] )
|
||||
{
|
||||
$values['gbw_delete_attachments'] = false;
|
||||
}
|
||||
|
||||
/* Ratings */
|
||||
$group->g_topic_rate_setting = 0;
|
||||
if ( $values['g_topic_rate_setting'] )
|
||||
|
||||
@@ -72,6 +72,7 @@ class GroupSettings extends GroupFormAbstract
|
||||
catch ( Exception $e ) { }
|
||||
}
|
||||
$form->add( new Upload( 'g_icon', $icon, FALSE, array( 'storageExtension' => 'core_Theme', 'allowedFileTypes' => Image::supportedExtensions() ) ) );
|
||||
$form->add( new Number( 'g_icon_width', $group->g_icon_width, false, [], null, null, 'px' ) );
|
||||
|
||||
/* Prefix/Suffix */
|
||||
$form->add( new Custom( 'g_prefixsuffix', array( 'prefix' => $group->prefix, 'suffix' => $group->suffix ), FALSE, array(
|
||||
@@ -127,7 +128,7 @@ class GroupSettings extends GroupFormAbstract
|
||||
}
|
||||
}
|
||||
|
||||
$form->add( new YesNo( 'g_view_board', $group->g_id ? $group->g_view_board : TRUE, FALSE, array( 'togglesOn' => array_merge( $tabs, array( 'group_username', 'g_use_search', "gbw_cannot_be_ignored", "{$form->id}_header_group_signatures", 'g_use_signatures', "{$form->id}_header_group_staff", 'g_access_offline', 'g_search_flood', 'gbw_cannot_be_ignored' ) ) ) ) );
|
||||
$form->add( new YesNo( 'g_view_board', $group->g_id ? $group->g_view_board : TRUE, FALSE, array( 'togglesOn' => array_merge( $tabs, array( 'group_username', "gbw_cannot_be_ignored", "{$form->id}_header_group_signatures", 'g_use_signatures', "{$form->id}_header_group_staff", 'g_access_offline', 'g_search_flood' ) ) ) ) );
|
||||
$form->add( new YesNo( 'g_access_offline', $group->g_access_offline, FALSE, array(), NULL, NULL, NULL, 'g_access_offline' ) );
|
||||
|
||||
/* Usernames */
|
||||
@@ -266,6 +267,11 @@ class GroupSettings extends GroupFormAbstract
|
||||
if ( $values['g_icon'] instanceof File )
|
||||
{
|
||||
$group->g_icon = (string) $values['g_icon'];
|
||||
$group->g_icon_width = $values['g_icon_width'] ?: null;
|
||||
}
|
||||
else
|
||||
{
|
||||
$group->g_icon_width = null;
|
||||
}
|
||||
|
||||
/* Prefix/Suffix */
|
||||
@@ -278,6 +284,16 @@ class GroupSettings extends GroupFormAbstract
|
||||
$group->g_promote_exclude = !$values['g_promote_exclude'];
|
||||
}
|
||||
|
||||
/* If site access is disabled, override certain settings with the default values */
|
||||
if( !$values['g_view_board'] )
|
||||
{
|
||||
$values['g_access_offline'] = false;
|
||||
$values['group_username']['canchange'] = null;
|
||||
$values['can_be_ignored'] = true;
|
||||
$values['g_search_flood'] = 0;
|
||||
$values['g_use_signatures'] = false;
|
||||
}
|
||||
|
||||
/* Username changes */
|
||||
if( $group->g_id != Settings::i()->guest_group )
|
||||
{
|
||||
|
||||
@@ -23,6 +23,7 @@ use IPS\Helpers\Form\YesNo;
|
||||
use IPS\Member;
|
||||
use IPS\Member\Club;
|
||||
use IPS\Member\Group;
|
||||
use IPS\Platform\Bridge;
|
||||
use IPS\Settings;
|
||||
use function count;
|
||||
use function defined;
|
||||
@@ -136,6 +137,19 @@ class Social extends GroupFormAbstract
|
||||
|
||||
$form->addHeader( 'follows' );
|
||||
$form->add( new YesNo( 'g_view_followers', $group->g_view_followers, false ) );
|
||||
|
||||
/* Quests */
|
||||
if( Bridge::i()->featureIsEnabled( 'quests' ) )
|
||||
{
|
||||
$form->addHeader( 'quests_header' );
|
||||
$form->add( new YesNo( 'g_quest_view', $group->g_id ? $group->g_quest_view : true, false, [ 'togglesOn' => [ 'g_quest_join', 'g_quest_moderate' ] ] ) );
|
||||
$form->add( new YesNo( 'g_quest_join', $group->g_id ? $group->g_quest_join : true, false, [], null, null, null, 'g_quest_join' ) );
|
||||
|
||||
if( $group->g_id != Settings::i()->guest_group )
|
||||
{
|
||||
$form->add( new YesNo( 'g_quest_moderate', $group->g_id ? $group->g_quest_moderate : false, false, [], null, null, null, 'g_quest_moderate' ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -157,6 +171,22 @@ class Social extends GroupFormAbstract
|
||||
$group->g_view_displaynamehistory = $values['g_view_displaynamehistory'];
|
||||
}
|
||||
|
||||
/* Quests */
|
||||
if( Bridge::i()->featureIsEnabled( 'quests' ) )
|
||||
{
|
||||
$keys = array_merge( $keys, [ 'g_quest_view', 'g_quest_join' ] );
|
||||
if( $group->g_id != Settings::i()->guest_group )
|
||||
{
|
||||
$keys[] = 'g_quest_moderate';
|
||||
}
|
||||
|
||||
if( !$values['g_quest_view'] )
|
||||
{
|
||||
$values['g_quest_join'] = false;
|
||||
$values['g_quest_moderate'] = false;
|
||||
}
|
||||
}
|
||||
|
||||
if( $group->g_id != Settings::i()->guest_group )
|
||||
{
|
||||
/* Helpful */
|
||||
|
||||
@@ -38,7 +38,7 @@ class GroupLimits extends GroupLimitsAbstract
|
||||
public function getLimits(): array
|
||||
{
|
||||
return array(
|
||||
'exclude' => array( 'g_id', 'g_icon', 'prefix', 'suffix', 'g_promotion', 'g_bitoptions' ),
|
||||
'exclude' => array( 'g_id', 'g_icon', 'g_icon_width', 'prefix', 'suffix', 'g_promotion', 'g_bitoptions' ),
|
||||
'lessIsMore' => array( 'g_search_flood', 'g_pm_flood_mins' ),
|
||||
'neg1IsBest' => array( 'g_attach_max', 'g_max_bgimg_upload', 'g_max_messages', 'g_pm_perday', 'g_pm_flood_mins', 'g_max_mass_pm', 'g_rep_max_positive' ),
|
||||
'zeroIsBest' => array( 'g_displayname_unit', 'g_sig_unit', 'g_mod_preview', 'g_ppd_limit', 'g_ppd_unit', 'g_max_bgimg_upload', 'gbw_disable_prefixes', 'gbw_disable_tagging' ),
|
||||
|
||||
@@ -20,7 +20,10 @@ use IPS\Helpers\Table\Db as TableDb;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Member;
|
||||
use IPS\Theme;
|
||||
use function count;
|
||||
use function defined;
|
||||
use function implode;
|
||||
use function iterator_to_array;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
@@ -132,5 +135,92 @@ class ModeratorLogs extends IpAddressesAbstract
|
||||
public function findByMember( Member $member ) : array|Select
|
||||
{
|
||||
return Db::i()->select( "ip_address AS ip, count(*) AS count, MIN(ctime) AS first, MAX(ctime) AS last", 'core_moderator_logs', array( 'member_id=?', $member->member_id ), NULL, NULL, 'ip_address' )->setKeyField( 'ip' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the logged IP address
|
||||
*
|
||||
* @param int $time
|
||||
* @return void
|
||||
*/
|
||||
public function pruneIpAddresses( int $time ) : void
|
||||
{
|
||||
/* Get all admins */
|
||||
$adminMember = [];
|
||||
$adminGroup = [];
|
||||
$query = [];
|
||||
$where = [];
|
||||
|
||||
foreach ( Db::i()->select( '*', 'core_admin_permission_rows' ) as $mod )
|
||||
{
|
||||
if ( $mod['row_id_type'] == 'group' )
|
||||
{
|
||||
$adminGroup[] = $mod['row_id'];
|
||||
}
|
||||
else
|
||||
{
|
||||
$adminMember[] = $mod['row_id'];
|
||||
}
|
||||
}
|
||||
|
||||
if ( count( $adminGroup ) )
|
||||
{
|
||||
$query[] = Db::i()->in( 'member_group_id', $adminGroup );
|
||||
$query[] = Db::i()->findInSet( 'mgroup_others', $adminGroup );
|
||||
}
|
||||
|
||||
if ( count( $adminMember ) )
|
||||
{
|
||||
$query[] = Db::i()->in( 'member_id', $adminMember );
|
||||
}
|
||||
|
||||
/* I mean $query should never be empty, but let's not wait for a ticket and have to release a patch to find out that it could be... */
|
||||
if ( count( $query ) )
|
||||
{
|
||||
$where[] = [ 'member_id IN(?)', Db::i()->select( 'member_id', 'core_members', implode( ' OR ', $query ) ) ];
|
||||
}
|
||||
|
||||
$admins = Db::i()->select( 'member_id', 'core_members', $where, NULL, [ 0, 500 ] );
|
||||
|
||||
/* Get all moderators */
|
||||
$modMember = [];
|
||||
$modGroup = [];
|
||||
$query = [];
|
||||
$where = [];
|
||||
|
||||
foreach ( Db::i()->select( '*', 'core_moderators' ) as $mod )
|
||||
{
|
||||
if ( $mod['type'] == 'g' )
|
||||
{
|
||||
$modGroup[] = $mod['id'];
|
||||
}
|
||||
else
|
||||
{
|
||||
$modMember[] = $mod['id'];
|
||||
}
|
||||
}
|
||||
|
||||
if ( count( $modGroup ) )
|
||||
{
|
||||
$query[] = Db::i()->in( 'member_group_id', $modGroup );
|
||||
$query[] = Db::i()->findInSet( 'mgroup_others', $modGroup );
|
||||
}
|
||||
|
||||
if ( count( $modMember ) )
|
||||
{
|
||||
$query[] = Db::i()->in( 'member_id', $modMember );
|
||||
}
|
||||
|
||||
/* I mean $query should never be empty, but let's not wait for a ticket and have to release a patch to find out that it could be... */
|
||||
if ( count( $query ) )
|
||||
{
|
||||
$where[] = [ 'member_id IN(?)', Db::i()->select( 'member_id', 'core_members', implode( ' OR ', $query ) ) ];
|
||||
}
|
||||
|
||||
$moderators = Db::i()->select( 'member_id', 'core_members', $where, NULL, [ 0, 500 ] );
|
||||
|
||||
$staff = iterator_to_array( $moderators ) + iterator_to_array( $admins );
|
||||
|
||||
Db::i()->update( 'core_moderator_logs', array( 'ip_address' => '' ), array( [ 'ctime < ?', $time ], [ Db::i()->in( 'member_id', $staff, true ) ] ) );
|
||||
}
|
||||
}
|
||||
@@ -58,6 +58,12 @@ class Settings extends LiveSearchAbstract
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Is this disabled by a callback function? */
|
||||
if( !empty( $word['callback'] ) and !eval( $word['callback'] ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$app = Application::load( $word['app'] );
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ namespace IPS\core\extensions\core\MemberACPProfileBlocks;
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
|
||||
use IPS\Application;
|
||||
use IPS\core\DataLayer;
|
||||
use IPS\core\MemberACPProfile\Block;
|
||||
use IPS\Helpers\Menu;
|
||||
use IPS\Helpers\Menu\Link;
|
||||
@@ -100,7 +101,7 @@ class BasicInformation extends Block
|
||||
|
||||
/* Data Layer PII Option */
|
||||
if (
|
||||
Settings::i()->core_datalayer_enabled AND
|
||||
DataLayer::enabled() AND
|
||||
Settings::i()->core_datalayer_include_pii AND
|
||||
Settings::i()->core_datalayer_member_pii_choice AND
|
||||
( ( $this->member->isAdmin() AND Member::loggedIn()->hasAcpRestriction('core', 'members', 'member_edit_admin' ) ) OR Member::loggedIn()->hasAcpRestriction( 'core', 'members', 'member_edit' ) )
|
||||
|
||||
@@ -88,7 +88,12 @@ class Joined extends MemberFilterAbstract
|
||||
}
|
||||
elseif( $post['bmf_members_joined'][2] == 'range' )
|
||||
{
|
||||
return ( empty( $post['bmf_members_joined'][0] ) OR empty( $post['bmf_members_joined'][0]['start'] ) ) ? FALSE : array( 'range' => json_decode( json_encode( $post['bmf_members_joined'][0] ), TRUE ) );
|
||||
if( empty( $post['bmf_members_joined'][0] ) or ( empty( $post['bmf_members_joined'][0]['start'] ) and empty( $post['bmf_members_joined'][0]['end'] ) ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return array( 'range' => json_decode( json_encode( $post['bmf_members_joined'][0] ), true ) );
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -108,7 +113,7 @@ class Joined extends MemberFilterAbstract
|
||||
*/
|
||||
public function getQueryWhereClause( array $data ): ?array
|
||||
{
|
||||
if( !empty($data['range']) AND !empty($data['range']['end']) )
|
||||
if( !empty($data['range']) or !empty($data['range']['end']) )
|
||||
{
|
||||
$start = NULL;
|
||||
$end = NULL;
|
||||
@@ -126,19 +131,30 @@ class Joined extends MemberFilterAbstract
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
if( $data['range']['end'] )
|
||||
{
|
||||
$end = new DateTime( $data['range']['end'] );
|
||||
}
|
||||
catch( Exception $e )
|
||||
{
|
||||
$end = new DateTime( str_replace( '/', '-', $data['range']['end'] ) );
|
||||
try
|
||||
{
|
||||
$end = new DateTime( $data['range']['end'] );
|
||||
}
|
||||
catch( Exception $e )
|
||||
{
|
||||
$end = new DateTime( str_replace( '/', '-', $data['range']['end'] ) );
|
||||
}
|
||||
}
|
||||
|
||||
if( $start )
|
||||
if( $start and $end )
|
||||
{
|
||||
return array( "core_members.joined BETWEEN {$start->getTimestamp()} AND {$end->getTimestamp()}" );
|
||||
}
|
||||
elseif( $start )
|
||||
{
|
||||
return array( "core_members.joined >= {$start->getTimestamp()}" );
|
||||
}
|
||||
else
|
||||
{
|
||||
return array( "core_members.joined < {$end->getTimestamp()}" );
|
||||
}
|
||||
}
|
||||
elseif( !empty( $data['days'] ) AND (int) $data['days'] )
|
||||
{
|
||||
@@ -168,7 +184,7 @@ class Joined extends MemberFilterAbstract
|
||||
public function matches( Member $member, array $filters, ?object $object=NULL ) : bool
|
||||
{
|
||||
/* If we aren't filtering by this, then any member matches */
|
||||
if( ( !isset( $filters['range'] ) OR !$filters['range'] OR empty( $filters['range']['end'] ) ) AND ( !isset( $filters['days'] ) OR !$filters['days'] ) AND ( !isset( $filters['days_lt'] ) OR !$filters['days_lt'] ) )
|
||||
if( ( !isset( $filters['range'] ) OR !$filters['range'] OR ( empty( $filters['range']['end'] ) AND empty( $filters['range']['start'] ) ) ) AND ( !isset( $filters['days'] ) OR !$filters['days'] ) AND ( !isset( $filters['days_lt'] ) OR !$filters['days_lt'] ) )
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
@@ -176,7 +192,7 @@ class Joined extends MemberFilterAbstract
|
||||
/* \IPS\Member::get_joined() is defined and returns an \IPS\DateTime object already, so we don't need to use ts() here */
|
||||
$joinedDate = $member->joined;
|
||||
|
||||
if( !empty( $filters['range'] ) AND !empty( $filters['range']['end'] ) )
|
||||
if( !empty( $filters['range'] ) )
|
||||
{
|
||||
$start = NULL;
|
||||
$end = NULL;
|
||||
@@ -193,19 +209,29 @@ class Joined extends MemberFilterAbstract
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
if( $filters['date']['end'] )
|
||||
{
|
||||
$end = new DateTime( $filters['range']['end'] );
|
||||
}
|
||||
catch( Exception $e )
|
||||
{
|
||||
$end = new DateTime( str_replace( '/', '-', $filters['range']['end'] ) );
|
||||
try
|
||||
{
|
||||
$end = new DateTime( $filters['range']['end'] );
|
||||
}
|
||||
catch( Exception $e )
|
||||
{
|
||||
$end = new DateTime( str_replace( '/', '-', $filters['range']['end'] ) );
|
||||
}
|
||||
}
|
||||
|
||||
if( $start )
|
||||
if( $start and $joinedDate->getTimestamp() < $start->getTimestamp() )
|
||||
{
|
||||
return ( $joinedDate->getTimestamp() > $start->getTimestamp() AND $joinedDate->getTimestamp() < $end->getTimestamp() );
|
||||
return false;
|
||||
}
|
||||
|
||||
if( $end and $joinedDate->getTimestamp() > $end->getTimestamp() )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
elseif( !empty( $filters['days'] ) AND (int) $filters['days'] )
|
||||
{
|
||||
@@ -244,19 +270,29 @@ class Joined extends MemberFilterAbstract
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
if( $filters['range']['end'] )
|
||||
{
|
||||
$end = new DateTime( $filters['range']['end'] );
|
||||
}
|
||||
catch( Exception $e )
|
||||
{
|
||||
$end = new DateTime( str_replace( '/', '-', $filters['range']['end'] ) );
|
||||
try
|
||||
{
|
||||
$end = new DateTime( $filters['range']['end'] );
|
||||
}
|
||||
catch( Exception $e )
|
||||
{
|
||||
$end = new DateTime( str_replace( '/', '-', $filters['range']['end'] ) );
|
||||
}
|
||||
}
|
||||
|
||||
if ( $start )
|
||||
if ( $start and $end )
|
||||
{
|
||||
return Member::loggedIn()->language()->addToStack( 'member_filter_core_joined_range_desc', FALSE, array( 'sprintf' => array( $start->localeDate(), $end->localeDate() ) ) );
|
||||
}
|
||||
|
||||
if( $start )
|
||||
{
|
||||
return Member::loggedIn()->language()->addToStack( 'member_filter_core_joined_start_desc', FALSE, array( 'sprintf' => array( $start->localeDate() ) ) );
|
||||
}
|
||||
|
||||
return Member::loggedIn()->language()->addToStack( 'member_filter_core_joined_end_desc', FALSE, array( 'sprintf' => array( $end->localeDate() ) ) );
|
||||
}
|
||||
elseif( !empty( $filters['days'] ) AND (int) $filters['days'] )
|
||||
{
|
||||
|
||||
@@ -88,7 +88,12 @@ class Lastpost extends MemberFilterAbstract
|
||||
}
|
||||
elseif( $post['mf_members_last_post'][2] == 'range' )
|
||||
{
|
||||
return ( empty( $post['mf_members_last_post'][0] ) OR empty( $post['mf_members_last_post'][0]['start'] ) ) ? FALSE : array( 'range' => json_decode( json_encode( $post['mf_members_last_post'][0] ), TRUE ) );
|
||||
if( empty( $post['mf_members_last_post'][0] ) or ( empty( $post['mf_members_last_post'][0]['start'] ) and empty( $post['mf_members_last_post'][0]['end'] ) ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return array( 'range' => json_decode( json_encode( $post['mf_members_last_post'][0] ), true ) );
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -108,7 +113,7 @@ class Lastpost extends MemberFilterAbstract
|
||||
*/
|
||||
public function getQueryWhereClause( array $data ): ?array
|
||||
{
|
||||
if( !empty($data['range']) AND !empty($data['range']['end']) )
|
||||
if( !empty($data['range']) or !empty($data['range']['end']) )
|
||||
{
|
||||
$start = NULL;
|
||||
$end = NULL;
|
||||
@@ -126,19 +131,30 @@ class Lastpost extends MemberFilterAbstract
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
if( $data['range']['end'] )
|
||||
{
|
||||
$end = new DateTime( $data['range']['end'] );
|
||||
}
|
||||
catch( Exception $e )
|
||||
{
|
||||
$end = new DateTime( str_replace( '/', '-', $data['range']['end'] ) );
|
||||
try
|
||||
{
|
||||
$end = new DateTime( $data['range']['end'] );
|
||||
}
|
||||
catch( Exception $e )
|
||||
{
|
||||
$end = new DateTime( str_replace( '/', '-', $data['range']['end'] ) );
|
||||
}
|
||||
}
|
||||
|
||||
if( $start )
|
||||
if( $start and $end )
|
||||
{
|
||||
return array( "core_members.member_last_post BETWEEN {$start->getTimestamp()} AND {$end->getTimestamp()}" );
|
||||
}
|
||||
elseif( $start )
|
||||
{
|
||||
return array( "core_members.member_last_post >= {$start->getTimestamp()}" );
|
||||
}
|
||||
else
|
||||
{
|
||||
return array( "core_members.member_last_post < {$end->getTimestamp()}" );
|
||||
}
|
||||
}
|
||||
elseif( !empty( $data['days'] ) AND (int) $data['days'] )
|
||||
{
|
||||
@@ -169,7 +185,7 @@ class Lastpost extends MemberFilterAbstract
|
||||
public function matches( Member $member, array $filters, ?object $object=NULL ) : bool
|
||||
{
|
||||
/* If we aren't filtering by this, then any member matches */
|
||||
if( ( !isset( $filters['range'] ) OR !$filters['range'] OR empty( $filters['range']['end'] ) ) AND ( !isset( $filters['days'] ) OR !$filters['days'] ) AND ( !isset( $filters['days_lt'] ) OR !$filters['days_lt'] ) )
|
||||
if( ( !isset( $filters['range'] ) OR !$filters['range'] OR ( empty( $filters['range']['end'] ) AND empty( $filters['range']['start'] ) ) ) AND ( !isset( $filters['days'] ) OR !$filters['days'] ) AND ( !isset( $filters['days_lt'] ) OR !$filters['days_lt'] ) )
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
@@ -190,36 +206,44 @@ class Lastpost extends MemberFilterAbstract
|
||||
|
||||
$lastPost = DateTime::ts( $member->member_last_post );
|
||||
|
||||
if( !empty( $filters['range'] ) AND !empty( $filters['range']['end'] ) )
|
||||
if( !empty( $filters['range'] ) )
|
||||
{
|
||||
$start = NULL;
|
||||
$end = NULL;
|
||||
|
||||
$start = NULL;
|
||||
$end = NULL;
|
||||
|
||||
if ( $filters['range']['start'] )
|
||||
{
|
||||
try
|
||||
{
|
||||
$start = new DateTime( $filters['range']['start'] );
|
||||
}
|
||||
catch( Exception $e )
|
||||
} catch ( Exception $e )
|
||||
{
|
||||
$start = new DateTime( $filters['range']['start'] );
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
if ( $filters['range']['end'] )
|
||||
{
|
||||
$end = new DateTime( $filters['range']['end'] );
|
||||
}
|
||||
catch( Exception $e )
|
||||
{
|
||||
$end = new DateTime( str_replace( '/', '-', $filters['range']['end'] ) );
|
||||
try
|
||||
{
|
||||
$end = new DateTime( $filters['range']['end'] );
|
||||
} catch ( Exception $e )
|
||||
{
|
||||
$end = new DateTime( str_replace( '/', '-', $filters['range']['end'] ) );
|
||||
}
|
||||
}
|
||||
|
||||
if( $start )
|
||||
if ( $start and $lastPost->getTimestamp() < $start->getTimestamp() )
|
||||
{
|
||||
return ( $lastPost->getTimestamp() > $start->getTimestamp() AND $lastPost->getTimestamp() < $end->getTimestamp() );
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( $end and $lastPost->getTimestamp() > $end->getTimestamp() )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
elseif( !empty( $filters['days'] ) AND (int) $filters['days'] )
|
||||
{
|
||||
@@ -258,19 +282,29 @@ class Lastpost extends MemberFilterAbstract
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
if( $filters['range']['end'] )
|
||||
{
|
||||
$end = new DateTime( $filters['range']['end'] );
|
||||
}
|
||||
catch( Exception $e )
|
||||
{
|
||||
$end = new DateTime( str_replace( '/', '-', $filters['range']['end'] ) );
|
||||
try
|
||||
{
|
||||
$end = new DateTime( $filters['range']['end'] );
|
||||
}
|
||||
catch( Exception $e )
|
||||
{
|
||||
$end = new DateTime( str_replace( '/', '-', $filters['range']['end'] ) );
|
||||
}
|
||||
}
|
||||
|
||||
if ( $start )
|
||||
if ( $start and $end )
|
||||
{
|
||||
return Member::loggedIn()->language()->addToStack( 'member_filter_core_lastpost_range_desc', FALSE, array( 'sprintf' => array( $start->localeDate(), $end->localeDate() ) ) );
|
||||
}
|
||||
|
||||
if( $start )
|
||||
{
|
||||
return Member::loggedIn()->language()->addToStack( 'member_filter_core_lastpost_start_desc', FALSE, array( 'sprintf' => array( $start->localeDate() ) ) );
|
||||
}
|
||||
|
||||
return Member::loggedIn()->language()->addToStack( 'member_filter_core_lastpost_end_desc', FALSE, array( 'sprintf' => array( $end->localeDate() ) ) );
|
||||
}
|
||||
elseif( !empty( $filters['days'] ) AND (int) $filters['days'] )
|
||||
{
|
||||
|
||||
@@ -88,7 +88,12 @@ class Lastvisit extends MemberFilterAbstract
|
||||
}
|
||||
elseif( $post['bmf_members_last_visit'][2] == 'range' )
|
||||
{
|
||||
return ( empty( $post['bmf_members_last_visit'][0] ) OR empty( $post['bmf_members_last_visit'][0]['start'] ) ) ? FALSE : array( 'range' => json_decode( json_encode( $post['bmf_members_last_visit'][0] ), TRUE ) );
|
||||
if( empty( $post['bmf_members_last_visit'][0] ) or ( empty( $post['bmf_members_last_visit'][0]['start'] ) and empty( $post['bmf_members_last_visit'][0]['end'] ) ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return array( 'range' => json_decode( json_encode( $post['bmf_members_last_visit'][0] ), true ) );
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -108,7 +113,7 @@ class Lastvisit extends MemberFilterAbstract
|
||||
*/
|
||||
public function getQueryWhereClause( array $data ): ?array
|
||||
{
|
||||
if( !empty($data['range']) AND !empty($data['range']['end']) )
|
||||
if( !empty($data['range']) or !empty($data['range']['end']) )
|
||||
{
|
||||
$start = NULL;
|
||||
$end = NULL;
|
||||
@@ -126,19 +131,30 @@ class Lastvisit extends MemberFilterAbstract
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
if( $data['range']['end'] )
|
||||
{
|
||||
$end = new DateTime( $data['range']['end'] );
|
||||
}
|
||||
catch( Exception $e )
|
||||
{
|
||||
$end = new DateTime( str_replace( '/', '-', $data['range']['end'] ) );
|
||||
try
|
||||
{
|
||||
$end = new DateTime( $data['range']['end'] );
|
||||
}
|
||||
catch( Exception $e )
|
||||
{
|
||||
$end = new DateTime( str_replace( '/', '-', $data['range']['end'] ) );
|
||||
}
|
||||
}
|
||||
|
||||
if( $start )
|
||||
if( $start and $end )
|
||||
{
|
||||
return array( "GREATEST( core_members.last_activity, core_members.last_visit ) BETWEEN {$start->getTimestamp()} AND {$end->getTimestamp()}" );
|
||||
}
|
||||
elseif( $start )
|
||||
{
|
||||
return array( "GREATEST( core_members.last_activity, core_members.last_visit ) >= {$start->getTimestamp()}" );
|
||||
}
|
||||
else
|
||||
{
|
||||
return array( "GREATEST( core_members.last_activity, core_members.last_visit ) < {$end->getTimestamp()}" );
|
||||
}
|
||||
}
|
||||
elseif( !empty( $data['days'] ) AND (int) $data['days'] )
|
||||
{
|
||||
@@ -168,14 +184,14 @@ class Lastvisit extends MemberFilterAbstract
|
||||
public function matches( Member $member, array $filters, ?object $object=NULL ) : bool
|
||||
{
|
||||
/* If we aren't filtering by this, then any member matches */
|
||||
if( ( !isset( $filters['range'] ) OR !$filters['range'] OR empty( $filters['range']['end'] ) ) AND ( !isset( $filters['days'] ) OR !$filters['days'] ) AND ( !isset( $filters['days_lt'] ) OR !$filters['days_lt'] ) )
|
||||
if( ( !isset( $filters['range'] ) OR !$filters['range'] OR ( empty( $filters['range']['end'] ) AND empty( $filters['range']['start'] ) ) ) AND ( !isset( $filters['days'] ) OR !$filters['days'] ) AND ( !isset( $filters['days_lt'] ) OR !$filters['days_lt'] ) )
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
$lastVisit = DateTime::ts( max( (int) $member->last_visit, (int) $member->last_activity ) );
|
||||
|
||||
if( !empty( $filters['range'] ) AND !empty( $filters['range']['end'] ) )
|
||||
if( !empty( $filters['range'] ) )
|
||||
{
|
||||
$start = NULL;
|
||||
$end = NULL;
|
||||
@@ -192,19 +208,28 @@ class Lastvisit extends MemberFilterAbstract
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
if( $filters['range']['end'] )
|
||||
{
|
||||
$end = new DateTime( $filters['range']['end'] );
|
||||
}
|
||||
catch( Exception $e )
|
||||
{
|
||||
$end = new DateTime( str_replace( '/', '-', $filters['range']['end'] ) );
|
||||
try
|
||||
{
|
||||
$end = new DateTime( $filters['range']['end'] );
|
||||
}
|
||||
catch( Exception $e )
|
||||
{
|
||||
$end = new DateTime( str_replace( '/', '-', $filters['range']['end'] ) );
|
||||
}
|
||||
}
|
||||
|
||||
if( $start )
|
||||
if( $start and $lastVisit->getTimestamp() < $start->getTimestamp() )
|
||||
{
|
||||
return ( $lastVisit->getTimestamp() > $start->getTimestamp() AND $lastVisit->getTimestamp() < $end->getTimestamp() );
|
||||
return false;
|
||||
}
|
||||
if( $end and $lastVisit->getTimestamp() > $end->getTimestamp() )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
elseif( !empty( $filters['days'] ) AND (int) $filters['days'] )
|
||||
{
|
||||
@@ -243,19 +268,29 @@ class Lastvisit extends MemberFilterAbstract
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
if( $filters['range']['end'] )
|
||||
{
|
||||
$end = new DateTime( $filters['range']['end'] );
|
||||
}
|
||||
catch( Exception $e )
|
||||
{
|
||||
$end = new DateTime( str_replace( '/', '-', $filters['range']['end'] ) );
|
||||
try
|
||||
{
|
||||
$end = new DateTime( $filters['range']['end'] );
|
||||
}
|
||||
catch( Exception $e )
|
||||
{
|
||||
$end = new DateTime( str_replace( '/', '-', $filters['range']['end'] ) );
|
||||
}
|
||||
}
|
||||
|
||||
if ( $start )
|
||||
if ( $start and $end )
|
||||
{
|
||||
return Member::loggedIn()->language()->addToStack( 'member_filter_core_lastvisit_range_desc', FALSE, array( 'sprintf' => array( $start->localeDate(), $end->localeDate() ) ) );
|
||||
}
|
||||
|
||||
if( $start )
|
||||
{
|
||||
return Member::loggedIn()->language()->addToStack( 'member_filter_core_lastvisit_start_desc', FALSE, array( 'sprintf' => array( $start->localeDate() ) ) );
|
||||
}
|
||||
|
||||
return Member::loggedIn()->language()->addToStack( 'member_filter_core_lastvisit_end_desc', FALSE, array( 'sprintf' => array( $end->localeDate() ) ) );
|
||||
}
|
||||
elseif( !empty( $filters['days'] ) AND (int) $filters['days'] )
|
||||
{
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief MemberHistory: Mailchimp
|
||||
* @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 Aug 2025
|
||||
*/
|
||||
|
||||
namespace IPS\core\extensions\core\MemberHistory;
|
||||
|
||||
use IPS\Extensions\MemberHistoryAbstract;
|
||||
use IPS\Member;
|
||||
use IPS\Theme;
|
||||
use function defined;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Member History: Mailchimp
|
||||
*/
|
||||
class Mailchimp extends MemberHistoryAbstract
|
||||
{
|
||||
/**
|
||||
* Return the valid member history log types
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getTypes(): array
|
||||
{
|
||||
return array(
|
||||
'mailchimp_subscribed'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse LogType column
|
||||
*
|
||||
* @param string $value column value
|
||||
* @param array $row entire log row
|
||||
* @return string
|
||||
*/
|
||||
public function parseLogType( string $value, array $row ): string
|
||||
{
|
||||
return Theme::i()->getTemplate( 'members', 'core', 'admin' )->logType( 'email_change' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse LogData column
|
||||
*
|
||||
* @param string $value column value
|
||||
* @param array $row entire log row
|
||||
* @return string
|
||||
*/
|
||||
public function parseLogData( string $value, array $row ): string
|
||||
{
|
||||
$jsonValue = json_decode( $value, TRUE );
|
||||
|
||||
if( isset( $jsonValue['error_code'] ) )
|
||||
{
|
||||
return Member::loggedIn()->language()->addToStack( 'history__mailchimp_error', true, [ 'sprintf' => [ $jsonValue['error_code'], $jsonValue['error_message'] ] ] );
|
||||
}
|
||||
|
||||
return Member::loggedIn()->language()->addToStack( 'history__mailchimp_' . $jsonValue['action'], true, [ 'sprintf' => [ $jsonValue['list'] ] ] );
|
||||
}
|
||||
}
|
||||
@@ -75,7 +75,7 @@ class Featured extends ModCpAbstract
|
||||
}
|
||||
|
||||
/* Create the table */
|
||||
$table = new Table( Url::internal( 'app=core&module=feature&controller=feature', 'front', 'featured_show' ) );
|
||||
$table = new Table( Url::internal( 'app=core&module=feature&controller=featured', 'front', 'featured_show' ) );
|
||||
|
||||
Output::i()->cssFiles = array_merge( Output::i()->cssFiles, Theme::i()->css( 'styles/promote.css' ) );
|
||||
Output::i()->title = Member::loggedIn()->language()->addToStack('promote_manage_link');
|
||||
|
||||
@@ -28,6 +28,7 @@ use IPS\Helpers\Form\YesNo;
|
||||
use IPS\Helpers\Table\Content;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Member;
|
||||
use IPS\Node\Permissions as NodePermissions;
|
||||
use IPS\Output;
|
||||
use IPS\Request;
|
||||
use IPS\Session;
|
||||
@@ -214,7 +215,6 @@ class Reports extends Controller
|
||||
}
|
||||
|
||||
/* Check permission. We do it this way rather than loadAndCheckPerms() since we need to know if the user *had* permission if the content has been deleted */
|
||||
$allowedPermIds = iterator_to_array( Db::i()->select( 'perm_id', 'core_permission_index', Db::i()->findInSet( 'perm_view', Member::loggedIn()->permissionArray() ) . " OR perm_view='*'" ) );
|
||||
$workingClass = $report->class;
|
||||
if ( isset( $workingClass::$itemClass ) )
|
||||
{
|
||||
@@ -224,14 +224,19 @@ class Reports extends Controller
|
||||
{
|
||||
$workingClass = $workingClass::$containerNodeClass;
|
||||
}
|
||||
if ( isset( $workingClass::$permissionMap ) and isset( $workingClass::$permissionMap['read'] ) and $workingClass::$permissionMap['read'] !== 'view' )
|
||||
if( in_array( NodePermissions::class, class_implements( $workingClass ) ) )
|
||||
{
|
||||
$allowedPermIds = array_intersect( $allowedPermIds, iterator_to_array( Db::i()->select( 'perm_id', 'core_permission_index', Db::i()->findInSet( 'perm_' . $workingClass::$permissionMap['read'], Member::loggedIn()->permissionArray() ) . " OR perm_" . $workingClass::$permissionMap['read'] . "='*'" ) ) );
|
||||
}
|
||||
$allowedPermIds = iterator_to_array( Db::i()->select( 'perm_id', 'core_permission_index', Db::i()->findInSet( 'perm_view', Member::loggedIn()->permissionArray() ) . " OR perm_view='*'" ) );
|
||||
|
||||
if ( !in_array( $report->perm_id, $allowedPermIds ) )
|
||||
{
|
||||
Output::i()->error( 'node_error_no_perm', '2C139/5', 403, '' );
|
||||
if ( isset( $workingClass::$permissionMap ) and isset( $workingClass::$permissionMap['read'] ) and $workingClass::$permissionMap['read'] !== 'view' )
|
||||
{
|
||||
$allowedPermIds = array_intersect( $allowedPermIds, iterator_to_array( Db::i()->select( 'perm_id', 'core_permission_index', Db::i()->findInSet( 'perm_' . $workingClass::$permissionMap['read'], Member::loggedIn()->permissionArray() ) . " OR perm_" . $workingClass::$permissionMap['read'] . "='*'" ) ) );
|
||||
}
|
||||
|
||||
if ( !in_array( $report->perm_id, $allowedPermIds ) )
|
||||
{
|
||||
Output::i()->error( 'node_error_no_perm', '2C139/5', 403, '' );
|
||||
}
|
||||
}
|
||||
|
||||
/* Setting status? */
|
||||
@@ -392,7 +397,7 @@ class Reports extends Controller
|
||||
}
|
||||
|
||||
$form = new Form( 'form', 'modal_author_notification_form_submit' );
|
||||
$form->class .= 'ipsForm_vertical';
|
||||
$form->class .= 'ipsForm--vertical';
|
||||
$form->addButton( 'cancel', 'link', $report->url() );
|
||||
|
||||
/* Get the optional notifications */
|
||||
@@ -459,7 +464,7 @@ class Reports extends Controller
|
||||
$form->add( new Select( 'report_type', $report['report_type'], null, [ 'options' => $options ] ) );
|
||||
$form->add( new YesNo( 'report_type_change_log', true, null ) );
|
||||
}
|
||||
$form->class .= 'ipsForm_vertical';
|
||||
$form->class .= 'ipsForm--vertical';
|
||||
|
||||
/* Handle submissions */
|
||||
if ( $values = $form->values() )
|
||||
|
||||
@@ -130,14 +130,6 @@ class Unapproved extends ModCpAbstract
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function getCount() : void
|
||||
{
|
||||
Output::i()->json( array( 'count' => $this->getApprovalQueueCount( true ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the approval queue count
|
||||
*
|
||||
@@ -207,6 +199,12 @@ class Unapproved extends ModCpAbstract
|
||||
*/
|
||||
protected function _buildWhere( array $types ) : array
|
||||
{
|
||||
/* No content types, so no results */
|
||||
if( !count( $types ) )
|
||||
{
|
||||
return array( array( '1=0' ) );
|
||||
}
|
||||
|
||||
$clause = [];
|
||||
$binds = [];
|
||||
foreach( $types as $class )
|
||||
|
||||
@@ -22,6 +22,7 @@ use IPS\Lang;
|
||||
use IPS\Member;
|
||||
use IPS\nexus\Donation\Goal;
|
||||
use IPS\Notification\Inline;
|
||||
use IPS\Settings;
|
||||
use OutOfRangeException;
|
||||
use function defined;
|
||||
use function is_array;
|
||||
@@ -46,17 +47,22 @@ class Achievements extends NotificationsAbstract
|
||||
public static function configurationOptions( Member $member = NULL ): array
|
||||
{
|
||||
// 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', 'donation_goal_reached' ),
|
||||
'title' => 'notifications__core_Achievements',
|
||||
'showTitle' => FALSE,
|
||||
'description' => 'notifications__core_Achievements_desc',
|
||||
'default' => array( 'inline' ),
|
||||
'disabled' => array()
|
||||
)
|
||||
);
|
||||
if( Settings::i()->achievements_enabled or Settings::i()->donation_goals )
|
||||
{
|
||||
return array(
|
||||
'core_Achievements' => array(
|
||||
'type' => 'standard',
|
||||
'notificationTypes' => array( 'new_rank', 'new_badge', 'new_recognize', 'donation_goal_reached' ),
|
||||
'title' => 'notifications__core_Achievements',
|
||||
'showTitle' => FALSE,
|
||||
'description' => 'notifications__core_Achievements_desc',
|
||||
'default' => array( 'inline' ),
|
||||
'disabled' => array()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -567,15 +567,13 @@ class Moderation extends NotificationsAbstract
|
||||
|
||||
$item = $notification->item;
|
||||
$unread = false;
|
||||
$title = ( $item instanceof Comment ) ? $item->item()->mapped( 'title' ) : $item->mapped( 'title' );
|
||||
|
||||
if( IPS::classUsesTrait( $item, ReadMarkers::class ) )
|
||||
{
|
||||
if ( $item instanceof Comment )
|
||||
{
|
||||
$title = $item->item()->mapped('title');
|
||||
|
||||
/* Unread? */
|
||||
|
||||
if ( $item->item()->timeLastRead() instanceof DateTime )
|
||||
{
|
||||
$unread =( $item->item()->timeLastRead()->getTimestamp() < $notification->updated_time->getTimestamp() );
|
||||
@@ -583,7 +581,6 @@ class Moderation extends NotificationsAbstract
|
||||
}
|
||||
else
|
||||
{
|
||||
$title = $item->mapped('title');
|
||||
$unread = (bool) ( $item->unread() );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +67,23 @@ class Badges extends OverviewStatisticsAbstract
|
||||
* @return string
|
||||
*/
|
||||
public function getBlock( array|string $dateRange = NULL, string $subBlock = NULL ): string
|
||||
{
|
||||
$data = $this->getBlockNumbers( $dateRange, $subBlock );
|
||||
$count = $data['statsreports_current_count'];
|
||||
$previousCount = $data['statsreports_previous_count'];
|
||||
|
||||
return Theme::i()->getTemplate( 'stats' )->overviewComparisonCount( $count, $previousCount );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the block numbers
|
||||
*
|
||||
* @param array|string|null $dateRange String for a fixed time period in days, 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 array{statsreports_current_count: number, statsreports_previous_count: number}
|
||||
*/
|
||||
public function getBlockNumbers( array|string $dateRange = NULL, string $subBlock=NULL ) : array
|
||||
{
|
||||
$where = NULL;
|
||||
$previousCount = NULL;
|
||||
@@ -92,7 +109,9 @@ class Badges extends OverviewStatisticsAbstract
|
||||
}
|
||||
|
||||
$count = Db::i()->select( 'COUNT(*)', 'core_member_badges', $where )->first();
|
||||
|
||||
return Theme::i()->getTemplate( 'stats' )->overviewComparisonCount( $count, $previousCount );
|
||||
return [
|
||||
'statsreports_current_count' => $count,
|
||||
'statsreports_previous_count' => $previousCount,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,7 @@ use IPS\Theme;
|
||||
use function count;
|
||||
use function defined;
|
||||
use function is_array;
|
||||
use function round;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
@@ -101,32 +102,8 @@ class Clubs extends OverviewStatisticsAbstract
|
||||
*/
|
||||
public function _showJoins( array|string|null $dateRange = NULL ) : string
|
||||
{
|
||||
$where = NULL;
|
||||
$previousCount = NULL;
|
||||
|
||||
if( $dateRange !== NULL )
|
||||
{
|
||||
if( is_array( $dateRange ) )
|
||||
{
|
||||
$where = array(
|
||||
array( 'joined > ?', $dateRange['start']->getTimestamp() ),
|
||||
array( 'joined < ?', $dateRange['end']->getTimestamp() ),
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
$currentDate = new DateTime;
|
||||
$interval = static::getInterval( $dateRange );
|
||||
$initialTimestamp = $currentDate->sub( $interval )->getTimestamp();
|
||||
$where = array( array( 'joined > ?', $initialTimestamp ) );
|
||||
|
||||
$previousCount = Db::i()->select( 'COUNT(*)', 'core_clubs_memberships', array( array( 'joined BETWEEN ? AND ?', $currentDate->sub( $interval )->getTimestamp(), $initialTimestamp ) ) )->first();
|
||||
}
|
||||
}
|
||||
|
||||
$count = Db::i()->select( 'COUNT(*)', 'core_clubs_memberships', $where )->first();
|
||||
|
||||
return Theme::i()->getTemplate( 'stats' )->overviewComparisonCount( $count, $previousCount );
|
||||
$data = $this->getBlockNumbers( $dateRange, 'joins' );
|
||||
return Theme::i()->getTemplate( 'stats' )->overviewComparisonCount( $data['statsreports_current_count'], $data['statsreports_previous_count'] );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -137,6 +114,81 @@ class Clubs extends OverviewStatisticsAbstract
|
||||
*/
|
||||
public function _showClubs( array|string|null $dateRange = NULL ) : string
|
||||
{
|
||||
/* Init Chart */
|
||||
$pieBarData = array();
|
||||
$data = $this->getBlockNumbers( $dateRange, 'clubs' );
|
||||
|
||||
$total = $data['statsreports_current_count'];
|
||||
$chart = NULL;
|
||||
|
||||
// Add percentages
|
||||
foreach ( $data as $key => $value )
|
||||
{
|
||||
if ( in_array( $key, ['statsreports_current_count', 'statsreports_previous_count'] ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$pieBarData[] = [
|
||||
'name' => Member::loggedIn()->language()->addToStack( $key ),
|
||||
'value' => $value,
|
||||
'percentage' => round( ( $value / $total ) * 100, 2 ),
|
||||
];
|
||||
}
|
||||
|
||||
if ( count( $pieBarData ) )
|
||||
{
|
||||
$chart = Theme::i()->getTemplate( 'global', 'core', 'global' )->applePieChart( $pieBarData );
|
||||
}
|
||||
|
||||
return Theme::i()->getTemplate( 'stats' )->overviewComparisonCount( $total, $data['statsreports_previous_count'], $chart );
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get the block numbers
|
||||
*
|
||||
* @param array|string|null $dateRange String for a fixed time period in days, 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 array{statsreports_current_count: number|null, statsreports_previous_count: number|null}|number[]
|
||||
*/
|
||||
public function getBlockNumbers( array|string $dateRange = NULL, string $subBlock=NULL ) : array
|
||||
{
|
||||
if( $subBlock == 'joins' )
|
||||
{
|
||||
$where = NULL;
|
||||
$previousCount = NULL;
|
||||
|
||||
if( $dateRange !== NULL )
|
||||
{
|
||||
if( is_array( $dateRange ) )
|
||||
{
|
||||
$where = array(
|
||||
array( 'joined > ?', $dateRange['start']->getTimestamp() ),
|
||||
array( 'joined < ?', $dateRange['end']->getTimestamp() ),
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
$currentDate = new DateTime;
|
||||
$interval = static::getInterval( $dateRange );
|
||||
$initialTimestamp = $currentDate->sub( $interval )->getTimestamp();
|
||||
$where = array( array( 'joined > ?', $initialTimestamp ) );
|
||||
|
||||
$previousCount = Db::i()->select( 'COUNT(*)', 'core_clubs_memberships', array( array( 'joined BETWEEN ? AND ?', $currentDate->sub( $interval )->getTimestamp(), $initialTimestamp ) ) )->first();
|
||||
}
|
||||
}
|
||||
|
||||
$count = Db::i()->select( 'COUNT(*)', 'core_clubs_memberships', $where )->first();
|
||||
return [
|
||||
'statsreports_current_count' => $count,
|
||||
'statsreports_previous_count' => $previousCount
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/* Init Chart */
|
||||
$pieBarData = array();
|
||||
|
||||
@@ -165,30 +217,28 @@ class Clubs extends OverviewStatisticsAbstract
|
||||
}
|
||||
|
||||
$total = 0;
|
||||
$chart = NULL;
|
||||
|
||||
foreach( Db::i()->select( 'COUNT(*) as total, `type`', 'core_clubs', $where, NULL, NULL, 'type' ) as $result )
|
||||
{
|
||||
$pieBarData[] = array(
|
||||
'name' => Member::loggedIn()->language()->addToStack('club_type_' . $result['type'] ),
|
||||
'name' => 'club_type_' . $result['type'],
|
||||
'value' => $result['total'],
|
||||
'percentage' => 0
|
||||
);
|
||||
|
||||
$total += $result['total'];
|
||||
}
|
||||
|
||||
$return = [
|
||||
'statsreports_current_count' => $total,
|
||||
'statsreports_previous_count' => $previousCount
|
||||
];
|
||||
|
||||
// Add percentages
|
||||
foreach( $pieBarData as &$segment )
|
||||
foreach( $pieBarData as $segment )
|
||||
{
|
||||
$segment['percentage'] = round( ( $segment['value'] / $total ) * 100, 2 );
|
||||
$return[$segment['name']] = $segment['value'];
|
||||
}
|
||||
|
||||
if( count( $pieBarData ) )
|
||||
{
|
||||
$chart = Theme::i()->getTemplate( 'global', 'core', 'global' )->applePieChart( $pieBarData );
|
||||
}
|
||||
|
||||
return Theme::i()->getTemplate( 'stats' )->overviewComparisonCount( $total, $previousCount, $chart );
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ use IPS\Member;
|
||||
use IPS\Request;
|
||||
use IPS\Theme;
|
||||
use function defined;
|
||||
use function explode;
|
||||
use function in_array;
|
||||
use function is_array;
|
||||
|
||||
@@ -107,10 +108,29 @@ class ContentActivity extends OverviewStatisticsAbstract
|
||||
return '';
|
||||
}
|
||||
|
||||
$data = $this->getBlockNumbers( $dateRange, $subBlock );
|
||||
$values = $data['statsreports_current_count'];
|
||||
$previousValues = $data['statsreports_previous_count'];
|
||||
|
||||
return Theme::i()->getTemplate( 'activitystats' )->overviewCounts( $values, $previousValues, $this->nodeNames );
|
||||
}
|
||||
|
||||
protected array $nodeNames = [];
|
||||
|
||||
/**
|
||||
* Get the block numbers
|
||||
*
|
||||
* @param array|string|null $dateRange String for a fixed time period in days, 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 array{statsreports_current_count: number[], statsreports_previous_count: number[]}
|
||||
*/
|
||||
public function getBlockNumbers( array|string $dateRange = NULL, string $subBlock=NULL ) : array
|
||||
{
|
||||
$classesToCheck = array( $subBlock );
|
||||
$values = array();
|
||||
$previousValues = array();
|
||||
$nodeNames = array();
|
||||
$this->nodeNames = [];
|
||||
|
||||
/* @var Item $subBlock */
|
||||
if ( isset( $subBlock::$commentClass ) )
|
||||
@@ -165,14 +185,17 @@ class ContentActivity extends OverviewStatisticsAbstract
|
||||
foreach( explode( ',', Request::i()->nodes ) as $nodeId )
|
||||
{
|
||||
$containerClass = $class::$containerNodeClass;
|
||||
$nodeNames[] = $containerClass::load( $nodeId )->_title;
|
||||
$this->nodeNames[] = $containerClass::load( $nodeId )->_title;
|
||||
}
|
||||
}
|
||||
|
||||
$values[ $class::$title . '_pl' ] = Db::i()->select( 'COUNT(*)', $class::$databaseTable, $this->_modifyWhereClause( $where, $class ) )->first();
|
||||
}
|
||||
|
||||
return Theme::i()->getTemplate( 'activitystats' )->overviewCounts( $values, $previousValues, $nodeNames );
|
||||
return [
|
||||
"statsreports_previous_count" => $previousValues,
|
||||
"statsreports_current_count" => $values,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -236,7 +259,7 @@ class ContentActivity extends OverviewStatisticsAbstract
|
||||
$containerClass = $subBlock::$containerNodeClass;
|
||||
|
||||
$form = new Form;
|
||||
$form->attributes['data-controller'] = 'core.admin.stats.nodeFilters';
|
||||
$form->class = "ipsForm--vertical";
|
||||
$form->attributes['data-block'] = Request::i()->blockKey;
|
||||
$form->attributes['data-subblock'] = $subBlock;
|
||||
$form->add( new Node( $containerClass::$nodeTitle, NULL, TRUE, array( 'class' => $containerClass, 'multiple' => TRUE, 'clubs' => TRUE ) ) );
|
||||
|
||||
@@ -68,6 +68,23 @@ class Contributors extends OverviewStatisticsAbstract
|
||||
*/
|
||||
public function getBlock( array|string $dateRange = NULL, ?string $subBlock = NULL ): string
|
||||
{
|
||||
$data = $this->getBlockNumbers( $dateRange, $subBlock );
|
||||
$count = $data['statsreports_current_count'];
|
||||
$previousCount = $data['statsreports_previous_count'];
|
||||
return Theme::i()->getTemplate( 'stats' )->overviewComparisonCount( $count, $previousCount );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the block numbers
|
||||
*
|
||||
* @param array|string|null $dateRange String for a fixed time period in days, 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 array{statsreports_current_count: number, statsreports_previous_count: number}
|
||||
*/
|
||||
public function getBlockNumbers( array|string $dateRange = NULL, string $subBlock=NULL ) : array
|
||||
{
|
||||
|
||||
$classes = Content::routedClasses( FALSE, TRUE );
|
||||
$unions = array();
|
||||
$prevUnions = array();
|
||||
@@ -113,6 +130,9 @@ class Contributors extends OverviewStatisticsAbstract
|
||||
$previousCount = Db::i()->union( $prevUnions, NULL, NULL, NULL, NULL, 0, NULL, 'COUNT(DISTINCT(member_id))' )->first();
|
||||
}
|
||||
|
||||
return Theme::i()->getTemplate( 'stats' )->overviewComparisonCount( $count, $previousCount );
|
||||
return [
|
||||
'statsreports_current_count' => $count,
|
||||
'statsreports_previous_count' => $previousCount,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,8 @@ use IPS\Extensions\OverviewStatisticsAbstract;
|
||||
use IPS\Settings;
|
||||
use IPS\Theme;
|
||||
use function defined;
|
||||
use function round;
|
||||
use function time;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
@@ -31,9 +33,9 @@ if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
class DeletedContent extends OverviewStatisticsAbstract
|
||||
{
|
||||
/**
|
||||
* @brief Which statistics page (activity or user)
|
||||
* @brief Which statistics page (activity or user)
|
||||
*/
|
||||
public string $page = 'activity';
|
||||
public string $page = 'activity';
|
||||
|
||||
/**
|
||||
* Return the sub-block keys
|
||||
@@ -41,7 +43,7 @@ class DeletedContent extends OverviewStatisticsAbstract
|
||||
* @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(): array
|
||||
public function getBlocks (): array
|
||||
{
|
||||
return array( 'deletedcontent' );
|
||||
}
|
||||
@@ -49,36 +51,74 @@ class DeletedContent extends OverviewStatisticsAbstract
|
||||
/**
|
||||
* Return block details (title and description)
|
||||
*
|
||||
* @param string|NULL $subBlock The subblock we are loading as returned by getBlocks()
|
||||
* @return array
|
||||
* @param string|NULL $subBlock The subblock we are loading as returned by getBlocks()
|
||||
* @return array
|
||||
*/
|
||||
public function getBlockDetails( string $subBlock = NULL ): array
|
||||
public function getBlockDetails ( string $subBlock = NULL ): array
|
||||
{
|
||||
/* 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|string|null $dateRange String for a fixed time period in days, 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
|
||||
* @param array|string|null $dateRange String for a fixed time period in days, 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( array|string $dateRange = NULL, string $subBlock = NULL ): string
|
||||
public function getBlock ( array|string $dateRange = NULL, string $subBlock = NULL ): string
|
||||
{
|
||||
if( !Settings::i()->dellog_retention_period )
|
||||
if ( !Settings::i()->dellog_retention_period )
|
||||
{
|
||||
$total = $deleted = $value = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
$total = Db::i()->select( 'COUNT(*)', 'core_search_index', array( 'index_date_created > ?', time() - Settings::i()->dellog_retention_period ) )->first();
|
||||
$deleted = Db::i()->select( 'COUNT(*)', 'core_deletion_log', array() )->first();
|
||||
$value = $total ? round( $deleted / $total * 100, 2 ) : 0;
|
||||
$values = $this->getBlockNumbersRaw();
|
||||
$total = $values['total'];
|
||||
$deleted = $values['deleted'];
|
||||
$value = $values['value'];
|
||||
}
|
||||
|
||||
return Theme::i()->getTemplate( 'activitystats', 'core' )->deletedPercentage( $value, $total, $deleted );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the raw numbers relevant to this block
|
||||
*
|
||||
* @return array{total: number, deleted: number, value: number}
|
||||
*/
|
||||
protected function getBlockNumbersRaw () : array
|
||||
{
|
||||
$total = Db::i()->select( 'COUNT(*)', 'core_search_index', array( 'index_date_created > ?', time() - Settings::i()->dellog_retention_period ) )->first();
|
||||
$deleted = Db::i()->select( 'COUNT(*)', 'core_deletion_log', array() )->first();
|
||||
$value = $total ? round( $deleted / $total * 100, 2 ) : 0;
|
||||
|
||||
return [
|
||||
'total' => $total,
|
||||
'deleted' => $deleted,
|
||||
'value' => $value,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the block numbers
|
||||
*
|
||||
* @param array|string|null $dateRange String for a fixed time period in days, 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 array{stats_deletedcontent_csv_title: number|null}
|
||||
*/
|
||||
public function getBlockNumbers( array|string $dateRange = NULL, string $subBlock=NULL ) : array
|
||||
{
|
||||
if ( !Settings::i()->dellog_retention_period )
|
||||
{
|
||||
return [ 'stats_deletedcontent_csv_title' => null ];
|
||||
}
|
||||
|
||||
return ['stats_deletedcontent_csv_title' => $this->getBlockNumbersRaw()['value']];
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ use IPS\Settings;
|
||||
use IPS\Theme;
|
||||
use function defined;
|
||||
use function is_array;
|
||||
use function round;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
@@ -74,7 +75,36 @@ class Devices extends OverviewStatisticsAbstract
|
||||
{
|
||||
/* Init Chart */
|
||||
$pieBarData = array();
|
||||
|
||||
$numbers = $this->getBlockNumbers( $dateRange, $subBlock );
|
||||
foreach( $numbers as $key => $value )
|
||||
{
|
||||
if ( $key === 'statsreports_current_total' )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$pieBarData[] = array(
|
||||
'name' => Member::loggedIn()->language()->addToStack( $key ),
|
||||
'value' => $value,
|
||||
'percentage' => $numbers['statsreports_current_total'] ? round( ( $value / $numbers['statsreports_current_total'] ) * 100, 2 ) : 0
|
||||
);
|
||||
}
|
||||
|
||||
return Theme::i()->getTemplate( 'global', 'core', 'global' )->applePieChart( $pieBarData );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the block numbers
|
||||
*
|
||||
* @param array|string|null $dateRange String for a fixed time period in days, 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 array
|
||||
*/
|
||||
public function getBlockNumbers( array|string $dateRange = NULL, string $subBlock=NULL ) : array
|
||||
{
|
||||
|
||||
/* Add Rows */
|
||||
$where = NULL;
|
||||
|
||||
@@ -95,42 +125,43 @@ class Devices extends OverviewStatisticsAbstract
|
||||
{
|
||||
case '7':
|
||||
$where = array( array( 'time > ? ', $currentDate->sub( new DateInterval( 'P7D' ) )->getTimestamp() ) );
|
||||
break;
|
||||
break;
|
||||
|
||||
case '30':
|
||||
$where = array( array( 'time > ? ', $currentDate->sub( new DateInterval( 'P1M' ) )->getTimestamp() ) );
|
||||
break;
|
||||
break;
|
||||
|
||||
case '90':
|
||||
$where = array( array( 'time > ? ', $currentDate->sub( new DateInterval( 'P3M' ) )->getTimestamp() ) );
|
||||
break;
|
||||
break;
|
||||
|
||||
case '180':
|
||||
$where = array( array( 'time > ? ', $currentDate->sub( new DateInterval( 'P6M' ) )->getTimestamp() ) );
|
||||
break;
|
||||
break;
|
||||
|
||||
case '365':
|
||||
$where = array( array( 'time > ? ', $currentDate->sub( new DateInterval( 'P1Y' ) )->getTimestamp() ) );
|
||||
break;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$where[] = ['type=?', 'devices'];
|
||||
$result = Db::i()->select( 'SUM(value_1) as mobiles, SUM(value_2) AS tablets, SUM(value_3) as consoles, SUM(value_4) as desktops', 'core_statistics', $where )->first();
|
||||
$total = $result['mobiles'] + $result['tablets'] + $result['consoles'] + $result['desktops'];
|
||||
$total = 0;
|
||||
$return = [];
|
||||
|
||||
foreach( array('mobiles', 'tablets', 'consoles', 'desktops' ) as $device )
|
||||
{
|
||||
if( $result[ $device ] > 0 )
|
||||
{
|
||||
$pieBarData[] = array(
|
||||
'name' => Member::loggedIn()->language()->addToStack('stats_devices_' . $device),
|
||||
'value' => $result[ $device ],
|
||||
'percentage' => round( ( $result[ $device ] / $total ) * 100, 2 )
|
||||
);
|
||||
$return[ 'stats_devices_' . $device ] = $result[ $device ];
|
||||
$total += $result[$device];
|
||||
}
|
||||
}
|
||||
|
||||
return Theme::i()->getTemplate( 'global', 'core', 'global' )->applePieChart( $pieBarData );
|
||||
$return['statsreports_current_total'] = $total;
|
||||
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
@@ -67,6 +67,22 @@ class Followers extends OverviewStatisticsAbstract
|
||||
*/
|
||||
public function getBlock( array|string $dateRange = null, string $subBlock = null ): string
|
||||
{
|
||||
$data = $this->getBlockNumbers( $dateRange, $subBlock );
|
||||
return Theme::i()->getTemplate( 'stats' )->overviewComparisonCount( $data['statsreports_current_count'], $data['statsreports_previous_count'] );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the block numbers
|
||||
*
|
||||
* @param array|string|null $dateRange String for a fixed time period in days, 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 array{statsreports_current_count: (number|null), statsreports_previous_count: (number|null)}
|
||||
*/
|
||||
public function getBlockNumbers( array|string $dateRange = NULL, string $subBlock=NULL ) : array
|
||||
{
|
||||
|
||||
$where = NULL;
|
||||
$previousCount = NULL;
|
||||
|
||||
@@ -92,6 +108,9 @@ class Followers extends OverviewStatisticsAbstract
|
||||
|
||||
$count = Db::i()->select( 'COUNT(DISTINCT(follow_member_id))', 'core_follow', $where )->first();
|
||||
|
||||
return Theme::i()->getTemplate( 'stats' )->overviewComparisonCount( $count, $previousCount );
|
||||
return [
|
||||
'statsreports_current_count' => $count,
|
||||
'statsreports_previous_count' => $previousCount
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ use IPS\Session\Store;
|
||||
use IPS\Theme;
|
||||
use function count;
|
||||
use function defined;
|
||||
use function round;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
@@ -70,11 +71,46 @@ class Online extends OverviewStatisticsAbstract
|
||||
{
|
||||
/* Init Chart */
|
||||
$pieBarData = array();
|
||||
$chart = NULL;
|
||||
$numbers = $this->getBlockNumbers( $dateRange, $subBlock );
|
||||
foreach ( $numbers as $key => $value )
|
||||
{
|
||||
if ( $key === 'statsreports_current_total' )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$pieBarData[] = [
|
||||
'name' => Member::loggedIn()->language()->addToStack( $key ),
|
||||
'value' => $value,
|
||||
'percentage' => $numbers['statsreports_current_total'] ? round( ( $value / $numbers['statsreports_current_total'] ) * 100, 2 ) : 0
|
||||
];
|
||||
}
|
||||
|
||||
if( count( $pieBarData ) )
|
||||
{
|
||||
$chart = Theme::i()->getTemplate( 'global', 'core', 'global' )->applePieChart( $pieBarData );
|
||||
}
|
||||
|
||||
return Theme::i()->getTemplate( 'stats' )->overviewComparisonCount( $numbers['statsreports_current_total'], NULL, $chart );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the block numbers
|
||||
*
|
||||
* @param array|string|null $dateRange String for a fixed time period in days, 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 array
|
||||
*/
|
||||
public function getBlockNumbers( array|string $dateRange = NULL, string $subBlock=NULL ) : array
|
||||
{
|
||||
$numbers = array();
|
||||
|
||||
/* Add Rows */
|
||||
$online = array();
|
||||
$seen = array();
|
||||
$chart = NULL;
|
||||
|
||||
foreach( Store::i()->getOnlineUsers( Store::ONLINE_MEMBERS | Store::ONLINE_GUESTS, 'desc' ) as $row )
|
||||
{
|
||||
@@ -85,14 +121,14 @@ class Online extends OverviewStatisticsAbstract
|
||||
}
|
||||
|
||||
$key = ( $row['member_id'] ?: $row['id'] );
|
||||
|
||||
|
||||
if ( ! isset( $seen[ $key ] ) )
|
||||
{
|
||||
$online[ $row['current_appcomponent'] ][ $key ] = $row['id'];
|
||||
$seen[ $key ] = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$total = 0;
|
||||
foreach ( $online as $app => $data )
|
||||
{
|
||||
@@ -101,18 +137,12 @@ class Online extends OverviewStatisticsAbstract
|
||||
|
||||
foreach( $online as $app => $data )
|
||||
{
|
||||
$pieBarData[] = array(
|
||||
'name' => Member::loggedIn()->language()->addToStack( '__app_' . $app ),
|
||||
'value' => count( $data ),
|
||||
'percentage' => round( ( count( $data ) / $total ) * 100, 2 )
|
||||
);
|
||||
$total += count( $data );
|
||||
$numbers['__app_' . $app] = count( $data );
|
||||
}
|
||||
|
||||
if( count( $pieBarData ) )
|
||||
{
|
||||
$chart = Theme::i()->getTemplate( 'global', 'core', 'global' )->applePieChart( $pieBarData );
|
||||
}
|
||||
$numbers['statsreports_current_total'] = $total;
|
||||
|
||||
return Theme::i()->getTemplate( 'stats' )->overviewComparisonCount( $total, NULL, $chart );
|
||||
return $numbers;
|
||||
}
|
||||
}
|
||||
@@ -67,6 +67,21 @@ class Points extends OverviewStatisticsAbstract
|
||||
* @return string
|
||||
*/
|
||||
public function getBlock( array|string $dateRange = NULL, string $subBlock = NULL ): string
|
||||
{
|
||||
$counts = $this->getBlockNumbers( $dateRange, $subBlock );
|
||||
return Theme::i()->getTemplate( 'stats' )->overviewComparisonCount( $counts['statsreports_current_count'], $counts['statsreports_previous_count'] );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the block numbers
|
||||
*
|
||||
* @param array|string|null $dateRange String for a fixed time period in days, 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 array{statsreports_current_count: number|null, statsreports_previous_count: number|null}
|
||||
*/
|
||||
public function getBlockNumbers( array|string $dateRange = NULL, string $subBlock=NULL ) : array
|
||||
{
|
||||
$where = NULL;
|
||||
$previousCount = NULL;
|
||||
@@ -92,7 +107,9 @@ class Points extends OverviewStatisticsAbstract
|
||||
}
|
||||
|
||||
$count = Db::i()->select( 'SUM(points)', 'core_points_log', $where )->first();
|
||||
|
||||
return Theme::i()->getTemplate( 'stats' )->overviewComparisonCount( $count, $previousCount );
|
||||
return [
|
||||
"statsreports_current_count" => $count,
|
||||
"statsreports_previous_count" => $previousCount,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -97,7 +97,12 @@ class ProfileFields extends OverviewStatisticsAbstract
|
||||
{
|
||||
if( $id == $subBlock )
|
||||
{
|
||||
return $this->_getChart( $field );
|
||||
$pieBarData = $this->_getChart( $field );
|
||||
foreach ( $pieBarData as &$slice )
|
||||
{
|
||||
$slice['name'] = Member::loggedIn()->language()->addToStack( $slice['name'] );
|
||||
}
|
||||
return Theme::i()->getTemplate( 'global', 'core', 'global' )->applePieChart( $pieBarData );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -105,13 +110,54 @@ class ProfileFields extends OverviewStatisticsAbstract
|
||||
return '';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the numbers to enter in the CSV export for this block
|
||||
*
|
||||
* @param array|string|NULL $dateRange
|
||||
* @param string|NULL $subBlock
|
||||
* @return array
|
||||
*/
|
||||
public function getBlockNumbers( array|string $dateRange = NULL, string $subBlock = NULL ) : array
|
||||
{
|
||||
$numbers = [ 'statsreports_current_total' => 0 ];
|
||||
foreach ( Field::fieldData() as $group => $fields )
|
||||
{
|
||||
foreach ( $fields as $id => $field )
|
||||
{
|
||||
if( $id == $subBlock )
|
||||
{
|
||||
$pieBarData = $this->_getChart( $field );
|
||||
foreach ( $pieBarData as $slice )
|
||||
{
|
||||
$name = $slice['name'];
|
||||
|
||||
/* Hacky, but in the case of ratings we use this formatted string */
|
||||
if ( isset( $slice['colTitle'] ) )
|
||||
{
|
||||
$name = Member::loggedIn()->language()->addToStack( $slice['colTitle'], options: [
|
||||
'pluralize' => $slice['name'],
|
||||
'sprintf' => [ $slice['name'] ]
|
||||
] );
|
||||
Member::loggedIn()->language()->parseOutputForDisplay( $name );
|
||||
}
|
||||
$numbers[$name] = $slice['value'];
|
||||
$numbers['statsreports_current_total'] += $slice['value'];
|
||||
}
|
||||
return $numbers;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $numbers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the HTML to display for this block
|
||||
*
|
||||
* @param array $field Field data
|
||||
* @return string
|
||||
* @return array
|
||||
*/
|
||||
protected function _getChart( array $field ) : string
|
||||
protected function _getChart( array $field ) : array
|
||||
{
|
||||
/* Init Chart */
|
||||
$pieBarData = array();
|
||||
@@ -146,13 +192,13 @@ class ProfileFields extends OverviewStatisticsAbstract
|
||||
if( $field['pf_type'] == 'Checkbox' )
|
||||
{
|
||||
$pieBarData[] = array(
|
||||
'name' => Member::loggedIn()->language()->addToStack('stats_pfields__unchecked'),
|
||||
'name' => 'stats_pfields__unchecked',
|
||||
'value' => isset( $results[0] ) ? $results[0]['total'] : 0,
|
||||
'percentage' => ( isset( $results[0] ) AND $results[0]['total'] > 0 ) ? round( ( $results[0]['total'] / $total ) * 100, 2 ) : 0
|
||||
);
|
||||
|
||||
$pieBarData[] = array(
|
||||
'name' => Member::loggedIn()->language()->addToStack('stats_pfields__checked'),
|
||||
'name' => 'stats_pfields__checked',
|
||||
'value' => isset( $results[1] ) ? $results[1]['total'] : 0,
|
||||
'percentage' => ( isset( $results[1] ) AND $results[1]['total'] > 0 ) ? round( ( $results[1]['total'] / $total ) * 100, 2 ) : 0
|
||||
);
|
||||
@@ -160,13 +206,13 @@ class ProfileFields extends OverviewStatisticsAbstract
|
||||
elseif( $field['pf_type'] == 'YesNo' )
|
||||
{
|
||||
$pieBarData[] = array(
|
||||
'name' => Member::loggedIn()->language()->addToStack('stats_pfields__no'),
|
||||
'name' => 'stats_pfields__no',
|
||||
'value' => isset( $results[0] ) ? $results[0]['total'] : 0,
|
||||
'percentage' => ( isset( $results[0] ) AND $results[0]['total'] > 0 ) ? round( ( $results[0]['total'] / $total ) * 100, 2 ) : 0
|
||||
);
|
||||
|
||||
$pieBarData[] = array(
|
||||
'name' => Member::loggedIn()->language()->addToStack('stats_pfields__yes'),
|
||||
'name' => 'stats_pfields__yes',
|
||||
'value' => isset( $results[1] ) ? $results[1]['total'] : 0,
|
||||
'percentage' => ( isset( $results[1] ) AND $results[1]['total'] > 0 ) ? round( ( $results[1]['total'] / $total ) * 100, 2 ) : 0
|
||||
);
|
||||
@@ -178,6 +224,7 @@ class ProfileFields extends OverviewStatisticsAbstract
|
||||
$pieBarData[] = array(
|
||||
'name' => $step,
|
||||
'nameRaw' => str_repeat( "<i class='fa-solid fa-star'></i>", $step ),
|
||||
'colTitle' => 'stats_pfields__stars',
|
||||
'value' => isset( $results[$step] ) ? $results[$step]['total'] : 0,
|
||||
'percentage' => ( isset( $results[$step] ) AND $results[$step]['total'] > 0 ) ? round( ( $results[$step]['total'] / $total ) * 100, 2 ) : 0
|
||||
);
|
||||
@@ -197,8 +244,8 @@ class ProfileFields extends OverviewStatisticsAbstract
|
||||
$percentage = $resultValue > 0 ? round( ( $resultValue / $total ) * 100, 2 ) : 0;
|
||||
|
||||
$pieBarData[] = array(
|
||||
'name' => $v,
|
||||
'tooltip' => $v . ": " . $resultValue . ' (' . $percentage . '%)',
|
||||
'name' => ( $v ?: Member::loggedIn()->language()->addToStack( 'stats_pfields__novalue' ) ),
|
||||
'tooltip' => ( $v ?: Member::loggedIn()->language()->addToStack( 'no_value') ) . ": " . $resultValue . ' (' . $percentage . '%)',
|
||||
'value' => $resultValue,
|
||||
'percentage' => $percentage
|
||||
);
|
||||
@@ -206,8 +253,8 @@ class ProfileFields extends OverviewStatisticsAbstract
|
||||
else
|
||||
{
|
||||
$pieBarData[] = array(
|
||||
'name' => $v,
|
||||
'tooltip' => $v . ": 0 (0%)",
|
||||
'name' => ( $v ?: Member::loggedIn()->language()->addToStack( 'stats_pfields__novalue' ) ),
|
||||
'tooltip' => ( $v ?: Member::loggedIn()->language()->addToStack( 'no_value' ) ) . ": 0 (0%)",
|
||||
'value' => 0,
|
||||
'percentage' => 0
|
||||
);
|
||||
@@ -215,6 +262,6 @@ class ProfileFields extends OverviewStatisticsAbstract
|
||||
}
|
||||
}
|
||||
|
||||
return Theme::i()->getTemplate( 'global', 'core', 'global' )->applePieChart( $pieBarData );
|
||||
return $pieBarData;
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ use IPS\Theme;
|
||||
use function count;
|
||||
use function defined;
|
||||
use function is_array;
|
||||
use function round;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
@@ -72,7 +73,44 @@ class Reactions extends OverviewStatisticsAbstract
|
||||
{
|
||||
/* Init Chart */
|
||||
$pieBarData = array();
|
||||
|
||||
$numbers = $this->getBlockNumbers( $dateRange, $subBlock );
|
||||
|
||||
foreach( $numbers as $title => $value )
|
||||
{
|
||||
if ( $title === 'statsreports_current_total' or $title === 'statsreports_previous_total' )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$pieBarData[] = array(
|
||||
'name' => Member::loggedIn()->language()->addToStack( $title ),
|
||||
'value' => $value,
|
||||
'percentage' => $numbers['statsreports_current_total'] ? round( ( $value / $numbers['statsreports_current_total'] ) * 100, 2 ) : 0,
|
||||
);
|
||||
}
|
||||
|
||||
if( count( $pieBarData ) )
|
||||
{
|
||||
$chart = Theme::i()->getTemplate( 'global', 'core', 'global' )->applePieChart( $pieBarData );
|
||||
}
|
||||
|
||||
return Theme::i()->getTemplate( 'stats' )->overviewComparisonCount( $numbers['statsreports_current_total'], $numbers['statsreports_previous_total'], $chart );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the block numbers
|
||||
*
|
||||
* @param array|string|null $dateRange String for a fixed time period in days, 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 array
|
||||
*/
|
||||
public function getBlockNumbers( array|string $dateRange = NULL, string $subBlock=NULL ) : array
|
||||
{
|
||||
/* Init Chart */
|
||||
$numbers = array();
|
||||
|
||||
/* Add Rows */
|
||||
$where = NULL;
|
||||
$previousCount = NULL;
|
||||
@@ -98,38 +136,24 @@ class Reactions extends OverviewStatisticsAbstract
|
||||
}
|
||||
|
||||
/* Figure out what reactions we have */
|
||||
$reactions = array();
|
||||
$total = 0;
|
||||
$chart = NULL;
|
||||
|
||||
foreach( Reaction::roots( NULL ) as $reaction )
|
||||
{
|
||||
$reactions[ 'reaction_title_' . $reaction->id ] = 0;
|
||||
$numbers[ 'reaction_title_' . $reaction->id ] = 0;
|
||||
}
|
||||
|
||||
foreach( Db::i()->select( 'COUNT(*) as total, reaction', 'core_reputation_index', $where, NULL, NULL, 'reaction' ) as $result )
|
||||
{
|
||||
$reactions['reaction_title_' . $result['reaction'] ] = $result['total'];
|
||||
$total += $result['total'];
|
||||
}
|
||||
|
||||
foreach( $reactions as $title => $value )
|
||||
{
|
||||
if( $value > 0 )
|
||||
if ( $result['total'] > 0 )
|
||||
{
|
||||
$pieBarData[] = array(
|
||||
'name' => Member::loggedIn()->language()->addToStack( $title ),
|
||||
'value' => $value,
|
||||
'percentage' => round( ( $value / $total ) * 100, 2 )
|
||||
);
|
||||
$numbers[ 'reaction_title_' . $result['reaction'] ] = $result['total'];
|
||||
$total += $result['total'];
|
||||
}
|
||||
}
|
||||
|
||||
if( count( $pieBarData ) )
|
||||
{
|
||||
$chart = Theme::i()->getTemplate( 'global', 'core', 'global' )->applePieChart( $pieBarData );
|
||||
}
|
||||
$numbers['statsreports_current_total'] = $total;
|
||||
$numbers['statsreports_previous_total'] = $previousCount;
|
||||
|
||||
return Theme::i()->getTemplate( 'stats' )->overviewComparisonCount( $total, $previousCount, $chart );
|
||||
return $numbers;
|
||||
}
|
||||
}
|
||||
@@ -66,6 +66,21 @@ class Registrations extends OverviewStatisticsAbstract
|
||||
* @return string
|
||||
*/
|
||||
public function getBlock( array|string $dateRange = NULL, string $subBlock = NULL ): string
|
||||
{
|
||||
$numbers = $this->getBlockNumbers( $dateRange, $subBlock );
|
||||
return Theme::i()->getTemplate( 'stats' )->overviewComparisonCount( $numbers['statsreports_current_count'], $numbers['statsreports_previous_count'] );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the block numbers
|
||||
*
|
||||
* @param array|string|null $dateRange String for a fixed time period in days, 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 array{statsreports_current_count: (number|null), statsreports_previous_count: (number|null)}
|
||||
*/
|
||||
public function getBlockNumbers( array|string $dateRange = NULL, string $subBlock=NULL ) : array
|
||||
{
|
||||
$where = NULL;
|
||||
$previousCount = NULL;
|
||||
@@ -91,7 +106,9 @@ class Registrations extends OverviewStatisticsAbstract
|
||||
}
|
||||
|
||||
$count = Db::i()->select( 'COUNT(*)', 'core_members', $where )->first();
|
||||
|
||||
return Theme::i()->getTemplate( 'stats' )->overviewComparisonCount( $count, $previousCount );
|
||||
return [
|
||||
'statsreports_current_count' => $count,
|
||||
'statsreports_previous_count' => $previousCount,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -66,6 +66,21 @@ class RepGiven extends OverviewStatisticsAbstract
|
||||
* @return string
|
||||
*/
|
||||
public function getBlock( array|string $dateRange = NULL, string $subBlock = NULL ): string
|
||||
{
|
||||
$numbers = $this->getBlockNumbers( $dateRange, $subBlock );
|
||||
return Theme::i()->getTemplate( 'stats' )->overviewComparisonCount( $numbers['statsreports_current_count'], $numbers['statsreports_previous_count'] );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the block numbers
|
||||
*
|
||||
* @param array|string|null $dateRange String for a fixed time period in days, 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 array{statsreports_current_count: (number|null), statsreports_previous_count: (number|null)}
|
||||
*/
|
||||
public function getBlockNumbers( array|string $dateRange = NULL, string $subBlock=NULL ) : array
|
||||
{
|
||||
$where = NULL;
|
||||
$previousCount = NULL;
|
||||
@@ -91,7 +106,9 @@ class RepGiven extends OverviewStatisticsAbstract
|
||||
}
|
||||
|
||||
$count = Db::i()->select( 'COUNT(DISTINCT(member_id))', 'core_reputation_index', $where )->first();
|
||||
|
||||
return Theme::i()->getTemplate( 'stats' )->overviewComparisonCount( $count, $previousCount );
|
||||
return [
|
||||
'statsreports_current_count' => $count,
|
||||
'statsreports_previous_count' => $previousCount,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -66,6 +66,21 @@ class RepReceived extends OverviewStatisticsAbstract
|
||||
* @return string
|
||||
*/
|
||||
public function getBlock( array|string $dateRange = NULL, string $subBlock = NULL ): string
|
||||
{
|
||||
$numbers = $this->getBlockNumbers( $dateRange, $subBlock );
|
||||
return Theme::i()->getTemplate( 'stats' )->overviewComparisonCount( $numbers['statsreports_current_count'], $numbers['statsreports_previous_count'] );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the block numbers
|
||||
*
|
||||
* @param array|string|null $dateRange String for a fixed time period in days, 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 array{statsreports_current_count: (number|null), statsreports_previous_count: (number|null)}
|
||||
*/
|
||||
public function getBlockNumbers( array|string $dateRange = NULL, string $subBlock=NULL ) : array
|
||||
{
|
||||
$where = NULL;
|
||||
$previousCount = NULL;
|
||||
@@ -91,7 +106,9 @@ class RepReceived extends OverviewStatisticsAbstract
|
||||
}
|
||||
|
||||
$count = Db::i()->select( 'COUNT(DISTINCT(member_received))', 'core_reputation_index', $where )->first();
|
||||
|
||||
return Theme::i()->getTemplate( 'stats' )->overviewComparisonCount( $count, $previousCount );
|
||||
return [
|
||||
'statsreports_current_count' => $count,
|
||||
'statsreports_previous_count' => $previousCount,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ use DateInterval;
|
||||
use IPS\DateTime;
|
||||
use IPS\Db;
|
||||
use IPS\Extensions\OverviewStatisticsAbstract;
|
||||
use IPS\Member;
|
||||
use IPS\Settings;
|
||||
use IPS\Theme;
|
||||
use function defined;
|
||||
@@ -68,11 +69,25 @@ class Tags extends OverviewStatisticsAbstract
|
||||
* @return string
|
||||
*/
|
||||
public function getBlock( array|string $dateRange = NULL, string $subBlock = NULL ): string
|
||||
{
|
||||
$topTags = $this->getBlockNumbers( $dateRange, $subBlock );
|
||||
return Theme::i()->getTemplate( 'stats' )->overviewTable( $topTags );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the numbers to add to the CSV export
|
||||
*
|
||||
* @param array|string|null $dateRange
|
||||
* @param string|null $subBlock
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getBlockNumbers( array|string $dateRange=null, string $subBlock=null ) : array
|
||||
{
|
||||
$topTags = array();
|
||||
|
||||
/* Add Rows */
|
||||
$where = NULL;
|
||||
$where = array();
|
||||
|
||||
if( $dateRange !== NULL )
|
||||
{
|
||||
@@ -91,35 +106,33 @@ class Tags extends OverviewStatisticsAbstract
|
||||
{
|
||||
case '7':
|
||||
$where = array( array( 'tag_added > ? ', $currentDate->sub( new DateInterval( 'P7D' ) )->getTimestamp() ) );
|
||||
break;
|
||||
break;
|
||||
|
||||
case '30':
|
||||
$where = array( array( 'tag_added > ? ', $currentDate->sub( new DateInterval( 'P1M' ) )->getTimestamp() ) );
|
||||
break;
|
||||
break;
|
||||
|
||||
case '90':
|
||||
$where = array( array( 'tag_added > ? ', $currentDate->sub( new DateInterval( 'P3M' ) )->getTimestamp() ) );
|
||||
break;
|
||||
break;
|
||||
|
||||
case '180':
|
||||
$where = array( array( 'tag_added > ? ', $currentDate->sub( new DateInterval( 'P6M' ) )->getTimestamp() ) );
|
||||
break;
|
||||
break;
|
||||
|
||||
case '365':
|
||||
$where = array( array( 'tag_added > ? ', $currentDate->sub( new DateInterval( 'P1Y' ) )->getTimestamp() ) );
|
||||
break;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Only get visible tags */
|
||||
$where[] = array( 'tag_aai_lookup IN(?)', Db::i()->select( 'tag_perm_aai_lookup', 'core_tags_perms', [ 'tag_perm_visible=1' ] ) );
|
||||
|
||||
foreach( Db::i()->select( 'COUNT(*) as total, tag_text', 'core_tags', $where, 'total DESC', 5, 'tag_text' ) as $tag )
|
||||
{
|
||||
$topTags[ $tag['tag_text'] ] = $tag['total'];
|
||||
}
|
||||
|
||||
return Theme::i()->getTemplate( 'stats' )->overviewTable( $topTags );
|
||||
return $topTags;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
<?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 19 Sep 2025
|
||||
*/
|
||||
|
||||
namespace IPS\core\extensions\core\Queue;
|
||||
|
||||
use IPS\Db;
|
||||
use IPS\Extensions\QueueAbstract;
|
||||
use IPS\Member;
|
||||
use IPS\Member\Club;
|
||||
use IPS\Node\Model;
|
||||
use IPS\Patterns\ActiveRecordIterator;
|
||||
use IPS\Task\Queue\OutOfRangeException as QueueOutOfRangeException;
|
||||
use OutOfRangeException;
|
||||
use function defined;
|
||||
use const IPS\REBUILD_SLOW;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Background Task
|
||||
*/
|
||||
class AutoFollowClubs extends QueueAbstract
|
||||
{
|
||||
/**
|
||||
* Parse data before queuing
|
||||
*
|
||||
* @param array $data
|
||||
* @return array|null
|
||||
*/
|
||||
public function preQueueData( array $data ): ?array
|
||||
{
|
||||
$data['count'] = (int) Db::i()->select( 'count(*)', 'core_clubs_memberships', [
|
||||
[ 'club_id=?', $data['club' ] ],
|
||||
[ Db::i()->in( 'status', [ Club::STATUS_MODERATOR, Club::STATUS_LEADER, Club::STATUS_MODERATOR ] ) ]
|
||||
] )->first();
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run Background Task
|
||||
*
|
||||
* @param array $data Data as it was passed to \IPS\Task::queue()
|
||||
* @param int $offset Offset
|
||||
* @return int New offset
|
||||
* @throws QueueOutOfRangeException Indicates offset doesn't exist and thus task is complete
|
||||
*/
|
||||
public function run( array &$data, int $offset ): int
|
||||
{
|
||||
$limit = REBUILD_SLOW;
|
||||
$members = iterator_to_array(
|
||||
new ActiveRecordIterator(
|
||||
Db::i()->select( '*', 'core_clubs_memberships', [
|
||||
[ 'club_id=?', $data['club' ] ],
|
||||
[ Db::i()->in( 'status', [ Club::STATUS_MEMBER, Club::STATUS_LEADER, Club::STATUS_MODERATOR ] ) ]
|
||||
], 'core_members.member_id', [ $offset, $limit ] )
|
||||
->join( 'core_members', 'core_clubs_memberships.member_id=core_members.member_id' ),
|
||||
Member::class
|
||||
)
|
||||
);
|
||||
|
||||
/* If we have a node specified, we are only following the node */
|
||||
if( isset( $data['node'] ) and isset( $data['nodeId'] ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
/* @var Model $nodeClass */
|
||||
$nodeClass = $data['node'];
|
||||
$node = $nodeClass::load( $data['nodeId'] );
|
||||
|
||||
foreach( $members as $member )
|
||||
{
|
||||
$node->follow( 'immediate', true, $member );
|
||||
}
|
||||
}
|
||||
catch( OutOfRangeException )
|
||||
{
|
||||
throw new QueueOutOfRangeException;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Otherwise we are following the club itself */
|
||||
try
|
||||
{
|
||||
$club = Club::load( $data['club'] );
|
||||
foreach( $members as $member )
|
||||
{
|
||||
$club->follow( 'immediate', true, $member );
|
||||
}
|
||||
}
|
||||
catch( OutOfRangeException )
|
||||
{
|
||||
throw new QueueOutOfRangeException;
|
||||
}
|
||||
}
|
||||
|
||||
$offset += $limit;
|
||||
if( $offset >= $data['count'] )
|
||||
{
|
||||
throw new QueueOutOfRangeException;
|
||||
}
|
||||
|
||||
return $offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Progress
|
||||
*
|
||||
* @param array $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( array $data, int $offset ): array
|
||||
{
|
||||
return [
|
||||
'text' => Member::loggedIn()->language()->addToStack( 'queue_club_auto_follow', false, [ 'sprintf' => Club::load( $data['club'] )->name ] ),
|
||||
'complete' => ( $offset ? round( 100 / $data['count'] * $offset, 2 ) : 0 )
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
<?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 Sep 2025
|
||||
*/
|
||||
|
||||
namespace IPS\core\extensions\core\Queue;
|
||||
|
||||
use IPS\Application;
|
||||
use IPS\Db;
|
||||
use IPS\Extensions\QueueAbstract;
|
||||
use IPS\Member;
|
||||
use IPS\Task\Queue\OutOfRangeException as QueueOutOfRangeException;
|
||||
use OutOfRangeException;
|
||||
use function defined;
|
||||
use const IPS\REBUILD_NORMAL;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Background Task
|
||||
*/
|
||||
class CleanupApprovalQueue extends QueueAbstract
|
||||
{
|
||||
/**
|
||||
* Parse data before queuing
|
||||
*
|
||||
* @param array $data
|
||||
* @return array|null
|
||||
*/
|
||||
public function preQueueData( array $data ): ?array
|
||||
{
|
||||
$data['count'] = (int) Db::i()->select( 'count(approval_id)', 'core_approval_queue' )->first();
|
||||
$data['processed'] = 0;
|
||||
$data['lastId'] = 0;
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run Background Task
|
||||
*
|
||||
* @param array $data Data as it was passed to \IPS\Task::queue()
|
||||
* @param int $offset Offset
|
||||
* @return int New offset
|
||||
* @throws QueueOutOfRangeException Indicates offset doesn't exist and thus task is complete
|
||||
*/
|
||||
public function run( array &$data, int $offset ): int
|
||||
{
|
||||
$limit = REBUILD_NORMAL;
|
||||
$rows = iterator_to_array(
|
||||
Db::i()->select( '*', 'core_approval_queue', [ 'approval_id > ?', $data['lastId'] ], 'approval_id', [ 0, $limit ] )
|
||||
);
|
||||
|
||||
if( empty( $rows ) )
|
||||
{
|
||||
throw new QueueOutOfRangeException;
|
||||
}
|
||||
|
||||
$enabledApplications = array_keys( Application::enabledApplications() );
|
||||
|
||||
foreach( $rows as $row )
|
||||
{
|
||||
$class = $row['approval_content_class'];
|
||||
|
||||
/* Make sure this class exists. First we'll check the application,
|
||||
in the case of non-upgraded apps */
|
||||
$bits = explode( "\\", $class );
|
||||
if( $bits[1] != 'Member' and !in_array( $bits[1], $enabledApplications ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if( !class_exists( $class ) )
|
||||
{
|
||||
throw new OutOfRangeException;
|
||||
}
|
||||
|
||||
$class::load( $row['approval_content_id'] );
|
||||
}
|
||||
catch( OutOfRangeException )
|
||||
{
|
||||
Db::i()->delete( 'core_approval_queue', [ 'approval_id=?', $row['approval_id'] ] );
|
||||
}
|
||||
|
||||
$data['processed']++;
|
||||
$data['lastId'] = $row['approval_id'];
|
||||
}
|
||||
|
||||
return $offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Progress
|
||||
*
|
||||
* @param array $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( array $data, int $offset ): array
|
||||
{
|
||||
return [
|
||||
'text' => Member::loggedIn()->language()->addToStack( 'queue_cleanup_approval_queue' ),
|
||||
'complete' => ( $data['processed'] ? round( 100 / $data['count'] * $data['processed'], 2 ) : 0 )
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -100,7 +100,7 @@ class InstallLanguage extends QueueAbstract
|
||||
{
|
||||
$data = json_decode( $data['data'], TRUE );
|
||||
|
||||
Db::i()->query( "REPLACE INTO " . Db::i()->prefix . "core_sys_lang_words (lang_id, word_app, word_plugin, word_theme, word_key, word_custom, word_default, word_default_version, word_custom_version, word_js, word_export ) SELECT {$data['language_id']} AS lang_id, word_app, word_plugin, word_theme, word_key, word_custom, word_default, word_default_version, word_custom_version, word_js, 0 FROM " . Db::i()->prefix . "core_sys_lang_words WHERE word_export=0 AND word_app='{$data['application']}' AND lang_id=" . Lang::defaultLanguage() );
|
||||
Db::i()->query( "REPLACE INTO " . Db::i()->prefix . "core_sys_lang_words (lang_id, word_app, word_theme, word_key, word_custom, word_default, word_default_version, word_custom_version, word_js, word_export ) SELECT {$data['language_id']} AS lang_id, word_app, word_plugin, word_theme, word_key, word_custom, word_default, word_default_version, word_custom_version, word_js, 0 FROM " . Db::i()->prefix . "core_sys_lang_words WHERE word_export=0 AND word_app='{$data['application']}' AND lang_id=" . Lang::defaultLanguage() );
|
||||
|
||||
unset( Store::i()->languages );
|
||||
}
|
||||
|
||||
@@ -120,6 +120,7 @@ class RebuildContainerCounts extends QueueAbstract
|
||||
}
|
||||
catch( Exception $e )
|
||||
{
|
||||
Log::log( $e, 'rebuild_container_counts_bgtask' );
|
||||
throw new \IPS\Task\Queue\OutOfRangeException;
|
||||
}
|
||||
|
||||
|
||||
@@ -87,17 +87,18 @@ class UpdateTaggedItems extends QueueAbstract
|
||||
{
|
||||
$item = $itemClass::load( $row['tag_meta_id'] );
|
||||
$currentTags = $item->tags();
|
||||
|
||||
/* If the tag is a prefix, we will just leave the prefix out of the tag set */
|
||||
if( !$row['tag_prefix'] )
|
||||
if( $prefix = $item->prefix() )
|
||||
{
|
||||
$index = array_search( $data['tag'], $currentTags );
|
||||
if( $index !== false )
|
||||
{
|
||||
unset( $currentTags[ $index ] );
|
||||
}
|
||||
$currentTags[ 'prefix' ] = $prefix;
|
||||
}
|
||||
|
||||
/* First we remove the old tag from the list */
|
||||
$index = array_search( $data['tag'], $currentTags );
|
||||
if( $index !== false )
|
||||
{
|
||||
unset( $currentTags[ $index ] );
|
||||
}
|
||||
|
||||
/* Are we renaming the tag? Put the new name in */
|
||||
if( isset( $data['new'] ) and $data['new'] )
|
||||
{
|
||||
@@ -116,7 +117,7 @@ class UpdateTaggedItems extends QueueAbstract
|
||||
/* Update the search index */
|
||||
if( SearchContent::isSearchable( $item ) )
|
||||
{
|
||||
if( $item::$firstCommentRequired )
|
||||
if( $item::$firstCommentRequired and $item->firstComment() )
|
||||
{
|
||||
Index::i()->index( $item->firstComment() );
|
||||
}
|
||||
|
||||
@@ -217,7 +217,7 @@ class Content extends ExtensionGenerator
|
||||
$where = [];
|
||||
|
||||
/* Get the count in the most efficient way possible */
|
||||
if ( in_array( 'IPS\Content\Permissions', class_implements( $class ) ) )
|
||||
if ( in_array( 'IPS\Node\Permissions', class_implements( $containerClass ) ) )
|
||||
{
|
||||
$member = new Member;
|
||||
$containerWhere = [];
|
||||
|
||||
@@ -59,6 +59,10 @@ class Emails extends \IPS\core\Statistics\Chart
|
||||
try
|
||||
{
|
||||
$oldestLog = Db::i()->select( 'MIN(time)', 'core_statistics', array( 'type=?', 'emails_sent' ) )->first();
|
||||
if ( is_null( $oldestLog ) )
|
||||
{
|
||||
throw new UnderflowException; // If there are no emails_sent rows, MIN(time) will be null
|
||||
}
|
||||
|
||||
if( !$minimumDate OR $oldestLog < $minimumDate->getTimestamp() )
|
||||
{
|
||||
|
||||
@@ -13,11 +13,18 @@ namespace IPS\core\extensions\core\Statistics;
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
|
||||
use DateInterval;
|
||||
use IPS\Application;
|
||||
use IPS\Content\Taggable;
|
||||
use IPS\DateTime;
|
||||
use IPS\Db;
|
||||
use IPS\Helpers\Chart;
|
||||
use IPS\Helpers\Chart\Database;
|
||||
use IPS\Helpers\Form\Node;
|
||||
use IPS\Helpers\Form\Select;
|
||||
use IPS\Http\Url;
|
||||
use IPS\IPS;
|
||||
use IPS\Member;
|
||||
use IPS\Node\Model;
|
||||
use function defined;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
@@ -77,7 +84,159 @@ class Tags extends \IPS\core\Statistics\Chart
|
||||
{
|
||||
$chart->addSeries( $tag, 'number', 'COUNT(*)', TRUE, $tag );
|
||||
}
|
||||
|
||||
$customValues = $chart->savedCustomFilters;
|
||||
$chart->customFiltersForm = array(
|
||||
'form' => $this->buildFormFields( $customValues ),
|
||||
'where' => function( $values )
|
||||
{
|
||||
$clause = [];
|
||||
$binds = [];
|
||||
$this->parseFormValues( $values );
|
||||
if( !empty( $values['app_key'] ) )
|
||||
{
|
||||
$clause[] = 'tag_meta_app=?';
|
||||
$binds[] = $values['app_key'];
|
||||
if( !empty( $values['item_class'] ) and !empty( $values['node_id'] ) )
|
||||
{
|
||||
$class = $values['item_class'];
|
||||
$clause[] = 'tag_meta_area=?';
|
||||
$binds[] = $class::$module;
|
||||
$clause[] = 'tag_meta_parent_id=?';
|
||||
$binds[] = $values['node_id'];
|
||||
}
|
||||
}
|
||||
|
||||
if( count( $clause ) )
|
||||
{
|
||||
return array_merge( array( implode( " and ", $clause ) ), $binds );
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
'groupBy' => 'tag_text',
|
||||
'series' => function( $values )
|
||||
{
|
||||
return [];
|
||||
},
|
||||
'defaultSeries' => function()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
);
|
||||
|
||||
return $chart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the form values so that we can easily handle them in the logic above
|
||||
*
|
||||
* @param array $values
|
||||
* @return void
|
||||
*/
|
||||
protected function parseFormValues( array &$values ) : void
|
||||
{
|
||||
if( empty( $values['app_key'] ) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/* If we already parsed this, stop here */
|
||||
if( array_key_exists( 'item_class', $values ) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/* Clear these out so that we don't have a mix-up later */
|
||||
if( isset( $values['node_class'] ) )
|
||||
{
|
||||
unset( $values['node_class'] );
|
||||
unset( $values['node_id'] );
|
||||
}
|
||||
|
||||
foreach( Application::load( $values['app_key'] )->extensions( 'core', 'ContentRouter' ) as $extension )
|
||||
{
|
||||
foreach( $extension->classes as $class )
|
||||
{
|
||||
if( IPS::classUsesTrait( $class, Taggable::class ) )
|
||||
{
|
||||
$values['item_class'] = $class;
|
||||
if( isset( $class::$containerNodeClass ) )
|
||||
{
|
||||
$fieldKey = str_replace( '\\', '_', $class );
|
||||
if( array_key_exists( $fieldKey, $values ) and !empty( $values[ $fieldKey ] ) )
|
||||
{
|
||||
$values['node_class'] = $class::$containerNodeClass;
|
||||
$values['node_id'] = ( $values[ $fieldKey ] instanceof Model ? $values[ $fieldKey ]->_id : null );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an array of form fields for the custom filters
|
||||
*
|
||||
* @param array $customValues
|
||||
* @return Select[]
|
||||
*/
|
||||
protected function buildFormFields( array $customValues ) : array
|
||||
{
|
||||
$options = array( '' => "All" );
|
||||
$fields = array();
|
||||
$toggles = [];
|
||||
foreach( Application::appsWithExtension( 'core', 'ContentRouter' ) as $key => $application )
|
||||
{
|
||||
$appHasTags = false;
|
||||
foreach( $application->extensions( 'core', 'ContentRouter' ) as $router )
|
||||
{
|
||||
foreach ( $router->classes as $class )
|
||||
{
|
||||
if( !IPS::classUsesTrait( $class, Taggable::class ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$appHasTags = true;
|
||||
if( isset( $class::$containerNodeClass ) )
|
||||
{
|
||||
$nodeField = new Node( str_replace( '\\', '_', $class ), ( isset( $customValues['app_key'] ) and $customValues['app_key'] == $key and isset( $customValues['node_id'] ) ) ? $customValues['node_id'] : null, FALSE, array(
|
||||
'class' => $class::$containerNodeClass,
|
||||
'clubs' => FALSE,
|
||||
'multiple' => FALSE,
|
||||
'autoPopulate' => TRUE
|
||||
), NULL, NULL, NULL, str_replace( '\\', '_', $class ) );
|
||||
$nodeField->label = Member::loggedIn()->language()->addToStack( $class::$containerNodeClass::$nodeTitle );
|
||||
$fields[] = $nodeField;
|
||||
|
||||
$toggles[ $key ][] = str_replace( '\\', '_', $class );
|
||||
}
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
if( $appHasTags )
|
||||
{
|
||||
$options[ $key ] = $application->_title;
|
||||
}
|
||||
}
|
||||
|
||||
$appField = new Select( 'app_key', $customValues['app_key'] ?? null, false, [
|
||||
'options' => $options,
|
||||
'toggles' => $toggles
|
||||
]);
|
||||
$appField->label = Member::loggedIn()->language()->addToStack( Application::$nodeTitle );
|
||||
$return = [
|
||||
$appField
|
||||
];
|
||||
|
||||
foreach( $fields as $field )
|
||||
{
|
||||
$return[] = $field;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user