Version 4.7.23
This commit is contained in:
1 parent
7124a02564
commit
25ddeb65d6
1791 files changed
+76990
-44452
No files matched your search
@@ -156,7 +156,10 @@ class _directory extends \IPS\Dispatcher\Controller
|
||||
}
|
||||
}
|
||||
}
|
||||
$baseUrl = $baseUrl->setQueryString( 'f' . $field->id, array_keys( $filters[ $field->id ] ) );
|
||||
if( isset( $filters[ $field->id ] ) )
|
||||
{
|
||||
$baseUrl = $baseUrl->setQueryString( 'f' . $field->id, array_keys( $filters[ $field->id ] ) );
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -249,7 +252,7 @@ class _directory extends \IPS\Dispatcher\Controller
|
||||
}
|
||||
|
||||
\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 );
|
||||
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate('clubs')->directory( $featuredClubs, $allClubs, $pagination, $baseUrl, $sortOption, $myClubsActivity, $mapMarkers, $view, $mineOnly );
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1344,6 +1344,11 @@ class _view extends \IPS\Helpers\CoverPhoto\Controller
|
||||
/* Find the purchase */
|
||||
foreach ( \IPS\core\extensions\nexus\Item\ClubMembership::getPurchases( \IPS\nexus\Customer::loggedIn(), $this->club->id ) as $purchase )
|
||||
{
|
||||
if ( $invoice = $purchase->invoice_pending )
|
||||
{
|
||||
\IPS\Output::i()->redirect( $invoice->checkoutUrl() );
|
||||
}
|
||||
|
||||
\IPS\Output::i()->redirect( $purchase->url()->setQueryString( array( 'do' => 'renew', 'cycles' => 1 ) )->csrf() );
|
||||
}
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@ class _contact extends \IPS\Dispatcher\Controller
|
||||
\IPS\Output::i()->bodyClasses[] = 'ipsLayout_minimal';
|
||||
|
||||
$form = new \IPS\Helpers\Form( 'contact', 'send' );
|
||||
$form->hiddenValues['contact_referrer'] = (string) \IPS\Request::i()->referrer();
|
||||
$form->class = 'ipsForm_vertical';
|
||||
|
||||
$form->add( new \IPS\Helpers\Form\Editor( 'contact_text', NULL, TRUE, array(
|
||||
@@ -71,7 +72,7 @@ class _contact extends \IPS\Dispatcher\Controller
|
||||
{
|
||||
$form->add( new \IPS\Helpers\Form\Text( 'contact_name', NULL, TRUE ) );
|
||||
$form->add( new \IPS\Helpers\Form\Email( 'email_address', NULL, TRUE, array( 'bypassProfanity' => \IPS\Helpers\Form\Text::BYPASS_PROFANITY_ALL ) ) );
|
||||
if ( \IPS\Settings::i()->bot_antispam_type !== 'none' and \IPS\Settings::i()->guest_captcha )
|
||||
if ( \IPS\Settings::i()->bot_antispam_type !== 'none' )
|
||||
{
|
||||
$form->add( new \IPS\Helpers\Form\Captcha );
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ class _popular extends \IPS\Dispatcher\Controller
|
||||
}
|
||||
}
|
||||
|
||||
if ( \IPS\Application::appIsEnabled('cloud') and \IPS\cloud\Application::featureIsEnabled('trending') )
|
||||
if ( \IPS\Application::appIsEnabled('cloud') and \IPS\cloud\Application::trendingIsEnabled( alwaysOn: FALSE ) )
|
||||
{
|
||||
$tabs[] = 'trending';
|
||||
}
|
||||
@@ -277,25 +277,22 @@ class _popular extends \IPS\Dispatcher\Controller
|
||||
$areas[ $item::$application . '-' . $item::reactionType() ] = array( $item, \IPS\Member::loggedIn()->language()->addToStack( "{$item::$title}_pl" ) );
|
||||
}
|
||||
|
||||
if ( isset( $item::$commentClass ) )
|
||||
if ( $item::supportsComments( \IPS\Member::loggedIn() ) and $commentClass = $item::$commentClass and \IPS\IPS::classUsesTrait( $commentClass, 'IPS\Content\Reactable' ) )
|
||||
{
|
||||
$commentClass = $item::$commentClass;
|
||||
if ( \IPS\IPS::classUsesTrait( $commentClass, 'IPS\Content\Reactable' ) )
|
||||
$supportsComments = \IPS\IPS::classUsesTrait( $commentClass, 'IPS\Content\Reactable' ) and $item::supportsComments( \IPS\Member::loggedIn() );
|
||||
if ( $supportsComments )
|
||||
{
|
||||
$areas[ $item::$application . '-' . $commentClass::reactionType() ] = array( $commentClass, \IPS\Member::loggedIn()->language()->addToStack( "{$commentClass::$title}_pl" ) );
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $item::$reviewClass ) )
|
||||
if ( $item::supportsReviews( \IPS\Member::loggedIn() ) and $reviewClass = $item::$reviewClass and \IPS\IPS::classUsesTrait( $reviewClass, 'IPS\Content\Reactable' ) )
|
||||
{
|
||||
$reviewClass = $item::$reviewClass;
|
||||
if ( \IPS\IPS::classUsesTrait( $reviewClass, 'IPS\Content\Reactable' ) )
|
||||
{
|
||||
$areas[ $item::$application . '-' . $reviewClass::reactionType() ] = array( $reviewClass, \IPS\Member::loggedIn()->language()->addToStack( "{$reviewClass::$title}_pl" ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$form = new \IPS\Helpers\Form( 'popular_date', 'continue' );
|
||||
$form->class = 'ipsForm_vertical';
|
||||
$customStart = ( isset( \IPS\Request::i()->custom_date_start ) and \is_numeric( \IPS\Request::i()->custom_date_start ) ) ? (int) \IPS\Request::i()->custom_date_start : NULL;
|
||||
|
||||
@@ -1003,6 +1003,7 @@ class _streams extends \IPS\Dispatcher\Controller
|
||||
protected function _rebuildStreams()
|
||||
{
|
||||
$default = \IPS\Member::loggedIn()->defaultStream;
|
||||
/* @note SELECT_FROM_WRITE_SERVER: Avoid race conditions when the writer has the new stream in the table, but the reader may not */
|
||||
\IPS\Member::loggedIn()->member_streams = json_encode( array( 'default' => $default, 'streams' => iterator_to_array( \IPS\Db::i()->select( 'id, title', 'core_streams', array( '`member`=?', \IPS\Member::loggedIn()->member_id ), NULL, NULL, NULL, NULL, \IPS\Db::SELECT_FROM_WRITE_SERVER )->setKeyField('id')->setValueField('title') ) ) );
|
||||
\IPS\Member::loggedIn()->save();
|
||||
}
|
||||
|
||||
@@ -1,155 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief account
|
||||
* @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 21 Aug 2023
|
||||
*/
|
||||
|
||||
namespace IPS\core\modules\front\hive;
|
||||
|
||||
use IPS\core\Hive;
|
||||
use IPS\Db;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Member;
|
||||
use IPS\Output;
|
||||
use IPS\Session;
|
||||
use IPS\Settings;
|
||||
use IPS\Theme;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* account
|
||||
*/
|
||||
class _account extends \IPS\Dispatcher\Controller
|
||||
{
|
||||
/**
|
||||
* Execute
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
|
||||
parent::execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* ...
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function manage()
|
||||
{
|
||||
Output::i()->error( 'node_error', '2HV100/2', 404, '' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Follow Community
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function follow()
|
||||
{
|
||||
/* Check Hive is Enabled */
|
||||
if( empty( Settings::i()->hive_key ) )
|
||||
{
|
||||
Output::i()->error( 'node_error', '2HV100/3', 404, '' );
|
||||
}
|
||||
|
||||
if( Member::loggedIn()->member_id )
|
||||
{
|
||||
$form = new \IPS\Helpers\Form( 'hive', 'hive_subscribe_button' );
|
||||
$form->class = 'ipsForm_vertical ipsType_center ipsForm_noLabels';
|
||||
$form->add( new \IPS\Helpers\Form\Email( 'hive_subscriber_email_address', Member::loggedIn()->email ?? '', TRUE, [ 'placeholder' => Member::loggedIn()->language()->addToStack('email_address')] ) );
|
||||
|
||||
if ( $values = $form->values() )
|
||||
{
|
||||
Session::i()->csrfCheck();
|
||||
|
||||
try
|
||||
{
|
||||
$response = Hive::api( 'subscribe', [
|
||||
'site_member_id' => Member::loggedIn()->member_id ?? 0,
|
||||
'group_hash' => Member::loggedin()->member_id ? Hive::groupHash( Member::loggedin() ) : 'guest',
|
||||
'member_email' => $values['hive_subscriber_email_address'],
|
||||
] );
|
||||
|
||||
/* Save subscribe */
|
||||
if ( Member::loggedIn()->member_id )
|
||||
{
|
||||
try
|
||||
{
|
||||
Db::i()->insert( 'core_hive_subscribers', [ 'member_id' => Member::loggedIn()->member_id, 'subscribe_date' => time() ] );
|
||||
}
|
||||
catch ( Db\Exception $e ){}
|
||||
}
|
||||
|
||||
if ( !empty( $response['redirect_url'] ) )
|
||||
{
|
||||
Output::i()->redirect( Url::external( $response['redirect_url'] ) );
|
||||
}
|
||||
}
|
||||
catch ( \IPS\Http\Url\Exception|\LogicException $e )
|
||||
{
|
||||
$form->error = $e->getMessage();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$form = Theme::i()->getTemplate( 'hive', 'core', 'front' )->signin();
|
||||
}
|
||||
|
||||
Output::i()->title = Member::loggedIn()->language()->addToStack('hive_subscribe_to_hive', NULL, [ 'sprintf' => Settings::i()->board_name ]);
|
||||
Output::i()->output = Theme::i()->getTemplate( 'hive', 'core', 'front' )->follow( $form );
|
||||
}
|
||||
|
||||
/**
|
||||
* Anonymously follow community
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function anonymousFollow()
|
||||
{
|
||||
Output::i()->pageCaching = FALSE;
|
||||
|
||||
/* Check Hive is Enabled */
|
||||
if( empty( Settings::i()->hive_key ) )
|
||||
{
|
||||
Output::i()->error( 'node_error', '2HV100/4', 404, '' );
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$response = Hive::api( 'subscribe', [
|
||||
'site_member_id' => 0,
|
||||
'group_hash' => 'guest',
|
||||
'member_email' => false,
|
||||
] );
|
||||
|
||||
/* Save subscribe */
|
||||
if ( !empty( $response['redirect_url'] ) )
|
||||
{
|
||||
Output::i()->redirect( Url::external( $response['redirect_url'] ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new \LogicException( 'unknown_error' );
|
||||
}
|
||||
}
|
||||
catch ( \IPS\Http\Url\Exception|\LogicException $e )
|
||||
{
|
||||
Output::i()->error( $e->getMessage(), '2HV100/1', 403, '' );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,402 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief content
|
||||
* @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 21 Aug 2023
|
||||
*/
|
||||
|
||||
namespace IPS\core\modules\front\hive;
|
||||
|
||||
use IPS\core\Hive;
|
||||
use IPS\Content\Item;
|
||||
use IPS\core\Stream;
|
||||
use IPS\Db;
|
||||
use IPS\File;
|
||||
use IPS\File\Exception;
|
||||
use IPS\Http\Url;
|
||||
use IPS\IPS;
|
||||
use IPS\Member;
|
||||
use IPS\Output;
|
||||
use IPS\Patterns\ActiveRecordIterator;
|
||||
use IPS\Request;
|
||||
use IPS\Settings;
|
||||
use Jose\Component\Checker\AudienceChecker;
|
||||
use Jose\Component\Checker\ClaimCheckerManager;
|
||||
use Jose\Component\Checker\ExpirationTimeChecker;
|
||||
use Jose\Component\Checker\InvalidClaimException;
|
||||
use Jose\Component\Checker\IssuedAtChecker;
|
||||
use Jose\Component\Checker\IssuerChecker;
|
||||
use Jose\Component\Checker\MissingMandatoryClaimException;
|
||||
use Jose\Component\Checker\NotBeforeChecker;
|
||||
use Jose\Component\Signature\Serializer\CompactSerializer;
|
||||
|
||||
/* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* content
|
||||
*/
|
||||
class _content extends \IPS\Dispatcher\Controller
|
||||
{
|
||||
public function __construct( $url = NULL )
|
||||
{
|
||||
parent::__construct( $url );
|
||||
|
||||
IPS::$PSR0Namespaces['Jose'] = \IPS\ROOT_PATH . '/system/3rd_party/JwtFramework/src';
|
||||
IPS::$PSR0Namespaces['Base64Url'] = \IPS\ROOT_PATH .'/system/3rd_party/Base64Url';
|
||||
|
||||
\IPS\Output::i()->pageCaching = FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
if( isset( Request::i()->click ) )
|
||||
{
|
||||
$this->_doClick();
|
||||
}
|
||||
elseif( isset( Request::i()->follow ) )
|
||||
{
|
||||
$this->_doFollowCommunity();
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Don't feed any content if disabled */
|
||||
if( !Settings::i()->hive_enabled )
|
||||
{
|
||||
Output::i()->json( [ 'error' => 'Community Hive not enabled' ], 404 );
|
||||
}
|
||||
|
||||
$this->_verifyJwt();
|
||||
}
|
||||
|
||||
parent::execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* ...
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function manage()
|
||||
{
|
||||
try
|
||||
{
|
||||
match ( $this->payload['request_type'] )
|
||||
{
|
||||
'content' => $this->_getContent(),
|
||||
'sync' => $this->_syncMembers(),
|
||||
'unfollow' => $this->_unfollow()
|
||||
};
|
||||
}
|
||||
catch( \UnhandledMatchError $e )
|
||||
{
|
||||
Output::i()->json( array( 'error' => 'Invalid request type' ), 400 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process Hive click and redirect
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function _doClick()
|
||||
{
|
||||
try
|
||||
{
|
||||
$string = base64_decode( Request::i()->click );
|
||||
$result = [];
|
||||
parse_str( $string, $result );
|
||||
|
||||
if( empty( $result['key2' ] ) )
|
||||
{
|
||||
throw new \UnexpectedValueException( 'key2 missing' );
|
||||
}
|
||||
|
||||
$item = explode( '/', $result['key2'] );
|
||||
|
||||
if( \count( $item ) !== 2 )
|
||||
{
|
||||
throw new \UnexpectedValueException( 'key2 format unexpected' );
|
||||
}
|
||||
|
||||
if( !class_exists( $item[0], TRUE ) )
|
||||
{
|
||||
throw new \UnexpectedValueException( 'key2 data unexpected' );
|
||||
}
|
||||
|
||||
$object = $item[0]::load( (int) $item[1] );
|
||||
|
||||
if( !( $object instanceof \IPS\Content ) )
|
||||
{
|
||||
throw new \UnexpectedValueException( 'key2 data unexpected 2' );
|
||||
}
|
||||
|
||||
Output::i()->redirect( $object->url() );
|
||||
}
|
||||
catch( \UnexpectedValueException | \OutOfRangeException $e )
|
||||
{
|
||||
Output::i()->redirect( Url::internal( '' ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect to Hive follow page
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function _doFollowCommunity()
|
||||
{
|
||||
Output::i()->redirect( Url::internal( 'app=core&module=hive&controller=account&do=follow', 'front', 'hive_follow' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get content for Hive
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function _getContent()
|
||||
{
|
||||
/* Make sure we have a valid payload */
|
||||
if( ! isset ( $this->payload['group_hash'] ) )
|
||||
{
|
||||
Output::i()->json( [ 'error' => 'Missing Group Hash' ], 400 );
|
||||
}
|
||||
|
||||
/* Use new member object for permissions */
|
||||
$member = new Member;
|
||||
|
||||
/* Set secondary groups */
|
||||
$member->member_group_id = Settings::i()->guest_group;
|
||||
if( $this->payload['group_hash'] !== 'guest' )
|
||||
{
|
||||
$member->mgroup_others = $this->payload['group_hash'];
|
||||
}
|
||||
|
||||
$stream = Stream::allActivityStream();
|
||||
$stream->date_relative_days = 365;
|
||||
$stream->id = 0;
|
||||
$stream->include_comments = TRUE;
|
||||
$stream->baseUrl = Url::internal( "app=core&module=discover&controller=streams", 'front', 'discover_all' );
|
||||
|
||||
/* Content Limitations */
|
||||
$settings = json_decode( Settings::i()->hive_content, TRUE );
|
||||
if( $settings['content_classes'] !== '*' )
|
||||
{
|
||||
$stream->classes = $settings['content_classes'];
|
||||
}
|
||||
else
|
||||
{
|
||||
$stream->classes = '';
|
||||
foreach ( \IPS\Content::routedClasses( TRUE, FALSE, TRUE ) as $class )
|
||||
{
|
||||
if ( is_subclass_of( $class, 'IPS\Content\Searchable' ) and isset( $class::$databaseColumnMap['date'] ) )
|
||||
{
|
||||
$stream->classes .= ',' . $class;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Container - Stream wants JSON and we have an array... */
|
||||
$stream->containers = json_encode( $settings['content_containers'] );
|
||||
|
||||
$query = $stream->query( $member )->setLimit( 10 );
|
||||
$results = $query->search();
|
||||
|
||||
$return = [];
|
||||
foreach( $results as $comment )
|
||||
{
|
||||
$commentData = $comment->asArray();
|
||||
$commentClass = $commentData['indexData']['index_class'];
|
||||
$itemClass = $commentClass::$itemClass ?? $commentClass;
|
||||
$item = $itemClass::load( $commentData['indexData']['index_item_id'] );
|
||||
|
||||
/* Attachments */
|
||||
$attachment = $fileObj = NULL;
|
||||
try
|
||||
{
|
||||
if( !empty( $commentData['itemData']['attachedImages'] ) )
|
||||
{
|
||||
$fileObj = File::get( $commentData['itemData']['attachedImages'][0]['extension'], $commentData['itemData']['attachedImages'][0]['thumb_location'] ?: $commentData['itemData']['attachedImages'][0]['location'] );
|
||||
}
|
||||
elseif( is_subclass_of( $commentData['indexData']['index_class'], 'IPS\Content\Item' ) AND $contentImage = $item->contentImages( 1, TRUE ) )
|
||||
{
|
||||
$attachType = key( $contentImage[0] );
|
||||
$fileObj = File::get( $attachType, $contentImage[0][ $attachType ] );
|
||||
}
|
||||
|
||||
if( $fileObj !== NULL )
|
||||
{
|
||||
if ( $fileObj->isImage() )
|
||||
{
|
||||
$fileContents = $fileObj->contents();
|
||||
if ( \strlen( $fileContents ) > 1000000 )
|
||||
{
|
||||
throw new \OutOfRangeException;
|
||||
}
|
||||
|
||||
$attachment = [
|
||||
'name' => $fileObj->originalFilename,
|
||||
'file' => base64_encode( $fileContents )
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
catch( Exception | \UnderflowException | \OutOfRangeException $e ) { }
|
||||
|
||||
/* Truncate content */
|
||||
if( mb_strlen( $commentData['indexData']['index_content'] ) > 500 )
|
||||
{
|
||||
$commentData['indexData']['index_content'] = trim( mb_substr( $commentData['indexData']['index_content'], 0, 500 ) ) . '...';
|
||||
}
|
||||
$classObj = $commentClass::load( $commentData['indexData']['index_object_id'] );
|
||||
$return[] = array(
|
||||
'title' => $item->mapped('title'),
|
||||
'content' => $commentData['indexData']['index_content'],
|
||||
'date' => $commentData['indexData']['index_date_commented'],
|
||||
'author' => $classObj->isAnonymous() ? \IPS\Member::loggedIn()->language()->get( "post_anonymously_placename" ) : $commentData['authorData']['name'],
|
||||
'key1' => $itemClass . '/' . $commentData['indexData']['index_item_id'],
|
||||
'key2' => $commentClass . '/' . $commentData['indexData']['index_object_id'],
|
||||
'replies' => ( $item instanceof Item ) ? $item->commentCount() : NULL,
|
||||
'reactions' => IPS::classUsesTrait( $itemClass, 'IPS\Content\Reactable' ) ? $item->reactionCount() : NULL,
|
||||
'image' => $attachment
|
||||
);
|
||||
}
|
||||
|
||||
Output::i()->json([
|
||||
'results' => $return
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync Hive Membership data
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function _syncMembers()
|
||||
{
|
||||
if( !isset( $this->payload['sync_data'] ) OR !\count( $this->payload['sync_data'] ) )
|
||||
{
|
||||
Output::i()->json( [ 'error' => 'Missing sync data' ], 400 );
|
||||
}
|
||||
|
||||
$memberIds = array_keys( $this->payload['sync_data'] );
|
||||
$memberData = new ActiveRecordIterator( Db::i()->select( '*', 'core_members', [ Db::i()->in( 'member_id', $memberIds ) ] ), 'IPS\Member' );
|
||||
$respond = [];
|
||||
$seen = [];
|
||||
|
||||
foreach( $memberData as $member )
|
||||
{
|
||||
$generatedHash = Hive::groupHash( $member );
|
||||
|
||||
if( $generatedHash !== $this->payload['sync_data'][ $member->member_id ] )
|
||||
{
|
||||
$respond[ $member->member_id ] = $generatedHash;
|
||||
}
|
||||
$seen[] = $member->member_id;
|
||||
}
|
||||
|
||||
/* Record subscribe confirm */
|
||||
Db::i()->update( 'core_hive_subscribers', [ 'subscribe_confirmed' => 1 ], [ [ 'subscribe_confirmed=?', 0 ], [ Db::i()->in( 'member_id', $memberIds ) ] ] );
|
||||
|
||||
/* Those that haven't been seen, thus deleted */
|
||||
$deleted = array_diff( $memberIds, $seen );
|
||||
|
||||
foreach( $deleted as $del )
|
||||
{
|
||||
$respond[ $del ] = 'guest';
|
||||
}
|
||||
|
||||
Output::i()->json( $respond );
|
||||
}
|
||||
|
||||
/**
|
||||
* Unfollow
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function _unfollow()
|
||||
{
|
||||
Db::i()->delete( 'core_hive_subscribers', [ 'member_id=?', (int) $this->payload['member_id'] ] );
|
||||
Output::i()->json( 'ok' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify JWT
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function _verifyJwt()
|
||||
{
|
||||
/* Make sure the request is PUT */
|
||||
if( ! isset( $_SERVER['REQUEST_METHOD'] ) OR mb_strtoupper( $_SERVER['REQUEST_METHOD'] ) !== 'POST' )
|
||||
{
|
||||
Output::i()->json( array( 'error' => 'Invalid HTTP Method' ), 400 );
|
||||
}
|
||||
|
||||
/* Make sure we have the hive key (activated) */
|
||||
if( ! Settings::i()->hive_enabled )
|
||||
{
|
||||
Output::i()->json( array( 'error' => 'Community Hive not enabled' ), 404 );
|
||||
}
|
||||
|
||||
/* Do we have the JWT */
|
||||
$token = file_get_contents('php://input');
|
||||
if( empty( $token ) )
|
||||
{
|
||||
Output::i()->json( array( 'error' => 'Missing JWT' ), 401 );
|
||||
}
|
||||
|
||||
$jwk = new \Jose\Component\Core\JWK([
|
||||
'kty' => 'oct',
|
||||
'k' => base64_encode( Settings::i()->hive_key )
|
||||
]);
|
||||
|
||||
$jwsVerifier = new \Jose\Component\Signature\JWSVerifier(
|
||||
new \Jose\Component\Core\AlgorithmManager([ new \Jose\Component\Signature\Algorithm\HS256() ])
|
||||
);
|
||||
|
||||
$jwsCompactSerializer = new CompactSerializer();
|
||||
|
||||
$data = $jwsCompactSerializer->unserialize( $token );
|
||||
if( !$jwsVerifier->verifyWithKey( $data, $jwk, 0 ) )
|
||||
{
|
||||
Output::i()->json( array( 'error' => 'Invalid JWT' ), 401 );
|
||||
}
|
||||
|
||||
$this->payload = json_decode( $data->getPayload(), true );
|
||||
|
||||
$claimCheckerManager = new ClaimCheckerManager(
|
||||
[
|
||||
new IssuerChecker( ['communityhive'] ),
|
||||
new AudienceChecker( rtrim( Settings::i()->base_url, '/' ) ),
|
||||
new IssuedAtChecker( 1000 ),
|
||||
new NotBeforeChecker( 1000 ),
|
||||
new ExpirationTimeChecker( 1000 )
|
||||
]
|
||||
);
|
||||
|
||||
try
|
||||
{
|
||||
$claimCheckerManager->check( $this->payload, [ 'iss', 'sub', 'exp', 'aud', 'nbf', 'iat' ] );
|
||||
}
|
||||
catch( MissingMandatoryClaimException | InvalidClaimException $e )
|
||||
{
|
||||
Output::i()->json( array( 'message' => $e->getMessage() ), 400 );
|
||||
}
|
||||
|
||||
parent::execute();
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,9 @@
|
||||
namespace IPS\core\modules\front\members;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
|
||||
use IPS\Member;
|
||||
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
@@ -57,12 +60,12 @@ class _profile extends \IPS\Helpers\CoverPhoto\Controller
|
||||
if( !\IPS\Request::i()->isAjax() )
|
||||
{
|
||||
/* Don't index new empty profiles */
|
||||
if( ! $this->member->member_posts )
|
||||
{
|
||||
\IPS\Output::i()->metaTags['robots'] = 'noindex, follow';
|
||||
}
|
||||
|
||||
\IPS\Output::i()->linkTags['canonical'] = (string) $this->member->url();
|
||||
if( ! $this->shouldBeIndexed() )
|
||||
{
|
||||
\IPS\Output::i()->metaTags['robots'] = 'noindex, follow';
|
||||
}
|
||||
|
||||
\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' ) );
|
||||
@@ -322,8 +325,11 @@ class _profile extends \IPS\Helpers\CoverPhoto\Controller
|
||||
$page = 1;
|
||||
}
|
||||
|
||||
$clubsCount = \IPS\Member\Club::clubs( \IPS\Member::loggedIn(), array( ( $page - 1 ) * $perPage, $perPage ), 'last_activity', $this->member, array(), \IPS\Member::loggedIn()->modPermission( 'can_access_all_clubs' ) ? NULL : "( show_membertab = 'nonmember' OR show_membertab IS NULL )", TRUE );
|
||||
$allClubs = \IPS\Member\Club::clubs( \IPS\Member::loggedIn(), array( ( $page - 1 ) * $perPage, $perPage ), 'last_activity', $this->member, array(), \IPS\Member::loggedIn()->modPermission( 'can_access_all_clubs' ) ? NULL : "( show_membertab = 'nonmember' OR show_membertab IS NULL )" );
|
||||
/* Show all clubs this member belongs to if the viewer is a moderator with permissions to access all clubs or if the viewer is on his own profile */
|
||||
$extraWhere = ( Member::loggedIn()->member_id === $this->member->member_id OR \IPS\Member::loggedIn()->modPermission( 'can_access_all_clubs' ) ) ? NULL : "( show_membertab = 'nonmember' OR show_membertab IS NULL )";
|
||||
|
||||
$clubsCount = \IPS\Member\Club::clubs( \IPS\Member::loggedIn(), array( ( $page - 1 ) * $perPage, $perPage ), 'last_activity', $this->member, array(), $extraWhere, TRUE );
|
||||
$allClubs = \IPS\Member\Club::clubs( \IPS\Member::loggedIn(), array( ( $page - 1 ) * $perPage, $perPage ), 'last_activity', $this->member, array(), $extraWhere );
|
||||
$pagination = \IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->pagination( $baseUrl, ( ceil( $clubsCount / $perPage ) ), $page, $perPage );
|
||||
|
||||
\IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'styles/clubs.css', 'core', 'front' ) );
|
||||
@@ -354,7 +360,7 @@ class _profile extends \IPS\Helpers\CoverPhoto\Controller
|
||||
}
|
||||
|
||||
/* If this is AJAX request to change the tab, just display that */
|
||||
if ( \IPS\Request::i()->isAjax() and isset( \IPS\Request::i()->tab ) )
|
||||
if ( \IPS\Request::i()->isAjax() and isset( \IPS\Request::i()->tab ) and !isset( \IPS\Request::i()->entireSection ) )
|
||||
{
|
||||
\IPS\Output::i()->sendOutput( \IPS\Theme::i()->getTemplate( 'global', 'core' )->blankTemplate( $tabContents ) );
|
||||
}
|
||||
@@ -428,6 +434,11 @@ class _profile extends \IPS\Helpers\CoverPhoto\Controller
|
||||
'@type' => "ProfilePage",
|
||||
'url' => (string) $this->member->url(),
|
||||
'name' => $this->member->name,
|
||||
'mainEntity'=> [
|
||||
'@type' => 'Person',
|
||||
'name' => $this->member->name,
|
||||
'identifier' => $this->member->member_id,
|
||||
],
|
||||
'primaryImageOfPage' => array(
|
||||
'@type' => "ImageObject",
|
||||
'contentUrl' => (string) $this->member->get_photo( TRUE, TRUE ),
|
||||
@@ -1988,7 +1999,6 @@ class _profile extends \IPS\Helpers\CoverPhoto\Controller
|
||||
{
|
||||
/* 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 ) );
|
||||
@@ -1999,6 +2009,11 @@ class _profile extends \IPS\Helpers\CoverPhoto\Controller
|
||||
}
|
||||
}
|
||||
|
||||
if( !count( $types ) )
|
||||
{
|
||||
\IPS\Output::i()->error( 'no_module_permission', '2C138/T', 403, '' );
|
||||
}
|
||||
|
||||
/* What type are we looking at? */
|
||||
$currentType = NULL;
|
||||
if ( isset( \IPS\Request::i()->type ) AND array_key_exists( \IPS\Request::i()->type, $types ) )
|
||||
@@ -2236,4 +2251,19 @@ class _profile extends \IPS\Helpers\CoverPhoto\Controller
|
||||
\IPS\Output::i()->redirect( $content->url(), 'recognize_removed_success' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the member profile be indexed?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function shouldBeIndexed(): bool
|
||||
{
|
||||
/* Don't index new empty profiles */
|
||||
if( ! $this->member->member_posts )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
@@ -45,12 +45,16 @@ class _search extends \IPS\Dispatcher\Controller
|
||||
*/
|
||||
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 ) )
|
||||
try
|
||||
{
|
||||
header('HTTP/1.1 304 Not Modified' );
|
||||
exit;
|
||||
/* 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;
|
||||
}
|
||||
}
|
||||
catch( \Exception $e ){}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1066,7 +1070,12 @@ class _search extends \IPS\Dispatcher\Controller
|
||||
/* Fields */
|
||||
foreach ( $fields as $id => $field )
|
||||
{
|
||||
|
||||
/* Only show to non-staff if available to view by all. */
|
||||
if ( \IPS\core\ProfileFields\Field::load( $id )->member_hide != 'all' AND ( !\IPS\Member::loggedIn()->isAdmin() OR !\IPS\Member::loggedIn()->modPermissions() ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Alias the lang keys */
|
||||
$realLangKey = "core_pfield_{$id}";
|
||||
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
|
||||
namespace IPS\core\modules\front\system;
|
||||
|
||||
use IPS\Http\Url;
|
||||
use IPS\Output;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
@@ -103,58 +106,64 @@ class _ajax extends \IPS\Dispatcher\Controller
|
||||
|
||||
/* As you can insert "other media" from other apps (such as Downloads), we need to route the attachIDs appropriately. */
|
||||
$attachmentIds = array();
|
||||
|
||||
foreach( array_keys( \IPS\Request::i()->attachIDs ) as $attachId )
|
||||
{
|
||||
if( (int) $attachId == $attachId AND mb_strlen( (int) $attachId ) == mb_strlen( $attachId ) )
|
||||
{
|
||||
$attachmentIds[] = $attachId;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
$url = \IPS\Http\Url::createFromString( $attachId );
|
||||
|
||||
/* Get the "real" query string (whatever the query string is, plus what we can get from decoding the FURL) */
|
||||
$qs = array_merge( $url->queryString, $url->hiddenQueryString );
|
||||
|
||||
/* We need an app, and it needs to not be an RSS link */
|
||||
if ( !isset( $qs['app'] ) )
|
||||
{
|
||||
throw new \UnexpectedValueException;
|
||||
}
|
||||
|
||||
/* Load the application */
|
||||
$application = \IPS\Application::load( $qs['app'] );
|
||||
|
||||
/* Loop through our content classes and see if we can find one that matches */
|
||||
foreach ( $application->extensions( 'core', 'ContentRouter' ) as $key => $extension )
|
||||
{
|
||||
$classes = $extension->classes;
|
||||
|
||||
/* So for each of those... */
|
||||
foreach ( $classes as $class )
|
||||
if( \IPS\Request::i()->attachIDs )
|
||||
{
|
||||
foreach( array_keys( \IPS\Request::i()->attachIDs ) as $attachId )
|
||||
{
|
||||
if( (int) $attachId == $attachId and mb_strlen( (int) $attachId ) == mb_strlen( $attachId ) )
|
||||
{
|
||||
$attachmentIds[] = $attachId;
|
||||
}else
|
||||
{
|
||||
try
|
||||
{
|
||||
$url = \IPS\Http\Url::createFromString( $attachId );
|
||||
|
||||
/* Get the "real" query string (whatever the query string is, plus what we can get from decoding the FURL) */
|
||||
$qs = array_merge( $url->queryString, $url->hiddenQueryString );
|
||||
|
||||
/* We need an app, and it needs to not be an RSS link */
|
||||
if( !isset( $qs[ 'app' ] ) )
|
||||
{
|
||||
/* Try to load it */
|
||||
try
|
||||
throw new \UnexpectedValueException;
|
||||
}
|
||||
|
||||
/* Load the application */
|
||||
$application = \IPS\Application::load( $qs[ 'app' ] );
|
||||
|
||||
/* Loop through our content classes and see if we can find one that matches */
|
||||
foreach( $application->extensions( 'core', 'ContentRouter' ) as $key => $extension )
|
||||
{
|
||||
$classes = $extension->classes;
|
||||
|
||||
/* So for each of those... */
|
||||
foreach( $classes as $class )
|
||||
{
|
||||
$item = $class::loadFromURL( $url );
|
||||
|
||||
if( !$item->canView() )
|
||||
/* Try to load it */
|
||||
try
|
||||
{
|
||||
throw new \OutOfRangeException;
|
||||
}
|
||||
$item = $class::loadFromURL( $url );
|
||||
|
||||
/* If we're still here, we should be good. Any exceptions will have been caught by our general try/catch. */
|
||||
$toReturn[ $attachId ] = $item->getAttachmentInfo();
|
||||
break;
|
||||
if( !$item->canView() )
|
||||
{
|
||||
throw new \OutOfRangeException;
|
||||
}
|
||||
|
||||
/* If we're still here, we should be good. Any exceptions will have been caught by our general try/catch. */
|
||||
$toReturn[ $attachId ] = $item->getAttachmentInfo();
|
||||
break;
|
||||
}
|
||||
catch( \OutOfRangeException $e )
|
||||
{
|
||||
}
|
||||
}
|
||||
catch( \OutOfRangeException $e ){}
|
||||
}
|
||||
}
|
||||
catch( \Exception $e )
|
||||
{
|
||||
}
|
||||
}
|
||||
catch( \Exception $e ){}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -197,7 +206,7 @@ class _ajax extends \IPS\Dispatcher\Controller
|
||||
{
|
||||
try
|
||||
{
|
||||
if ( $loadedExtensions[ $map['location_key'] ]->attachmentPermissionCheck( $member, $map['id1'], $map['id2'], $map['id3'], $attachment ) )
|
||||
if ( method_exists( $loadedExtensions[ $map['location_key'] ], 'attachmentPermissionCheck') AND $loadedExtensions[ $map['location_key'] ]->attachmentPermissionCheck( $member, $map['id1'], $map['id2'], $map['id3'], $attachment ) )
|
||||
{
|
||||
$permission = TRUE;
|
||||
break;
|
||||
@@ -685,7 +694,10 @@ class _ajax extends \IPS\Dispatcher\Controller
|
||||
public function getCsrfKey()
|
||||
{
|
||||
/* Don't cache the CSRF key */
|
||||
\IPS\Output::i()->pageCaching = FALSE;
|
||||
\IPS\Output::setCacheTime( false );
|
||||
|
||||
/* Restrict endpoint to our origin JS */
|
||||
Output::i()->httpHeaders['Access-Control-Allow-Origin'] = Url::internal('')->data[ Url::COMPONENT_SCHEME ] . '://' . Url::internal('')->data[ Url::COMPONENT_HOST ];
|
||||
|
||||
if ( isset( \IPS\Request::i()->path ) )
|
||||
{
|
||||
@@ -717,7 +729,7 @@ class _ajax extends \IPS\Dispatcher\Controller
|
||||
}
|
||||
}
|
||||
|
||||
\IPS\Output::i()->json( [ 'key' => \IPS\Session::i()->csrfKey ] );
|
||||
\IPS\Output::i()->json( [ 'key' => \IPS\Session::i()->csrfKey, 'expiry' => time() + 500 ] );
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
namespace IPS\core\modules\front\system;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
|
||||
use IPS\core\Alerts\Alert;
|
||||
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
@@ -47,7 +50,7 @@ class _alerts extends \IPS\Dispatcher\Controller
|
||||
{
|
||||
$alert = \IPS\core\Alerts\Alert::load( \IPS\Request::i()->id );
|
||||
|
||||
if( $alert->reply == 2 and \IPS\Member::loggedIn()->member_id and \IPS\Member::loggedIn()->canUseMessenger() )
|
||||
if( $alert->reply == Alert::REPLY_REQUIRED and \IPS\Member::loggedIn()->member_id and \IPS\Member::loggedIn()->canUseMessenger() and \IPS\Member::load( $alert->member_id )->member_id )
|
||||
{
|
||||
\IPS\Output::i()->error( 'alert_cant_dismiss', '3C428/1', 403, '' );
|
||||
}
|
||||
|
||||
@@ -43,9 +43,18 @@ class _content extends \IPS\Dispatcher\Controller
|
||||
|
||||
try
|
||||
{
|
||||
$commentClass = $class::$commentClass;
|
||||
$item = $class::load( \IPS\Request::i()->content_id );
|
||||
|
||||
if( isset( $item::$archiveClass ) AND method_exists( $item, 'isArchived' ) AND $item->isArchived() )
|
||||
{
|
||||
$commentClass = $class::$archiveClass;
|
||||
}
|
||||
else
|
||||
{
|
||||
$commentClass = $class::$commentClass;
|
||||
}
|
||||
|
||||
$comment = $commentClass::load( \IPS\Request::i()->content_commentid );
|
||||
$item = $comment->item();
|
||||
}
|
||||
catch( \OutOfRangeException $ex )
|
||||
{
|
||||
|
||||
@@ -30,7 +30,7 @@ class _cookies extends \IPS\Dispatcher\Controller
|
||||
{
|
||||
parent::execute();
|
||||
|
||||
\IPS\Output::i()->pageCaching = FALSE;
|
||||
\IPS\Output::setCacheTime( false );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,5 +76,6 @@ class _cookies extends \IPS\Dispatcher\Controller
|
||||
\IPS\Output::i()->redirect( $url );
|
||||
}
|
||||
}
|
||||
\IPS\Output::i()->redirect( \IPS\Http\Url::internal(''));
|
||||
}
|
||||
}
|
||||
@@ -62,13 +62,30 @@ 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 );
|
||||
|
||||
/* Let's do some casting here */
|
||||
|
||||
\IPS\Request::i()->width = (int) \IPS\Request::i()->width;
|
||||
\IPS\Request::i()->height = (int) \IPS\Request::i()->height;
|
||||
\IPS\Request::i()->actualWidth = (int) \IPS\Request::i()->actualWidth;
|
||||
\IPS\Request::i()->actualHeight = (int) \IPS\Request::i()->actualHeight;
|
||||
$maxImageDims = array_map('intval', $maxImageDims);
|
||||
|
||||
foreach( array( 'width', 'height', 'actualWidth', 'actualHeight' ) as $key )
|
||||
{
|
||||
if( \IPS\Request::i()->$key <= 0 )
|
||||
{
|
||||
\IPS\Output::i()->error( 'invalid_image_dimensions', '2C270/2', 403, '' );
|
||||
}
|
||||
}
|
||||
|
||||
$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 )
|
||||
if( $maxImageDims[0] === 0 && $maxImageDims[1] === 0 )
|
||||
{
|
||||
$maxWidth = \IPS\Request::i()->actualWidth;
|
||||
$maxHeight = \IPS\Request::i()->actualHeight;
|
||||
$maxWidth = (int) \IPS\Request::i()->actualWidth;
|
||||
$maxHeight = (int) \IPS\Request::i()->actualHeight;
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -353,7 +370,7 @@ class _editor extends \IPS\Dispatcher\Controller
|
||||
try
|
||||
{
|
||||
$item = $class::load( \IPS\Request::i()->contentId );
|
||||
foreach( $item->mostRecent( \IPS\Request::i()->input ) AS $row )
|
||||
foreach( $item->mostRecent( \IPS\Request::i()->input, 10, FALSE ) AS $row )
|
||||
{
|
||||
$memberIds[] = $row->member_id;
|
||||
$results .= \IPS\Theme::i()->getTemplate( 'editor', 'core', 'global' )->mentionRow( $row );
|
||||
|
||||
@@ -57,6 +57,10 @@ class _embed extends \IPS\Content\Controller
|
||||
\IPS\Output::i()->js( 'js/commonEmbedHandler.js', 'core', 'interface' ),
|
||||
\IPS\Output::i()->js( 'js/externalEmbedHandler.js', 'core', 'interface' )
|
||||
);
|
||||
|
||||
/* We don't want search engines indexing this */
|
||||
\IPS\Output::i()->metaTags['robots'] = 'noindex';
|
||||
|
||||
/* Intentionally replace the cssFiles array with a single file here, since we don't need the complete CSS framework in external embeds */
|
||||
\IPS\Output::i()->cssFiles = \IPS\Theme::i()->css( 'styles/embeds.css', 'core', 'front' );
|
||||
\IPS\Output::i()->sendOutput( \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' )->embedExternal( $return, $js ), 200 );
|
||||
|
||||
@@ -34,7 +34,7 @@ class _login extends \IPS\Dispatcher\Controller
|
||||
*/
|
||||
protected function manage()
|
||||
{
|
||||
\IPS\Output::i()->pageCaching = FALSE;
|
||||
\IPS\Output::setCacheTime( false );
|
||||
|
||||
/* Init login class */
|
||||
$login = new \IPS\Login( \IPS\Http\Url::internal( "app=core&module=system&controller=login", 'front', 'login' ) );
|
||||
@@ -250,7 +250,7 @@ class _login extends \IPS\Dispatcher\Controller
|
||||
/* Otherwise show the reauthenticate form */
|
||||
\IPS\Output::i()->bodyClasses[] = 'ipsLayout_minimal';
|
||||
\IPS\Output::i()->sidebar['enabled'] = FALSE;
|
||||
\IPS\Output::i()->pageCaching = FALSE;
|
||||
\IPS\Output::setCacheTime( false );
|
||||
\IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('login');
|
||||
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'system' )->mergeSocialAccount( $handler, $member, $login, $error );
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
|
||||
namespace IPS\core\modules\front\system;
|
||||
|
||||
use IPS\Text\Encrypt;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
@@ -152,27 +154,29 @@ class _lostpass extends \IPS\Dispatcher\Controller
|
||||
}
|
||||
else
|
||||
{
|
||||
\IPS\Db::i()->update( 'core_validating', array( 'email_sent' => time() ), array( 'vid=?', $vid ) );
|
||||
$plainSecurityKey = \IPS\Login::generateRandomString();
|
||||
\IPS\Db::i()->update( 'core_validating', [ 'email_sent' => time(), 'security_key' => Encrypt::fromPlaintext( $plainSecurityKey )->tag() ], [ 'vid=?', $vid ] );
|
||||
}
|
||||
}
|
||||
catch ( \UnderflowException $e )
|
||||
{
|
||||
$vid = md5( $member->members_pass_hash . \IPS\Login::generateRandomString() );
|
||||
|
||||
\IPS\Db::i()->insert( 'core_validating', array(
|
||||
'vid' => $vid,
|
||||
'member_id' => $member->member_id,
|
||||
'entry_date' => time(),
|
||||
'lost_pass' => 1,
|
||||
'ip_address' => $member->ip_address,
|
||||
'email_sent' => time(),
|
||||
) );
|
||||
$plainSecurityKey = \IPS\Login::generateRandomString();
|
||||
\IPS\Db::i()->insert( 'core_validating', [
|
||||
'vid' => $vid,
|
||||
'member_id' => $member->member_id,
|
||||
'entry_date' => time(),
|
||||
'lost_pass' => 1,
|
||||
'ip_address' => $member->ip_address,
|
||||
'email_sent' => time(),
|
||||
'security_key' => Encrypt::fromPlaintext( $plainSecurityKey )->tag()
|
||||
] );
|
||||
}
|
||||
|
||||
/* Send email */
|
||||
if ( $sendEmail )
|
||||
{
|
||||
\IPS\Email::buildFromTemplate( 'core', 'lost_password_init', array( $member, $vid ), \IPS\Email::TYPE_TRANSACTIONAL )->send( $member );
|
||||
\IPS\Email::buildFromTemplate( 'core', 'lost_password_init', array( $member, $vid, $plainSecurityKey ), \IPS\Email::TYPE_TRANSACTIONAL )->send( $member );
|
||||
$message = "lost_pass_confirm";
|
||||
}
|
||||
else
|
||||
@@ -205,6 +209,12 @@ class _lostpass extends \IPS\Dispatcher\Controller
|
||||
\IPS\Output::i()->error( 'no_validation_key', '2S151/1', 410, '' );
|
||||
}
|
||||
|
||||
/* Check security key */
|
||||
if( !\IPS\Login::compareHashes( Encrypt::fromTag( $record['security_key'] )->decrypt(), \IPS\Request::i()->security_key ) )
|
||||
{
|
||||
\IPS\Output::i()->error( 'lostpass_invalid_security_key', '2S151/5', 403, '' );
|
||||
}
|
||||
|
||||
/* Show a nicer error message if their link has expired */
|
||||
if( $record['entry_date'] < \IPS\DateTime::create()->sub( new \DateInterval( 'PT1H' ) )->getTimestamp() )
|
||||
{
|
||||
@@ -223,7 +233,8 @@ class _lostpass extends \IPS\Dispatcher\Controller
|
||||
$member = \IPS\Member::load( $record['member_id'] );
|
||||
|
||||
/* Reset the failed logins storage - we don't need to save because the login handler will do that for us later */
|
||||
$member->failed_logins = array();
|
||||
\IPS\Db::i()->delete( 'core_login_failures', [ 'login_member_id=?', $member->member_id ] );
|
||||
$member->failed_login_count = 0;
|
||||
|
||||
/* Now reset the member's password. If no handlers accept the change, create a local password */
|
||||
if ( !$member->changePassword( $values['password'], 'lost' ) )
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief marketplace
|
||||
* @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 16 Jul 2020
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* marketplace
|
||||
*/
|
||||
class _marketplace extends \IPS\Dispatcher\Controller
|
||||
{
|
||||
/**
|
||||
* ...
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function manage()
|
||||
{
|
||||
if ( isset( \IPS\Request::i()->member ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
$token = \IPS\Db::i()->select( 'token', 'core_marketplace_tokens', array( 'id=?', \IPS\Request::i()->member ) )->first();
|
||||
if ( $token !== 'pending-' . \IPS\Request::i()->hash )
|
||||
{
|
||||
throw new \UnderflowException;
|
||||
}
|
||||
|
||||
\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 ) );
|
||||
|
||||
$response = 'OK';
|
||||
$responseCode = 200;
|
||||
}
|
||||
catch ( \UnderflowException $e )
|
||||
{
|
||||
$response = 'BAD_HASH';
|
||||
$responseCode = 403;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$response = 'NO_MEMBER';
|
||||
$responseCode = 404;
|
||||
}
|
||||
|
||||
\IPS\Output::i()->sendOutput( $response, $responseCode, 'text/plain' );
|
||||
}
|
||||
}
|
||||
@@ -176,6 +176,11 @@ class _metatags extends \IPS\Dispatcher\Controller
|
||||
{
|
||||
$manifest = json_decode( \IPS\Settings::i()->manifest_details, TRUE );
|
||||
|
||||
if( !$manifest )
|
||||
{
|
||||
$manifest = [];
|
||||
}
|
||||
|
||||
$output = array(
|
||||
'scope' => rtrim( \IPS\Settings::i()->base_url, '/' ) . '/',
|
||||
'name' => \IPS\Settings::i()->board_name,
|
||||
@@ -205,7 +210,7 @@ class _metatags extends \IPS\Dispatcher\Controller
|
||||
$file = \IPS\File::get( 'core_Icons', $v['url'] );
|
||||
|
||||
$output['icons'][] = array(
|
||||
'src' => (string) $file->url,
|
||||
'src' => (string) $file->url->setQueryString( 'v', $manifest['cache_key'] ),
|
||||
'type' => \IPS\File::getMimeType( $file->originalFilename ),
|
||||
'sizes' => $v['width'] . 'x' . $v['height'],
|
||||
'purpose' => 'any'
|
||||
@@ -225,7 +230,7 @@ class _metatags extends \IPS\Dispatcher\Controller
|
||||
$file = \IPS\File::get( 'core_Icons', $v['url'] );
|
||||
|
||||
$output['icons'][] = array(
|
||||
'src' => (string) $file->url,
|
||||
'src' => (string) $file->url->setQueryString( 'v', $manifest['cache_key'] ),
|
||||
'type' => \IPS\File::getMimeType( $file->originalFilename ),
|
||||
'sizes' => $v['width'] . 'x' . $v['height'],
|
||||
'purpose' => 'maskable'
|
||||
|
||||
@@ -11,6 +11,9 @@
|
||||
namespace IPS\core\modules\front\system;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
|
||||
use function md5;
|
||||
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
@@ -827,7 +830,7 @@ class _notifications extends \IPS\Dispatcher\Controller
|
||||
protected function unfollowFromEmail()
|
||||
{
|
||||
/* Logged in? */
|
||||
if ( \IPS\Member::loggedIn()->member_id )
|
||||
if ( \IPS\Member::loggedIn()->member_id and ! isset( \IPS\Request::i()->listunsubscribe ) )
|
||||
{
|
||||
/* Go to the normal page */
|
||||
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( "app=core&module=system&controller=notifications&do=follow&follow_app=". \IPS\Request::i()->follow_app ."&follow_area=". \IPS\Request::i()->follow_area ."&follow_id=" . \IPS\Request::i()->follow_id ) );
|
||||
@@ -835,7 +838,7 @@ class _notifications extends \IPS\Dispatcher\Controller
|
||||
|
||||
if ( ! empty( \IPS\Request::i()->gkey ) )
|
||||
{
|
||||
list( $followKey, $memberKey ) = explode( '-', \IPS\Request::i()->gkey );
|
||||
[ $followKey, $memberKey ] = explode( '-', \IPS\Request::i()->gkey );
|
||||
/* Do we follow it? */
|
||||
try
|
||||
{
|
||||
@@ -910,7 +913,29 @@ class _notifications extends \IPS\Dispatcher\Controller
|
||||
|
||||
/* Grab a count */
|
||||
$count = \IPS\Db::i()->select( 'COUNT(*)', 'core_follow', array( 'follow_member_id=? and follow_notify_freq != ?', $member->member_id, 'none' ) )->first();
|
||||
|
||||
|
||||
if ( isset( \IPS\Request::i()->listunsubscribe ) and \IPS\Request::i()->requestMethod() == 'POST' )
|
||||
{
|
||||
\IPS\Db::i()->update( 'core_follow', array( 'follow_notify_freq' => 'none' ), array( 'follow_id=? AND follow_member_id=?', $current['follow_id'], $member->member_id ) );
|
||||
|
||||
/* Unfollow club areas */
|
||||
if ( $class == "IPS\Member\Club" )
|
||||
{
|
||||
foreach ( $thing->nodes() as $node )
|
||||
{
|
||||
$itemClass = $node['node_class']::$contentItemClass;
|
||||
$followApp = $itemClass::$application;
|
||||
$followArea = mb_strtolower( mb_substr( $node['node_class'], mb_strrpos( $node['node_class'], '\\' ) + 1 ) );
|
||||
|
||||
\IPS\Db::i()->update( 'core_follow', array( 'follow_notify_freq' => 'none' ), array( 'follow_id=? AND follow_member_id=?', md5( $followApp . ';' . $followArea . ';' . $node['node_id'] . ';' . $member->member_id ), $member->member_id ) );
|
||||
}
|
||||
}
|
||||
|
||||
/* Done */
|
||||
\IPS\Output::i()->output = \IPS\Member::loggedIn()->language()->addToStack('unsubscribed');
|
||||
return;
|
||||
}
|
||||
|
||||
$form = new \IPS\Helpers\Form( 'unfollowFromEmail', 'update_follow' );
|
||||
$form->class = 'ipsForm_vertical';
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
|
||||
namespace IPS\core\modules\front\system;
|
||||
|
||||
use IPS\Text\Encrypt;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
@@ -61,7 +63,7 @@ class _register extends \IPS\Dispatcher\Controller
|
||||
\IPS\Output::i()->bodyClasses[] = 'ipsLayout_minimalNoHome';
|
||||
}
|
||||
\IPS\Output::i()->sidebar['enabled'] = FALSE;
|
||||
\IPS\Output::i()->pageCaching = FALSE;
|
||||
\IPS\Output::setCacheTime( false );
|
||||
\IPS\Output::i()->linkTags['canonical'] = (string) \IPS\Http\Url::internal( 'app=core&module=system&controller=register', 'front', 'register' );
|
||||
}
|
||||
|
||||
@@ -430,14 +432,20 @@ class _register extends \IPS\Dispatcher\Controller
|
||||
/* Set the form label */
|
||||
\IPS\Member::loggedIn()->language()->words['q_and_a'] = \IPS\Member::loggedIn()->language()->addToStack( 'core_question_and_answer_' . $question['qa_id'], FALSE );
|
||||
}
|
||||
|
||||
|
||||
$captcha = new \IPS\Helpers\Form\Captcha;
|
||||
|
||||
|
||||
/* If PBR request is from the last 5 minutes, don't ask for a captcha again */
|
||||
if( $postBeforeRegister !== NULL AND \IPS\DateTime::ts( $postBeforeRegister['timestamp'] )->add( new \DateInterval('PT5M' ) )->getTimestamp() > time() )
|
||||
{
|
||||
$captcha = '';
|
||||
}
|
||||
|
||||
if ( (string) $captcha !== '' )
|
||||
{
|
||||
$form->add( $captcha );
|
||||
}
|
||||
|
||||
|
||||
if ( $question OR (string) $captcha !== '' )
|
||||
{
|
||||
$form->addSeparator();
|
||||
@@ -600,7 +608,7 @@ class _register extends \IPS\Dispatcher\Controller
|
||||
$answers[ $v ] = array(
|
||||
'answer_question_id' => $v,
|
||||
'answer_member_id' => $member->member_id,
|
||||
'answer_answer' => \IPS\Text\Encrypt::fromPlaintext( $values[ 'security_question_a_' . $matches[1] ] )->tag()
|
||||
'answer_answer' => Encrypt::fromPlaintext( $values[ 'security_question_a_' . $matches[1] ] )->tag()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -683,6 +691,13 @@ class _register extends \IPS\Dispatcher\Controller
|
||||
}
|
||||
catch ( \UnderflowException $e )
|
||||
{
|
||||
/* Reset the validation flag and redirect the member to the index page if we have no row */
|
||||
if( \IPS\Member::loggedIn()->members_bitoptions['validating'] )
|
||||
{
|
||||
\IPS\Member::loggedIn()->members_bitoptions['validating'] = FALSE;
|
||||
\IPS\Member::loggedIn()->save();
|
||||
$this->_performRedirect( NULL, FALSE, 'validate_no_record' );
|
||||
}
|
||||
\IPS\Output::i()->error( 'validate_no_record', '2S129/4', 404, '' );
|
||||
}
|
||||
|
||||
@@ -727,10 +742,12 @@ class _register extends \IPS\Dispatcher\Controller
|
||||
{
|
||||
\IPS\Output::i()->error( \IPS\Member::loggedIn()->language()->addToStack('validation_email_rate_limit', FALSE, array( 'sprintf' => array( \IPS\DateTime::ts( $reg['email_sent'] )->relative( \IPS\DateTime::RELATIVE_FORMAT_LOWER ) ) ) ), '1C223/4', 429, '', array( 'Retry-After' => \IPS\DateTime::ts( $reg['email_sent'] )->add( new \DateInterval( 'PT15M' ) )->format('r') ) );
|
||||
}
|
||||
|
||||
/* Rotate security key */
|
||||
$plainSecurityKey = \IPS\Login::generateRandomString();
|
||||
\IPS\Db::i()->update( 'core_validating', [ 'security_key' => Encrypt::fromPlaintext( $plainSecurityKey )->tag(), 'email_sent' => time() ], [ 'vid=?', $reg['vid'] ] );
|
||||
|
||||
\IPS\Email::buildFromTemplate( 'core', $reg['email_chg'] ? 'email_change' : 'registration_validate', array( \IPS\Member::loggedIn(), $reg['vid'] ), \IPS\Email::TYPE_TRANSACTIONAL )->send( \IPS\Member::loggedIn() );
|
||||
|
||||
\IPS\Db::i()->update( 'core_validating', array( 'email_sent' => time() ), array( 'vid=?', $reg['vid'] ) );
|
||||
\IPS\Email::buildFromTemplate( 'core', $reg['email_chg'] ? 'email_change' : 'registration_validate', array( \IPS\Member::loggedIn(), $reg['vid'], $plainSecurityKey ), \IPS\Email::TYPE_TRANSACTIONAL )->send( \IPS\Member::loggedIn() );
|
||||
}
|
||||
|
||||
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=system&controller=register&do=validating', 'front', 'register' ), 'reg_email_resent' );
|
||||
@@ -758,6 +775,12 @@ class _register extends \IPS\Dispatcher\Controller
|
||||
$this->_performRedirect( NULL, FALSE, 'validate_no_record' );
|
||||
}
|
||||
|
||||
/* Check security key */
|
||||
if( !\IPS\Login::compareHashes( Encrypt::fromTag( $record['security_key'] )->decrypt(), \IPS\Request::i()->security_key ) )
|
||||
{
|
||||
\IPS\Output::i()->error( 'validate_invalid_security_key', '2C223/I', 403, '' );
|
||||
}
|
||||
|
||||
if ( isset( $record['ref'] ) )
|
||||
{
|
||||
\IPS\Request::i()->ref = base64_encode( $record['ref'] );
|
||||
@@ -878,17 +901,21 @@ class _register extends \IPS\Dispatcher\Controller
|
||||
{
|
||||
if( isset( $values['username'] ) )
|
||||
{
|
||||
\IPS\Member::loggedIn()->logHistory( 'core', 'display_name', array( 'new' => $values['username'], 'old' => \IPS\Member::loggedIn()->language()->get('none'), 'by' => 'manual' ) );
|
||||
\IPS\Member::loggedIn()->name = $values['username'];
|
||||
}
|
||||
$spamCode = NULL;
|
||||
$spamAction = NULL;
|
||||
$disposable = FALSE;
|
||||
$geoBlock = FALSE;
|
||||
if( isset( $values['email_address'] ) )
|
||||
{
|
||||
\IPS\Member::loggedIn()->logHistory( 'core', 'email_change', array( 'new' => $values['new_email'], 'old' => \IPS\Member::loggedIn()->language()->get('none'), 'by' => 'manual' ) );
|
||||
\IPS\Member::loggedIn()->email = $values['email_address'];
|
||||
|
||||
if( \IPS\Settings::i()->spam_service_enabled )
|
||||
{
|
||||
$spamAction = \IPS\Member::loggedIn()->spamService( 'register', NULL, $spamCode );
|
||||
$spamAction = \IPS\Member::loggedIn()->spamService( 'register', NULL, $spamCode, $disposable, $geoBlock );
|
||||
if( $spamAction == 4 )
|
||||
{
|
||||
$action = \IPS\Settings::i()->spam_service_action_4;
|
||||
@@ -905,6 +932,12 @@ class _register extends \IPS\Dispatcher\Controller
|
||||
}
|
||||
\IPS\Member::loggedIn()->members_bitoptions['must_reaccept_terms'] = FALSE;
|
||||
\IPS\Member::loggedIn()->allow_admin_mails = $values['reg_admin_mails'];
|
||||
|
||||
/* We should run geolocation again, this may have been an account created via login handler that has since changed details - check for admin validation */
|
||||
if( !$spamAction )
|
||||
{
|
||||
\IPS\Member::loggedIn()->geoSpamCheck( $geoBlock );
|
||||
}
|
||||
|
||||
/* Save */
|
||||
\IPS\Member::loggedIn()->save();
|
||||
@@ -967,10 +1000,8 @@ class _register extends \IPS\Dispatcher\Controller
|
||||
$pending = null;
|
||||
}
|
||||
|
||||
/* If we're pending *admin* validation, don't let them change their email - otherwise doing so would allow them to bypass validation
|
||||
@todo - this is kind of an unsatisfcatory solution as ideally it would put them back into admin approval, but this would require
|
||||
significant reengineering to address - see commit notes on this code block */
|
||||
if ( $pending and $pending['new_reg'] and $pending['user_verified'] )
|
||||
/* If we're a new registration, no longer allow email addresses to be changed. */
|
||||
if ( $pending and $pending['new_reg'] )
|
||||
{
|
||||
\IPS\Output::i()->error( 'no_module_permission', '2C223/6', 403, '' );
|
||||
}
|
||||
@@ -1033,8 +1064,9 @@ class _register extends \IPS\Dispatcher\Controller
|
||||
}
|
||||
|
||||
$vid = \IPS\Login::generateRandomString();
|
||||
$plainSecurityKey = \IPS\Login::generateRandomString();
|
||||
|
||||
\IPS\Db::i()->insert( 'core_validating', array(
|
||||
\IPS\Db::i()->insert( 'core_validating', [
|
||||
'vid' => $vid,
|
||||
'member_id' => \IPS\Member::loggedIn()->member_id,
|
||||
'entry_date' => time(),
|
||||
@@ -1043,9 +1075,10 @@ class _register extends \IPS\Dispatcher\Controller
|
||||
'user_verified' => ( \IPS\Settings::i()->reg_auth_type == 'admin' ) ?: FALSE,
|
||||
'ip_address' => \IPS\Request::i()->ipAddress(),
|
||||
'email_sent' => time(),
|
||||
) );
|
||||
'security_key' => Encrypt::fromPlaintext( $plainSecurityKey )->tag()
|
||||
] );
|
||||
|
||||
\IPS\Email::buildFromTemplate( 'core', $pending['email_chg'] ? 'email_change' : 'registration_validate', array( \IPS\Member::loggedIn(), $vid ), \IPS\Email::TYPE_TRANSACTIONAL )->send( \IPS\Member::loggedIn() );
|
||||
\IPS\Email::buildFromTemplate( 'core', $pending['email_chg'] ? 'email_change' : 'registration_validate', array( \IPS\Member::loggedIn(), $vid, $plainSecurityKey ), \IPS\Email::TYPE_TRANSACTIONAL )->send( \IPS\Member::loggedIn() );
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1233,7 +1266,7 @@ class _register extends \IPS\Dispatcher\Controller
|
||||
return $ref;
|
||||
}
|
||||
|
||||
\IPS\Output::i()->redirect( $ref, $message );
|
||||
\IPS\Output::i()->redirect( $ref, $message );
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
namespace IPS\core\modules\front\system;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
|
||||
use function mb_strlen;
|
||||
use const LIBXML_NOWARNING;
|
||||
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
@@ -30,7 +34,6 @@ class _serviceworker extends \IPS\Dispatcher\Controller
|
||||
protected function manage()
|
||||
{
|
||||
$cachedUrls = array();
|
||||
$cachedUrls[] = (string) \IPS\Http\Url::internal("app=core&module=system&controller=offline", "front", "user_offline");
|
||||
|
||||
$notificationIcon = NULL;
|
||||
|
||||
@@ -50,31 +53,55 @@ class _serviceworker extends \IPS\Dispatcher\Controller
|
||||
}
|
||||
|
||||
/* 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.
|
||||
$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');
|
||||
$variables = [
|
||||
"DEBUG" => boolval( ( \IPS\IN_DEV and \IPS\DEV_DEBUG_JS ) or \IPS\DEBUG_JS ),
|
||||
"BASE_URL" => \IPS\Settings::i()->base_url,
|
||||
"CACHED_ASSETS" => $cachedUrls,
|
||||
"CACHE_NAME" => "invision-community-{$CACHE_VERSION}",
|
||||
"CACHE_VERSION" => $CACHE_VERSION,
|
||||
"NOTIFICATION_ICON" => $notificationIcon ?: null,
|
||||
"DEFAULT_NOTIFICATION_TITLE" => \IPS\Member::loggedIn()->language()->addToStack('default_notification_title'),
|
||||
"DEFAULT_NOTIFICATION_BODY" => \IPS\Member::loggedIn()->language()->addToStack('default_notification_body'),
|
||||
"OFFLINE_PAGE" => $this->buildCollapsedOfflinePage(),
|
||||
];
|
||||
|
||||
$output = <<<JAVASCRIPT
|
||||
"use strict";
|
||||
const DEBUG = {$DEBUG};
|
||||
const BASE_URL = "{$BASE_URL}";
|
||||
const CACHED_ASSETS = {$CACHED_ASSETS};
|
||||
const CACHE_NAME = 'invision-community-{$CACHE_VERSION}';
|
||||
const OFFLINE_URL = "{$OFFLINE_URL}";
|
||||
const NOTIFICATION_ICON = {$NOTIFICATION_ICON};
|
||||
const DEFAULT_NOTIFICATION_TITLE = "{$DEFAULT_NOTIFICATION_TITLE}";
|
||||
const DEFAULT_NOTIFICATION_BODY = "{$DEFAULT_NOTIFICATION_BODY}";
|
||||
$variables["OFFLINE_PAGE_SIZE"] = \strlen( $variables["OFFLINE_PAGE"] );
|
||||
|
||||
$output = "\"use strict;\"\n\n";
|
||||
foreach ( $variables as $var => $value )
|
||||
{
|
||||
$toEncode = json_encode( $value, JSON_UNESCAPED_SLASHES );
|
||||
$output .= <<<JAVASCRIPT
|
||||
const {$var} = {$toEncode};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return template for offline page
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function buildCollapsedOfflinePage() : string
|
||||
{
|
||||
$html = \IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->swOffline();
|
||||
\IPS\Member::loggedIn()->language()->parseOutputForDisplay( $html );
|
||||
|
||||
$doc = new \IPS\Xml\DOMDocument( "2.0", "utf-8" );
|
||||
$doc->preserveWhiteSpace = false;
|
||||
$doc->loadHTML( $html, LIBXML_NOBLANKS );
|
||||
$doc->formatOutput = true;
|
||||
$compact = $doc->saveHTML();
|
||||
|
||||
// We don't need these segments of whitespace. HTML entities can be used if it's absolutely necessary
|
||||
return preg_replace( "/\s+/", " ", $compact );
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,9 @@
|
||||
|
||||
namespace IPS\core\modules\front\system;
|
||||
|
||||
use IPS\Member;
|
||||
use IPS\Text\Encrypt;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
@@ -130,8 +133,6 @@ class _settings extends \IPS\Dispatcher\Controller
|
||||
{
|
||||
$canConfigureMfa = TRUE;
|
||||
}
|
||||
|
||||
$canChangeSignature = (bool) \IPS\Member::loggedIn()->canEditSignature();
|
||||
|
||||
/* Add login handlers */
|
||||
$loginMethods = \IPS\Login::methods();
|
||||
@@ -139,7 +140,7 @@ class _settings extends \IPS\Dispatcher\Controller
|
||||
/* Show our own oauth clients? */
|
||||
$showApps = (bool) \IPS\Db::i()->select( 'COUNT(*)', 'core_oauth_clients', array( array( 'oauth_enabled=1 AND oauth_ucp=1' ) ) )->first();
|
||||
/* Return */
|
||||
return \IPS\Theme::i()->getTemplate( 'system' )->settings( $area, $output, ( \IPS\Settings::i()->allow_email_changes != 'disabled' ), $canChangePassword, \IPS\Member::loggedIn()->group['g_dname_changes'], $canChangeSignature, $loginMethods, $canConfigureMfa, $showApps );
|
||||
return \IPS\Theme::i()->getTemplate( 'system' )->settings( $area, $output, ( \IPS\Settings::i()->allow_email_changes != 'disabled' ), $canChangePassword, \IPS\Member::loggedIn()->group['g_dname_changes'], (bool) \IPS\Settings::i()->signatures_enabled, $loginMethods, $canConfigureMfa, $showApps );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -240,7 +241,7 @@ class _settings extends \IPS\Dispatcher\Controller
|
||||
$form = new \IPS\Helpers\Form;
|
||||
$form->class = 'ipsForm_collapseTablet';
|
||||
$currentEmail = htmlspecialchars( \IPS\Member::loggedIn()->email, ENT_DISALLOWED, 'UTF-8', FALSE );
|
||||
$form->addDummy( 'current_email', \IPS\Member::loggedIn()->members_bitoptions["email_messages_bounce"] ? \IPS\Theme::i()->getTemplate( 'global', 'core' )->memberEmailBlockedMessage( $currentEmail ) : $currentEmail );
|
||||
$form->addDummy( 'current_email', $currentEmail );
|
||||
$form->add( new \IPS\Helpers\Form\Email(
|
||||
'new_email',
|
||||
'',
|
||||
@@ -301,8 +302,9 @@ class _settings extends \IPS\Dispatcher\Controller
|
||||
unset( $_SESSION['newEmail'] );
|
||||
|
||||
$vid = \IPS\Login::generateRandomString();
|
||||
$plainSecurityKey = \IPS\Login::generateRandomString();
|
||||
|
||||
\IPS\Db::i()->insert( 'core_validating', array(
|
||||
\IPS\Db::i()->insert( 'core_validating', [
|
||||
'vid' => $vid,
|
||||
'member_id' => \IPS\Member::loggedIn()->member_id,
|
||||
'entry_date' => time(),
|
||||
@@ -310,12 +312,13 @@ class _settings extends \IPS\Dispatcher\Controller
|
||||
'ip_address' => \IPS\Request::i()->ipAddress(),
|
||||
'prev_email' => $oldEmail,
|
||||
'email_sent' => time(),
|
||||
) );
|
||||
'security_key' => Encrypt::fromPlaintext( $plainSecurityKey )->tag()
|
||||
] );
|
||||
|
||||
\IPS\Member::loggedIn()->members_bitoptions['validating'] = TRUE;
|
||||
\IPS\Member::loggedIn()->save();
|
||||
|
||||
\IPS\Email::buildFromTemplate( 'core', 'email_change', array( \IPS\Member::loggedIn(), $vid ), \IPS\Email::TYPE_TRANSACTIONAL )->send( \IPS\Member::loggedIn() );
|
||||
\IPS\Email::buildFromTemplate( 'core', 'email_change', array( \IPS\Member::loggedIn(), $vid, $plainSecurityKey ), \IPS\Email::TYPE_TRANSACTIONAL )->send( \IPS\Member::loggedIn() );
|
||||
|
||||
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( '' ) );
|
||||
}
|
||||
@@ -614,7 +617,7 @@ class _settings extends \IPS\Dispatcher\Controller
|
||||
\IPS\Output::i()->bypassCsrfKeyCheck = true;
|
||||
|
||||
/* Validate password */
|
||||
if ( !isset( $_SESSION['passwordValidatedForMfa'] ) )
|
||||
if ( !isset( $_SESSION['passwordForMfa'] ) )
|
||||
{
|
||||
$login = new \IPS\Login( \IPS\Http\Url::internal( 'app=core&module=system&controller=settings&area=mfa', 'front', 'settings_mfa' ), \IPS\Login::LOGIN_REAUTHENTICATE );
|
||||
$usernamePasswordMethods = $login->usernamePasswordMethods();
|
||||
@@ -628,7 +631,7 @@ class _settings extends \IPS\Dispatcher\Controller
|
||||
{
|
||||
if ( $success = $login->authenticate() )
|
||||
{
|
||||
$_SESSION['passwordValidatedForMfa'] = TRUE;
|
||||
$_SESSION['passwordForMfa'] = TRUE;
|
||||
\IPS\Output::i()->redirect( \IPS\Http\Url::internal( 'app=core&module=system&controller=settings&area=mfa', 'front', 'settings_mfa' ) );
|
||||
}
|
||||
}
|
||||
@@ -661,6 +664,9 @@ class _settings extends \IPS\Dispatcher\Controller
|
||||
}
|
||||
return $output . $mfaOutput;
|
||||
}
|
||||
|
||||
/* We got past the prompts */
|
||||
$_SESSION['passwordValidatedForMfa'] = TRUE;
|
||||
|
||||
/* Do any enabling/disabling */
|
||||
if ( isset( \IPS\Request::i()->act ) )
|
||||
@@ -836,6 +842,13 @@ class _settings extends \IPS\Dispatcher\Controller
|
||||
*/
|
||||
protected function confirmAccountDeletion()
|
||||
{
|
||||
$mfaOutput = \IPS\MFA\MFAHandler::accessToArea( 'core', 'SecurityQuestions', \IPS\Http\Url::internal( 'app=core&module=system&controller=settings&area=confirmAccountDeletion', 'front' )->setQueryString('vid', \IPS\Request::i()->vid ) );
|
||||
if ( $mfaOutput )
|
||||
{
|
||||
\IPS\Output::i()->output = $mfaOutput;
|
||||
return;
|
||||
}
|
||||
|
||||
$key = \IPS\Request::i()->vid;
|
||||
try
|
||||
{
|
||||
@@ -1000,27 +1013,30 @@ class _settings extends \IPS\Dispatcher\Controller
|
||||
}
|
||||
else
|
||||
{
|
||||
\IPS\Db::i()->update( 'core_validating', array( 'email_sent' => time() ), array( 'vid=?', $vid ) );
|
||||
$plainSecurityKey = \IPS\Login::generateRandomString();
|
||||
\IPS\Db::i()->update( 'core_validating', [ 'email_sent' => time(), 'security_key' => Encrypt::fromPlaintext( $plainSecurityKey )->tag() ], [ 'vid=?', $vid ] );
|
||||
}
|
||||
}
|
||||
catch ( \UnderflowException $e )
|
||||
{
|
||||
$vid = md5( $member->members_pass_hash . \IPS\Login::generateRandomString() );
|
||||
$plainSecurityKey = \IPS\Login::generateRandomString();
|
||||
|
||||
\IPS\Db::i()->insert( 'core_validating', array(
|
||||
\IPS\Db::i()->insert( 'core_validating', [
|
||||
'vid' => $vid,
|
||||
'member_id' => $member->member_id,
|
||||
'entry_date' => time(),
|
||||
'forgot_security' => 1,
|
||||
'ip_address' => \IPS\Request::i()->ipAddress(),
|
||||
'email_sent' => time(),
|
||||
) );
|
||||
'security_key' => Encrypt::fromPlaintext( $plainSecurityKey )->tag()
|
||||
] );
|
||||
}
|
||||
|
||||
/* Send email */
|
||||
if ( $sendEmail )
|
||||
{
|
||||
\IPS\Email::buildFromTemplate( 'core', 'mfaRecovery', array( $member, $vid ), \IPS\Email::TYPE_TRANSACTIONAL )->send( $member );
|
||||
\IPS\Email::buildFromTemplate( 'core', 'mfaRecovery', array( $member, $vid, $plainSecurityKey ), \IPS\Email::TYPE_TRANSACTIONAL )->send( $member );
|
||||
$message = "mfa_recovery_email_sent";
|
||||
}
|
||||
else
|
||||
@@ -1051,6 +1067,12 @@ class _settings extends \IPS\Dispatcher\Controller
|
||||
{
|
||||
\IPS\Output::i()->error( 'mfa_recovery_no_validation_key', '2C122/K', 410, '' );
|
||||
}
|
||||
|
||||
/* Check security key */
|
||||
if( !\IPS\Login::compareHashes( Encrypt::fromTag( $record['security_key'] )->decrypt(), \IPS\Request::i()->security_key ) )
|
||||
{
|
||||
\IPS\Output::i()->error( 'mfavalidate_invalid_security_key', '3C122/16', 403, '' );
|
||||
}
|
||||
|
||||
/* Remove all MFA */
|
||||
$member = \IPS\Member::load( $record['member_id'] );
|
||||
@@ -1226,7 +1248,7 @@ class _settings extends \IPS\Dispatcher\Controller
|
||||
/* Check they have permission to change their signature */
|
||||
$sigLimits = explode( ":", \IPS\Member::loggedIn()->group['g_signature_limits']);
|
||||
|
||||
if( !\IPS\Member::loggedIn()->canEditSignature() )
|
||||
if( (bool) \IPS\Settings::i()->signatures_enabled === FALSE )
|
||||
{
|
||||
\IPS\Output::i()->error( 'signatures_disabled', '2C122/C', 403, '' );
|
||||
}
|
||||
@@ -1268,12 +1290,15 @@ class _settings extends \IPS\Dispatcher\Controller
|
||||
$form = new \IPS\Helpers\Form;
|
||||
$form->class = 'ipsForm_collapseTablet';
|
||||
$form->add( new \IPS\Helpers\Form\YesNo( 'view_sigs', \IPS\Member::loggedIn()->members_bitoptions['view_sigs'], FALSE ) );
|
||||
$form->add( new \IPS\Helpers\Form\Editor( 'signature', \IPS\Member::loggedIn()->signature, FALSE, array( 'app' => 'core', 'key' => 'Signatures', 'autoSaveKey' => "frontsig-" .\IPS\Member::loggedIn()->member_id, 'attachIds' => array( \IPS\Member::loggedIn()->member_id ) ) ) );
|
||||
|
||||
if( Member::loggedIn()->canEditSignature() )
|
||||
{
|
||||
$form->add( new \IPS\Helpers\Form\Editor( 'signature', \IPS\Member::loggedIn()->signature, FALSE, ['app' => 'core', 'key' => 'Signatures', 'autoSaveKey' => 'frontsig-'.\IPS\Member::loggedIn()->member_id, 'attachIds' => [\IPS\Member::loggedIn()->member_id]] ) );
|
||||
}
|
||||
|
||||
/* Handle submissions */
|
||||
if ( $values = $form->values() )
|
||||
{
|
||||
if( $values['signature'] )
|
||||
if( isset( $values['signature'] ) and $values['signature'] )
|
||||
{
|
||||
/* Check Limits */
|
||||
$signature = new \IPS\Xml\DOMDocument( '1.0', 'UTF-8' );
|
||||
@@ -1357,6 +1382,7 @@ class _settings extends \IPS\Dispatcher\Controller
|
||||
{
|
||||
$errors[] = \IPS\Member::loggedIn()->language()->addToStack('sig_num_lines_exceeded');
|
||||
}
|
||||
\IPS\Member::loggedIn()->signature = $values['signature'];
|
||||
}
|
||||
|
||||
if( !empty( $errors ) )
|
||||
@@ -1367,7 +1393,6 @@ class _settings extends \IPS\Dispatcher\Controller
|
||||
return \IPS\Theme::i()->getTemplate( 'system' )->settingsSignature( $form, $sigLimits );
|
||||
}
|
||||
|
||||
\IPS\Member::loggedIn()->signature = $values['signature'];
|
||||
\IPS\Member::loggedIn()->members_bitoptions['view_sigs'] = $values['view_sigs'];
|
||||
|
||||
\IPS\Member::loggedIn()->save();
|
||||
@@ -1897,7 +1922,7 @@ class _settings extends \IPS\Dispatcher\Controller
|
||||
*/
|
||||
protected function togglePii()
|
||||
{
|
||||
if ( ! \IPS\Settings::i()->core_datalayer_member_pii_choice )
|
||||
if ( ! \IPS\Settings::i()->core_datalayer_member_pii_choice or !isset( $_SESSION['passwordValidatedForMfa'] ) OR \IPS\Settings::i()->pii_type !== 'on' )
|
||||
{
|
||||
\IPS\Output::i()->error( 'page_not_found', '3T251/7', 404 );
|
||||
}
|
||||
@@ -1982,6 +2007,12 @@ class _settings extends \IPS\Dispatcher\Controller
|
||||
{
|
||||
\IPS\Session::i()->csrfCheck();
|
||||
|
||||
if ( $output = \IPS\MFA\MFAHandler::accessToArea( 'core', 'DeviceManagement', $this->url->setQuerystring('do','updateDeviceEmail')->csrf()) )
|
||||
{
|
||||
\IPS\Output::i()->output = $output;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Update the bitwise flag */
|
||||
\IPS\Member::loggedIn()->members_bitoptions['new_device_email'] = (bool) \IPS\Request::i()->value;
|
||||
\IPS\Member::loggedIn()->save();
|
||||
|
||||
@@ -198,6 +198,7 @@ class _warnings extends \IPS\Content\Controller
|
||||
/* Acknowledge it */
|
||||
$warning->acknowledged = TRUE;
|
||||
$warning->save();
|
||||
/* @note SELECT_FROM_WRITE_SERVER: Avoid issue where warning may be in writer table, but not in reader */
|
||||
$member->members_bitoptions['unacknowledged_warnings'] = (bool) \IPS\Db::i()->select( 'COUNT(*)', 'core_members_warn_logs', array( "wl_member=? AND wl_acknowledged=0", $member->member_id ), NULL, NULL, NULL, NULL, \IPS\Db::SELECT_FROM_WRITE_SERVER )->first();
|
||||
$member->save();
|
||||
|
||||
@@ -369,7 +370,7 @@ class _warnings extends \IPS\Content\Controller
|
||||
|
||||
try
|
||||
{
|
||||
$action = \IPS\Db::i()->select( '*', 'core_members_warn_actions', array( 'wa_points<=?', ( $member->warn_level + \IPS\Request::i()->points ) ), 'wa_points DESC', 1 )->first();
|
||||
$action = \IPS\Db::i()->select( '*', 'core_members_warn_actions', array( 'wa_points<=?', ( $member->warn_level + (int) \IPS\Request::i()->points ) ), 'wa_points DESC', 1 )->first();
|
||||
foreach ( array( 'mq', 'rpa', 'suspend' ) as $k )
|
||||
{
|
||||
if ( $action[ 'wa_' . $k ] == -1 )
|
||||
|
||||
Reference in new issue
Block a user