Version 4.7.23

This commit is contained in:
Neo committed 2025-12-19 16:21:27 -08:00
1 parent 7124a02564
commit 25ddeb65d6
1791 files changed
+76990 -44452

No files matched your search

+10 -8
View File
@@ -186,6 +186,7 @@ class _Index extends \IPS\Content\Search\Index
$existingData = NULL;
$existingIndexId = NULL;
$resetLastComment = FALSE;
$firstCommentId = $this->getFirstCommentId( $object );
try
{
@@ -198,7 +199,7 @@ class _Index extends \IPS\Content\Search\Index
}
catch( \Exception $e ) { }
if ( $object instanceof \IPS\Content\Comment and $existingIndexId and $existingData['index_is_last_comment'] and $indexData['index_is_last_comment'] and $indexData['index_item_id'] and $indexData['index_hidden'] !== 0 )
if ( $object instanceof \IPS\Content\Comment and $existingIndexId and $existingData['index_is_last_comment'] and $indexData['index_item_id'] and $indexData['index_hidden'] !== 0 )
{
/* We do not allow hidden or needing approval comments to become flagged as the last comment as this means users without hidden view permission never see the item in an item only stream */
$indexData['index_is_last_comment'] = false;
@@ -223,7 +224,7 @@ class _Index extends \IPS\Content\Search\Index
if ( $resetLastComment )
{
$this->resetLastComment( array( $indexData['index_class'] ), $indexData['index_item_id'] );
$this->resetLastComment( array( $indexData['index_class'] ), $indexData['index_item_id'], null, $firstCommentId );
}
/* Views / Comments / Reviews */
@@ -415,9 +416,9 @@ class _Index extends \IPS\Content\Search\Index
* Retrieve the search ID for an item
*
* @param \IPS\Content\Searchable $object Item to add
* @return void
* @return string
*/
public function getIndexId( \IPS\Content\Searchable $object )
public function getIndexId( \IPS\Content\Searchable $object ): string
{
$databaseColumnId = $object::$databaseColumnId;
return \strtolower( str_replace( '\\', '_', \substr( \get_class( $object ), 4 ) ) ) . '-' . $object->$databaseColumnId;
@@ -455,7 +456,7 @@ class _Index extends \IPS\Content\Search\Index
$classes[] = $itemClass::$reviewClass;
}
$this->resetLastComment( $classes, $object->item()->$itemIdColumn, $object->$idColumn );
$this->resetLastComment( $classes, $object->item()->$itemIdColumn, $object->$idColumn, $this->getFirstCommentId( $object ) );
}
}
catch ( \IPS\Http\Request\Exception $e )
@@ -858,16 +859,17 @@ class _Index extends \IPS\Content\Search\Index
$this->init();
}
}
/**
* Reset the last comment flag in any given class/index_item_id
*
* @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
* @param int|null $firstCommentId The first comment in this item, used only if $firstCommentRequired
* @return void
*/
public function resetLastComment( $classes, $indexItemId, $ignoreId = NULL )
public function resetLastComment( $classes, $indexItemId, $ignoreId = NULL, $firstCommentId = NULL )
{
try
{
@@ -964,7 +966,7 @@ class _Index extends \IPS\Content\Search\Index
),
array(
'term' => array(
'index_object_id' => $indexItemId
'index_object_id' => ( $firstCommentId ?? $indexItemId )
)
),
)
+2 -2
View File
@@ -1107,7 +1107,7 @@ class _Query extends \IPS\Content\Search\Query
$filters = $this->filters;
/* Set our conditions for this search */
if ( $term !== NULL or $tags !== NULL )
if ( $term !== NULL or ( \IPS\Settings::i()->tags_enabled AND $tags !== NULL ) )
{
$searchConditions = array();
$titleField = \IPS\Settings::i()->search_title_boost ? ( 'index_title^' . \intval( \IPS\Settings::i()->search_title_boost ) ) : 'index_title';
@@ -1165,7 +1165,7 @@ class _Query extends \IPS\Content\Search\Query
}
}
/* Build the condition for the tags */
if ( $tags !== NULL )
if ( \IPS\Settings::i()->tags_enabled and $tags !== NULL )
{
$searchConditions[] = array(
'bool' => array(
+40 -4
View File
@@ -250,7 +250,7 @@ abstract class _Index extends \IPS\Patterns\Singleton
}
/* If this comment is hidden, don't actually mark as the last comment as that will cause this item to be hidden in search if we are getting only the last comment. */
if ( $isLastComment AND $object->hidden() )
if ( $isLastComment AND $object->hidden() AND !$object->isFirst() )
{
$isLastComment = 0;
}
@@ -432,6 +432,41 @@ abstract class _Index extends \IPS\Patterns\Singleton
'index_item_solved' => ( $itemClass and \IPS\IPS::classUsesTrait( $itemClass, 'IPS\Content\Solvable' ) ) ? ( $object->item()->mapped('solved_comment_id') == $object->$idColumn ) ? 1 : 0 : NULL
);
}
/**
* Get the object ID of the first comment.
* Used for posts, where the index_object_id is NOT the topic ID
*
* @param \IPS\Content\Searchable $object
* @return int|null
*/
protected function getFirstCommentId( \IPS\Content\Searchable $object ) : ?int
{
if( $object instanceof \IPS\Content\Comment )
{
$itemClass = $object::$itemClass;
if( $itemClass::$firstCommentRequired )
{
if ( $object->isFirst() )
{
$column = $object::$databaseColumnId;
return $object->$column;
}
else if ( isset( $itemClass::$databaseColumnMap['first_comment_id'] ) )
{
$column = $itemClass::$databaseColumnMap['first_comment_id'];
return $object->item()->$column;
}
else
{
$column = $itemClass::$databaseColumnId;
return $object->item()->firstComment()->$column;
}
}
}
return null;
}
/**
* Index an item
@@ -455,9 +490,9 @@ abstract class _Index extends \IPS\Patterns\Singleton
* Retrieve the search ID for an item
*
* @param \IPS\Content\Searchable $object Item to add
* @return void
* @return string
*/
abstract public function getIndexId( \IPS\Content\Searchable $object );
abstract public function getIndexId( \IPS\Content\Searchable $object ): string;
/**
* Remove item
@@ -555,9 +590,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
* @param int|null $firstCommentId The first comment in this item, used only if $firstCommentRequired
* @return void
*/
abstract public function resetLastComment( $class, $indexItemId, $ignoreId = NULL );
abstract public function resetLastComment( $class, $indexItemId, $ignoreId = NULL, $firstCommentId = NULL );
/**
* Given a list of item index IDs, return the ones that a given member has participated in
+12 -8
View File
@@ -73,6 +73,7 @@ class _Index extends \IPS\Content\Search\Index
$existingIndexId = NULL;
$resetLastComment = FALSE;
$newIndexId = NULL;
$firstCommentId = $this->getFirstCommentId( $object );
try
{
@@ -91,7 +92,7 @@ class _Index extends \IPS\Content\Search\Index
unset( $indexData['index_prefix'] );
}
if ( $object instanceof \IPS\Content\Comment and $existingIndexId and $existingData['index_is_last_comment'] and $indexData['index_is_last_comment'] and $indexData['index_item_id'] and $indexData['index_hidden'] !== 0 )
if ( $object instanceof \IPS\Content\Comment and $existingIndexId and $existingData['index_is_last_comment'] and $indexData['index_item_id'] and $indexData['index_hidden'] !== 0 )
{
/* We do not allow hidden or needing approval comments to become flagged as the last comment as this means users without hidden view permission never see the item in an item only stream */
$indexData['index_is_last_comment'] = 0;
@@ -129,6 +130,8 @@ class _Index extends \IPS\Content\Search\Index
}
\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=? AND index_is_last_comment=1', $indexData['index_item_id'] ) );
\IPS\Db::i()->update( 'core_search_index', array( 'index_date_updated' => $indexData['index_date_updated'], 'index_date_commented' => $indexData['index_date_commented'] ), array( \IPS\Db::i()->in( 'index_class', $classes ) . ' AND index_item_id=? and index_object_id=?', $indexData['index_item_id'], ( $firstCommentId ?? $indexData['index_item_id'] ) ) );
}
if ( $existingData !== NULL and ( $indexData['index_class'] == $existingData['index_class'] and $indexData['index_object_id'] == $existingData['index_object_id'] ) )
@@ -214,7 +217,7 @@ class _Index extends \IPS\Content\Search\Index
if ( $resetLastComment )
{
$this->resetLastComment( array( $indexData['index_class'] ), $indexData['index_item_id'] );
$this->resetLastComment( array( $indexData['index_class'] ), $indexData['index_item_id'], null, $firstCommentId );
}
}
}
@@ -224,9 +227,9 @@ class _Index extends \IPS\Content\Search\Index
* Retrieve the search ID for an item
*
* @param \IPS\Content\Searchable $object Item to add
* @return void
* @return string
*/
public function getIndexId( \IPS\Content\Searchable $object )
public function getIndexId( \IPS\Content\Searchable $object ): string
{
$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();
@@ -266,7 +269,7 @@ class _Index extends \IPS\Content\Search\Index
try
{
$this->resetLastComment( $classes, $object->mapped('item'), $object->$idColumn );
$this->resetLastComment( $classes, $object->mapped('item'), $object->$idColumn, $this->getFirstCommentId( $object ) );
}
catch( \Exception $ex ) { }
@@ -583,14 +586,15 @@ 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
* @param int|null $firstCommentId The first comment in this item, used only if $firstCommentRequired
* @return void
*/
public function resetLastComment( $classes, $indexItemId, $ignoreId = NULL )
public function resetLastComment( $classes, $indexItemId, $ignoreId = NULL, $firstCommentId = 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=? AND index_is_last_comment=1', $indexItemId ) );
try
{
$latest = \IPS\Db::i()->select( 'index_object_id, index_date_updated, index_class', 'core_search_index', array( \IPS\Db::i()->in( 'index_class', $classes ) . ' AND index_item_id=? and index_hidden=0', $indexItemId ), 'index_date_created DESC', array( 0, 1 ) )->first();
$latest = \IPS\Db::i()->select( 'index_object_id, index_date_updated, index_class, index_date_commented', 'core_search_index', array( \IPS\Db::i()->in( 'index_class', $classes ) . ' AND index_item_id=? and index_hidden=0', $indexItemId ), 'index_date_created DESC', array( 0, 1 ) )->first();
}
catch( \Exception )
{
@@ -619,7 +623,7 @@ class _Index extends \IPS\Content\Search\Index
\IPS\Db::i()->update( 'core_search_index', array( 'index_is_last_comment' => 1 ), array( 'index_class=? AND index_object_id=?', $latest['index_class'], $latest['index_object_id'] ) );
/* Now reset the item index with the latest comment time */
\IPS\Db::i()->update( 'core_search_index', array( 'index_date_updated' => $latest['index_date_updated'], 'index_date_commented' => $latest['index_date_commented'] ), array( \IPS\Db::i()->in( 'index_class', $classes ) . ' AND index_item_id=? AND index_object_id=?', $indexItemId, $indexItemId ) );
\IPS\Db::i()->update( 'core_search_index', array( 'index_date_updated' => $latest['index_date_updated'], 'index_date_commented' => $latest['index_date_commented'] ), array( \IPS\Db::i()->in( 'index_class', $classes ) . ' AND index_item_id=? AND index_object_id=?', $indexItemId, ( $firstCommentId ?? $indexItemId ) ) );
}
+75 -20
View File
@@ -172,19 +172,19 @@ class _Query extends \IPS\Content\Search\Query
$this->joins[] = array( 'from' => $class::$databaseTable, 'where' => $class::$databaseTable . '.' . $class::$databasePrefix . $class::$databaseColumnId . '=main.index_item_id' );
if ( $filter->minimumComments )
if ( $filter->minimumComments AND isset( $class::$databaseColumnMap['num_comments'] ) )
{
$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 );
}
if ( $filter->minimumReviews )
if ( $filter->minimumReviews AND isset( $class::$databaseColumnMap['num_reviews'] ) )
{
$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 );
}
if ( $filter->minimumViews )
if ( $filter->minimumViews AND isset( $class::$databaseColumnMap['views'] ) )
{
$this->select[ $class::$databaseTable . '_views' ] = $class::$databaseTable . '.' . $class::$databasePrefix . $class::$databaseColumnMap['views'];
$clause[] = $class::$databaseTable . '.' . $class::$databasePrefix . $class::$databaseColumnMap['views'] . '>=' . \intval( $filter->minimumViews );
@@ -503,21 +503,24 @@ class _Query extends \IPS\Content\Search\Query
else if ( \count( $followed ) )
{
/* IN is not a SIN. It's been a long day */
$where[] = \IPS\Db::i()->in( 'index_author', $followed );
$where[] = \IPS\Db::i()->in( 'index_author', $followed );
}
else
else if ( ! $includeItems and ! $includeContainers )
{
/* There are no results */
/* There are no results, and we only wanted members they followed */
$this->where[] = "1=2 /*Filter by followed returned nothing*/";
}
}
catch( \UnderflowException $ex )
{
/* There are no results */
$this->where[] = "1=2 /*Filter by followed returned nothing*/";
if ( ! $includeItems and ! $includeContainers )
{
/* There are no results and we only wanted members they followed */
$this->where[] = "1=2 /*Filter by followed returned nothing*/";
}
}
}
/* Put it all together */
if ( \count( $where ) )
{
@@ -905,7 +908,7 @@ class _Query extends \IPS\Content\Search\Query
$termWhere = NULL;
/* Do we have tags? */
if ( $tags !== NULL )
if ( \IPS\Settings::i()->tags_enabled AND $tags !== NULL )
{
$itemsOnlyTagSearch = '';
@@ -1001,15 +1004,39 @@ class _Query extends \IPS\Content\Search\Query
}
}
$results = iterator_to_array( \IPS\Db::i()->select( 'index_item_id, index_class', array( 'core_search_index_item_map', 'sub' ), array( array( 'index_author_id=' . \intval( $this->member->member_id ) . ' AND ' . \IPS\Db::i()->in( 'index_class', $classes ) ) ), 'index_item_id desc', array( 0, 501 ) ) );
if ( \count( $results ) )
$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, 801 ) ) );
if ( \count( $results ) == 801 )
{
/* Assume we have loads of matches, so do a full query */
$subClause = array( array( 'sub.index_item_id=main.index_item_id AND index_author_id=' . \intval( $this->member->member_id ) . ' AND ' . $this->makeEmbeddedIfClauseForSearchMapTable('sub.index_class') ) );
$query = \IPS\Db::i()->select( 'index_item_id', array( 'core_search_index_item_map', 'sub' ), $subClause );
$where[] = array( 'EXISTS(?)', $query );
}
else if ( \count( $results ) )
{
$classIds = array();
foreach( $results as $result )
{
$classIds[ $result['index_class'] ][] = $result['index_item_id'];
}
$subWhere = array();
foreach( $classIds as $class => $ids )
{
$stack = array( $class );
if ( isset( $class::$commentClass ) )
{
$stack[] = $class::$commentClass;
}
$subWhere[] = '(' . \IPS\Db::i()->in( 'index_class', $stack ) . ' and ' . \IPS\Db::i()->in( 'index_item_id', $ids ) . ')';
}
/* IN is not a SIN. It's been a long day */
$where[] = '(' . implode( ' OR ', $subWhere ) . ')';
}
else
{
/* There are no results */
@@ -1264,7 +1291,7 @@ 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 ( !\IPS\CIC )
{
@@ -1276,7 +1303,7 @@ class _Query extends \IPS\Content\Search\Query
}
/* If we're filtering by unread, and not using MariaDB, then force an index because MySQL tends to use the incorrect one in this scenario. */
if ( !mb_stristr( \IPS\Db::i()->server_info, '-MariaDB' ) and $this->filterByUnread === TRUE )
if ( !mb_stristr( \IPS\Db::i()->server_info, '-MariaDB' ) and $this->filterByUnread === TRUE and \IPS\Db::i()->server_version < 80000 )
{
$this->forceIndex = 'index_date_updated';
}
@@ -1288,7 +1315,7 @@ class _Query extends \IPS\Content\Search\Query
if ( mb_substr( $this->order, 0, 9 ) === 'calcscore' )
{
/* But we can only do that if there's a term (rather than tag-only) */
if ( $term !== NULL )
if ( static::canUseRelevancy() and $term !== NULL )
{
if ( \IPS\Settings::i()->search_title_boost )
{
@@ -1311,7 +1338,7 @@ class _Query extends \IPS\Content\Search\Query
. ',1.5)' // To the power of 1.5
. ' AS calcscore';
}
/* So if we don't have a term, fallback to last updated */
/* So if we don't have a term ot can't use relevancy, fallback to last updated */
else
{
$order = 'index_date_updated DESC';
@@ -1369,7 +1396,7 @@ class _Query extends \IPS\Content\Search\Query
*/
public function getDefaultDateCutOff()
{
if ( \IPS\USE_MYSQL_SEARCH_BASIC_MODE_THRESHOLD and static::getTableSize() >= \IPS\USE_MYSQL_SEARCH_BASIC_MODE_THRESHOLD )
if ( \IPS\USE_MYSQL_SEARCH_OPTIMIZED_MODE )
{
return 'year';
}
@@ -1384,13 +1411,23 @@ class _Query extends \IPS\Content\Search\Query
*/
public function getDefaultSortMethod()
{
if ( \IPS\USE_MYSQL_SEARCH_BASIC_MODE_THRESHOLD and static::getTableSize() >= \IPS\USE_MYSQL_SEARCH_BASIC_MODE_THRESHOLD )
if ( \IPS\USE_MYSQL_SEARCH_OPTIMIZED_MODE )
{
return 'newest';
}
return parent::getDefaultSortMethod();
}
/**
* Can use relevancy?
*
* @return boolean
*/
public function canUseRelevancy(): bool
{
return ! \IPS\USE_MYSQL_SEARCH_OPTIMIZED_MODE;
}
protected static $tableCount = NULL;
@@ -1403,9 +1440,27 @@ class _Query extends \IPS\Content\Search\Query
{
if ( static::$tableCount === NULL )
{
/* Try and be more efficient */
try
{
if( $result = \IPS\Db::i()->query( "SELECT TABLE_ROWS as _size FROM `information_schema`.`TABLES` WHERE TABLE_SCHEMA = '" . \IPS\Settings::i()->sql_database . "' AND TABLE_NAME='" . \IPS\Db::i()->prefix . "core_search_index'" ) )
{
if( $resultSet = $result->fetch_assoc() )
{
static::$tableCount = (int) $resultSet['_size'];
}
}
}
catch( \Exception $e ) { }
}
/* Did that fail? */
if ( static::$tableCount === NULL )
{
/* Do it the old-fashioned way */
static::$tableCount = \IPS\Db::i()->select( 'COUNT(*)', 'core_search_index' )->first();
}
return static::$tableCount;
}
+10
View File
@@ -283,6 +283,16 @@ abstract class _Query
{
return 'relevancy';
}
/**
* Can use relevancy?
*
* @return boolean
*/
public function canUseRelevancy(): bool
{
return true;
}
/**
* Get count
+8 -2
View File
@@ -248,9 +248,15 @@ class _Results extends \ArrayIterator
}
/* Load tags and prefixes */
if ( \count( $itemIndexIds ) and \IPS\Settings::i()->search_method == 'mysql' )
if ( \count( $itemIndexIds ) and \IPS\Settings::i()->search_method == 'mysql' and \IPS\Settings::i()->tags_enabled )
{
$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') );
$orderBy = '';
if ( \IPS\Settings::i()->tags_alphabetical )
{
$orderBy = ' ORDER BY index_tag ASC';
}
$this->tags = iterator_to_array( \IPS\Db::i()->select( 'index_id, GROUP_CONCAT(index_tag' . $orderBy . ') 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') );
}