Version 4.6.0
This commit is contained in:
1 parent
f79dcf067a
commit
517a5e1f70
2036 files changed
+110041
-26162
No files matched your search
@@ -0,0 +1,174 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Achievement Action 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 03 Mar 2021
|
||||
*/
|
||||
|
||||
namespace IPS\core\extensions\core\AchievementAction;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Achievement Action Extension
|
||||
*/
|
||||
class _Comment extends \IPS\core\Achievements\Actions\AbstractContentAchievementAction
|
||||
{
|
||||
protected static $includeItems = FALSE;
|
||||
protected static $includeReviews = FALSE;
|
||||
|
||||
/**
|
||||
* Work out if the filters applies for a given action
|
||||
*
|
||||
* Important note for milestones: consider the context. This method is called by \IPS\Member::achievementAction(). If your code
|
||||
* calls that BEFORE making its change in the database (or there is read/write separation), you will need to add
|
||||
* 1 to the value being considered for milestones
|
||||
*
|
||||
* @param \IPS\Member $subject The subject member
|
||||
* @param array $filters The value returned by formatFilterValues()
|
||||
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
|
||||
* @return bool
|
||||
*/
|
||||
public function filtersMatch( \IPS\Member $subject, array $filters, $extra = NULL ): bool
|
||||
{
|
||||
if ( !parent::filtersMatch( $subject, $filters, $extra ) )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if ( isset( $filters['milestone'] ) )
|
||||
{
|
||||
$count = 0;
|
||||
$classes = [];
|
||||
$itemClassesToDeduct = [];
|
||||
if ( isset( $filters['type'] ) )
|
||||
{
|
||||
if ( isset( $filters[ 'nodes_' . str_replace( '\\', '-', $filters['type'] ) ] ) )
|
||||
{
|
||||
$class = $filters['type'];
|
||||
$where = [];
|
||||
$where[] = [ $class::$databasePrefix . $class::$databaseColumnMap['author'] . '=?', $subject->member_id ];
|
||||
$where[] = [ \IPS\Db::i()->in( $class::$databasePrefix . $class::$databaseColumnMap['container'] ), $filters[ 'nodes_' . str_replace( '\\', '-', $filters['type'] ) ] ];
|
||||
|
||||
$count += \IPS\Db::i()->select( 'COUNT(*)', $class::$databaseTable, $where )->first();
|
||||
}
|
||||
else
|
||||
{
|
||||
$classes[] = $filters['type'];
|
||||
}
|
||||
|
||||
foreach ( $classes as $class )
|
||||
{
|
||||
$count += $class::memberPostCount( $subject, TRUE, FALSE );
|
||||
}
|
||||
foreach ( $itemClassesToDeduct as $class )
|
||||
{
|
||||
$count -= $class::memberPostCount( $subject, TRUE, FALSE );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* It's too expensive to get a live count from every single item/comment class available to the suite
|
||||
so lets just use the post count here. It won't be quite as accurate but we're not a bank so whatever */
|
||||
$count = $subject->member_posts;
|
||||
}
|
||||
|
||||
if ( $count < $filters['milestone'] )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the labels for the people this action might give awards to
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function awardOptions(): array
|
||||
{
|
||||
return [
|
||||
'subject' => 'achievement_filter_Comment_author',
|
||||
'other' => 'achievement_filter_Comment_item_author'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "other" people we need to award =stuff to
|
||||
*
|
||||
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
|
||||
* @return array
|
||||
*/
|
||||
public function awardOther( $extra = NULL ): array
|
||||
{
|
||||
return [ $extra->item()->author() ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a description for this action to show in the log
|
||||
*
|
||||
* @param string $identifier The identifier as returned by identifier()
|
||||
* @param array $actor If the member was the "subject", "other", or both
|
||||
* @return string
|
||||
*/
|
||||
public function logRow( string $identifier, array $actor ): string
|
||||
{
|
||||
$exploded = explode( ':', $identifier );
|
||||
|
||||
$sprintf = [];
|
||||
try
|
||||
{
|
||||
$comment = $exploded[0]::load( $exploded[1] );
|
||||
$item = $comment->item();
|
||||
$sprintf = [ 'htmlsprintf' => [
|
||||
\IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->basicUrl( $comment->url(), TRUE, $item->mapped('title') ?: $item->indefiniteArticle(), FALSE )
|
||||
] ];
|
||||
}
|
||||
catch ( \OutOfRangeException $e )
|
||||
{
|
||||
$sprintf = [ 'sprintf' => [ \IPS\Member::loggedIn()->language()->addToStack('modcp_deleted') ] ];
|
||||
}
|
||||
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__Comment_log', FALSE, $sprintf );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get "description" for rule
|
||||
*
|
||||
* @param \IPS\core\Achievements\Rule $rule The rule
|
||||
* @return string|NULL
|
||||
*/
|
||||
public function ruleDescription( \IPS\core\Achievements\Rule $rule ): ?string
|
||||
{
|
||||
$type = $rule->filters['type'] ?? NULL;
|
||||
|
||||
$conditions = [];
|
||||
if ( isset( $rule->filters['milestone'] ) )
|
||||
{
|
||||
$conditions[] = \IPS\Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone', FALSE, [
|
||||
'htmlsprintf' => [
|
||||
\IPS\Theme::i()->getTemplate( 'achievements' )->ruleDescriptionBadge( 'milestone', \IPS\Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone_nth', FALSE, [ 'pluralize' => [ $rule->filters['milestone'] ] ] ) )
|
||||
],
|
||||
'sprintf' => [ $type ? \IPS\Member::loggedIn()->language()->addToStack( $type::$title ) : \IPS\Member::loggedIn()->language()->addToStack('AchievementAction__Comment_title_generic') ]
|
||||
] );
|
||||
}
|
||||
if ( $nodeCondition = $this->_nodeFilterDescription( $rule ) )
|
||||
{
|
||||
$conditions[] = $nodeCondition;
|
||||
}
|
||||
|
||||
return \IPS\Theme::i()->getTemplate( 'achievements' )->ruleDescription(
|
||||
$type ? \IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__NewContentItem_title_t', FALSE, [ 'sprintf' => [ \IPS\Member::loggedIn()->language()->addToStack( $type::$title ) ] ] ) : \IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__Comment_title' ),
|
||||
$conditions
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,408 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Achievement Action 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 05 Mar 2021
|
||||
*/
|
||||
|
||||
namespace IPS\core\extensions\core\AchievementAction;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Achievement Action Extension
|
||||
*/
|
||||
class _ContentPromotion extends \IPS\core\Achievements\Actions\AbstractContentAchievementAction
|
||||
{
|
||||
/**
|
||||
* Get filter form elements
|
||||
*
|
||||
* @param array|NULL $filters Current filter values (if editing)
|
||||
* @param \IPS\Http\Url $url The URL the form is being shown on
|
||||
* @return array
|
||||
*/
|
||||
public function filters( ?array $filters, \IPS\Http\Url $url ): array
|
||||
{
|
||||
$return = parent::filters( $filters, $url );
|
||||
|
||||
$promoFilter = new \IPS\Helpers\Form\CheckboxSet( 'achievement_filter_ContentPromotion_promotype', ( $filters and isset( $filters['promotype'] ) and $filters['promotype'] ) ? $filters['promotype'] : 0, FALSE, [
|
||||
'options' => [
|
||||
'promote' => 'achievement_filter_ContentPromotion_promotype_promote',
|
||||
'feature' => 'achievement_filter_ContentPromotion_promotype_feature',
|
||||
'recommend' => 'achievement_filter_ContentPromotion_promotype_recommend'
|
||||
]
|
||||
], NULL, \IPS\Member::loggedIn()->language()->addToStack( 'achievement_subfilter_ContentPromotion_promotype_prefix' ) );
|
||||
$return['promotype'] = $promoFilter;
|
||||
|
||||
if ( isset( $return['milestone'] ) )
|
||||
{
|
||||
$return['milestone']->suffix = \IPS\Member::loggedIn()->language()->addToStack('achievement_filter_ContentPromotion_nth_suffix');
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format filter form values
|
||||
*
|
||||
* @param array $values The values from the form
|
||||
* @return array
|
||||
*/
|
||||
public function formatFilterValues( array $values ): array
|
||||
{
|
||||
$return = parent::formatFilterValues( $values );
|
||||
if ( isset( $values['achievement_filter_ContentPromotion_promotype'] ) )
|
||||
{
|
||||
$return['promotype'] = $values['achievement_filter_ContentPromotion_promotype'];
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Work out if the filters applies for a given action
|
||||
*
|
||||
* Important note for milestones: consider the context. This method is called by \IPS\Member::achievementAction(). If your code
|
||||
* calls that BEFORE making its change in the database (or there is read/write separation), you will need to add
|
||||
* 1 to the value being considered for milestones
|
||||
*
|
||||
* @param \IPS\Member $subject The subject member
|
||||
* @param array $filters The value returned by formatFilterValues()
|
||||
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
|
||||
* @return bool
|
||||
*/
|
||||
public function filtersMatch( \IPS\Member $subject, array $filters, $extra = NULL ): bool
|
||||
{
|
||||
if ( !parent::filtersMatch( $subject, $filters, $extra['content'] ) )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if ( isset( $filters['promotype'] ) and ! \in_array( $extra['promotype'], $filters['promotype'] ) )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if ( isset( $filters['milestone'] ) )
|
||||
{
|
||||
if ( ( $this->getQuery( 'COUNT(*)', $filters, $filters['promotype'], $subject )->first() + 1 ) < $filters['milestone'] )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get identifier to prevent the member being awarded points for the same action twice
|
||||
* Must be unique within within of this domain, must not exceed 32 chars.
|
||||
*
|
||||
* @param \IPS\Member $subject The subject member
|
||||
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
|
||||
* @return string
|
||||
*/
|
||||
public function identifier( \IPS\Member $subject, $extra = NULL ): string
|
||||
{
|
||||
return \get_class( $extra['content'] ) . ':' . $extra['content']->{$extra['content']::$databaseColumnId} . ':' . $extra['promotype'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a description for this action to show in the log
|
||||
*
|
||||
* @param string $identifier The identifier as returned by identifier()
|
||||
* @param array $actor If the member was the "subject", "other", or both
|
||||
* @return string
|
||||
*/
|
||||
public function logRow( string $identifier, array $actor ): string
|
||||
{
|
||||
$exploded = explode( ':', $identifier );
|
||||
|
||||
$contentLink = \IPS\Member::loggedIn()->language()->addToStack('modcp_deleted');
|
||||
try
|
||||
{
|
||||
$class = $exploded[0];
|
||||
$content = $class::load( $exploded[1] );
|
||||
$contentLink = \IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->basicUrl( $content->url(), TRUE, $content->mapped('title'), FALSE );
|
||||
$promoType = \IPS\Member::loggedIn()->language()->addToStack( 'achievement_filter_ContentPromotion_promotype_' . $exploded[2] );
|
||||
}
|
||||
catch ( \OutOfRangeException $e ) { }
|
||||
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__ContentPromotion_log_subject', FALSE, [ 'sprintf' => [ $promoType ], 'htmlsprintf' => [ $contentLink ] ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get "description" for rule
|
||||
*
|
||||
* @param \IPS\core\Achievements\Rule $rule The rule
|
||||
* @return string|NULL
|
||||
*/
|
||||
public function ruleDescription( \IPS\core\Achievements\Rule $rule ): ?string
|
||||
{
|
||||
$conditions = [];
|
||||
if ( isset( $rule->filters['milestone'] ) )
|
||||
{
|
||||
$conditions[] = \IPS\Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone', FALSE, [
|
||||
'htmlsprintf' => [
|
||||
\IPS\Theme::i()->getTemplate( 'achievements' )->ruleDescriptionBadge( 'milestone', \IPS\Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone_nth', FALSE, [ 'pluralize' => [ $rule->filters['milestone'] ] ] ) )
|
||||
],
|
||||
'sprintf' => \IPS\Member::loggedIn()->language()->addToStack('AchievementAction__ContentPromotion_title_generic')
|
||||
] );
|
||||
}
|
||||
if ( isset( $rule->filters['promotype'] ) )
|
||||
{
|
||||
$promoTypes = [];
|
||||
foreach ( $rule->filters['promotype'] as $type )
|
||||
{
|
||||
try
|
||||
{
|
||||
$promoTypes[] = \IPS\Member::loggedIn()->language()->addToStack( 'achievement_filter_ContentPromotion_promotype_' . $type );
|
||||
}
|
||||
catch ( \OutOfRangeException $e ) {}
|
||||
}
|
||||
if ( $promoTypes )
|
||||
{
|
||||
$conditions[] = \IPS\Member::loggedIn()->language()->addToStack( 'achievements_title_filter_type', FALSE, [
|
||||
'htmlsprintf' => [
|
||||
\IPS\Theme::i()->getTemplate( 'achievements' )->ruleDescriptionBadge( 'other',
|
||||
\count( $promoTypes ) === 1 ? $promoTypes[0] : \IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__ContentPromotion_types', FALSE, [ 'sprintf' => [
|
||||
\count( $promoTypes ),
|
||||
] ] ),
|
||||
\count( $promoTypes ) === 1 ? NULL : $promoTypes
|
||||
)
|
||||
],
|
||||
] );
|
||||
}
|
||||
}
|
||||
if ( $nodeCondition = $this->_nodeFilterDescription( $rule ) )
|
||||
{
|
||||
$conditions[] = $nodeCondition;
|
||||
}
|
||||
|
||||
return \IPS\Theme::i()->getTemplate( 'achievements' )->ruleDescription(
|
||||
\IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__ContentPromotion_title' ),
|
||||
$conditions
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get rebuild data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
static public function rebuildData()
|
||||
{
|
||||
return [ [
|
||||
'table' => 'core_content_meta',
|
||||
'pkey' => 'meta_id',
|
||||
'date' => 'meta_added',
|
||||
'where' => [ ['meta_type=?', 'core_FeaturedComments'] ],
|
||||
],
|
||||
[
|
||||
'table' => 'core_content_featured',
|
||||
'pkey' => 'feature_id',
|
||||
'date' => 'feature_date',
|
||||
'where' => [],
|
||||
],
|
||||
[
|
||||
'table' => 'core_social_promote',
|
||||
'pkey' => 'promote_id',
|
||||
'date' => 'promote_added',
|
||||
'where' => [],
|
||||
] ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the rebuild row
|
||||
*
|
||||
* @param array $row Row from database
|
||||
* @param array $data Data collected when starting rebuild [table, pkey...]
|
||||
* @return void
|
||||
*/
|
||||
public static function rebuildRow( $row, $data )
|
||||
{
|
||||
switch ( $data['table'] )
|
||||
{
|
||||
case 'core_content_meta':
|
||||
$metaData = json_decode( $row['meta_data'], TRUE );
|
||||
$commentClass = $row['meta_class']::$commentClass;
|
||||
$comment = $commentClass::load( $metaData['comment'] );
|
||||
$comment->author()->achievementAction( 'core', 'ContentPromotion', [
|
||||
'content' => $comment,
|
||||
'promotype' => 'recommend'
|
||||
] );
|
||||
break;
|
||||
case 'core_content_featured':
|
||||
$class = $row['feature_content_class'];
|
||||
$item = $class::load( $row['feature_content_id'] );
|
||||
$item->author()->achievementAction( 'core', 'ContentPromotion', [
|
||||
'content' => $item,
|
||||
'promotype' => 'feature'
|
||||
] );
|
||||
break;
|
||||
case 'core_social_promote':
|
||||
$promote = \IPS\core\Promote::load( $row['promote_id'] );
|
||||
$promote->author()->achievementAction( 'core', 'ContentPromotion', [
|
||||
'content' => $promote->object(),
|
||||
'promotype' => 'promote'
|
||||
] );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data based on the things.
|
||||
*
|
||||
* @param $select
|
||||
* @param $filters
|
||||
* @param $type
|
||||
* @param null $subject
|
||||
* @param null $time
|
||||
* @param null|boolean $order
|
||||
* @param null $limit
|
||||
* @return \IPS\Db\Select
|
||||
*/
|
||||
protected function getQuery( $select, $filters, $type, $subject=NULL, $lastId=NULL, $time=NULL, $order=NULL, $limit=NULL )
|
||||
{
|
||||
$itemClass = $filters['type'];
|
||||
|
||||
if ( isset( $itemClass::$databaseColumnMap['hidden'] ) )
|
||||
{
|
||||
$subWhere = [ [ $itemClass::$databaseColumnMap['hidden'] . '=0' ] ];
|
||||
}
|
||||
elseif ( isset( $itemClass::$databaseColumnMap['approved'] ) )
|
||||
{
|
||||
$subWhere = [ [ $itemClass::$databaseColumnMap['approved'] . '=1' ] ];
|
||||
}
|
||||
|
||||
/* Recommended (but really called featured in the code just to confuse me) */
|
||||
if ( $type === 'recommend' )
|
||||
{
|
||||
if ( $subject )
|
||||
{
|
||||
$where = [
|
||||
['meta_item_author=? and meta_type=?', $subject->member_id, 'core_FeaturedComments'],
|
||||
];
|
||||
}
|
||||
else
|
||||
{
|
||||
$where = [
|
||||
['meta_type=?', 'core_FeaturedComments'],
|
||||
];
|
||||
}
|
||||
|
||||
if ( $time )
|
||||
{
|
||||
$where[] = [ 'meta_added >= ' . $time->getTimestamp() ];
|
||||
}
|
||||
|
||||
if ( $lastId )
|
||||
{
|
||||
$where[] = [ 'meta_id > ' . $lastId ];
|
||||
}
|
||||
|
||||
if ( $order )
|
||||
{
|
||||
$order = 'meta_id ASC';
|
||||
}
|
||||
|
||||
if ( isset( $filters['type'] ) )
|
||||
{
|
||||
$where[] = [ 'meta_class=?', $filters['type'] ];
|
||||
|
||||
if ( isset( $filters[ 'nodes_' . str_replace( '\\', '-', $itemClass ) ] ) )
|
||||
{
|
||||
$subWhere[] = [ \IPS\Db::i()->in( 'forum_id', $filters[ 'nodes_' . str_replace( '\\', '-', $itemClass ) ] ) ];
|
||||
}
|
||||
|
||||
$where[] = [ 'meta_item_id IN(?)', \IPS\Db::i()->select( $itemClass::$databaseColumnId, $itemClass::$databasePrefix . $itemClass::$databaseTable, $subWhere ) ];
|
||||
}
|
||||
|
||||
$query = \IPS\Db::i()->select( $select, 'core_content_meta', $where );
|
||||
}
|
||||
else if ( $type === 'feature' )
|
||||
{
|
||||
if ( $subject )
|
||||
{
|
||||
$where = [
|
||||
['feature_content_author=?', $subject->member_id],
|
||||
];
|
||||
}
|
||||
|
||||
if ( $time )
|
||||
{
|
||||
$where[] = [ 'feature_date >= ' . $time->getTimestamp() ];
|
||||
}
|
||||
|
||||
if ( $lastId )
|
||||
{
|
||||
$where[] = [ 'feature_id > ' . $lastId ];
|
||||
}
|
||||
|
||||
if ( $order )
|
||||
{
|
||||
$order = 'feature_id ASC';
|
||||
}
|
||||
|
||||
if ( isset( $filters['type'] ) )
|
||||
{
|
||||
$where[] = [ 'feature_content_class=?', $filters['type'] ];
|
||||
|
||||
if ( isset( $filters[ 'nodes_' . str_replace( '\\', '-', $itemClass ) ] ) )
|
||||
{
|
||||
$subWhere[] = [ \IPS\Db::i()->in( 'forum_id', $filters[ 'nodes_' . str_replace( '\\', '-', $itemClass ) ] ) ];
|
||||
}
|
||||
|
||||
$where[] = [ 'feature_content_id IN(?)', \IPS\Db::i()->select( $itemClass::$databaseColumnId, $itemClass::$databasePrefix . $itemClass::$databaseTable, $subWhere ) ];
|
||||
}
|
||||
|
||||
$query = \IPS\Db::i()->select( $select, 'core_content_featured', $where );
|
||||
}
|
||||
else if ( $type === 'promote' )
|
||||
{
|
||||
if ( $subject )
|
||||
{
|
||||
$where = [
|
||||
['promote_author_id=?', $subject->member_id],
|
||||
];
|
||||
}
|
||||
|
||||
if ( $time )
|
||||
{
|
||||
$where[] = [ 'promote_added >= ' . $time->getTimestamp() ];
|
||||
}
|
||||
|
||||
if ( $lastId )
|
||||
{
|
||||
$where[] = [ 'promote_id > ' . $lastId ];
|
||||
}
|
||||
|
||||
if ( $order )
|
||||
{
|
||||
$order = 'promote_id ASC';
|
||||
}
|
||||
|
||||
if ( isset( $filters['type'] ) )
|
||||
{
|
||||
$where[] = [ 'promote_hide=0 and promote_class=?', $filters['type'] ];
|
||||
|
||||
if ( isset( $filters[ 'nodes_' . str_replace( '\\', '-', $itemClass ) ] ) )
|
||||
{
|
||||
$subWhere[] = [ \IPS\Db::i()->in( 'forum_id', $filters[ 'nodes_' . str_replace( '\\', '-', $itemClass ) ] ) ];
|
||||
}
|
||||
|
||||
$where[] = [ 'promote_class_id IN(?)', \IPS\Db::i()->select( $itemClass::$databaseColumnId, $itemClass::$databasePrefix . $itemClass::$databaseTable, $subWhere ) ];
|
||||
}
|
||||
|
||||
$query = \IPS\Db::i()->select( $select, 'core_social_promote', $where, $order, $limit );
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Achievement Action 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 04 Mar 2021
|
||||
*/
|
||||
|
||||
namespace IPS\core\extensions\core\AchievementAction;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Achievement Action Extension
|
||||
*/
|
||||
class _FollowContentItem extends \IPS\core\Achievements\Actions\AbstractContentAchievementAction
|
||||
{
|
||||
/**
|
||||
* Get filter form elements
|
||||
*
|
||||
* @param array|NULL $filters Current filter values (if editing)
|
||||
* @param \IPS\Http\Url $url The URL the form is being shown on
|
||||
* @return array
|
||||
*/
|
||||
public function filters( ?array $filters, \IPS\Http\Url $url ): array
|
||||
{
|
||||
$filters = parent::filters( $filters, $url );
|
||||
|
||||
foreach( $filters['type']->options['options'] as $class => $value )
|
||||
{
|
||||
if ( ! \in_array( 'IPS\Content\Followable', class_implements( $class, TRUE ) ) )
|
||||
{
|
||||
unset( $filters['type']->options['options'][ $class ] );
|
||||
unset( $filters['nodes_' . str_replace( '\\', '-', $class ) ] );
|
||||
}
|
||||
}
|
||||
|
||||
return $filters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Work out if the filters applies for a given action
|
||||
*
|
||||
* Important note for milestones: consider the context. This method is called by \IPS\Member::achievementAction(). If your code
|
||||
* calls that BEFORE making its change in the database (or there is read/write separation), you will need to add
|
||||
* 1 to the value being considered for milestones
|
||||
*
|
||||
* @param \IPS\Member $subject The subject member
|
||||
* @param array $filters The value returned by formatFilterValues()
|
||||
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
|
||||
* @return bool
|
||||
*/
|
||||
public function filtersMatch( \IPS\Member $subject, array $filters, $extra = NULL ): bool
|
||||
{
|
||||
/* The parent already matches content type and node, so do that first */
|
||||
if( !parent::filtersMatch( $subject, $filters, $extra['item'] ) )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if ( isset( $filters['milestone'] ) )
|
||||
{
|
||||
$query = $this->getQuery( 'COUNT(*)', [ [ 'follow_member_id=?', $subject->member_id ] ], NULL, NULL, $filters );
|
||||
|
||||
if ( $query->first() < $filters['milestone'] )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the labels for the people this action might give awards to
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function awardOptions(): array
|
||||
{
|
||||
return [
|
||||
'subject' => 'achievement_filter_FollowContentItem_follower',
|
||||
'other' => 'achievement_filter_FollowContentItem_author'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "other" people we need to award =stuff to
|
||||
*
|
||||
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
|
||||
* @return array
|
||||
*/
|
||||
public function awardOther( $extra = NULL ): array
|
||||
{
|
||||
return [ $extra['author'] ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get identifier to prevent the member being awarded points for the same action twice
|
||||
* Must be unique within within of this domain, must not exceed 32 chars.
|
||||
*
|
||||
* @param \IPS\Member $subject The subject member
|
||||
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
|
||||
* @return string
|
||||
*/
|
||||
public function identifier( \IPS\Member $subject, $extra = NULL ): string
|
||||
{
|
||||
return \get_class( $extra['item'] ) . ':' . $extra['item']->{$extra['item']::$databaseColumnId} . ':' . $subject->member_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a description for this action to show in the log
|
||||
*
|
||||
* @param string $identifier The identifier as returned by identifier()
|
||||
* @param array $actor If the member was the "subject", "other", or both
|
||||
* @return string
|
||||
*/
|
||||
public function logRow( string $identifier, array $actor ): string
|
||||
{
|
||||
$exploded = explode( ':', $identifier );
|
||||
|
||||
$sprintf = [];
|
||||
try
|
||||
{
|
||||
$item = $exploded[0]::load( $exploded[1] );
|
||||
$sprintf = [ 'htmlsprintf' => [
|
||||
\IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->basicUrl( $item->url(), TRUE, $item->mapped('title') ?: $item->indefiniteArticle(), FALSE )
|
||||
] ];
|
||||
}
|
||||
catch ( \OutOfRangeException $e )
|
||||
{
|
||||
$sprintf = [ 'sprintf' => [ \IPS\Member::loggedIn()->language()->addToStack('modcp_deleted') ] ];
|
||||
}
|
||||
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__FollowContentItem_log', FALSE, $sprintf );
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get "description" for rule
|
||||
*
|
||||
* @param \IPS\core\Achievements\Rule $rule The rule
|
||||
* @return string|NULL
|
||||
*/
|
||||
public function ruleDescription( \IPS\core\Achievements\Rule $rule ): ?string
|
||||
{
|
||||
$type = $rule->filters['type'] ?? NULL;
|
||||
|
||||
$conditions = [];
|
||||
if ( isset( $rule->filters['milestone'] ) )
|
||||
{
|
||||
$conditions[] = \IPS\Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone', FALSE, [
|
||||
'htmlsprintf' => [
|
||||
\IPS\Theme::i()->getTemplate( 'achievements' )->ruleDescriptionBadge( 'milestone', \IPS\Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone_nth', FALSE, [ 'pluralize' => [ $rule->filters['milestone'] ] ] ) )
|
||||
],
|
||||
'sprintf' => [ $type ? \IPS\Member::loggedIn()->language()->addToStack( $type::$title, FALSE, [ 'strtolower' => TRUE ] ) : \IPS\Member::loggedIn()->language()->addToStack('AchievementAction__NewContentItem_title_generic') ]
|
||||
] );
|
||||
}
|
||||
if ( $nodeCondition = $this->_nodeFilterDescription( $rule ) )
|
||||
{
|
||||
$conditions[] = $nodeCondition;
|
||||
}
|
||||
|
||||
return \IPS\Theme::i()->getTemplate( 'achievements' )->ruleDescription(
|
||||
$type ? \IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__FollowContentItem_title_t', FALSE, [ 'sprintf' => [ \IPS\Member::loggedIn()->language()->addToStack( $type::$title ) ] ] ) : \IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__FollowContentItem_title' ),
|
||||
$conditions
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get rebuild data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
static public function rebuildData()
|
||||
{
|
||||
return [ [
|
||||
'table' => 'core_follow',
|
||||
'pkey' => 'follow_id',
|
||||
'date' => 'follow_added',
|
||||
'where' => [ [ '(follow_app !=? and follow_area !=?)', 'core', 'member' ] ],
|
||||
] ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the rebuild row
|
||||
*
|
||||
* @param array $row Row from database
|
||||
* @param array $data Data collected when starting rebuild [table, pkey...]
|
||||
* @return void
|
||||
*/
|
||||
public static function rebuildRow( $row, $data )
|
||||
{
|
||||
$className = 'IPS\\' . $row['follow_app'] . '\\' . mb_ucfirst( $row['follow_area'] );
|
||||
|
||||
if ( \in_array( 'IPS\Content\Item', class_parents( $className ) ) )
|
||||
{
|
||||
$item = $className::load( $row['follow_rel_id'] );
|
||||
\IPS\Member::load( $row['follow_member_id'] )->achievementAction( 'core', 'FollowContentItem', [
|
||||
'item' => $item,
|
||||
'author' => $item->author()
|
||||
] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a query to use for multiple methods within this extension
|
||||
* @param string $select Select for the query
|
||||
* @param array|NULL $where Where for the query
|
||||
* @param int|NULL $limit Limit for the query
|
||||
* @param string|NULL $order Order by for the query
|
||||
* @param array $filters Rule filters
|
||||
* @return \IPS\Db\Select
|
||||
*/
|
||||
public function getQuery( $select, $where, $limit, $order, $filters ): \IPS\Db\Select
|
||||
{
|
||||
$joinContainers = FALSE;
|
||||
$extraJoinCondition = NULL;
|
||||
$where = \is_array( $where ) ? $where : array();
|
||||
|
||||
/* Limit by type and node */
|
||||
if ( isset( $filters['type'] ) )
|
||||
{
|
||||
list( $ns, $app, $area ) = explode( '\\', $filters['type'] );
|
||||
$where[] = [ 'follow_app=? and follow_area=?', \mb_strtolower( $app ), \mb_strtolower( $area ) ];
|
||||
|
||||
$itemClass = $filters['type'];
|
||||
if ( \in_array( 'IPS\Content\Comment', class_parents( $filters['type'] ) ) )
|
||||
{
|
||||
$itemClass = $filters['type']::$itemClass;
|
||||
}
|
||||
|
||||
if ( isset( $filters['nodes_' . str_replace( '\\', '-', $itemClass )] ) )
|
||||
{
|
||||
$joinContainers = TRUE;
|
||||
$extraJoinCondition = ' AND ' . \IPS\Db::i()->in( $itemClass::$databaseTable . '.' . $itemClass::$databaseColumnMap['container'], $filters['nodes_' . str_replace( '\\', '-', $itemClass )] );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$where[] = [ '(follow_app !=? and follow_area !=?)', 'core', 'member' ];
|
||||
}
|
||||
|
||||
$query = \IPS\Db::i()->select( $select, 'core_follow', $where, $order, $limit );
|
||||
|
||||
if ( $joinContainers )
|
||||
{
|
||||
$query->join( $itemClass::$databaseTable, 'core_follow.follow_rel_id=' . $itemClass::$databaseTable . '.' . $itemClass::$databasePrefix . $itemClass::$databaseColumnId . $extraJoinCondition, 'INNER' );
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Achievement Action 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 03 Mar 2021
|
||||
*/
|
||||
|
||||
namespace IPS\core\extensions\core\AchievementAction;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Achievement Action Extension
|
||||
*/
|
||||
class _FollowMember extends \IPS\core\Achievements\Actions\AbstractAchievementAction
|
||||
{
|
||||
/**
|
||||
* Get filter form elements
|
||||
*
|
||||
* @param array|NULL $filters Current filter values (if editing)
|
||||
* @param \IPS\Http\Url $url The URL the form is being shown on
|
||||
* @return array
|
||||
*/
|
||||
public function filters( ?array $filters, \IPS\Http\Url $url ): array
|
||||
{
|
||||
$nthFilter = new \IPS\Helpers\Form\Number( 'achievement_filter_FollowMember_nth', ( $filters and isset( $filters['milestone'] ) and $filters['milestone'] ) ? $filters['milestone'] : 0, FALSE, [], NULL, \IPS\Member::loggedIn()->language()->addToStack('achievement_filter_nth_their'), \IPS\Member::loggedIn()->language()->addToStack('achievement_filter_FollowMember_nth_suffix') );
|
||||
$nthFilter->label = \IPS\Member::loggedIn()->language()->addToStack('achievement_filter_NewClub_nth');
|
||||
|
||||
$return = array();
|
||||
|
||||
$return['milestone'] = $nthFilter;
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format filter form values
|
||||
*
|
||||
* @param array $values The values from the form
|
||||
* @return array
|
||||
*/
|
||||
public function formatFilterValues( array $values ): array
|
||||
{
|
||||
$return = [];
|
||||
|
||||
if ( isset( $values['achievement_filter_FollowMember_nth'] ) )
|
||||
{
|
||||
$return['milestone'] = $values['achievement_filter_FollowMember_nth'];
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Work out if the filters applies for a given action
|
||||
*
|
||||
* Important note for milestones: consider the context. This method is called by \IPS\Member::achievementAction(). If your code
|
||||
* calls that BEFORE making its change in the database (or there is read/write separation), you will need to add
|
||||
* 1 to the value being considered for milestones
|
||||
*
|
||||
* @param \IPS\Member $subject The subject member
|
||||
* @param array $filters The value returned by formatFilterValues()
|
||||
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
|
||||
* @return bool
|
||||
*/
|
||||
public function filtersMatch( \IPS\Member $subject, array $filters, $extra = NULL ): bool
|
||||
{
|
||||
if ( isset( $filters['milestone'] ) )
|
||||
{
|
||||
$where = [];
|
||||
$where[] = [ 'follow_app=? and follow_area=? and follow_member_id=?', 'core', 'member', $subject->member_id ];
|
||||
|
||||
$count = \IPS\Db::i()->select( 'COUNT(*)', 'core_follow', $where )->first();
|
||||
|
||||
if ( $count < $filters['milestone'] )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the labels for the people this action might give awards to
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function awardOptions(): array
|
||||
{
|
||||
return [
|
||||
'subject' => 'achievement_filter_FollowMember_receiver',
|
||||
'other' => 'achievement_filter_FollowMember_giver'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "other" people we need to award =stuff to
|
||||
*
|
||||
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
|
||||
* @return array
|
||||
*/
|
||||
public function awardOther( $extra = NULL ): array
|
||||
{
|
||||
return [ $extra['giver'] ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get identifier to prevent the member being awarded points for the same action twice
|
||||
* Must be unique within within of this domain, must not exceed 32 chars.
|
||||
*
|
||||
* @param \IPS\Member $subject The subject member
|
||||
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
|
||||
* @return string
|
||||
*/
|
||||
public function identifier( \IPS\Member $subject, $extra = NULL ): string
|
||||
{
|
||||
return $subject->member_id . ':' . $extra['giver']->member_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a description for this action to show in the log
|
||||
*
|
||||
* @param string $identifier The identifier as returned by identifier()
|
||||
* @param array $actor If the member was the "subject", "other", or both
|
||||
* @return string
|
||||
*/
|
||||
public function logRow( string $identifier, array $actor ): string
|
||||
{
|
||||
$exploded = explode( ':', $identifier );
|
||||
|
||||
$reactionName = \IPS\Member::loggedIn()->language()->addToStack('unknown');
|
||||
$receivedLink = \IPS\Member::loggedIn()->language()->addToStack('modcp_deleted');
|
||||
$giverLink = \IPS\Member::loggedIn()->language()->addToStack('modcp_deleted');
|
||||
try
|
||||
{
|
||||
$giver = \IPS\Member::load( $exploded[0] );
|
||||
$received = \IPS\Member::load( $exploded[1] );
|
||||
$receivedLink = \IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->basicUrl( $received->url(), TRUE, $received->name, FALSE );
|
||||
$giverLink = \IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->basicUrl( $giver->url(), TRUE, $giver->name, FALSE );
|
||||
}
|
||||
catch ( \OutOfRangeException $e ) { }
|
||||
|
||||
if ( \in_array( 'subject', $actor ) )
|
||||
{
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__FollowMember_log_subject', FALSE, [ 'htmlsprintf' => [ $receivedLink ] ] );
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__FollowMember_log_other', FALSE, [ 'htmlsprintf' => [ $giverLink ] ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get "description" for rule
|
||||
*
|
||||
* @param \IPS\core\Achievements\Rule $rule The rule
|
||||
* @return string|NULL
|
||||
*/
|
||||
public function ruleDescription( \IPS\core\Achievements\Rule $rule ): ?string
|
||||
{
|
||||
$conditions = [];
|
||||
if ( isset( $rule->filters['milestone'] ) )
|
||||
{
|
||||
$conditions[] = \IPS\Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone', FALSE, [
|
||||
'htmlsprintf' => [
|
||||
\IPS\Theme::i()->getTemplate( 'achievements' )->ruleDescriptionBadge( 'milestone', \IPS\Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone_nth', FALSE, [ 'pluralize' => [ $rule->filters['milestone'] ] ] ) )
|
||||
],
|
||||
'sprintf' => \IPS\Member::loggedIn()->language()->addToStack('AchievementAction__FollowMember_title_generic')
|
||||
] );
|
||||
}
|
||||
|
||||
return \IPS\Theme::i()->getTemplate( 'achievements' )->ruleDescription(
|
||||
\IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__FollowMember_title' ),
|
||||
$conditions
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get rebuild data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
static public function rebuildData()
|
||||
{
|
||||
return [ [
|
||||
'table' => 'core_follow',
|
||||
'pkey' => 'follow_id',
|
||||
'date' => 'follow_added',
|
||||
'where' => [ ['follow_app=? and follow_area=?', 'core', 'member' ] ],
|
||||
] ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the rebuild row
|
||||
*
|
||||
* @param array $row Row from database
|
||||
* @param array $data Data collected when starting rebuild [table, pkey...]
|
||||
* @return void
|
||||
*/
|
||||
public static function rebuildRow( $row, $data )
|
||||
{
|
||||
$receiver = \IPS\Member::load( $row['follow_rel_id'] );
|
||||
$receiver->achievementAction( 'core', 'FollowMember', [
|
||||
'giver' => \IPS\Member::load( $row['follow_member_id'] )
|
||||
] );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Achievement Action 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 04 Mar 2021
|
||||
*/
|
||||
|
||||
namespace IPS\core\extensions\core\AchievementAction;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Achievement Action Extension
|
||||
*/
|
||||
class _FollowNode extends \IPS\core\Achievements\Actions\AbstractNodeAchievementAction
|
||||
{
|
||||
/**
|
||||
* Get filter form elements
|
||||
*
|
||||
* @param array|NULL $filters Current filter values (if editing)
|
||||
* @param \IPS\Http\Url $url The URL the form is being shown on
|
||||
* @return array
|
||||
*/
|
||||
public function filters( ?array $filters, \IPS\Http\Url $url ): array
|
||||
{
|
||||
$filters = parent::filters( $filters, $url );
|
||||
|
||||
foreach( $filters['type']->options['options'] as $class => $value )
|
||||
{
|
||||
if ( isset( $class::$contentItemClass ) and ! \in_array( 'IPS\Content\Followable', class_implements( $class::$contentItemClass ) ) )
|
||||
{
|
||||
unset( $filters['type']->options['options'][ $class ] );
|
||||
unset( $filters['nodes_' . str_replace( '\\', '-', $class ) ] );
|
||||
}
|
||||
}
|
||||
|
||||
return $filters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a description for this action to show in the log
|
||||
*
|
||||
* @param string $identifier The identifier as returned by identifier()
|
||||
* @param array $actor If the member was the "subject", "other", or both
|
||||
* @return string
|
||||
*/
|
||||
public function logRow( string $identifier, array $actor ): string
|
||||
{
|
||||
$exploded = explode( ':', $identifier );
|
||||
|
||||
$sprintf = [];
|
||||
try
|
||||
{
|
||||
$node = $exploded[0]::load( $exploded[1] );
|
||||
$sprintf = [ 'htmlsprintf' => [
|
||||
\IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->basicUrl( $node->url(), TRUE, $node->_title, FALSE )
|
||||
] ];
|
||||
}
|
||||
catch ( \OutOfRangeException $e )
|
||||
{
|
||||
$sprintf = [ 'sprintf' => [ \IPS\Member::loggedIn()->language()->addToStack('modcp_deleted') ] ];
|
||||
}
|
||||
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__FollowNode_log', FALSE, $sprintf );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get "description" for rule
|
||||
*
|
||||
* @param \IPS\core\Achievements\Rule $rule The rule
|
||||
* @return string|NULL
|
||||
*/
|
||||
public function ruleDescription( \IPS\core\Achievements\Rule $rule ): ?string
|
||||
{
|
||||
$type = $rule->filters['type'] ?? NULL;
|
||||
|
||||
$conditions = [];
|
||||
if ( isset( $rule->filters['milestone'] ) )
|
||||
{
|
||||
$conditions[] = \IPS\Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone', FALSE, [
|
||||
'htmlsprintf' => [
|
||||
\IPS\Theme::i()->getTemplate( 'achievements' )->ruleDescriptionBadge( 'milestone', \IPS\Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone_nth', FALSE, [ 'pluralize' => [ $rule->filters['milestone'] ] ] ) )
|
||||
],
|
||||
'sprintf' => [ $type ? \IPS\Member::loggedIn()->language()->addToStack( $type::fullyQualifiedType(), FALSE, [ 'strtolower' => TRUE ] ) : \IPS\Member::loggedIn()->language()->addToStack('AchievementAction__NewContentItem_title_generic') ]
|
||||
] );
|
||||
}
|
||||
if ( $nodeCondition = $this->_nodeFilterDescription( $rule ) )
|
||||
{
|
||||
$conditions[] = $nodeCondition;
|
||||
}
|
||||
|
||||
return \IPS\Theme::i()->getTemplate( 'achievements' )->ruleDescription(
|
||||
$type ? \IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__FollowNode_title_t', FALSE, [ 'sprintf' => [ \IPS\Member::loggedIn()->language()->addToStack( $type::fullyQualifiedType() ) ] ] ) : \IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__FollowNode_title' ),
|
||||
$conditions
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get rebuild data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
static public function rebuildData()
|
||||
{
|
||||
return [ [
|
||||
'table' => 'core_follow',
|
||||
'pkey' => 'follow_id',
|
||||
'date' => 'follow_added',
|
||||
'where' => [ [ '(follow_app !=? and follow_area !=?)', 'core', 'member' ] ],
|
||||
] ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the rebuild row
|
||||
*
|
||||
* @param array $row Row from database
|
||||
* @param array $data Data collected when starting rebuild [table, pkey...]
|
||||
* @return void
|
||||
*/
|
||||
public static function rebuildRow( $row, $data )
|
||||
{
|
||||
$class = 'IPS\\' . $row['follow_app'] . '\\' . mb_ucfirst( $row['follow_area'] );
|
||||
if ( \in_array( 'IPS\Node\Model', class_parents( $class ) ) )
|
||||
{
|
||||
\IPS\Member::load( $row['follow_member_id'] )->achievementAction( 'core', 'FollowNode', $class::load( $row['follow_rel_id'] ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Achievement Action 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 04 Mar 2021
|
||||
*/
|
||||
|
||||
namespace IPS\core\extensions\core\AchievementAction;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Achievement Action Extension
|
||||
*/
|
||||
class _JoinClub extends \IPS\core\Achievements\Actions\AbstractAchievementAction // NOTE: Other classes exist to provided bases for common situations, like where node-based filters will be required
|
||||
{
|
||||
/**
|
||||
* Can use this rule?
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function canUse(): bool
|
||||
{
|
||||
return (bool) parent::canUse() and \IPS\Settings::i()->clubs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get filter form elements
|
||||
*
|
||||
* @param array|NULL $filters Current filter values (if editing)
|
||||
* @param \IPS\Http\Url $url The URL the form is being shown on
|
||||
* @return array
|
||||
*/
|
||||
public function filters( ?array $filters, \IPS\Http\Url $url ): array
|
||||
{
|
||||
$nthFilter = new \IPS\Helpers\Form\Number( 'achievement_filter_club_joined_nth', ( $filters and isset( $filters['milestone'] ) and $filters['milestone'] ) ? $filters['milestone'] : 0, FALSE, [], NULL, \IPS\Member::loggedIn()->language()->addToStack('achievement_filter_nth_their'), \IPS\Member::loggedIn()->language()->addToStack('achievement_filter_JoinClub_nth_suffix') );
|
||||
$nthFilter->label = \IPS\Member::loggedIn()->language()->addToStack('achievement_filter_JoinClub_nth');
|
||||
|
||||
$return = array();
|
||||
|
||||
$return['milestone'] = $nthFilter;
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format filter form values
|
||||
*
|
||||
* @param array $values The values from the form
|
||||
* @return array
|
||||
*/
|
||||
public function formatFilterValues( array $values ): array
|
||||
{
|
||||
$return = [];
|
||||
|
||||
if ( isset( $values['achievement_filter_club_joined_nth'] ) )
|
||||
{
|
||||
$return['milestone'] = $values['achievement_filter_club_joined_nth'];
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Work out if the filters applies for a given action
|
||||
*
|
||||
* Important note for milestones: consider the context. This method is called by \IPS\Member::achievementAction(). If your code
|
||||
* calls that BEFORE making its change in the database (or there is read/write separation), you will need to add
|
||||
* 1 to the value being considered for milestones
|
||||
*
|
||||
* @param \IPS\Member $subject The subject member
|
||||
* @param array $filters The value returned by formatFilterValues()
|
||||
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
|
||||
* @return bool
|
||||
*/
|
||||
public function filtersMatch( \IPS\Member $subject, array $filters, $extra = NULL ): bool
|
||||
{
|
||||
if ( isset( $filters['milestone'] ) )
|
||||
{
|
||||
$where = [];
|
||||
$where[] = [ "member_id=? and status IN('" . \IPS\Member\Club::STATUS_MEMBER . "','" . \IPS\Member\Club::STATUS_MODERATOR . "','" . \IPS\Member\Club::STATUS_LEADER . "','" . \IPS\Member\Club::STATUS_EXPIRED . "','" . \IPS\Member\Club::STATUS_EXPIRED_MODERATOR . "')", $subject->member_id ];
|
||||
|
||||
$count = \IPS\Db::i()->select( 'COUNT(*)', 'core_clubs_memberships', $where )->first();
|
||||
|
||||
if ( $count < $filters['milestone'] )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the labels for the people this action might give awards to
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function awardOptions(): array
|
||||
{
|
||||
return [
|
||||
'subject' => 'achievement_filter_JoinClub_joiner',
|
||||
'other' => 'achievement_filter_JoinClub_owner'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "other" people we need to award =stuff to
|
||||
*
|
||||
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
|
||||
* @return array
|
||||
*/
|
||||
public function awardOther( $extra = NULL ): array
|
||||
{
|
||||
return [ $extra->owner ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get identifier to prevent the member being awarded points for the same action twice
|
||||
* Must be unique within within of this domain, must not exceed 32 chars.
|
||||
*
|
||||
* @param \IPS\Member $subject The subject member
|
||||
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
|
||||
* @return string
|
||||
*/
|
||||
public function identifier( \IPS\Member $subject, $extra = NULL ): string
|
||||
{
|
||||
return (string) $extra->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a description for this action to show in the log
|
||||
*
|
||||
* @param string $identifier The identifier as returned by identifier()
|
||||
* @param array $actor If the member was the "subject", "other", or both
|
||||
* @return string
|
||||
*/
|
||||
public function logRow( string $identifier, array $actor ): string
|
||||
{
|
||||
$club = \IPS\Member\Club::load( $identifier );
|
||||
$clubLink = \IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->basicUrl( $club->url(), TRUE, $club->name, FALSE );
|
||||
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__JoinClub_log', FALSE, [ 'htmlsprintf' => [ $clubLink ] ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get "description" for rule
|
||||
*
|
||||
* @param \IPS\core\Achievements\Rule $rule The rule
|
||||
* @return string|NULL
|
||||
*/
|
||||
public function ruleDescription( \IPS\core\Achievements\Rule $rule ): ?string
|
||||
{
|
||||
$conditions = [];
|
||||
if ( isset( $rule->filters['milestone'] ) )
|
||||
{
|
||||
$conditions[] = \IPS\Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone', FALSE, [
|
||||
'htmlsprintf' => [
|
||||
\IPS\Theme::i()->getTemplate( 'achievements' )->ruleDescriptionBadge( 'milestone', \IPS\Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone_nth', FALSE, [ 'pluralize' => [ $rule->filters['milestone'] ] ] ) )
|
||||
],
|
||||
'sprintf' => [ \IPS\Member::loggedIn()->language()->addToStack('AchievementAction__JoinClub_title_generic') ]
|
||||
] );
|
||||
}
|
||||
|
||||
return \IPS\Theme::i()->getTemplate( 'achievements' )->ruleDescription(
|
||||
\IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__JoinClub_title' ),
|
||||
$conditions
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get rebuild data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
static public function rebuildData()
|
||||
{
|
||||
return [ [
|
||||
'table' => 'core_clubs_memberships',
|
||||
'pkey' => 'club_id',
|
||||
'date' => 'joined',
|
||||
'where' => [],
|
||||
] ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the rebuild row
|
||||
*
|
||||
* @param array $row Row from database
|
||||
* @param array $data Data collected when starting rebuild [table, pkey...]
|
||||
* @return void
|
||||
*/
|
||||
public static function rebuildRow( $row, $data )
|
||||
{
|
||||
if ( \in_array( $row['status'], array( \IPS\Member\Club::STATUS_MEMBER, \IPS\Member\Club::STATUS_MODERATOR, \IPS\Member\Club::STATUS_LEADER, \IPS\Member\Club::STATUS_EXPIRED, \IPS\Member\Club::STATUS_EXPIRED_MODERATOR ) ) )
|
||||
{
|
||||
\IPS\Member::load( $row['member_id'] )->achievementAction( 'core', 'JoinClub', \IPS\Member\Club::load( $row['club_id'] ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Achievement Action 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 03 Mar 2021
|
||||
*/
|
||||
|
||||
namespace IPS\core\extensions\core\AchievementAction;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Achievement Action Extension
|
||||
*/
|
||||
class _NewClub extends \IPS\core\Achievements\Actions\AbstractAchievementAction // NOTE: Other classes exist to provided bases for common situations, like where node-based filters will be required
|
||||
{
|
||||
/**
|
||||
* Can use this rule?
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function canUse(): bool
|
||||
{
|
||||
return (bool) parent::canUse() and \IPS\Settings::i()->clubs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get filter form elements
|
||||
*
|
||||
* @param array|NULL $filters Current filter values (if editing)
|
||||
* @param \IPS\Http\Url $url The URL the form is being shown on
|
||||
* @return array
|
||||
*/
|
||||
public function filters( ?array $filters, \IPS\Http\Url $url ): array
|
||||
{
|
||||
$nthFilter = new \IPS\Helpers\Form\Number( 'achievement_filter_club_nth', ( $filters and isset( $filters['milestone'] ) and $filters['milestone'] ) ? $filters['milestone'] : 0, FALSE, [], NULL, \IPS\Member::loggedIn()->language()->addToStack('achievement_filter_nth_their'), \IPS\Member::loggedIn()->language()->addToStack('achievement_filter_NewClub_nth_suffix') );
|
||||
$nthFilter->label = \IPS\Member::loggedIn()->language()->addToStack('achievement_filter_NewClub_nth');
|
||||
|
||||
$return = array();
|
||||
|
||||
$return['milestone'] = $nthFilter;
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format filter form values
|
||||
*
|
||||
* @param array $values The values from the form
|
||||
* @return array
|
||||
*/
|
||||
public function formatFilterValues( array $values ): array
|
||||
{
|
||||
$return = [];
|
||||
|
||||
if ( isset( $values['achievement_filter_club_nth'] ) )
|
||||
{
|
||||
$return['milestone'] = $values['achievement_filter_club_nth'];
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Work out if the filters applies for a given action
|
||||
*
|
||||
* Important note for milestones: consider the context. This method is called by \IPS\Member::achievementAction(). If your code
|
||||
* calls that BEFORE making its change in the database (or there is read/write separation), you will need to add
|
||||
* 1 to the value being considered for milestones
|
||||
*
|
||||
* @param \IPS\Member $subject The subject member
|
||||
* @param array $filters The value returned by formatFilterValues()
|
||||
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
|
||||
* @return bool
|
||||
*/
|
||||
public function filtersMatch( \IPS\Member $subject, array $filters, $extra = NULL ): bool
|
||||
{
|
||||
if ( isset( $filters['milestone'] ) )
|
||||
{
|
||||
$where = [];
|
||||
$where[] = [ 'owner=? and approved=?', $subject->member_id, 1 ];
|
||||
|
||||
$count = \IPS\Db::i()->select( 'COUNT(*)', 'core_clubs', $where )->first();
|
||||
|
||||
if ( $count < $filters['milestone'] )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get identifier to prevent the member being awarded points for the same action twice
|
||||
* Must be unique within within of this domain, must not exceed 32 chars.
|
||||
*
|
||||
* @param \IPS\Member $subject The subject member
|
||||
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
|
||||
* @return string
|
||||
*/
|
||||
public function identifier( \IPS\Member $subject, $extra = NULL ): string
|
||||
{
|
||||
return (string) $extra->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a description for this action to show in the log
|
||||
*
|
||||
* @param string $identifier The identifier as returned by identifier()
|
||||
* @param array $actor If the member was the "subject", "other", or both
|
||||
* @return string
|
||||
*/
|
||||
public function logRow( string $identifier, array $actor ): string
|
||||
{
|
||||
$club = \IPS\Member\Club::load( $identifier );
|
||||
$clubLink = \IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->basicUrl( $club->url(), TRUE, $club->name, FALSE );
|
||||
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__NewClub_log', FALSE, [ 'htmlsprintf' => [ $clubLink ] ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get "description" for rule
|
||||
*
|
||||
* @param \IPS\core\Achievements\Rule $rule The rule
|
||||
* @return string|NULL
|
||||
*/
|
||||
public function ruleDescription( \IPS\core\Achievements\Rule $rule ): ?string
|
||||
{
|
||||
$conditions = [];
|
||||
if ( isset( $rule->filters['milestone'] ) )
|
||||
{
|
||||
$conditions[] = \IPS\Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone', FALSE, [
|
||||
'htmlsprintf' => [
|
||||
\IPS\Theme::i()->getTemplate( 'achievements' )->ruleDescriptionBadge( 'milestone', \IPS\Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone_nth', FALSE, [ 'pluralize' => [ $rule->filters['milestone'] ] ] ) )
|
||||
],
|
||||
'sprintf' => [ \IPS\Member::loggedIn()->language()->addToStack('AchievementAction__NewClub_title_generic') ]
|
||||
] );
|
||||
}
|
||||
|
||||
return \IPS\Theme::i()->getTemplate( 'achievements' )->ruleDescription(
|
||||
\IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__NewClub_title' ),
|
||||
$conditions
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get rebuild data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
static public function rebuildData()
|
||||
{
|
||||
return [ [
|
||||
'table' => 'core_clubs',
|
||||
'pkey' => 'id',
|
||||
'date' => 'created',
|
||||
'where' => [ [ 'approved=1' ] ],
|
||||
] ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the rebuild row
|
||||
*
|
||||
* @param array $row Row from database
|
||||
* @param array $data Data collected when starting rebuild [table, pkey...]
|
||||
* @return void
|
||||
*/
|
||||
public static function rebuildRow( $row, $data )
|
||||
{
|
||||
\IPS\Member::load( $row['owner'] )->achievementAction( 'core', 'NewClub', \IPS\Member\Club::load( $row['id'] ) );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Achievement Action Extension: User posts new content item
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @since 24 Feb 2021
|
||||
*/
|
||||
|
||||
namespace IPS\core\extensions\core\AchievementAction;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Achievement Action Extension: User posts new content item
|
||||
*/
|
||||
class _NewContentItem extends \IPS\core\Achievements\Actions\AbstractContentAchievementAction
|
||||
{
|
||||
protected static $includeComments = FALSE;
|
||||
protected static $includeReviews = FALSE;
|
||||
|
||||
/**
|
||||
* Work out if the filters applies for a given action
|
||||
*
|
||||
* Important note for milestones: consider the context. This method is called by \IPS\Member::achievementAction(). If your code
|
||||
* calls that BEFORE making its change in the database (or there is read/write separation), you will need to add
|
||||
* 1 to the value being considered for milestones
|
||||
*
|
||||
* @param \IPS\Member $subject The subject member
|
||||
* @param array $filters The value returned by formatFilterValues()
|
||||
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
|
||||
* @return bool
|
||||
*/
|
||||
public function filtersMatch( \IPS\Member $subject, array $filters, $extra = NULL ): bool
|
||||
{
|
||||
if ( !parent::filtersMatch( $subject, $filters, $extra ) )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if ( isset( $filters['milestone'] ) )
|
||||
{
|
||||
$count = 0;
|
||||
$classes = [];
|
||||
if ( isset( $filters['type'] ) )
|
||||
{
|
||||
if ( isset( $filters[ 'nodes_' . str_replace( '\\', '-', $filters['type'] ) ] ) )
|
||||
{
|
||||
$class = $filters['type'];
|
||||
$where = [];
|
||||
$where[] = [ $class::$databasePrefix . $class::$databaseColumnMap['author'] . '=?', $subject->member_id ];
|
||||
$where[] = [ \IPS\Db::i()->in( $class::$databasePrefix . $class::$databaseColumnMap['container'], $filters[ 'nodes_' . str_replace( '\\', '-', $filters['type'] ) ] ) ];
|
||||
|
||||
$count += \IPS\Db::i()->select( 'COUNT(*)', $class::$databaseTable, $where )->first();
|
||||
}
|
||||
else
|
||||
{
|
||||
$classes[] = $filters['type'];
|
||||
}
|
||||
|
||||
foreach ( $classes as $class )
|
||||
{
|
||||
$count += $class::memberPostCount( $subject, TRUE, FALSE );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* It's too expensive to get a live count from every single item/comment class available to the suite
|
||||
so lets just use the post count here. It won't be quite as accurate but we're not a bank so whatever */
|
||||
$count = $subject->member_posts;
|
||||
}
|
||||
|
||||
if ( $count < $filters['milestone'] )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a description for this action to show in the log
|
||||
*
|
||||
* @param string $identifier The identifier as returned by identifier()
|
||||
* @param array $actor If the member was the "subject", "other", or both
|
||||
* @return string
|
||||
*/
|
||||
public function logRow( string $identifier, array $actor ): string
|
||||
{
|
||||
$exploded = explode( ':', $identifier );
|
||||
|
||||
$sprintf = [];
|
||||
try
|
||||
{
|
||||
$item = $exploded[0]::load( $exploded[1] );
|
||||
$sprintf = [ 'htmlsprintf' => [
|
||||
\IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->basicUrl( $item->url(), TRUE, $item->mapped('title') ?: $item->indefiniteArticle(), FALSE )
|
||||
] ];
|
||||
}
|
||||
catch ( \OutOfRangeException $e )
|
||||
{
|
||||
$sprintf = [ 'sprintf' => [ \IPS\Member::loggedIn()->language()->addToStack('modcp_deleted') ] ];
|
||||
}
|
||||
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__NewContentItem_log', FALSE, $sprintf );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get "description" for rule
|
||||
*
|
||||
* @param \IPS\core\Achievements\Rule $rule The rule
|
||||
* @return string|NULL
|
||||
*/
|
||||
public function ruleDescription( \IPS\core\Achievements\Rule $rule ): ?string
|
||||
{
|
||||
$type = $rule->filters['type'] ?? NULL;
|
||||
|
||||
$conditions = [];
|
||||
if ( isset( $rule->filters['milestone'] ) )
|
||||
{
|
||||
$conditions[] = \IPS\Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone', FALSE, [
|
||||
'htmlsprintf' => [
|
||||
\IPS\Theme::i()->getTemplate( 'achievements' )->ruleDescriptionBadge( 'milestone', \IPS\Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone_nth', FALSE, [ 'pluralize' => [ $rule->filters['milestone'] ] ] ) )
|
||||
],
|
||||
'sprintf' => [ $type ? \IPS\Member::loggedIn()->language()->addToStack( $type::$title ) : \IPS\Member::loggedIn()->language()->addToStack('AchievementAction__NewContentItem_title_generic') ]
|
||||
] );
|
||||
}
|
||||
if ( $nodeCondition = $this->_nodeFilterDescription( $rule ) )
|
||||
{
|
||||
$conditions[] = $nodeCondition;
|
||||
}
|
||||
|
||||
return \IPS\Theme::i()->getTemplate( 'achievements' )->ruleDescription(
|
||||
$type ? \IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__NewContentItem_title_t', FALSE, [ 'sprintf' => [ \IPS\Member::loggedIn()->language()->addToStack( $type::$title ) ] ] ) : \IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__NewContentItem_title' ),
|
||||
$conditions
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Achievement Action 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 05 Mar 2021
|
||||
*/
|
||||
|
||||
namespace IPS\core\extensions\core\AchievementAction;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Achievement Action Extension
|
||||
*/
|
||||
class _NewPoll extends \IPS\core\Achievements\Actions\AbstractAchievementAction // NOTE: Other classes exist to provided bases for common situations, like where node-based filters will be required
|
||||
{
|
||||
/**
|
||||
* Get filter form elements
|
||||
*
|
||||
* @param array|NULL $filters Current filter values (if editing)
|
||||
* @param \IPS\Http\Url $url The URL the form is being shown on
|
||||
* @return array
|
||||
*/
|
||||
public function filters( ?array $filters, \IPS\Http\Url $url ): array
|
||||
{
|
||||
$nthFilter = new \IPS\Helpers\Form\Number( 'achievement_filter_poll_nth', ( $filters and isset( $filters['milestone'] ) and $filters['milestone'] ) ? $filters['milestone'] : 0, FALSE, [], NULL, \IPS\Member::loggedIn()->language()->addToStack('achievement_filter_nth_their'), \IPS\Member::loggedIn()->language()->addToStack('achievement_filter_NewPoll_nth_suffix') );
|
||||
$nthFilter->label = \IPS\Member::loggedIn()->language()->addToStack('achievement_filter_NewPoll_nth');
|
||||
|
||||
$return = array();
|
||||
|
||||
$return['milestone'] = $nthFilter;
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format filter form values
|
||||
*
|
||||
* @param array $values The values from the form
|
||||
* @return array
|
||||
*/
|
||||
public function formatFilterValues( array $values ): array
|
||||
{
|
||||
$return = [];
|
||||
|
||||
if ( isset( $values['achievement_filter_poll_nth'] ) )
|
||||
{
|
||||
$return['milestone'] = $values['achievement_filter_poll_nth'];
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Work out if the filters applies for a given action
|
||||
*
|
||||
* Important note for milestones: consider the context. This method is called by \IPS\Member::achievementAction(). If your code
|
||||
* calls that BEFORE making its change in the database (or there is read/write separation), you will need to add
|
||||
* 1 to the value being considered for milestones
|
||||
*
|
||||
* @param \IPS\Member $subject The subject member
|
||||
* @param array $filters The value returned by formatFilterValues()
|
||||
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
|
||||
* @return bool
|
||||
*/
|
||||
public function filtersMatch( \IPS\Member $subject, array $filters, $extra = NULL ): bool
|
||||
{
|
||||
if ( isset( $filters['milestone'] ) )
|
||||
{
|
||||
$where = [];
|
||||
$where[] = [ 'starter_id=?', $subject->member_id ];
|
||||
|
||||
$count = \IPS\Db::i()->select( 'COUNT(*)', 'core_polls', $where )->first();
|
||||
|
||||
if ( $count < $filters['milestone'] )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get identifier to prevent the member being awarded points for the same action twice
|
||||
* Must be unique within within of this domain, must not exceed 32 chars.
|
||||
*
|
||||
* @param \IPS\Member $subject The subject member
|
||||
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
|
||||
* @return string
|
||||
*/
|
||||
public function identifier( \IPS\Member $subject, $extra = NULL ): string
|
||||
{
|
||||
return (string) $extra->pid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a description for this action to show in the log
|
||||
*
|
||||
* @param string $identifier The identifier as returned by identifier()
|
||||
* @param array $actor If the member was the "subject", "other", or both
|
||||
* @return string
|
||||
*/
|
||||
public function logRow( string $identifier, array $actor ): string
|
||||
{
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__NewPoll_log', FALSE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get "description" for rule
|
||||
*
|
||||
* @param \IPS\core\Achievements\Rule $rule The rule
|
||||
* @return string|NULL
|
||||
*/
|
||||
public function ruleDescription( \IPS\core\Achievements\Rule $rule ): ?string
|
||||
{
|
||||
$conditions = [];
|
||||
if ( isset( $rule->filters['milestone'] ) )
|
||||
{
|
||||
$conditions[] = \IPS\Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone', FALSE, [
|
||||
'htmlsprintf' => [
|
||||
\IPS\Theme::i()->getTemplate( 'achievements' )->ruleDescriptionBadge( 'milestone', \IPS\Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone_nth', FALSE, [ 'pluralize' => [ $rule->filters['milestone'] ] ] ) )
|
||||
],
|
||||
'sprintf' => [ \IPS\Member::loggedIn()->language()->addToStack('AchievementAction__NewPoll_title_generic') ]
|
||||
] );
|
||||
}
|
||||
|
||||
return \IPS\Theme::i()->getTemplate( 'achievements' )->ruleDescription(
|
||||
\IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__NewPoll_title' ),
|
||||
$conditions
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get rebuild data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
static public function rebuildData()
|
||||
{
|
||||
return [ [
|
||||
'table' => 'core_polls',
|
||||
'pkey' => 'pid',
|
||||
'date' => 'start_date',
|
||||
'where' => [],
|
||||
] ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the rebuild row
|
||||
*
|
||||
* @param array $row Row from database
|
||||
* @param array $data Data collected when starting rebuild [table, pkey...]
|
||||
* @return void
|
||||
*/
|
||||
public static function rebuildRow( $row, $data )
|
||||
{
|
||||
\IPS\Member::load( $row['starter_id'] )->achievementAction( 'core', 'NewPoll', \IPS\Poll::load( $row['pid'] ) );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,324 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Achievement Action 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 01 Mar 2021
|
||||
*/
|
||||
|
||||
namespace IPS\core\extensions\core\AchievementAction;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Achievement Action Extension
|
||||
*/
|
||||
class _Reaction extends \IPS\core\Achievements\Actions\AbstractContentAchievementAction
|
||||
{
|
||||
/**
|
||||
* Get filter form elements
|
||||
*
|
||||
* @param array|NULL $filters Current filter values (if editing)
|
||||
* @param \IPS\Http\Url $url The URL the form is being shown on
|
||||
* @return array
|
||||
*/
|
||||
public function filters( ?array $filters, \IPS\Http\Url $url ): array
|
||||
{
|
||||
$return = parent::filters( $filters, $url );
|
||||
|
||||
$reactionFilter = new \IPS\Helpers\Form\Node( 'achievement_filter_Reaction_reaction', ( $filters and isset( $filters['reactions'] ) and $filters['reactions'] ) ? $filters['reactions'] : 0, FALSE, [
|
||||
'url' => $url,
|
||||
'class' => 'IPS\Content\Reaction',
|
||||
'showAllNodes' => TRUE,
|
||||
'multiple' => TRUE,
|
||||
], NULL, \IPS\Member::loggedIn()->language()->addToStack( 'achievement_subfilter_Reaction_reaction_prefix' ) );
|
||||
$return['reactions'] = $reactionFilter;
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format filter form values
|
||||
*
|
||||
* @param array $values The values from the form
|
||||
* @return array
|
||||
*/
|
||||
public function formatFilterValues( array $values ): array
|
||||
{
|
||||
$return = parent::formatFilterValues( $values );
|
||||
if ( isset( $values['achievement_filter_Reaction_reaction'] ) )
|
||||
{
|
||||
$return['reactions'] = array_keys( $values['achievement_filter_Reaction_reaction'] );
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Work out if the filters applies for a given action
|
||||
*
|
||||
* Important note for milestones: consider the context. This method is called by \IPS\Member::achievementAction(). If your code
|
||||
* calls that BEFORE making its change in the database (or there is read/write separation), you will need to add
|
||||
* 1 to the value being considered for milestones
|
||||
*
|
||||
* @param \IPS\Member $subject The subject member
|
||||
* @param array $filters The value returned by formatFilterValues()
|
||||
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
|
||||
* @return bool
|
||||
*/
|
||||
public function filtersMatch( \IPS\Member $subject, array $filters, $extra = NULL ): bool
|
||||
{
|
||||
if ( !parent::filtersMatch( $subject, $filters, $extra['content'] ) )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if ( isset( $filters['reactions'] ) )
|
||||
{
|
||||
if ( !\in_array( $extra['reaction']->id, $filters['reactions'] ) )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $filters['milestone'] ) )
|
||||
{
|
||||
$query = $this->getQuery( 'COUNT(*)', [ [ 'member_id=?', $subject->member_id ] ], NULL, $filters );
|
||||
|
||||
if ( ( $query->first() + 1 ) < $filters['milestone'] )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get identifier to prevent the member being awarded points for the same action twice
|
||||
* Must be unique within within of this domain, must not exceed 32 chars.
|
||||
*
|
||||
* @param \IPS\Member $subject The subject member
|
||||
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
|
||||
* @return string
|
||||
*/
|
||||
public function identifier( \IPS\Member $subject, $extra = NULL ): string
|
||||
{
|
||||
return \get_class( $extra['content'] ) . ':' . $extra['content']->{$extra['content']::$databaseColumnId} . ':' . $extra['giver']->member_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the labels for the people this action might give awards to
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function awardOptions(): array
|
||||
{
|
||||
return [
|
||||
'subject' => 'achievement_filter_Reaction_receiver',
|
||||
'other' => 'achievement_filter_Reaction_giver'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "other" people we need to award =stuff to
|
||||
*
|
||||
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
|
||||
* @return array
|
||||
*/
|
||||
public function awardOther( $extra = NULL ): array
|
||||
{
|
||||
return [ $extra['giver'] ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a description for this action to show in the log
|
||||
*
|
||||
* @param string $identifier The identifier as returned by identifier()
|
||||
* @param array $actor If the member was the "subject", "other", or both
|
||||
* @return string
|
||||
*/
|
||||
public function logRow( string $identifier, array $actor ): string
|
||||
{
|
||||
$exploded = explode( ':', $identifier );
|
||||
|
||||
$reactionName = \IPS\Member::loggedIn()->language()->addToStack('unknown');
|
||||
$contentLink = \IPS\Member::loggedIn()->language()->addToStack('modcp_deleted');
|
||||
try
|
||||
{
|
||||
$class = $exploded[0];
|
||||
$content = $class::load( $exploded[1] );
|
||||
$contentLink = \IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->basicUrl( $content->url(), TRUE, $content->indefiniteArticle(), FALSE );
|
||||
|
||||
$reaction = $content->reacted( \IPS\Member::load( $exploded[2] ) );
|
||||
$reactionName = $reaction->_title;
|
||||
}
|
||||
catch ( \Exception $e ) { }
|
||||
|
||||
if ( \in_array( 'subject', $actor ) )
|
||||
{
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__Reaction_log_subject', FALSE, [ 'sprintf' => [ $reactionName ], 'htmlsprintf' => [ $contentLink ] ] );
|
||||
}
|
||||
else
|
||||
{
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__Reaction_log_other', FALSE, [ 'sprintf' => [ $reactionName ], 'htmlsprintf' => [ $contentLink ] ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get "description" for rule
|
||||
*
|
||||
* @param \IPS\core\Achievements\Rule $rule The rule
|
||||
* @return string|NULL
|
||||
*/
|
||||
public function ruleDescription( \IPS\core\Achievements\Rule $rule ): ?string
|
||||
{
|
||||
$conditions = [];
|
||||
if ( isset( $rule->filters['milestone'] ) )
|
||||
{
|
||||
$conditions[] = \IPS\Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone', FALSE, [
|
||||
'htmlsprintf' => [
|
||||
\IPS\Theme::i()->getTemplate( 'achievements' )->ruleDescriptionBadge( 'milestone', \IPS\Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone_nth', FALSE, [ 'pluralize' => [ $rule->filters['milestone'] ] ] ) )
|
||||
],
|
||||
'sprintf' => \IPS\Member::loggedIn()->language()->addToStack('AchievementAction__Reaction_title_generic')
|
||||
] );
|
||||
}
|
||||
if ( isset( $rule->filters['reactions'] ) )
|
||||
{
|
||||
$reactionNames = [];
|
||||
foreach ( $rule->filters['reactions'] as $id )
|
||||
{
|
||||
try
|
||||
{
|
||||
$reactionNames[] = \IPS\Content\Reaction::load( $id )->_title;
|
||||
}
|
||||
catch ( \OutOfRangeException $e ) {}
|
||||
}
|
||||
if ( $reactionNames )
|
||||
{
|
||||
$conditions[] = \IPS\Member::loggedIn()->language()->addToStack( 'achievements_title_filter_type', FALSE, [
|
||||
'htmlsprintf' => [
|
||||
\IPS\Theme::i()->getTemplate( 'achievements' )->ruleDescriptionBadge( 'other',
|
||||
\count( $reactionNames ) === 1 ? $reactionNames[0] : \IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__Reaction_types', FALSE, [ 'sprintf' => [
|
||||
\count( $reactionNames ),
|
||||
] ] ),
|
||||
\count( $reactionNames ) === 1 ? NULL : $reactionNames
|
||||
)
|
||||
],
|
||||
] );
|
||||
}
|
||||
}
|
||||
if ( $nodeCondition = $this->_nodeFilterDescription( $rule ) )
|
||||
{
|
||||
$conditions[] = $nodeCondition;
|
||||
}
|
||||
|
||||
return \IPS\Theme::i()->getTemplate( 'achievements' )->ruleDescription(
|
||||
\IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__Reaction_title' ),
|
||||
$conditions
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get rebuild data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
static public function rebuildData()
|
||||
{
|
||||
return [ [
|
||||
'table' => 'core_reputation_index',
|
||||
'pkey' => 'id',
|
||||
'date' => 'rep_date',
|
||||
'where' => [],
|
||||
] ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the rebuild row
|
||||
*
|
||||
* @param array $row Row from database
|
||||
* @param array $data Data collected when starting rebuild [table, pkey...]
|
||||
* @return void
|
||||
*/
|
||||
public static function rebuildRow( $row, $data )
|
||||
{
|
||||
if ( !$row['rep_class'] OR !\class_exists( $row['rep_class'] ) )
|
||||
{
|
||||
/* Class either isn't set or doesn't exist, so move on. */
|
||||
return;
|
||||
}
|
||||
|
||||
$object = $row['rep_class']::load( $row['item_id'] );
|
||||
$reaction = \IPS\Content\Reaction::load( $row['reaction'] );
|
||||
|
||||
/* Figure out the owner of this - if it is content, it will be the author. If it is a node, then it will be the person who created it */
|
||||
if ( $object instanceof \IPS\Content )
|
||||
{
|
||||
$owner = $object->author();
|
||||
}
|
||||
else if ( $object instanceof \IPS\Node\Model )
|
||||
{
|
||||
$owner = $object->owner();
|
||||
}
|
||||
|
||||
/* Give points */
|
||||
$owner->achievementAction( 'core', 'Reaction', [
|
||||
'giver' => \IPS\Member::load( $row['member_id'] ),
|
||||
'content' => $object,
|
||||
'reaction' => $reaction
|
||||
] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a query to use for multiple methods within this extension
|
||||
* @param string $select Select for the query
|
||||
* @param array|NULL $where Where for the query
|
||||
* @param int|NULL $limit Limit for the query
|
||||
* @param array $filters Rule filters
|
||||
* @return \IPS\Db\Select
|
||||
*/
|
||||
public function getQuery( $select, $where, $limit, $filters ): \IPS\Db\Select
|
||||
{
|
||||
$joinContainers = FALSE;
|
||||
$extraJoinCondition = NULL;
|
||||
$where = \is_array( $where ) ? $where : array();
|
||||
|
||||
/* Limit by type and node */
|
||||
if ( isset( $filters['type'] ) )
|
||||
{
|
||||
$where[] = ['rep_class=?', $filters['type']];
|
||||
$itemClass = $filters['type'];
|
||||
if ( \in_array( 'IPS\Content\Comment', class_parents( $filters['type'] ) ) )
|
||||
{
|
||||
$itemClass = $filters['type']::$itemClass;
|
||||
}
|
||||
|
||||
if ( isset( $filters['nodes_' . str_replace( '\\', '-', $itemClass )] ) )
|
||||
{
|
||||
$joinContainers = TRUE;
|
||||
$extraJoinCondition = ' AND ' . \IPS\Db::i()->in( $itemClass::$databaseTable . '.' . $itemClass::$databaseColumnMap['container'], $filters['nodes_' . str_replace( '\\', '-', $itemClass )] );
|
||||
}
|
||||
}
|
||||
|
||||
/* Limit by reaction type */
|
||||
if ( isset( $filters['reactions'] ) )
|
||||
{
|
||||
$where[] = [ \IPS\Db::i()->in( 'reaction', $filters['reactions'] ) ];
|
||||
}
|
||||
|
||||
$query = \IPS\Db::i()->select( $select, 'core_reputation_index', $where, NULL, $limit );
|
||||
|
||||
if ( $joinContainers )
|
||||
{
|
||||
$query->join( $itemClass::$databaseTable, 'core_reputation_index.item_id=' . $itemClass::$databaseTable . '.' . $itemClass::$databasePrefix . $itemClass::$databaseColumnId . $extraJoinCondition, 'INNER' );
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Achievement Action 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 03 Mar 2021
|
||||
*/
|
||||
|
||||
namespace IPS\core\extensions\core\AchievementAction;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Achievement Action Extension
|
||||
*/
|
||||
class _Review extends \IPS\core\Achievements\Actions\AbstractContentAchievementAction
|
||||
{
|
||||
protected static $includeItems = FALSE;
|
||||
protected static $includeComments = FALSE;
|
||||
|
||||
/**
|
||||
* Work out if the filters applies for a given action
|
||||
*
|
||||
* Important note for milestones: consider the context. This method is called by \IPS\Member::achievementAction(). If your code
|
||||
* calls that BEFORE making its change in the database (or there is read/write separation), you will need to add
|
||||
* 1 to the value being considered for milestones
|
||||
*
|
||||
* @param \IPS\Member $subject The subject member
|
||||
* @param array $filters The value returned by formatFilterValues()
|
||||
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
|
||||
* @return bool
|
||||
*/
|
||||
public function filtersMatch( \IPS\Member $subject, array $filters, $extra = NULL ): bool
|
||||
{
|
||||
if ( !parent::filtersMatch( $subject, $filters, $extra ) )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if ( isset( $filters['milestone'] ) )
|
||||
{
|
||||
$count = 0;
|
||||
$classes = [];
|
||||
if ( isset( $filters['type'] ) )
|
||||
{
|
||||
if ( isset( $filters[ 'nodes_' . str_replace( '\\', '-', $filters['type'] ) ] ) )
|
||||
{
|
||||
$class = $filters['type'];
|
||||
$where = [];
|
||||
$where[] = [ $class::$databasePrefix . $class::$databaseColumnMap['author'] . '=?', $subject->member_id ];
|
||||
$where[] = [ \IPS\Db::i()->in( $class::$databasePrefix . $class::$databaseColumnMap['container'] ), $filters[ 'nodes_' . str_replace( '\\', '-', $filters['type'] ) ] ];
|
||||
|
||||
$count += \IPS\Db::i()->select( 'COUNT(*)', $class::$databaseTable, $where )->first();
|
||||
}
|
||||
else
|
||||
{
|
||||
$classes[] = $filters['type'];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach ( \IPS\Application::allExtensions( 'core', 'ContentRouter' ) as $contentRouter )
|
||||
{
|
||||
foreach ( $contentRouter->classes as $class )
|
||||
{
|
||||
$exploded = explode( '\\', $class );
|
||||
if ( \in_array( 'IPS\Content\Item', class_parents( $class ) ) and isset( $class::$reviewClass ) )
|
||||
{
|
||||
$classes[] = $class::$reviewClass;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( $classes as $class )
|
||||
{
|
||||
$count += $class::memberPostCount( $subject, TRUE, FALSE );
|
||||
}
|
||||
|
||||
if ( $count < $filters['milestone'] )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the labels for the people this action might give awards to
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function awardOptions(): array
|
||||
{
|
||||
return [
|
||||
'subject' => 'achievement_filter_Review_author',
|
||||
'other' => 'achievement_filter_Review_item_author'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "other" people we need to award =stuff to
|
||||
*
|
||||
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
|
||||
* @return array
|
||||
*/
|
||||
public function awardOther( $extra = NULL ): array
|
||||
{
|
||||
return [ $extra->item()->author() ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a description for this action to show in the log
|
||||
*
|
||||
* @param string $identifier The identifier as returned by identifier()
|
||||
* @param array $actor If the member was the "subject", "other", or both
|
||||
* @return string
|
||||
*/
|
||||
public function logRow( string $identifier, array $actor ): string
|
||||
{
|
||||
$exploded = explode( ':', $identifier );
|
||||
|
||||
$sprintf = [];
|
||||
try
|
||||
{
|
||||
$comment = $exploded[0]::load( $exploded[1] );
|
||||
$item = $comment->item();
|
||||
$sprintf = [ 'htmlsprintf' => [
|
||||
\IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->basicUrl( $comment->url(), TRUE, $item->mapped('title') ?: $item->indefiniteArticle(), FALSE )
|
||||
] ];
|
||||
}
|
||||
catch ( \OutOfRangeException $e )
|
||||
{
|
||||
$sprintf = [ 'sprintf' => [ \IPS\Member::loggedIn()->language()->addToStack('modcp_deleted') ] ];
|
||||
}
|
||||
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__Review_log', FALSE, $sprintf );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get "description" for rule
|
||||
*
|
||||
* @param \IPS\core\Achievements\Rule $rule The rule
|
||||
* @return string|NULL
|
||||
*/
|
||||
public function ruleDescription( \IPS\core\Achievements\Rule $rule ): ?string
|
||||
{
|
||||
$type = $rule->filters['type'] ?? NULL;
|
||||
|
||||
$conditions = [];
|
||||
if ( isset( $rule->filters['milestone'] ) )
|
||||
{
|
||||
$conditions[] = \IPS\Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone', FALSE, [
|
||||
'htmlsprintf' => [
|
||||
\IPS\Theme::i()->getTemplate( 'achievements' )->ruleDescriptionBadge( 'milestone', \IPS\Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone_nth', FALSE, [ 'pluralize' => [ $rule->filters['milestone'] ] ] ) )
|
||||
],
|
||||
'sprintf' => [ $type ? \IPS\Member::loggedIn()->language()->addToStack( $type::$title, FALSE, [ 'strtolower' => TRUE ] ) : \IPS\Member::loggedIn()->language()->addToStack('AchievementAction__Review_title_generic') ]
|
||||
] );
|
||||
}
|
||||
if ( $nodeCondition = $this->_nodeFilterDescription( $rule ) )
|
||||
{
|
||||
$conditions[] = $nodeCondition;
|
||||
}
|
||||
|
||||
return \IPS\Theme::i()->getTemplate( 'achievements' )->ruleDescription(
|
||||
$type ? \IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__NewContentItem_title_t', FALSE, [ 'sprintf' => [ \IPS\Member::loggedIn()->language()->addToStack( $type::$title ) ] ] ) : \IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__Review_title' ),
|
||||
$conditions
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Achievement Action Extension: Member visits the community, and has not previously done so that day
|
||||
* @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 24 Feb 2021
|
||||
*/
|
||||
|
||||
namespace IPS\core\extensions\core\AchievementAction;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Achievement Action Extension: Member visits the community, and has not previously done so that day
|
||||
*/
|
||||
class _SessionStartDaily extends \IPS\core\Achievements\Actions\AbstractAchievementAction
|
||||
{
|
||||
/**
|
||||
* Get filter form elements
|
||||
*
|
||||
* @param array|NULL $filters Current filter values (if editing)
|
||||
* @param \IPS\Http\Url $url The URL the form is being shown on
|
||||
* @return array
|
||||
*/
|
||||
public function filters( ?array $filters, \IPS\Http\Url $url ): array
|
||||
{
|
||||
$return['milestone_registered'] = new \IPS\Helpers\Form\Interval( 'achievement_filter_SessionStartDaily', ( $filters and isset( $filters['milestone_registered'] ) and $filters['milestone_registered'] ) ? $filters['milestone_registered'] : 0, FALSE, [ 'valueAs' => \IPS\Helpers\Form\Interval::DAYS, 'min' => NULL ], NULL, \IPS\Member::loggedIn()->language()->addToStack('achievement_filter_SessionStartDaily_prefix') );
|
||||
$return['milestone_concurrent'] = new \IPS\Helpers\Form\Number( 'achievement_filter_SessionStartDaily_concurrent', ( $filters and isset( $filters['milestone_concurrent'] ) and $filters['milestone_concurrent'] ) ? $filters['milestone_concurrent'] : 0, FALSE, [], NULL, \IPS\Member::loggedIn()->language()->addToStack('achievement_filter_SessionStartDaily_concurrent_prefix'), \IPS\Member::loggedIn()->language()->addToStack('achievement_filter_SessionStartDaily_concurrent_suffix') );
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format filter form values
|
||||
*
|
||||
* @param array $values The values from the form
|
||||
* @return array
|
||||
*/
|
||||
public function formatFilterValues( array $values ): array
|
||||
{
|
||||
$return = [];
|
||||
if ( isset( $values['achievement_filter_SessionStartDaily'] ) )
|
||||
{
|
||||
$return['milestone_registered'] = $values['achievement_filter_SessionStartDaily'];
|
||||
}
|
||||
if ( isset( $values['achievement_filter_SessionStartDaily_concurrent'] ) )
|
||||
{
|
||||
$return['milestone_concurrent'] = $values['achievement_filter_SessionStartDaily_concurrent'];
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Work out if the filters applies for a given action
|
||||
*
|
||||
* Important note for milestones: consider the context. This method is called by \IPS\Member::achievementAction(). If your code
|
||||
* calls that BEFORE making its change in the database (or there is read/write separation), you will need to add
|
||||
* 1 to the value being considered for milestones
|
||||
*
|
||||
* @param \IPS\Member $subject The subject member
|
||||
* @param array $filters The value returned by formatFilterValues()
|
||||
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
|
||||
* @return bool
|
||||
*/
|
||||
public function filtersMatch( \IPS\Member $subject, array $filters, $extra = NULL ): bool
|
||||
{
|
||||
if ( isset( $filters['milestone_registered'] ) )
|
||||
{
|
||||
if ( $subject->joined->getTimestamp() > ( time() - ( 86400 * $filters['milestone_registered'] ) ) )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $filters['milestone_concurrent'] ) )
|
||||
{
|
||||
/* Get the count of all logins since milestone ago. If it matches the count with milestone, then we have one for each day */
|
||||
if ( $subject->timezone )
|
||||
{
|
||||
$date = ( new \DateTime( 'now', new \DateTimeZone( $subject->timezone ) ) )->sub( new \DateInterval( 'P' . $filters['milestone_concurrent'] . 'D' ) )->format( 'Y-m-d' );
|
||||
}
|
||||
else
|
||||
{
|
||||
$date = ( new \DateTime( 'now' ) )->sub( new \DateInterval( 'P' . $filters['milestone_concurrent'] . 'D' ) )->format( 'Y-m-d' );
|
||||
}
|
||||
if ( $filters['milestone_concurrent'] > (int) \IPS\Db::i()->select( 'COUNT(*)', 'core_members_logins', [ 'member_id=? and member_date >=?', $subject->member_id, $date ] )->first() )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get identifier to prevent the member being awarded points for the same action twice
|
||||
* Must be unique within within of this domain, must not exceed 32 chars.
|
||||
*
|
||||
* @param \IPS\Member $subject The subject member
|
||||
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
|
||||
* @return string
|
||||
*/
|
||||
public function identifier( \IPS\Member $subject, $extra = NULL ): string
|
||||
{
|
||||
if ( $subject->timezone )
|
||||
{
|
||||
return $subject->member_id . '.' . ( new \DateTime( 'now', new \DateTimeZone( $subject->timezone ) ) )->format( 'Y-m-d' ); // User's own timezone: Jordan's idea
|
||||
}
|
||||
else
|
||||
{
|
||||
return $subject->member_id . '.' . ( new \DateTime( 'now' ) )->format( 'Y-m-d' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a description for this action to show in the log
|
||||
*
|
||||
* @param string $identifier The identifier as returned by identifier()
|
||||
* @param array $actor If the member was the "subject", "other", or both
|
||||
* @return string
|
||||
*/
|
||||
public function logRow( string $identifier, array $actor ): string
|
||||
{
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__SessionStartDaily_log', FALSE, array( 'sprintf' => ( new \IPS\DateTime( $identifier ) )->dayAndMonth() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get "description" for rule
|
||||
*
|
||||
* @param \IPS\core\Achievements\Rule $rule The rule
|
||||
* @return string|NULL
|
||||
*/
|
||||
public function ruleDescription( \IPS\core\Achievements\Rule $rule ): ?string
|
||||
{
|
||||
$conditions = [];
|
||||
if ( isset( $rule->filters['milestone_registered'] ) )
|
||||
{
|
||||
$conditions[] = \IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__SessionStartDaily_milestone', FALSE, [
|
||||
'htmlsprintf' => [
|
||||
\IPS\Theme::i()->getTemplate( 'achievements' )->ruleDescriptionBadge( 'milestone_registered', \IPS\Member::loggedIn()->language()->addToStack( 'f_days', FALSE, [ 'pluralize' => [ $rule->filters['milestone_registered'] ] ] ) )
|
||||
],
|
||||
] );
|
||||
}
|
||||
elseif ( isset( $rule->filters['milestone_concurrent'] ) )
|
||||
{
|
||||
$conditions[] = \IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__SessionStartDaily_concurrent_milestone', FALSE, [
|
||||
'htmlsprintf' => [
|
||||
\IPS\Theme::i()->getTemplate( 'achievements' )->ruleDescriptionBadge( 'milestone', \IPS\Member::loggedIn()->language()->addToStack( 'f_days', FALSE, [ 'pluralize' => [ $rule->filters['milestone_concurrent'] ] ] ) )
|
||||
],
|
||||
] );
|
||||
}
|
||||
|
||||
return \IPS\Theme::i()->getTemplate( 'achievements', 'core', 'admin' )->ruleDescription(
|
||||
\IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__SessionStartDaily_title' ),
|
||||
$conditions
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a user friendly description of why this was awarded
|
||||
*
|
||||
* @param \IPS\core\Achievements\Rule $rule The rule
|
||||
* @return string|NULL
|
||||
*/
|
||||
public function awardDescription( \IPS\core\Achievements\Rule $rule ): ?string
|
||||
{
|
||||
if ( isset( $rule->filters['milestone_registered'] ) )
|
||||
{
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__SessionStartDaily_registered_badge_blurb', FALSE, [
|
||||
'pluralize' => [
|
||||
$rule->filters['milestone_registered']
|
||||
],
|
||||
] );
|
||||
}
|
||||
elseif ( isset( $rule->filters['milestone_concurrent'] ) )
|
||||
{
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__SessionStartDaily_concurrent_badge_blurb', FALSE, [
|
||||
'pluralize' => [
|
||||
$rule->filters['milestone_registered']
|
||||
],
|
||||
] );
|
||||
}
|
||||
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__SessionStartDaily_badge_blurb' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get rebuild data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
static public function rebuildData()
|
||||
{
|
||||
return [ [
|
||||
'table' => 'core_members',
|
||||
'pkey' => 'member_id',
|
||||
'date' => 'joined',
|
||||
'where' => [ [ 'last_visit > 0 AND ( ! ' . \IPS\Db::i()->bitwiseWhere( \IPS\Member::$bitOptions['members_bitoptions'], 'bw_is_spammer' ) . ')' ] ],
|
||||
] ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the rebuild row
|
||||
*
|
||||
* @param array $row Row from database
|
||||
* @param array $data Data collected when starting rebuild [table, pkey...]
|
||||
* @return void
|
||||
*/
|
||||
public static function rebuildRow( $row, $data )
|
||||
{
|
||||
\IPS\Member::constructFromData( $row )->achievementAction( 'core', 'SessionStartDaily' );
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Achievement Action 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 08 Mar 2021
|
||||
*/
|
||||
|
||||
namespace IPS\core\extensions\core\AchievementAction;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Achievement Action Extension
|
||||
*/
|
||||
class _VotePoll extends \IPS\core\Achievements\Actions\AbstractAchievementAction // NOTE: Other classes exist to provided bases for common situations, like where node-based filters will be required
|
||||
{
|
||||
/**
|
||||
* Get filter form elements
|
||||
*
|
||||
* @param array|NULL $filters Current filter values (if editing)
|
||||
* @param \IPS\Http\Url $url The URL the form is being shown on
|
||||
* @return array
|
||||
*/
|
||||
public function filters( ?array $filters, \IPS\Http\Url $url ): array
|
||||
{
|
||||
$nthFilter = new \IPS\Helpers\Form\Number( 'achievement_filter_poll_vote_nth', ( $filters and isset( $filters['milestone'] ) and $filters['milestone'] ) ? $filters['milestone'] : 0, FALSE, [], NULL, \IPS\Member::loggedIn()->language()->addToStack('achievement_filter_nth_their'), \IPS\Member::loggedIn()->language()->addToStack('achievement_filter_VotePoll_nth_suffix') );
|
||||
$nthFilter->label = \IPS\Member::loggedIn()->language()->addToStack('achievement_filter_NewPoll_nth');
|
||||
|
||||
$return = array();
|
||||
|
||||
$return['milestone'] = $nthFilter;
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format filter form values
|
||||
*
|
||||
* @param array $values The values from the form
|
||||
* @return array
|
||||
*/
|
||||
public function formatFilterValues( array $values ): array
|
||||
{
|
||||
$return = [];
|
||||
|
||||
if ( isset( $values['achievement_filter_poll_vote_nth'] ) )
|
||||
{
|
||||
$return['milestone'] = $values['achievement_filter_poll_vote_nth'];
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Work out if the filters applies for a given action
|
||||
*
|
||||
* Important note for milestones: consider the context. This method is called by \IPS\Member::achievementAction(). If your code
|
||||
* calls that BEFORE making its change in the database (or there is read/write separation), you will need to add
|
||||
* 1 to the value being considered for milestones
|
||||
*
|
||||
* @param \IPS\Member $subject The subject member
|
||||
* @param array $filters The value returned by formatFilterValues()
|
||||
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
|
||||
* @return bool
|
||||
*/
|
||||
public function filtersMatch( \IPS\Member $subject, array $filters, $extra = NULL ): bool
|
||||
{
|
||||
if ( isset( $filters['milestone'] ) )
|
||||
{
|
||||
$where = [];
|
||||
$where[] = [ 'member_id=?', $subject->member_id ];
|
||||
|
||||
$count = \IPS\Db::i()->select( 'COUNT(*)', 'core_voters', $where )->first();
|
||||
|
||||
if ( $count < $filters['milestone'] )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the labels for the people this action might give awards to
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function awardOptions(): array
|
||||
{
|
||||
return [
|
||||
'subject' => 'achievement_filter_VotePoll_voter',
|
||||
'other' => 'achievement_filter_VotePoll_author'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the "other" people we need to award =stuff to
|
||||
*
|
||||
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
|
||||
* @return array
|
||||
*/
|
||||
|
||||
public function awardOther( $extra = NULL ): array
|
||||
{
|
||||
return [ $extra->author() ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get identifier to prevent the member being awarded points for the same action twice
|
||||
* Must be unique within within of this domain, must not exceed 32 chars.
|
||||
*
|
||||
* @param \IPS\Member $subject The subject member
|
||||
* @param mixed $extra Any additional information about what is happening (e.g. if a post is being made: the post object)
|
||||
* @return string
|
||||
*/
|
||||
public function identifier( \IPS\Member $subject, $extra = NULL ): string
|
||||
{
|
||||
return (string) $extra->pid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a description for this action to show in the log
|
||||
*
|
||||
* @param string $identifier The identifier as returned by identifier()
|
||||
* @param array $actor If the member was the "subject", "other", or both
|
||||
* @return string
|
||||
*/
|
||||
public function logRow( string $identifier, array $actor ): string
|
||||
{
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__VotePoll_log', FALSE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get "description" for rule
|
||||
*
|
||||
* @param \IPS\core\Achievements\Rule $rule The rule
|
||||
* @return string|NULL
|
||||
*/
|
||||
public function ruleDescription( \IPS\core\Achievements\Rule $rule ): ?string
|
||||
{
|
||||
$conditions = [];
|
||||
if ( isset( $rule->filters['milestone'] ) )
|
||||
{
|
||||
$conditions[] = \IPS\Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone', FALSE, [
|
||||
'htmlsprintf' => [
|
||||
\IPS\Theme::i()->getTemplate( 'achievements' )->ruleDescriptionBadge( 'milestone', \IPS\Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone_nth', FALSE, [ 'pluralize' => [ $rule->filters['milestone'] ] ] ) )
|
||||
],
|
||||
'sprintf' => [ \IPS\Member::loggedIn()->language()->addToStack('AchievementAction__VotePoll_title_generic') ]
|
||||
] );
|
||||
}
|
||||
|
||||
return \IPS\Theme::i()->getTemplate( 'achievements' )->ruleDescription(
|
||||
\IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__VotePoll_title' ),
|
||||
$conditions
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get rebuild data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
static public function rebuildData()
|
||||
{
|
||||
return [ [
|
||||
'table' => 'core_voters',
|
||||
'pkey' => 'vid',
|
||||
'date' => 'vote_date',
|
||||
'where' => [],
|
||||
] ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the rebuild row
|
||||
*
|
||||
* @param array $row Row from database
|
||||
* @param array $data Data collected when starting rebuild [table, pkey...]
|
||||
* @return void
|
||||
*/
|
||||
public static function rebuildRow( $row, $data )
|
||||
{
|
||||
\IPS\Member::load( $row['member_id'] )->achievementAction( 'core', 'VotePoll', \IPS\Poll::load( $row['poll'] ) );
|
||||
}
|
||||
}
|
||||
@@ -95,7 +95,14 @@ class _Bulletin extends \IPS\core\AdminNotification
|
||||
return $this->_bulletinData;
|
||||
}
|
||||
|
||||
$data = \IPS\Db::i()->select( '*', 'core_ips_bulletins', array( 'id=?', $this->extra ) )->first();
|
||||
try
|
||||
{
|
||||
$data = \IPS\Db::i()->select( '*', 'core_ips_bulletins', array( 'id=?', $this->extra ) )->first();
|
||||
}
|
||||
catch( \UnderflowException $e )
|
||||
{
|
||||
$data = array( 'cached' => 0, 'id' => $this->extra );
|
||||
}
|
||||
|
||||
if ( ( time() - $data['cached'] ) > 3600 ) // If data was cached more than an hour ago, check again in case it's been updated
|
||||
{
|
||||
@@ -113,7 +120,9 @@ class _Bulletin extends \IPS\core\AdminNotification
|
||||
'dismissible' => $bulletin['dismissible'],
|
||||
'link' => $bulletin['link'],
|
||||
'conditions' => $bulletin['conditions'],
|
||||
'cached' => time()
|
||||
'cached' => time(),
|
||||
'min_version' => $bulletin['minVersion'],
|
||||
'max_version' => $bulletin['maxVersion']
|
||||
);
|
||||
\IPS\Db::i()->update( 'core_ips_bulletins', $data, array( 'id=?', $this->extra ) );
|
||||
}
|
||||
@@ -125,6 +134,20 @@ class _Bulletin extends \IPS\core\AdminNotification
|
||||
catch ( \Exception $e )
|
||||
{
|
||||
\IPS\Db::i()->update( 'core_ips_bulletins', array( 'cached' => ( time() + 3600 - 900 ) ), array( 'id=?', $this->extra ) ); // Try again in 15 minutes
|
||||
|
||||
$data = array(
|
||||
'id' => $data['id'],
|
||||
'title' => null,
|
||||
'body' => null,
|
||||
'severity' => null,
|
||||
'style' => null,
|
||||
'dismissible' => false,
|
||||
'link' => null,
|
||||
'conditions' => null,
|
||||
'cached' => ( time() + 3600 - 900 ),
|
||||
'min_version' => null,
|
||||
'max_version' => null
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,6 +226,16 @@ class _Bulletin extends \IPS\core\AdminNotification
|
||||
{
|
||||
try
|
||||
{
|
||||
if( $this->data()['min_version'] AND $this->data()['min_version'] > \IPS\Application::load('core')->long_version )
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if( $this->data()['max_version'] AND $this->data()['max_version'] < \IPS\Application::load('core')->long_version )
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return !@eval( $this->data()['conditions'] );
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
|
||||
@@ -158,11 +158,9 @@ class _ConfigurationError extends \IPS\core\AdminNotification
|
||||
/* CiC Email Quota */
|
||||
if ( \IPS\CIC )
|
||||
{
|
||||
preg_match( '/^\/var\/www\/html\/(.+?)(?:\/|$)/i', \IPS\ROOT_PATH, $matches );
|
||||
|
||||
try
|
||||
{
|
||||
$cicEmails = \IPS\Http\Url::external( "http://ips-cic-email.invisioncic.com/blocked.php?account={$matches[1]}" )->request()->get()->decodeJson();
|
||||
$cicEmails = \IPS\Http\Url::external( \IPS\IPS::$cicConfig['email']['quota_check'] )->setQueryString('account', \IPS\Cicloud\getCicUsername() )->request()->get()->decodeJson();
|
||||
|
||||
if ( isset( $cicEmails['status'] ) AND $cicEmails['status'] == 'BLOCKED' )
|
||||
{
|
||||
@@ -184,6 +182,18 @@ class _ConfigurationError extends \IPS\core\AdminNotification
|
||||
else
|
||||
{
|
||||
\IPS\core\AdminNotification::remove( 'core', 'ConfigurationError', 'failedMail' );
|
||||
\IPS\Db::i()->update( 'core_mail_error_logs', array( 'mlog_notification_sent' => TRUE ) );
|
||||
}
|
||||
|
||||
/* Username logins enabled */
|
||||
$login = new \IPS\Login;
|
||||
if( $login->authType() & \IPS\Login::AUTH_TYPE_USERNAME )
|
||||
{
|
||||
\IPS\core\AdminNotification::send( 'core', 'ConfigurationError', 'usernameLoginEnabled', FALSE );
|
||||
}
|
||||
else
|
||||
{
|
||||
\IPS\core\AdminNotification::remove( 'core', 'ConfigurationError', 'usernameLoginEnabled' );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -255,7 +265,8 @@ class _ConfigurationError extends \IPS\core\AdminNotification
|
||||
$member->hasAcpRestriction( 'core', 'settings', 'advanced_manage' ) or
|
||||
$member->hasAcpRestriction( 'core', 'settings', 'advanced_manage' ) or
|
||||
$member->hasAcpRestriction( 'core', 'overview', 'system_notifications' ) or
|
||||
$member->hasAcpRestriction( 'core', 'marketplace', 'marketplace_manage' )
|
||||
$member->hasAcpRestriction( 'core', 'marketplace', 'marketplace_manage' ) or
|
||||
$member->hasAcpRestriction( 'core', 'settings', 'login_access' )
|
||||
);
|
||||
}
|
||||
|
||||
@@ -319,6 +330,10 @@ class _ConfigurationError extends \IPS\core\AdminNotification
|
||||
{
|
||||
return $member->hasAcpRestriction( 'core', 'marketplace', 'marketplace_manage' );
|
||||
}
|
||||
elseif ( $this->extra === 'usernameLoginEnabled' )
|
||||
{
|
||||
return $member->hasAcpRestriction( 'core', 'settings', 'login_access' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -380,6 +395,10 @@ class _ConfigurationError extends \IPS\core\AdminNotification
|
||||
{
|
||||
return \IPS\Member::loggedIn()->language()->addToStack('marketplace_onboard_title');
|
||||
}
|
||||
elseif ( $this->extra === 'usernameLoginEnabled' )
|
||||
{
|
||||
return \IPS\Member::loggedIn()->language()->addToStack('username_login_enabled');
|
||||
}
|
||||
else
|
||||
{
|
||||
return htmlentities( $this->extra, ENT_DISALLOWED, 'UTF-8', FALSE );
|
||||
@@ -445,6 +464,10 @@ class _ConfigurationError extends \IPS\core\AdminNotification
|
||||
{
|
||||
return \IPS\Member::loggedIn()->language()->addToStack('marketplace_onboard_notification');
|
||||
}
|
||||
elseif ( $this->extra === 'usernameLoginEnabled' )
|
||||
{
|
||||
return \IPS\Member::loggedIn()->language()->addToStack('username_login_enabled_desc');
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -489,7 +512,16 @@ class _ConfigurationError extends \IPS\core\AdminNotification
|
||||
elseif ( $this->extra === 'recommendations' )
|
||||
{
|
||||
$requirementsAndRecommendations = \IPS\core\Setup\Upgrade::systemRequirements();
|
||||
return \IPS\Theme::i()->getTemplate( 'notifications', 'core' )->systemRecommendations( isset( $requirementsAndRecommendations['advice'] ) ? $requirementsAndRecommendations['advice'] : array() );
|
||||
$advice = array();
|
||||
|
||||
if( isset( $requirementsAndRecommendations['advice'] ) )
|
||||
{
|
||||
foreach( $requirementsAndRecommendations['advice'] as $category )
|
||||
{
|
||||
$advice = array_merge( $advice, $category );
|
||||
}
|
||||
}
|
||||
return \IPS\Theme::i()->getTemplate( 'notifications', 'core' )->systemRecommendations( $advice );
|
||||
}
|
||||
elseif ( $this->extra === 'origTables' )
|
||||
{
|
||||
@@ -526,6 +558,10 @@ class _ConfigurationError extends \IPS\core\AdminNotification
|
||||
{
|
||||
return \IPS\Theme::i()->getTemplate( 'notifications', 'core' )->marketplaceSetup();
|
||||
}
|
||||
elseif ( $this->extra === 'usernameLoginEnabled' )
|
||||
{
|
||||
return \IPS\Theme::i()->getTemplate( 'notifications', 'core' )->usernameLoginEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -560,6 +596,10 @@ class _ConfigurationError extends \IPS\core\AdminNotification
|
||||
{
|
||||
return static::DISMISSIBLE_NO;
|
||||
}
|
||||
elseif( $this->extra == 'usernameLoginEnabled' )
|
||||
{
|
||||
return static::DISMISSIBLE_PERMANENT;
|
||||
}
|
||||
else
|
||||
{
|
||||
return static::DISMISSIBLE_TEMPORARY;
|
||||
@@ -651,21 +691,10 @@ class _ConfigurationError extends \IPS\core\AdminNotification
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete AdminCP Notification
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
/* If deleting a failedMail notification, mark previous logs as notification sent */
|
||||
if( $this->extra == 'failedMail' )
|
||||
elseif( $this->extra == 'usernameLoginEnabled' )
|
||||
{
|
||||
\IPS\Db::i()->update( 'core_mail_error_logs', array( 'mlog_notification_sent' => TRUE ) );
|
||||
$login = new \IPS\Login;
|
||||
return !( $login->authType() & \IPS\Login::AUTH_TYPE_USERNAME );
|
||||
}
|
||||
|
||||
parent::delete();
|
||||
}
|
||||
}
|
||||
@@ -121,7 +121,7 @@ class _NewVersion extends \IPS\core\AdminNotification
|
||||
*/
|
||||
public function body()
|
||||
{
|
||||
return \IPS\Theme::i()->getTemplate( 'notifications', 'core', 'admin' )->newVersion( $this->_details );
|
||||
return \IPS\Theme::i()->getTemplate( 'notifications', 'core', 'global' )->newVersion( $this->_details );
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -184,7 +184,7 @@ class _Spammer extends \IPS\core\AdminNotification
|
||||
) as $user
|
||||
)
|
||||
{
|
||||
$users[ $user['member_id'] ]['member'] = \IPS\Member::constructFromData( $user );
|
||||
$users[ $user['member_id'] ] = array( 'member' => \IPS\Member::constructFromData( $user ), 'blurb' => '' );
|
||||
|
||||
foreach ( \IPS\Db::i()->select( '*', 'core_member_history', array( "log_member=? AND log_type='account'", $user['member_id'] ), 'log_date DESC', 50 ) as $row )
|
||||
{
|
||||
|
||||
@@ -89,8 +89,9 @@ class _Pixabay
|
||||
{
|
||||
$groups[ $group->g_id ] = $group->name;
|
||||
}
|
||||
|
||||
$form->add( new \IPS\Helpers\Form\Select( 'pixabay_editor_permissions', \IPS\Settings::i()->pixabay_editor_permissions == '*' ? '*' : explode( ',', \IPS\Settings::i()->pixabay_editor_permissions ), NULL, array( 'multiple' => TRUE, 'options' => $groups, 'unlimited' => '*', 'unlimitedLang' => 'everyone' ), NULL, NULL, NULL, 'pixabay_editor_permissions_access' ) );
|
||||
|
||||
$form->add( new \IPS\Helpers\Form\YesNo( 'pixabay_safesearch', \IPS\Settings::i()->pixabay_safesearch, FALSE, array(), NULL, NULL, NULL, 'pixabay_safesearch' ) );
|
||||
$form->add( new \IPS\Helpers\Form\CheckboxSet( 'pixabay_editor_permissions', \IPS\Settings::i()->pixabay_editor_permissions == '*' ? '*' : explode( ',', \IPS\Settings::i()->pixabay_editor_permissions ), NULL, array( 'multiple' => TRUE, 'options' => $groups, 'unlimited' => '*', 'unlimitedLang' => 'everyone', 'impliedUnlimited' => TRUE ), NULL, NULL, NULL, 'pixabay_editor_permissions_access' ) );
|
||||
|
||||
if ( $values = $form->values() )
|
||||
{
|
||||
|
||||
@@ -77,7 +77,7 @@ class _SpamMonitoring
|
||||
}
|
||||
catch ( \LogicException $e )
|
||||
{
|
||||
// \IPS\Output::i()->error( $e->getMessage(), '2C116/2', 403, '' );
|
||||
\IPS\Output::i()->error( $e->getMessage(), '2C116/2', 403, '' );
|
||||
}
|
||||
|
||||
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=moderation&controller=spam&tab=service' ) );
|
||||
|
||||
@@ -79,8 +79,8 @@ class _Viglink
|
||||
$form->add( new \IPS\Helpers\Form\Text( 'viglink_api_key', \IPS\Settings::i()->viglink_api_key, FALSE, array(), NULL, NULL, NULL, 'viglink_api_key' ) );
|
||||
}
|
||||
|
||||
$form->add( new \IPS\Helpers\Form\Select( 'viglink_groups', ( \IPS\Settings::i()->viglink_groups == 'all' ) ? 'all' : explode( ',', \IPS\Settings::i()->viglink_groups ), FALSE, array( 'options' => \IPS\Member\Group::groups(), 'parse' => 'normal', 'multiple' => true, 'unlimited' => 'all', 'unlimitedLang' => 'all_groups' ), NULL, NULL, NULL, 'viglink_groups' ) );
|
||||
$form->add( new \IPS\Helpers\Form\Select( 'viglink_norewrite', explode( ',', \IPS\Settings::i()->viglink_norewrite ), FALSE, array( 'options' => \IPS\Member\Group::groups(), 'parse' => 'normal', 'multiple' => true ), NULL, NULL, NULL, 'viglink_norewrite' ) );
|
||||
$form->add( new \IPS\Helpers\Form\CheckboxSet( 'viglink_groups', ( \IPS\Settings::i()->viglink_groups == 'all' ) ? 'all' : explode( ',', \IPS\Settings::i()->viglink_groups ), FALSE, array( 'options' => \IPS\Member\Group::groups(), 'parse' => 'normal', 'multiple' => true, 'unlimited' => 'all', 'unlimitedLang' => 'all_groups', 'impliedUnlimited' => TRUE ), NULL, NULL, NULL, 'viglink_groups' ) );
|
||||
$form->add( new \IPS\Helpers\Form\CheckboxSet( 'viglink_norewrite', explode( ',', \IPS\Settings::i()->viglink_norewrite ), FALSE, array( 'options' => \IPS\Member\Group::groups(), 'parse' => 'normal', 'multiple' => true ), NULL, NULL, NULL, 'viglink_norewrite' ) );
|
||||
|
||||
if ( $values = $form->values( TRUE ) )
|
||||
{
|
||||
|
||||
@@ -261,6 +261,8 @@ class _Zapier
|
||||
|
||||
if ( $isNew )
|
||||
{
|
||||
\IPS\Lang::saveCustom( 'core', "core_api_name_{$apiKey->id}", "Zapier" );
|
||||
|
||||
throw new \DomainException;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,6 +194,48 @@ class _Reports
|
||||
{
|
||||
\IPS\Db::i()->update( 'core_rc_reports', $update, array( 'id=?', $report['id'] ) );
|
||||
}
|
||||
|
||||
/* Now rebuild any comments on this report */
|
||||
foreach( \IPS\Db::i()->select( '*', 'core_rc_comments', array( 'rid=?', $report['id'] ) ) as $comment )
|
||||
{
|
||||
$updateComment = NULL;
|
||||
|
||||
try
|
||||
{
|
||||
if( $callback == 'parseImageProxy' )
|
||||
{
|
||||
$updateComment = \IPS\Text\Parser::removeImageProxy( $comment['comment'], $this->proxyUrl );
|
||||
}
|
||||
elseif( $callback == 'parseLazyLoad' )
|
||||
{
|
||||
$updateComment = \IPS\Text\Parser::parseLazyLoad( $comment['comment'], $this->_lazyLoadStatus );
|
||||
}
|
||||
elseif( $callback[1] == 'rebuildAttachmentUrls' )
|
||||
{
|
||||
$updateComment = \IPS\Text\Parser::rebuildAttachmentUrls( $comment['comment'], \IPS\Member::load( $comment['comment_by'] ) );
|
||||
}
|
||||
elseif( $callback[1] == 'parseStatic' )
|
||||
{
|
||||
$updateComment = $callback( $comment['comment'], \IPS\Member::load( $comment['comment_by'] ), FALSE, 'core_Reports', $comment['id'], NULL, NULL );
|
||||
}
|
||||
}
|
||||
catch( \InvalidArgumentException $e )
|
||||
{
|
||||
if( $callback[1] == 'parseStatic' AND $e->getcode() == 103014 )
|
||||
{
|
||||
$updateComment = preg_replace( "#\[/?([^\]]+?)\]#", '', $comment['comment'] );
|
||||
}
|
||||
else
|
||||
{
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
if( $updateComment )
|
||||
{
|
||||
\IPS\Db::i()->update( 'core_rc_comments', array( 'comment' => $updateComment ), array( 'id=?', $comment['id'] ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $did;
|
||||
|
||||
@@ -75,6 +75,10 @@ class _Attachment
|
||||
foreach ( \IPS\Db::i()->select( 'core_attachments.*', 'core_attachments', $where, 'attach_date DESC', array( ( $page - 1 ) * $limit, $limit ) ) as $row )
|
||||
{
|
||||
$url = \IPS\Settings::i()->base_url . "applications/core/interface/file/attachment.php?id={$row['attach_id']}";
|
||||
if ( $row['attach_security_key'] )
|
||||
{
|
||||
$url .= "&key={$row['attach_security_key']}";
|
||||
}
|
||||
$obj = \IPS\File::get( 'core_Attachment', $row['attach_location'] );
|
||||
$obj->originalFilename = $row['attach_file'];
|
||||
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief File Storage Extension: Badges
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
|
||||
* @since 23 Feb 2021
|
||||
*/
|
||||
|
||||
namespace IPS\core\extensions\core\FileStorage;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* File Storage Extension: Badges
|
||||
*/
|
||||
class _Badges
|
||||
{
|
||||
/**
|
||||
* Count stored files
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
return \IPS\Db::i()->select( 'COUNT(*)', 'core_badges', 'image IS NOT NULL' )->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Move stored files
|
||||
*
|
||||
* @param int $offset This will be sent starting with 0, increasing to get all files stored by this extension
|
||||
* @param int $storageConfiguration New storage configuration ID
|
||||
* @param int|NULL $oldConfiguration Old storage configuration ID
|
||||
* @throws \UnderflowException When file record doesn't exist. Indicating there are no more files to move
|
||||
* @return void|int An offset integer to use on the next cycle, or nothing
|
||||
*/
|
||||
public function move( $offset, $storageConfiguration, $oldConfiguration=NULL )
|
||||
{
|
||||
$badgeData = \IPS\Db::i()->select( '*', 'core_badges', 'image IS NOT NULL', 'id', array( $offset, 1 ) )->first();
|
||||
|
||||
try
|
||||
{
|
||||
$file = \IPS\File::get( $oldConfiguration ?: 'core_Badges', $badgeData['image'] )->move( $storageConfiguration );
|
||||
|
||||
if ( (string) $file != $badgeData['image'] )
|
||||
{
|
||||
\IPS\Db::i()->update( 'core_badges', array( 'image' => (string) $file ), array( 'id=?', $badgeData['id'] ) );
|
||||
}
|
||||
}
|
||||
catch( \Exception $e )
|
||||
{
|
||||
/* Any issues are logged */
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fix all URLs
|
||||
*
|
||||
* @param int $offset This will be sent starting with 0, increasing to get all files stored by this extension
|
||||
* @return void
|
||||
*/
|
||||
public function fixUrls( $offset )
|
||||
{
|
||||
$badgeData = \IPS\Db::i()->select( '*', 'core_badges', 'image IS NOT NULL', 'id', array( $offset, 1 ) )->first();
|
||||
|
||||
try
|
||||
{
|
||||
if ( $new = \IPS\File::repairUrl( $badgeData['image'] ) )
|
||||
{
|
||||
\IPS\Db::i()->update( 'core_badges', [ 'image' => $new ], array( 'id=?', $badgeData['id'] ) );
|
||||
}
|
||||
}
|
||||
catch( \Exception $e ) { }
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a file is valid
|
||||
*
|
||||
* @param string $file The file path to check
|
||||
* @return bool
|
||||
*/
|
||||
public function isValidFile( $file )
|
||||
{
|
||||
try
|
||||
{
|
||||
$badgeData = \IPS\Db::i()->select( '*', 'core_badges', array( 'image=?', (string) $file ) )->first();
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
catch ( \UnderflowException $e )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all stored files
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
foreach( \IPS\Db::i()->select( '*', 'core_badges', 'image IS NOT NULL' ) as $badgeData )
|
||||
{
|
||||
try
|
||||
{
|
||||
\IPS\File::get( 'core_Badges', $badgeData['image'] )->delete();
|
||||
}
|
||||
catch( \Exception $e ){}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief File Storage Extension: Feature
|
||||
* @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 10 Sep 2019
|
||||
*/
|
||||
|
||||
namespace IPS\core\extensions\core\FileStorage;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* File Storage Extension: Feature
|
||||
*/
|
||||
class _Feature
|
||||
{
|
||||
/**
|
||||
* Count stored files
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
return \IPS\Db::i()->select( 'COUNT(*)', 'core_members_features', array( "feature_image IS NOT NULL" ) )->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Move stored files
|
||||
*
|
||||
* @param int $offset This will be sent starting with 0, increasing to get all files stored by this extension
|
||||
* @param int $storageConfiguration New storage configuration ID
|
||||
* @param int|NULL $oldConfiguration Old storage configuration ID
|
||||
* @throws \UnderflowException When file record doesn't exist. Indicating there are no more files to move
|
||||
* @return void|int An offset integer to use on the next cycle, or nothing
|
||||
*/
|
||||
public function move( $offset, $storageConfiguration, $oldConfiguration=NULL )
|
||||
{
|
||||
$feature = \IPS\Db::i()->select( '*', 'core_members_features', array( "feature_image IS NOT NULL" ), 'feature_id', array( $offset, 1 ) )->first();
|
||||
|
||||
$url = (string) \IPS\File::get( $oldConfiguration ?: 'core_Feature', $feature['feature_image'] )->move( $storageConfiguration );
|
||||
|
||||
if ( $url != $feature['feature_image'] )
|
||||
{
|
||||
\IPS\Db::i()->update( 'core_members_features', array( 'feature_image' => $url ), array( 'feature_id=?', $feature['feature_id'] ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a file is valid
|
||||
*
|
||||
* @param string $file The file path to check
|
||||
* @return bool
|
||||
*/
|
||||
public function isValidFile( $file )
|
||||
{
|
||||
try
|
||||
{
|
||||
\IPS\Db::i()->select( '*', 'core_members_features', array( "feature_image=?", $file ) )->first();
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
catch( \UnderflowException $e )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all stored files
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
foreach( \IPS\Db::i()->select( 'feature_image', 'core_members_features', array( "feature_image IS NOT NULL" ) ) AS $file )
|
||||
{
|
||||
\IPS\File::get( 'core_Feature', $file )->delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,6 +41,11 @@ class _Icons
|
||||
$count += \count( json_decode( \IPS\Settings::i()->icons_homescreen, TRUE ) );
|
||||
}
|
||||
|
||||
if( \IPS\Settings::i()->icons_apple_startup )
|
||||
{
|
||||
$count += \count( json_decode( \IPS\Settings::i()->icons_apple_startup, TRUE ) );
|
||||
}
|
||||
|
||||
if( \IPS\Settings::i()->icons_mask_icon )
|
||||
{
|
||||
$count += 1;
|
||||
@@ -62,7 +67,14 @@ class _Icons
|
||||
{
|
||||
if( \IPS\Settings::i()->icons_favicon )
|
||||
{
|
||||
\IPS\File::get( $oldConfiguration ?: 'core_Icons', \IPS\Settings::i()->icons_favicon )->move( $storageConfiguration );
|
||||
try
|
||||
{
|
||||
\IPS\File::get( $oldConfiguration ?: 'core_Icons', \IPS\Settings::i()->icons_favicon )->move( $storageConfiguration );
|
||||
}
|
||||
catch( \Exception $e )
|
||||
{
|
||||
/* Any issues are logged */
|
||||
}
|
||||
}
|
||||
|
||||
if( \IPS\Settings::i()->icons_sharer_logo )
|
||||
@@ -71,7 +83,14 @@ class _Icons
|
||||
|
||||
foreach( $logos as $logo )
|
||||
{
|
||||
\IPS\File::get( $oldConfiguration ?: 'core_Icons', $logo )->move( $storageConfiguration );
|
||||
try
|
||||
{
|
||||
\IPS\File::get( $oldConfiguration ?: 'core_Icons', $logo )->move( $storageConfiguration );
|
||||
}
|
||||
catch( \Exception $e )
|
||||
{
|
||||
/* Any issues are logged */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,13 +100,44 @@ class _Icons
|
||||
|
||||
foreach( $homeScreen as $key => $logo )
|
||||
{
|
||||
\IPS\File::get( $oldConfiguration ?: 'core_Icons', ( $key == 'original' ) ? $logo : $logo['url'] )->move( $storageConfiguration );
|
||||
try
|
||||
{
|
||||
\IPS\File::get( $oldConfiguration ?: 'core_Icons', ( $key == 'original' ) ? $logo : $logo['url'] )->move( $storageConfiguration );
|
||||
}
|
||||
catch( \Exception $e )
|
||||
{
|
||||
/* Any issues are logged */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( \IPS\Settings::i()->icons_apple_startup )
|
||||
{
|
||||
$apple = json_decode( \IPS\Settings::i()->icons_apple_startup, TRUE );
|
||||
|
||||
foreach( $apple as $key => $logo )
|
||||
{
|
||||
try
|
||||
{
|
||||
\IPS\File::get( $oldConfiguration ?: 'core_Icons', ( $key == 'original' ) ? $logo : $logo['url'] )->move( $storageConfiguration );
|
||||
}
|
||||
catch( \Exception $e )
|
||||
{
|
||||
/* Any issues are logged */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( \IPS\Settings::i()->icons_mask_icon )
|
||||
{
|
||||
\IPS\File::get( $oldConfiguration ?: 'core_Icons', \IPS\Settings::i()->icons_mask_icon )->move( $storageConfiguration );
|
||||
try
|
||||
{
|
||||
\IPS\File::get( $oldConfiguration ?: 'core_Icons', \IPS\Settings::i()->icons_mask_icon )->move( $storageConfiguration );
|
||||
}
|
||||
catch( \Exception $e )
|
||||
{
|
||||
/* Any issues are logged */
|
||||
}
|
||||
}
|
||||
|
||||
throw new \UnderflowException;
|
||||
@@ -140,6 +190,26 @@ class _Icons
|
||||
$updated['icons_homescreen'] = json_encode( $newLogos );
|
||||
}
|
||||
|
||||
if( \IPS\Settings::i()->icons_apple_startup )
|
||||
{
|
||||
$newLogos = array();
|
||||
|
||||
foreach( json_decode( \IPS\Settings::i()->icons_apple_startup, TRUE ) as $key => $logo )
|
||||
{
|
||||
if( $key == 'original' )
|
||||
{
|
||||
$newLogos[ $key ] = \IPS\File::repairUrl( $logo );
|
||||
}
|
||||
else
|
||||
{
|
||||
$newLogos[ $key ] = $logo;
|
||||
$newLogos[ $key ]['url'] = \IPS\File::repairUrl( $logo['url'] );
|
||||
}
|
||||
}
|
||||
|
||||
$updated['icons_apple_startup'] = json_encode( $newLogos );
|
||||
}
|
||||
|
||||
if( \IPS\Settings::i()->icons_mask_icon )
|
||||
{
|
||||
$updated['icons_mask_icon'] = \IPS\File::repairUrl( \IPS\Settings::i()->icons_mask_icon );
|
||||
@@ -187,6 +257,17 @@ class _Icons
|
||||
}
|
||||
}
|
||||
|
||||
if( \IPS\Settings::i()->icons_apple_startup )
|
||||
{
|
||||
foreach( json_decode( \IPS\Settings::i()->icons_apple_startup, TRUE ) as $key => $logo )
|
||||
{
|
||||
if( ( $key == 'original' AND $file == $logo ) OR ( $key != 'original' AND $file == $logo['url'] ) )
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( \IPS\Settings::i()->icons_mask_icon AND $file == \IPS\Settings::i()->icons_mask_icon )
|
||||
{
|
||||
return TRUE;
|
||||
@@ -235,6 +316,18 @@ class _Icons
|
||||
}
|
||||
}
|
||||
|
||||
if( \IPS\Settings::i()->icons_apple_startup )
|
||||
{
|
||||
foreach( json_decode( \IPS\Settings::i()->icons_apple_startup, TRUE ) as $key => $logo )
|
||||
{
|
||||
try
|
||||
{
|
||||
\IPS\File::get( 'core_Icons', ( $key == 'original' ) ? $logo : $logo['url'] )->delete();
|
||||
}
|
||||
catch( \OutOfRangeException $e ){}
|
||||
}
|
||||
}
|
||||
|
||||
if( \IPS\Settings::i()->icons_mask_icon )
|
||||
{
|
||||
try
|
||||
@@ -244,6 +337,6 @@ class _Icons
|
||||
catch( \OutOfRangeException $e ){}
|
||||
}
|
||||
|
||||
\IPS\Settings::i()->changeValues( array( 'icons_favicon' => NULL, 'icons_sharer_logo' => NULL, 'icons_homescreen' => NULL, 'icons_mask_icon' => NULL ) );
|
||||
\IPS\Settings::i()->changeValues( array( 'icons_favicon' => NULL, 'icons_sharer_logo' => NULL, 'icons_homescreen' => NULL, 'icons_apple_startup' => NULL, 'icons_mask_icon' => NULL ) );
|
||||
}
|
||||
}
|
||||
@@ -45,7 +45,14 @@ class _Reaction
|
||||
{
|
||||
$reaction = \IPS\Db::i()->select( '*', 'core_reactions', array(), 'reaction_id', array( $offset, 1 ) )->first();
|
||||
|
||||
$url = (string) \IPS\File::get( $oldConfiguration ?: 'core_Reaction', $reaction['reaction_icon'] )->move( $storageConfiguration );
|
||||
try
|
||||
{
|
||||
$url = (string) \IPS\File::get( $oldConfiguration ?: 'core_Reaction', $reaction['reaction_icon'] )->move( $storageConfiguration );
|
||||
}
|
||||
catch( \Exception $e )
|
||||
{
|
||||
/* Any issues are logged */
|
||||
}
|
||||
|
||||
if ( $url != $reaction['reaction_icon'] )
|
||||
{
|
||||
|
||||
@@ -73,6 +73,25 @@ class _Content
|
||||
$form->addHeader( 'group_viewing_title' );
|
||||
|
||||
$form->add( new \IPS\Helpers\Form\YesNo( 'gbw_post_highlight', $group->g_id ? $group->g_bitoptions['gbw_post_highlight'] : FALSE ) );
|
||||
$form->add( new \IPS\Helpers\Form\YesNo( 'gbw_hide_inline_modevents', $group->g_id ? !$group->g_bitoptions['gbw_hide_inline_modevents'] : TRUE ) );
|
||||
|
||||
$groupItemPostedIn = 'disabled';
|
||||
if ( $group->g_bitoptions['gbw_posted_in'] )
|
||||
{
|
||||
$groupItemPostedIn = 'primary';
|
||||
}
|
||||
if ( $group->g_bitoptions['gbw_posted_in_secondary'] )
|
||||
{
|
||||
$groupItemPostedIn = 'both';
|
||||
}
|
||||
|
||||
$form->add( new \IPS\Helpers\Form\Radio( 'gbw_posted_in', $groupItemPostedIn, FALSE, array(
|
||||
'options' => array(
|
||||
'primary' => 'gbw_posted_in_primary',
|
||||
'both' => 'gbw_posted_in_secondary',
|
||||
'disabled' => 'gbw_posted_in_disabled'
|
||||
)
|
||||
) ) );
|
||||
|
||||
/* Tags */
|
||||
$form->addHeader( 'tags' );
|
||||
@@ -138,7 +157,6 @@ class _Content
|
||||
|
||||
if( $group->g_id != \IPS\Settings::i()->guest_group )
|
||||
{
|
||||
|
||||
$form->add( new \IPS\Helpers\Form\YesNo( 'gbw_immune_auto_mod', $group->g_bitoptions['gbw_immune_auto_mod'] ) );
|
||||
$form->add( new \IPS\Helpers\Form\CheckboxSet( 'g_lock_unlock_own', !$group->g_lock_unlock_own ? 0 : ( $group->g_lock_unlock_own == '1' ? '1' : explode( ',', $group->g_lock_unlock_own ) ), FALSE, array(
|
||||
'options' => array_filter( $contentClasses, function( $class ) {
|
||||
@@ -224,7 +242,26 @@ class _Content
|
||||
$values['gbw_disable_tagging'] = \IPS\Settings::i()->tags_enabled ? !$values['gbw_disable_tagging'] : $group->g_bitoptions['gbw_disable_tagging'];
|
||||
$values['gbw_disable_prefixes'] = ( \IPS\Settings::i()->tags_enabled AND \IPS\Settings::i()->tags_can_prefix ) ? !$values['gbw_disable_prefixes'] : $group->g_bitoptions['gbw_disable_prefixes'];
|
||||
|
||||
$bwKeys = array( 'gbw_disable_tagging', 'gbw_disable_prefixes', 'gbw_delete_attachments', 'gbw_post_highlight', 'gbw_promote', 'gbw_immune_auto_mod' );
|
||||
/* This is an inverse setting - the UI says "Show .." but the setting actually causes it to be hidden. */
|
||||
$values['gbw_hide_inline_modevents'] = !$values['gbw_hide_inline_modevents'];
|
||||
|
||||
switch( $values['gbw_posted_in'] )
|
||||
{
|
||||
case 'primary':
|
||||
$values['gbw_posted_in'] = 1;
|
||||
$values['gbw_posted_in_secondary'] = 0;
|
||||
break;
|
||||
case 'both':
|
||||
$values['gbw_posted_in'] = 1;
|
||||
$values['gbw_posted_in_secondary'] = 1;
|
||||
break;
|
||||
case 'disabled':
|
||||
$values['gbw_posted_in'] = 0;
|
||||
$values['gbw_posted_in_secondary'] = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
$bwKeys = array( 'gbw_disable_tagging', 'gbw_disable_prefixes', 'gbw_delete_attachments', 'gbw_post_highlight', 'gbw_promote', 'gbw_immune_auto_mod', 'gbw_hide_inline_modevents', 'gbw_posted_in', 'gbw_posted_in_secondary' );
|
||||
|
||||
foreach ( $bwKeys as $k )
|
||||
{
|
||||
|
||||
@@ -191,6 +191,8 @@ class _GroupSettings
|
||||
$form->add( new \IPS\Helpers\Form\YesNo( 'can_be_ignored', $group->g_id ? ( !$group->g_bitoptions['gbw_cannot_be_ignored'] ) : TRUE, FALSE, array(), NULL, NULL, NULL, 'gbw_cannot_be_ignored' ) );
|
||||
}
|
||||
|
||||
$form->add( new \IPS\Helpers\Form\YesNo( 'can_post_anonymously', $group->g_id ? ( $group->g_bitoptions['gbw_can_post_anonymously'] ) : FALSE, FALSE, array(), NULL, NULL, NULL, 'gbw_can_post_anonymously' ) );
|
||||
|
||||
/* Signatures */
|
||||
if( \IPS\Settings::i()->signatures_enabled AND $group->g_id != \IPS\Settings::i()->guest_group )
|
||||
{
|
||||
@@ -302,6 +304,11 @@ class _GroupSettings
|
||||
{
|
||||
$group->g_bitoptions['gbw_cannot_be_ignored'] = !$values['can_be_ignored'];
|
||||
}
|
||||
|
||||
if ( isset( $values['can_post_anonymously'] ) )
|
||||
{
|
||||
$group->g_bitoptions['gbw_can_post_anonymously'] = $values['can_post_anonymously'];
|
||||
}
|
||||
|
||||
/* Update existing sessions if this setting is changed */
|
||||
if( isset( $values['g_hide_online_list'] ) AND $group->g_hide_online_list != (int) $values['g_hide_online_list'] )
|
||||
|
||||
@@ -36,7 +36,7 @@ class _GroupLimits
|
||||
'exclude' => array( 'g_id', 'g_icon', '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', 'gbw_no_status_update', 'g_max_bgimg_upload', 'gbw_disable_prefixes', 'gbw_disable_tagging', 'g_hide_online_list' ),
|
||||
'zeroIsBest' => array( 'g_displayname_unit', 'g_sig_unit', 'g_mod_preview', 'g_ppd_limit', 'g_ppd_unit', 'gbw_no_status_update', 'g_max_bgimg_upload', 'gbw_disable_prefixes', 'gbw_disable_tagging' ),
|
||||
'callback' => array(
|
||||
'g_edit_cutoff' => array( $this, '_editCutoff' ),
|
||||
'g_attach_per_post' => array( $this, '_attachMaxPerPost' ),
|
||||
@@ -53,10 +53,39 @@ class _GroupLimits
|
||||
'g_can_report' => array( $this, '_perAppSettings' ),
|
||||
'g_club_allowed_nodes' => array( $this, '_clubNodes' ),
|
||||
'g_create_clubs' => array( $this, '_createClub' ),
|
||||
'g_hide_online_list' => array( $this, '_hideOnlineList' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow users in the group to login anonymously?
|
||||
*
|
||||
* @param array $a Group A's values
|
||||
* @param array $b Group B's values
|
||||
* @param string $k The key we want to get the combined value for
|
||||
* @param array $member Member data
|
||||
* @return mixed
|
||||
*/
|
||||
public function _hideOnlineList( $a, $b, $k, $member )
|
||||
{
|
||||
/* If any group is forced, use that as priority */
|
||||
if( $a[$k] == 1 OR $b[$k] == 1 )
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
/* Else if any group allows anonymous logins, allow them */
|
||||
elseif( $a[$k] == 0 OR $b[$k] == 0 )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
/* Otherwise disable them */
|
||||
else
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cutoff for editing - only use a group's value if the group is able to edit
|
||||
*
|
||||
|
||||
@@ -119,6 +119,6 @@ class _Ratings
|
||||
*/
|
||||
public function findByMember( $member )
|
||||
{
|
||||
return \IPS\Db::i()->select( "ip, count(*) AS count, 0 AS first, 0 AS last", 'core_ratings', array( '`member`=?', $member->member_id ), NULL, NULL, 'ip' )->setKeyField( 'ip' );
|
||||
return \IPS\Db::i()->select( "ip, count(*) AS count, MIN(rating_date) AS first, MAX(rating_date) AS last", 'core_ratings', array( '`member`=?', $member->member_id ), NULL, NULL, 'ip' )->setKeyField( 'ip' );
|
||||
}
|
||||
}
|
||||
@@ -124,7 +124,7 @@ class _ContentStatistics extends \IPS\core\MemberACPProfile\TabbedBlock
|
||||
$this->rawCounts[ $app ][ $class ] = 0;
|
||||
}
|
||||
|
||||
$count = $class::rawMemberPostCount( $member );
|
||||
$count = $class::memberPostCount( $member, TRUE );
|
||||
$sum += $count;
|
||||
$counts[ $app ][ $type ] += $count;
|
||||
$this->rawCounts[ $app ][ $class ] += $count;
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief ACP Member Profile Block
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 25 Feb 2021
|
||||
*/
|
||||
|
||||
namespace IPS\core\extensions\core\MemberACPProfileBlocks;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief ACP Member Profile Block
|
||||
*/
|
||||
class _Points extends \IPS\core\MemberACPProfile\Block
|
||||
{
|
||||
/**
|
||||
* Get output
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function output()
|
||||
{
|
||||
return \IPS\Theme::i()->getTemplate('memberprofile')->rank( $this->member );
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit Window
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function edit()
|
||||
{
|
||||
if ( \IPS\Member::loggedIn()->hasAcpRestriction( 'core', 'members', 'member_edit' ) )
|
||||
{
|
||||
\IPS\Output::i()->sidebar['actions']['addpoints'] = array(
|
||||
'primary' => true,
|
||||
'icon' => 'pencil',
|
||||
'link' => $this->member->acpUrl()->setQueryString('do', 'points')->csrf(),
|
||||
'data' => array( 'ipsDialog' => '', 'ipsDialog-title' => \IPS\Member::loggedIn()->language()->addToStack('acp_profile_points_manage_title' ) ),
|
||||
'title' => \IPS\Member::loggedIn()->language()->addToStack('acp_profile_edit_points', NULL, [ 'sprintf' => $this->member->name ] ),
|
||||
);
|
||||
}
|
||||
|
||||
$table = new \IPS\Helpers\Table\Db( 'core_points_log', $this->member->acpUrl()->setQueryString( array( 'do' => 'editBlock', 'block' => \get_class( $this ) ) ), [ [ 'member=?', $this->member->member_id ] ] );
|
||||
$table->joins[] = [
|
||||
'select' => 'action,identifier',
|
||||
'from' => 'core_achievements_log',
|
||||
'where' => 'core_achievements_log.id=core_points_log.action_log',
|
||||
'type' => 'LEFT'
|
||||
];
|
||||
|
||||
$table->include = [ 'action_log', 'new_rank', 'points', 'balance', 'datetime' ];
|
||||
$table->sortBy = $table->sortBy ?: 'datetime';
|
||||
$table->langPrefix = 'acp_points_log_table_';
|
||||
|
||||
/* Filters */
|
||||
$table->filters = [
|
||||
'acp_manage_points_manual' => '(LENGTH(core_points_log.rules)=0)',
|
||||
'acp_manage_points_rule' => '(LENGTH(core_points_log.rules)>0)',
|
||||
'acp_manage_points_promote' => '(new_rank IS NOT NULL)'
|
||||
];
|
||||
|
||||
$table->parsers = [
|
||||
'action_log' => function( $val, $row ) {
|
||||
$exploded = explode( '_', $row['action'] );
|
||||
|
||||
if ( isset( $exploded[1] ) )
|
||||
{
|
||||
$extension = \IPS\Application::load( $exploded[0] )->extensions( 'core', 'AchievementAction' )[$exploded[1]];
|
||||
return $extension->logRow( $row['identifier'], explode( ',', $row['actor'] ) );
|
||||
}
|
||||
else if ( isset( $row['rules'] ) and empty( $row['rules'] ) )
|
||||
{
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( 'acp_points_log_manual');
|
||||
}
|
||||
else
|
||||
{
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( 'acp_points_rule_deleted');
|
||||
}
|
||||
},
|
||||
'datetime' => function( $val ) {
|
||||
return \IPS\DateTime::ts( $val );
|
||||
},
|
||||
'new_rank' => function( $val, $row )
|
||||
{
|
||||
if ( ! $val )
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$rank = \IPS\core\Achievements\Rank::load( $val );
|
||||
return \IPS\Theme::i()->getTemplate( 'global' )->genericLink( \IPS\Http\Url::internal( "app=core&module=achievements&controller=ranks", 'admin' ), $rank->_title );
|
||||
}
|
||||
catch( \Exception $e )
|
||||
{
|
||||
return \IPS\Member::loggedIn()->language()->addToStack('deleted');
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('acp_profile_points_manage_title');
|
||||
return \IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'members' )->pointsLog( $table, $this->member );
|
||||
}
|
||||
}
|
||||
@@ -64,7 +64,7 @@ class _ProfileData extends \IPS\core\MemberACPProfile\TabbedBlock
|
||||
public function tabs()
|
||||
{
|
||||
$return = array();
|
||||
if ( \count( $this->fields ) || $this->member->member_title || $this->member->rank['title'] || $this->member->rank['image'] || \IPS\Settings::i()->profile_birthday_type != 'none' || \IPS\Settings::i()->signatures_enabled )
|
||||
if ( \count( $this->fields ) || $this->member->rank['title'] || $this->member->rank['image'] || \IPS\Settings::i()->profile_birthday_type != 'none' || \IPS\Settings::i()->signatures_enabled )
|
||||
{
|
||||
$return['fields'] = 'profile_fields';
|
||||
}
|
||||
@@ -113,7 +113,6 @@ class _ProfileData extends \IPS\core\MemberACPProfile\TabbedBlock
|
||||
/* Build basic form */
|
||||
$form = new \IPS\Helpers\Form;
|
||||
$form->addHeader('profile_data');
|
||||
$form->add( new \IPS\Helpers\Form\Text( 'member_title', $this->member->member_title, FALSE, array( 'maxLength' => 64 ) ) );
|
||||
if ( \IPS\Settings::i()->profile_birthday_type !== 'none' )
|
||||
{
|
||||
$form->add( new \IPS\Helpers\Form\Custom( 'bday', array( 'year' => $this->member->bday_year, 'month' => $this->member->bday_month, 'day' => $this->member->bday_day ), FALSE, array( 'getHtml' => function( $element )
|
||||
@@ -188,7 +187,6 @@ class _ProfileData extends \IPS\core\MemberACPProfile\TabbedBlock
|
||||
\IPS\Db::i()->replace( 'core_pfields_content', $profileFields );
|
||||
|
||||
/* Profile Preferences */
|
||||
$this->member->member_title = $values['member_title'];
|
||||
if ( $values['bday'] )
|
||||
{
|
||||
$this->member->bday_day = $values['bday']['day'];
|
||||
|
||||
@@ -59,6 +59,7 @@ class _Main extends \IPS\core\MemberACPProfile\MainTab
|
||||
return array(
|
||||
'IPS\core\extensions\core\MemberACPProfileBlocks\Header',
|
||||
'IPS\core\extensions\core\MemberACPProfileBlocks\ContentStatistics',
|
||||
'IPS\core\extensions\core\MemberACPProfileBlocks\Points',
|
||||
'IPS\core\extensions\core\MemberACPProfileBlocks\Quotas',
|
||||
'IPS\core\extensions\core\MemberACPProfileBlocks\Warnings',
|
||||
'IPS\core\extensions\core\MemberACPProfileBlocks\ProfileData',
|
||||
|
||||
@@ -43,7 +43,6 @@ class _Main
|
||||
'last_post',
|
||||
'birthday',
|
||||
'allow_admin_mails',
|
||||
'member_title'
|
||||
);
|
||||
|
||||
foreach( $columns as $col )
|
||||
|
||||
@@ -56,11 +56,12 @@ class _Group
|
||||
}
|
||||
|
||||
return array(
|
||||
new \IPS\Helpers\Form\Select( 'bmf_members_groups', ( isset( $criteria['groups'] ) AND $criteria['groups'] != 'all' ) ? explode( ',', $criteria['groups'] ) : 'all', FALSE, array(
|
||||
new \IPS\Helpers\Form\CheckboxSet( 'bmf_members_groups', ( isset( $criteria['groups'] ) AND $criteria['groups'] != 'all' ) ? explode( ',', $criteria['groups'] ) : 'all', FALSE, array(
|
||||
'options' => $criteria['options'],
|
||||
'multiple' => TRUE,
|
||||
'unlimited' => 'all',
|
||||
'unlimitedLang' => 'all_groups'
|
||||
'unlimitedLang' => 'all_groups',
|
||||
'impliedUnlimited' => TRUE
|
||||
) )
|
||||
);
|
||||
}
|
||||
|
||||
@@ -43,7 +43,9 @@ class _Core
|
||||
'mfa',
|
||||
'oauth',
|
||||
'admin_mails',
|
||||
'terms_acceptance'
|
||||
'terms_acceptance',
|
||||
'points',
|
||||
'badges'
|
||||
);
|
||||
}
|
||||
|
||||
@@ -62,7 +64,7 @@ class _Core
|
||||
$byStaff = '';
|
||||
if ( $row['log_by'] )
|
||||
{
|
||||
if ( $row['log_by'] === $row['log_member'] )
|
||||
if ( $row['log_by'] === $row['log_member'] and ( $row['log_type'] !== 'points' and $row['log_type'] !== 'badges' ) )
|
||||
{
|
||||
$byMember = \IPS\Member::loggedIn()->language()->addToStack('history_by_member');
|
||||
}
|
||||
@@ -559,7 +561,7 @@ class _Core
|
||||
$consequences = array();
|
||||
if ( isset( $jsonValue['consequences'] ) )
|
||||
{
|
||||
foreach ( array( 'modq' => 'mod_posts', 'nopost' => 'restrict_post', 'banned' => 'temp_ban' ) as $k => $v )
|
||||
foreach ( array( 'modq' => 'mod_posts', 'nopost' => 'restrict_post', 'banned' => 'temp_ban', 'cheev_point_reduction' => 'cheev_point_reduction' ) as $k => $v )
|
||||
{
|
||||
if ( isset( $jsonValue['consequences'][ $v ] ) )
|
||||
{
|
||||
@@ -576,16 +578,23 @@ class _Core
|
||||
$consequences = array();
|
||||
if ( isset( $jsonValue['consequences'] ) )
|
||||
{
|
||||
foreach ( array( 'modq' => 'mod_posts', 'nopost' => 'restrict_post', 'banned' => 'temp_ban' ) as $k => $v )
|
||||
foreach ( array( 'modq' => 'mod_posts', 'nopost' => 'restrict_post', 'banned' => 'temp_ban', 'cheev_point_reduction' => 'cheev_point_reduction' ) as $k => $v )
|
||||
{
|
||||
if ( isset( $jsonValue['consequences'][ $v ] ) )
|
||||
{
|
||||
$c = \IPS\Member::loggedIn()->language()->addToStack( 'moderation_' . $k );
|
||||
if ( $jsonValue['consequences'][ $v ] != -1 )
|
||||
if( $v == 'cheev_point_reduction' )
|
||||
{
|
||||
$c = \IPS\Member::loggedIn()->language()->addToStack( 'history_received_warning_penalty_time', FALSE, array( 'sprintf' => array( $c, \IPS\DateTime::formatInterval( new \DateInterval( $jsonValue['consequences'][ $v ] ), 2 ) ) ) );
|
||||
$consequences[]= \IPS\Member::loggedIn()->language()->addToStack( 'moderation_cheev_point_reduction', FALSE, array('sprintf' => array( $jsonValue['consequences'][ $v ] ) ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
$c = \IPS\Member::loggedIn()->language()->addToStack( 'moderation_' . $k );
|
||||
if ( $jsonValue['consequences'][$v] != -1 )
|
||||
{
|
||||
$c = \IPS\Member::loggedIn()->language()->addToStack( 'history_received_warning_penalty_time', FALSE, array('sprintf' => array($c, \IPS\DateTime::formatInterval( new \DateInterval( $jsonValue['consequences'][$v] ), 2 ))) );
|
||||
}
|
||||
$consequences[] = $c;
|
||||
}
|
||||
$consequences[] = $c;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -696,8 +705,48 @@ class _Core
|
||||
return \IPS\Member::loggedIn()->language()->addToStack('history_terms_accepted_terms');
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'points':
|
||||
if ( isset( $jsonValue['recognize'] ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
$recognize = \IPS\core\Achievements\Recognize::load( $jsonValue['recognize'] );
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( 'history_recognize_points_adjustment', FALSE, array( 'sprintf' => array( $recognize->points, $recognize->content()->url(), $recognize->content()->indefiniteArticle() ) ) );
|
||||
|
||||
}
|
||||
catch( \Exception $e )
|
||||
{
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( 'history_recognize_points_adjustment_deleted' );
|
||||
}
|
||||
}
|
||||
else if ( isset( $jsonValue['by'] ) and $jsonValue['by'] === 'manual' )
|
||||
{
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( 'history_manual_points_adjustment', FALSE, array( 'htmlsprintf' => array( \intval( $jsonValue['old'] ), \intval( $jsonValue['new'] ), $byMember ?: $byStaff ) ) );
|
||||
}
|
||||
|
||||
break;
|
||||
case 'badges':
|
||||
if ( isset( $jsonValue['action'] ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
$badge = \IPS\core\Achievements\Badge::load( $jsonValue['id'] );
|
||||
|
||||
if ( $jsonValue['action'] == 'manual' )
|
||||
{
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( 'history_manual_badge_addition', FALSE, array('htmlsprintf' => array( $badge->_title, $byMember ?: $byStaff ) ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( 'history_manual_badge_deletion', FALSE, array('htmlsprintf' => array( $badge->_title, $byMember ?: $byStaff ) ) );
|
||||
}
|
||||
}
|
||||
catch( \Exception $e ) { }
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
$member->logHistory( 'core', 'badges', [ 'action' => 'manual', 'id' => $values['acp_manual_badge']->_id ] );
|
||||
return $value;
|
||||
}
|
||||
|
||||
|
||||
@@ -107,7 +107,10 @@ class _System
|
||||
array( 'core_members_known_devices', 'member_id' ),
|
||||
array( 'core_members_known_ip_addresses', 'member_id' ),
|
||||
array( 'core_streams', 'member' ),
|
||||
array( 'core_rss_import', 'rss_import_member' )
|
||||
array( 'core_rss_import', 'rss_import_member' ),
|
||||
array( 'core_anonymous_posts', 'anonymous_member_id' ),
|
||||
array( 'core_member_badges', 'member' ),
|
||||
array( 'core_notifications_pwa_keys', 'member' )
|
||||
) as $toMerge
|
||||
)
|
||||
{
|
||||
@@ -200,6 +203,27 @@ class _System
|
||||
{
|
||||
\IPS\Db::i()->delete( 'core_follow', array( "follow_id IN('" . implode( "','", $duplicateFollows ) . "')" ) );
|
||||
}
|
||||
|
||||
/* Remove duplicate PWA subscriptions */
|
||||
$duplicatePwa = array();
|
||||
$uniquePwa = array();
|
||||
foreach( \IPS\Db::i()->select( '*', 'core_notifications_pwa_keys', array( "`member`=?", $member->member_id ) ) AS $pwa )
|
||||
{
|
||||
$key = "{$pwa['p256dh']}-{$pwa['auth']}";
|
||||
if ( !\in_array( $key, $uniquePwa ) )
|
||||
{
|
||||
$uniquePwa[ $pwa['id'] ] = $key;
|
||||
}
|
||||
else
|
||||
{
|
||||
$duplicatePwa[ $pwa['id'] ] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
if ( \count( $duplicatePwa ) )
|
||||
{
|
||||
\IPS\Db::i()->delete( 'core_notifications_pwa_keys', array( \IPS\Db::i()->in( 'id', \array_keys( $duplicatePwa ) ) ) );
|
||||
}
|
||||
|
||||
/* Set warning level */
|
||||
$member->warn_level += $member2->warn_level;
|
||||
@@ -279,7 +303,10 @@ class _System
|
||||
array( 'core_streams', 'member' ),
|
||||
array( 'core_referrals', 'referred_by' ),
|
||||
array( 'core_referrals', 'member_id' ),
|
||||
array( 'core_marketplace_tokens', 'id' )
|
||||
array( 'core_anonymous_posts', 'anonymous_member_id' ),
|
||||
array( 'core_marketplace_tokens', 'id' ),
|
||||
array( 'core_member_badges', 'member' ),
|
||||
array( 'core_notifications_pwa_keys', 'member' )
|
||||
) as $toDelete )
|
||||
{
|
||||
\IPS\Db::i()->delete( $toDelete[0], array( '`' . $toDelete[1] . '`=?', $member->member_id ) );
|
||||
@@ -303,10 +330,10 @@ class _System
|
||||
\IPS\Db::i()->delete( 'core_follow', array( 'follow_app=? AND follow_area=? AND follow_rel_id=?', 'core', 'member', $member->member_id ) );
|
||||
\IPS\Db::i()->delete( 'core_follow_count_cache', array( 'id=? AND class=?', $member->member_id, 'IPS\core\Member' ) );
|
||||
\IPS\Db::i()->delete( 'core_moderators', array( 'id=? AND type=?', $member->member_id, 'm' ) );
|
||||
\IPS\Db::i()->delete( 'core_member_recognize', array( 'r_member_id=? OR r_given_by=?', $member->member_id, $member->member_id ) );
|
||||
|
||||
unset ( \IPS\Data\Store::i()->moderators );
|
||||
|
||||
|
||||
/* Remove the group from the staff directory */
|
||||
foreach ( \IPS\core\StaffDirectory\User::roots() as $staff )
|
||||
{
|
||||
@@ -320,8 +347,8 @@ class _System
|
||||
\IPS\File::unclaimAttachments( 'core_Staffdirectory', $member->member_id );
|
||||
\IPS\File::unclaimAttachments( 'core_Members', NULL, NULL, $member->member_id );
|
||||
|
||||
|
||||
$member->deletePhoto();
|
||||
$member->coverPhoto()->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -353,7 +380,7 @@ class _System
|
||||
foreach( $groupIds as $id )
|
||||
{
|
||||
$key = 'groupMembersCount_' . $id;
|
||||
unset( \IPS\Data\Cache::i()->$key );
|
||||
unset( \IPS\Data\Store::i()->$key );
|
||||
}
|
||||
|
||||
/* Reset profile completion flag to see if there are any items to complete now permissions are elevated */
|
||||
|
||||
@@ -83,7 +83,7 @@ class _ContentMessages
|
||||
* @param bool $isPublic Who should see the message
|
||||
* @return int
|
||||
*/
|
||||
public function addMessage( $message, $color = NULL, \IPS\Content\Item $item, \IPS\Member $member = NULL, bool $isPublic = TRUE )
|
||||
public function addMessage( $message, $color, \IPS\Content\Item $item, \IPS\Member $member = NULL, bool $isPublic = TRUE )
|
||||
{
|
||||
$member = $member ?: \IPS\Member::loggedIn();
|
||||
|
||||
@@ -109,7 +109,7 @@ class _ContentMessages
|
||||
* @param bool $isPublic Who should see the message
|
||||
* @return void
|
||||
*/
|
||||
public function editMessage( $id, $message, $color = NULL, \IPS\Content\Item $item, \IPS\Member $member = NULL, bool $isPublic = TRUE )
|
||||
public function editMessage( $id, $message, $color, \IPS\Content\Item $item, \IPS\Member $member = NULL, bool $isPublic = TRUE )
|
||||
{
|
||||
$member = $member ?: \IPS\Member::loggedIn();
|
||||
|
||||
|
||||
@@ -94,11 +94,11 @@ class _ItemModeration
|
||||
|
||||
try
|
||||
{
|
||||
return $item::modPermission( 'can_toggle_item_moderation', $member, $item->container() );
|
||||
return $item::modPermission( 'toggle_item_moderation', $member, $item->container() );
|
||||
}
|
||||
catch( \BadMethodCallException $e )
|
||||
{
|
||||
return $member->modPermission( 'can_toggle_item_moderation' );
|
||||
return $member->modPermission( 'can_toggle_item_moderation_content' );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ class _ItemModeration
|
||||
* @param \IPS\Member|NULL $member The member enabling moderation, or NULL for currently logged in member.
|
||||
* @return void
|
||||
*/
|
||||
public function enable( \IPS\Content\Item $item, ?\IPS\Member $member = NULL ): void
|
||||
public function enable( \IPS\Content\Item $item, ?\IPS\Member $member = NULL )
|
||||
{
|
||||
$member = $member ?: \IPS\Member::loggedIn();
|
||||
|
||||
@@ -141,7 +141,7 @@ class _ItemModeration
|
||||
* @return void
|
||||
* @throws \OutOfRangeException
|
||||
*/
|
||||
public function disable( \IPS\Content\Item $item ): void
|
||||
public function disable( \IPS\Content\Item $item )
|
||||
{
|
||||
$meta = $item->getMeta();
|
||||
|
||||
|
||||
@@ -109,74 +109,21 @@ class _Deleted
|
||||
switch( \IPS\Request::i()->modaction )
|
||||
{
|
||||
case 'restore':
|
||||
if ( isset( $class::$databaseColumnMap['hidden'] ) )
|
||||
{
|
||||
$column = $class::$databaseColumnMap['hidden'];
|
||||
$content->$column = 0;
|
||||
}
|
||||
else if ( isset( $class::$databaseColumnMap['approved'] ) )
|
||||
{
|
||||
$column = $class::$databaseColumnMap['approved'];
|
||||
$content->$column = 1;
|
||||
}
|
||||
|
||||
\IPS\Session::i()->modLog( 'modlog__action_restore', array(
|
||||
$content::$title => FALSE,
|
||||
$content->url()->__toString() => FALSE
|
||||
) );
|
||||
|
||||
try
|
||||
{
|
||||
if ( $content->container() )
|
||||
{
|
||||
if ( $content->container()->_comments !== NULL )
|
||||
{
|
||||
if ( $content instanceof \IPS\Content\Item )
|
||||
{
|
||||
$content->container()->_comments = ( $content->container()->_comments + $content->mapped('num_comments') );
|
||||
}
|
||||
else
|
||||
{
|
||||
$content->container()->_comments = $content->container()->_comments + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $content->container()->_unapprovedComments !== NULL )
|
||||
{
|
||||
if ( $content instanceof \IPS\Content\Item )
|
||||
{
|
||||
$content->container()->_unapprovedComments = ( $content->container()->_unapprovedComments + $content->mapped('unapproved_comments') );
|
||||
}
|
||||
}
|
||||
$content->container()->setLastComment();
|
||||
$content->container()->save();
|
||||
}
|
||||
}
|
||||
catch( \BadMethodCallException $e ) {}
|
||||
|
||||
$content->save();
|
||||
$log->delete();
|
||||
$content->modAction( 'restore' );
|
||||
break;
|
||||
|
||||
case 'restore_as_hidden':
|
||||
if ( isset( $class::$databaseColumnMap['hidden'] ) )
|
||||
{
|
||||
$column = $class::$databaseColumnMap['hidden'];
|
||||
}
|
||||
else if ( isset( $class::$databaseColumnMap['approved'] ) )
|
||||
{
|
||||
$column = $class::$databaseColumnMap['approved'];
|
||||
}
|
||||
|
||||
$content->$column = -1;
|
||||
|
||||
\IPS\Session::i()->modLog( 'modlog__action_restore_hidden', array(
|
||||
$content::$title => FALSE,
|
||||
$content->url()->__toString() => FALSE
|
||||
) );
|
||||
|
||||
$content->save();
|
||||
$log->delete();
|
||||
$content->modAction( 'restoreAsHidden' );
|
||||
break;
|
||||
|
||||
case 'delete':
|
||||
|
||||
@@ -58,7 +58,14 @@ class _Members
|
||||
$form = new \IPS\Helpers\Form( 'form', 'edit' );
|
||||
|
||||
$form->class = 'ipsForm_vertical';
|
||||
$form->add( new \IPS\Helpers\Form\Member( 'modcp_member_find', NULL, TRUE, array( 'multiple' => 1, 'placeholder' => \IPS\Member::loggedIn()->language()->addToStack('modcp_member_find') ) ) );
|
||||
$form->add( new \IPS\Helpers\Form\Member( 'modcp_member_find', NULL, TRUE, array( 'multiple' => 1, 'placeholder' => \IPS\Member::loggedIn()->language()->addToStack('modcp_member_find'), 'autocomplete' => array(
|
||||
'source' => 'app=core&module=system&controller=ajax&do=findMember&type=mod', 'resultItemTemplate' => 'core.autocomplete.memberItem',
|
||||
'commaTrigger' => false,
|
||||
'unique' => true,
|
||||
'minAjaxLength' => 3,
|
||||
'disallowedCharacters' => array(),
|
||||
'lang' => 'mem_optional',
|
||||
) ) ) );
|
||||
|
||||
if ( $values = $form->values() )
|
||||
{
|
||||
|
||||
@@ -73,16 +73,48 @@ class _Reports extends \IPS\Content\Controller
|
||||
*/
|
||||
public function manage()
|
||||
{
|
||||
$where = '( perm_id IN (?) OR perm_id IS NULL )';
|
||||
$where = [];
|
||||
|
||||
/* Make sure we're only loading reports where we have permission to view the content */
|
||||
$classWhere = [];
|
||||
$classBinds = [];
|
||||
$viewPermIds = iterator_to_array( \IPS\Db::i()->select( 'perm_id', 'core_permission_index', \IPS\Db::i()->findInSet( 'perm_view', \IPS\Member::loggedIn()->permissionArray() ) . " OR perm_view='*'" ) );
|
||||
$permIds = [];
|
||||
foreach ( array_merge( [ 'IPS\core\Messenger\Conversation', 'IPS\core\Messenger\Message' ], array_values( \IPS\Content::routedClasses( FALSE, TRUE ) ) ) as $class )
|
||||
{
|
||||
$workingClass = $class;
|
||||
if ( isset( $workingClass::$itemClass ) )
|
||||
{
|
||||
$workingClass = $workingClass::$itemClass;
|
||||
}
|
||||
if ( isset( $workingClass::$containerNodeClass ) )
|
||||
{
|
||||
$workingClass = $workingClass::$containerNodeClass;
|
||||
}
|
||||
if ( isset( $workingClass::$permissionMap ) and isset( $workingClass::$permissionMap['read'] ) and $workingClass::$permissionMap['read'] !== 'view' )
|
||||
{
|
||||
if ( !isset( $permIds[ $workingClass::$permissionMap['read'] ] ) )
|
||||
{
|
||||
$permIds[ $workingClass::$permissionMap['read'] ] = iterator_to_array( \IPS\Db::i()->select( 'perm_id', 'core_permission_index', \IPS\Db::i()->findInSet( 'perm_' . $workingClass::$permissionMap['read'], \IPS\Member::loggedIn()->permissionArray() ) . " OR perm_" . $workingClass::$permissionMap['read'] . "='*'" ) );
|
||||
}
|
||||
|
||||
$classWhere[] = "( class='" . str_replace( '\\', '\\\\', $class ) . "' AND ( perm_id IN(" . implode( ',', array_intersect( $viewPermIds, $permIds[ $workingClass::$permissionMap['read'] ] ) ) . ") ) OR perm_id IS NULL )";
|
||||
}
|
||||
else
|
||||
{
|
||||
$classWhere[] = "( class='" . str_replace( '\\', '\\\\', $class ) . "' AND ( perm_id IN(" . implode( ',', $viewPermIds ) . ") ) OR perm_id IS NULL )";
|
||||
}
|
||||
}
|
||||
$where[] = array( '(' . implode( ' OR ', $classWhere ) . ')' );
|
||||
|
||||
/* Make sure we're loading the correct statuses */
|
||||
if( \IPS\Request::i()->isAjax() and isset( \IPS\Request::i()->overview ) )
|
||||
{
|
||||
$where .= " AND status IN( 1,2 )";
|
||||
$where[] = array( "status IN( 1,2 )" );
|
||||
}
|
||||
|
||||
/* fetch only reports for content of enabled applications */
|
||||
$where .= " AND " . \IPS\Db::i()->in( 'class', array_merge( array( 'IPS\core\Messenger\Conversation', 'IPS\core\Messenger\Message' ), array_values( \IPS\Content::routedClasses( FALSE, TRUE ) ) ) );
|
||||
|
||||
$table = new \IPS\Helpers\Table\Content( '\IPS\core\Reports\Report', \IPS\Http\Url::internal( 'app=core&module=modcp&controller=modcp&tab=reports', NULL, 'modcp_reports' ), array( array( $where, \IPS\Db::i()->select( 'perm_id', 'core_permission_index', \IPS\Db::i()->findInSet( 'perm_view', \IPS\Member::loggedIn()->permissionArray() ) . " OR perm_view='*'" ) ) ) );
|
||||
|
||||
/* Create table */
|
||||
$table = new \IPS\Helpers\Table\Content( '\IPS\core\Reports\Report', \IPS\Http\Url::internal( 'app=core&module=modcp&controller=modcp&tab=reports', NULL, 'modcp_reports' ), $where );
|
||||
$table->sortBy = $table->sortBy ?: 'first_report_date';
|
||||
$table->sortDirection = 'desc';
|
||||
|
||||
@@ -124,6 +156,27 @@ class _Reports extends \IPS\Content\Controller
|
||||
\IPS\Output::i()->error( 'node_error', '2C139/3', 404, '' );
|
||||
}
|
||||
|
||||
/* 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( \IPS\Db::i()->select( 'perm_id', 'core_permission_index', \IPS\Db::i()->findInSet( 'perm_view', \IPS\Member::loggedIn()->permissionArray() ) . " OR perm_view='*'" ) );
|
||||
$workingClass = $report->class;
|
||||
if ( isset( $workingClass::$itemClass ) )
|
||||
{
|
||||
$workingClass = $workingClass::$itemClass;
|
||||
}
|
||||
if ( isset( $workingClass::$containerNodeClass ) )
|
||||
{
|
||||
$workingClass = $workingClass::$containerNodeClass;
|
||||
}
|
||||
if ( isset( $workingClass::$permissionMap ) and isset( $workingClass::$permissionMap['read'] ) and $workingClass::$permissionMap['read'] !== 'view' )
|
||||
{
|
||||
$allowedPermIds = array_intersect( $allowedPermIds, iterator_to_array( \IPS\Db::i()->select( 'perm_id', 'core_permission_index', \IPS\Db::i()->findInSet( 'perm_' . $workingClass::$permissionMap['read'], \IPS\Member::loggedIn()->permissionArray() ) . " OR perm_" . $workingClass::$permissionMap['read'] . "='*'" ) ) );
|
||||
}
|
||||
|
||||
if ( \in_array( 'IPS\Content\Permissions', class_implements( $workingClass ) ) and !\in_array( $report->perm_id, $allowedPermIds ) )
|
||||
{
|
||||
\IPS\Output::i()->error( 'node_error_no_perm', '2C139/5', 403, '' );
|
||||
}
|
||||
|
||||
/* Setting status? */
|
||||
if( isset( \IPS\Request::i()->setStatus ) and \in_array( \IPS\Request::i()->setStatus, range( 1, 3 ) ) )
|
||||
{
|
||||
|
||||
@@ -64,7 +64,15 @@ class _Content
|
||||
'can_add_item_message_content' => array( 'YesNo', array( 'togglesOff' => $toggles['add_item_message'] ) ),
|
||||
'can_edit_item_message_content' => array( 'YesNo', array( 'togglesOff' => $toggles['edit_item_message'] ) ),
|
||||
'can_delete_item_message_content'=> array( 'YesNo', array( 'togglesOff' => $toggles['delete_item_message'] ) ),
|
||||
'can_toggle_item_moderation' => array( 'YesNo', array( 'togglesOff' => $toggles['toggle_item_moderation'] ) ),
|
||||
'can_toggle_item_moderation_content' => array( 'YesNo', array( 'togglesOff' => $toggles['toggle_item_moderation'] ) ),
|
||||
'can_recognize_content' => array( 'YesNo', array( 'togglesOn' => [ 'can_recognize_content_options', 'can_recognize_content_no_point_limit' ] ) ),
|
||||
'can_recognize_content_options' => array( 'CheckboxSet', array(
|
||||
'options' => [
|
||||
'points' => 'can_recognize_content_points',
|
||||
'badges' => 'can_recognize_content_badges',
|
||||
]
|
||||
) ),
|
||||
'can_recognize_content_no_point_limit' => 'YesNo'
|
||||
);
|
||||
|
||||
if ( \IPS\Settings::i()->edit_log == 2 )
|
||||
@@ -81,6 +89,7 @@ class _Content
|
||||
$return['can_close_polls'] = 'YesNo';
|
||||
|
||||
$return['can_manage_deleted_content'] = 'YesNo';
|
||||
$return['can_view_anonymous_posters'] = 'YesNo';
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,270 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Notification Options
|
||||
* @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 18 Feb 2021
|
||||
*/
|
||||
|
||||
namespace IPS\core\extensions\core\Notifications;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notification Options
|
||||
*/
|
||||
class _Achievements
|
||||
{
|
||||
/**
|
||||
* Get fields for configuration
|
||||
*
|
||||
* @param \IPS\Member|null $member The member (to take out any notification types a given member will never see) or NULL if this is for the ACP
|
||||
* @return array
|
||||
*/
|
||||
public static function configurationOptions( \IPS\Member $member = NULL )
|
||||
{
|
||||
// Return an array defining each notification type, including the form elements necessary to allow its configuration
|
||||
|
||||
return array(
|
||||
'core_Achievements' => array(
|
||||
'type' => 'standard',
|
||||
'notificationTypes' => array( 'new_rank', 'new_badge', 'new_recognize' ),
|
||||
'title' => 'notifications__core_Achievements',
|
||||
'showTitle' => FALSE,
|
||||
'description' => 'notifications__core_Achievements_desc',
|
||||
'default' => array( 'inline' ),
|
||||
'disabled' => array()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse notification: new_rank
|
||||
*
|
||||
* @param \IPS\Notification\Inline $notification The notification
|
||||
* @param bool $htmlEscape TRUE to escape HTML in title
|
||||
* @return array
|
||||
* @code
|
||||
return array(
|
||||
'title' => "Mark has replied to A Topic", // The notification title
|
||||
'url' => \IPS\Http\Url::internal( ... ), // The URL the notification should link to
|
||||
'content' => "Lorem ipsum dolar sit", // [Optional] Any appropriate content. Do not format this like an email where the text
|
||||
// explains what the notification is about - just include any appropriate content.
|
||||
// For example, if the notification is about a post, set this as the body of the post.
|
||||
'author' => \IPS\Member::load( 1 ), // [Optional] The user whose photo should be displayed for this notification
|
||||
);
|
||||
* @endcode
|
||||
*/
|
||||
public function parse_new_rank( \IPS\Notification\Inline $notification, $htmlEscape=TRUE )
|
||||
{
|
||||
if ( $notification->extra )
|
||||
{
|
||||
/* \IPS\Notification->extra will always be an array, but for bulk content notifications we are only storing a single badge ID,
|
||||
so we need to grab just the one array entry (the badge ID we stored) */
|
||||
$rankId = $notification->extra;
|
||||
|
||||
if( \is_array( $rankId ) )
|
||||
{
|
||||
$rankId = array_pop( $rankId );
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$rank = \IPS\core\Achievements\Rank::load( $rankId );
|
||||
$title = $rank->_title;
|
||||
}
|
||||
catch( \Exception $e )
|
||||
{
|
||||
throw new \OutOfRangeException;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$title = \IPS\Member::loggedIn()->rank['title'];
|
||||
}
|
||||
|
||||
return array(
|
||||
'title' => \IPS\Member::loggedIn()->language()->addToStack( 'notification__new_rank', FALSE, array( 'sprintf' => array( $title ) ) ),
|
||||
'url' => \IPS\Member::loggedIn()->url(),
|
||||
'author' => \IPS\Member::loggedIn()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse notification for mobile: new_rank
|
||||
*
|
||||
* @param \IPS\Lang $language The language that the notification should be in
|
||||
* @param \IPS\Member $member The member that earned the rank
|
||||
* @return array
|
||||
*/
|
||||
public static function parse_mobile_new_rank( \IPS\Lang $language, \IPS\Member $member )
|
||||
{
|
||||
return array(
|
||||
'body' => $language->addToStack( 'notification__new_rank', FALSE, array( 'sprintf' => array( $member->rank['title'] ) ) ),
|
||||
'data' => array(
|
||||
'url' => (string) $member->url(),
|
||||
'author' => $member,
|
||||
),
|
||||
'channelId' => 'achievements',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse notification: new_badge
|
||||
*
|
||||
* @param \IPS\Notification\Inline $notification The notification
|
||||
* @param bool $htmlEscape TRUE to escape HTML in title
|
||||
* @return array
|
||||
* @code
|
||||
return array(
|
||||
'title' => "Mark has replied to A Topic", // The notification title
|
||||
'url' => \IPS\Http\Url::internal( ... ), // The URL the notification should link to
|
||||
'content' => "Lorem ipsum dolar sit", // [Optional] Any appropriate content. Do not format this like an email where the text
|
||||
// explains what the notification is about - just include any appropriate content.
|
||||
// For example, if the notification is about a post, set this as the body of the post.
|
||||
'author' => \IPS\Member::load( 1 ), // [Optional] The user whose photo should be displayed for this notification
|
||||
);
|
||||
* @endcode
|
||||
*/
|
||||
public function parse_new_badge( \IPS\Notification\Inline $notification, $htmlEscape=TRUE )
|
||||
{
|
||||
if ( $notification->extra )
|
||||
{
|
||||
/* \IPS\Notification->extra will always be an array, but for bulk content notifications we are only storing a single badge ID,
|
||||
so we need to grab just the one array entry (the badge ID we stored) */
|
||||
$badgeId = $notification->extra;
|
||||
|
||||
if( \is_array( $badgeId ) )
|
||||
{
|
||||
$badgeId = array_pop( $badgeId );
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$badge = \IPS\core\Achievements\Badge::load( $badgeId );
|
||||
$content = \IPS\Member::loggedIn()->language()->addToStack( 'notification__new_badge_content', NULL, [ 'sprintf' => [ $badge->_title ] ] );
|
||||
$title = \IPS\Member::loggedIn()->language()->addToStack( 'notification__new_badge', NULL, [ 'sprintf' => [ $badge->_title ] ] );
|
||||
}
|
||||
catch( \Exception $e )
|
||||
{
|
||||
throw new \OutOfRangeException;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$title = \IPS\Member::loggedIn()->language()->addToStack( 'mailsub__core_notification_new_badge' );
|
||||
$content = NULL;
|
||||
}
|
||||
|
||||
return array(
|
||||
'title' => $title,
|
||||
'url' => (string) \IPS\Http\Url::internal( "app=core&module=members&controller=profile&do=badges&id=" . \IPS\Member::loggedIn()->member_id, 'front', 'profile_badges', \IPS\Member::loggedIn()->members_seo_name ),
|
||||
'content' => $content,
|
||||
'author' => \IPS\Member::loggedIn()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse notification for mobile: new_badge
|
||||
*
|
||||
* @param \IPS\Lang $language The language that the notification should be in
|
||||
* @param \IPS\Member $member The member that earned the badge
|
||||
* @param \IPS\core\Achievements\Badge $badge The badge the member earned
|
||||
* @return array
|
||||
*/
|
||||
public static function parse_mobile_new_badge( \IPS\Lang $language, \IPS\Member $member, \IPS\core\Achievements\Badge $badge )
|
||||
{
|
||||
return array(
|
||||
'body' => $language->addToStack( 'notification__new_badge', NULL, [ 'sprintf' => [ $badge->_title ] ] ),
|
||||
'data' => array(
|
||||
'url' => (string) \IPS\Http\Url::internal( "app=core&module=members&controller=profile&do=badges&id={$member->member_id}", 'front', 'profile_badges', $member->members_seo_name ),
|
||||
'author' => $member,
|
||||
),
|
||||
'channelId' => 'achievements',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse notification: new_badge
|
||||
*
|
||||
* @param \IPS\Notification\Inline $notification The notification
|
||||
* @param bool $htmlEscape TRUE to escape HTML in title
|
||||
* @return array
|
||||
* @code
|
||||
return array(
|
||||
'title' => "Mark has replied to A Topic", // The notification title
|
||||
'url' => \IPS\Http\Url::internal( ... ), // The URL the notification should link to
|
||||
'content' => "Lorem ipsum dolar sit", // [Optional] Any appropriate content. Do not format this like an email where the text
|
||||
// explains what the notification is about - just include any appropriate content.
|
||||
// For example, if the notification is about a post, set this as the body of the post.
|
||||
'author' => \IPS\Member::load( 1 ), // [Optional] The user whose photo should be displayed for this notification
|
||||
);
|
||||
* @endcode
|
||||
*/
|
||||
public function parse_new_recognize( \IPS\Notification\Inline $notification, $htmlEscape=TRUE )
|
||||
{
|
||||
if ( $notification->extra )
|
||||
{
|
||||
/* \IPS\Notification->extra will always be an array, but for bulk content notifications we are only storing a single badge ID,
|
||||
so we need to grab just the one array entry (the badge ID we stored) */
|
||||
$recognizeId = $notification->extra;
|
||||
|
||||
if( \is_array( $recognizeId ) )
|
||||
{
|
||||
$recognizeId = array_pop( $recognizeId );
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$recognize = \IPS\core\Achievements\Recognize::load( $recognizeId );
|
||||
$url = $recognize->content()->url();
|
||||
}
|
||||
catch( \Exception $e )
|
||||
{
|
||||
throw new \OutOfRangeException;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
$url = NULL;
|
||||
}
|
||||
|
||||
$content = \IPS\Member::loggedIn()->language()->addToStack( 'notification__new_recognized' );
|
||||
$title = \IPS\Member::loggedIn()->language()->addToStack( 'notification__new_recognized' );
|
||||
|
||||
return array(
|
||||
'title' => $title,
|
||||
'url' => (string) $url,
|
||||
'content' => $content,
|
||||
'author' => \IPS\Member::loggedIn()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse notification for mobile: new_badge
|
||||
*
|
||||
* @param \IPS\Lang $language The language that the notification should be in
|
||||
* @param \IPS\Member $member The member that earned the badge
|
||||
* @param \IPS\core\Achievements\Badge $badge The badge the member earned
|
||||
* @return array
|
||||
*/
|
||||
public static function parse_mobile_new_recognize( \IPS\Lang $language, \IPS\Member $member, \IPS\core\Achievements\Recognize $recognize )
|
||||
{
|
||||
return array(
|
||||
'body' => $language->addToStack( 'notification__new_recognized' ),
|
||||
'data' => array(
|
||||
'url' => $recognize->content()->url(),
|
||||
'author' => $member,
|
||||
),
|
||||
'channelId' => 'achievements',
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -66,7 +66,7 @@ class _BulkMails
|
||||
* @param bool $value The value
|
||||
* @return void
|
||||
*/
|
||||
public static function saveExtra( \IPS\Member $member = NULL, $key, $value )
|
||||
public static function saveExtra( ?\IPS\Member $member, $key, $value )
|
||||
{
|
||||
switch ( $key )
|
||||
{
|
||||
@@ -94,7 +94,7 @@ class _BulkMails
|
||||
* @param string $method The method type
|
||||
* @return void
|
||||
*/
|
||||
public static function disableExtra( \IPS\Member $member = NULL, $method )
|
||||
public static function disableExtra( ?\IPS\Member $member, $method )
|
||||
{
|
||||
if ( $method === 'email' and $member->allow_admin_mails )
|
||||
{
|
||||
|
||||
@@ -112,6 +112,7 @@ class _Clubs
|
||||
public static function parse_mobile_club_invitation( \IPS\Lang $language, \IPS\Member\Club $club, \IPS\Member $invitedBy )
|
||||
{
|
||||
return array(
|
||||
'title' => $language->addToStack( 'notification__club_invitation_by_title' ),
|
||||
'body' => $language->addToStack( $invitedBy->member_id ? 'notification__club_invitation_by' : 'notification__club_invitation_generic', FALSE, array( 'htmlsprintf' => array( $invitedBy->name, $club->name ) ) ),
|
||||
'data' => array(
|
||||
'url' => (string) $club->url(),
|
||||
@@ -168,8 +169,9 @@ class _Clubs
|
||||
public static function parse_mobile_club_response( \IPS\Lang $language, \IPS\Member\Club $club, $response )
|
||||
{
|
||||
$memberStatus = $club->memberStatus( \IPS\Member::loggedIn(), 2 );
|
||||
|
||||
|
||||
return array(
|
||||
'title' => $language->addToStack( "notification__club_response_title" ),
|
||||
'body' => $language->addToStack( $response ? 'notification__club_response_accepted' : 'notification__club_response_declined', FALSE, array( 'htmlsprintf' => array( $club->name ) ) ),
|
||||
'data' => array(
|
||||
'url' => (string) $club->url(),
|
||||
@@ -258,6 +260,7 @@ class _Clubs
|
||||
public static function parse_mobile_club_request( \IPS\Lang $language, \IPS\Member\Club $club, \IPS\Member $member )
|
||||
{
|
||||
return array(
|
||||
'title' => $language->addToStack( 'notification__club_request_title' ),
|
||||
'body' => $language->addToStack( 'notification__club_request', FALSE, array(
|
||||
'pluralize' => array( 1 ),
|
||||
'htmlsprintf' => array(
|
||||
@@ -352,6 +355,7 @@ class _Clubs
|
||||
public static function parse_mobile_club_join( \IPS\Lang $language, \IPS\Member\Club $club, \IPS\Member $member )
|
||||
{
|
||||
return array(
|
||||
'title' => $language->addToStack( 'notification__club_join_title' ),
|
||||
'body' => $language->addToStack( 'notification__club_join', FALSE, array(
|
||||
'pluralize' => array( 1 ),
|
||||
'htmlsprintf' => array(
|
||||
@@ -418,6 +422,7 @@ class _Clubs
|
||||
public static function parse_mobile_unapproved_club( \IPS\Lang $language, \IPS\Member\Club $club )
|
||||
{
|
||||
return array(
|
||||
'title' => $language->addToStack( 'notification__new_club_unapproved_title' ),
|
||||
'body' => $language->addToStack( 'notification__new_club_unapproved', FALSE, array( 'htmlsprintf' => array( $club->owner->name, $club->name ) ) ),
|
||||
'data' => array(
|
||||
'url' => (string) $club->url(),
|
||||
|
||||
@@ -127,7 +127,7 @@ class _Content
|
||||
* @param bool $value The value
|
||||
* @return void
|
||||
*/
|
||||
public static function saveExtra( \IPS\Member $member = NULL, $key, $value )
|
||||
public static function saveExtra( ?\IPS\Member $member, $key, $value )
|
||||
{
|
||||
switch ( $key )
|
||||
{
|
||||
@@ -178,9 +178,20 @@ class _Content
|
||||
* @param string $method The method type
|
||||
* @return void
|
||||
*/
|
||||
public static function disableExtra( \IPS\Member $member = NULL, $method )
|
||||
public static function disableExtra( ?\IPS\Member $member, $method )
|
||||
{
|
||||
// Do nothing
|
||||
/* If we are disabling all emails, set any digest follows to be "no notification" */
|
||||
if ( $method === 'email' )
|
||||
{
|
||||
\IPS\Db::i()->update( 'core_follow', [
|
||||
'follow_added' => time(),
|
||||
'follow_notify_do' => 0,
|
||||
'follow_notify_freq' => 'none',
|
||||
], [
|
||||
[ 'follow_member_id=?', $member->member_id ],
|
||||
[ "( follow_notify_freq='daily' OR follow_notify_freq='weekly' )" ],
|
||||
] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -232,10 +243,12 @@ class _Content
|
||||
$url = $url->setQueryString( 'showDeleted', 1 );
|
||||
}
|
||||
|
||||
$name = ( $item->isAnonymous() ) ? \IPS\Member::loggedIn()->language()->addToStack( 'post_anonymously_placename' ) : $item->author()->name;
|
||||
|
||||
return array(
|
||||
'title' => \IPS\Member::loggedIn()->language()->addToStack( 'notification__new_content', FALSE, array(
|
||||
( $htmlEscape ? 'sprintf' : 'htmlsprintf' ) => array(
|
||||
$item->author()->name,
|
||||
$name,
|
||||
mb_strtolower( $item->indefiniteArticle() ),
|
||||
$item->searchIndexContainerClass()->getTitleForLanguage( \IPS\Member::loggedIn()->language(), $htmlEscape ? array( 'escape' => TRUE ) : array() ),
|
||||
$item->mapped('title')
|
||||
@@ -257,17 +270,39 @@ class _Content
|
||||
*/
|
||||
public static function parse_mobile_new_content( \IPS\Lang $language, \IPS\Content\Item $item )
|
||||
{
|
||||
$name = ( $item->isAnonymous() ) ? \IPS\Member::loggedIn()->language()->addToStack( 'post_anonymously_placename' ) : $item->author()->name;
|
||||
$container = $item->containerWrapper();
|
||||
$containerId = $container ? $container->_id : "-"; // This is used to generate the tag. Use ID if we have one, otherwise just a dash
|
||||
|
||||
return array(
|
||||
'title' => $language->addToStack( 'notification__new_content_title', FALSE, array( 'htmlsprintf' => array(
|
||||
mb_strtolower( $item->definiteArticle( $language ) ),
|
||||
) ) ),
|
||||
'body' => $language->addToStack( 'notification__new_content', FALSE, array( 'htmlsprintf' => array(
|
||||
$item->author()->name,
|
||||
$name,
|
||||
mb_strtolower( $item->indefiniteArticle( $language ) ),
|
||||
$item->searchIndexContainerClass()->getTitleForLanguage( $language ),
|
||||
$container ?
|
||||
$language->addToStack( 'notification__container', FALSE, array( 'sprintf' => array( $item->searchIndexContainerClass()->getTitleForLanguage( $language ) ) ) )
|
||||
: "",
|
||||
$item->mapped('title')
|
||||
) ) ),
|
||||
'data' => array(
|
||||
'url' => (string) $item->url(),
|
||||
'author' => $item->author()
|
||||
'author' => $item->author(),
|
||||
'grouped' => $language->addToStack( 'notification__new_content_grouped', FALSE, array(
|
||||
'htmlsprintf' => array(
|
||||
$item->definiteArticle( $language, TRUE ),
|
||||
$container ?
|
||||
$language->addToStack( 'notification__container', FALSE, array( 'sprintf' => array( $item->searchIndexContainerClass()->getTitleForLanguage( $language ) ) ) )
|
||||
: "",
|
||||
)
|
||||
) ),
|
||||
'groupedTitle' => $language->addToStack( 'notification__new_content_grouped_title', FALSE, array( 'htmlsprintf' => array(
|
||||
$item->definiteArticle( $language, TRUE ),
|
||||
) ) ),
|
||||
'groupedUrl' => $container ? $container->url() : NULL
|
||||
),
|
||||
'tag' => md5( 'newcontent' . \get_class( $item ) . $containerId ), // Group new item notifications by container ID (if available)
|
||||
'channelId' => 'followed',
|
||||
);
|
||||
}
|
||||
@@ -337,6 +372,9 @@ class _Content
|
||||
public static function parse_mobile_new_content_bulk( \IPS\Lang $language, \IPS\Node\Model $node, \IPS\Member $author, $contentClass )
|
||||
{
|
||||
return array(
|
||||
'title' => $language->addToStack( 'notification__new_content_bulk_title', FALSE, array( 'htmlsprintf' => array(
|
||||
$language->get( $contentClass::$title . '_pl_lc' ),
|
||||
) ) ),
|
||||
'body' => $language->addToStack( 'notification__new_content_bulk', FALSE, array( 'htmlsprintf' => array(
|
||||
$author->name,
|
||||
$language->get( $contentClass::$title . '_pl_lc' ),
|
||||
@@ -454,7 +492,13 @@ class _Content
|
||||
*/
|
||||
public static function parse_mobile_new_comment( \IPS\Lang $language, \IPS\Content\Comment $comment )
|
||||
{
|
||||
$item = $comment->item();
|
||||
$idColumn = $item::$databaseColumnId;
|
||||
|
||||
return array(
|
||||
'title' => $language->addToStack( 'notification__new_comment_title', FALSE, array(
|
||||
'pluralize' => array( 1 )
|
||||
) ),
|
||||
'body' => $language->addToStack( 'notification__new_comment', FALSE, array(
|
||||
'pluralize' => array( 1 ),
|
||||
'htmlsprintf'=> array(
|
||||
@@ -464,8 +508,16 @@ class _Content
|
||||
) ),
|
||||
'data' => array(
|
||||
'url' => (string) $comment->url(),
|
||||
'author' => $comment->author()
|
||||
'author' => $comment->author(),
|
||||
'grouped' => $language->addToStack( 'notification__new_comment_grouped', FALSE, array(
|
||||
'htmlsprintf' => array(
|
||||
$comment->item()->mapped('title')
|
||||
)
|
||||
) ),
|
||||
'groupedTitle' => $language->addToStack( 'notification__new_comment_title' ), // Pluralized on the client
|
||||
// No need for groupedUrl here - latest comment url will do
|
||||
),
|
||||
'tag' => md5( 'newcomment' . \get_class( $item ) . $item->$idColumn ), // Group comment notifications by item ID (if available)
|
||||
'channelId' => 'followed',
|
||||
);
|
||||
}
|
||||
@@ -565,7 +617,13 @@ class _Content
|
||||
*/
|
||||
public static function parse_mobile_new_review( \IPS\Lang $language, \IPS\Content\Review $review )
|
||||
{
|
||||
$item = $review->item();
|
||||
$idColumn = $item::$databaseColumnId;
|
||||
|
||||
return array(
|
||||
'title' => $language->addToStack( 'notification__new_review_title', FALSE, array(
|
||||
'pluralize' => array(1)
|
||||
) ),
|
||||
'body' => $language->addToStack( 'notification__new_review', FALSE, array(
|
||||
'pluralize' => array( 1 ),
|
||||
'htmlsprintf'=> array(
|
||||
@@ -575,8 +633,16 @@ class _Content
|
||||
) ),
|
||||
'data' => array(
|
||||
'url' => (string) $review->url(),
|
||||
'author' => $review->author()
|
||||
'author' => $review->author(),
|
||||
'grouped' => $language->addToStack( 'notification__new_review_grouped', FALSE, array(
|
||||
'htmlsprintf' => array(
|
||||
$review->item()->mapped('title')
|
||||
)
|
||||
) ),
|
||||
'groupedTitle' => $language->addToStack( 'notification__new_review_title' ), // Pluralized on the client
|
||||
// No need for groupedUrl here - latest comment url will do
|
||||
),
|
||||
'tag' => md5( 'newreview' . \get_class( $item ) . $item->$idColumn ), // Group review notifications by item ID (if available)
|
||||
'channelId' => 'followed',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ class _Messenger
|
||||
* @param bool $value The value
|
||||
* @return void
|
||||
*/
|
||||
public static function saveExtra( \IPS\Member $member = NULL, $key, $value )
|
||||
public static function saveExtra( ?\IPS\Member $member, $key, $value )
|
||||
{
|
||||
switch ( $key )
|
||||
{
|
||||
@@ -93,7 +93,7 @@ class _Messenger
|
||||
* @param string $method The method type
|
||||
* @return void
|
||||
*/
|
||||
public static function disableExtra( \IPS\Member $member = NULL, $method )
|
||||
public static function disableExtra( ?\IPS\Member $member, $method )
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
@@ -166,11 +166,19 @@ class _Messenger
|
||||
public static function parse_mobile_new_private_message( \IPS\Lang $language, \IPS\core\Messenger\Message $message )
|
||||
{
|
||||
return array(
|
||||
'title' => $language->addToStack( 'notification__new_private_message_title', FALSE, array(
|
||||
'pluralize' => array(1),
|
||||
'htmlsprintf' => array( $message->author()->name )
|
||||
) ),
|
||||
'body' => $language->addToStack( 'notification__new_private_message', FALSE, array( 'htmlsprintf' => array( $message->author()->name ) ) ),
|
||||
'data' => array(
|
||||
'url' => (string) $message->url(),
|
||||
'author' => $message->author()
|
||||
'author' => $message->author(),
|
||||
'grouped' => $language->addToStack( 'notification__new_private_message_grouped' ), // Pluralized on the client
|
||||
'groupedTitle' => $language->addToStack( 'notification__new_private_message_title' ), // Pluralized on the client
|
||||
'groupedUrl' => \IPS\Http\Url::internal( 'app=core&module=messaging&controller=messenger', 'front', 'messaging' ) // For more than one message, go to messenger
|
||||
),
|
||||
'tag' => md5( 'new-personal-messages' ), // Group all new personal messages
|
||||
'channelId' => 'personal-messages',
|
||||
);
|
||||
}
|
||||
@@ -217,11 +225,13 @@ class _Messenger
|
||||
public static function parse_mobile_private_message_added( \IPS\Lang $language, \IPS\core\Messenger\Conversation $conversation, \IPS\Member $added )
|
||||
{
|
||||
return array(
|
||||
'title' => $language->addToStack( 'notification__private_message_added_title', FALSE, array( 'pluralize' => array(1) ) ),
|
||||
'body' => $language->addToStack( 'notification__private_message_added', FALSE, array( 'htmlsprintf' => array( $added->name ) ) ),
|
||||
'data' => array(
|
||||
'url' => (string) $conversation->url(),
|
||||
'author' => $added
|
||||
),
|
||||
'tag' => md5( 'new-personal-messages-added' ), // Group all added-to personal messages
|
||||
'channelId' => 'personal-messages',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ class _Moderation
|
||||
* @param bool $value The value
|
||||
* @return void
|
||||
*/
|
||||
public static function saveExtra( \IPS\Member $member, $key, $value )
|
||||
public static function saveExtra( ?\IPS\Member $member, $key, $value )
|
||||
{
|
||||
switch ( $key )
|
||||
{
|
||||
@@ -144,7 +144,7 @@ class _Moderation
|
||||
* @param string $method The method type
|
||||
* @return void
|
||||
*/
|
||||
public static function disableExtra( \IPS\Member $member = NULL, $method )
|
||||
public static function disableExtra( ?\IPS\Member $member, $method )
|
||||
{
|
||||
// Do nothing
|
||||
}
|
||||
@@ -294,14 +294,19 @@ class _Moderation
|
||||
public static function parse_mobile_warning_mods( \IPS\Lang $language, \IPS\core\Warnings\Warning $warning )
|
||||
{
|
||||
return array(
|
||||
'title' => $language->addToStack( 'notification__warning_mods_title', FALSE, array( 'pluralize' => array(1) ) ),
|
||||
'body' => $language->addToStack( 'notification__warning_mods', FALSE, array( 'htmlsprintf' => array(
|
||||
\IPS\Member::load( $warning->member )->name,
|
||||
\IPS\Member::load( $warning->moderator )->name
|
||||
) ) ),
|
||||
'data' => array(
|
||||
'url' => (string) $warning->url(),
|
||||
'author' => $warning->moderator
|
||||
'author' => $warning->moderator,
|
||||
'grouped' => $language->addToStack( 'notification__warning_mods_grouped'), // Pluralized on the client
|
||||
'groupedTitle' => $language->addToStack( 'notification__warning_mods_title' ), // Pluralized on the client
|
||||
'groupedUrl' => \IPS\Http\Url::internal( 'app=core&module=modcp&controller=modcp&tab=recent_warnings', 'front', 'modcp_recent_warnings' )
|
||||
),
|
||||
'tag' => md5('recent-warnings'), // Group warning notifications
|
||||
'channelId' => 'moderation',
|
||||
);
|
||||
}
|
||||
@@ -318,6 +323,9 @@ class _Moderation
|
||||
public static function parse_mobile_unapproved_content_bulk( \IPS\Lang $language, \IPS\Node\Model $node, \IPS\Member $author, $contentClass )
|
||||
{
|
||||
return array(
|
||||
'title' => $language->addToStack( 'notification__unapproved_content_bulk_title', FALSE, array( 'htmlsprintf' => array(
|
||||
$language->get( $contentClass::$title . '_pl_lc' ),
|
||||
) ) ),
|
||||
'body' => $language->addToStack( 'notification__unapproved_content_bulk', FALSE, array( 'htmlsprintf' => array(
|
||||
$author->name,
|
||||
$language->get( $contentClass::$title . '_pl_lc' ),
|
||||
@@ -380,6 +388,7 @@ class _Moderation
|
||||
public static function parse_mobile_report_center( \IPS\Lang $language, \IPS\core\Reports\Report $report, array $latestReport, \IPS\Content $reportedContent )
|
||||
{
|
||||
return array(
|
||||
'title' => $language->addToStack( 'notification__report_center_title' ),
|
||||
'body' => $language->addToStack( 'notification__report_center', FALSE, array( 'htmlsprintf' => array(
|
||||
\IPS\Member::load( $latestReport['report_by'] )->name,
|
||||
mb_strtolower( $reportedContent->indefiniteArticle( $language ) ),
|
||||
@@ -387,8 +396,12 @@ class _Moderation
|
||||
) ) ),
|
||||
'data' => array(
|
||||
'url' => (string) $report->url(),
|
||||
'author' => $report->author()
|
||||
'author' => $report->author(),
|
||||
'grouped' => $language->addToStack( 'notification__report_center_grouped'), // Pluralized on the client
|
||||
'groupedTitle' => $language->addToStack( 'notification__report_center_title' ), // Pluralized on the client
|
||||
'groupedUrl' => \IPS\Http\Url::internal( 'app=core&module=modcp&controller=modcp&tab=reports', NULL, 'modcp_reports' )
|
||||
),
|
||||
'tag' => md5('report-center'),
|
||||
'channelId' => 'moderation',
|
||||
);
|
||||
}
|
||||
@@ -442,6 +455,7 @@ class _Moderation
|
||||
public static function parse_mobile_automatic_moderation( \IPS\Lang $language, \IPS\core\Reports\Report $report, array $latestReport, \IPS\Content $reportedContent )
|
||||
{
|
||||
return array(
|
||||
'title' => $language->addToStack( 'notification__automatic_moderation_title' ),
|
||||
'body' => $language->addToStack( 'notification__automatic_moderation', FALSE, array( 'htmlsprintf' => array(
|
||||
mb_strtolower( $reportedContent->indefiniteArticle( $language ) ),
|
||||
$report->mapped('title')
|
||||
@@ -489,8 +503,10 @@ class _Moderation
|
||||
$title = $item->mapped('title');
|
||||
}
|
||||
|
||||
$name = ( $item->isAnonymous() ) ? \IPS\Member::loggedIn()->language()->addToStack( 'post_anonymously_placename' ) : $item->author()->name;
|
||||
|
||||
return array(
|
||||
'title' => \IPS\Member::loggedIn()->language()->addToStack( 'notification__unapproved_content', FALSE, array( ( $htmlEscape ? 'sprintf' : 'htmlsprintf' ) => array( $item->author()->name, mb_strtolower( $item->indefiniteArticle() ), $title ) ) ),
|
||||
'title' => \IPS\Member::loggedIn()->language()->addToStack( 'notification__unapproved_content', FALSE, array( ( $htmlEscape ? 'sprintf' : 'htmlsprintf' ) => array( $name, mb_strtolower( $item->indefiniteArticle() ), $title ) ) ),
|
||||
'url' => $item->url(),
|
||||
'content' => $item->content(),
|
||||
'author' => $item->author(),
|
||||
@@ -507,8 +523,13 @@ class _Moderation
|
||||
public static function parse_mobile_unapproved_content( \IPS\Lang $language, \IPS\Content $content )
|
||||
{
|
||||
$item = ( $content instanceof \IPS\Content\Item ) ? $content : $content->item();
|
||||
|
||||
$container = $item->containerWrapper();
|
||||
$containerId = $container ? $container->_id : "-"; // This is used to generate the tag. Use ID if we have one, otherwise just a dash
|
||||
|
||||
return array(
|
||||
'title' => $language->addToStack( 'notification__unapproved_content_title', FALSE, array( 'htmlsprintf' => array(
|
||||
( $content instanceof \IPS\Content\Item ) ? $content->definiteArticle( $language ) : $language->get( $content::$title . '_lc' ),
|
||||
) ) ),
|
||||
'body' => $language->addToStack( 'notification__unapproved_content', FALSE, array( 'htmlsprintf' => array(
|
||||
$content->author()->name,
|
||||
mb_strtolower( $content->indefiniteArticle( $language ) ),
|
||||
@@ -516,8 +537,19 @@ class _Moderation
|
||||
) ) ),
|
||||
'data' => array(
|
||||
'url' => (string) $content->url(),
|
||||
'author' => $content->author()
|
||||
'author' => $content->author(),
|
||||
'grouped' => $language->addToStack( 'notification__unapproved_content_grouped', FALSE, array( 'htmlsprintf' => array(
|
||||
$content->definiteArticle( $language, TRUE ),
|
||||
$container ?
|
||||
$language->addToStack( 'notification__container', FALSE, array( 'sprintf' => array( $container->_title ) ) )
|
||||
: ""
|
||||
))), // Pluralized on the client
|
||||
'groupedTitle' => $language->addToStack( 'notification__unapproved_content_title', FALSE, array( 'htmlsprintf' => array(
|
||||
$content->definiteArticle( $language, TRUE ),
|
||||
)) ), // Pluralized on the client
|
||||
'groupedUrl' => $container ? $container->url() : NULL
|
||||
),
|
||||
'tag' => md5('unapproved' . \get_class( $item ) . $containerId ),
|
||||
'channelId' => 'moderation',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -33,52 +33,22 @@ class _MyStuff
|
||||
return array(
|
||||
'core_MyStuff' => array(
|
||||
'type' => 'standard',
|
||||
'notificationTypes' => array( 'quote', 'mention', 'embed', 'new_likes', 'best_answer' ),
|
||||
'notificationTypes' => array( 'quote', 'mention', 'embed', 'my_solution', 'mine_solved', 'approved_content' ),
|
||||
'title' => 'notifications__core_MyStuff',
|
||||
'showTitle' => FALSE,
|
||||
'showTitle' => TRUE,
|
||||
'description' => 'notifications__core_MyStuff_desc',
|
||||
'default' => array( 'inline' ),
|
||||
'disabled' => array()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse notification: best_answer
|
||||
*
|
||||
* @param \IPS\Notification\Inline $notification The notification
|
||||
* @return array
|
||||
* @code
|
||||
return array(
|
||||
'title' => "Mark has replied to A Topic", // The notification title
|
||||
'url' => \IPS\Http\Url::internal( ... ), // The URL the notification should link to
|
||||
'content' => "Lorem ipsum dolar sit", // [Optional] Any appropriate content. Do not format this like an email where the text
|
||||
// explains what the notification is about - just include any appropriate content.
|
||||
// For example, if the notification is about a post, set this as the body of the post.
|
||||
'author' => \IPS\Member::load( 1 ), // [Optional] The user whose photo should be displayed for this notification
|
||||
);
|
||||
* @endcode
|
||||
*/
|
||||
public function parse_best_answer( $notification )
|
||||
{
|
||||
$item = ( $notification->item instanceof \IPS\Content\Comment ) ? $notification->item->item() : $notification->item;
|
||||
if ( !$item )
|
||||
{
|
||||
throw new \OutOfRangeException;
|
||||
}
|
||||
|
||||
$comment = $notification->item_sub ?: $item;
|
||||
if ( !$comment )
|
||||
{
|
||||
throw new \OutOfRangeException;
|
||||
}
|
||||
|
||||
return array(
|
||||
'title' => \IPS\Member::loggedIn()->language()->addToStack( 'notification__best_answer', FALSE, array( 'sprintf' => array( $item->mapped('title') ) ) ),
|
||||
'url' => $comment instanceof \IPS\Content\Item ? $item->url() : $comment->url('find'),
|
||||
'content' => $comment->truncated(),
|
||||
'author' => $comment->author(),
|
||||
'unread' => (bool) ( $item->unread() ),
|
||||
),
|
||||
'core_Reactions' => array(
|
||||
'type' => 'standard',
|
||||
'notificationTypes' => array( 'new_likes' ),
|
||||
'title' => 'notifications__core_Likes',
|
||||
'showTitle' => TRUE,
|
||||
'description' => 'notifications__core_Likes_desc',
|
||||
'default' => array( 'inline' ),
|
||||
'disabled' => array()
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -139,8 +109,15 @@ class _MyStuff
|
||||
public static function parse_mobile_mention( \IPS\Lang $language, \IPS\Content $content )
|
||||
{
|
||||
$item = ( $content instanceof \IPS\Content\Item ) ? $content : $content->item();
|
||||
|
||||
$idField = $item::$databaseColumnId;
|
||||
|
||||
return array(
|
||||
'title' => $language->addToStack( 'notification__new_mention_title', FALSE, array(
|
||||
'pluralize' => array(1),
|
||||
'htmlsprintf' => array(
|
||||
mb_strtolower( $item->indefiniteArticle( $language ) ),
|
||||
)
|
||||
) ),
|
||||
'body' => $language->addToStack( 'notification__new_mention', FALSE, array(
|
||||
'pluralize' => array( 1 ),
|
||||
'htmlsprintf' => array(
|
||||
@@ -149,10 +126,24 @@ class _MyStuff
|
||||
$item->mapped('title')
|
||||
)
|
||||
) ),
|
||||
'data' => array(
|
||||
'data' => array(
|
||||
'url' => (string) $content->url(),
|
||||
'author' => $content->author()
|
||||
'author' => $content->author(),
|
||||
'grouped' => $language->addToStack( 'notification__new_mention_grouped', FALSE, array(
|
||||
'htmlsprintf' => array(
|
||||
mb_strtolower( $item->indefiniteArticle( $language ) ),
|
||||
$item->mapped('title')
|
||||
)
|
||||
) ),
|
||||
'groupedTitle' => $language->addToStack( 'notification__new_mention_title', FALSE, array(
|
||||
// Pluralized on the client
|
||||
'htmlsprintf' => array(
|
||||
mb_strtolower( $item->indefiniteArticle( $language ) ),
|
||||
)
|
||||
) ),
|
||||
// No need for groupedUrl - just go to the most recent mention
|
||||
),
|
||||
'tag' => md5( 'mention' . \get_class( $item ) . $item->$idField ), // Group mention notifications by content item
|
||||
'channelId' => 'my-content',
|
||||
);
|
||||
}
|
||||
@@ -214,8 +205,14 @@ class _MyStuff
|
||||
public static function parse_mobile_quote( \IPS\Lang $language, \IPS\Content $content )
|
||||
{
|
||||
$item = ( $content instanceof \IPS\Content\Item ) ? $content : $content->item();
|
||||
|
||||
$idField = $item::$databaseColumnId;
|
||||
|
||||
return array(
|
||||
'title' => $language->addToStack( 'notification__new_quote_title', FALSE, array(
|
||||
'htmlsprintf' => array(
|
||||
mb_strtolower( $item->indefiniteArticle( $language ) ),
|
||||
)
|
||||
) ),
|
||||
'body' => $language->addToStack( 'notification__new_quote', FALSE, array(
|
||||
'pluralize' => array( 1 ),
|
||||
'htmlsprintf' => array(
|
||||
@@ -226,8 +223,16 @@ class _MyStuff
|
||||
) ),
|
||||
'data' => array(
|
||||
'url' => (string) $content->url(),
|
||||
'author' => $content->author()
|
||||
'author' => $content->author(),
|
||||
'grouped' => $language->addToStack( 'notification__new_quote_grouped', FALSE, array(
|
||||
'htmlsprintf' => array(
|
||||
mb_strtolower( $item->indefiniteArticle( $language ) ),
|
||||
$item->mapped('title')
|
||||
)
|
||||
) ),
|
||||
// No need for groupedUrl - just go to the most recent quote
|
||||
),
|
||||
'tag' => md5( 'quote' . \get_class( $item ) . $item->$idField ), // Group quote notifications by content item
|
||||
'channelId' => 'my-content',
|
||||
);
|
||||
}
|
||||
@@ -318,9 +323,17 @@ class _MyStuff
|
||||
public static function parse_mobile_new_likes( \IPS\Lang $language, \IPS\Content $content, \IPS\Member $liker )
|
||||
{
|
||||
$item = ( $content instanceof \IPS\Content\Item ) ? $content : $content->item();
|
||||
|
||||
$idField = $item::$databaseColumnId;
|
||||
$lang = \IPS\Content\Reaction::isLikeMode() ? 'notification__new_likes' : 'notification__new_react';
|
||||
|
||||
return array(
|
||||
'body' => $language->addToStack( \IPS\Content\Reaction::isLikeMode() ? 'notification__new_likes' : 'notification__new_react', FALSE, array(
|
||||
'title' => $language->addToStack( $lang . '_title', FALSE, array(
|
||||
'pluralize' => array(1),
|
||||
'htmlsprintf' => array(
|
||||
mb_strtolower( $item->indefiniteArticle( $language ) )
|
||||
)
|
||||
) ),
|
||||
'body' => $language->addToStack( $lang, FALSE, array(
|
||||
'pluralize' => array( 1 ),
|
||||
'htmlsprintf' => array(
|
||||
( $content->author()->group['gbw_view_reps'] ) ? $language->formatList( array( $liker->name ) ) : $language->pluralize(
|
||||
@@ -332,8 +345,21 @@ class _MyStuff
|
||||
) ) . ' ' . $item->mapped('title'),
|
||||
'data' => array(
|
||||
'url' => (string) $content->url(),
|
||||
'author' => $liker
|
||||
'author' => $liker,
|
||||
'grouped' => $language->addToStack( $lang . '_grouped', FALSE, array(
|
||||
'htmlsprintf' => array(
|
||||
mb_strtolower( $content->indefiniteArticle( $language ) )
|
||||
)
|
||||
) ),
|
||||
'groupedTitle' => $language->addToStack( $lang . '_title', FALSE, array(
|
||||
// Pluralized on the client
|
||||
'htmlsprintf' => array(
|
||||
mb_strtolower( $item->indefiniteArticle( $language ) )
|
||||
)
|
||||
) ),
|
||||
// No need for groupedUrl - just go to the most recent thing
|
||||
),
|
||||
'tag' => md5( 'likes' . \get_class( $item ) . $item->$idField ), // Group quote notifications by content item
|
||||
'channelId' => 'my-content',
|
||||
);
|
||||
}
|
||||
@@ -392,8 +418,10 @@ class _MyStuff
|
||||
public static function parse_mobile_embed( \IPS\Lang $language, \IPS\Content $content )
|
||||
{
|
||||
$item = ( $content instanceof \IPS\Content\Item ) ? $content : $content->item();
|
||||
|
||||
$idField = $item::$databaseColumnId;
|
||||
|
||||
return array(
|
||||
'title' => $language->addToStack( 'notification__new_embed_title' ),
|
||||
'body' => $language->addToStack( 'notification__new_embed', FALSE, array(
|
||||
'pluralize' => array( 1 ),
|
||||
'htmlsprintf' => array(
|
||||
@@ -402,6 +430,244 @@ class _MyStuff
|
||||
$item->mapped('title')
|
||||
)
|
||||
) ),
|
||||
'data' => array(
|
||||
'url' => (string) $content->url(),
|
||||
'author' => $content->author(),
|
||||
'grouped' => $language->addToStack( 'notification__new_embed_grouped', FALSE, array(
|
||||
'htmlsprintf' => array(
|
||||
mb_strtolower( $item->indefiniteArticle( $language ) ),
|
||||
$item->mapped('title')
|
||||
)
|
||||
) ),
|
||||
// No need for groupedUrl - just go to the most recent thing
|
||||
),
|
||||
'tag' => md5( 'embed' . \get_class( $item ) . $item->$idField ), // Group embed notifications by content item
|
||||
'channelId' => 'my-content',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse notification: mine_solved
|
||||
*
|
||||
* @param \IPS\Notification\Inline $notification The notification
|
||||
* @param bool $htmlEscape TRUE to escape HTML in title
|
||||
* @return array
|
||||
* @code
|
||||
return array(
|
||||
'title' => "Mark has replied to A Topic", // The notification title
|
||||
'url' => \IPS\Http\Url::internal( ... ), // The URL the notification should link to
|
||||
'content' => "Lorem ipsum dolar sit", // [Optional] Any appropriate content. Do not format this like an email where the text
|
||||
// explains what the notification is about - just include any appropriate content.
|
||||
// For example, if the notification is about a post, set this as the body of the post.
|
||||
'author' => \IPS\Member::load( 1 ), // [Optional] The user whose photo should be displayed for this notification
|
||||
);
|
||||
* @endcode
|
||||
*/
|
||||
public function parse_mine_solved( \IPS\Notification\Inline $notification, $htmlEscape=TRUE )
|
||||
{
|
||||
$item = $notification->item;
|
||||
if ( !$item )
|
||||
{
|
||||
throw new \OutOfRangeException;
|
||||
}
|
||||
|
||||
$commentClass = $item::$commentClass;
|
||||
$comment = $commentClass::loadAndCheckPerms( $notification->item_sub_id );
|
||||
|
||||
$name = ( $comment->isAnonymous() ) ? \IPS\Member::loggedIn()->language()->addToStack( 'post_anonymously_placename' ) : $comment->author()->name;
|
||||
|
||||
/* Unread? */
|
||||
$unread = false;
|
||||
if ( $item->timeLastRead() instanceof \IPS\DateTime )
|
||||
{
|
||||
$unread = (bool) ( $item->timeLastRead()->getTimestamp() < $notification->updated_time->getTimestamp() );
|
||||
}
|
||||
|
||||
return array(
|
||||
'title' => \IPS\Member::loggedIn()->language()->addToStack( 'notification__minesolved', FALSE, array(
|
||||
( $htmlEscape ? 'sprintf' : 'htmlsprintf' ) => array(
|
||||
$name,
|
||||
mb_strtolower( $item->indefiniteArticle() ),
|
||||
$item->mapped('title')
|
||||
)
|
||||
) ),
|
||||
'url' => $comment->url('find'),
|
||||
'content' => $comment->content(),
|
||||
'author' => $comment->author(),
|
||||
'unread' => (bool) $unread
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse notification for mobile: mine_solved
|
||||
*
|
||||
* @param \IPS\Lang $language The language that the notification should be in
|
||||
* @param \IPS\Content $content The content
|
||||
* @return array
|
||||
*/
|
||||
public static function parse_mobile_mine_solved( \IPS\Lang $language, \IPS\Content $content )
|
||||
{
|
||||
$item = ( $content instanceof \IPS\Content\Item ) ? $content : $content->item();
|
||||
$name = ( $content->isAnonymous() ) ? \IPS\Member::loggedIn()->language()->addToStack( 'post_anonymously_placename' ) : $content->author()->name;
|
||||
|
||||
return array(
|
||||
'title' => $language->addToStack( 'notification__minesolved_title', FALSE, array(
|
||||
'htmlsprintf' => array(
|
||||
mb_strtolower( $item->definiteArticle() ),
|
||||
)
|
||||
) ),
|
||||
'body' => $language->addToStack( 'notification__minesolved', FALSE, array(
|
||||
'htmlsprintf' => array(
|
||||
$name,
|
||||
mb_strtolower( $item->indefiniteArticle() ),
|
||||
$item->mapped('title')
|
||||
)
|
||||
) ),
|
||||
'data' => array(
|
||||
'url' => (string) $content->url('find'),
|
||||
'author' => $content->author()
|
||||
),
|
||||
'channelId' => 'my-content',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse notification: my_solution
|
||||
*
|
||||
* @param \IPS\Notification\Inline $notification The notification
|
||||
* @param bool $htmlEscape TRUE to escape HTML in title
|
||||
* @return array
|
||||
* @code
|
||||
return array(
|
||||
'title' => "Mark has replied to A Topic", // The notification title
|
||||
'url' => \IPS\Http\Url::internal( ... ), // The URL the notification should link to
|
||||
'content' => "Lorem ipsum dolar sit", // [Optional] Any appropriate content. Do not format this like an email where the text
|
||||
// explains what the notification is about - just include any appropriate content.
|
||||
// For example, if the notification is about a post, set this as the body of the post.
|
||||
'author' => \IPS\Member::load( 1 ), // [Optional] The user whose photo should be displayed for this notification
|
||||
);
|
||||
* @endcode
|
||||
*/
|
||||
public function parse_my_solution( \IPS\Notification\Inline $notification, $htmlEscape=TRUE )
|
||||
{
|
||||
$item = $notification->item;
|
||||
if ( !$item )
|
||||
{
|
||||
throw new \OutOfRangeException;
|
||||
}
|
||||
|
||||
$commentClass = $item::$commentClass;
|
||||
$comment = $commentClass::loadAndCheckPerms( $notification->item_sub_id );
|
||||
|
||||
/* Unread? */
|
||||
$unread = false;
|
||||
if ( $item->timeLastRead() instanceof \IPS\DateTime )
|
||||
{
|
||||
$unread = (bool) ( $item->timeLastRead()->getTimestamp() < $notification->updated_time->getTimestamp() );
|
||||
}
|
||||
|
||||
return array(
|
||||
'title' => \IPS\Member::loggedIn()->language()->addToStack( 'notification__mysolution', FALSE, array(
|
||||
( $htmlEscape ? 'sprintf' : 'htmlsprintf' ) => array(
|
||||
$item->mapped('title')
|
||||
)
|
||||
) ),
|
||||
'url' => $comment->url('find'),
|
||||
'content' => $comment->content(),
|
||||
'author' => $comment->author(),
|
||||
'unread' => (bool) $unread
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse notification for mobile: my_solution
|
||||
*
|
||||
* @param \IPS\Lang $language The language that the notification should be in
|
||||
* @param \IPS\Content $content The content
|
||||
* @return array
|
||||
*/
|
||||
public static function parse_mobile_my_solution( \IPS\Lang $language, \IPS\Content $content )
|
||||
{
|
||||
$item = ( $content instanceof \IPS\Content\Item ) ? $content : $content->item();
|
||||
|
||||
return array(
|
||||
'title' => $language->addToStack( 'notification__mysolution_title', FALSE, array(
|
||||
'htmlsprintf' => array(
|
||||
mb_strtolower( $item->indefiniteArticle() ),
|
||||
)
|
||||
) ),
|
||||
'body' => $language->addToStack( 'notification__mysolution', FALSE, array(
|
||||
'htmlsprintf' => array(
|
||||
$item->mapped('title')
|
||||
)
|
||||
) ),
|
||||
'data' => array(
|
||||
'url' => (string) $content->url('find'),
|
||||
'author' => $content->author()
|
||||
),
|
||||
'channelId' => 'my-content',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse notification: approved
|
||||
*
|
||||
* @param \IPS\Notification\Inline $notification The notification
|
||||
* @param bool $htmlEscape TRUE to escape HTML in title
|
||||
* @return array|NULL
|
||||
* @code
|
||||
return array(
|
||||
'title' => "Mark has replied to A Topic", // The notification title
|
||||
'url' => \IPS\Http\Url::internal( ... ), // The URL the notification should link to
|
||||
'content' => "Lorem ipsum dolar sit", // [Optional] Any appropriate content. Do not format this like an email where the text
|
||||
// explains what the notification is about - just include any appropriate content.
|
||||
// For example, if the notification is about a post, set this as the body of the post.
|
||||
'author' => \IPS\Member::load( 1 ), // [Optional] The user whose photo should be displayed for this notification
|
||||
);
|
||||
* @endcode
|
||||
*/
|
||||
public function parse_approved_content( \IPS\Notification\Inline $notification, $htmlEscape=TRUE ): array
|
||||
{
|
||||
$item = ( $notification->item instanceof \IPS\Content\Comment ) ? $notification->item->item() : $notification->item;
|
||||
if ( !$item )
|
||||
{
|
||||
throw new \OutOfRangeException;
|
||||
}
|
||||
|
||||
$comment = $notification->item_sub ?: $item;
|
||||
if ( !$comment )
|
||||
{
|
||||
throw new \OutOfRangeException;
|
||||
}
|
||||
|
||||
return array(
|
||||
'title' => \IPS\Member::loggedIn()->language()->addToStack( 'notification__approved_content', FALSE, array( ( $htmlEscape ? 'sprintf' : 'htmlsprintf' ) => array( mb_strtolower( $comment->definiteArticle() ), $item->mapped('title') ) ) ),
|
||||
'url' => $comment instanceof \IPS\Content\Item ? $item->url() : $comment->url('find'),
|
||||
'content' => $comment->truncated(),
|
||||
'author' => $comment->author(),
|
||||
'unread' => (bool) ( $item->unread() ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse notification for mobile: approved
|
||||
*
|
||||
* @param \IPS\Lang $language The language that the notification should be in
|
||||
* @param \IPS\Content $content The content
|
||||
* @return array
|
||||
*/
|
||||
public static function parse_mobile_approved_content( \IPS\Lang $language, \IPS\Content $content ): array
|
||||
{
|
||||
$item = ( $content instanceof \IPS\Content\Item ) ? $content : $content->item();
|
||||
|
||||
return array(
|
||||
'title' => $language->addToStack( 'notification__approved_content_title' ),
|
||||
'body' => $language->addToStack( 'notification__approved_content', FALSE, array(
|
||||
'htmlsprintf' => array(
|
||||
mb_strtolower( $content->definiteArticle( $language ) ),
|
||||
$item->mapped('title')
|
||||
)
|
||||
) ),
|
||||
'data' => array(
|
||||
'url' => (string) $content->url(),
|
||||
'author' => $content->author()
|
||||
|
||||
@@ -35,16 +35,19 @@ class _Profile
|
||||
$module = \IPS\Application\Module::get( 'core', 'members', 'front' );
|
||||
if ( $module->_enabled and ( $member === NULL or $member->canAccessModule( $module ) ) )
|
||||
{
|
||||
$return['core_Profile_status'] = array(
|
||||
'type' => 'standard',
|
||||
'notificationTypes' => array( 'profile_comment', 'profile_reply', 'new_status' ),
|
||||
'title' => 'notifications__core_Profile_status',
|
||||
'showTitle' => TRUE,
|
||||
'description' => 'notifications__core_Profile_status_desc',
|
||||
'default' => array( 'inline', 'push' ),
|
||||
'disabled' => array(),
|
||||
);
|
||||
|
||||
if( $member == NULL or $member->canAccessModule( \IPS\Application\Module::get( 'core', 'status', 'front' ) ) and \IPS\Settings::i()->profile_comments )
|
||||
{
|
||||
$return['core_Profile_status'] = array(
|
||||
'type' => 'standard',
|
||||
'notificationTypes' => array( 'profile_comment', 'profile_reply', 'new_status' ),
|
||||
'title' => 'notifications__core_Profile_status',
|
||||
'showTitle' => TRUE,
|
||||
'description' => 'notifications__core_Profile_status_desc',
|
||||
'default' => array( 'inline', 'push' ),
|
||||
'disabled' => array(),
|
||||
);
|
||||
}
|
||||
|
||||
$return['core_Profile_follow'] = array(
|
||||
'type' => 'standard',
|
||||
'notificationTypes' => array( 'member_follow' ),
|
||||
@@ -110,6 +113,7 @@ class _Profile
|
||||
public static function parse_mobile_member_follow( \IPS\Lang $language, \IPS\Member $member )
|
||||
{
|
||||
return array(
|
||||
'title' => $language->addToStack( 'notification__member_follow_title' ),
|
||||
'body' => $language->addToStack( 'notification__member_follow', FALSE, array( 'htmlsprintf' => array( $member->name ) ) ),
|
||||
'data' => array(
|
||||
'url' => (string) $member->url(),
|
||||
@@ -163,6 +167,7 @@ class _Profile
|
||||
public static function parse_mobile_profile_comment( \IPS\Lang $language, \IPS\core\Statuses\Status $comment )
|
||||
{
|
||||
return array(
|
||||
'body' => $language->addToStack( 'notification__profile_comment_title' ),
|
||||
'body' => $language->addToStack( 'notification__profile_comment', FALSE, array( 'htmlsprintf' => array( $comment->author()->name ) ) ),
|
||||
'data' => array(
|
||||
'url' => (string) $comment->url(),
|
||||
@@ -216,6 +221,7 @@ class _Profile
|
||||
public static function parse_mobile_new_status( \IPS\Lang $language, \IPS\core\Statuses\Status $status )
|
||||
{
|
||||
return array(
|
||||
'title' => $language->addToStack( 'notification__new_status_title' ),
|
||||
'body' => $language->addToStack( 'notification__new_status', FALSE, array( 'htmlsprintf' => array( $status->author()->name ) ) ),
|
||||
'data' => array(
|
||||
'url' => (string) $status->url(),
|
||||
@@ -269,6 +275,7 @@ class _Profile
|
||||
public static function parse_mobile_profile_reply( \IPS\Lang $language, \IPS\core\Statuses\Reply $reply )
|
||||
{
|
||||
return array(
|
||||
'title' => $language->addToStack( 'notification__profile_reply_title' ),
|
||||
'body' => $language->addToStack( 'notification__profile_reply', FALSE, array( 'htmlsprintf' => array( $reply->author()->name ) ) ),
|
||||
'data' => array(
|
||||
'url' => (string) $reply->item()->url(),
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Overview statistics extension: Badges
|
||||
* @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 10 Mar 2021
|
||||
*/
|
||||
|
||||
namespace IPS\core\extensions\core\OverviewStatistics;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Overview statistics extension: Badges
|
||||
*/
|
||||
class _Badges
|
||||
{
|
||||
/**
|
||||
* @brief Which statistics page (activity or user)
|
||||
*/
|
||||
public $page = 'user';
|
||||
|
||||
/**
|
||||
* Return the sub-block keys
|
||||
*
|
||||
* @note This is designed to allow one class to support multiple blocks, for instance using the ContentRouter to generate blocks.
|
||||
* @return array
|
||||
*/
|
||||
public function getBlocks()
|
||||
{
|
||||
return array( 'subblockKey' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return block details (title and description)
|
||||
*
|
||||
* @param string|NULL $subBlock The subblock we are loading as returned by getBlocks()
|
||||
* @return array
|
||||
*/
|
||||
public function getBlockDetails( $subBlock = NULL )
|
||||
{
|
||||
/* Description can be null and will not be shown if so */
|
||||
return array( 'app' => 'core', 'title' => 'stats_member_badges_overview', 'description' => null, 'refresh' => 60 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the block HTML to show
|
||||
*
|
||||
* @param array|NULL $dateRange NULL for all time, or an array with 'start' and 'end' \IPS\DateTime objects to restrict to
|
||||
* @param string|NULL $subBlock The subblock we are loading as returned by getBlocks()
|
||||
* @return string
|
||||
*/
|
||||
public function getBlock( $dateRange = NULL, $subBlock = NULL )
|
||||
{
|
||||
$where = NULL;
|
||||
$previousCount = NULL;
|
||||
|
||||
if( $dateRange !== NULL )
|
||||
{
|
||||
if( \is_array( $dateRange ) )
|
||||
{
|
||||
$where = array(
|
||||
array( 'datetime > ?', $dateRange['start']->getTimestamp() ),
|
||||
array( 'datetime < ?', $dateRange['end']->getTimestamp() ),
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
$currentDate = new \IPS\DateTime;
|
||||
$interval = NULL;
|
||||
|
||||
switch( $dateRange )
|
||||
{
|
||||
case '7':
|
||||
$interval = new \DateInterval( 'P7D' );
|
||||
break;
|
||||
|
||||
case '30':
|
||||
$interval = new \DateInterval( 'P1M' );
|
||||
break;
|
||||
|
||||
case '90':
|
||||
$interval = new \DateInterval( 'P3M' );
|
||||
break;
|
||||
|
||||
case '180':
|
||||
$interval = new \DateInterval( 'P6M' );
|
||||
break;
|
||||
|
||||
case '365':
|
||||
$interval = new \DateInterval( 'P1Y' );
|
||||
break;
|
||||
}
|
||||
|
||||
$initialTimestamp = $currentDate->sub( $interval )->getTimestamp();
|
||||
$where = array( array( 'datetime > ?', $initialTimestamp ) );
|
||||
|
||||
$previousCount = \IPS\Db::i()->select( 'COUNT(*)', 'core_member_badges', array( array( 'datetime BETWEEN ? AND ?', $currentDate->sub( $interval )->getTimestamp(), $initialTimestamp ) ) )->first();
|
||||
}
|
||||
}
|
||||
|
||||
$count = \IPS\Db::i()->select( 'COUNT(*)', 'core_member_badges', $where )->first();
|
||||
|
||||
return \IPS\Theme::i()->getTemplate( 'stats' )->overviewComparisonCount( $count, $previousCount );
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
|
||||
* @since 16 Jan 2020
|
||||
*/
|
||||
|
||||
@@ -157,19 +156,7 @@ class _ContentActivity
|
||||
$initialTimestamp = $currentDate->sub( $interval )->getTimestamp();
|
||||
$where = array( array( $class::$databasePrefix . $dateColumn . ' > ?', $initialTimestamp ) );
|
||||
|
||||
if( isset( $class::$containerNodeClass ) AND \IPS\Request::i()->nodes )
|
||||
{
|
||||
$where[] = array( \IPS\Db::i()->in( $class::$databasePrefix . $class::$databaseColumnMap['container'], explode( ',', \IPS\Request::i()->nodes ) ) );
|
||||
}
|
||||
|
||||
$previousValues[ $class::$title . '_pl' ] = \IPS\Db::i()->select( 'COUNT(*)', $class::$databaseTable, array( array( $class::$databasePrefix . $dateColumn . ' BETWEEN ? AND ?', $currentDate->sub( $interval )->getTimestamp(), $initialTimestamp ) ) )->first();
|
||||
}
|
||||
}
|
||||
else if ( $dateRange === NULL and isset( \IPS\Request::i()->nodes ) )
|
||||
{
|
||||
if( isset( $class::$containerNodeClass ) AND \IPS\Request::i()->nodes )
|
||||
{
|
||||
$where = array( array( \IPS\Db::i()->in( $class::$databasePrefix . $class::$databaseColumnMap['container'], explode( ',', \IPS\Request::i()->nodes ) ) ) );
|
||||
$previousValues[ $class::$title . '_pl' ] = \IPS\Db::i()->select( 'COUNT(*)', $class::$databaseTable, $this->_modifyWhereClause( array( array( $class::$databasePrefix . $dateColumn . ' BETWEEN ? AND ?', $currentDate->sub( $interval )->getTimestamp(), $initialTimestamp ) ), $class ) )->first();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,12 +169,50 @@ class _ContentActivity
|
||||
}
|
||||
}
|
||||
|
||||
$values[ $class::$title . '_pl' ] = \IPS\Db::i()->select( 'COUNT(*)', $class::$databaseTable, $where )->first();
|
||||
$values[ $class::$title . '_pl' ] = \IPS\Db::i()->select( 'COUNT(*)', $class::$databaseTable, $this->_modifyWhereClause( $where, $class ) )->first();
|
||||
}
|
||||
|
||||
return \IPS\Theme::i()->getTemplate( 'activitystats' )->overviewCounts( $values, $previousValues, $nodeNames );
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify the where clause to apply other filters
|
||||
*
|
||||
* @param array $where Current where clause
|
||||
* @param \IPS\Content $class Class we are working with
|
||||
* @return array
|
||||
*/
|
||||
protected function _modifyWhereClause( $where, $class )
|
||||
{
|
||||
/* Don't include soft deleted content */
|
||||
$column = NULL;
|
||||
if ( isset( $class::$databaseColumnMap['hidden'] ) )
|
||||
{
|
||||
$column = $class::$databasePrefix . $class::$databaseColumnMap['hidden'];
|
||||
}
|
||||
elseif ( isset( $class::$databaseColumnMap['approved'] ) )
|
||||
{
|
||||
$column = $class::$databasePrefix . $class::$databaseColumnMap['approved'];
|
||||
}
|
||||
|
||||
if ( $column )
|
||||
{
|
||||
$where[] = array( \IPS\Db::i()->in( $column, array( -2, -3 ), TRUE ) );
|
||||
}
|
||||
|
||||
if ( method_exists( $class, 'overviewStatisticsWhere' ) )
|
||||
{
|
||||
$where = array_merge( $where, $class::overviewStatisticsWhere() );
|
||||
}
|
||||
|
||||
if( isset( $class::$containerNodeClass ) AND \IPS\Request::i()->nodes )
|
||||
{
|
||||
$where[] = array( \IPS\Db::i()->in( $class::$databasePrefix . $class::$databaseColumnMap['container'], explode( ',', \IPS\Request::i()->nodes ) ) );
|
||||
}
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return block filter form, or the updated block result upon submit
|
||||
*
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Overview statistics extension: Points
|
||||
* @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 10 Mar 2021
|
||||
*/
|
||||
|
||||
namespace IPS\core\extensions\core\OverviewStatistics;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Overview statistics extension: Points
|
||||
*/
|
||||
class _Points
|
||||
{
|
||||
/**
|
||||
* @brief Which statistics page (activity or user)
|
||||
*/
|
||||
public $page = 'user';
|
||||
|
||||
/**
|
||||
* Return the sub-block keys
|
||||
*
|
||||
* @note This is designed to allow one class to support multiple blocks, for instance using the ContentRouter to generate blocks.
|
||||
* @return array
|
||||
*/
|
||||
public function getBlocks()
|
||||
{
|
||||
return array( 'points' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return block details (title and description)
|
||||
*
|
||||
* @param string|NULL $subBlock The subblock we are loading as returned by getBlocks()
|
||||
* @return array
|
||||
*/
|
||||
public function getBlockDetails( $subBlock = NULL )
|
||||
{
|
||||
/* Description can be null and will not be shown if so */
|
||||
return array( 'app' => 'core', 'title' => 'stats_member_points_overview', 'description' => null, 'refresh' => 60 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the block HTML to show
|
||||
*
|
||||
* @param array|NULL $dateRange NULL for all time, or an array with 'start' and 'end' \IPS\DateTime objects to restrict to
|
||||
* @param string|NULL $subBlock The subblock we are loading as returned by getBlocks()
|
||||
* @return string
|
||||
*/
|
||||
public function getBlock( $dateRange = NULL, $subBlock = NULL )
|
||||
{
|
||||
$where = NULL;
|
||||
$previousCount = NULL;
|
||||
|
||||
if( $dateRange !== NULL )
|
||||
{
|
||||
if( \is_array( $dateRange ) )
|
||||
{
|
||||
$where = array(
|
||||
array( 'datetime > ?', $dateRange['start']->getTimestamp() ),
|
||||
array( 'datetime < ?', $dateRange['end']->getTimestamp() ),
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
$currentDate = new \IPS\DateTime;
|
||||
$interval = NULL;
|
||||
|
||||
switch( $dateRange )
|
||||
{
|
||||
case '7':
|
||||
$interval = new \DateInterval( 'P7D' );
|
||||
break;
|
||||
|
||||
case '30':
|
||||
$interval = new \DateInterval( 'P1M' );
|
||||
break;
|
||||
|
||||
case '90':
|
||||
$interval = new \DateInterval( 'P3M' );
|
||||
break;
|
||||
|
||||
case '180':
|
||||
$interval = new \DateInterval( 'P6M' );
|
||||
break;
|
||||
|
||||
case '365':
|
||||
$interval = new \DateInterval( 'P1Y' );
|
||||
break;
|
||||
}
|
||||
|
||||
$initialTimestamp = $currentDate->sub( $interval )->getTimestamp();
|
||||
$where = array( array( 'datetime > ?', $initialTimestamp ) );
|
||||
|
||||
$previousCount = \IPS\Db::i()->select( 'SUM(points)', 'core_points_log', array( array( 'datetime BETWEEN ? AND ?', $currentDate->sub( $interval )->getTimestamp(), $initialTimestamp ) ) )->first();
|
||||
}
|
||||
}
|
||||
|
||||
$count = \IPS\Db::i()->select( 'SUM(points)', 'core_points_log', $where )->first();
|
||||
|
||||
return \IPS\Theme::i()->getTemplate( 'stats' )->overviewComparisonCount( $count, $previousCount );
|
||||
}
|
||||
}
|
||||
@@ -101,7 +101,10 @@ class _Tags
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Only get visible tags */
|
||||
$where[] = array( 'tag_aai_lookup IN(?)', \IPS\Db::i()->select( 'tag_perm_aai_lookup', 'core_tags_perms', [ 'tag_perm_visible=1' ] ) );
|
||||
|
||||
foreach( \IPS\Db::i()->select( 'COUNT(*) as total, tag_text', 'core_tags', $where, 'total DESC', 5, 'tag_text' ) as $tag )
|
||||
{
|
||||
$topTags[ $tag['tag_text'] ] = $tag['total'];
|
||||
|
||||
@@ -119,7 +119,7 @@ class _ProfileFields
|
||||
{
|
||||
if ( $key == 'core_pfield_' . $fieldId )
|
||||
{
|
||||
if ( (bool) $value )
|
||||
if ( (bool) $value or $value === "0" )
|
||||
{
|
||||
$done++;
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ class _DeleteImageProxyFiles
|
||||
* @return int New offset
|
||||
* @throws \IPS\Task\Queue\OutOfRangeException Indicates offset doesn't exist and thus task is complete
|
||||
*/
|
||||
public function run( $data, $offset )
|
||||
public function run( &$data, $offset )
|
||||
{
|
||||
/* We don't want to delete the files if we are caching indefinitely */
|
||||
if( !$data['cachePeriod'] )
|
||||
@@ -53,6 +53,12 @@ class _DeleteImageProxyFiles
|
||||
throw new \IPS\Task\Queue\OutOfRangeException;
|
||||
}
|
||||
|
||||
/* Kill the process if there table doesn't exist anymore */
|
||||
if( !\IPS\Db::i()->checkForTable( 'core_image_proxy' ) )
|
||||
{
|
||||
throw new \IPS\Task\Queue\OutOfRangeException;
|
||||
}
|
||||
|
||||
$select = \IPS\Db::i()->select( 'location', 'core_image_proxy', array(), 'cache_time ASC', \IPS\REBUILD_SLOW );
|
||||
|
||||
$completed = 0;
|
||||
@@ -76,7 +82,7 @@ class _DeleteImageProxyFiles
|
||||
throw new \IPS\Task\Queue\OutOfRangeException;
|
||||
}
|
||||
|
||||
return $completed;
|
||||
return $completed + $offset;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
<?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 05 Feb 2021
|
||||
*/
|
||||
|
||||
namespace IPS\core\extensions\core\Queue;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Background Task
|
||||
*/
|
||||
class _FileCleanup
|
||||
{
|
||||
/**
|
||||
* @brief Number of thumbnails to build per cycle
|
||||
*/
|
||||
public $perCycle = \IPS\REBUILD_SLOW;
|
||||
|
||||
/**
|
||||
* Parse data before queuing
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
public function preQueueData( $data )
|
||||
{
|
||||
/* Make sure we have the minimal amount of data we need */
|
||||
if( !isset( $data['table'] ) OR empty( $data['column'] ) OR empty( $data['storageExtension'] ) )
|
||||
{
|
||||
throw new \OutOfRangeException;
|
||||
}
|
||||
|
||||
$data['maxId'] = NULL;
|
||||
|
||||
if( isset( $data['primaryId'] ) )
|
||||
{
|
||||
$data['maxId'] = \IPS\Db::i()->select( 'MAX(' . $data['primaryId'] . ')', $data['table'] )->first();
|
||||
}
|
||||
|
||||
$data['count'] = \IPS\Db::i()->select( 'count(*)', $data['table'] )->first();
|
||||
$data['deleted'] = 0;
|
||||
|
||||
/* Convert the storage extension to get the configuration ID */
|
||||
if( !\is_numeric( $data['storageExtension'] ) )
|
||||
{
|
||||
$data['storageExtension'] = (int) \IPS\File::getClass( $data['storageExtension'] )->configurationId;
|
||||
}
|
||||
|
||||
/* Normalize columns */
|
||||
$data['column'] = !\is_array( $data['column'] ) ? array( $data['column'] ) : $data['column'];
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run Background Task
|
||||
*
|
||||
* @param mixed $data Data as it was passed to \IPS\Task::queue()
|
||||
* @param int $offset Offset
|
||||
* @return int New offset
|
||||
* @throws \IPS\Task\Queue\OutOfRangeException Indicates offset doesn't exist and thus task is complete
|
||||
*/
|
||||
public function run( &$data, $offset )
|
||||
{
|
||||
$last = ( isset( $data['primaryId'] ) ) ? NULL : $offset;
|
||||
$deleted = 0;
|
||||
|
||||
\IPS\Log::debug( "Deleting files from an offset of " . $offset, 'deleteFilesTask' );
|
||||
|
||||
$where = isset( $data['where'] ) ? $data['where'] : array();
|
||||
if( isset( $data['primaryId'] ) )
|
||||
{
|
||||
$where[] = array( $data['primaryId'] . '> ?', $offset );
|
||||
$limit = array( 0, $this->perCycle );
|
||||
$order = $data['primaryId'] . ' ASC';
|
||||
}
|
||||
else
|
||||
{
|
||||
$limit = array( $offset, $this->perCycle );
|
||||
$order = NULL;
|
||||
}
|
||||
|
||||
foreach( \IPS\Db::i()->select( '*', $data['table'], $where, $order, $limit ) as $row )
|
||||
{
|
||||
/* Set the last ID we deleted now */
|
||||
$last = ( isset( $data['primaryId'] ) ) ? $row[ $data['primaryId'] ] : ( $last + 1 );
|
||||
|
||||
/* Increment the counter for the progress bar */
|
||||
$deleted++;
|
||||
|
||||
/* Handle each column */
|
||||
foreach( $data['column'] as $column )
|
||||
{
|
||||
if( isset( $row[ $column ] ) AND $row[ $column ] )
|
||||
{
|
||||
$values = ( isset( $data['multipleFiles'] ) AND $data['multipleFiles'] ) ? explode( ',', $row[ $column ] ) : array( $row[ $column ] );
|
||||
|
||||
foreach( $values as $fileLocation )
|
||||
{
|
||||
try
|
||||
{
|
||||
\IPS\File::get( $data['storageExtension'], $fileLocation )->delete();
|
||||
}
|
||||
catch( \Exception $e )
|
||||
{
|
||||
\IPS\Log::log( $e, 'fileCleanupDeleteFailed' );
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$data['deleted'] = $data['deleted'] + $deleted;
|
||||
|
||||
if( $deleted === 0 )
|
||||
{
|
||||
throw new \IPS\Task\Queue\OutOfRangeException;
|
||||
}
|
||||
|
||||
return $last;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Progress
|
||||
*
|
||||
* @param mixed $data Data as it was passed to \IPS\Task::queue()
|
||||
* @param int $offset Offset
|
||||
* @return array( 'text' => 'Doing something...', 'complete' => 50 ) Text explaining task and percentage complete
|
||||
* @throws \OutOfRangeException Indicates offset doesn't exist and thus task is complete
|
||||
*/
|
||||
public function getProgress( $data, $offset )
|
||||
{
|
||||
return array( 'text' => \IPS\Member::loggedIn()->language()->addToStack('deleting_files_generic'), 'complete' => $data['count'] ? ( round( ( 100 / $data['count'] ) * $data['deleted'], 2 ) ) : 100 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform post-completion processing
|
||||
*
|
||||
* @param array $data Data returned from preQueueData
|
||||
* @param bool $processed Was anything processed or not? If preQueueData returns NULL, this will be FALSE.
|
||||
* @return void
|
||||
*/
|
||||
public function postComplete( $data, $processed = TRUE )
|
||||
{
|
||||
$data = json_decode( $data['data'], TRUE );
|
||||
|
||||
if( isset( $data['dropTable'] ) )
|
||||
{
|
||||
\IPS\Db::i()->dropTable( $data['dropTable'], TRUE );
|
||||
}
|
||||
elseif( isset( $data['dropColumn'] ) AND isset( $data['dropColumnTable'] ) )
|
||||
{
|
||||
\IPS\Db::i()->dropColumn( $data['dropColumnTable'], $data['dropColumn'] );
|
||||
}
|
||||
elseif( isset( $data['deleteRows'] ) AND $data['deleteRows'] )
|
||||
{
|
||||
\IPS\Db::i()->delete( $data['table'], $data['where'] );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,20 @@ if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
*/
|
||||
class _Follow
|
||||
{
|
||||
/**
|
||||
* Parse data before queuing
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
public function preQueueData( $data )
|
||||
{
|
||||
/* We need to store this for progress bars */
|
||||
$data['realCount'] = $data['followerCount'];
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run Background Task
|
||||
*
|
||||
|
||||
@@ -37,16 +37,31 @@ class _IndexSingleItem
|
||||
|
||||
if ( \in_array( 'IPS\Content\Comment', class_parents( $classname ) ) )
|
||||
{
|
||||
$where = array( $classname::$databasePrefix . $classname::$databaseColumnMap['item'] . '=?', $data['id'] );
|
||||
$where = array( array( $classname::$databasePrefix . $classname::$databaseColumnMap['item'] . '=?', $data['id'] ) );
|
||||
|
||||
if( \IPS\Settings::i()->search_method == 'mysql' and \IPS\Settings::i()->search_index_timeframe )
|
||||
{
|
||||
if( isset( $classname::$databaseColumnMap['date'] ) )
|
||||
{
|
||||
$where[] = array( $classname::$databasePrefix . $classname::$databaseColumnMap['date'] . '> ?', \IPS\DateTime::ts( time() - ( 86400 * \IPS\Settings::i()->search_index_timeframe ) )->getTimestamp() );
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Just in case an item slips in here */
|
||||
$where = array( $classname::$databasePrefix . $classname::$databaseColumnId . '=?', $data['id'] );
|
||||
$where = array( array( $classname::$databasePrefix . $classname::$databaseColumnId . '=?', $data['id'] ) );
|
||||
}
|
||||
|
||||
$data['count'] = \IPS\Db::i()->select( 'COUNT(*)', $classname::$databaseTable, $where )->first();
|
||||
$data['indexed'] = 0;
|
||||
$data['lastId'] = 0;
|
||||
$data['where'] = $where;
|
||||
|
||||
if( $data['count'] == 0 )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
@@ -59,49 +74,44 @@ class _IndexSingleItem
|
||||
* @return int New offset
|
||||
* @throws \IPS\Task\Queue\OutOfRangeException Indicates offset doesn't exist and thus task is complete
|
||||
*/
|
||||
public function run( $data, $offset )
|
||||
public function run( &$data, $offset )
|
||||
{
|
||||
$classname = $data['class'];
|
||||
$idColumn = $classname::$databaseColumnId;
|
||||
if ( !is_subclass_of( $classname, 'IPS\Content\Searchable' ) )
|
||||
{
|
||||
throw new \IPS\Task\Queue\OutOfRangeException;
|
||||
}
|
||||
|
||||
if ( \in_array( 'IPS\Content\Comment', class_parents( $classname ) ) )
|
||||
{
|
||||
$where = array( $classname::$databasePrefix . $classname::$databaseColumnMap['item'] . '=?', $data['id'] );
|
||||
}
|
||||
else
|
||||
{
|
||||
$where = array( $classname::$databasePrefix . $classname::$databaseColumnId . '=?', $data['id'] );
|
||||
}
|
||||
$where = $data['where']; // We do not want to modify the stored where clause
|
||||
|
||||
/* Refine results for ones we haven't done */
|
||||
$where[] = array( $classname::$databasePrefix . $classname::$databaseColumnId . '>?', $data['lastId'] );
|
||||
|
||||
$done = 0;
|
||||
foreach( new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', $classname::$databaseTable, $where, $classname::$databasePrefix . $classname::$databaseColumnId . ' ASC', array( $offset, 50 ) ), $classname ) AS $object )
|
||||
foreach( new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', $classname::$databaseTable, $where, $classname::$databasePrefix . $classname::$databaseColumnId . ' ASC', 50 ), $classname ) AS $object )
|
||||
{
|
||||
/* If this comment is queued for deleting, skip it */
|
||||
if ( $object->hidden() == -2 )
|
||||
/* If this comment is queued for deleting or pbr, skip it */
|
||||
if ( !\in_array( $object->hidden(), [ -2, -3 ] ) )
|
||||
{
|
||||
$done++;
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if ( !$object->isFutureDate() )
|
||||
try
|
||||
{
|
||||
\IPS\Content\Search\Index::i()->index( $object );
|
||||
if ( !$object->isFutureDate() )
|
||||
{
|
||||
\IPS\Content\Search\Index::i()->index( $object );
|
||||
}
|
||||
}
|
||||
catch( \Exception $e )
|
||||
{
|
||||
\IPS\Log::log( $e, 'index_single_item' );
|
||||
}
|
||||
catch( \Throwable $e )
|
||||
{
|
||||
\IPS\Log::log( $e, 'index_single_item' );
|
||||
}
|
||||
}
|
||||
catch( \Exception $e )
|
||||
{
|
||||
\IPS\Log::log( $e, 'index_single_item' );
|
||||
}
|
||||
catch( \Throwable $e )
|
||||
{
|
||||
\IPS\Log::log( $e, 'index_single_item' );
|
||||
}
|
||||
|
||||
|
||||
$data['lastId'] = $object->$idColumn;
|
||||
$done++;
|
||||
}
|
||||
|
||||
@@ -143,6 +153,6 @@ class _IndexSingleItem
|
||||
$lang = \IPS\Member::loggedIn()->language()->addToStack('reindexing_single_item', FALSE, array( 'sprintf' => array( $data['url'], $data['title'] ) ) );
|
||||
}
|
||||
|
||||
return array( 'text' => $lang, 'complete' => $data['count'] ? ( round( 100 / $data['count'] * $data['indexed'], 2 ) ) : 100 );
|
||||
return array( 'text' => $lang, 'complete' => $data['count'] ? ( round( 100 / $data['count'] * $offset, 2 ) ) : 100 );
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,15 @@ class _MemberContent
|
||||
}
|
||||
|
||||
/* Get count */
|
||||
$data['count'] = $classname::db()->select( 'COUNT(*)', $classname::$databaseTable, static::_getWhere( $data ) )->first();
|
||||
try
|
||||
{
|
||||
$data['count'] = $classname::db()->select( 'COUNT(*)', $classname::$databaseTable, static::_getWhere( $data ) )->first();
|
||||
}
|
||||
catch( \IPS\Db\Exception $e )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if ( !$data['count'] )
|
||||
{
|
||||
return NULL;
|
||||
|
||||
@@ -62,11 +62,28 @@ class _MoveMembers
|
||||
$select = $this->getQuery( 'core_members.*', $data, $offset );
|
||||
|
||||
if ( !$select->count() or $offset > $data['count'] )
|
||||
{
|
||||
if( isset( $data['oldGroup'] ) AND $data['oldGroup'] )
|
||||
{
|
||||
$cacheKey = 'groupMembersCount_' . $data['oldGroup'];
|
||||
unset( \IPS\Data\Store::i()->$cacheKey );
|
||||
}
|
||||
|
||||
$cacheKey = 'groupMembersCount_' . $data['group'];
|
||||
unset( \IPS\Data\Store::i()->$cacheKey );
|
||||
|
||||
throw new \IPS\Task\Queue\OutOfRangeException;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$newGroup = \IPS\Member\Group::load( $data['group'] );
|
||||
}
|
||||
catch ( \OutOfRangeException $e )
|
||||
{
|
||||
throw new \IPS\Task\Queue\OutOfRangeException;
|
||||
}
|
||||
|
||||
$newGroup = \IPS\Member\Group::load( $data['group'] );
|
||||
$by = isset( $data['by'] ) ? \IPS\Member::load( $data['by'] ) : FALSE;
|
||||
|
||||
foreach( $select AS $row )
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
<?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 18 Feb 2021
|
||||
*/
|
||||
|
||||
namespace IPS\core\extensions\core\Queue;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Background Task
|
||||
*/
|
||||
class _PWANotifications
|
||||
{
|
||||
/**
|
||||
* Parse data before queuing
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
public function preQueueData( $data )
|
||||
{
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run Background Task
|
||||
*
|
||||
* @param mixed $data Data as it was passed to \IPS\Task::queue()
|
||||
* @param int $offset Offset
|
||||
* @return int New offset
|
||||
* @throws \IPS\Task\Queue\OutOfRangeException Indicates offset doesn't exist and thus task is complete
|
||||
*/
|
||||
public function run( $data, $offset )
|
||||
{
|
||||
for ( $i = 0; $i < $offset; $i++ )
|
||||
{
|
||||
next( $data );
|
||||
}
|
||||
|
||||
$current = current( $data );
|
||||
if ( $current === FALSE )
|
||||
{
|
||||
throw new \IPS\Task\Queue\OutOfRangeException;
|
||||
}
|
||||
|
||||
\IPS\Notification::sendPWANotifications( [ key( $data ) => $current ] );
|
||||
|
||||
return ++$offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Progress
|
||||
*
|
||||
* @param mixed $data Data as it was passed to \IPS\Task::queue()
|
||||
* @param int $offset Offset
|
||||
* @return array( 'text' => 'Doing something...', 'complete' => 50 ) Text explaining task and percentage complete
|
||||
* @throws \OutOfRangeException Indicates offset doesn't exist and thus task is complete
|
||||
*/
|
||||
public function getProgress( $data, $offset )
|
||||
{
|
||||
return [
|
||||
'text' => \IPS\Member::loggedIn()->language()->addToStack('backgroundQueue_pwaNotifications'),
|
||||
'complete' => round( 100 / \count( $data ) * $offset, 2 ),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform post-completion processing
|
||||
*
|
||||
* @param array $data Data returned from preQueueData
|
||||
* @param bool $processed Was anything processed or not? If preQueueData returns NULL, this will be FALSE.
|
||||
* @return void
|
||||
*/
|
||||
public function postComplete( $data, $processed = TRUE )
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -87,6 +87,22 @@ class _PruneLargeTable
|
||||
*/
|
||||
public function getProgress( $data, $offset )
|
||||
{
|
||||
return array( 'text' => \IPS\Member::loggedIn()->language()->addToStack( 'backgroundQueue_pruning_table', FALSE, array( 'sprintf' => \IPS\Member::loggedIn()->language()->addToStack( 'prunetable_' . key( $data['setting'] ) ) ) ), 'complete' => $data['count'] ? ( round( 100 / $data['count'] * $data['pruned'], 2 ) ) : 100 );
|
||||
return array( 'text' => \IPS\Member::loggedIn()->language()->addToStack( 'backgroundQueue_pruning_table', FALSE, array( 'sprintf' => \IPS\Member::loggedIn()->language()->addToStack( 'prunetable_' . $data['setting'] ) ) ), 'complete' => $data['count'] ? ( round( 100 / $data['count'] * $data['pruned'], 2 ) ) : 100 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform post-completion processing
|
||||
*
|
||||
* @param array $data Data returned from preQueueData
|
||||
* @param bool $processed Was anything processed or not? If preQueueData returns NULL, this will be FALSE.
|
||||
* @return void
|
||||
*/
|
||||
public function postComplete( $data, $processed = TRUE )
|
||||
{
|
||||
/* If this was pruning follows, make sure to clear the follow count cache so it can rebuild */
|
||||
if( $data['setting'] == 'prune_follows' )
|
||||
{
|
||||
\IPS\Db::i()->delete( 'core_follow_count_cache' );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,6 +63,12 @@ class _PruneMembers
|
||||
|
||||
if ( !$total )
|
||||
{
|
||||
if( !empty( $data['group'] ) )
|
||||
{
|
||||
$cacheKey = 'groupMembersCount_' . $data['group'];
|
||||
unset( \IPS\Data\Store::i()->$cacheKey );
|
||||
}
|
||||
|
||||
throw new \IPS\Task\Queue\OutOfRangeException;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,218 @@
|
||||
<?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 15 Mar 2021
|
||||
*/
|
||||
|
||||
namespace IPS\core\extensions\core\Queue;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Background Task
|
||||
*/
|
||||
class _RebuildAchievements
|
||||
{
|
||||
/**
|
||||
* @brief Number of items to build per cycle
|
||||
*/
|
||||
public $perCycle = \IPS\REBUILD_SLOW;
|
||||
|
||||
/**
|
||||
* Parse data before queuing
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
public function preQueueData( $data )
|
||||
{
|
||||
$data['processed'] = 0;
|
||||
$data['count'] = 0;
|
||||
$data['lastId'] = 0;
|
||||
|
||||
try
|
||||
{
|
||||
list( $app, $extension ) = explode( '_', $data['extension'] );
|
||||
$extensionObject = \IPS\Application::load( $app )->extensions( 'core', 'AchievementAction' )[ $extension ];
|
||||
$first = false;
|
||||
foreach( $data['data'] as $entry )
|
||||
{
|
||||
if ( ! $first )
|
||||
{
|
||||
$first = true;
|
||||
$data['currentTable'] = $entry['table'];
|
||||
}
|
||||
|
||||
$where = $entry['where'];
|
||||
if ( $data['time'] and $entry['date'] )
|
||||
{
|
||||
$where[] = [ $entry['date'] . ' > ?', $data['time'] ];
|
||||
}
|
||||
|
||||
$data['count'] += \IPS\Db::i()->select( 'COUNT(*)', $entry['table'], $where )->first();
|
||||
}
|
||||
}
|
||||
catch( \Exception $ex )
|
||||
{
|
||||
throw new \OutOfRangeException;
|
||||
}
|
||||
|
||||
if( $data['count'] == 0 )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run Background Task
|
||||
*
|
||||
* @param mixed $data Data as it was passed to \IPS\Task::queue()
|
||||
* @param int $offset Offset
|
||||
* @return int New offset
|
||||
* @throws \IPS\Task\Queue\OutOfRangeException Indicates offset doesn't exist and thus task is complete
|
||||
*/
|
||||
public function run( &$data, $offset )
|
||||
{
|
||||
/* Silence notifications */
|
||||
\IPS\Notification::silence();
|
||||
|
||||
$process = NULL;
|
||||
foreach( $data['data'] as $entry )
|
||||
{
|
||||
if ( $data['currentTable'] == $entry['table'] )
|
||||
{
|
||||
$process = $entry;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $process === NULL )
|
||||
{
|
||||
\IPS\Log::log( "Nothing to process for " . json_encode( $data['data'] ), "achievements" );
|
||||
throw new \IPS\Task\Queue\OutOfRangeException;
|
||||
}
|
||||
|
||||
list( $app, $extension ) = explode( '_', $data['extension'] );
|
||||
$extensionObject = \IPS\Application::load( $app )->extensions( 'core', 'AchievementAction' )[ $extension ];
|
||||
|
||||
$where = $process['where'];
|
||||
if ( \stristr( $process['pkey'], '(' ) )
|
||||
{
|
||||
$where[] = [ $process['pkey'] . ' > ?', $data['lastId'] ];
|
||||
}
|
||||
else
|
||||
{
|
||||
$where[] = [ '`' . $process['pkey'] . '` > ?', $data['lastId'] ];
|
||||
}
|
||||
|
||||
if ( $data['time'] and $process['date'] )
|
||||
{
|
||||
$where[] = [ '`' . $process['date'] . '` > ?', $data['time'] ];
|
||||
}
|
||||
|
||||
$done = 0;
|
||||
$select = ( isset( $process['select'] ) ) ? array_merge( [ '*' ], $process['select'] ) : [ '*' ];
|
||||
foreach( \IPS\Db::i()->select( implode( ',', $select ), $process['table'], $where, $process['pkey'], $this->perCycle ) as $dbRow )
|
||||
{
|
||||
$data['lastId'] = $dbRow[ $process['pkey'] ];
|
||||
|
||||
$done++;
|
||||
try
|
||||
{
|
||||
$extensionObject::rebuildRow( $dbRow, $process );
|
||||
}
|
||||
catch( \Exception $e ) {}
|
||||
}
|
||||
|
||||
if( ! $done )
|
||||
{
|
||||
if ( \count( $data['data'] ) > 1 )
|
||||
{
|
||||
$oldTable = $data['currentTable'];
|
||||
$doNext = FALSE;
|
||||
$found = FALSE;
|
||||
foreach( $data['data'] as $entry )
|
||||
{
|
||||
if ( $oldTable == $entry['table'] )
|
||||
{
|
||||
$doNext = TRUE;
|
||||
}
|
||||
else if ( $doNext )
|
||||
{
|
||||
$found = TRUE;
|
||||
$data['currentTable'] = $entry['table'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $found )
|
||||
{
|
||||
$data['lastId'] = 0;
|
||||
\IPS\Log::debug( "Finished {$data['extension']} {$oldTable}, next {$data['currentTable']}", "achievements" );
|
||||
}
|
||||
else
|
||||
{
|
||||
\IPS\Log::debug("Finished " . $data['extension'] . " as nothing to process", "achievements");
|
||||
throw new \IPS\Task\Queue\OutOfRangeException;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
\IPS\Log::debug("Finished " . $data['extension'] . " as nothing to process", "achievements");
|
||||
throw new \IPS\Task\Queue\OutOfRangeException;
|
||||
}
|
||||
}
|
||||
|
||||
$data['processed'] += $done;
|
||||
|
||||
/* We use lastId in the data and not the offset anyway, but it's nice to be nice to the framework */
|
||||
return \is_numeric( $data['lastId'] ) ? $data['lastId'] : 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Progress
|
||||
*
|
||||
* @param mixed $data Data as it was passed to \IPS\Task::queue()
|
||||
* @param int $offset Offset
|
||||
* @return array( 'text' => 'Doing something...', 'complete' => 50 ) Text explaining task and percentage complete
|
||||
* @throws \OutOfRangeException Indicates offset doesn't exist and thus task is complete
|
||||
*/
|
||||
public function getProgress( $data, $offset )
|
||||
{
|
||||
list( $app, $extension ) = explode( '_', $data['extension'] );
|
||||
if ( !\IPS\Application::appIsEnabled( $app ) )
|
||||
{
|
||||
throw new \OutOfRangeException;
|
||||
}
|
||||
|
||||
return array( 'text' => \IPS\Member::loggedIn()->language()->addToStack('rebuilding_achievements', FALSE, array( 'htmlsprintf' => array( $data['extension'] ) ) ), 'complete' => $data['count'] ? ( round( 100 / $data['count'] * $data['processed'], 2 ) ) : 100 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform post-completion processing
|
||||
*
|
||||
* @param array $data Data returned from preQueueData
|
||||
* @param bool $processed Was anything processed or not? If preQueueData returns NULL, this will be FALSE.
|
||||
* @return void
|
||||
*/
|
||||
public function postComplete( $data, $processed = TRUE )
|
||||
{
|
||||
/* The core_queue row is deleted before this is run */
|
||||
if ( ! \IPS\Db::i()->select( 'COUNT(*)', 'core_queue', [ [ '`key`=?', 'RebuildAchievements' ] ] )->first() )
|
||||
{
|
||||
/* Rebuilding is complete! */
|
||||
\IPS\Settings::i()->changeValues( array( 'achievements_rebuilding' => 0, 'achievements_last_rebuilt' => time() ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,11 @@ class _RebuildAttachmentThumbnails
|
||||
*/
|
||||
public function preQueueData( $data )
|
||||
{
|
||||
if( \IPS\Settings::i()->attachment_image_size == '0x0' )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$data['count'] = \IPS\Db::i()->select( 'MAX(attach_id)', 'core_attachments', array( 'attach_is_image=?', 1 ) )->first();
|
||||
@@ -65,6 +70,11 @@ class _RebuildAttachmentThumbnails
|
||||
*/
|
||||
public function run( &$data, $offset )
|
||||
{
|
||||
if( \IPS\Settings::i()->attachment_image_size == '0x0' )
|
||||
{
|
||||
throw new \IPS\Task\Queue\OutOfRangeException;
|
||||
}
|
||||
|
||||
$last = NULL;
|
||||
|
||||
\IPS\Log::debug( "Rebuilding attachment thumbnails with an offset of " . $offset, 'rebuildAttachmentThumbnails' );
|
||||
@@ -110,7 +120,6 @@ class _RebuildAttachmentThumbnails
|
||||
we don't need to build a thumbnail */
|
||||
if( !$attachment['attach_thumb_location'] )
|
||||
{
|
||||
$thumbDims = \IPS\Settings::i()->attachment_image_size ? explode( 'x', \IPS\Settings::i()->attachment_image_size ) : array( 1000, 750 );
|
||||
$dimensions = $file->getImageDimensions();
|
||||
|
||||
if ( $dimensions[0] < $thumbDims[0] and $dimensions[1] < $thumbDims[1] )
|
||||
|
||||
@@ -136,7 +136,7 @@ class _RebuildImageProxy
|
||||
\IPS\Data\Store::i()->currentImageProxyRebuild = array( $classname, $item->$idColumn );
|
||||
|
||||
/* Only run an update if the content has actually changed */
|
||||
if( $newContent = \IPS\Text\Parser::removeImageProxy( $item->$contentColumn, !$data['cachePeriod'] ) AND $newContent != $item->$contentColumn )
|
||||
if( mb_strpos( $item->$contentColumn, 'imageproxy.php' ) AND $newContent = \IPS\Text\Parser::removeImageProxy( $item->$contentColumn, !$data['cachePeriod'] ) AND $newContent != $item->$contentColumn )
|
||||
{
|
||||
$item->$contentColumn = $newContent;
|
||||
|
||||
@@ -146,6 +146,20 @@ class _RebuildImageProxy
|
||||
}
|
||||
/* Content item may be orphaned, continue if we cannot save it. */
|
||||
catch( \OutOfRangeException $e ) {}
|
||||
/* Status updates could cause an error with legacy data, which we'll need to ignore */
|
||||
catch( \IPS\Db\Exception $e )
|
||||
{
|
||||
/* If this is not a "data too long for column" error we want to throw the exception regardless of what type of content */
|
||||
if( $e->getCode() != 1406 )
|
||||
{
|
||||
throw $e;
|
||||
}
|
||||
/* We are only hiding this error for status updates and replies */
|
||||
elseif( !( $item instanceof \IPS\core\Statuses\Status OR $item instanceof \IPS\core\Statuses\Reply ) )
|
||||
{
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$last = $item->$idColumn;
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Background Task: Rebuild URL Rels
|
||||
* @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 05 Apr 2017
|
||||
*/
|
||||
|
||||
namespace IPS\core\extensions\core\Queue;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Background Task: Rebuild image proxy
|
||||
*/
|
||||
class _RebuildUrlRels
|
||||
{
|
||||
/**
|
||||
* @brief Number of content items to rebuild per cycle
|
||||
*/
|
||||
public $rebuild = \IPS\REBUILD_NORMAL;
|
||||
|
||||
/**
|
||||
* Parse data before queuing
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
public function preQueueData( $data )
|
||||
{
|
||||
$classname = $data['class'];
|
||||
|
||||
\IPS\Log::debug( "Getting preQueueData for " . $classname, 'RebuildUrlRels' );
|
||||
|
||||
try
|
||||
{
|
||||
$data['count'] = $classname::db()->select( 'MAX(' . $classname::$databasePrefix . $classname::$databaseColumnId . ')', $classname::$databaseTable, ( is_subclass_of( $classname, 'IPS\Content\Comment' ) ) ? $classname::commentWhere() : array() )->first();
|
||||
$data['realCount'] = $classname::db()->select( 'COUNT(*)', $classname::$databaseTable, ( is_subclass_of( $classname, 'IPS\Content\Comment' ) ) ? $classname::commentWhere() : array() )->first();
|
||||
|
||||
/* We're going to use the < operator, so we need to ensure the most recent item is rebuilt */
|
||||
$data['runPid'] = $data['count'] + 1;
|
||||
}
|
||||
catch( \Exception $ex )
|
||||
{
|
||||
throw new \OutOfRangeException;
|
||||
}
|
||||
|
||||
\IPS\Log::debug( "PreQueue count for " . $classname . " is " . $data['count'], 'RebuildUrlRels' );
|
||||
|
||||
if( $data['count'] == 0 )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
$data['indexed'] = 0;
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run Background Task
|
||||
*
|
||||
* @param mixed $data Data as it was passed to \IPS\Task::queue()
|
||||
* @param int $offset Offset
|
||||
* @return int New offset
|
||||
* @throws \IPS\Task\Queue\OutOfRangeException Indicates offset doesn't exist and thus task is complete
|
||||
*/
|
||||
public function run( &$data, $offset )
|
||||
{
|
||||
/* We want to allow read/write separation in this task */
|
||||
\IPS\Db::i()->readWriteSeparation = TRUE;
|
||||
|
||||
$classname = $data['class'];
|
||||
$exploded = explode( '\\', $classname );
|
||||
if ( !class_exists( $classname ) or !\IPS\Application::appIsEnabled( $exploded[1] ) )
|
||||
{
|
||||
/* Turn off read/write separation before returning */
|
||||
\IPS\Db::i()->readWriteSeparation = FALSE;
|
||||
|
||||
throw new \IPS\Task\Queue\OutOfRangeException;
|
||||
}
|
||||
|
||||
/* Make sure there's even content to parse */
|
||||
if( !isset( $classname::$databaseColumnMap['content'] ) )
|
||||
{
|
||||
/* Turn off read/write separation before returning */
|
||||
\IPS\Db::i()->readWriteSeparation = FALSE;
|
||||
|
||||
throw new \IPS\Task\Queue\OutOfRangeException;
|
||||
}
|
||||
|
||||
\IPS\Log::debug( "Running " . $classname . ", with an offset of " . $offset, 'RebuildUrlRels' );
|
||||
|
||||
$where = ( is_subclass_of( $classname, 'IPS\Content\Comment' ) ) ? ( \is_array( $classname::commentWhere() ) ? array( $classname::commentWhere() ) : array() ) : array();
|
||||
$select = $classname::db()->select( '*', $classname::$databaseTable, array_merge( $where, array( array( $classname::$databasePrefix . $classname::$databaseColumnId . ' < ?', $data['runPid'] ) ) ), $classname::$databasePrefix . $classname::$databaseColumnId . ' DESC', array( 0, $this->rebuild ) );
|
||||
$iterator = new \IPS\Patterns\ActiveRecordIterator( $select, $classname );
|
||||
$last = NULL;
|
||||
|
||||
foreach( $iterator as $item )
|
||||
{
|
||||
$idColumn = $classname::$databaseColumnId;
|
||||
|
||||
/* Did the rebuild previously time out on this? If so we need to skip it and move along */
|
||||
if( isset( \IPS\Data\Store::i()->currentUrlRefRebuild ) )
|
||||
{
|
||||
/* If the last rebuild cycle timed out, currentRebuild might be set and we might have already rebuilt this post (the post that caused the rebuild to fail might come after this (but before in chronological order)).
|
||||
If that is the case, we should skip rebuilding this post again. */
|
||||
if( \is_array( \IPS\Data\Store::i()->currentUrlRefRebuild ) AND \IPS\Data\Store::i()->currentUrlRefRebuild[0] == $classname AND \IPS\Data\Store::i()->currentUrlRefRebuild[1] < $item->$idColumn )
|
||||
{
|
||||
$last = $item->$idColumn;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* If the last rebuild cycle failed and we have just retrieved the post we last attempted to rebuild, skip it and move along */
|
||||
if( \is_array( \IPS\Data\Store::i()->currentUrlRefRebuild ) AND \IPS\Data\Store::i()->currentUrlRefRebuild[0] == $classname AND \IPS\Data\Store::i()->currentUrlRefRebuild[1] == $item->$idColumn )
|
||||
{
|
||||
unset( \IPS\Data\Store::i()->currentUrlRefRebuild );
|
||||
$last = $item->$idColumn;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$contentColumn = $classname::$databaseColumnMap['content'];
|
||||
|
||||
/* Before we start trying to rebuild, set a flag to note what we are trying to rebuild. If it times out, we can check
|
||||
this on the next load and skip the problematic content */
|
||||
\IPS\Data\Store::i()->currentUrlRefRebuild = array( $classname, $item->$idColumn );
|
||||
|
||||
/* Only run an update if the content has actually changed */
|
||||
if( $newContent = \IPS\Text\Parser::rebuildUrlRels( $item->$contentColumn, $item->author() ) AND $newContent != $item->$contentColumn )
|
||||
{
|
||||
$item->$contentColumn = $newContent;
|
||||
|
||||
try
|
||||
{
|
||||
$item->save();
|
||||
}
|
||||
/* Content item may be orphaned, continue if we cannot save it. */
|
||||
catch( \OutOfRangeException $e ) {}
|
||||
/* Status updates could cause an error with legacy data, which we'll need to ignore */
|
||||
catch( \IPS\Db\Exception $e )
|
||||
{
|
||||
/* If this is not a "data too long for column" error we want to throw the exception regardless of what type of content */
|
||||
if( $e->getCode() != 1406 )
|
||||
{
|
||||
throw $e;
|
||||
}
|
||||
/* We are only hiding this error for status updates and replies */
|
||||
elseif( !( $item instanceof \IPS\core\Statuses\Status OR $item instanceof \IPS\core\Statuses\Reply ) )
|
||||
{
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$last = $item->$idColumn;
|
||||
|
||||
$data['indexed']++;
|
||||
|
||||
/* Now we will reset the rebuild flag we previously set since it rebuilt and saved successfully */
|
||||
unset( \IPS\Data\Store::i()->currentUrlRefRebuild );
|
||||
}
|
||||
|
||||
/* Store the runPid for the next iteration of this Queue task. This allows the progress bar to show correctly. */
|
||||
$data['runPid'] = $last;
|
||||
|
||||
/* Turn off read/write separation before returning */
|
||||
\IPS\Db::i()->readWriteSeparation = FALSE;
|
||||
|
||||
if( $last === NULL )
|
||||
{
|
||||
throw new \IPS\Task\Queue\OutOfRangeException;
|
||||
}
|
||||
|
||||
/* Return the number rebuilt so far, so that the rebuild progress bar text makes sense */
|
||||
return $data['indexed'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Progress
|
||||
*
|
||||
* @param mixed $data Data as it was passed to \IPS\Task::queue()
|
||||
* @param int $offset Offset
|
||||
* @return array( 'text' => 'Doing something...', 'complete' => 50 ) Text explaining task and percentage complete
|
||||
* @throws \OutOfRangeException Indicates offset doesn't exist and thus task is complete
|
||||
*/
|
||||
public function getProgress( $data, $offset )
|
||||
{
|
||||
$class = $data['class'];
|
||||
$exploded = explode( '\\', $class );
|
||||
if ( !class_exists( $class ) or !\IPS\Application::appIsEnabled( $exploded[1] ) )
|
||||
{
|
||||
throw new \OutOfRangeException;
|
||||
}
|
||||
|
||||
return array( 'text' => \IPS\Member::loggedIn()->language()->addToStack('rebuilding_urlrefs_stuff', FALSE, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->addToStack( $class::$title . '_pl_lc' ) ) ) ), 'complete' => $data['realCount'] ? ( round( 100 / $data['realCount'] * $data['indexed'], 2 ) ) : 100 );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
<?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 07 Apr 2021
|
||||
*/
|
||||
|
||||
namespace IPS\core\extensions\core\Queue;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Background Task
|
||||
*/
|
||||
class _Upgrade46FeaturedContent
|
||||
{
|
||||
/**
|
||||
* @brief Number of content items to rebuild per cycle
|
||||
*/
|
||||
public $rebuild = \IPS\REBUILD_SLOW;
|
||||
|
||||
/**
|
||||
* Parse data before queuing
|
||||
*
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
public function preQueueData( $data )
|
||||
{
|
||||
$classname = $data['class'];
|
||||
|
||||
try
|
||||
{
|
||||
$data['count'] = $classname::db()->select( 'MAX(' . $classname::$databasePrefix . $classname::$databaseColumnId . ')', $classname::$databaseTable, [ $classname::$databasePrefix . $classname::$databaseColumnMap['featured'] . '=?', 1 ] )->first();
|
||||
$data['realCount'] = $classname::db()->select( 'COUNT(*)', $classname::$databaseTable, [ $classname::$databasePrefix . $classname::$databaseColumnMap['featured'] . '=?', 1 ] )->first();
|
||||
|
||||
/* We're going to use the < operator, so we need to ensure the most recent item is rebuilt */
|
||||
$data['runId'] = $data['count'] + 1;
|
||||
}
|
||||
catch( \Exception $ex )
|
||||
{
|
||||
throw new \OutOfRangeException;
|
||||
}
|
||||
|
||||
\IPS\Log::debug( "PreQueue count for " . $classname . " is " . $data['count'], 'Upgrade46FeaturedContent' );
|
||||
|
||||
if( $data['count'] == 0 )
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
$data['indexed'] = 0;
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run Background Task
|
||||
*
|
||||
* @param mixed $data Data as it was passed to \IPS\Task::queue()
|
||||
* @param int $offset Offset
|
||||
* @return int New offset
|
||||
* @throws \IPS\Task\Queue\OutOfRangeException Indicates offset doesn't exist and thus task is complete
|
||||
*/
|
||||
public function run( &$data, $offset )
|
||||
{
|
||||
$classname = $data['class'];
|
||||
$exploded = explode( '\\', $classname );
|
||||
|
||||
if ( !class_exists( $classname ) or !\IPS\Application::appIsEnabled( $exploded[1] ) )
|
||||
{
|
||||
throw new \IPS\Task\Queue\OutOfRangeException;
|
||||
}
|
||||
|
||||
$select = $classname::db()->select( '*', $classname::$databaseTable, [ $classname::$databasePrefix . $classname::$databaseColumnMap['featured'] . '=1 AND ' . $classname::$databasePrefix . $classname::$databaseColumnId . ' < ?', $data['runId'] ], $classname::$databasePrefix . $classname::$databaseColumnId . ' DESC', array( 0, $this->rebuild ) );
|
||||
$last = NULL;
|
||||
|
||||
foreach( $select as $row )
|
||||
{
|
||||
try
|
||||
{
|
||||
\IPS\Db::i()->delete( 'core_content_featured', [ 'feature_content_id=? and feature_content_class=?', $row[ $classname::$databasePrefix . $classname::$databaseColumnId ], $classname ] );
|
||||
\IPS\Db::i()->insert( 'core_content_featured', [
|
||||
'feature_content_id' => $row[ $classname::$databasePrefix . $classname::$databaseColumnId ],
|
||||
'feature_content_class' => $classname,
|
||||
'feature_content_author' => $row[ $classname::$databaseColumnMap['author'] ],
|
||||
'feature_date' => $row[ $classname::$databaseColumnMap['date'] ]
|
||||
] );
|
||||
}
|
||||
catch( \Exception $e ){}
|
||||
|
||||
$last = $row[ $classname::$databasePrefix . $classname::$databaseColumnId ];
|
||||
|
||||
$data['indexed']++;
|
||||
}
|
||||
|
||||
/* Store the runId for the next iteration of this Queue task. This allows the progress bar to show correctly. */
|
||||
$data['runId'] = $last;
|
||||
|
||||
if( $last === NULL )
|
||||
{
|
||||
throw new \IPS\Task\Queue\OutOfRangeException;
|
||||
}
|
||||
|
||||
/* Return the number rebuilt so far, so that the rebuild progress bar text makes sense */
|
||||
return $data['indexed'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Progress
|
||||
*
|
||||
* @param mixed $data Data as it was passed to \IPS\Task::queue()
|
||||
* @param int $offset Offset
|
||||
* @return array( 'text' => 'Doing something...', 'complete' => 50 ) Text explaining task and percentage complete
|
||||
* @throws \OutOfRangeException Indicates offset doesn't exist and thus task is complete
|
||||
*/
|
||||
public function getProgress( $data, $offset )
|
||||
{
|
||||
$class = $data['class'];
|
||||
$exploded = explode( '\\', $class );
|
||||
if ( !class_exists( $class ) or !\IPS\Application::appIsEnabled( $exploded[1] ) )
|
||||
{
|
||||
throw new \OutOfRangeException;
|
||||
}
|
||||
|
||||
return array( 'text' => \IPS\Member::loggedIn()->language()->addToStack('rebuilding_featured_stuff', FALSE, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->addToStack( $class::$title . '_pl_lc' ) ) ) ), 'complete' => $data['realCount'] ? ( round( 100 / $data['realCount'] * $data['indexed'], 2 ) ) : 100 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform post-completion processing
|
||||
*
|
||||
* @param array $data Data returned from preQueueData
|
||||
* @param bool $processed Was anything processed or not? If preQueueData returns NULL, this will be FALSE.
|
||||
* @return void
|
||||
*/
|
||||
public function postComplete( $data, $processed = TRUE )
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -232,7 +232,7 @@ class _Content extends \IPS\Content\ExtensionGenerator
|
||||
/* We only want nodes we can see, and that have actual content inside */
|
||||
if( $node->can( 'view', new \IPS\Member ) and $node->getContentItemCount() )
|
||||
{
|
||||
$data = array( 'url' => $node->url(), 'lastmod' => $node->getLastCommentTime() );
|
||||
$data = array( 'url' => $node->url(), 'lastmod' => $node->getLastCommentTime( new \IPS\Member ) );
|
||||
|
||||
$priority = \intval( isset( $settings["sitemap_{$nodeClass::$nodeTitle}_priority"] ) ? $settings["sitemap_{$nodeClass::$nodeTitle}_priority"] : self::RECOMMENDED_NODE_PRIORIY );
|
||||
if ( $priority !== -1 )
|
||||
@@ -266,6 +266,8 @@ class _Content extends \IPS\Content\ExtensionGenerator
|
||||
$limitClause = array( $offset, $limit );
|
||||
|
||||
$where = $class::sitemapWhere();
|
||||
|
||||
$direction = ( $totalLimit > \IPS\Sitemap::MAX_PER_FILE ) ? 'ASC' : 'DESC';
|
||||
|
||||
/* Try to fetch the highest ID built in the last sitemap, if it exists */
|
||||
try
|
||||
@@ -274,7 +276,7 @@ class _Content extends \IPS\Content\ExtensionGenerator
|
||||
|
||||
if( $lastId > 0 )
|
||||
{
|
||||
$where[] = array( $class::$databaseTable . '.' . $class::$databasePrefix . $class::$databaseColumnId . ' > ?', $lastId );
|
||||
$where[] = array( $class::$databaseTable . '.' . $class::$databasePrefix . $class::$databaseColumnId . ' ' . ( $direction === 'ASC' ? '>' : '<' ) . ' ?', $lastId );
|
||||
$limitClause = $limit;
|
||||
}
|
||||
}
|
||||
@@ -283,8 +285,7 @@ class _Content extends \IPS\Content\ExtensionGenerator
|
||||
$idColumn = $class::$databaseColumnId;
|
||||
|
||||
$guest = new \IPS\Member;
|
||||
$direction = ( $totalLimit > \IPS\Sitemap::MAX_PER_FILE ) ? ' ASC' : ' DESC';
|
||||
foreach ( $class::getItemsWithPermission( $where, $class::$databasePrefix . $class::$databaseColumnId . $direction, $limitClause, 'read', \IPS\Content\Hideable::FILTER_PUBLIC_ONLY, \IPS\Content\Item::SELECT_IDS_FIRST, new \IPS\Member, TRUE ) as $item )
|
||||
foreach ( $class::getItemsWithPermission( $where, $class::$databasePrefix . $class::$databaseColumnId . ' ' . $direction, $limitClause, 'read', \IPS\Content\Hideable::FILTER_PUBLIC_ONLY, \IPS\Content\Item::SELECT_IDS_FIRST, new \IPS\Member, TRUE ) as $item )
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user