Files
IPB/applications/core/sources/Hive/Hive.php
T
2025-12-19 16:14:56 -08:00

221 lines
5.4 KiB
PHP

<?php
/**
* @brief Community Hive Helpers
* @author <a href='https://communityhive.com'>CommunityHive.com</a>
* @copyright (c) CommunityHive.com
* @license https://www.invisioncommunity.com/legal/standards/
* @package Community Hive
* @since 16 May 2023
*/
namespace IPS\core;
/* To prevent PHP errors (extending class does not exist) revealing path */
use IPS\Application;
use IPS\Content;
use IPS\Content\Item;
use IPS\Content\Searchable;
use IPS\Http\Url;
use IPS\IPS;
use IPS\Member;
use IPS\Settings;
use Jose\Component\Core\JWK;
use IPS\Db;
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Community Hive Helpers
*/
class _Hive extends \IPS\Patterns\Singleton
{
/**
* Framework coding standards....
*/
public function __construct()
{
}
/**
* Make an API request with a JWT
*
* @param string $endpoint
* @param array $payload
* @return array
*/
public static function api( string $endpoint, array $payload ): array
{
$hostname = \defined( 'COMMUNITYHIVE_URL' ) ? COMMUNITYHIVE_URL : 'https://communityhive.com';
$request = Url::external( $hostname . '/api/v1/' . $endpoint )
->request(3)
->put( static::_assembleJwt( $payload ) );
if( (int) $request->httpResponseCode !== 200 )
{
\IPS\Log::log( [ 'endpoint' => $hostname . '/api/v1/' . $endpoint, 'payload' => $payload, 'response' => $request ], 'hive_error' );
throw new \LogicException( $request->decodeJson()['error'] ?? 'an unknown error occurred.' );
}
return $request->decodeJson();
}
/**
* Generate group hash in an ordered way
*
* @param Member $member
* @return string
*/
public static function groupHash( Member $member ): string
{
if( $member->isBanned() === FALSE )
{
$hash = array_filter( array_merge( [ $member->member_group_id ], explode( ',', $member->mgroup_others ) ) );
natcasesort( $hash );
return implode( ',', $hash );
}
else
{
return 'guest';
}
}
/**
* @brief store any keys for removal in __destruct()
*/
protected static array $_deleteQueue = [];
/**
* Remove content from Hive - Used if something is moved or content is hidden
*
* @param Content $content
* @return void
*/
public function removeContent( Content $content )
{
/* Wrap it all because we don't want it to stop the moderation action */
try
{
if ( !( $content instanceof Searchable ) or empty( Settings::i()->hive_key ) )
{
return;
}
$idColumn = $content::$databaseColumnId;
if ( $content instanceof Item )
{
$key1 = \get_class( $content ) . '/' . $content->$idColumn;
}
else
{
$itemIdColumn = $content->item()::$databaseColumnId;
$key1 = \get_class( $content->item() ) . '/' . $content->item()->$itemIdColumn;
$key2 = \get_class( $content ) . '/' . $content->$idColumn;
}
/* Store IDs for removal */
static::$_deleteQueue[] = [ 'key1' => $key1, 'key2' => $key2 ?? NULL ];
}
catch( \Exception | \Throwable $e )
{
\IPS\Log::log( $e, 'hive_content_removal' );
}
}
/**
* Assemble a JWT with payload
*
* @param array $payload
* @return string
*/
protected static function _assembleJwt( array $payload ): string
{
IPS::$PSR0Namespaces['Jose'] = \IPS\ROOT_PATH . '/system/3rd_party/JwtFramework/src';
IPS::$PSR0Namespaces['Base64Url'] = \IPS\ROOT_PATH . '/system/3rd_party/Base64Url';
$jwk = new JWK([
'kty' => 'oct',
'k' => base64_encode( Settings::i()->hive_site_key )
]);
$jwsCompactSerializer = new \Jose\Component\Signature\Serializer\CompactSerializer();
$jwsBuilder = new \Jose\Component\Signature\JWSBuilder( new \Jose\Component\Core\AlgorithmManager( [ new \Jose\Component\Signature\Algorithm\HS256() ] ) );
$defaultPayload = [
'sub' => Settings::i()->hive_site_id,
'iss' => rtrim( Settings::i()->base_url, '/' ),
'iat' => time(),
'nbf' => time(),
'exp' => time() + 60,
'aud' => 'communityhive',
'hive_system_version' => Application::load('core')->long_version
];
$jws = $jwsBuilder
->create()
->withPayload( json_encode( array_merge( $defaultPayload, $payload ) ) )
->addSignature( $jwk, [
'typ' => 'JWT',
'alg' => 'HS256',
])
->build();
return $jwsCompactSerializer->serialize( $jws, 0 );
}
/**
* @brief Cache whether the member is subscribed
*/
protected static ?bool $_showPromotion = NULL;
/**
* Check if we should show the Hive Promotion for the viewing member
*
* @return bool
*/
public static function showPromotion(): bool
{
if( static::$_showPromotion !== NULL )
{
return static::$_showPromotion;
}
if ( !Settings::i()->hive_enabled )
{
return static::$_showPromotion = FALSE;
}
try
{
Db::i()->select('member_id', 'core_hive_subscribers', [ 'member_id=?', Member::loggedIn()->member_id ] )->first();
return static::$_showPromotion = FALSE;
}
catch ( \UnderflowException $e ){}
return static::$_showPromotion = TRUE;
}
/**
* Send API request to remove any content IDs that have been queued
*/
public function __destruct()
{
/* Wrap it all because we don't want it to stop the moderation action */
try
{
if( \count( static::$_deleteQueue ) )
{
static::api( 'contentremove', [ 'keys' => static::$_deleteQueue ] );
}
}
catch( \Exception | \Throwable $e )
{
\IPS\Log::log( $e, 'hive_content_removal' );
}
}
}