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

+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;
}
}