Make search mechanism pluggable
Currently, TinyTinyRSS can use raw SQL or the Sphinx search engine for searching. It would be nice if other search engines (such as Xapian) could be used, or if features of the underlying SQL engine (such as MySQL's FULLTEXT indexes) could be leveraged. This commit makes searching into a plugin hook, falling back to the builtin behavior if no search plugin is active. The Sphinx search behavior has been broken out into a plugin.
This commit is contained in:
parent
87ddd5e1f9
commit
baaf4c3043
8 changed files with 70 additions and 56 deletions
|
@ -1147,7 +1147,7 @@ class Feeds extends Handler_Protected {
|
||||||
|
|
||||||
print "<div class=\"dlgButtons\">";
|
print "<div class=\"dlgButtons\">";
|
||||||
|
|
||||||
if (!SPHINX_ENABLED) {
|
if (count(PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SEARCH)) == 0) {
|
||||||
print "<div style=\"float : left\">
|
print "<div style=\"float : left\">
|
||||||
<a class=\"visibleLink\" target=\"_blank\" href=\"http://tt-rss.org/wiki/SearchSyntax\">".__("Search syntax")."</a>
|
<a class=\"visibleLink\" target=\"_blank\" href=\"http://tt-rss.org/wiki/SearchSyntax\">".__("Search syntax")."</a>
|
||||||
</div>";
|
</div>";
|
||||||
|
|
|
@ -39,6 +39,7 @@ class PluginHost {
|
||||||
const HOOK_FETCH_FEED = 22;
|
const HOOK_FETCH_FEED = 22;
|
||||||
const HOOK_QUERY_HEADLINES = 23;
|
const HOOK_QUERY_HEADLINES = 23;
|
||||||
const HOOK_HOUSE_KEEPING = 24;
|
const HOOK_HOUSE_KEEPING = 24;
|
||||||
|
const HOOK_SEARCH = 25;
|
||||||
|
|
||||||
const KIND_ALL = 1;
|
const KIND_ALL = 1;
|
||||||
const KIND_SYSTEM = 2;
|
const KIND_SYSTEM = 2;
|
||||||
|
|
|
@ -108,10 +108,6 @@
|
||||||
// *** Sphinx search ***
|
// *** Sphinx search ***
|
||||||
// *********************
|
// *********************
|
||||||
|
|
||||||
define('SPHINX_ENABLED', false);
|
|
||||||
// Enable fulltext search using Sphinx (http://www.sphinxsearch.com)
|
|
||||||
// Please see http://tt-rss.org/wiki/SphinxSearch for more information.
|
|
||||||
|
|
||||||
define('SPHINX_SERVER', 'localhost:9312');
|
define('SPHINX_SERVER', 'localhost:9312');
|
||||||
// Hostname:port combination for the Sphinx server.
|
// Hostname:port combination for the Sphinx server.
|
||||||
|
|
||||||
|
|
|
@ -397,20 +397,15 @@
|
||||||
$search_words = array();
|
$search_words = array();
|
||||||
|
|
||||||
if ($search) {
|
if ($search) {
|
||||||
|
foreach (PluginHost::getInstance()->get_hooks(PluginHost::HOOK_SEARCH) as $plugin) {
|
||||||
if (SPHINX_ENABLED) {
|
list($search_query_part, $search_words) = $plugin->hook_search($search);
|
||||||
$ids = join(",", @sphinx_search($search, 0, 500));
|
|
||||||
|
|
||||||
if ($ids)
|
|
||||||
$search_query_part = "ref_id IN ($ids) AND ";
|
|
||||||
else
|
|
||||||
$search_query_part = "ref_id = -1 AND ";
|
|
||||||
|
|
||||||
} else {
|
|
||||||
list($search_query_part, $search_words) = search_to_sql($search);
|
|
||||||
$search_query_part .= " AND ";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fall back in case of no plugins
|
||||||
|
if (!$search_query_part) {
|
||||||
|
list($search_query_part, $search_words) = search_to_sql($search);
|
||||||
|
}
|
||||||
|
$search_query_part .= " AND ";
|
||||||
} else {
|
} else {
|
||||||
$search_query_part = "";
|
$search_query_part = "";
|
||||||
}
|
}
|
||||||
|
@ -1994,39 +1989,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function sphinx_search($query, $offset = 0, $limit = 30) {
|
|
||||||
require_once 'lib/sphinxapi.php';
|
|
||||||
|
|
||||||
$sphinxClient = new SphinxClient();
|
|
||||||
|
|
||||||
$sphinxpair = explode(":", SPHINX_SERVER, 2);
|
|
||||||
|
|
||||||
$sphinxClient->SetServer($sphinxpair[0], (int)$sphinxpair[1]);
|
|
||||||
$sphinxClient->SetConnectTimeout(1);
|
|
||||||
|
|
||||||
$sphinxClient->SetFieldWeights(array('title' => 70, 'content' => 30,
|
|
||||||
'feed_title' => 20));
|
|
||||||
|
|
||||||
$sphinxClient->SetMatchMode(SPH_MATCH_EXTENDED2);
|
|
||||||
$sphinxClient->SetRankingMode(SPH_RANK_PROXIMITY_BM25);
|
|
||||||
$sphinxClient->SetLimits($offset, $limit, 1000);
|
|
||||||
$sphinxClient->SetArrayResult(false);
|
|
||||||
$sphinxClient->SetFilter('owner_uid', array($_SESSION['uid']));
|
|
||||||
|
|
||||||
$result = $sphinxClient->Query($query, SPHINX_INDEX);
|
|
||||||
|
|
||||||
$ids = array();
|
|
||||||
|
|
||||||
if (is_array($result['matches'])) {
|
|
||||||
foreach (array_keys($result['matches']) as $int_id) {
|
|
||||||
$ref_id = $result['matches'][$int_id]['attrs']['ref_id'];
|
|
||||||
array_push($ids, $ref_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $ids;
|
|
||||||
}
|
|
||||||
|
|
||||||
function cleanup_tags($days = 14, $limit = 1000) {
|
function cleanup_tags($days = 14, $limit = 1000) {
|
||||||
|
|
||||||
if (DB_TYPE == "pgsql") {
|
if (DB_TYPE == "pgsql") {
|
||||||
|
|
|
@ -146,11 +146,6 @@
|
||||||
array_push($errors, "PHP support for CURL is required for PubSubHubbub.");
|
array_push($errors, "PHP support for CURL is required for PubSubHubbub.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (SPHINX_ENABLED && class_exists("SphinxClient")) {
|
|
||||||
array_push($errors, "Your PHP has a separate systemwide Sphinx client installed which conflicts with the client library used by tt-rss. Either remove the system library or disable Sphinx support.");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!class_exists("DOMDocument")) {
|
if (!class_exists("DOMDocument")) {
|
||||||
array_push($errors, "PHP support for DOMDocument is required, but was not found.");
|
array_push($errors, "PHP support for DOMDocument is required, but was not found.");
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
<?php # This file has been generated at: Fri Sep 27 13:42:37 MSK 2013
|
<?php # This file has been generated at: Fri Sep 27 13:42:37 MSK 2013
|
||||||
define('GENERATED_CONFIG_CHECK', 26);
|
define('GENERATED_CONFIG_CHECK', 26);
|
||||||
$requred_defines = array( 'DB_TYPE', 'DB_HOST', 'DB_USER', 'DB_NAME', 'DB_PASS', 'MYSQL_CHARSET', 'SELF_URL_PATH', 'FEED_CRYPT_KEY', 'SINGLE_USER_MODE', 'SIMPLE_UPDATE_MODE', 'PHP_EXECUTABLE', 'LOCK_DIRECTORY', 'CACHE_DIR', 'ICONS_DIR', 'ICONS_URL', 'AUTH_AUTO_CREATE', 'AUTH_AUTO_LOGIN', 'FORCE_ARTICLE_PURGE', 'PUBSUBHUBBUB_HUB', 'PUBSUBHUBBUB_ENABLED', 'SPHINX_ENABLED', 'SPHINX_SERVER', 'SPHINX_INDEX', 'ENABLE_REGISTRATION', 'REG_NOTIFY_ADDRESS', 'REG_MAX_USERS', 'SESSION_COOKIE_LIFETIME', 'SESSION_CHECK_ADDRESS', 'SMTP_FROM_NAME', 'SMTP_FROM_ADDRESS', 'DIGEST_SUBJECT', 'SMTP_SERVER', 'SMTP_LOGIN', 'SMTP_PASSWORD', 'SMTP_SECURE', 'CHECK_FOR_NEW_VERSION', 'DETECT_ARTICLE_LANGUAGE', 'ENABLE_GZIP_OUTPUT', 'PLUGINS', 'LOG_DESTINATION', 'CONFIG_VERSION'); ?>
|
$requred_defines = array( 'DB_TYPE', 'DB_HOST', 'DB_USER', 'DB_NAME', 'DB_PASS', 'MYSQL_CHARSET', 'SELF_URL_PATH', 'FEED_CRYPT_KEY', 'SINGLE_USER_MODE', 'SIMPLE_UPDATE_MODE', 'PHP_EXECUTABLE', 'LOCK_DIRECTORY', 'CACHE_DIR', 'ICONS_DIR', 'ICONS_URL', 'AUTH_AUTO_CREATE', 'AUTH_AUTO_LOGIN', 'FORCE_ARTICLE_PURGE', 'PUBSUBHUBBUB_HUB', 'PUBSUBHUBBUB_ENABLED', 'ENABLE_REGISTRATION', 'REG_NOTIFY_ADDRESS', 'REG_MAX_USERS', 'SESSION_COOKIE_LIFETIME', 'SESSION_CHECK_ADDRESS', 'SMTP_FROM_NAME', 'SMTP_FROM_ADDRESS', 'DIGEST_SUBJECT', 'SMTP_SERVER', 'SMTP_LOGIN', 'SMTP_PASSWORD', 'SMTP_SECURE', 'CHECK_FOR_NEW_VERSION', 'DETECT_ARTICLE_LANGUAGE', 'ENABLE_GZIP_OUTPUT', 'PLUGINS', 'LOG_DESTINATION', 'CONFIG_VERSION'); ?>
|
||||||
|
|
60
plugins/search_sphinx/init.php
Normal file
60
plugins/search_sphinx/init.php
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class Search_Sphinx extends Plugin {
|
||||||
|
function about() {
|
||||||
|
return array(1.0,
|
||||||
|
"Delegate searching for articles to Sphinx",
|
||||||
|
"hoelzro",
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function init($host) {
|
||||||
|
$host->add_hook($host::HOOK_SEARCH, $this);
|
||||||
|
|
||||||
|
require_once __DIR__ . "/sphinxapi.php";
|
||||||
|
}
|
||||||
|
|
||||||
|
function hook_search($search) {
|
||||||
|
$offset = 0;
|
||||||
|
$limit = 500;
|
||||||
|
|
||||||
|
$sphinxClient = new SphinxClient();
|
||||||
|
|
||||||
|
$sphinxpair = explode(":", SPHINX_SERVER, 2);
|
||||||
|
|
||||||
|
$sphinxClient->SetServer($sphinxpair[0], (int)$sphinxpair[1]);
|
||||||
|
$sphinxClient->SetConnectTimeout(1);
|
||||||
|
|
||||||
|
$sphinxClient->SetFieldWeights(array('title' => 70, 'content' => 30,
|
||||||
|
'feed_title' => 20));
|
||||||
|
|
||||||
|
$sphinxClient->SetMatchMode(SPH_MATCH_EXTENDED2);
|
||||||
|
$sphinxClient->SetRankingMode(SPH_RANK_PROXIMITY_BM25);
|
||||||
|
$sphinxClient->SetLimits($offset, $limit, 1000);
|
||||||
|
$sphinxClient->SetArrayResult(false);
|
||||||
|
$sphinxClient->SetFilter('owner_uid', array($_SESSION['uid']));
|
||||||
|
|
||||||
|
$result = $sphinxClient->Query($search, SPHINX_INDEX);
|
||||||
|
|
||||||
|
$ids = array();
|
||||||
|
|
||||||
|
if (is_array($result['matches'])) {
|
||||||
|
foreach (array_keys($result['matches']) as $int_id) {
|
||||||
|
$ref_id = $result['matches'][$int_id]['attrs']['ref_id'];
|
||||||
|
array_push($ids, $ref_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$ids = join(",", $ids);
|
||||||
|
|
||||||
|
if ($ids)
|
||||||
|
return array("ref_id IN ($ids)", array());
|
||||||
|
else
|
||||||
|
return array("ref_id = -1", array());
|
||||||
|
}
|
||||||
|
|
||||||
|
function api_version() {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
?>
|
Loading…
Reference in a new issue