* Invision Power Services * IP.Board v3.4.8 * Global Search * Last Updated: $Date: 2012-10-09 12:16:32 -0400 (Tue, 09 Oct 2012) $ * * * @author $Author: bfarber $ * @copyright (c) 2001 - 2009 Invision Power Services, Inc. * @license http://www.invisionpower.com/company/standards.php#license * @package IP.Board * @subpackage Core * @link http://www.invisionpower.com * @version $Rev: 11427 $ */ if ( ! defined( 'IN_IPB' ) ) { print "

Incorrect access

You cannot access this file directly. If you have recently upgraded, make sure you upgraded all the relevant files."; exit(); } /** * Slightly nice way of dumping vars and stuff globally * Yeah, it's an evil global variable, but it's FANCY */ class IPSSearchRegistry { /** * Registry vars * @var array */ static protected $_vars = array(); /** * Set var * @param mixed Key * @param mixed value */ static public function set( $k, $v ) { if ( $k and isset( $v ) ) { self::$_vars[ $k ] = $v; } } /** * Get var * @param mixed */ static public function get( $k ) { if ( isset( self::$_vars[ $k ] ) ) { return self::$_vars[ $k ]; } return null; } /** * Get is title only * */ static public function searchTitleOnly() { if ( self::get('opt.searchType') == 'titles' ) { return true; } if ( self::get('opt.searchType') == 'both' ) { return false; } if ( self::get('opt.searchType') == 'content' ) { return false; } if ( self::get('opt.noPostPreview') ) { return true; } return false; } } /** * Ties in: * extensions/search/form.php * extensions/search/{engine}.php * extensions/search/format.php */ class IPSSearch { /**#@+ * Registry Object Shortcuts * * @var object */ protected $registry; protected $DB; protected $settings; protected $request; protected $lang; protected $member; protected $memberData; protected $cache; protected $caches; /**#@-*/ /** * Plug in for search * * @var object */ protected $SEARCH; /** * Plug in for formatting results * * @var object */ protected $FORMAT; /** * App * * @var string */ protected $_app; /** * Engine * * @var string */ protected $_engine; /** * Result count * * @param int */ protected $_count; /** * Result array * * @param array */ protected $_results; /** * Raw unformatted result array * * @param array */ protected $_rawResults; static public $aso; static public $ask; static public $ast; /** * Setup registry objects * * @param object ipsRegistry $registry * @return @e void */ public function __construct( ipsRegistry $registry, $engine, $app ) { /* Make object */ $this->registry = $registry; $this->DB = $this->registry->DB(); $this->settings =& $this->registry->fetchSettings(); $this->request =& $this->registry->fetchRequest(); $this->lang = $this->registry->getClass('class_localization'); $this->member = $this->registry->member(); $this->memberData =& $this->registry->member()->fetchMemberData(); $this->cache = $this->registry->cache(); $this->caches =& $this->registry->cache()->fetchCaches(); /* Set engine */ $this->_engine = strtolower( IPSText::alphanumericalClean( $engine ) ); /* Set app */ $this->_app = IPSText::alphanumericalClean( $app ); /* Quick check */ if ( ! is_file( IPS_ROOT_PATH . 'sources/classes/search/engines/' . $this->_engine . '.php' ) ) { /* Try SQL */ if ( $this->_engine != 'sql' ) { $this->_engine = 'sql'; if ( ! is_file( IPS_ROOT_PATH . 'sources/classes/search/engines/' . $this->_engine . '.php' ) ) { throw new Exception( "NO_SUCH_ENGINE" ); } } else { throw new Exception( "NO_SUCH_ENGINE" ); } } if ( ! isset( ipsRegistry::$applications[ $this->_app ] ) ) { throw new Exception( "NO_SUCH_APP" ); } /* Set in registry */ IPSSearchRegistry::set( 'global.engine', $this->_engine ); IPSSearchRegistry::set( 'global.app' , $this->_app ); /* Load up the relevant engines */ require_once( IPS_ROOT_PATH . 'sources/classes/search/format.php' );/*noLibHook*/ /* Got an app specific file? Lets hope so */ if ( is_file( IPSLib::getAppDir( $this->_app ) . '/extensions/search/format.php' ) ) { /* We may not have sphinx specific stuff, so... */ if ( ! is_file( IPSLib::getAppDir( $this->_app ) . '/extensions/search/engines/' . $this->_engine . '.php' ) ) { $this->_engine = 'sql'; IPSSearchRegistry::set( 'global.engine', $this->_engine ); if ( ! is_file( IPSLib::getAppDir( $this->_app ) . '/extensions/search/engines/' . $this->_engine . '.php' ) ) { throw new Exception( "NO_SUCH_APP_ENGINE" ); } } /* SEARCH file */ require_once( IPS_ROOT_PATH . 'sources/classes/search/engines/' . $this->_engine . '.php' );/*noLibHook*/ $classToLoad = IPSLib::loadLibrary( IPSLib::getAppDir( $this->_app ) . '/extensions/search/engines/' . $this->_engine . '.php', 'search_engine_' . $this->_app, $this->_app ); $this->SEARCH = new $classToLoad( $registry ); /* FORMAT file */ $classToLoad = IPSLib::loadLibrary( IPSLib::getAppDir( $this->_app ) . '/extensions/search/format.php', 'search_format_' . $this->_app, $this->_app ); $this->FORMAT = new $classToLoad( $registry ); /* Grab config */ $CONFIG = array(); require( IPSLib::getAppDir( $this->_app ) . '/extensions/search/config.php' );/*noLibHook*/ if ( is_array( $CONFIG ) && count( $CONFIG ) ) { foreach( $CONFIG as $k => $v ) { IPSSearchRegistry::set( 'config.' . $k, $v ); } } } else { throw new Exception( "NO_SUCH_APP_ENGINE" ); } /* Multi content types */ if ( IPSSearchRegistry::get( 'config.contentTypes' ) ) { $c = IPSSearchRegistry::get( 'config.contentTypes' ); if ( is_array( $c ) AND count( $c ) ) { /* Set up default content type if supported */ IPSSearchRegistry::set( $this->_app . '.searchInKey' , $c[0] ); /* Filter specific search */ if ( isset( $this->request['search_app_filters'][ $this->_app ]['searchInKey'] ) ) { IPSSearchRegistry::set( $this->_app . '.searchInKey', $this->request['search_app_filters'][ $this->_app ]['searchInKey'] ); } } } } /** * Magic __call methods * Aka too lazy to create a proper function */ public function __call( $funcName, $args ) { /* Output format stuff.. */ switch ( $funcName ) { case 'isBoolean': return $this->SEARCH->isBoolean(); break; case 'formatSearchTerm': return $this->SEARCH->formatSearchTerm( $args[0] ); break; case 'getResultCount': return $this->_count; break; case 'getResultSet': return $this->_results; break; case 'getRawResultSet': return $this->_rawResults; break; case 'fetchTemplates': return $this->FORMAT->fetchTemplates(); break; case 'fetchSortDropDown': return $this->SEARCH->fetchSortDropDown(); break; /* Primarily shortcuts for 'Content I follow' */ case 'fetchFollowedContentOutput': return $this->FORMAT->parseFollowedContentOutput( $args[0], $args[1] ); break; } } /** * Generic: Return sort drop down * * @param string App * @return array */ public function fetchSortDropDown( $app='' ) { $app = ( $app ) ? $app : $this->_app; /* results page? */ $filter = ( ! IPSSearchRegistry::get('view.search_form') AND IPSSearchRegistry::get( $app . '.searchInKey' ) ) ? IPSSearchRegistry::get( $app . '.searchInKey' ) : ''; if ( is_file( IPSLib::getAppDir( $app ) . '/extensions/search/form.php' ) ) { $classToLoad = IPSLib::loadLibrary( IPSLib::getAppDir( $app ) . '/extensions/search/form.php', 'search_form_' . $app, $app ); if ( class_exists( $classToLoad ) ) { $_obj = new $classToLoad(); $_dd = $_obj->fetchSortDropDown(); if ( $filter ) { return $_dd[ $filter ]; } else { return $_dd; } } } return array( 'date' => $this->lang->words['s_search_type_0'] ); } /** * Generic: Return sort in * * @param string [App] * @return array */ public function fetchSortIn( $app='' ) { $app = ( $app ) ? $app : $this->_app; if ( is_file( IPSLib::getAppDir( $app ) . '/extensions/search/form.php' ) ) { $classToLoad = IPSLib::loadLibrary( IPSLib::getAppDir( $app ) . '/extensions/search/form.php', 'search_form_' . $app, $app ); if ( class_exists( $classToLoad ) ) { $_obj = new $classToLoad(); if ( method_exists( $_obj, 'fetchSortIn' ) ) { return $_obj->fetchSortIn(); } } } return FALSE; } /** * Returns boxes for the search form * * @param boolean Grab all apps or just the current one * @return array */ public function getHtml( $allApps = TRUE ) { /* INIT */ $filtersHtml = ''; /* Loop through apps */ foreach( ipsRegistry::$applications as $app ) { /* Not all? */ if ( ! $allApps and $app['app_directory'] != $this->_app ) { continue; } if( IPSLib::appIsSearchable( $app['app_directory'] ) ) { /* got custom filter? */ if ( is_file( IPSLib::getAppDir( $app['app_directory'] ) . '/extensions/search/form.php' ) ) { $classToLoad = IPSLib::loadLibrary( IPSLib::getAppDir( $app['app_directory'] ) . '/extensions/search/form.php', 'search_form_' . $app['app_directory'], $app['app_directory'] ); if ( class_exists( $classToLoad ) and method_exists( $classToLoad, 'getHtml' ) ) { $_obj = new $classToLoad(); $filtersHtml[ $app['app_directory'] ] = $_obj->getHtml(); } else { $filtersHtml[ $app['app_directory'] ] = array( 'title' => IPSLib::getAppTitle( $app['app_directory'] ), 'html' => '' ); } } else { $filtersHtml[ $app['app_directory'] ] = array( 'title' => IPSLib::getAppTitle( $app['app_directory'] ), 'html' => '' ); } $filtersHtml[ $app['app_directory'] ]['sortDropIn'] = $this->fetchSortIn( $app['app_directory'] ); $filtersHtml[ $app['app_directory'] ]['sortDropDown'] = $this->fetchSortDropDown( $app['app_directory'] ); } } return $filtersHtml; } /** * Get which applications can use tags * * @return array */ public function getTagSupport() { if ( !ipsRegistry::$settings['tags_enabled'] ) { return array(); } $return = array(); foreach( ipsRegistry::$applications as $app ) { $return[ $app['app_directory'] ] = (bool) IPSLib::appIsSearchable( $app['app_directory'], 'tags' ); } return $return; } /** * Perform the search * Populates $this->_count and $this->_results * * @return nothin' */ public function search() { $APP = IPSSearchRegistry::get('in.search_app'); $filterData = $this->request['search_app_filters'][ $APP ]; /* Check for an author filter */ if ( IPSSearchRegistry::get('in.search_author') ) { /* Query the member id */ $mem = $this->DB->buildAndFetch( array( 'select' => 'member_id', 'from' => 'members', 'where' => "members_display_name='" . $this->DB->addSlashes( IPSSearchRegistry::get('in.search_author') ) . "'" ) ); IPSSearchRegistry::set('opt.searchAuthor', true ); IPSSearchRegistry::set('in.search_author_id', intval( $mem['member_id'] ) ); /* Add the condition to our search */ $this->SEARCH->setCondition( 'member_id', '=', $mem['member_id'] ? $mem['member_id'] : -1 ); } /* Check for application specific filters - just need to do active app here */ $filterData = $this->SEARCH->buildFilterSQL( $filterData ); if( $filterData ) { if ( isset( $filterData[0] ) ) { foreach( $filterData as $_data ) { $this->SEARCH->setCondition( $_data['column'], $_data['operator'], $_data['value'], 'AND' ); } } else { $this->SEARCH->setCondition( $filterData['column'], $filterData['operator'], $filterData['value'], 'OR' ); } } /* Check Date Range */ if( IPSSearchRegistry::get('in.search_date_start') || IPSSearchRegistry::get('in.search_date_end') ) { /* Start Range Date */ $search_date_start = 0; if( IPSSearchRegistry::get('in.search_date_start') ) { $search_date_start = strtotime( IPSSearchRegistry::get('in.search_date_start') ); /* Correct for timezone...hopefully */ $search_date_start += abs( $this->registry->class_localization->getTimeOffset() ); } /* End Range Date */ $search_date_end = 0; if( IPSSearchRegistry::get('in.search_date_end') AND IPSSearchRegistry::get('in.search_date_end') != 'now' ) { $search_date_end = strtotime( IPSSearchRegistry::get('in.search_date_end') ); /* Correct for timezone...hopefully */ $search_date_end += abs( $this->registry->class_localization->getTimeOffset() ); } /* If the times are exactly equaly, we're going to assume they are trying to search all posts from one day */ if( ( $search_date_start && $search_date_end ) && $search_date_start == $search_date_end ) { $search_date_end += 86400; } $this->SEARCH->setDateRange( $search_date_start, $search_date_end ); } /* Init session */ $processId = $this->_startSession(); /* Run the search */ $results = $this->SEARCH->search(); /* Set data */ $this->_count = intval( $results['count'] ); $this->_results = $this->_rawResults = $results['resultSet']; /* Now format results */ if ( count( $this->_results ) ) { $this->_results = $this->_rawResults = $this->FORMAT->processResults( $this->_results ); /* Now generate HTML */ $this->_results = $this->FORMAT->parseAndFetchHtmlBlocks( $this->_results ); } /* Kill session */ $this->_endSession( $processId ); } /** * Perform the search * Populates $this->_count and $this->_results * * @return nothin' */ public function viewNewContent() { IPSSearchRegistry::set('opt.searchTitleOnly', true); IPSSearchRegistry::set('in.period_in_seconds', false ); /* Hard fix mobile app users to VNC based on ACP default VNC method */ if ( $this->member->isMobileApp ) { IPSSearchRegistry::set( 'in.period', $this->settings['default_vnc_method'] ); } /* Do we have a period? */ switch( IPSSearchRegistry::get('in.period') ) { case 'today': default: $date = 86400; // 24 hours break; case 'week': $date = 604800; // 1 week break; case 'weeks': $date = 1209600; // 2 weeks break; case 'month': $date = 2592000; // 30 days break; case 'months': $date = 15552000; // 6 months break; case 'year': $date = 31536000; // 365 days break; case 'lastvisit': $date = time() - intval( $this->memberData['last_visit'] ); break; case 'unread': $date = false; break; } /* Set date up */ IPSSearchRegistry::set('in.period_in_seconds', $date ); /* Run the search */ $results = $this->SEARCH->viewNewContent(); /* Set data */ $this->_count = intval( $results['count'] ); $this->_results = $this->_rawResults = $results['resultSet']; /* Now format results */ if ( count( $this->_results ) ) { $this->_results = $this->_rawResults = $this->FORMAT->processResults( $this->_results ); /* Now generate HTML */ $this->_results = $this->FORMAT->parseAndFetchHtmlBlocks( $this->_results ); } } /** * Perform the search * Populates $this->_count and $this->_results * * @return nothin' */ public function viewUserContent( $member ) { /* Run the search */ $results = $this->SEARCH->viewUserContent( $member ); /* Set data */ $this->_count = intval( $results['count'] ); $this->_results = $this->_rawResults = $results['resultSet']; /* Now format results */ if ( count( $this->_results ) ) { $this->_results = $this->_rawResults = $this->FORMAT->processResults( $this->_results ); /* Now generate HTML */ $this->_results = $this->FORMAT->parseAndFetchHtmlBlocks( $this->_results ); } } /** * Flag a search session * * @return int Process ID */ protected function _startSession() { /** * If we've already run a search and it's not clear, kill it now * Added kill_search_after setting @link http://community.invisionpower.com/tracker/issue-35838-kill-search-queries/ */ if( $this->settings['kill_search_after'] && $this->member->sessionClass()->session_data['search_thread_id'] ) { $this->DB->return_die = true; $this->DB->kill( $this->member->sessionClass()->session_data['search_thread_id'] ); $this->DB->return_die = false; } /** * Store the process id */ $processId = $this->DB->getThreadId(); if ( $processId ) { $this->DB->update( 'sessions', array( 'search_thread_id' => $processId, 'search_thread_time' => time() ), "id='" . $this->member->session_id . "'" ); } return $processId; } /** * End a search session * * @return void */ protected function _endSession( $processId ) { if ( $processId ) { $this->DB->update( 'sessions', array( 'search_thread_id' => 0, 'search_thread_time' => 0 ), "id='" . $this->member->session_id . "'" ); } } /** * Custom sort function to avoid filesorts in the system * * @param array A * @param array B * @return boolean */ static function usort( $a, $b ) { switch ( self::$ast ) { case 'numeric': case 'numerical': if ( self::$aso == 'asc' ) { return ($a[ self::$ask ] > $b[ self::$ask ]) ? +1 : -1; } else { return ($a[ self::$ask ] < $b[ self::$ask ]) ? +1 : -1; } break; case 'string': if ( self::$aso == 'asc' ) { return strcasecmp($a[ self::$ask ], $b[ self::$ask ]) <= 0 ? -1 : +1; } else { return strcasecmp($a[ self::$ask ], $b[ self::$ask ]) <= 0 ? +1 : -1; } break; } } }