Version 4.7.0
This commit is contained in:
1 parent
517a5e1f70
commit
8ba7d43898
1933 files changed
+65613
-11332
No files matched your search
@@ -239,13 +239,13 @@ class _CommentController extends \IPS\Api\Controller
|
||||
$class = $this->class;
|
||||
if ( \in_array( 'IPS\Content\Review', class_parents( $class ) ) )
|
||||
{
|
||||
$comment = $class::create( $item, $content, FALSE, \intval( \IPS\Request::i()->rating ), $author->member_id ? NULL : $author->real_name, $author, $date, ( !$this->member and \IPS\Request::i()->ip_address ) ? \IPS\Request::i()->ip_address : \IPS\Request::i()->ipAddress(), $hidden );
|
||||
$comment = $class::create( $item, $content, FALSE, \intval( \IPS\Request::i()->rating ), $author->member_id ? NULL : $author->real_name, $author, $date, ( !$this->member and \IPS\Request::i()->ip_address ) ? \IPS\Request::i()->ip_address : \IPS\Request::i()->ipAddress(), $hidden, ( isset( \IPS\Request::i()->anonymous ) ? (bool) \IPS\Request::i()->anonymous : NULL ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
$comment = $class::create( $item, $content, FALSE, $author->member_id ? NULL : $author->real_name, NULL, $author, $date, ( !$this->member and \IPS\Request::i()->ip_address ) ? \IPS\Request::i()->ip_address : \IPS\Request::i()->ipAddress(), $hidden );
|
||||
$comment = $class::create( $item, $content, FALSE, $author->member_id ? NULL : $author->real_name, NULL, $author, $date, ( !$this->member and \IPS\Request::i()->ip_address ) ? \IPS\Request::i()->ip_address : \IPS\Request::i()->ipAddress(), $hidden, ( isset( \IPS\Request::i()->anonymous ) ? (bool) \IPS\Request::i()->anonymous : NULL ) );
|
||||
}
|
||||
|
||||
|
||||
/* Index */
|
||||
if ( $item instanceof \IPS\Content\Searchable )
|
||||
{
|
||||
@@ -369,6 +369,158 @@ class _CommentController extends \IPS\Api\Controller
|
||||
|
||||
/* Return */
|
||||
return new \IPS\Api\Response( 200, $comment->apiOutput( $this->member ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a reaction to a comment
|
||||
*
|
||||
* @param $id
|
||||
* @return \IPS\Api\Response
|
||||
* @throws \IPS\Api\Exception
|
||||
*/
|
||||
public function _reactRemove( $id ): \IPS\Api\Response
|
||||
{
|
||||
try
|
||||
{
|
||||
$member = \IPS\Member::load( \IPS\Request::i()->author );
|
||||
}
|
||||
catch( \OutOfRangeException $e )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'NO_AUTHOR', '1S425/6', 404 );
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$class = $this->class;
|
||||
if ( $this->member )
|
||||
{
|
||||
$object = $class::loadAndCheckPerms( $id, $this->member );
|
||||
}
|
||||
else
|
||||
{
|
||||
$object = $class::load( $id );
|
||||
}
|
||||
|
||||
$object->removeReaction( $member );
|
||||
|
||||
return new \IPS\Api\Response( 200, $object->apiOutput( $this->member ) );
|
||||
}
|
||||
catch ( \DomainException $e )
|
||||
{
|
||||
throw new \IPS\Api\Exception( $e->getMessage(), '1S425/7', 403 );
|
||||
}
|
||||
catch ( \OutOfRangeException $e )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'INVALID_ID', '1S425/8', 404 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* React to a comment
|
||||
*
|
||||
* @param $id
|
||||
* @return \IPS\Api\Response
|
||||
* @throws \IPS\Api\Exception
|
||||
*/
|
||||
public function _reactAdd( $id ): \IPS\Api\Response
|
||||
{
|
||||
try
|
||||
{
|
||||
$reaction = \IPS\Content\Reaction::load( \IPS\Request::i()->id );
|
||||
}
|
||||
catch( \OutOfRangeException $e )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'NO_REACTION', '1S425/2', 404 );
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$member = \IPS\Member::load( \IPS\Request::i()->author );
|
||||
}
|
||||
catch( \OutOfRangeException $e )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'NO_AUTHOR', '1S425/3', 404 );
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$class = $this->class;
|
||||
if ( $this->member )
|
||||
{
|
||||
$object = $class::loadAndCheckPerms( $id, $this->member );
|
||||
}
|
||||
else
|
||||
{
|
||||
$object = $class::load( $id );
|
||||
}
|
||||
|
||||
$object->react( $reaction, $member );
|
||||
|
||||
return new \IPS\Api\Response( 200, $object->apiOutput( $this->member ) );
|
||||
}
|
||||
catch ( \DomainException $e )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'REACT_ERROR_' . $e->getMessage(), '1S425/4', 403 );
|
||||
}
|
||||
catch ( \OutOfRangeException $e )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'INVALID_ID', '1S425/5', 404 );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Report a comment
|
||||
*
|
||||
* @param $id
|
||||
* @return \IPS\Api\Response
|
||||
* @throws \IPS\Api\Exception
|
||||
*/
|
||||
public function _report( $id ): \IPS\Api\Response
|
||||
{
|
||||
try
|
||||
{
|
||||
$member = \IPS\Member::load( \IPS\Request::i()->author );
|
||||
}
|
||||
catch( \OutOfRangeException $e )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'NO_AUTHOR', '1S425/B', 404 );
|
||||
}
|
||||
|
||||
$class = $this->class;
|
||||
$idColumn = $class::$databaseColumnId;
|
||||
if ( $this->member )
|
||||
{
|
||||
$object = $class::loadAndCheckPerms( $id, $this->member );
|
||||
}
|
||||
else
|
||||
{
|
||||
$object = $class::load( $id );
|
||||
}
|
||||
|
||||
/* Has this member already reported this in the past 24 hours */
|
||||
try
|
||||
{
|
||||
$index = \IPS\core\Reports\Report::loadByClassAndId( \get_class( $object ), $object->$idColumn );
|
||||
$report = \IPS\Db::i()->select( '*', 'core_rc_reports', array( 'rid=? and report_by=? and date_reported > ?', $index->id, $member, time() - ( \IPS\Settings::i()->automoderation_report_again_mins * 60 ) ) )->first();
|
||||
|
||||
/* They have aleady reported, so do nothing */
|
||||
throw new \IPS\Api\Exception( 'REPORTED_ALREADY', '1S425/C', 404 );
|
||||
}
|
||||
catch( \Exception $e )
|
||||
{
|
||||
/* No issues here */
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$object->report( ( isset( \IPS\Request::i()->message ) ? \IPS\Request::i()->message : '' ), ( isset(\IPS\Request::i()->report_type) ? \IPS\Request::i()->report_type : 0 ), $member );
|
||||
}
|
||||
catch( \UnexpectedValueException $e )
|
||||
{
|
||||
throw new \IPS\Api\Exception( 'REPORT_ERROR_' . $e->getMessage(), '1S425/B', 403 );
|
||||
}
|
||||
|
||||
return new \IPS\Api\Response( 200, $object->apiOutput( $this->member ) );
|
||||
}
|
||||
}
|
||||
@@ -339,6 +339,23 @@ class _ItemController extends \IPS\Api\Controller
|
||||
}
|
||||
}
|
||||
|
||||
/** We intentionally allow anonymous content in nodes where it's possible (but where it's disabled via the settings */
|
||||
if( isset( \IPS\Request::i()->anonymous ) AND \in_array( 'IPS\Content\Anonymous', class_implements( \get_class( $item ) ) ) )
|
||||
{
|
||||
/* we need to save the item before we set the anonymous data */
|
||||
$idColumn = $item::$databaseColumnId;
|
||||
if ( !$item->$idColumn )
|
||||
{
|
||||
$item->save();
|
||||
}
|
||||
try
|
||||
{
|
||||
$item->setAnonymous( (bool) \IPS\Request::i()->anonymous, $item->author() );
|
||||
}
|
||||
catch ( \BadMethodCallException $e ){}
|
||||
|
||||
}
|
||||
|
||||
/* Update first comment if required, and it's not a new item */
|
||||
$field = isset( $item::$databaseColumnMap['first_comment_id'] ) ? $item::$databaseColumnMap['first_comment_id'] : NULL;
|
||||
$commentClass = $item::$commentClass;
|
||||
@@ -392,9 +409,10 @@ class _ItemController extends \IPS\Api\Controller
|
||||
|
||||
/* Create item */
|
||||
$item = $class::createItem( $author, ( !$this->member and \IPS\Request::i()->ip_address ) ? \IPS\Request::i()->ip_address : \IPS\Request::i()->ipAddress(), $date, $container );
|
||||
|
||||
$this->_createOrUpdate( $item, 'add' );
|
||||
$item->save();
|
||||
|
||||
|
||||
/* Create post */
|
||||
if ( $class::$firstCommentRequired )
|
||||
{
|
||||
@@ -404,9 +422,9 @@ class _ItemController extends \IPS\Api\Controller
|
||||
{
|
||||
$postContents = \IPS\Text\Parser::parseStatic( $postContents, TRUE, NULL, $this->member, $class::$application . '_' . mb_ucfirst( $class::$module ) );
|
||||
}
|
||||
|
||||
|
||||
$commentClass = $item::$commentClass;
|
||||
$post = $commentClass::create( $item, $postContents, TRUE, $author->member_id ? NULL : $author->real_name, NULL, $author, $date, ( !$this->member and \IPS\Request::i()->ip_address ) ? \IPS\Request::i()->ip_address : \IPS\Request::i()->ipAddress() );
|
||||
$post = $commentClass::create( $item, $postContents, TRUE, $author->member_id ? NULL : $author->real_name, NULL, $author, $date, ( !$this->member and \IPS\Request::i()->ip_address ) ? \IPS\Request::i()->ip_address : \IPS\Request::i()->ipAddress(), NULL, ( isset( \IPS\Request::i()->anonymous ) ? (bool) \IPS\Request::i()->anonymous : NULL ) );
|
||||
|
||||
if ( isset( $class::$databaseColumnMap['first_comment_id'] ) )
|
||||
{
|
||||
@@ -516,21 +534,24 @@ class _ItemController extends \IPS\Api\Controller
|
||||
}
|
||||
$itemIdColumn = $itemClass::$databaseColumnId;
|
||||
$where [] = array( $commentClass::$databasePrefix . $commentClass::$databaseColumnMap['item'] . '=?', $item->$itemIdColumn );
|
||||
|
||||
/* Hidden? */
|
||||
if ( isset( \IPS\Request::i()->hidden ) AND \in_array( 'IPS\Content\Hideable', class_implements( $commentClass ) ) )
|
||||
|
||||
/* Hideable? */
|
||||
if ( \in_array( 'IPS\Content\Hideable', class_implements( $commentClass ) ) )
|
||||
{
|
||||
if ( \IPS\Request::i()->hidden )
|
||||
/* If request for hidden comments, only show if request is via API key or by a moderator */
|
||||
if ( isset( \IPS\Request::i()->hidden ) AND \IPS\Request::i()->hidden
|
||||
AND ( $this->member === NULL OR $commentClass::modPermission( 'view_hidden', $this->member ) ) )
|
||||
{
|
||||
if ( isset( $commentClass::$databaseColumnMap['hidden'] ) )
|
||||
{
|
||||
$where[] = array( $commentClass::$databasePrefix . $commentClass::$databaseColumnMap['hidden'] . '<>0' );
|
||||
$where[] = array( $commentClass::$databasePrefix . $commentClass::$databaseColumnMap['hidden'] . ' NOT IN( -3, -2 )' );
|
||||
}
|
||||
else
|
||||
{
|
||||
$where[] = array( $commentClass::$databasePrefix . $commentClass::$databaseColumnMap['approved'] . '<>1' );
|
||||
$where[] = array( $commentClass::$databasePrefix . $commentClass::$databaseColumnMap['approved'] . ' NOT IN( -3, -2 )' );
|
||||
}
|
||||
}
|
||||
/* Do not show hidden comments */
|
||||
else
|
||||
{
|
||||
if ( isset( $commentClass::$databaseColumnMap['hidden'] ) )
|
||||
|
||||
@@ -60,7 +60,7 @@ trait ClubContainer
|
||||
*/
|
||||
public function clubCheckRules()
|
||||
{
|
||||
if ( $club = $this->club() AND !$club->rulesAcknowledged() )
|
||||
if ( $club = $this->club() AND !$club->rulesAcknowledged() AND !\IPS\Member::loggedIn()->modPermission( 'can_access_all_clubs' ) )
|
||||
{
|
||||
\IPS\Output::i()->redirect( $club->url()->setQueryString( 'do', 'rules' )->addRef( \IPS\Request::i()->url() ) );
|
||||
}
|
||||
@@ -94,7 +94,7 @@ trait ClubContainer
|
||||
* @param \IPS\Helpers\Form $form Form object
|
||||
* @return void
|
||||
*/
|
||||
public function clubForm( \IPS\Helpers\Form $form )
|
||||
public function clubForm( \IPS\Helpers\Form $form, \IPS\Member\Club $club )
|
||||
{
|
||||
$itemClass = static::$contentItemClass;
|
||||
$form->add( new \IPS\Helpers\Form\Text( 'club_node_name', $this->_id ? $this->_title : \IPS\Member::loggedIn()->language()->addToStack( static::clubFrontTitle() ), TRUE, array( 'maxLength' => 255 ) ) );
|
||||
@@ -116,6 +116,7 @@ trait ClubContainer
|
||||
$this->_saveClubForm( $club, $values );
|
||||
|
||||
$needToUpdatePermissions = FALSE;
|
||||
|
||||
if ( !$haveId )
|
||||
{
|
||||
$clubIdColumn = $this->clubIdColumn();
|
||||
@@ -125,15 +126,21 @@ trait ClubContainer
|
||||
'club_id' => $club->id,
|
||||
'node_class' => $nodeClass,
|
||||
'node_id' => $this->_id,
|
||||
'name' => $values['club_node_name']
|
||||
'name' => $values['club_node_name'],
|
||||
'public' => ( isset( $values['club_node_public'] ) ) ? $values['club_node_public'] : 0
|
||||
) );
|
||||
|
||||
$needToUpdatePermissions = TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
if( isset( $values['club_node_public'] ) AND $values['club_node_public'] != $this->isPublic() )
|
||||
{
|
||||
$needToUpdatePermissions = TRUE;
|
||||
}
|
||||
|
||||
$this->save();
|
||||
\IPS\Db::i()->update( 'core_clubs_node_map', array( 'name' => $values['club_node_name'] ), array( 'club_id=? AND node_class=? AND node_id=?', $club->id, $nodeClass, $this->_id ) );
|
||||
\IPS\Db::i()->update( 'core_clubs_node_map', array( 'name' => $values['club_node_name'], 'public' => $values['club_node_public'] ?? 0), array( 'club_id=? AND node_class=? AND node_id=?', $club->id, $nodeClass, $this->_id ) );
|
||||
}
|
||||
|
||||
\IPS\Lang::saveCustom( $itemClass::$application, static::$titleLangPrefix . $this->_id, $values['club_node_name'] );
|
||||
@@ -197,9 +204,10 @@ trait ClubContainer
|
||||
case $club::TYPE_OPEN:
|
||||
case $club::TYPE_READONLY:
|
||||
$insert[ 'perm_' . $v ] = '*';
|
||||
break;
|
||||
|
||||
break;
|
||||
case $club::TYPE_CLOSED:
|
||||
$insert[ 'perm_' . $v ] = ( $this->isPublic() ) ? "*" : "cm,c{$club->id}";
|
||||
break;
|
||||
case $club::TYPE_PRIVATE:
|
||||
$insert[ 'perm_' . $v ] = "cm,c{$club->id}";
|
||||
break;
|
||||
@@ -398,4 +406,21 @@ trait ClubContainer
|
||||
return parent::parents();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Is public
|
||||
*
|
||||
* @return Bool
|
||||
*/
|
||||
public function isPublic()
|
||||
{
|
||||
try
|
||||
{
|
||||
return (bool) \IPS\Db::i()->select( 'public', 'core_clubs_node_map', array( 'club_id=? AND node_class=? AND node_id=?', $this->club()->id, \get_called_class(), $this->_id ) )->first();
|
||||
}
|
||||
catch ( \UnderflowException $e )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
+210
-20
@@ -59,15 +59,18 @@ abstract class _Comment extends \IPS\Content
|
||||
* @param \IPS\DateTime|NULL $time The time
|
||||
* @param string|NULL $ipAddress The IP address or NULL to detect automatically
|
||||
* @param int|NULL $hiddenStatus NULL to set automatically or override: 0 = unhidden; 1 = hidden, pending moderator approval; -1 = hidden (as if hidden by a moderator)
|
||||
* @param int|NULL $anonymous NULL for no value, 0 or 1 for a value (0=no, 1=yes)
|
||||
* @return static
|
||||
*/
|
||||
public static function create( $item, $comment, $first=FALSE, $guestName=NULL, $incrementPostCount=NULL, $member=NULL, \IPS\DateTime $time=NULL, $ipAddress=NULL, $hiddenStatus=NULL )
|
||||
public static function create( $item, $comment, $first=FALSE, $guestName=NULL, $incrementPostCount=NULL, $member=NULL, \IPS\DateTime $time=NULL, $ipAddress=NULL, $hiddenStatus=NULL, $anonymous=NULL )
|
||||
{
|
||||
if ( $member === NULL )
|
||||
{
|
||||
$member = \IPS\Member::loggedIn();
|
||||
}
|
||||
|
||||
$idColumn = $item::$databaseColumnId;
|
||||
|
||||
/* Create the object */
|
||||
$obj = new static;
|
||||
foreach ( array( 'item', 'date', 'author', 'author_name', 'content', 'ip_address', 'first', 'approved', 'hidden' ) as $k )
|
||||
@@ -78,7 +81,6 @@ abstract class _Comment extends \IPS\Content
|
||||
switch ( $k )
|
||||
{
|
||||
case 'item':
|
||||
$idColumn = $item::$databaseColumnId;
|
||||
$val = $item->$idColumn;
|
||||
break;
|
||||
|
||||
@@ -184,42 +186,52 @@ abstract class _Comment extends \IPS\Content
|
||||
}
|
||||
|
||||
/* Check if profanity filters should mod-queue this comment */
|
||||
$obj->checkProfanityFilters( $first );
|
||||
$obj->checkProfanityFilters( $first, FALSE );
|
||||
|
||||
/* Save the comment */
|
||||
$obj->save();
|
||||
|
||||
/* Set anonymous status */
|
||||
if( $anonymous !== NULL )
|
||||
{
|
||||
try
|
||||
{
|
||||
$obj->setAnonymous( (bool) $anonymous, $member );
|
||||
}
|
||||
catch ( \Exception $e ) { }
|
||||
}
|
||||
|
||||
/* Increment post count */
|
||||
try
|
||||
{
|
||||
if ( !$obj->isAnonymous() and !$obj->hidden() and ( $incrementPostCount === TRUE or ( $incrementPostCount === NULL and static::incrementPostCount( $item->container() ) ) ) )
|
||||
{
|
||||
$obj->author()->member_posts++;
|
||||
$member->member_posts++;
|
||||
}
|
||||
}
|
||||
catch( \BadMethodCallException $e ) { }
|
||||
|
||||
|
||||
/* Update member's last post and daily post limits */
|
||||
if( $obj->author()->member_id AND $obj::incrementPostCount() )
|
||||
if( $member->member_id AND $obj::incrementPostCount() )
|
||||
{
|
||||
$obj->author()->member_last_post = time();
|
||||
|
||||
$member->member_last_post = time();
|
||||
|
||||
/* Update posts per day limits */
|
||||
if ( $obj->author()->group['g_ppd_limit'] )
|
||||
if ( $member->group['g_ppd_limit'] )
|
||||
{
|
||||
$current = $obj->author()->members_day_posts;
|
||||
$current = $member->members_day_posts;
|
||||
|
||||
$current[0] += 1;
|
||||
if ( $current[1] == 0 )
|
||||
{
|
||||
$current[1] = \IPS\DateTime::create()->getTimestamp();
|
||||
}
|
||||
|
||||
$obj->author()->members_day_posts = $current;
|
||||
|
||||
$member->members_day_posts = $current;
|
||||
}
|
||||
|
||||
$obj->author()->save();
|
||||
|
||||
$member->save();
|
||||
}
|
||||
|
||||
/* Send notifications and dish out points */
|
||||
@@ -228,7 +240,7 @@ abstract class _Comment extends \IPS\Content
|
||||
if ( !$obj->hidden() and ( !$first or !$item::$firstCommentRequired ) )
|
||||
{
|
||||
$obj->sendNotifications();
|
||||
$obj->author()->achievementAction( 'core', 'Comment', $obj );
|
||||
$member->achievementAction( 'core', 'Comment', $obj );
|
||||
}
|
||||
else if( $obj->hidden() === 1 )
|
||||
{
|
||||
@@ -241,10 +253,45 @@ abstract class _Comment extends \IPS\Content
|
||||
{
|
||||
\IPS\Api\Webhook::fire( str_replace( '\\', '', \substr( \get_called_class(), 3 ) ) . '_create', $obj, $obj->webhookFilters() );
|
||||
}
|
||||
|
||||
|
||||
/* DataLayer Event */
|
||||
if ( \IPS\Settings::i()->core_datalayer_enabled and !( $item::$firstCommentRequired and $obj->isFirst() ) )
|
||||
{
|
||||
if ( isset( $item::$title ) and $item::$title === \IPS\core\Statuses\Status::$title )
|
||||
{
|
||||
$event = 'social_reply';
|
||||
$member = \IPS\Member::load( $item->member_id );
|
||||
$properties = array(
|
||||
'profile_id' => $member->member_id,
|
||||
'profile_name' => $member->name,
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
$event = 'comment_create';
|
||||
$properties = $obj->getDataLayerProperties();
|
||||
}
|
||||
|
||||
if ( $properties['content_area'] === 'personal_messages' )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$properties = array_merge( $properties, array(
|
||||
'author_id' => $obj->author()->member_id,
|
||||
'author_name' => $obj->author()->name ?? 'Guest',
|
||||
) );
|
||||
|
||||
\IPS\core\DataLayer::i()->addEvent( $event, $properties );
|
||||
}
|
||||
|
||||
/* Update item */
|
||||
$obj->postCreate();
|
||||
|
||||
/* This needs to happen before the index is added */
|
||||
$parameters = array_merge( array( 'reply-' . $item::$application . '/' . $item::$module . '-' . $item->$idColumn ), $obj->attachmentIds() );
|
||||
\IPS\File::claimAttachments( ...$parameters );
|
||||
|
||||
/* Add to search index */
|
||||
if ( $obj instanceof \IPS\Content\Searchable )
|
||||
{
|
||||
@@ -254,6 +301,32 @@ abstract class _Comment extends \IPS\Content
|
||||
/* Return */
|
||||
return $obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Field cache for getDataLayerProperties
|
||||
*/
|
||||
protected $_dataLayerProperties = array();
|
||||
|
||||
/**
|
||||
* Get the properties that can be added to the datalayer for this key
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getDataLayerProperties()
|
||||
{
|
||||
if ( empty( $this->_dataLayerProperties ) )
|
||||
{
|
||||
$item = $this->item();
|
||||
$this->_dataLayerProperties = $item->getDataLayerProperties( $this );
|
||||
if ( $this->_dataLayerProperties['content_type'] === 'question' )
|
||||
{
|
||||
$this->_dataLayerProperties['comment_type'] = 'answer';
|
||||
}
|
||||
$this->_dataLayerProperties = \IPS\core\DataLayer::i()->filterProperties( $this->_dataLayerProperties );
|
||||
}
|
||||
|
||||
return $this->_dataLayerProperties;
|
||||
}
|
||||
|
||||
/**
|
||||
* Join profile fields when loading comments?
|
||||
@@ -376,6 +449,18 @@ abstract class _Comment extends \IPS\Content
|
||||
{
|
||||
$item->container()->rebuildPostedIn( array( $item->$itemIdColumn ), array( $this->author() ) );
|
||||
}
|
||||
|
||||
/* Update trending */
|
||||
$class = \get_class( $item );
|
||||
if( \IPS\Application::appIsEnabled( 'cloud' ) and \IPS\cloud\Realtime::i()->isEnabled('trending') and $class::$includeInTrending )
|
||||
{
|
||||
/* Score by timestamp for trending */
|
||||
try
|
||||
{
|
||||
\IPS\Redis::i()->zIncrBy( 'trending', time(), $class . '__' . $item->$itemIdColumn );
|
||||
}
|
||||
catch( \Exception $e ) { }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -827,6 +912,12 @@ abstract class _Comment extends \IPS\Content
|
||||
{
|
||||
$item->clearCachedStatistics();
|
||||
}
|
||||
|
||||
/* Send the content items URL to IndexNow if the guest can view it */
|
||||
if( $item->canView( new \IPS\Member ) )
|
||||
{
|
||||
\IPS\core\IndexNow::addUrlToQueue( $item->url() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -844,6 +935,48 @@ abstract class _Comment extends \IPS\Content
|
||||
$itemColumn = static::$databaseColumnMap['item'];
|
||||
$commentIdColumn = static::$databaseColumnId;
|
||||
$this->$itemColumn = $item->$idColumn;
|
||||
|
||||
/* Does this comment's hidden status align with its item's hidden status? */
|
||||
if ( $this instanceof \IPS\Content\Hideable AND $this->hidden() !== 1 )
|
||||
{
|
||||
if ( $item->hidden() === -1 )
|
||||
{
|
||||
if ( isset( static::$databaseColumnMap['hidden'] ) )
|
||||
{
|
||||
$column = static::$databaseColumnMap['hidden'];
|
||||
|
||||
/* if this comment was directly hidden, we don't want to inherit its status from the item */
|
||||
if ( \intval( $this->$column ) !== -1 )
|
||||
{
|
||||
$this->$column = 2;
|
||||
}
|
||||
}
|
||||
elseif ( isset( static::$databaseColumnMap['approved'] ) )
|
||||
{
|
||||
$column = static::$databaseColumnMap['approved'];
|
||||
$this->$column = -1;
|
||||
}
|
||||
}
|
||||
elseif ( $this->hidden() === -1 )
|
||||
{
|
||||
if ( isset( static::$databaseColumnMap['hidden'] ) )
|
||||
{
|
||||
$column = static::$databaseColumnMap['hidden'];
|
||||
|
||||
/* if this comment was directly hidden, we don't want to inherit its status from the item */
|
||||
if ( \intval( $this->$column ) !== -1 )
|
||||
{
|
||||
$this->$column = 0;
|
||||
}
|
||||
}
|
||||
elseif ( isset( static::$databaseColumnMap['approved'] ) )
|
||||
{
|
||||
$column = static::$databaseColumnMap['approved'];
|
||||
$this->$column = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->save();
|
||||
|
||||
/* The new item needs to re-claim any attachments associated with this comment */
|
||||
@@ -865,7 +998,16 @@ abstract class _Comment extends \IPS\Content
|
||||
}
|
||||
|
||||
/* Update member map */
|
||||
\IPS\Db::i()->update( 'core_item_member_map', array( 'map_item_id' => $item->$idColumn ), array( 'map_class=? and map_item_id=?', \get_class( $oldItem ), $oldItem->$idColumn ) );
|
||||
try
|
||||
{
|
||||
\IPS\Db::i()->update( 'core_item_member_map', array( 'map_item_id' => $item->$idColumn ), array( 'map_class=? and map_item_id=?', \get_class( $oldItem ), $oldItem->$idColumn ) );
|
||||
}
|
||||
catch( \IPS\Db\Exception $e )
|
||||
{
|
||||
/* If we are splitting to a topic that this member has already posted in, this will cause a duplicate key issue. Best to remove rows and let the next page load rebuild */
|
||||
\IPS\Db::i()->delete( 'core_item_member_map', array( 'map_class=? and map_item_id=?', \get_class( $oldItem ), $oldItem->$idColumn ) );
|
||||
\IPS\Db::i()->delete( 'core_item_member_map', array( 'map_class=? and map_item_id=?', \get_class( $item ), $item->$idColumn ) );
|
||||
}
|
||||
|
||||
if( $skip === FALSE )
|
||||
{
|
||||
@@ -883,6 +1025,23 @@ abstract class _Comment extends \IPS\Content
|
||||
$item->clearCachedStatistics();
|
||||
}
|
||||
}
|
||||
|
||||
/* Send the content items URL to IndexNow */
|
||||
$noIndexUrls = [];
|
||||
$guest = new \IPS\Member;
|
||||
|
||||
if( $oldItem->canView( $guest ) )
|
||||
{
|
||||
$noIndexUrls[] = $oldItem->url();
|
||||
}
|
||||
if( $item->canView( $guest ) )
|
||||
{
|
||||
$noIndexUrls[] = $item->url();
|
||||
}
|
||||
if( $oldItem->canView( new \IPS\Member ) )
|
||||
{
|
||||
\IPS\core\IndexNow::addUrlsToQueue( $noIndexUrls );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1505,7 +1664,7 @@ abstract class _Comment extends \IPS\Content
|
||||
$attachments[] = array( 'core_Clubs' => $club->cover_photo );
|
||||
}
|
||||
|
||||
return \count( $attachments ) ? $attachments : NULL;
|
||||
return \count( $attachments ) ? \array_slice( $attachments, 0, $limit ) : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1593,12 +1752,13 @@ abstract class _Comment extends \IPS\Content
|
||||
* @apiresponse string content The content
|
||||
* @apiresponse bool hidden Is hidden?
|
||||
* @apiresponse string url URL to content
|
||||
* @apiresponse array reactions Array of reactions given, array key is member_id of reaction giver ([member_id => [ title, id, value, icon ]])
|
||||
*/
|
||||
public function apiOutput( \IPS\Member $authorizedMember = NULL )
|
||||
{
|
||||
$idColumn = static::$databaseColumnId;
|
||||
$itemColumn = static::$databaseColumnMap['item'];
|
||||
return array(
|
||||
$return = array(
|
||||
'id' => $this->$idColumn,
|
||||
'item_id' => $this->$itemColumn,
|
||||
'author' => $this->author()->apiOutput( $authorizedMember ),
|
||||
@@ -1607,6 +1767,35 @@ abstract class _Comment extends \IPS\Content
|
||||
'hidden' => (bool) $this->hidden(),
|
||||
'url' => (string) $this->url()
|
||||
);
|
||||
|
||||
if ( \IPS\IPS::classUsesTrait( $this, 'IPS\Content\Reactable' ) )
|
||||
{
|
||||
if ( $reactions = $this->reactions() )
|
||||
{
|
||||
$enabledReactions = \IPS\Content\Reaction::enabledReactions();
|
||||
$finalReactions = [];
|
||||
foreach( $reactions as $memberId => $array )
|
||||
{
|
||||
foreach( $array as $reaction )
|
||||
{
|
||||
$finalReactions[ $memberId ][] = [
|
||||
'title' => $enabledReactions[ $reaction ]->_title,
|
||||
'id' => $reaction,
|
||||
'value' => $enabledReactions[ $reaction ]->value,
|
||||
'icon' => (string) $enabledReactions[ $reaction ]->_icon->url
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$return['reactions'] = $finalReactions;
|
||||
}
|
||||
else
|
||||
{
|
||||
$return['reactions'] = [];
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1618,9 +1807,10 @@ abstract class _Comment extends \IPS\Content
|
||||
{
|
||||
$filters = parent::webhookFilters();
|
||||
$item = $this->item();
|
||||
|
||||
|
||||
/* This is kept for legacy compatibility, but starting with 4.6.13 the Content and Item class handle this inside their own webhookFilter methods */
|
||||
$filters['hidden'] = ( ( (bool) $this->hidden() ) or ( (bool) $item->hidden() ) );
|
||||
|
||||
|
||||
if ( \in_array( 'IPS\Content\Lockable', class_implements( $item ) ) )
|
||||
{
|
||||
$filters['locked'] = $item->locked();
|
||||
|
||||
+418
-105
@@ -402,7 +402,9 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
}
|
||||
|
||||
\IPS\Db::i()->delete( 'core_content_featured', [ 'feature_content_id=? and feature_content_class=?', $this->$idColumn, \get_class( $this ) ] );
|
||||
|
||||
|
||||
\IPS\Api\Webhook::fire( str_replace( '\\', '', \substr( \get_called_class(), 3 ) ) . '_delete', $this, $this->webhookFilters() );
|
||||
|
||||
parent::delete();
|
||||
|
||||
$this->expireWidgetCaches();
|
||||
@@ -444,19 +446,22 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
}
|
||||
|
||||
/**
|
||||
* Check comment against profanity filters
|
||||
* Check comment against profanity filters but do not act on it
|
||||
*
|
||||
* @param bool $first Is this the first comment?
|
||||
* @param bool $edit Are we editing or merging (true) or is this a new comment (false)?
|
||||
* @param string|NULL $content The content to check - useful for if the content needs to be checked first, before it gets saved to the database.
|
||||
* @param string|NULL|bool $title The title of the content to check, or NULL to check the current title, or FALSE to not check at all.
|
||||
* @return bool Whether to send unapproved notifications
|
||||
* @param bool $first Is this the first comment?
|
||||
* @param bool $edit Are we editing or merging (true) or is this a new comment (false)?
|
||||
* @param string|NULL $content The content to check - useful for if the content needs to be checked first, before it gets saved to the database.
|
||||
* @param string|NULL|bool $title The title of the content to check, or NULL to check the current title, or FALSE to not check at all.
|
||||
* @param string $autoSaveLocation The autosave location key of any editors to check, or NULL to use the default.
|
||||
* @param string|NULL $autoSaveKeys The autosave keys (for new content) or attach lookup ids (for an edit) of any editors to check, or NULL to use the default.
|
||||
* @param array $imageUploads Images that have been uploaded that may require moderation
|
||||
* @return bool Whether to send unapproved notifications (i.e. true if the content was hidden)
|
||||
*/
|
||||
public function checkProfanityFilters( $first=FALSE, $edit=TRUE, $content=NULL, $title=NULL )
|
||||
{
|
||||
public function shouldTriggerProfanityFilters( $first=FALSE, $edit=TRUE, $content=NULL, $title=NULL, $autoSaveLocation=NULL, $autoSaveKeys=NULL, $imageUploads=[], &$hiddenByFilter = FALSE, &$itemHiddenByFilter = FALSE )
|
||||
{
|
||||
/* Set our content */
|
||||
$content = $content ?: $this->content();
|
||||
|
||||
|
||||
/* We need our item */
|
||||
$item = $this;
|
||||
if ( $this instanceof \IPS\Content\Comment )
|
||||
@@ -482,31 +487,78 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* And the author */
|
||||
/* Check the author cannot bypass */
|
||||
$member = $this->author();
|
||||
|
||||
/* Pass this through our profanity filters to see if it needs to be mod queued */
|
||||
$sendNotifications = FALSE;
|
||||
|
||||
/* Do we need to recount item comments and container comments? */
|
||||
$recount = FALSE;
|
||||
|
||||
try
|
||||
if ( $member->group['g_bypass_badwords'] )
|
||||
{
|
||||
$hiddenByFilter = FALSE;
|
||||
/* If this is a content item that doesn't require a first comment, or a comment that isn't the first comment, this just pass the content as it stands. */
|
||||
if ( !$this->hidden() AND ( ( $this instanceof \IPS\Content\Item ) OR ( $this instanceof \IPS\Content\Comment AND ( !$item::$firstCommentRequired OR ( $item::$firstCommentRequired AND !$first ) ) ) ) AND !$member->group['g_bypass_badwords'] )
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* First, check the image scanner */
|
||||
$idColumn = static::$databaseColumnId;
|
||||
$itemIdColumn = $item::$databaseColumnId;
|
||||
if ( \IPS\Settings::i()->ips_imagescanner_enable )
|
||||
{
|
||||
if ( $edit )
|
||||
{
|
||||
$autoSaveLocation = $autoSaveLocation ?: ( $item::$application . '_' . mb_ucfirst( $item::$module ) );
|
||||
|
||||
if ( !$autoSaveKeys )
|
||||
{
|
||||
if ( $this instanceof \IPS\Content\Comment and ( !( $this instanceof \IPS\Content\Review ) or !$first or !$item::$firstCommentRequired ) )
|
||||
{
|
||||
$autoSaveKeys = [ $this->attachmentIds() ];
|
||||
}
|
||||
else
|
||||
{
|
||||
$autoSaveKeys = [ [ $item->$itemIdColumn ] ];
|
||||
}
|
||||
}
|
||||
|
||||
$clauses = [];
|
||||
$binds = [];
|
||||
foreach ( $autoSaveKeys as $_autoSaveKeys )
|
||||
{
|
||||
$idLookups = [];
|
||||
foreach ( range( 1, \count( $_autoSaveKeys ) ) as $i )
|
||||
{
|
||||
$idLookups[]= "id{$i}=?";
|
||||
}
|
||||
$clauses[] = '( ' . implode( ' AND ', $idLookups ) . ' )';
|
||||
$binds = array_merge( $binds, $_autoSaveKeys );
|
||||
}
|
||||
|
||||
$hiddenByFilter = \IPS\core\Profanity::hiddenByFilters( $content, $member );
|
||||
|
||||
if ( $title AND !$hiddenByFilter )
|
||||
{
|
||||
$hiddenByFilter = \IPS\core\Profanity::hiddenByFilters( $title, $member );
|
||||
}
|
||||
|
||||
$attachmentsMapWhere = array_merge( array( 'location_key=? AND ( ' . implode( ' OR ', $clauses ) . ' )', $autoSaveLocation ), $binds );
|
||||
}
|
||||
/* If the comment is hidden (probably requires moderator approval) and this is the first post in the item that requires a comment (i.e. a post in a topic), then we need to set the item as requiring approval instead. */
|
||||
elseif ( $item instanceof \IPS\Content\Hideable AND !$member->group['g_bypass_badwords'] AND ( $item::$firstCommentRequired and $first ) )
|
||||
else
|
||||
{
|
||||
if ( $autoSaveKeys === NULL )
|
||||
{
|
||||
if ( $this instanceof \IPS\Content\Item or ( $first and $item::$firstCommentRequired ) )
|
||||
{
|
||||
$container = $item->containerWrapper();
|
||||
$autoSaveKeys[] = 'newContentItem-' . $item::$application . '/' . $item::$module . '-' . ( $container ? $container->_id : 0 );
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( $this instanceof \IPS\Content\Review )
|
||||
{
|
||||
$autoSaveKeys[] = 'review-' . $item::$application . '/' . $item::$module . '-' . $item->$itemIdColumn;
|
||||
}
|
||||
else
|
||||
{
|
||||
$autoSaveKeys[] = 'reply-' . $item::$application . '/' . $item::$module . '-' . $item->$itemIdColumn;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$itemHiddenByFilter = \IPS\core\Profanity::hiddenByFilters( $content, $member );
|
||||
|
||||
if ( $title AND !$itemHiddenByFilter )
|
||||
@@ -514,54 +566,142 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
$itemHiddenByFilter = \IPS\core\Profanity::hiddenByFilters( $title, $member );
|
||||
}
|
||||
|
||||
if ( isset( $item::$databaseColumnMap['approved'] ) and $itemHiddenByFilter )
|
||||
$attachmentsMapWhere = \IPS\Db::i()->in( 'temp', array_map( 'md5', $autoSaveKeys ) );
|
||||
}
|
||||
|
||||
foreach ( \IPS\Db::i()->select( 'attach_moderation_status', 'core_attachments', [ 'attach_id IN (?)', \IPS\Db::i()->select( 'attachment_id', 'core_attachments_map', $attachmentsMapWhere ) ] ) as $attachModerationStatus )
|
||||
{
|
||||
if ( $attachModerationStatus === 'pending' )
|
||||
{
|
||||
/* 'approved' is easy, clear and concise */
|
||||
$column = $item::$databaseColumnMap['approved'];
|
||||
$item->$column = 0;
|
||||
$item->save();
|
||||
$recount = TRUE;
|
||||
if ( $this instanceof \IPS\Content\Item or ( $first and $item::$firstCommentRequired ) )
|
||||
{
|
||||
$itemHiddenByFilter = TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
$hiddenByFilter = TRUE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
elseif ( isset( $item::$databaseColumnMap['hidden'] ) and $itemHiddenByFilter )
|
||||
}
|
||||
|
||||
if ( !$hiddenByFilter and !$itemHiddenByFilter )
|
||||
{
|
||||
foreach ( $imageUploads as $file )
|
||||
{
|
||||
/* 'hidden' is backwards */
|
||||
$column = $item::$databaseColumnMap['hidden'];
|
||||
$item->$column = 1;
|
||||
$item->save();
|
||||
$recount = TRUE;
|
||||
if ( $file->requiresModeration )
|
||||
{
|
||||
if ( $this instanceof \IPS\Content\Item or ( $first and $item::$firstCommentRequired ) )
|
||||
{
|
||||
$itemHiddenByFilter = TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
$hiddenByFilter = TRUE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( $edit === TRUE )
|
||||
/* Then pass this through our profanity and link filters */
|
||||
try
|
||||
{
|
||||
if ( $this instanceof \IPS\Content\Item or ( $first and $item::$firstCommentRequired ) )
|
||||
{
|
||||
if ( !$itemHiddenByFilter )
|
||||
{
|
||||
$sendNotifications = TRUE;
|
||||
$itemHiddenByFilter = \IPS\core\Profanity::hiddenByFilters( $content );
|
||||
}
|
||||
if ( !$itemHiddenByFilter and $title )
|
||||
{
|
||||
$itemHiddenByFilter = \IPS\core\Profanity::hiddenByFilters( $title );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( !$hiddenByFilter )
|
||||
{
|
||||
$hiddenByFilter = \IPS\core\Profanity::hiddenByFilters( $content );
|
||||
}
|
||||
if ( !$hiddenByFilter and $title )
|
||||
{
|
||||
$hiddenByFilter = \IPS\core\Profanity::hiddenByFilters( $title );
|
||||
}
|
||||
}
|
||||
}
|
||||
catch( \BadMethodCallException $e ) { }
|
||||
|
||||
if ( $hiddenByFilter === TRUE )
|
||||
/* Return */
|
||||
return ( $hiddenByFilter or $itemHiddenByFilter );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check comment against profanity filters AND act on it
|
||||
*
|
||||
* @param bool $first Is this the first comment?
|
||||
* @param bool $edit Are we editing or merging (true) or is this a new comment (false)?
|
||||
* @param string|NULL $content The content to check - useful for if the content needs to be checked first, before it gets saved to the database.
|
||||
* @param string|NULL|bool $title The title of the content to check, or NULL to check the current title, or FALSE to not check at all.
|
||||
* @param string $autoSaveLocation The autosave location key of any editors to check, or NULL to use the default.
|
||||
* @param string|NULL $autoSaveKeys The autosave keys (for new content) or attach lookup ids (for an edit) of any editors to check, or NULL to use the default.
|
||||
* @param array $imageUploads Images that have been uploaded that may require moderation
|
||||
* @return bool Whether to send unapproved notifications (i.e. true if the content was hidden)
|
||||
*/
|
||||
public function checkProfanityFilters( $first=FALSE, $edit=TRUE, $content=NULL, $title=NULL, $autoSaveLocation=NULL, $autoSaveKeys=NULL, $imageUploads=[] )
|
||||
{
|
||||
/* Check it */
|
||||
$hiddenByFilter = FALSE;
|
||||
$itemHiddenByFilter = FALSE;
|
||||
$sendNotifications = FALSE;
|
||||
$item = ( $this instanceof \IPS\Content\Item ) ? $this : $this->item();
|
||||
$this->shouldTriggerProfanityFilters( $first, $edit, $content, $title, $autoSaveLocation, $autoSaveKeys, $imageUploads, $hiddenByFilter, $itemHiddenByFilter );
|
||||
|
||||
/* If we need to hide the item, then do that */
|
||||
if ( $itemHiddenByFilter )
|
||||
{
|
||||
$recount = TRUE;
|
||||
$sendNotifications = $edit;
|
||||
|
||||
/* 'approved' is easy, clear and concise */
|
||||
if ( isset( $item::$databaseColumnMap['approved'] ) )
|
||||
{
|
||||
$column = $item::$databaseColumnMap['approved'];
|
||||
$item->$column = 0;
|
||||
$item->save();
|
||||
}
|
||||
/* 'hidden' is backwards */
|
||||
elseif ( isset( $item::$databaseColumnMap['hidden'] ) )
|
||||
{
|
||||
$column = $item::$databaseColumnMap['hidden'];
|
||||
$item->$column = 1;
|
||||
$item->save();
|
||||
}
|
||||
}
|
||||
|
||||
/* If we need to hide this, then do that */
|
||||
if ( $hiddenByFilter === TRUE or $itemHiddenByFilter === TRUE )
|
||||
{
|
||||
$sendNotifications = TRUE;
|
||||
|
||||
/* 'approved' is easy, clear and concise */
|
||||
if ( isset( static::$databaseColumnMap['approved'] ) )
|
||||
{
|
||||
/* 'approved' is easy, clear and concise */
|
||||
$column = static::$databaseColumnMap['approved'];
|
||||
$this->$column = 0;
|
||||
$this->save();
|
||||
}
|
||||
/* 'hidden' is backwards */
|
||||
else if ( isset( static::$databaseColumnMap['hidden'] ) )
|
||||
{
|
||||
/* 'hidden' is backwards */
|
||||
$column = static::$databaseColumnMap['hidden'];
|
||||
$this->$column = 1;
|
||||
$this->$column = ( $itemHiddenByFilter and $this instanceof \IPS\Content\Comment ) ? 2 : 1; # We use the special 2 flag to note it is only hidden because the parent is
|
||||
$this->save();
|
||||
}
|
||||
|
||||
/* We have to save here because the status change is relevant to resyncCommentCounts() and resyncLastComment(), and our comment object changes we just set can be overwritten when those methods are called. */
|
||||
$this->save();
|
||||
|
||||
$sendNotifications = TRUE;
|
||||
}
|
||||
|
||||
if ( $recount === TRUE )
|
||||
|
||||
/* If we did either, then recount number of comments */
|
||||
if ( $itemHiddenByFilter or $hiddenByFilter )
|
||||
{
|
||||
$item->resyncCommentCounts();
|
||||
$item->resyncLastComment();
|
||||
@@ -574,6 +714,7 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
}
|
||||
}
|
||||
|
||||
/* Return */
|
||||
return $sendNotifications;
|
||||
}
|
||||
|
||||
@@ -646,7 +787,7 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
/**
|
||||
* Return a list of groups that cannot see this item
|
||||
*
|
||||
* @return NULL|Array
|
||||
* @return NULL|array
|
||||
*/
|
||||
public function cannotViewGroups()
|
||||
{
|
||||
@@ -1235,7 +1376,7 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
{
|
||||
$this->sendApprovedNotification();
|
||||
}
|
||||
|
||||
|
||||
/* Award points */
|
||||
$itemClass = \in_array( 'IPS\Content\Comment', class_parents( \get_called_class() ) ) ? static::$itemClass : \get_called_class();
|
||||
if ( $this instanceof \IPS\Content\Item )
|
||||
@@ -1246,7 +1387,7 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
{
|
||||
$this->author()->achievementAction( 'core', 'Review', $this );
|
||||
}
|
||||
elseif ( $this instanceof \IPS\Content\Comment and ( !$itemClass::$firstCommentRequired or $this->isFirst() ) )
|
||||
elseif ( $this instanceof \IPS\Content\Comment and ( ( $itemClass::$firstCommentRequired AND ! $this->isFirst() ) OR ( ! $itemClass::$firstCommentRequired ) ) )
|
||||
{
|
||||
$this->author()->achievementAction( 'core', 'Comment', $this );
|
||||
}
|
||||
@@ -1455,19 +1596,23 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
/**
|
||||
* Report
|
||||
*
|
||||
* @param string $reportContent Report content message from member
|
||||
* @param int $reportType Report type (see constants in \IPS\core\Reports\Report
|
||||
* @param string $reportContent Report content message from member
|
||||
* @param int $reportType Report type (see constants in \IPS\core\Reports\Report)
|
||||
* @param \IPS\Member|NULL $member Member making the report (or NULL for loggedIn())
|
||||
* @return \IPS\core\Reports\Report
|
||||
* @throws \UnexpectedValueException If there is a permission error - you should only call this method after checking canReport
|
||||
*/
|
||||
public function report( $reportContent, $reportType=1 )
|
||||
public function report( $reportContent, $reportType=1, $member=NULL )
|
||||
{
|
||||
$member = ( $member ) ? $member : \IPS\Member::loggedIn();
|
||||
|
||||
/* Permission check */
|
||||
if ( $this->canReport() !== TRUE )
|
||||
$permCheck = $this->canReport( $member );
|
||||
if ( $permCheck !== TRUE )
|
||||
{
|
||||
throw new \UnexpectedValueException;
|
||||
throw new \UnexpectedValueException( $permCheck );
|
||||
}
|
||||
|
||||
|
||||
/* Find or create an index */
|
||||
$idColumn = static::$databaseColumnId;
|
||||
try
|
||||
@@ -1481,7 +1626,7 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
$index->class = \get_called_class();
|
||||
$index->content_id = $this->$idColumn;
|
||||
$index->perm_id = $this->permId();
|
||||
$index->first_report_by = (int) \IPS\Member::loggedIn()->member_id;
|
||||
$index->first_report_by = (int) $member->member_id;
|
||||
$index->first_report_date = time();
|
||||
$index->last_updated = time();
|
||||
$index->author = (int) $this->author()->member_id;
|
||||
@@ -1502,10 +1647,10 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
$reportInsert = array(
|
||||
'rid' => $index->id,
|
||||
'report' => $reportContent,
|
||||
'report_by' => (int) \IPS\Member::loggedIn()->member_id,
|
||||
'report_by' => (int) $member->member_id,
|
||||
'date_reported' => time(),
|
||||
'ip_address' => \IPS\Request::i()->ipAddress(),
|
||||
'report_type' => \IPS\Member::loggedIn()->member_id ? $reportType : 0
|
||||
'report_type' => $member->member_id ? $reportType : 0
|
||||
);
|
||||
|
||||
$insertID = \IPS\Db::i()->insert( 'core_rc_reports', $reportInsert );
|
||||
@@ -1596,12 +1741,19 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
case 'author':
|
||||
$this->$col = $newAuthor->member_id ? $newAuthor->member_id : 0;
|
||||
break;
|
||||
|
||||
case 'author_name':
|
||||
case 'edit_member_name':
|
||||
/* Real name will contain the custom guest name if available or '' if not */
|
||||
$this->$col = $newAuthor->member_id ? $newAuthor->name : $newAuthor->real_name;
|
||||
break;
|
||||
case 'edit_member_name':
|
||||
/* Real name will contain the custom guest name if available or '' if not.
|
||||
But we only want to update the user if oldName is the same as the current edit_member_name
|
||||
So if "Bob" edited his own post, you want that to change if Bob becomes Bob2.
|
||||
But if a moderator edited it, we don't want to change that name. */
|
||||
if ( $oldAuthor->name == $this->$col )
|
||||
{
|
||||
$this->$col = $newAuthor->member_id ? $newAuthor->name : $newAuthor->real_name;
|
||||
}
|
||||
break;
|
||||
case 'is_anon':
|
||||
/* We are specifying an author so turn off is_anon column */
|
||||
if ( $newAuthor->member_id )
|
||||
@@ -1621,6 +1773,29 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get language articles for the given container
|
||||
*
|
||||
* @param string $itemClass The classname of this content
|
||||
* @param array|NULL $containerData Basic data about the container. Only includes columns returned by container::basicDataColumns()
|
||||
*/
|
||||
public static function articlesFromIndexData( $itemClass, ?array $containerData ): array
|
||||
{
|
||||
/* Object URL */
|
||||
$indefiniteArticle = static::_indefiniteArticle( $containerData );
|
||||
$definiteArticle = static::_definiteArticle( $containerData );
|
||||
$definiteArticleUc = static::_definiteArticle( $containerData, NULL, array( 'ucfirst' => TRUE ) );
|
||||
|
||||
if ( \in_array( 'IPS\Content\Comment', class_parents( \get_called_class() ) ) )
|
||||
{
|
||||
$indefiniteArticle = $itemClass::_indefiniteArticle( $containerData );
|
||||
$definiteArticle = $itemClass::_definiteArticle( $containerData );
|
||||
$definiteArticleUc = $itemClass::_definiteArticle( $containerData, NULL, array( 'ucfirst' => TRUE ) );
|
||||
}
|
||||
|
||||
return array( 'indefinite' => $indefiniteArticle, 'definite' => $definiteArticle, 'definite_uc' => $definiteArticleUc );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get HTML for search result display
|
||||
*
|
||||
@@ -1654,9 +1829,6 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
$itemUrl = $itemClass::urlFromIndexData( $indexData, $itemData, 'getPrefComment' );
|
||||
|
||||
/* Object URL */
|
||||
$indefiniteArticle = static::_indefiniteArticle( $containerData );
|
||||
$definiteArticle = static::_definiteArticle( $containerData );
|
||||
$definiteArticleUc = static::_definiteArticle( $containerData, NULL, array( 'ucfirst' => TRUE ) );
|
||||
if ( \in_array( 'IPS\Content\Comment', class_parents( \get_called_class() ) ) )
|
||||
{
|
||||
if ( \in_array( 'IPS\Content\Review', class_parents( \get_called_class() ) ) )
|
||||
@@ -1669,17 +1841,15 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
$objectUrl = $itemUrl->setQueryString( array( 'do' => 'findComment', 'comment' => $indexData['index_object_id'] ) );
|
||||
$showRepUrl = $itemUrl->setQueryString( array( 'do' => 'showReactionsComment', 'comment' => $indexData['index_object_id'] ) );
|
||||
}
|
||||
|
||||
$indefiniteArticle = $itemClass::_indefiniteArticle( $containerData );
|
||||
$definiteArticle = $itemClass::_definiteArticle( $containerData );
|
||||
$definiteArticleUc = $itemClass::_definiteArticle( $containerData, NULL, array( 'ucfirst' => TRUE ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
$objectUrl = $itemUrl;
|
||||
$showRepUrl = $itemUrl->setQueryString( 'do', 'showReactions' );
|
||||
}
|
||||
$articles = array( 'indefinite' => $indefiniteArticle, 'definite' => $definiteArticle, 'definite_uc' => $definiteArticleUc );
|
||||
|
||||
/* Articles language */
|
||||
$articles = static::articlesFromIndexData( $itemClass, $containerData );
|
||||
|
||||
/* Container details */
|
||||
$containerUrl = NULL;
|
||||
@@ -1740,7 +1910,7 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
*/
|
||||
public static function searchResultSnippet( array $indexData, array $authorData, array $itemData, ?array $containerData, array $reputationData, $reviewRating, $view )
|
||||
{
|
||||
return $view == 'expanded' ? \IPS\Theme::i()->getTemplate( 'system', 'core', 'front' )->searchResultSnippet( $indexData ) : '';
|
||||
return $view == 'expanded' ? \IPS\Theme::i()->getTemplate( 'system', 'core', 'front' )->searchResultSnippet( $indexData, $itemData ) : '';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1752,17 +1922,32 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
* @param array $articles Articles language strings
|
||||
* @param array $indexData Search index data
|
||||
* @param array $itemData Data about the item
|
||||
* @param bool $includeLinks Include links to member profile
|
||||
* @return string
|
||||
*/
|
||||
public static function searchResultSummaryLanguage( $authorData, $articles, $indexData, $itemData )
|
||||
public static function searchResultSummaryLanguage( $authorData, $articles, $indexData, $itemData, $includeLinks = TRUE )
|
||||
{
|
||||
$authorTemplate = \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' )->userLinkFromData( $authorData['member_id'], $authorData['name'], $authorData['members_seo_name'], $authorData['member_group_id'] ?? \IPS\Settings::i()->guest_group, NULL, $indexData['index_is_anon'] );
|
||||
if( $includeLinks )
|
||||
{
|
||||
$authorTemplate = \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' )->userLinkFromData( $authorData['member_id'], $authorData['name'], $authorData['members_seo_name'], $authorData['member_group_id'] ?? \IPS\Settings::i()->guest_group, NULL, $indexData['index_is_anon'] );
|
||||
}
|
||||
else
|
||||
{
|
||||
$authorTemplate = \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' )->userNameFromData( $authorData['name'], $authorData['member_group_id'] ?? \IPS\Settings::i()->guest_group, NULL, $indexData['index_is_anon'] );
|
||||
}
|
||||
|
||||
if( \in_array( 'IPS\Content\Comment', class_parents( $indexData['index_class'] ) ) )
|
||||
{
|
||||
if( isset( $itemData['author'] ) )
|
||||
{
|
||||
$itemAuthorTemplate = \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' )->userLinkFromData( $itemData['author']['member_id'], $itemData['author']['name'], $itemData['author']['members_seo_name'], $itemData['author']['member_group_id'] ?? \IPS\Settings::i()->guest_group );
|
||||
if( $includeLinks )
|
||||
{
|
||||
$itemAuthorTemplate = \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' )->userLinkFromData( $itemData['author']['member_id'], $itemData['author']['name'], $itemData['author']['members_seo_name'], $itemData['author']['member_group_id'] ?? \IPS\Settings::i()->guest_group );
|
||||
}
|
||||
else
|
||||
{
|
||||
$itemAuthorTemplate = \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' )->userNameFromData( $itemData['author']['name'], $itemData['author']['member_group_id'] ?? \IPS\Settings::i()->guest_group );
|
||||
}
|
||||
}
|
||||
|
||||
if( \in_array( 'IPS\Content\Review', class_parents( $indexData['index_class'] ) ) )
|
||||
@@ -1930,6 +2115,75 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
{
|
||||
return \IPS\Email::template( 'core', '_genericContent', $type, array( $this, $includeLinks, $includeAuthor, $email ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns "Foo posted {{indefart}} in {{container}}, {{date}}
|
||||
*
|
||||
* @param \IPS\Lang|NULL $plaintextLanguage If specified, will return plaintext (not linking the user or the container in the language specified). If NULL, returns with links based on logged in user's theme and language
|
||||
* @note This function was extracted from IPS\Promote
|
||||
* @return string
|
||||
*/
|
||||
public function objectMetaDescription( $plaintextLanguage=NULL )
|
||||
{
|
||||
$object = $this;
|
||||
$author = $this->author();
|
||||
|
||||
if ( $object instanceof \IPS\Content\Item )
|
||||
{
|
||||
$container = $object->containerWrapper();
|
||||
|
||||
if ( $container )
|
||||
{
|
||||
if ( !$plaintextLanguage )
|
||||
{
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( 'promote_metadescription_container', FALSE, array(
|
||||
'htmlsprintf' => array( $author->link(), \IPS\DateTime::ts( $this->mapped('date') )->html( FALSE ) ),
|
||||
'sprintf' => array( $object->indefiniteArticle(), $container->url(), $container->_title ),
|
||||
) );
|
||||
}
|
||||
else
|
||||
{
|
||||
return $plaintextLanguage->addToStack( 'promote_metadescription_container_nolink', FALSE, array(
|
||||
'sprintf' => array( $object->indefiniteArticle( $plaintextLanguage ), $container->getTitleForLanguage( $plaintextLanguage ), $author->name, \IPS\DateTime::ts( $this->mapped('date') ) ),
|
||||
) );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( !$plaintextLanguage )
|
||||
{
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( 'promote_metadescription_nocontainer', FALSE, array(
|
||||
'htmlsprintf' => array( $author->link(), \IPS\DateTime::ts( $this->mapped('date') )->html( FALSE ) ),
|
||||
'sprintf' => array( $object->indefiniteArticle() )
|
||||
) );
|
||||
}
|
||||
else
|
||||
{
|
||||
return $plaintextLanguage->addToStack( 'promote_metadescription_nocontainer', FALSE, array(
|
||||
'sprintf' => array( $object->indefiniteArticle( $plaintextLanguage ), $author->name, \IPS\DateTime::ts( $this->mapped('date') ) )
|
||||
) );
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ( $object instanceof \IPS\Content\Comment )
|
||||
{
|
||||
if ( !$plaintextLanguage )
|
||||
{
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( 'promote_metadescription_nocontainer', FALSE, array(
|
||||
'htmlsprintf' => array( $author->link(), \IPS\DateTime::ts( $this->mapped('date') )->html( FALSE ) ),
|
||||
'sprintf' => array( $object->indefiniteArticle() )
|
||||
) );
|
||||
}
|
||||
else
|
||||
{
|
||||
return $plaintextLanguage->addToStack( 'promote_metadescription_nocontainer', FALSE, array(
|
||||
'sprintf' => array( $object->indefiniteArticle( $plaintextLanguage ), $author->name, \IPS\DateTime::ts( $this->mapped('date') ) )
|
||||
) );
|
||||
}
|
||||
}
|
||||
|
||||
throw new \OutofRangeException('object_not_valid');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a count of the database table
|
||||
@@ -2046,8 +2300,17 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
}
|
||||
|
||||
$followIds = array();
|
||||
$followers = iterator_to_array( $this->notificationRecipients( array( $offset, static::NOTIFICATIONS_PER_BATCH ), $extra ) );
|
||||
|
||||
$followers = $this->notificationRecipients( array( $offset, static::NOTIFICATIONS_PER_BATCH ), $extra );
|
||||
|
||||
/* If $followers is NULL (which can be the case if the follows are just people following the author), just return as there is nothing to do */
|
||||
if ( $followers === NULL )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* If we're still here, our iterator may not necessarily be one that implements Countable so we need to convert it to an array. */
|
||||
$followers = iterator_to_array( $followers );
|
||||
|
||||
if( !\count( $followers ) )
|
||||
{
|
||||
return NULL;
|
||||
@@ -2351,21 +2614,28 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
$item = $this;
|
||||
}
|
||||
|
||||
/* We have to send a limit even though we want all records because otherwise the Database store does not return all columns */
|
||||
foreach( \IPS\Session\Store::i()->getOnlineUsers( 0, 'desc', array( 0, 5000 ), NULL, TRUE ) as $session )
|
||||
/** Item::url() can throw a LogicException exception in specific cases like when a Pages Record has no valid page */
|
||||
try
|
||||
{
|
||||
if( mb_strpos( $session['location_url'], (string) $item->url() ) === 0 )
|
||||
/* We have to send a limit even though we want all records because otherwise the Database store does not return all columns */
|
||||
foreach( \IPS\Session\Store::i()->getOnlineUsers( 0, 'desc', array( 0, 5000 ), NULL, TRUE ) as $session )
|
||||
{
|
||||
$sessionData = $session;
|
||||
$sessionData['location_url'] = NULL;
|
||||
$sessionData['location_lang'] = NULL;
|
||||
$sessionData['location_data'] = json_encode( array() );
|
||||
$sessionData['current_id'] = 0;
|
||||
$sessionData['location_permissions'] = 0;
|
||||
if( mb_strpos( $session['location_url'], (string) $item->url() ) === 0 )
|
||||
{
|
||||
$sessionData = $session;
|
||||
$sessionData['location_url'] = NULL;
|
||||
$sessionData['location_lang'] = NULL;
|
||||
$sessionData['location_data'] = json_encode( array() );
|
||||
$sessionData['current_id'] = 0;
|
||||
$sessionData['location_permissions'] = 0;
|
||||
|
||||
\IPS\Session\Store::i()->updateSession( $sessionData );
|
||||
\IPS\Session\Store::i()->updateSession( $sessionData );
|
||||
}
|
||||
}
|
||||
}
|
||||
catch( \LogicException $e ){}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2709,58 +2979,90 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
{
|
||||
$filters = array();
|
||||
$filters['author'] = $this->author()->member_id;
|
||||
|
||||
/* All our Zapier Triggers for content have a hidden setting, so we're using this one global for items and comments */
|
||||
$filters['hidden'] = (bool) $this->hidden();
|
||||
|
||||
return $filters;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set anonymous state
|
||||
*
|
||||
* @param bool $state The state, TRUE for anonymous FALSE for not
|
||||
* @param \IPS\Member|NULL $member The member posting anonymously or NULL for logged in member
|
||||
* @param bool $state The state, TRUE for anonymous FALSE for not
|
||||
* @param \IPS\Member|NULL $member The member posting anonymously or NULL for logged in member
|
||||
* @return void
|
||||
*/
|
||||
public function setAnonymous( $state = TRUE, $member = NULL )
|
||||
public function setAnonymous( bool $state = TRUE, \IPS\Member $member = NULL)
|
||||
{
|
||||
if( !$this instanceof \IPS\Content\Anonymous or !$this->container()->canPostAnonymously() )
|
||||
if( !$this instanceof \IPS\Content\Anonymous )
|
||||
{
|
||||
throw new \BadMethodCallException;
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
$member = $member ?: \IPS\Member::loggedIn();
|
||||
|
||||
if( $state == $this->isAnonymous() )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$class = \get_class( $this );
|
||||
$idColumn = static::$databaseColumnId;
|
||||
$anonColumn = static::$databaseColumnMap['is_anon'];
|
||||
|
||||
if( !$state )
|
||||
{
|
||||
try
|
||||
{
|
||||
$originalAuthor = \IPS\DB::i()->select( 'anonymous_member_id', 'core_anonymous_posts', array( 'anonymous_object_class=? and anonymous_object_id=?', $class, $this->$idColumn ) )->first();
|
||||
|
||||
$member = \IPS\Member::load( $originalAuthor );
|
||||
}
|
||||
catch (\UnderflowException $e )
|
||||
{
|
||||
$member = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
$member = $member ?: \IPS\Member::loggedIn();
|
||||
|
||||
if( $state and !$this->container()->canPostAnonymously( 0, $member ) )
|
||||
{
|
||||
throw new \BadMethodCallException();
|
||||
}
|
||||
|
||||
if( $state )
|
||||
{
|
||||
{
|
||||
/* Insert the anonymous map */
|
||||
$save = array(
|
||||
'anonymous_member_id' => $member->member_id,
|
||||
'anonymous_member_id' => ( $this->author()->member_id ) ? $this->author()->member_id : $member->member_id,
|
||||
'anonymous_object_class' => $class,
|
||||
'anonymous_object_id' => $this->$idColumn
|
||||
);
|
||||
|
||||
\IPS\Db::i()->replace( 'core_anonymous_posts', $save );
|
||||
|
||||
|
||||
$member = new \IPS\Member;
|
||||
$this->$anonColumn = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
\IPS\Db::i()->delete( 'core_anonymous_posts', array( 'anonymous_member_id=? and anonymous_object_class=? and anonymous_object_id=?', $member->member_id, $class, $this->$idColumn ) );
|
||||
\IPS\Db::i()->delete( 'core_anonymous_posts', array( 'anonymous_object_class=? and anonymous_object_id=?', $class, $this->$idColumn ) );
|
||||
|
||||
$this->$anonColumn = 0;
|
||||
}
|
||||
|
||||
|
||||
/* @todo only run the rest of the code here if the $anonColumn state is different from original. Waste of processing otherwise */
|
||||
$this->save();
|
||||
$this->changeAuthor( $member, FALSE );
|
||||
|
||||
|
||||
if ( \in_array( 'IPS\Content\Comment', class_parents( $this ) ) )
|
||||
{
|
||||
$this->item()->rebuildFirstAndLastCommentData();
|
||||
}
|
||||
|
||||
|
||||
$this->expireWidgetCaches();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Is this an anonymous entry?
|
||||
*
|
||||
@@ -2770,4 +3072,15 @@ abstract class _Content extends \IPS\Patterns\ActiveRecord
|
||||
{
|
||||
return $this->mapped('is_anon');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the content images
|
||||
*
|
||||
* @return array|NULL
|
||||
* @throws \BadMethodCallException
|
||||
*/
|
||||
public function imageLabelsForSearch()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
+201
-74
@@ -47,7 +47,25 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
|
||||
/* Ensure JS loaded for forms/content functions such as moderation */
|
||||
\IPS\Output::i()->jsFiles = array_merge( \IPS\Output::i()->jsFiles, \IPS\Output::i()->js( 'front_core.js', 'core' ) );
|
||||
|
||||
return parent::execute();
|
||||
parent::execute();
|
||||
|
||||
/* Do this to prevent non-existent and non-accessible items from returning a status of 200 */
|
||||
if ( empty( \IPS\Output::i()->output ) AND !\IPS\Request::i()->_bypassItemIdCheck AND isset( \IPS\Request::i()->id ) )
|
||||
{
|
||||
$contentModel = static::$contentModel;
|
||||
try
|
||||
{
|
||||
$item = $contentModel::load( \IPS\Request::i()->id );
|
||||
if ( !$item->can( 'read' ) )
|
||||
{
|
||||
\IPS\Output::i()->error( 'node_error', '2S136/1X', 403, '' );
|
||||
}
|
||||
}
|
||||
catch ( \OutOfRangeException $e )
|
||||
{
|
||||
\IPS\Output::i()->error( 'node_error', '2S136/1Y', 404, '' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -132,7 +150,15 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
|
||||
{
|
||||
\IPS\Output::i()->redirect( $redirectToUrl );
|
||||
}
|
||||
|
||||
|
||||
/* Get ready to store data layer info */
|
||||
$dataLayer = \IPS\Settings::i()->core_datalayer_enabled;
|
||||
$dataLayerProperties = array();
|
||||
if ( $dataLayer )
|
||||
{
|
||||
$dataLayerProperties = $item->getDataLayerProperties();
|
||||
}
|
||||
|
||||
/* Check we're on a valid page */
|
||||
$paginationType = 'comment';
|
||||
if ( isset( \IPS\Request::i()->tab ) and \IPS\Request::i()->tab === 'reviews' )
|
||||
@@ -168,6 +194,8 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
|
||||
$lastPageMethod = 'last' . mb_ucfirst( $paginationType ) . 'PageUrl';
|
||||
\IPS\Output::i()->redirect( $item->$lastPageMethod(), NULL, 303 );
|
||||
}
|
||||
|
||||
$dataLayerProperties['page_number'] = \intval( \IPS\Request::i()->page );
|
||||
}
|
||||
|
||||
/* Add rel tags */
|
||||
@@ -211,10 +239,21 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
|
||||
try
|
||||
{
|
||||
\IPS\Redis::i()->zIncrBy( 'topic_views', 1, $class .'__' . $item->$idColumn );
|
||||
|
||||
$countUpdated = true;
|
||||
}
|
||||
catch( \Exception $e ) {}
|
||||
}
|
||||
|
||||
if( \IPS\Application::appIsEnabled( 'cloud' ) and \IPS\cloud\Realtime::i()->isEnabled('trending') and $class::$includeInTrending )
|
||||
{
|
||||
try
|
||||
{
|
||||
/* Score by timestamp for trending */
|
||||
\IPS\Redis::i()->zIncrBy( 'trending', time() * 0.1, $class .'__' . $item->$idColumn );
|
||||
}
|
||||
catch( \Exception $e ) {}
|
||||
}
|
||||
|
||||
if ( ! $countUpdated )
|
||||
{
|
||||
@@ -262,7 +301,21 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
|
||||
'item_type' => isset( $class::$contentType ) ? $class::$contentType : $class::$title,
|
||||
'category_name' => isset( $item::$databaseColumnMap['container'] ) ? $item->container()->_title : NULL
|
||||
);
|
||||
|
||||
|
||||
/* Data Layer */
|
||||
if ( $dataLayer )
|
||||
{
|
||||
if ( !\IPS\Request::i()->isAjax() )
|
||||
{
|
||||
\IPS\core\DataLayer::i()->addEvent( 'content_view', $dataLayerProperties );
|
||||
}
|
||||
unset( $dataLayerProperties['ips_key'] );
|
||||
foreach ( $dataLayerProperties as $key => $value )
|
||||
{
|
||||
\IPS\core\DataLayer::i()->addContextProperty( $key, $value );
|
||||
}
|
||||
}
|
||||
|
||||
if( $item->mapped( 'updated' ) OR $item->mapped( 'last_comment' ) OR $item->mapped( 'last_review' ) )
|
||||
{
|
||||
\IPS\Output::i()->metaTags['og:updated_time'] = \IPS\DateTime::ts( $item->mapped( 'updated' ) ? $item->mapped( 'updated' ) : ( $item->mapped( 'last_comment' ) ? $item->mapped( 'last_comment' ) : $item->mapped( 'last_review' ) ) )->rfc3339();
|
||||
@@ -356,6 +409,14 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
|
||||
|
||||
$item = $class::loadAndCheckPerms( \IPS\Request::i()->id );
|
||||
$commentClass = $class::$commentClass;
|
||||
|
||||
/* Don't auto-poll archived content, it never gets updated */
|
||||
if( isset( $item::$archiveClass ) AND method_exists( $item, 'isArchived' ) AND $item->isArchived() )
|
||||
{
|
||||
\IPS\Output::i()->json( array( 'error' => 'auto_polling_disabled' ) );
|
||||
return;
|
||||
}
|
||||
|
||||
$commentIdColumn = $commentClass::$databaseColumnId;
|
||||
$commentDateColumn = $commentClass::$databaseColumnMap['date'];
|
||||
|
||||
@@ -514,7 +575,7 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
|
||||
$item->processAfterEdit( $values );
|
||||
|
||||
/* Moderator log */
|
||||
$toLog = array( $item::$title => FALSE, $item->url()->__toString() => FALSE, $item::$title => TRUE, $item->mapped( 'title' ) => FALSE );
|
||||
$toLog = array( $item::$title => TRUE, $item->url()->__toString() => FALSE, $item::$title => TRUE, $item->mapped( 'title' ) => FALSE );
|
||||
|
||||
if ( $oldTitle != $item->$titleField )
|
||||
{
|
||||
@@ -723,12 +784,12 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
|
||||
|
||||
$newTitle = new \IPS\Helpers\Form\Text( 'newTitle', \IPS\Request::i()->newTitle );
|
||||
|
||||
if( $newTitle->validate() !== TRUE or $newTitle->value !== \IPS\Request::i()->newTitle )
|
||||
if( $newTitle->validate() !== TRUE )
|
||||
{
|
||||
throw new \InvalidArgumentException( \IPS\Member::loggedIn()->language()->addToStack( 'form_tags_not_allowed', FALSE, array( 'sprintf' => array( \IPS\Request::i()->newTitle ) ) ) );
|
||||
}
|
||||
|
||||
$item->$titleField = \IPS\Request::i()->newTitle;
|
||||
$item->$titleField = $newTitle->value;
|
||||
$idField = $item::$databaseColumnId;
|
||||
$item->save();
|
||||
|
||||
@@ -761,7 +822,7 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
|
||||
\IPS\Session::i()->modLog( 'modlog__comment_edit_title', array( (string) $item->url() => FALSE, $item->$titleField => FALSE, $oldTitle => FALSE ), $item );
|
||||
}
|
||||
|
||||
\IPS\Output::i()->json( 'OK' );
|
||||
\IPS\Output::i()->json( $item->$titleField );
|
||||
}
|
||||
catch( \LogicException $e )
|
||||
{
|
||||
@@ -1557,7 +1618,7 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
|
||||
/* If we do not have a time last read set for this content, fallback to the reset time */
|
||||
$resetTimes = \IPS\Member::loggedIn()->markersResetTimes( $class::$application );
|
||||
|
||||
if ( array_key_exists( $item->container()->_id, $resetTimes ) and $item->mapped('date') < $resetTimes[ $item->container()->_id ] )
|
||||
if ( ( \is_array( $resetTimes ) AND array_key_exists( $item->container()->_id, $resetTimes ) ) and $item->mapped('date') < $resetTimes[ $item->container()->_id ] )
|
||||
{
|
||||
$comment = $item->comments( 1, NULL, 'date', 'asc', NULL, NULL, \IPS\DateTime::ts( $resetTimes[ $item->container()->_id ] ) );
|
||||
|
||||
@@ -1597,7 +1658,7 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
|
||||
$error = 'node_error';
|
||||
}
|
||||
|
||||
\IPS\Output::i()->error( $error, '2S136/V', 404, '' );
|
||||
\IPS\Output::i()->error( $error, '2S136/V', 403, '' );
|
||||
}
|
||||
catch( \LogicException $e )
|
||||
{
|
||||
@@ -1666,7 +1727,7 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
|
||||
$error = 'node_error';
|
||||
}
|
||||
|
||||
\IPS\Output::i()->error( $error, '2S136/Q', 404, '' );
|
||||
\IPS\Output::i()->error( $error, '2S136/Q', 403, '' );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1708,7 +1769,7 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
|
||||
}
|
||||
catch( \LogicException $e )
|
||||
{
|
||||
\IPS\Output::i()->error( 'node_error', '2S136/X', 404, '' );
|
||||
\IPS\Output::i()->error( 'node_error', '2S136/X', 403, '' );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1909,7 +1970,8 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
|
||||
}
|
||||
|
||||
$form = new \IPS\Helpers\Form;
|
||||
$form->add( new \IPS\Helpers\Form\YesNo( 'message_is_public', isset( $current['is_public'] ) ? $current['is_public'] : TRUE ) );
|
||||
$form->attributes['data-controller'] = 'core.front.core.contentMessage';
|
||||
$form->add( new \IPS\Helpers\Form\YesNo( 'message_is_public', isset( $current['is_public'] ) ? $current['is_public'] : FALSE ) );
|
||||
$form->add( new \IPS\Helpers\Form\Editor( 'message', $current ? $current['message'] : NULL , TRUE, array( 'app' => 'core', 'key' => 'Meta', 'autoSaveKey' => $current ? "meta-message-" . \IPS\Request::i()->meta_id : "meta-message-new", 'attachIds' => $current ? array( \IPS\Request::i()->meta_id, NULL, 'core_ContentMessages' ) : NULL ) ) );
|
||||
$form->add( new \IPS\Helpers\Form\Custom( 'message_color', isset( $current['color'] ) ? $current['color'] : 'none', FALSE, array( 'getHtml' => function( $element )
|
||||
{
|
||||
@@ -2083,7 +2145,7 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
|
||||
|
||||
if ( \IPS\Member::loggedIn()->modPermission('can_set_best_answer') )
|
||||
{
|
||||
\IPS\Session::i()->modLog( 'modlog__best_answer_unset', array( $item->$solvedField => FALSE ), $item );
|
||||
\IPS\Session::i()->modLog( 'modlog__best_answer_unset', array( $comment->$idField => FALSE ), $item );
|
||||
}
|
||||
}
|
||||
catch ( \Exception $e ) {}
|
||||
@@ -2248,7 +2310,7 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
|
||||
}
|
||||
}
|
||||
}
|
||||
$commentPosition = \IPS\Db::i()->select( 'COUNT(*) AS position', $commentClass::$databaseTable, $where )->first();
|
||||
$commentPosition = $commentClass::db()->select( 'COUNT(*) AS position', $commentClass::$databaseTable, $where )->first();
|
||||
|
||||
/* Now work out what page that makes it */
|
||||
$url = $item->url();
|
||||
@@ -2381,7 +2443,7 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
|
||||
if ( isset( \IPS\Request::i()->restoreAsHidden ) )
|
||||
{
|
||||
\IPS\Session::i()->modLog( 'modlog__action_restore_hidden', array(
|
||||
$comment::$title => FALSE,
|
||||
$comment::$title => TRUE,
|
||||
$comment->url()->__toString() => FALSE
|
||||
) );
|
||||
|
||||
@@ -2390,7 +2452,7 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
|
||||
else
|
||||
{
|
||||
\IPS\Session::i()->modLog( 'modlog__action_restore', array(
|
||||
$comment::$title => FALSE,
|
||||
$comment::$title => TRUE,
|
||||
$comment->url()->__toString() => FALSE
|
||||
) );
|
||||
|
||||
@@ -2452,7 +2514,7 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
|
||||
|
||||
/* Post Anonymously */
|
||||
$container = $item->containerWrapper();
|
||||
if ( $container and $container->canPostAnonymously( $container::ANON_COMMENTS ) and $comment instanceof \IPS\Content\Anonymous and \IPS\Member::loggedIn()->member_id and \IPS\Member::loggedIn()->group['gbw_can_post_anonymously'] )
|
||||
if ( $container and $container->canPostAnonymously( $container::ANON_COMMENTS ) and $comment instanceof \IPS\Content\Anonymous and ( $comment->author() and $comment->author()->group['gbw_can_post_anonymously'] or $comment->isAnonymous() ) )
|
||||
{
|
||||
$form->add ( new \IPS\Helpers\Form\YesNo( 'post_anonymously', $comment->isAnonymous(), FALSE, array( 'label' => \IPS\Member::loggedIn()->language()->addToStack( 'post_anonymously_suffix' ) ), NULL, NULL, NULL, 'post_anonymously' ) );
|
||||
}
|
||||
@@ -2545,11 +2607,17 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
|
||||
|
||||
$comment->editRating( $values['rating_value'] );
|
||||
}
|
||||
|
||||
|
||||
/* Anonymous posting */
|
||||
if( isset( $values[ 'post_anonymously' ] ) )
|
||||
{
|
||||
$comment->setAnonymous( $values[ 'post_anonymously' ] );
|
||||
/* The anon changes to the user details (photo, name, etc) is outside the JS scope to change when editing a comment */
|
||||
if ( (bool) $comment->isAnonymous() !== (bool) $values['post_anonymously'] )
|
||||
{
|
||||
$reloadPage = true;
|
||||
}
|
||||
|
||||
$comment->setAnonymous( $values[ 'post_anonymously' ], $comment->author() );
|
||||
}
|
||||
|
||||
/* Moderator log */
|
||||
@@ -2770,12 +2838,18 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
|
||||
\IPS\Application::load('core')->extensions( 'core', 'MetaData' )['FeaturedComments']->unfeatureComment( $oldItem, $comment );
|
||||
}
|
||||
|
||||
/* Remove solved associations */
|
||||
if ( $oldItem and \IPS\IPS::classUsesTrait( $oldItem, 'IPS\Content\Solvable' ) and $oldItem->isSolved() and ( $oldItem->mapped('solved_comment_id') === $comment->$commentIdColumn ) )
|
||||
{
|
||||
$oldItem->toggleSolveComment( $comment->$commentIdColumn, FALSE );
|
||||
}
|
||||
|
||||
$comment->move( $item );
|
||||
$oldItem->rebuildFirstAndLastCommentData();
|
||||
|
||||
/* Log it */
|
||||
\IPS\Session::i()->modLog( 'modlog__action_split', array(
|
||||
$item::$title => FALSE,
|
||||
$item::$title => TRUE,
|
||||
$item->url()->__toString() => FALSE,
|
||||
$item->mapped( 'title' ) => FALSE,
|
||||
$oldItem->url()->__toString() => FALSE,
|
||||
@@ -3239,7 +3313,7 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
|
||||
/* Create a copy of the old item for logging */
|
||||
$oldItem = $item;
|
||||
|
||||
$comments = new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select(
|
||||
$comments = iterator_to_array( new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select(
|
||||
'*',
|
||||
$commentClass::$databaseTable,
|
||||
array(
|
||||
@@ -3247,7 +3321,7 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
|
||||
\IPS\Db::i()->in( $commentClass::$databasePrefix . $commentClass::$databaseColumnId, array_keys( \IPS\Request::i()->multimod ) )
|
||||
),
|
||||
$commentClass::$databasePrefix . $commentClass::$databaseColumnMap['date']
|
||||
), $commentClass );
|
||||
), $commentClass ) );
|
||||
|
||||
foreach ( $comments as $comment )
|
||||
{
|
||||
@@ -3322,6 +3396,12 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
|
||||
\IPS\Application::load('core')->extensions( 'core', 'MetaData' )['FeaturedComments']->unfeatureComment( $oldItem, $comment );
|
||||
}
|
||||
|
||||
/* Remove solved associations */
|
||||
if ( $oldItem and \IPS\IPS::classUsesTrait( $oldItem, 'IPS\Content\Solvable' ) and $oldItem->isSolved() and ( $oldItem->mapped('solved_comment_id') === $comment->$commentIdColumn ) )
|
||||
{
|
||||
$oldItem->toggleSolveComment( $comment->$commentIdColumn, FALSE );
|
||||
}
|
||||
|
||||
if( $comment == $firstComment AND $comment->hidden() !== 0 )
|
||||
{
|
||||
if ( isset( $comment::$databaseColumnMap['hidden'] ) )
|
||||
@@ -3364,7 +3444,7 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
|
||||
|
||||
/* Log it */
|
||||
\IPS\Session::i()->modLog( 'modlog__action_split', array(
|
||||
$item::$title => FALSE,
|
||||
$item::$title => TRUE,
|
||||
$item->url()->__toString() => FALSE,
|
||||
$item->mapped( 'title' ) => FALSE,
|
||||
$oldItem->url()->__toString() => FALSE,
|
||||
@@ -3460,7 +3540,7 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
|
||||
|
||||
/* Log it */
|
||||
\IPS\Session::i()->modLog( 'modlog__action_merge_comments', array(
|
||||
$firstComment::$title => FALSE,
|
||||
$firstComment::$title => TRUE,
|
||||
$firstComment->url()->__toString() => FALSE,
|
||||
$firstComment->$commentIdColumn => FALSE,
|
||||
), $item );
|
||||
@@ -3751,64 +3831,96 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
|
||||
{
|
||||
$class = static::$contentModel;
|
||||
$item = $class::loadAndCheckPerms( \IPS\Request::i()->id );
|
||||
$items = [];
|
||||
|
||||
if( !$item instanceof \IPS\Content\Tags OR ( $item->tags() === NULL AND $item->prefix( FALSE ) === NULL ) )
|
||||
/* Are we using elasticsearch? */
|
||||
if ( \IPS\Settings::i()->search_method == 'elastic' )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
$query = \IPS\Content\Search\Query::init( \IPS\Member::loggedIn() );
|
||||
$query->resultsToGet = $limit;
|
||||
$query->filterByMoreLikeThis( $item );
|
||||
$query->filterByLastUpdatedDate( \IPS\DateTime::create()->sub( new \DateInterval( 'P2Y' ) ) );
|
||||
$results = $query->search();
|
||||
$results->init();
|
||||
|
||||
/* Store tags in array, so that we can add a prefix if set */
|
||||
$tags = $item->tags() ?: array();
|
||||
$tags[] = $item->prefix( FALSE );
|
||||
|
||||
/* Build the where clause */
|
||||
$where = array(
|
||||
array( '(' . \IPS\Db::i()->in( 'tag_text', $tags ) . ')' ),
|
||||
array( '!(tag_meta_app=? and tag_meta_area=? and tag_meta_id=?)', $class::$application, $class::$module, \IPS\Request::i()->id ),
|
||||
array( '(' . \IPS\Db::i()->findInSet( 'tag_perm_text', \IPS\Member::loggedIn()->groups ) . ' OR ' . 'tag_perm_text=? )', '*' ),
|
||||
array( 'tag_perm_visible=1' )
|
||||
);
|
||||
|
||||
/* Allow the item to manipulate the query if needed */
|
||||
if( $item->similarContentFilter() )
|
||||
{
|
||||
$where = array_merge( $where, $item->similarContentFilter() );
|
||||
}
|
||||
|
||||
$select = \IPS\Db::i()->select(
|
||||
'tag_meta_app,tag_meta_area,tag_meta_id',
|
||||
'core_tags',
|
||||
$where,
|
||||
'tag_added DESC',
|
||||
array( 0, $limit ),
|
||||
array( 'tag_meta_app', 'tag_meta_area', 'tag_meta_id', 'tag_added' )
|
||||
)->join(
|
||||
'core_tags_perms',
|
||||
array( 'tag_perm_aai_lookup=tag_aai_lookup' )
|
||||
);
|
||||
|
||||
$items = array();
|
||||
|
||||
foreach( $select as $result )
|
||||
{
|
||||
foreach( \IPS\Application::load( $result['tag_meta_app'] )->extensions( 'core', 'ContentRouter' ) as $key => $router )
|
||||
foreach( $results as $result )
|
||||
{
|
||||
foreach( $router->classes AS $itemClass )
|
||||
$object = $result->asArray()['indexData'];
|
||||
$class = $object['index_class'];
|
||||
$loaded = $class::loadAndCheckPerms( $object['index_object_id'] );
|
||||
|
||||
if ( $loaded instanceof \IPS\Content\Item )
|
||||
{
|
||||
if( $itemClass::$module == $result['tag_meta_area'] )
|
||||
{
|
||||
try
|
||||
{
|
||||
$items[ $result['tag_meta_id'] ] = $itemClass::loadAndCheckPerms( $result['tag_meta_id'] );
|
||||
break;
|
||||
}
|
||||
catch( \Exception $e ){}
|
||||
}
|
||||
$obj = $loaded;
|
||||
}
|
||||
else
|
||||
{
|
||||
$obj = $loaded->item();
|
||||
}
|
||||
|
||||
$items[] = $obj;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( !$item instanceof \IPS\Content\Tags or ( $item->tags() === NULL and $item->prefix( FALSE ) === NULL ) )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Store tags in array, so that we can add a prefix if set */
|
||||
$tags = $item->tags() ?: array();
|
||||
$tags[] = $item->prefix( FALSE );
|
||||
|
||||
/* Build the where clause */
|
||||
$where = array(
|
||||
array('(' . \IPS\Db::i()->in( 'tag_text', $tags ) . ')'),
|
||||
array('!(tag_meta_app=? and tag_meta_area=? and tag_meta_id=?)', $class::$application, $class::$module, \IPS\Request::i()->id),
|
||||
array('(' . \IPS\Db::i()->findInSet( 'tag_perm_text', \IPS\Member::loggedIn()->groups ) . ' OR ' . 'tag_perm_text=? )', '*'),
|
||||
array('tag_perm_visible=1')
|
||||
);
|
||||
|
||||
/* Allow the item to manipulate the query if needed */
|
||||
if ( $item->similarContentFilter() )
|
||||
{
|
||||
$where = array_merge( $where, $item->similarContentFilter() );
|
||||
}
|
||||
|
||||
$select = \IPS\Db::i()->select(
|
||||
'tag_meta_app,tag_meta_area,tag_meta_id',
|
||||
'core_tags',
|
||||
$where,
|
||||
'tag_added DESC',
|
||||
array(0, $limit),
|
||||
array('tag_meta_app', 'tag_meta_area', 'tag_meta_id', 'tag_added')
|
||||
)->join(
|
||||
'core_tags_perms',
|
||||
array('tag_perm_aai_lookup=tag_aai_lookup')
|
||||
);
|
||||
|
||||
foreach ( $select as $result )
|
||||
{
|
||||
foreach ( \IPS\Application::load( $result['tag_meta_app'] )->extensions( 'core', 'ContentRouter' ) as $key => $router )
|
||||
{
|
||||
foreach ( $router->classes as $itemClass )
|
||||
{
|
||||
if ( $itemClass::$module == $result['tag_meta_area'] )
|
||||
{
|
||||
try
|
||||
{
|
||||
$items[$result['tag_meta_id']] = $itemClass::loadAndCheckPerms( $result['tag_meta_id'] );
|
||||
break;
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $items;
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
@@ -3905,7 +4017,12 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
|
||||
}
|
||||
catch ( \UnderflowException $e )
|
||||
{
|
||||
\IPS\Output::i()->error( 'node_error', '2S136/1W', 403, '' );
|
||||
if( \IPS\Request::i()->isAjax() )
|
||||
{
|
||||
\IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->basicHover( \IPS\Member::loggedIn()->language()->addToStack( 'anon_user_deleted' ) );
|
||||
return;
|
||||
}
|
||||
\IPS\Output::i()->error( 'anon_user_deleted', '2S136/1W', 403, '' );
|
||||
}
|
||||
|
||||
$author = \IPS\Member::load( $anonymousAuthor );
|
||||
@@ -3935,4 +4052,14 @@ class _Controller extends \IPS\Helpers\CoverPhoto\Controller
|
||||
{
|
||||
$this->reveal();
|
||||
}
|
||||
|
||||
/**
|
||||
* A convenient hook point to finish any set up in manage()
|
||||
*
|
||||
* @param \IPS\Content\Item $item The item that is being set up in manage()
|
||||
* @return void
|
||||
*/
|
||||
protected function finishManage( $item )
|
||||
{
|
||||
}
|
||||
}
|
||||
+269
-53
@@ -54,12 +54,22 @@ abstract class _Item extends \IPS\Content
|
||||
* @brief [Content\Item] Include the ability to search this content item in global site searches
|
||||
*/
|
||||
public static $includeInSiteSearch = TRUE;
|
||||
|
||||
/**
|
||||
* @brief [Content\Item] Include these items in trending content
|
||||
*/
|
||||
public static $includeInTrending = TRUE;
|
||||
|
||||
/**
|
||||
* @brief [Content\Item] Sharelink HTML
|
||||
*/
|
||||
protected $sharelinks = array();
|
||||
|
||||
/**
|
||||
* @brief [Content\Item] Group Posted cache
|
||||
*/
|
||||
public $groupsPosted = [];
|
||||
|
||||
/**
|
||||
* Whether or not to include in site search
|
||||
*
|
||||
@@ -387,7 +397,7 @@ abstract class _Item extends \IPS\Content
|
||||
{
|
||||
$member->name = $values['guest_name'];
|
||||
}
|
||||
|
||||
|
||||
/* Create the item */
|
||||
$time = ( static::canFuturePublish( NULL, $container ) and isset( static::$databaseColumnMap['date'] ) and isset( $values[ static::$formLangPrefix . 'date' ] ) and $values[ static::$formLangPrefix . 'date' ] instanceof \IPS\DateTime ) ? $values[ static::$formLangPrefix . 'date' ] : new \IPS\DateTime;
|
||||
|
||||
@@ -397,6 +407,12 @@ abstract class _Item extends \IPS\Content
|
||||
$obj->processForm( $values );
|
||||
$obj->save();
|
||||
|
||||
/* It is possible that $member has changed */
|
||||
if( $obj->author()->member_id != $member->member_id )
|
||||
{
|
||||
$member = $obj->author();
|
||||
}
|
||||
|
||||
/* Create the comment */
|
||||
$comment = NULL;
|
||||
if ( isset( static::$commentClass ) and static::$firstCommentRequired )
|
||||
@@ -418,7 +434,7 @@ abstract class _Item extends \IPS\Content
|
||||
$obj->save();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Update posts per day limits - don't do this for content items that require a first comment as the comment class will handle that */
|
||||
if ( $member->member_id AND $member->group['g_ppd_limit'] AND static::$firstCommentRequired === FALSE )
|
||||
{
|
||||
@@ -433,12 +449,11 @@ abstract class _Item extends \IPS\Content
|
||||
$member->members_day_posts = $current;
|
||||
$member->save();
|
||||
}
|
||||
|
||||
|
||||
/* Post anonymously */
|
||||
if( isset( $values[ 'post_anonymously' ] ) )
|
||||
{
|
||||
$obj->setAnonymous( $values[ 'post_anonymously' ] );
|
||||
$obj->setAnonymous( $values[ 'post_anonymously' ], $member );
|
||||
}
|
||||
|
||||
/* Do any processing */
|
||||
@@ -770,7 +785,7 @@ abstract class _Item extends \IPS\Content
|
||||
}
|
||||
|
||||
/* Post Anonymously */
|
||||
if ( $container and $container->canPostAnonymously( $container::ANON_ITEMS ) )
|
||||
if ( $container and $container->canPostAnonymously( $container::ANON_ITEMS ) and ( $item === NULL or ( $item->author() and $item->author()->group['gbw_can_post_anonymously'] ) or $item->isAnonymous() ) )
|
||||
{
|
||||
$return['post_anonymously'] = new \IPS\Helpers\Form\YesNo( 'post_anonymously', ( $item ) ? $item->isAnonymous() : FALSE , FALSE, array( 'label' => \IPS\Member::loggedIn()->language()->addToStack( 'post_anonymously_suffix' ) ), NULL, NULL, NULL, 'post_anonymously' );
|
||||
}
|
||||
@@ -997,7 +1012,15 @@ abstract class _Item extends \IPS\Content
|
||||
* @param array $values Values from form
|
||||
* @return void
|
||||
*/
|
||||
protected function processBeforeCreate( $values ) {}
|
||||
protected function processBeforeCreate( $values ) {
|
||||
|
||||
/* Check for banned IP - The banned ip addresses are only checked inside the register and login controller, so people are able to bypass them when PBR is used */
|
||||
if( !\IPS\Member::loggedIn()->member_id AND \IPS\Request::i()->ipAddressIsBanned() )
|
||||
{
|
||||
\IPS\Output::i()->showBanned();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Process created object AFTER the object has been created
|
||||
@@ -1022,6 +1045,32 @@ abstract class _Item extends \IPS\Content
|
||||
{
|
||||
\IPS\Api\Webhook::fire( str_replace( '\\', '', \substr( \get_called_class(), 3 ) ) . '_create', $this, $this->webhookFilters() );
|
||||
}
|
||||
|
||||
/* Data Layer Event */
|
||||
if ( \IPS\Settings::i()->core_datalayer_enabled )
|
||||
{
|
||||
/* Is it a status update? */
|
||||
if ( isset( static::$title ) and static::$title === \IPS\core\Statuses\Status::$title )
|
||||
{
|
||||
$member = \IPS\Member::load( $this->member_id );
|
||||
$properties = array(
|
||||
'profile_id' => $member->member_id,
|
||||
'profile_name' => $member->real_name ?: null,
|
||||
);
|
||||
\IPS\core\DataLayer::i()->addEvent( 'social_update', $properties );
|
||||
}
|
||||
/* Otherwise, make sure it isn't a PM Conversation */
|
||||
elseif ( !isset( static::$title ) or static::$title !== \IPS\core\Messenger\Conversation::$title )
|
||||
{
|
||||
\IPS\core\DataLayer::i()->addEvent( 'content_create', $this->getDataLayerProperties() );
|
||||
}
|
||||
}
|
||||
|
||||
/* Send this URL to IndexNow if the guest can view it */
|
||||
if ( $this->canView( new \IPS\Member ) )
|
||||
{
|
||||
\IPS\core\IndexNow::addUrlToQueue( $this->url() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1125,10 +1174,12 @@ abstract class _Item extends \IPS\Content
|
||||
{
|
||||
$this->logEdit( $values );
|
||||
}
|
||||
|
||||
|
||||
$container = $this->containerWrapper();
|
||||
|
||||
if ( $this instanceof \IPS\Content\FuturePublishing AND isset( $values[ static::$formLangPrefix . 'date' ] ) )
|
||||
{
|
||||
$container = $this->containerWrapper();
|
||||
|
||||
$column = static::$databaseColumnMap['is_future_entry'];
|
||||
|
||||
if ( $container AND $this->$column )
|
||||
@@ -1150,10 +1201,17 @@ abstract class _Item extends \IPS\Content
|
||||
}
|
||||
|
||||
/* Post anonymously */
|
||||
if( isset( $values[ 'post_anonymously' ] ) and ( isset( $container ) and $container->canPostAnonymously( $container::ANON_ITEMS ) ) )
|
||||
if( isset( $values[ 'post_anonymously' ] ) and ( $container and $container->canPostAnonymously( $container::ANON_ITEMS ) ) )
|
||||
{
|
||||
$this->setAnonymous( $values[ 'post_anonymously' ] );
|
||||
$this->setAnonymous( $values[ 'post_anonymously' ], $this->author() );
|
||||
}
|
||||
|
||||
/* Send this URL to IndexNow if the guest can view it */
|
||||
if( $this->canView( new \IPS\Member ) )
|
||||
{
|
||||
\IPS\core\IndexNow::addUrlToQueue( $this->url() );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* Holds the old content for edit logging */
|
||||
@@ -1354,7 +1412,7 @@ abstract class _Item extends \IPS\Content
|
||||
*/
|
||||
public function url( $action=NULL )
|
||||
{
|
||||
if( $action == 'getPrefComment' )
|
||||
if( $action === 'getPrefComment' AND \IPS\Member::loggedIn()->member_id )
|
||||
{
|
||||
$pref = \IPS\Member::loggedIn()->linkPref() ?: \IPS\Settings::i()->link_default;
|
||||
|
||||
@@ -1373,7 +1431,7 @@ abstract class _Item extends \IPS\Content
|
||||
break;
|
||||
}
|
||||
}
|
||||
elseif( !\IPS\Member::loggedIn()->member_id AND $action == 'getNewComment' )
|
||||
elseif( ( $action == 'getPrefComment' OR $action == 'getNewComment' OR $action == 'getLastComment' ) AND !\IPS\Member::loggedIn()->member_id )
|
||||
{
|
||||
$action = NULL;
|
||||
}
|
||||
@@ -1625,7 +1683,7 @@ abstract class _Item extends \IPS\Content
|
||||
}
|
||||
}
|
||||
|
||||
return \count( $attachments ) ? $attachments : NULL;
|
||||
return \count( $attachments ) ? \array_slice( $attachments, 0, $limit ) : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2263,7 +2321,7 @@ abstract class _Item extends \IPS\Content
|
||||
$hasPrevious = TRUE;
|
||||
try
|
||||
{
|
||||
$previousFirstComment = $commentClass::constructFromData( \IPS\Db::i()->select( '*', $commentClass::$databaseTable, array( $commentClass::$databasePrefix . $commentClass::$databaseColumnMap['item'] . '=? AND ' . $commentClass::$databasePrefix . $commentClass::$databaseColumnMap['first'] . '=?', $this->$idColumn, TRUE ) )->first() );
|
||||
$previousFirstComment = $commentClass::constructFromData( \IPS\Db::i()->select( '*', $commentClass::$databaseTable, array( $commentClass::$databasePrefix . $commentClass::$databaseColumnMap['item'] . '=? AND ' . $commentClass::$databasePrefix . $commentClass::$databaseColumnMap['first'] . '=?', $this->$idColumn, TRUE ), NULL, 1 )->first() );
|
||||
}
|
||||
catch( \UnderflowException $e )
|
||||
{
|
||||
@@ -2717,6 +2775,14 @@ abstract class _Item extends \IPS\Content
|
||||
{
|
||||
\IPS\Member::load( $member )->recountNotifications();
|
||||
}
|
||||
|
||||
/** Item::url() can throw a LogicException exception in specific cases like when a Pages Record has no valid page */
|
||||
try
|
||||
{
|
||||
\IPS\core\IndexNow::addUrlToQueue( $this->url() );
|
||||
}
|
||||
catch( \LogicException $e ) {}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -4343,39 +4409,47 @@ abstract class _Item extends \IPS\Content
|
||||
|
||||
/* Solved count */
|
||||
$commentClass = static::$commentClass;
|
||||
if ( \IPS\IPS::classUsesTrait( $this, 'IPS\Content\Solvable' ) )
|
||||
$showSolvedStats = FALSE;
|
||||
|
||||
if ( method_exists( $this, 'isQuestion' ) and $this->isQuestion() )
|
||||
{
|
||||
if ( $this->containerAllowsMemberSolvable() OR $this->containerAllowsSolvable() )
|
||||
$showSolvedStats = TRUE;
|
||||
}
|
||||
else if ( \IPS\IPS::classUsesTrait( $this, 'IPS\Content\Solvable' ) and ( $this->containerAllowsMemberSolvable() OR $this->containerAllowsSolvable() ) )
|
||||
{
|
||||
$showSolvedStats = TRUE;
|
||||
}
|
||||
|
||||
if ( $commentClass AND $showSolvedStats )
|
||||
{
|
||||
$memberIds = array();
|
||||
$solvedCounts = array();
|
||||
$authorField = $commentClass::$databaseColumnMap['author'];
|
||||
|
||||
foreach( $results as $id => $data )
|
||||
{
|
||||
$memberIds = array();
|
||||
$solvedCounts = array();
|
||||
$authorField = $commentClass::$databaseColumnMap['author'];
|
||||
$memberIds[ $data->$authorField ] = $data->$authorField;
|
||||
}
|
||||
|
||||
if ( \count( $memberIds ) )
|
||||
{
|
||||
foreach( \IPS\Db::i()->select( 'COUNT(*) as count, member_id', 'core_solved_index', array( \IPS\Db::i()->in( 'member_id', $memberIds ) ), NULL, NULL, 'member_id' ) as $member )
|
||||
{
|
||||
$solvedCounts[ $member['member_id'] ] = $member['count'];
|
||||
}
|
||||
|
||||
foreach( $results as $id => $data )
|
||||
{
|
||||
$memberIds[ $data->$authorField ] = $data->$authorField;
|
||||
}
|
||||
|
||||
if ( \count( $memberIds ) )
|
||||
{
|
||||
foreach( \IPS\Db::i()->select( 'COUNT(*) as count, member_id', 'core_solved_index', array( \IPS\Db::i()->in( 'member_id', $memberIds ) ), NULL, NULL, 'member_id' ) as $member )
|
||||
if ( isset( $solvedCounts[ $data->$authorField ] ) )
|
||||
{
|
||||
$solvedCounts[ $member['member_id'] ] = $member['count'];
|
||||
}
|
||||
|
||||
foreach( $results as $id => $data )
|
||||
{
|
||||
if ( isset( $solvedCounts[ $data->$authorField ] ) )
|
||||
{
|
||||
$results[ $id ]->author_solved_count = $solvedCounts[ $data->$authorField ];
|
||||
}
|
||||
$results[ $id ]->author_solved_count = $solvedCounts[ $data->$authorField ];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Recognized content */
|
||||
if ( \IPS\IPS::classUsesTrait( $commentClass, 'IPS\Content\Recognizable' ) )
|
||||
if ( $commentClass AND \IPS\IPS::classUsesTrait( $commentClass, 'IPS\Content\Recognizable' ) )
|
||||
{
|
||||
foreach( \IPS\Db::i()->select( '*', 'core_member_recognize', [ [ 'r_content_class=?', $commentClass ], [ \IPS\Db::i()->in('r_content_id', array_keys( $results ) ) ] ] ) as $row )
|
||||
{
|
||||
@@ -4402,7 +4476,7 @@ abstract class _Item extends \IPS\Content
|
||||
*/
|
||||
public function mergeConcurrentComment()
|
||||
{
|
||||
if ( \IPS\Member::loggedIn()->member_id and \IPS\Settings::i()->merge_concurrent_posts and $this->lastCommenter()->member_id == \IPS\Member::loggedIn()->member_id )
|
||||
if ( \IPS\Member::loggedIn()->member_id and \IPS\Settings::i()->merge_concurrent_posts and $this->lastCommenter()->member_id == \IPS\Member::loggedIn()->member_id and !\IPS\Member::loggedIn()->moderateNewContent() )
|
||||
{
|
||||
$lastComment = $this->comments( 1, 0, 'date', 'desc', NULL, TRUE );
|
||||
|
||||
@@ -4523,6 +4597,12 @@ abstract class _Item extends \IPS\Content
|
||||
\IPS\Output::i()->error( 'posts_per_day_error', '2S177/2', 403, '' );
|
||||
}
|
||||
}
|
||||
|
||||
/* Check for banned IP - The banned ip addresses are only checked inside the register and login controller, so people are able to bypass them when PBR is used */
|
||||
if( !\IPS\Member::loggedIn()->member_id AND \IPS\Request::i()->ipAddressIsBanned() )
|
||||
{
|
||||
\IPS\Output::i()->showBanned();
|
||||
}
|
||||
|
||||
$currentPageCount = \IPS\Request::i()->currentPage;
|
||||
|
||||
@@ -4607,8 +4687,23 @@ abstract class _Item extends \IPS\Content
|
||||
{
|
||||
$message = \IPS\Member::loggedIn()->language()->addToStack( 'mod_queue_message' );
|
||||
}
|
||||
|
||||
\IPS\Output::i()->json( array( 'type' => 'add', 'id' => $comment->$commentIdColumn, 'page' => $newPageCount, 'total' => $this->mapped('num_comments'), 'content' => \IPS\Output::i()->replaceEmojiWithImages( $output ), 'message' => $message ) );
|
||||
|
||||
/* Data Layer stuff for comments here, not in Comment class. We track user actions, and the user action is handled here */
|
||||
$dataLayer = \IPS\Settings::i()->core_datalayer_enabled ? $this->getDataLayerProperties( $comment ) : array();
|
||||
|
||||
\IPS\Output::i()->json(
|
||||
array(
|
||||
'type' => 'add',
|
||||
'id' => $comment->$commentIdColumn,
|
||||
'page' => $newPageCount,
|
||||
'total' => $this->mapped('num_comments'),
|
||||
'content' => \IPS\Output::i()->replaceEmojiWithImages( $output ),
|
||||
'message' => $message,
|
||||
'postedByLoggedInMember' => true,
|
||||
/* This is used on the front end */
|
||||
"dataLayer" => $dataLayer,
|
||||
)
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -4667,6 +4762,102 @@ abstract class _Item extends \IPS\Content
|
||||
|
||||
return $this->_commentFormHtml;
|
||||
}
|
||||
|
||||
protected $_dataLayerProperties = array();
|
||||
|
||||
/**
|
||||
* Most, if not all of these are the same for different events, so we can just have one method
|
||||
*
|
||||
* @param IPS\Content\Comment $comment A comment item, leave null for these keys to be omitted
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getDataLayerProperties( ?\IPS\Content\Comment $comment = null )
|
||||
{
|
||||
$commentIdColumn = $comment ? $comment::$databaseColumnId : null;
|
||||
$index = $commentIdColumn ? ( $comment->$commentIdColumn ?: 0 ) : 0;
|
||||
|
||||
if ( !isset( $this->_dataLayerProperties[$index] ) )
|
||||
{
|
||||
$app = static::$application ?? null;
|
||||
$idColumn = static::$databaseColumnId;
|
||||
$dataLayer = array(
|
||||
'author_id' => \IPS\core\DataLayer::i()->getSsoId( $this->author()->member_id ),
|
||||
'author_name' => $this->author()->real_name ?: null,
|
||||
'content_age' => $this->mapped( 'date' ) ? \intval( floor( ( time() - $this->mapped( 'date' ) ) / 86400 ) ) : null,
|
||||
'content_container_id' => null,
|
||||
'content_container_name' => null,
|
||||
'content_container_type' => null,
|
||||
'content_container_url' => null,
|
||||
'content_id' => $this->$idColumn,
|
||||
'content_title' => $this->mapped( 'title' ),
|
||||
'content_type' => static::$contentType ?? null,
|
||||
'content_url' => (string) $this->url(),
|
||||
);
|
||||
|
||||
if ( $comment AND ( $commentIdColumn = $comment::$databaseColumnId ?? null ) )
|
||||
{
|
||||
$dataLayer = array_replace( $dataLayer, array(
|
||||
'comment_id' => $comment->$commentIdColumn,
|
||||
'comment_type' => $comment::$commentType ?? null,
|
||||
'comment_url' => (string) $comment->url()
|
||||
) );
|
||||
}
|
||||
|
||||
/* For QA forums, the comment_type and content_type is an exception */
|
||||
if ( \IPS\Settings::i()->core_datalayer_distinguish_qa AND
|
||||
$this instanceof \IPS\forums\Topic AND
|
||||
$this->container()->_forum_type === 'qa' )
|
||||
{
|
||||
$dataLayer['content_type'] = 'question';
|
||||
if ( isset( $comment ) )
|
||||
{
|
||||
$dataLayer['comment_type'] = 'answer';
|
||||
}
|
||||
}
|
||||
/* If we didn't find a Comment or Content type, try pulling that info from the static title fields */
|
||||
elseif ( ( !isset( $dataLayer['content_type'] ) AND isset( static::$title ) ) OR ( !isset( $dataLayer['comment_type'] ) AND isset( $comment, $comment::$title ) ) )
|
||||
{
|
||||
$contentType = ( $app ? preg_replace( "/[{$app}_]?(.*)/", '$1', static::$title ?? "" ) : ( static::$title ?? null ) ) ?: null;
|
||||
if ( $contentType AND !isset( $dataLayer['content_type'] ) )
|
||||
{
|
||||
$dataLayer['content_type'] = $contentType;
|
||||
}
|
||||
|
||||
if ( $comment AND isset( $comment::$title ) AND !isset( $dataLayer['comment_type'] ) )
|
||||
{
|
||||
$commentType = ($app or $contentType) ? preg_replace( "/" . ( $app ? "[{$app}_]?" : '' ) . ( $contentType ? "[{$contentType}_]?" : '' ) . "(.*)/", "$1", $comment::$title ) : $comment::$title;
|
||||
$dataLayer['comment_type'] = $commentType;
|
||||
}
|
||||
}
|
||||
|
||||
/* Use either comment or review if there still is no comment type (note this should never happen as of IPS v4.6 unless 3rd party things are going on) */
|
||||
if ( $comment AND !isset( $dataLayer['comment_type'] ) )
|
||||
{
|
||||
$dataLayer['comment_type'] = $comment instanceof \IPS\Content\Review ? 'review' : 'comment';
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$container = $this->container();
|
||||
$dataLayer = array_replace( $dataLayer, $container->getDataLayerProperties() );
|
||||
}
|
||||
catch ( \OutOfRangeException | \BadMethodCallException $e ) {}
|
||||
|
||||
if ( !isset( $dataLayer['content_area'] ) AND $app )
|
||||
{
|
||||
$lang = \IPS\Lang::load( \IPS\Lang::defaultLanguage() );
|
||||
if ( $lang->checkKeyExists( '__app_' . static::$application ) )
|
||||
{
|
||||
$dataLayer['content_area'] = $lang->addToStack( '__app_' . static::$application );
|
||||
}
|
||||
}
|
||||
|
||||
$this->_dataLayerProperties[$index] = \IPS\core\DataLayer::i()->filterProperties( $dataLayer );
|
||||
}
|
||||
|
||||
return $this->_dataLayerProperties[$index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the comment form elements
|
||||
@@ -4760,16 +4951,7 @@ abstract class _Item extends \IPS\Content
|
||||
$hidden = NULL;
|
||||
}
|
||||
|
||||
$comment = $commentClass::create( $this, $values[ static::$formLangPrefix . 'comment' . '_' . $this->$idColumn ], FALSE, isset( $values['guest_name'] ) ? $values['guest_name'] : NULL, NULL, NULL, NULL, NULL, $hidden );
|
||||
|
||||
$parameters = array_merge( array( 'reply-' . static::$application . '/' . static::$module . '-' . $this->$idColumn ), $comment->attachmentIds() );
|
||||
\IPS\File::claimAttachments( ...$parameters );
|
||||
|
||||
/* Post anonymously */
|
||||
if( isset( $values[ 'post_anonymously' ] ) )
|
||||
{
|
||||
$comment->setAnonymous( $values[ 'post_anonymously' ] );
|
||||
}
|
||||
$comment = $commentClass::create( $this, $values[ static::$formLangPrefix . 'comment' . '_' . $this->$idColumn ], FALSE, isset( $values['guest_name'] ) ? $values['guest_name'] : NULL, NULL, NULL, NULL, NULL, $hidden, ( isset( $values[ 'post_anonymously' ] ) ? (bool) $values[ 'post_anonymously' ] : NULL ) );
|
||||
|
||||
/* Auto-follow - If posted anonymously we should set follow to anonymous as well */
|
||||
if( isset( $values[ static::$formLangPrefix . 'auto_follow' ] ) )
|
||||
@@ -5606,7 +5788,7 @@ abstract class _Item extends \IPS\Content
|
||||
if ( isset( static::$databaseColumnMap['last_comment_anon'] ) )
|
||||
{
|
||||
$lastCommentAnonField = static::$databaseColumnMap['last_comment_anon'];
|
||||
$this->$lastCommentAnonField = $comment->isAnonymous();
|
||||
$this->$lastCommentAnonField = (int) $comment->isAnonymous();
|
||||
}
|
||||
}
|
||||
catch ( \UnderflowException $e )
|
||||
@@ -5634,6 +5816,11 @@ abstract class _Item extends \IPS\Content
|
||||
$field = static::$databaseColumnMap[$c];
|
||||
$this->$field = 0;
|
||||
}
|
||||
else if( $c === 'last_comment_anon' AND isset( static::$databaseColumnMap['last_comment_anon'] ) )
|
||||
{
|
||||
$field = static::$databaseColumnMap[$c];
|
||||
$this->$field = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( isset( static::$databaseColumnMap[$c] ) )
|
||||
@@ -6310,7 +6497,7 @@ abstract class _Item extends \IPS\Content
|
||||
{
|
||||
$member = $member ?: \IPS\Member::loggedIn();
|
||||
|
||||
/* If the member is banned they can't do antyhing? */
|
||||
/* If the member is banned they can't do anything? */
|
||||
if ( ! ( $member instanceof \IPS\Member\Group ) and ! $member->group['g_view_board'] )
|
||||
{
|
||||
return FALSE;
|
||||
@@ -7771,11 +7958,11 @@ abstract class _Item extends \IPS\Content
|
||||
}
|
||||
}
|
||||
|
||||
return ( isset ( $this->tags['tags'] ) ? $this->tags['tags'] : NULL);
|
||||
return ( isset ( $this->tags['tags'] ) ? $this->tags['tags'] : [] );
|
||||
}
|
||||
else
|
||||
{
|
||||
return NULL;
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8755,7 +8942,7 @@ abstract class _Item extends \IPS\Content
|
||||
'meta_item_id' => $this->$idColumn,
|
||||
'meta_type' => $type,
|
||||
'meta_data' => $data,
|
||||
'meta_item_author' => $this->author()->member_id,
|
||||
'meta_item_author' => (int) $this->author()->member_id,
|
||||
'meta_added' => time()
|
||||
) );
|
||||
|
||||
@@ -9212,4 +9399,33 @@ abstract class _Item extends \IPS\Content
|
||||
{
|
||||
return array( );
|
||||
}
|
||||
|
||||
/**
|
||||
* Webhook filters
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function webhookFilters()
|
||||
{
|
||||
$filters = parent::webhookFilters();
|
||||
|
||||
if ( \in_array( 'IPS\Content\Lockable', class_implements( $this ) ) )
|
||||
{
|
||||
$filters['locked'] = $this->locked();
|
||||
}
|
||||
if ( \in_array( 'IPS\Content\Pinnable', class_implements( $this ) ) )
|
||||
{
|
||||
$filters['pinned'] = (bool) $this->mapped('pinned');
|
||||
}
|
||||
if ( \in_array( 'IPS\Content\Featurable', class_implements( $this ) ) )
|
||||
{
|
||||
$filters['featured'] = (bool) $this->mapped('featured');
|
||||
}
|
||||
if ( \in_array( 'IPS\Content\Polls', class_implements( $this ) ) )
|
||||
{
|
||||
$filters['hasPoll'] = (bool) $this->mapped('poll');
|
||||
}
|
||||
|
||||
return $filters;
|
||||
}
|
||||
}
|
||||
@@ -96,6 +96,11 @@ class _Facebook extends PromoteAbstract
|
||||
{
|
||||
$facebook = static::getLoginHandler();
|
||||
|
||||
if ( $facebook === NULL )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
$authorizedScopes = $facebook->authorizedScopes( $this->member );
|
||||
|
||||
if( $authorizedScopes === NULL )
|
||||
@@ -165,7 +170,7 @@ class _Facebook extends PromoteAbstract
|
||||
{
|
||||
foreach( $this->settings['shareable']['pages'] as $id => $data )
|
||||
{
|
||||
$options[ 'p_' . $id ] = \IPS\Member::loggedIn()->language()->addToStack( 'promote_facebook_page_prefix', NULL, array( 'sprintf' => array( $data['name'] ) ) );
|
||||
$options[ 'p_' . $id ] = \IPS\Member::loggedIn()->language()->addToStack( 'promote_facebook_page_prefix', FALSE, array( 'sprintf' => array( $data['name'] ) ) );
|
||||
$names[] = $data['name'];
|
||||
}
|
||||
}
|
||||
@@ -174,7 +179,7 @@ class _Facebook extends PromoteAbstract
|
||||
{
|
||||
foreach( $this->settings['shareable']['groups'] as $id => $data )
|
||||
{
|
||||
$options[ 'g_' . $id ] = \IPS\Member::loggedIn()->language()->addToStack( 'promote_facebook_group_prefix', NULL, array( 'sprintf' => array( $data['name'] ) ) );
|
||||
$options[ 'g_' . $id ] = \IPS\Member::loggedIn()->language()->addToStack( 'promote_facebook_group_prefix', FALSE, array( 'sprintf' => array( $data['name'] ) ) );
|
||||
$names[] = $data['name'];
|
||||
}
|
||||
}
|
||||
@@ -277,7 +282,7 @@ class _Facebook extends PromoteAbstract
|
||||
\IPS\Log::log( $e, 'facebook' );
|
||||
}
|
||||
|
||||
if ( ! $photos or \count( $photos ) === 1 )
|
||||
if ( ! $photos or ( \is_array( $photos ) and \count( $photos ) === 1 ) )
|
||||
{
|
||||
/* Simple message */
|
||||
try
|
||||
@@ -450,6 +455,11 @@ class _Facebook extends PromoteAbstract
|
||||
{
|
||||
$facebook = \IPS\Login\Handler::findMethod('IPS\Login\Handler\Oauth2\Facebook');
|
||||
|
||||
if ( $facebook === NULL )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* We are using the default app here */
|
||||
if ( ! \is_numeric( \IPS\Settings::i()->promote_facebook_auth ) )
|
||||
{
|
||||
|
||||
@@ -106,6 +106,11 @@ class _Twitter extends PromoteAbstract
|
||||
public function canPostToPage()
|
||||
{
|
||||
$twitter = \IPS\Login\Handler::findMethod('IPS\Login\Handler\Oauth1\Twitter');
|
||||
if ( $twitter === NULL )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return $twitter->hasWritePermissions( $this->settings['token'], $this->settings['secret'] );
|
||||
}
|
||||
|
||||
@@ -194,8 +199,8 @@ class _Twitter extends PromoteAbstract
|
||||
{
|
||||
$photos = $promote->imageObjects();
|
||||
$mediaIds = array();
|
||||
|
||||
if ( \count( $photos ) )
|
||||
|
||||
if ( \is_array( $photos ) and \count( $photos ) )
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
@@ -152,6 +152,22 @@ trait Reactable
|
||||
$this->_reactions = NULL;
|
||||
|
||||
$this->hasReacted[ $member->member_id ] = $reaction;
|
||||
|
||||
if( \IPS\Application::appIsEnabled( 'cloud' ) and \IPS\cloud\Realtime::i()->isEnabled('realtime') )
|
||||
{
|
||||
$this->reactions();
|
||||
\IPS\cloud\Realtime::i()->publishEvent( 'reaction-count', array('count' => $this->_reactionCount) );
|
||||
}
|
||||
|
||||
if( \IPS\Application::appIsEnabled( 'cloud' ) and \IPS\cloud\Realtime::i()->isEnabled('trending') )
|
||||
{
|
||||
/* We need to make sure we're using the item */
|
||||
$item = ( ! $this instanceof \IPS\Content\Item ) ? $this->item() : $this;
|
||||
$itemIdColumn = $item::$databaseColumnId;
|
||||
$itemId = $item->$itemIdColumn;
|
||||
|
||||
\IPS\Redis::i()->zIncrBy( 'trending', time() * 0.4, \get_class( $item ) .'__' . $itemId );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -426,9 +442,9 @@ trait Reactable
|
||||
try
|
||||
{
|
||||
$class = $row['rep_class'];
|
||||
$comment = $class::load( $row['type_id'] );
|
||||
$item = $class::load( $row['type_id'] );
|
||||
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( '_defart_from_date', NULL, array( 'htmlsprintf' => array( $comment->url(), $comment::$title, \IPS\DateTime::ts( $date )->html() ) ) );
|
||||
return \IPS\Member::loggedIn()->language()->addToStack( '_defart_from_date', FALSE, array( 'htmlsprintf' => array( $item->url(), $item->mapped('title'), \IPS\DateTime::ts( $date )->html() ) ) );
|
||||
}
|
||||
catch( \Exception $e )
|
||||
{
|
||||
|
||||
@@ -72,6 +72,12 @@ trait Recognizable
|
||||
*/
|
||||
public function canRecognize( ?\IPS\Member $member=NULL ): bool
|
||||
{
|
||||
/* Are achievements enabled? */
|
||||
if ( !\IPS\Settings::i()->achievements_enabled )
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Already recognized? */
|
||||
if ( isset( $this->recognized ) )
|
||||
{
|
||||
@@ -161,38 +167,38 @@ trait Recognizable
|
||||
public function recognizedBlurb(): array
|
||||
{
|
||||
$return = [ 'message' => NULL ];
|
||||
$return['main'] = \IPS\Member::loggedIn()->language()->addToStack('recognize_blurb_main', NULL, [ 'htmlsprintf' => [ $this::$title, $this->recognized->_given_by->link() ] ] );
|
||||
$return['main'] = \IPS\Member::loggedIn()->language()->addToStack( 'recognize_blurb_main', FALSE, [ 'htmlsprintf' => [ $this::$title, $this->recognized->_given_by->link() ] ] );
|
||||
if ( $this->recognized->badge and $this->recognized->points )
|
||||
{
|
||||
if ( $this->author()->member_id === \IPS\Member::loggedIn()->member_id )
|
||||
{
|
||||
$return['awards'] = \IPS\Member::loggedIn()->language()->addToStack( 'recognize_blurb_awarded_points_and_badge', NULL, ['htmlsprintf' => [$this->recognized->badge()->_title, $this->recognized->points]] );
|
||||
$return['awards'] = \IPS\Member::loggedIn()->language()->addToStack( 'recognize_blurb_awarded_points_and_badge', FALSE, ['htmlsprintf' => [$this->recognized->badge()->_title, $this->recognized->points]] );
|
||||
}
|
||||
else
|
||||
{
|
||||
$return['awards'] = \IPS\Member::loggedIn()->language()->addToStack( 'recognize_blurb_awarded_points_and_badge_third', NULL, ['htmlsprintf' => [$this->author()->name, $this->recognized->badge()->_title, $this->recognized->points]] );
|
||||
$return['awards'] = \IPS\Member::loggedIn()->language()->addToStack( 'recognize_blurb_awarded_points_and_badge_third', FALSE, ['htmlsprintf' => [$this->author()->name, $this->recognized->badge()->_title, $this->recognized->points]] );
|
||||
}
|
||||
}
|
||||
else if ( $this->recognized->points )
|
||||
{
|
||||
if ( $this->author()->member_id === \IPS\Member::loggedIn()->member_id )
|
||||
{
|
||||
$return['awards'] = \IPS\Member::loggedIn()->language()->addToStack('recognize_blurb_awarded_points', NULL, [ 'sprintf' => [ $this->recognized->points ] ] );
|
||||
$return['awards'] = \IPS\Member::loggedIn()->language()->addToStack( 'recognize_blurb_awarded_points', FALSE, [ 'sprintf' => [ $this->recognized->points ] ] );
|
||||
}
|
||||
else
|
||||
{
|
||||
$return['awards'] = \IPS\Member::loggedIn()->language()->addToStack('recognize_blurb_awarded_points_third', NULL, [ 'sprintf' => [ $this->author()->name, $this->recognized->points ] ] );
|
||||
$return['awards'] = \IPS\Member::loggedIn()->language()->addToStack( 'recognize_blurb_awarded_points_third', FALSE, [ 'sprintf' => [ $this->author()->name, $this->recognized->points ] ] );
|
||||
}
|
||||
}
|
||||
else if ( $this->recognized->badge )
|
||||
{
|
||||
if ( $this->author()->member_id === \IPS\Member::loggedIn()->member_id )
|
||||
{
|
||||
$return['awards'] = \IPS\Member::loggedIn()->language()->addToStack('recognize_blurb_awarded_badge', NULL, [ 'htmlsprintf' => [ $this->recognized->badge()->_title ] ] );
|
||||
$return['awards'] = \IPS\Member::loggedIn()->language()->addToStack( 'recognize_blurb_awarded_badge', FALSE, [ 'htmlsprintf' => [ $this->recognized->badge()->_title ] ] );
|
||||
}
|
||||
else
|
||||
{
|
||||
$return['awards'] = \IPS\Member::loggedIn()->language()->addToStack('recognize_blurb_awarded_badge_third', NULL, [ 'htmlsprintf' => [ $this->author()->name, $this->recognized->badge()->_title ] ] );
|
||||
$return['awards'] = \IPS\Member::loggedIn()->language()->addToStack( 'recognize_blurb_awarded_badge_third', FALSE, [ 'htmlsprintf' => [ $this->author()->name, $this->recognized->badge()->_title ] ] );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,10 +44,11 @@ abstract class _Review extends \IPS\Content\Comment
|
||||
* @param \IPS\DateTime|NULL $time The time
|
||||
* @param string|NULL $ipAddress The IP address or NULL to detect automatically
|
||||
* @param int|NULL $hiddenStatus NULL to set automatically or override: 0 = unhidden; 1 = hidden, pending moderator approval; -1 = hidden (as if hidden by a moderator)
|
||||
* @param int|NULL $anonymous NULL for no value, 0 or 1 for a value (0=no, 1=yes)
|
||||
* @return static
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public static function create( $item, $comment, $first=FALSE, $rating=NULL, $guestName=NULL, $member=NULL, \IPS\DateTime $time=NULL, $ipAddress=NULL, $hiddenStatus=NULL )
|
||||
public static function create( $item, $comment, $first=FALSE, $rating=NULL, $guestName=NULL, $member=NULL, \IPS\DateTime $time=NULL, $ipAddress=NULL, $hiddenStatus=NULL, $anonymous=NULL )
|
||||
{
|
||||
if ( !\is_int( $rating ) or $rating < 1 or $rating > \intval( \IPS\Settings::i()->reviews_rating_out_of ) )
|
||||
{
|
||||
|
||||
@@ -25,8 +25,8 @@ class _Index extends \IPS\Content\Search\Index
|
||||
/**
|
||||
* @brief Elasticsearch version requirements
|
||||
*/
|
||||
const MINIMUM_VERSION = '6.0.0';
|
||||
const UNSUPPORTED_VERSION = '7.7.0';
|
||||
const MINIMUM_VERSION = '7.2.0';
|
||||
const UNSUPPORTED_VERSION = '8.0.0';
|
||||
|
||||
/**
|
||||
* @brief The server URL
|
||||
@@ -61,7 +61,7 @@ class _Index extends \IPS\Content\Search\Index
|
||||
$analyzer = key( $settings['analysis']['analyzer'] );
|
||||
}
|
||||
|
||||
$this->url->request()->delete();
|
||||
\IPS\Content\Search\Elastic\Index::request( $this->url )->delete();
|
||||
|
||||
$definition = array(
|
||||
'settings' => $settings,
|
||||
@@ -106,6 +106,8 @@ class _Index extends \IPS\Content\Search\Index
|
||||
'index_comments' => array( 'type' => 'long' ),
|
||||
'index_reviews' => array( 'type' => 'long' ),
|
||||
'index_participants' => array( 'type' => 'long' ),
|
||||
'index_is_anon' => array( 'type' => 'byte' ),
|
||||
'index_item_solved' => array( 'type' => 'byte' )
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -113,7 +115,7 @@ class _Index extends \IPS\Content\Search\Index
|
||||
|
||||
try
|
||||
{
|
||||
$response = $this->url->setQueryString( 'include_type_name', 'true' )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->put( json_encode( $definition ) );
|
||||
$response = \IPS\Content\Search\Elastic\Index::request( $this->url->setQueryString( 'include_type_name', 'true' ) )->setHeaders( array( 'Content-Type' => 'application/json' ) )->put( json_encode( $definition ) );
|
||||
|
||||
if( $response->httpResponseCode != 200 )
|
||||
{
|
||||
@@ -122,7 +124,7 @@ class _Index extends \IPS\Content\Search\Index
|
||||
}
|
||||
catch( \Exception $e )
|
||||
{
|
||||
$response = $this->url->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->put( json_encode( $definition ) );
|
||||
$response = \IPS\Content\Search\Elastic\Index::request( $this->url )->setHeaders( array( 'Content-Type' => 'application/json' ) )->put( json_encode( $definition ) );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,16 +139,15 @@ class _Index extends \IPS\Content\Search\Index
|
||||
if ( $indexData = parent::indexData( $object ) )
|
||||
{
|
||||
$indexData['index_permissions'] = explode( ',', $indexData['index_permissions'] );
|
||||
$indexData['index_tags'] = $indexData['index_tags'] ? explode( ',', $indexData['index_tags'] ) : array();
|
||||
$indexData['index_is_last_comment'] = (bool) $indexData['index_is_last_comment'];
|
||||
|
||||
if ( $object instanceof \IPS\Content\Item )
|
||||
{
|
||||
$indexData += $this->metaCounts( $object );
|
||||
$indexData = array_merge( $indexData, $this->metaData( $object ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
$indexData += $this->metaCounts( $object->item() );
|
||||
$indexData = array_merge( $indexData, $this->metaData( $object->item() ) );
|
||||
}
|
||||
|
||||
return $indexData;
|
||||
@@ -181,7 +182,7 @@ class _Index extends \IPS\Content\Search\Index
|
||||
|
||||
try
|
||||
{
|
||||
$r = $this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/_doc/' . $this->getIndexId( $object ) )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->get()->decodeJson();
|
||||
$r = \IPS\Content\Search\Elastic\Index::request( $this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/_doc/' . $this->getIndexId( $object ) ) )->setHeaders( array( 'Content-Type' => 'application/json' ) )->get()->decodeJson();
|
||||
if ( $r['found'] )
|
||||
{
|
||||
$existingData = $r['_source'];
|
||||
@@ -204,9 +205,7 @@ class _Index extends \IPS\Content\Search\Index
|
||||
}
|
||||
|
||||
/* Insert into index */
|
||||
$r = $this->url
|
||||
->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/_doc/' . $this->getIndexId( $object ) )
|
||||
->request( \IPS\LONG_REQUEST_TIMEOUT )
|
||||
$r = \IPS\Content\Search\Elastic\Index::request( $this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/_doc/' . $this->getIndexId( $object ) ), \IPS\LONG_REQUEST_TIMEOUT )
|
||||
->setHeaders( array( 'Content-Type' => 'application/json' ) )
|
||||
->put( json_encode( $indexData ) );
|
||||
|
||||
@@ -215,6 +214,11 @@ class _Index extends \IPS\Content\Search\Index
|
||||
throw new \IPS\Content\Search\Elastic\Exception( $error['type'] . ': ' . $error['reason'] );
|
||||
}
|
||||
|
||||
if ( $resetLastComment )
|
||||
{
|
||||
$this->resetLastComment( array( $indexData['index_class'] ), $indexData['index_item_id'] );
|
||||
}
|
||||
|
||||
/* Views / Comments / Reviews */
|
||||
if ( $object instanceof \IPS\Content\Item )
|
||||
{
|
||||
@@ -224,7 +228,8 @@ class _Index extends \IPS\Content\Search\Index
|
||||
{
|
||||
$item = $object->item();
|
||||
}
|
||||
$this->rebuildMetaCounts( $item, $indexData['index_is_last_comment'] ? $this->getIndexId( $object ) : NULL );
|
||||
|
||||
$this->rebuildMetaData( $item );
|
||||
}
|
||||
catch ( \IPS\Http\Request\Exception $e )
|
||||
{
|
||||
@@ -234,11 +239,6 @@ class _Index extends \IPS\Content\Search\Index
|
||||
{
|
||||
\IPS\Log::log( $e, 'elasticsearch_response_error' );
|
||||
}
|
||||
|
||||
if ( $resetLastComment )
|
||||
{
|
||||
$this->resetLastComment( array( $indexData['index_class'] ), $indexData['index_item_id'] );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -253,7 +253,7 @@ class _Index extends \IPS\Content\Search\Index
|
||||
try
|
||||
{
|
||||
/* This request *intentionally* goes to _tasks and not (ourpath)/_tasks */
|
||||
$response = $this->url->setPath( '/_tasks' )->request( \IPS\LONG_REQUEST_TIMEOUT )->setHeaders( array( 'Content-Type' => 'application/json' ) )->get();
|
||||
$response = \IPS\Content\Search\Elastic\Index::request( $this->url->setPath( '/_tasks' ), \IPS\LONG_REQUEST_TIMEOUT )->setHeaders( array( 'Content-Type' => 'application/json' ) )->get();
|
||||
|
||||
if( $error = $this->getResponseError( $response ) )
|
||||
{
|
||||
@@ -278,7 +278,7 @@ class _Index extends \IPS\Content\Search\Index
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->url->setPath( '/.tasks/task' . $taskId )->request( \IPS\LONG_REQUEST_TIMEOUT )->setHeaders( array( 'Content-Type' => 'application/json' ) )->delete();
|
||||
\IPS\Content\Search\Elastic\Index::request( $this->url->setPath( '/.tasks/task' . $taskId ), \IPS\LONG_REQUEST_TIMEOUT )->setHeaders( array( 'Content-Type' => 'application/json' ) )->delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -298,7 +298,7 @@ class _Index extends \IPS\Content\Search\Index
|
||||
* @param \IPS\Content\Searchable $item The content item
|
||||
* @return void
|
||||
*/
|
||||
protected function metaCounts( $item )
|
||||
protected function metaData( $item )
|
||||
{
|
||||
$databaseColumnId = $item::$databaseColumnId;
|
||||
|
||||
@@ -318,7 +318,9 @@ class _Index extends \IPS\Content\Search\Index
|
||||
return array(
|
||||
'index_comments' => $item->mapped('num_comments'),
|
||||
'index_reviews' => $item->mapped('num_reviews'),
|
||||
'index_participants' => $participants
|
||||
'index_participants' => $participants,
|
||||
'index_tags' => $item->tags(),
|
||||
'index_prefix' => $item->prefix(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -326,10 +328,9 @@ class _Index extends \IPS\Content\Search\Index
|
||||
* Rebuild the comment / review counts for an item
|
||||
*
|
||||
* @param \IPS\Content\Searchable $item The content item
|
||||
* @param string $lastCommentIndexId The index ID of the last comment, if you want to reset others
|
||||
* @return void
|
||||
*/
|
||||
protected function rebuildMetaCounts( $item, $lastCommentIndexId = NULL )
|
||||
protected function rebuildMetaData( $item )
|
||||
{
|
||||
$databaseColumnId = $item::$databaseColumnId;
|
||||
$class = \get_class( $item );
|
||||
@@ -347,12 +348,12 @@ class _Index extends \IPS\Content\Search\Index
|
||||
{
|
||||
$updates = array();
|
||||
$params = array();
|
||||
foreach ( $this->metaCounts( $item ) as $k => $v )
|
||||
foreach ( $this->metaData( $item ) as $k => $v )
|
||||
{
|
||||
if ( \is_array( $v ) )
|
||||
{
|
||||
$updates[] = "ctx._source.{$k} = params.param_{$k};";
|
||||
$params[ 'param_' . $k ] = array_map( 'intval', $v );
|
||||
$params[ 'param_' . $k ] = ( $k !== 'index_tags' ? array_map( 'intval', $v ) : $v );
|
||||
}
|
||||
elseif ( \is_null( $v ) )
|
||||
{
|
||||
@@ -365,12 +366,8 @@ class _Index extends \IPS\Content\Search\Index
|
||||
$params['param_' . $k ] = \intval( $v );
|
||||
}
|
||||
}
|
||||
if ( !\is_null( $lastCommentIndexId ) )
|
||||
{
|
||||
$updates[] = "ctx._source.index_is_last_comment = ( ctx._id == params.param_lastcommentid );";
|
||||
$params['param_lastcommentid'] = $lastCommentIndexId;
|
||||
}
|
||||
$this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/_doc/_update_by_query' )->setQueryString( array( 'conflicts' => 'proceed', 'wait_for_completion' => 'false', 'scroll_size' => \IPS\Settings::i()->search_index_maxresults ) )->request( \IPS\LONG_REQUEST_TIMEOUT )->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
|
||||
|
||||
$r = \IPS\Content\Search\Elastic\Index::request( $this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/_update_by_query' )->setQueryString( array( 'conflicts' => 'proceed', 'wait_for_completion' => 'false', 'scroll_size' => \IPS\Settings::i()->search_index_maxresults ) ), \IPS\LONG_REQUEST_TIMEOUT )->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
|
||||
'script' => array(
|
||||
'source' => implode( ' ', $updates ),
|
||||
'lang' => 'painless',
|
||||
@@ -429,7 +426,7 @@ class _Index extends \IPS\Content\Search\Index
|
||||
|
||||
if ( !( $object instanceof \IPS\Content\Item ) )
|
||||
{
|
||||
$this->rebuildMetaCounts( $object->item() );
|
||||
$this->rebuildMetaData( $object->item() );
|
||||
|
||||
$itemClass = \get_class( $object->item() );
|
||||
$itemIdColumn = $itemClass::$databaseColumnId;
|
||||
@@ -465,14 +462,14 @@ class _Index extends \IPS\Content\Search\Index
|
||||
try
|
||||
{
|
||||
$indexId = \strtolower( str_replace( '\\', '_', \substr( $class, 4 ) ) ) . '-' . $id;
|
||||
$this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/_doc/' . $indexId )->request()->delete();
|
||||
\IPS\Content\Search\Elastic\Index::request( $this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/_doc/' . $indexId ) )->delete();
|
||||
|
||||
if ( is_subclass_of( $class, 'IPS\Content\Item' ) )
|
||||
{
|
||||
if ( isset( $class::$commentClass ) )
|
||||
{
|
||||
$commentClass = $class::$commentClass;
|
||||
$response = $this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/_doc/_delete_by_query' )->setQueryString( array( 'conflicts' => 'proceed', 'wait_for_completion' => 'false' ) )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
|
||||
$response = \IPS\Content\Search\Elastic\Index::request( $this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/_doc/_delete_by_query' )->setQueryString( array( 'conflicts' => 'proceed', 'wait_for_completion' => 'false' ) ) )->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
|
||||
'query' => array(
|
||||
'bool' => array(
|
||||
'must' => array(
|
||||
@@ -495,7 +492,7 @@ class _Index extends \IPS\Content\Search\Index
|
||||
if ( isset( $class::$reviewClass ) )
|
||||
{
|
||||
$reviewClass = $class::$reviewClass;
|
||||
$this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/_doc/_delete_by_query' )->setQueryString( array( 'conflicts' => 'proceed', 'wait_for_completion' => 'false' ) )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
|
||||
\IPS\Content\Search\Elastic\Index::request( $this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/_doc/_delete_by_query' )->setQueryString( array( 'conflicts' => 'proceed', 'wait_for_completion' => 'false' ) ) )->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
|
||||
'query' => array(
|
||||
'bool' => array(
|
||||
'must' => array(
|
||||
@@ -565,15 +562,15 @@ class _Index extends \IPS\Content\Search\Index
|
||||
'index_author' => $authorId
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/_doc/_delete_by_query' )->setQueryString( array( 'conflicts' => 'proceed', 'wait_for_completion' => 'false' ) )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
|
||||
}
|
||||
|
||||
\IPS\Content\Search\Elastic\Index::request( $this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/_doc/_delete_by_query' )->setQueryString( array( 'conflicts' => 'proceed', 'wait_for_completion' => 'false' ) ) )->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
|
||||
'query' => $query
|
||||
) ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/_doc/_delete_by_query' )->setQueryString( array( 'conflicts' => 'proceed', 'wait_for_completion' => 'false' ) )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
|
||||
\IPS\Content\Search\Elastic\Index::request( $this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/_doc/_delete_by_query' )->setQueryString( array( 'conflicts' => 'proceed', 'wait_for_completion' => 'false' ) ) )->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
|
||||
'query' => array(
|
||||
'term' => array(
|
||||
'index_class' => $class
|
||||
@@ -677,7 +674,7 @@ class _Index extends \IPS\Content\Search\Index
|
||||
|
||||
if ( \count( $updates ) )
|
||||
{
|
||||
$this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/_doc/_update_by_query' )->setQueryString( array( 'conflicts' => 'proceed', 'wait_for_completion' => 'false', 'scroll_size' => \IPS\Settings::i()->search_index_maxresults ) )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
|
||||
\IPS\Content\Search\Elastic\Index::request( $this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/_doc/_update_by_query' )->setQueryString( array( 'conflicts' => 'proceed', 'wait_for_completion' => 'false', 'scroll_size' => \IPS\Settings::i()->search_index_maxresults ) ) )->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
|
||||
'script' => array(
|
||||
'source' => implode( ' ', $updates ),
|
||||
'lang' => 'painless',
|
||||
@@ -697,8 +694,8 @@ class _Index extends \IPS\Content\Search\Index
|
||||
'index_author' => 0
|
||||
)
|
||||
);
|
||||
|
||||
$this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/_doc/_update_by_query' )->setQueryString( array( 'conflicts' => 'proceed', 'wait_for_completion' => 'false', 'scroll_size' => \IPS\Settings::i()->search_index_maxresults ) )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
|
||||
|
||||
\IPS\Content\Search\Elastic\Index::request( $this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/_doc/_update_by_query' )->setQueryString( array( 'conflicts' => 'proceed', 'wait_for_completion' => 'false', 'scroll_size' => \IPS\Settings::i()->search_index_maxresults ) ) )->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
|
||||
'script' => array(
|
||||
'source' => "ctx._source.index_permissions.add( 'm' + ctx._source.index_author );",
|
||||
'lang' => 'painless'
|
||||
@@ -727,8 +724,8 @@ class _Index extends \IPS\Content\Search\Index
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/_doc/_update_by_query' )->setQueryString( array( 'conflicts' => 'proceed', 'wait_for_completion' => 'false', 'scroll_size' => \IPS\Settings::i()->search_index_maxresults ) )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
|
||||
|
||||
\IPS\Content\Search\Elastic\Index::request( $this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/_doc/_update_by_query' )->setQueryString( array( 'conflicts' => 'proceed', 'wait_for_completion' => 'false', 'scroll_size' => \IPS\Settings::i()->search_index_maxresults ) ) )->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
|
||||
'script' => array(
|
||||
'source' => "ctx._source.index_hidden = params.newHiddenStatus;",
|
||||
'lang' => 'painless',
|
||||
@@ -766,7 +763,7 @@ class _Index extends \IPS\Content\Search\Index
|
||||
{
|
||||
return array(
|
||||
'bool' => array(
|
||||
'must' => array( $conditions )
|
||||
'must' => $conditions
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -795,7 +792,7 @@ class _Index extends \IPS\Content\Search\Index
|
||||
|
||||
try
|
||||
{
|
||||
$this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/_doc/' . $this->getIndexId( $item ) . '/_update' )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
|
||||
\IPS\Content\Search\Elastic\Index::request( $this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/_doc/' . $this->getIndexId( $item ) . '/_update' ) )->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
|
||||
'doc' => $update
|
||||
) ) );
|
||||
|
||||
@@ -827,7 +824,7 @@ class _Index extends \IPS\Content\Search\Index
|
||||
{
|
||||
try
|
||||
{
|
||||
$this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/_doc/_delete_by_query' )->setQueryString( array( 'conflicts' => 'proceed', 'wait_for_completion' => 'false' ) )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
|
||||
\IPS\Content\Search\Elastic\Index::request( $this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/_doc/_delete_by_query' )->setQueryString( array( 'conflicts' => 'proceed', 'wait_for_completion' => 'false' ) ) )->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
|
||||
'query' => array(
|
||||
'range' => array(
|
||||
'index_date_updated' => array(
|
||||
@@ -861,7 +858,7 @@ class _Index extends \IPS\Content\Search\Index
|
||||
try
|
||||
{
|
||||
/* Remove the flag */
|
||||
$this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/_doc/_update_by_query' )->setQueryString( array( 'conflicts' => 'proceed', 'wait_for_completion' => 'false', 'scroll_size' => \IPS\Settings::i()->search_index_maxresults ) )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
|
||||
$r = \IPS\Content\Search\Elastic\Index::request( $this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/_update_by_query' )->setQueryString( array( 'conflicts' => 'proceed', 'wait_for_completion' => 'false' ) ) )->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
|
||||
'script' => array(
|
||||
'source' => "ctx._source.index_is_last_comment = false;",
|
||||
'lang' => 'painless'
|
||||
@@ -921,15 +918,15 @@ class _Index extends \IPS\Content\Search\Index
|
||||
if ( $lastComment = $item->comments( 1, 0, 'date', 'desc', NULL, FALSE, NULL, $where ) AND $lastComment instanceof \IPS\Content\Searchable )
|
||||
{
|
||||
/* Set that it is the latest comment */
|
||||
$r = $this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/_doc/' . $this->getIndexId( $lastComment ) . '/_update' )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
|
||||
$r = \IPS\Content\Search\Elastic\Index::request( $this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/_update/' . $this->getIndexId( $lastComment ) ) )->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
|
||||
'doc' => array(
|
||||
'index_is_last_comment' => true
|
||||
)
|
||||
) ) );
|
||||
|
||||
|
||||
/* And set the updated time on the main item (done as _update_by_query because it might not exist if the first comment is required) */
|
||||
$indexDataForLastComment = $this->indexData( $lastComment );
|
||||
$this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/_doc/_update_by_query' )->setQueryString( array( 'conflicts' => 'proceed', 'wait_for_completion' => 'false', 'scroll_size' => \IPS\Settings::i()->search_index_maxresults ) )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
|
||||
$r = \IPS\Content\Search\Elastic\Index::request( $this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/_update_by_query' )->setQueryString( array( 'conflicts' => 'proceed', 'wait_for_completion' => 'false' ) ) )->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
|
||||
'script' => array(
|
||||
'source' => "ctx._source.index_date_updated = params.dateUpdated; ctx._source.index_date_commented = params.dateCommented;",
|
||||
'lang' => 'painless',
|
||||
@@ -1001,7 +998,7 @@ class _Index extends \IPS\Content\Search\Index
|
||||
);
|
||||
|
||||
/* Get the count */
|
||||
$count = $this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/_doc/_search' )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->get( json_encode( array(
|
||||
$count = \IPS\Content\Search\Elastic\Index::request( $this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/_doc/_search' ) )->setHeaders( array( 'Content-Type' => 'application/json' ) )->get( json_encode( array(
|
||||
'size' => 0,
|
||||
'query' => $query
|
||||
) ) )->decodeJson();
|
||||
@@ -1012,7 +1009,7 @@ class _Index extends \IPS\Content\Search\Index
|
||||
}
|
||||
|
||||
/* Now get the unique item ids */
|
||||
$results = $this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/_doc/_search' )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->get( json_encode( array(
|
||||
$results = \IPS\Content\Search\Elastic\Index::request( $this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/_doc/_search' ) )->setHeaders( array( 'Content-Type' => 'application/json' ) )->get( json_encode( array(
|
||||
'aggs' => array(
|
||||
'itemIds' => array(
|
||||
'terms' => array(
|
||||
@@ -1054,7 +1051,7 @@ class _Index extends \IPS\Content\Search\Index
|
||||
{
|
||||
try
|
||||
{
|
||||
$results = $this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/_doc/_search' )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->get( json_encode( array(
|
||||
$results = \IPS\Content\Search\Elastic\Index::request( $this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/_doc/_search' ) )->setHeaders( array( 'Content-Type' => 'application/json' ) )->get( json_encode( array(
|
||||
'query' => array(
|
||||
'bool' => array(
|
||||
'filter' => array(
|
||||
@@ -1103,7 +1100,7 @@ class _Index extends \IPS\Content\Search\Index
|
||||
{
|
||||
try
|
||||
{
|
||||
$results = $this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/_doc/_search' )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->get( json_encode( array(
|
||||
$results = \IPS\Content\Search\Elastic\Index::request( $this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/_doc/_search' ) )->setHeaders( array( 'Content-Type' => 'application/json' ) )->get( json_encode( array(
|
||||
'size' => 1,
|
||||
'sort' => array( array( 'index_date_updated' => 'asc' ) )
|
||||
) ) )->decodeJson();
|
||||
@@ -1139,7 +1136,7 @@ class _Index extends \IPS\Content\Search\Index
|
||||
|
||||
try
|
||||
{
|
||||
$results = $this->url->setPath( '/_analyze' )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->get( json_encode( array(
|
||||
$results = \IPS\Content\Search\Elastic\Index::request( $this->url->setPath( '/_analyze' ) )->setHeaders( array( 'Content-Type' => 'application/json' ) )->get( json_encode( array(
|
||||
'analyzer' => $analyzer,
|
||||
'text' => implode( ' ', $terms )
|
||||
) ) )->decodeJson();
|
||||
@@ -1172,6 +1169,23 @@ class _Index extends \IPS\Content\Search\Index
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper to account for log in where needed
|
||||
*
|
||||
* @param $url
|
||||
* @param null $timeout
|
||||
* @return \IPS\Http\Request\Curl|Socket
|
||||
*/
|
||||
public static function request( $url, $timeout=NULL )
|
||||
{
|
||||
if ( \IPS\ELASTICSEARCH_USER or \IPS\ELASTICSEARCH_PASSWORD )
|
||||
{
|
||||
return $url->request( $timeout )->login( \IPS\ELASTICSEARCH_USER, \IPS\ELASTICSEARCH_PASSWORD );
|
||||
}
|
||||
|
||||
return $url->request( $timeout );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check response to see if an error was produced
|
||||
*
|
||||
|
||||
@@ -73,7 +73,7 @@ class _MassIndexer extends \IPS\Content\Search\Elastic\Index
|
||||
}
|
||||
$json = implode( "\n", $json );
|
||||
|
||||
$this->url->setPath( '/_bulk' )->request()->setHeaders( array( 'Content-Type' => 'application/x-ndjson' ) )->post( $json . "\n" );
|
||||
\IPS\Content\Search\Elastic\Index::request( $this->url->setPath( '/_bulk' ) )->setHeaders( array( 'Content-Type' => 'application/x-ndjson' ) )->post( $json . "\n" );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,7 +69,12 @@ class _Query extends \IPS\Content\Search\Query
|
||||
parent::__construct( $member );
|
||||
$this->url = $url;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @var bool Stores whether or not any content type needs to be limited by the latest comment
|
||||
*/
|
||||
protected $lastCommentMustBeTrue = false;
|
||||
|
||||
/**
|
||||
* Filter by multiple content types
|
||||
*
|
||||
@@ -198,11 +203,14 @@ class _Query extends \IPS\Content\Search\Query
|
||||
/* Only last comment? */
|
||||
if ( $filter->onlyLastComment )
|
||||
{
|
||||
/* Set this for when the search JSON is built */
|
||||
$this->lastCommentMustBeTrue = true;
|
||||
|
||||
$conditions[] = array(
|
||||
'term' => array( 'index_is_last_comment' => true )
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/* Put it together */
|
||||
if( \count( $conditions ) )
|
||||
{
|
||||
@@ -215,7 +223,7 @@ class _Query extends \IPS\Content\Search\Query
|
||||
{
|
||||
$this->filters[] = array(
|
||||
'bool' => array(
|
||||
( $type ? 'should' : 'must_not' ) => array( $contentFilterConditions )
|
||||
( $type ? 'should' : 'must_not' ) => $contentFilterConditions
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -363,7 +371,79 @@ class _Query extends \IPS\Content\Search\Query
|
||||
/* Return for daisy chaining */
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Stores the "more like this" Content object for search()
|
||||
* @param null
|
||||
*/
|
||||
protected $moreLikeThis = NULL;
|
||||
|
||||
/**
|
||||
* Filter by more like this
|
||||
* @param \IPS\Content\Searchable $object
|
||||
*/
|
||||
public function filterByMoreLikeThis( \IPS\Content\Searchable $object )
|
||||
{
|
||||
$index = new \IPS\Content\Search\Elastic\Index( $this->url );
|
||||
$this->moreLikeThis = $index->getIndexId( ( $object instanceof \IPS\Content\Item and $object::$firstCommentRequired ) ? $object->firstComment() : $object );
|
||||
|
||||
/* Some container types cannot be cached */
|
||||
$classes = [];
|
||||
$conditions = [];
|
||||
|
||||
foreach ( \IPS\Application::allExtensions( 'core', 'ContentRouter', FALSE ) as $object )
|
||||
{
|
||||
$classes = array_merge( $object->classes, $classes );
|
||||
}
|
||||
|
||||
/* Some nodes have complex permissions, so avoid any permission issues by not selecting these */
|
||||
foreach( $classes as $itemClass )
|
||||
{
|
||||
if ( isset( $itemClass::$containerNodeClass ) )
|
||||
{
|
||||
$containerClass = $itemClass::$containerNodeClass;
|
||||
$blockIds = [];
|
||||
if ( $customNodes = $containerClass::customPermissionNodes() )
|
||||
{
|
||||
foreach ( $customNodes as $key => $ids )
|
||||
{
|
||||
if ( $key !== 'count' )
|
||||
{
|
||||
$blockIds = array_merge( $blockIds, $ids );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( \count( $blockIds ) )
|
||||
{
|
||||
$conditions[] = array(
|
||||
'bool' => array(
|
||||
'filter' => array(
|
||||
array(
|
||||
'terms' => array( 'index_container_class' => [ $containerClass ] )
|
||||
),
|
||||
array(
|
||||
'terms' => array( 'index_container_id' => $blockIds )
|
||||
),
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ( \count( $conditions ) )
|
||||
{
|
||||
foreach( $conditions as $condition )
|
||||
{
|
||||
$this->mustNot[] = $condition;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Only show non-hidden and approved items */
|
||||
$this->setHiddenFilter( static::HIDDEN_VISIBLE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by container class
|
||||
*
|
||||
@@ -558,9 +638,7 @@ class _Query extends \IPS\Content\Search\Query
|
||||
if ( $includeMembers and $followed = iterator_to_array( \IPS\Db::i()->select( 'follow_rel_id', 'core_follow', array( 'follow_app=? AND follow_area=? AND follow_member_id=?', 'core', 'member', $this->member->member_id ), 'follow_rel_id asc' ) ) )
|
||||
{
|
||||
$conditions[] = array(
|
||||
array(
|
||||
'terms' => array( 'index_author' => $followed )
|
||||
)
|
||||
'terms' => array( 'index_author' => $followed )
|
||||
);
|
||||
}
|
||||
|
||||
@@ -799,7 +877,35 @@ class _Query extends \IPS\Content\Search\Query
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Filter by solved
|
||||
*
|
||||
* @return \IPS\Content\Search\Query (for daisy chaining)
|
||||
*/
|
||||
public function filterBySolved()
|
||||
{
|
||||
$this->filters[] = array(
|
||||
'term' => array( 'index_item_solved' => 1 )
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by Unsolved
|
||||
*
|
||||
* @return \IPS\Content\Search\Query (for daisy chaining)
|
||||
*/
|
||||
public function filterByUnsolved()
|
||||
{
|
||||
$this->filters[] = array(
|
||||
'term' => array( 'index_item_solved' => 0 )
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by start date
|
||||
*
|
||||
@@ -922,7 +1028,35 @@ class _Query extends \IPS\Content\Search\Query
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Debug by itemId, used for debugging purposes only
|
||||
* @note No permission checks run, do not use in production
|
||||
* @param $itemId
|
||||
* @return void
|
||||
*/
|
||||
public function debugByItemId( $itemId )
|
||||
{
|
||||
$array = array(
|
||||
'query' => [
|
||||
'term' => [
|
||||
'index_item_id' => [
|
||||
'value' => \intval( $itemId )
|
||||
]
|
||||
]
|
||||
],
|
||||
'sort' => $this->sort ?: array(),
|
||||
'from' => 0,
|
||||
'size' => 50,
|
||||
);
|
||||
|
||||
$json = json_encode( $array, JSON_PARTIAL_OUTPUT_ON_ERROR );
|
||||
|
||||
return \IPS\Content\Search\Elastic\Index::request( $this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/_search' ) )->setHeaders( array( 'Content-Type' => 'application/json' ) )->get( $json )->decodeJson();
|
||||
}
|
||||
|
||||
/**
|
||||
* Search
|
||||
*
|
||||
@@ -1017,6 +1151,12 @@ class _Query extends \IPS\Content\Search\Query
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
/* If we're not searching with a term, then just show the title record, not comments */
|
||||
if ( $term === NULL and ! $this->lastCommentMustBeTrue )
|
||||
{
|
||||
$must[] = ['exists' => ['field' => 'index_title']];
|
||||
}
|
||||
}
|
||||
|
||||
/* Put that with the rest of the conditions */
|
||||
@@ -1050,7 +1190,25 @@ class _Query extends \IPS\Content\Search\Query
|
||||
$filters[] = array( 'term' => array( 'index_hidden' => $this->hiddenStatuses ) );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$searchUrl = $this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/_search' );
|
||||
|
||||
if ( $this->moreLikeThis )
|
||||
{
|
||||
$must[] = [
|
||||
'more_like_this' => [
|
||||
'fields' => [ 'index_title' ],
|
||||
'like' => [ '_index' => \IPS\Settings::i()->search_elastic_index, '_id' => $this->moreLikeThis ],
|
||||
'min_term_freq' => 1,
|
||||
'max_query_terms' => 12,
|
||||
'min_word_length' => 3,
|
||||
]
|
||||
];
|
||||
|
||||
/* Let's enforce a relatively short timeout to prevent slow ES server from dragging down topics */
|
||||
$searchUrl = $searchUrl->setQueryString( 'timeout', '3s' );
|
||||
}
|
||||
|
||||
/* Peform the search */
|
||||
try
|
||||
{
|
||||
@@ -1062,9 +1220,9 @@ class _Query extends \IPS\Content\Search\Query
|
||||
'filter' => $filters
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
/* Add the time decay */
|
||||
if ( \IPS\Settings::i()->search_decay_factor and !$this->sort )
|
||||
if ( \IPS\Settings::i()->search_decay_factor and !$this->sort and ! $this->sort )
|
||||
{
|
||||
$query = array(
|
||||
'function_score' => array(
|
||||
@@ -1081,7 +1239,7 @@ class _Query extends \IPS\Content\Search\Query
|
||||
}
|
||||
|
||||
/* Add the self boost */
|
||||
if ( \IPS\Settings::i()->search_elastic_self_boost and $this->member->member_id and !$this->sort )
|
||||
if ( \IPS\Settings::i()->search_elastic_self_boost and $this->member->member_id and !$this->sort and ! $this->moreLikeThis )
|
||||
{
|
||||
$query = array(
|
||||
'function_score' => array(
|
||||
@@ -1117,10 +1275,10 @@ class _Query extends \IPS\Content\Search\Query
|
||||
}
|
||||
|
||||
/* Make the call! */
|
||||
$return = $this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/_search' )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->get( $json )->decodeJson();
|
||||
$return = \IPS\Content\Search\Elastic\Index::request( $searchUrl )->setHeaders( array( 'Content-Type' => 'application/json' ) )->get( $json )->decodeJson();
|
||||
if ( isset( $return['error'] ) )
|
||||
{
|
||||
\IPS\Log::log( print_r( $return['error'], TRUE ), 'elasticsearch' );
|
||||
\IPS\Log::log( print_r( array_merge( $array, ['error' => $return['error'] ] ), TRUE ), 'elasticsearch' );
|
||||
return new \IPS\Content\Search\Results( array(), 0 );
|
||||
}
|
||||
|
||||
|
||||
@@ -309,6 +309,7 @@ abstract class _Index extends \IPS\Patterns\Singleton
|
||||
|
||||
/* Get the item index ID */
|
||||
$itemIndexId = NULL;
|
||||
$itemClass = NULL;
|
||||
if ( $object instanceof \IPS\Content\Comment )
|
||||
{
|
||||
$itemClass = $object::$itemClass;
|
||||
@@ -390,6 +391,15 @@ abstract class _Index extends \IPS\Patterns\Singleton
|
||||
$containerClass = ( $object->item()->searchIndexContainerClass() ) ? \get_class( $object->item()->searchIndexContainerClass() ) : NULL;
|
||||
}
|
||||
|
||||
/* Do we have an extension to modify this? */
|
||||
foreach( \IPS\Application::enabledApplications() as $app )
|
||||
{
|
||||
foreach ( $app->extensions( 'core', 'SearchIndex' ) as $extension )
|
||||
{
|
||||
$content = $extension->content( $object, $content );
|
||||
}
|
||||
}
|
||||
|
||||
/* Return */
|
||||
return array(
|
||||
'index_class' => $class,
|
||||
@@ -412,7 +422,8 @@ abstract class _Index extends \IPS\Patterns\Singleton
|
||||
'index_is_last_comment' => $isLastComment,
|
||||
'index_club_id' => $clubId,
|
||||
'index_class_type_id_hash' => md5( $class . ':' . $object->$idColumn ),
|
||||
'index_is_anon' => $object->isAnonymous()
|
||||
'index_is_anon' => (int) $object->isAnonymous(),
|
||||
'index_item_solved' => ( $itemClass and \IPS\IPS::classUsesTrait( $itemClass, 'IPS\Content\Solvable' ) ) ? ( $object->item()->mapped('solved_comment_id') == $object->$idColumn ) ? 1 : 0 : NULL
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -609,7 +609,31 @@ class _Query extends \IPS\Content\Search\Query
|
||||
|
||||
$this->filterByUnread = TRUE;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Filter only solved content
|
||||
*
|
||||
* @return \IPS\Content\Search\Query (for daisy chaining)
|
||||
*/
|
||||
public function filterBySolved()
|
||||
{
|
||||
$this->where[] = array( 'index_item_solved=?', 1 );
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter only unsolved content
|
||||
*
|
||||
* @return \IPS\Content\Search\Query (for daisy chaining)
|
||||
*/
|
||||
public function filterByUnsolved()
|
||||
{
|
||||
$this->where[] = array( 'index_item_solved=?', 0 );
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the 'unread' where SQL
|
||||
*
|
||||
@@ -629,54 +653,61 @@ class _Query extends \IPS\Content\Search\Query
|
||||
$unreadWheres = array();
|
||||
$containerIds = array();
|
||||
$containerClass = isset( $class::$containerNodeClass ) ? $class::$containerNodeClass : NULL;
|
||||
|
||||
foreach( $resetTimes as $containerId => $timestamp )
|
||||
{
|
||||
/* Pages has different classes per database, but recorded as 'cms' and the container ID in the marking tables */
|
||||
if ( $containerClass and method_exists( $containerClass, 'isValidContainerId' ) )
|
||||
{
|
||||
if ( ! $containerClass::isValidContainerId( $containerId ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$timestamp = $timestamp ?: $this->member->marked_site_read;
|
||||
|
||||
$containerIds[] = $containerId;
|
||||
$unreadWheres[] = '( index_container_id=' . $containerId . ' AND index_date_updated > ' . (int) $timestamp . ')';
|
||||
|
||||
$items = $this->member->markersItems( $application, \IPS\Content\Item::makeMarkerKey( $containerId ) );
|
||||
|
||||
if ( \count( $items ) )
|
||||
if ( \is_array( $resetTimes ) )
|
||||
{
|
||||
foreach( $resetTimes as $containerId => $timestamp )
|
||||
{
|
||||
foreach( $items as $mid => $mtime )
|
||||
/* Pages has different classes per database, but recorded as 'cms' and the container ID in the marking tables */
|
||||
if ( $containerClass and method_exists( $containerClass, 'isValidContainerId' ) )
|
||||
{
|
||||
if ( $mtime > $timestamp )
|
||||
if ( ! $containerClass::isValidContainerId( $containerId ) )
|
||||
{
|
||||
/* If an item has been moved from one container to another, the user may have a marker
|
||||
in it's old location, with the previously 'read' time. In this circumstance, we need
|
||||
to only use more recent read time, otherwise the topic may be incorrectly included
|
||||
in the results */
|
||||
if ( \in_array( $mid, $markers ) )
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$timestamp = $timestamp ?: $this->member->marked_site_read;
|
||||
|
||||
$containerIds[] = $containerId;
|
||||
$unreadWheres[] = '( index_container_id=' . $containerId . ' AND index_date_updated > ' . (int) $timestamp . ')';
|
||||
|
||||
$items = $this->member->markersItems( $application, \IPS\Content\Item::makeMarkerKey( $containerId ) );
|
||||
|
||||
if ( \count( $items ) )
|
||||
{
|
||||
foreach( $items as $mid => $mtime )
|
||||
{
|
||||
if ( $mtime > $timestamp )
|
||||
{
|
||||
$_key = array_search( $mid, $markers );
|
||||
$_mtime = \intval( mb_substr( $_key, 0, mb_strpos( $_key, '.' ) ) );
|
||||
if ( $_mtime < $mtime )
|
||||
/* If an item has been moved from one container to another, the user may have a marker
|
||||
in it's old location, with the previously 'read' time. In this circumstance, we need
|
||||
to only use more recent read time, otherwise the topic may be incorrectly included
|
||||
in the results */
|
||||
if ( \in_array( $mid, $markers ) )
|
||||
{
|
||||
unset( $markers[ $_key ] );
|
||||
}
|
||||
/* If the existing timestamp is higher, retain that since we reset the $markers array below */
|
||||
else
|
||||
{
|
||||
$mtime = $_mtime;
|
||||
$_key = array_search( $mid, $markers );
|
||||
$_mtime = \intval( mb_substr( $_key, 0, mb_strpos( $_key, '.' ) ) );
|
||||
if ( $_mtime < $mtime )
|
||||
{
|
||||
unset( $markers[ $_key ] );
|
||||
}
|
||||
/* If the existing timestamp is higher, retain that since we reset the $markers array below */
|
||||
else
|
||||
{
|
||||
$mtime = $_mtime;
|
||||
}
|
||||
}
|
||||
|
||||
$markers[ $mtime . '.' . $mid ] = $mid;
|
||||
}
|
||||
|
||||
$markers[ $mtime . '.' . $mid ] = $mid;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$unreadWheres[] = "( index_date_updated > " . \intval( $resetTimes ) . ")";
|
||||
}
|
||||
|
||||
if( \count( $containerIds ) )
|
||||
@@ -972,37 +1003,13 @@ class _Query extends \IPS\Content\Search\Query
|
||||
|
||||
$results = iterator_to_array( \IPS\Db::i()->select( 'index_item_id, index_class', array( 'core_search_index_item_map', 'sub' ), array( array( 'index_author_id=' . \intval( $this->member->member_id ) . ' AND ' . \IPS\Db::i()->in( 'index_class', $classes ) ) ), 'index_item_id desc', array( 0, 501 ) ) );
|
||||
|
||||
if ( \count( $results ) == 501 )
|
||||
if ( \count( $results ) )
|
||||
{
|
||||
/* Assume we have loads of matches, so do a full query */
|
||||
$subClause = array( array( 'sub.index_item_id=main.index_item_id AND index_author_id=' . \intval( $this->member->member_id ) . ' AND ' . $this->makeEmbeddedIfClauseForSearchMapTable('sub.index_class') ) );
|
||||
$query = \IPS\Db::i()->select( 'index_item_id', array( 'core_search_index_item_map', 'sub' ), $subClause );
|
||||
$where[] = array( 'EXISTS(?)', $query );
|
||||
}
|
||||
else if ( \count( $results ) )
|
||||
{
|
||||
$classIds = array();
|
||||
foreach( $results as $result )
|
||||
{
|
||||
$classIds[ $result['index_class'] ][] = $result['index_item_id'];
|
||||
}
|
||||
|
||||
$subWhere = array();
|
||||
foreach( $classIds as $class => $ids )
|
||||
{
|
||||
$stack = array( $class );
|
||||
|
||||
if ( isset( $class::$commentClass ) )
|
||||
{
|
||||
$stack[] = $class::$commentClass;
|
||||
}
|
||||
|
||||
$subWhere[] = '(' . \IPS\Db::i()->in( 'index_class', $stack ) . ' and ' . \IPS\Db::i()->in( 'index_item_id', $ids ) . ')';
|
||||
}
|
||||
|
||||
/* IN is not a SIN. It's been a long day */
|
||||
$where[] = '(' . implode( ' OR ', $subWhere ) . ')';
|
||||
}
|
||||
else
|
||||
{
|
||||
/* There are no results */
|
||||
@@ -1260,8 +1267,9 @@ class _Query extends \IPS\Content\Search\Query
|
||||
{
|
||||
$order .= ", index_date_updated";
|
||||
}
|
||||
|
||||
if( $this->filterByUnread === TRUE )
|
||||
|
||||
/* If we're filtering by unread, and not using MariaDB, then force an index because MySQL tends to use the incorrect one in this scenario. */
|
||||
if( !mb_stristr( \IPS\Db::i()->server_info, '-MariaDB' ) AND $this->filterByUnread === TRUE )
|
||||
{
|
||||
$this->forceIndex = 'index_date_updated';
|
||||
}
|
||||
|
||||
@@ -174,6 +174,20 @@ abstract class _Query
|
||||
* @return \IPS\Content\Search\Query (for daisy chaining)
|
||||
*/
|
||||
abstract public function filterByUnread();
|
||||
|
||||
/**
|
||||
* Filter by content the user has not read
|
||||
*
|
||||
* @return \IPS\Content\Search\Query (for daisy chaining)
|
||||
*/
|
||||
abstract public function filterBySolved();
|
||||
|
||||
/**
|
||||
* Filter by content the user has not read
|
||||
*
|
||||
* @return \IPS\Content\Search\Query (for daisy chaining)
|
||||
*/
|
||||
abstract public function filterByUnsolved();
|
||||
|
||||
/**
|
||||
* Filter by start date
|
||||
|
||||
@@ -138,7 +138,7 @@ class _Content extends \IPS\Content\Search\Result
|
||||
catch( \Exception $ex ) { }
|
||||
}
|
||||
|
||||
$document->addItem( $object instanceof \IPS\Content\Comment ? $object->item()->searchIndexTitle() : $object->searchIndexTitle(), $object->url(), $this->indexData['index_content'], \IPS\DateTime::ts( $this->indexData['index_date_created'] ), NULL, $enclosure );
|
||||
$document->addItem( $object instanceof \IPS\Content\Comment ? $object->item()->searchIndexTitle() : $object->searchIndexTitle(), $object->url(), \IPS\Content\Search\Result::preDisplay( $this->indexData['index_content'] ), \IPS\DateTime::ts( $this->indexData['index_date_created'] ), NULL, $enclosure );
|
||||
}
|
||||
/* If the search result was orphaned, let us continue */
|
||||
catch( \OutOfRangeException $e ){}
|
||||
|
||||
@@ -38,12 +38,10 @@ abstract class _Result
|
||||
|
||||
if ( \IPS\Settings::i()->emoji_style == 'twemoji' )
|
||||
{
|
||||
return preg_replace( '/(?:' . \IPS\Text\Parser::EMOJI_REGEX . '|\x{200d})+/u', '', $content );
|
||||
}
|
||||
else
|
||||
{
|
||||
return $content;
|
||||
$content = preg_replace( '/(?:' . \IPS\Text\Parser::EMOJI_REGEX . '|\x{200d})+/u', '', $content );
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -56,6 +56,11 @@ class _Results extends \ArrayIterator
|
||||
* @brief Reaction Data
|
||||
*/
|
||||
protected $reactions = array();
|
||||
|
||||
/**
|
||||
* @brief Attached Images
|
||||
*/
|
||||
protected $attachedImages = array();
|
||||
|
||||
/**
|
||||
* @brief Review Ratings
|
||||
@@ -100,7 +105,8 @@ class _Results extends \ArrayIterator
|
||||
$reviewRatings = array();
|
||||
$itemIndexIds = array();
|
||||
$indexIds = array();
|
||||
|
||||
$attachedImages = array();
|
||||
|
||||
/* Loop */
|
||||
foreach ( $this->getArrayCopy() as $result )
|
||||
{
|
||||
@@ -162,8 +168,10 @@ class _Results extends \ArrayIterator
|
||||
{
|
||||
$containersToLoad[ 'IPS\Member\Club' ][ $result['index_club_id'] ] = $result['index_club_id'];
|
||||
}
|
||||
|
||||
$attachedImages[ $result['index_class'] ][ $result['index_item_id'] ][] = $result['index_object_id'];
|
||||
}
|
||||
|
||||
|
||||
/* Load item data */
|
||||
foreach ( $itemsToLoad as $itemClass => $itemIds )
|
||||
{
|
||||
@@ -245,7 +253,47 @@ class _Results extends \ArrayIterator
|
||||
$this->tags = iterator_to_array( \IPS\Db::i()->select( 'index_id, GROUP_CONCAT(index_tag) as index_tags', 'core_search_index_tags', array( array( \IPS\Db::i()->in( 'index_id', $itemIndexIds ) ), array( 'index_is_prefix = 0' ) ), NULL, NULL, 'index_id' )->setKeyField('index_id') );
|
||||
$this->prefixes = iterator_to_array( \IPS\Db::i()->select( 'index_id, index_tag as index_prefix', 'core_search_index_tags', array( array( \IPS\Db::i()->in( 'index_id', $itemIndexIds ) ), array( 'index_is_prefix = 1' ) ) )->setKeyField('index_id') );
|
||||
}
|
||||
|
||||
|
||||
/* Load attached images */
|
||||
$clause = [];
|
||||
$binds = [];
|
||||
$locationKeyMap = [];
|
||||
foreach ( $attachedImages as $class => $ids )
|
||||
{
|
||||
$itemClass = \in_array( 'IPS\Content\Comment', class_parents( $class ) ) ? $class::$itemClass : $class;
|
||||
foreach( $ids as $indexId => $commentIds )
|
||||
{
|
||||
foreach( $commentIds as $commentId )
|
||||
{
|
||||
$locationKey = $itemClass::$application . '_' . mb_ucfirst( $itemClass::$module );
|
||||
$locationKeyMap[$locationKey] = $class;
|
||||
$clause[] = "( location_key=? AND id1=? AND id2=?)";
|
||||
$binds[] = $locationKey;
|
||||
$binds[] = $indexId;
|
||||
$binds[] = $commentId;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( \count( $attachedImages ) and \count( $clause ) )
|
||||
{
|
||||
/* As we need to join back onto the map table, 2 queries are much more efficient */
|
||||
$ids = iterator_to_array( \IPS\Db::i()->select( 'attachment_id', 'core_attachments_map', array( array_merge( array( implode( ' OR ', $clause ) ), $binds ) ) ) );
|
||||
foreach ( \IPS\Db::i()->select( '*', 'core_attachments', array( array( \IPS\Db::i()->in( 'attach_id', $ids ) ), array('attach_is_image=1') ), 'attach_id ASC' )
|
||||
->join( 'core_attachments_map', 'core_attachments_map.attachment_id=core_attachments.attach_id' ) as $row )
|
||||
{
|
||||
if ( isset( $locationKeyMap[ $row['location_key'] ] ) and isset( $row['id2'] ) )
|
||||
{
|
||||
$this->attachedImages[ $locationKeyMap[ $row['location_key'] ] ][ $row['id2'] ][] = [
|
||||
'extension' => 'core_Attachment',
|
||||
'location' => $row['attach_location'],
|
||||
'thumb_location' => $row['attach_thumb_location'],
|
||||
'labels' => implode( ', ', \IPS\Text\Parser::getAttachmentLabels( $row ) )
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Set that we're initiated */
|
||||
$this->initated = TRUE;
|
||||
}
|
||||
@@ -303,6 +351,11 @@ class _Results extends \ArrayIterator
|
||||
}
|
||||
}
|
||||
|
||||
if ( \is_array( $this->attachedImages ) and isset( $this->attachedImages[ $class ][ $data['index_object_id'] ] ) )
|
||||
{
|
||||
$itemData['attachedImages'] = $this->attachedImages[ $class ][ $data['index_object_id'] ];
|
||||
}
|
||||
|
||||
if ( isset( $this->tags[ $data['index_item_index_id'] ] ) )
|
||||
{
|
||||
$data['index_tags'] = $this->tags[ $data['index_item_index_id'] ]['index_tags'];
|
||||
|
||||
@@ -81,6 +81,8 @@ trait Solvable
|
||||
$oldComment = $commentClass::load( $this->$solvedField );
|
||||
$oldComment->setSolved( FALSE );
|
||||
$oldComment->save();
|
||||
|
||||
\IPS\Db::i()->delete( 'core_solved_index', array( 'comment_class=? AND comment_id=?', $commentClass, $oldComment->$commentIdField ) );
|
||||
}
|
||||
catch( \Exception $e ) { }
|
||||
}
|
||||
@@ -134,6 +136,9 @@ trait Solvable
|
||||
\IPS\Member::load( $memberToRecount )->recountNotifications();
|
||||
}
|
||||
}
|
||||
|
||||
/* Update search index */
|
||||
\IPS\Content\Search\Index::i()->index( $comment );
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -83,6 +83,7 @@ trait Statistics
|
||||
catch( \OutOfRangeException $e )
|
||||
{
|
||||
$where = $this->_getVisibleWhere();
|
||||
$where[] = [ $commentClass::$databasePrefix . $commentClass::$databaseColumnMap['author'] . '!=?', 0];
|
||||
|
||||
$members = iterator_to_array( \IPS\Db::i()->select( "count(*) as sum, {$authorColumn}", $commentClass::$databaseTable, $where, 'sum DESC', array( 0, $count ), array( $commentClass::$databasePrefix . $commentClass::$databaseColumnMap['author'] ) ) );
|
||||
|
||||
@@ -108,6 +109,11 @@ trait Statistics
|
||||
$return[] = array( 'member' => $member, 'count' => $counts[ $member->member_id ] );
|
||||
}
|
||||
|
||||
usort($return, function ( $member, $member2 )
|
||||
{
|
||||
return $member2['count'] <=> $member['count'];
|
||||
});
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
@@ -247,7 +253,7 @@ trait Statistics
|
||||
public function clearCachedStatistics()
|
||||
{
|
||||
$idField = static::$databaseColumnId;
|
||||
\IPS\Db::i()->delete( 'core_item_statistics_cache', array( 'cache_class=? and cache_item_id=?', \get_class( $this ), $this->$idField ) );
|
||||
\IPS\Db::i()->delete( 'core_item_statistics_cache', array( 'cache_class=? and cache_item_id=?', \get_class( $this ), $this->$idField ) );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -378,17 +384,19 @@ trait Statistics
|
||||
*/
|
||||
protected function _getCached( $key )
|
||||
{
|
||||
if ( static::$cachedActivity === NULL )
|
||||
$idField = static::$databaseColumnId;
|
||||
|
||||
$class = \get_class( $this );
|
||||
$arrayKey = $class.'.'.$this->$idField;
|
||||
if ( !isset( static::$cachedActivity[$arrayKey] ) )
|
||||
{
|
||||
$idField = static::$databaseColumnId;
|
||||
|
||||
try
|
||||
{
|
||||
$cache = \IPS\Db::i()->select( '*', 'core_item_statistics_cache', array( 'cache_class=? and cache_item_id=?', \get_class( $this ), $this->$idField ) )->first();
|
||||
$cache = \IPS\Db::i()->select( '*', 'core_item_statistics_cache', array( 'cache_class=? and cache_item_id=?', $class, $this->$idField ) )->first();
|
||||
|
||||
if ( $cache['cache_added'] > time() - 86400 )
|
||||
{
|
||||
static::$cachedActivity = json_decode( $cache['cache_contents'], TRUE );
|
||||
static::$cachedActivity[ $arrayKey ] = json_decode( $cache['cache_contents'], TRUE );
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -398,18 +406,18 @@ trait Statistics
|
||||
catch( \UnderflowException $e ) { }
|
||||
}
|
||||
|
||||
if ( static::$cachedActivity !== NULL and isset( static::$cachedActivity[ $key ] ) )
|
||||
if ( isset(static::$cachedActivity[ $arrayKey ] ) and isset( static::$cachedActivity[ $arrayKey ][ $key ] ) )
|
||||
{
|
||||
return static::$cachedActivity[ $key ];
|
||||
return static::$cachedActivity[ $arrayKey ][ $key ];
|
||||
}
|
||||
else
|
||||
{
|
||||
if( static::$cachedActivity === NULL )
|
||||
if( !isset( static::$cachedActivity[ $arrayKey ] ) )
|
||||
{
|
||||
static::$cachedActivity = array();
|
||||
static::$cachedActivity[ $arrayKey ] = array();
|
||||
}
|
||||
|
||||
static::$cachedActivity[ $key ] = array();
|
||||
static::$cachedActivity[ $arrayKey ][ $key ] = array();
|
||||
throw new \OutOfRangeException;
|
||||
}
|
||||
}
|
||||
@@ -435,10 +443,12 @@ trait Statistics
|
||||
catch( \Exception $e ) { }
|
||||
|
||||
$idField = static::$databaseColumnId;
|
||||
|
||||
static::$cachedActivity[ $key ] = $value;
|
||||
$class = \get_class( $this );
|
||||
$arrayKey = $class.'.'.$this->$idField;
|
||||
|
||||
$this->storeCache = $this->$idField;
|
||||
static::$cachedActivity[ $arrayKey ][ $key ] = $value;
|
||||
|
||||
$this->storeCache[ $arrayKey ][ $key ] = $this->$idField;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -450,12 +460,15 @@ trait Statistics
|
||||
{
|
||||
if( $this->storeCache )
|
||||
{
|
||||
\IPS\Db::i()->insert( 'core_item_statistics_cache', array(
|
||||
'cache_class' => \get_class( $this ),
|
||||
'cache_item_id' => $this->storeCache,
|
||||
'cache_contents' => json_encode( static::$cachedActivity ),
|
||||
'cache_added' => time()
|
||||
), TRUE );
|
||||
foreach( $this->storeCache as $key => $data )
|
||||
{
|
||||
\IPS\Db::i()->insert( 'core_item_statistics_cache', array(
|
||||
'cache_class' => \get_class( $this ),
|
||||
'cache_item_id' => str_replace( \get_class($this) . '.', '', $key),
|
||||
'cache_contents' => json_encode( static::$cachedActivity[$key] ),
|
||||
'cache_added' => time()
|
||||
), TRUE );
|
||||
}
|
||||
}
|
||||
|
||||
if( \is_callable( 'parent::__destruct' ) )
|
||||
|
||||
@@ -201,7 +201,8 @@ abstract class _Widget extends \IPS\Widget\PermissionCache
|
||||
/* Sort */
|
||||
$sortOptions = $class::getWidgetSortOptions();
|
||||
|
||||
$return['sort'] = new \IPS\Helpers\Form\Select( 'widget_feed_sort_on', isset( $this->configuration['widget_feed_sort_on'] ) ? $this->configuration['widget_feed_sort_on'] : $class::$databaseColumnMap['updated'], FALSE, array( 'options' => $sortOptions ), NULL, NULL, NULL, 'widget_feed_sort_on' );
|
||||
$dateColumn = $class::$databasePrefix . ( isset( $class::$databaseColumnMap['updated'] ) ? $class::$databaseColumnMap['updated'] : $class::$databaseColumnMap['date'] );
|
||||
$return['sort'] = new \IPS\Helpers\Form\Select( 'widget_feed_sort_on', $this->configuration['widget_feed_sort_on'] ?? $dateColumn, FALSE, array( 'options' => $sortOptions ), NULL, NULL, NULL, 'widget_feed_sort_on' );
|
||||
|
||||
$return['direction'] = new \IPS\Helpers\Form\Select( 'widget_feed_sort_dir', isset( $this->configuration['widget_feed_sort_dir'] ) ? $this->configuration['widget_feed_sort_dir'] : 'desc', FALSE, array(
|
||||
'options' => array(
|
||||
@@ -439,7 +440,7 @@ abstract class _Widget extends \IPS\Widget\PermissionCache
|
||||
$binds = array( $class::$application, $class::$module );
|
||||
foreach( $this->configuration['widget_feed_tags'] as $tag )
|
||||
{
|
||||
$tagWhere[] = "( tag_text LIKE CONCAT( ?, '%') )";
|
||||
$tagWhere[] = "( tag_text=? )";
|
||||
$binds[] = $tag;
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user