Version 4.6.0

This commit is contained in:
Neo committed 2025-12-19 05:55:31 -08:00
1 parent f79dcf067a
commit 517a5e1f70
2036 files changed
+110041 -26162

No files matched your search

@@ -60,6 +60,24 @@ class _directory extends \IPS\Dispatcher\Controller
protected function manage()
{
$baseUrl = \IPS\Http\Url::internal( 'app=core&module=clubs&controller=directory', 'front', 'clubs_list' );
/* Display */
$view = \IPS\Settings::i()->clubs_default_view;
if ( \IPS\Settings::i()->clubs_allow_view_change )
{
if ( isset( \IPS\Request::i()->view ) and \in_array( \IPS\Request::i()->view, array( 'grid', 'list' ) ) )
{
\IPS\Session::i()->csrfCheck();
\IPS\Request::i()->setCookie( 'clubs_view', \IPS\Request::i()->view, \IPS\DateTime::create()->add( new \DateInterval('P1Y' ) ) );
$view = \IPS\Request::i()->view;
\IPS\Output::i()->redirect( $baseUrl );
}
elseif ( isset( \IPS\Request::i()->cookie['clubs_view'] ) and \in_array( \IPS\Request::i()->cookie['clubs_view'], array( 'grid', 'list' ) ) )
{
$view = \IPS\Request::i()->cookie['clubs_view'];
}
}
/* All Clubs: Sort */
$sortOption = \IPS\Settings::i()->clubs_default_sort;
@@ -74,7 +92,7 @@ class _directory extends \IPS\Dispatcher\Controller
$filters = array();
$mineOnly = FALSE;
$extraWhere = NULL;
if ( isset( \IPS\Request::i()->filter ) and \IPS\Request::i()->filter === 'mine' )
if ( isset( \IPS\Request::i()->filter ) and \IPS\Request::i()->filter === 'mine' AND \IPS\Member::loggedIn()->member_id )
{
$mineOnly = TRUE;
$baseUrl = $baseUrl->setQueryString( 'filter', 'mine' );
@@ -216,22 +234,7 @@ class _directory extends \IPS\Dispatcher\Controller
}
}
}
/* Display */
$view = \IPS\Settings::i()->clubs_default_view;
if ( \IPS\Settings::i()->clubs_allow_view_change )
{
if ( isset( \IPS\Request::i()->view ) and \in_array( \IPS\Request::i()->view, array( 'grid', 'list' ) ) )
{
\IPS\Session::i()->csrfCheck();
\IPS\Request::i()->setCookie( 'clubs_view', \IPS\Request::i()->view, \IPS\DateTime::create()->add( new \DateInterval('P1Y' ) ) );
$view = \IPS\Request::i()->view;
}
elseif ( isset( \IPS\Request::i()->cookie['clubs_view'] ) and \in_array( \IPS\Request::i()->cookie['clubs_view'], array( 'grid', 'list' ) ) )
{
$view = \IPS\Request::i()->cookie['clubs_view'];
}
}
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('module__core_clubs');
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate('clubs')->directory( $featuredClubs, $allClubs, $pagination, $baseUrl, $sortOption, $myClubsActivity, $mapMarkers, $view );
}
@@ -391,6 +394,11 @@ class _directory extends \IPS\Dispatcher\Controller
$club->save();
}
if( $club->approved )
{
\IPS\Member::loggedIn()->achievementAction( 'core', 'NewClub', $club );
}
\IPS\Output::i()->redirect( $club->url() );
}
@@ -41,7 +41,7 @@ class _page extends \IPS\Dispatcher\Controller
}
catch( \OutOfRangeException $e )
{
\IPS\Output::i()->error( 'node_error', '12345', 404 );
\IPS\Output::i()->error( 'node_error', '2S410/4', 404, '' );
}
\IPS\Output::i()->sidebar['contextual'] = '';
+32 -9
View File
@@ -527,8 +527,10 @@ class _view extends \IPS\Helpers\CoverPhoto\Controller
/* Fetch them */
$members = $this->club->members( $statuses, array( $offset, $perPage ), $orderByClause, $this->club->isLeader() ? 5 : 1 );
$pagination = \IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->pagination( $baseUrl, ceil( $members->count(TRUE) / $perPage ), $activePage, $perPage );
$pagination = \IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->pagination( $baseUrl, ceil( $this->club->members( $statuses, NULL, $orderByClause, 4 ) / $perPage ), $activePage, $perPage );
/* Display */
if ( \IPS\Request::i()->isAjax() )
{
@@ -1112,9 +1114,17 @@ class _view extends \IPS\Helpers\CoverPhoto\Controller
if ( $values = $form->values() )
{
/* If we're not a member of this club yet, send us back to the join form */
if( $this->club->memberStatus( \IPS\Member::loggedIn() ) === NULL AND $referrer = \IPS\Request::i()->referrer() )
if( $this->club->memberStatus( \IPS\Member::loggedIn() ) === NULL )
{
\IPS\Output::i()->redirect( $referrer->setQueryString( 'rulesAcknowledged', 1 ), 'accepted' );
if( $referrer = \IPS\Request::i()->referrer() )
{
\IPS\Output::i()->redirect( $referrer->setQueryString( 'rulesAcknowledged', 1 ), 'accepted' );
}
else
{
\IPS\Output::i()->redirect( $this->club->url()->setQueryString( 'rulesAcknowledged', 1 ), 'accepted' );
}
}
$this->club->acknowledgeRules( \IPS\Member::loggedIn() );
@@ -1150,10 +1160,16 @@ class _view extends \IPS\Helpers\CoverPhoto\Controller
}
/* Are there rules that need to be acknowledged which we haven't acknowledged yet? */
if( $this->club->rules_required AND !\IPS\Request::i()->rulesAcknowledged )
if( $this->club->rules_required AND !$this->club->rulesAcknowledged() AND !\IPS\Request::i()->rulesAcknowledged )
{
$ref = \IPS\Request::i()->url()->addRef( \IPS\Request::i()->referrer() );
\IPS\Output::i()->redirect( $this->club->url()->setQueryString( 'do', 'rules' )->addRef( $ref ) );
$rulesUrl = $this->club->url()->setQueryString( 'do', 'rules' );
if( $referrer = \IPS\Request::i()->referrer() )
{
$rulesUrl = $rulesUrl->addRef( \IPS\Request::i()->url()->addRef( $referrer ) );
}
\IPS\Output::i()->redirect( $rulesUrl );
}
/* CSRF Check */
@@ -1484,6 +1500,8 @@ class _view extends \IPS\Helpers\CoverPhoto\Controller
$this->club->approved = TRUE;
$this->club->save();
$this->club->owner->achievementAction( 'core', 'NewClub', $this->club );
\IPS\Output::i()->redirect( $this->club->url() );
}
@@ -1515,7 +1533,7 @@ class _view extends \IPS\Helpers\CoverPhoto\Controller
{
try
{
$node = $class::load( \IPS\Request::i()->node );
$node = $class::load( (int) \IPS\Request::i()->node );
$club = $node->club();
if ( !$club or $club->id !== $this->club->id )
{
@@ -1566,9 +1584,14 @@ class _view extends \IPS\Helpers\CoverPhoto\Controller
/* Load */
$class = \IPS\Request::i()->type;
if( !$class instanceof \IPS\Node\Model )
{
throw new \Exception;
}
try
{
$node = $class::load( \IPS\Request::i()->node );
$node = $class::load( (int) \IPS\Request::i()->node );
$club = $node->club();
if ( !$club or $club->id !== $this->club->id )
{
@@ -168,6 +168,7 @@ class _popular extends \IPS\Dispatcher\Controller
}
);
\IPS\Output::i()->linkTags['canonical'] = (string) \IPS\Http\Url::internal( "app=core&module=discover&controller=popular&tab=history", 'front', 'leaderboard_history' );
if ( \IPS\Request::i()->isAjax() )
{
\IPS\Output::i()->sendOutput( \IPS\Theme::i()->getTemplate( 'global', 'core' )->blankTemplate( $table ), 200, 'text/html' );
@@ -385,15 +385,20 @@ class _streams extends \IPS\Dispatcher\Controller
$view = 'expanded';
$streamID = ( \IPS\Request::i()->id ) ? \IPS\Request::i()->id : 'all';
if ( isset( \IPS\Request::i()->cookie['stream_view_' . $streamID] ) )
{
$view = \IPS\Request::i()->cookie['stream_view_' . $streamID];
}
elseif( isset( \IPS\Request::i()->view ) AND \IPS\Login::compareHashes( (string) \IPS\Session::i()->csrfKey, (string) \IPS\Request::i()->csrfKey ) )
if( isset( \IPS\Request::i()->view ) AND \IPS\Login::compareHashes( (string) \IPS\Session::i()->csrfKey, (string) \IPS\Request::i()->csrfKey ) )
{
$view = \IPS\Request::i()->view;
\IPS\Request::i()->setCookie( 'stream_view_' . $streamID, \IPS\Request::i()->view, \IPS\DateTime::create()->add( new \DateInterval( 'P1Y' ) ) );
if( !\IPS\Request::i()->isAjax() )
{
\IPS\Output::i()->redirect( $stream->url() );
}
}
elseif ( isset( \IPS\Request::i()->cookie['stream_view_' . $streamID] ) )
{
$view = \IPS\Request::i()->cookie['stream_view_' . $streamID];
}
else
{
@@ -516,7 +521,7 @@ class _streams extends \IPS\Dispatcher\Controller
$form = new \IPS\Helpers\Form;
$form->class = 'ipsForm_vertical';
$form->add( new \IPS\Helpers\Form\Text( 'stream_title', $stream->title ) );
$form->add( new \IPS\Helpers\Form\Text( 'stream_title', $stream->title, NULL, array( 'maxLength' => 255 ) ) );
if ( $values = $form->values() )
{
@@ -54,7 +54,7 @@ class _profile extends \IPS\Helpers\CoverPhoto\Controller
\IPS\Output::i()->metaTags['robots'] = 'noindex, follow';
}
\IPS\Output::i()->linkTags['canonical'] = (string)$this->member->url();
\IPS\Output::i()->linkTags['canonical'] = (string) $this->member->url();
\IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js( 'front_statuses.js', 'core' ) );
\IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js( 'global_core.js', 'core', 'global' ) );
@@ -114,7 +114,7 @@ class _profile extends \IPS\Helpers\CoverPhoto\Controller
$canAccessStatuses = $canAccessSingleStatuses and ( $this->member->pp_setting_count_comments or \IPS\Member::loggedIn()->member_id == $this->member->member_id );
/* Are we loading a different page of comments? */
if ( \IPS\Request::i()->status && \IPS\Request::i()->isAjax() && \IPS\Request::i()->page && $canAccessStatuses && !isset( \IPS\Request::i()->getUploader ) )
if ( \IPS\Request::i()->status && \IPS\Request::i()->isAjax() && \IPS\Request::i()->commentPage && $canAccessStatuses && !isset( \IPS\Request::i()->getUploader ) )
{
try
{
@@ -378,6 +378,9 @@ class _profile extends \IPS\Helpers\CoverPhoto\Controller
/* Get followers */
$followers = $this->member->followers( ( \IPS\Member::loggedIn()->isAdmin() OR \IPS\Member::loggedIn()->member_id === $this->member->member_id ) ? \IPS\Member::FOLLOW_PUBLIC + \IPS\Member::FOLLOW_ANONYMOUS : \IPS\Member::FOLLOW_PUBLIC, array( 'immediate', 'daily', 'weekly', 'none' ), NULL, array( 0, 12 ) );
/* Get solutions */
$solutions = \IPS\Db::i()->select( 'COUNT(*)', 'core_solved_index', array( 'member_id=?', $this->member->member_id ) )->first();
/* Update online location */
$module = \IPS\Application\Module::get( 'core', 'members', 'front' )->permissions();
@@ -398,15 +401,15 @@ class _profile extends \IPS\Helpers\CoverPhoto\Controller
'name' => $this->member->name,
'primaryImageOfPage' => array(
'@type' => "ImageObject",
'contentUrl' => (string) $this->member->get_photo(),
'contentUrl' => (string) $this->member->get_photo( TRUE, TRUE ),
'representativeOfPage' => true,
'thumbnail' => array(
'@type' => "ImageObject",
'contentUrl' => (string) $this->member->get_photo( TRUE ),
'contentUrl' => (string) $this->member->get_photo( TRUE, TRUE ),
)
),
'thumbnailUrl' => (string) $this->member->get_photo( TRUE ),
'image' => (string) $this->member->get_photo(),
'thumbnailUrl' => (string) $this->member->get_photo( TRUE, TRUE ),
'image' => (string) $this->member->get_photo( FALSE, TRUE ),
'relatedLink' => (string) \IPS\Http\Url::internal( "app=core&module=members&controller=profile&do=content&id={$this->member->member_id}", "front", "profile_content", array( $this->member->members_seo_name ) ),
'dateCreated' => $this->member->joined->format( \IPS\DateTime::ISO8601 ),
'interactionStatistic' => array(
@@ -425,7 +428,7 @@ class _profile extends \IPS\Helpers\CoverPhoto\Controller
/* Output */
\IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js( 'front_profile.js', 'core' ) );
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'profile' )->profile( $this->member, $mainContent, $visitors, $sidebarFields, $followers, $addWarningUrl );
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'profile' )->profile( $this->member, $mainContent, $visitors, $sidebarFields, $followers, $addWarningUrl, $solutions );
}
/**
@@ -620,6 +623,33 @@ class _profile extends \IPS\Helpers\CoverPhoto\Controller
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'profile' )->userContent( $this->member, $types, $currentAppModule, $currentType, (string) $output );
}
}
/**
* Show badges
*
* @return void
*/
public function badges()
{
if( !$this->member->canHaveAchievements() || !\IPS\core\Achievements\Badge::show() )
{
\IPS\Output::i()->error( 'no_module_permission', '2C138/S', 403, '' );
}
$percentage = NULL;
if ( $this->member->achievements_points )
{
$percentage = \IPS\Db::i()->select( "CEIL( 100 * COUNT( IF( achievements_points > " . ( $this->member->achievements_points - 1 ) . ", 1, NULL ) ) / COUNT(*) ) as percentage", 'core_members', ['achievements_points > 0'] )->first();
if ( $percentage > 51 )
{
$percentage = NULL;
}
}
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack( 'members_badges', FALSE, array( 'sprintf' => array( $this->member->name ) ) );;
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'profile' )->userBadgeOverview( $this->member, $percentage );
}
/**
* Show Reputation
@@ -992,20 +1022,14 @@ class _profile extends \IPS\Helpers\CoverPhoto\Controller
/* The basics */
$form->addTab( 'profile_edit_basic_tab', 'user');
$canChangeTitle = ( \IPS\Settings::i()->post_titlechange != -1 and ( isset( \IPS\Settings::i()->post_titlechange ) and $this->member->member_posts >= \IPS\Settings::i()->post_titlechange ) );
$canChangeBirthday = ( \IPS\Settings::i()->profile_birthday_type !== 'none' );
$canEnableDisableStatuses = ( \IPS\Settings::i()->profile_comments and $this->member->canAccessModule( \IPS\Application\Module::get( 'core', 'status' ) ) AND !$this->member->members_bitoptions['bw_no_status_update'] );
if( $canChangeTitle or $canChangeBirthday or $canEnableDisableStatuses )
if( $canChangeBirthday or $canEnableDisableStatuses )
{
$form->addHeader( 'profile_edit_basic_header' );
}
if( \IPS\Settings::i()->post_titlechange != -1 and ( isset( \IPS\Settings::i()->post_titlechange ) and $this->member->member_posts >= \IPS\Settings::i()->post_titlechange ) )
{
$form->add( new \IPS\Helpers\Form\Text( 'member_title', $this->member->member_title, FALSE, array( 'maxLength' => 64 ) ) );
}
if ( \IPS\Settings::i()->profile_birthday_type !== 'none' )
{
$form->add( new \IPS\Helpers\Form\Custom( 'bday', array( 'year' => $this->member->bday_year, 'month' => $this->member->bday_month, 'day' => $this->member->bday_day ), FALSE, array( 'getHtml' => function( $element )
@@ -1107,11 +1131,6 @@ class _profile extends \IPS\Helpers\CoverPhoto\Controller
*/
protected function _saveMember( $form, array $values )
{
if( ( \IPS\Settings::i()->post_titlechange == -1 or ( isset( \IPS\Settings::i()->post_titlechange ) and $this->member->member_posts >= \IPS\Settings::i()->post_titlechange ) ) AND isset( $values['member_title'] ) )
{
$this->member->member_title = $values['member_title'];
}
if( isset( $values['bday'] ) )
{
if( $values['bday'] and ( ( $values['bday']['day'] and !$values['bday']['month'] ) or ( $values['bday']['month'] and !$values['bday']['day'] ) ) )
@@ -1909,4 +1928,262 @@ class _profile extends \IPS\Helpers\CoverPhoto\Controller
\IPS\Request::i()->action = 'delete';
return $this->moderate();
}
/**
* Show Solutions
*
* @return void
*/
public function solutions()
{
/* Get the different types */
$types = array();
$hasCallback = array();
foreach ( \IPS\Content::routedClasses( TRUE, FALSE, TRUE ) as $class )
{
\IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'profile.css', $class::$application ) );
if ( \IPS\IPS::classUsesTrait( $class, 'IPS\Content\Solvable' ) )
{
$types[ mb_strtolower( str_replace( '\\', '_', mb_substr( $class, 4 ) ) ) ] = $class::$commentClass;
}
}
/* What type are we looking at? */
$currentType = NULL;
if ( isset( \IPS\Request::i()->type ) AND array_key_exists( \IPS\Request::i()->type, $types ) )
{
$currentType = \IPS\Request::i()->type;
}
if ( $currentType === NULL )
{
foreach ( $types as $key => $type )
{
$currentType = $key;
break;
}
}
$currentClass = $types[ $currentType ];
/* Build Output */
$url = \IPS\Http\Url::internal( "app=core&module=members&controller=profile&id={$this->member->member_id}&do=reputation&type={$currentType}", 'front', 'profile_reputation', array( $this->member->members_seo_name ) );
$table = new \IPS\Helpers\Table\Content( $currentClass, $url, NULL, NULL, \IPS\Content\Hideable::FILTER_AUTOMATIC, 'read', FALSE );
$table->joinContainer = TRUE;
$table->sortOptions = array( 'solved_date' );
$table->sortBy = 'solved_date';
$table->joins = array(
array(
'select' => "core_solved_index.id AS solved_id, core_solved_index.solved_date",
'from' => 'core_solved_index',
'where' => array( "core_solved_index.comment_id=" . $currentClass::$databaseTable . "." . $currentClass::$databasePrefix . $currentClass::$databaseColumnId . " AND core_solved_index.member_id=? AND core_solved_index.comment_class=?", $this->member->member_id, $currentClass ),
'type' => 'INNER'
)
);
$table->tableTemplate = array( \IPS\Theme::i()->getTemplate( 'profile', 'core' ), 'userSolutionsTable' );
$table->rowsTemplate = array( \IPS\Theme::i()->getTemplate( 'profile', 'core' ), 'userSolutionsRows' );
$table->showAdvancedSearch = FALSE;
/* Display */
if ( \IPS\Request::i()->isAjax() && \IPS\Request::i()->change_section )
{
\IPS\Output::i()->sendOutput( (string) $table );
}
else
{
\IPS\Output::i()->title = ( $table->page > 1 ) ? \IPS\Member::loggedIn()->language()->addToStack( 'title_with_page_number', FALSE, array( 'sprintf' => array( \IPS\Member::loggedIn()->language()->addToStack( 'member_solutions', FALSE, array('sprintf' => $this->member->name ) ), $table->page ) ) ) : \IPS\Member::loggedIn()->language()->addToStack( 'member_solutions', FALSE, array('sprintf' => $this->member->name ) );
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'profile' )->userSolutions( $this->member, $types, $currentType, (string) $table, $this->member->solutionCount() );
}
}
/**
* Recognize
*
* @return void
*/
protected function recognize()
{
$class = \IPS\Request::i()->content_class;
$id = \IPS\Request::i()->content_id;
/* Does this content exist? */
try
{
$content = $class::loadAndCheckPerms( $id );
/* Can we view this item? */
if ( ! $content->canView() )
{
\IPS\Output::i()->error( 'node_error', '2C138/O', 403, '' );
}
/* Make sure there's no shenanigans */
if ( $this->member->member_id != $content->author()->member_id )
{
\IPS\Output::i()->error( 'node_error', '2C138/P', 403, '' );
}
/* Can we recognize this ? */
if ( ! $content->canRecognize() )
{
\IPS\Output::i()->error( 'node_error', '2C138/Q', 403, '' );
}
/* Now build the form */
$form = new \IPS\Helpers\Form( 'badge_form', 'save' );
$form->class = 'ipsForm_vertical ipsForm_fullWidth';
/* What has been awarded so far today? */
$message = NULL;
if ( $this->member->todaysRecognizePoints() and $this->member->todaysRecognizeBadges() )
{
$message = \IPS\Member::loggedIn()->language()->addToStack('recognize_points_so_far_both', NULL, [
'sprintf' => [
$this->member->name,
\IPS\Member::loggedIn()->language()->addToStack('recognize_points_pluralize', NULL, [ 'sprintf' => [ $this->member->todaysRecognizePoints() ], 'pluralize' => [ $this->member->todaysRecognizePoints() ] ]),
\IPS\Member::loggedIn()->language()->addToStack('recognize_badges_pluralize', NULL, [ 'sprintf' => [ $this->member->todaysRecognizeBadges() ], 'pluralize' => [ $this->member->todaysRecognizeBadges() ] ]),
] ] );
}
else if ( $this->member->todaysRecognizePoints() )
{
$message = \IPS\Member::loggedIn()->language()->addToStack('recognize_points_so_far_single', NULL, [
'sprintf' => [
$this->member->name,
\IPS\Member::loggedIn()->language()->addToStack('recognize_points_pluralize', NULL, [ 'sprintf' => [ $this->member->todaysRecognizePoints() ], 'pluralize' => [ $this->member->todaysRecognizePoints() ] ])
] ] );
}
else if ( $this->member->todaysRecognizeBadges() )
{
$message = \IPS\Member::loggedIn()->language()->addToStack('recognize_points_so_far_single', NULL, [
'sprintf' => [
$this->member->name,
\IPS\Member::loggedIn()->language()->addToStack('recognize_points_pluralize', NULL, [ 'sprintf' => [ $this->member->todaysRecognizeBadges() ], 'pluralize' => [ $this->member->todaysRecognizeBadges() ] ])
] ] );
}
$maxPoints = NULL;
$disabled = FALSE;
if ( \IPS\Settings::i()->achievements_recognize_max_per_user_day != -1 )
{
$maxPoints = \IPS\Settings::i()->achievements_recognize_max_per_user_day - $this->member->todaysRecognizePoints();
if ( $maxPoints > 0 )
{
$message .= \IPS\Member::loggedIn()->language()->addToStack( 'recognize_points_so_far_limit', NULL, ['sprintf' => [$this->member->name, $maxPoints]] );
}
else
{
$message .= \IPS\Member::loggedIn()->language()->addToStack( 'recognize_points_so_far_limit_none', NULL, ['sprintf' => [$this->member->name]] );
$disabled = TRUE;
}
if ( \IPS\Member::loggedIn()->modPermission('can_recognize_content_no_point_limit') )
{
$message .= \IPS\Member::loggedIn()->language()->addToStack('recognize_points_so_far_limit_but_none');
$maxPoints = NULL;
$disabled = FALSE;
}
}
if ( $message )
{
$form->addMessage( $message, 'ipsMessage ipsMessage_info' );
}
if ( \IPS\Member::loggedIn()->modPermission('can_recognize_content') == '*' OR \in_array( 'badges', \IPS\Member::loggedIn()->modPermission('can_recognize_content_options') ) )
{
$form->add( new \IPS\Helpers\Form\Node( 'recognize_badge', NULL, FALSE, [
'class' => '\IPS\core\Achievements\Badge',
'permissionCheck' => function ( $node ) {
return $node->manually_awarded;
},
'url' => $content->author()->url()->setQueryString( array('do' => 'recognize', 'content_class' => $class, 'content_id' => $id) )
] ) );
}
if ( \IPS\Member::loggedIn()->modPermission('can_recognize_content') == '*' OR \in_array( 'points', \IPS\Member::loggedIn()->modPermission('can_recognize_content_options') ) )
{
$form->add( new \IPS\Helpers\Form\Number( 'recognize_points', 0, FALSE, [ 'disabled' => $disabled, 'min' => 0, 'max' => $maxPoints ] ) );
}
$form->add( new \IPS\Helpers\Form\Text( 'recognize_message', NULL, FALSE ) );
$form->add( new \IPS\Helpers\Form\YesNo( 'recognize_public', TRUE, FALSE ) );
\IPS\Member::loggedIn()->language()->words['recognize_message_desc'] = \IPS\Member::loggedIn()->language()->addToStack( 'recognize_message__desc', NULL, [ 'sprintf' => [ $content->author()->name ] ] );
if ( $values = $form->values() )
{
if ( ! $values['recognize_badge'] and ! $values['recognize_points'] )
{
$form->error = \IPS\Member::loggedIn()->language()->addToStack('recognize_form_empty');
}
else
{
\IPS\core\Achievements\Recognize::add( $content, $this->member, $values['recognize_points'], $values['recognize_badge'], $values['recognize_message'], \IPS\Member::loggedIn(), $values['recognize_public'] );
if ( \IPS\Request::i()->isAjax() )
{
\IPS\Output::i()->sendOutput( \IPS\Member::loggedIn()->language()->addToStack( 'recognize_submit_success' ) );
}
else
{
\IPS\Output::i()->redirect( $content->url(), 'recognize_submit_success' );
}
}
}
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('recognize_author', NULL, [ 'sprintf' => [ $content->author()->name ] ] );
\IPS\Output::i()->output = $form->customTemplate( array( \IPS\Theme::i()->getTemplate( 'forms', 'core' ), 'popupTemplate' ) );
}
catch( \Exception $e )
{
\IPS\Output::i()->error( 'node_error', '2C138/R', 403, '' );
}
}
/**
* Recognize
*
* @return void
*/
protected function unrecognize()
{
$class = \IPS\Request::i()->content_class;
$id = \IPS\Request::i()->content_id;
/* Does this content exist? */
try
{
$content = $class::loadAndCheckPerms( $id );
/* Can we view this item? */
if ( ! $content->canView() )
{
\IPS\Output::i()->error( 'node_error', '2C138/S', 403, '' );
}
/* Can we recognize this ? */
if ( ! $content->canRemoveRecognize() )
{
\IPS\Output::i()->error( 'node_error', '2C138/T', 403, '' );
}
$content->removeRecognize();
}
catch ( \Exception $e )
{
\IPS\Output::i()->error( 'node_error', '2C138/Ug', 403, '' );
}
if ( \IPS\Request::i()->isAjax() )
{
\IPS\Output::i()->sendOutput( \IPS\Member::loggedIn()->language()->addToStack( 'recognize_removed_success' ) );
}
else
{
\IPS\Output::i()->redirect( $content->url(), 'recognize_removed_success' );
}
}
}
@@ -468,7 +468,15 @@ class _messenger extends \IPS\Content\Controller
$form->class = 'ipsForm_vertical';
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('compose_new');
\IPS\Output::i()->output = $form->customTemplate( array( \IPS\Theme::i()->getTemplate( 'forms', 'core' ), 'popupTemplate' ) );
if( \IPS\Request::i()->isAjax() )
{
\IPS\Output::i()->output = $form->customTemplate( array( \IPS\Theme::i()->getTemplate( 'forms', 'core' ), 'popupTemplate' ) );
}
else
{
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate('messaging')->submitForm( \IPS\Member::loggedIn()->language()->addToStack('compose_new'), $form->customTemplate( array( \IPS\Theme::i()->getTemplate( 'forms', 'core' ), 'popupTemplate' ) ) );
}
}
/**
@@ -95,6 +95,7 @@ class _online extends \IPS\Dispatcher\Controller
$table->sortDirection = $table->sortDirection ?: 'desc';
/* Display */
\IPS\Output::i()->linkTags['canonical'] = (string) \IPS\Http\Url::internal( 'app=core&module=online&controller=online', 'front', 'online' );
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('online_users');
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'online', 'core', 'front' )->onlineUsersList( (string) $table, $table->count );
}
@@ -321,7 +321,7 @@ class _promote extends \IPS\Dispatcher\Controller
throw new \InvalidArgumentException;
}
$object = $class::load( $id );
$object = $class::load( (int) $id );
}
catch ( \Exception $ex )
{
@@ -563,9 +563,11 @@ class _promote extends \IPS\Dispatcher\Controller
}
}
}
$isNew = FALSE;
if ( ! $promote )
{
$isNew = TRUE;
$promote = new \IPS\core\Promote;
$promote->class = $class;
$promote->class_id = $id;
@@ -584,8 +586,23 @@ class _promote extends \IPS\Dispatcher\Controller
$promote->failed = 0; # Reset the failed flag if we're re-promoting
$promote->sent = 0;
$promote->form_data = $processedForms;
if ( is_subclass_of( $class, 'IPS\Content' ) )
{
$promote->author_id = $object->author()->member_id;
}
$promote->save();
if ( $isNew )
{
/* Points */
$promote->author()->achievementAction( 'core', 'ContentPromotion', [
'content' => $promote->object(),
'promotype' => 'promote'
] );
}
if ( isset( \IPS\Request::i()->promote_id ) )
{
/* Redirect back to the list */
@@ -22,13 +22,36 @@ if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
*/
class _search extends \IPS\Dispatcher\Controller
{
/**
* @brief Amount of time to cache search result in browser/CDN
*/
protected $_cacheTimeout = 30 * 60;
/**
* Check if this was a cached result we should just return a 304 against
*
* @return void
*/
protected function _checkCached()
{
/* Check whether a 304 Not modified response is appropriate */
if( !empty( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) AND ( new \IPS\DateTime( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) )->getTimestamp() > ( time() - $this->_cacheTimeout ) )
{
header('HTTP/1.1 304 Not Modified' );
exit;
}
}
/**
* Search Form
*
* @return void
*/
protected function manage()
{
{
/* Make sure this isn't a cached result we should just honor */
$this->_checkCached();
/* Init stuff for the output */
\IPS\Output::i()->sidebar['enabled'] = FALSE;
\IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'styles/streams.css' ) );
@@ -392,7 +415,7 @@ class _search extends \IPS\Dispatcher\Controller
{
try
{
$item = $class::loadAndCheckPerms( \IPS\Request::i()->item );
$item = $class::loadAndCheckPerms( (int) \IPS\Request::i()->item );
$query->filterByContent( array( \IPS\Content\Search\ContentFilter::init( $class )->onlyInItems( array( \IPS\Request::i()->item ) ) ) );
$baseUrl = $baseUrl->setQueryString( 'item', \intval( \IPS\Request::i()->item ) );
$titleConditions[] = \IPS\Member::loggedIn()->language()->addToStack( 'search_blurb_in', FALSE, array( 'sprintf' => array( $item->mapped('title') ) ) );
@@ -725,13 +748,26 @@ class _search extends \IPS\Dispatcher\Controller
/* Search tracking, only if there's a term for a non-member search and we're on the first page */
if( \IPS\Request::i()->q AND $page == 1 )
{
\IPS\Db::i()->insert( 'core_statistics', array(
'type' => 'search',
'time' => time(),
'value_4' => \IPS\Request::i()->q,
'value_2' => $count,
'extra_data' => md5( \IPS\Member::loggedIn()->email . \IPS\Member::loggedIn()->joined ) // Intentionally anonymized
) );
try
{
\IPS\Db::i()->insert( 'core_statistics', array(
'type' => 'search',
'time' => time(),
'value_4' => mb_substr( \IPS\Request::i()->q, 0, 255 ), // Some search queries can be unnecessarily long
'value_2' => $count,
'extra_data' => md5( \IPS\Member::loggedIn()->email . \IPS\Member::loggedIn()->joined ) // Intentionally anonymized
) );
}
catch( \IPS\Db\Exception $e ) {}
}
/* Enable caching for the search results request */
if ( !\IPS\Member::loggedIn()->member_id )
{
$httpHeaders = array( 'Expires' => \IPS\DateTime::create()->add( new \DateInterval( 'PT3M' ) )->rfc1123(),
'Cache-Control' => "max-age=" . $this->_cacheTimeout . ", s-maxage=" . $this->_cacheTimeout . ", public, stale-if-error, stale-while-revalidate" );
\IPS\Output::i()->httpHeaders += $httpHeaders;
}
/* Display results */
@@ -747,11 +783,6 @@ class _search extends \IPS\Dispatcher\Controller
}
else
{
$httpHeaders = array( 'Expires' => \IPS\DateTime::create()->add( new \DateInterval( 'PT3M' ) )->rfc1123() ,
'Cache-Control' => "max-age=" . 30 * 60 . ", private" );
\IPS\Output::i()->httpHeaders += $httpHeaders;
\IPS\Output::i()->title = $title;
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'search' )->search( $this->_splitTermsForDisplay(), $title, $results, $pagination, $baseUrl, $types, $this->_form()->customTemplate( array( \IPS\Theme::i()->getTemplate( 'search' ), 'filters' ), $baseUrl, $count ), $count );
}
@@ -82,7 +82,7 @@ class _directory extends \IPS\Dispatcher\Controller
$form = new \IPS\Helpers\Form;
$form->add( new \IPS\Helpers\Form\Text( 'leader_custom_name', ( \IPS\Member::loggedIn()->language()->checkKeyExists("core_staff_directory_name_{$user->id}") ) ? \IPS\Member::loggedIn()->language()->get("core_staff_directory_name_{$user->id}") : \IPS\Member::loggedIn()->name, FALSE ) );
$form->add( new \IPS\Helpers\Form\Text( 'leader_custom_title', ( \IPS\Member::loggedIn()->language()->checkKeyExists("core_staff_directory_title_{$user->id}") ) ? \IPS\Member::loggedIn()->language()->get("core_staff_directory_title_{$user->id}") : \IPS\Member::loggedIn()->member_title, FALSE ) );
$form->add( new \IPS\Helpers\Form\Text( 'leader_custom_title', ( \IPS\Member::loggedIn()->language()->checkKeyExists("core_staff_directory_title_{$user->id}") ) ? \IPS\Member::loggedIn()->language()->get("core_staff_directory_title_{$user->id}") : '', FALSE ) );
$form->add( new \IPS\Helpers\Form\Editor( 'leader_custom_bio', \IPS\Member::loggedIn()->language()->addToStack("core_staff_directory_bio_{$user->id}"), FALSE, array(
'app' => 'core',
'key' => 'Staffdirectory',
+51 -79
View File
@@ -35,29 +35,39 @@ class _ajax extends \IPS\Dispatcher\Controller
$where = array( "name LIKE CONCAT(?, '%')" );
$binds = array( $input );
if ( \IPS\Dispatcher::i()->controllerLocation === 'admin' )
if ( \IPS\Dispatcher::i()->controllerLocation === 'admin' OR ( \IPS\Member::loggedIn()->modPermission('can_see_emails') AND \IPS\Request::i()->type == 'mod' ) )
{
$where[] = "email LIKE CONCAT(?, '%')";
$binds[] = $input;
}
if ( \IPS\Dispatcher::i()->controllerLocation === 'admin' )
{
if ( \is_numeric( \IPS\Request::i()->input ) )
{
$where[] = "member_id=?";
$binds[] = \intval( \IPS\Request::i()->input );
}
}
/* Build the array item for this member after constructing a record */
/* The value should be just the name so that it's inserted into the input properly, but for display, we wrap it in the group *fix */
foreach ( \IPS\Db::i()->select( '*', 'core_members', array_merge( array( implode( ' OR ', $where ) ), $binds ), 'LENGTH(name) ASC', array( 0, 20 ) ) as $row )
{
$member = \IPS\Member::constructFromData( $row );
$extra = \IPS\Dispatcher::i()->controllerLocation == 'admin' ? htmlspecialchars( $member->email, ENT_DISALLOWED | ENT_QUOTES, 'UTF-8', FALSE ) : $member->groupName;
if ( \IPS\Member::loggedIn()->modPermission('can_see_emails') AND \IPS\Request::i()->type == 'mod' )
{
$extra = htmlspecialchars( $member->email, ENT_DISALLOWED | ENT_QUOTES, 'UTF-8', FALSE ) . '<br>' . $member->groupName;
}
$results[] = array(
'id' => $member->member_id,
'value' => $member->name,
'name' => \IPS\Dispatcher::i()->controllerLocation == 'admin' ? $member->group['prefix'] . htmlspecialchars( $member->name, ENT_DISALLOWED | ENT_QUOTES, 'UTF-8', FALSE ) . $member->group['suffix'] : htmlspecialchars( $member->name, ENT_DISALLOWED | ENT_QUOTES, 'UTF-8', FALSE ),
'extra' => \IPS\Dispatcher::i()->controllerLocation == 'admin' ? htmlspecialchars( $member->email, ENT_DISALLOWED | ENT_QUOTES, 'UTF-8', FALSE ) : $member->groupName,
'extra' => $extra,
'photo' => (string) $member->photo,
);
}
@@ -256,77 +266,6 @@ class _ajax extends \IPS\Dispatcher\Controller
\IPS\Output::i()->json( $result );
}
/**
* Returns boolean in json indicating whether the supplied email already exists
*
* @return void
*/
public function emailExists()
{
$result = array( 'result' => 'ok' );
/* The value comes urlencoded so we need to decode so length is correct (and not using a percent-encoded value) */
$email = urldecode( \IPS\Request::i()->input );
/* Check is valid */
if ( !$email )
{
$result = array( 'result' => 'fail', 'message' => \IPS\Member::loggedIn()->language()->addToStack('form_required') );
}
elseif ( filter_var( $email, FILTER_VALIDATE_EMAIL ) === FALSE )
{
$result = array( 'result' => 'fail', 'message' => \IPS\Member::loggedIn()->language()->addToStack('form_bad_value') );
}
/* Check if it exists */
else if ( $error = \IPS\Login::emailIsInUse( $email ) )
{
if ( \IPS\Member::loggedIn()->isAdmin() )
{
$result = array( 'result' => 'fail', 'message' => $error );
}
else
{
$result = array( 'result' => 'fail', 'message' => \IPS\Member::loggedIn()->language()->addToStack('member_email_exists') );
}
}
/* Check it's not banned */
if ( $result == array( 'result' => 'ok' ) )
{
foreach( \IPS\Db::i()->select( 'ban_content', 'core_banfilters', array("ban_type=?", 'email') ) as $bannedEmail )
{
if( preg_match( '/^' . str_replace( '\*', '.*', preg_quote( $bannedEmail, '/' ) ) . '$/i', $email ) )
{
$result = array( 'result' => 'fail', 'message' => \IPS\Member::loggedIn()->language()->addToStack('form_email_banned') );
break;
}
}
}
/* Check it's an allowed domain */
if ( \IPS\Settings::i()->allowed_reg_email !== '' AND $allowedEmailDomains = explode( ',', \IPS\Settings::i()->allowed_reg_email ) )
{
$matched = FALSE;
foreach ( $allowedEmailDomains AS $domain )
{
if( \mb_stripos( $email, "@" . $domain ) !== FALSE )
{
$matched = TRUE;
break;
}
}
if ( \count( $allowedEmailDomains ) AND !$matched )
{
$result = array( 'result' => 'fail', 'message' => $email );
}
}
\IPS\Output::i()->json( $result );
}
/**
* Get state/region list for country
*
@@ -396,6 +335,7 @@ class _ajax extends \IPS\Dispatcher\Controller
}
else
{
\IPS\Output::i()->metaTags['robots'] = 'noindex';
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack( 'block_topContributors' );
\IPS\Output::i()->output = $output;
}
@@ -410,6 +350,11 @@ class _ajax extends \IPS\Dispatcher\Controller
{
/* How many? */
$limit = \intval( ( isset( \IPS\Request::i()->limit ) and \IPS\Request::i()->limit <= 25 ) ? \IPS\Request::i()->limit : 5 );
if( $limit < 0 )
{
$limit = 5;
}
/* What timeframe? */
$where = array( array( 'member_id > 0' ) );
@@ -454,6 +399,7 @@ class _ajax extends \IPS\Dispatcher\Controller
}
else
{
\IPS\Output::i()->metaTags['robots'] = 'noindex';
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack( 'block_mostSolved' );
\IPS\Output::i()->output = $output;
}
@@ -472,6 +418,7 @@ class _ajax extends \IPS\Dispatcher\Controller
}
$preview = \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' )->navBar( TRUE );
\IPS\Output::i()->metaTags['robots'] = 'noindex';
\IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'system/menumanager.css', 'core', 'admin' ) );
\IPS\Output::i()->sendOutput( \IPS\Theme::i()->getTemplate( 'applications', 'core', 'admin' )->menuPreviewWrapper( $preview ) );
}
@@ -608,7 +555,8 @@ class _ajax extends \IPS\Dispatcher\Controller
{
$mysqlTimezone = "GMT" . ( ( $matches[2] == 0 ) ? '' : ( ( $matches[1] ?: '+' ) . \intval( $matches[2] ) ) );
}
\IPS\Output::i()->metaTags['robots'] = 'noindex';
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->chartTimezoneInfo( $mysqlTimezone );
}
@@ -626,7 +574,16 @@ class _ajax extends \IPS\Dispatcher\Controller
\IPS\core\AdminNotification::dismissNotification( \IPS\Request::i()->id );
}
\IPS\Output::i()->json( array( 'status' => 'OK' ) );
if( \IPS\Request::i()->isAjax() )
{
\IPS\Output::i()->json( array( 'status' => 'OK' ) );
}
else
{
$ref = \IPS\Request::i()->referrer();
\IPS\Output::i()->redirect( $ref ?? \IPS\Http\Url::internal( '' ) );
}
}
/**
@@ -650,7 +607,7 @@ class _ajax extends \IPS\Dispatcher\Controller
try
{
$container = $containerClass::load( \IPS\Request::i()->container );
$container = $containerClass::load( (int) \IPS\Request::i()->container );
}
catch( \OutOfRangeException $e )
{
@@ -695,4 +652,19 @@ class _ajax extends \IPS\Dispatcher\Controller
\IPS\Output::i()->json( $results );
}
/**
* Return current CSRF token
*
* @return string|null
*/
public function getCsrfKey(): ?string
{
if ( ! \IPS\Member::loggedIn()->member_id )
{
\IPS\Output::i()->json( [ 'key' => \IPS\Session::i()->csrfKey ] );
}
return NULL;
}
}
@@ -97,7 +97,7 @@ class _announcement extends \IPS\Content\Controller
}
/* Make sure this is a local URL */
if( !$url->isInternal )
if( !( $url instanceof \IPS\Http\Url\Internal ) )
{
\IPS\Output::i()->json( array( 'status' => 'not_local' ) );
}
@@ -37,7 +37,7 @@ class _attachments extends \IPS\Dispatcher\Controller
/* Build Table */
$table = new \IPS\core\Attachments\Table( 'core_attachments', \IPS\Http\Url::internal( 'app=core&module=system&controller=attachments', 'front', 'attachments' ), array( array( 'attach_member_id=?', \IPS\Member::loggedIn()->member_id ) ) );
$table->include = array( 'attach_id', 'attach_location', 'attach_date', 'attach_file', 'attach_filesize', 'attach_is_image', 'attach_content', 'attach_hits' );
$table->include = array( 'attach_id', 'attach_location', 'attach_date', 'attach_file', 'attach_filesize', 'attach_is_image', 'attach_content', 'attach_hits', 'attach_security_key' );
$table->rowsTemplate = array( \IPS\Theme::i()->getTemplate('myAttachments'), 'rows' );
$table->joins = array();
@@ -51,7 +51,7 @@ class _editor extends \IPS\Dispatcher\Controller
protected function code()
{
\IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'codemirror/codemirror.css', 'core', 'interface' ) );
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'editor', 'core', 'global' )->code( \IPS\Request::i()->val, \IPS\Request::i()->editorId, md5( mt_rand() ), \IPS\Request::i()->lang ?: '' );
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'editor', 'core', 'global' )->code( htmlentities( \IPS\Request::i()->val, ENT_QUOTES | ENT_DISALLOWED, 'UTF-8' ), \IPS\Request::i()->editorId, md5( mt_rand() ), \IPS\Request::i()->lang ?: '' );
}
/**
@@ -62,11 +62,20 @@ class _editor extends \IPS\Dispatcher\Controller
protected function image()
{
$maxImageDims = \IPS\Settings::i()->attachment_image_size ? explode( 'x', \IPS\Settings::i()->attachment_image_size ) : array( 1000, 750 );
$maxWidth = ( \IPS\Request::i()->actualWidth < $maxImageDims[0] ) ? \IPS\Request::i()->actualWidth : $maxImageDims[0];
$maxHeight = ( \IPS\Request::i()->actualHeight < $maxImageDims[1] ) ? \IPS\Request::i()->actualHeight : $maxImageDims[1];
$ratioH = round( \IPS\Request::i()->height / \IPS\Request::i()->width, 2 );
$ratioW = round( \IPS\Request::i()->width / \IPS\Request::i()->height, 2 );
if( \intval( $maxImageDims[0] ) === 0 && \intval( $maxImageDims[1] ) === 0 )
{
$maxWidth = \IPS\Request::i()->actualWidth;
$maxHeight = \IPS\Request::i()->actualHeight;
}
else
{
$maxWidth = ( \IPS\Request::i()->actualWidth < $maxImageDims[0] ) ? \IPS\Request::i()->actualWidth : $maxImageDims[0];
$maxHeight = ( \IPS\Request::i()->actualHeight < $maxImageDims[1] ) ? \IPS\Request::i()->actualHeight : $maxImageDims[1];
}
if ( \IPS\Request::i()->width > $maxWidth )
{
\IPS\Request::i()->width = $maxWidth;
@@ -115,13 +124,9 @@ class _editor extends \IPS\Dispatcher\Controller
{
try
{
if( \IPS\Request::i()->image )
{
\IPS\Text\Parser::isAllowedImageUrl( $url );
}
if ( \IPS\Request::i()->image and \IPS\Request::i()->width and \IPS\Request::i()->height )
{
\IPS\Text\Parser::isAllowedImageUrl( $url );
$embed = \IPS\Text\Parser::imageEmbed( $url, \intval( \IPS\Request::i()->width ), \intval( \IPS\Request::i()->height ) );
}
else
@@ -176,7 +181,7 @@ class _editor extends \IPS\Dispatcher\Controller
\IPS\Log::debug( $url . "\n" . $result['errorMessage'], 'embed_failure' );
}
\IPS\Data\Cache::i()->storeWithExpire( $cacheKey, $result, \IPS\DateTime::create()->add( new\DateInterval( 'PT10S' ) ), TRUE );
\IPS\Data\Cache::i()->storeWithExpire( $cacheKey, $result, \IPS\DateTime::create()->add( new \DateInterval( 'PT10S' ) ), TRUE );
\IPS\Output::i()->json( $result );
}
@@ -426,6 +431,11 @@ class _editor extends \IPS\Dispatcher\Controller
'title' => $row['title']
);
}
if ( empty( $results['images'] ) )
{
\IPS\Output::i()->json( array( 'error' => \IPS\Theme::i()->getTemplate( 'system', 'core' )->noResults() ) );
}
\IPS\Output::i()->json( $results );
}
@@ -34,18 +34,13 @@ class _login extends \IPS\Dispatcher\Controller
*/
protected function manage()
{
/* Did we just log in? */
if ( \IPS\Member::loggedIn()->member_id and isset( \IPS\Request::i()->_fromLogin ) )
{
\IPS\Output::i()->redirect( \IPS\Http\Url::internal('') );
}
/* Init login class */
$login = new \IPS\Login( \IPS\Http\Url::internal( "app=core&module=system&controller=login", 'front', 'login' ) );
/* What's our referrer? */
$postBeforeRegister = NULL;
$ref = \IPS\Request::i()->ref;
$ref = \IPS\Request::i()->referrer( FALSE, FALSE );
if ( !$ref and isset( \IPS\Request::i()->cookie['post_before_register'] ) )
{
try
@@ -111,12 +106,18 @@ class _login extends \IPS\Dispatcher\Controller
$error = $e->getMessage();
}
/* Are we already logged in? */
if ( \IPS\Member::loggedIn()->member_id AND ( !\IPS\Request::i()->_err OR \IPS\Request::i()->_err != 'login_as_user_login' ) )
{
\IPS\Output::i()->redirect( \IPS\Http\Url::internal('') );
}
/* Display Login Form */
\IPS\Output::i()->allowDefaultWidgets = FALSE;
\IPS\Output::i()->bodyClasses[] = 'ipsLayout_minimal';
\IPS\Output::i()->sidebar['enabled'] = FALSE;
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('login');
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'system' )->login( $login, $ref, $error );
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'system' )->login( $login, base64_encode( $ref ), $error );
/* Don't cache for a short while to ensure sessions work */
\IPS\Request::i()->setCookie( 'noCache', 1 );
@@ -290,7 +291,7 @@ class _login extends \IPS\Dispatcher\Controller
*/
protected function loginas()
{
if ( !\IPS\Request::i()->key or \IPS\Data\Store::i()->admin_login_as_user != \IPS\Request::i()->key )
if ( !\IPS\Request::i()->key or !\IPS\Login::compareHashes( (string) \IPS\Data\Store::i()->admin_login_as_user, (string) \IPS\Request::i()->key ) )
{
\IPS\Output::i()->error( 'invalid_login_as_user_key', '3S167/1', 403, '' );
}
@@ -56,18 +56,29 @@ class _lostpass extends \IPS\Dispatcher\Controller
{
/* Build the form */
$form = new \IPS\Helpers\Form( "lostpass", 'request_password' );
$form->add( new \IPS\Helpers\Form\Email( 'email_address', NULL, TRUE, array( 'bypassProfanity' => \IPS\Helpers\Form\Text::BYPASS_PROFANITY_ALL ), function( $val ){
if( !\IPS\Login::emailIsInUse( $val ) )
{
throw new \LogicException( 'lost_pass_no_email' );
}
}) );
$form->add( new \IPS\Helpers\Form\Email( 'email_address', NULL, TRUE, array( 'bypassProfanity' => \IPS\Helpers\Form\Text::BYPASS_PROFANITY_ALL ) ) );
$captcha = new \IPS\Helpers\Form\Captcha;
if ( (string) $captcha !== '' )
{
$form->add( $captcha );
}
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('lost_password');
/* Handle the reset */
if ( $values = $form->values() )
{
if( !\IPS\Login::emailIsInUse( $values['email_address'] ) )
{
/* We intentionally show the same message as if the request was successful to avoid leaking information about membership */
\IPS\Output::i()->sidebar['enabled'] = FALSE;
\IPS\Output::i()->bodyClasses[] = 'ipsLayout_minimal';
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'system' )->lostPassConfirm( 'lost_pass_confirm' );
return;
}
/* If using "normal" method and we have an account, and at least one login handler we can process a password change for, we're good */
$member = \IPS\Member::load( $values['email_address'], 'email' );
if ( $member->member_id and \IPS\Settings::i()->allow_forgot_password == 'normal' )
@@ -42,19 +42,21 @@ class _marketplace extends \IPS\Dispatcher\Controller
\IPS\Db::i()->update( 'core_marketplace_tokens', array( 'token' => \IPS\Request::i()->access_token, 'expires_at' => time() + \IPS\Request::i()->expires_in ), array( 'id=?', \IPS\Request::i()->member ) );
echo 'OK';
exit;
$response = 'OK';
$responseCode = 200;
}
catch ( \UnderflowException $e )
{
echo 'BAD_HASH';
exit;
$response = 'BAD_HASH';
$responseCode = 403;
}
}
else
{
echo 'NO_MEMBER';
exit;
$response = 'NO_MEMBER';
$responseCode = 404;
}
\IPS\Output::i()->sendOutput( $response, $responseCode, 'text/plain' );
}
}
@@ -65,12 +65,84 @@ class _notifications extends \IPS\Dispatcher\Controller
}
else
{
\IPS\Output::i()->metaTags['robots'] = 'noindex';
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('notifications');
\IPS\Output::i()->breadcrumb[] = array( NULL, \IPS\Output::i()->title );
\IPS\Output::i()->output = (string) $table;
}
}
/**
* Subscribe a user's device to push notifications
*
* @return void
*/
protected function subscribeToPush()
{
$this->_checkLoggedIn();
\IPS\Session::i()->csrfCheck();
if ( isset( \IPS\Request::i()->subscription ) AND $subscription = json_decode( \IPS\Request::i()->subscription, TRUE ) )
{
$device = \IPS\Member\Device::loadOrCreate( \IPS\Member::loggedIn(), FALSE );
/* If a subscription already exists, then just return */
try
{
\IPS\Db::i()->select( '*', 'core_notifications_pwa_keys', array( "`member`=? AND p256dh=? AND auth=?", \IPS\Member::loggedIn()->member_id, $subscription['keys']['p256dh'], $subscription['keys']['auth'] ) )->first();
/* Make sure encoding and device is up to date, though since it's needed for encryption transfer. */
\IPS\Db::i()->update( 'core_notifications_pwa_keys', array(
'encoding' => \IPS\Request::i()->encoding,
'device' => $device->device_key
), array( "`member`=? AND p256dh=? AND auth=?", \IPS\Member::loggedIn()->member_id, $subscription['keys']['p256dh'], $subscription['keys']['auth'] ) );
}
catch( \UnderflowException $e )
{
\IPS\Db::i()->insert( 'core_notifications_pwa_keys', array(
'member' => \IPS\Member::loggedIn()->member_id,
'endpoint' => $subscription['endpoint'],
'p256dh' => $subscription['keys']['p256dh'],
'auth' => $subscription['keys']['auth'],
'encoding' => \IPS\Request::i()->encoding,
'device' => $device->device_key
) );
}
\IPS\Output::i()->json( 'OK' );
}
else
{
\IPS\Output::i()->error( 'invalid_push_subscription', '2C154/H', 403, '' );
}
}
/**
* Verify a subscription with the provided key exists for the logged-in user
*
* @return void
*/
protected function verifySubscription()
{
$this->_checkLoggedIn();
\IPS\Session::i()->csrfCheck();
if ( isset( \IPS\Request::i()->key ) )
{
/* See if a subscription already exists */
try
{
\IPS\Db::i()->select( '*', 'core_notifications_pwa_keys', array( "`member`=? AND p256dh=?", \IPS\Member::loggedIn()->member_id, \IPS\Request::i()->key ) )->first();
\IPS\Output::i()->json( 'OK' );
}
catch( \UnderflowException $e )
{
// Just let it return the error below
}
}
\IPS\Output::i()->error( 'invalid_push_subscription', '2C154/I', 403, '' );
}
/**
* Options: Dispatcher
*
@@ -127,8 +199,7 @@ class _notifications extends \IPS\Dispatcher\Controller
if ( $form === TRUE )
{
if ( \IPS\Request::i()->isAjax() )
{
\IPS\Member::loggedIn()->notificationsConfiguration = NULL;
{
$categories = \IPS\Notification::membersOptionCategories( \IPS\Member::loggedIn(), array( $extensionKey => $extension ) );
\IPS\Output::i()->sendOutput( \IPS\Theme::i()->getTemplate('system')->notificationSettingsIndexRowDetails( $extensionKey, $categories[ $extensionKey ] ), 200, 'text/html', \IPS\Output::i()->httpHeaders );
}
@@ -153,30 +224,6 @@ class _notifications extends \IPS\Dispatcher\Controller
}
}
/**
* Enable/Disable Sounds
*
* @param array $extensions The extensions
* @return void
*/
protected function sounds()
{
$this->_checkLoggedIn();
\IPS\Session::i()->csrfCheck();
\IPS\Member::loggedIn()->members_bitoptions['disable_notification_sounds'] = ( \IPS\Request::i()->enable ? false : true );
\IPS\Member::loggedIn()->save();
if ( \IPS\Request::i()->isAjax() )
{
\IPS\Output::i()->json( 'ok' );
}
else
{
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=system&controller=notifications&do=options', 'front', 'notifications_options' ) );
}
}
/**
* Stop receiving all notifications to a particular method
*
@@ -219,6 +266,11 @@ class _notifications extends \IPS\Dispatcher\Controller
$extension::disableExtra( \IPS\Member::loggedIn(), \IPS\Request::i()->type );
}
}
if ( \IPS\Request::i()->type === 'push' )
{
\IPS\Member::loggedIn()->clearPwaAuths();
}
if ( \IPS\Request::i()->isAjax() )
{
@@ -239,9 +291,18 @@ class _notifications extends \IPS\Dispatcher\Controller
{
$this->_checkLoggedIn();
try
{
$application = \IPS\Application::load( \IPS\Request::i()->follow_app );
}
catch( \OutOfRangeException $e )
{
\IPS\Output::i()->error( 'error_no_app', '3C154/F', 404, '' );
}
/* Get class */
$class = NULL;
foreach ( \IPS\Application::load( \IPS\Request::i()->follow_app )->extensions( 'core', 'ContentRouter' ) as $ext )
foreach ( $application->extensions( 'core', 'ContentRouter' ) as $ext )
{
foreach ( $ext->classes as $classname )
{
@@ -309,7 +370,7 @@ class _notifications extends \IPS\Dispatcher\Controller
{
if ( \in_array( 'IPS\Node\Model', class_parents( $class ) ) )
{
$thing = $class::loadAndCheckPerms( \IPS\Request::i()->follow_id );
$thing = $class::loadAndCheckPerms( (int) \IPS\Request::i()->follow_id );
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack( 'follow_thing', FALSE, array( 'sprintf' => array( $thing->_title ) ) );
/* Set navigation */
@@ -325,7 +386,7 @@ class _notifications extends \IPS\Dispatcher\Controller
}
elseif ( $class == 'IPS\Member\Club' )
{
$thing = $class::loadAndCheckPerms( \IPS\Request::i()->follow_id );
$thing = $class::loadAndCheckPerms( (int) \IPS\Request::i()->follow_id );
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack( 'follow_thing', FALSE, array( 'sprintf' => array( $thing->_title ) ) );
\IPS\Output::i()->breadcrumb = array(
array( \IPS\Http\Url::internal( 'app=core&module=clubs&controller=directory', 'front', 'clubs_list' ), \IPS\Member::loggedIn()->language()->addToStack('module__core_clubs') ),
@@ -333,8 +394,13 @@ class _notifications extends \IPS\Dispatcher\Controller
);
}
elseif ( $class != "IPS\Member" )
{
$thing = $class::loadAndCheckPerms( \IPS\Request::i()->follow_id );
{
if( !is_subclass_of( $class, "\IPS\Content\Followable" ) )
{
throw new \OutOfRangeException;
}
$thing = $class::loadAndCheckPerms( (int) \IPS\Request::i()->follow_id );
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack( 'follow_thing', FALSE, array( 'sprintf' => array( $thing->mapped('title') ) ) );
/* Set navigation */
@@ -355,7 +421,7 @@ class _notifications extends \IPS\Dispatcher\Controller
}
else
{
$thing = $class::load( \IPS\Request::i()->follow_id );
$thing = $class::load( (int) \IPS\Request::i()->follow_id );
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('follow_thing', FALSE, array( 'sprintf' => array( $thing->name ) ) );
@@ -535,10 +601,28 @@ class _notifications extends \IPS\Dispatcher\Controller
/* Send notification if following member */
if( $class == "IPS\Member" )
{
/* Give points */
$receiver = \IPS\Member::load( \IPS\Request::i()->follow_rel_id );
$receiver->achievementAction( 'core', 'FollowMember', [
'giver' => \IPS\Member::loggedIn()
] );
$notification = new \IPS\Notification( \IPS\Application::load( 'core' ), 'member_follow', \IPS\Member::loggedIn(), array( \IPS\Member::loggedIn() ) );
$notification->recipients->attach( $thing );
$notification->send();
}
else if ( \in_array( 'IPS\Node\Model', class_parents( $class ) ) )
{
\IPS\Member::loggedIn()->achievementAction( 'core', 'FollowNode', $class::load( \IPS\Request::i()->follow_id ) );
}
else if ( \in_array( 'IPS\Content\Item', class_parents( $class ) ) )
{
$item = $class::load( \IPS\Request::i()->follow_id );
\IPS\Member::loggedIn()->achievementAction( 'core', 'FollowContentItem', [
'item' => $item,
'author' => $item->author()
] );
}
/* Boink */
if ( \IPS\Request::i()->isAjax() )
@@ -597,7 +681,7 @@ class _notifications extends \IPS\Dispatcher\Controller
try
{
$thing = $class::loadAndCheckPerms( $follow['follow_rel_id'] );
$thing = $class::loadAndCheckPerms( (int) $follow['follow_rel_id'] );
foreach ( $thing->nodes() as $node )
{
@@ -651,7 +735,7 @@ class _notifications extends \IPS\Dispatcher\Controller
$class = 'IPS\\' . \IPS\Request::i()->follow_app . '\\' . mb_ucfirst( \IPS\Request::i()->follow_area );
}
if ( !class_exists( $class ) )
if ( !class_exists( $class ) or !array_key_exists( \IPS\Request::i()->follow_app, \IPS\Application::applications() ) )
{
\IPS\Output::i()->error( 'node_error', '2C154/5', 404, '' );
}
@@ -665,7 +749,7 @@ class _notifications extends \IPS\Dispatcher\Controller
{
$classname = $class::$contentItemClass;
$containerClass = $class;
$thing = $containerClass::loadAndCheckPerms( \IPS\Request::i()->follow_id );
$thing = $containerClass::loadAndCheckPerms( (int) \IPS\Request::i()->follow_id );
$followers = $classname::containerFollowers( $thing, $classname::FOLLOW_PUBLIC, array( 'none', 'immediate', 'daily', 'weekly' ), NULL, array( ( $thisPage - 1 ) * $perPage, $perPage ), 'name' );
$followersCount = $classname::containerFollowerCount( $thing );
$anonymous = $classname::containerFollowerCount( $thing, $classname::FOLLOW_ANONYMOUS );
@@ -673,7 +757,7 @@ class _notifications extends \IPS\Dispatcher\Controller
}
else if ( $class == "IPS\Member\Club" )
{
$thing = $class::loadAndCheckPerms( \IPS\Request::i()->follow_id );
$thing = $class::loadAndCheckPerms( (int) \IPS\Request::i()->follow_id );
$followers = $thing->followers( $class::FOLLOW_PUBLIC, array( 'none', 'immediate', 'daily', 'weekly' ), NULL, array( ( $thisPage - 1 ) * $perPage, $perPage ), 'name' );
$followersCount = $thing->followersCount();
$anonymous = $thing->followersCount( $class::FOLLOW_ANONYMOUS );
@@ -681,7 +765,7 @@ class _notifications extends \IPS\Dispatcher\Controller
}
else if ( $class != "IPS\Member" )
{
$thing = $class::loadAndCheckPerms( \IPS\Request::i()->follow_id );
$thing = $class::loadAndCheckPerms( (int) \IPS\Request::i()->follow_id );
$followers = $thing->followers( $class::FOLLOW_PUBLIC, array( 'none', 'immediate', 'daily', 'weekly' ), NULL, array( ( $thisPage - 1 ) * $perPage, $perPage ), 'name' );
$followersCount = $thing->followersCount();
$anonymous = $thing->followersCount( $class::FOLLOW_ANONYMOUS );
@@ -689,7 +773,7 @@ class _notifications extends \IPS\Dispatcher\Controller
}
else
{
$thing = $class::load( \IPS\Request::i()->follow_id );
$thing = $class::load( (int) \IPS\Request::i()->follow_id );
$followers = $thing->followers( $class::FOLLOW_PUBLIC, array( 'none', 'immediate', 'daily', 'weekly' ), NULL, array( ( $thisPage - 1 ) * $perPage, $perPage ), 'name' );
$followersCount = $thing->followersCount();
$anonymous = $thing->followersCount( $class::FOLLOW_ANONYMOUS );
@@ -735,7 +819,7 @@ class _notifications extends \IPS\Dispatcher\Controller
* @return void
*/
protected function unfollowFromEmail()
{
{
/* Logged in? */
if ( \IPS\Member::loggedIn()->member_id )
{
@@ -764,6 +848,11 @@ class _notifications extends \IPS\Dispatcher\Controller
throw new \Exception;
}
if( !array_key_exists( \IPS\Request::i()->follow_app, \IPS\Application::applications() ) )
{
throw new \Exception;
}
/* Get class */
if( \IPS\Request::i()->follow_app == 'core' and \IPS\Request::i()->follow_area == 'member' )
{
@@ -776,6 +865,11 @@ class _notifications extends \IPS\Dispatcher\Controller
else
{
$class = 'IPS\\' . \IPS\Request::i()->follow_app . '\\' . mb_ucfirst( \IPS\Request::i()->follow_area );
if( !is_subclass_of( $class, "\IPS\Content\Followable" ) )
{
throw new \Exception;
}
}
if ( !class_exists( $class ) )
{
@@ -789,22 +883,22 @@ class _notifications extends \IPS\Dispatcher\Controller
{
$classname = $class::$contentItemClass;
$containerClass = $class;
$thing = $containerClass::load( \IPS\Request::i()->follow_id );
$thing = $containerClass::load( (int) \IPS\Request::i()->follow_id );
$title = $thing->_title;
}
else if ( $class == "IPS\Member\Club" )
{
$thing = $class::load( \IPS\Request::i()->follow_id );
$thing = $class::load( (int) \IPS\Request::i()->follow_id );
$title = $thing->_title;
}
else if ( $class != "IPS\Member" )
{
$thing = $class::load( \IPS\Request::i()->follow_id );
$thing = $class::load( (int) \IPS\Request::i()->follow_id );
$title = $thing->mapped('title');
}
else
{
$thing = $class::load( \IPS\Request::i()->follow_id );
$thing = $class::load( (int) \IPS\Request::i()->follow_id );
$title = $thing->name;
}
@@ -890,7 +984,8 @@ class _notifications extends \IPS\Dispatcher\Controller
{
$class = 'IPS\\' . \IPS\Request::i()->follow_app . '\\' . mb_ucfirst( \IPS\Request::i()->follow_area );
}
if ( !class_exists( $class ) )
if ( !class_exists( $class ) or !array_key_exists( \IPS\Request::i()->follow_app, \IPS\Application::applications() ) )
{
\IPS\Output::i()->error( 'node_error', '2C154/5', 404, '' );
}
@@ -903,17 +998,22 @@ class _notifications extends \IPS\Dispatcher\Controller
{
$classname = $class::$contentItemClass;
$containerClass = $class;
$thing = $containerClass::loadAndCheckPerms( \IPS\Request::i()->follow_id );
$thing = $containerClass::loadAndCheckPerms( (int) \IPS\Request::i()->follow_id );
$count = $classname::containerFollowerCount( $thing );
}
else if ( $class != "IPS\Member" )
{
$thing = $class::loadAndCheckPerms( \IPS\Request::i()->follow_id );
$thing = $class::loadAndCheckPerms( (int) \IPS\Request::i()->follow_id );
$count = $thing->followersCount();
}
else
{
$thing = $class::load( \IPS\Request::i()->follow_id );
if( !is_subclass_of( $class, "\IPS\Content\Followable" ) AND $class != "IPS\Member" )
{
\IPS\Output::i()->error( 'node_error', '2C154/J', 404, '' );
}
$thing = $class::load( (int) \IPS\Request::i()->follow_id );
$count = $thing->followersCount();
}
}
@@ -983,4 +1083,39 @@ class _notifications extends \IPS\Dispatcher\Controller
\IPS\Output::i()->redirect( \IPS\Request::i()->referrer() ?: \IPS\Http\Url::internal( '' ), 'followers_removed' );
}
/**
* Retrieve notification data and return it to the service worker
*
* @return void
*/
protected function fetchNotification()
{
/* Got the ID? */
if( !\IPS\Request::i()->id )
{
\IPS\Output::i()->json( array( 'error' => 'missing_id' ), 404 );
}
/* Got the notification? */
try
{
$notification = \IPS\Db::i()->select( '*', 'core_notifications_pwa_queue', array( 'id=?', \IPS\Request::i()->id ) )->first();
}
catch( \UnderflowException $e )
{
\IPS\Output::i()->json( array( 'error' => 'not_found' ), 404 );
}
/* Is this our notification? */
$data = json_decode( $notification['notification_data'], true );
if( !isset( $data['member'] ) OR $data['member'] != \IPS\Member::loggedIn()->member_id )
{
\IPS\Output::i()->json( array( 'error' => 'no_permission' ), 403 );
}
/* Send the data */
\IPS\Output::i()->json( $data );
}
}
@@ -0,0 +1,34 @@
<?php
/**
* @brief Offline page output
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
* @copyright (c) Invision Power Services, Inc.
* @license https://www.invisioncommunity.com/legal/standards/
* @package Invision Community
* @since 12 Feb 2021
*/
namespace IPS\core\modules\front\system;
/* 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;
}
/**
* Offline page Controller
*/
class _offline extends \IPS\Dispatcher\Controller
{
/**
* View Notifications
*
* @return void
*/
protected function manage()
{
\IPS\Output::i()->sendOutput( \IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->offline(), 200, 'text/html' );
}
}
@@ -56,7 +56,8 @@ class _pixabay extends \IPS\Dispatcher\Controller
'key' => \IPS\Settings::i()->pixabay_apikey,
'image_type' => 'photo',
'per_page' => 20,
'page' => ( $offset ) ? ceil( $offset / 20 ) + 1 : 1
'page' => ( $offset ) ? ceil( $offset / 20 ) + 1 : 1,
'safesearch' => ( \IPS\Settings::i()->pixabay_safesearch ) ? 'true' : 'false',
);
$parameters['q'] = urlencode( $query );
@@ -79,6 +80,7 @@ class _pixabay extends \IPS\Dispatcher\Controller
\IPS\Output::i()->json( array('error' => $request['message'] ) );
}
$results = array( 'pagination' => array( 'total_count' => $request['total'] ) );
foreach ( $request['hits'] as $row )
{
@@ -89,6 +91,11 @@ class _pixabay extends \IPS\Dispatcher\Controller
);
}
if ( empty( $results['images'] ) )
{
\IPS\Output::i()->json( array( 'error' => \IPS\Theme::i()->getTemplate( 'system', 'core' )->noResults() ) );
}
\IPS\Output::i()->json( $results );
}
}
@@ -34,6 +34,18 @@ class _privacy extends \IPS\Dispatcher\Controller
\IPS\Output::i()->error( 'node_error', '2C381/1', 404, 'privacy_set_to_none_acp' );
}
if ( \IPS\Settings::i()->privacy_type == "external" )
{
if ( $url = \IPS\Settings::i()->privacy_link )
{
\IPS\Output::i()->redirect( \IPS\Http\Url::external( $url ) );
}
else
{
\IPS\Output::i()->error( 'node_error', '2C381/1', 404, 'privacy_link_not_set_acp' );
}
}
$subprocessors = array();
/* Work out the main subprocessors that the user has no direct choice over */
if ( \IPS\Settings::i()->privacy_show_processors )
@@ -39,7 +39,7 @@ class _register extends \IPS\Dispatcher\Controller
\IPS\Output::i()->redirect( \IPS\Settings::i()->base_url );
}
if( \IPS\Request::i()->do !== 'complete'
if( \IPS\Request::i()->do !== 'complete' and \IPS\Request::i()->do !== 'setPassword'
and \IPS\Request::i()->do !== 'changeEmail' and \IPS\Request::i()->do !== 'validate'
and \IPS\Request::i()->do !== 'validating' and \IPS\Request::i()->do !== 'reconfirm'
and \IPS\Request::i()->do !== 'finish' and \IPS\Request::i()->do !== 'cancel' )
@@ -60,6 +60,7 @@ class _register extends \IPS\Dispatcher\Controller
\IPS\Output::i()->bodyClasses[] = 'ipsLayout_minimalNoHome';
}
\IPS\Output::i()->sidebar['enabled'] = FALSE;
\IPS\Output::i()->linkTags['canonical'] = (string) \IPS\Http\Url::internal( 'app=core&module=system&controller=register', 'front', 'register' );
}
/**
@@ -524,7 +525,7 @@ class _register extends \IPS\Dispatcher\Controller
* @param \IPS\Helpers\Form $form The form object
* @return \IPS\Member
*/
public static function _createMember( $values, $profileFields, $postBeforeRegister = NULL, &$form )
public static function _createMember( $values, $profileFields, $postBeforeRegister, &$form )
{
/* Create */
$member = new \IPS\Member;
@@ -1003,7 +1004,7 @@ class _register extends \IPS\Dispatcher\Controller
}
catch( \BadMethodCallException $e ) {}
}
\IPS\Member::loggedIn()->memberSync( 'onEmailChange', array( $values['new_email'], $oldEmail ) );
\IPS\Member::loggedIn()->logHistory( 'core', 'email_change', array( 'old' => $oldEmail, 'new' => $values['new_email'], 'by' => 'manual' ) );
/* Invalidate sessions except this one */
@@ -1037,6 +1038,10 @@ class _register extends \IPS\Dispatcher\Controller
\IPS\Email::buildFromTemplate( 'core', $pending['email_chg'] ? 'email_change' : 'registration_validate', array( \IPS\Member::loggedIn(), $vid ), \IPS\Email::TYPE_TRANSACTIONAL )->send( \IPS\Member::loggedIn() );
}
else
{
\IPS\Member::loggedIn()->memberSync( 'onEmailChange', array( $values['new_email'], $oldEmail ) );
}
/* Redirect */
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( '' ) );
@@ -1087,10 +1092,7 @@ class _register extends \IPS\Dispatcher\Controller
/* Generate form */
$form = new \IPS\Helpers\Form( 'reconfirm_checkbox', 'reconfirm_checkbox' );
if ( isset( \IPS\Request::i()->ref ) )
{
$form->hiddenValues['ref'] = \IPS\Request::i()->ref;
}
$form->hiddenValues['ref'] = base64_encode( \IPS\Request::i()->referrer( FALSE, FALSE, 'front' ) ?? \IPS\Http\Url::baseUrl() );
$form->class = 'ipsForm_vertical';
/* Handle submissions */
@@ -1250,7 +1252,7 @@ class _register extends \IPS\Dispatcher\Controller
*
* @return void
*/
public function setPassword(): void
public function setPassword()
{
try
{
@@ -0,0 +1,85 @@
<?php
/**
* @brief Service worker output
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
* @copyright (c) Invision Power Services, Inc.
* @license https://www.invisioncommunity.com/legal/standards/
* @package Invision Community
* @since 12 Feb 2021
*/
namespace IPS\core\modules\front\system;
/* 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;
}
/**
* Service worker controller
*/
class _serviceworker extends \IPS\Dispatcher\Controller
{
/**
* View Notifications
*
* @return void
*/
protected function manage()
{
$cachedUrls = array();
$cachedUrls[] = (string) \IPS\Http\Url::internal("app=core&module=system&controller=offline", "front", "user_offline");
$notificationIcon = NULL;
/* Get an icon to use in notifications */
$homeScreenIcons = json_decode( \IPS\Settings::i()->icons_homescreen, TRUE ) ?? array();
if( \count( $homeScreenIcons ) )
{
foreach( $homeScreenIcons as $name => $image )
{
if( isset( $image['width'] ) and $image['width'] == 192 )
{
$notificationIcon = \IPS\File::get( 'core_Icons', $image['url'] )->url;
break;
}
}
}
/* VARIABLES TO PASS THROUGH TO JS */
$DEBUG = ( ( \IPS\IN_DEV and \IPS\DEV_DEBUG_JS ) or \IPS\DEBUG_JS ) ? 'true' : 'false'; // Weird casting is intentional.
$SW_CACHE_BUST = \IPS\SW_CACHE_BUST ? 'true' : 'false'; // Weird casting is intentional.
$HTTP_CACHE_DURATION = \IPS\CACHE_PAGE_TIMEOUT;
$BASE_URL = \IPS\Settings::i()->base_url;
$CACHED_ASSETS = json_encode( $cachedUrls, JSON_UNESCAPED_SLASHES );
$OFFLINE_URL = (string) \IPS\Http\Url::internal("app=core&module=system&controller=offline", "front", "user_offline");
$CACHE_VERSION = \IPS\Theme::i()->cssCacheBustKey();
$NOTIFICATION_ICON = $notificationIcon ? "\"{$notificationIcon}\"" : 'null'; // Weird casting is intentional. 'null' will become literal null in JS file.
$DEFAULT_NOTIFICATION_TITLE = \IPS\Member::loggedIn()->language()->addToStack('default_notification_title');
$DEFAULT_NOTIFICATION_BODY = \IPS\Member::loggedIn()->language()->addToStack('default_notification_body');
$output = <<<JAVASCRIPT
"use strict";
const DEBUG = {$DEBUG};
const SW_CACHE_BUST = {$SW_CACHE_BUST};
const BASE_URL = "{$BASE_URL}";
const CACHED_ASSETS = {$CACHED_ASSETS};
const CACHE_NAME = 'invision-community-{$CACHE_VERSION}';
const HTTP_CACHE_DURATION = {$HTTP_CACHE_DURATION};
const HTTP_CACHE_BUFFER = 60;
const OFFLINE_URL = "{$OFFLINE_URL}";
const NOTIFICATION_ICON = {$NOTIFICATION_ICON};
const DEFAULT_NOTIFICATION_TITLE = "{$DEFAULT_NOTIFICATION_TITLE}";
const DEFAULT_NOTIFICATION_BODY = "{$DEFAULT_NOTIFICATION_BODY}";
JAVASCRIPT;
\IPS\Member::loggedIn()->language()->parseOutputForDisplay( $output );
$output .= file_get_contents( \IPS\ROOT_PATH . '/applications/core/interface/js/serviceWorker.js' );
$cacheHeaders = \IPS\IN_DEV !== true ? \IPS\Output::getCacheHeaders(time(), 86400) : array();
\IPS\Output::i()->sendOutput($output, 200, 'text/javascript', $cacheHeaders);
}
}
@@ -350,6 +350,22 @@ class _settings extends \IPS\Dispatcher\Controller
{
\IPS\Output::i()->error( 'no_module_permission', '2C122/T', 403, '' );
}
$canChangePassword = FALSE;
foreach ( \IPS\Login::methods() as $method )
{
if ( $method->canChangePassword( \IPS\Member::loggedIn() ) )
{
$canChangePassword = TRUE;
break;
}
}
if( !$canChangePassword )
{
\IPS\Output::i()->error( 'no_module_permission', '3C122/W', 403, '' );
}
if( \IPS\Member::loggedIn()->isAdmin() )
{
@@ -538,7 +554,7 @@ class _settings extends \IPS\Dispatcher\Controller
}
}
$oauthApps = \IPS\Db::i()->select( 'COUNT(DISTINCT client_id)', 'core_oauth_server_access_tokens', array( 'member_id=? AND oauth_enabled=1 AND oauth_ucp=1', \IPS\Member::loggedIn()->member_id ) )
$oauthApps = \IPS\Db::i()->select( 'COUNT(DISTINCT client_id)', 'core_oauth_server_access_tokens', array( "member_id=? AND oauth_enabled=1 AND oauth_ucp=1 AND status='active'", \IPS\Member::loggedIn()->member_id ) )
->join( 'core_oauth_clients', 'oauth_client_id=client_id' )
->first();
@@ -563,7 +579,7 @@ class _settings extends \IPS\Dispatcher\Controller
$device->login_key = NULL;
$device->save();
\IPS\Db::i()->delete( 'core_oauth_server_access_tokens', array( 'member_id=? AND device_key=?', $device->member_id, $device->device_key ) );
\IPS\Db::i()->update( 'core_oauth_server_access_tokens', array( 'status' => 'revoked' ), array( 'member_id=? AND device_key=?', $device->member_id, $device->device_key ) );
\IPS\Member::loggedIn()->logHistory( 'core', 'login', array( 'type' => 'logout', 'device' => $device->device_key ) );
@@ -585,20 +601,27 @@ class _settings extends \IPS\Dispatcher\Controller
if ( !isset( $_SESSION['passwordValidatedForMfa'] ) )
{
$login = new \IPS\Login( \IPS\Http\Url::internal( 'app=core&module=system&controller=settings&area=mfa', 'front', 'settings_mfa' ), \IPS\Login::LOGIN_REAUTHENTICATE );
$error = NULL;
try
$usernamePasswordMethods = $login->usernamePasswordMethods();
$buttonMethods = $login->buttonMethods();
/* Only prompt for re-authentication if it is possible */
if( $usernamePasswordMethods OR $buttonMethods )
{
if ( $success = $login->authenticate() )
$error = NULL;
try
{
$_SESSION['passwordValidatedForMfa'] = TRUE;
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=system&controller=settings&area=mfa', 'front', 'settings_mfa' ) );
if ( $success = $login->authenticate() )
{
$_SESSION['passwordValidatedForMfa'] = TRUE;
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=system&controller=settings&area=mfa', 'front', 'settings_mfa' ) );
}
}
catch ( \IPS\Login\Exception $e )
{
$error = $e->getMessage();
}
return \IPS\Theme::i()->getTemplate( 'system' )->settingsMfaPassword( $login, $error );
}
catch ( \IPS\Login\Exception $e )
{
$error = $e->getMessage();
}
return \IPS\Theme::i()->getTemplate( 'system' )->settingsMfaPassword( $login, $error );
}
/* Get our handlers and the output, even if it's just for a backdrop */
@@ -742,7 +765,7 @@ class _settings extends \IPS\Dispatcher\Controller
$this->_performRedirect( \IPS\Http\Url::internal('') );
}
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('reg_complete_details');
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('reg_complete_2fa_title');
\IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( '2fa.css', 'core', 'global' ) );
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'login', 'core', 'global' )->mfaSetup( $handlers, \IPS\Member::loggedIn(), \IPS\Http\Url::internal( 'app=core&module=system&controller=settings&do=initialMfa', 'front', 'settings' )->addRef( $this->_performRedirect( \IPS\Http\Url::internal(''), '', TRUE ) ) );
}
@@ -1128,7 +1151,7 @@ class _settings extends \IPS\Dispatcher\Controller
}
/* Number of Images */
if ( \is_numeric( $sigLimits[1] ) and $signature->getElementsByTagName('img')->length > 0 )
if ( \is_numeric( $sigLimits[1] ) )
{
$imageCount = 0;
foreach ( $signature->getElementsByTagName('img') as $img )
@@ -1138,6 +1161,15 @@ class _settings extends \IPS\Dispatcher\Controller
$imageCount++;
}
}
/* Look for background-image URLs too */
$xpath = new \DOMXpath( $signature );
foreach ( $xpath->query("//*[contains(@style, 'url') and contains(@style, 'background')]") as $styleUrl )
{
$imageCount++;
}
if( $imageCount > $sigLimits[1] )
{
$errors[] = \IPS\Member::loggedIn()->language()->addToStack('sig_num_images_exceeded');
@@ -1479,6 +1511,10 @@ class _settings extends \IPS\Dispatcher\Controller
foreach ( \IPS\Db::i()->select( '*', 'core_oauth_server_access_tokens', array( 'member_id=?', \IPS\Member::loggedIn()->member_id ), 'issued DESC' ) as $accessToken )
{
if ( $accessToken['status'] == 'revoked' )
{
continue;
}
try
{
$client = \IPS\Api\OAuthClient::load( $accessToken['client_id'] );
@@ -1538,7 +1574,7 @@ class _settings extends \IPS\Dispatcher\Controller
try
{
$client = \IPS\Api\OAuthClient::load( \IPS\Request::i()->client_id );
\IPS\Db::i()->delete( 'core_oauth_server_access_tokens', array( 'client_id=? AND member_id=?', $client->client_id, \IPS\Member::loggedIn()->member_id ) );
\IPS\Db::i()->update( 'core_oauth_server_access_tokens', array( 'status' => 'revoked' ), array( 'client_id=? AND member_id=?', $client->client_id, \IPS\Member::loggedIn()->member_id ) );
\IPS\Member::loggedIn()->logHistory( 'core', 'oauth', array( 'type' => 'revoked_access_token', 'client' => $client->client_id ) );
}
catch ( \Exception $e ) { }
@@ -280,6 +280,8 @@ class _warnings extends \IPS\Content\Controller
'remove' => $remove,
'remove_override' => TRUE,
'notes' => NULL,
'cheev_point_reduction' => 0,
'cheev_override' => TRUE
) );
}
@@ -313,6 +315,8 @@ class _warnings extends \IPS\Content\Controller
'remove' => $remove,
'remove_override' => $reason->remove_override,
'notes' => $reason->notes,
'cheev_point_reduction' => $reason->cheev_point_reduction,
'cheev_override' => $reason->cheev_override
) );
}
@@ -324,6 +328,8 @@ class _warnings extends \IPS\Content\Controller
'remove' => $remove,
'remove_override' => FALSE,
'notes' => NULL,
'cheev_point_reduction' => 0,
'cheev_override' => FALSE
) );
}
}