Version 4.5.0

This commit is contained in:
Neo committed 2025-12-19 05:51:58 -08:00
1 parent 1a0c7fd3c2
commit f79dcf067a
3791 files changed
+248032 -76372

No files matched your search

@@ -0,0 +1,25 @@
<?php
/**
* @brief Elasticsearch Search Exception Class
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
* @copyright (c) Invision Power Services, Inc.
* @license https://www.invisioncommunity.com/legal/standards/
* @package Invision Community
* @since 26 Nov 2019
*/
namespace IPS\Content\Search\Elastic;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
}
/**
* Elasticsearch Search Exception Class
*/
class _Exception extends \DomainException
{
}
+243 -182
View File
@@ -11,7 +11,7 @@
namespace IPS\Content\Search\Elastic;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
@@ -22,6 +22,12 @@ if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
*/
class _Index extends \IPS\Content\Search\Index
{
/**
* @brief Elasticsearch version requirements
*/
const MINIMUM_VERSION = '6.0.0';
const UNSUPPORTED_VERSION = '8.0.0';
/**
* @brief The server URL
*/
@@ -44,14 +50,23 @@ class _Index extends \IPS\Content\Search\Index
* @return void
*/
public function init()
{
{
$analyzer = \IPS\Settings::i()->search_elastic_analyzer;
$settings = array(
'max_result_window' => \IPS\Settings::i()->search_index_maxresults
);
if ( $analyzer === 'custom' )
{
$settings['analysis'] = json_decode( '{' . \IPS\Settings::i()->search_elastic_custom_analyzer . '}', TRUE );
$analyzer = key( $settings['analysis']['analyzer'] );
}
$this->url->request()->delete();
$this->url->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->put( json_encode( array(
'settings' => array(
'max_result_window' => \IPS\Settings::i()->search_index_maxresults
),
$definition = array(
'settings' => $settings,
'mappings' => array(
'content' => array(
'_doc' => array(
'properties' => array(
'index_class' => array( 'type' => 'keyword' ),
'index_object_id' => array( 'type' => 'long' ),
@@ -60,11 +75,11 @@ class _Index extends \IPS\Content\Search\Index
'index_container_id' => array( 'type' => 'long' ),
'index_title' => array(
'type' => 'text',
'analyzer' => \IPS\Settings::i()->search_elastic_analyzer,
'analyzer' => $analyzer,
),
'index_content' => array(
'type' => 'text',
'analyzer' => \IPS\Settings::i()->search_elastic_analyzer,
'analyzer' => $analyzer,
),
'index_permissions' => array( 'type' => 'keyword' ),
'index_date_created' => array(
@@ -88,14 +103,27 @@ class _Index extends \IPS\Content\Search\Index
'index_is_last_comment' => array( 'type' => 'boolean' ),
'index_club_id' => array( 'type' => 'long' ),
'index_class_type_id_hash' => array( 'type' => 'keyword' ),
'index_views' => array( 'type' => 'long' ),
'index_comments' => array( 'type' => 'long' ),
'index_reviews' => array( 'type' => 'long' ),
'index_participants' => array( 'type' => 'long' ),
)
)
)
) ) );
);
try
{
$response = $this->url->setQueryString( 'include_type_name', 'true' )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->put( json_encode( $definition ) );
if( $response->httpResponseCode != 200 )
{
throw new \RuntimeException;
}
}
catch( \Exception $e )
{
$response = $this->url->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->put( json_encode( $definition ) );
}
}
/**
@@ -147,15 +175,13 @@ class _Index extends \IPS\Content\Search\Index
{
try
{
$class = get_class( $object );
$existingData = NULL;
$existingIndexId = NULL;
$resetLastComment = FALSE;
try
{
$r = $this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/' . $this->getIndexId( $object ) )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->get()->decodeJson();
$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();
if ( $r['found'] )
{
$existingData = $r['_source'];
@@ -173,65 +199,22 @@ class _Index extends \IPS\Content\Search\Index
}
else if ( $indexData['index_is_last_comment'] and $indexData['index_item_id'] )
{
$classes = array( $class );
/* If this is the latest comment, unflag what was set before on both item and comment */
if ( $object instanceof \IPS\Content\Comment )
{
$itemClass = $object::$itemClass;
if ( ! $itemClass::$firstCommentRequired )
{
$classes[] = $itemClass;
}
if ( isset( $itemClass::$reviewClass ) )
{
$classes[] = $itemClass::$reviewClass;
}
}
else if ( $object instanceof \IPS\Content\Item )
{
if ( isset( $class::$commentClass ) )
{
$classes[] = $class::$commentClass;
}
if ( isset( $class::$reviewClass ) )
{
$classes[] = $class::$reviewClass;
}
}
$this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/_update_by_query' )->setQueryString( array( 'conflicts' => 'proceed', 'wait_for_completion' => 'false' ) )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
'script' => array(
'inline' => "ctx._source.index_is_last_comment = false;",
'lang' => 'painless'
),
'query' => array(
'bool' => array(
'must' => array(
array(
'terms' => array(
'index_class' => $classes
)
),
array(
'term' => array(
'index_item_id' => $indexData['index_item_id']
)
),
)
)
)
) ) );
/* We have a new "last comment" */
$resetLastComment = TRUE;
}
/* Insert into index */
$r = $this->url
->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/' . $this->getIndexId( $object ) )
->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/_doc/' . $this->getIndexId( $object ) )
->request( \IPS\LONG_REQUEST_TIMEOUT )
->setHeaders( array( 'Content-Type' => 'application/json' ) )
->put( json_encode( $indexData ) );
if( $error = $this->getResponseError( $r ) )
{
throw new \IPS\Content\Search\Elastic\Exception( $error['type'] . ' ' . $error['type'] );
}
/* Views / Comments / Reviews */
if ( $object instanceof \IPS\Content\Item )
{
@@ -247,6 +230,10 @@ class _Index extends \IPS\Content\Search\Index
{
\IPS\Log::log( $e, 'elasticsearch' );
}
catch ( \IPS\Content\Search\Elastic\Exception $e )
{
\IPS\Log::log( $e, 'elasticsearch_response_error' );
}
if ( $resetLastComment )
{
@@ -256,50 +243,6 @@ class _Index extends \IPS\Content\Search\Index
}
}
/**
* Update view count
*
* @param string $class Class to update
* @param int $id ID of item
* @param int $count Count to update
* @throws \OutOfRangeException When table to update no longer exists
*/
public function updateViewCounts( $class, $id, $count )
{
$classes = array( $class );
if ( isset( $class::$commentClass ) )
{
$classes[] = $class::$commentClass;
}
if ( isset( $class::$reviewClass ) )
{
$classes[] = $class::$reviewClass;
}
$this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/_update_by_query' )->setQueryString( array( 'conflicts' => 'proceed', 'wait_for_completion' => 'false' ) )->request( \IPS\LONG_REQUEST_TIMEOUT )->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
'script' => array(
'inline' => "ctx._source.index_views += ". intval( $count ) . ";",
'lang' => 'painless'
),
'query' => array(
'bool' => array(
'must' => array(
array(
'terms' => array(
'index_class' => $classes
)
),
array(
'term' => array(
'index_item_id' => $id
)
),
)
)
)
) ) );
}
/**
* Clear out any tasks associated with the search index method
*
@@ -310,7 +253,14 @@ 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()->decodeJson();
$response = $this->url->setPath( '/_tasks' )->request( \IPS\LONG_REQUEST_TIMEOUT )->setHeaders( array( 'Content-Type' => 'application/json' ) )->get();
if( $error = $this->getResponseError( $response ) )
{
throw new \IPS\Content\Search\Elastic\Exception( $error['type'] . ' ' . $error['type'] );
}
$response = $response->decodeJson();
foreach( $response['nodes'] as $nodeId => $nodeData )
{
@@ -332,6 +282,10 @@ class _Index extends \IPS\Content\Search\Index
}
}
}
catch ( \IPS\Content\Search\Elastic\Exception $e )
{
\IPS\Log::log( $e, 'elasticsearch_response_error' );
}
catch( \Exception $e )
{
\IPS\Log::log( $e, 'elasticsearch' );
@@ -362,7 +316,6 @@ class _Index extends \IPS\Content\Search\Index
$participants = array_values( array_unique( $participants ) );
return array(
'index_views' => $item->mapped('views'),
'index_comments' => $item->mapped('num_comments'),
'index_reviews' => $item->mapped('num_reviews'),
'index_participants' => $participants
@@ -379,7 +332,7 @@ class _Index extends \IPS\Content\Search\Index
protected function rebuildMetaCounts( $item, $lastCommentIndexId = NULL )
{
$databaseColumnId = $item::$databaseColumnId;
$class = get_class( $item );
$class = \get_class( $item );
$classes = array( $class );
if ( isset( $class::$commentClass ) )
{
@@ -392,30 +345,36 @@ class _Index extends \IPS\Content\Search\Index
try
{
$updates = array();
$updates = array();
$params = array();
foreach ( $this->metaCounts( $item ) as $k => $v )
{
if ( is_array( $v ) )
if ( \is_array( $v ) )
{
$updates[] = "ctx._source.{$k} = [" . implode( ',', array_map( 'intval', $v ) ) . "];";
$updates[] = "ctx._source.{$k} = params.param_{$k};";
$params[ 'param_' . $k ] = array_map( 'intval', $v );
}
elseif ( is_null( $v ) )
elseif ( \is_null( $v ) )
{
$updates[] = "ctx._source.{$k} = null;";
$updates[] = "ctx._source.{$k} = params.param_{$k};";
$params['param_' . $k ] = null;
}
else
{
$updates[] = "ctx._source.{$k} = " . intval( $v ) . ';';
$updates[] = "ctx._source.{$k} = params.param_{$k};";
$params['param_' . $k ] = \intval( $v );
}
}
if ( !is_null( $lastCommentIndexId ) )
if ( !\is_null( $lastCommentIndexId ) )
{
$updates[] = "ctx._source.index_is_last_comment = ( ctx._id == '{$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 ] . '/content/_update_by_query' )->setQueryString( array( 'conflicts' => 'proceed', 'wait_for_completion' => 'false' ) )->request( \IPS\LONG_REQUEST_TIMEOUT )->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
$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(
'script' => array(
'inline' => implode( ' ', $updates ),
'lang' => 'painless'
'source' => implode( ' ', $updates ),
'lang' => 'painless',
'params' => $params
),
'query' => array(
'bool' => array(
@@ -450,7 +409,7 @@ class _Index extends \IPS\Content\Search\Index
public function getIndexId( \IPS\Content\Searchable $object )
{
$databaseColumnId = $object::$databaseColumnId;
return \strtolower( str_replace( '\\', '_', \substr( get_class( $object ), 4 ) ) ) . '-' . $object->$databaseColumnId;
return \strtolower( str_replace( '\\', '_', \substr( \get_class( $object ), 4 ) ) ) . '-' . $object->$databaseColumnId;
}
/**
@@ -463,16 +422,57 @@ class _Index extends \IPS\Content\Search\Index
{
try
{
$this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/' . $this->getIndexId( $object ) )->request()->delete();
$class = \get_class( $object );
$idColumn = $class::$databaseColumnId;
$this->directIndexRemoval( $class, $object->$idColumn );
if ( !( $object instanceof \IPS\Content\Item ) )
{
$this->rebuildMetaCounts( $object->item() );
$itemClass = \get_class( $object->item() );
$itemIdColumn = $itemClass::$databaseColumnId;
$classes = array( $itemClass );
if ( isset( $itemClass::$commentClass ) )
{
$classes[] = $itemClass::$commentClass;
}
if ( isset( $itemClass::$reviewClass ) )
{
$classes[] = $itemClass::$reviewClass;
}
$this->resetLastComment( $classes, $object->item()->$itemIdColumn, $object->$idColumn );
}
}
catch ( \IPS\Http\Request\Exception $e )
{
\IPS\Log::log( $e, 'elasticsearch' );
}
}
/**
* Direct removal from the search index - only used when we don't need to perform ancillary cleanup (i.e. orphaned data)
*
* @param string $class Class
* @param int $id ID
* @return void
*/
public function directIndexRemoval( $class, $id )
{
try
{
$indexId = \strtolower( str_replace( '\\', '_', \substr( $class, 4 ) ) ) . '-' . $id;
$this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/_doc/' . $indexId )->request()->delete();
if ( $object instanceof \IPS\Content\Item )
if ( is_subclass_of( $class, 'IPS\Content\Item' ) )
{
$class = get_class( $object );
$idColumn = $class::$databaseColumnId;
if ( isset( $class::$commentClass ) )
{
$commentClass = $class::$commentClass;
$response = $this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/_delete_by_query' )->setQueryString( array( 'conflicts' => 'proceed', 'wait_for_completion' => 'false' ) )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
$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(
'query' => array(
'bool' => array(
'must' => array(
@@ -483,7 +483,7 @@ class _Index extends \IPS\Content\Search\Index
),
array(
'term' => array(
'index_item_id' => $object->$idColumn
'index_item_id' => $id
)
),
)
@@ -495,7 +495,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 ] . '/content/_delete_by_query' )->setQueryString( array( 'conflicts' => 'proceed', 'wait_for_completion' => 'false' ) )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
$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(
'query' => array(
'bool' => array(
'must' => array(
@@ -506,7 +506,7 @@ class _Index extends \IPS\Content\Search\Index
),
array(
'term' => array(
'index_item_id' => $object->$idColumn
'index_item_id' => $id
)
),
)
@@ -516,10 +516,6 @@ class _Index extends \IPS\Content\Search\Index
) ) );
}
}
else
{
$this->rebuildMetaCounts( $object->item() );
}
}
catch ( \IPS\Http\Request\Exception $e )
{
@@ -571,13 +567,13 @@ class _Index extends \IPS\Content\Search\Index
);
}
$this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/_delete_by_query' )->setQueryString( array( 'conflicts' => 'proceed', 'wait_for_completion' => 'false' ) )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
$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(
'query' => $query
) ) );
}
else
{
$this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/_delete_by_query' )->setQueryString( array( 'conflicts' => 'proceed', 'wait_for_completion' => 'false' ) )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
$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(
'query' => array(
'term' => array(
'index_class' => $class
@@ -642,42 +638,50 @@ class _Index extends \IPS\Content\Search\Index
);
}
$updates = array();
$updates = array();
$params = array();
if ( $newPermissions !== NULL )
{
$updates[] = "ctx._source.index_permissions = " . json_encode( explode( ',', $newPermissions ) ) . ";";
$updates[] = "ctx._source.index_permissions = params.params_indexpermissions;";
$params['params_indexpermissions'] = explode( ',', $newPermissions );
}
if ( $newContainer )
{
$updates[] = "ctx._source.index_container_id = " . intval( $newContainer ) . ";";
$updates[] = "ctx._source.index_container_id = params.params_indexcontainer;";
$params['params_indexcontainer'] = \intval( $newContainer );
if ( $itemClass = ( in_array( 'IPS\Content\Item', class_parents( $class ) ) ? $class : $class::$itemClass ) and $containerClass = $itemClass::$containerNodeClass and \IPS\IPS::classUsesTrait( $containerClass, 'IPS\Content\ClubContainer' ) and $clubIdColumn = $containerClass::clubIdColumn() )
if ( $itemClass = ( \in_array( 'IPS\Content\Item', class_parents( $class ) ) ? $class : $class::$itemClass ) and $containerClass = $itemClass::$containerNodeClass and \IPS\IPS::classUsesTrait( $containerClass, 'IPS\Content\ClubContainer' ) and $clubIdColumn = $containerClass::clubIdColumn() )
{
try
{
$updates[] = "ctx._source.index_club_id = " . intval( $containerClass::load( $newContainer )->$clubIdColumn ) . ";";
$updates[] = "ctx._source.index_club_id = params.params_indexclub;";
$params['params_indexclub'] = \intval( $containerClass::load( $newContainer )->$clubIdColumn );
}
catch ( \OutOfRangeException $e )
{
$updates[] = "ctx._source.index_club_id = null;";
$updates[] = "ctx._source.index_club_id = params.params_indexclub;";
$params['params_indexclub'] = null;
}
}
}
if ( $newItemId )
{
$updates[] = "ctx._source.index_item_id = " . intval( $newItemId ) . ";";
$updates[] = "ctx._source.index_item_id = params.params_indexitem;";
$params['params_indexitem'] = \intval( $newItemId );
}
if ( $newItemAuthorId )
{
$updates[] = "ctx._source.index_item_author = " . intval( $newItemAuthorId ) . ";";
$updates[] = "ctx._source.index_item_author = params.params_indexauthor;";
$params['params_indexauthor'] = \intval( $newItemAuthorId );
}
if ( count( $updates ) )
if ( \count( $updates ) )
{
$this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/_update_by_query' )->setQueryString( array( 'conflicts' => 'proceed', 'wait_for_completion' => 'false' ) )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
$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(
'script' => array(
'inline' => implode( ' ', $updates ),
'lang' => 'painless'
'source' => implode( ' ', $updates ),
'lang' => 'painless',
'params' => $params
),
'query' => array(
'bool' => $conditions
@@ -694,9 +698,9 @@ class _Index extends \IPS\Content\Search\Index
)
);
$this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/_update_by_query' )->setQueryString( array( 'conflicts' => 'proceed', 'wait_for_completion' => 'false' ) )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
$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(
'script' => array(
'inline' => "ctx._source.index_permissions.add( 'm.' + ctx._source.index_author );",
'source' => "ctx._source.index_permissions.add( 'm' + ctx._source.index_author );",
'lang' => 'painless'
),
'query' => array(
@@ -724,10 +728,11 @@ class _Index extends \IPS\Content\Search\Index
);
}
$this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/_update_by_query' )->setQueryString( array( 'conflicts' => 'proceed', 'wait_for_completion' => 'false' ) )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
$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(
'script' => array(
'inline' => "ctx._source.index_hidden = " . intval( $newHiddenStatus ) . ";",
'lang' => 'painless'
'source' => "ctx._source.index_hidden = params.newHiddenStatus;",
'lang' => 'painless',
'params' => array( 'newHiddenStatus' => \intval( $newHiddenStatus ) )
),
'query' => array(
'bool' => $conditions
@@ -749,11 +754,11 @@ class _Index extends \IPS\Content\Search\Index
*/
public static function convertConditionsToQuery( $conditions )
{
if ( count( $conditions ) == 1 )
if ( \count( $conditions ) == 1 )
{
return $conditions[0];
}
elseif ( count( $conditions ) == 0 )
elseif ( \count( $conditions ) == 0 )
{
return array( 'match_all' => new \StdClass );
}
@@ -790,7 +795,7 @@ class _Index extends \IPS\Content\Search\Index
try
{
$this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/' . $this->getIndexId( $item ) . '/_update' )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
$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(
'doc' => $update
) ) );
@@ -822,7 +827,7 @@ class _Index extends \IPS\Content\Search\Index
{
try
{
$this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/_delete_by_query' )->setQueryString( array( 'conflicts' => 'proceed', 'wait_for_completion' => 'false' ) )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
$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(
'query' => array(
'range' => array(
'index_date_updated' => array(
@@ -848,16 +853,17 @@ class _Index extends \IPS\Content\Search\Index
*
* @param array $classes The classes (when first post is required, this is typically just \IPS\forums\Topic\Post but for others, it will be both item and comment classes)
* @param int|NULL $indexItemId The index item ID
* @param int|NULL $ignoreId ID to ignore because it is being removed
* @return void
*/
public function resetLastComment( $classes, $indexItemId )
public function resetLastComment( $classes, $indexItemId, $ignoreId = NULL )
{
try
{
/* Remove the flag */
$this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/_update_by_query' )->setQueryString( array( 'conflicts' => 'proceed', 'wait_for_completion' => 'false' ) )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
$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(
'script' => array(
'inline' => "ctx._source.index_is_last_comment = false;",
'source' => "ctx._source.index_is_last_comment = false;",
'lang' => 'painless'
),
'query' => array(
@@ -873,6 +879,11 @@ class _Index extends \IPS\Content\Search\Index
'index_item_id' => $indexItemId
)
),
array(
'term' => array(
'index_is_last_comment' => true
)
)
)
)
)
@@ -882,7 +893,7 @@ class _Index extends \IPS\Content\Search\Index
$itemClass = NULL;
foreach ( $classes as $class )
{
if ( in_array( 'IPS\Content\Item', class_parents( $class ) ) )
if ( \in_array( 'IPS\Content\Item', class_parents( $class ) ) )
{
$itemClass = $class;
break;
@@ -898,10 +909,19 @@ class _Index extends \IPS\Content\Search\Index
{
$item = $itemClass::load( $indexItemId );
if ( $lastComment = $item->comments( 1, 0, 'date', 'desc', NULL, FALSE ) )
$where = NULL;
if( $ignoreId !== NULL )
{
$commentClass = $itemClass::$commentClass;
$commentIdColumn = $commentClass::$databaseColumnId;
$where = array( $commentClass::$databaseTable . '.' . $commentClass::$databasePrefix . $commentIdColumn . '<>?', $ignoreId );
}
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 ] . '/content/' . $this->getIndexId( $lastComment ) . '/_update' )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
$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(
'doc' => array(
'index_is_last_comment' => true
)
@@ -909,10 +929,14 @@ class _Index extends \IPS\Content\Search\Index
/* 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 ] . '/content/_update_by_query' )->setQueryString( array( 'conflicts' => 'proceed', 'wait_for_completion' => 'false' ) )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->post( json_encode( array(
$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(
'script' => array(
'inline' => "ctx._source.index_date_updated = " . intval( $indexDataForLastComment['index_date_updated'] ) ."; ctx._source.index_date_commented = " . intval( $indexDataForLastComment['index_date_commented'] ) .";",
'lang' => 'painless'
'source' => "ctx._source.index_date_updated = params.dateUpdated; ctx._source.index_date_commented = params.dateCommented;",
'lang' => 'painless',
'params' => array(
'dateUpdated' => \intval( $indexDataForLastComment['index_date_updated'] ),
'dateCommented' => \intval( $indexDataForLastComment['index_date_commented'] )
)
),
'query' => array(
'bool' => array(
@@ -938,7 +962,7 @@ class _Index extends \IPS\Content\Search\Index
) ) );
}
}
catch ( \OutOfRangeException $e ) { }
catch ( \OutOfRangeException $e ) {}
}
}
catch ( \Exception $e )
@@ -977,22 +1001,23 @@ class _Index extends \IPS\Content\Search\Index
);
/* Get the count */
$count = $this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/_search' )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->get( json_encode( array(
$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(
'size' => 0,
'query' => $query
) ) )->decodeJson();
if ( !$count['hits']['total'] )
$total = $count['hits']['total']['value'] ?? $count['hits']['total'];
if ( !$total )
{
return array();
}
/* Now get the unique item ids */
$results = $this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/_search' )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->get( json_encode( array(
$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(
'aggs' => array(
'itemIds' => array(
'terms' => array(
'field' => 'index_item_index_id',
'size' => $count['hits']['total']
'size' => $total
)
)
),
@@ -1020,15 +1045,16 @@ class _Index extends \IPS\Content\Search\Index
/**
* Given a list of "index_class_type_id_hash"s, return the ones that a given member has permission to view
*
* @param array $itemIndexIds Item index IDs
* @param array $hashes Item index hashes
* @param \IPS\Member $member The member
* @param int|NULL $limit Number of results to return
* @return array
*/
public function hashesWithPermission( array $hashes, \IPS\Member $member )
public function hashesWithPermission( array $hashes, \IPS\Member $member, $limit = NULL )
{
try
{
$results = $this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/_search' )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->get( json_encode( array(
$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(
'query' => array(
'bool' => array(
'filter' => array(
@@ -1049,7 +1075,8 @@ class _Index extends \IPS\Content\Search\Index
)
)
)
)
),
'size' => $limit ?: 10 // If we define a limit, use that, otherwise default to 10 which is ElasticSearch's default
) ) )->decodeJson();
}
catch ( \Exception $e )
@@ -1076,7 +1103,7 @@ class _Index extends \IPS\Content\Search\Index
{
try
{
$results = $this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/content/_search' )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->get( json_encode( array(
$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(
'size' => 1,
'sort' => array( array( 'index_date_updated' => 'asc' ) )
) ) )->decodeJson();
@@ -1103,16 +1130,23 @@ class _Index extends \IPS\Content\Search\Index
*/
public function stemmedTerms( $terms )
{
$analyzer = \IPS\Settings::i()->search_elastic_analyzer;
if ( $analyzer === 'custom' )
{
$analysisSettings = json_decode( '{' . \IPS\Settings::i()->search_elastic_custom_analyzer . '}', TRUE );
$analyzer = key( $analysisSettings['analyzer'] );
}
try
{
$results = $this->url->setPath( '/_analyze' )->request()->setHeaders( array( 'Content-Type' => 'application/json' ) )->get( json_encode( array(
'analyzer' => \IPS\Settings::i()->search_elastic_analyzer,
'analyzer' => $analyzer,
'text' => implode( ' ', $terms )
) ) )->decodeJson();
if ( isset( $results['tokens'] ) )
{
$stemmed = array();
$stemmed = $terms;
foreach ( $results['tokens'] as $token )
{
$stemmed[] = $token['token'];
@@ -1127,4 +1161,31 @@ class _Index extends \IPS\Content\Search\Index
return $terms;
}
}
/**
* Supports filtering by views?
*
* @return bool
*/
public function supportViewFiltering()
{
return FALSE;
}
/**
* Check response to see if an error was produced
*
* @param \IPS\Http\Response $response Response object
* @return string|null
*/
protected function getResponseError( \IPS\Http\Response $response )
{
/* Log any errors */
if( $response->httpResponseCode != 200 AND $content = $response->decodeJson() AND isset( $content['error'] ) )
{
return $content['error'];
}
return NULL;
}
}
@@ -11,7 +11,7 @@
namespace IPS\Content\Search\Elastic;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
@@ -22,6 +22,9 @@ if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
*/
class _MassIndexer extends \IPS\Content\Search\Elastic\Index
{
/**
* @brief Data to store in bulk
*/
protected $data = array();
/**
@@ -46,7 +49,7 @@ class _MassIndexer extends \IPS\Content\Search\Elastic\Index
$this->data[] = array(
'index' => array(
'_index' => trim( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ], '/' ),
'_type' => 'content',
'_type' => '_doc',
'_id' => $this->getIndexId( $object ),
)
);
+93 -34
View File
@@ -11,7 +11,7 @@
namespace IPS\Content\Search\Elastic;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
exit;
@@ -174,8 +174,8 @@ class _Query extends \IPS\Content\Search\Query
}
}
/* Minimum views / comments / reviews */
foreach ( array( 'minimumViews' => 'index_views', 'minimumComments' => 'index_comments', 'minimumReviews' => 'index_reviews' ) as $filterKey => $indexKey )
/* Minimum comments / reviews */
foreach ( array( 'minimumComments' => 'index_comments', 'minimumReviews' => 'index_reviews' ) as $filterKey => $indexKey )
{
if ( $filter->$filterKey )
{
@@ -204,11 +204,14 @@ class _Query extends \IPS\Content\Search\Query
}
/* Put it together */
$contentFilterConditions[] = Index::convertConditionsToQuery( $conditions );
if( \count( $conditions ) )
{
$contentFilterConditions[] = Index::convertConditionsToQuery( $conditions );
}
}
/* Put them together */
if ( count( $contentFilterConditions ) > 1 )
if ( \count( $contentFilterConditions ) > 1 )
{
$this->filters[] = array(
'bool' => array(
@@ -218,11 +221,17 @@ class _Query extends \IPS\Content\Search\Query
}
elseif ( $type )
{
$this->filters[] = $contentFilterConditions[0];
if( \count( $contentFilterConditions ) )
{
$this->filters[] = $contentFilterConditions[0];
}
}
else
{
$this->mustNot[] = $contentFilterConditions[0];
if( \count( $contentFilterConditions ) )
{
$this->mustNot[] = $contentFilterConditions[0];
}
}
return $this;
@@ -236,7 +245,7 @@ class _Query extends \IPS\Content\Search\Query
*/
public function filterByAuthor( $author )
{
if ( is_array( $author ) )
if ( \is_array( $author ) )
{
$this->filters[] = array( 'terms' => array( 'index_author' => $author ) );
}
@@ -262,7 +271,7 @@ class _Query extends \IPS\Content\Search\Query
'exists' => array( 'field' => 'index_club_id' )
);
}
elseif ( is_array( $club ) )
elseif ( \is_array( $club ) )
{
$this->filters[] = array(
'terms' => array( 'index_club_id' => $club )
@@ -274,6 +283,25 @@ class _Query extends \IPS\Content\Search\Query
'term' => array( 'index_club_id' => $club instanceof \IPS\Member\Club ? $club->id : $club )
);
}
/* Get the list of valid classes */
foreach ( \IPS\Application::allExtensions( 'core', 'ContentRouter', FALSE ) as $object )
{
foreach ( $object->classes as $class )
{
if ( \in_array( 'IPS\Content\Item', class_parents( $class ) ) )
{
$classesChecked[] = $class;
}
}
}
/* Give content item classes a chance to inspect and manipulate filters */
$filters = array();
foreach( $classesChecked as $itemClass )
{
$itemClass::searchEngineFiltering( $filters, $this );
}
return $this;
}
@@ -318,7 +346,7 @@ class _Query extends \IPS\Content\Search\Query
{
foreach ( $object->classes as $class )
{
if ( in_array( 'IPS\Content\Item', class_parents( $class ) ) )
if ( \in_array( 'IPS\Content\Item', class_parents( $class ) ) )
{
$classesChecked[] = $class;
}
@@ -399,7 +427,7 @@ class _Query extends \IPS\Content\Search\Query
*
* @param bool $includeContainers Include content in containers the user follows?
* @param bool $includeItems Include items and comments/reviews on items the user follows?
* @param bool $includeContainers Include content posted by members the user follows?
* @param bool $includeMembers Include content posted by members the user follows?
* @return \IPS\Content\Search\Query (for daisy chaining)
*/
public function filterByFollowed( $includeContainers, $includeItems, $includeMembers )
@@ -477,7 +505,7 @@ class _Query extends \IPS\Content\Search\Query
{
$followedItems[ $follow['follow_area'] ][] = $follow['follow_rel_id'];
}
else if( in_array( $follow['follow_area'], $containerCase ) )
else if( \in_array( $follow['follow_area'], $containerCase ) )
{
$followedContainers[ $follow['follow_area'] ][] = $follow['follow_rel_id'];
}
@@ -537,7 +565,7 @@ class _Query extends \IPS\Content\Search\Query
}
/* Put it all together */
if ( count( $conditions ) )
if ( \count( $conditions ) )
{
$this->filters[] = array( 'bool' => array( 'should' => $conditions ) );
}
@@ -646,7 +674,7 @@ class _Query extends \IPS\Content\Search\Query
/* And get the times each individual item was read for later */
$items = $this->member->markersItems( $application, \IPS\Content\Item::makeMarkerKey( $containerId ) );
if ( count( $items ) )
if ( \count( $items ) )
{
foreach( $items as $mid => $mtime )
{
@@ -656,10 +684,10 @@ class _Query extends \IPS\Content\Search\Query
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 ) )
if ( \in_array( $mid, $markers ) )
{
$_key = array_search( $mid, $markers );
$_mtime = intval( mb_substr( $_key, 0, mb_strpos( $_key, '.' ) ) );
$_mtime = \intval( mb_substr( $_key, 0, mb_strpos( $_key, '.' ) ) );
if ( $_mtime < $mtime )
{
unset( $markers[ $_key ] );
@@ -704,7 +732,7 @@ class _Query extends \IPS\Content\Search\Query
}
$notIn = array();
if ( count( $markers ) )
if ( \count( $markers ) )
{
$useIds = array_flip( $markers );
@@ -713,7 +741,7 @@ class _Query extends \IPS\Content\Search\Query
{
if ( isset( $class::$databaseColumnMap[ $k ] ) )
{
if ( is_array( $class::$databaseColumnMap[ $k ] ) )
if ( \is_array( $class::$databaseColumnMap[ $k ] ) )
{
foreach ( $class::$databaseColumnMap[ $k ] as $v )
{
@@ -726,7 +754,7 @@ class _Query extends \IPS\Content\Search\Query
}
}
}
$dateColumnExpression = count( $dateColumns ) > 1 ? ( 'GREATEST(' . implode( ',', $dateColumns ) . ')' ) : array_pop( $dateColumns );
$dateColumnExpression = \count( $dateColumns ) > 1 ? ( 'GREATEST(' . implode( ',', $dateColumns ) . ')' ) : array_pop( $dateColumns );
foreach( \IPS\Db::i()->select( $class::$databaseTable . '.' . $class::$databasePrefix . $class::$databaseColumnId. ' as _id, ' . $dateColumnExpression . ' as _date', $class::$databaseTable, \IPS\Db::i()->in( $class::$databasePrefix . $class::$databaseColumnId, array_keys( $useIds ) ) ) as $row )
{
@@ -735,7 +763,7 @@ class _Query extends \IPS\Content\Search\Query
if ( $useIds[ $row['_id'] ] >= $row['_date'] )
{
/* Still read */
$notIn[] = intval( $row['_id'] );
$notIn[] = \intval( $row['_id'] );
}
}
}
@@ -747,7 +775,7 @@ class _Query extends \IPS\Content\Search\Query
'filter' => $classConditions
)
);
if ( count( $notIn ) )
if ( \count( $notIn ) )
{
$_condition['bool']['must_not'] = array(
'terms' => array(
@@ -760,7 +788,7 @@ class _Query extends \IPS\Content\Search\Query
}
/* Put it all together */
if ( count( $conditions ) )
if ( \count( $conditions ) )
{
$this->filters[] = array(
'bool' => array(
@@ -907,7 +935,7 @@ class _Query extends \IPS\Content\Search\Query
public function search( $term = NULL, $tags = NULL, $method = 1, $operator = NULL )
{
/* If we're looking for more results than we can fetch, we don't need to ask Elastic Search */
if( ( $this->offset + $this->resultsToGet ) >= \IPS\Settings::i()->search_index_maxresults )
if( ( $this->offset + $this->resultsToGet ) > \IPS\Settings::i()->search_index_maxresults )
{
return new \IPS\Content\Search\Results( array(), 0 );
}
@@ -920,11 +948,12 @@ class _Query extends \IPS\Content\Search\Query
if ( $term !== NULL or $tags !== NULL )
{
$searchConditions = array();
$titleField = \IPS\Settings::i()->search_title_boost ? ( 'index_title^' . intval( \IPS\Settings::i()->search_title_boost ) ) : 'index_title';
$titleField = \IPS\Settings::i()->search_title_boost ? ( 'index_title^' . \intval( \IPS\Settings::i()->search_title_boost ) ) : 'index_title';
/* Build the condition for the search term */
if ( $term !== NULL )
{
/* If term is in "quotes" handle it as a phrase */
if ( static::termIsPhrase( $term ) )
{
$term = trim( $term, '"' );
@@ -937,6 +966,30 @@ class _Query extends \IPS\Content\Search\Query
$searchConditions[] = array( 'multi_match' => array( 'query' => $term, 'fields' => array( 'index_content', $titleField ), 'type' => 'phrase' ) );
}
}
/* If term contains * wildcard, but doesn't start with it (from elasticsearch docs: "In order to prevent extremely slow wildcard queries, a wildcard term should not start with one of the wildcards * or ?"), handle it as a wildcard search - NOTE this will not use the analyzer */
elseif ( preg_match( '/^[^\s\*].*\*.*$/', $term ) )
{
if ( $method & static::TERM_TITLES_ONLY )
{
$searchConditions[] = array( 'wildcard' => array( 'index_title' => array( 'value' => $term ) ) );
}
else
{
$searchConditions[] = array(
'bool' => array(
'should' => array(
array(
'wildcard' => array( 'index_title' => ( \IPS\Settings::i()->search_title_boost ? array( 'value' => $term, 'boost' => \intval( \IPS\Settings::i()->search_title_boost ) ) : array( 'value' => $term ) ) )
),
array(
'wildcard' => array( 'index_content' => array( 'value' => $term ) )
)
)
)
);
}
}
/* Otherwise just do it as a match search */
else
{
if ( $method & static::TERM_TITLES_ONLY )
@@ -988,7 +1041,7 @@ class _Query extends \IPS\Content\Search\Query
$filters[] = array( 'terms' => array( 'index_permissions' => array_merge( $this->permissionArray(), array( '*' ) ) ) );
if ( $this->hiddenStatuses !== NULL )
{
if ( is_array( $this->hiddenStatuses ) )
if ( \is_array( $this->hiddenStatuses ) )
{
$filters[] = array( 'terms' => array( 'index_hidden' => $this->hiddenStatuses ) );
}
@@ -1018,8 +1071,9 @@ class _Query extends \IPS\Content\Search\Query
'query' => $query,
'linear' => array(
'index_date_updated' => array(
'scale' => intval( \IPS\Settings::i()->search_decay_days ) . 'd',
'decay' => number_format( \IPS\Settings::i()->search_decay_factor, 1, '.', '' )
'scale' => \intval( \IPS\Settings::i()->search_decay_days ) . 'd',
'decay' => number_format( \IPS\Settings::i()->search_decay_factor, 1, '.', '' ),
'origin' => time(),
)
)
)
@@ -1034,8 +1088,12 @@ class _Query extends \IPS\Content\Search\Query
'query' => $query,
'script_score' => array(
'script' => array(
'inline' => "doc['index_author'].value == " . intval( $this->member->member_id ) . " ? ( _score * " . number_format( \IPS\Settings::i()->search_elastic_self_boost, 1, '.', '' ) . " ) : _score",
'lang' => 'painless'
'source' => "doc['index_author'].value == params.param_memberId ? ( _score * Float.parseFloat( params.param_booster ) ) : _score",
'lang' => 'painless',
'params' => array(
'param_memberId' => \intval( $this->member->member_id ),
'param_booster' => number_format( \IPS\Settings::i()->search_elastic_self_boost, 1, '.', '' )
)
)
)
)
@@ -1057,7 +1115,7 @@ class _Query extends \IPS\Content\Search\Query
{
return new \IPS\Content\Search\Results( array(), 0 );
}
/* 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();
if ( isset( $return['error'] ) )
@@ -1065,14 +1123,15 @@ class _Query extends \IPS\Content\Search\Query
\IPS\Log::log( print_r( $return['error'], TRUE ), 'elasticsearch' );
return new \IPS\Content\Search\Results( array(), 0 );
}
/* Set results */
/* Set results */
$total = $return['hits']['total']['value'] ?? $return['hits']['total'];
return new \IPS\Content\Search\Results( array_map( function( $hit ) {
$indexData = $hit['_source'];
$indexData['index_permissions'] = implode( ',', $indexData['index_permissions'] );
$indexData['index_tags'] = $indexData['index_tags'] ? implode( ',', $indexData['index_tags'] ) : NULL;
return $indexData;
}, $return['hits']['hits'] ), $return['hits']['total'] <= \IPS\Settings::i()->search_index_maxresults ? $return['hits']['total'] : (int) \IPS\Settings::i()->search_index_maxresults );
}, $return['hits']['hits'] ), $total <= \IPS\Settings::i()->search_index_maxresults ? $total : (int) \IPS\Settings::i()->search_index_maxresults );
}
catch ( \Exception $e )
{