Mittwoch, 17. September 2008
Typo3 ajaxbasierte Suchengine - neue Extension
Gestern habe ich für meinen Arbeitgeber eine größere Extension fertiggestellt. Die Ausgangslage war bisher so bei uns, dass es für Typo3 selber die IndexedSearch und für das Newsmodul tt_news die dort eingebaute Suchfunktion gab. Zusätzlich kommt in unserem Szenario noch hinzu, dass wir die Knowledge Base von Interspire einsetzen. Diese bringt natürlich auch eine Suchfunktion mit, so dass wir den Benutzer mit drei verschiedene Suchmasken quälten. Ziel war nun, diese unter einen Hut zu bringen.
Es besteht zwar auch die Möglichkeit mit der IndexedSearch auch die Newsmeldungen zu durchsuchen, aber ich wollte die Ergebnisse gerne nach den unterschiedlichen Kategorien sortiert haben. Die Extension habe ich mit dem Kickstarter erstellt. Zuerst habe ich den grafischen Rahmen der Seite gebaut. Die Seite enthält ein Suchfeld, einen kurzen Hinweis und danach den Ergebnisbereich, der zunächst versteckt wird. Oben sind drei grafische Kategorienreiter, die als Buttons fungieren mit denen der Benutzer hin- und herschalten kann, darunter wird das jewelige Suchergebnis angezeigt.
Die Suche selber ist mit Xajax - einem Ajax Framework - realisiert. Es werden nacheinander eine versteckte Seite der IndexedSearch, die tt_news-Suchseite und die Suchseite der Knowledge Base aufgerufen. Die Resultate sind komplette HTML-Seiten über die ein kurzer selbstgeschriebener Parser läuft, der die relevanten Bereiche extrahiert und in die vorbereitet <div>-Box schreibt. Neben den Ergebnissen wird auch noch die Anzahl der Treffer gefiltert und in den Kategoriereitern angezeigt.
Über die Kategoriereiter kann der Benutzer jetzt zwischen den Ergebnislisten hin- und herschalten. Als Pferdefuß stellte sich dabei die Anzeige der Trefferanzahl heraus, die jetzt bei jedem Wechsel der Kategorie eingelesen, zwischengespeichert und wieder ausgegeben werden muss.
Live in Action ist diese Suchengine auf unseren Seiten.
Bisher ist noch sehr viel der Extension hart codiert im Quellcode, die Extension ist noch nicht so modular und erweiterbar aufgebaut, wie sie evtl. sein könnte. Deshalb habe ich sie auch nicht ins TER hochgeladen. Durch das Einfügen weiterer Parser lässt sich die Suchengine natürlich auf weitere Suchmaschinen erweitern.
In der erweiterten Fassung dieses Eintrages habe ich den kompletten Quellcode angehängt.....
<?php
/***************************************************************
* Copyright notice
*
* (c) 2008 Matthias Häger <matthias.haeger@uni-konstanz.de>
* 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');
/**
Plugin 'RZ Ajax Search' for the 'rzajaxsearch' extension.
*
@author Matthias Häger <matthias.haeger@uni-konstanz.de>
@package TYPO3
@subpackage tx_rzajaxsearch
*/
class tx_rzajaxsearch_pi1 extends tslib_pibase {
var $prefixId = 'tx_rzajaxsearch_pi1'; // Same as class name
var $scriptRelPath = 'pi1/class.tx_rzajaxsearch_pi1.php'; // Path to this script relative to the extension dir.
var $extKey = 'rzajaxsearch'; // The extension key.
var $conf;
/**
The main method of the PlugIn. Zuerst werden grundlegende Variablen definiert, die für den weiteren Programmverlauf wichtig sind.
Anschließend wird Xajax instanziiert und die Rückgabe-Methoden werden registriert. Danach wird das Formular generiert, in das der
Suchbegriff eingegeben werden kann. Zum Schluß wird die Ergebnis-Box gerendert, aber vorerst nicht angezeigt.
@param string $content: The PlugIn content
@param array $conf: The PlugIn configuration
@return The content that is displayed on the website
/
function main($content,$conf) {
$this->conf=$conf;
$this->pi_loadLL();
$this->pi_initPIflexForm();
$this->pi_USER_INT_obj = 1;
$GLOBALS["TSFE"]->no_cache=1;
/**
Definition zentral verfügbarer Variablen für aktive und inaktive Rubrikreiter. Vorab Definition des JS-Ausdruck um den Wert
der Menge der gefundenen Treffer aus der HTML-Seite zu lesen.
/
$this->readHeader1 = "document.getElementById('header1').firstChild.nodeValue";
$this->readHeader2 = "document.getElementById('header2').firstChild.nodeValue";
$this->readHeader3 = "document.getElementById('header3').firstChild.nodeValue";
$this->active1 = '<a class="rActive" href="#" onclick="'.$this->prefixId.'showBox(1, '.$this->readHeader1.', '.$this->readHeader2.', '.$this->readHeader3.'); return false">Webseite <span id="header1"></span></a>';
$this->active2 = '<a class="rActive" href="#" onclick="'.$this->prefixId.'showBox(2, '.$this->readHeader1.', '.$this->readHeader2.', '.$this->readHeader3.'); return false">Knowledge Base <span id="header2"></span></a>';
$this->active3 = '<a class="rActive" href="#" onclick="'.$this->prefixId.'showBox(3, '.$this->readHeader1.', '.$this->readHeader2.', '.$this->readHeader3.'); return false">Newsmeldungen <span id="header3"></span></a>';
$this->inactive1 = '<a class="rInactive" href="#" onclick="'.$this->prefixId.'showBox(1, '.$this->readHeader1.', '.$this->readHeader2.', '.$this->readHeader3.'); return false">Webseite <span id="header1"></span></a>';
$this->inactive2 = '<a class="rInactive" href="#" onclick="'.$this->prefixId.'showBox(2, '.$this->readHeader1.', '.$this->readHeader2.', '.$this->readHeader3.'); return false">Knowledge Base <span id="header2"></span></a>';
$this->inactive3 = '<a class="rInactive" href="#" onclick="'.$this->prefixId.'showBox(3, '.$this->readHeader1.', '.$this->readHeader2.', '.$this->readHeader3.'); return false">Newsmeldungen <span id="header3"></span></a>';
//Xajax-Objekt instanziieren und Methoden registrieren
require_once(t3lib_extMgm::extPath('xajax') . 'class.tx_xajax.php');
$this->xajax = t3lib_div::makeInstance('tx_xajax');
$this->xajax->decodeUTF8InputOn();
$this->xajax->setCharEncoding('utf-8');
#$this->xajax->debugOn();
$this->xajax->setWrapperPrefix($this->prefixId);
$this->xajax->registerFunction(array('getEngine', &$this, 'getEngine'));
$this->xajax->registerFunction(array('showBox', &$this, 'showBox'));
$this->xajax->processRequests();
$GLOBALS['TSFE']->additionalHeaderData[$this->prefixId] = $this->xajax->getJavascript(t3lib_extMgm::siteRelPath('xajax'));
//Formular für die Eingabe des Suchbegriffes
$content ="";
//Übernahme der Suchbegriffe, wenn jemand die Suchbox auf der Startseite benutzt. Wenn dort ein Begriff eingegeben wurde, dann wird
//direkt die Ergebnisseite aufgerufen
$post_phrase = t3lib_div::_POST();
$post_phrase = $post_phrase[tx_indexedsearch];
$post_phrase = $post_phrase[sword];
$content='
<form method="post" action="" onSubmit="'.$this->prefixId.'getEngine(document.getElementById(\'phrase\').value); return false;">
<input type="text" id="phrase" name="'.$this->prefixId.'[input_field]" size="50" value="'.htmlspecialchars($this->piVars['input_field']).'" />
<input type="submit" name="'.$this->prefixId.'[submit_button]" value="'.htmlspecialchars($this->pi_getLL('submit_button_label')).'" />
</form>';
if(isset($post_phrase)) {
$content .= '<script language="javascript">'.$this->prefixId.'getEngine(\''.$post_phrase.'\');</script>';
$content .= "<div id=\"hilfe\">Suche läuft.....</div>";
} else {
$content.='<br /><div id="hilfe">Durchsuchen Sie unsere Webseite, unsere Knowledge Base und die aktuellen Newsmeldungen. Nach Eingabe des Suchbegriffes erhalten Sie die Ergebnisse nach Kategorie geordnet. Klicken Sie auf die jeweilige Kategorie um zu den einzelnen Ergebnislisten zu gelangen.</div>
';
}
//Ergebnis-Box rendern, wird vorerst nicht angezeigt.
$content .= $this->renderBox();
return $this->pi_wrapInBaseClass($content);
}
/**
Methode stellt die Ergebnis-Box dar. Die nötigen <div>-Bereiche werden angelegt, der Ergebnis-Bereich ist aber grundsätzlich ausgeblendet.
@return <div>-Box für die Resultate, die auf der Webseite angezeigt wird
/
function renderBox() {
$output = "";
$output .= ' <div id=\'resultWrapper\'>
<span class="description">Wählen Sie die gewünschte Kategorie der Ergebnisse.</span>
<div id=\'resultMenu\'>
<ul>
<li id="resultLink1">'.$this->inactive1.'</li>
<li id="resultLink2">'.$this->inactive2.'</li>
<li id="resultLink3">'.$this->inactive3.'</li>
</ul>
<div style="clear:both"></div>
</div>
<div id=\'result1\'></div>
<div id=\'result2\'></div>
<div id=\'result3\'></div>
</div>';
return $output;
}
/**
Diese Methode dient zur Umschaltung zwischen den unterschiedlichen Ergebnislisten. Je nach angegebenr Engine werden die zugehörigen <div>-Boxen
ein- bzw. ausgeblendet. Anschließend werden die Trefferzahlen neu eingefügt.
@param integer Nummer der Suchengine, die angezeigt werden soll
@param string Anzahl der Treffer von Engine 1, bereits umklammert als String dargestellt
@param string Anzahl der Treffer von Engine 2, bereits umklammert als String dargestellt
@param string Anzahl der Treffer von Engine 3, bereits umklammert als String dargestellt
@return XajaxResponseObject
/
function showBox($engine, $header1, $header2, $header3) {
$this->piVars = $data[$this->prefixId];
$objResponse = new tx_xajax_response();
/**
Engine 1: Webseite
Engine 2: Knowledge Base
Engine 3: Newsmeldungen
*/
switch($engine) {
case 1:
$objResponse->addAssign("resultLink1", 'innerHTML', $this->active1);
$objResponse->addAssign("resultLink2", 'innerHTML', $this->inactive2);
$objResponse->addAssign("resultLink3", 'innerHTML', $this->inactive3);
$objResponse->addAssign("result1", 'style.display', 'block');
$objResponse->addAssign("result2", 'style.display', 'none');
$objResponse->addAssign("result3", 'style.display', 'none');
break;
case 2:
$objResponse->addAssign("resultLink2", 'innerHTML', $this->active2);
$objResponse->addAssign("resultLink1", 'innerHTML', $this->inactive1);
$objResponse->addAssign("resultLink3", 'innerHTML', $this->inactive3);
$objResponse->addAssign("result2", 'style.display', 'block');
$objResponse->addAssign("result1", 'style.display', 'none');
$objResponse->addAssign("result3", 'style.display', 'none');
break;
case 3:
$objResponse->addAssign("resultLink3", 'innerHTML', $this->active3);
$objResponse->addAssign("resultLink2", 'innerHTML', $this->inactive2);
$objResponse->addAssign("resultLink1", 'innerHTML', $this->inactive1);
$objResponse->addAssign("result3", 'style.display', 'block');
$objResponse->addAssign("result2", 'style.display', 'none');
$objResponse->addAssign("result1", 'style.display', 'none');
break;
}
//Trefferanzahlen neu schreiben
$objResponse->addAssign('header1', 'innerHTML', $header1);
$objResponse->addAssign('header2', 'innerHTML', $header2);
$objResponse->addAssign('header3', 'innerHTML', $header3);
return $objResponse->getXML();
}
/**
Diese Funktion ist die Hauptantwortfunktion. In Ihr werden nacheinander die drei Suchmaschinen aufgerufen und geparset und dann
damit die jeweiligen Result-Blöcke gefüllt.
@param String Suchbegriff
@return XajaxResponseObject
/
function getEngine($phrase) {
//Suchbegriff wird korrekt formatiert
$phrase = urlencode($phrase);
//Die drei Suchmaschinen werden nacheinander aufgerufen, der Rückgabewert ist jeweils ein Array, dass an der Stelle 0 die Ergebnisse
//und an Stelle 1 die Anzahl der Treffer enthält.
$output1 = $this->parseSearch($phrase);
$output2 = $this->parseKB($phrase);
$output3 = $this->parseNews($phrase);
//Neues Antwortobjekt wird instanziiert
$objResponse = new tx_xajax_response();
$objResponse->addAssign('hilfe', 'style.display', 'none');
//Ergebnisse 1 (Webseite) -> aktiv gesetzt
$objResponse->addAssign('result1', 'innerHTML', $output1[0]);
$objResponse->addAssign('result1', 'style.display', 'block');
$objResponse->addAssign("resultLink1", 'innerHTML', $this->active1);
$objResponse->addAssign('header1', 'innerHTML', $output1[1]);
//Ergebnisse 2 (Knowledge Base)
$objResponse->addAssign('result2', 'innerHTML', $output2[0]);
$objResponse->addAssign('result2', 'style.display', 'none');
$objResponse->addAssign('header2', 'innerHTML', $output2[1]);
//Ergebnisse 3 (Newsmeldungen)
$objResponse->addAssign('result3', 'innerHTML', $output3[0]);
$objResponse->addAssign('result3', 'style.display', 'none');
$objResponse->addAssign('header3', 'innerHTML', $output3[1]);
//Der Gesamtblock mit den Ergebnissen wird sichtbar geschaltet und die Antwort wird zurückgegeben.
$objResponse->addAssign('resultWrapper', 'style.display', 'block');
return $objResponse->getXML();
}
/**
Diese Funktion parst die Seite mit den Suchergebnissen der Typo3 Indexed Search und gibt Sie in einer Variable zurück. Dazu wird noch
die Anzahl der Treffer extrahiert und mit Klammern umschlossen als String zurückgegeben. Die Ergebnisseite wird in ein Array geladen
und dann zeilenweise ausgewertet. Über die Kennzeichnung der <div>-Boxen lässt sich bestimmen, wo der relevante Teil der Ergebnisseite
beginnt und aufhört. Diese Zeilen werden dann in die Rückgabe-Variable geschrieben.
@param String Suchbegriff
@return Array Suchergebnisse und Anzahl der Suchtreffer
*/
function parseSearch($phrase) {
$return;
$max;
$parse = false;
$res = file("http://www.rz.uni-konstanz.de/rechenzentrum/indexsuche/?tx_indexedsearch[sword]=$phrase");
for($i=0; $i<count($res); $i++) {
if($parse == false && (strstr(utf8_encode($res[$i]),"tx-indexedsearch-whatis") != false)) {
$parse = true;
}
if($parse == true && (strstr(utf8_encode($res[$i]),"tx-indexedsearch-browsebox\"></div>") != false)) {
$return .= utf8_encode($res[$i]);
$parse = false;
}
if($parse==true) {
$return .=($res[$i]);
}
if(strstr(utf8_encode($res[$i]), "insgesamt <strong>") != false) {
$max = strtok(substr(strstr(utf8_encode($res[$i]), "insgesamt <strong>"),18),"<");
}
}
return array($return, "(".$max.")");
}
/**
Diese Funktion parst die Seite mit den Suchergebnissen der Knowledge Base und gibt Sie in einer Variable zurück. Dazu wird noch
die Anzahl der Treffer extrahiert und mit Klammern umschlossen als String zurückgegeben. Die Ergebnisseite wird in ein Array geladen
und dann zeilenweise ausgewertet. Über Kommentare im Quellcode Seite lässt sich bestimmen, wo der relevante Teil der Ergebnisseite
beginnt und aufhört. Diese Zeilen werden dann in die Rückgabe-Variable geschrieben.
@param String Suchbegriff
@return Array Suchergebnisse und Anzahl der Suchtreffer
*/
function parseKB($phrase) {
$return;
$max;
$parse = false;
$res = file("http://www.rz.uni-konstanz.de/activekb/search/$phrase");
for($i=0; $i<count($res); $i++) {
if($parse == false && (strstr(utf8_encode($res[$i]),"<!-- Start Search Results -->") != false)) {
$parse = true;
}
if($parse == true && (strstr(utf8_encode($res[$i]),"<!-- End Search Results -->") != false)) {
$return .= utf8_encode($res[$i]);
$parse = false;
}
if($parse==true) {
$return .=($res[$i]);
}
if(strstr(utf8_encode($res[$i]), "Es wurde") != false) {
$max = trim(strtok(substr(strstr(utf8_encode($res[$i]), "Es wurde"),9),"A"));
if($max == "keine") $max = 0;
}
}
return array($return, "(".$max.")");
}
/**
Diese Funktion parst die Seite mit den Suchergebnissen der tt_news-Suche und gibt Sie in einer Variable zurück. Dazu wird noch
die Anzahl der Treffer extrahiert und mit Klammern umschlossen als String zurückgegeben. Die Ergebnisseite wird in ein Array geladen
und dann zeilenweise ausgewertet. Über die Kennzeichnung der <div>-Boxen lässt sich bestimmen, wo der relevante Teil der Ergebnisseite
beginnt und aufhört. Diese Zeilen werden dann in die Rückgabe-Variable geschrieben.
@param String Suchbegriff
* @return Array Suchergebnisse und Anzahl der Suchtreffer
*/
function parseNews($phrase) {
$return;
$max = 0;
$parse = false;
$res = file("http://www.rz.uni-konstanz.de/rechenzentrum/aktuelles/suche/?tx_ttnews[swords]=$phrase");
for($i=0; $i<count($res); $i++) {
if($parse == false && (strstr(utf8_encode($res[$i]),"news-list-container") != false)) {
$parse = true;
}
if($parse == true && (strstr(utf8_encode($res[$i]),"news-list-browse") != false)) {
$return .= "</div>";
$parse = false;
}
if($parse==true) {
$return .=($res[$i]);
}
if(strstr(utf8_encode($res[$i]), "news-list-item") != false) {
$max++;
}
}
return array($return, "(".$max.")");
}
}
if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/rzajaxsearch/pi1/class.tx_rzajaxsearch_pi1.php']) {
include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/rzajaxsearch/pi1/class.tx_rzajaxsearch_pi1.php']);
}
?>
Trackbacks
Trackback für spezifische URI dieses Eintrags
Keine Trackbacks



