Version 5.0.0 beta 1
This commit is contained in:
1 parent
25ddeb65d6
commit
15c7beabc5
6736 files changed
+627902
-497943
No files matched your search
@@ -11,60 +11,82 @@
|
||||
namespace IPS\Content\Search\Elastic;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
|
||||
use Exception;
|
||||
use IPS\Application;
|
||||
use IPS\Content;
|
||||
use IPS\Content\Item;
|
||||
use IPS\Content\Search\Query as SearchQuery;
|
||||
use IPS\Content\Search\Results;
|
||||
use IPS\DateTime;
|
||||
use IPS\Db;
|
||||
use IPS\Http\Url;
|
||||
use IPS\IPS;
|
||||
use IPS\Log;
|
||||
use IPS\Member;
|
||||
use IPS\Member\Club;
|
||||
use IPS\Settings;
|
||||
use StdClass;
|
||||
use function count;
|
||||
use function defined;
|
||||
use function in_array;
|
||||
use function intval;
|
||||
use function is_array;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Elasticsearch Search Query
|
||||
*/
|
||||
class _Query extends \IPS\Content\Search\Query
|
||||
class Query extends SearchQuery
|
||||
{
|
||||
/**
|
||||
* @brief The server URL
|
||||
*/
|
||||
protected $url;
|
||||
protected Url $url;
|
||||
|
||||
/**
|
||||
* @brief Filters
|
||||
*/
|
||||
protected $filters = array();
|
||||
protected array $filters = array();
|
||||
|
||||
/**
|
||||
* @brief "Must Not" filter
|
||||
*/
|
||||
protected $mustNot = array();
|
||||
protected array $mustNot = array();
|
||||
|
||||
/**
|
||||
* @brief The sort clause
|
||||
*/
|
||||
protected $sort = NULL;
|
||||
protected array|string|null $sort = NULL;
|
||||
|
||||
/**
|
||||
* @brief The offset
|
||||
*/
|
||||
protected $offset = 0;
|
||||
protected int $offset = 0;
|
||||
|
||||
/**
|
||||
* @brief index_hidden statuses
|
||||
*/
|
||||
protected $hiddenStatuses = NULL;
|
||||
protected int|array|null $hiddenStatuses = NULL;
|
||||
|
||||
/**
|
||||
* @brief Item classes included
|
||||
*/
|
||||
protected $itemClasses = NULL;
|
||||
protected array|null $itemClasses = NULL;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param \IPS\Member $member The member performing the search
|
||||
* @param \IPS\Http\Url $url The server URL
|
||||
* @param Member $member The member performing the search
|
||||
* @param Url $url The server URL
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( \IPS\Member $member, \IPS\Http\Url $url )
|
||||
public function __construct( Member $member, Url $url )
|
||||
{
|
||||
parent::__construct( $member );
|
||||
$this->url = $url;
|
||||
@@ -73,16 +95,16 @@ class _Query extends \IPS\Content\Search\Query
|
||||
/**
|
||||
* @var bool Stores whether or not any content type needs to be limited by the latest comment
|
||||
*/
|
||||
protected $lastCommentMustBeTrue = false;
|
||||
protected bool $lastCommentMustBeTrue = false;
|
||||
|
||||
/**
|
||||
* Filter by multiple content types
|
||||
*
|
||||
* @param array $contentFilters Array of \IPS\Content\Search\ContentFilter objects
|
||||
* @param bool $type TRUE means only include results matching the filters, FALSE means exclude all results matching the filters
|
||||
* @return \IPS\Content\Search\Query (for daisy chaining)
|
||||
* @return Query (for daisy chaining)
|
||||
*/
|
||||
public function filterByContent( array $contentFilters, $type = TRUE )
|
||||
public function filterByContent( array $contentFilters, bool $type = TRUE ): static
|
||||
{
|
||||
$contentFilterConditions = array();
|
||||
if ( $type )
|
||||
@@ -212,14 +234,14 @@ class _Query extends \IPS\Content\Search\Query
|
||||
}
|
||||
|
||||
/* Put it together */
|
||||
if( \count( $conditions ) )
|
||||
if( count( $conditions ) )
|
||||
{
|
||||
$contentFilterConditions[] = Index::convertConditionsToQuery( $conditions );
|
||||
}
|
||||
}
|
||||
|
||||
/* Put them together */
|
||||
if ( \count( $contentFilterConditions ) > 1 )
|
||||
if ( count( $contentFilterConditions ) > 1 )
|
||||
{
|
||||
$this->filters[] = array(
|
||||
'bool' => array(
|
||||
@@ -229,14 +251,14 @@ class _Query extends \IPS\Content\Search\Query
|
||||
}
|
||||
elseif ( $type )
|
||||
{
|
||||
if( \count( $contentFilterConditions ) )
|
||||
if( count( $contentFilterConditions ) )
|
||||
{
|
||||
$this->filters[] = $contentFilterConditions[0];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if( \count( $contentFilterConditions ) )
|
||||
if( count( $contentFilterConditions ) )
|
||||
{
|
||||
$this->mustNot[] = $contentFilterConditions[0];
|
||||
}
|
||||
@@ -248,18 +270,18 @@ class _Query extends \IPS\Content\Search\Query
|
||||
/**
|
||||
* Filter by author
|
||||
*
|
||||
* @param \IPS\Member|int|array $author The author, or an array of author IDs
|
||||
* @return \IPS\Content\Search\Query (for daisy chaining)
|
||||
* @param Member|int|array $author The author, or an array of author IDs
|
||||
* @return Query (for daisy chaining)
|
||||
*/
|
||||
public function filterByAuthor( $author )
|
||||
public function filterByAuthor( Member|int|array $author ): static
|
||||
{
|
||||
if ( \is_array( $author ) )
|
||||
if ( is_array( $author ) )
|
||||
{
|
||||
$this->filters[] = array( 'terms' => array( 'index_author' => $author ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->filters[] = array( 'term' => array( 'index_author' => $author instanceof \IPS\Member ? $author->member_id : $author ) );
|
||||
$this->filters[] = array( 'term' => array( 'index_author' => $author instanceof Member ? $author->member_id : $author ) );
|
||||
}
|
||||
|
||||
return $this;
|
||||
@@ -268,10 +290,10 @@ class _Query extends \IPS\Content\Search\Query
|
||||
/**
|
||||
* Filter by club
|
||||
*
|
||||
* @param \IPS\Member\Club|int|array $club The club, or array of club IDs
|
||||
* @return \IPS\Content\Search\Query (for daisy chaining)
|
||||
* @param Club|int|array|null $club The club, or array of club IDs
|
||||
* @return Query (for daisy chaining)
|
||||
*/
|
||||
public function filterByClub( $club )
|
||||
public function filterByClub( Club|int|array|null $club ): static
|
||||
{
|
||||
if ( $club === NULL )
|
||||
{
|
||||
@@ -279,7 +301,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 )
|
||||
@@ -288,28 +310,12 @@ class _Query extends \IPS\Content\Search\Query
|
||||
else
|
||||
{
|
||||
$this->filters[] = array(
|
||||
'term' => array( 'index_club_id' => $club instanceof \IPS\Member\Club ? $club->id : $club )
|
||||
'term' => array( 'index_club_id' => $club instanceof 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 );
|
||||
}
|
||||
$this->customFiltering( TRUE );
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -317,10 +323,10 @@ class _Query extends \IPS\Content\Search\Query
|
||||
/**
|
||||
* Filter for profile
|
||||
*
|
||||
* @param \IPS\Member $member The member whose profile is being viewed
|
||||
* @return \IPS\Content\Search\Query (for daisy chaining)
|
||||
* @param Member $member The member whose profile is being viewed
|
||||
* @return Query (for daisy chaining)
|
||||
*/
|
||||
public function filterForProfile( \IPS\Member $member )
|
||||
public function filterForProfile( Member $member ): static
|
||||
{
|
||||
/* Filter by content they've posted or posts on their wall */
|
||||
$this->filters[] = array(
|
||||
@@ -328,71 +334,40 @@ class _Query extends \IPS\Content\Search\Query
|
||||
'should' => array(
|
||||
array(
|
||||
'term' => array( 'index_author' => $member->member_id )
|
||||
),
|
||||
array(
|
||||
'bool' => array(
|
||||
'filter' => array(
|
||||
array(
|
||||
'term' => array(
|
||||
'index_class' => 'IPS\core\Statuses\Status'
|
||||
)
|
||||
),
|
||||
array(
|
||||
'term' => array(
|
||||
'index_container_id' => $member->member_id
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
/* 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 );
|
||||
}
|
||||
$this->customFiltering( TRUE );
|
||||
|
||||
/* Return for daisy chaining */
|
||||
/* Return for daisy-chaining */
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the "more like this" Content object for search()
|
||||
* @param null
|
||||
* @param array|null $moreLikeThis
|
||||
*/
|
||||
protected $moreLikeThis = NULL;
|
||||
protected array|null $moreLikeThis = NULL;
|
||||
|
||||
/**
|
||||
* Filter by more like this
|
||||
* @param \IPS\Content\Searchable $object
|
||||
* @param Content $object
|
||||
* @return Query
|
||||
*/
|
||||
public function filterByMoreLikeThis( \IPS\Content\Searchable $object )
|
||||
public function filterByMoreLikeThis( Content $object ): static
|
||||
{
|
||||
$index = new \IPS\Content\Search\Elastic\Index( $this->url );
|
||||
$this->moreLikeThis = $index->getIndexId( ( $object instanceof \IPS\Content\Item and $object::$firstCommentRequired ) ? $object->firstComment() : $object );
|
||||
$index = new Index( $this->url );
|
||||
$this->moreLikeThis = $index->getIndexId( ( $object instanceof Item and $object::$firstCommentRequired ) ? $object->firstComment() : $object );
|
||||
|
||||
/* Some container types cannot be cached */
|
||||
$classes = [];
|
||||
$conditions = [];
|
||||
$noSimilarContentClasses = [];
|
||||
|
||||
foreach ( \IPS\Application::allExtensions( 'core', 'ContentRouter', FALSE ) as $object )
|
||||
foreach ( Application::allExtensions( 'core', 'ContentRouter', FALSE ) as $object )
|
||||
{
|
||||
$classes = array_merge( $object->classes, $classes );
|
||||
|
||||
@@ -428,7 +403,7 @@ class _Query extends \IPS\Content\Search\Query
|
||||
}
|
||||
}
|
||||
|
||||
if ( \count( $blockIds ) )
|
||||
if ( count( $blockIds ) )
|
||||
{
|
||||
$conditions[] = array(
|
||||
'bool' => array(
|
||||
@@ -444,7 +419,7 @@ class _Query extends \IPS\Content\Search\Query
|
||||
);
|
||||
}
|
||||
|
||||
if ( \count( $conditions ) )
|
||||
if ( count( $conditions ) )
|
||||
{
|
||||
foreach( $conditions as $condition )
|
||||
{
|
||||
@@ -455,7 +430,7 @@ class _Query extends \IPS\Content\Search\Query
|
||||
}
|
||||
|
||||
/* Prevent some things from being in similar content widget */
|
||||
if ( \count( $noSimilarContentClasses ) )
|
||||
if ( count( $noSimilarContentClasses ) )
|
||||
{
|
||||
$this->mustNot[] = array(
|
||||
'bool' => array(
|
||||
@@ -470,6 +445,8 @@ class _Query extends \IPS\Content\Search\Query
|
||||
|
||||
/* Only show non-hidden and approved items */
|
||||
$this->setHiddenFilter( static::HIDDEN_VISIBLE );
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -478,9 +455,9 @@ class _Query extends \IPS\Content\Search\Query
|
||||
* @param array $classes Container classes to exclude from results.
|
||||
* @param array $exclude Content classes to exclude from the filter. For cases where multiple content classes may have the same container class
|
||||
* such as Gallery images, comments and reviews.
|
||||
* @return \IPS\Content\Search\Query (for daisy chaining)
|
||||
* @return Query (for daisy chaining)
|
||||
*/
|
||||
public function filterByContainerClasses( $classes=array(), $exclude=array() )
|
||||
public function filterByContainerClasses( array $classes=array(), array $exclude=array() ): static
|
||||
{
|
||||
if( empty( $exclude ) )
|
||||
{
|
||||
@@ -519,10 +496,10 @@ class _Query extends \IPS\Content\Search\Query
|
||||
/**
|
||||
* Filter by item author
|
||||
*
|
||||
* @param \IPS\Member $author The author
|
||||
* @return \IPS\Content\Search\Query (for daisy chaining)
|
||||
* @param Member $author The author
|
||||
* @return Query (for daisy chaining)
|
||||
*/
|
||||
public function filterByItemAuthor( \IPS\Member $author )
|
||||
public function filterByItemAuthor( Member $author ): static
|
||||
{
|
||||
$this->filters[] = array(
|
||||
'term' => array( 'index_item_author' => $author->member_id )
|
||||
@@ -536,9 +513,9 @@ 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 $includeMembers Include content posted by members the user follows?
|
||||
* @return \IPS\Content\Search\Query (for daisy chaining)
|
||||
* @return Query (for daisy chaining)
|
||||
*/
|
||||
public function filterByFollowed( $includeContainers, $includeItems, $includeMembers )
|
||||
public function filterByFollowed( bool $includeContainers, bool $includeItems, bool $includeMembers ): static
|
||||
{
|
||||
$conditions = array();
|
||||
$followApps = $followAreas = $case = $containerCase = array();
|
||||
@@ -556,7 +533,7 @@ class _Query extends \IPS\Content\Search\Query
|
||||
else
|
||||
{
|
||||
$classes = array();
|
||||
foreach ( \IPS\Application::allExtensions( 'core', 'ContentRouter', FALSE ) as $object )
|
||||
foreach ( Application::allExtensions( 'core', 'ContentRouter', FALSE ) as $object )
|
||||
{
|
||||
$classes = array_merge( $object->classes, $classes );
|
||||
}
|
||||
@@ -565,7 +542,7 @@ class _Query extends \IPS\Content\Search\Query
|
||||
/* Loop them */
|
||||
foreach ( $classes as $class )
|
||||
{
|
||||
if( is_subclass_of( $class, 'IPS\Content\Followable' ) )
|
||||
if( IPS::classUsesTrait( $class, 'IPS\Content\Followable' ) )
|
||||
{
|
||||
$followApps[ $class::$application ] = $class::$application;
|
||||
$followArea = mb_strtolower( mb_substr( $class, mb_strrpos( $class, '\\' ) + 1 ) );
|
||||
@@ -607,13 +584,13 @@ class _Query extends \IPS\Content\Search\Query
|
||||
}
|
||||
|
||||
/* Get the stuff we follow */
|
||||
foreach( \IPS\Db::i()->select( '*', 'core_follow', array( 'follow_member_id=? AND ' . \IPS\Db::i()->in( 'follow_app', $followApps ) . ' AND ' . \IPS\Db::i()->in( 'follow_area', $followAreas ), $this->member->member_id ) ) as $follow )
|
||||
foreach( Db::i()->select( '*', 'core_follow', array( 'follow_member_id=? AND ' . Db::i()->in( 'follow_app', $followApps ) . ' AND ' . Db::i()->in( 'follow_area', $followAreas ), $this->member->member_id ) ) as $follow )
|
||||
{
|
||||
if( array_key_exists( $follow['follow_area'], $case ) )
|
||||
{
|
||||
$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'];
|
||||
}
|
||||
@@ -663,7 +640,7 @@ class _Query extends \IPS\Content\Search\Query
|
||||
}
|
||||
|
||||
/* Are we including content posted by followed members? */
|
||||
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' ) ) )
|
||||
if ( $includeMembers and $followed = iterator_to_array( 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(
|
||||
'terms' => array( 'index_author' => $followed )
|
||||
@@ -671,13 +648,13 @@ class _Query extends \IPS\Content\Search\Query
|
||||
}
|
||||
|
||||
/* Put it all together */
|
||||
if ( \count( $conditions ) )
|
||||
if ( count( $conditions ) )
|
||||
{
|
||||
$this->filters[] = array( 'bool' => array( 'should' => $conditions ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->filters[] = array( 'match_none' => new \StdClass );
|
||||
$this->filters[] = array( 'match_none' => new StdClass );
|
||||
}
|
||||
|
||||
/* And return */
|
||||
@@ -687,9 +664,9 @@ class _Query extends \IPS\Content\Search\Query
|
||||
/**
|
||||
* Filter by content the user has posted in
|
||||
*
|
||||
* @return \IPS\Content\Search\Query (for daisy chaining)
|
||||
* @return Query (for daisy chaining)
|
||||
*/
|
||||
public function filterByItemsIPostedIn()
|
||||
public function filterByItemsIPostedIn(): static
|
||||
{
|
||||
$this->filters[] = array(
|
||||
'term' => array(
|
||||
@@ -703,9 +680,9 @@ class _Query extends \IPS\Content\Search\Query
|
||||
* Filter by content the user has not read
|
||||
*
|
||||
* @note If applicable, it is more efficient to call filterByContent() before calling this method
|
||||
* @return \IPS\Content\Search\Query (for daisy chaining)
|
||||
* @return Query (for daisy chaining)
|
||||
*/
|
||||
public function filterByUnread()
|
||||
public function filterByUnread(): static
|
||||
{
|
||||
/* Work out what classes we need to examine */
|
||||
if ( $this->itemClasses !== NULL )
|
||||
@@ -715,7 +692,7 @@ class _Query extends \IPS\Content\Search\Query
|
||||
else
|
||||
{
|
||||
$classes = array();
|
||||
foreach ( \IPS\Application::allExtensions( 'core', 'ContentRouter', FALSE ) as $object )
|
||||
foreach ( Application::allExtensions( 'core', 'ContentRouter', FALSE ) as $object )
|
||||
{
|
||||
$classes = array_merge( $object->classes, $classes );
|
||||
}
|
||||
@@ -726,9 +703,9 @@ class _Query extends \IPS\Content\Search\Query
|
||||
$resetTimes = $this->member->markersResetTimes( NULL );
|
||||
foreach ( $classes as $class )
|
||||
{
|
||||
if( is_subclass_of( $class, 'IPS\Content\ReadMarkers' ) )
|
||||
if( IPS::classUsesTrait( $class, 'IPS\Content\ReadMarkers' ) )
|
||||
{
|
||||
$containerClass = ( $class::$containerNodeClass ) ? $class::$containerNodeClass : NULL;
|
||||
$containerClass = $class::$containerNodeClass ?? NULL;
|
||||
$classConditions = array();
|
||||
|
||||
/* Work out what classes this applies to - need to specify comment and review classes */
|
||||
@@ -779,21 +756,21 @@ 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 ) )
|
||||
$items = $this->member->markersItems( $application, $class::makeMarkerKey( $containerId ) );
|
||||
if ( count( $items ) )
|
||||
{
|
||||
foreach( $items as $mid => $mtime )
|
||||
{
|
||||
if ( $mtime > $timestamp )
|
||||
{
|
||||
/* 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
|
||||
in its 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 ] );
|
||||
@@ -838,7 +815,7 @@ class _Query extends \IPS\Content\Search\Query
|
||||
}
|
||||
|
||||
$notIn = array();
|
||||
if ( \count( $markers ) )
|
||||
if ( count( $markers ) )
|
||||
{
|
||||
$useIds = array_flip( $markers );
|
||||
|
||||
@@ -847,7 +824,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 )
|
||||
{
|
||||
@@ -860,16 +837,16 @@ 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 )
|
||||
foreach( Db::i()->select( $class::$databaseTable . '.' . $class::$databasePrefix . $class::$databaseColumnId. ' as _id, ' . $dateColumnExpression . ' as _date', $class::$databaseTable, Db::i()->in( $class::$databasePrefix . $class::$databaseColumnId, array_keys( $useIds ) ) ) as $row )
|
||||
{
|
||||
if ( isset( $useIds[ $row['_id'] ] ) )
|
||||
{
|
||||
if ( $useIds[ $row['_id'] ] >= $row['_date'] )
|
||||
{
|
||||
/* Still read */
|
||||
$notIn[] = \intval( $row['_id'] );
|
||||
$notIn[] = intval( $row['_id'] );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -881,7 +858,7 @@ class _Query extends \IPS\Content\Search\Query
|
||||
'filter' => $classConditions
|
||||
)
|
||||
);
|
||||
if ( \count( $notIn ) )
|
||||
if ( count( $notIn ) )
|
||||
{
|
||||
$_condition['bool']['must_not'] = array(
|
||||
'terms' => array(
|
||||
@@ -894,7 +871,7 @@ class _Query extends \IPS\Content\Search\Query
|
||||
}
|
||||
|
||||
/* Put it all together */
|
||||
if ( \count( $conditions ) )
|
||||
if ( count( $conditions ) )
|
||||
{
|
||||
$this->filters[] = array(
|
||||
'bool' => array(
|
||||
@@ -909,9 +886,9 @@ class _Query extends \IPS\Content\Search\Query
|
||||
/**
|
||||
* Filter by solved
|
||||
*
|
||||
* @return \IPS\Content\Search\Query (for daisy chaining)
|
||||
* @return Query (for daisy chaining)
|
||||
*/
|
||||
public function filterBySolved()
|
||||
public function filterBySolved(): static
|
||||
{
|
||||
$this->filters[] = array(
|
||||
'term' => array( 'index_item_solved' => 1 )
|
||||
@@ -923,9 +900,9 @@ class _Query extends \IPS\Content\Search\Query
|
||||
/**
|
||||
* Filter by Unsolved
|
||||
*
|
||||
* @return \IPS\Content\Search\Query (for daisy chaining)
|
||||
* @return Query (for daisy chaining)
|
||||
*/
|
||||
public function filterByUnsolved()
|
||||
public function filterByUnsolved(): static
|
||||
{
|
||||
$this->filters[] = array(
|
||||
'term' => array( 'index_item_solved' => 0 )
|
||||
@@ -937,11 +914,11 @@ class _Query extends \IPS\Content\Search\Query
|
||||
/**
|
||||
* Filter by start date
|
||||
*
|
||||
* @param \IPS\DateTime|NULL $start The start date (only results AFTER this date will be returned)
|
||||
* @param \IPS\DateTime|NULL $end The end date (only results BEFORE this date will be returned)
|
||||
* @return \IPS\Content\Search\Query (for daisy chaining)
|
||||
* @param DateTime|NULL $start The start date (only results AFTER this date will be returned)
|
||||
* @param DateTime|NULL $end The end date (only results BEFORE this date will be returned)
|
||||
* @return Query (for daisy chaining)
|
||||
*/
|
||||
public function filterByCreateDate( \IPS\DateTime $start = NULL, \IPS\DateTime $end = NULL )
|
||||
public function filterByCreateDate( DateTime|null $start = null, DateTime|null $end = null ): static
|
||||
{
|
||||
$range = array();
|
||||
|
||||
@@ -967,11 +944,11 @@ class _Query extends \IPS\Content\Search\Query
|
||||
/**
|
||||
* Filter by last updated date
|
||||
*
|
||||
* @param \IPS\DateTime|NULL $start The start date (only results AFTER this date will be returned)
|
||||
* @param \IPS\DateTime|NULL $end The end date (only results BEFORE this date will be returned)
|
||||
* @return \IPS\Content\Search\Query (for daisy chaining)
|
||||
* @param DateTime|NULL $start The start date (only results AFTER this date will be returned)
|
||||
* @param DateTime|NULL $end The end date (only results BEFORE this date will be returned)
|
||||
* @return Query (for daisy chaining)
|
||||
*/
|
||||
public function filterByLastUpdatedDate( \IPS\DateTime $start = NULL, \IPS\DateTime $end = NULL )
|
||||
public function filterByLastUpdatedDate( DateTime|null $start = null, DateTime|null $end = null ): static
|
||||
{
|
||||
$range = array();
|
||||
|
||||
@@ -998,9 +975,9 @@ class _Query extends \IPS\Content\Search\Query
|
||||
* Set hidden status
|
||||
*
|
||||
* @param int|array|NULL $statuses The statuses (see HIDDEN_ constants) or NULL for any
|
||||
* @return \IPS\Content\Search\Query (for daisy chaining)
|
||||
* @return Query (for daisy chaining)
|
||||
*/
|
||||
public function setHiddenFilter( $statuses )
|
||||
public function setHiddenFilter( int|array|null $statuses ): static
|
||||
{
|
||||
$this->hiddenStatuses = $statuses;
|
||||
return $this;
|
||||
@@ -1010,9 +987,9 @@ class _Query extends \IPS\Content\Search\Query
|
||||
* Set page
|
||||
*
|
||||
* @param int $page The page number
|
||||
* @return \IPS\Content\Search\Query (for daisy chaining)
|
||||
* @return Query (for daisy chaining)
|
||||
*/
|
||||
public function setPage( $page )
|
||||
public function setPage( int $page ): static
|
||||
{
|
||||
$this->offset = ( $page - 1 ) * $this->resultsToGet;
|
||||
|
||||
@@ -1023,9 +1000,9 @@ class _Query extends \IPS\Content\Search\Query
|
||||
* Set order
|
||||
*
|
||||
* @param int $order Order (see ORDER_ constants)
|
||||
* @return \IPS\Content\Search\Query (for daisy chaining)
|
||||
* @return Query (for daisy chaining)
|
||||
*/
|
||||
public function setOrder( $order )
|
||||
public function setOrder( int $order ): static
|
||||
{
|
||||
switch ( $order )
|
||||
{
|
||||
@@ -1062,16 +1039,16 @@ class _Query extends \IPS\Content\Search\Query
|
||||
/**
|
||||
* Debug by itemId, used for debugging purposes only
|
||||
* @note No permission checks run, do not use in production
|
||||
* @param $itemId
|
||||
* @return void
|
||||
* @param int $itemId
|
||||
* @return array
|
||||
*/
|
||||
public function debugByItemId( $itemId )
|
||||
public function debugByItemId( int $itemId ): array
|
||||
{
|
||||
$array = array(
|
||||
'query' => [
|
||||
'term' => [
|
||||
'index_item_id' => [
|
||||
'value' => \intval( $itemId )
|
||||
'value' => $itemId
|
||||
]
|
||||
]
|
||||
],
|
||||
@@ -1082,7 +1059,7 @@ class _Query extends \IPS\Content\Search\Query
|
||||
|
||||
$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();
|
||||
return Index::request( $this->url->setPath( $this->url->data[ Url::COMPONENT_PATH ] . '/_search' ) )->setHeaders( array( 'Content-Type' => 'application/json' ) )->get( $json )->decodeJson();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1092,25 +1069,25 @@ class _Query extends \IPS\Content\Search\Query
|
||||
* @param array|null $tags The tags to search for
|
||||
* @param int $method See \IPS\Content\Search\Query::TERM_* contants
|
||||
* @param string|null $operator If $term contains more than one word, determines if searching for both ("and") or any ("or") of those terms. NULL will go to admin-defined setting
|
||||
* @return \IPS\Content\Search\Results
|
||||
* @return Results
|
||||
*/
|
||||
public function search( $term = NULL, $tags = NULL, $method = 1, $operator = NULL )
|
||||
public function search( string|null $term = null, array|null $tags = null, int $method = 1, string|null $operator = null ): Results
|
||||
{
|
||||
/* 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 we're looking for more results than we can fetch, we don't need to ask ElasticSearch */
|
||||
if( ( $this->offset + $this->resultsToGet ) > Settings::i()->search_index_maxresults )
|
||||
{
|
||||
return new \IPS\Content\Search\Results( array(), 0 );
|
||||
return new Results( array(), 0 );
|
||||
}
|
||||
|
||||
$operator = $operator ?: \IPS\Settings::i()->search_default_operator;
|
||||
$operator = $operator ?: Settings::i()->search_default_operator;
|
||||
$must = array();
|
||||
$filters = $this->filters;
|
||||
|
||||
/* Set our conditions for this search */
|
||||
if ( $term !== NULL or ( \IPS\Settings::i()->tags_enabled AND $tags !== NULL ) )
|
||||
if ( $term !== NULL or ( Settings::i()->tags_enabled AND $tags !== NULL ) )
|
||||
{
|
||||
$searchConditions = array();
|
||||
$titleField = \IPS\Settings::i()->search_title_boost ? ( 'index_title^' . \intval( \IPS\Settings::i()->search_title_boost ) ) : 'index_title';
|
||||
$titleField = Settings::i()->search_title_boost ? ( 'index_title^' . intval( Settings::i()->search_title_boost ) ) : 'index_title';
|
||||
|
||||
/* Build the condition for the search term */
|
||||
if ( $term !== NULL )
|
||||
@@ -1141,7 +1118,7 @@ class _Query extends \IPS\Content\Search\Query
|
||||
'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 ) ) )
|
||||
'wildcard' => array( 'index_title' => ( Settings::i()->search_title_boost ? array( 'value' => $term, 'boost' => intval( Settings::i()->search_title_boost ) ) : array( 'value' => $term ) ) )
|
||||
),
|
||||
array(
|
||||
'wildcard' => array( 'index_content' => array( 'value' => $term ) )
|
||||
@@ -1165,7 +1142,7 @@ class _Query extends \IPS\Content\Search\Query
|
||||
}
|
||||
}
|
||||
/* Build the condition for the tags */
|
||||
if ( \IPS\Settings::i()->tags_enabled and $tags !== NULL )
|
||||
if ( Settings::i()->tags_enabled and $tags !== NULL )
|
||||
{
|
||||
$searchConditions[] = array(
|
||||
'bool' => array(
|
||||
@@ -1209,7 +1186,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 ) );
|
||||
}
|
||||
@@ -1219,14 +1196,14 @@ class _Query extends \IPS\Content\Search\Query
|
||||
}
|
||||
}
|
||||
|
||||
$searchUrl = $this->url->setPath( $this->url->data[ \IPS\Http\Url::COMPONENT_PATH ] . '/_search' );
|
||||
$searchUrl = $this->url->setPath( $this->url->data[ 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 ],
|
||||
'like' => [ '_index' => Settings::i()->search_elastic_index, '_id' => $this->moreLikeThis ],
|
||||
'min_term_freq' => 1,
|
||||
'max_query_terms' => 12,
|
||||
'min_word_length' => 3,
|
||||
@@ -1250,15 +1227,15 @@ class _Query extends \IPS\Content\Search\Query
|
||||
);
|
||||
|
||||
/* Add the time decay */
|
||||
if ( \IPS\Settings::i()->search_decay_factor and !$this->sort and ! $this->sort )
|
||||
if ( Settings::i()->search_decay_factor and !$this->sort )
|
||||
{
|
||||
$query = array(
|
||||
'function_score' => array(
|
||||
'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( Settings::i()->search_decay_days ) . 'd',
|
||||
'decay' => number_format( Settings::i()->search_decay_factor, 1, '.', '' ),
|
||||
'origin' => time(),
|
||||
)
|
||||
)
|
||||
@@ -1267,7 +1244,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 and ! $this->moreLikeThis )
|
||||
if ( Settings::i()->search_elastic_self_boost and $this->member->member_id and !$this->sort and ! $this->moreLikeThis )
|
||||
{
|
||||
$query = array(
|
||||
'function_score' => array(
|
||||
@@ -1277,8 +1254,8 @@ class _Query extends \IPS\Content\Search\Query
|
||||
'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, '.', '' )
|
||||
'param_memberId' => intval( $this->member->member_id ),
|
||||
'param_booster' => number_format( Settings::i()->search_elastic_self_boost, 1, '.', '' )
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -1299,30 +1276,30 @@ class _Query extends \IPS\Content\Search\Query
|
||||
$json = json_encode( $array, JSON_PARTIAL_OUTPUT_ON_ERROR );
|
||||
if ( $json === FALSE )
|
||||
{
|
||||
return new \IPS\Content\Search\Results( array(), 0 );
|
||||
return new Results( array(), 0 );
|
||||
}
|
||||
|
||||
/* Make the call! */
|
||||
$return = \IPS\Content\Search\Elastic\Index::request( $searchUrl )->setHeaders( array( 'Content-Type' => 'application/json' ) )->get( $json )->decodeJson();
|
||||
$return = Index::request( $searchUrl )->setHeaders( array( 'Content-Type' => 'application/json' ) )->get( $json )->decodeJson();
|
||||
if ( isset( $return['error'] ) )
|
||||
{
|
||||
\IPS\Log::log( print_r( array_merge( $array, ['error' => $return['error'] ] ), TRUE ), 'elasticsearch' );
|
||||
return new \IPS\Content\Search\Results( array(), 0 );
|
||||
Log::log( print_r( array_merge( $array, ['error' => $return['error'] ] ), TRUE ), 'elasticsearch' );
|
||||
return new Results( array(), 0 );
|
||||
}
|
||||
|
||||
/* Set results */
|
||||
$total = $return['hits']['total']['value'] ?? $return['hits']['total'];
|
||||
return new \IPS\Content\Search\Results( array_map( function( $hit ) {
|
||||
return new 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'] ), $total <= \IPS\Settings::i()->search_index_maxresults ? $total : (int) \IPS\Settings::i()->search_index_maxresults );
|
||||
}, $return['hits']['hits'] ), $total <= Settings::i()->search_index_maxresults ? $total : (int) Settings::i()->search_index_maxresults );
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
catch ( Exception $e )
|
||||
{
|
||||
\IPS\Log::log( $e, 'elasticsearch' );
|
||||
return new \IPS\Content\Search\Results( array(), 0 );
|
||||
Log::log( $e, 'elasticsearch' );
|
||||
return new Results( array(), 0 );
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user