Invision Power Services, Inc.
* @copyright (c) Invision Power Services, Inc.
* @license https://www.invisioncommunity.com/legal/standards/
* @package Invision Community
* @since 16 Feb 2017
*/
namespace IPS\core;
/* To prevent PHP errors (extending class does not exist) revealing path */
use Exception;
use IPS\Content;
use IPS\Content\Comment;
use IPS\Content\Item;
use IPS\DateTime;
use IPS\Db;
use IPS\File;
use IPS\IPS;
use IPS\Lang;
use IPS\Member;
use IPS\Node\Model;
use IPS\Patterns\ActiveRecord;
use IPS\Settings;
use OutofRangeException;
use UnderflowException;
use function count;
use function defined;
use function get_class;
use function htmlspecialchars;
use function intval;
use function is_array;
use function nl2br;
use function str_replace;
use const ENT_DISALLOWED;
use const ENT_QUOTES;
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* @brief Promote Model
*/
class Feature extends ActiveRecord
{
/**
* @brief Multiton Store
*/
protected static array $multitons = array();
/**
* @brief [ActiveRecord] Database Table
*/
public static ?string $databaseTable = 'core_content_promote';
/**
* @brief [ActiveRecord] ID Database Column
*/
public static string $databaseColumnId = 'id';
/**
* @brief Database Prefix
*/
public static string $databasePrefix = 'promote_';
/**
* @brief Class object
*/
protected Content|Model|null $object = NULL;
/**
* @brief Author object
*/
protected ?Member $author = NULL;
/**
* Set Default Values
*
* @return void
*/
public function setDefaultValues() : void
{
/* Ensure TEXT fields are never NULL */
$this->_data['text'] = array();
$this->_data['images'] = array();
$this->_data['media'] = array();
}
/**
* Set the "text" field
*
* @param string|array $value
* @return void
*/
public function set_text( string|array $value ) : void
{
$this->_data['text'] = ( is_array( $value ) ? json_encode( $value ) : $value );
}
/**
* Get the "text" field
*
* @return array|null
*/
public function get_text() : array|null
{
return json_decode( $this->_data['text'], TRUE );
}
/**
* Set the "attach_ids" field
*
* @param string|array $value
* @return void
*/
public function set_images( string|array $value ) : void
{
$this->_data['images'] = ( is_array( $value ) ? json_encode( $value ) : $value );
}
/**
* Get the "attach_ids" field
*
* @return array
*/
public function get_images() : array
{
return $this->_data['images'] ? json_decode( $this->_data['images'], TRUE ) : array();
}
/**
* Set the "media" field
*
* @param string|array $value
* @return void
*/
public function set_media( string|array $value ) : void
{
$this->_data['media'] = ( is_array( $value ) ? json_encode( $value ) : $value );
}
/**
* Get the "media" field
*
* @return array
*/
public function get_media() : array
{
return $this->_data['media'] ? json_decode( $this->_data['media'], TRUE ) : array();
}
/**
* Set the "form_data" field
*
* @param string|array $value
* @return void
*/
public function set_form_data( string|array $value ) : void
{
$this->_data['form_data'] = ( is_array( $value ) ? json_encode( $value ) : $value );
}
/**
* Get the "form_data" field
*
* @return array
*/
public function get_form_data() : array
{
return $this->_data['form_data'] ? json_decode( $this->_data['form_data'], TRUE ) : array();
}
/**
* The author object
*
* @return Member
*/
public function author() : Member
{
if ( $this->author === NULL )
{
if ( $this->added_by )
{
try
{
$this->author = Member::load( $this->added_by );
}
catch ( Exception $e )
{
$this->author = new Member;
$this->author->name = Settings::i()->board_name;
}
}
else
{
$this->author = new Member;
$this->author->name = Settings::i()->board_name;
}
}
return $this->author;
}
/**
* The content object
*
* @return Content
*/
public function object() : Content
{
if ( $this->object === NULL )
{
$class = $this->class;
if( IPS::classUsesTrait( $class, 'IPS\Content\Featurable' ) )
{
$this->object = $class::load( $this->class_id );
}
}
return $this->object;
}
/**
* Get Our Picks title
*
* @return string
*/
public function get_ourPicksTitle() : string
{
$settings = $this->form_data;
return isset( $settings['internal']['title'] ) ? $settings['internal']['title'] : $this->objectTitle;
}
/**
* Get the object title
*
* @return string
*/
public function get_objectTitle() : string
{
return static::objectTitle( $this->object() );
}
/**
* Get the object date posted
*
* @return DateTime|NULL
*/
public function get_objectDatePosted() : DateTime|NULL
{
$object = $this->object();
if ( isset( $object::$databaseColumnMap['date'] ) )
{
return DateTime::ts( $object->mapped('date') );
}
/* Valid object, but there isn't any date data available */
return null;
}
/**
* Get the object unread status
*
* @return bool
*/
public function get_objectIsUnread() : bool
{
$object = $this->object();
if ( $object instanceof Item )
{
return $object->unread() === 1;
}
else
{
return $object->item()->unread() === 1;
}
}
/**
* Get the number and indefinite article for replies/children where applicable
*
* @return array|null
*/
public function get_objectDataCount() : array|NULL
{
return $this->objectDataCount();
}
/**
* Get the number and indefinite article for replies/children where applicable
*
* @param Lang|null $language Language to use (or NULL for currently logged in member's language)
* @return array|null
*/
public function objectDataCount( ?Lang $language=NULL ) : array|NULL
{
$language = $language ?: Member::loggedIn()->language();
$object = $this->object();
if ( $object instanceof Item )
{
try
{
$container = $object->container();
}
catch( Exception $e ){}
if ( $object::supportsComments( NULL, $container ) )
{
$count = $object->mapped('num_comments');
if ( $count AND isset( $object::$firstCommentRequired ) AND $object::$firstCommentRequired === TRUE )
{
$count--;
}
return array( 'count' => $count, 'words' => $language->addToStack( 'num_replies', FALSE, array( 'pluralize' => array( $count ) ) ) );
}
if ( $object::supportsReviews( NULL, $container ) )
{
$count = $object->mapped('num_reviews');
return array( 'count' => $count, 'words' => $language->addToStack( 'num_reviews', FALSE, array( 'pluralize' => array( $count ) ) ) );
}
}
return null;
}
/**
* Get reactions class for this object
*
* @return Content|null
*/
public function get_objectReactionClass() : Content|NULL
{
$object = $this->object();
$class = NULL;
if ( ! Settings::i()->reputation_enabled )
{
return NULL;
}
if ( $object instanceof Item )
{
/* The first post has the reactions for this item */
if ( $object::$firstCommentRequired )
{
try
{
$class = $object->comments( 1, NULL, 'date', 'asc' );
}
catch( Exception $e )
{
}
}
else
{
$class = $object;
}
}
else
{
$class = $object;
}
return ( $class and IPS::classUsesTrait( $class, 'IPS\Content\Reactable' ) ) ? $class : NULL;
}
/**
* Return an array of File objects
*
* @return array|null
*/
public function imageObjects() : array|NULL
{
$photos = array();
if ( count( $this->images ) )
{
foreach( $this->images as $image )
{
foreach( $image as $ext => $url )
{
$photos[] = File::get( $ext, $url );
}
}
}
if ( count( $this->media ) )
{
foreach( $this->media as $media )
{
$photos[] = File::get( 'core_Promote', $media );
}
}
return ( count( $photos ) ) ? $photos : NULL;
}
/**
* Look for a specific image
*
* @param string $path Image path monthly_x_x/foo.gif
* @param string $extension Storage extension
* @return boolean
*/
public function hasImage( string $path, string $extension='core_Attachment' ) : bool
{
foreach( $this->images as $image )
{
foreach( $image as $ext => $url )
{
if ( $ext == $extension and $path == $url )
{
return TRUE;
}
}
}
return FALSE;
}
/**
* Returns text sent to a named service
*
* @param boolean $forDisplay Is this for display in output?
* @return string
*/
public function getText( bool $forDisplay=false ) : string
{
$text = $this->text;
if ( isset( $text['internal'] ) )
{
return ( $forDisplay ? nl2br( htmlspecialchars( $text[ 'internal' ], ENT_QUOTES | ENT_DISALLOWED, 'UTF-8', FALSE ) ) : $text['internal'] );
}
/* Fall back to the original content @note can this be more efficient when listing multiple items? */
return strip_tags( $forDisplay ? str_replace( [ "
", "