Fix Bug #985487: Auto-suggestion for the search interface
This commit is contained in:
commit
91a7039392
|
@ -45,6 +45,24 @@ class tx_dlf_helper {
|
|||
*/
|
||||
public static $extKey = 'dlf';
|
||||
|
||||
/**
|
||||
* The encryption key
|
||||
* @see encrypt() / decrypt()
|
||||
*
|
||||
* @var string
|
||||
* @access private
|
||||
*/
|
||||
private static $ENCRYPTION_KEY = 'b8b311560d3e6f8dea0aa445995b1b2b';
|
||||
|
||||
/**
|
||||
* The initialization vector for encryption
|
||||
* @see encrypt() / decrypt()
|
||||
*
|
||||
* @var string
|
||||
* @access private
|
||||
*/
|
||||
private static $ENCRYPTION_IV = '381416de30a5c970f8f486aa6d5cc932';
|
||||
|
||||
/**
|
||||
* Searches the array recursively for a given value and returns the corresponding key if successful
|
||||
* @see http://php.net/array_search
|
||||
|
@ -195,6 +213,87 @@ class tx_dlf_helper {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt encrypted value with given control hash
|
||||
* @see http://yavkata.co.uk/weblog/php/securing-html-hidden-input-fields-using-encryption-and-hashing/
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $encrypted: The encrypted value to decrypt
|
||||
* @param string $hash: The control hash for decrypting
|
||||
*
|
||||
* @return mixed The decrypted value or NULL on error
|
||||
*/
|
||||
public static function decrypt($encrypted, $hash) {
|
||||
|
||||
$decrypted = NULL;
|
||||
|
||||
// Check for PHP extension "mcrypt".
|
||||
if (!extension_loaded('mcrypt')) {
|
||||
|
||||
trigger_error('PHP extension "mcrypt" not available', E_USER_WARNING);
|
||||
|
||||
return NULL;
|
||||
|
||||
}
|
||||
|
||||
if (empty($encrypted) || empty($hash)) {
|
||||
|
||||
return NULL;
|
||||
|
||||
}
|
||||
|
||||
$iv = substr(self::$ENCRYPTION_IV, 0, mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_CFB));
|
||||
|
||||
$decrypted = mcrypt_decrypt(MCRYPT_BLOWFISH, self::$ENCRYPTION_KEY, base64_decode($encrypted), MCRYPT_MODE_CFB, $iv);
|
||||
|
||||
$salt = substr($hash, 0, 10);
|
||||
|
||||
$hashed = $salt.substr(sha1($salt.$decrypted), -10);
|
||||
|
||||
if ($hashed !== $hash) {
|
||||
|
||||
return NULL;
|
||||
|
||||
}
|
||||
|
||||
return $decrypted;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt the given string
|
||||
* @see http://yavkata.co.uk/weblog/php/securing-html-hidden-input-fields-using-encryption-and-hashing/
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $string: The string to encrypt
|
||||
*
|
||||
* @return array Array with encrypted string and control hash
|
||||
*/
|
||||
public static function encrypt($string) {
|
||||
|
||||
// Check for PHP extension "mcrypt".
|
||||
if (!extension_loaded('mcrypt')) {
|
||||
|
||||
trigger_error('PHP extension "mcrypt" not available', E_USER_WARNING);
|
||||
|
||||
return NULL;
|
||||
|
||||
}
|
||||
|
||||
$iv = substr(self::$ENCRYPTION_IV, 0, mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_CFB));
|
||||
|
||||
$encrypted = base64_encode(mcrypt_encrypt(MCRYPT_BLOWFISH, self::$ENCRYPTION_KEY, $string, MCRYPT_MODE_CFB, $iv));
|
||||
|
||||
$salt = substr(md5(uniqid(rand(), TRUE)), 0, 10);
|
||||
|
||||
$hash = $salt.substr(sha1($salt.$string), -10);
|
||||
|
||||
return array ('encrypted' => $encrypted, 'hash' => $hash);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a backend user object (even in frontend mode)
|
||||
*
|
||||
|
@ -236,28 +335,36 @@ class tx_dlf_helper {
|
|||
*
|
||||
* @param integer $uid: The UID of the record
|
||||
* @param string $table: Get the "index_name" from this table
|
||||
* @param string $pid: Get the "index_name" from this page
|
||||
* @param integer $pid: Get the "index_name" from this page
|
||||
*
|
||||
* @return string "index_name" for the given UID
|
||||
*/
|
||||
public static function getIndexName($uid, $table, $pid) {
|
||||
public static function getIndexName($uid, $table, $pid = -1) {
|
||||
|
||||
$uid = max(intval($uid), 0);
|
||||
|
||||
$pid = max(intval($pid), 0);
|
||||
if (!$uid || !in_array($table, array ('tx_dlf_collections', 'tx_dlf_libraries', 'tx_dlf_metadata', 'tx_dlf_structures', 'tx_dlf_solrcores'))) {
|
||||
|
||||
if (!$uid || !$pid || !in_array($table, array ('tx_dlf_collections', 'tx_dlf_libraries', 'tx_dlf_metadata', 'tx_dlf_structures'))) {
|
||||
|
||||
trigger_error('At least one argument is not valid: UID='.$uid.' PID='.$pid.' TABLE='.$table, E_USER_WARNING);
|
||||
trigger_error('At least one argument is not valid: UID='.$uid.' or TABLE='.$table, E_USER_WARNING);
|
||||
|
||||
return '';
|
||||
|
||||
}
|
||||
|
||||
$where = '';
|
||||
|
||||
if ($pid !== -1) {
|
||||
|
||||
$pid = max(intval($pid), 0);
|
||||
|
||||
$where = ' AND '.$table.'.pid='.$pid;
|
||||
|
||||
}
|
||||
|
||||
$_result = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
|
||||
$table.'.index_name AS index_name',
|
||||
$table,
|
||||
$table.'.uid='.$uid.' AND '.$table.'.pid='.$pid.self::whereClause($table),
|
||||
$table.'.uid='.$uid.$where.self::whereClause($table),
|
||||
'',
|
||||
'',
|
||||
'1'
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
*
|
||||
* This copyright notice MUST APPEAR in all copies of the script!
|
||||
***************************************************************/
|
||||
|
||||
// TODO: Clean up and reduce code duplication. Consider switching to Solarium.
|
||||
/**
|
||||
* [CLASS/FUNCTION INDEX of SCRIPT]
|
||||
*/
|
||||
|
@ -30,6 +30,7 @@
|
|||
* Solr class 'tx_dlf_solr' for the 'dlf' extension.
|
||||
*
|
||||
* @author Sebastian Meyer <sebastian.meyer@slub-dresden.de>
|
||||
* @author Henrik Lochmann <dev@mentalmotive.com>
|
||||
* @copyright Copyright (c) 2011, Sebastian Meyer, SLUB Dresden
|
||||
* @package TYPO3
|
||||
* @subpackage tx_dlf
|
||||
|
@ -45,6 +46,41 @@ class tx_dlf_solr {
|
|||
*/
|
||||
public static $extKey = 'dlf';
|
||||
|
||||
/**
|
||||
* Returns the request URL for a specific Solr core
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $core: Name of the core to load
|
||||
*
|
||||
* @return string The request URL for a specific Solr core
|
||||
*/
|
||||
public static function getSolrUrl($core = '') {
|
||||
|
||||
// Extract extension configuration.
|
||||
$conf = unserialize($GLOBALS['TYPO3_CONF_VARS']['EXT']['extConf'][self::$extKey]);
|
||||
|
||||
// Derive Solr host name.
|
||||
$host = ($conf['solrHost'] ? $conf['solrHost'] : 'localhost');
|
||||
|
||||
// Prepend username and password to hostname.
|
||||
if ($conf['solrUser'] && $conf['solrPass']) {
|
||||
|
||||
$host = $conf['solrUser'].':'.$conf['solrPass'].'@'.$host;
|
||||
|
||||
}
|
||||
|
||||
// Set port if not set.
|
||||
$port = t3lib_div::intInRange($conf['solrPort'], 1, 65535, 8180);
|
||||
|
||||
// Append core name to path.
|
||||
$path = trim($conf['solrPath'], '/').'/'.$core;
|
||||
|
||||
// Return entire request URL.
|
||||
return 'http://'.$host.':'.$port.'/'.$path;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SolrPhpClient service object and establish connection to Solr server
|
||||
* @see EXT:dlf/lib/SolrPhpClient/Apache/Solr/Service.php
|
||||
|
@ -82,20 +118,9 @@ class tx_dlf_solr {
|
|||
// Get core name if UID is given.
|
||||
if (t3lib_div::testInt($core)) {
|
||||
|
||||
$result = $GLOBALS['TYPO3_DB']->exec_SELECTquery(
|
||||
'tx_dlf_solrcores.index_name AS index_name',
|
||||
'tx_dlf_solrcores',
|
||||
'tx_dlf_solrcores.uid='.intval($core).tx_dlf_helper::whereClause('tx_dlf_solrcores'),
|
||||
'',
|
||||
'',
|
||||
'1'
|
||||
);
|
||||
$core = tx_dlf_helper::getIndexName($core, 'tx_dlf_solrcores');
|
||||
|
||||
if ($GLOBALS['TYPO3_DB']->sql_num_rows($result)) {
|
||||
|
||||
list ($core) = $GLOBALS['TYPO3_DB']->sql_fetch_row($result);
|
||||
|
||||
} else {
|
||||
if (empty($core)) {
|
||||
|
||||
trigger_error('Could not find Solr core with UID '.$core, E_USER_NOTICE);
|
||||
|
||||
|
|
|
@ -49,6 +49,7 @@ return array (
|
|||
'tx_dlf_oai' => $extensionPath.'plugins/oai/class.tx_dlf_oai.php',
|
||||
'tx_dlf_pageview' => $extensionPath.'plugins/pageview/class.tx_dlf_pageview.php',
|
||||
'tx_dlf_search' => $extensionPath.'plugins/search/class.tx_dlf_search.php',
|
||||
'tx_dlf_search_suggest' => $extensionPath.'plugins/search/class.tx_dlf_search_suggest.php',
|
||||
'tx_dlf_statistics' => $extensionPath.'plugins/statistics/class.tx_dlf_statistics.php',
|
||||
'tx_dlf_toc' => $extensionPath.'plugins/toc/class.tx_dlf_toc.php',
|
||||
'tx_dlf_toolbox' => $extensionPath.'plugins/toolbox/class.tx_dlf_toolbox.php',
|
||||
|
|
|
@ -49,6 +49,7 @@ $EM_CONF[$_EXTKEY] = array(
|
|||
),
|
||||
'suggests' => array(
|
||||
'realurl' => '',
|
||||
't3jquery' => '2.1.2-',
|
||||
),
|
||||
),
|
||||
'_md5_values_when_last_written' => '',
|
||||
|
|
|
@ -66,4 +66,6 @@ $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['dlf/common/class.tx_dlf_document.php'
|
|||
// Register command line scripts.
|
||||
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['GLOBAL']['cliKeys'][$_EXTKEY] = array ('EXT:'.$_EXTKEY.'/cli/class.tx_dlf_cli.php', '_CLI_dlf');
|
||||
|
||||
// Register AJAX eID handlers.
|
||||
$GLOBALS['TYPO3_CONF_VARS']['FE']['eID_include']['tx_dlf_search_suggest'] = 'EXT:'.$_EXTKEY.'/plugins/search/class.tx_dlf_search_suggest.php';
|
||||
?>
|
|
@ -30,6 +30,7 @@
|
|||
* Plugin 'DLF: Search' for the 'dlf' extension.
|
||||
*
|
||||
* @author Sebastian Meyer <sebastian.meyer@slub-dresden.de>
|
||||
* @author Henrik Lochmann <dev@mentalmotive.com>
|
||||
* @copyright Copyright (c) 2011, Sebastian Meyer, SLUB Dresden
|
||||
* @package TYPO3
|
||||
* @subpackage tx_dlf
|
||||
|
@ -39,6 +40,74 @@ class tx_dlf_search extends tx_dlf_plugin {
|
|||
|
||||
public $scriptRelPath = 'plugins/search/class.tx_dlf_search.php';
|
||||
|
||||
/**
|
||||
* Adds the JS files necessary for autocompletion
|
||||
*
|
||||
* @access protected
|
||||
*
|
||||
* @return boolean TRUE on success or FALSE on error
|
||||
*/
|
||||
protected function addAutocompleteJS() {
|
||||
|
||||
// Ensure extension "t3jquery" is available.
|
||||
if (t3lib_extMgm::isLoaded('t3jquery')) {
|
||||
|
||||
require_once(t3lib_extMgm::extPath('t3jquery').'class.tx_t3jquery.php');
|
||||
|
||||
}
|
||||
|
||||
// Is "t3jquery" loaded and the custom library created?
|
||||
if (T3JQUERY === TRUE) {
|
||||
|
||||
tx_t3jquery::addJqJS();
|
||||
|
||||
$GLOBALS['TSFE']->additionalHeaderData[$this->prefixId.'_search_suggest'] = ' <script type="text/javascript" src="'.t3lib_extMgm::siteRelPath($this->extKey).'plugins/search/tx_dlf_search_suggest.js"></script>';
|
||||
|
||||
return TRUE;
|
||||
|
||||
} else {
|
||||
|
||||
// No autocompletion available!
|
||||
return FALSE;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the encrypted Solr core name to the search form
|
||||
*
|
||||
* @access protected
|
||||
*
|
||||
* @param integer $core: UID of the core
|
||||
*
|
||||
* @return string HTML input fields with encrypted core name and hash
|
||||
*/
|
||||
protected function addEncryptedCoreName($core) {
|
||||
|
||||
// Get core name.
|
||||
$name = tx_dlf_helper::getIndexName($core, 'tx_dlf_solrcores');
|
||||
|
||||
// Encrypt core name.
|
||||
if (!empty($name)) {
|
||||
|
||||
$name = tx_dlf_helper::encrypt($name);
|
||||
|
||||
}
|
||||
|
||||
// Add encrypted fields to search form.
|
||||
if (is_array($name)) {
|
||||
|
||||
return '<input type="hidden" name="'.$this->prefixId.'"[encrypted]" value="'.$name['encrypted'].'" /><input type="hidden" name="'.$this->prefixId.'[hashed]" value="'.$name['hash'].'" />';
|
||||
|
||||
} else {
|
||||
|
||||
return '';
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* The main method of the PlugIn
|
||||
*
|
||||
|
@ -67,6 +136,9 @@ class tx_dlf_search extends tx_dlf_plugin {
|
|||
|
||||
if (empty($this->piVars['query'])) {
|
||||
|
||||
// Add javascript for autocompletion if available.
|
||||
$autocomplete = $this->addAutocompleteJS();
|
||||
|
||||
// Load template file.
|
||||
if (!empty($this->conf['templateFile'])) {
|
||||
|
||||
|
@ -96,8 +168,16 @@ class tx_dlf_search extends tx_dlf_plugin {
|
|||
'###LABEL_SUBMIT###' => $this->pi_getLL('label.submit'),
|
||||
'###FIELD_QUERY###' => $this->prefixId.'[query]',
|
||||
'###QUERY###' => htmlspecialchars($lastQuery),
|
||||
'###ADDITIONAL_INPUTS###' => '',
|
||||
);
|
||||
|
||||
// Encrypt Solr core name and add as hidden input field to the search form.
|
||||
if ($autocomplete) {
|
||||
|
||||
$markerArray['###ADDITIONAL_INPUTS###'] = $this->addEncryptedCoreName($this->conf['solrcore']);
|
||||
|
||||
}
|
||||
|
||||
// Display search form.
|
||||
$content .= $this->cObj->substituteMarkerArray($this->template, $markerArray);
|
||||
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
<?php
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) 2012 Henrik Lochmann <dev@mentalmotive.com>
|
||||
* All rights reserved
|
||||
*
|
||||
* This script is part of the TYPO3 project. The TYPO3 project is
|
||||
* free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The GNU General Public License can be found at
|
||||
* http://www.gnu.org/copyleft/gpl.html.
|
||||
*
|
||||
* This script is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* This copyright notice MUST APPEAR in all copies of the script!
|
||||
***************************************************************/
|
||||
|
||||
require_once (PATH_tslib.'class.tslib_pibase.php');
|
||||
|
||||
/**
|
||||
* Autocompletion for the search plugin of the 'dlf' extension.
|
||||
*
|
||||
* @author Henrik Lochmann <dev@mentalmotive.com>
|
||||
* @copyright Copyright (c) 2012, Zeutschel GmbH
|
||||
* @package TYPO3
|
||||
* @subpackage tx_dlf
|
||||
* @access public
|
||||
*/
|
||||
class tx_dlf_search_suggest extends tx_dlf_plugin {
|
||||
|
||||
public $scriptRelPath = 'plugins/search/class.tx_dlf_search_suggest.php';
|
||||
|
||||
/**
|
||||
* The main method of the PlugIn
|
||||
*
|
||||
* @access public
|
||||
*
|
||||
* @param string $content: The PlugIn content
|
||||
* @param array $conf: The PlugIn configuration
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function main($content = '', $conf = array ()) {
|
||||
|
||||
if (!empty($this->piVars['encrypted']) && !empty($this->piVars['hashed'])) {
|
||||
|
||||
$core = tx_dlf_helper::decrypt($this->piVars['encrypted'], $this->piVars['hashed']);
|
||||
|
||||
}
|
||||
|
||||
if (!empty($core)) {
|
||||
|
||||
$url = trim(tx_dlf_solr::getSolrUrl($core), '/').'/suggest/?q='.t3lib_div::_POST('q');
|
||||
|
||||
if ($stream = fopen($url, 'r')) {
|
||||
|
||||
$content .= stream_get_contents($stream);
|
||||
|
||||
fclose($stream);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
echo $content;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$cObj = t3lib_div::makeInstance('tx_dlf_search_suggest');
|
||||
|
||||
$cObj->main();
|
||||
|
||||
?>
|
|
@ -0,0 +1,27 @@
|
|||
$(
|
||||
function(){
|
||||
// jQuery autocomplete integration
|
||||
$(".autocomplete").autocomplete({
|
||||
source: function(request, response) {
|
||||
return $.post(
|
||||
'/',
|
||||
{
|
||||
eID: "tx_dlf_search_suggest",
|
||||
q: escape(request.term),
|
||||
encrypted: $("input[name='tx_dlf[encrypted]']").val(),
|
||||
hashed: $("input[name='tx_dlf[hashed]']").val()
|
||||
},
|
||||
function(xmlData) {
|
||||
var result = new array();
|
||||
$('arr[name="suggestion"] str', xmlData).each(function(i) {
|
||||
if ($(this).text().indexOf(request.term) == 0) {
|
||||
result.push($(this).text());
|
||||
}
|
||||
});
|
||||
return response(result);
|
||||
},
|
||||
'xml');
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
|
@ -1,7 +1,8 @@
|
|||
<!-- ###TEMPLATE### -->
|
||||
<form class="tx-dlf-search-form" action="###ACTION_URL###" method="post" enctype="multipart/form-data">
|
||||
<label for="###FIELD_QUERY###">###LABEL_QUERY###</label>
|
||||
<input type="text" id="###FIELD_QUERY###" name="###FIELD_QUERY###" value="###QUERY###" />
|
||||
<input type="text" id="###FIELD_QUERY###" name="###FIELD_QUERY###" value="###QUERY###" class="autocomplete" autocomplete="off" role="textbox" aria-autocomplete="list" aria-haspopup="true">
|
||||
<input type="submit" value="###LABEL_SUBMIT###" />
|
||||
###ADDITIONAL_INPUTS###
|
||||
</form>
|
||||
<!-- ###TEMPLATE### -->
|
|
@ -0,0 +1 @@
|
|||
components=jQuery,Core,Position,Autocomplete
|
Loading…
Reference in New Issue