Files
2025-12-19 19:04:57 -08:00

461 lines
13 KiB
PHP

<?php
/**
* @brief MetaData Trait for Content Models/Comments
* @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 Nov 2013
*/
namespace IPS\Content;
use IPS\Member;
use IPS\Db;
use IPS\Application;
use IPS\Request;
use IPS\Output;
use IPS\Theme;
use IPS\Member\Group;
use BadMethodCallException;
use OutOfRangeException;
use UnderflowException;
use InvalidArgumentException;
use function defined;
use function header;
use function get_class;
use function in_array;
use function time;
use function json_encode;
use function json_decode;
use function count;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ($_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0') . ' 403 Forbidden' );
exit;
}
/**
* MetaData Trait for Content Models/Comments
*/
trait MetaData
{
/**
* Metadata types supported by this content
*
* @return array
*/
abstract static function supportedMetaDataTypes(): array;
/**
* Check if this content has metadata
*
* @return bool
* @throws BadMethodCallException
*/
public function hasMetaData(): bool
{
if ( isset( static::$databaseColumnMap['meta_data'] ) )
{
$column = static::$databaseColumnMap['meta_data'];
return (bool) $this->$column;
}
throw new BadMethodCallException( "Class using must define meta_data column in column map" );
}
/**
* @brief Meta Data Cache
*/
protected ?array $_metaData = NULL;
/**
* Fetch Meta Data
*
* @return array
*/
public function getMeta(): array
{
/* If we don't have any, don't bother */
if ( $this->hasMetaData() === FALSE )
{
return array();
}
$idColumn = static::$databaseColumnId;
if ( $this->_metaData === NULL )
{
$this->_metaData = array();
foreach( Db::i()->select( '*', 'core_content_meta', array( "meta_class=? AND meta_item_id=?", get_class( $this ), $this->$idColumn ) ) AS $row )
{
$this->_metaData[ $row['meta_type'] ][ $row['meta_id'] ] = json_decode( $row['meta_data'], TRUE );
}
}
return $this->_metaData;
}
/**
* Add Meta Data
*
* @param string $type The type of data
* @param array $data The data
* @return int The ID of the inserted metadata record
* @throws BadMethodCallException
*/
public function addMeta( string $type, array $data ): int
{
if ( !static::supportedMetaDataTypes() OR !in_array( $type, static::supportedMetaDataTypes() ) )
{
throw new BadMethodCallException;
}
$idColumn = static::$databaseColumnId;
$data = json_encode( $data );
$id = Db::i()->insert( 'core_content_meta', array(
'meta_class' => get_class( $this ),
'meta_item_id' => $this->$idColumn,
'meta_type' => $type,
'meta_data' => $data,
'meta_item_author' => (int) $this->author()->member_id,
'meta_added' => time()
) );
$column = static::$databaseColumnMap['meta_data'];
$this->$column = 1;
$this->save();
$this->_metaData = NULL;
return $id;
}
/**
* Edit Meta Data
*
* @param int $id The ID
* @param array $data The data
* @return void
* @throws BadMethodCallException
*/
public function editMeta( int $id, array $data ): void
{
try
{
/* Get current data */
$idColumn = static::$databaseColumnId;
$current = json_decode( Db::i()->select( 'meta_data', 'core_content_meta', array( "meta_class=? AND meta_item_id=? AND meta_id=?", get_class( $this ), $this->$idColumn, $id ) )->first(), true );
foreach ( $data as $key => $value )
{
$current[ $key ] = $value;
}
Db::i()->update( 'core_content_meta', array( 'meta_data' => json_encode( $current ) ), array( "meta_id=?", $id ) );
/* Make sure our flag is set */
$column = static::$databaseColumnMap['meta_data'];
$this->$column = TRUE;
$this->save();
$this->_metaData = NULL;
}
catch( UnderflowException )
{
throw new OutOfRangeException;
}
}
/**
* Delete Meta Data
*
* @param int $id The ID
* @return void
*/
public function deleteMeta( int $id ): void
{
$idColumn = static::$databaseColumnId;
Db::i()->delete( 'core_content_meta', array( "meta_class=? AND meta_item_id=? AND meta_id=?", get_class( $this ), $this->$idColumn, $id ) );
/* Any left? */
$count = Db::i()->select( 'COUNT(*)', 'core_content_meta', array( "meta_class=? AND meta_item_id=?", get_class( $this ), $this->$idColumn ), NULL, NULL, NULL, NULL, Db::SELECT_FROM_WRITE_SERVER )->first();
if ( !$count )
{
$column = static::$databaseColumnMap['meta_data'];
$this->$column = FALSE;
$this->save();
}
$this->_metaData = NULL;
}
/**
* Delete All Meta Data
*
* @return void
*/
public function deleteAllMeta(): void
{
$idColumn = static::$databaseColumnId;
Db::i()->delete( 'core_content_meta', array( "meta_class=? AND meta_item_id=?", get_class( $this ), $this->$idColumn ) );
}
/**
* Can perform an action on a message
*
* @param string $action The action
* @param Member|NULL $member The member, or NULL for currently logged in
* @return bool
* @note This is a wrapper for the extension so content items can extend and apply their own logic
*/
public function canOnMessage( string $action, ?Member $member = NULL ): bool
{
/* Extensions go first */
if( $permCheck = Permissions::can( 'onMessage', $this, $member ) )
{
return ( $permCheck === Permissions::PERM_ALLOW );
}
if( !$this->actionEnabled( 'onMessage', $member ) )
{
return false;
}
return Application::load('core')->extensions( 'core', 'MetaData' )['ContentMessages']->canOnMessage( $action, $this, $member );
}
/**
* Add Item Message
*
* @param string $message The message
* @param string|NULL $color The message color
* @param Member|NULL $member User adding the message
* @param bool $isPublic Who should see the message
* @return int
* @note This is a wrapper for the extension so content items can extend and apply their own logic
*/
public function addMessage( string $message, ?string $color = NULL, ?Member $member = NULL, bool $isPublic = TRUE ): int
{
return Application::load('core')->extensions( 'core', 'MetaData' )['ContentMessages']->addMessage( $message, $color, $this, $member, $isPublic );
}
/**
* Edit Item Message
*
* @param int $id The ID
* @param string $message The new message
* @param string|NULL $color Color
* @param Member|NULL $member The member editing the message, or NULL for currently logged in
* @param bool $onlyStaff Who should see the message
* @return void
* @note This is a wrapper for the extension so content items can extend and apply their own logic
*/
public function editMessage( int $id, string $message, ?string $color = NULL, ?Member $member = NULL, bool $onlyStaff = FALSE ): void
{
Application::load('core')->extensions( 'core', 'MetaData' )['ContentMessages']->editMessage( $id, $message, $color, $this, $member, $onlyStaff );
}
/**
* Delete Item Message
*
* @param int $id The ID
* @param Member|NULL $member The member deleting the message
* @return void
* @note This is a wrapper for the extension so content items can extend and apply their own logic
*/
public function deleteMessage( int $id, ?Member $member = NULL ): void
{
Application::load('core')->extensions( 'core', 'MetaData' )['ContentMessages']->deleteMessage( $id, $this, $member );
}
/**
* Get Item Messages
*
* @return array
* @note This is a wrapper for the extension so content items can extend and apply their own logic
*/
public function getMessages(): array
{
return Application::load('core')->extensions( 'core', 'MetaData' )['ContentMessages']->getMessages( $this );
}
/**
* Is a featured comment?
*
* @param Comment $comment
* @return bool
* @note This is a wrapper for the extension so content items can extend and apply their own logic
*/
public function isFeaturedComment( Comment $comment ): bool
{
return Application::load('core')->extensions( 'core', 'MetaData' )['FeaturedComments']->isFeaturedComment( $comment );
}
/**
* Can Feature a Comment
*
* @param Member|NULL $member The member, or NULL for currently logged in
* @return bool
* @note This is a wrapper for the extension so content items can extend and apply their own logic
*/
public function canFeatureComment( ?Member $member = NULL ): bool
{
/* Extensions go first */
if( $permCheck = Permissions::can( 'featureComment', $this, $member ) )
{
return ( $permCheck === Permissions::PERM_ALLOW );
}
if( !$this->actionEnabled( 'featureComment', $member ) )
{
return false;
}
return Application::load('core')->extensions( 'core', 'MetaData' )['FeaturedComments']->canFeatureComment( $this, $member );
}
/**
* Can Unfeature a Comment
*
* @param Member|NULL $member The member, or NULL for currently logged in
* @return bool
* @note This is a wrapper for the extension so content items can extend and apply their own logic
*/
public function canUnfeatureComment( ?Member $member = NULL ): bool
{
/* Extensions go first */
if( $permCheck = Permissions::can( 'unfeatureComment', $this, $member ) )
{
return ( $permCheck === Permissions::PERM_ALLOW );
}
if( !$this->actionEnabled( 'unfeatureComment', $member ) )
{
return false;
}
return Application::load('core')->extensions( 'core', 'MetaData' )['FeaturedComments']->canUnfeatureComment( $this, $member );
}
/**
* Feature A Comment
*
* @param Comment $comment The Comment
* @param string|NULL $note An optional note to include
* @param Member|NULL $member The member featuring the comment
* @return void
* @note This is a wrapper for the extension so content items can extend and apply their own logic
*/
public function featureComment( Comment $comment, ?string $note = NULL, ?Member $member = NULL ): void
{
Application::load('core')->extensions( 'core', 'MetaData' )['FeaturedComments']->featureComment( $this, $comment, $note, $member );
/* Give points */
$comment->author()->achievementAction( 'core', 'ContentPromotion', [
'content' => $comment,
'promotype' => 'recommend' //Yeah it says feature, but it's really recommend
] );
}
/**
* Unfeature a comment
*
* @param Comment $comment The Comment
* @param Member|NULL $member The member unfeaturing the comment
* @return void
* @note This is a wrapper for the extension so content items can extend and apply their own logic
*/
public function unfeatureComment( Comment $comment, ?Member $member = NULL ): void
{
Application::load('core')->extensions( 'core', 'MetaData' )['FeaturedComments']->unfeatureComment( $this, $comment, $member );
}
/**
* Get Featured Comments in the most efficient way possible
*
* @return array
*/
public function featuredComments(): array
{
$featured = Application::load('core')->extensions( 'core', 'MetaData' )['FeaturedComments']->featuredComments( $this );
if ( Request::i()->isAjax() && Request::i()->recommended == 'comments' )
{
/* @todo this really shouldn't be here, and should be in the controller */
Output::i()->json( array(
'html' => Theme::i()->getTemplate( 'global', 'core', 'front' )->featuredComments( $featured, $this->url()->setQueryString( 'recommended', 'comments' ) ),
'count' => count( $featured )
) );
}
return $featured;
}
/**
* Is item-level moderation enabled?
*
* @param Member|Group|NULL $memberOrGroup A member of member group to check for bypassing moderation.
* @return bool
*/
public function itemModerationEnabled( Member|Group|null $memberOrGroup = NULL ): bool
{
return (bool) Application::load('core')->extensions( 'core', 'MetaData' )['ItemModeration']->enabled( $this, $memberOrGroup );
}
/**
* Can toggle item-level moderation?
*
* @param Member|NULL $member
* @return bool
*/
public function canToggleItemModeration( ?Member $member = NULL ): bool
{
/* Extensions go first */
if( $permCheck = Permissions::can( 'toggleItemModeration', $this, $member ) )
{
return ( $permCheck === Permissions::PERM_ALLOW );
}
if( !$this->actionEnabled( 'toggleItemModeration', $member ) )
{
return false;
}
$member = $member ?: Member::loggedIn();
return (bool) Application::load('core')->extensions( 'core', 'MetaData' )['ItemModeration']->canToggle( $this, $member );
}
/**
* Toggle item-level moderation
*
* @param string $action
* @param Member|NULL $member
* @return void
* @throws
* @li \InvalidArgumentException
* @li \BadMethodCallException
*/
public function toggleItemModeration( string $action, ?Member $member = NULL ): void
{
if ( !in_array( $action, array( 'enable', 'disable' ) ) )
{
throw new InvalidArgumentException;
}
$member = $member ?: Member::loggedIn();
if ( !$this->canToggleItemModeration( $member ) )
{
throw new BadMethodCallException;
}
Application::load('core')->extensions( 'core', 'MetaData' )['ItemModeration']->$action( $this, $member );
}
}