Files
IPB/system/Content/Search/Query.php
T
2025-12-19 05:32:19 -08:00

293 lines
8.0 KiB
PHP

<?php
/**
* @brief Abstract Search Query
* @author <a href='http://www.invisionpower.com'>Invision Power Services, Inc.</a>
* @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc.
* @license http://www.invisionpower.com/legal/standards/
* @package IPS Social Suite
* @since 21 Aug 2014
* @version SVN_VERSION_NUMBER
*/
namespace IPS\Content\Search;
/* 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;
}
/**
* Abstract Search Query
*/
abstract class _Query
{
const TERM_OR_TAGS = 1;
const TERM_AND_TAGS = 2;
const TERM_OR_MODE = 4;
const TERM_TITLES_ONLY = 8;
const HIDDEN_VISIBLE = 0;
const HIDDEN_UNAPPROVED = 1;
const HIDDEN_HIDDEN = -1;
const HIDDEN_PARENT_HIDDEN = 2;
const ORDER_NEWEST_UPDATED = 1;
const ORDER_NEWEST_CREATED = 2;
const ORDER_RELEVANCY = 3;
const ORDER_OLDEST_UPDATED = 4;
const ORDER_OLDEST_CREATED = 5;
const SUPPORTS_JOIN_FILTERS = TRUE;
/**
* Create new query
*
* @param \IPS\Member $member The member performing the search (NULL for currently logged in member)
* @return \IPS\Content\Search
*/
public static function init( \IPS\Member $member = NULL )
{
return new \IPS\Content\Search\Mysql\Query( $member ?: \IPS\Member::loggedIn() );
}
/**
* @brief Number of results to get
*/
public $resultsToGet = 25;
/**
* @brief The member performing the search
*/
protected $member;
/**
* Constructor
*
* @param \IPS\Member $member The member performing the search
* @return void
*/
public function __construct( \IPS\Member $member )
{
$this->member = $member;
/* Exclude hidden items */
if ( !$member->modPermission('can_view_hidden_content') )
{
$this->setHiddenFilter( static::HIDDEN_VISIBLE );
}
/* Exclude disabled applications */
$filters = array();
foreach ( \IPS\Application::applications() as $application )
{
if ( !$application->enabled )
{
foreach ( $application->extensions( 'core', 'ContentRouter' ) as $extension )
{
foreach ( $extension->classes as $class )
{
$filters[] = \IPS\Content\Search\ContentFilter::init( $class );
}
}
}
}
if ( !empty( $filters ) )
{
$this->filterByContent( $filters, FALSE );
}
}
/**
* Filter by multiple content types
*
* @param array $contentFilters Array of \IPS\Content\Search\ContentFilter objects
* @param bool $type TRUE means only include results matching the filters, FALSE means exclude all results matching the filters
* @return \IPS\Content\Search\Query (for daisy chaining)
*/
abstract public function filterByContent( array $contentFilters, $type = TRUE );
/**
* Filter by author
*
* @param \IPS\Member|int|array $author The author, or an array of author IDs
* @return \IPS\Content\Search\Query (for daisy chaining)
*/
abstract public function filterByAuthor( $author );
/**
* Filter for profile
*
* @param \IPS\Member $member The member whose profile is being viewed
* @return \IPS\Content\Search\Query (for daisy chaining)
*/
abstract public function filterForProfile( \IPS\Member $member );
/**
* Filter by item author
*
* @param \IPS\Member $author The author
* @return \IPS\Content\Search\Query (for daisy chaining)
*/
abstract public function filterByItemAuthor( \IPS\Member $author );
/**
* 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 $includeContainers Include content posted by members the user follows?
* @return \IPS\Content\Search\Query (for daisy chaining)
*/
abstract public function filterByFollowed( $includeContainers, $includeItems, $includeMembers );
/**
* Filter by content the user has posted in
*
* @return \IPS\Content\Search\Query (for daisy chaining)
*/
abstract public function filterByItemsIPostedIn();
/**
* 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)
*/
abstract public function filterByUnread();
/**
* Filter by start date
*
* @param \IPS\DateTime|NULL $start The start date (only results AFTER this date will be returned)
* @param \IPS\DateTime|NULL $end The end date (only results BEFORE this date will be returned)
* @return \IPS\Content\Search\Query (for daisy chaining)
*/
abstract public function filterByCreateDate( \IPS\DateTime $start = NULL, \IPS\DateTime $end = NULL );
/**
* Filter by last updated date
*
* @param \IPS\DateTime|NULL $start The start date (only results AFTER this date will be returned)
* @param \IPS\DateTime|NULL $end The end date (only results BEFORE this date will be returned)
* @return \IPS\Content\Search\Query (for daisy chaining)
*/
abstract public function filterByLastUpdatedDate( \IPS\DateTime $start = NULL, \IPS\DateTime $end = NULL );
/**
* Set hidden status
*
* @param int|array|NULL $statuses The statuses (see HIDDEN_ constants) or NULL for any
* @return \IPS\Content\Search\Query (for daisy chaining)
*/
abstract public function setHiddenFilter( $statuses );
/**
* Set limit
*
* @param int $limit Number per page
* @return \IPS\Content\Search\Query (for daisy chaining)
*/
public function setLimit( $limit )
{
$this->resultsToGet = $limit;
return $this;
}
/**
* Set page
*
* @param int $page The page number
* @return \IPS\Content\Search\Query (for daisy chaining)
*/
abstract public function setPage( $page );
/**
* Set order
*
* @param int $order Order (see ORDER_ constants)
* @return \IPS\Content\Search\Query (for daisy chaining)
*/
abstract public function setOrder( $order );
/**
* Permission Array
*
* @return array
*/
public function permissionArray()
{
return $this->member->permissionArray();
}
/**
* Search
*
* @param string|null $term The term to search for
* @param array|null $tags The tags to search for
* @param int $method \IPS\Content\Search\Index::i()->TERM_OR_TAGS or \IPS\Content\Search\Index::i()->TERM_AND_TAGS
* @return \Traversable
*/
abstract public function search( $term = NULL, $tags = NULL, $method = 1 );
/**
* Get count
*
* @param string|null $term The term to search for
* @param array|null $tags The tags to search for
* @param int $method \IPS\Content\Search\Index::i()->TERM_OR_TAGS or \IPS\Content\Search\Index::i()->TERM_AND_TAGS
* @return int
*/
public function count( $term = NULL, $tags = NULL, $method = 1 )
{
return $this->search( $term, $tags, $method )->count( TRUE );
}
/**
* Is this term a phrase?
*
* @param string $term The term to search for
* @return boolean
*/
public static function termIsPhrase( $term )
{
return (boolean) preg_match( '#^".*"$#', $term );
}
/**
* Term as words array
* Returns an array of words. A word is considered as either an entire phrase (eg "sticks stones") when $ignorePhrase is FALSE, or a sequence of characters delimited by a space.
* Words smaller than 2 characters are ignored.
*
* @param string $term The term to search for
* @param boolean $ignorePhrase When true, phrases are stripped of quotes and treated as normal words
* @param int $minLength The minimum length a sequence of characters has to be before it is considered a word
* @return array
*/
public static function termAsWordsArray( $term, $ignorePhrase=FALSE, $minLength=3 )
{
if ( static::termIsPhrase( $term ) )
{
if ( ! $ignorePhrase )
{
return array( $term );
}
else
{
$term = str_replace( '"', '', $term );
}
}
$theWords = array();
foreach( explode( ' ', $term ) as $_term )
{
if ( mb_strlen( $_term ) > $minLength )
{
$theWords[] = $_term;
}
}
return $theWords;
}
}