429 lines
12 KiB
PHP
429 lines
12 KiB
PHP
<?php
|
|
/**
|
|
* @brief Future publishing Trait for Content Models
|
|
* @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 10 Jan 2013
|
|
*/
|
|
|
|
namespace IPS\Content;
|
|
|
|
use IPS\Content;
|
|
use IPS\Events\Event;
|
|
use IPS\IPS;
|
|
use IPS\Member;
|
|
use IPS\Db;
|
|
use IPS\DateTime;
|
|
use IPS\Node\Model;
|
|
use IPS\Content\Search\Index;
|
|
use IPS\Helpers\Form\Date;
|
|
use BadMethodCallException;
|
|
|
|
use function defined;
|
|
use function header;
|
|
use function time;
|
|
use function is_array;
|
|
use function method_exists;
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/**
|
|
* Future publishing Trait for Content Models
|
|
*/
|
|
trait FuturePublishing
|
|
{
|
|
/**
|
|
* Can view future publishing items?
|
|
*
|
|
* @param Member|NULL $member The member to check for (NULL for currently logged in member)
|
|
* @param Model|null $container Container
|
|
* @return bool
|
|
* @note If called without passing $container, this method falls back to global "can view hidden content" moderator permission which isn't always what you want - pass $container if in doubt
|
|
*/
|
|
public static function canViewFutureItems( ?Member $member=NULL, ?Model $container = NULL ): bool
|
|
{
|
|
$member = $member ?: Member::loggedIn();
|
|
return $container ? static::modPermission( 'view_future', $member, $container ) : $member->modPermission( "can_view_future_content" );
|
|
}
|
|
|
|
/**
|
|
* Can set items to be published in the future?
|
|
*
|
|
* @param Member|NULL $member The member to check for (NULL for currently logged in member)
|
|
* @param Model|null $container Container
|
|
* @return bool
|
|
*/
|
|
public static function canFuturePublish( ?Member $member=NULL, ?Model $container = NULL ): bool
|
|
{
|
|
$member = $member ?: Member::loggedIn();
|
|
return $container ? static::modPermission( 'future_publish', $member, $container ) : $member->modPermission( "can_future_publish_content" );
|
|
}
|
|
|
|
/**
|
|
* Can publish future items?
|
|
*
|
|
* @param Member|NULL $member The member to check for (NULL for currently logged in member)
|
|
* @param Model|null $container Container
|
|
* @return bool
|
|
*/
|
|
public function canPublish( ?Member $member=NULL, ?Model $container = NULL ): bool
|
|
{
|
|
/* Extensions go first */
|
|
if( $permCheck = Permissions::can( 'publish', $this, $member ) )
|
|
{
|
|
return ( $permCheck === Permissions::PERM_ALLOW );
|
|
}
|
|
|
|
if( !$this->actionEnabled( 'publish', $member ) )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return static::canFuturePublish( $member, $container );
|
|
}
|
|
|
|
/**
|
|
* "Unpublishes" an item.
|
|
* @note This will not change the item's date. This should be done via the form methods if required
|
|
*
|
|
* @param Member|NULL $member The member doing the action (NULL for currently logged in member)
|
|
* @return void
|
|
*/
|
|
public function unpublish( ?Member $member=NULL ): void
|
|
{
|
|
/* Now do the actual stuff */
|
|
if ( isset( static::$databaseColumnMap['is_future_entry'] ) AND isset( static::$databaseColumnMap['future_date'] ) )
|
|
{
|
|
$future = static::$databaseColumnMap['is_future_entry'];
|
|
|
|
$this->$future = 1;
|
|
}
|
|
|
|
$this->save();
|
|
$this->onUnpublish( $member );
|
|
|
|
/* And update the tags perm cache */
|
|
if ( IPS::classUsesTrait( $this, 'IPS\Content\Taggable' ) )
|
|
{
|
|
Db::i()->update( 'core_tags_perms', array( 'tag_perm_visible' => 0 ), array( 'tag_perm_aai_lookup=?', $this->tagAAIKey() ) );
|
|
}
|
|
|
|
/* Update search index */
|
|
if( Content\Search\SearchContent::isSearchable( $this ) )
|
|
{
|
|
Index::i()->removeFromSearchIndex( $this );
|
|
}
|
|
|
|
$this->expireWidgetCaches();
|
|
$this->adjustSessions();
|
|
}
|
|
|
|
/**
|
|
* Publishes a 'future' entry now
|
|
*
|
|
* @param Member|NULL $member The member doing the action (NULL for currently logged in member)
|
|
* @return void
|
|
*/
|
|
public function publish( ?Member $member=NULL ): void
|
|
{
|
|
/* Now do the actual stuff */
|
|
if ( isset( static::$databaseColumnMap['is_future_entry'] ) AND isset( static::$databaseColumnMap['future_date'] ) )
|
|
{
|
|
$date = static::$databaseColumnMap['future_date'];
|
|
$future = static::$databaseColumnMap['is_future_entry'];
|
|
|
|
$this->$date = time();
|
|
$this->$future = 0;
|
|
}
|
|
|
|
$dateColumn = static::$databaseColumnMap['date'];
|
|
if ( \is_array( $dateColumn ) )
|
|
{
|
|
$dateColumn = array_pop( $dateColumn );
|
|
}
|
|
$this->$dateColumn = time();
|
|
|
|
/* Update the item */
|
|
if ( isset( static::$databaseColumnMap['last_comment'] ) or isset( static::$databaseColumnMap['last_review'] ) )
|
|
{
|
|
if ( isset( static::$databaseColumnMap['last_comment'] ) )
|
|
{
|
|
$lastCommentField = static::$databaseColumnMap['last_comment'];
|
|
if ( is_array( $lastCommentField ) )
|
|
{
|
|
foreach ( $lastCommentField as $column )
|
|
{
|
|
$this->$column = time();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$this->$lastCommentField = time();
|
|
}
|
|
}
|
|
|
|
if ( isset( static::$databaseColumnMap['last_review'] ) )
|
|
{
|
|
$lastReviewField = static::$databaseColumnMap['last_review'];
|
|
$this->$lastReviewField = time();
|
|
}
|
|
}
|
|
|
|
$this->save();
|
|
$this->onPublish( $member );
|
|
|
|
/* And update the tags perm cache */
|
|
if ( IPS::classUsesTrait( $this, 'IPS\Content\Taggable' ) )
|
|
{
|
|
Db::i()->update( 'core_tags_perms', array( 'tag_perm_visible' => 1 ), array( 'tag_perm_aai_lookup=?', $this->tagAAIKey() ) );
|
|
}
|
|
|
|
/* Update the first comment */
|
|
if ( static::$firstCommentRequired )
|
|
{
|
|
$comment = $this->firstComment();
|
|
|
|
/* @var array $databaseColumnMap */
|
|
$date = $comment::$databaseColumnMap['date'];
|
|
|
|
$comment->$date = time();
|
|
$comment->save();
|
|
}
|
|
|
|
/* Mark this item as read for the author */
|
|
$this->markRead( $this->author() );
|
|
|
|
/* Update search index */
|
|
if( Content\Search\SearchContent::isSearchable( $this ) )
|
|
{
|
|
Index::i()->index( ( static::$firstCommentRequired ) ? $this->firstComment() : $this );
|
|
}
|
|
|
|
/* Send notifications if necessary */
|
|
$this->sendNotifications();
|
|
|
|
/* Give out points */
|
|
$this->author()->achievementAction( 'core', 'NewContentItem', $this );
|
|
}
|
|
|
|
/**
|
|
* Syncing to run when publishing something previously pending publishing
|
|
*
|
|
* @param Member|NULL|FALSE $member The member doing the action (NULL for currently logged in member, FALSE for no member)
|
|
* @return void
|
|
*/
|
|
public function onPublish(Member|null|bool $member ): void
|
|
{
|
|
if ( method_exists( $this, 'container' ) )
|
|
{
|
|
try
|
|
{
|
|
$container = $this->container();
|
|
|
|
if ( $container->_futureItems !== NULL )
|
|
{
|
|
$container->_futureItems = ( $container->_futureItems > 0 ) ? $container->_futureItems - 1 : 0;
|
|
}
|
|
|
|
if( !$this->skipContainerRebuild )
|
|
{
|
|
$container->_items = $container->_items + 1;
|
|
|
|
if ( isset( static::$commentClass ) )
|
|
{
|
|
$container->_comments = $container->_comments + $this->mapped('num_comments');
|
|
$container->setLastComment();
|
|
}
|
|
if ( isset( static::$reviewClass ) )
|
|
{
|
|
$container->_reviews = $container->_reviews + $this->mapped('num_reviews');
|
|
$container->setLastReview();
|
|
}
|
|
|
|
$container->save();
|
|
}
|
|
}
|
|
catch ( BadMethodCallException ) { }
|
|
}
|
|
|
|
Event::fire( 'onStatusChange', $this, array( 'publish' ) );
|
|
|
|
/* Synch topics */
|
|
if( IPS::classUsesTrait( $this,'IPS\Content\ItemTopic' ) )
|
|
{
|
|
$this->itemPublished( $member );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Syncing to run when unpublishing an item (making it a future dated entry when it was already published)
|
|
*
|
|
* @param Member|NULL|FALSE $member The member doing the action (NULL for currently logged in member, FALSE for no member)
|
|
* @return void
|
|
*/
|
|
public function onUnpublish( Member|NULL|false $member ): void
|
|
{
|
|
if ( method_exists( $this, 'container' ) )
|
|
{
|
|
try
|
|
{
|
|
$container = $this->container();
|
|
|
|
if ( $container->_futureItems !== NULL )
|
|
{
|
|
$container->_futureItems = $container->_futureItems + 1;
|
|
}
|
|
|
|
$container->_items = $container->_items - 1;
|
|
|
|
if ( isset( static::$commentClass ) )
|
|
{
|
|
$container->_comments = $container->_comments - $this->mapped('num_comments');
|
|
$container->setLastComment();
|
|
}
|
|
if ( isset( static::$reviewClass ) )
|
|
{
|
|
$container->_reviews = $container->_reviews - $this->mapped('num_reviews');
|
|
$container->setLastReview();
|
|
}
|
|
|
|
$container->save();
|
|
}
|
|
catch ( BadMethodCallException ) { }
|
|
}
|
|
|
|
Event::fire( 'onStatusChange', $this, array( 'unpublish' ) );
|
|
|
|
/* Synch topics */
|
|
if( IPS::classUsesTrait( $this,'IPS\Content\ItemTopic' ) )
|
|
{
|
|
$this->itemUnpublished( $member );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Is this a future entry?
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function isFutureDate(): bool
|
|
{
|
|
if ( isset( static::$databaseColumnMap['is_future_entry'] ) and isset( static::$databaseColumnMap['future_date'] ) )
|
|
{
|
|
$column = static::$databaseColumnMap['future_date'];
|
|
if ( $this->$column > time() )
|
|
{
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw new BadMethodCallException( "Class using must implement is_future_entry and future_date column maps." );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return the tooltip blurb for future entries
|
|
*
|
|
* @return string
|
|
*/
|
|
public function futureDateBlurb(): string
|
|
{
|
|
$column = static::$databaseColumnMap['future_date'];
|
|
$time = DateTime::ts( $this->$column );
|
|
return Member::loggedIn()->language()->addToStack("content_future_date_blurb", FALSE, array( 'sprintf' => array( $time->localeDate(), $time->localeTime() ) ) );
|
|
}
|
|
|
|
/**
|
|
* Set future publishing dates
|
|
*
|
|
* @param array $values Values from form
|
|
* @return void
|
|
*/
|
|
public function setFuturePublishingDates( array $values ): void
|
|
{
|
|
$time = $values[ static::$formLangPrefix . 'date' ] instanceof DateTime ? $values[ static::$formLangPrefix . 'date' ] : DateTime::create();
|
|
|
|
if( isset( static::$databaseColumnMap['date'] ) )
|
|
{
|
|
$isFutureEntry = ( $time->getTimestamp() > time() );
|
|
$column = static::$databaseColumnMap['is_future_entry'];
|
|
if( $this->$column != $isFutureEntry )
|
|
{
|
|
$this->$column = $isFutureEntry;
|
|
}
|
|
}
|
|
|
|
if( isset( static::$databaseColumnMap['future_date'] ) )
|
|
{
|
|
$column = static::$databaseColumnMap['future_date'];
|
|
$this->$column = $time->getTimestamp();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the Date Field for the publish date
|
|
*
|
|
* @param Content|null $item
|
|
* @return Date
|
|
*/
|
|
public static function getPublishDateField( ?Content $item = NULL ): Date
|
|
{
|
|
/* If it's not published, we don't want to allow any past times */
|
|
$column = static::$databaseColumnMap['future_date'];
|
|
|
|
$minFutureTime = static::getMinimumPublishDate();
|
|
$unlimited = 0;
|
|
$unlimitedLang = 'immediately';
|
|
if( $item )
|
|
{
|
|
$unlimited = NULL;
|
|
$unlimitedLang = NULL;
|
|
}
|
|
|
|
return new Date( static::$formLangPrefix . 'date', ( $item and $item->$column ) ? DateTime::ts( $item->$column ) : 0, FALSE, array( 'time' => TRUE, 'unlimited' => $unlimited, 'unlimitedLang' => $unlimitedLang, 'min' => $minFutureTime ), NULL, NULL, NULL, static::$formLangPrefix . 'date' );
|
|
}
|
|
|
|
/**
|
|
* Can the publish date be changed while editing the item?
|
|
* Formerly a properly, however classes cannot overload / redeclare properties from traits.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public static function allowPublishDateWhileEditing(): bool
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* Whether this content supports future publish dates
|
|
*
|
|
* @param Item|null $item
|
|
* @return bool
|
|
*/
|
|
public static function supportsPublishDate( ?Item $item ): bool
|
|
{
|
|
return isset( static::$databaseColumnMap['future_date'] ) AND ( ( $item AND ( $item->isFutureDate() OR static::allowPublishDateWhileEditing() ) OR !$item ) );
|
|
}
|
|
|
|
/**
|
|
* Returns the earliest publish date for the new content item , should be the current timestamp for most content types
|
|
*
|
|
* @return DateTime|null
|
|
*/
|
|
public static function getMinimumPublishDate(): DateTime|NULL
|
|
{
|
|
return DateTime::create();
|
|
}
|
|
} |