Version 5.0.0 beta 1

This commit is contained in:
Neo committed 2025-12-19 16:27:35 -08:00
1 parent 25ddeb65d6
commit 15c7beabc5
6736 files changed
+627902 -497943

No files matched your search

@@ -11,28 +11,46 @@
namespace IPS\forums\extensions\core\AchievementAction;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\core\Achievements\Actions\AchievementActionAbstract;
use IPS\core\Achievements\Rule;
use IPS\DateTime;
use IPS\Db;
use IPS\forums\Forum;
use IPS\forums\Topic\Post;
use IPS\Helpers\Form\Node;
use IPS\Helpers\Form\Number;
use IPS\Helpers\Form\Select;
use IPS\Http\Url;
use IPS\Member;
use IPS\Theme;
use OutOfRangeException;
use function count;
use function defined;
use function in_array;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Achievement Action Extension: Answer a member in a Q&A Forum is marked as the best answer
*/
class _AnswerMarkedBest extends \IPS\core\Achievements\Actions\AbstractAchievementAction
class AnswerMarkedBest extends AchievementActionAbstract
{
/**
* 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
* @param Url $url The URL the form is being shown on
* @return array
*/
public function filters( ?array $filters, \IPS\Http\Url $url ): array
public function filters( ?array $filters, Url $url ): array
{
return [
'nodes' => new \IPS\Helpers\Form\Node( 'achievement_filter_AnswerMarkedBest_forum', ( $filters and isset( $filters['nodes'] ) and $filters['nodes'] ) ? $filters['nodes'] : NULL, FALSE, [
'nodes' => new Node( 'achievement_filter_AnswerMarkedBest_forum', ( $filters and isset( $filters['nodes'] ) and $filters['nodes'] ) ? $filters['nodes'] : NULL, FALSE, [
'url' => $url,
'class' => 'IPS\forums\Forum',
'showAllNodes' => TRUE,
@@ -40,8 +58,14 @@ class _AnswerMarkedBest extends \IPS\core\Achievements\Actions\AbstractAchieveme
'permissionCheck' => function( $forum ) {
return $forum->forums_bitoptions['bw_enable_answers'];
}
], NULL, \IPS\Member::loggedIn()->language()->addToStack('achievement_filter_AnswerMarkedBest_forum_prefix') ),
'milestone' => new \IPS\Helpers\Form\Number( 'achievement_filter_AnswerMarkedBest_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_AnswerMarkedBest_nth_suffix') )
], NULL, Member::loggedIn()->language()->addToStack('achievement_filter_AnswerMarkedBest_forum_prefix') ),
'helpful_or_solved' => new Select( 'achievement_filter_AnswerMarkedBest_helpful_or_solved', ( $filters and isset( $filters['helpful_or_solved'] ) and $filters['helpful_or_solved'] ) ? $filters['helpful_or_solved'] : NULL, NULL, [
'options' => [
'helpful' => Member::loggedIn()->language()->addToStack('achievement_filter_AnswerMarkedBest_helpful_or_solved_helpful'),
'solved' => Member::loggedIn()->language()->addToStack('achievement_filter_AnswerMarkedBest_helpful_or_solved_solved')
]
] ),
'milestone' => new Number( 'achievement_filter_AnswerMarkedBest_nth', ( $filters and isset( $filters['milestone'] ) and $filters['milestone'] ) ? $filters['milestone'] : 0, FALSE, [], NULL, Member::loggedIn()->language()->addToStack('achievement_filter_nth_their'), Member::loggedIn()->language()->addToStack('achievement_filter_AnswerMarkedBest_nth_suffix') )
];
}
@@ -62,6 +86,10 @@ class _AnswerMarkedBest extends \IPS\core\Achievements\Actions\AbstractAchieveme
{
$return['milestone'] = $values['achievement_filter_AnswerMarkedBest_nth'];
}
if ( isset( $values['achievement_filter_AnswerMarkedBest_helpful_or_solved'] ) )
{
$return['helpful_or_solved'] = $values['achievement_filter_AnswerMarkedBest_helpful_or_solved'];
}
return $return;
}
@@ -72,14 +100,16 @@ class _AnswerMarkedBest extends \IPS\core\Achievements\Actions\AbstractAchieveme
* 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 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
public function filtersMatch( Member $subject, array $filters, mixed $extra = NULL ): bool
{
if ( isset( $filters['nodes'] ) and !\in_array( $extra->container()->_id, $filters['nodes'] ) )
$post = $extra['post'];
$type = $extra['type'] == 'helpful'? 'helpful' :'solved';
if ( isset( $filters['nodes'] ) and !in_array( $post->container()->_id, $filters['nodes'] ) )
{
return FALSE;
}
@@ -87,13 +117,13 @@ class _AnswerMarkedBest extends \IPS\core\Achievements\Actions\AbstractAchieveme
if ( isset( $filters['milestone'] ) )
{
$where = [
[ 'member_id=? AND app=?', $extra->author()->member_id, 'forums' ],
[ 'member_id=? AND app=? AND type=? AND hidden=0', $post->author()->member_id, 'forums', $type ],
];
if ( isset( $filters['nodes'] ) )
{
$where[] = [ \IPS\Db::i()->in( 'forum_id', $filters['nodes'] ) ];
$where[] = [ Db::i()->in( 'forum_id', $filters['nodes'] ) ];
}
$query = \IPS\Db::i()->select( 'COUNT(*)', 'core_solved_index', $where );
$query = Db::i()->select( 'COUNT(*)', 'core_solved_index', $where );
if ( isset( $filters['nodes'] ) )
{
$query->join( 'forums_topics', 'core_solved_index.item_id=forums_topics.tid' );
@@ -129,7 +159,7 @@ class _AnswerMarkedBest extends \IPS\core\Achievements\Actions\AbstractAchieveme
* @param array|NULL $filters Current filter values
* @return array
*/
public function awardOther( $extra = NULL, ?array $filters = NULL ): array
public function awardOther( mixed $extra = NULL, ?array $filters = NULL ): array
{
return [ $extra->item()->author() ];
}
@@ -138,13 +168,16 @@ class _AnswerMarkedBest extends \IPS\core\Achievements\Actions\AbstractAchieveme
* 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 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
public function identifier( Member $subject, mixed $extra = NULL ): string
{
return (string) $extra->pid;
$post = $extra['post'];
$type = $extra['type'] == 'helpful'? ':helpful' : ''; // Prevent existing rows from becoming obsolete by changing format to 123:solved
return $post->pid . $type;
}
/**
@@ -157,44 +190,57 @@ class _AnswerMarkedBest extends \IPS\core\Achievements\Actions\AbstractAchieveme
public function logRow( string $identifier, array $actor ): string
{
$sprintf = [];
try
if ( stristr( $identifier, ':') )
{
$post = \IPS\forums\Topic\Post::load( $identifier );
$sprintf = [ 'htmlsprintf' => [
\IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->basicUrl( $post->url(), TRUE, $post->item()->title, FALSE )
] ];
}
catch ( \OutOfRangeException $e )
{
$sprintf = [ 'sprintf' => [ \IPS\Member::loggedIn()->language()->addToStack('AchievementAction__AnswerMarkedBest_log_deleted') ] ];
}
if ( \in_array( 'subject', $actor ) )
{
return \IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__AnswerMarkedBest_log_subject', FALSE, $sprintf );
[ $identifier, $type ] = explode( ':', $identifier );
}
else
{
return \IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__AnswerMarkedBest_log_other', FALSE, $sprintf );
$type = 'solved';
}
try
{
$post = Post::load( $identifier );
$sprintf = [ 'htmlsprintf' => [
Theme::i()->getTemplate( 'global', 'core', 'global' )->basicUrl( $post->url(), TRUE, $post->item()->title, FALSE )
] ];
}
catch ( OutOfRangeException $e )
{
$sprintf = [ 'sprintf' => [ Member::loggedIn()->language()->addToStack('AchievementAction__AnswerMarkedBest_log_deleted') ] ];
}
if ( in_array( 'subject', $actor ) )
{
return $type === 'solved'
? Member::loggedIn()->language()->addToStack( 'AchievementAction__AnswerMarkedBest_log_subject', FALSE, $sprintf )
: Member::loggedIn()->language()->addToStack( 'AchievementAction__AnswerMarkedBest_log_subject_helpful', FALSE, $sprintf );
}
else
{
return $type === 'solved'
? Member::loggedIn()->language()->addToStack( 'AchievementAction__AnswerMarkedBest_log_other', FALSE, $sprintf )
: Member::loggedIn()->language()->addToStack( 'AchievementAction__AnswerMarkedBest_log_other_helpful', FALSE, $sprintf );
}
}
/**
* Get "description" for rule
*
* @param \IPS\core\Achievements\Rule $rule The rule
* @param Rule $rule The rule
* @return string|null
*/
public function ruleDescription( \IPS\core\Achievements\Rule $rule ): ?string
public function ruleDescription( Rule $rule ): ?string
{
$conditions = [];
if ( isset( $rule->filters['milestone'] ) )
{
$conditions[] = \IPS\Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone', FALSE, [
$conditions[] = 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'] ] ] ) )
Theme::i()->getTemplate( 'achievements' )->ruleDescriptionBadge( 'milestone', Member::loggedIn()->language()->addToStack( 'achievements_title_filter_milestone_nth', FALSE, [ 'pluralize' => [ $rule->filters['milestone'] ] ] ) )
],
'sprintf' => \IPS\Member::loggedIn()->language()->addToStack( 'best_answer_post', FALSE, [ 'strtolower' => TRUE ] )
'sprintf' => Member::loggedIn()->language()->addToStack( 'best_answer_post', FALSE, [ 'strtolower' => TRUE ] )
] );
}
if ( isset( $rule->filters['nodes'] ) )
@@ -204,30 +250,32 @@ class _AnswerMarkedBest extends \IPS\core\Achievements\Actions\AbstractAchieveme
{
try
{
$forumNames[] = \IPS\forums\Forum::load( $id )->_title;
$forumNames[] = Forum::load( $id )->_title;
}
catch ( \OutOfRangeException $e ) {}
catch ( OutOfRangeException $e ) {}
}
if ( $forumNames )
{
$conditions[] = \IPS\Member::loggedIn()->language()->addToStack( 'achievements_title_filter_location', FALSE, [
$conditions[] = Member::loggedIn()->language()->addToStack( 'achievements_title_filter_location', FALSE, [
'htmlsprintf' => [
\IPS\Theme::i()->getTemplate( 'achievements' )->ruleDescriptionBadge( 'location',
\count( $forumNames ) === 1 ? $forumNames[0] : \IPS\Member::loggedIn()->language()->addToStack( 'achievements_title_filter_location_val', FALSE, [ 'sprintf' => [
\count( $forumNames ),
\IPS\Member::loggedIn()->language()->addToStack( \IPS\forum\Forum::$nodeTitle, FALSE, [ 'strtolower' => TRUE ] )
Theme::i()->getTemplate( 'achievements' )->ruleDescriptionBadge( 'location',
count( $forumNames ) === 1 ? $forumNames[0] : Member::loggedIn()->language()->addToStack( 'achievements_title_filter_location_val', FALSE, [ 'sprintf' => [
count( $forumNames ),
Member::loggedIn()->language()->addToStack( Forum::$nodeTitle, FALSE, [ 'strtolower' => TRUE ] )
] ] ),
\count( $forumNames ) === 1 ? NULL : $forumNames
count( $forumNames ) === 1 ? NULL : $forumNames
)
],
] );
}
}
return \IPS\Theme::i()->getTemplate( 'achievements' )->ruleDescription(
\IPS\Member::loggedIn()->language()->addToStack( 'AchievementAction__AnswerMarkedBest_title' ),
$conditions
);
$title = Member::loggedIn()->language()->addToStack( 'AchievementAction__AnswerMarkedBest_title' );
if ( isset( $rule->filters['helpful_or_solved'] ) and $rule->filters['helpful_or_solved'] == 'helpful' )
{
$title = Member::loggedIn()->language()->addToStack( 'AchievementAction__AnswerMarkedHelpful_title' );
}
return Theme::i()->getTemplate( 'achievements' )->ruleDescription( $title, $conditions );
}
/**
@@ -235,13 +283,13 @@ class _AnswerMarkedBest extends \IPS\core\Achievements\Actions\AbstractAchieveme
*
* @return array
*/
static public function rebuildData()
static public function rebuildData(): array
{
return [ [
'table' => 'core_solved_index',
'pkey' => 'id',
'date' => 'solved_date',
'where' => [ [ 'app=?', 'forums' ] ],
'where' => [ [ 'app=? AND type=?', 'forums', 'solved' ] ],
] ];
}
@@ -252,9 +300,9 @@ class _AnswerMarkedBest extends \IPS\core\Achievements\Actions\AbstractAchieveme
* @param array $data Data collected when starting rebuild [table, pkey...]
* @return void
*/
public static function rebuildRow( $row, $data )
public static function rebuildRow( array $row, array $data ) : void
{
$post = \IPS\forums\Topic\Post::load( $row['comment_id'] );
$post->author()->achievementAction( 'forums', 'AnswerMarkedBest', $post, \IPS\DateTime::ts( $row[ $data['date'] ] ) );
$post = Post::load( $row['comment_id'] );
$post->author()->achievementAction( 'forums', 'AnswerMarkedBest', $post, DateTime::ts( $row[ $data['date'] ] ) );
}
}
@@ -12,24 +12,36 @@
namespace IPS\forums\extensions\core\AdvertisementLocations;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\Extensions\AdvertisementLocationsAbstract;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Advertisement locations extension
*/
class _ForumListing
class ForumListing extends AdvertisementLocationsAbstract
{
/**
* If this ad location is used in a list view, we allow the admin
* to define intervals or fixed positions within the list
*
* @var bool
*/
public static bool $listView = true;
/**
* Get the locations and the additional settings
*
* @param array $settings Current setting values
* @return array Array with two elements: 'locations' which should have keys as the location keys and values as the fields to toggle, and 'settings' which are additional fields to add to the form
*/
public function getSettings( $settings )
public function getSettings( array $settings ): array
{
return array( 'locations' => array( 'ad_forum_listing' => array() ), 'settings' => array() );
}
@@ -40,7 +52,7 @@ class _ForumListing
* @param array $values Values from the form submission
* @return array Array of setting key => value to store
*/
public function parseSettings( $values )
public function parseSettings( array $values ): array
{
return array();
}
@@ -12,32 +12,39 @@
namespace IPS\forums\extensions\core\AdvertisementLocations;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\Extensions\AdvertisementLocationsAbstract;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Advertisement locations extension
*/
class _IndexView
class IndexView extends AdvertisementLocationsAbstract
{
/**
/**
* If this ad location is used in a list view, we allow the admin
* to define intervals or fixed positions within the list
*
* @var bool
*/
public static bool $listView = true;
/**
* Get the locations and the additional settings
*
* @param array $settings Current setting values
* @return array Array with two elements: 'locations' which should have keys as the location keys and values as the fields to toggle, and 'settings' which are additional fields to add to the form
*/
public function getSettings( $settings )
public function getSettings( array $settings ): array
{
return array(
'locations' => array( 'ad_fluid_index_view' => array( 'ad_fluid_index_view_number', 'ad_fluid_index_view_repeat' ) ),
'settings' => array(
/* Some communities may already have a 0 stored for `ad_fluid_index_view_number`, we now need to make sure it's at least 1 */
new \IPS\Helpers\Form\Number( 'ad_fluid_index_view_number', ( isset( $settings['ad_fluid_index_view_number'] ) AND $settings['ad_fluid_index_view_number'] ) ? $settings['ad_fluid_index_view_number'] : 1, FALSE, array( 'min' => 1 ), NULL, NULL, \IPS\Member::loggedIn()->language()->addToStack('ad_fluid_index_view_number_suffix'), 'ad_fluid_index_view_number' ),
new \IPS\Helpers\Form\Number( 'ad_fluid_index_view_repeat', ( isset( $settings['ad_fluid_index_view_repeat'] ) ) ? $settings['ad_fluid_index_view_repeat'] : 0, FALSE, array( 'unlimited' => -1 ), NULL, NULL, \IPS\Member::loggedIn()->language()->addToStack('ad_fluid_index_view_repeat_suffix'), 'ad_fluid_index_view_repeat' )
)
'locations' => array( 'ad_fluid_index_view' => array() )
);
}
@@ -47,11 +54,8 @@ class _IndexView
* @param array $values Values from the form submission
* @return array Array of setting key => value to store
*/
public function parseSettings( $values )
public function parseSettings( array $values ): array
{
return array(
'ad_fluid_index_view_number' => $values['ad_fluid_index_view_number'],
'ad_fluid_index_view_repeat' => $values['ad_fluid_index_view_repeat']
);
return array();
}
}
@@ -12,24 +12,36 @@
namespace IPS\forums\extensions\core\AdvertisementLocations;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\Extensions\AdvertisementLocationsAbstract;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Advertisement locations extension
*/
class _TopicView
class TopicView extends AdvertisementLocationsAbstract
{
/**
* If this ad location is used in a list view, we allow the admin
* to define intervals or fixed positions within the list
*
* @var bool
*/
public static bool $listView = true;
/**
* Get the locations and the additional settings
*
* @param array $settings Current setting values
* @return array Array with two elements: 'locations' which should have keys as the location keys and values as the fields to toggle, and 'settings' which are additional fields to add to the form
*/
public function getSettings( $settings )
public function getSettings( array $settings ): array
{
return array( 'locations' => array( 'ad_topic_view' => array() ), 'settings' => array() );
}
@@ -40,7 +52,7 @@ class _TopicView
* @param array $values Values from the form submission
* @return array Array of setting key => value to store
*/
public function parseSettings( $values )
public function parseSettings( array $values ): array
{
return array();
}
@@ -12,26 +12,33 @@
namespace IPS\forums\extensions\core\Announcements;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\core\Announcements\Announcement;
use IPS\Extensions\AnnouncementsAbstract;
use IPS\Helpers\Form\FormAbstract;
use IPS\Helpers\Form\Node;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Announcements Extension: Forums
*/
class _Forums
class Forums extends AnnouncementsAbstract
{
/**
* @brief ID Field
*/
public static $idField = "id";
public static string $idField = "id";
/**
* @brief Controller classes
*/
public static $controllers = array(
public static array $controllers = array(
"IPS\\forums\\modules\\front\\forums\\forums",
"IPS\\forums\\modules\\front\\forums\\topic",
"IPS\\forums\\modules\\front\\forums\\index"
@@ -40,12 +47,12 @@ class _Forums
/**
* Get Setting Field
*
* @param \IPS\core\Announcements\Announcement $announcement
* @return Form element
* @param Announcement|null $announcement
* @return FormAbstract Form element
*/
public function getSettingField( $announcement )
public function getSettingField( ?Announcement $announcement ): FormAbstract
{
return new \IPS\Helpers\Form\Node( 'announce_forums', ( $announcement AND $announcement->ids ) ? explode( ",", $announcement->ids ) : 0, FALSE, array( 'class' => 'IPS\forums\Forum', 'zeroVal' => 'any', 'multiple' => TRUE, 'permissionCheck' => function ( $forum )
return new Node( 'announce_forums', ( $announcement AND $announcement->ids ) ? explode( ",", $announcement->ids ) : 0, FALSE, array( 'class' => 'IPS\forums\Forum', 'zeroVal' => 'any', 'multiple' => TRUE, 'permissionCheck' => function ( $forum )
{
return $forum->sub_can_post and !$forum->redirect_url;
} ), NULL, NULL, NULL, 'announce_forums' );
Whitespace-only changes.
@@ -1,71 +0,0 @@
<?php
/**
* @brief BBCode Tag
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
* @copyright (c) Invision Power Services, Inc.
* @license https://www.invisioncommunity.com/legal/standards/
* @package Invision Community
* @subpackage Forums
* @since 08 Feb 2016
*/
namespace IPS\forums\extensions\core\BBCode;
/* 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;
}
/**
* BBCode Tag
*/
class _post
{
/**
* Permission Check
*
* @param \IPS\Member $member The member
* @param string $area The Editor area
* @return bool
*/
public function permissionCheck( \IPS\Member $member, $area )
{
return \IPS\Text\Parser::canUse( $member, 'ipsLink', $area );
}
/**
* Get Configuration
*
* @code
return array(
'tag' => 'span', // The HTML tag to use
'attributes' => array( ... ) // Key/Value pairs of attributes to use (optional) - can use {option} to get the [tag=option] value
'callback' => function( $node, $matches, $document ) // A callback to modify the DOMNode object
{
...
},
'block' => FALSE, // If this is a block-level tag (optional, default false)
'single' => FALSE, // If this is a single tag, with no content (optional, default false)
)
* @endcode
* @return array
*/
public function getConfiguration()
{
return array(
'tag' => 'a',
'callback' => function( \DOMElement $node, $matches, \DOMDocument $document )
{
try
{
$node->setAttribute( 'href', (string) \IPS\forums\Topic\Post::load( \intval( $matches[2] ) )->url() );
}
catch ( \Exception $e ) {}
return $node;
},
);
}
}
@@ -1,73 +0,0 @@
<?php
/**
* @brief BBCode Tag
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
* @copyright (c) Invision Power Services, Inc.
* @license https://www.invisioncommunity.com/legal/standards/
* @package Invision Community
* @subpackage Forums
* @since 08 Feb 2016
*/
namespace IPS\forums\extensions\core\BBCode;
/* 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;
}
/**
* BBCode Tag
*/
class _snapback
{
/**
* Permission Check
*
* @param \IPS\Member $member The member
* @param string $area The Editor area
* @return bool
*/
public function permissionCheck( \IPS\Member $member, $area )
{
return \IPS\Text\Parser::canUse( $member, 'ipsLink', $area );
}
/**
* Get Configuration
*
* @code
return array(
'tag' => 'span', // The HTML tag to use
'attributes' => array( ... ) // Key/Value pairs of attributes to use (optional) - can use {option} to get the [tag=option] value
'callback' => function( $node, $matches, $document ) // A callback to modify the DOMNode object
{
...
},
'block' => FALSE, // If this is a block-level tag (optional, default false)
'single' => FALSE, // If this is a single tag, with no content (optional, default false)
)
* @endcode
* @return array
*/
public function getConfiguration()
{
return array(
'tag' => 'a',
'callback' => function( \DOMElement $node, $matches, \DOMDocument $document )
{
try
{
$node->setAttribute( 'href', (string) \IPS\forums\Topic\Post::load( \intval( $matches[2] ) )->url() );
$node->appendChild( $document->createEntityReference( 'larr' ) );
}
catch ( \Exception $e ) {}
return $node;
},
'single' => TRUE,
);
}
}
@@ -1,71 +0,0 @@
<?php
/**
* @brief BBCode Tag
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
* @copyright (c) Invision Power Services, Inc.
* @license https://www.invisioncommunity.com/legal/standards/
* @package Invision Community
* @subpackage Forums
* @since 08 Feb 2016
*/
namespace IPS\forums\extensions\core\BBCode;
/* 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;
}
/**
* BBCode Tag
*/
class _topic
{
/**
* Permission Check
*
* @param \IPS\Member $member The member
* @param string $area The Editor area
* @return bool
*/
public function permissionCheck( \IPS\Member $member, $area )
{
return \IPS\Text\Parser::canUse( $member, 'ipsLink', $area );
}
/**
* Get Configuration
*
* @code
return array(
'tag' => 'span', // The HTML tag to use
'attributes' => array( ... ) // Key/Value pairs of attributes to use (optional) - can use {option} to get the [tag=option] value
'callback' => function( $node, $matches, $document ) // A callback to modify the DOMNode object
{
...
},
'block' => FALSE, // If this is a block-level tag (optional, default false)
'single' => FALSE, // If this is a single tag, with no content (optional, default false)
)
* @endcode
* @return array
*/
public function getConfiguration()
{
return array(
'tag' => 'a',
'callback' => function( \DOMElement $node, $matches, \DOMDocument $document )
{
try
{
$node->setAttribute( 'href', (string) \IPS\forums\Topic::load( \intval( $matches[2] ) )->url() );
}
catch ( \Exception $e ) {}
return $node;
},
);
}
}
@@ -1,116 +0,0 @@
<?php
/**
* @brief Moderator Permissions: Topics
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
* @copyright (c) Invision Power Services, Inc.
* @license https://www.invisioncommunity.com/legal/standards/
* @package Invision Community
* @subpackage Forums
* @since 16 Jan 2014
*/
namespace IPS\forums\extensions\core\ContentModeratorPermissions;
/* 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;
}
/**
* Moderator Permissions: Topics
*/
class _Topics
{
/**
* Get Permissions
*
* @param array $toggles Toggle data
* @code
return array(
'key' => 'YesNo', // Can just return a string with type
'key' => array( // Or an array for more options
'YesNo', // Type
array( ... ), // Options (as defined by type's class)
'prefix', // Prefix
'suffix', // Suffix
),
...
);
* @endcode
* @return array
*/
public function getPermissions( $toggles )
{
$return = array();
if ( \IPS\Db::i()->select( 'COUNT(*)', 'forums_forums', 'can_view_others=0 AND club_id IS NULL' )->first() )
{
$return['can_read_all_topics'] = 'YesNo';
}
if ( \IPS\Db::i()->select( 'COUNT(*)', 'forums_forums', '(' . \IPS\Db::i()->bitwiseWhere( \IPS\forums\Forum::$bitOptions['forums_bitoptions'], 'bw_enable_answers' ) . ') OR ( ' . \IPS\Db::i()->bitwiseWhere( \IPS\forums\Forum::$bitOptions['forums_bitoptions'], 'bw_enable_answers_moderator' ) . ' )' )->first() )
{
$return['can_set_best_answer'] = 'YesNo';
}
if ( \IPS\Db::i()->select( 'COUNT(*)', 'forums_topic_mmod' )->first() )
{
$return['can_use_saved_actions'] = 'YesNo';
}
return $return;
}
/**
* After change
*
* @param array $moderator The moderator
* @param array $changed Values that were changed
* @return void
*/
public function onChange( $moderator, $changed )
{
if ( $changed === '*' or array_key_exists( 'can_read_all_topics', $changed ) OR array_key_exists( 'forums', $changed ) )
{
$deleteFirst = TRUE;
if ( $changed === '*' or !empty( $changed['can_read_all_topics'] ) )
{
$deleteFirst = FALSE;
}
$this->reindexAuthorOnlyForums( $deleteFirst );
}
}
/**
* After change
*
* @param array $moderator The moderator
* @return void
*/
public function onDelete( $moderator )
{
$this->reindexAuthorOnlyForums( TRUE );
}
/**
* Reindex forums where members can only see their own topics
*
* @param bool $deleteFirst If TRUE, will delete first
* @return void
*/
public function reindexAuthorOnlyForums( $deleteFirst )
{
foreach ( new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', 'forums_forums', 'can_view_others=0 AND club_id IS NULL' ), 'IPS\forums\Forum' ) as $forum )
{
if ( $deleteFirst )
{
\IPS\Content\Search\Index::i()->removeClassFromSearchIndex( 'IPS\forums\Topic', $forum->id );
\IPS\Content\Search\Index::i()->removeClassFromSearchIndex( 'IPS\forums\Topic\Post', $forum->id );
}
\IPS\Task::queue( 'core', 'RebuildSearchIndex', array( 'class' => 'IPS\forums\Topic', 'container' => $forum->id ), 5, 'container' );
\IPS\Task::queue( 'core', 'RebuildSearchIndex', array( 'class' => 'IPS\forums\Topic\Post', 'container' => $forum->id ), 5, 'container' );
}
}
}
@@ -12,36 +12,38 @@
namespace IPS\forums\extensions\core\ContentRouter;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\Application\Module;
use IPS\Extensions\ContentRouterAbstract;
use IPS\Member;
use IPS\Member\Group;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* @brief Content Router extension: Topics
*/
class _Topics
class Topics extends ContentRouterAbstract
{
/**
* @brief Content Item Classes
*/
public $classes = array();
/**
* @brief Can be shown in similar content
*/
public $similarContent = TRUE;
public bool $similarContent = TRUE;
/**
* Constructor
*
* @param \IPS\Member|IPS\Member\Group|NULL $member If checking access, the member/group to check for, or NULL to not check access
* @param Member|Group|NULL $member If checking access, the member/group to check for, or NULL to not check access
* @return void
*/
public function __construct( $member = NULL )
public function __construct( Member|Group $member = NULL )
{
if ( $member === NULL or $member->canAccessModule( \IPS\Application\Module::get( 'forums', 'forums', 'front' ) ) )
if ( $member === NULL or $member->canAccessModule( Module::get( 'forums', 'forums', 'front' ) ) )
{
$this->classes[] = 'IPS\forums\Topic';
}
@@ -1,57 +0,0 @@
<?php
/**
* @brief Create Menu Extension : Topic
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
* @copyright (c) Invision Power Services, Inc.
* @license https://www.invisioncommunity.com/legal/standards/
* @package Invision Community
* @subpackage Forums
* @since 14 Feb 2014
*/
namespace IPS\forums\extensions\core\CreateMenu;
/* 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;
}
/**
* Create Menu Extension: Topic
*/
class _Topic
{
/**
* Get Items
*
* @return array
*/
public function getItems()
{
if ( \IPS\forums\Forum::canOnAny( 'add', NULL, \IPS\Settings::i()->club_nodes_in_apps ? array() : array( array( 'club_id IS NULL' ) ) ) )
{
if ( !\IPS\Settings::i()->club_nodes_in_apps and $theOnlyForum = \IPS\forums\Forum::theOnlyForum() )
{
return array(
'topic' => array(
'link' => $theOnlyForum->url()->setQueryString( 'do', 'add' ),
)
);
}
else
{
return array(
'topic' => array(
'link' => \IPS\Http\Url::internal( "app=forums&module=forums&controller=forums&do=createMenu", 'front', 'topic_create' ),
'extraData' => array( 'data-ipsDialog' => true, 'data-ipsDialog-size' => "narrow" ),
'title' => 'select_forum'
)
);
}
}
return array();
}
}
Whitespace-only changes.
@@ -12,36 +12,44 @@
namespace IPS\forums\extensions\core\EditorLocations;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use InvalidArgumentException;
use IPS\Content;
use IPS\Db;
use IPS\Extensions\EditorLocationsAbstract;
use IPS\forums\Forum;
use IPS\forums\Topic;
use IPS\forums\Topic\ArchivedPost;
use IPS\forums\Topic\Post;
use IPS\Helpers\Form\Editor;
use IPS\Http\Url;
use IPS\Member;
use IPS\Node\Model;
use IPS\Text\Parser;
use LogicException;
use OutOfRangeException;
use RuntimeException;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Editor Extension: Forums
*/
class _Forums
class Forums extends EditorLocationsAbstract
{
/**
* Can we use HTML in this editor?
*
* @param \IPS\Member $member The member
* @return bool|null NULL will cause the default value (based on the member's permissions) to be used, and is recommended in most cases. A boolean value will override that.
*/
public function canUseHtml( $member )
{
return NULL;
}
/**
* Can we use attachments in this editor?
*
* @param \IPS\Member $member The member
* @param \IPS\Helpers\Form\Editor $field The editor field
* @param Member $member The member
* @param Editor $field The editor field
* @return bool|null NULL will cause the default value (based on the member's permissions) to be used, and is recommended in most cases. A boolean value will override that.
*/
public function canAttach( $member, $field )
public function canAttach( Member $member, Editor $field ): ?bool
{
return NULL;
}
@@ -50,11 +58,11 @@ class _Forums
* Can whatever is posted in this editor be moderated?
* If this returns TRUE, we must ensure the content is ran through word, link and image filters
*
* @param \IPS\Member $member The member
* @param \IPS\Helpers\Form\Editor $field The editor field
* @param Member $member The member
* @param Editor $field The editor field
* @return bool
*/
public function canBeModerated( $member, $field )
public function canBeModerated( Member $member, Editor $field ): bool
{
/* Creating/editing a forum */
if ( preg_match( '/^forums\-(?:forum|rules|permerror)\-\d+$/', $field->options['autoSaveKey'] ) or preg_match( '/^forums\-new\-(?:forum|rules|permerror)\d*$/', $field->options['autoSaveKey'] ) )
@@ -76,16 +84,17 @@ class _Forums
{
if ( \IPS\IN_DEV )
{
throw new \RuntimeException( 'Unknown canBeModerated: ' . $field->options['autoSaveKey'] );
throw new RuntimeException( 'Unknown canBeModerated: ' . $field->options['autoSaveKey'] );
}
return FALSE;
return parent::canBeModerated( $member, $field );
}
}
/**
* Permission check for attachments
*
* @param \IPS\Member $member The member
* @param Member $member The member
* @param int|null $id1 Primary ID
* @param int|null $id2 Secondary ID
* @param string|null $id3 Arbitrary data
@@ -93,25 +102,25 @@ class _Forums
* @param bool $viewOnly If true, just check if the user can see the attachment rather than download it
* @return bool
*/
public function attachmentPermissionCheck( $member, $id1, $id2, $id3, $attachment, $viewOnly=FALSE )
public function attachmentPermissionCheck( Member $member, ?int $id1, ?int $id2, ?string $id3, array $attachment, bool $viewOnly=FALSE ): bool
{
try
{
if ( $id3 )
{
return \IPS\forums\Forum::load( $id1 )->can( 'attachments', $member );
return Forum::load( $id1 )->can( 'attachments', $member );
}
elseif ( $id2 )
{
$topic = \IPS\forums\Topic::load( $id1 );
$topic = Topic::load( $id1 );
if( $topic->isArchived() )
{
$post = \IPS\forums\Topic\ArchivedPost::load( $id2 );
$post = ArchivedPost::load( $id2 );
}
else
{
$post = \IPS\forums\Topic\Post::load( $id2 );
$post = Post::load( $id2 );
}
if ( !$post->canView( $member ) )
@@ -123,7 +132,7 @@ class _Forums
}
else
{
$topic = \IPS\forums\Topic::load( $id1 );
$topic = Topic::load( $id1 );
if ( !$topic->canView( $member ) )
{
@@ -133,7 +142,7 @@ class _Forums
return $viewOnly or $topic->container()->can( 'attachments', $member );
}
}
catch ( \OutOfRangeException $e )
catch ( OutOfRangeException $e )
{
return FALSE;
}
@@ -145,47 +154,34 @@ class _Forums
* @param int|null $id1 Primary ID
* @param int|null $id2 Secondary ID
* @param string|null $id3 Arbitrary data
* @return \IPS\Http\Url|\IPS\Content|\IPS\Node\Model
* @throws \LogicException
* @return Url|Content|Model|Member|null
* @throws LogicException
*/
public function attachmentLookup( $id1, $id2, $id3 )
public function attachmentLookup( int $id1=NULL, int $id2=NULL, string $id3=NULL ): Model|Content|Url|Member|null
{
if ( $id3 )
{
return \IPS\forums\Forum::load( $id1 );
return Forum::load( $id1 );
}
elseif ( $id2 )
{
$topic = \IPS\forums\Topic::load( $id1 );
$topic = Topic::load( $id1 );
if( $topic->isArchived() )
{
return \IPS\forums\Topic\ArchivedPost::load( $id2 );
return ArchivedPost::load( $id2 );
}
else
{
return \IPS\forums\Topic\Post::load( $id2 );
return Post::load( $id2 );
}
}
else
{
return \IPS\forums\Topic::load( $id1 );
return Topic::load( $id1 );
}
}
/**
* Rebuild attachment images in non-content item areas
*
* @param int|null $offset Offset to start from
* @param int|null $max Maximum to parse
* @return int Number completed
* @note This method is optional and will only be called if it exists
*/
public function rebuildAttachmentImages( $offset, $max )
{
return $this->performRebuild( $offset, $max, array( 'IPS\Text\Parser', 'rebuildAttachmentUrls' ) );
}
/**
* Rebuild content post-upgrade
*
@@ -194,16 +190,11 @@ class _Forums
* @return int Number completed
* @note This method is optional and will only be called if it exists
*/
public function rebuildContent( $offset, $max )
public function rebuildContent( ?int $offset, ?int $max ): int
{
return $this->performRebuild( $offset, $max, array( 'IPS\Text\LegacyParser', 'parseStatic' ) );
}
/**
* @brief Use the cached image URL instead of the original URL
*/
protected $proxyUrl = FALSE;
/**
* Rebuild content to add or remove image proxy
*
@@ -213,31 +204,25 @@ class _Forums
* @return int Number completed
* @note This method is optional and will only be called if it exists
*/
public function rebuildImageProxy( $offset, $max, $proxyUrl = FALSE )
public function rebuildImageProxy( ?int $offset, ?int $max, bool $proxyUrl = FALSE ): int
{
$this->proxyUrl = $proxyUrl;
return $this->performRebuild( $offset, $max, 'parseImageProxy' );
$callback = function( $value ) use ( $proxyUrl ) {
return Parser::removeImageProxy( $value, $proxyUrl );
};
return $this->performRebuild( $offset, $max, $callback );
}
/**
* @brief Store lazy loading status ( true = enabled )
*/
protected $_lazyLoadStatus = null;
/**
* Rebuild content to add or remove lazy loading
*
* @param int|null $offset Offset to start from
* @param int|null $max Maximum to parse
* @param bool $status Enable/Disable lazy loading
* @return int Number completed
* @note This method is optional and will only be called if it exists
*/
public function rebuildLazyLoad( $offset, $max, $status=TRUE )
public function rebuildLazyLoad( ?int $offset, ?int $max ): int
{
$this->_lazyLoadStatus = $status;
return $this->performRebuild( $offset, $max, 'parseLazyLoad' );
return $this->performRebuild( $offset, $max, [ 'IPS\Text\Parser', 'parseLazyLoad' ] );
}
/**
@@ -248,35 +233,28 @@ class _Forums
* @param callable $callback Method to call to rebuild content
* @return int Number completed
*/
protected function performRebuild( $offset, $max, $callback )
protected function performRebuild( ?int $offset, ?int $max, callable $callback ): int
{
$did = 0;
/* Language bits */
foreach( \IPS\Db::i()->select( '*', 'core_sys_lang_words', "word_key LIKE 'forums_forum_%_desc' OR word_key LIKE 'forums_forum_%_rules' OR word_key LIKE 'forums_forum_%_permerror'", 'word_id ASC', array( $offset, $max ) ) as $word )
foreach( Db::i()->select( '*', 'core_sys_lang_words', "word_key LIKE 'forums_forum_%_desc' OR word_key LIKE 'forums_forum_%_rules' OR word_key LIKE 'forums_forum_%_permerror'", 'word_id ASC', array( $offset, $max ) ) as $word )
{
$did++;
$rebuilt = FALSE;
try
{
if( $callback == 'parseImageProxy' )
{
$rebuilt = \IPS\Text\Parser::removeImageProxy( $word['word_custom'], $this->proxyUrl );
}
elseif( $callback == 'parseLazyLoad' )
{
$rebuilt = \IPS\Text\Parser::parseLazyLoad( $word['word_custom'], $this->_lazyLoadStatus );
}
else
if( !empty( $word['word_custom'] ) )
{
$rebuilt = $callback( $word['word_custom'] );
}
}
catch( \InvalidArgumentException $e )
catch( InvalidArgumentException $e )
{
if( $callback[1] == 'parseStatic' AND $e->getcode() == 103014 )
{
$rebuilt = preg_replace( "#\[/?([^\]]+?)\]#", '', $word['word_custom'] );
$rebuilt = preg_replace( "#\[/?([^\]]+?)\]#", '', $word['word_custom'] );
}
else
{
@@ -286,7 +264,7 @@ class _Forums
if( $rebuilt !== FALSE )
{
\IPS\Db::i()->update( 'core_sys_lang_words', array( 'word_custom' => $rebuilt ), 'word_id=' . $word['word_id'] );
Db::i()->update( 'core_sys_lang_words', array( 'word_custom' => $rebuilt ), 'word_id=' . $word['word_id'] );
}
}
@@ -298,8 +276,8 @@ class _Forums
*
* @return int Total Count
*/
public function contentCount()
public function contentCount(): int
{
return \IPS\Db::i()->select( 'COUNT(*)', 'core_sys_lang_words', "word_key LIKE 'forums_forum_%_desc' OR word_key LIKE 'forums_forum_%_rules' OR word_key LIKE 'forums_forum_%_permerror'" )->first();
return Db::i()->select( 'COUNT(*)', 'core_sys_lang_words', "word_key LIKE 'forums_forum_%_desc' OR word_key LIKE 'forums_forum_%_rules' OR word_key LIKE 'forums_forum_%_permerror'" )->first();
}
}
@@ -12,25 +12,34 @@
namespace IPS\forums\extensions\core\FileStorage;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use Exception;
use IPS\Db;
use IPS\Extensions\FileStorageAbstract;
use IPS\File;
use IPS\forums\Forum;
use UnderflowException;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* File Storage Extension: Cards
*/
class _Cards
class Cards extends FileStorageAbstract
{
/**
* Count stored files
*
* @return int
*/
public function count()
public function count(): int
{
return \IPS\Db::i()->select( 'COUNT(*)', 'forums_forums', 'card_image IS NOT NULL' )->first();
return Db::i()->select( 'COUNT(*)', 'forums_forums', 'card_image IS NOT NULL' )->first();
}
/**
@@ -39,56 +48,39 @@ class _Cards
* @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
* @throws Underflowexception When file record doesn't exist. Indicating there are no more files to move
* @return void
*/
public function move( $offset, $storageConfiguration, $oldConfiguration=NULL )
public function move( int $offset, int $storageConfiguration, int $oldConfiguration=NULL ) : void
{
$file = \IPS\Db::i()->select( '*', 'forums_forums', 'card_image IS NOT NULL', 'id', array( $offset, 1 ) )->first();
$file = Db::i()->select( '*', 'forums_forums', 'card_image IS NOT NULL', 'id', array( $offset, 1 ) )->first();
try
{
$forum = \IPS\forums\Forum::constructFromData( $file );
$forum->card_image = \IPS\File::get( $oldConfiguration ?: 'forums_Cards', $forum->card_image )->move( $storageConfiguration );
$forum = Forum::constructFromData( $file );
$forum->icon = File::get( $oldConfiguration ?: 'forums_Cards', $forum->card_image )->move( $storageConfiguration );
$forum->save();
}
catch( \Exception $e )
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 )
{
$forum = \IPS\forums\Forum::constructFromData( \IPS\Db::i()->select( '*', 'forums_forums', 'card_image IS NOT NULL', 'id', array( $offset, 1 ) )->first() );
if ( $new = \IPS\File::repairUrl( $forum->card_image ) )
{
$forum->card_image = $new;
$forum->save();
}
}
/**
* Check if a file is valid
*
* @param string $file The file path to check
* @param File|string $file The file path to check
* @return bool
*/
public function isValidFile( $file )
public function isValidFile( File|string $file ): bool
{
try
{
\IPS\Db::i()->select( 'id', 'forums_forums', array( 'card_image=?', $file ) )->first();
Db::i()->select( 'id', 'forums_forums', array( 'card_image=?', $file ) )->first();
return TRUE;
}
catch ( \UnderflowException $e )
catch ( UnderflowException $e )
{
return FALSE;
}
@@ -99,15 +91,15 @@ class _Cards
*
* @return void
*/
public function delete()
public function delete() : void
{
foreach( \IPS\Db::i()->select( '*', 'forums_forums', "card_image IS NOT NULL" ) as $forum )
foreach( Db::i()->select( '*', 'forums_forums', "card_image IS NOT NULL" ) as $forum )
{
try
{
\IPS\File::get( 'forums_Cards', $forum['card_image'] )->delete();
File::get( 'forums_Cards', $forum['card_image'] )->delete();
}
catch( \Exception $e ){}
catch( Exception $e ){}
}
}
}
@@ -12,25 +12,37 @@
namespace IPS\forums\extensions\core\FileStorage;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use Exception;
use IPS\Db;
use IPS\Extensions\FileStorageAbstract;
use IPS\File;
use IPS\forums\Forum;
use UnderflowException;
use function defined;
use function json_decode;
use function json_last_error;
use const JSON_ERROR_NONE;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* File Storage Extension: Icons
*/
class _Icons
class Icons extends FileStorageAbstract
{
/**
* Count stored files
*
* @return int
*/
public function count()
public function count(): int
{
return \IPS\Db::i()->select( 'COUNT(*)', 'forums_forums', 'icon IS NOT NULL' )->first();
return Db::i()->select( 'COUNT(*)', 'forums_forums', 'icon IS NOT NULL' )->first();
}
/**
@@ -39,55 +51,42 @@ class _Icons
* @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
* @throws Underflowexception When file record doesn't exist. Indicating there are no more files to move
* @return void
*/
public function move( $offset, $storageConfiguration, $oldConfiguration=NULL )
public function move( int $offset, int $storageConfiguration, int $oldConfiguration=NULL ) : void
{
$forum = \IPS\forums\Forum::constructFromData( \IPS\Db::i()->select( '*', 'forums_forums', 'icon IS NOT NULL', 'id', array( $offset, 1 ) )->first() );
$forum = Forum::constructFromData( Db::i()->select( '*', 'forums_forums', 'icon IS NOT NULL', 'id', array( $offset, 1 ) )->first() );
try
{
$forum->icon = \IPS\File::get( $oldConfiguration ?: 'forums_Icons', $forum->icon )->move( $storageConfiguration );
$forum->save();
json_decode( $forum->icon );
if ( json_last_error() !== JSON_ERROR_NONE )
{
$forum->icon = File::get( $oldConfiguration ?: 'forums_Icons', $forum->icon )->move( $storageConfiguration );
$forum->save();
}
}
catch( \Exception $e )
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 )
{
$forum = \IPS\forums\Forum::constructFromData( \IPS\Db::i()->select( '*', 'forums_forums', 'icon IS NOT NULL', 'id', array( $offset, 1 ) )->first() );
if ( $new = \IPS\File::repairUrl( $forum->icon ) )
{
$forum->icon = $new;
$forum->save();
}
}
/**
* Check if a file is valid
*
* @param string $file The file path to check
* @param File|string $file The file path to check
* @return bool
*/
public function isValidFile( $file )
public function isValidFile( File|string $file ): bool
{
try
{
\IPS\Db::i()->select( 'id', 'forums_forums', array( 'icon=?', $file ) )->first();
Db::i()->select( 'id', 'forums_forums', array( 'icon=?', $file ) )->first();
return TRUE;
}
catch ( \UnderflowException $e )
catch ( UnderflowException $e )
{
return FALSE;
}
@@ -98,15 +97,15 @@ class _Icons
*
* @return void
*/
public function delete()
public function delete() : void
{
foreach( \IPS\Db::i()->select( '*', 'forums_forums', "icon IS NOT NULL" ) as $forum )
foreach( Db::i()->select( '*', 'forums_forums', "icon IS NOT NULL" ) as $forum )
{
try
{
\IPS\File::get( 'forums_Icons', $forum['icon'] )->delete();
File::get( 'forums_Icons', $forum['icon'] )->delete();
}
catch( \Exception $e ){}
catch( Exception $e ){}
}
}
}
@@ -12,64 +12,78 @@
namespace IPS\forums\extensions\core\FrontNavigation;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\Application\Module;
use IPS\core\FrontNavigation;
use IPS\core\FrontNavigation\FrontNavigationAbstract;
use IPS\Dispatcher;
use IPS\Http\Url;
use IPS\Member;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Front Navigation Extension: Forums
*/
class _Forums extends \IPS\core\FrontNavigation\FrontNavigationAbstract
class Forums extends FrontNavigationAbstract
{
/**
* @var string Default icon
*/
public string $defaultIcon = '\f075';
/**
* Get Type Title which will display in the AdminCP Menu Manager
*
* @return string
*/
public static function typeTitle()
public static function typeTitle(): string
{
return \IPS\Member::loggedIn()->language()->addToStack('frontnavigation_forums');
return Member::loggedIn()->language()->addToStack('frontnavigation_forums');
}
/**
* Can the currently logged in user access the content this item links to?
*
* @return bool
* @return bool
*/
public function canAccessContent()
public function canAccessContent(): bool
{
return \IPS\Member::loggedIn()->canAccessModule( \IPS\Application\Module::get( 'forums', 'forums' ) );
return Member::loggedIn()->canAccessModule( Module::get( 'forums', 'forums' ) );
}
/**
* Get Title
*
* @return string
* @return string
*/
public function title()
public function title(): string
{
return \IPS\Member::loggedIn()->language()->addToStack('frontnavigation_forums');
return Member::loggedIn()->language()->addToStack('frontnavigation_forums');
}
/**
* Get Link
*
* @return \IPS\Http\Url
* @return string|Url|null
*/
public function link()
public function link(): Url|string|null
{
return \IPS\Http\Url::internal( "app=forums&module=forums&controller=index", 'front', 'forums' );
return Url::internal( "app=forums&module=forums&controller=index", 'front', 'forums' );
}
/**
* Is Active?
*
* @return bool
* @return bool
*/
public function active()
public function active(): bool
{
return !\IPS\core\FrontNavigation::$clubTabActive and \IPS\Dispatcher::i()->application->directory === 'forums';
return !FrontNavigation::$clubTabActive and !FrontNavigation::nodeExtensionIsActive( 'forums' ) and Dispatcher::i()->application->directory === 'forums';
}
}
@@ -12,26 +12,35 @@
namespace IPS\forums\extensions\core\LiveSearch;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\Db;
use IPS\Dispatcher;
use IPS\Extensions\LiveSearchAbstract;
use IPS\forums\Forum;
use IPS\Member;
use IPS\Theme;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* @brief ACP Live Search Extension
*/
class _Forums
class Forums extends LiveSearchAbstract
{
/**
* Check we have access
*
* @return bool
*/
public function hasAccess()
public function hasAccess(): bool
{
/* Check Permissions */
return \IPS\Member::loggedIn()->hasAcpRestriction( 'forums', 'forums', 'forums_manage' );
return Member::loggedIn()->hasAcpRestriction( 'forums', 'forums', 'forums_manage' );
}
/**
@@ -40,7 +49,7 @@ class _Forums
* @param string $searchTerm Search Term
* @return array Array of results
*/
public function getResults( $searchTerm )
public function getResults( string $searchTerm ): array
{
/* Init */
$results = array();
@@ -50,10 +59,10 @@ class _Forums
if( $this->hasAccess() )
{
/* Perform the search */
$forums = \IPS\Db::i()->select(
$forums = Db::i()->select(
"*",
'forums_forums',
array( "club_id IS NULL AND word_custom LIKE CONCAT( '%', ?, '%' ) AND lang_id=?", $searchTerm, \IPS\Member::loggedIn()->language()->id ),
array( "club_id IS NULL AND word_custom LIKE CONCAT( '%', ?, '%' ) AND lang_id=?", $searchTerm, Member::loggedIn()->language()->id ),
NULL,
NULL
)->join(
@@ -64,9 +73,9 @@ class _Forums
/* Format results */
foreach ( $forums as $forum )
{
$forum = \IPS\forums\Forum::constructFromData( $forum );
$forum = Forum::constructFromData( $forum );
$results[] = \IPS\Theme::i()->getTemplate( 'livesearch', 'forums', 'admin' )->forum( $forum );
$results[] = Theme::i()->getTemplate( 'livesearch', 'forums', 'admin' )->forum( $forum );
}
}
@@ -78,8 +87,8 @@ class _Forums
*
* @return bool
*/
public function isDefault()
public function isDefault(): bool
{
return \IPS\Dispatcher::i()->application->directory == 'forums';
return Dispatcher::i()->application->directory == 'forums';
}
}
@@ -1,75 +0,0 @@
<?php
/**
* @brief Member Sync
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
* @copyright (c) Invision Power Services, Inc.
* @license https://www.invisioncommunity.com/legal/standards/
* @package Invision Community
* @subpackage Forums
* @since 14 Jul 2014
*/
namespace IPS\forums\extensions\core\MemberSync;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Member Sync
*/
class _Forums
{
/**
* Member is merged with another member
*
* @param \IPS\Member $member Member being kept
* @param \IPS\Member $member2 Member being removed
* @return void
*/
public function onMerge( $member, $member2 )
{
\IPS\Db::i()->update( 'forums_answer_ratings', array( 'member' => $member->member_id ), array( '`member`=?', $member2->member_id ), array(), NULL, \IPS\Db::IGNORE );
\IPS\Db::i()->update( 'forums_question_ratings', array( 'member' => $member->member_id ), array( '`member`=?', $member2->member_id ), array(), NULL, \IPS\Db::IGNORE );
\IPS\Db::i()->update( 'forums_view_method', array( 'member_id' => $member->member_id ), array( 'member_id=?', $member2->member_id ), array(), NULL, \IPS\Db::IGNORE );
if ( \IPS\Settings::i()->archive_on )
{
/* Connect to the remote DB if needed */
if ( \IPS\CIC2 )
{
$archiveStorage = \IPS\Cicloud\getForumArchiveDb();
}
else
{
$archiveStorage = ( !\IPS\Settings::i()->archive_remote_sql_host ) ? \IPS\Db::i() : \IPS\Db::i( 'archive', array(
'sql_host' => \IPS\Settings::i()->archive_remote_sql_host,
'sql_user' => \IPS\Settings::i()->archive_remote_sql_user,
'sql_pass' => \IPS\Settings::i()->archive_remote_sql_pass,
'sql_database' => \IPS\Settings::i()->archive_remote_sql_database,
'sql_port' => \IPS\Settings::i()->archive_sql_port,
'sql_socket' => \IPS\Settings::i()->archive_sql_socket,
'sql_tbl_prefix'=> \IPS\Settings::i()->archive_sql_tbl_prefix,
'sql_utf8mb4' => isset( \IPS\Settings::i()->sql_utf8mb4 ) ? \IPS\Settings::i()->sql_utf8mb4 : FALSE
) );
}
$archiveStorage->update( 'forums_archive_posts', array( 'archive_author_id' => $member->member_id ), array( 'archive_author_id=?', $member2->member_id ) );
}
}
/**
* Member is deleted
*
* @param $member \IPS\Member The member
* @return void
*/
public function onDelete( $member )
{
\IPS\Db::i()->delete( 'forums_answer_ratings', array( '`member`=?', $member->member_id ) );
\IPS\Db::i()->delete( 'forums_question_ratings', array( '`member`=?', $member->member_id ) );
\IPS\Db::i()->delete( 'forums_view_method', array( 'member_id=?', $member->member_id ) );
}
}
Whitespace-only changes.
@@ -12,25 +12,32 @@
namespace IPS\forums\extensions\core\MobileNavigation;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\Application\Module;
use IPS\core\MobileNavigation\MobileNavigationAbstract;
use IPS\Http\Url;
use IPS\Member;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Mobile Navigation Extension: Topics
*/
class _Forums extends \IPS\core\MobileNavigation\MobileNavigationAbstract
class Forums extends MobileNavigationAbstract
{
/**
* Get Type Title which will display in the AdminCP Menu Manager
*
* @return string
*/
public static function typeTitle()
public static function typeTitle(): string
{
return \IPS\Member::loggedIn()->language()->addToStack('mobilenavigation_Forums');
return Member::loggedIn()->language()->addToStack('mobilenavigation_Forums');
}
/**
@@ -38,9 +45,9 @@ class _Forums extends \IPS\core\MobileNavigation\MobileNavigationAbstract
*
* @return bool
*/
public function canAccessContent()
public function canAccessContent(): bool
{
return \IPS\Member::loggedIn()->canAccessModule( \IPS\Application\Module::get( 'forums', 'forums', 'front' ) );
return Member::loggedIn()->canAccessModule( Module::get( 'forums', 'forums', 'front' ) );
}
/**
@@ -48,19 +55,19 @@ class _Forums extends \IPS\core\MobileNavigation\MobileNavigationAbstract
*
* @return string
*/
public function title()
public function title(): string
{
return \IPS\Member::loggedIn()->language()->addToStack('mobilenavigation_Forums');
return Member::loggedIn()->language()->addToStack('mobilenavigation_Forums');
}
/**
* Get Link
*
* @return \IPS\Http\Url
* @return Url|string
*/
public function link()
public function link(): Url|string
{
return \IPS\Http\Url::internal( "app=forums&module=forums&controller=index&method=table", 'front', 'forums' );
return Url::internal( "app=forums&module=forums&controller=index&method=table", 'front', 'forums' );
}
/**
@@ -68,7 +75,7 @@ class _Forums extends \IPS\core\MobileNavigation\MobileNavigationAbstract
*
* @return string|null
*/
public function icon()
public function icon(): ?string
{
return 'LIST_ROWS';
}
@@ -12,25 +12,32 @@
namespace IPS\forums\extensions\core\MobileNavigation;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\Application\Module;
use IPS\core\MobileNavigation\MobileNavigationAbstract;
use IPS\Http\Url;
use IPS\Member;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Mobile Navigation Extension: Topics
*/
class _Topics extends \IPS\core\MobileNavigation\MobileNavigationAbstract
class Topics extends MobileNavigationAbstract
{
/**
* Get Type Title which will display in the AdminCP Menu Manager
*
* @return string
*/
public static function typeTitle()
public static function typeTitle(): string
{
return \IPS\Member::loggedIn()->language()->addToStack('mobilenavigation_Topics');
return Member::loggedIn()->language()->addToStack('mobilenavigation_Topics');
}
/**
@@ -38,9 +45,9 @@ class _Topics extends \IPS\core\MobileNavigation\MobileNavigationAbstract
*
* @return bool
*/
public function canAccessContent()
public function canAccessContent(): bool
{
return \IPS\Member::loggedIn()->canAccessModule( \IPS\Application\Module::get( 'forums', 'forums', 'front' ) );
return Member::loggedIn()->canAccessModule( Module::get( 'forums', 'forums', 'front' ) );
}
/**
@@ -48,19 +55,19 @@ class _Topics extends \IPS\core\MobileNavigation\MobileNavigationAbstract
*
* @return string
*/
public function title()
public function title(): string
{
return \IPS\Member::loggedIn()->language()->addToStack('mobilenavigation_Topics');
return Member::loggedIn()->language()->addToStack('mobilenavigation_Topics');
}
/**
* Get Link
*
* @return \IPS\Http\Url
* @return Url|string
*/
public function link()
public function link(): Url|string
{
return \IPS\Http\Url::internal( "app=forums&module=forums&controller=index&method=fluid", 'front', 'forums' );
return Url::internal( "app=forums&module=forums&controller=index&method=fluid", 'front', 'forums' );
}
/**
@@ -68,7 +75,7 @@ class _Topics extends \IPS\core\MobileNavigation\MobileNavigationAbstract
*
* @return string|null
*/
public function icon()
public function icon(): ?string
{
return 'COMMENTS';
}
@@ -0,0 +1,150 @@
<?php
/**
* @brief Moderator Permissions
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
* @copyright (c) Invision Power Services, Inc.
* @license https://www.invisioncommunity.com/legal/standards/
* @package Invision Community
* @subpackage Forums
* @since 07 Sep 2023
*/
namespace IPS\forums\extensions\core\ModeratorPermissions;
/* To prevent PHP errors (extending class does not exist) revealing path */
use IPS\Extensions\ModeratorPermissionsAbstract;
use IPS\Content\Search\Index;
use IPS\Db;
use IPS\forums\Forum;
use IPS\Patterns\ActiveRecordIterator;
use IPS\Task;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Moderator Permissions
*/
class Topic extends ModeratorPermissionsAbstract
{
/**
* Get Permissions
*
* @param array $toggles
* @code
return array(
'key' => 'YesNo', // Can just return a string with type
'key' => array( // Or an array for more options
'YesNo', // Type
array( ... ), // Options (as defined by type's class)
'prefix', // Prefix
'suffix', // Suffix
),
...
);
* @endcode
* @return array
*/
public function getPermissions( array $toggles ): array
{
return array();
}
/**
* Get Permissions
*
* @param array $toggles Toggle data
* @code
return array(
'key' => 'YesNo', // Can just return a string with type
'key' => array( // Or an array for more options
'YesNo', // Type
array( ... ), // Options (as defined by type's class)
'prefix', // Prefix
'suffix', // Suffix
),
...
);
* @endcode
* @return array
*/
public function getContentPermissions( array $toggles ): array
{
$return = array();
if ( Db::i()->select( 'COUNT(*)', 'forums_forums', 'can_view_others=0 AND club_id IS NULL' )->first() )
{
$return['can_read_all_topics'] = 'YesNo';
}
if ( Db::i()->select( 'COUNT(*)', 'forums_forums', '(' . Db::i()->bitwiseWhere( Forum::$bitOptions['forums_bitoptions'], 'bw_enable_answers' ) . ') OR ( ' . Db::i()->bitwiseWhere( Forum::$bitOptions['forums_bitoptions'], 'bw_solved_set_by_moderator' ) . ' )' )->first() )
{
$return['can_set_best_answer'] = 'YesNo';
}
if ( Db::i()->select( 'COUNT(*)', 'forums_topic_mmod' )->first() )
{
$return['can_use_saved_actions'] = 'YesNo';
}
return $return;
}
/**
* After change
*
* @param array $moderator The moderator
* @param array $changed Values that were changed
* @return void
*/
public function onContentChange( array $moderator, array $changed ) : void
{
if ( $changed == '*' or array_key_exists( 'can_read_all_topics', $changed ) OR array_key_exists( 'forums', $changed ) )
{
$deleteFirst = TRUE;
if ( $changed == '*' or !empty( $changed['can_read_all_topics'] ) )
{
$deleteFirst = FALSE;
}
$this->reindexAuthorOnlyForums( $deleteFirst );
}
}
/**
* After change
*
* @param array $moderator The moderator
* @return void
*/
public function onContentDelete( array $moderator ) : void
{
$this->reindexAuthorOnlyForums( TRUE );
}
/**
* Reindex forums where members can only see their own topics
*
* @param bool $deleteFirst If TRUE, will delete first
* @return void
*/
public function reindexAuthorOnlyForums( bool $deleteFirst=FALSE ) : void
{
foreach ( new ActiveRecordIterator( Db::i()->select( '*', 'forums_forums', 'can_view_others=0 AND club_id IS NULL' ), 'IPS\forums\Forum' ) as $forum )
{
if ( $deleteFirst )
{
Index::i()->removeClassFromSearchIndex( 'IPS\forums\Topic', $forum->id );
Index::i()->removeClassFromSearchIndex( 'IPS\forums\Topic\Post', $forum->id );
}
Task::queue( 'core', 'RebuildSearchIndex', array( 'class' => 'IPS\forums\Topic', 'container' => $forum->id ), 5, 'container' );
Task::queue( 'core', 'RebuildSearchIndex', array( 'class' => 'IPS\forums\Topic\Post', 'container' => $forum->id ), 5, 'container' );
}
}
}
@@ -12,21 +12,39 @@
namespace IPS\forums\extensions\core\OverviewStatistics;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use DateTimeZone;
use Exception;
use IPS\DateTime;
use IPS\Db;
use IPS\Extensions\OverviewStatisticsAbstract;
use IPS\forums\Forum;
use IPS\forums\Topic;
use IPS\Helpers\Form;
use IPS\Helpers\Form\Date;
use IPS\Helpers\Form\Node;
use IPS\Member;
use IPS\Request;
use IPS\Theme;
use function defined;
use function in_array;
use function is_array;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* @brief Overview statistics extension: Solved
*/
class _Solved
class Solved extends OverviewStatisticsAbstract
{
/**
* @brief Which statistics page (activity or user)
*/
public $page = 'activity';
public string $page = 'activity';
/**
* Return the sub-block keys
@@ -34,7 +52,7 @@ class _Solved
* @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()
public function getBlocks(): array
{
return array( 'percentagesolved', 'averagetimesolved' );
}
@@ -45,31 +63,33 @@ class _Solved
* @param string|NULL $subBlock The subblock we are loading as returned by getBlocks()
* @return array
*/
public function getBlockDetails( $subBlock = NULL )
public function getBlockDetails( string $subBlock = NULL ): array
{
switch( $subBlock )
{
case 'percentagesolved':
return array( 'app' => 'forums', 'title' => 'stats_percentagesolved', 'description' => 'stats_percentagesolved_desc', 'refresh' => 60, 'form' => true );
break;
case 'averagetimesolved':
return array( 'app' => 'forums', 'title' => 'stats_averagetimesolved', 'description' => 'stats_averagetimesolved_desc', 'refresh' => 60, 'form' => true );
break;
}
return [];
}
/**
* 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 array|string|null $dateRange String for a fixed time period in days, NULL for all time, or an array with 'start' and 'end' \IPS\DateTime objects to restrict to
* @param string|NULL $subBlock The subblock we are loading as returned by getBlocks()
* @return string
*/
public function getBlock( $dateRange = NULL, $subBlock = NULL )
public function getBlock( array|string $dateRange = NULL, string $subBlock = NULL ): string
{
/* Make sure someone isn't trying to manipulate the request or do something weird */
if( !\in_array( $subBlock, $this->getBlocks() ) )
if( !in_array( $subBlock, $this->getBlocks() ) )
{
return '';
}
@@ -84,7 +104,7 @@ class _Solved
if( $dateRange !== NULL )
{
if( \is_array( $dateRange ) )
if( is_array( $dateRange ) )
{
$where = array(
array( 'start_date > ?', $dateRange['start']->getTimestamp() ),
@@ -93,49 +113,26 @@ class _Solved
}
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;
}
$currentDate = new DateTime;
$interval = static::getInterval( $dateRange );
$initialTimestamp = $currentDate->sub( $interval )->getTimestamp();
$where = array( array( 'start_date > ?', $initialTimestamp ) );
$previousWhere = array( array( 'start_date BETWEEN ? AND ?', $currentDate->sub( $interval )->getTimestamp(), $initialTimestamp ) );
}
}
else if ( $dateRange === NULL and isset( \IPS\Request::i()->nodes ) and \IPS\Request::i()->nodes )
else if ( isset( Request::i()->nodes ) and Request::i()->nodes )
{
$where = array( array( \IPS\Db::i()->in( 'forum_id', explode( ',', \IPS\Request::i()->nodes ) ) ) );
$where = array( array( Db::i()->in( 'forum_id', explode( ',', Request::i()->nodes ) ) ) );
}
$where[] = array( \IPS\Db::i()->in( 'forum_id', iterator_to_array( \IPS\Db::i()->select( 'id', 'forums_forums', '(' . \IPS\Db::i()->bitwiseWhere( \IPS\forums\Forum::$bitOptions['forums_bitoptions'], 'bw_enable_answers' ) . ') OR ( ' . \IPS\Db::i()->bitwiseWhere( \IPS\forums\Forum::$bitOptions['forums_bitoptions'], 'bw_enable_answers_moderator' ) . ' )' ) ) ) );
$where[] = array( 'type=?', 'solved' );
$where[] = array( Db::i()->in( 'forum_id', iterator_to_array( Db::i()->select( 'id', 'forums_forums', '(' . Db::i()->bitwiseWhere( Forum::$bitOptions['forums_bitoptions'], 'bw_enable_answers' ) . ') OR ( ' . Db::i()->bitwiseWhere( Forum::$bitOptions['forums_bitoptions'], 'bw_solved_set_by_moderator' ) . ' )' ) ) ) );
if( \IPS\Request::i()->nodes )
if( Request::i()->nodes )
{
foreach( explode( ',', \IPS\Request::i()->nodes ) as $nodeId )
foreach( explode( ',', Request::i()->nodes ) as $nodeId )
{
$nodeNames[] = \IPS\forums\Forum::load( $nodeId )->_title;
$nodeNames[] = Forum::load( $nodeId )->_title;
}
}
@@ -143,8 +140,8 @@ class _Solved
switch( $subBlock )
{
case 'percentagesolved':
$total = \IPS\Db::i()->select( 'COUNT(*)', 'forums_topics', $this->_modifyWhereClause( $where ) )->first();
$solved = \IPS\Db::i()->select( 'COUNT(*)', 'forums_topics', array_merge( $this->_modifyWhereClause( $where ), array( array( 'core_solved_index.id IS NOT NULL' ) ) ) )->join( 'core_solved_index', "core_solved_index.app='forums' AND core_solved_index.item_id=forums_topics.tid")->first();
$total = Db::i()->select( 'COUNT(*)', 'forums_topics', $this->_modifyWhereClause( $where ) )->first();
$solved = Db::i()->select( 'COUNT(*)', 'forums_topics', array_merge( $this->_modifyWhereClause( $where ), array( array( 'core_solved_index.id IS NOT NULL' ) ) ) )->join( 'core_solved_index', "core_solved_index.app='forums' AND core_solved_index.item_id=forums_topics.tid")->first();
$value = $total ? round( $solved / $total * 100, 2 ) : 0;
@@ -152,45 +149,45 @@ class _Solved
if( $previousWhere !== NULL )
{
$previousTotal = \IPS\Db::i()->select( 'COUNT(*)', 'forums_topics', $this->_modifyWhereClause( $previousWhere ) )->first();
$previousSolved = \IPS\Db::i()->select( 'COUNT(*)', 'forums_topics', array_merge( $this->_modifyWhereClause( $previousWhere ), array( array( 'core_solved_index.id IS NOT NULL' ) ) ) )->join( 'core_solved_index', "core_solved_index.app='forums' AND core_solved_index.item_id=forums_topics.tid")->first();
$previousTotal = Db::i()->select( 'COUNT(*)', 'forums_topics', $this->_modifyWhereClause( $previousWhere ) )->first();
$previousSolved = Db::i()->select( 'COUNT(*)', 'forums_topics', array_merge( $this->_modifyWhereClause( $previousWhere ), array( array( 'core_solved_index.id IS NOT NULL' ) ) ) )->join( 'core_solved_index', "core_solved_index.app='forums' AND core_solved_index.item_id=forums_topics.tid")->first();
$previousValue = $previousTotal ? round( $previousSolved / $previousTotal * 100, 2 ) : 0;
}
return \IPS\Theme::i()->getTemplate( 'stats', 'forums' )->solvedPercentage( $value, $total, $solved, $previousValue, $previousTotal, $previousSolved, $nodeNames );
break;
return Theme::i()->getTemplate( 'stats', 'forums' )->solvedPercentage( $value, $total, $solved, $previousValue, $previousTotal, $previousSolved, $nodeNames );
case 'averagetimesolved':
$value = \IPS\Db::i()->select( 'AVG(core_solved_index.solved_date-forums_topics.start_date)', 'forums_topics', array_merge( $this->_modifyWhereClause( $where ), array( array( 'core_solved_index.id IS NOT NULL' ) ) ) )->join( 'core_solved_index', "core_solved_index.app='forums' AND core_solved_index.item_id=forums_topics.tid")->first();
$value = Db::i()->select( 'AVG(CAST(core_solved_index.solved_date AS SIGNED)-forums_topics.start_date)', 'forums_topics', array_merge( $this->_modifyWhereClause( $where ), array( array( 'core_solved_index.id IS NOT NULL' ) ) ) )->join( 'core_solved_index', "core_solved_index.app='forums' AND core_solved_index.item_id=forums_topics.tid")->first();
if( $previousWhere !== NULL )
{
$previousValue = \IPS\Db::i()->select( 'AVG(core_solved_index.solved_date-forums_topics.start_date)', 'forums_topics', array_merge( $this->_modifyWhereClause( $previousWhere ), array( array( 'core_solved_index.id IS NOT NULL' ) ) ) )->join( 'core_solved_index', "core_solved_index.app='forums' AND core_solved_index.item_id=forums_topics.tid")->first();
$previousValue = Db::i()->select( 'AVG(CAST(core_solved_index.solved_date AS SIGNED)-forums_topics.start_date)', 'forums_topics', array_merge( $this->_modifyWhereClause( $previousWhere ), array( array( 'core_solved_index.id IS NOT NULL' ) ) ) )->join( 'core_solved_index', "core_solved_index.app='forums' AND core_solved_index.item_id=forums_topics.tid")->first();
}
return \IPS\Theme::i()->getTemplate( 'stats', 'forums' )->timeToSolved( $value, $previousValue, $nodeNames );
break;
return Theme::i()->getTemplate( 'stats', 'forums' )->timeToSolved( $value, $previousValue, $nodeNames );
}
return \IPS\Theme::i()->getTemplate( 'stats', 'forums' )->solvedPercentage( $values, $previousValues, $nodeNames );
return Theme::i()->getTemplate( 'stats', 'forums' )->solvedPercentage( $value, $previousValue, $nodeNames );
}
/**
* Modify the where clause to apply other filters
*
* @param array $where Current where clause
* @param array $where Current where clause
* @return array
*/
protected function _modifyWhereClause( $where )
protected function _modifyWhereClause( array $where ): array
{
$where[] = array( \IPS\Db::i()->in( 'approved', array( -2, -3 ), TRUE ) );
$where[] = array( Db::i()->in( 'approved', array( -2, -3 ), TRUE ) );
$where = array_merge( $where, \IPS\forums\Topic::overviewStatisticsWhere() );
$where = array_merge( $where, Topic::overviewStatisticsWhere() );
if( \IPS\Request::i()->nodes )
if( Request::i()->nodes )
{
$where[] = array( \IPS\Db::i()->in( 'forum_id', explode( ',', \IPS\Request::i()->nodes ) ) );
$where[] = array( Db::i()->in( 'forum_id', explode( ',', Request::i()->nodes ) ) );
}
return $where;
@@ -202,46 +199,46 @@ class _Solved
* @param string|NULL $subBlock The subblock we are loading as returned by getBlocks()
* @return array
*/
public function getBlockForm( $subBlock = NULL )
public function getBlockForm( string $subBlock = NULL ): mixed
{
/* Make sure someone isn't trying to manipulate the request or do something weird */
if( !\in_array( $subBlock, $this->getBlocks() ) )
if( !in_array( $subBlock, $this->getBlocks() ) )
{
return '';
}
$form = new \IPS\Helpers\Form;
$form = new Form;
$form->attributes['data-controller'] = 'core.admin.stats.nodeFilters';
$form->attributes['data-block'] = \IPS\Request::i()->blockKey;
$form->attributes['data-block'] = Request::i()->blockKey;
$form->attributes['data-subblock'] = $subBlock;
$form->add( new \IPS\Helpers\Form\Node( \IPS\forums\Forum::$nodeTitle, NULL, TRUE, array( 'class' => '\IPS\forums\Forum', 'multiple' => TRUE, 'clubs' => FALSE ) ) );
$form->add( new Node( Forum::$nodeTitle, NULL, TRUE, array( 'class' => '\IPS\forums\Forum', 'multiple' => TRUE, 'clubs' => FALSE ) ) );
if( $values = $form->values() )
{
$dateFilters = NULL;
if( \IPS\Request::i()->range )
if( Request::i()->range )
{
$dateFilters = \IPS\Request::i()->range;
$dateFilters = Request::i()->range;
}
elseif( \IPS\Request::i()->start )
elseif( Request::i()->start )
{
try
{
$timezone = \IPS\Member::loggedIn()->timezone ? new \DateTimeZone( \IPS\Member::loggedIn()->timezone ) : NULL;
$timezone = Member::loggedIn()->timezone ? new DateTimeZone( Member::loggedIn()->timezone ) : NULL;
}
catch ( \Exception $e )
catch ( Exception $e )
{
$timezone = NULL;
}
$dateFilters = array(
'start' => new \IPS\DateTime( \IPS\Helpers\Form\Date::_convertDateFormat( \IPS\Request::i()->start ), $timezone ),
'end' => new \IPS\DateTime( \IPS\Helpers\Form\Date::_convertDateFormat( \IPS\Request::i()->end ), $timezone )
'start' => new DateTime( Date::_convertDateFormat( Request::i()->start ), $timezone ),
'end' => new DateTime( Date::_convertDateFormat( Request::i()->end ), $timezone )
);
}
return $this->getBlock( $dateFilters, $subblock );
return $this->getBlock( $dateFilters, $subBlock );
}
return $form;
@@ -12,32 +12,37 @@
namespace IPS\forums\extensions\core\Permissions;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\Extensions\PermissionsAbstract;
use IPS\forums\Forum;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Permissions
*/
class _Permissions
class Permissions extends PermissionsAbstract
{
/**
* Get node classes
*
* @return array
*/
public function getNodeClasses()
public function getNodeClasses(): array
{
return array(
'IPS\forums\Forum' => function( $current, $group )
{
$rows = array();
foreach( \IPS\forums\Forum::roots( NULL ) AS $root )
foreach( Forum::roots( NULL ) AS $root )
{
\IPS\forums\Forum::populatePermissionMatrix( $rows, $root, $group, $current );
Forum::populatePermissionMatrix( $rows, $root, $group, $current );
}
return $rows;
@@ -1,85 +0,0 @@
<?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
* @subpackage Forums
* @since 18 Mar 2015
*/
namespace IPS\forums\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 _DeleteLegacyPosts
{
/**
* Parse data before queuing
*
* @param array $data
* @return array
*/
public function preQueueData( $data )
{
$data['count'] = \IPS\Db::i()->select( 'COUNT(*)', 'forums_posts', 'queued > 2' )->first();
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 )
{
$select = \IPS\Db::i()->select( '*', 'forums_posts', 'queued > 2', NULL, array( 0, \IPS\REBUILD_NORMAL ) );
if ( !\count( $select ) )
{
throw new \IPS\Task\Queue\OutOfRangeException;
}
$done = 0;
foreach( $select as $post )
{
try
{
\IPS\forums\Topic\Post::constructFromData( $post )->logDelete( FALSE );
}
catch( \Exception $e )
{
\IPS\Db::i()->update( 'forums_posts', [ 'queued' => '-1' ], [ 'pid=?', $post['pid'] ] );
}
$done++;
}
return $offset + $done;
}
/**
* 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('queue_deleting_legacy_posts'), 'complete' => round( 100 / ( $data['count'] ?: 1 + $offset ) * $offset, 2 ) );
}
}
@@ -1,77 +0,0 @@
<?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
* @subpackage Forums
* @since 18 Mar 2015
*/
namespace IPS\forums\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 _DeleteLegacyTopics
{
/**
* Parse data before queuing
*
* @param array $data
* @return array
*/
public function preQueueData( $data )
{
$data['count'] = \IPS\Db::i()->select( 'COUNT(*)', 'forums_topics', 'approved > 1' )->first();
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 )
{
$select = \IPS\Db::i()->select( '*', 'forums_topics', 'approved > 1', 'tid ASC', array( 0, \IPS\REBUILD_NORMAL ) );
if ( !\count( $select ) )
{
throw new \IPS\Task\Queue\OutOfRangeException;
}
$done = 0;
foreach( new \IPS\Patterns\ActiveRecordIterator( $select, 'IPS\forums\Topic' ) as $topic )
{
$topic->logDelete( FALSE );
$done++;
}
return $offset + $done;
}
/**
* 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('queue_deleting_legacy_topics'), 'complete' => round( 100 / ( $data['count'] ?: 1 + $offset ) * $offset, 2 ) );
}
}
@@ -1,79 +0,0 @@
<?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
* @subpackage Forums
* @since 18 Mar 2015
*/
namespace IPS\forums\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 to fix an issue where topics are not hidden, but the first post is hidden with -1.
*/
class _HiddenFirstPost
{
/**
* Parse data before queuing
*
* @param array $data
* @return array
*/
public function preQueueData( $data )
{
$data['count'] = \IPS\Db::i()->select( 'COUNT(*)', 'forums_posts', 'new_topic = 1 and queued = -1' )->first();
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 )
{
$select = \IPS\Db::i()->select( '*', 'forums_posts', 'new_topic = 1 and queued = -1', 'topic_id ASC', array( 0, \IPS\REBUILD_SLOW ) );
if ( !\count( $select ) )
{
throw new \IPS\Task\Queue\OutOfRangeException;
}
$done = 0;
foreach( new \IPS\Patterns\ActiveRecordIterator( $select, 'IPS\forums\Topic\Post' ) as $post )
{
\IPS\Db::i()->update( 'forums_posts', array( 'queued' => 2 ), array( 'topic_id=?', $post->topic_id ) );
$item = $post->item();
$item->hide( NULL );
$done++;
}
return $offset + $done;
}
/**
* 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('queue_fixing_hidden_first_post'), 'complete' => round( 100 / ( $data['count'] ?: 1 + $offset ) * $offset, 2 ) );
}
}
@@ -1,154 +0,0 @@
<?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
* @subpackage Converter
* @since 26 Feb 2016
*/
namespace IPS\forums\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 _RebuildNewTopicFlag
{
/**
* Parse data before queuing
*
* @param array $data
* @return array
*/
public function preQueueData( $data )
{
try
{
$data['count'] = \IPS\Db::i()->select( 'MAX(tid)', 'forums_topics' )->first();
$data['realCount'] = \IPS\Db::i()->select( 'COUNT(*)', 'forums_topics' )->first();
}
catch( \Exception $e )
{
throw new \OutOfRangeException;
}
if( $data['count'] == 0 )
{
return NULL;
}
$data['completed'] = 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 )
{
if ( !class_exists( 'IPS\forums\Topic' ) OR !\IPS\Application::appisEnabled( 'forums' ) )
{
throw new \IPS\Task\Queue\OutOfRangeException;
}
$last = NULL;
$topicIdsToReset = array();
$archivedTopicIdsToReset = array();
$firstPostIds = array();
$firstPostArchivedIds = array();
foreach( new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', 'forums_topics', array( "tid>?", $offset ), "tid ASC", array( 0, \IPS\REBUILD_SLOW ) ), 'IPS\forums\Topic' ) AS $topic )
{
if ( $topic->isArchived() == FALSE )
{
try
{
/* Set first post */
$topic->topic_firstpost = \IPS\Db::i()->select( 'pid', 'forums_posts', array( 'topic_id=?', $topic->tid ), 'post_date ASC', 1 )->first();
$topic->save();
/* Reset new_topic value for topic */
$topicIdsToReset[] = $topic->tid;
$firstPostIds[] = $topic->topic_firstpost;
}
/* Underflow exception may occur if the topic doesn't have any posts for an unknown reason */
catch( \UnderflowException $e ) {}
}
else
{
try
{
/* Set first post */
$topic->topic_firstpost = \IPS\forums\Topic\ArchivedPost::db()->select( 'archive_id', 'forums_archive_posts', array( "archive_topic_id=?", $topic->tid ), "archive_content_date ASC", 1 )->first();
$topic->save();
/* Reset new_topic value for topic */
$archivedTopicIdsToReset[] = $topic->tid;
$firstPostArchivedIds[] = $topic->topic_firstpost;
}
/* Underflow exception may occur if the topic doesn't have any posts for an unknown reason */
catch( \UnderflowException $e ) {}
}
$data['completed']++;
$last = $topic->tid;
}
/* Reset flags as needed */
if( \count( $topicIdsToReset ) )
{
\IPS\Db::i()->update( 'forums_posts', array( 'new_topic' => 0 ), array( 'topic_id IN(' . implode( ',', $topicIdsToReset ) . ')' ) );
}
if( \count( $firstPostIds ) )
{
\IPS\Db::i()->update( 'forums_posts', array( 'new_topic' => 1 ), array( 'pid IN(' . implode( ',', $firstPostIds ) . ')' ) );
}
if( \count( $archivedTopicIdsToReset ) )
{
\IPS\forums\Topic\ArchivedPost::db()->update( 'forums_archive_posts', array( 'archive_is_first' => 0 ), array( 'archive_topic_id IN(' . implode( ',', $archivedTopicIdsToReset ) . ')' ) );
}
if( \count( $firstPostArchivedIds ) )
{
\IPS\forums\Topic\ArchivedPost::db()->update( 'forums_archive_posts', array( 'archive_is_first' => 1 ), array( 'archive_id IN(' . implode( ',', $firstPostArchivedIds ) . ')' ) );
}
if( $last === NULL )
{
throw new \IPS\Task\Queue\OutOfRangeException;
}
return $last;
}
/**
* Get Progress
*
* @param mixed $data Data as it was passed to \IPS\Task::queue()
* @param int $offset Offset
* @return array( 'text' => 'Doing something...', 'complete' => 50 ) Text explaining task and percentage complete
* @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('queue_rebuilding_new_topic_flag'), 'complete' => $data['realCount'] ? ( round( 100 / $data['realCount'] * $data['completed'], 2 ) ) : 100 );
}
}
@@ -11,33 +11,46 @@
namespace IPS\forums\extensions\core\Queue;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use Exception;
use IPS\Application;
use IPS\Db;
use IPS\Extensions\QueueAbstract;
use IPS\forums\Topic\Post;
use IPS\Member;
use IPS\Patterns\ActiveRecordIterator;
use OutOfRangeException;
use UnderflowException;
use function defined;
use const IPS\REBUILD_QUICK;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Background Task: Rebuild Solved Index
*/
class _RebuildSolvedIndex
class RebuildSolvedIndex extends QueueAbstract
{
/**
* Parse data before queuing
*
* @param array $data
* @return array
* @return array|null
*/
public function preQueueData( $data )
public function preQueueData( array $data ): ?array
{
try
{
$data['count'] = \IPS\Db::i()->select( 'MAX(tid)', 'forums_topics', array( 'topic_answered_pid > 0' ) )->first();
$data['realCount'] = \IPS\Db::i()->select( 'COUNT(*)', 'forums_topics', array( 'topic_answered_pid > 0' ) )->first();
$data['count'] = Db::i()->select( 'MAX(tid)', 'forums_topics', array( 'topic_answered_pid > 0' ) )->first();
$data['realCount'] = Db::i()->select( 'COUNT(*)', 'forums_topics', array( 'topic_answered_pid > 0' ) )->first();
}
catch( \Exception $e )
catch( Exception $e )
{
throw new \OutOfRangeException;
throw new OutOfRangeException;
}
if( $data['count'] == 0 )
@@ -58,39 +71,40 @@ class _RebuildSolvedIndex
* @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( mixed &$data, int $offset ): int
{
if ( !class_exists( 'IPS\forums\Topic' ) OR !\IPS\Application::appisEnabled( 'forums' ) )
if ( !class_exists( 'IPS\forums\Topic' ) OR !Application::appisEnabled( 'forums' ) )
{
throw new \IPS\Task\Queue\OutOfRangeException;
}
$last = NULL;
foreach( new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', 'forums_topics', array( "tid>? and topic_answered_pid > 0", $offset ), "tid ASC", array( 0, \IPS\REBUILD_QUICK ) ), 'IPS\forums\Topic' ) AS $topic )
foreach( new ActiveRecordIterator( Db::i()->select( '*', 'forums_topics', array( "tid>? and topic_answered_pid > 0", $offset ), "tid ASC", array( 0, REBUILD_QUICK ) ), 'IPS\forums\Topic' ) AS $topic )
{
/* I told him we already got one! */
try
{
\IPS\Db::i()->select( '*', 'core_solved_index', array( 'comment_class=? and item_id=? and comment_id=?', 'IPS\\forums\\Topic\\Post', $topic->tid, $topic->topic_answered_pid ) )->first();
Db::i()->select( '*', 'core_solved_index', array( 'comment_class=? and item_id=? and comment_id=? AND type=?', 'IPS\\forums\\Topic\\Post', $topic->tid, $topic->topic_answered_pid, 'solved' ) )->first();
}
catch( \UnderflowException $e )
catch( UnderflowException $e )
{
/* No? Best slap it in then */
try
{
$comment = \IPS\forums\Topic\Post::load( $topic->topic_answered_pid );
$comment = Post::load( $topic->topic_answered_pid );
\IPS\Db::i()->insert( 'core_solved_index', array(
Db::i()->insert( 'core_solved_index', array(
'member_id' => $comment->author()->member_id,
'app' => 'forums',
'comment_class' => 'IPS\\forums\\Topic\\Post',
'comment_id' => $comment->pid,
'item_id' => $topic->tid,
'solved_date' => $comment->post_date // We don't have the real solve date so this will have to do
'solved_date' => $comment->post_date, // We don't have the real solve date so this will have to do
'type' => 'solved'
) );
}
catch( \Exception $e ) { }
catch( Exception $e ) { }
}
$data['completed']++;
@@ -111,11 +125,11 @@ class _RebuildSolvedIndex
* @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
* @throws OutOfRangeException Indicates offset doesn't exist and thus task is complete
*/
public function getProgress( $data, $offset )
public function getProgress( mixed $data, int $offset ): array
{
return array( 'text' => \IPS\Member::loggedIn()->language()->addToStack('queue_rebuilding_solved_posts'), 'complete' => $data['realCount'] ? ( round( 100 / $data['realCount'] * $data['completed'], 2 ) ) : 100 );
return array( 'text' => Member::loggedIn()->language()->addToStack('queue_rebuilding_solved_posts'), 'complete' => $data['realCount'] ? ( round( 100 / $data['realCount'] * $data['completed'], 2 ) ) : 100 );
}
}
@@ -12,42 +12,56 @@
namespace IPS\forums\extensions\core\Queue;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use DateInterval;
use DateTimeZone;
use Exception;
use IPS\Application;
use IPS\DateTime;
use IPS\Db;
use IPS\Extensions\QueueAbstract;
use IPS\forums\Forum;
use IPS\Member;
use IPS\Settings;
use OutOfRangeException;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Background Task
*/
class _RebuildSolvedStats
class RebuildSolvedStats extends QueueAbstract
{
/**
* @brief Number of days to rebuild per cycle
*/
public $rebuild = 30;
public int $rebuild = 30;
/**
* Parse data before queuing
*
* @param array $data
* @return array
* @return array|null
*/
public function preQueueData( $data )
public function preQueueData( array $data ): ?array
{
try
{
$forum = \IPS\forums\Forum::load( $data['forum_id'] );
$timezone = new \DateTimeZone( \IPS\Settings::i()->reputation_timezone );
$forum = Forum::load( $data['forum_id'] );
$timezone = new DateTimeZone( Settings::i()->reputation_timezone );
/* Reset solved time */
$forum->solved_stats_from = 0;
$forum->save();
/* We work a day in arrears */
$oldest = \IPS\DateTime::ts( $forum->getFirstSolvedTime() )->setTimezone( $timezone )->setTime( 12, 0 );
$newest = \IPS\DateTime::create()->setTimezone( $timezone )->setTime( 12, 0 );
$oldest = DateTime::ts( $forum->getFirstSolvedTime() )->setTimezone( $timezone )->setTime( 12, 0 );
$newest = DateTime::create()->setTimezone( $timezone )->setTime( 12, 0 );
$diff = $newest->diff( $oldest );
@@ -55,11 +69,11 @@ class _RebuildSolvedStats
$data['date'] = $oldest->getTimeStamp();
$data['max'] = $newest->getTimeStamp();
\IPS\Db::i()->delete( 'core_statistics', [ 'type=? and value_1=?', 'solved', $forum->_id ] );
Db::i()->delete( 'core_statistics', [ 'type=? and value_1=?', 'solved', $forum->_id ] );
}
catch( \Exception $ex )
catch( Exception $ex )
{
throw new \OutOfRangeException;
throw new OutOfRangeException;
}
if( $data['count'] == 0 )
@@ -78,18 +92,18 @@ class _RebuildSolvedStats
* @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( mixed &$data, int $offset ): int
{
if ( !class_exists( 'IPS\forums\Topic' ) OR !\IPS\Application::appisEnabled( 'forums' ) )
if ( !class_exists( 'IPS\forums\Topic' ) OR !Application::appisEnabled( 'forums' ) )
{
throw new \IPS\Task\Queue\OutOfRangeException;
}
try
{
$forum = \IPS\forums\Forum::load( $data['forum_id'] );
$forum = Forum::load( $data['forum_id'] );
}
catch( \Exception $ex )
catch( Exception $ex )
{
throw new \IPS\Task\Queue\OutOfRangeException;
}
@@ -97,8 +111,8 @@ class _RebuildSolvedStats
$done = 0;
for( $i = 0; $i < $this->rebuild; $i++ )
{
$timezone = new \DateTimeZone( \IPS\Settings::i()->reputation_timezone );
$end = \IPS\DateTime::ts( $data['date'], true )->setTimezone( $timezone )->sub( new \DateInterval('P1D') )->setTime( 23, 59 );
$timezone = new DateTimeZone( Settings::i()->reputation_timezone );
$end = DateTime::ts( $data['date'], true )->setTimezone( $timezone )->sub( new DateInterval('P1D') )->setTime( 23, 59 );
if ( $end->getTimeStamp() >= $data['max'] )
{
@@ -106,21 +120,22 @@ class _RebuildSolvedStats
throw new \IPS\Task\Queue\OutOfRangeException;
}
$start = \IPS\DateTime::ts( $forum->getFirstSolvedTime() )->setTimezone( $timezone );
$start = DateTime::ts( $forum->getFirstSolvedTime() )->setTimezone( $timezone );
$where = [
[ \IPS\Db::i()->in( 'state', array( 'link', 'merged' ), TRUE ) ],
[ \IPS\Db::i()->in( 'approved', array( -2, -3 ), TRUE ) ],
[ Db::i()->in( 'state', array( 'link', 'merged' ), TRUE ) ],
[ Db::i()->in( 'approved', array( -2, -3 ), TRUE ) ],
[ 'forum_id=?', $forum->_id ],
[ 'start_date > ? AND start_date < ?', $start->getTimestamp(), $end->getTimestamp() ]
[ 'start_date > ? AND start_date < ?', $start->getTimestamp(), $end->getTimestamp() ],
[ 'type=?', 'solved' ]
];
$total = \IPS\Db::i()->select( 'COUNT(*)', 'forums_topics', $where )->first();
$solved = \IPS\Db::i()->select( 'COUNT(*)', 'forums_topics', array_merge( $where, [ [ 'core_solved_index.id IS NOT NULL' ] ] ) )->join( 'core_solved_index', "core_solved_index.app='forums' AND core_solved_index.item_id=forums_topics.tid")->first();
$avg = \IPS\Db::i()->select( 'AVG(CAST(core_solved_index.solved_date AS SIGNED)-forums_topics.start_date)', 'forums_topics', array_merge( $where, [ [ 'core_solved_index.id IS NOT NULL' ] ] ) )->join( 'core_solved_index', "core_solved_index.app='forums' AND core_solved_index.item_id=forums_topics.tid")->first();
$total = Db::i()->select( 'COUNT(*)', 'forums_topics', $where )->first();
$solved = Db::i()->select( 'COUNT(*)', 'forums_topics', array_merge( $where, [ [ 'core_solved_index.id IS NOT NULL' ] ] ) )->join( 'core_solved_index', "core_solved_index.app='forums' AND core_solved_index.item_id=forums_topics.tid")->first();
$avg = Db::i()->select( 'AVG(CAST(core_solved_index.solved_date AS SIGNED)-forums_topics.start_date)', 'forums_topics', array_merge( $where, [ [ 'core_solved_index.id IS NOT NULL' ] ] ) )->join( 'core_solved_index', "core_solved_index.app='forums' AND core_solved_index.item_id=forums_topics.tid")->first();
\IPS\Db::i()->insert( 'core_statistics', [
Db::i()->insert( 'core_statistics', [
'type' => 'solved',
'value_1' => $forum->_id,
'value_2' => $total,
@@ -129,7 +144,7 @@ class _RebuildSolvedStats
'time' => $end->getTimestamp()
] );
$data['date'] = \IPS\DateTime::ts( $data['date'] )->add( new \DateInterval('P1D') )->getTimeStamp();
$data['date'] = DateTime::ts( $data['date'] )->add( new DateInterval('P1D') )->getTimeStamp();
$done++;
}
@@ -147,20 +162,20 @@ class _RebuildSolvedStats
* @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
* @throws OutOfRangeException Indicates offset doesn't exist and thus task is complete
*/
public function getProgress( $data, $offset )
public function getProgress( mixed $data, int $offset ): array
{
try
{
$forum = \IPS\forums\Forum::load( $data['forum_id'] );
$forum = Forum::load( $data['forum_id'] );
}
catch( \Exception $ex )
catch( Exception $ex )
{
throw new \OutOfRangeException;
throw new OutOfRangeException;
}
return array( 'text' => \IPS\Member::loggedIn()->language()->addToStack('solved_rebuilding_stats', NULL, [ 'sprintf' => [ $forum->_title ] ] ), 'complete' => $data['count'] ? ( round( 100 / $data['count'] * $offset, 2 ) ) : 100 );
return array( 'text' => Member::loggedIn()->language()->addToStack('solved_rebuilding_stats', NULL, [ 'sprintf' => [ $forum->_title ] ] ), 'complete' => $data['count'] ? ( round( 100 / $data['count'] * $offset, 2 ) ) : 100 );
}
/**
@@ -170,7 +185,7 @@ class _RebuildSolvedStats
* @param bool $processed Was anything processed or not? If preQueueData returns NULL, this will be FALSE.
* @return void
*/
public function postComplete( $data, $processed = TRUE )
public function postComplete( array $data, bool $processed = TRUE ) : void
{
}
@@ -1,105 +0,0 @@
<?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
* @subpackage Forums
* @since 04 Jan 2016
*/
namespace IPS\forums\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 _TopicStarterIds
{
/**
* Parse data before queuing
*
* @param array $data
* @return array
*/
public function preQueueData( $data )
{
try
{
$data['count'] = \IPS\Db::i()->select( 'COUNT(*)', 'forums_topics' )->first();
}
catch( \Exception $ex )
{
throw new \OutOfRangeException;
}
if( $data['count'] == 0 )
{
return null;
}
$data['completed'] = 0;
return $data;
}
/**
* Run Background Task
*
* @param mixed $data Data as it was passed to \IPS\Task::queue()
* @param int $offset Offset
* @return int New offset
* @throws \IPS\Task\Queue\OutOfRangeException Indicates offset doesn't exist and thus task is complete
*/
public function run( &$data, $offset )
{
$last = NULL;
foreach( \IPS\Db::i()->select( 'tid, starter_id', 'forums_topics', array( 'tid>?', $offset ), 'tid ASC', array( 0, \IPS\REBUILD_QUICK ) ) as $topic )
{
$last = $topic['tid'];
$data['completed']++;
try
{
$firstPost = \IPS\Db::i()->select( 'author_id', 'forums_posts', array( 'new_topic=? AND topic_id=?', 1, $topic['tid'] ), 'pid ASC', array( 0, 1 ) )->first();
if( $firstPost != $topic['starter_id'] )
{
\IPS\Db::i()->update( 'forums_topics', array( 'starter_id' => $firstPost ), array( 'tid=?', $topic['tid'] ) );
}
}
catch( \UnderflowException $e )
{
continue;
}
}
if( $last === NULL )
{
throw new \IPS\Task\Queue\OutOfRangeException;
}
return $last;
}
/**
* Get Progress
*
* @param mixed $data Data as it was passed to \IPS\Task::queue()
* @param int $offset Offset
* @return array( 'text' => 'Doing something...', 'complete' => 50 ) Text explaining task and percentage complete
* @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('rebuilding_starter_ids'), 'complete' => round( 100 / $data['count'] * $data['completed'], 2 ) );
}
}
@@ -12,23 +12,31 @@
namespace IPS\forums\extensions\core\RssImport;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\Content;
use IPS\Content\Search\Index;
use IPS\core\Rss\Import;
use IPS\Db;
use IPS\Extensions\RssImportAbstract;
use IPS\forums\Topic\Post;
use IPS\Helpers\Form;
use IPS\Helpers\Form\Radio;
use IPS\Member;
use IPS\Node\Model;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* @brief RSS Import extension: RssImport
*/
class _RssImport
class RssImport extends RssImportAbstract
{
/**
* @brief RSSImport Class
*/
public $classes = array();
public $fileStorage = 'forums_Forums';
public string $fileStorage = 'forums_Forums';
/**
* Constructor
@@ -45,18 +53,17 @@ class _RssImport
*
* @return array
*/
public function availableOptions()
public function availableOptions(): array
{
return array( 'IPS\\forums\\Topic' => \IPS\Member::loggedIn()->language()->addToStack('__app_forums') );
return array( 'IPS\\forums\\Topic' => Member::loggedIn()->language()->addToStack('__app_forums') );
}
/**
* Show in the Admin CP?
*
* @param Object $class The class to check
* @return boolean
*/
public function showInAdminCp( $class ): bool
public function showInAdminCp(): bool
{
return true;
}
@@ -64,10 +71,10 @@ class _RssImport
/**
* Node selector options
*
* @param \IPS\core\Rss\Import|null $rss Existing RSS object if editing|NULL if not
* @param Import|null $rss Existing RSS object if editing|null if not
* @return array
*/
public function nodeSelectorOptions( $rss )
public function nodeSelectorOptions( ?Import $rss ): array
{
return array( 'class' => 'IPS\forums\Forum', 'permissionCheck' => function ( $forum )
{
@@ -76,17 +83,17 @@ class _RssImport
}
/**
* @param \IPS\core\Rss\Import $rss RSS object
* @param Import $rss RSS object
* @param array $article RSS feed article importing
* @param \IPS\Node\Model $container Container object
* @param Model $container Container object
* @param string $content Post content with read more link if set
* @return \IPS\Content
* @return Content
*/
public function create( \IPS\core\Rss\Import $rss, $article, \IPS\Node\Model $container, $content )
public function create( Import $rss, array $article, Model $container, string $content ): Content
{
$settings = $rss->settings;
$class = $rss->_class;
$member = \IPS\Member::load( $rss->member );
$member = Member::load( $rss->member );
$topic = $class::createItem( $member, NULL, $article['date'], $container, $settings['topic_hide'] );
$topic->title = $rss->topic_pre . $article['title'];
@@ -104,9 +111,9 @@ class _RssImport
}
/* Add to search index */
\IPS\Content\Search\Index::i()->index( $topic );
Index::i()->index( $topic );
$post = \IPS\forums\Topic\Post::create( $topic, $content, TRUE, NULL, \IPS\forums\Topic\Post::incrementPostCount( $container ), $member, $article['date'], ( array_key_exists( 'SERVER_ADDR', $_SERVER ) ) ? $_SERVER['SERVER_ADDR'] : NULL );
$post = Post::create( $topic, $content, true, null, Post::incrementPostCount( $container ), $member, $article['date'], ( array_key_exists( 'SERVER_ADDR', $_SERVER ) ) ? $_SERVER['SERVER_ADDR'] : null );
$topic->topic_firstpost = $post->pid;
$topic->save();
@@ -114,7 +121,7 @@ class _RssImport
/* Claim any attachments */
if ( isset( $article['attachment'] ) )
{
\IPS\Db::i()->insert( 'core_attachments_map', array(
Db::i()->insert( 'core_attachments_map', array(
'attachment_id' => $article['attachment']['attach_id'],
'location_key' => 'forums_Forums',
'id1' => $topic->tid,
@@ -127,11 +134,11 @@ class _RssImport
/**
* Addition Form elements
*
* @param \IPS\Helpers\Form $form The form
* @param \IPS\core\Rss\Import|null $rss Existing RSS object if editing|NULL if not
* @param Form $form The form
* @param Import|null $rss Existing RSS object if editing|null if not
* @return void
*/
public function form( &$form, $rss=NULL )
public function form( Form $form, Import $rss=NULL ) : void
{
$settings = NULL;
@@ -141,20 +148,20 @@ class _RssImport
}
/* Make rss_import_node_id make sense for forums */
\IPS\Member::loggedIn()->language()->words['rss_import_node_id'] = \IPS\Member::loggedIn()->language()->addToStack('topic_container');
Member::loggedIn()->language()->words['rss_import_node_id'] = Member::loggedIn()->language()->addToStack('topic_container');
$form->add( new \IPS\Helpers\Form\Radio( 'rss_import_topic_open', ( $settings ? $settings['topic_open'] : 1 ), FALSE, array( 'options' => array( 1 => 'unlocked', 0 => 'locked' ) ) ) );
$form->add( new \IPS\Helpers\Form\Radio( 'rss_import_topic_hide', ( $settings ? $settings['topic_hide'] : 0 ), FALSE, array( 'options' => array( 0 => 'unhidden', 1 => 'hidden' ) ) ) );
$form->add( new Radio( 'rss_import_topic_open', ( $settings ? $settings['topic_open'] : 1 ), FALSE, array( 'options' => array( 1 => 'unlocked', 0 => 'locked' ) ) ) );
$form->add( new Radio( 'rss_import_topic_hide', ( $settings ? $settings['topic_hide'] : 0 ), FALSE, array( 'options' => array( 0 => 'unhidden', 1 => 'hidden' ) ) ) );
}
/**
* Process additional fields unique to this extension
*
* @param array $values Values from form
* @param \IPS\core\Rss\Import $rss Existing RSS object
* @param array $values Values from form
* @param Import $rss Existing RSS object
* @return array
*/
public function saveForm( &$values, $rss )
public function saveForm( array &$values, Import $rss ) : array
{
$return = array(
'topic_open' => $values['rss_import_topic_open'],
@@ -0,0 +1,128 @@
<?php
/**
* @brief SearchContent extension: Posts
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
* @copyright (c) Invision Power Services, Inc.
* @license https://www.invisioncommunity.com/legal/standards/
* @package Invision Community
* @subpackage Forums
* @since 01 Jul 2023
*/
namespace IPS\forums\extensions\core\SearchContent;
/* To prevent PHP errors (extending class does not exist) revealing path */
use IPS\Content\Search\SearchContentAbstract;
use IPS\forums\Forum;
use IPS\forums\Topic;
use IPS\forums\Topic\Post;
use IPS\Http\Url;
use IPS\Login;
use IPS\Member;
use IPS\Request;
use IPS\Theme;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* @brief SearchContent extension: Topics
*/
class Posts extends SearchContentAbstract
{
/**
* Return all searchable classes in your application,
* including comments and/or reviews
*
* @return array
*/
public static function supportedClasses() : array
{
return array(
Topic::class,
Post::class
);
}
/**
* Search Index Permissions
*
* @return string Comma-delimited values or '*'
* @li Number indicates a group
* @li Number prepended by "m" indicates a member
* @li Number prepended by "s" indicates a social group
*/
public function searchIndexPermissions(): string
{
$topic = $this->object instanceof Post ? $this->object->item() : $this->object;
$return = $topic->container()->searchIndexPermissions();
if ( !$topic->container()->can_view_others )
{
/* If the search index permissions are empty, just return now because no one can see content in this forum */
if( !$return )
{
return $return;
}
$return = $topic->container()->permissionsThatCanAccessAllTopics();
if ( $topic->starter_id )
{
$return[] = "m{$topic->starter_id}";
}
$return = implode( ',', $return );
}
return $return;
}
/**
* Get HTML for search result display
*
* @param array $indexData Data from the search index
* @param array $authorData Basic data about the author. Only includes columns returned by \IPS\Member::columnsForPhoto()
* @param array $itemData Basic data about the item. Only includes columns returned by item::basicDataColumns()
* @param array|NULL $containerData Basic data about the container. Only includes columns returned by container::basicDataColumns()
* @param array $reputationData Array of people who have given reputation and the reputation they gave
* @param int|NULL $reviewRating If this is a review, the rating
* @param bool $iPostedIn If the user has posted in the item
* @param string $view 'expanded' or 'condensed'
* @param bool $asItem Displaying results as items?
* @param bool $canIgnoreComments Can ignore comments in the result stream? Activity stream can, but search results cannot.
* @param array|null $template Optional custom template
* @param array $reactions Reaction Data
* @return string
*/
public static function searchResult( array $indexData, array $authorData, array $itemData, array|null $containerData, array $reputationData, int|null $reviewRating, bool $iPostedIn, string $view, bool $asItem, bool $canIgnoreComments=FALSE, array|null $template=null, array $reactions=array() ): string
{
if( $indexData['index_class'] == Post::class )
{
/* Password protected */
if (
$containerData['password'] // There is a password
and !Member::loggedIn()->inGroup( explode( ',', $containerData['password_override'] ) ) // We can't bypass it
and (
!isset( Request::i()->cookie[ 'ipbforumpass_' . $indexData['index_container_id'] ] )
or
!Login::compareHashes( md5( $containerData['password'] ), Request::i()->cookie[ 'ipbforumpass_' . $indexData['index_container_id'] ] )
) // We don't have the correct password
)
{
return Theme::i()->getTemplate( 'global', 'forums' )->searchNoPermission(
Member::loggedIn()->language()->addToStack('no_perm_post_password'),
Url::internal( Forum::$urlBase . $indexData['index_container_id'], 'front', Forum::$urlTemplate, array( $containerData[ Forum::$databasePrefix . Forum::$seoTitleColumn ] ) )->setQueryString( 'topic', $indexData['index_item_id'] )
);
}
}
return parent::searchResult( $indexData, $authorData, $itemData, $containerData, $reputationData, $reviewRating, $iPostedIn, $view, $asItem, $canIgnoreComments, $template, $reactions );
}
}
@@ -11,29 +11,41 @@
namespace IPS\forums\extensions\core\Statistics;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use Exception;
use IPS\DateTime;
use IPS\Db;
use IPS\forums\Forum;
use IPS\Helpers\Chart;
use IPS\Helpers\Chart\Database;
use IPS\Http\Url;
use IPS\Member;
use UnderflowException;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Statistics Chart Extension
*/
class _PercentageSolved extends \IPS\core\Statistics\Chart
class PercentageSolved extends \IPS\core\Statistics\Chart
{
/**
* @brief Controller
*/
public $controller = 'forums_stats_solved_percentage';
public ?string $controller = 'forums_stats_solved_percentage';
/**
* Render Chart
*
* @param \IPS\Http\Url $url URL the chart is being shown on.
* @return \IPS\Helpers\Chart
* @param Url $url URL the chart is being shown on.
* @return Chart
*/
public function getChart( \IPS\Http\Url $url ): \IPS\Helpers\Chart
public function getChart( Url $url ): Chart
{
/* Determine minimum date */
$minimumDate = NULL;
@@ -41,20 +53,20 @@ class _PercentageSolved extends \IPS\core\Statistics\Chart
/* We can't retrieve any stats prior to the new tracking being implemented */
try
{
$oldestLog = \IPS\Db::i()->select( 'MIN(time)', 'core_statistics', array( 'type=?', 'solved' ) )->first();
$oldestLog = Db::i()->select( 'MIN(time)', 'core_statistics', array( 'type=?', 'solved' ) )->first();
if( !$minimumDate OR $oldestLog < $minimumDate->getTimestamp() )
{
$minimumDate = \IPS\DateTime::ts( $oldestLog );
$minimumDate = DateTime::ts( $oldestLog );
}
}
catch( \UnderflowException $e )
catch( UnderflowException $e )
{
/* We have nothing tracked, set minimum date to today */
$minimumDate = \IPS\DateTime::create();
$minimumDate = DateTime::create();
}
$chart = new \IPS\Helpers\Chart\Database( $url, 'core_statistics', 'time', '', array(
$chart = new Database( $url, 'core_statistics', 'time', '', array(
'isStacked' => FALSE,
'backgroundColor' => '#ffffff',
'hAxis' => array('gridlines' => array('color' => '#f5f5f5')),
@@ -68,11 +80,11 @@ class _PercentageSolved extends \IPS\core\Statistics\Chart
);
$chart->setExtension( $this );
$chart->description = \IPS\Member::loggedIn()->language()->addToStack( 'solved_stats_chart_desc' );
$chart->description = Member::loggedIn()->language()->addToStack( 'solved_stats_chart_desc' );
$chart->availableTypes = array('AreaChart', 'ColumnChart', 'BarChart');
$chart->enableHourly = FALSE;
$chart->groupBy = 'value_1';
$chart->title = \IPS\Member::loggedIn()->language()->addToStack('forums_solved_stats_percentage');
$chart->title = Member::loggedIn()->language()->addToStack('forums_solved_stats_percentage');
$chart->where[] = array( "type=?", 'solved' );
foreach( $validForumIds = $this->getValidForumIds() as $forumId => $forum )
@@ -88,17 +100,17 @@ class _PercentageSolved extends \IPS\core\Statistics\Chart
*
* @return array
*/
protected function getValidForumIds()
protected function getValidForumIds() : array
{
$validForumIds = [];
foreach( \IPS\Db::i()->select( 'value_1', 'core_statistics', [ 'type=?', 'solved' ], NULL, NULL, 'value_1' ) as $forumId )
foreach( Db::i()->select( 'value_1', 'core_statistics', [ 'type=?', 'solved' ], NULL, NULL, 'value_1' ) as $forumId )
{
try
{
$validForumIds[ $forumId ] = \IPS\forums\Forum::load( $forumId );
$validForumIds[ $forumId ] = Forum::load( $forumId );
}
catch( \Exception $e ) { }
catch( Exception $e ) { }
}
return $validForumIds;
@@ -11,31 +11,39 @@
namespace IPS\forums\extensions\core\Statistics;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\Db;
use IPS\Helpers\Chart;
use IPS\Helpers\Chart\Database;
use IPS\Http\Url;
use IPS\Member;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Statistics Chart Extension
*/
class _Posts extends \IPS\core\Statistics\Chart
class Posts extends \IPS\core\Statistics\Chart
{
/**
* @brief Controller
*/
public $controller = 'forums_stats_posts_total';
public ?string $controller = 'forums_stats_posts_total';
/**
* Render Chart
*
* @param \IPS\Http\Url $url URL the chart is being shown on.
* @return \IPS\Helpers\Chart
* @param Url $url URL the chart is being shown on.
* @return Chart
*/
public function getChart( \IPS\Http\Url $url ): \IPS\Helpers\Chart
public function getChart( Url $url ): Chart
{
$chart = new \IPS\Helpers\Chart\Database( $url, 'forums_posts', 'post_date', '', array(
$chart = new Database( $url, 'forums_posts', 'post_date', '', array(
'isStacked' => FALSE,
'backgroundColor' => '#ffffff',
'colors' => array( '#10967e' ),
@@ -45,9 +53,9 @@ class _Posts extends \IPS\core\Statistics\Chart
) );
$chart->setExtension( $this );
$chart->where = array( array( \IPS\Db::i()->in( 'queued', array( -2, -3 ), TRUE ) ) );
$chart->addSeries( \IPS\Member::loggedIn()->language()->addToStack( 'stats_new_posts' ), 'number', 'COUNT(*)', FALSE );
$chart->title = \IPS\Member::loggedIn()->language()->addToStack( 'stats_posts_title' );
$chart->where = array( array( Db::i()->in( 'queued', array( -2, -3 ), TRUE ) ) );
$chart->addSeries( Member::loggedIn()->language()->addToStack( 'stats_new_posts' ), 'number', 'COUNT(*)', FALSE );
$chart->title = Member::loggedIn()->language()->addToStack( 'stats_posts_title' );
$chart->availableTypes = array( 'AreaChart', 'ColumnChart', 'BarChart' );
return $chart;
@@ -11,31 +11,43 @@
namespace IPS\forums\extensions\core\Statistics;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use DateInterval;
use IPS\DateTime;
use IPS\Db;
use IPS\Helpers\Chart;
use IPS\Helpers\Chart\Database;
use IPS\Helpers\Form\Node;
use IPS\Http\Url;
use IPS\Member;
use function defined;
use function is_array;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Statistics Chart Extension
*/
class _PostsByForum extends \IPS\core\Statistics\Chart
class PostsByForum extends \IPS\core\Statistics\Chart
{
/**
* @brief Controller
*/
public $controller = 'forums_stats_posts_byforum';
public ?string $controller = 'forums_stats_posts_byforum';
/**
* Render Chart
*
* @param \IPS\Http\Url $url URL the chart is being shown on.
* @return \IPS\Helpers\Chart
* @param Url $url URL the chart is being shown on.
* @return Chart
*/
public function getChart( \IPS\Http\Url $url ): \IPS\Helpers\Chart
public function getChart( Url $url ): Chart
{
$chart = new \IPS\Helpers\Chart\Database( $url, 'forums_posts', 'post_date', '', array(
$chart = new Database( $url, 'forums_posts', 'post_date', '', array(
'isStacked' => FALSE,
'backgroundColor' => '#ffffff',
'hAxis' => array( 'gridlines' => array( 'color' => '#f5f5f5' ) ),
@@ -46,15 +58,15 @@ class _PostsByForum extends \IPS\core\Statistics\Chart
),
'ColumnChart',
'monthly',
array( 'start' => ( new \IPS\DateTime )->sub( new \DateInterval('P90D') ), 'end' => new \IPS\DateTime ),
array( 'start' => ( new DateTime )->sub( new DateInterval('P90D') ), 'end' => new DateTime ),
array(),
'byforum'
);
$chart->setExtension( $this );
$chart->title = \IPS\Member::loggedIn()->language()->addToStack( 'stats_posts_title_byforum' );
$chart->title = Member::loggedIn()->language()->addToStack( 'stats_posts_title_byforum' );
$chart->availableTypes = array( 'ColumnChart' );
$chart->where = array( array( \IPS\Db::i()->in( 'queued', array( -2, -3 ), TRUE ) ) );
$chart->where = array( array( Db::i()->in( 'queued', array( -2, -3 ), TRUE ) ) );
$chart->groupBy = 'forum_id';
$chart->joins = array( array( 'forums_topics', 'forums_topics.tid=forums_posts.topic_id' ) );
@@ -62,33 +74,33 @@ class _PostsByForum extends \IPS\core\Statistics\Chart
$chart->customFiltersForm = array(
'form' => array(
new \IPS\Helpers\Form\Node( 'chart_forums', $customValues, FALSE, array( 'class' => 'IPS\forums\Forum', 'zeroVal' => 'any', 'multiple' => TRUE, 'permissionCheck' => function ( $forum )
new Node( 'chart_forums', $customValues, FALSE, array( 'class' => 'IPS\forums\Forum', 'zeroVal' => 'any', 'multiple' => TRUE, 'permissionCheck' => function ( $forum )
{
return $forum->sub_can_post and !$forum->redirect_url;
} ), NULL, NULL, NULL, 'chart_forums' )
),
'where' => function( $values )
{
$forumIds = \is_array( $values['chart_forums'] ) ? array_keys( $values['chart_forums'] ) : explode( ',', $values['chart_forums'] );
return \IPS\Db::i()->in( 'forums_topics.forum_id', $forumIds );
$forumIds = is_array( $values['chart_forums'] ) ? array_keys( $values['chart_forums'] ) : explode( ',', $values['chart_forums'] );
return Db::i()->in( 'forums_topics.forum_id', $forumIds );
},
'groupBy' => 'forum_id',
'series' => function( $values )
{
$series = array();
$forumIds = \is_array( $values['chart_forums'] ) ? array_keys( $values['chart_forums'] ) : explode( ',', $values['chart_forums'] );
$forumIds = is_array( $values['chart_forums'] ) ? array_keys( $values['chart_forums'] ) : explode( ',', $values['chart_forums'] );
foreach( $forumIds as $id )
{
$series[] = array( \IPS\Member::loggedIn()->language()->addToStack( 'forums_forum_' . $id ), 'number', 'COUNT(*)', FALSE, $id );
$series[] = array( Member::loggedIn()->language()->addToStack( 'forums_forum_' . $id ), 'number', 'COUNT(*)', FALSE, $id );
}
return $series;
},
'defaultSeries' => function()
{
$series = array();
foreach( \IPS\Db::i()->select( '*', 'forums_forums', array( 'posts>? AND club_id IS NULL', 0 ), 'last_post desc', array( 0, 50 ) ) as $forum )
foreach( Db::i()->select( '*', 'forums_forums', array( 'posts>? AND club_id IS NULL', 0 ), 'topics desc', array( 0, 50 ) ) as $forum )
{
$series[] = array( \IPS\Member::loggedIn()->language()->addToStack( 'forums_forum_' . $forum['id'] ), 'number', 'COUNT(*)', FALSE, $forum['id'] );
$series[] = array( Member::loggedIn()->language()->addToStack( 'forums_forum_' . $forum['id'] ), 'number', 'COUNT(*)', FALSE, $forum['id'] );
}
return $series;
@@ -1,60 +0,0 @@
<?php
/**
* @brief Statistics Chart 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/
* @subpackage Forums
* @since 26 Jan 2023
*/
namespace IPS\forums\extensions\core\Statistics;
/* 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;
}
/**
* Statistics Chart Extension
*/
class _Questions extends \IPS\core\Statistics\Chart
{
/**
* @brief Controller
*/
public $controller = 'forums_stats_questions';
/**
* Render Chart
*
* @param \IPS\Http\Url $url URL the chart is being shown on.
* @return \IPS\Helpers\Chart
*/
public function getChart( \IPS\Http\Url $url ): \IPS\Helpers\Chart
{
$chart = new \IPS\Helpers\Chart\Database( $url, 'forums_question_ratings', 'date', '', array(
'isStacked' => FALSE,
'backgroundColor' => '#ffffff',
'hAxis' => array( 'gridlines' => array( 'color' => '#f5f5f5' ) ),
'lineWidth' => 1,
'areaOpacity' => 0.4
) );
$chart->setExtension( $this );
$chart->title = \IPS\Member::loggedIn()->language()->addToStack('stats_question_ratings_title');
$chart->availableTypes = array( 'AreaChart', 'ColumnChart', 'BarChart' );
$chart->groupBy = 'rating';
foreach( array( 1, -1 ) as $v )
{
$label = ( $v == -1 ) ? 'negative' : 'positive';
$chart->addSeries( \IPS\Member::loggedIn()->language()->addToStack('stats_question_ratings_' . $label ), 'number', 'COUNT(*)', TRUE, $label );
}
return $chart;
}
}
@@ -11,29 +11,44 @@
namespace IPS\forums\extensions\core\Statistics;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use DateInterval;
use Exception;
use IPS\DateTime;
use IPS\Db;
use IPS\forums\Forum;
use IPS\Helpers\Chart;
use IPS\Helpers\Chart\Database;
use IPS\Helpers\Form\Node;
use IPS\Http\Url;
use IPS\Member;
use UnderflowException;
use function defined;
use function is_array;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Statistics Chart Extension
*/
class _SolvedByForum extends \IPS\core\Statistics\Chart
class SolvedByForum extends \IPS\core\Statistics\Chart
{
/**
* @brief Controller
*/
public $controller = 'forums_stats_solved_solved';
public ?string $controller = 'forums_stats_solved_solved';
/**
* Render Chart
*
* @param \IPS\Http\Url $url URL the chart is being shown on.
* @return \IPS\Helpers\Chart
* @param Url $url URL the chart is being shown on.
* @return Chart
*/
public function getChart( \IPS\Http\Url $url ): \IPS\Helpers\Chart
public function getChart( Url $url ): Chart
{
/* Determine minimum date */
$minimumDate = NULL;
@@ -41,20 +56,20 @@ class _SolvedByForum extends \IPS\core\Statistics\Chart
/* We can't retrieve any stats prior to the new tracking being implemented */
try
{
$oldestLog = \IPS\Db::i()->select( 'MIN(time)', 'core_statistics', array( 'type=?', 'solved' ) )->first();
$oldestLog = Db::i()->select( 'MIN(time)', 'core_statistics', array( 'type=?', 'solved' ) )->first();
if( !$minimumDate OR $oldestLog < $minimumDate->getTimestamp() )
{
$minimumDate = \IPS\DateTime::ts( $oldestLog );
$minimumDate = DateTime::ts( $oldestLog );
}
}
catch( \UnderflowException $e )
catch( UnderflowException $e )
{
/* We have nothing tracked, set minimum date to today */
$minimumDate = \IPS\DateTime::create();
$minimumDate = DateTime::create();
}
$chart = new \IPS\Helpers\Chart\Database( $url, 'core_solved_index', 'solved_date', '', array(
$chart = new Database( $url, 'core_solved_index', 'solved_date', '', array(
'isStacked' => FALSE,
'backgroundColor' => '#ffffff',
'hAxis' => array( 'gridlines' => array( 'color' => '#f5f5f5' ) ),
@@ -65,15 +80,16 @@ class _SolvedByForum extends \IPS\core\Statistics\Chart
),
'LineChart',
'monthly',
array( 'start' => ( new \IPS\DateTime )->sub( new \DateInterval('P90D') ), 'end' => new \IPS\DateTime ),
array( 'start' => ( new DateTime )->sub( new DateInterval('P90D') ), 'end' => new DateTime ),
array(),
'solved'
);
$chart->setExtension( $this );
$chart->joins = array( array( 'forums_topics', array( 'comment_class=? and core_solved_index.item_id=forums_topics.tid', 'IPS\forums\Topic\Post' ) ) );
$chart->where = array( array( \IPS\Db::i()->in( 'state', array( 'link', 'merged' ), TRUE ) ), array( \IPS\Db::i()->in( 'approved', array( -2, -3 ), TRUE ) ) );
$chart->title = \IPS\Member::loggedIn()->language()->addToStack( 'stats_topics_title_solved' );
$chart->where = array( array( Db::i()->in( 'state', array( 'link', 'merged' ), TRUE ) ), array( Db::i()->in( 'approved', array( -2, -3 ), TRUE ) ) );
$chart->where[] = array( 'type=?', 'solved' );
$chart->title = Member::loggedIn()->language()->addToStack( 'stats_topics_title_solved' );
$chart->availableTypes = array( 'LineChart', 'ColumnChart' );
$chart->groupBy = 'forum_id';
@@ -81,17 +97,17 @@ class _SolvedByForum extends \IPS\core\Statistics\Chart
$chart->customFiltersForm = array(
'form' => array(
new \IPS\Helpers\Form\Node( 'chart_forums', $customValues, FALSE, array( 'class' => 'IPS\forums\Forum', 'zeroVal' => 'any', 'multiple' => TRUE, 'permissionCheck' => function ( $forum )
new Node( 'chart_forums', $customValues, FALSE, array( 'class' => 'IPS\forums\Forum', 'zeroVal' => 'any', 'multiple' => TRUE, 'permissionCheck' => function ( $forum )
{
return $forum->sub_can_post and !$forum->redirect_url;
} ), NULL, NULL, NULL, 'chart_forums' )
),
'where' => function( $values )
{
$forumIds = \is_array( $values['chart_forums'] ) ? array_keys( $values['chart_forums'] ) : explode( ',', $values['chart_forums'] );
$forumIds = is_array( $values['chart_forums'] ) ? array_keys( $values['chart_forums'] ) : explode( ',', $values['chart_forums'] );
if ( count( $forumIds ) )
{
return \IPS\Db::i()->in( 'forum_id', $forumIds );
return Db::i()->in( 'forum_id', $forumIds );
}
else
{
@@ -102,19 +118,19 @@ class _SolvedByForum extends \IPS\core\Statistics\Chart
'series' => function( $values )
{
$series = array();
$forumIds = array_filter( \is_array( $values['chart_forums'] ) ? array_keys( $values['chart_forums'] ) : explode( ',', $values['chart_forums'] ) );
$forumIds = array_filter( is_array( $values['chart_forums'] ) ? array_keys( $values['chart_forums'] ) : explode( ',', $values['chart_forums'] ) );
if ( count( $forumIds ) )
{
foreach( $forumIds as $id )
{
$series[] = array( \IPS\Member::loggedIn()->language()->addToStack( 'forums_forum_' . $id ), 'number', 'COUNT(*)', FALSE, $id );
$series[] = array( Member::loggedIn()->language()->addToStack( 'forums_forum_' . $id ), 'number', 'COUNT(*)', FALSE, $id );
}
}
else
{
foreach( \IPS\Db::i()->select( '*', 'forums_forums', array( 'topics>? and ( forums_bitoptions & ? or forums_bitoptions & ? or forums_bitoptions & ? )', 0, 4, 8, 16 ), 'last_post desc', array( 0, 50 ) ) as $forum )
foreach( Db::i()->select( '*', 'forums_forums', array( 'topics>? and ( forums_bitoptions & ? or forums_bitoptions & ? or forums_bitoptions & ? )', 0, 4, 8, 16 ), 'topics desc', array( 0, 50 ) ) as $forum )
{
$series[] = array( \IPS\Member::loggedIn()->language()->addToStack( 'forums_forum_' . $forum['id'] ), 'number', 'COUNT(*)', FALSE, $forum['id'] );
$series[] = array( Member::loggedIn()->language()->addToStack( 'forums_forum_' . $forum['id'] ), 'number', 'COUNT(*)', FALSE, $forum['id'] );
}
}
return $series;
@@ -122,9 +138,9 @@ class _SolvedByForum extends \IPS\core\Statistics\Chart
'defaultSeries' => function()
{
$series = array();
foreach( \IPS\Db::i()->select( '*', 'forums_forums', array( 'topics>? and ( forums_bitoptions & ? or forums_bitoptions & ? or forums_bitoptions & ? )', 0, 4, 8, 16 ), 'last_post desc', array( 0, 50 ) ) as $forum )
foreach( Db::i()->select( '*', 'forums_forums', array( 'topics>? and ( forums_bitoptions & ? or forums_bitoptions & ? or forums_bitoptions & ? )', 0, 4, 8, 16 ), 'topics desc', array( 0, 50 ) ) as $forum )
{
$series[] = array( \IPS\Member::loggedIn()->language()->addToStack( 'forums_forum_' . $forum['id'] ), 'number', 'COUNT(*)', FALSE, $forum['id'] );
$series[] = array( Member::loggedIn()->language()->addToStack( 'forums_forum_' . $forum['id'] ), 'number', 'COUNT(*)', FALSE, $forum['id'] );
}
return $series;
@@ -139,17 +155,17 @@ class _SolvedByForum extends \IPS\core\Statistics\Chart
*
* @return array
*/
protected function getValidForumIds()
protected function getValidForumIds() : array
{
$validForumIds = [];
foreach( \IPS\Db::i()->select( 'value_1', 'core_statistics', [ 'type=?', 'solved' ], NULL, NULL, 'value_1' ) as $forumId )
foreach( Db::i()->select( 'value_1', 'core_statistics', [ 'type=?', 'solved' ], NULL, NULL, 'value_1' ) as $forumId )
{
try
{
$validForumIds[ $forumId ] = \IPS\forums\Forum::load( $forumId );
$validForumIds[ $forumId ] = Forum::load( $forumId );
}
catch( \Exception $e ) { }
catch( Exception $e ) { }
}
return $validForumIds;
@@ -14,42 +14,41 @@ use IPS\core\Statistics\Chart as ParentClass;
use IPS\Db;
use IPS\Helpers\Chart;
use IPS\Helpers\Chart\Database as DbChart;
use IPS\Helpers\Form\Select;
use IPS\Http\Url;
use IPS\DateTime as IPSDateTime;
use IPS\Member;
use IPS\Helpers\Form\Select;
use IPS\Member\Group;
use UnderflowException;
use DateInterval;
use Exception;
use function defined;
use function header;
use function is_array;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Statistics Chart Extension
*/
class _SolvedByGroup extends ParentClass
class SolvedByGroup extends ParentClass
{
/**
* @brief Controller
*/
public $controller = 'forums_stats_solved_groups';
public ?string $controller = 'forums_stats_solved_groups';
/**
* Render Chart
*
* @param \IPS\Http\Url $url URL the chart is being shown on.
* @return \IPS\Helpers\Chart
* @param Url $url URL the chart is being shown on.
* @return Chart
*/
public function getChart( Url $url ): Chart
{
@@ -59,20 +58,20 @@ class _SolvedByGroup extends ParentClass
/* We can't retrieve any stats prior to the new tracking being implemented */
try
{
$oldestLog = \IPS\Db::i()->select( 'MIN(time)', 'core_statistics', array( 'type=?', 'solved' ) )->first();
$oldestLog = Db::i()->select( 'MIN(time)', 'core_statistics', array( 'type=?', 'solved' ) )->first();
if( !$minimumDate OR $oldestLog < $minimumDate->getTimestamp() )
{
$minimumDate = \IPS\DateTime::ts( $oldestLog );
$minimumDate = IPSDateTime::ts( $oldestLog );
}
}
catch( \UnderflowException $e )
catch( UnderflowException $e )
{
/* We have nothing tracked, set minimum date to today */
$minimumDate = \IPS\DateTime::create();
$minimumDate = IPSDateTime::create();
}
$chart = new \IPS\Helpers\Chart\Database( $url, 'core_solved_index', 'solved_date', '', array(
$chart = new DbChart( $url, 'core_solved_index', 'solved_date', '', array(
'isStacked' => FALSE,
'backgroundColor' => '#ffffff',
'hAxis' => array( 'gridlines' => array( 'color' => '#f5f5f5' ) ),
@@ -83,7 +82,7 @@ class _SolvedByGroup extends ParentClass
),
'LineChart',
'monthly',
array( 'start' => ( new \IPS\DateTime )->sub( new \DateInterval('P90D') ), 'end' => new \IPS\DateTime ),
array( 'start' => ( new IPSDateTime )->sub( new DateInterval('P90D') ), 'end' => new IPSDateTime ),
array(),
'solved'
);
@@ -91,7 +90,7 @@ class _SolvedByGroup extends ParentClass
$chart->joins = array( array( 'forums_posts', array( 'comment_class=? and core_solved_index.comment_id=forums_posts.pid', 'IPS\forums\Topic\Post' ) ), array( 'core_members', array( "core_members.member_id=forums_posts.author_id" ) ) );
$chart->where = array( array( 'queued=?', 0 ) );
$chart->title = \IPS\Member::loggedIn()->language()->addToStack( 'stats_topics_tab_groups' );
$chart->title = Member::loggedIn()->language()->addToStack( 'stats_topics_tab_groups' );
$chart->availableTypes = array( 'LineChart', 'ColumnChart' );
$chart->groupBy = 'member_group_id';
@@ -99,14 +98,14 @@ class _SolvedByGroup extends ParentClass
$chart->customFiltersForm = array(
'form' => array(
new \IPS\Helpers\Form\Select( 'chart_groups', $customValues, FALSE, array( 'options' => $this->_getGroups(), 'zeroVal' => 'any', 'multiple' => TRUE ), NULL, NULL, NULL, 'chart_groups' )
new Select( 'chart_groups', $customValues, FALSE, array( 'options' => $this->_getGroups(), 'zeroVal' => 'any', 'multiple' => TRUE ), NULL, NULL, NULL, 'chart_groups' )
),
'where' => function( $values )
{
$groups = \is_array( $values['chart_groups'] ) ? array_values( $values['chart_groups'] ) : explode( ',', $values['chart_groups'] );
$groups = is_array( $values['chart_groups'] ) ? array_values( $values['chart_groups'] ) : explode( ',', $values['chart_groups'] );
if ( count( $groups ) )
{
return \IPS\Db::i()->in( 'member_group_id', $groups );
return Db::i()->in( 'member_group_id', $groups );
}
else
{
@@ -117,19 +116,19 @@ class _SolvedByGroup extends ParentClass
'series' => function( $values )
{
$series = array();
$groups = array_filter( \is_array( $values['chart_groups'] ) ? array_values( $values['chart_groups'] ) : explode( ',', $values['chart_groups'] ) );
$groups = array_filter( is_array( $values['chart_groups'] ) ? array_values( $values['chart_groups'] ) : explode( ',', $values['chart_groups'] ) );
if ( count( $groups ) )
{
foreach( $groups as $id )
{
$series[] = array( \IPS\Member::loggedIn()->language()->addToStack( 'core_group_' . $id ), 'number', 'COUNT(*)', FALSE, $id );
$series[] = array( Member::loggedIn()->language()->addToStack( 'core_group_' . $id ), 'number', 'COUNT(*)', FALSE, $id );
}
}
else
{
foreach( $this->_getGroups() AS $id => $group )
{
$series[] = array( \IPS\Member::loggedIn()->language()->addToStack( 'core_group_' . $id ), 'number', 'COUNT(*)', FALSE, $id );
$series[] = array( Member::loggedIn()->language()->addToStack( 'core_group_' . $id ), 'number', 'COUNT(*)', FALSE, $id );
}
}
return $series;
@@ -139,7 +138,7 @@ class _SolvedByGroup extends ParentClass
$series = array();
foreach( $this->_getGroups() AS $id => $group )
{
$series[] = array( \IPS\Member::loggedIn()->language()->addToStack( 'core_group_' . $id ), 'number', 'COUNT(*)', FALSE, $id );
$series[] = array( Member::loggedIn()->language()->addToStack( 'core_group_' . $id ), 'number', 'COUNT(*)', FALSE, $id );
}
return $series;
}
@@ -156,7 +155,7 @@ class _SolvedByGroup extends ParentClass
protected function _getGroups(): array
{
$return = array();
foreach( \IPS\Member\Group::groups( TRUE, FALSE ) AS $group )
foreach( Group::groups( TRUE, FALSE ) AS $group )
{
$return[ $group->g_id ] = $group->name;
}
@@ -11,29 +11,41 @@
namespace IPS\forums\extensions\core\Statistics;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use Exception;
use IPS\DateTime;
use IPS\Db;
use IPS\forums\Forum;
use IPS\Helpers\Chart;
use IPS\Helpers\Chart\Database;
use IPS\Http\Url;
use IPS\Member;
use UnderflowException;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Statistics Chart Extension
*/
class _TimeSolved extends \IPS\core\Statistics\Chart
class TimeSolved extends \IPS\core\Statistics\Chart
{
/**
* @brief Controller
*/
public $controller = 'forums_stats_solved_time';
public ?string $controller = 'forums_stats_solved_time';
/**
* Render Chart
*
* @param \IPS\Http\Url $url URL the chart is being shown on.
* @return \IPS\Helpers\Chart
* @param Url $url URL the chart is being shown on.
* @return Chart
*/
public function getChart( \IPS\Http\Url $url ): \IPS\Helpers\Chart
public function getChart( Url $url ): Chart
{
/* Determine minimum date */
$minimumDate = NULL;
@@ -41,20 +53,20 @@ class _TimeSolved extends \IPS\core\Statistics\Chart
/* We can't retrieve any stats prior to the new tracking being implemented */
try
{
$oldestLog = \IPS\Db::i()->select( 'MIN(time)', 'core_statistics', array( 'type=?', 'solved' ) )->first();
$oldestLog = Db::i()->select( 'MIN(time)', 'core_statistics', array( 'type=?', 'solved' ) )->first();
if( !$minimumDate OR $oldestLog < $minimumDate->getTimestamp() )
if( $oldestLog !== null and ( !$minimumDate OR $oldestLog < $minimumDate->getTimestamp() ) )
{
$minimumDate = \IPS\DateTime::ts( $oldestLog );
$minimumDate = DateTime::ts( $oldestLog );
}
}
catch( \UnderflowException $e )
catch( UnderflowException $e )
{
/* We have nothing tracked, set minimum date to today */
$minimumDate = \IPS\DateTime::create();
$minimumDate = DateTime::create();
}
$chart = new \IPS\Helpers\Chart\Database( $url, 'core_statistics', 'time', '', array(
$chart = new Database( $url, 'core_statistics', 'time', '', array(
'isStacked' => FALSE,
'backgroundColor' => '#ffffff',
'hAxis' => array('gridlines' => array('color' => '#f5f5f5')),
@@ -68,11 +80,11 @@ class _TimeSolved extends \IPS\core\Statistics\Chart
);
$chart->setExtension( $this );
$chart->description = \IPS\Member::loggedIn()->language()->addToStack( 'solved_stats_chart_desc' );
$chart->description = Member::loggedIn()->language()->addToStack( 'solved_stats_chart_desc' );
$chart->availableTypes = array('AreaChart', 'ColumnChart', 'BarChart');
$chart->enableHourly = FALSE;
$chart->groupBy = 'value_1';
$chart->title = \IPS\Member::loggedIn()->language()->addToStack('forums_solved_stats_time');
$chart->title = Member::loggedIn()->language()->addToStack('forums_solved_stats_time');
$chart->where[] = array( "type=?", 'solved' );
foreach( $validForumIds = $this->getValidForumIds() as $forumId => $forum )
@@ -88,18 +100,18 @@ class _TimeSolved extends \IPS\core\Statistics\Chart
*
* @return array
*/
protected function getValidForumIds()
protected function getValidForumIds() : array
{
$validForumIds = [];
foreach( \IPS\Db::i()->select( 'value_1', 'core_statistics', [ 'value_4 IS NOT NULL and type=?', 'solved' ], NULL, [ 0,100 ], 'value_1' ) as $forumId )
foreach( Db::i()->select( 'value_1', 'core_statistics', [ 'value_4 IS NOT NULL and type=?', 'solved' ], NULL, [ 0,100 ], 'value_1' ) as $forumId )
{
try
{
/* @var $forumId int */
$validForumIds[ $forumId ] = \IPS\forums\Forum::load( $forumId );
$validForumIds[ $forumId ] = Forum::load( $forumId );
}
catch( \Exception $e ) { }
catch( Exception $e ) { }
}
return $validForumIds;
@@ -11,31 +11,39 @@
namespace IPS\forums\extensions\core\Statistics;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use IPS\Db;
use IPS\Helpers\Chart;
use IPS\Helpers\Chart\Database;
use IPS\Http\Url;
use IPS\Member;
use function defined;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Statistics Chart Extension
*/
class _Topics extends \IPS\core\Statistics\Chart
class Topics extends \IPS\core\Statistics\Chart
{
/**
* @brief Controller
*/
public $controller = 'forums_stats_topics_total';
public ?string $controller = 'forums_stats_topics_total';
/**
* Render Chart
*
* @param \IPS\Http\Url $url URL the chart is being shown on.
* @return \IPS\Helpers\Chart
* @param Url $url URL the chart is being shown on.
* @return Chart
*/
public function getChart( \IPS\Http\Url $url ): \IPS\Helpers\Chart
public function getChart( Url $url ): Chart
{
$chart = new \IPS\Helpers\Chart\Database( $url, 'forums_topics', 'start_date', '', array(
$chart = new Database( $url, 'forums_topics', 'start_date', '', array(
'isStacked' => FALSE,
'backgroundColor' => '#ffffff',
'colors' => array( '#10967e' ),
@@ -47,9 +55,9 @@ class _Topics extends \IPS\core\Statistics\Chart
);
$chart->setExtension( $this );
$chart->where = array( array( \IPS\Db::i()->in( 'state', array( 'link', 'merged' ), TRUE ) ), array( \IPS\Db::i()->in( 'approved', array( -2, -3 ), TRUE ) ) );
$chart->addSeries( \IPS\Member::loggedIn()->language()->addToStack( 'stats_new_topics' ), 'number', 'COUNT(*)', FALSE );
$chart->title = \IPS\Member::loggedIn()->language()->addToStack( 'stats_topics_title' );
$chart->where = array( array( Db::i()->in( 'state', array( 'link', 'merged' ), TRUE ) ), array( Db::i()->in( 'approved', array( -2, -3 ), TRUE ) ) );
$chart->addSeries( Member::loggedIn()->language()->addToStack( 'stats_new_topics' ), 'number', 'COUNT(*)', FALSE );
$chart->title = Member::loggedIn()->language()->addToStack( 'stats_topics_title' );
$chart->availableTypes = array( 'AreaChart', 'ColumnChart', 'BarChart' );
return $chart;
@@ -11,31 +11,43 @@
namespace IPS\forums\extensions\core\Statistics;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
use DateInterval;
use IPS\DateTime;
use IPS\Db;
use IPS\Helpers\Chart;
use IPS\Helpers\Chart\Database;
use IPS\Helpers\Form\Node;
use IPS\Http\Url;
use IPS\Member;
use function defined;
use function is_array;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Statistics Chart Extension
*/
class _TopicsByForum extends \IPS\core\Statistics\Chart
class TopicsByForum extends \IPS\core\Statistics\Chart
{
/**
* @brief Controller
*/
public $controller = 'forums_stats_topics_byforum';
public ?string $controller = 'forums_stats_topics_byforum';
/**
* Render Chart
*
* @param \IPS\Http\Url $url URL the chart is being shown on.
* @return \IPS\Helpers\Chart
* @param Url $url URL the chart is being shown on.
* @return Chart
*/
public function getChart( \IPS\Http\Url $url ): \IPS\Helpers\Chart
public function getChart( Url $url ): Chart
{
$chart = new \IPS\Helpers\Chart\Database( $url, 'forums_topics', 'start_date', '', array(
$chart = new Database( $url, 'forums_topics', 'start_date', '', array(
'isStacked' => FALSE,
'backgroundColor' => '#ffffff',
'hAxis' => array( 'gridlines' => array( 'color' => '#f5f5f5' ) ),
@@ -46,14 +58,14 @@ class _TopicsByForum extends \IPS\core\Statistics\Chart
),
'ColumnChart',
'monthly',
array( 'start' => ( new \IPS\DateTime )->sub( new \DateInterval('P90D') ), 'end' => new \IPS\DateTime ),
array( 'start' => ( new DateTime )->sub( new DateInterval('P90D') ), 'end' => new DateTime ),
array(),
'byforum'
);
$chart->setExtension( $this );
$chart->where = array( array( \IPS\Db::i()->in( 'state', array( 'link', 'merged' ), TRUE ) ), array( \IPS\Db::i()->in( 'approved', array( -2, -3 ), TRUE ) ) );
$chart->title = \IPS\Member::loggedIn()->language()->addToStack( 'stats_topics_title_byforum' );
$chart->where = array( array( Db::i()->in( 'state', array( 'link', 'merged' ), TRUE ) ), array( Db::i()->in( 'approved', array( -2, -3 ), TRUE ) ) );
$chart->title = Member::loggedIn()->language()->addToStack( 'stats_topics_title_byforum' );
$chart->availableTypes = array( 'ColumnChart' );
$chart->groupBy = 'forum_id';
@@ -61,33 +73,33 @@ class _TopicsByForum extends \IPS\core\Statistics\Chart
$chart->customFiltersForm = array(
'form' => array(
new \IPS\Helpers\Form\Node( 'chart_forums', $customValues, FALSE, array( 'class' => 'IPS\forums\Forum', 'zeroVal' => 'any', 'multiple' => TRUE, 'permissionCheck' => function ( $forum )
new Node( 'chart_forums', $customValues, FALSE, array( 'class' => 'IPS\forums\Forum', 'zeroVal' => 'any', 'multiple' => TRUE, 'permissionCheck' => function ( $forum )
{
return $forum->sub_can_post and !$forum->redirect_url;
} ), NULL, NULL, NULL, 'chart_forums' )
),
'where' => function( $values )
{
$forumIds = \is_array( $values['chart_forums'] ) ? array_keys( $values['chart_forums'] ) : explode( ',', $values['chart_forums'] );
return \IPS\Db::i()->in( 'forum_id', $forumIds );
$forumIds = is_array( $values['chart_forums'] ) ? array_keys( $values['chart_forums'] ) : explode( ',', $values['chart_forums'] );
return Db::i()->in( 'forum_id', $forumIds );
},
'groupBy' => 'forum_id',
'series' => function( $values )
{
$series = array();
$forumIds = \is_array( $values['chart_forums'] ) ? array_keys( $values['chart_forums'] ) : explode( ',', $values['chart_forums'] );
$forumIds = is_array( $values['chart_forums'] ) ? array_keys( $values['chart_forums'] ) : explode( ',', $values['chart_forums'] );
foreach( $forumIds as $id )
{
$series[] = array( \IPS\Member::loggedIn()->language()->addToStack( 'forums_forum_' . $id ), 'number', 'COUNT(*)', FALSE, $id );
$series[] = array( Member::loggedIn()->language()->addToStack( 'forums_forum_' . $id ), 'number', 'COUNT(*)', FALSE, $id );
}
return $series;
},
'defaultSeries' => function()
{
$series = array();
foreach( \IPS\Db::i()->select( '*', 'forums_forums', array( 'topics>? AND club_id IS NULL', 0 ), 'last_post desc', array( 0, 50 ) ) as $forum )
foreach( Db::i()->select( '*', 'forums_forums', array( 'topics>? AND club_id IS NULL', 0 ), 'topics desc', array( 0, 50 ) ) as $forum )
{
$series[] = array( \IPS\Member::loggedIn()->language()->addToStack( 'forums_forum_' . $forum['id'] ), 'number', 'COUNT(*)', FALSE, $forum['id'] );
$series[] = array( Member::loggedIn()->language()->addToStack( 'forums_forum_' . $forum['id'] ), 'number', 'COUNT(*)', FALSE, $forum['id'] );
}
return $series;