Version 4.5.0
This commit is contained in:
1 parent
1a0c7fd3c2
commit
f79dcf067a
3791 files changed
+248032
-76372
No files matched your search
@@ -11,7 +11,7 @@
|
||||
namespace IPS\Content\Search;
|
||||
|
||||
/* 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;
|
||||
@@ -30,7 +30,7 @@ class _ApiResponse extends \IPS\Api\PaginatedResponse
|
||||
public function getOutput()
|
||||
{
|
||||
/* Get the query object from the stored select */
|
||||
$queryObject = $this->select[0];
|
||||
$queryObject = $this->iterator[0];
|
||||
|
||||
/* Set pagination */
|
||||
$queryObject->setLimit( $this->resultsPerPage );
|
||||
@@ -38,20 +38,44 @@ class _ApiResponse extends \IPS\Api\PaginatedResponse
|
||||
|
||||
/* Run query */
|
||||
$searchResults = $queryObject->search(
|
||||
$this->select[2],
|
||||
$this->select[3],
|
||||
$this->select[1] + \IPS\Content\Search\Query::TAGS_MATCH_ITEMS_ONLY,
|
||||
$this->select[4]
|
||||
$this->iterator[2],
|
||||
$this->iterator[3],
|
||||
$this->iterator[1] + \IPS\Content\Search\Query::TAGS_MATCH_ITEMS_ONLY,
|
||||
$this->iterator[4]
|
||||
);
|
||||
|
||||
$this->count = $searchResults->count( TRUE );
|
||||
|
||||
/* Search tracking, only if there's a term for a non-member search and we're on the first page */
|
||||
if( \IPS\Request::i()->q AND $this->page == 1 AND ( !isset( \IPS\Request::i()->doNotTrack ) OR \IPS\Request::i()->doNotTrack != 1 ) )
|
||||
{
|
||||
$memberPermissions = $this->authorizedMember;
|
||||
|
||||
if( !$this->authorizedMember AND isset( \IPS\Request::i()->search_as ) )
|
||||
{
|
||||
$memberPermissions = \IPS\Member::load( \IPS\Request::i()->search_as );
|
||||
|
||||
if( !$memberPermissions->member_id )
|
||||
{
|
||||
$memberPermissions = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
\IPS\Db::i()->insert( 'core_statistics', array(
|
||||
'type' => 'search',
|
||||
'time' => time(),
|
||||
'value_4' => \IPS\Request::i()->q,
|
||||
'value_2' => $this->count,
|
||||
'extra_data' => $memberPermissions ? md5( $memberPermissions->email . $memberPermissions->joined ) : md5( 'rest-api' ) // Intentionally anonymized
|
||||
) );
|
||||
}
|
||||
|
||||
$results = array();
|
||||
foreach ( $searchResults as $result )
|
||||
{
|
||||
$resultData = $result->asArray();
|
||||
$indexClass = $resultData['indexData']['index_class'];
|
||||
$itemClass = ( in_array( 'IPS\Content\Comment', class_parents( $indexClass ) ) ) ? $indexClass::$itemClass : $indexClass;
|
||||
$itemClass = ( \in_array( 'IPS\Content\Comment', class_parents( $indexClass ) ) ) ? $indexClass::$itemClass : $indexClass;
|
||||
|
||||
/* Container details */
|
||||
$containerUrl = NULL;
|
||||
@@ -78,7 +102,7 @@ class _ApiResponse extends \IPS\Api\PaginatedResponse
|
||||
}
|
||||
else
|
||||
{
|
||||
$repCount = count( $resultData['reputationData'] );
|
||||
$repCount = \count( $resultData['reputationData'] );
|
||||
}
|
||||
|
||||
$commentCount = NULL;
|
||||
@@ -99,9 +123,9 @@ class _ApiResponse extends \IPS\Api\PaginatedResponse
|
||||
{
|
||||
$reviewCount = $resultData['itemData'][ $itemClass::$databasePrefix . $itemClass::$databaseColumnMap['num_reviews'] ];
|
||||
}
|
||||
|
||||
|
||||
$results[] = array(
|
||||
'title' => $resultData['itemData'][ $itemClass::$databasePrefix . $itemClass::$databaseColumnMap['title'] ],
|
||||
'title' => ( isset( $itemClass::$databaseColumnMap['title'] ) and isset( $resultData['itemData'][ $itemClass::$databasePrefix . $itemClass::$databaseColumnMap['title'] ] ) ) ? $resultData['itemData'][ $itemClass::$databasePrefix . $itemClass::$databaseColumnMap['title'] ] : $resultData['indexData']['index_title'],
|
||||
'content' => $resultData['indexData']['index_content'],
|
||||
'class' => $indexClass,
|
||||
'objectId' => $resultData['indexData']['index_object_id'],
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace IPS\Content\Search;
|
||||
|
||||
/* 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;
|
||||
@@ -37,6 +37,12 @@ class _ContentFilter
|
||||
* @note NULL means not to filter by container classes
|
||||
*/
|
||||
public $containerClasses = NULL;
|
||||
|
||||
/**
|
||||
* @brief Classes to include when filtering by container classes
|
||||
* @note NULL means no special overrides
|
||||
*/
|
||||
public $containerClassExclusions = NULL;
|
||||
|
||||
/**
|
||||
* @brief Container ID filter
|
||||
@@ -100,7 +106,7 @@ class _ContentFilter
|
||||
* @param bool $includeItems Include items in results?
|
||||
* @param bool $includeComments Include comments in results?
|
||||
* @param bool $includeReviews Include reviews in results?
|
||||
* @return \IPS\Content\Search\ContentFilter
|
||||
* @return static
|
||||
*/
|
||||
public static function init( $itemClass, $includeItems=TRUE, $includeComments=TRUE, $includeReviews=TRUE )
|
||||
{
|
||||
@@ -130,7 +136,7 @@ class _ContentFilter
|
||||
* init() adds in review classes, comment classes, etc. But if we want to filter based on just the comment class, we need this method
|
||||
*
|
||||
* @param string $class The class
|
||||
* @return \IPS\Content\Search\ContentFilter
|
||||
* @return static
|
||||
*/
|
||||
public static function initWithSpecificClass( $class )
|
||||
{
|
||||
@@ -144,7 +150,7 @@ class _ContentFilter
|
||||
* Only include results in containers
|
||||
*
|
||||
* @param array $ids Acceptable container IDs
|
||||
* @return \IPS\Content\Search\ContentFilter (for daisy chaining)
|
||||
* @return static (for daisy chaining)
|
||||
*/
|
||||
public function onlyInContainers( array $ids )
|
||||
{
|
||||
@@ -158,7 +164,7 @@ class _ContentFilter
|
||||
* Exclude results in containers
|
||||
*
|
||||
* @param array $ids Acceptable container IDs
|
||||
* @return \IPS\Content\Search\ContentFilter (for daisy chaining)
|
||||
* @return static (for daisy chaining)
|
||||
*/
|
||||
public function excludeInContainers( array $ids )
|
||||
{
|
||||
@@ -172,7 +178,7 @@ class _ContentFilter
|
||||
* Only include results in items
|
||||
*
|
||||
* @param array $ids Acceptable item IDs
|
||||
* @return \IPS\Content\Search\ContentFilter (for daisy chaining)
|
||||
* @return static (for daisy chaining)
|
||||
*/
|
||||
public function onlyInItems( array $ids )
|
||||
{
|
||||
@@ -186,7 +192,7 @@ class _ContentFilter
|
||||
* Only include results with specific IDs
|
||||
*
|
||||
* @param array $ids Acceptable object IDs
|
||||
* @return \IPS\Content\Search\ContentFilter (for daisy chaining)
|
||||
* @return static (for daisy chaining)
|
||||
*/
|
||||
public function onlyInIds( array $ids )
|
||||
{
|
||||
@@ -200,7 +206,7 @@ class _ContentFilter
|
||||
* Exclude results in items
|
||||
*
|
||||
* @param array $ids Acceptable container IDs
|
||||
* @return \IPS\Content\Search\ContentFilter (for daisy chaining)
|
||||
* @return static (for daisy chaining)
|
||||
*/
|
||||
public function excludeInItems( array $ids )
|
||||
{
|
||||
@@ -214,7 +220,7 @@ class _ContentFilter
|
||||
* Set minimum number of comments
|
||||
*
|
||||
* @param int $minimumComments The minimum number of comments
|
||||
* @return \IPS\Content\Search\ContentFilter (for daisy chaining)
|
||||
* @return static (for daisy chaining)
|
||||
*/
|
||||
public function minimumComments( $minimumComments )
|
||||
{
|
||||
@@ -227,7 +233,7 @@ class _ContentFilter
|
||||
* Set minimum number of reviews
|
||||
*
|
||||
* @param int $minimumReviews The minimum number of reviews
|
||||
* @return \IPS\Content\Search\ContentFilter (for daisy chaining)
|
||||
* @return static (for daisy chaining)
|
||||
*/
|
||||
public function minimumReviews( $minimumReviews )
|
||||
{
|
||||
@@ -240,7 +246,7 @@ class _ContentFilter
|
||||
* Set minimum number of views
|
||||
*
|
||||
* @param int $minimumViews The minimum number of views
|
||||
* @return \IPS\Content\Search\ContentFilter (for daisy chaining)
|
||||
* @return static (for daisy chaining)
|
||||
*/
|
||||
public function minimumViews( $minimumViews )
|
||||
{
|
||||
@@ -252,7 +258,7 @@ class _ContentFilter
|
||||
/**
|
||||
* Only include results which are the first comment on an item requiring a comment
|
||||
*
|
||||
* @return \IPS\Content\Search\ContentFilter (for daisy chaining)
|
||||
* @return static (for daisy chaining)
|
||||
*/
|
||||
public function onlyFirstComment()
|
||||
{
|
||||
@@ -264,7 +270,7 @@ class _ContentFilter
|
||||
/**
|
||||
* Only include results which are the last comment on an item requiring a comment
|
||||
*
|
||||
* @return \IPS\Content\Search\ContentFilter (for daisy chaining)
|
||||
* @return static (for daisy chaining)
|
||||
*/
|
||||
public function onlyLastComment()
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -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 ),
|
||||
)
|
||||
);
|
||||
|
||||
@@ -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 )
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace IPS\Content\Search;
|
||||
|
||||
/* 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;
|
||||
@@ -112,17 +112,30 @@ abstract class _Index extends \IPS\Patterns\Singleton
|
||||
*/
|
||||
public function indexSingleItem( \IPS\Content\Searchable $object )
|
||||
{
|
||||
/* It is possible for some items to not have a valid URL */
|
||||
try
|
||||
{
|
||||
if( !$url = (string) $object->url() )
|
||||
{
|
||||
throw new \LogicException;
|
||||
}
|
||||
}
|
||||
catch( \LogicException $e )
|
||||
{
|
||||
$url = '';
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$idColumn = $object::$databaseColumnId;
|
||||
if ( isset( $object::$commentClass ) AND is_subclass_of( $object::$commentClass, 'IPS\Content\Searchable' ) )
|
||||
{
|
||||
\IPS\Task::queue( 'core', 'IndexSingleItem', array( 'class' => $object::$commentClass, 'id' => $object->$idColumn, 'title' => $object->mapped('title'), 'url' => (string) $object->url() ), 5, TRUE );
|
||||
\IPS\Task::queue( 'core', 'IndexSingleItem', array( 'class' => $object::$commentClass, 'id' => $object->$idColumn, 'title' => $object->mapped('title'), 'url' => $url ), 5, TRUE );
|
||||
}
|
||||
|
||||
if ( isset( $object::$reviewClass ) AND is_subclass_of( $object::$reviewClass, 'IPS\Content\Searchable' ) )
|
||||
{
|
||||
\IPS\Task::queue( 'core', 'IndexSingleItem', array( 'class' => $object::$reviewClass, 'id' => $object->$idColumn, 'title' => $object->mapped('title'), 'url' => (string) $object->url() ), 5, TRUE );
|
||||
\IPS\Task::queue( 'core', 'IndexSingleItem', array( 'class' => $object::$reviewClass, 'id' => $object->$idColumn, 'title' => $object->mapped('title'), 'url' => $url ), 5, TRUE );
|
||||
}
|
||||
}
|
||||
catch( \OutOfRangeException $e ) {}
|
||||
@@ -137,7 +150,7 @@ abstract class _Index extends \IPS\Patterns\Singleton
|
||||
public function indexData( \IPS\Content\Searchable $object )
|
||||
{
|
||||
/* Init */
|
||||
$class = get_class( $object );
|
||||
$class = \get_class( $object );
|
||||
$idColumn = $class::$databaseColumnId;
|
||||
$tags = ( $object instanceof \IPS\Content\Tags and \IPS\Settings::i()->tags_enabled ) ? implode( ',', array_filter( $object->tags() ) ) : NULL;
|
||||
$prefix = ( $object instanceof \IPS\Content\Tags and \IPS\Settings::i()->tags_enabled ) ? $object->prefix() : NULL;
|
||||
@@ -277,7 +290,7 @@ abstract class _Index extends \IPS\Patterns\Singleton
|
||||
}
|
||||
|
||||
/* Take the HTML out of the content */
|
||||
$content = trim( str_replace( chr(0xC2) . chr(0xA0), ' ', strip_tags( preg_replace( "/(<br(?: \/)?>|<\/p>)/i", ' ', preg_replace( "#<blockquote(?:[^>]+?)>.+?(?<!<blockquote)</blockquote>#s", " ", preg_replace( "#<script(.*?)>(.*)</script>#uis", "", ' ' . $content . ' ' ) ) ) ) ) );
|
||||
$content = trim( str_replace( \chr(0xC2) . \chr(0xA0), ' ', strip_tags( preg_replace( "/(<br(?: \/)?>|<\/p>)/i", ' ', preg_replace( "#<blockquote(?:[^>]+?)>.+?(?<!<blockquote)</blockquote>#s", " ", preg_replace( "#<script(.*?)>(.*)</script>#uis", "", ' ' . $content . ' ' ) ) ) ) ) );
|
||||
|
||||
/* Work out the hidden status */
|
||||
$hiddenStatus = $object->hidden();
|
||||
@@ -289,6 +302,10 @@ abstract class _Index extends \IPS\Patterns\Singleton
|
||||
{
|
||||
$hiddenStatus = 0;
|
||||
}
|
||||
if ( $hiddenStatus === -3 )
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Get the item index ID */
|
||||
$itemIndexId = NULL;
|
||||
@@ -363,25 +380,35 @@ abstract class _Index extends \IPS\Patterns\Singleton
|
||||
$clubId = $container->{$container::clubIdColumn()};
|
||||
}
|
||||
|
||||
/* Work out the container class */
|
||||
if( $object instanceof \IPS\Content\Item )
|
||||
{
|
||||
$containerClass = ( $object->searchIndexContainerClass() ) ? \get_class( $object->searchIndexContainerClass() ) : NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
$containerClass = ( $object->item()->searchIndexContainerClass() ) ? \get_class( $object->item()->searchIndexContainerClass() ) : NULL;
|
||||
}
|
||||
|
||||
/* Return */
|
||||
return array(
|
||||
'index_class' => $class,
|
||||
'index_object_id' => $object->$idColumn,
|
||||
'index_item_id' => ( $object instanceof \IPS\Content\Item ) ? $object->$idColumn : $object->mapped('item'),
|
||||
'index_container_class' => ( $object instanceof \IPS\Content\Item ) ? ( $object->searchIndexContainerClass() ? get_class( $object->searchIndexContainerClass() ) : NULL ) : get_class( $object->item()->searchIndexContainerClass() ),
|
||||
'index_container_class' => $containerClass,
|
||||
'index_container_id' => ( $object instanceof \IPS\Content\Item ) ? (int) $object->searchIndexContainer() : (int) $object->item()->mapped('container'),
|
||||
'index_title' => $title,
|
||||
'index_content' => $content,
|
||||
'index_permissions' => $object->searchIndexPermissions(),
|
||||
'index_date_created' => intval( $object->mapped('date') ),
|
||||
'index_date_updated' => intval( $dateUpdated ),
|
||||
'index_date_commented' => intval( $dateCommented ),
|
||||
'index_date_created' => \intval( $object->mapped('date') ),
|
||||
'index_date_updated' => \intval( $dateUpdated ),
|
||||
'index_date_commented' => \intval( $dateCommented ),
|
||||
'index_author' => (int) $object->mapped('author'),
|
||||
'index_tags' => $tags,
|
||||
'index_prefix' => $prefix,
|
||||
'index_hidden' => $hiddenStatus,
|
||||
'index_item_index_id' => $itemIndexId,
|
||||
'index_item_author' => intval( ( $object instanceof \IPS\Content\Item ) ? $object->mapped('author') : $object->item()->mapped('author') ),
|
||||
'index_item_author' => \intval( ( $object instanceof \IPS\Content\Item ) ? $object->mapped('author') : $object->item()->mapped('author') ),
|
||||
'index_is_last_comment' => $isLastComment,
|
||||
'index_club_id' => $clubId,
|
||||
'index_class_type_id_hash' => md5( $class . ':' . $object->$idColumn )
|
||||
@@ -397,19 +424,6 @@ abstract class _Index extends \IPS\Patterns\Singleton
|
||||
*/
|
||||
abstract public function index( \IPS\Content\Searchable $object );
|
||||
|
||||
/**
|
||||
* 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 )
|
||||
{
|
||||
// Do nothing by default
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear out any tasks associated with the search index method
|
||||
*
|
||||
@@ -523,9 +537,10 @@ abstract class _Index extends \IPS\Patterns\Singleton
|
||||
*
|
||||
* @param string $class The class
|
||||
* @param int|NULL $indexItemId The index item ID
|
||||
* @param int|NULL $ignoreId ID to ignore because it is being removed
|
||||
* @return void
|
||||
*/
|
||||
abstract public function resetLastComment( $class, $indexItemId );
|
||||
abstract public function resetLastComment( $class, $indexItemId, $ignoreId = NULL );
|
||||
|
||||
/**
|
||||
* Given a list of item index IDs, return the ones that a given member has participated in
|
||||
@@ -539,11 +554,12 @@ abstract class _Index extends \IPS\Patterns\Singleton
|
||||
/**
|
||||
* 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 \IPS\Member $member The member
|
||||
* @param array $hashes Hashes
|
||||
* @param \IPS\Member $member The member
|
||||
* @param int|NULL $limit Number of results to return
|
||||
* @return array
|
||||
*/
|
||||
abstract public function hashesWithPermission( array $hashes, \IPS\Member $member );
|
||||
abstract public function hashesWithPermission( array $hashes, \IPS\Member $member, $limit = NULL );
|
||||
|
||||
/**
|
||||
* Get timestamp of oldest thing in index
|
||||
@@ -562,4 +578,14 @@ abstract class _Index extends \IPS\Patterns\Singleton
|
||||
{
|
||||
return $terms;
|
||||
}
|
||||
|
||||
/**
|
||||
* Supports filtering by views?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function supportViewFiltering()
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace IPS\Content\Search\Mysql;
|
||||
|
||||
/* 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;
|
||||
@@ -56,7 +56,7 @@ class _Index extends \IPS\Content\Search\Index
|
||||
{
|
||||
/* Get the index data */
|
||||
$indexData = $this->indexData( $object );
|
||||
$class = get_class( $object );
|
||||
$class = \get_class( $object );
|
||||
|
||||
/* If we got the data... */
|
||||
if( $indexData )
|
||||
@@ -167,7 +167,7 @@ class _Index extends \IPS\Content\Search\Index
|
||||
}
|
||||
|
||||
/* Add add them back if we have any */
|
||||
if ( count( $tags ) )
|
||||
if ( \count( $tags ) )
|
||||
{
|
||||
foreach( $tags as $tag )
|
||||
{
|
||||
@@ -229,7 +229,7 @@ class _Index extends \IPS\Content\Search\Index
|
||||
public function getIndexId( \IPS\Content\Searchable $object )
|
||||
{
|
||||
$databaseColumnId = $object::$databaseColumnId;
|
||||
return \IPS\Db::i()->select( 'index_id', 'core_search_index', array( 'index_class=? AND index_object_id=?', get_class( $object ),$object->$databaseColumnId ) )->first();
|
||||
return \IPS\Db::i()->select( 'index_id', 'core_search_index', array( 'index_class=? AND index_object_id=?', \get_class( $object ),$object->$databaseColumnId ) )->first();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -240,7 +240,7 @@ class _Index extends \IPS\Content\Search\Index
|
||||
*/
|
||||
public function removeFromSearchIndex( \IPS\Content\Searchable $object )
|
||||
{
|
||||
$class = get_class( $object );
|
||||
$class = \get_class( $object );
|
||||
$idColumn = $class::$databaseColumnId;
|
||||
|
||||
/* Tags */
|
||||
@@ -266,7 +266,7 @@ class _Index extends \IPS\Content\Search\Index
|
||||
|
||||
try
|
||||
{
|
||||
$this->resetLastComment( $classes, $object->mapped('item') );
|
||||
$this->resetLastComment( $classes, $object->mapped('item'), $object->$idColumn );
|
||||
}
|
||||
catch( \Exception $ex ) { }
|
||||
|
||||
@@ -300,6 +300,41 @@ class _Index extends \IPS\Content\Search\Index
|
||||
\IPS\Db::i()->delete( 'core_search_index', array( 'index_class=? AND index_item_id=?', $reviewClass, $object->$idColumn ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 )
|
||||
{
|
||||
/* Tags */
|
||||
$this->_deleteTagsFromIndex( $class, $id );
|
||||
|
||||
\IPS\Db::i()->delete( 'core_search_index', array( 'index_class=? AND index_object_id=?', $class, $id ) );
|
||||
|
||||
/* If this was a comment, we really need to reset the index_is_last_comment flag if it was set */
|
||||
if ( is_subclass_of( $class, 'IPS\Content\Item' ) )
|
||||
{
|
||||
\IPS\Db::i()->delete( 'core_search_index_item_map', array( 'index_class=? AND index_item_id=?', $class, $id ) );
|
||||
}
|
||||
|
||||
if ( isset( $class::$commentClass ) )
|
||||
{
|
||||
$commentClass = $class::$commentClass;
|
||||
$this->_deleteTagsFromIndex( $commentClass, $id );
|
||||
\IPS\Db::i()->delete( 'core_search_index', array( 'index_class=? AND index_item_id=?', $commentClass, $id ) );
|
||||
}
|
||||
|
||||
if ( isset( $class::$reviewClass ) )
|
||||
{
|
||||
$reviewClass = $class::$reviewClass;
|
||||
$this->_deleteTagsFromIndex( $reviewClass, $id );
|
||||
\IPS\Db::i()->delete( 'core_search_index', array( 'index_class=? AND index_item_id=?', $reviewClass, $id ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the index IDs associated with this class and $id
|
||||
@@ -319,7 +354,7 @@ class _Index extends \IPS\Content\Search\Index
|
||||
$ids = FALSE;
|
||||
}
|
||||
|
||||
if ( is_array( $ids ) and count( $ids ) < 1000 )
|
||||
if ( \is_array( $ids ) and \count( $ids ) < 1000 )
|
||||
{
|
||||
\IPS\Db::i()->delete( 'core_search_index_tags', array( \IPS\Db::i()->in( 'index_id', $ids ) ) );
|
||||
}
|
||||
@@ -328,6 +363,52 @@ class _Index extends \IPS\Content\Search\Index
|
||||
\IPS\Db::i()->delete( 'core_search_index_tags', array( 'index_id IN( ? )', \IPS\Db::i()->select( 'index_id', 'core_search_index', array( 'index_class=? AND index_item_id=?', $class, $id ) ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all content for a specific application from the index (for example, when uninstalling).
|
||||
*
|
||||
* @param \IPS\Application $application The application
|
||||
* @return void
|
||||
*/
|
||||
public function removeApplicationContent( \IPS\Application $application )
|
||||
{
|
||||
$classes = array();
|
||||
|
||||
foreach ( $application->extensions( 'core', 'ContentRouter' ) as $router )
|
||||
{
|
||||
foreach( $router->classes AS $class )
|
||||
{
|
||||
if ( is_subclass_of( $class, 'IPS\Content\Searchable' ) )
|
||||
{
|
||||
$classes[] = $class;
|
||||
|
||||
if ( isset( $class::$commentClass ) )
|
||||
{
|
||||
$commentClass = $class::$commentClass;
|
||||
if ( is_subclass_of( $commentClass, 'IPS\Content\Searchable' ) )
|
||||
{
|
||||
$classes[] = $commentClass;
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $class::$reviewClass ) )
|
||||
{
|
||||
$reviewClass = $class::$reviewClass;
|
||||
if ( is_subclass_of( $reviewClass, 'IPS\Content\Searchable' ) )
|
||||
{
|
||||
$classes[] = $reviewClass;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$where = array( \IPS\Db::i()->in( 'index_class', $classes ) );
|
||||
|
||||
\IPS\Db::i()->delete( 'core_search_index_item_map', $where );
|
||||
\IPS\Db::i()->delete( 'core_search_index_tags', array( 'index_id IN( ? )', \IPS\Db::i()->select( 'index_id', 'core_search_index', $where ) ) );
|
||||
\IPS\Db::i()->delete( 'core_search_index', $where );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all content for a classs
|
||||
@@ -394,7 +475,7 @@ class _Index extends \IPS\Content\Search\Index
|
||||
{
|
||||
$update['index_container_id'] = $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
|
||||
{
|
||||
@@ -415,7 +496,7 @@ class _Index extends \IPS\Content\Search\Index
|
||||
$update['index_item_author'] = $newItemAuthorId;
|
||||
}
|
||||
|
||||
if ( count( $update ) )
|
||||
if ( \count( $update ) )
|
||||
{
|
||||
\IPS\Db::i()->update( 'core_search_index', $update, $where );
|
||||
}
|
||||
@@ -423,7 +504,7 @@ class _Index extends \IPS\Content\Search\Index
|
||||
{
|
||||
$addAuthorToPermissionsWhere = $where;
|
||||
$addAuthorToPermissionsWhere[] = array( 'index_author<>0' );
|
||||
\IPS\Db::i()->update( 'core_search_index', "index_permissions = CONCAT( index_permissions, ',m.', index_author )", $addAuthorToPermissionsWhere );
|
||||
\IPS\Db::i()->update( 'core_search_index', "index_permissions = CONCAT( index_permissions, ',m', index_author )", $addAuthorToPermissionsWhere );
|
||||
}
|
||||
|
||||
if ( $newHiddenStatus !== NULL )
|
||||
@@ -486,8 +567,8 @@ class _Index extends \IPS\Content\Search\Index
|
||||
{
|
||||
$prefix = \IPS\Db::i()->prefix;
|
||||
|
||||
\IPS\Db::i()->query( "DELETE {$prefix}core_search_index_item_map FROM {$prefix}core_search_index_item_map, {$prefix}core_search_index WHERE {$prefix}core_search_index_item_map.index_item_id={$prefix}core_search_index.index_item_id AND index_date_updated < " . $cutoff->getTimestamp() );
|
||||
\IPS\Db::i()->query( "DELETE {$prefix}core_search_index_tags FROM {$prefix}core_search_index_tags, {$prefix}core_search_index WHERE {$prefix}core_search_index_tags.index_id={$prefix}core_search_index.index_id AND index_date_updated < " . $cutoff->getTimestamp() );
|
||||
\IPS\Db::i()->delete( array( 'core_search_index_item_map', 'core_search_index' ), array( "core_search_index_item_map.index_item_id=core_search_index.index_item_id AND (core_search_index.index_id=core_search_index.index_item_index_id AND index_date_updated < ?)", $cutoff->getTimestamp() ), NULL, NULL, NULL, 'core_search_index_item_map' );
|
||||
\IPS\Db::i()->delete( array( 'core_search_index_tags', 'core_search_index' ), array( "core_search_index_tags.index_id=core_search_index.index_id AND (core_search_index.index_id=core_search_index.index_item_index_id AND index_date_updated < ?)", $cutoff->getTimestamp() ), NULL, NULL, NULL, 'core_search_index_tags' );
|
||||
\IPS\Db::i()->delete( 'core_search_index', array( 'index_date_updated < ?', $cutoff->getTimestamp() ) );
|
||||
}
|
||||
else
|
||||
@@ -503,9 +584,10 @@ 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 )
|
||||
{
|
||||
\IPS\Db::i()->update( 'core_search_index', array( 'index_is_last_comment' => 0 ), array( \IPS\Db::i()->in( 'index_class', $classes ) . ' AND index_item_id=?', $indexItemId ) );
|
||||
|
||||
@@ -537,14 +619,15 @@ class _Index extends \IPS\Content\Search\Index
|
||||
*
|
||||
* @param array $itemIndexIds Item index IDs
|
||||
* @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 )
|
||||
{
|
||||
return iterator_to_array( \IPS\Db::i()->select( 'index_class_type_id_hash', array( 'core_search_index', 'si' ), array(
|
||||
array( "( si.index_permissions = '*' OR " . \IPS\Db::i()->findInSet( 'si.index_permissions', \IPS\Member::loggedIn()->permissionArray() ) . ' AND si.index_hidden=0 )' ),
|
||||
array( \IPS\Db::i()->in( 'si.index_class_type_id_hash', $hashes ) )
|
||||
) )->setKeyField('index_class_type_id_hash') );
|
||||
), NULL, $limit )->setKeyField('index_class_type_id_hash') );
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace IPS\Content\Search\Mysql;
|
||||
|
||||
/* 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;
|
||||
@@ -72,7 +72,13 @@ class _Query extends \IPS\Content\Search\Query
|
||||
* @see filterByItemsIPostedIn()
|
||||
*/
|
||||
protected $filterByItemsIPostedIn = FALSE;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Filter by unread items?
|
||||
* @see filterByUnread()
|
||||
*/
|
||||
protected $filterByUnread = FALSE;
|
||||
|
||||
/**
|
||||
* Filter by multiple content types
|
||||
*
|
||||
@@ -100,7 +106,7 @@ class _Query extends \IPS\Content\Search\Query
|
||||
}
|
||||
|
||||
/* Set the class */
|
||||
if ( count( $filter->classes ) > 1 )
|
||||
if ( \count( $filter->classes ) > 1 )
|
||||
{
|
||||
$clause[] = \IPS\Db::i()->in( 'index_class', $filter->classes );
|
||||
}
|
||||
@@ -134,7 +140,14 @@ class _Query extends \IPS\Content\Search\Query
|
||||
/* Are we excluding certain container classes? */
|
||||
if( $filter->containerClasses !== NULL )
|
||||
{
|
||||
$clause[] = \IPS\Db::i()->in( 'index_container_class', $filter->containerClasses );
|
||||
if( $filter->containerClassExclusions !== NULL )
|
||||
{
|
||||
$clause[] = '(' . \IPS\Db::i()->in( 'index_container_class', $filter->containerClasses ) . ' OR ' . \IPS\Db::i()->in( 'index_class', $filter->containerClassExclusions ) . ')';
|
||||
}
|
||||
else
|
||||
{
|
||||
$clause[] = \IPS\Db::i()->in( 'index_container_class', $filter->containerClasses );
|
||||
}
|
||||
}
|
||||
|
||||
/* Set the item IDs */
|
||||
@@ -157,19 +170,19 @@ class _Query extends \IPS\Content\Search\Query
|
||||
if ( $filter->minimumComments )
|
||||
{
|
||||
$this->select[ $class::$databaseTable . '_comments' ] = $class::$databaseTable . '.' . $class::$databasePrefix . $class::$databaseColumnMap['num_comments'];
|
||||
$clause[] = $class::$databaseTable . '.' . $class::$databasePrefix . $class::$databaseColumnMap['num_comments'] . '>=' . intval( $filter->minimumComments );
|
||||
$clause[] = $class::$databaseTable . '.' . $class::$databasePrefix . $class::$databaseColumnMap['num_comments'] . '>=' . \intval( $filter->minimumComments );
|
||||
}
|
||||
|
||||
if ( $filter->minimumReviews )
|
||||
{
|
||||
$this->select[ $class::$databaseTable . '_reviews' ] = $class::$databaseTable . '.' . $class::$databasePrefix . $class::$databaseColumnMap['num_reviews'];
|
||||
$clause[] = $class::$databaseTable . '.' . $class::$databasePrefix . $class::$databaseColumnMap['num_reviews'] . '>=' . intval( $filter->minimumReviews );
|
||||
$clause[] = $class::$databaseTable . '.' . $class::$databasePrefix . $class::$databaseColumnMap['num_reviews'] . '>=' . \intval( $filter->minimumReviews );
|
||||
}
|
||||
|
||||
if ( $filter->minimumViews )
|
||||
{
|
||||
$this->select[ $class::$databaseTable . '_views' ] = $class::$databaseTable . '.' . $class::$databasePrefix . $class::$databaseColumnMap['views'];
|
||||
$clause[] = $class::$databaseTable . '.' . $class::$databasePrefix . $class::$databaseColumnMap['views'] . '>=' . intval( $filter->minimumViews );
|
||||
$clause[] = $class::$databaseTable . '.' . $class::$databasePrefix . $class::$databaseColumnMap['views'] . '>=' . \intval( $filter->minimumViews );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,7 +199,7 @@ class _Query extends \IPS\Content\Search\Query
|
||||
}
|
||||
|
||||
/* Put it together */
|
||||
if ( count( $clause ) > 1 )
|
||||
if ( \count( $clause ) > 1 )
|
||||
{
|
||||
$filters[] = '( ' . implode( ' AND ', $clause ) . ' )';
|
||||
}
|
||||
@@ -211,7 +224,7 @@ class _Query extends \IPS\Content\Search\Query
|
||||
*/
|
||||
public function filterByAuthor( $author )
|
||||
{
|
||||
if ( is_array( $author ) )
|
||||
if ( \is_array( $author ) )
|
||||
{
|
||||
$this->where[] = array( \IPS\Db::i()->in( 'index_author', $author ) );
|
||||
}
|
||||
@@ -236,7 +249,7 @@ class _Query extends \IPS\Content\Search\Query
|
||||
{
|
||||
$this->where[] = 'index_club_id IS NULL';
|
||||
}
|
||||
if ( is_array( $club ) )
|
||||
if ( \is_array( $club ) )
|
||||
{
|
||||
$this->where[] = array( \IPS\Db::i()->in( 'index_club_id', $club ) );
|
||||
}
|
||||
@@ -244,6 +257,26 @@ class _Query extends \IPS\Content\Search\Query
|
||||
{
|
||||
$this->where[] = 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;
|
||||
}
|
||||
@@ -261,14 +294,7 @@ class _Query extends \IPS\Content\Search\Query
|
||||
|
||||
if ( $count )
|
||||
{
|
||||
if ( $count > 500 )
|
||||
{
|
||||
$this->where[] = array( '( index_author=? OR ( index_class=? AND index_container_id=? ) )', $member->member_id, 'IPS\core\Statuses\Status', $member->member_id );
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->where[] = array( '( index_author=? OR (' . \IPS\Db::i()->in( 'index_id', iterator_to_array( \IPS\Db::i()->select( 'index_id', 'core_search_index', array( 'index_class=? AND index_container_id=?', 'IPS\core\Statuses\Status', $member->member_id ) ) ) ) . ') )', $member->member_id );
|
||||
}
|
||||
$this->where[] = array( '( index_author=? OR ( index_class=? AND index_container_id=? ) )', $member->member_id, 'IPS\core\Statuses\Status', $member->member_id );
|
||||
|
||||
$filterResult = $this;
|
||||
}
|
||||
@@ -282,7 +308,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;
|
||||
}
|
||||
@@ -428,7 +454,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'];
|
||||
}
|
||||
@@ -463,13 +489,13 @@ class _Query extends \IPS\Content\Search\Query
|
||||
{
|
||||
$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', array( 0, 501 ) ) );
|
||||
|
||||
if ( count( $followed ) == 501 )
|
||||
if ( \count( $followed ) == 501 )
|
||||
{
|
||||
/* Assume we have loads of matches, so do a full query */
|
||||
$where[] = 'index_author IN(?)';
|
||||
$params[] = \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 ) );
|
||||
}
|
||||
else if ( count( $followed ) )
|
||||
else if ( \count( $followed ) )
|
||||
{
|
||||
/* IN is not a SIN. It's been a long day */
|
||||
$where[] = \IPS\Db::i()->in( 'index_author', $followed );
|
||||
@@ -488,7 +514,7 @@ class _Query extends \IPS\Content\Search\Query
|
||||
}
|
||||
|
||||
/* Put it all together */
|
||||
if ( count( $where ) )
|
||||
if ( \count( $where ) )
|
||||
{
|
||||
$this->where[] = array_merge( array( '( ' . implode( ' OR ', $where ) . ' )' ), $params );
|
||||
}
|
||||
@@ -570,11 +596,13 @@ class _Query extends \IPS\Content\Search\Query
|
||||
}
|
||||
}
|
||||
|
||||
if ( count( $where ) )
|
||||
if ( \count( $where ) )
|
||||
{
|
||||
/* Put it all together */
|
||||
$this->where[] = array_merge( array( '( ' . implode( ' OR ', $where ) . ' )' ), $params );
|
||||
}
|
||||
|
||||
$this->filterByUnread = TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -595,7 +623,7 @@ class _Query extends \IPS\Content\Search\Query
|
||||
$where = array();
|
||||
$unreadWheres = array();
|
||||
$containerIds = array();
|
||||
$containerClass = ( $class::$containerNodeClass ) ? $class::$containerNodeClass : NULL;
|
||||
$containerClass = isset( $class::$containerNodeClass ) ? $class::$containerNodeClass : NULL;
|
||||
|
||||
foreach( $resetTimes as $containerId => $timestamp )
|
||||
{
|
||||
@@ -615,7 +643,7 @@ class _Query extends \IPS\Content\Search\Query
|
||||
|
||||
$items = $this->member->markersItems( $application, \IPS\Content\Item::makeMarkerKey( $containerId ) );
|
||||
|
||||
if ( count( $items ) )
|
||||
if ( \count( $items ) )
|
||||
{
|
||||
foreach( $items as $mid => $mtime )
|
||||
{
|
||||
@@ -625,10 +653,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 ] );
|
||||
@@ -646,25 +674,25 @@ class _Query extends \IPS\Content\Search\Query
|
||||
}
|
||||
}
|
||||
|
||||
if( count( $containerIds ) )
|
||||
if( \count( $containerIds ) )
|
||||
{
|
||||
$unreadWheres[] = "( index_date_updated > " . intval( $this->member->marked_site_read ) . " AND ( index_container_id NOT IN(" . implode( ',', $containerIds ) . ") ) )";
|
||||
$unreadWheres[] = "( index_date_updated > " . \intval( $this->member->marked_site_read ) . " AND ( index_container_id NOT IN(" . implode( ',', $containerIds ) . ") ) )";
|
||||
}
|
||||
else
|
||||
{
|
||||
$unreadWheres[] = "( index_date_updated > " . intval( $this->member->marked_site_read ) . ")";
|
||||
$unreadWheres[] = "( index_date_updated > " . \intval( $this->member->marked_site_read ) . ")";
|
||||
}
|
||||
|
||||
if( count( $unreadWheres ) )
|
||||
if( \count( $unreadWheres ) )
|
||||
{
|
||||
$where[] = array( "(" . implode( " OR ", $unreadWheres ) . ")" );
|
||||
}
|
||||
|
||||
if ( count( $markers ) )
|
||||
if ( \count( $markers ) )
|
||||
{
|
||||
/* Avoid packet issues */
|
||||
krsort( $markers );
|
||||
$useIds = array_flip( array_slice( $markers, 0, 500, TRUE ) );
|
||||
$useIds = array_flip( \array_slice( $markers, 0, 500, TRUE ) );
|
||||
$select = '';
|
||||
$from = '';
|
||||
$notIn = array();
|
||||
@@ -675,7 +703,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 )
|
||||
{
|
||||
@@ -688,7 +716,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 )
|
||||
{
|
||||
@@ -697,12 +725,12 @@ class _Query extends \IPS\Content\Search\Query
|
||||
if ( $useIds[ $row['_id'] ] >= $row['_date'] )
|
||||
{
|
||||
/* Still read */
|
||||
$notIn[] = intval( $row['_id'] );
|
||||
$notIn[] = \intval( $row['_id'] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( count( $notIn ) )
|
||||
if ( \count( $notIn ) )
|
||||
{
|
||||
$where[] = array( "( index_item_id NOT IN (" . implode( ',', $notIn ) . ") )" );
|
||||
}
|
||||
@@ -759,11 +787,11 @@ class _Query extends \IPS\Content\Search\Query
|
||||
*/
|
||||
public function setHiddenFilter( $statuses )
|
||||
{
|
||||
if ( is_null( $statuses ) )
|
||||
if ( \is_null( $statuses ) )
|
||||
{
|
||||
$this->hiddenClause = NULL;
|
||||
}
|
||||
if ( is_array( $statuses ) )
|
||||
if ( \is_array( $statuses ) )
|
||||
{
|
||||
$this->hiddenClause = array( \IPS\Db::i()->in( 'index_hidden', $statuses ) );
|
||||
}
|
||||
@@ -856,7 +884,7 @@ class _Query extends \IPS\Content\Search\Query
|
||||
$tagIds = iterator_to_array( \IPS\Db::i()->select( 'index_id', 'core_search_index_tags', array( \IPS\Db::i()->in( 'index_tag', $tags ) ), 'index_id asc', array( 0, 501 ) ) );
|
||||
|
||||
/* Now, if we have 501 results, then we have to assume there are more, so the join is required */
|
||||
if ( count( $tagIds ) == 501 )
|
||||
if ( \count( $tagIds ) == 501 )
|
||||
{
|
||||
$tagWhere = array( $itemsOnlyTagSearch . 'index_item_index_id IN ( ? )', \IPS\Db::i()->select( 'index_id', 'core_search_index_tags', array( \IPS\Db::i()->in( 'index_tag', $tags ) ) ) );
|
||||
}
|
||||
@@ -937,16 +965,16 @@ class _Query extends \IPS\Content\Search\Query
|
||||
}
|
||||
}
|
||||
|
||||
$results = iterator_to_array( \IPS\Db::i()->select( 'index_item_id, index_class', array( 'core_search_index_item_map', 'sub' ), array( array( 'index_author_id=' . intval( $this->member->member_id ) . ' AND ' . \IPS\Db::i()->in( 'index_class', $classes ) ) ), 'index_item_id desc', array( 0, 501 ) ) );
|
||||
$results = iterator_to_array( \IPS\Db::i()->select( 'index_item_id, index_class', array( 'core_search_index_item_map', 'sub' ), array( array( 'index_author_id=' . \intval( $this->member->member_id ) . ' AND ' . \IPS\Db::i()->in( 'index_class', $classes ) ) ), 'index_item_id desc', array( 0, 501 ) ) );
|
||||
|
||||
if ( count( $results ) == 501 )
|
||||
if ( \count( $results ) == 501 )
|
||||
{
|
||||
/* Assume we have loads of matches, so do a full query */
|
||||
$subClause = array( array( 'sub.index_item_id=main.index_item_id AND index_author_id=' . intval( $this->member->member_id ) . ' AND ' . $this->makeEmbeddedIfClauseForSearchMapTable('sub.index_class') ) );
|
||||
$subClause = array( array( 'sub.index_item_id=main.index_item_id AND index_author_id=' . \intval( $this->member->member_id ) . ' AND ' . $this->makeEmbeddedIfClauseForSearchMapTable('sub.index_class') ) );
|
||||
$query = \IPS\Db::i()->select( 'index_item_id', array( 'core_search_index_item_map', 'sub' ), $subClause );
|
||||
$where[] = array( 'EXISTS(?)', $query );
|
||||
}
|
||||
else if ( count( $results ) )
|
||||
else if ( \count( $results ) )
|
||||
{
|
||||
$classIds = array();
|
||||
foreach( $results as $result )
|
||||
@@ -1027,7 +1055,7 @@ class _Query extends \IPS\Content\Search\Query
|
||||
}
|
||||
}
|
||||
|
||||
if ( count( $stack ) )
|
||||
if ( \count( $stack ) )
|
||||
{
|
||||
$if = '';
|
||||
foreach( $stack as $commentClass => $class )
|
||||
@@ -1035,7 +1063,7 @@ class _Query extends \IPS\Content\Search\Query
|
||||
$if .= " IF( main.index_class='" . \IPS\Db::i()->escape_string( $commentClass ) . "', '" . \IPS\Db::i()->escape_string( $class ) . "', ";
|
||||
}
|
||||
|
||||
$if .= 'main.index_class' . str_repeat( ' )', count( $stack ) );
|
||||
$if .= 'main.index_class' . str_repeat( ' )', \count( $stack ) );
|
||||
}
|
||||
|
||||
return $column . '=' . ( $if == '' ? $column : $if );
|
||||
@@ -1066,25 +1094,14 @@ class _Query extends \IPS\Content\Search\Query
|
||||
foreach ( static::termAsWordsArray( $term ) as $word )
|
||||
{
|
||||
/* Operators are not allowed */
|
||||
$word = preg_replace( '/^([\+\-~<>]){1,}/', '', $word );
|
||||
$word = preg_replace( '/([\+\-~\*<>]){2,}$/', '$1', $word );
|
||||
|
||||
/* +* and +- are not allowed anywhere in the word */
|
||||
$word = str_replace( array( '+*', '+-', '+' ), '', $word );
|
||||
|
||||
/* Trailing + or -s are not allowed */
|
||||
$word = rtrim( $word, '+-' );
|
||||
|
||||
/* Nor is @ or parenthesis (while paranthesis can be used to group words and apply operators to the group,
|
||||
(e.g. "+apple +(>turnover <strudel)") - it's unlikely a user intends this behaviour) */
|
||||
$word = str_replace( array( '(', ')' ), '', str_replace( '@', ' ', $word ) );
|
||||
$word = preg_replace( '/[+\-><\(\)~*\"@%]+/', '', $word );
|
||||
|
||||
if ( $word )
|
||||
{
|
||||
$words[] = $word;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$booleanTerm = implode( ' +', $words );
|
||||
|
||||
/* Return */
|
||||
@@ -1119,7 +1136,7 @@ class _Query extends \IPS\Content\Search\Query
|
||||
foreach ( static::termAsWordsArray( $term ) as $word )
|
||||
{
|
||||
/* Add the default operator */
|
||||
if ( $defaultOperator and !in_array( mb_substr( $word, 0, 1 ), array( '+', '-', '~', '<', '>' ) ) )
|
||||
if ( $defaultOperator and !\in_array( mb_substr( $word, 0, 1 ), array( '+', '-', '~', '<', '>' ) ) )
|
||||
{
|
||||
/* Clear out leading * symbols so we don't end up with +*a** */
|
||||
$word = $defaultOperator . ltrim( $word, '*' );
|
||||
@@ -1179,11 +1196,11 @@ class _Query extends \IPS\Content\Search\Query
|
||||
if ( $prepared )
|
||||
{
|
||||
/* Force newest as of 4.2.6 to test search results */
|
||||
if ( count( $likeWhere ) and count( $words ) )
|
||||
if ( \count( $likeWhere ) and \count( $words ) )
|
||||
{
|
||||
$return = array( implode( ' AND ', $likeWhere ) . " AND MATCH({$columns}) AGAINST (? IN BOOLEAN MODE)", $term );
|
||||
}
|
||||
else if ( count( $likeWhere ) )
|
||||
else if ( \count( $likeWhere ) )
|
||||
{
|
||||
$return = array( implode( ' AND ', $likeWhere ) );
|
||||
}
|
||||
@@ -1195,7 +1212,7 @@ class _Query extends \IPS\Content\Search\Query
|
||||
else
|
||||
{
|
||||
/* Force newest as of 4.2.6 to test search results */
|
||||
if ( count( $likeWhere ) )
|
||||
if ( \count( $likeWhere ) )
|
||||
{
|
||||
$return[] = implode( ' AND ', $likeWhere );
|
||||
}
|
||||
@@ -1231,7 +1248,13 @@ class _Query extends \IPS\Content\Search\Query
|
||||
|
||||
/* Set order clause */
|
||||
$order = $this->order;
|
||||
|
||||
|
||||
/* This forces MySQL to strongly hint a better index to the optimiser for MySQL 5.7+, even though sorting will not be affected */
|
||||
if ( $this->filterByUnread and ( ! mb_stristr( \IPS\Db::i()->server_info, '-MariaDB' ) and \IPS\Db::i()->server_version >= 50700 ) )
|
||||
{
|
||||
$order .= ", index_date_updated";
|
||||
}
|
||||
|
||||
/* But we're sorting by relevancy, we need to actually select that value with our fancy algorithm */
|
||||
if ( mb_substr( $this->order, 0, 9 ) === 'calcscore' )
|
||||
{
|
||||
@@ -1240,7 +1263,7 @@ class _Query extends \IPS\Content\Search\Query
|
||||
{
|
||||
if ( \IPS\Settings::i()->search_title_boost )
|
||||
{
|
||||
$titleField = '(' . static::matchClause( 'index_title', $term, $operator === static::OPERATOR_AND ? '+' : '', FALSE ) . '*' . intval( \IPS\Settings::i()->search_title_boost ) . ')'; // The title score times multiplier
|
||||
$titleField = '(' . static::matchClause( 'index_title', $term, $operator === static::OPERATOR_AND ? '+' : '', FALSE ) . '*' . \intval( \IPS\Settings::i()->search_title_boost ) . ')'; // The title score times multiplier
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1394,11 +1417,11 @@ class _Query extends \IPS\Content\Search\Query
|
||||
{
|
||||
if ( $row['Variable_name'] === $minVariable )
|
||||
{
|
||||
\IPS\Data\Store::i()->mysqlMinWord = intval( $row['Value'] );
|
||||
\IPS\Data\Store::i()->mysqlMinWord = \intval( $row['Value'] );
|
||||
}
|
||||
elseif ( $row['Variable_name'] === $maxVariable )
|
||||
{
|
||||
\IPS\Data\Store::i()->mysqlMaxWord = intval( $row['Value'] );
|
||||
\IPS\Data\Store::i()->mysqlMaxWord = \intval( $row['Value'] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace IPS\Content\Search;
|
||||
|
||||
/* 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;
|
||||
@@ -145,8 +145,7 @@ abstract class _Query
|
||||
* Filter by container class
|
||||
*
|
||||
* @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.
|
||||
* @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)
|
||||
*/
|
||||
abstract public function filterByContainerClasses( $classes=array(), $exclude=array() );
|
||||
@@ -156,7 +155,7 @@ abstract class _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)
|
||||
*/
|
||||
abstract public function filterByFollowed( $includeContainers, $includeItems, $includeMembers );
|
||||
@@ -307,8 +306,8 @@ abstract class _Query
|
||||
*/
|
||||
public static function termAsWordsArray( $term, $ignorePhrase=FALSE, $minLength=NULL, $maxLength=NULL )
|
||||
{
|
||||
$minLength = is_null( $minLength ) ? 0 : $minLength;
|
||||
$maxLength = is_null( $maxLength ) ? 255 : $maxLength;
|
||||
$minLength = \is_null( $minLength ) ? 0 : $minLength;
|
||||
$maxLength = \is_null( $maxLength ) ? 255 : $maxLength;
|
||||
|
||||
/* Sub quotes with smaller than min word strings can cause issues for larger tables, so we convert the sub quote into normal words for a boolean search */
|
||||
if ( ! static::termIsPhrase( $term ) )
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace IPS\Content\Search\Result;
|
||||
|
||||
/* 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;
|
||||
@@ -90,7 +90,8 @@ class _Content extends \IPS\Content\Search\Result
|
||||
*/
|
||||
public function html( $view = 'expanded', $asItem = FALSE, $canIgnoreComments=FALSE, $template=NULL )
|
||||
{
|
||||
return call_user_func( array( $this->indexData['index_class'], 'searchResult' ), $this->indexData, $this->authorData, $this->itemData, $this->containerData, $this->reputationData, $this->reviewRating, $this->iPostedIn, $view, $asItem, $canIgnoreComments, $template, $this->reactions );
|
||||
$searchResultTemplate = array( $this->indexData['index_class'], 'searchResult' );
|
||||
return $searchResultTemplate( $this->indexData, $this->authorData, $this->itemData, $this->containerData, $this->reputationData, $this->reviewRating, $this->iPostedIn, $view, $asItem, $canIgnoreComments, $template, $this->reactions );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -123,7 +124,21 @@ class _Content extends \IPS\Content\Search\Result
|
||||
{
|
||||
$class = $this->indexData['index_class'];
|
||||
$object = $class::load( $this->indexData['index_object_id'] );
|
||||
$document->addItem( $object instanceof \IPS\Content\Comment ? $object->item()->searchIndexTitle() : $object->searchIndexTitle(), $object->url(), $this->indexData['index_content'], \IPS\DateTime::ts( $this->indexData['index_date_created'] ) );
|
||||
$enclosure = NULL;
|
||||
|
||||
if ( $images = $object->contentImages(1) )
|
||||
{
|
||||
$data = array_pop( $images );
|
||||
$key = key( $data );
|
||||
|
||||
try
|
||||
{
|
||||
$enclosure = \IPS\File::get( $key, $data[ $key ] );
|
||||
}
|
||||
catch( \Exception $ex ) { }
|
||||
}
|
||||
|
||||
$document->addItem( $object instanceof \IPS\Content\Comment ? $object->item()->searchIndexTitle() : $object->searchIndexTitle(), $object->url(), $this->indexData['index_content'], \IPS\DateTime::ts( $this->indexData['index_date_created'] ), NULL, $enclosure );
|
||||
}
|
||||
/* If the search result was orphaned, let us continue */
|
||||
catch( \OutOfRangeException $e ){}
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace IPS\Content\Search\Result;
|
||||
|
||||
/* 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;
|
||||
@@ -58,7 +58,7 @@ class _Custom extends \IPS\Content\Search\Result
|
||||
/**
|
||||
* Merge in new data
|
||||
*
|
||||
* @param string $html New HTML to merge in as a list
|
||||
* @param string $html New HTML to merge in as a list
|
||||
* @param string|NULL $mergeData Data for merging
|
||||
* @return void
|
||||
*/
|
||||
@@ -81,6 +81,7 @@ class _Custom extends \IPS\Content\Search\Result
|
||||
/**
|
||||
* HTML
|
||||
*
|
||||
* @param string $view The view to use (expanded or condensed)
|
||||
* @return string
|
||||
*/
|
||||
public function html( $view = 'expanded' )
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
namespace IPS\Content\Search;
|
||||
|
||||
/* 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;
|
||||
@@ -25,7 +25,7 @@ abstract class _Result
|
||||
/**
|
||||
* Pre-Display logic for search index content
|
||||
*
|
||||
* @param bool $createdDate If TRUE, uses $createdDate, otherwise uses $lastUpdatedDate
|
||||
* @param string $content The search result content
|
||||
* @return string
|
||||
*/
|
||||
public static function preDisplay( $content )
|
||||
@@ -35,7 +35,11 @@ abstract class _Result
|
||||
for this reason, but Emoji (because they're text) won't be. We can't swap the Emoji back out
|
||||
with one of the alternative Emoji style images though because that re-introduces images, so we
|
||||
just strip them out (making them behave like emoticons) */
|
||||
if ( \IPS\Settings::i()->emoji_style == 'twemoji' or \IPS\Settings::i()->emoji_style == 'emojione' )
|
||||
|
||||
/* Trim content down to a sensible length for display */
|
||||
$content = mb_substr( $content, 0, 600 );
|
||||
|
||||
if ( \IPS\Settings::i()->emoji_style == 'twemoji' )
|
||||
{
|
||||
return preg_replace( '/(?:' . \IPS\Text\Parser::EMOJI_REGEX . '|\x{200d})+/u', '', $content );
|
||||
}
|
||||
|
||||
Whitespace-only changes.
@@ -11,7 +11,7 @@
|
||||
namespace IPS\Content\Search;
|
||||
|
||||
/* 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;
|
||||
@@ -109,7 +109,7 @@ class _Results extends \ArrayIterator
|
||||
$membersToLoad[ $result['index_author'] ] = $result['index_author'];
|
||||
}
|
||||
|
||||
if ( in_array( 'IPS\Content\Comment', class_parents( $result['index_class'] ) ) )
|
||||
if ( \in_array( 'IPS\Content\Comment', class_parents( $result['index_class'] ) ) )
|
||||
{
|
||||
$commentClass = $result['index_class'];
|
||||
$itemsToLoad[ $commentClass::$itemClass ][ $result['index_item_id'] ] = $result['index_item_id'];
|
||||
@@ -131,7 +131,7 @@ class _Results extends \ArrayIterator
|
||||
}
|
||||
else
|
||||
{
|
||||
$itemClass = in_array( 'IPS\Content\Comment', class_parents( $class ) ) ? $class::$itemClass : $class;
|
||||
$itemClass = \in_array( 'IPS\Content\Comment', class_parents( $class ) ) ? $class::$itemClass : $class;
|
||||
if ( isset( $itemClass::$containerNodeClass ) )
|
||||
{
|
||||
$containerClass = $itemClass::$containerNodeClass;
|
||||
@@ -148,7 +148,7 @@ class _Results extends \ArrayIterator
|
||||
$reputationIds[ $result['index_class'] ][ $result['index_object_id'] ] = $result['index_object_id'];
|
||||
}
|
||||
|
||||
if ( in_array( 'IPS\Content\Review', class_parents( $result['index_class'] ) ) )
|
||||
if ( \in_array( 'IPS\Content\Review', class_parents( $result['index_class'] ) ) )
|
||||
{
|
||||
$reviewRatings[ $result['index_class'] ][ $result['index_object_id'] ] = $result['index_object_id'];
|
||||
}
|
||||
@@ -188,7 +188,7 @@ class _Results extends \ArrayIterator
|
||||
}
|
||||
|
||||
/* Load author data */
|
||||
if( count( $membersToLoad ) )
|
||||
if( \count( $membersToLoad ) )
|
||||
{
|
||||
$this->authorData = iterator_to_array( \IPS\Db::i()->select( \IPS\Member::columnsForPhoto(), 'core_members', \IPS\Db::i()->in( 'member_id', $membersToLoad ) )->setKeyField( 'member_id' ) );
|
||||
}
|
||||
@@ -201,7 +201,7 @@ class _Results extends \ArrayIterator
|
||||
|
||||
/* Load reputation data */
|
||||
$reputation = array();
|
||||
if ( count( $reputationIds ) )
|
||||
if ( \count( $reputationIds ) )
|
||||
{
|
||||
$clause = array();
|
||||
$binds = array();
|
||||
@@ -234,13 +234,13 @@ class _Results extends \ArrayIterator
|
||||
}
|
||||
|
||||
/* Load data for the "I posted in this" stars */
|
||||
if ( count( $itemIndexIds ) and \IPS\Member::loggedIn()->member_id )
|
||||
if ( \count( $itemIndexIds ) and \IPS\Member::loggedIn()->member_id )
|
||||
{
|
||||
$this->iPostedIn = \IPS\Content\Search\Index::i()->iPostedIn( $itemIndexIds, \IPS\Member::loggedIn() );
|
||||
}
|
||||
|
||||
/* Load tags and prefixes */
|
||||
if ( count( $itemIndexIds ) and \IPS\Settings::i()->search_method == 'mysql' )
|
||||
if ( \count( $itemIndexIds ) and \IPS\Settings::i()->search_method == 'mysql' )
|
||||
{
|
||||
$this->tags = iterator_to_array( \IPS\Db::i()->select( 'index_id, GROUP_CONCAT(index_tag) as index_tags', 'core_search_index_tags', array( array( \IPS\Db::i()->in( 'index_id', $itemIndexIds ) ), array( 'index_is_prefix = 0' ) ), NULL, NULL, 'index_id' )->setKeyField('index_id') );
|
||||
$this->prefixes = iterator_to_array( \IPS\Db::i()->select( 'index_id, index_tag as index_prefix', 'core_search_index_tags', array( array( \IPS\Db::i()->in( 'index_id', $itemIndexIds ) ), array( 'index_is_prefix = 1' ) ) )->setKeyField('index_id') );
|
||||
@@ -260,7 +260,7 @@ class _Results extends \ArrayIterator
|
||||
$data = parent::current();
|
||||
|
||||
$class = $data['index_class'];
|
||||
$itemClass = in_array( 'IPS\Content\Comment', class_parents( $class ) ) ? $class::$itemClass : $class;
|
||||
$itemClass = \in_array( 'IPS\Content\Comment', class_parents( $class ) ) ? $class::$itemClass : $class;
|
||||
$containerClass = isset( $itemClass::$containerNodeClass ) ? $itemClass::$containerNodeClass : NULL;
|
||||
|
||||
$reputationData = array();
|
||||
@@ -278,8 +278,13 @@ class _Results extends \ArrayIterator
|
||||
/* Ensure we have an item if results are gathered from the map table */
|
||||
if ( ! isset( $this->itemData[ $itemClass ][ $data['index_item_id'] ] ) )
|
||||
{
|
||||
/* This should not happen in production, so debug log and move on to the next item */
|
||||
/* This should not happen in production, so debug log */
|
||||
\IPS\Log::debug( "index_item_id ID " . $data['index_item_id'] . " for " . $itemClass . " has no itemData" , 'searchResults' );
|
||||
|
||||
/* Then cleanup/remove the item now */
|
||||
\IPS\Content\Search\Index::i()->directIndexRemoval( $data['index_class'], $data['index_object_id'] );
|
||||
|
||||
/* And continue, moving on to the next item */
|
||||
static::next();
|
||||
return static::current();
|
||||
}
|
||||
@@ -325,7 +330,7 @@ class _Results extends \ArrayIterator
|
||||
$containerData,
|
||||
$reputationData,
|
||||
isset( $this->reviewRatings[ $data['index_class'] ][ $data['index_object_id'] ] ) ? $this->reviewRatings[ $data['index_class'] ][ $data['index_object_id'] ] : NULL,
|
||||
in_array( $data['index_item_index_id'], $this->iPostedIn ),
|
||||
\in_array( $data['index_item_index_id'], $this->iPostedIn ),
|
||||
$reactions
|
||||
);
|
||||
}
|
||||
@@ -354,11 +359,11 @@ class _Results extends \ArrayIterator
|
||||
{
|
||||
if ( $allRows )
|
||||
{
|
||||
if ( is_integer( $this->countAllRows ) )
|
||||
if ( \is_integer( $this->countAllRows ) )
|
||||
{
|
||||
return $this->countAllRows;
|
||||
}
|
||||
else if ( gettype( $this->countAllRows ) === 'object' and get_class( $this->countAllRows ) === 'IPS\Db\Select' )
|
||||
else if ( \gettype( $this->countAllRows ) === 'object' and \get_class( $this->countAllRows ) === 'IPS\Db\Select' )
|
||||
{
|
||||
$this->countAllRows = $this->countAllRows->first();
|
||||
|
||||
@@ -378,7 +383,7 @@ class _Results extends \ArrayIterator
|
||||
/**
|
||||
* Add in "extra" items
|
||||
*
|
||||
* @param array $extra Types ('register', 'follow_member', 'follow_content', 'photo', 'like', 'rep_neg')
|
||||
* @param array $extra Types ('register', 'follow_member', 'follow_content', 'photo', 'votes', 'like', 'rep_neg')
|
||||
* @param \IPS\Member|NULL $author The author to limit extra items to
|
||||
* @param \IPS\DateTime|NULL $lastTime If provided, only items since this date is included. If NULL, it works out which to include based on what results are being shown
|
||||
* @param \IPS\DateTime|NULL $firstTime If provided, only items before this date is included. If NULL, it works out which to include based on what results are being shown
|
||||
@@ -431,9 +436,9 @@ class _Results extends \ArrayIterator
|
||||
if ( $lastTime )
|
||||
{
|
||||
/* Users registered */
|
||||
if ( in_array( 'register', $extra ) )
|
||||
if ( \in_array( 'register', $extra ) )
|
||||
{
|
||||
$where = array( array( 'joined>?', $lastTime ), array( '( name<>? and email<>? )', '', '' ), array( 'temp_ban!=?', '-1' ) );
|
||||
$where = array( array( 'joined>?', $lastTime ), array( 'completed=?', true ), array( 'temp_ban!=?', '-1' ) );
|
||||
if ( $firstTime )
|
||||
{
|
||||
$where[] = array( 'joined<?', $firstTime );
|
||||
@@ -451,7 +456,7 @@ class _Results extends \ArrayIterator
|
||||
{
|
||||
$extraItems[] = new Result\Custom(
|
||||
\IPS\DateTime::ts( $member['joined'] ),
|
||||
\IPS\Member::loggedIn()->language()->addToStack( 'activity_member_joined', FALSE, array( 'htmlsprintf' => \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' )->userLinkFromData( $member['member_id'], $member['name'], $member['members_seo_name'] ) ) ),
|
||||
\IPS\Member::loggedIn()->language()->addToStack( 'activity_member_joined', FALSE, array( 'htmlsprintf' => \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' )->userLinkFromData( $member['member_id'], $member['name'], $member['members_seo_name'], $member['member_group_id'] ) ) ),
|
||||
\IPS\Theme::i()->getTemplate( 'global', 'core', 'front' )->userPhotoFromData( $member['member_id'], $member['name'], $member['members_seo_name'], \IPS\Member::photoUrl( $member ), 'tiny' )
|
||||
);
|
||||
}
|
||||
@@ -459,9 +464,9 @@ class _Results extends \ArrayIterator
|
||||
}
|
||||
|
||||
/* Users changed photo */
|
||||
if ( in_array( 'photo', $extra ) )
|
||||
if ( \in_array( 'photo', $extra ) )
|
||||
{
|
||||
$where = array( array( 'photo_last_update>? AND photo_last_update>(joined+300)', $lastTime ), array( '( name<>? and email<>? )', '', '' ) );
|
||||
$where = array( array( 'photo_last_update>? AND photo_last_update>(joined+300)', $lastTime ), array( 'completed=?', true ) );
|
||||
if ( $firstTime )
|
||||
{
|
||||
$where[] = array( 'photo_last_update<?', $firstTime );
|
||||
@@ -474,23 +479,23 @@ class _Results extends \ArrayIterator
|
||||
{
|
||||
$extraItems[] = new Result\Custom(
|
||||
\IPS\DateTime::ts( $member['photo_last_update'] ),
|
||||
\IPS\Member::loggedIn()->language()->addToStack( 'activity_member_updated_photo', FALSE, array( 'htmlsprintf' => \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' )->userLinkFromData( $member['member_id'], $member['name'], $member['members_seo_name'] ) ) ),
|
||||
\IPS\Member::loggedIn()->language()->addToStack( 'activity_member_updated_photo', FALSE, array( 'htmlsprintf' => \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' )->userLinkFromData( $member['member_id'], $member['name'], $member['members_seo_name'], $member['member_group_id'] ) ) ),
|
||||
\IPS\Theme::i()->getTemplate( 'global', 'core', 'front' )->userPhotoFromData( $member['member_id'], $member['name'], $member['members_seo_name'], \IPS\Member::photoUrl( $member ), 'tiny' )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/* Follow */
|
||||
if ( in_array( 'follow_member', $extra ) or in_array( 'follow_content', $extra ) )
|
||||
if ( \in_array( 'follow_member', $extra ) or \in_array( 'follow_content', $extra ) )
|
||||
{
|
||||
$where = array( array( 'follow_is_anon=0' ), array( 'follow_added>?', $lastTime ) );
|
||||
if ( $firstTime )
|
||||
{
|
||||
$where[] = array( 'follow_added<?', $firstTime );
|
||||
}
|
||||
if ( in_array( 'follow_member', $extra ) xor in_array( 'follow_content', $extra ) )
|
||||
if ( \in_array( 'follow_member', $extra ) xor \in_array( 'follow_content', $extra ) )
|
||||
{
|
||||
if ( in_array( 'follow_member', $extra ) )
|
||||
if ( \in_array( 'follow_member', $extra ) )
|
||||
{
|
||||
$where[] = array( 'follow_app=? AND follow_area=?', 'core', 'member' );
|
||||
}
|
||||
@@ -501,11 +506,11 @@ class _Results extends \ArrayIterator
|
||||
}
|
||||
if ( $author )
|
||||
{
|
||||
if ( in_array( 'follow_member', $extra ) and in_array( 'follow_content', $extra ) )
|
||||
if ( \in_array( 'follow_member', $extra ) and \in_array( 'follow_content', $extra ) )
|
||||
{
|
||||
$where[] = array( '( follow_member_id=? OR ( follow_app=? AND follow_area=? AND follow_rel_id=? ) )', $author->member_id, 'core', 'member', $author->member_id );
|
||||
$where[] = array( '( follow_id IN (?) )', \IPS\Db::i()->select( 'follow_id', 'core_follow', array( '( follow_member_id=? OR ( follow_app=? AND follow_area=? AND follow_rel_id=? ) )', $author->member_id, 'core', 'member', $author->member_id ) ) );
|
||||
}
|
||||
elseif ( in_array( 'follow_member', $extra ) )
|
||||
elseif ( \in_array( 'follow_member', $extra ) )
|
||||
{
|
||||
$where[] = array( 'follow_rel_id=?', $author->member_id );
|
||||
}
|
||||
@@ -538,22 +543,40 @@ class _Results extends \ArrayIterator
|
||||
{
|
||||
if ( class_exists( $class ) )
|
||||
{
|
||||
$thingBeingFollowed = $class::loadAndCheckPerms( $follow['follow_rel_id'] );
|
||||
$urlToThing = \IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->basicUrl( $thingBeingFollowed->url(), FALSE, $thingBeingFollowed instanceof \IPS\Node\Model ? $thingBeingFollowed->_title : $thingBeingFollowed->mapped('title'), FALSE );
|
||||
/* It is possible we've already queried and fetched this item, so attempt to pull from our search results first */
|
||||
$urlToThing = NULL;
|
||||
|
||||
foreach( $results as $result )
|
||||
{
|
||||
$result = $result->asArray();
|
||||
|
||||
if( $result['indexData']['index_class'] == $class AND $result['indexData']['index_object_id'] == $follow['follow_rel_id'] )
|
||||
{
|
||||
$urlToThing = \IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->basicUrl( $class::urlFromIndexData( $result['indexData'], $result['itemData'] ), FALSE, $result['indexData']['index_title'], FALSE );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if( $urlToThing === NULL )
|
||||
{
|
||||
$thingBeingFollowed = $class::loadAndCheckPerms( $follow['follow_rel_id'] );
|
||||
$urlToThing = \IPS\Theme::i()->getTemplate( 'global', 'core', 'global' )->basicUrl( $thingBeingFollowed->url(), FALSE, $thingBeingFollowed instanceof \IPS\Node\Model ? $thingBeingFollowed->_title : $thingBeingFollowed->mapped('title'), FALSE );
|
||||
}
|
||||
|
||||
/* Do we need to merge with the last result? */
|
||||
if( $lastClass !== NULL AND $lastClass === $class AND $lastMember === $follow['follow_member_id'] AND count( $extraItems ) )
|
||||
if( $lastClass !== NULL AND $lastClass === $class AND $lastMember === $follow['follow_member_id'] AND \count( $extraItems ) )
|
||||
{
|
||||
$extraItem = array_pop( $extraItems );
|
||||
|
||||
$mergeList = array_filter( array_merge( array( $urlToThing ), $extraItem->getMergeData() ) );
|
||||
$listToDisplay = $mergeList;
|
||||
|
||||
if( count( $mergeList ) > 3 )
|
||||
if( \count( $mergeList ) > 3 )
|
||||
{
|
||||
$listToDisplay = array_slice( $mergeList, 0, 3 );
|
||||
$listToDisplay[] = \IPS\Member::loggedIn()->language()->addToStack( 'and_x_others', FALSE, array( 'pluralize' => array( count( $mergeList ) - 3 ) ) );
|
||||
$listToDisplay = \array_slice( $mergeList, 0, 3 );
|
||||
$listToDisplay[] = \IPS\Member::loggedIn()->language()->addToStack( 'x_others', FALSE, array( 'pluralize' => array( \count( $mergeList ) - 3 ) ) );
|
||||
}
|
||||
|
||||
$extraItem->mergeInData( \IPS\Member::loggedIn()->language()->addToStack( 'activity_member_followed', FALSE, array( 'htmlsprintf' => array( \IPS\Theme::i()->getTemplate( 'global', 'core', 'front' )->userLink( \IPS\Member::load( $follow['follow_member_id'] ) ), \IPS\Member::loggedIn()->language()->formatList( $listToDisplay ) ) ) ), $urlToThing );
|
||||
$extraItems[] = $extraItem;
|
||||
}
|
||||
@@ -578,7 +601,7 @@ class _Results extends \ArrayIterator
|
||||
}
|
||||
|
||||
/* Likes */
|
||||
if ( \IPS\Settings::i()->reputation_enabled and \IPS\Member::loggedIn()->group['gbw_view_reps'] and ( in_array( 'like', $extra ) or in_array( 'react', $extra ) ) )
|
||||
if ( \IPS\Settings::i()->reputation_enabled and \IPS\Member::loggedIn()->group['gbw_view_reps'] and ( \in_array( 'like', $extra ) or \in_array( 'react', $extra ) ) )
|
||||
{
|
||||
$where = array( array( 'rep_date>?', $lastTime ) );
|
||||
$where[] = array( 'reaction_enabled=?', 1 );
|
||||
@@ -612,7 +635,7 @@ class _Results extends \ArrayIterator
|
||||
}
|
||||
|
||||
/* Clubs */
|
||||
if ( \IPS\Settings::i()->clubs and in_array( 'clubs', $extra ) )
|
||||
if ( \IPS\Settings::i()->clubs and \in_array( 'clubs', $extra ) )
|
||||
{
|
||||
$where = array( array( 'created>?', $lastTime ) );
|
||||
if ( $firstTime )
|
||||
|
||||
Reference in new issue
Block a user