Version 4.1.2
This commit is contained in:
1 parent
3ba79295ee
commit
28bd025b35
1131 files changed
+63658
-20579
No files matched your search
@@ -24,6 +24,11 @@ if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
class _Query extends \IPS\Content\Search\Query
|
||||
{
|
||||
/**
|
||||
* @brief The SELECT clause
|
||||
*/
|
||||
protected $select = array( 'main' => 'main.*' );
|
||||
|
||||
/**
|
||||
* @brief The WHERE clause
|
||||
*/
|
||||
protected $where = array();
|
||||
@@ -49,137 +54,465 @@ class _Query extends \IPS\Content\Search\Query
|
||||
protected $joins = array();
|
||||
|
||||
/**
|
||||
* Filter by content type
|
||||
* @brief Item classes included
|
||||
*/
|
||||
protected $itemClasses = NULL;
|
||||
|
||||
/**
|
||||
* Filter by multiple content types
|
||||
*
|
||||
* @param string $class The type of content to search (including all comment/review classes)
|
||||
* @param array|NULL $containers An array of container IDs to filter by, or NULL to not filter by containers
|
||||
* @param int|NULL $minimumComments The minimum number of comments
|
||||
* @param int|NULL $minimumReviews The minimum number of reviews
|
||||
* @param int|NULL $minimumViews The minimum number of views
|
||||
* @param array $contentFilters Array of \IPS\Content\Search\ContentFilter objects
|
||||
* @param bool $type TRUE means only include results matching the filters, FALSE means exclude all results matching the filters
|
||||
* @return \IPS\Content\Search\Query (for daisy chaining)
|
||||
*/
|
||||
public function filterByContent( $class, $containers = NULL, $minimumComments = NULL, $minimumReviews = NULL, $minimumViews = NULL )
|
||||
public function filterByContent( array $contentFilters, $type = TRUE )
|
||||
{
|
||||
$classes = array( $class );
|
||||
if ( isset( $class::$commentClass ) )
|
||||
/* Init */
|
||||
$filters = array();
|
||||
$params = array();
|
||||
if ( $type )
|
||||
{
|
||||
$classes[] = $class::$commentClass;
|
||||
}
|
||||
if ( isset( $class::$reviewClass ) )
|
||||
{
|
||||
$classes[] = $class::$reviewClass;
|
||||
}
|
||||
$this->where[] = array( \IPS\Db::i()->in( 'index_class', $classes ) );
|
||||
|
||||
if ( $containers !== NULL )
|
||||
{
|
||||
$this->where[] = array( \IPS\Db::i()->in( 'index_container_id', $containers ) );
|
||||
$this->itemClasses = array();
|
||||
}
|
||||
|
||||
if ( $minimumComments !== NULL or $minimumReviews !== NULL or $minimumViews !== NULL )
|
||||
/* Loop the filters */
|
||||
foreach ( $contentFilters as $filter )
|
||||
{
|
||||
$this->joins[ $class::$databaseTable ] = array( $class::$databaseTable . '.' . $class::$databasePrefix . $class::$databaseColumnId . '=core_search_index.index_item_id' );
|
||||
$clause = array();
|
||||
if ( $type )
|
||||
{
|
||||
$this->itemClasses[] = $filter->itemClass;
|
||||
}
|
||||
|
||||
if ( $minimumComments !== NULL )
|
||||
/* Set the class */
|
||||
if ( count( $filter->classes ) > 1 )
|
||||
{
|
||||
$this->where[] = array( $class::$databasePrefix . $class::$databaseColumnMap['num_comments'] . '>=?', $minimumComments );
|
||||
$clause[] = \IPS\Db::i()->in( 'index_class', $filter->classes );
|
||||
}
|
||||
if ( $minimumReviews !== NULL )
|
||||
else
|
||||
{
|
||||
$this->where[] = array( $class::$databasePrefix . $class::$databaseColumnMap['num_reviews'] . '>=?', $minimumReviews );
|
||||
$clause[] = 'index_class=?';
|
||||
$params[] = array_pop( $filter->classes );
|
||||
}
|
||||
if ( $minimumViews !== NULL )
|
||||
|
||||
/* Set the containers */
|
||||
if ( $filter->containerIdFilter !== NULL )
|
||||
{
|
||||
$this->where[] = array( $class::$databasePrefix . $class::$databaseColumnMap['views'] . '>=?', $minimumViews );
|
||||
$clause[] = \IPS\Db::i()->in( 'index_container_id', $filter->containerIds, $filter->containerIdFilter === FALSE );
|
||||
}
|
||||
|
||||
/* Set the item IDs */
|
||||
if ( $filter->itemIdFilter !== NULL )
|
||||
{
|
||||
$clause[] = \IPS\Db::i()->in( 'index_item_id', $filter->itemIds, $filter->itemIdFilter === FALSE );
|
||||
}
|
||||
|
||||
/* Minimum comments/reviews/views? */
|
||||
if ( $filter->minimumComments or $filter->minimumReviews or $filter->minimumViews )
|
||||
{
|
||||
$class = $filter->itemClass;
|
||||
|
||||
$this->joins[] = array( 'from' => $class::$databaseTable, 'where' => $class::$databaseTable . '.' . $class::$databasePrefix . $class::$databaseColumnId . '=main.index_item_id' );
|
||||
|
||||
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 );
|
||||
}
|
||||
|
||||
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 );
|
||||
}
|
||||
|
||||
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 );
|
||||
}
|
||||
}
|
||||
|
||||
/* Only first comment? */
|
||||
if ( $filter->onlyFirstComment )
|
||||
{
|
||||
$clause[] = "index_title IS NOT NULL";
|
||||
}
|
||||
|
||||
/* Only last comment? */
|
||||
if ( $filter->onlyLastComment )
|
||||
{
|
||||
$clause[] = "index_is_last_comment=1";
|
||||
}
|
||||
|
||||
/* Put it together */
|
||||
if ( count( $clause ) > 1 )
|
||||
{
|
||||
$filters[] = '( ' . implode( ' AND ', $clause ) . ' )';
|
||||
}
|
||||
else
|
||||
{
|
||||
$filters[] = array_pop( $clause );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Put it all together */
|
||||
$this->where[] = array_merge( array( $type ? ( '( ' . implode( ' OR ', $filters ) . ' )' ) : ( '!( ' . implode( ' OR ', $filters ) . ' )' ) ), $params );
|
||||
|
||||
/* Return */
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Filter by content type to exclude
|
||||
* Filter by author
|
||||
*
|
||||
* @param array $classes The types of content to exclude (not including all comment/review classes)
|
||||
* @param bool $allowedContiners If TRUE, $class should be an array of allowed containers for that class - e.g. array( 'IPS\forums\Topic' => array( 1, 2 ) - otherwise, a simple array - e.g. array( 'IPS\forums\Topic' )
|
||||
* @param \IPS\Member|int|array $author The author, or an array of author IDs
|
||||
* @return \IPS\Content\Search\Query (for daisy chaining)
|
||||
*/
|
||||
public function filterByExcludeContent( array $classes, $allowedContiners=FALSE )
|
||||
{
|
||||
if ( $allowedContiners )
|
||||
public function filterByAuthor( $author )
|
||||
{
|
||||
if ( is_array( $author ) )
|
||||
{
|
||||
$where = array();
|
||||
$params = array();
|
||||
$excludeAll = array();
|
||||
foreach ( $classes as $class => $allowed )
|
||||
{
|
||||
if ( count( $allowed ) )
|
||||
{
|
||||
$where[] = '( index_class<>? OR ' . \IPS\Db::i()->in( 'index_container_id', $allowed ) . ' )';
|
||||
$params[] = $class;
|
||||
}
|
||||
else
|
||||
{
|
||||
$excludeAll[] = $class;
|
||||
}
|
||||
}
|
||||
|
||||
$finalWhere = array();
|
||||
if ( count( $excludeAll ) )
|
||||
{
|
||||
$finalWhere[] = \IPS\Db::i()->in( 'index_class', $excludeAll, TRUE );
|
||||
}
|
||||
if ( count( $where ) )
|
||||
{
|
||||
$finalWhere[] = implode( ' OR ', $where );
|
||||
}
|
||||
|
||||
$this->where[] = array_merge( array( implode( ' AND ', $finalWhere ) ), $params );
|
||||
$this->where[] = array( \IPS\Db::i()->in( 'index_author', $author ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
$this->where[] = array( \IPS\Db::i()->in( 'index_class', $classes, TRUE ) );
|
||||
$this->where[] = array( 'index_author=?', $author instanceof \IPS\Member ? $author->member_id : $author );
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by content item
|
||||
* Filter for profile
|
||||
*
|
||||
* @param \IPS\Content\Item $item The item
|
||||
* @param \IPS\Member $member The member whose profile is being viewed
|
||||
* @return \IPS\Content\Search\Query (for daisy chaining)
|
||||
*/
|
||||
public function filterByItem( \IPS\Content\Item $item )
|
||||
public function filterForProfile( \IPS\Member $member )
|
||||
{
|
||||
$classes = array();
|
||||
if ( isset( $item::$commentClass ) )
|
||||
{
|
||||
$classes[] = $item::$commentClass;
|
||||
}
|
||||
if ( isset( $item::$reviewClass ) )
|
||||
{
|
||||
$classes[] = $item::$reviewClass;
|
||||
}
|
||||
|
||||
$this->where[] = array( \IPS\Db::i()->in( 'index_class', $classes ) );
|
||||
|
||||
$idColumn = $item::$databaseColumnId;
|
||||
$this->where[] = array( 'index_item_id=?', $item->$idColumn );
|
||||
|
||||
$this->where[] = array( '( index_author=? OR ( index_class=? AND index_container_id=? ) )', $member->member_id, 'IPS\core\Statuses\Status', $member->member_id );
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by author
|
||||
* Filter by item author
|
||||
*
|
||||
* @param \IPS\Member $author The author
|
||||
* @return \IPS\Content\Search\Query (for daisy chaining)
|
||||
*/
|
||||
public function filterByAuthor( \IPS\Member $author )
|
||||
public function filterByItemAuthor( \IPS\Member $author )
|
||||
{
|
||||
$this->where[] = array( 'index_author=?', $author->member_id );
|
||||
$this->where[] = array( 'index_item_author=?', $author->member_id );
|
||||
|
||||
return $this;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by content the user follows
|
||||
*
|
||||
* @param bool $includeContainers Include content in containers the user follows?
|
||||
* @param bool $includeItems Include items and comments/reviews on items the user follows?
|
||||
* @param bool $includeMembers Include content posted by members the user follows?
|
||||
* @return \IPS\Content\Search\Query (for daisy chaining)
|
||||
*/
|
||||
public function filterByFollowed( $includeContainers, $includeItems, $includeMembers )
|
||||
{
|
||||
$where = array();
|
||||
$params = array();
|
||||
|
||||
/* Are we including items or containers? */
|
||||
if ( $includeContainers or $includeItems )
|
||||
{
|
||||
/* Work out what classes we need to examine */
|
||||
if ( $this->itemClasses !== NULL )
|
||||
{
|
||||
$classes = $this->itemClasses;
|
||||
}
|
||||
else
|
||||
{
|
||||
$classes = array();
|
||||
foreach ( \IPS\Application::allExtensions( 'core', 'ContentRouter', FALSE ) as $object )
|
||||
{
|
||||
$classes = array_merge( $object->classes, $classes );
|
||||
}
|
||||
}
|
||||
|
||||
/* Loop them */
|
||||
foreach ( $classes as $class )
|
||||
{
|
||||
if( is_subclass_of( $class, 'IPS\Content\Followable' ) )
|
||||
{
|
||||
/* Construct a subquery which will get the IDs of the items of this class that we follow */
|
||||
$subQueryWhere = array( array( 'follow_app=?', $class::$application ) );
|
||||
if ( $includeContainers and $includeItems )
|
||||
{
|
||||
$subQueryWhere[] = array( '( follow_area=? OR follow_area=? )', mb_strtolower( mb_substr( $class::$containerNodeClass, mb_strrpos( $class::$containerNodeClass, '\\' ) + 1 ) ), mb_strtolower( mb_substr( $class, mb_strrpos( $class, '\\' ) + 1 ) ) );
|
||||
}
|
||||
elseif ( $includeItems )
|
||||
{
|
||||
$subQueryWhere[] = array( 'follow_area=?', mb_strtolower( mb_substr( $class, mb_strrpos( $class, '\\' ) + 1 ) ) );
|
||||
}
|
||||
elseif ( $includeContainers )
|
||||
{
|
||||
$subQueryWhere[] = array( 'follow_area=?', mb_strtolower( mb_substr( $class::$containerNodeClass, mb_strrpos( $class::$containerNodeClass, '\\' ) + 1 ) ) );
|
||||
}
|
||||
$subQueryWhere[] = array( 'follow_member_id=?', $this->member->member_id );
|
||||
$subQuery = \IPS\Db::i()->select( 'follow_rel_id', 'core_follow', $subQueryWhere );
|
||||
|
||||
/* If we're only doing one class, there's no point in doing "item_class=? AND ( item_class=? AND item_id IN(?) )" - we can just do "item_class=? AND item_id IN(?)" */
|
||||
if ( count( $classes ) == 1 )
|
||||
{
|
||||
$where[] = 'index_item_id IN(?)';
|
||||
$params[] = $subQuery;
|
||||
}
|
||||
/* But otherwise we need to group by class, because item IDs are not unique */
|
||||
else
|
||||
{
|
||||
/* Work out what classes this applies to - need to specify comment and review classes */
|
||||
$_classes = array( $class );
|
||||
if ( isset( $class::$commentClass ) )
|
||||
{
|
||||
$_classes[] = $class::$commentClass;
|
||||
}
|
||||
if ( isset( $class::$reviewClass ) )
|
||||
{
|
||||
$_classes[] = $class::$reviewClass;
|
||||
}
|
||||
|
||||
/* Constuct the where */
|
||||
$where[] = '( ' . \IPS\Db::i()->in( 'index_class', $_classes ) . ' AND index_item_id IN( ? ) )';
|
||||
$params[] = $subQuery;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Are we including content posted by followed members? */
|
||||
if ( $includeMembers )
|
||||
{
|
||||
$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 ) );
|
||||
}
|
||||
|
||||
/* Put it all together */
|
||||
if ( count( $where ) )
|
||||
{
|
||||
$this->where[] = array_merge( array( '( ' . implode( ' OR ', $where ) . ' )' ), $params );
|
||||
}
|
||||
|
||||
/* And return */
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter by content the user has posted in
|
||||
*
|
||||
* @return \IPS\Content\Search\Query (for daisy chaining)
|
||||
*/
|
||||
public function filterByItemsIPostedIn()
|
||||
{
|
||||
$this->joins[] = array(
|
||||
'from' => \IPS\Db::i()->select(
|
||||
'index_item_index_id',
|
||||
array('core_search_index', 'sub' ),
|
||||
array( array( 'index_author=' . intval( $this->member->member_id ) ) ),
|
||||
NULL, NULL, NULL, NULL, \IPS\Db::SELECT_DISTINCT
|
||||
),
|
||||
'where' => 'sub.index_item_index_id=main.index_item_index_id',
|
||||
'type' => 'STRAIGHT_JOIN'
|
||||
);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Filter by content the user has not read
|
||||
*
|
||||
* @note If applicable, it is more efficient to call filterByContent() before calling this method
|
||||
* @return \IPS\Content\Search\Query (for daisy chaining)
|
||||
*/
|
||||
public function filterByUnread()
|
||||
{
|
||||
/* Work out what classes we need to examine */
|
||||
if ( $this->itemClasses !== NULL )
|
||||
{
|
||||
$classes = $this->itemClasses;
|
||||
}
|
||||
else
|
||||
{
|
||||
$classes = array();
|
||||
foreach ( \IPS\Application::allExtensions( 'core', 'ContentRouter', FALSE ) as $object )
|
||||
{
|
||||
$classes = array_merge( $object->classes, $classes );
|
||||
}
|
||||
}
|
||||
|
||||
/* Loop them */
|
||||
$where = array();
|
||||
$params = array();
|
||||
foreach ( $classes as $class )
|
||||
{
|
||||
if( is_subclass_of( $class, 'IPS\Content\ReadMarkers' ) )
|
||||
{
|
||||
/* Get the actual clause */
|
||||
$unreadWhere = $this->_getUnreadWhere( $class );
|
||||
|
||||
/* Work out what classes this applies to - need to specify comment and review classes */
|
||||
$_classes = array( $class );
|
||||
if ( isset( $class::$commentClass ) )
|
||||
{
|
||||
$_classes[] = $class::$commentClass;
|
||||
}
|
||||
if ( isset( $class::$reviewClass ) )
|
||||
{
|
||||
$_classes[] = $class::$reviewClass;
|
||||
}
|
||||
|
||||
/* Add it to the array */
|
||||
$clause = array( \IPS\Db::i()->in( 'index_class', $_classes ) );
|
||||
foreach ( $unreadWhere as $_clause )
|
||||
{
|
||||
$clause[] = array_shift( $_clause );
|
||||
$params = array_merge( $params, $_clause );
|
||||
}
|
||||
$where[] = '( ' . implode( ' AND ', $clause ) . ' )';
|
||||
}
|
||||
}
|
||||
|
||||
/* Put it all together */
|
||||
$this->where[] = array_merge( array( '( ' . implode( ' OR ', $where ) . ' )' ), $params );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the 'unread' where SQL
|
||||
*
|
||||
* @param string $class Content class (\IPS\forums\Forum)
|
||||
* @return array
|
||||
*/
|
||||
protected function _getUnreadWhere( $class )
|
||||
{
|
||||
$classBits = explode( "\\", $class );
|
||||
$application = $classBits[1];
|
||||
$resetTimes = $this->member->markersResetTimes( NULL );
|
||||
$resetTimes = isset( $resetTimes[ $application ] ) ? $resetTimes[ $application ] : array();
|
||||
$oldestTime = time();
|
||||
$markers = array();
|
||||
$excludeIds = array();
|
||||
$where = array();
|
||||
$unreadWheres = array();
|
||||
$containerIds = array();
|
||||
$containerClass = ( $class::$containerNodeClass ) ? $class::$containerNodeClass : NULL;
|
||||
|
||||
foreach( $resetTimes as $containerId => $timestamp )
|
||||
{
|
||||
/* Pages has different classes per database, but recorded as 'cms' and the container ID in the marking tables */
|
||||
if ( $containerClass and method_exists( $containerClass, 'isValidContainerId' ) )
|
||||
{
|
||||
if ( ! $containerClass::isValidContainerId( $containerId ) )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$timestamp = $timestamp ?: $this->member->marked_site_read;
|
||||
|
||||
$containerIds[] = $containerId;
|
||||
$unreadWheres[] = '( index_container_id=' . $containerId . ' AND index_date_updated > ' . (int) $timestamp . ')';
|
||||
|
||||
$items = $this->member->markersItems( $application, \IPS\Content\Item::makeMarkerKey( $containerId ) );
|
||||
|
||||
if ( count( $items ) )
|
||||
{
|
||||
foreach( $items as $mid => $mtime )
|
||||
{
|
||||
if ( $mtime > $timestamp )
|
||||
{
|
||||
/* If an item has been moved from one container to another, the user may have a marker
|
||||
in it's old location, with the previously 'read' time. In this circumstance, we need
|
||||
to only use more recent read time, otherwise the topic may be incorrectly included
|
||||
in the results */
|
||||
if ( in_array( $mid, $markers ) )
|
||||
{
|
||||
$_key = array_search( $mid, $markers );
|
||||
$_mtime = intval( mb_substr( $_key, 0, mb_strpos( $_key, '.' ) ) );
|
||||
if ( $_mtime < $mtime )
|
||||
{
|
||||
unset( $markers[ $_key ] );
|
||||
}
|
||||
}
|
||||
|
||||
$markers[ $mtime . '.' . $mid ] = $mid;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( count( $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 ) . ")";
|
||||
}
|
||||
|
||||
if( count( $unreadWheres ) )
|
||||
{
|
||||
$where[] = array( "(" . implode( " OR ", $unreadWheres ) . ")" );
|
||||
}
|
||||
|
||||
if ( count( $markers ) )
|
||||
{
|
||||
/* Avoid packet issues */
|
||||
krsort( $markers );
|
||||
$useIds = array_flip( array_slice( $markers, 0, 500, TRUE ) );
|
||||
$select = '';
|
||||
$from = '';
|
||||
$notIn = array();
|
||||
|
||||
/* What is the best date column? */
|
||||
$dateColumns = array();
|
||||
foreach ( array( 'updated', 'last_comment', 'last_review' ) as $k )
|
||||
{
|
||||
if ( isset( $class::$databaseColumnMap[ $k ] ) )
|
||||
{
|
||||
if ( is_array( $class::$databaseColumnMap[ $k ] ) )
|
||||
{
|
||||
foreach ( $class::$databaseColumnMap[ $k ] as $v )
|
||||
{
|
||||
$dateColumns[] = " IFNULL( " . $class::$databaseTable . '.'. $class::$databasePrefix . $v . ", 0 )";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$dateColumns[] = " IFNULL( " . $class::$databaseTable . '.'. $class::$databasePrefix . $class::$databaseColumnMap[ $k ] . ", 0 )";
|
||||
}
|
||||
}
|
||||
}
|
||||
$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 )
|
||||
{
|
||||
if ( isset( $useIds[ $row['_id'] ] ) )
|
||||
{
|
||||
if ( $useIds[ $row['_id'] ] >= $row['_date'] )
|
||||
{
|
||||
/* Still read */
|
||||
$notIn[] = intval( $row['_id'] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( count( $notIn ) )
|
||||
{
|
||||
$where[] = array( "( index_item_id NOT IN (" . implode( ',', $notIn ) . ") )" );
|
||||
}
|
||||
}
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -272,13 +605,21 @@ class _Query extends \IPS\Content\Search\Query
|
||||
case static::ORDER_NEWEST_UPDATED:
|
||||
$this->order = 'index_date_updated DESC';
|
||||
break;
|
||||
|
||||
case static::ORDER_OLDEST_UPDATED:
|
||||
$this->order = 'index_date_updated ASC';
|
||||
break;
|
||||
|
||||
case static::ORDER_NEWEST_CREATED:
|
||||
$this->order = 'index_date_created DESC';
|
||||
break;
|
||||
|
||||
case static::ORDER_OLDEST_CREATED:
|
||||
$this->order = 'index_date_updated DESC';
|
||||
break;
|
||||
|
||||
case static::ORDER_RELEVANCY:
|
||||
$this->order = NULL;
|
||||
$this->order = 'calcscore DESC';
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -295,75 +636,86 @@ class _Query extends \IPS\Content\Search\Query
|
||||
*/
|
||||
protected function _searchWhereClause( $term = NULL, $tags = NULL, $method = 1 )
|
||||
{
|
||||
/* Do we have a term? */
|
||||
$where = array();
|
||||
if ( $term !== NULL )
|
||||
/* Boolean tags */
|
||||
if ( $tags !== NULL )
|
||||
{
|
||||
if( mb_strpos( $term, ' ' ) !== FALSE AND mb_strpos( $term, '+' ) === FALSE AND mb_strpos( $term, '-' ) === FALSE AND mb_strpos( $term, '"' ) === FALSE )
|
||||
$theTags = array();
|
||||
foreach( $tags as $tagEntry )
|
||||
{
|
||||
$terms = explode( ' ', $term );
|
||||
$newTerms = array();
|
||||
|
||||
foreach( $terms as $_term )
|
||||
foreach( explode( ' ', $tagEntry ) as $_term )
|
||||
{
|
||||
if( mb_strlen( $_term ) > 2 )
|
||||
{
|
||||
$newTerms[] = "+" . $_term;
|
||||
$theTags[] = "+" . $_term;
|
||||
}
|
||||
else
|
||||
{
|
||||
$newTerms[] = $_term;
|
||||
$theTags[] = $_term;
|
||||
}
|
||||
}
|
||||
|
||||
$term = implode( ' ', $newTerms );
|
||||
|
||||
$searchTags[] = implode( ' ', $theTags );
|
||||
}
|
||||
}
|
||||
|
||||
/* Try to guess what mode will be best for this query. If it looks like a MySQL boolean mode search, use that */
|
||||
if ( mb_substr( $term, 0, 1 ) === '+' or mb_substr( $term, -1, 1 ) === '*' or preg_match( '/^".*"$/', $term ) )
|
||||
/* Do we have a term? */
|
||||
$where = array();
|
||||
if ( $term !== NULL )
|
||||
{
|
||||
$indexes = 'index_content,index_title';
|
||||
if ( $method & static::TERM_TITLES_ONLY )
|
||||
{
|
||||
$mode = 'IN BOOLEAN MODE';
|
||||
$indexes = 'index_title';
|
||||
}
|
||||
/* Otherwise, we're just using the default mode - we tried using query expansion for short queries, but it didn't work well (lots of irrelevant results) */
|
||||
else
|
||||
|
||||
/* Default to 'AND' boolean mode search unless asked otherwise*/
|
||||
if ( ! ( $method & static::TERM_OR_MODE ) and ! static::termIsPhrase( $term ) )
|
||||
{
|
||||
$mode = '';
|
||||
$theWords = array();
|
||||
foreach( static::termAsWordsArray( $term ) as $_term )
|
||||
{
|
||||
$potentialOperator = mb_substr( $_term, 0, 1 );
|
||||
|
||||
if ( ! in_array( $potentialOperator, array( '-', '+', '~' ) ) )
|
||||
{
|
||||
if( mb_strlen( $_term ) > 2 )
|
||||
{
|
||||
$theWords[] = "+" . $_term;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$term = implode( ' ', $theWords );
|
||||
}
|
||||
|
||||
/* If we also have tags, create a combined where */
|
||||
if ( $tags !== NULL )
|
||||
{
|
||||
if ( $method === static::TERM_OR_TAGS )
|
||||
{
|
||||
$where[] = array( "( MATCH(index_content,index_title) AGAINST (? {$mode}) OR " . \IPS\Db::i()->findInSet( 'index_tags', $tags ) . ' )', $term );
|
||||
if ( $method & static::TERM_OR_TAGS )
|
||||
{
|
||||
$where[] = array( "( MATCH({$indexes}) AGAINST (? IN BOOLEAN MODE) ) OR " . \IPS\Db::i()->findInSet( 'index_tags', $tags ), $term );
|
||||
}
|
||||
else
|
||||
{
|
||||
$where[] = array( "MATCH(index_content,index_title) AGAINST (? {$mode})", $term );
|
||||
$where[] = array( \IPS\Db::i()->findInSet( 'index_tags', $tags ) );
|
||||
$where[] = array( "MATCH({$indexes}) AGAINST (? IN BOOLEAN MODE)", $term );
|
||||
$where[] = \IPS\Db::i()->findInSet( 'index_tags', $tags );
|
||||
}
|
||||
}
|
||||
/* Or just use the term */
|
||||
else
|
||||
{
|
||||
$where[] = array( "MATCH(index_content,index_title) AGAINST (? {$mode})", $term );
|
||||
$where[] = array( "MATCH({$indexes}) AGAINST (? IN BOOLEAN MODE)", $term );
|
||||
}
|
||||
}
|
||||
/* Or do we have tags? */
|
||||
elseif ( $tags !== NULL )
|
||||
{
|
||||
//$this->where[] = array( \IPS\Db::i()->findInSet( 'index_tags', $tags ) );
|
||||
|
||||
$tagWhere = array();
|
||||
$params = array();
|
||||
|
||||
foreach( $tags as $tag )
|
||||
{
|
||||
$where[] = \IPS\Db::i()->findInSet( 'index_tags', $tags );
|
||||
|
||||
if ( $this->order = 'calcscore DESC' )
|
||||
{
|
||||
array_push( $params, $tag, $tag, $tag, $tag );
|
||||
$tagWhere[] = "( index_tags=? OR index_tags LIKE CONCAT( ?, ',%' ) OR index_tags LIKE CONCAT( '%,', ? ) OR index_tags LIKE CONCAT( '%,', ?, ',%' ) )";
|
||||
$this->setOrder( static::ORDER_NEWEST_UPDATED );
|
||||
}
|
||||
|
||||
$where[] = array_merge( array( '(' . implode( ' OR ', $tagWhere ) . ')' ), $params );
|
||||
}
|
||||
|
||||
/* Only get stuff we have permission for */
|
||||
@@ -373,6 +725,12 @@ class _Query extends \IPS\Content\Search\Query
|
||||
$where[] = $this->hiddenClause;
|
||||
}
|
||||
|
||||
/* MOST PERTINENT search? */
|
||||
if ( mb_substr( $this->order, 0, 9 ) === 'calcscore' )
|
||||
{
|
||||
$this->select['main'] .= ',' . "( ( MATCH (index_title) AGAINST ('" . \IPS\Db::i()->escape_string( $term ) ."' IN BOOLEAN MODE) * 5 ) + (MATCH ({$indexes}) AGAINST ('" . \IPS\Db::i()->escape_string( $term ) ."' IN BOOLEAN MODE) ) ) / POWER( ( ( UNIX_TIMESTAMP( NOW() ) - index_date_updated ) / 3600 ) + 2, 1.5 ) AS calcscore";
|
||||
}
|
||||
|
||||
/* Return */
|
||||
return $where;
|
||||
}
|
||||
@@ -387,13 +745,14 @@ class _Query extends \IPS\Content\Search\Query
|
||||
*/
|
||||
public function search( $term = NULL, $tags = NULL, $method = 1 )
|
||||
{
|
||||
$query = \IPS\Db::i()->select( '*', 'core_search_index', array_merge( $this->where, $this->_searchWhereClause( $term, $tags, $method ) ), $this->order, array( $this->offset, $this->resultsToGet ), NULL, NULL, \IPS\Db::SELECT_SQL_CALC_FOUND_ROWS );
|
||||
foreach ( $this->joins as $table => $where )
|
||||
$where = array_merge( $this->where, $this->_searchWhereClause( $term, $tags, $method ) );
|
||||
$query = \IPS\Db::i()->select( implode( ', ', $this->select ), array( 'core_search_index', 'main' ), $where, $this->order, array( $this->offset, $this->resultsToGet ), NULL, NULL );
|
||||
foreach ( $this->joins as $data )
|
||||
{
|
||||
$query->join( $table, $where );
|
||||
$query->join( $data['from'], $data['where'], isset( $data['type'] ) ? $data['type'] : 'LEFT' );
|
||||
}
|
||||
|
||||
$count = $query->count( TRUE );
|
||||
|
||||
$count = $this->count( $term, $tags, $method );
|
||||
return new \IPS\Content\Search\Results( iterator_to_array( $query ), $count );
|
||||
}
|
||||
|
||||
@@ -407,6 +766,13 @@ class _Query extends \IPS\Content\Search\Query
|
||||
*/
|
||||
public function count( $term = NULL, $tags = NULL, $method = 1 )
|
||||
{
|
||||
return \IPS\Db::i()->select( 'COUNT(*)', 'core_search_index', array_merge( $this->where, $this->_searchWhereClause( $term, $tags, $method ) ) )->first();
|
||||
$query = \IPS\Db::i()->select( 'COUNT(*)', array( 'core_search_index', 'main' ), array_merge( $this->where, $this->_searchWhereClause( $term, $tags, $method ) ) );
|
||||
|
||||
foreach ( $this->joins as $data )
|
||||
{
|
||||
$query->join( $data['from'], $data['where'], isset( $data['type'] ) ? $data['type'] : 'LEFT' );
|
||||
}
|
||||
|
||||
return $query->first();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user