Version 4.5.0

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

No files matched your search

+91 -68
View File
@@ -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'] );
}
}
}