Commit 80b375de4845fe5875fc92c52f727c1e7ffab991

Authored by Conrad Vermeulen
1 parent 5bb8b475

KTS-673

"The search algorithm needs some work"
Implemented. 

Committed By: Conrad Vermeulen
Reviewed By: Kevin Fourie

git-svn-id: https://kt-dms.svn.sourceforge.net/svnroot/kt-dms/trunk@7144 c91229c3-7414-0410-bfa2-8a42b809f60b
Showing 66 changed files with 8082 additions and 0 deletions
search2/ajax/ajax.inc.php 0 → 100755
  1 +<?php
  2 +
  3 +require_once('../../config/dmsDefaults.php');
  4 +session_start();
  5 +
  6 +require_once(KT_DIR . '/search2/search/search.inc.php');
  7 +
  8 +
  9 +class AjaxSearchHelper
  10 +{
  11 + const STATUS_SUCCESS = 0;
  12 + const STATUS_MISSING_QUERY = 1;
  13 + const STATUS_SESSION = 3;
  14 + const STATUS_MISSING_WHAT = 4;
  15 + const STATUS_MISSING_FIELDSET = 5;
  16 + const STATUS_INTERNAL = 99;
  17 + const STATUS_PARSE_PROBLEM = 20;
  18 + const STATUS_MISSING_SAVED = 6;
  19 + const STATUS_MISSING_NAME = 7;
  20 + const STATUS_SAVED_SEARCH_EXISTS = 30;
  21 + const STATUS_MISSING_DOCUMENT_TYPE = 8;
  22 + const STATUS_MISSING_FOLDER = 9;
  23 +
  24 +
  25 + public static function checkVar($var, $code, $message)
  26 + {
  27 + if (empty($var))
  28 + {
  29 + AjaxSearchHelper::createResponse($code,$message);
  30 + }
  31 + return $var;
  32 + }
  33 +
  34 + public static function checkPOST($var, $code, $message)
  35 + {
  36 + return AjaxSearchHelper::checkVar($_GET[$var], $code, $message);
  37 + }
  38 +
  39 + public static function checkSESSION($var, $code, $message)
  40 + {
  41 + return AjaxSearchHelper::checkVar($_SESSION[$var], $code, $message);
  42 + }
  43 +
  44 + public static function getSessionUser()
  45 + {
  46 + return AjaxSearchHelper::checkSESSION('userID', AjaxSearchHelper::STATUS_SESSION , _kt('Session has expired.'));
  47 + }
  48 +
  49 + public static function checkGET($var, $code, $message)
  50 + {
  51 + return AjaxSearchHelper::checkVar($_GET[$var], $code, $message);
  52 + }
  53 +
  54 +
  55 + public static function createResponse($status, $message=null, $rsName=null,$rs=null)
  56 + {
  57 + $resp = array('status'=>$status);
  58 + if (isset($message))
  59 + {
  60 + $resp['message'] = $message;
  61 + }
  62 + if (isset($rsName))
  63 + {
  64 + $resp[$rsName] = $rs;
  65 + }
  66 + print json_encode($resp);
  67 + exit;
  68 + }
  69 +
  70 + public static function parseQuery($txtQuery, $exitOnSuccess=true)
  71 + {
  72 + try
  73 + {
  74 + $expr = parseExpression($txtQuery);
  75 + if ($exitOnSuccess)
  76 + {
  77 + AjaxSearchHelper::createResponse(AjaxSearchHelper::STATUS_SUCCESS );
  78 + }
  79 + return $expr;
  80 + }
  81 + catch(Exception $e)
  82 + {
  83 + AjaxSearchHelper::createResponse(AjaxSearchHelper::STATUS_PARSE_PROBLEM , $e->getMessage());
  84 + }
  85 + }
  86 +
  87 + public static function updateQuery($iSavedId,$txtQuery, $userID)
  88 + {
  89 + $txtQuery = sanitizeForSQL($txtQuery);
  90 + $iSavedId = sanitizeForSQL($iSavedId);
  91 +
  92 + $sql = "UPDATE search_saved SET expression='$txtQuery' WHERE id=$iSavedId";
  93 + if (!Permission::userIsSystemAdministrator($userID))
  94 + {
  95 + $sql .= " AND user_id = $userID";
  96 + }
  97 + $result = DBUtil::runQuery($sql);
  98 + if (PEAR::isError($result))
  99 + {
  100 + AjaxSearchHelper::createResponse(AjaxSearchHelper::STATUS_INTERNAL );
  101 + }
  102 + AjaxSearchHelper::createResponse(AjaxSearchHelper::STATUS_SUCCESS );
  103 + }
  104 +
  105 +
  106 + public static function saveQuery($txtName,$txtQuery, $userID)
  107 + {
  108 + $lookup = sanitizeForSQL($txtName);
  109 + $sql = "select 1 from search_saved where name='$lookup'";
  110 + $result = DBUtil::getResultArray($sql);
  111 + if (PEAR::isError($result))
  112 + {
  113 + AjaxSearchHelper::createResponse(AjaxSearchHelper::STATUS_INTERNAL );
  114 + }
  115 + if (count($result) > 0)
  116 + {
  117 + AjaxSearchHelper::createResponse(AjaxSearchHelper::STATUS_SAVED_SEARCH_EXISTS, _kt('Search with this name already exists') );
  118 + }
  119 +
  120 + // autoInsert does escaping...
  121 + $values = array(
  122 + 'name'=>$txtName,
  123 + 'expression'=>$txtQuery,
  124 + 'type'=>'S',
  125 + 'shared'=>0,
  126 + 'user_id' => $userID
  127 + );
  128 +
  129 + $result = DBUtil::autoInsert('search_saved', $values);
  130 +
  131 + if (PEAR::isError($result))
  132 + {
  133 + AjaxSearchHelper::createResponse(AjaxSearchHelper::STATUS_INTERNAL );
  134 + }
  135 + AjaxSearchHelper::createResponse(AjaxSearchHelper::STATUS_SUCCESS );
  136 + }
  137 +
  138 + public static function getSavedSearches($userID)
  139 + {
  140 + $rs = SearchHelper::getSavedSearches($userID);
  141 + if (PEAR::isError($rs))
  142 + {
  143 + AjaxSearchHelper::createResponse(AjaxSearchHelper::STATUS_INTERNAL );
  144 + }
  145 +
  146 + AjaxSearchHelper::createResponse(AjaxSearchHelper::STATUS_SUCCESS , null, 'searches', $rs);
  147 + }
  148 +
  149 +
  150 +
  151 + public static function getDocumentTypes()
  152 + {
  153 + $rs = SearchHelper::getDocumentTypes();
  154 + if (PEAR::isError($rs))
  155 + {
  156 + AjaxSearchHelper::createResponse(AjaxSearchHelper::STATUS_INTERNAL, $rs->getMessage() );
  157 + }
  158 + AjaxSearchHelper::createResponse(AjaxSearchHelper::STATUS_SUCCESS , null, 'documenttypes', $rs);
  159 + }
  160 +
  161 + public static function getDocumentTypeFieldsets($documentTypeID)
  162 + {
  163 + $rs = SearchHelper::getDocumentTypeFieldsets($documentTypeID);
  164 +
  165 + if (PEAR::isError($rs))
  166 + {
  167 + AjaxSearchHelper::createResponse(AjaxSearchHelper::STATUS_INTERNAL, $rs->getMessage() );
  168 + }
  169 + AjaxSearchHelper::createResponse(AjaxSearchHelper::STATUS_SUCCESS , null, 'fieldsets', $rs);
  170 + }
  171 +
  172 +
  173 + public static function getFieldsets()
  174 + {
  175 + $rs = SearchHelper::getFieldsets();
  176 + if (PEAR::isError($rs))
  177 + {
  178 + AjaxSearchHelper::createResponse(AjaxSearchHelper::STATUS_INTERNAL, $rs->getMessage() );
  179 + }
  180 + AjaxSearchHelper::createResponse(AjaxSearchHelper::STATUS_SUCCESS , null, 'fieldsets', $rs);
  181 + }
  182 +
  183 + public static function getFields($fieldsetID)
  184 + {
  185 + $result = SearchHelper::getFields($fieldsetID);
  186 +
  187 + if (PEAR::isError($result))
  188 + {
  189 + AjaxSearchHelper::createResponse(AjaxSearchHelper::STATUS_INTERNAL, $result->getMessage() );
  190 + }
  191 +
  192 + AjaxSearchHelper::createResponse(AjaxSearchHelper::STATUS_SUCCESS , null, 'fields', $result);
  193 + }
  194 +
  195 +
  196 + public static function getFolder($folderID)
  197 + {
  198 + $userid = AjaxSearchHelper::getSessionUser();
  199 +
  200 + $folders = SearchHelper::getFolder($folderID, $userid);
  201 + if (PEAR::isError($folders))
  202 + {
  203 + AjaxSearchHelper::createResponse(AjaxSearchHelper::STATUS_MISSING_FOLDER, $folders->getMessage() );
  204 + }
  205 +
  206 + AjaxSearchHelper::createResponse(AjaxSearchHelper::STATUS_SUCCESS , null, 'folders', $folders);
  207 +
  208 + }
  209 +
  210 + public static function getSearchFields()
  211 + {
  212 + $results = SearchHelper::getSearchFields();
  213 + AjaxSearchHelper::createResponse(AjaxSearchHelper::STATUS_SUCCESS , null, 'fields', $results);
  214 + }
  215 +
  216 +}
  217 +
  218 +?>
0 219 \ No newline at end of file
... ...
search2/ajax/metadata.php 0 → 100755
  1 +<?php
  2 +require_once('ajax.inc.php');
  3 +
  4 +$what = AjaxSearchHelper::checkGET('what', AjaxSearchHelper::STATUS_MISSING_WHAT , _kt('What is required? fieldsets or fields?'));
  5 +
  6 +switch ($what)
  7 +{
  8 + case 'documenttypes':
  9 + AjaxSearchHelper::getDocumentTypes();
  10 + case 'documenttypefieldsets':
  11 + $documentTypeID = AjaxSearchHelper::checkGET('documenttypeid', AjaxSearchHelper::STATUS_MISSING_DOCUMENT_TYPE, _kt('Document type id is not specified.'));
  12 + AjaxSearchHelper::getDocumentTypeFieldsets($documentTypeID);
  13 + case 'fieldsets':
  14 + AjaxSearchHelper::getFieldsets();
  15 + case 'fields':
  16 + $fieldsetID = AjaxSearchHelper::checkGET('fieldsetid', AjaxSearchHelper::STATUS_MISSING_FIELDSET, _kt('Field set id is not specified.'));
  17 + AjaxSearchHelper::getFields($fieldsetID);
  18 + default:
  19 + AjaxSearchHelper::createResponse(AjaxSearchHelper::STATUS_INTERNAL , _kt('Nothing else is available'));
  20 +}
  21 +
  22 +
  23 +
  24 +?>
0 25 \ No newline at end of file
... ...
search2/ajax/parseExpr.php 0 → 100755
  1 +<?php
  2 +
  3 +require_once('ajax.inc.php');
  4 +
  5 +$txtQuery = AjaxSearchHelper::checkGET('txtQuery',AjaxSearchHelper::STATUS_MISSING_QUERY ,_kt('Query is empty'));
  6 +
  7 +AjaxSearchHelper::parseQuery($txtQuery);
  8 +
  9 +?>
0 10 \ No newline at end of file
... ...
search2/ajax/saveExpr.php 0 → 100755
  1 +<?php
  2 +require_once('ajax.inc.php');
  3 +
  4 +$userID = AjaxSearchHelper::getSessionUser();
  5 +$txtQuery = AjaxSearchHelper::checkGET('txtQuery',AjaxSearchHelper::STATUS_MISSING_QUERY ,_kt('Query is empty'));
  6 +
  7 +AjaxSearchHelper::parseQuery($txtQuery, false);
  8 +
  9 +if (array_key_exists('iSavedId',$_GET))
  10 +{
  11 + $iSavedId = AjaxSearchHelper::checkGET('iSavedId', AjaxSearchHelper::STATUS_MISSING_SAVED, _kt('Saved search ID is missing'));
  12 +
  13 + if (!is_numeric($iSavedId))
  14 + {
  15 + AjaxHelper::ajaxResponse(AjaxSearchHelper::STATUS_MISSING_SAVED, _kt('Saved search ID is not numeric') );
  16 + }
  17 +
  18 + AjaxSearchHelper::updateQuery($iSavedId, $txtQuery, $userID);
  19 +
  20 +}
  21 +else
  22 +{
  23 + $txtName = AjaxSearchHelper::checkGET('txtName',AjaxSearchHelper::STATUS_MISSING_NAME ,_kt('Query name is empty'));
  24 +
  25 + AjaxSearchHelper::saveQuery($txtName, $txtQuery, $userID);
  26 +
  27 +}
  28 +
  29 +
  30 +
  31 +
  32 +
  33 +
  34 +
  35 +
  36 +
  37 +
  38 +
  39 +
  40 +?>
0 41 \ No newline at end of file
... ...
search2/ajax/savedSearches.php 0 → 100755
  1 +<?php
  2 +
  3 +require_once('ajax.inc.php');
  4 +
  5 +$userID = AjaxSearchHelper::getSessionUser();
  6 +
  7 +AjaxSearchHelper::getSavedSearches($userID);
  8 +
  9 +?>
0 10 \ No newline at end of file
... ...
search2/ajax/searchFields.php 0 → 100755
  1 +<?php
  2 +
  3 +require_once('ajax.inc.php');
  4 +
  5 +$reg = ExprFieldRegistry::getRegistry();
  6 +
  7 +AjaxSearchHelper::getSearchFields();
  8 +?>
0 9 \ No newline at end of file
... ...
search2/ajax/treeNodes.php 0 → 100755
  1 +<?php
  2 +
  3 +require_once('ajax.inc.php');
  4 +
  5 +$folderID = AjaxSearchHelper::checkGET('folderid', AjaxSearchHelper::STATUS_MISSING_FOLDER, _kt('Folder id is not specified.'));
  6 +AjaxSearchHelper::getFolder($folderID);
  7 +
  8 +?>
0 9 \ No newline at end of file
... ...
search2/images/kn.png 0 → 100755

863 Bytes

search2/images/o-red.png 0 → 100755

595 Bytes

search2/images/o-yellow.png 0 → 100755

489 Bytes

search2/images/wledgetree.png 0 → 100755

2.96 KB

search2/indexing/bin/cronIndexer.php 0 → 100755
  1 +<?php
  2 +
  3 +require_once(realpath('../../../config/dmsDefaults.php'));
  4 +require_once('indexing/indexerCore.inc.php');
  5 +
  6 +$indexer = Indexer::get();
  7 +$indexer->indexDocuments();
  8 +
  9 +?>
0 10 \ No newline at end of file
... ...
search2/indexing/bin/diagnose.php 0 → 100755
  1 +<?php
  2 +
  3 +require_once(realpath('../../../config/dmsDefaults.php'));
  4 +require_once('indexing/indexerCore.inc.php');
  5 +
  6 +$indexer = Indexer::get();
  7 +$diagnoses = $indexer->diagnose();
  8 +
  9 +var_dump($diagnoses);
  10 +
  11 +?>
0 12 \ No newline at end of file
... ...
search2/indexing/bin/optimise.php 0 → 100755
  1 +<?php
  2 +
  3 +require_once(realpath('../../../config/dmsDefaults.php'));
  4 +require_once('indexing/indexerCore.inc.php');
  5 +
  6 +$indexer = Indexer::get();
  7 +$indexer->optimise();
  8 +
  9 +?>
0 10 \ No newline at end of file
... ...
search2/indexing/bin/recreateIndex.php 0 → 100755
  1 +<?php
  2 +
  3 +if (true)
  4 +{
  5 + die('are you sure?');
  6 +}
  7 +
  8 +require_once(realpath('../../../config/dmsDefaults.php'));
  9 +require_once('indexing/indexerCore.inc.php');
  10 +require_once('indexing/indexers/PHPLuceneIndexer.inc.php');
  11 +
  12 +
  13 +
  14 +// this is a function specific to PHP
  15 +PHPLuceneIndexer::createIndex();
  16 +PHPLuceneIndexer::indexAll();
  17 +
  18 +print "The lucene index has been deleted. All documents are now in the queue.\n";
  19 +
  20 +?>
0 21 \ No newline at end of file
... ...
search2/indexing/bin/registerTypes.php 0 → 100755
  1 +<?php
  2 +
  3 +require_once(realpath('../../../config/dmsDefaults.php'));
  4 +require_once('indexing/indexerCore.inc.php');
  5 +
  6 +$indexer = Indexer::get();
  7 +$indexer->registerTypes(true);
  8 +
  9 +?>
0 10 \ No newline at end of file
... ...
search2/indexing/extractorCore.inc.php 0 → 100755
  1 +<?php
  2 +
  3 +/**
  4 + * DocumentExtractor is the base class for all text extractors.
  5 + *
  6 + */
  7 +abstract class DocumentExtractor
  8 +{
  9 + /**
  10 + * The source filename from which to extract text.
  11 + *
  12 + * @var string
  13 + */
  14 + protected $sourcefile;
  15 +
  16 + /**
  17 + * The target filename, where the extracted text must be stored.
  18 + *
  19 + * @var string
  20 + */
  21 + protected $targetfile;
  22 +
  23 + /**
  24 + * The mime type of the source file.
  25 + *
  26 + * @var string
  27 + */
  28 + protected $mimetype;
  29 +
  30 + /**
  31 + * The extension of the source file.
  32 + *
  33 + * @var string
  34 + */
  35 + protected $extension;
  36 +
  37 + /**
  38 + * Reference to the document being indexed.
  39 + *
  40 + * @var Document
  41 + */
  42 + protected $document;
  43 +
  44 + /**
  45 + * Indicates if the extractor needs an intermediate file or not.
  46 + * Generally the source file will be a file within the respository itself. Some extractors may
  47 + * require the source file to have the correct extension. Setting this to true will result in
  48 + * a file being created with the extension of the file. It is ideal to disable this if possible.
  49 + *
  50 + * @var boolean
  51 + */
  52 + protected $needsIntermediate;
  53 +
  54 + /**
  55 + * The status of the extraction. If null, the extraction has not been done yet.
  56 + *
  57 + * @var boolean
  58 + */
  59 + protected $extractionStatus;
  60 +
  61 + /**
  62 + * The status of the indexing. If null, the indexing has not been done yet.
  63 + *
  64 + * @var boolean
  65 + */
  66 + protected $indexStatus;
  67 +
  68 +
  69 + public function __construct()
  70 + {
  71 + $this->needsIntermediate=false;
  72 + $this->extractionStatus = null;
  73 + $this->indexStatus = null;
  74 + }
  75 +
  76 + /**
  77 + * Sets the status of the indexing.
  78 + *
  79 + * @param unknown_type $status
  80 + */
  81 + public function setIndexingStatus($status)
  82 + {
  83 + $this->indexStatus = $status;
  84 + }
  85 + /**
  86 + * Returns the indexing status.
  87 + *
  88 + * @return boolean
  89 + */
  90 + public function getIndexingStatus()
  91 + {
  92 + return $this->indexStatus;
  93 + }
  94 +
  95 + /**
  96 + * Sets the extraction status.
  97 + *
  98 + * @param boolean $status
  99 + */
  100 + public function setExtractionStatus($status)
  101 + {
  102 + $this->extractionStatus = $status;
  103 + }
  104 + /**
  105 + * Return the extraction status.
  106 + *
  107 + * @return boolean
  108 + */
  109 + public function getExtractionStatus()
  110 + {
  111 + return $this->extractionStatus;
  112 + }
  113 +
  114 + /**
  115 + * This associates all the mime types associated with the extractor class.
  116 + *
  117 + */
  118 + public function registerMimeTypes()
  119 + {
  120 + $types = $this->getSupportedMimeTypes();
  121 + if (empty($types))
  122 + {
  123 + return;
  124 + }
  125 + $classname=get_class($this);
  126 +
  127 + foreach($types as $type)
  128 + {
  129 + $sql = "update mime_types set extractor='$classname' where mimetypes='$type' and extractor is null";
  130 + DBUtil::runQuery($sql);
  131 + }
  132 + }
  133 +
  134 + /**
  135 + * Indicates if an intermediate file is required.
  136 + *
  137 + * @param $value boolean Optional. If set, we set the value.
  138 + * @return boolean
  139 + */
  140 + public function needsIntermediateSourceFile($value = null)
  141 + {
  142 + if (!is_null($value))
  143 + {
  144 + $this->needsIntermediate = $value;
  145 + }
  146 + return $this->needsIntermediate;
  147 + }
  148 +
  149 + /**
  150 + * Sets the source filename for the document extractor.
  151 + *
  152 + * @param string $sourcefile
  153 + */
  154 + public function setSourceFile($sourcefile)
  155 + {
  156 + $this->sourcefile=$sourcefile;
  157 + }
  158 +
  159 + /**
  160 + * Returns the source file name.
  161 + *
  162 + * @return string
  163 + */
  164 + public function getSourceFile() { return $this->sourcefile; }
  165 +
  166 + /**
  167 + * Sets the source file's mime type.
  168 + *
  169 + * @param string $mimetype
  170 + */
  171 + public function setMimeType($mimetype)
  172 + {
  173 + $this->mimetype=$mimetype;
  174 + }
  175 + /**
  176 + * Returns the mime type for the source file.
  177 + *
  178 + * @return string
  179 + */
  180 + public function getMimeType() { return $this->mimetype; }
  181 +
  182 + /**
  183 + * Indicates the extension for the source file.
  184 + *
  185 + * @param string $extension
  186 + */
  187 + public function setExtension($extension)
  188 + {
  189 + $this->extension=$extension;
  190 + }
  191 + /**
  192 + * Returns the extension of the source file.
  193 + *
  194 + * @return string
  195 + */
  196 + public function getExtension() { return $this->extension; }
  197 +
  198 + /**
  199 + * Sets the file name of the target text file.
  200 + *
  201 + * @param string $targetfile
  202 + */
  203 + public function setTargetFile($targetfile)
  204 + {
  205 + $this->targetfile=$targetfile;
  206 + }
  207 +
  208 + /**
  209 + * Gets the file name of the target text file containing the extracted text.
  210 + *
  211 + * @return unknown
  212 + */
  213 + public function getTargetFile() { return $this->targetfile; }
  214 +
  215 + /**
  216 + * Filter function that may be applied after extraction. This may be overridden.
  217 + *
  218 + * @param string $text
  219 + * @return string
  220 + */
  221 + protected function filter($text)
  222 + {
  223 + return $text;
  224 + }
  225 +
  226 + /**
  227 + * Set the document that will be indexed.
  228 + *
  229 + * @param Document $document
  230 + */
  231 + public function setDocument($document)
  232 + {
  233 + $this->document = $document;
  234 + }
  235 +
  236 + /**
  237 + * Returns a reference to the document.
  238 + *
  239 + * @return string
  240 + */
  241 + public function getDocument()
  242 + {
  243 + return $this->document;
  244 + }
  245 +
  246 + /**
  247 + * Returns an array of supported mime types.
  248 + * e.g. return array('plain/text');
  249 + *
  250 + *
  251 + * @return array
  252 + *
  253 + */
  254 + public abstract function getSupportedMimeTypes();
  255 +
  256 + /**
  257 + * Extracts the content from the source file.
  258 + *
  259 + * @return boolean
  260 + */
  261 + public abstract function extractTextContent();
  262 +
  263 + /**
  264 + * Returns a friendly name for the document text extractor.
  265 + *
  266 + * @return string
  267 + */
  268 + public abstract function getDisplayName();
  269 +
  270 + /**
  271 + * Attempts to diagnose any problems with the indexing process.
  272 + *
  273 + * @return string
  274 + */
  275 + public abstract function diagnose();
  276 +
  277 +}
  278 +
  279 +/**
  280 + * This class extends the document extractor to execute some command line application.
  281 + * The getCommandLine() method needs to be overridden.
  282 + *
  283 + */
  284 +abstract class ExternalDocumentExtractor extends DocumentExtractor
  285 +{
  286 + /**
  287 + * Initialise the extractor.
  288 + *
  289 + */
  290 + public function __construct()
  291 + {
  292 + parent::__construct();
  293 + putenv('LANG=en_US.UTF-8');
  294 + }
  295 +
  296 + /**
  297 + * Executes a command. Returns true if successful.
  298 + *
  299 + * @param string $cmd A command line instruction.
  300 + * @return boolean
  301 + */
  302 + protected function exec($cmd)
  303 + {
  304 + $aRet = KTUtil::pexec($cmd);
  305 + return $aRet['ret'] == 0;
  306 + }
  307 +
  308 + /**
  309 + * Returns the command line string to be executed.
  310 + * The command returned should include the target filename.
  311 + *
  312 + * @return string
  313 + */
  314 + protected function getCommandLine()
  315 + {
  316 + throw new Exception('getCommandLine is not implemented');
  317 + }
  318 +
  319 + /**
  320 + * Executes the command that executes the command.
  321 + * Returns true if success.
  322 + *
  323 + * @return boolean
  324 + */
  325 + public function extractTextContent()
  326 + {
  327 + global $default;
  328 +
  329 + $cmdline = $this->getCommandLine();
  330 +
  331 + $class = get_class($this);
  332 + $default->log->debug("$class: " . $cmdline);
  333 +
  334 + return $this->exec($cmdline);
  335 + }
  336 +
  337 +}
  338 +
  339 +/**
  340 + * An extension to the extenal document extractor. A derived class simply needs
  341 + * to implement a constructor and getSupportedMimeTypes().
  342 + *
  343 + */
  344 +abstract class ApplicationExtractor extends ExternalDocumentExtractor
  345 +{
  346 + /**
  347 + * The full path to the application that will be run. This will be resolved from
  348 + * the path or using the config file.
  349 + *
  350 + * @var string
  351 + */
  352 + private $application;
  353 + /**
  354 + * The command name of the application that can be run.
  355 + *
  356 + * @var string
  357 + */
  358 + private $command;
  359 + /**
  360 + * This is the friendly name for the extractor.
  361 + *
  362 + * @var string
  363 + */
  364 + private $displayname;
  365 + /**
  366 + * The command line parameters for the application.
  367 + * This may include {source} and {target} where substitutions will be done.
  368 + *
  369 + * @var string
  370 + */
  371 + private $params;
  372 +
  373 + /**
  374 + * Initialise the extractor.
  375 + *
  376 + * @param string $section The section in the config file.
  377 + * @param string $appname The application name in the config file.
  378 + * @param string $command The command that can be run.
  379 + * @param string $displayname
  380 + * @param string $params
  381 + */
  382 + public function __construct($section, $appname, $command, $displayname, $params)
  383 + {
  384 + parent::__construct();
  385 +
  386 + $this->application = KTUtil::findCommand("$section/$appname", $command);
  387 + $this->command = $command;
  388 + $this->displayname = $displayname;
  389 + $this->params = $params;
  390 + }
  391 +
  392 + /**
  393 + * Return the display name.
  394 + *
  395 + * @return string
  396 + */
  397 + public function getDisplayName()
  398 + {
  399 + return _kt($this->displayname);
  400 + }
  401 +
  402 + /**
  403 + * Returns the command line after performing substitutions.
  404 + *
  405 + * @return unknown
  406 + */
  407 + protected function getCommandLine()
  408 + {
  409 + $sources = array('{source}','{target}');
  410 + $target = array($this->sourcefile, $this->targetfile);
  411 + $cmdline = $this->command . ' ' . str_replace($sources,$target, $params);
  412 +
  413 + return $cmdline;
  414 + }
  415 +
  416 + /**
  417 + * Identifies if there are any circumstances why the command can not run that could result in the text extraction process
  418 + * failing.
  419 + *
  420 + * @return mixed Returns string if there is a problem, null otherwise.
  421 + */
  422 + public function diagnose()
  423 + {
  424 + if (false === $this->application)
  425 + {
  426 + return _kt("Cannot locate binary for $this->displayname ($this->command).");
  427 + }
  428 +
  429 + return null;
  430 + }
  431 +}
  432 +
  433 +abstract class TextExtractor extends DocumentExtractor
  434 +{
  435 + /**
  436 + * This extracts the text from the document.
  437 + *
  438 + * @return boolean
  439 + */
  440 + public function extractTextContent()
  441 + {
  442 + $content = file_get_contents($this->sourcefile);
  443 + if (false === $content)
  444 + {
  445 + return false;
  446 + }
  447 +
  448 + $result = file_put_contents($this->targetfile, $this->filter($content));
  449 +
  450 + return false !== $result;
  451 + }
  452 +
  453 + /**
  454 + * There are no external dependancies to diagnose.
  455 + *
  456 + * @return null
  457 + */
  458 + public function diagnose()
  459 + {
  460 + return null;
  461 + }
  462 +
  463 +}
  464 +
  465 +/**
  466 + * The composite extractor implies that a conversion is done to an intermediate form before another extractor is run.
  467 + *
  468 + */
  469 +abstract class CompositeExtractor extends DocumentExtractor
  470 +{
  471 + /**
  472 + * The initial extractor
  473 + *
  474 + * @var DocumentExtractor
  475 + */
  476 + private $sourceExtractor;
  477 + /**
  478 + * The text extractor
  479 + *
  480 + * @var DocumentExtractor
  481 + */
  482 + private $targetExtractor;
  483 + /**
  484 + * The extension for the initial extraction
  485 + *
  486 + * @var string
  487 + */
  488 + private $targetExtension;
  489 + /**
  490 + * The mime type of the initial extraction.
  491 + *
  492 + * @var string
  493 + */
  494 + private $targetMimeType;
  495 +
  496 + public function __construct($sourceExtractor, $targetExtension, $targetMimeType, $targetExtractor, $needsIntermediate)
  497 + {
  498 + $this->sourceExtractor = $sourceExtractor;
  499 + $this->targetExtractor = $targetExtractor;
  500 + $this->targetExtension = $targetExtension;
  501 + $this->targetMimeType = $targetMimeType;
  502 + $this->needsIntermediateSourceFile($needsIntermediate);
  503 + }
  504 +
  505 + /**
  506 + * Extracts the content of the document
  507 + *
  508 + * @return string
  509 + */
  510 + public function extractTextContent()
  511 + {
  512 + $intermediateFile = $this->targetfile . '.' . $this->targetExtension;
  513 +
  514 + $this->sourceExtractor->setSourceFile($this->sourcefile);
  515 + $this->sourceExtractor->setTargetFile($intermediateFile);
  516 + $this->sourceExtractor->setMimeType($this->mimetype);
  517 + $this->sourceExtractor->setExtension($this->extension);
  518 + if ($this->sourceExtractor->extractTextContent())
  519 + {
  520 + return false;
  521 + }
  522 +
  523 + $this->targetExtractor->setSourceFile($intermediateFile);
  524 + $this->targetExtractor->setTargetFile($this->targetfile);
  525 + $this->targetExtractor->setMimeType($this->targetMimeType);
  526 + $this->targetExtractor->setExtension($this->targetExtension);
  527 + $result = $this->targetExtractor->extractTextContent();
  528 +
  529 + unlink(@$intermediateFile);
  530 +
  531 + return $result;
  532 + }
  533 +
  534 + /**
  535 + * Diagnose the extractors
  536 + *
  537 + * @return mixed
  538 + */
  539 + public function diagnose()
  540 + {
  541 + $diagnosis = $this->sourceExtractor->diagnose();
  542 + if (!empty($diagnosis))
  543 + {
  544 + return $diagnosis;
  545 + }
  546 +
  547 + $diagnosis = $this->targetExtractor->diagnose();
  548 + if (!empty($diagnosis))
  549 + {
  550 + return $diagnosis;
  551 + }
  552 +
  553 + return null;
  554 + }
  555 +}
  556 +
  557 +
  558 +/**
  559 + * The purpose of an extractor hook is to effect the
  560 + *
  561 + */
  562 +abstract class ExtractorHook
  563 +{
  564 + /**
  565 + * Returns an array of supported mime types.
  566 + * e.g. return array('plain/text');
  567 + *
  568 + *
  569 + * @return array
  570 + *
  571 + */
  572 + public abstract function getSupportedMimeTypes();
  573 +
  574 + /**
  575 + * Returns the friendly name for the hook.
  576 + *
  577 + * @return string
  578 + */
  579 + public abstract function getDisplayName();
  580 +
  581 + /**
  582 + * This does a basic diagnosis on the hook.
  583 + *
  584 + * @return string
  585 + */
  586 + public function diagnose()
  587 + {
  588 + return null;
  589 + }
  590 +
  591 + /**
  592 + * Perform any pre extraction activities.
  593 + *
  594 + * @param DocumentExtractor $extractor
  595 + */
  596 + public function pre_extract($extractor)
  597 + {
  598 + }
  599 +
  600 + /**
  601 + * Perform any post extraction activities.
  602 + *
  603 + * @param DocumentExtractor $extractor
  604 + */
  605 + public function post_extract($extractor)
  606 + {
  607 +
  608 + }
  609 +
  610 + /**
  611 + * Perform any pre indexing activities.
  612 + *
  613 + * @param DocumentExtractor $extractor
  614 + */
  615 + public function pre_index($extractor)
  616 + {
  617 +
  618 + }
  619 +
  620 + /**
  621 + * Perform any post indexing activities.
  622 + *
  623 + * @param DocumentExtractor $extractor
  624 + */
  625 + public function post_index($extractor)
  626 + {
  627 +
  628 + }
  629 +}
  630 +
  631 +?>
0 632 \ No newline at end of file
... ...
search2/indexing/extractors/MailMimeExtractor.inc.php 0 → 100755
  1 +<?php
  2 +
  3 +class MailMimeExtractor extends TextExtractor
  4 +{
  5 + public function getDisplayName()
  6 + {
  7 + return _kt('Mail Mime Extractor');
  8 + }
  9 +
  10 + public function getSupportedMimeTypes()
  11 + {
  12 + return array('text/msg');
  13 + }
  14 +
  15 +}
  16 +
  17 +?>
0 18 \ No newline at end of file
... ...
search2/indexing/extractors/OOPDFTextExtractor.inc.php 0 → 100755
  1 +<?php
  2 +
  3 +require_once('PDFExtractor.inc.php');
  4 +require_once('OOTextExtractor.inc.php');
  5 +
  6 +class OOPDFTextExtractor extends CompositeExtractor
  7 +{
  8 + public function __construct()
  9 + {
  10 + parent::__construct(new OOTextExtractor('application/pdf'),'pdf','application/pdf',new PDFExtractor(), true);
  11 + }
  12 +
  13 + public function getSupportedMimeTypes()
  14 + {
  15 + // we provide this so diagnose doesn't fail
  16 + return array();
  17 + }
  18 +
  19 + public function getDisplayName()
  20 + {
  21 + // we provide this so diagnose doesn't fail
  22 + throw new Exception('This should be overriden');
  23 + }
  24 +
  25 +}
  26 +
  27 +/*
  28 +class OOPDFTextExtractor extends DocumentExtractor
  29 +{
  30 +
  31 + private $pdf2txt;
  32 +
  33 +
  34 + private $text2pdf;
  35 +
  36 + public function __construct()
  37 + {
  38 + $this->pdf2txt = new PDFExtractor();
  39 + $this->text2pdf = new OOTextExtractor();
  40 + }
  41 +
  42 + public function needsIntermediateSourceFile()
  43 + {
  44 + // we need the intermediate file because it
  45 + // has the correct extension. jodconverter uses the extension to determine mimetype
  46 + return true;
  47 + }
  48 +
  49 + public function getDisplayName()
  50 + {
  51 + throw new Exception('This should be overriden');
  52 + }
  53 +
  54 + public function getSupportedMimeTypes()
  55 + {
  56 + return array();
  57 + }
  58 +
  59 + public function extractTextContent()
  60 + {
  61 + $pdffile = $this->targetfile . '.pdf';
  62 +
  63 + $this->text2pdf->setSourceFile($this->sourcefile);
  64 + $this->text2pdf->setTargetFile($pdffile);
  65 + $this->text2pdf->setMimeType($this->mimetype);
  66 + $this->text2pdf->setExtension($this->extension);
  67 + if ($this->extractTextContent())
  68 + {
  69 + return false;
  70 + }
  71 +
  72 + $this->pdf2txt->setSourceFile($pdffile);
  73 + $this->pdf2txt->setTargetFile($this->targetfile);
  74 + $this->pdf2txt->setMimeType('application/pdf');
  75 + $this->pdf2txt->setExtension('pdf');
  76 + $result = $this->pdf2txt->extractTextContent();
  77 +
  78 + unlink(@$pdffile);
  79 +
  80 + return $result;
  81 + }
  82 +
  83 + public function diagnose()
  84 + {
  85 + $diagnosis = $this->pdf2txt->diagnose();
  86 + if (!empty($diagnosis))
  87 + {
  88 + return $diagnosis;
  89 + }
  90 +
  91 + $diagnosis = $this->text2pdf->diagnose();
  92 + if (!empty($diagnosis))
  93 + {
  94 + return $diagnosis;
  95 + }
  96 +
  97 + return null;
  98 + }
  99 +} */
  100 +
  101 +?>
0 102 \ No newline at end of file
... ...
search2/indexing/extractors/OOPresentationExtractor.inc.php 0 → 100755
  1 +<?php
  2 +
  3 +require_once('OOPDFTextExtractor.inc.php');
  4 +
  5 +class OOPresentationExtractor extends OOPDFTextExtractor
  6 +{
  7 + public function getDisplayName()
  8 + {
  9 + return _kt('OpenOffice Presentation Extractor');
  10 + }
  11 +
  12 + public function getSupportedMimeTypes()
  13 + {
  14 + return array(
  15 + 'application/vnd.oasis.opendocument.presentation',
  16 + 'application/vnd.oasis.opendocument.presentation-template',
  17 + );
  18 + }
  19 +}
  20 +
  21 +?>
0 22 \ No newline at end of file
... ...
search2/indexing/extractors/OOSpreadsheetExtractor.inc.php 0 → 100755
  1 +<?php
  2 +
  3 +require_once('OOPDFTextExtractor.inc.php');
  4 +
  5 +class OOSpreadsheetExtractor extends OOPDFTextExtractor
  6 +{
  7 + public function getDisplayName()
  8 + {
  9 + return _kt('OpenOffice Spreadsheet Extractor');
  10 + }
  11 +
  12 + public function getSupportedMimeTypes()
  13 + {
  14 + return array(
  15 + 'application/vnd.ms-excel',
  16 + 'application/vnd.oasis.opendocument.spreadsheet',
  17 + 'application/vnd.oasis.opendocument.spreadsheet-template',
  18 + 'application/vnd.sun.xml.calc',
  19 + 'application/vnd.sun.xml.calc.template'
  20 + );
  21 + }
  22 +}
  23 +
  24 +
  25 +?>
0 26 \ No newline at end of file
... ...
search2/indexing/extractors/OOTextExtractor.inc.php 0 → 100755
  1 +<?php
  2 +
  3 +class OOTextExtractor extends ExternalDocumentExtractor
  4 +{
  5 + private $converter;
  6 + private $javaPath;
  7 + private $ooHost;
  8 + private $ooPort;
  9 + private $targetMimeType;
  10 +
  11 + public function __construct($targetMimeType='plain/text')
  12 + {
  13 + parent::__construct();
  14 + $config =& KTConfig::getSingleton();
  15 +
  16 + $this->converter = KTUtil::findCommand('extractors/jodconverter', 'jodconverter');
  17 + $this->javaPath = KTUtil::findCommand('extractors/java', 'java');
  18 + $this->ooHost = $config->get('openoffice/host', 'localhost');
  19 + $this->ooPort = $config->get('openoffice/port', 8100);
  20 + $this->targetMimeType = $targetMimeType;
  21 + }
  22 +
  23 + public function getDisplayName()
  24 + {
  25 + return _kt('OpenOffice Text Extractor');
  26 + }
  27 +
  28 + public function getSupportedMimeTypes()
  29 + {
  30 + return array(
  31 + 'text/rtf',
  32 + 'application/vnd.oasis.opendocument.text',
  33 + 'application/vnd.oasis.opendocument.text-template',
  34 + 'application/vnd.oasis.opendocument.text-web',
  35 + 'application/vnd.oasis.opendocument.text-master',
  36 + 'application/vnd.sun.xml.writer',
  37 + 'application/vnd.sun.xml.writer.template',
  38 + 'application/vnd.sun.xml.writer.global',
  39 + );
  40 + }
  41 +
  42 + public function needsIntermediateSourceFile()
  43 + {
  44 + // we need the intermediate file because it
  45 + // has the correct extension. jodconverter uses the extension to determine mimetype
  46 + return true;
  47 + }
  48 +
  49 + protected function getCommandLine()
  50 + {
  51 + $cmdline = "$this->javaPath -jar $this->converter $this->sourcefile $this->mimetype $this->targetfile $this->targetMimeType $this->ooHost $this->ooPort";
  52 + return $cmdline;
  53 + }
  54 +
  55 + public function diagnose()
  56 + {
  57 + if (false === $this->converter)
  58 + {
  59 + return _kt('Cannot locate jodconverter');
  60 + }
  61 +
  62 + if (false === $this->javaPath)
  63 + {
  64 + return _kt('Cannot locate java');
  65 + }
  66 +
  67 +
  68 +
  69 + $connection = @fsockopen($this->ooHost, $this->ooPort,$errno, $errstr,5 );
  70 + if (false === $connection)
  71 + {
  72 + return _kt('Cannot connect to openoffice host');
  73 + }
  74 + fclose($connection);
  75 +
  76 +
  77 + return null;
  78 + }
  79 +}
  80 +
  81 +?>
0 82 \ No newline at end of file
... ...
search2/indexing/extractors/PDFExtractor.inc.php 0 → 100755
  1 +<?php
  2 +
  3 +class PDFExtractor extends ApplicationExtractor
  4 +{
  5 + public function __construct()
  6 + {
  7 + parent::__construct('extractors','pdftotext','pdftotext','PDF Text Extractor','-nopgbrk -enc UTF-8 {source} {target}');
  8 + }
  9 +
  10 + public function getSupportedMimeTypes()
  11 + {
  12 + return array('application/pdf');
  13 + }
  14 +}
  15 +
  16 +?>
0 17 \ No newline at end of file
... ...
search2/indexing/extractors/PSExtractor.inc.php 0 → 100755
  1 +<?php
  2 +
  3 +class PSExtractor extends ApplicationExtractor
  4 +{
  5 + public function __construct()
  6 + {
  7 + parent::__construct('extractors','pstotext','pstotext','PostScript Text Extractor','-nopgbrk -enc UTF-8 {source} {target}');
  8 + }
  9 +
  10 + public function getSupportedMimeTypes()
  11 + {
  12 + return array('application/postscript');
  13 + }
  14 +}
  15 +
  16 +?>
0 17 \ No newline at end of file
... ...
search2/indexing/extractors/PlainTextExtractor.inc.php 0 → 100755
  1 +<?php
  2 +
  3 +class PlainTextExtractor extends TextExtractor
  4 +{
  5 + public function getDisplayName()
  6 + {
  7 + return _kt('Plain Text Extractor');
  8 + }
  9 +
  10 + public function getSupportedMimeTypes()
  11 + {
  12 + return array('text/plain','text/csv');
  13 + }
  14 +
  15 +}
  16 +
  17 +?>
0 18 \ No newline at end of file
... ...
search2/indexing/extractors/ScriptExtractor.inc.php 0 → 100755
  1 +<?php
  2 +
  3 +class ScriptExtractor extends TextExtractor
  4 +{
  5 + public function getDisplayName()
  6 + {
  7 + return _kt('Script Extractor');
  8 + }
  9 +
  10 + public function getSupportedMimeTypes()
  11 + {
  12 + return array('application/x-shellscript','application/javascript');
  13 + }
  14 +
  15 +}
  16 +
  17 +?>
0 18 \ No newline at end of file
... ...
search2/indexing/extractors/XMLExtractor.inc.php 0 → 100755
  1 +<?php
  2 +
  3 +class XMLExtractor extends TextExtractor
  4 +{
  5 + public function getDisplayName()
  6 + {
  7 + return _kt('XML Text Extractor');
  8 + }
  9 +
  10 + public function getSupportedMimeTypes()
  11 + {
  12 + return array('text/xml','application/xml','text/html');
  13 + }
  14 +
  15 + protected function filter($text)
  16 + {
  17 + return preg_replace ("@(</?[^>]*>)+@", " ", $text);
  18 + }
  19 +}
  20 +
  21 +?>
0 22 \ No newline at end of file
... ...
search2/indexing/indexerCore.inc.php 0 → 100755
  1 +<?php
  2 +
  3 +require_once('indexing/extractorCore.inc.php');
  4 +
  5 +
  6 +class MatchResult
  7 +{
  8 + protected $document_id;
  9 + protected $title;
  10 + protected $rank;
  11 + protected $text;
  12 + protected $filesize;
  13 + protected $fullpath;
  14 + protected $live;
  15 + protected $version;
  16 + protected $filename;
  17 + protected $thumbnail; // TODO: if not null, gui can display a thumbnail
  18 + protected $viewer; // TODO: if not null, a viewer can be used to view the document
  19 + protected $document;
  20 + protected $checkoutuser;
  21 + protected $workflowstate;
  22 + protected $workflow;
  23 +
  24 + public function __construct($document_id, $rank, $title, $text)
  25 + {
  26 + $this->document_id=$document_id;
  27 + $this->rank= $rank;
  28 + $this->title=$title;
  29 + $this->text = $text;
  30 + $this->loadDocumentInfo();
  31 + }
  32 +
  33 + protected function __isset($property)
  34 + {
  35 + switch($property)
  36 + {
  37 + case 'DocumentID': return isset($this->document_id);
  38 + case 'Rank': return isset($this->rank);
  39 + case 'Text': return isset($this->text);
  40 + case 'Title': return isset($this->title);
  41 + case null: break;
  42 + default:
  43 + throw new Exception("Unknown property '$property' to get on MatchResult");
  44 + }
  45 + }
  46 +
  47 + private function loadDocumentInfo()
  48 + {
  49 + $sql = "SELECT
  50 + f.full_path, f.name, dcv.size as filesize, dcv.major_version,
  51 + dcv.minor_version, dcv.filename, cou.name as checkoutuser, w.human_name as workflow, ws.human_name as workflowstate
  52 +
  53 + FROM
  54 + documents d
  55 + INNER JOIN document_metadata_version dmv ON d.metadata_version_id = dmv.id
  56 + INNER JOIN document_content_version dcv ON dmv.content_version_id = dcv.id
  57 + LEFT JOIN folders f ON f.id=d.folder_id
  58 + LEFT JOIN users cou ON d.checked_out_user_id=cou.id
  59 + LEFT JOIN workflows w ON dmv.workflow_id=w.id
  60 + LEFT JOIN workflow_states ws ON dmv.workflow_state_id = ws.id
  61 + WHERE
  62 + d.id=$this->document_id";
  63 +
  64 + $result = DBUtil::getOneResult($sql);
  65 +
  66 + if (PEAR::isError($result) || empty($result))
  67 + {
  68 + $this->live = false;
  69 + return;
  70 + }
  71 +
  72 + $this->live = true;
  73 + if (is_null($result['name']))
  74 + {
  75 + $this->fullpath = '(orphaned)';
  76 + }
  77 + else
  78 + {
  79 + $this->fullpath = $result['full_path'] . '/' . $result['name'];
  80 + if (substr($this->fullpath,0,1) == '/') $this->fullpath = substr($this->fullpath,1);
  81 + }
  82 +
  83 +
  84 + $this->filesize = $result['filesize'] + 0;
  85 +
  86 + if ($this->filesize > 1024 * 1024 * 1024)
  87 + {
  88 + $this->filesize = floor($this->filesize / (1024 * 1024 * 1024)) . 'g';
  89 + }
  90 + elseif ($this->filesize > 1024 * 1024)
  91 + {
  92 + $this->filesize = floor($this->filesize / (1024 * 1024)) . 'm';
  93 + }
  94 + elseif ($this->filesize > 1024)
  95 + {
  96 + $this->filesize = floor($this->filesize / (1024)) . 'k';
  97 + }
  98 + else
  99 + {
  100 + $this->filesize .= 'b';
  101 + }
  102 +
  103 + $this->version = $result['major_version'] . '.' . $result['minor_version'];
  104 + $this->filename=$result['filename'];
  105 + $this->checkoutuser = $result['checkoutuser'];
  106 + $this->workflow = $result['workflow'];
  107 + $this->workflowstate = $result['workflowstate'];
  108 +
  109 + }
  110 +
  111 +
  112 +
  113 + protected function __get($property)
  114 + {
  115 + switch($property)
  116 + {
  117 + case 'DocumentID': return $this->document_id;
  118 + case 'Rank': return $this->rank;
  119 + case 'Text': return $this->text;
  120 + case 'Title': return $this->title;
  121 + case 'FullPath': return $this->fullpath;
  122 + case 'IsLive': return $this->live;
  123 + case 'Filesize': return $this->filesize;
  124 + case 'Version': return $this->version;
  125 + case 'Filename': return $this->filename;
  126 + case 'Document':
  127 + if (is_null($this->document))
  128 + $this->document = Document::get($this->document_id);
  129 + return $this->document;
  130 + case 'IsAvailable':
  131 + return $this->Document->isLive();
  132 +
  133 + case 'CheckedOutUser':
  134 + return $this->checkoutuser;
  135 + case 'Workflow':
  136 + if (is_null($this->workflow))
  137 + {
  138 + return '';
  139 + }
  140 + return "$this->workflow - $this->workflowstate";
  141 + case null: break;
  142 + default:
  143 + throw new Exception("Unknown property '$property' to get on MatchResult");
  144 + }
  145 + }
  146 +
  147 + protected function __set($property, $value)
  148 + {
  149 + switch($property)
  150 + {
  151 + case 'Rank': $this->rank = number_format($value,2,'.',','); break;
  152 + case 'Text': $this->text = $value; break;
  153 + default:
  154 + throw new Exception("Unknown property '$property' to set on MatchResult");
  155 + }
  156 + }
  157 +}
  158 +
  159 +function MatchResultCompare($a, $b)
  160 +{
  161 + if ($a->Rank == $b->Rank) {
  162 + return 0;
  163 + }
  164 + return ($a->Rank < $b->Rank) ? -1 : 1;
  165 +}
  166 +
  167 +class QueryResultItem extends MatchResult
  168 +{
  169 + protected $discussion;
  170 +
  171 + public function __construct($document_id, $rank, $title, $text, $discussion)
  172 + {
  173 + parent::__construct($document_id, $rank, $title, $text);
  174 + $this->discussion=$discussion;
  175 + }
  176 +
  177 + protected function __isset($property)
  178 + {
  179 + switch($property)
  180 + {
  181 + case 'Discussion': return isset($this->discussion);
  182 + default: return parent::__isset($property);
  183 + }
  184 + }
  185 +
  186 + protected function __get($property)
  187 + {
  188 + switch($property)
  189 + {
  190 + case 'Discussion': return $this->discussion;
  191 + default: return parent::__get($property);
  192 + }
  193 + }
  194 +}
  195 +
  196 +abstract class Indexer
  197 +{
  198 + /**
  199 + * Cache of extractors
  200 + *
  201 + * @var array
  202 + */
  203 + private $extractorCache;
  204 +
  205 + /**
  206 + * Indicates if the indexer will do logging.
  207 + *
  208 + * @var boolean
  209 + */
  210 + private $debug;
  211 + /**
  212 + * Cache on mime related hooks
  213 + *
  214 + * @var unknown_type
  215 + */
  216 + private $mimeHookCache;
  217 + /**
  218 + * Cache on general hooks.
  219 + *
  220 + * @var array
  221 + */
  222 + private $generalHookCache;
  223 +
  224 + /**
  225 + * This is a path to the extractors.
  226 + *
  227 + * @var string
  228 + */
  229 + private $extractorPath;
  230 + /**
  231 + * This is a path to the hooks.
  232 + *
  233 + * @var string
  234 + */
  235 + private $hookPath;
  236 +
  237 + /**
  238 + * Initialise the indexer
  239 + *
  240 + */
  241 + protected function __construct()
  242 + {
  243 + $this->extractorCache=array();
  244 + $this->debug=true;
  245 + $this->hookCache = array();
  246 + $this->generalHookCache = array();
  247 +
  248 + $config = KTConfig::getSingleton();
  249 +
  250 + $this->extractorPath = $config->get('indexer/extractorPath', 'extractors');
  251 + $this->hookPath = $config->get('indexer/extractorHookPath','extractorHooks');
  252 + }
  253 +
  254 + /**
  255 + * Returns a reference to the main class
  256 + *
  257 + * @return Indexer
  258 + */
  259 + public static function get()
  260 + {
  261 + static $singleton = null;
  262 +
  263 + if (is_null($singleton))
  264 + {
  265 + $config = KTConfig::getSingleton();
  266 + $classname = $config->get('indexer/coreClass');
  267 +
  268 + require_once('indexing/indexers/' . $classname . '.inc.php');
  269 +
  270 + if (!class_exists($classname))
  271 + {
  272 + throw new Exception("Class '$classname' does not exist.");
  273 + }
  274 +
  275 + $singleton = new $classname;
  276 + }
  277 +
  278 + return $singleton;
  279 + }
  280 +
  281 + public abstract function deleteDocument($docid);
  282 +
  283 + /**
  284 + * Remove the association of all extractors to mime types on the database.
  285 + *
  286 + */
  287 + public function clearExtractors()
  288 + {
  289 + global $default;
  290 + $sql = "update mime_types set extractor=null";
  291 + DBUtil::runQuery($sql);
  292 +
  293 + $default->log->debug('clearExtractors');
  294 + }
  295 +
  296 + /**
  297 + * lookup the name of the extractor class based on the mime type.
  298 + *
  299 + * @param string $type
  300 + * @return string
  301 + */
  302 + public static function resolveExtractor($type)
  303 + {
  304 + global $default;
  305 + $sql = "select extractor from mime_types where filetypes='$type'";
  306 + $class = DBUtil::getOneResultKey($sql,'extractor');
  307 + if (PEAR::isError($class))
  308 + {
  309 + $default->log->error("resolveExtractor: cannot resolve $type");
  310 + return $class;
  311 + }
  312 + if ($this->debug) $default->log->debug("resolveExtractor: Resolved '$class' from mime type '$type'.");
  313 + return $class;
  314 + }
  315 +
  316 + /**
  317 + * Return all the discussion text.
  318 + *
  319 + * @param int $docid
  320 + * @return string
  321 + */
  322 + public static function getDiscussionText($docid)
  323 + {
  324 + $sql = "SELECT
  325 + dc.subject, dc.body
  326 + FROM
  327 + discussion_threads dt
  328 + INNER JOIN discussion_comments dc ON dc.thread_id=dt.id AND dc.id BETWEEN dt.first_comment_id AND dt.last_comment_id
  329 + WHERE
  330 + dt.document_id=$docid";
  331 + $result = DBUtil::getResultArray($sql);
  332 + $text = '';
  333 +
  334 + foreach($result as $record)
  335 + {
  336 + $text .= $record['subject'] . "\n" . $record['body'] . "\n";
  337 + }
  338 +
  339 + return $text;
  340 + }
  341 +
  342 + /**
  343 + * Schedule the indexing of a document.
  344 + *
  345 + * @param string $document
  346 + * @param string $what
  347 + */
  348 + public static function index($document, $what='C')
  349 + {
  350 + global $default;
  351 +
  352 + $document_id = $document->getId();
  353 + $userid=$_SESSION['userID'];
  354 + if (empty($userid)) $userid=1;
  355 +
  356 + // we dequeue the document so that there are no issues when enqueuing
  357 + Indexer::unqueueDocument($document_id);
  358 +
  359 + // enqueue item
  360 + $sql = "INSERT INTO index_files(document_id, user_id, what) VALUES($document_id, $userid, '$what')";
  361 + DBUtil::runQuery($sql);
  362 +
  363 +// if ($this->debug) $default->log->debug("index: Queuing indexing of $document_id");
  364 + }
  365 +
  366 +
  367 + public static function indexAll()
  368 + {
  369 + $userid=$_SESSION['userID'];
  370 + if (empty($userid)) $userid=1;
  371 + $sql = "INSERT INTO index_files(document_id, user_id, what) SELECT id, $userid, 'C' FROM documents WHERE status_id=1";
  372 + DBUtil::runQuery($sql);
  373 + }
  374 +
  375 + /**
  376 + * Clearout the scheduling of documents that no longer exist.
  377 + *
  378 + */
  379 + public static function clearoutDeleted()
  380 + {
  381 + global $default;
  382 +
  383 + $sql = 'DELETE FROM
  384 + index_files AS iff USING index_files AS iff, documents
  385 + WHERE
  386 + NOT EXISTS(
  387 + SELECT
  388 + d.id
  389 + FROM
  390 + documents AS d
  391 + INNER JOIN document_metadata_version dmv ON d.metadata_version_id=dmv.id
  392 + WHERE
  393 + iff.document_id = d.id OR dmv.status_id=3
  394 + );';
  395 + DBUtil::runQuery($sql);
  396 +
  397 + // if ($this->debug) $default->log->debug("clearoutDeleted: remove documents");
  398 + }
  399 +
  400 +
  401 + /**
  402 + * Check if a document is scheduled to be indexed
  403 + *
  404 + * @param mixed $document This may be a document or document id
  405 + * @return boolean
  406 + */
  407 + public static function isDocumentScheduled($document)
  408 + {
  409 + if (is_numeric($document))
  410 + {
  411 + $docid = $document;
  412 + }
  413 + else if ($document instanceof Document)
  414 + {
  415 + $docid = $document->getId();
  416 + }
  417 + else
  418 + {
  419 + return false;
  420 + }
  421 + $sql = "SELECT 1 FROM index_files WHERE document_id=$docid";
  422 + $result = DBUtil::getResultArray($sql);
  423 + return count($result) > 0;
  424 + }
  425 +
  426 + /**
  427 + * Filters text removing redundant characters such as continuous newlines and spaces.
  428 + *
  429 + * @param string $filename
  430 + */
  431 + private function filterText($filename)
  432 + {
  433 + $content = file_get_contents($filename);
  434 +
  435 + $src = array("([\r\n])","([\n][\n])","([\n])","([\t])",'([ ][ ])');
  436 + $tgt = array("\n","\n",' ',' ',' ');
  437 +
  438 + // shrink what is being stored.
  439 + do
  440 + {
  441 + $orig = $content;
  442 + $content = preg_replace($src, $tgt, $content);
  443 + } while ($content != $orig);
  444 +
  445 + return file_put_contents($filename, $content);
  446 + }
  447 +
  448 + /**
  449 + * Load hooks for text extraction process.
  450 + *
  451 + */
  452 + private function loadExtractorHooks()
  453 + {
  454 + $this->generalHookCache = array();
  455 + $this->mimeHookCache = array();
  456 +
  457 + $dir = opendir($this->hookPath);
  458 + while (($file = readdir($dir)) !== false)
  459 + {
  460 + if (substr($file,-12) == 'Hook.inc.php')
  461 + {
  462 + require_once($this->hookPath . '/' . $file);
  463 + $class = substr($file, 0, -8);
  464 +
  465 + if (!class_exists($class))
  466 + {
  467 + continue;
  468 + }
  469 +
  470 + $hook = new $class;
  471 + if (!($class instanceof ExtractorHook))
  472 + {
  473 + continue;
  474 + }
  475 +
  476 + $mimeTypes = $hook->registerMimeTypes();
  477 + if (is_null($mimeTypes))
  478 + {
  479 + $this->generalHookCache[] = & $hook;
  480 + }
  481 + else
  482 + {
  483 + foreach($mimeTypes as $type)
  484 + {
  485 + $this->mimeHookCache[$type][] = & $hook;
  486 + }
  487 + }
  488 +
  489 + }
  490 + }
  491 + closedir($dir);
  492 + }
  493 +
  494 + /**
  495 + * This is a refactored function to execute the hooks.
  496 + *
  497 + * @param DocumentExtractor $extractor
  498 + * @param string $phase
  499 + * @param string $mimeType Optional. If set, indicates which hooks must be used, else assume general.
  500 + */
  501 + private function executeHook($extractor, $phase, $mimeType = null)
  502 + {
  503 + $hooks = array();
  504 + if (is_null($mimeType))
  505 + {
  506 + $hooks = $this->generalHookCache;
  507 + }
  508 + else
  509 + {
  510 + if (array_key_exists($mimeType, $this->mimeHookCache))
  511 + {
  512 + $hooks = $this->mimeHookCache[$mimeType];
  513 + }
  514 + }
  515 + if (empty($hooks))
  516 + {
  517 + return;
  518 + }
  519 +
  520 + foreach($hooks as $hook)
  521 + {
  522 + $hook->$phase($extractor);
  523 + }
  524 + }
  525 +
  526 + /**
  527 + * The main function that may be called repeatedly to index documents.
  528 + *
  529 + * @param int $max Default 20
  530 + */
  531 + public function indexDocuments($max=null)
  532 + {
  533 + global $default;
  534 +
  535 + $config =& KTConfig::getSingleton();
  536 +
  537 + if (is_null($max))
  538 + {
  539 + $max = $config->get('indexer/batchDocuments',20);
  540 + }
  541 +
  542 + $this->loadExtractorHooks();
  543 +
  544 + Indexer::clearoutDeleted();
  545 +
  546 + // identify the indexers that must run
  547 + // mysql specific limit!
  548 + $sql = "SELECT
  549 + iff.document_id, mt.filetypes, mt.mimetypes, mt.extractor, iff.what
  550 + FROM
  551 + index_files iff
  552 + INNER JOIN documents d ON iff.document_id=d.id
  553 + INNER JOIN document_metadata_version dmv ON d.metadata_version_id=dmv.id
  554 + INNER JOIN document_content_version dcv ON dmv.content_version_id=dcv.id
  555 + INNER JOIN mime_types mt ON dcv.mime_id=mt.id
  556 + WHERE
  557 + iff.processdate IS NULL AND dmv.status_id=1
  558 + ORDER BY indexdate
  559 + LIMIT $max";
  560 + $result = DBUtil::getResultArray($sql);
  561 + if (PEAR::isError($result))
  562 + {
  563 + return;
  564 + }
  565 +
  566 + // bail if no work to do
  567 + if (count($result) == 0)
  568 + {
  569 + return;
  570 + }
  571 +
  572 + // identify any documents that need indexing and mark them
  573 + // so they are not taken in a followup run
  574 + $ids = array();
  575 + foreach($result as $docinfo)
  576 + {
  577 + $ids[] = $docinfo['document_id'];
  578 + }
  579 +
  580 + // mark the documents as being processed
  581 + $date = date('Y-m-d H:j:s');
  582 + $ids=implode(',',$ids);
  583 + $sql = "UPDATE index_files SET processdate='$date' WHERE document_id in ($ids)";
  584 + DBUtil::runQuery($sql);
  585 +
  586 + $extractorCache = array();
  587 + $storageManager = KTStorageManagerUtil::getSingleton();
  588 +
  589 + $tempPath = $config->get("urls/tmpDirectory");
  590 +
  591 + foreach($result as $docinfo)
  592 + {
  593 + $docId=$docinfo['document_id'];
  594 + $extension=$docinfo['filetypes'];
  595 + $mimeType=$docinfo['mimetypes'];
  596 + $extractorClass=$docinfo['extractor'];
  597 + $indexDocument = in_array($docinfo['what'], array('A','C'));
  598 + $indexDiscussion = in_array($docinfo['what'], array('A','D'));
  599 +
  600 + if ($this->debug) $default->log->debug("Indexing docid: $docId extension: '$extension' mimetype: '$mimeType' extractor: '$extractorClass'");
  601 +
  602 + if (empty($extractorClass))
  603 + {
  604 + if ($this->debug) $default->log->debug("No extractor for docid: $docId");
  605 +
  606 + Indexer::unqueueDocument($docId);
  607 + continue;
  608 + }
  609 +
  610 + if ($this->debug) print "Processing document $docId.\n";
  611 + if ($indexDocument)
  612 + {
  613 + if (array_key_exists($extractorClass, $extractorCache))
  614 + {
  615 + $extractor = $extractorCache[$extractorClass];
  616 + }
  617 + else
  618 + {
  619 + require_once('extractors/' . $extractorClass . '.inc.php');
  620 +
  621 + if (!class_exists($extractorClass))
  622 + {
  623 + $default->log->error("indexDocuments: extractor '$extractorClass' does not exist.");
  624 + continue;
  625 + }
  626 +
  627 + $extractor = $extractorCache[$extractorClass] = new $extractorClass();
  628 + }
  629 +
  630 + if (is_null($extractor))
  631 + {
  632 + $default->log->error("indexDocuments: extractor '$extractorClass' not resolved - it is null.");
  633 + continue;
  634 + }
  635 +
  636 + if (!($extractor instanceof DocumentExtractor))
  637 + {
  638 + $default->log->error("indexDocuments: extractor '$extractorClass' is not a document extractor class.");
  639 + continue;
  640 + }
  641 +
  642 + $document = Document::get($docId);
  643 + $sourceFile = $storageManager->temporaryFile($document);
  644 +
  645 + if (empty($sourceFile) || !is_file($sourceFile))
  646 + {
  647 + $default->log->error("indexDocuments: source file '$sourceFile' for document $docId does not exist.");
  648 + Indexer::unqueueDocument($docId);
  649 + continue;
  650 + }
  651 +
  652 + if ($extractor->needsIntermediateSourceFile())
  653 + {
  654 + $intermediate = $tempPath . '/'. $document->getFileName();
  655 + $result = @copy($sourceFile, $intermediate);
  656 + if ($result === false)
  657 + {
  658 + $default->log->error("Could not create intermediate file from document $docid");
  659 + // problem. lets try again later. probably permission related. log the issue.
  660 + continue;
  661 + }
  662 + $sourceFile = $intermediate;
  663 + }
  664 +
  665 + $targetFile = tempnam($tempPath, 'ktindexer') . '.txt';
  666 +
  667 + $extractor->setSourceFile($sourceFile);
  668 + $extractor->setMimeType($mimeType);
  669 + $extractor->setExtension($extension);
  670 + $extractor->setTargetFile($targetFile);
  671 + $extractor->setDocument($document);
  672 + $extractor->setIndexingStatus(null);
  673 + $extractor->setExtractionStatus(null);
  674 + if ($this->debug) $default->log->debug("Extra Info docid: $docId Source File: '$sourceFile' Target File: '$targetFile'");
  675 +
  676 + $this->executeHook($extractor, 'pre_extract');
  677 + $this->executeHook($extractor, 'pre_extract', $mimeType);
  678 +
  679 + if ($extractor->extractTextContent())
  680 + {
  681 + $extractor->setExtractionStatus(true);
  682 + $this->executeHook($extractor, 'pre_index');
  683 + $this->executeHook($extractor, 'pre_index', $mimeType);
  684 +
  685 + $title = $document->getName();
  686 + if ($indexDiscussion)
  687 + {
  688 + $indexStatus = $this->indexDocumentAndDiscussion($docId, $targetFile, $title);
  689 +
  690 + if (!$indexStatus) $default->log->error("Problem indexing document $docId");
  691 +
  692 + $extractor->setIndexingStatus($indexStatus);
  693 + }
  694 + else
  695 + {
  696 + if (!$this->filterText($targetFile))
  697 + {
  698 + $default->log->error("Problem filtering document $docId");
  699 + }
  700 + else
  701 + {
  702 + $indexStatus = $this->indexDocument($docId, $targetFile, $title);
  703 +
  704 + if (!$indexStatus) $default->log->error("Problem indexing document $docId");
  705 +
  706 + $extractor->setIndexingStatus($indexStatus);
  707 + }
  708 + }
  709 +
  710 + $this->executeHook($extractor, 'post_index', $mimeType);
  711 + $this->executeHook($extractor, 'post_index');
  712 + }
  713 + else
  714 + {
  715 + $extractor->setExtractionStatus(false);
  716 + $default->log->error("Could not extract contents from document $docId");
  717 + }
  718 +
  719 + $this->executeHook($extractor, 'post_extract', $mimeType);
  720 + $this->executeHook($extractor, 'post_extract');
  721 +
  722 + if ($extractor->needsIntermediateSourceFile())
  723 + {
  724 + @unlink($sourceFile);
  725 + }
  726 +
  727 + @unlink($targetFile);
  728 + }
  729 + else
  730 + {
  731 + $this->indexDiscussion($docId);
  732 + }
  733 +
  734 + Indexer::unqueueDocument($docId);
  735 + if ($this->debug) $default->log->debug("Done indexing docid: $docId");
  736 +
  737 + }
  738 + if ($this->debug) print "Done.\n";
  739 + }
  740 +
  741 + /**
  742 + * Index a document. The base class must override this function.
  743 + *
  744 + * @param int $docId
  745 + * @param string $textFile
  746 + */
  747 + protected abstract function indexDocument($docId, $textFile, $title='');
  748 +
  749 + /**
  750 + * Index a discussion. The base class must override this function.
  751 + *
  752 + * @param int $docId
  753 + */
  754 + protected abstract function indexDiscussion($docId);
  755 +
  756 + /**
  757 + * Diagnose the extractors.
  758 + *
  759 + * @return array
  760 + */
  761 + public function diagnose()
  762 + {
  763 + $diagnosis = $this->_diagnose($this->extractorPath, 'DocumentExtractor', 'Extractor.inc.php');
  764 + $diagnosis = array_merge($diagnosis, $this->_diagnose($this->hookPath, 'Hook', 'Hook.inc.php'));
  765 +
  766 + return $diagnosis;
  767 + }
  768 +
  769 + /**
  770 + * This is a refactored diagnose function.
  771 + *
  772 + * @param string $path
  773 + * @param string $class
  774 + * @param string $extension
  775 + * @return array
  776 + */
  777 + private function _diagnose($path, $baseclass, $extension)
  778 + {
  779 + global $default;
  780 +
  781 + $diagnoses = array();
  782 + $dir = opendir($path);
  783 + $extlen = - strlen($extension);
  784 + while (($file = readdir($dir)) !== false)
  785 + {
  786 + if (substr($file,$extlen) != $extension)
  787 + {
  788 + $default->log->error("diagnose: '$file' does not have extension '$extension'.");
  789 + continue;
  790 + }
  791 +
  792 + require_once($path . '/' . $file);
  793 +
  794 + $class = substr($file, 0, -8);
  795 + if (!class_exists($class))
  796 + {
  797 + $default->log->error("diagnose: class '$class' does not exist.");
  798 + continue;
  799 + }
  800 +
  801 + $extractor = new $class();
  802 + if (!is_a($extractor, $baseclass))
  803 + {
  804 + $default->log->error("diagnose(): '$class' is not of type DocumentExtractor");
  805 + continue;
  806 + }
  807 +
  808 + $types = $extractor->getSupportedMimeTypes();
  809 + if (empty($types))
  810 + {
  811 + if ($this->debug) $default->log->debug("diagnose: class '$class' does not support any types.");
  812 + continue;
  813 + }
  814 +
  815 + $diagnosis=$extractor->diagnose();
  816 + if (empty($diagnosis))
  817 + {
  818 + continue;
  819 + }
  820 + $diagnoses[$class] = array(
  821 + 'name'=>$extractor->getDisplayName(),
  822 + 'diagnosis'=>$diagnosis
  823 + );
  824 +
  825 + }
  826 + closedir($dir);
  827 +
  828 + return $diagnoses;
  829 + }
  830 +
  831 +
  832 + /**
  833 + * Register the extractor types.
  834 + *
  835 + * @param boolean $clear. Optional. Defaults to false.
  836 + */
  837 + public function registerTypes($clear=false)
  838 + {
  839 + if ($clear)
  840 + {
  841 + $this->clearExtractors();
  842 + }
  843 + $dir = opendir($this->extractorPath);
  844 + while (($file = readdir($dir)) !== false)
  845 + {
  846 + if (substr($file,-17) == 'Extractor.inc.php')
  847 + {
  848 + require_once($this->extractorPath . '/' . $file);
  849 + $class = substr($file, 0, -8);
  850 +
  851 + if (class_exists($class))
  852 + {
  853 + continue;
  854 + }
  855 +
  856 + $extractor = new $class;
  857 + if (!($class instanceof DocumentExtractor))
  858 + {
  859 + continue;
  860 + }
  861 +
  862 + $extractor->registerMimeTypes();
  863 + }
  864 + }
  865 + closedir($dir);
  866 + }
  867 +
  868 + /**
  869 + * This is used as a possible obtimisation effort. It may be overridden in that case.
  870 + *
  871 + * @param int $docId
  872 + * @param string $textFile
  873 + */
  874 + protected function indexDocumentAndDiscussion($docId, $textFile, $title='')
  875 + {
  876 + $this->indexDocument($docId, $textFile, $title);
  877 + $this->indexDiscussion($docId);
  878 + }
  879 +
  880 + /**
  881 + * Remove the document from the queue. This is normally called when it has been processed.
  882 + *
  883 + * @param int $docid
  884 + */
  885 + public static function unqueueDocument($docid)
  886 + {
  887 + $sql = "DELETE FROM index_files WHERE document_id=$docid";
  888 + DBUtil::runQuery($sql);
  889 + }
  890 +
  891 + /**
  892 + * Run a query on the index.
  893 + *
  894 + * @param string $query
  895 + * @return array
  896 + */
  897 + public abstract function query($query);
  898 +
  899 + /**
  900 + * Converts an integer to a string that can be easily compared and reversed.
  901 + *
  902 + * @param int $int
  903 + * @return string
  904 + */
  905 + public static function longToString($int)
  906 + {
  907 + $maxlen = 14;
  908 +
  909 + $a2z = array('a','b','c','d','e','f','g','h','i','j');
  910 + $o29 = array('0','1','2','3','4','5','6','7','8','9');
  911 + $l = str_pad('',$maxlen - strlen("$int"),'0') . $int;
  912 +
  913 + return str_replace($o29, $a2z, $l);
  914 + }
  915 +
  916 + /**
  917 + * Converts a string to an integer.
  918 + *
  919 + * @param string $str
  920 + * @return int
  921 + */
  922 + public static function stringToLong($str)
  923 + {
  924 + $a2z = array('a','b','c','d','e','f','g','h','i','j');
  925 + $o29 = array('0','1','2','3','4','5','6','7','8','9');
  926 +
  927 + $int = str_replace($a2z, $o29, $str) + 0;
  928 +
  929 + return $int;
  930 + }
  931 +
  932 + /**
  933 + * Possibly we can optimise indexes. This method must be overriden.
  934 + *
  935 + */
  936 + public function optimise()
  937 + {
  938 + // do nothing
  939 + }
  940 +}
  941 +
  942 +?>
0 943 \ No newline at end of file
... ...
search2/indexing/indexers/JavaXMLRPCLuceneIndexer.inc.php 0 → 100755
  1 +<?
  2 +require_once('indexer.inc.php');
  3 +
  4 +class JavaXMLRPCLuceneIndexer extends Indexer
  5 +{
  6 + protected function indexDocument($docid, $textfile)
  7 + {
  8 + throw new Exception('TODO');
  9 + }
  10 +}
  11 +?>
0 12 \ No newline at end of file
... ...
search2/indexing/indexers/PHPLuceneIndexer.inc.php 0 → 100755
  1 +<?
  2 +
  3 +require_once 'Zend/Search/Lucene.php';
  4 +
  5 +class PHPLuceneIndexer extends Indexer
  6 +{
  7 + /**
  8 + * @var Zend_Search_Lucene
  9 + */
  10 + private $lucene;
  11 +
  12 + /**
  13 + * The constructor for PHP Lucene
  14 + *
  15 + * @param boolean $create Optional. If true, the lucene index will be recreated.
  16 + */
  17 + public function __construct()
  18 + {
  19 + parent::__construct();
  20 + $config =& KTConfig::getSingleton();
  21 + $indexPath = $config->get('indexer/luceneDirectory');
  22 + $this->lucene = new Zend_Search_Lucene($indexPath, false);
  23 + }
  24 +
  25 + /**
  26 + * Creates an index to be used.
  27 + *
  28 + */
  29 + public static function createIndex()
  30 + {
  31 + $config =& KTConfig::getSingleton();
  32 + $indexPath = $config->get('indexer/luceneDirectory');
  33 + $lucene = new Zend_Search_Lucene($indexPath, true);
  34 + }
  35 +
  36 +
  37 + /**
  38 + * A refactored method to add the document to the index..
  39 + *
  40 + * @param int $docid
  41 + * @param string $content
  42 + * @param string $discussion
  43 + */
  44 + private function addDocument($docid, $content, $discussion, $title='')
  45 + {
  46 + $doc = new Zend_Search_Lucene_Document();
  47 + $doc->addField(Zend_Search_Lucene_Field::Text('DocumentID', PHPLuceneIndexer::longToString($docid)));
  48 + $doc->addField(Zend_Search_Lucene_Field::Text('Content', $content, 'UTF-8'));
  49 + $doc->addField(Zend_Search_Lucene_Field::Text('Discussion', $discussion, 'UTF-8'));
  50 + $doc->addField(Zend_Search_Lucene_Field::Text('Title', $title, 'UTF-8'));
  51 + $this->lucene->addDocument($doc);
  52 + }
  53 +
  54 + /**
  55 + * Indexes a document based on a text file.
  56 + *
  57 + * @param int $docid
  58 + * @param string $textfile
  59 + * @return boolean
  60 + */
  61 + protected function indexDocument($docid, $textfile, $title='')
  62 + {
  63 + global $default;
  64 +
  65 + if (!is_file($textfile))
  66 + {
  67 + $default->log->error("Attempting to index $docid $textfile but it is not available.");
  68 + return false;
  69 + }
  70 +
  71 + list($content, $discussion) = $this->deleteDocument($docid);
  72 +
  73 + $this->addDocument($docid, file_get_contents($textfile), $discussion, $title);
  74 +
  75 + return true;
  76 + }
  77 +
  78 + /**
  79 + * Indexes the content and discussions on a document.
  80 + *
  81 + * @param int $docid
  82 + * @param string $textfile
  83 + * @return boolean
  84 + */
  85 + protected function indexDocumentAndDiscussion($docid, $textfile, $title='')
  86 + {
  87 + global $default;
  88 +
  89 + if (!is_file($textfile))
  90 + {
  91 + $default->log->error("Attempting to index $docid $textfile but it is not available.");
  92 + return false;
  93 + }
  94 +
  95 + $this->deleteDocument($docid);
  96 +
  97 + $this->addDocument($docid, file_get_contents($textfile), Indexer::getDiscussionText($docid), $title);
  98 +
  99 + return true;
  100 + }
  101 +
  102 + /**
  103 + * Indexes a discussion on a document..
  104 + *
  105 + * @param int $docid
  106 + * @return boolean
  107 + */
  108 + protected function indexDiscussion($docid)
  109 + {
  110 + list($content, $discussion, $title) = $this->deleteDocument($docid);
  111 +
  112 + $this->addDocument($docid, $content, Indexer::getDiscussionText($docid), $title);
  113 +
  114 + return true;
  115 + }
  116 +
  117 + /**
  118 + * Optimise the lucene index.
  119 + * This can be called periodically to optimise performance and size of the lucene index.
  120 + *
  121 + */
  122 + public function optimise()
  123 + {
  124 + $this->lucene->optimize();
  125 + }
  126 +
  127 + /**
  128 + * Removes a document from the index.
  129 + *
  130 + * @param int $docid
  131 + * @return array containing (content, discussion, title)
  132 + */
  133 + public function deleteDocument($docid)
  134 + {
  135 + $content = '';
  136 + $discussion = '';
  137 + $query = Zend_Search_Lucene_Search_QueryParser::parse('DocumentID:' . PHPLuceneIndexer::longToString($docid));
  138 + $hits = $this->lucene->find($query);
  139 + // there should only be one, but we'll loop for safety
  140 + foreach ($hits as $hit)
  141 + {
  142 + $content = $hit->Content;
  143 + $discussion = $hit->Discussion;
  144 + $title = $hit->Title;
  145 + $title='';
  146 +
  147 + $this->lucene->delete($hit);
  148 + }
  149 + return array($content, $discussion, $title);
  150 + }
  151 +
  152 + /**
  153 + * Enter description here...
  154 + *
  155 + * @param string $query
  156 + * @return array
  157 + */
  158 + public function query($query)
  159 + {
  160 + $results = array();
  161 + $query = Zend_Search_Lucene_Search_QueryParser::parse($query);
  162 +
  163 + $hits = $this->lucene->find($query);
  164 + foreach ($hits as $hit)
  165 + {
  166 + $document = $hit->getDocument();
  167 +
  168 + $document_id = PHPLuceneIndexer::stringToLong($document->DocumentID);
  169 + $content = $document->Content ;
  170 + $discussion = $document->Discussion ;
  171 + $title = $document->Title;
  172 + $score = $hit->score;
  173 +
  174 + // avoid adding duplicates. If it is in already, it has higher priority.
  175 + if (!array_key_exists($document_id, $results) || $score > $results[$document_id]->Score)
  176 + {
  177 + $results[$document_id] = new QueryResultItem($document_id, $score, $title, $content, $discussion);
  178 + }
  179 + }
  180 + return $results;
  181 + }
  182 +}
  183 +?>
0 184 \ No newline at end of file
... ...
search2/search/SearchCommandLexer.php 0 → 100755
  1 +<?php
  2 +
  3 +class SearchCommandLexer
  4 +{
  5 + private $data;
  6 + public $offset;
  7 + public $length;
  8 + public $token;
  9 + public $value;
  10 + private $state;
  11 + private $escaped;
  12 + private $exit;
  13 + private $lookahead;
  14 + private $char;
  15 +
  16 +
  17 +
  18 + public function __construct($data)
  19 + {
  20 + $this->offset=0;
  21 + $this->data=$data;
  22 + $this->token=null;
  23 + $this->value='';
  24 + $this->length=strlen($data);
  25 + $this->state = 0;
  26 + $this->escaped=false;
  27 + $this->exit=false;
  28 + $this->lookahead=null;
  29 + $this->char=null;
  30 + }
  31 +
  32 + private function processNormalChar()
  33 + {
  34 + $append=true;
  35 + $clear=false;
  36 + $checkwords=false;
  37 + $word='';
  38 +
  39 + if (in_array($this->char, array('=','(',')','[',']',',','!','<','>','"')) && !empty($this->value))
  40 + {
  41 + $word=$this->value;
  42 + $checkwords=true;
  43 + $this->offset--;
  44 + $append=false;
  45 + $clear=false;
  46 + }
  47 + else
  48 + switch ($this->char)
  49 + {
  50 + case ' ':
  51 + case "\t":
  52 + case "\r":
  53 + case "\n":
  54 + if (!empty($this->value))
  55 + {
  56 + $word=$this->value;
  57 + $checkwords=true;
  58 + }
  59 + $append=false;
  60 + $clear=true;
  61 + break;
  62 + case '=':
  63 + $this->token=SearchCommandParser::IS;
  64 + break;
  65 + case '(':
  66 + $this->token=SearchCommandParser::PAR_OPEN;
  67 + break;
  68 + case ')':
  69 + $this->token=SearchCommandParser::PAR_CLOSE;
  70 + break;
  71 + case ',':
  72 + $this->token=SearchCommandParser::COMMA;
  73 + break;
  74 + case ':':
  75 + $this->token=SearchCommandParser::COLON;
  76 + break;
  77 + case '[':
  78 + $this->token=SearchCommandParser::SQUARE_OPEN;
  79 + break;
  80 + case ']':
  81 + $this->token=SearchCommandParser::SQUARE_CLOSE;
  82 + break;
  83 + case '!':
  84 + if ($this->lookahead == '=')
  85 + {
  86 + $this->zap();
  87 + $this->token=SearchCommandParser::IS_NOT;
  88 + }
  89 + else
  90 + {
  91 + throw new Exception('Unexpected token: ' . $this->lookahead);
  92 + }
  93 + break;
  94 + case '<':
  95 + case '>':
  96 + if ($this->lookahead == '>')
  97 + {
  98 + $this->zap();
  99 + $this->token=SearchCommandParser::IS_NOT;
  100 + }
  101 + elseif ($this->lookahead == '=')
  102 + {
  103 + $this->zap();
  104 + $this->token=($this->char == '<')?(SearchCommandParser::LE):(SearchCommandParser::GE);
  105 + }
  106 + else
  107 + {
  108 + $this->token=($this->char == '<')?(SearchCommandParser::LT):(SearchCommandParser::GT);
  109 + }
  110 + break;
  111 + case '"':
  112 + $clear=true;
  113 + $this->state=1;
  114 + break;
  115 +
  116 + }
  117 + if ($clear)
  118 + {
  119 + $this->char='';
  120 + $this->value='';
  121 + $this->token=null;
  122 + }
  123 + if ($append)
  124 + {
  125 + $this->value .= $this->char;
  126 + }
  127 + if (!is_null($this->token))
  128 + {
  129 + $this->exit=true;
  130 + }
  131 + if ($checkwords)
  132 + {
  133 + $this->exit=true;
  134 + $this->value = $word;
  135 + switch (strtolower($word))
  136 + {
  137 + case 'not':
  138 + $this->token = SearchCommandParser::NOT;
  139 + break;
  140 + case 'with':
  141 + $this->token = SearchCommandParser::WITH;
  142 + break;
  143 + case 'like':
  144 + $this->token = SearchCommandParser::LIKE;
  145 + break;
  146 + case 'contains':
  147 + case 'contain':
  148 + $this->token = SearchCommandParser::CONTAINS ;
  149 + break;
  150 + case 'starts':
  151 + case 'start':
  152 + $this->token = SearchCommandParser::START ;
  153 + break;
  154 + case 'ends':
  155 + case 'end':
  156 + $this->token = SearchCommandParser::END ;
  157 + break;
  158 + case 'does':
  159 + $this->token = SearchCommandParser::DOES ;
  160 + break;
  161 + case 'is':
  162 + $this->token = SearchCommandParser::IS ;
  163 + break;
  164 + case 'between':
  165 + $this->token = SearchCommandParser::BETWEEN ;
  166 + break;
  167 + case 'or':
  168 + $this->token = SearchCommandParser::OPOR ;
  169 + break;
  170 + case 'and':
  171 + $this->token = SearchCommandParser::OPAND ;
  172 + break;
  173 +
  174 + default:
  175 +
  176 + $this->token = SearchCommandParser::TERMINAL;
  177 + break;
  178 +
  179 + }
  180 + }
  181 +
  182 + }
  183 +
  184 + private function processStringChar()
  185 + {
  186 + if ($this->escaped)
  187 + {
  188 + switch($this->char)
  189 + {
  190 + case 'r':
  191 + $this->value .= "\r";
  192 + break;
  193 + case 'n':
  194 + $this->value .= "\n";
  195 + break;
  196 + case 't':
  197 + $this->value .= "\t";
  198 + break;
  199 + default:
  200 + $this->value .= $this->char;
  201 + }
  202 + $this->escaped=false;
  203 + }
  204 + else
  205 + {
  206 + switch($this->char)
  207 + {
  208 + case '\\':
  209 + $this->escaped=true;
  210 + break;
  211 + case '"':
  212 + $this->escaped=false;
  213 + $this->state=0;
  214 + $this->exit=true;
  215 + $this->token = SearchCommandParser::VALUE;
  216 + break;
  217 + default:
  218 + $this->value .= $this->char;
  219 + }
  220 + }
  221 + }
  222 +
  223 + private function zap()
  224 + {
  225 + $this->char = substr($this->data,$this->offset++,1);
  226 + if ($this->offset <= $this->length)
  227 + {
  228 + $this->lookahead= substr($this->data,$this->offset,1);
  229 + }
  230 + else
  231 + {
  232 + $this->lookahead=null;
  233 + }
  234 + }
  235 +
  236 + public function yylex()
  237 + {
  238 + $this->exit=false;
  239 + $this->token=null;
  240 + $this->value='';
  241 + while (!$this->exit)
  242 + {
  243 + if ($this->length <= $this->offset)
  244 + {
  245 + return false;
  246 + }
  247 +
  248 + $this->zap();
  249 + switch($this->state)
  250 + {
  251 + case 0: // initial
  252 + $this->processNormalChar();
  253 + break;
  254 + case 1: // instring
  255 + $this->processStringChar();
  256 + break;
  257 + }
  258 +
  259 + if (is_null($this->lookahead) || !is_null($this->token))
  260 + {
  261 + $this->exit=true;
  262 + }
  263 + }
  264 + return true;
  265 + }
  266 +}
  267 +
  268 +?>
0 269 \ No newline at end of file
... ...
search2/search/SearchCommandParser.php 0 → 100755
  1 +<?php
  2 +/* Driver template for the PHP_SearchCommandParserrGenerator parser generator. (PHP port of LEMON)
  3 +*/
  4 +
  5 +/**
  6 + * This can be used to store both the string representation of
  7 + * a token, and any useful meta-data associated with the token.
  8 + *
  9 + * meta-data should be stored as an array
  10 + */
  11 +class SearchCommandParseryyToken implements ArrayAccess
  12 +{
  13 + public $string = '';
  14 + public $metadata = array();
  15 +
  16 + function __construct($s, $m = array())
  17 + {
  18 + if ($s instanceof SearchCommandParseryyToken) {
  19 + $this->string = $s->string;
  20 + $this->metadata = $s->metadata;
  21 + } else {
  22 + $this->string = (string) $s;
  23 + if ($m instanceof SearchCommandParseryyToken) {
  24 + $this->metadata = $m->metadata;
  25 + } elseif (is_array($m)) {
  26 + $this->metadata = $m;
  27 + }
  28 + }
  29 + }
  30 +
  31 + function __toString()
  32 + {
  33 + return $this->_string;
  34 + }
  35 +
  36 + function offsetExists($offset)
  37 + {
  38 + return isset($this->metadata[$offset]);
  39 + }
  40 +
  41 + function offsetGet($offset)
  42 + {
  43 + return $this->metadata[$offset];
  44 + }
  45 +
  46 + function offsetSet($offset, $value)
  47 + {
  48 + if ($offset === null) {
  49 + if (isset($value[0])) {
  50 + $x = ($value instanceof SearchCommandParseryyToken) ?
  51 + $value->metadata : $value;
  52 + $this->metadata = array_merge($this->metadata, $x);
  53 + return;
  54 + }
  55 + $offset = count($this->metadata);
  56 + }
  57 + if ($value === null) {
  58 + return;
  59 + }
  60 + if ($value instanceof SearchCommandParseryyToken) {
  61 + if ($value->metadata) {
  62 + $this->metadata[$offset] = $value->metadata;
  63 + }
  64 + } elseif ($value) {
  65 + $this->metadata[$offset] = $value;
  66 + }
  67 + }
  68 +
  69 + function offsetUnset($offset)
  70 + {
  71 + unset($this->metadata[$offset]);
  72 + }
  73 +}
  74 +
  75 +/** The following structure represents a single element of the
  76 + * parser's stack. Information stored includes:
  77 + *
  78 + * + The state number for the parser at this level of the stack.
  79 + *
  80 + * + The value of the token stored at this level of the stack.
  81 + * (In other words, the "major" token.)
  82 + *
  83 + * + The semantic value stored at this level of the stack. This is
  84 + * the information used by the action routines in the grammar.
  85 + * It is sometimes called the "minor" token.
  86 + */
  87 +class SearchCommandParseryyStackEntry
  88 +{
  89 + public $stateno; /* The state-number */
  90 + public $major; /* The major token value. This is the code
  91 + ** number for the token at this stack level */
  92 + public $minor; /* The user-supplied minor token value. This
  93 + ** is the value of the token */
  94 +};
  95 +
  96 +// code external to the class is included here
  97 +
  98 +// declare_class is output here
  99 +#line 2 "SearchCommandParser.y"
  100 +class SearchCommandParser#line 102 "SearchCommandParser.php"
  101 +{
  102 +/* First off, code is included which follows the "include_class" declaration
  103 +** in the input file. */
  104 +#line 4 "SearchCommandParser.y"
  105 +
  106 +
  107 + private $expr_result;
  108 + private $parse_result;
  109 +
  110 + public function __construct()
  111 + {
  112 + $this->parse_result = 'ok';
  113 + }
  114 +
  115 + public function getExprResult()
  116 + {
  117 + return $this->expr_result;
  118 + }
  119 +
  120 + public function isExprOk()
  121 + {
  122 + return $this->parse_result == 'ok';
  123 + }
  124 +
  125 +#line 128 "SearchCommandParser.php"
  126 +
  127 +/* Next is all token values, as class constants
  128 +*/
  129 +/*
  130 +** These constants (all generated automatically by the parser generator)
  131 +** specify the various kinds of tokens (terminals) that the parser
  132 +** understands.
  133 +**
  134 +** Each symbol here is a terminal symbol in the grammar.
  135 +*/
  136 + const OPOR = 1;
  137 + const OPAND = 2;
  138 + const NOT = 3;
  139 + const IS = 4;
  140 + const CONTAIN = 5;
  141 + const LIKE = 6;
  142 + const BETWEEN = 7;
  143 + const START = 8;
  144 + const END = 9;
  145 + const GT = 10;
  146 + const LE = 11;
  147 + const LT = 12;
  148 + const GE = 13;
  149 + const PAR_OPEN = 14;
  150 + const PAR_CLOSE = 15;
  151 + const DOES = 16;
  152 + const COLON = 17;
  153 + const SQUARE_OPEN = 18;
  154 + const SQUARE_CLOSE = 19;
  155 + const TERMINAL = 20;
  156 + const VALUE = 21;
  157 + const COMMA = 22;
  158 + const CONTAINS = 23;
  159 + const WITH = 24;
  160 + const IS_NOT = 25;
  161 + const YY_NO_ACTION = 84;
  162 + const YY_ACCEPT_ACTION = 83;
  163 + const YY_ERROR_ACTION = 82;
  164 +
  165 +/* Next are that tables used to determine what action to take based on the
  166 +** current state and lookahead token. These tables are used to implement
  167 +** functions that take a state number and lookahead value and return an
  168 +** action integer.
  169 +**
  170 +** Suppose the action integer is N. Then the action is determined as
  171 +** follows
  172 +**
  173 +** 0 <= N < self::YYNSTATE Shift N. That is,
  174 +** push the lookahead
  175 +** token onto the stack
  176 +** and goto state N.
  177 +**
  178 +** self::YYNSTATE <= N < self::YYNSTATE+self::YYNRULE Reduce by rule N-YYNSTATE.
  179 +**
  180 +** N == self::YYNSTATE+self::YYNRULE A syntax error has occurred.
  181 +**
  182 +** N == self::YYNSTATE+self::YYNRULE+1 The parser accepts its
  183 +** input. (and concludes parsing)
  184 +**
  185 +** N == self::YYNSTATE+self::YYNRULE+2 No such action. Denotes unused
  186 +** slots in the yy_action[] table.
  187 +**
  188 +** The action table is constructed as a single large static array $yy_action.
  189 +** Given state S and lookahead X, the action is computed as
  190 +**
  191 +** self::$yy_action[self::$yy_shift_ofst[S] + X ]
  192 +**
  193 +** If the index value self::$yy_shift_ofst[S]+X is out of range or if the value
  194 +** self::$yy_lookahead[self::$yy_shift_ofst[S]+X] is not equal to X or if
  195 +** self::$yy_shift_ofst[S] is equal to self::YY_SHIFT_USE_DFLT, it means that
  196 +** the action is not in the table and that self::$yy_default[S] should be used instead.
  197 +**
  198 +** The formula above is for computing the action when the lookahead is
  199 +** a terminal symbol. If the lookahead is a non-terminal (as occurs after
  200 +** a reduce action) then the static $yy_reduce_ofst array is used in place of
  201 +** the static $yy_shift_ofst array and self::YY_REDUCE_USE_DFLT is used in place of
  202 +** self::YY_SHIFT_USE_DFLT.
  203 +**
  204 +** The following are the tables generated in this section:
  205 +**
  206 +** self::$yy_action A single table containing all actions.
  207 +** self::$yy_lookahead A table containing the lookahead for each entry in
  208 +** yy_action. Used to detect hash collisions.
  209 +** self::$yy_shift_ofst For each state, the offset into self::$yy_action for
  210 +** shifting terminals.
  211 +** self::$yy_reduce_ofst For each state, the offset into self::$yy_action for
  212 +** shifting non-terminals after a reduce.
  213 +** self::$yy_default Default action for each state.
  214 +*/
  215 + const YY_SZ_ACTTAB = 70;
  216 +static public $yy_action = array(
  217 + /* 0 */ 52, 15, 8, 7, 4, 23, 22, 37, 34, 54,
  218 + /* 10 */ 33, 3, 5, 16, 9, 2, 21, 83, 1, 13,
  219 + /* 20 */ 50, 32, 36, 3, 5, 44, 17, 26, 47, 1,
  220 + /* 30 */ 19, 39, 1, 41, 14, 46, 20, 1, 45, 38,
  221 + /* 40 */ 1, 6, 35, 10, 42, 27, 31, 12, 5, 24,
  222 + /* 50 */ 18, 53, 28, 52, 63, 63, 63, 30, 63, 63,
  223 + /* 60 */ 63, 49, 48, 29, 40, 43, 51, 63, 11, 25,
  224 + );
  225 + static public $yy_lookahead = array(
  226 + /* 0 */ 3, 4, 6, 7, 3, 8, 9, 10, 11, 12,
  227 + /* 10 */ 13, 1, 2, 16, 17, 14, 27, 28, 29, 18,
  228 + /* 20 */ 23, 20, 25, 1, 2, 15, 14, 27, 33, 29,
  229 + /* 30 */ 27, 24, 29, 21, 30, 27, 32, 29, 27, 24,
  230 + /* 40 */ 29, 2, 19, 18, 15, 21, 19, 5, 2, 33,
  231 + /* 50 */ 22, 31, 31, 3, 34, 34, 34, 31, 34, 34,
  232 + /* 60 */ 34, 31, 31, 31, 31, 31, 31, 34, 32, 32,
  233 +);
  234 + const YY_SHIFT_USE_DFLT = -5;
  235 + const YY_SHIFT_MAX = 31;
  236 + static public $yy_shift_ofst = array(
  237 + /* 0 */ 1, -3, 1, 1, 1, 1, 12, 12, 12, 12,
  238 + /* 10 */ 12, 12, 12, 12, 12, 50, 50, 24, 24, 10,
  239 + /* 20 */ -4, 22, 15, 7, 29, 42, 46, 28, 27, 39,
  240 + /* 30 */ 23, 25,
  241 +);
  242 + const YY_REDUCE_USE_DFLT = -12;
  243 + const YY_REDUCE_MAX = 18;
  244 + static public $yy_reduce_ofst = array(
  245 + /* 0 */ -11, 4, 3, 0, 8, 11, 31, 32, 33, 30,
  246 + /* 10 */ 26, 20, 35, 21, 34, 36, 37, 16, -5,
  247 +);
  248 + static public $yyExpectedTokens = array(
  249 + /* 0 */ array(3, 14, 18, 20, ),
  250 + /* 1 */ array(3, 4, 8, 9, 10, 11, 12, 13, 16, 17, 23, 25, ),
  251 + /* 2 */ array(3, 14, 18, 20, ),
  252 + /* 3 */ array(3, 14, 18, 20, ),
  253 + /* 4 */ array(3, 14, 18, 20, ),
  254 + /* 5 */ array(3, 14, 18, 20, ),
  255 + /* 6 */ array(14, 21, ),
  256 + /* 7 */ array(14, 21, ),
  257 + /* 8 */ array(14, 21, ),
  258 + /* 9 */ array(14, 21, ),
  259 + /* 10 */ array(14, 21, ),
  260 + /* 11 */ array(14, 21, ),
  261 + /* 12 */ array(14, 21, ),
  262 + /* 13 */ array(14, 21, ),
  263 + /* 14 */ array(14, 21, ),
  264 + /* 15 */ array(3, ),
  265 + /* 16 */ array(3, ),
  266 + /* 17 */ array(21, ),
  267 + /* 18 */ array(21, ),
  268 + /* 19 */ array(1, 2, 15, ),
  269 + /* 20 */ array(6, 7, ),
  270 + /* 21 */ array(1, 2, ),
  271 + /* 22 */ array(24, ),
  272 + /* 23 */ array(24, ),
  273 + /* 24 */ array(15, ),
  274 + /* 25 */ array(5, ),
  275 + /* 26 */ array(2, ),
  276 + /* 27 */ array(22, ),
  277 + /* 28 */ array(19, ),
  278 + /* 29 */ array(2, ),
  279 + /* 30 */ array(19, ),
  280 + /* 31 */ array(18, ),
  281 + /* 32 */ array(),
  282 + /* 33 */ array(),
  283 + /* 34 */ array(),
  284 + /* 35 */ array(),
  285 + /* 36 */ array(),
  286 + /* 37 */ array(),
  287 + /* 38 */ array(),
  288 + /* 39 */ array(),
  289 + /* 40 */ array(),
  290 + /* 41 */ array(),
  291 + /* 42 */ array(),
  292 + /* 43 */ array(),
  293 + /* 44 */ array(),
  294 + /* 45 */ array(),
  295 + /* 46 */ array(),
  296 + /* 47 */ array(),
  297 + /* 48 */ array(),
  298 + /* 49 */ array(),
  299 + /* 50 */ array(),
  300 + /* 51 */ array(),
  301 + /* 52 */ array(),
  302 + /* 53 */ array(),
  303 + /* 54 */ array(),
  304 +);
  305 + static public $yy_default = array(
  306 + /* 0 */ 82, 66, 82, 82, 82, 82, 82, 82, 82, 82,
  307 + /* 10 */ 82, 82, 82, 82, 82, 66, 66, 82, 82, 82,
  308 + /* 20 */ 82, 55, 82, 82, 82, 82, 57, 73, 82, 82,
  309 + /* 30 */ 82, 82, 69, 78, 77, 68, 81, 76, 80, 79,
  310 + /* 40 */ 62, 70, 71, 60, 59, 56, 58, 72, 61, 65,
  311 + /* 50 */ 74, 64, 67, 63, 75,
  312 +);
  313 +/* The next thing included is series of defines which control
  314 +** various aspects of the generated parser.
  315 +** self::YYNOCODE is a number which corresponds
  316 +** to no legal terminal or nonterminal number. This
  317 +** number is used to fill in empty slots of the hash
  318 +** table.
  319 +** self::YYFALLBACK If defined, this indicates that one or more tokens
  320 +** have fall-back values which should be used if the
  321 +** original value of the token will not parse.
  322 +** self::YYSTACKDEPTH is the maximum depth of the parser's stack.
  323 +** self::YYNSTATE the combined number of states.
  324 +** self::YYNRULE the number of rules in the grammar
  325 +** self::YYERRORSYMBOL is the code number of the error symbol. If not
  326 +** defined, then do no error processing.
  327 +*/
  328 + const YYNOCODE = 35;
  329 + const YYSTACKDEPTH = 100;
  330 + const YYNSTATE = 55;
  331 + const YYNRULE = 27;
  332 + const YYERRORSYMBOL = 26;
  333 + const YYERRSYMDT = 'yy0';
  334 + const YYFALLBACK = 0;
  335 + /** The next table maps tokens into fallback tokens. If a construct
  336 + * like the following:
  337 + *
  338 + * %fallback ID X Y Z.
  339 + *
  340 + * appears in the grammer, then ID becomes a fallback token for X, Y,
  341 + * and Z. Whenever one of the tokens X, Y, or Z is input to the parser
  342 + * but it does not parse, the type of the token is changed to ID and
  343 + * the parse is retried before an error is thrown.
  344 + */
  345 + static public $yyFallback = array(
  346 + );
  347 + /**
  348 + * Turn parser tracing on by giving a stream to which to write the trace
  349 + * and a prompt to preface each trace message. Tracing is turned off
  350 + * by making either argument NULL
  351 + *
  352 + * Inputs:
  353 + *
  354 + * - A stream resource to which trace output should be written.
  355 + * If NULL, then tracing is turned off.
  356 + * - A prefix string written at the beginning of every
  357 + * line of trace output. If NULL, then tracing is
  358 + * turned off.
  359 + *
  360 + * Outputs:
  361 + *
  362 + * - None.
  363 + * @param resource
  364 + * @param string
  365 + */
  366 + static function Trace($TraceFILE, $zTracePrompt)
  367 + {
  368 + if (!$TraceFILE) {
  369 + $zTracePrompt = 0;
  370 + } elseif (!$zTracePrompt) {
  371 + $TraceFILE = 0;
  372 + }
  373 + self::$yyTraceFILE = $TraceFILE;
  374 + self::$yyTracePrompt = $zTracePrompt;
  375 + }
  376 +
  377 + /**
  378 + * Output debug information to output (php://output stream)
  379 + */
  380 + static function PrintTrace()
  381 + {
  382 + self::$yyTraceFILE = fopen('php://output', 'w');
  383 + self::$yyTracePrompt = '';
  384 + }
  385 +
  386 + /**
  387 + * @var resource|0
  388 + */
  389 + static public $yyTraceFILE;
  390 + /**
  391 + * String to prepend to debug output
  392 + * @var string|0
  393 + */
  394 + static public $yyTracePrompt;
  395 + /**
  396 + * @var int
  397 + */
  398 + public $yyidx; /* Index of top element in stack */
  399 + /**
  400 + * @var int
  401 + */
  402 + public $yyerrcnt; /* Shifts left before out of the error */
  403 + /**
  404 + * @var array
  405 + */
  406 + public $yystack = array(); /* The parser's stack */
  407 +
  408 + /**
  409 + * For tracing shifts, the names of all terminals and nonterminals
  410 + * are required. The following table supplies these names
  411 + * @var array
  412 + */
  413 + static public $yyTokenName = array(
  414 + '$', 'OPOR', 'OPAND', 'NOT',
  415 + 'IS', 'CONTAIN', 'LIKE', 'BETWEEN',
  416 + 'START', 'END', 'GT', 'LE',
  417 + 'LT', 'GE', 'PAR_OPEN', 'PAR_CLOSE',
  418 + 'DOES', 'COLON', 'SQUARE_OPEN', 'SQUARE_CLOSE',
  419 + 'TERMINAL', 'VALUE', 'COMMA', 'CONTAINS',
  420 + 'WITH', 'IS_NOT', 'error', 'expr',
  421 + 'cmdline', 'terminal', 'operator', 'value',
  422 + 'notop', 'valuelist',
  423 + );
  424 +
  425 + /**
  426 + * For tracing reduce actions, the names of all rules are required.
  427 + * @var array
  428 + */
  429 + static public $yyRuleName = array(
  430 + /* 0 */ "cmdline ::= expr",
  431 + /* 1 */ "expr ::= expr OPAND expr",
  432 + /* 2 */ "expr ::= expr OPOR expr",
  433 + /* 3 */ "expr ::= NOT expr",
  434 + /* 4 */ "expr ::= PAR_OPEN expr PAR_CLOSE",
  435 + /* 5 */ "expr ::= terminal operator value",
  436 + /* 6 */ "expr ::= terminal notop BETWEEN value OPAND value",
  437 + /* 7 */ "expr ::= terminal notop LIKE value",
  438 + /* 8 */ "expr ::= terminal IS notop value",
  439 + /* 9 */ "expr ::= terminal DOES notop CONTAIN value",
  440 + /* 10 */ "expr ::= terminal COLON value",
  441 + /* 11 */ "notop ::=",
  442 + /* 12 */ "notop ::= NOT",
  443 + /* 13 */ "terminal ::= SQUARE_OPEN value SQUARE_CLOSE SQUARE_OPEN value SQUARE_CLOSE",
  444 + /* 14 */ "terminal ::= TERMINAL",
  445 + /* 15 */ "value ::= VALUE",
  446 + /* 16 */ "value ::= PAR_OPEN valuelist PAR_CLOSE",
  447 + /* 17 */ "valuelist ::= VALUE COMMA valuelist",
  448 + /* 18 */ "valuelist ::= VALUE",
  449 + /* 19 */ "operator ::= CONTAINS",
  450 + /* 20 */ "operator ::= LT",
  451 + /* 21 */ "operator ::= GT",
  452 + /* 22 */ "operator ::= LE",
  453 + /* 23 */ "operator ::= GE",
  454 + /* 24 */ "operator ::= START WITH",
  455 + /* 25 */ "operator ::= END WITH",
  456 + /* 26 */ "operator ::= IS_NOT",
  457 + );
  458 +
  459 + /**
  460 + * This function returns the symbolic name associated with a token
  461 + * value.
  462 + * @param int
  463 + * @return string
  464 + */
  465 + function tokenName($tokenType)
  466 + {
  467 + if ($tokenType === 0) {
  468 + return 'End of Input';
  469 + }
  470 + if ($tokenType > 0 && $tokenType < count(self::$yyTokenName)) {
  471 + return self::$yyTokenName[$tokenType];
  472 + } else {
  473 + return "Unknown";
  474 + }
  475 + }
  476 +
  477 + /**
  478 + * The following function deletes the value associated with a
  479 + * symbol. The symbol can be either a terminal or nonterminal.
  480 + * @param int the symbol code
  481 + * @param mixed the symbol's value
  482 + */
  483 + static function yy_destructor($yymajor, $yypminor)
  484 + {
  485 + switch ($yymajor) {
  486 + /* Here is inserted the actions which take place when a
  487 + ** terminal or non-terminal is destroyed. This can happen
  488 + ** when the symbol is popped from the stack during a
  489 + ** reduce or during error processing or when a parser is
  490 + ** being destroyed before it is finished parsing.
  491 + **
  492 + ** Note: during a reduce, the only symbols destroyed are those
  493 + ** which appear on the RHS of the rule, but which are not used
  494 + ** inside the C code.
  495 + */
  496 + default: break; /* If no destructor action specified: do nothing */
  497 + }
  498 + }
  499 +
  500 + /**
  501 + * Pop the parser's stack once.
  502 + *
  503 + * If there is a destructor routine associated with the token which
  504 + * is popped from the stack, then call it.
  505 + *
  506 + * Return the major token number for the symbol popped.
  507 + * @param SearchCommandParseryyParser
  508 + * @return int
  509 + */
  510 + function yy_pop_parser_stack()
  511 + {
  512 + if (!count($this->yystack)) {
  513 + return;
  514 + }
  515 + $yytos = array_pop($this->yystack);
  516 + if (self::$yyTraceFILE && $this->yyidx >= 0) {
  517 + fwrite(self::$yyTraceFILE,
  518 + self::$yyTracePrompt . 'Popping ' . self::$yyTokenName[$yytos->major] .
  519 + "\n");
  520 + }
  521 + $yymajor = $yytos->major;
  522 + self::yy_destructor($yymajor, $yytos->minor);
  523 + $this->yyidx--;
  524 + return $yymajor;
  525 + }
  526 +
  527 + /**
  528 + * Deallocate and destroy a parser. Destructors are all called for
  529 + * all stack elements before shutting the parser down.
  530 + */
  531 + function __destruct()
  532 + {
  533 + while ($this->yyidx >= 0) {
  534 + $this->yy_pop_parser_stack();
  535 + }
  536 + if (is_resource(self::$yyTraceFILE)) {
  537 + fclose(self::$yyTraceFILE);
  538 + }
  539 + }
  540 +
  541 + /**
  542 + * Based on the current state and parser stack, get a list of all
  543 + * possible lookahead tokens
  544 + * @param int
  545 + * @return array
  546 + */
  547 + function yy_get_expected_tokens($token)
  548 + {
  549 + $state = $this->yystack[$this->yyidx]->stateno;
  550 + $expected = self::$yyExpectedTokens[$state];
  551 + if (in_array($token, self::$yyExpectedTokens[$state], true)) {
  552 + return $expected;
  553 + }
  554 + $stack = $this->yystack;
  555 + $yyidx = $this->yyidx;
  556 + do {
  557 + $yyact = $this->yy_find_shift_action($token);
  558 + if ($yyact >= self::YYNSTATE && $yyact < self::YYNSTATE + self::YYNRULE) {
  559 + // reduce action
  560 + $done = 0;
  561 + do {
  562 + if ($done++ == 100) {
  563 + $this->yyidx = $yyidx;
  564 + $this->yystack = $stack;
  565 + // too much recursion prevents proper detection
  566 + // so give up
  567 + return array_unique($expected);
  568 + }
  569 + $yyruleno = $yyact - self::YYNSTATE;
  570 + $this->yyidx -= self::$yyRuleInfo[$yyruleno]['rhs'];
  571 + $nextstate = $this->yy_find_reduce_action(
  572 + $this->yystack[$this->yyidx]->stateno,
  573 + self::$yyRuleInfo[$yyruleno]['lhs']);
  574 + if (isset(self::$yyExpectedTokens[$nextstate])) {
  575 + $expected += self::$yyExpectedTokens[$nextstate];
  576 + if (in_array($token,
  577 + self::$yyExpectedTokens[$nextstate], true)) {
  578 + $this->yyidx = $yyidx;
  579 + $this->yystack = $stack;
  580 + return array_unique($expected);
  581 + }
  582 + }
  583 + if ($nextstate < self::YYNSTATE) {
  584 + // we need to shift a non-terminal
  585 + $this->yyidx++;
  586 + $x = new SearchCommandParseryyStackEntry;
  587 + $x->stateno = $nextstate;
  588 + $x->major = self::$yyRuleInfo[$yyruleno]['lhs'];
  589 + $this->yystack[$this->yyidx] = $x;
  590 + continue 2;
  591 + } elseif ($nextstate == self::YYNSTATE + self::YYNRULE + 1) {
  592 + $this->yyidx = $yyidx;
  593 + $this->yystack = $stack;
  594 + // the last token was just ignored, we can't accept
  595 + // by ignoring input, this is in essence ignoring a
  596 + // syntax error!
  597 + return array_unique($expected);
  598 + } elseif ($nextstate === self::YY_NO_ACTION) {
  599 + $this->yyidx = $yyidx;
  600 + $this->yystack = $stack;
  601 + // input accepted, but not shifted (I guess)
  602 + return $expected;
  603 + } else {
  604 + $yyact = $nextstate;
  605 + }
  606 + } while (true);
  607 + }
  608 + break;
  609 + } while (true);
  610 + return array_unique($expected);
  611 + }
  612 +
  613 + /**
  614 + * Based on the parser state and current parser stack, determine whether
  615 + * the lookahead token is possible.
  616 + *
  617 + * The parser will convert the token value to an error token if not. This
  618 + * catches some unusual edge cases where the parser would fail.
  619 + * @param int
  620 + * @return bool
  621 + */
  622 + function yy_is_expected_token($token)
  623 + {
  624 + if ($token === 0) {
  625 + return true; // 0 is not part of this
  626 + }
  627 + $state = $this->yystack[$this->yyidx]->stateno;
  628 + if (in_array($token, self::$yyExpectedTokens[$state], true)) {
  629 + return true;
  630 + }
  631 + $stack = $this->yystack;
  632 + $yyidx = $this->yyidx;
  633 + do {
  634 + $yyact = $this->yy_find_shift_action($token);
  635 + if ($yyact >= self::YYNSTATE && $yyact < self::YYNSTATE + self::YYNRULE) {
  636 + // reduce action
  637 + $done = 0;
  638 + do {
  639 + if ($done++ == 100) {
  640 + $this->yyidx = $yyidx;
  641 + $this->yystack = $stack;
  642 + // too much recursion prevents proper detection
  643 + // so give up
  644 + return true;
  645 + }
  646 + $yyruleno = $yyact - self::YYNSTATE;
  647 + $this->yyidx -= self::$yyRuleInfo[$yyruleno]['rhs'];
  648 + $nextstate = $this->yy_find_reduce_action(
  649 + $this->yystack[$this->yyidx]->stateno,
  650 + self::$yyRuleInfo[$yyruleno]['lhs']);
  651 + if (isset(self::$yyExpectedTokens[$nextstate]) &&
  652 + in_array($token, self::$yyExpectedTokens[$nextstate], true)) {
  653 + $this->yyidx = $yyidx;
  654 + $this->yystack = $stack;
  655 + return true;
  656 + }
  657 + if ($nextstate < self::YYNSTATE) {
  658 + // we need to shift a non-terminal
  659 + $this->yyidx++;
  660 + $x = new SearchCommandParseryyStackEntry;
  661 + $x->stateno = $nextstate;
  662 + $x->major = self::$yyRuleInfo[$yyruleno]['lhs'];
  663 + $this->yystack[$this->yyidx] = $x;
  664 + continue 2;
  665 + } elseif ($nextstate == self::YYNSTATE + self::YYNRULE + 1) {
  666 + $this->yyidx = $yyidx;
  667 + $this->yystack = $stack;
  668 + if (!$token) {
  669 + // end of input: this is valid
  670 + return true;
  671 + }
  672 + // the last token was just ignored, we can't accept
  673 + // by ignoring input, this is in essence ignoring a
  674 + // syntax error!
  675 + return false;
  676 + } elseif ($nextstate === self::YY_NO_ACTION) {
  677 + $this->yyidx = $yyidx;
  678 + $this->yystack = $stack;
  679 + // input accepted, but not shifted (I guess)
  680 + return true;
  681 + } else {
  682 + $yyact = $nextstate;
  683 + }
  684 + } while (true);
  685 + }
  686 + break;
  687 + } while (true);
  688 + $this->yyidx = $yyidx;
  689 + $this->yystack = $stack;
  690 + return true;
  691 + }
  692 +
  693 + /**
  694 + * Find the appropriate action for a parser given the terminal
  695 + * look-ahead token iLookAhead.
  696 + *
  697 + * If the look-ahead token is YYNOCODE, then check to see if the action is
  698 + * independent of the look-ahead. If it is, return the action, otherwise
  699 + * return YY_NO_ACTION.
  700 + * @param int The look-ahead token
  701 + */
  702 + function yy_find_shift_action($iLookAhead)
  703 + {
  704 + $stateno = $this->yystack[$this->yyidx]->stateno;
  705 +
  706 + /* if ($this->yyidx < 0) return self::YY_NO_ACTION; */
  707 + if (!isset(self::$yy_shift_ofst[$stateno])) {
  708 + // no shift actions
  709 + return self::$yy_default[$stateno];
  710 + }
  711 + $i = self::$yy_shift_ofst[$stateno];
  712 + if ($i === self::YY_SHIFT_USE_DFLT) {
  713 + return self::$yy_default[$stateno];
  714 + }
  715 + if ($iLookAhead == self::YYNOCODE) {
  716 + return self::YY_NO_ACTION;
  717 + }
  718 + $i += $iLookAhead;
  719 + if ($i < 0 || $i >= self::YY_SZ_ACTTAB ||
  720 + self::$yy_lookahead[$i] != $iLookAhead) {
  721 + if (count(self::$yyFallback) && $iLookAhead < count(self::$yyFallback)
  722 + && ($iFallback = self::$yyFallback[$iLookAhead]) != 0) {
  723 + if (self::$yyTraceFILE) {
  724 + fwrite(self::$yyTraceFILE, self::$yyTracePrompt . "FALLBACK " .
  725 + self::$yyTokenName[$iLookAhead] . " => " .
  726 + self::$yyTokenName[$iFallback] . "\n");
  727 + }
  728 + return $this->yy_find_shift_action($iFallback);
  729 + }
  730 + return self::$yy_default[$stateno];
  731 + } else {
  732 + return self::$yy_action[$i];
  733 + }
  734 + }
  735 +
  736 + /**
  737 + * Find the appropriate action for a parser given the non-terminal
  738 + * look-ahead token $iLookAhead.
  739 + *
  740 + * If the look-ahead token is self::YYNOCODE, then check to see if the action is
  741 + * independent of the look-ahead. If it is, return the action, otherwise
  742 + * return self::YY_NO_ACTION.
  743 + * @param int Current state number
  744 + * @param int The look-ahead token
  745 + */
  746 + function yy_find_reduce_action($stateno, $iLookAhead)
  747 + {
  748 + /* $stateno = $this->yystack[$this->yyidx]->stateno; */
  749 +
  750 + if (!isset(self::$yy_reduce_ofst[$stateno])) {
  751 + return self::$yy_default[$stateno];
  752 + }
  753 + $i = self::$yy_reduce_ofst[$stateno];
  754 + if ($i == self::YY_REDUCE_USE_DFLT) {
  755 + return self::$yy_default[$stateno];
  756 + }
  757 + if ($iLookAhead == self::YYNOCODE) {
  758 + return self::YY_NO_ACTION;
  759 + }
  760 + $i += $iLookAhead;
  761 + if ($i < 0 || $i >= self::YY_SZ_ACTTAB ||
  762 + self::$yy_lookahead[$i] != $iLookAhead) {
  763 + return self::$yy_default[$stateno];
  764 + } else {
  765 + return self::$yy_action[$i];
  766 + }
  767 + }
  768 +
  769 + /**
  770 + * Perform a shift action.
  771 + * @param int The new state to shift in
  772 + * @param int The major token to shift in
  773 + * @param mixed the minor token to shift in
  774 + */
  775 + function yy_shift($yyNewState, $yyMajor, $yypMinor)
  776 + {
  777 + $this->yyidx++;
  778 + if ($this->yyidx >= self::YYSTACKDEPTH) {
  779 + $this->yyidx--;
  780 + if (self::$yyTraceFILE) {
  781 + fprintf(self::$yyTraceFILE, "%sStack Overflow!\n", self::$yyTracePrompt);
  782 + }
  783 + while ($this->yyidx >= 0) {
  784 + $this->yy_pop_parser_stack();
  785 + }
  786 + /* Here code is inserted which will execute if the parser
  787 + ** stack ever overflows */
  788 + return;
  789 + }
  790 + $yytos = new SearchCommandParseryyStackEntry;
  791 + $yytos->stateno = $yyNewState;
  792 + $yytos->major = $yyMajor;
  793 + $yytos->minor = $yypMinor;
  794 + array_push($this->yystack, $yytos);
  795 + if (self::$yyTraceFILE && $this->yyidx > 0) {
  796 + fprintf(self::$yyTraceFILE, "%sShift %d\n", self::$yyTracePrompt,
  797 + $yyNewState);
  798 + fprintf(self::$yyTraceFILE, "%sStack:", self::$yyTracePrompt);
  799 + for($i = 1; $i <= $this->yyidx; $i++) {
  800 + fprintf(self::$yyTraceFILE, " %s",
  801 + self::$yyTokenName[$this->yystack[$i]->major]);
  802 + }
  803 + fwrite(self::$yyTraceFILE,"\n");
  804 + }
  805 + }
  806 +
  807 + /**
  808 + * The following table contains information about every rule that
  809 + * is used during the reduce.
  810 + *
  811 + * <pre>
  812 + * array(
  813 + * array(
  814 + * int $lhs; Symbol on the left-hand side of the rule
  815 + * int $nrhs; Number of right-hand side symbols in the rule
  816 + * ),...
  817 + * );
  818 + * </pre>
  819 + */
  820 + static public $yyRuleInfo = array(
  821 + array( 'lhs' => 28, 'rhs' => 1 ),
  822 + array( 'lhs' => 27, 'rhs' => 3 ),
  823 + array( 'lhs' => 27, 'rhs' => 3 ),
  824 + array( 'lhs' => 27, 'rhs' => 2 ),
  825 + array( 'lhs' => 27, 'rhs' => 3 ),
  826 + array( 'lhs' => 27, 'rhs' => 3 ),
  827 + array( 'lhs' => 27, 'rhs' => 6 ),
  828 + array( 'lhs' => 27, 'rhs' => 4 ),
  829 + array( 'lhs' => 27, 'rhs' => 4 ),
  830 + array( 'lhs' => 27, 'rhs' => 5 ),
  831 + array( 'lhs' => 27, 'rhs' => 3 ),
  832 + array( 'lhs' => 32, 'rhs' => 0 ),
  833 + array( 'lhs' => 32, 'rhs' => 1 ),
  834 + array( 'lhs' => 29, 'rhs' => 6 ),
  835 + array( 'lhs' => 29, 'rhs' => 1 ),
  836 + array( 'lhs' => 31, 'rhs' => 1 ),
  837 + array( 'lhs' => 31, 'rhs' => 3 ),
  838 + array( 'lhs' => 33, 'rhs' => 3 ),
  839 + array( 'lhs' => 33, 'rhs' => 1 ),
  840 + array( 'lhs' => 30, 'rhs' => 1 ),
  841 + array( 'lhs' => 30, 'rhs' => 1 ),
  842 + array( 'lhs' => 30, 'rhs' => 1 ),
  843 + array( 'lhs' => 30, 'rhs' => 1 ),
  844 + array( 'lhs' => 30, 'rhs' => 1 ),
  845 + array( 'lhs' => 30, 'rhs' => 2 ),
  846 + array( 'lhs' => 30, 'rhs' => 2 ),
  847 + array( 'lhs' => 30, 'rhs' => 1 ),
  848 + );
  849 +
  850 + /**
  851 + * The following table contains a mapping of reduce action to method name
  852 + * that handles the reduction.
  853 + *
  854 + * If a rule is not set, it has no handler.
  855 + */
  856 + static public $yyReduceMap = array(
  857 + 0 => 0,
  858 + 1 => 1,
  859 + 2 => 2,
  860 + 3 => 3,
  861 + 4 => 4,
  862 + 16 => 4,
  863 + 5 => 5,
  864 + 6 => 6,
  865 + 7 => 7,
  866 + 8 => 8,
  867 + 9 => 9,
  868 + 10 => 10,
  869 + 11 => 11,
  870 + 12 => 12,
  871 + 13 => 13,
  872 + 14 => 14,
  873 + 15 => 15,
  874 + 17 => 17,
  875 + 18 => 18,
  876 + 19 => 19,
  877 + 20 => 20,
  878 + 21 => 21,
  879 + 22 => 22,
  880 + 23 => 23,
  881 + 24 => 24,
  882 + 25 => 25,
  883 + 26 => 26,
  884 + );
  885 + /* Beginning here are the reduction cases. A typical example
  886 + ** follows:
  887 + ** #line <lineno> <grammarfile>
  888 + ** function yy_r0($yymsp){ ... } // User supplied code
  889 + ** #line <lineno> <thisfile>
  890 + */
  891 +#line 53 "SearchCommandParser.y"
  892 + function yy_r0(){
  893 + $this->expr_result = $this->yystack[$this->yyidx + 0]->minor;
  894 + }
  895 +#line 900 "SearchCommandParser.php"
  896 +#line 58 "SearchCommandParser.y"
  897 + function yy_r1(){
  898 + $this->_retvalue = new OpExpr($this->yystack[$this->yyidx + -2]->minor, ExprOp::OP_AND, $this->yystack[$this->yyidx + 0]->minor);
  899 + }
  900 +#line 905 "SearchCommandParser.php"
  901 +#line 63 "SearchCommandParser.y"
  902 + function yy_r2(){
  903 + $this->_retvalue = new OpExpr($this->yystack[$this->yyidx + -2]->minor, ExprOp::OP_OR, $this->yystack[$this->yyidx + 0]->minor);
  904 + }
  905 +#line 910 "SearchCommandParser.php"
  906 +#line 68 "SearchCommandParser.y"
  907 + function yy_r3(){
  908 + $expr = $this->yystack[$this->yyidx + 0]->minor;
  909 + $expr->not(!$expr->not());
  910 + $this->_retvalue = $expr;
  911 + }
  912 +#line 917 "SearchCommandParser.php"
  913 +#line 75 "SearchCommandParser.y"
  914 + function yy_r4(){
  915 + $this->_retvalue = $this->yystack[$this->yyidx + -1]->minor;
  916 + }
  917 +#line 922 "SearchCommandParser.php"
  918 +#line 80 "SearchCommandParser.y"
  919 + function yy_r5(){
  920 + $op = $this->yystack[$this->yyidx + -1]->minor;
  921 + $not = false;
  922 + if ($op == ExprOp::IS_NOT)
  923 + {
  924 + $op = ExprOp::IS;
  925 + $not = true;
  926 + }
  927 +
  928 + $fld = new OpExpr($this->yystack[$this->yyidx + -2]->minor, $op, $this->yystack[$this->yyidx + 0]->minor);
  929 + $fld->not($not);
  930 + $this->_retvalue = $fld;
  931 + }
  932 +#line 937 "SearchCommandParser.php"
  933 +#line 95 "SearchCommandParser.y"
  934 + function yy_r6(){
  935 + $expr = new OpExpr($this->yystack[$this->yyidx + -5]->minor, ExprOp::BETWEEN, new BetweenValueExpr($this->yystack[$this->yyidx + -2]->minor, $this->yystack[$this->yyidx + 0]->minor));
  936 + $expr->not($this->yystack[$this->yyidx + -4]->minor);
  937 + $this->_retvalue=$expr;
  938 + }
  939 +#line 944 "SearchCommandParser.php"
  940 +#line 102 "SearchCommandParser.y"
  941 + function yy_r7(){
  942 + $expr = new OpExpr($this->yystack[$this->yyidx + -3]->minor, ExprOp::LIKE, $this->yystack[$this->yyidx + 0]->minor);
  943 + $expr->not($this->yystack[$this->yyidx + -2]->minor);
  944 + $this->_retvalue=$expr;
  945 + }
  946 +#line 951 "SearchCommandParser.php"
  947 +#line 109 "SearchCommandParser.y"
  948 + function yy_r8(){
  949 + $expr = new OpExpr($this->yystack[$this->yyidx + -3]->minor, ExprOp::IS, $this->yystack[$this->yyidx + 0]->minor);
  950 + $expr->not($this->yystack[$this->yyidx + -1]->minor);
  951 + $this->_retvalue=$expr;
  952 + }
  953 +#line 958 "SearchCommandParser.php"
  954 +#line 116 "SearchCommandParser.y"
  955 + function yy_r9(){
  956 + $expr = new OpExpr($this->yystack[$this->yyidx + -4]->minor, ExprOp::CONTAINS, $this->yystack[$this->yyidx + 0]->minor);
  957 + $expr->not($this->yystack[$this->yyidx + -2]->minor);
  958 + $this->_retvalue=$expr;
  959 + }
  960 +#line 965 "SearchCommandParser.php"
  961 +#line 123 "SearchCommandParser.y"
  962 + function yy_r10(){
  963 + $this->_retvalue = new OpExpr($this->yystack[$this->yyidx + -2]->minor, ExprOp::CONTAINS, $this->yystack[$this->yyidx + 0]->minor);
  964 + }
  965 +#line 970 "SearchCommandParser.php"
  966 +#line 129 "SearchCommandParser.y"
  967 + function yy_r11(){
  968 + $this->_retvalue = false;
  969 + }
  970 +#line 975 "SearchCommandParser.php"
  971 +#line 134 "SearchCommandParser.y"
  972 + function yy_r12(){
  973 + $this->_retvalue = true;
  974 + }
  975 +#line 980 "SearchCommandParser.php"
  976 +#line 139 "SearchCommandParser.y"
  977 + function yy_r13(){
  978 + $registry = ExprFieldRegistry::getRegistry();
  979 + $field = $registry->resolveMetadataField($this->yystack[$this->yyidx + -4]->minor, $this->yystack[$this->yyidx + -1]->minor);
  980 + $this->_retvalue = $field;
  981 + }
  982 +#line 987 "SearchCommandParser.php"
  983 +#line 146 "SearchCommandParser.y"
  984 + function yy_r14(){
  985 + $registry = ExprFieldRegistry::getRegistry();
  986 + $field=$registry->resolveAlias($this->yystack[$this->yyidx + 0]->minor);
  987 + $this->_retvalue = $field;
  988 + }
  989 +#line 994 "SearchCommandParser.php"
  990 +#line 153 "SearchCommandParser.y"
  991 + function yy_r15(){
  992 + $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor;
  993 + }
  994 +#line 999 "SearchCommandParser.php"
  995 +#line 163 "SearchCommandParser.y"
  996 + function yy_r17(){
  997 + $this->yystack[$this->yyidx + 0]->minor->addValue($this->yystack[$this->yyidx + -2]->minor);
  998 + $this->_retvalue = $this->yystack[$this->yyidx + 0]->minor;
  999 + }
  1000 +#line 1005 "SearchCommandParser.php"
  1001 +#line 169 "SearchCommandParser.y"
  1002 + function yy_r18(){
  1003 + $this->_retvalue = new ValueListExpr($this->yystack[$this->yyidx + 0]->minor);
  1004 + }
  1005 +#line 1010 "SearchCommandParser.php"
  1006 +#line 174 "SearchCommandParser.y"
  1007 + function yy_r19(){
  1008 + $this->_retvalue = ExprOp::CONTAINS;
  1009 + }
  1010 +#line 1015 "SearchCommandParser.php"
  1011 +#line 179 "SearchCommandParser.y"
  1012 + function yy_r20(){
  1013 + $this->_retvalue = ExprOp::LESS_THAN;
  1014 + }
  1015 +#line 1020 "SearchCommandParser.php"
  1016 +#line 184 "SearchCommandParser.y"
  1017 + function yy_r21(){
  1018 + $this->_retvalue = ExprOp::GREATER_THAN;
  1019 + }
  1020 +#line 1025 "SearchCommandParser.php"
  1021 +#line 189 "SearchCommandParser.y"
  1022 + function yy_r22(){
  1023 + $this->_retvalue = ExprOp::LESS_THAN_EQUAL;
  1024 + }
  1025 +#line 1030 "SearchCommandParser.php"
  1026 +#line 194 "SearchCommandParser.y"
  1027 + function yy_r23(){
  1028 + $this->_retvalue = ExprOp::GREATER_THAN_EQUAL;
  1029 + }
  1030 +#line 1035 "SearchCommandParser.php"
  1031 +#line 199 "SearchCommandParser.y"
  1032 + function yy_r24(){
  1033 + $this->_retvalue = ExprOp::STARTS_WITH;
  1034 + }
  1035 +#line 1040 "SearchCommandParser.php"
  1036 +#line 204 "SearchCommandParser.y"
  1037 + function yy_r25(){
  1038 + $this->_retvalue = ExprOp::ENDS_WITH;
  1039 + }
  1040 +#line 1045 "SearchCommandParser.php"
  1041 +#line 209 "SearchCommandParser.y"
  1042 + function yy_r26(){
  1043 + $this->_retvalue = ExprOp::IS_NOT;
  1044 + }
  1045 +#line 1050 "SearchCommandParser.php"
  1046 +
  1047 + /**
  1048 + * placeholder for the left hand side in a reduce operation.
  1049 + *
  1050 + * For a parser with a rule like this:
  1051 + * <pre>
  1052 + * rule(A) ::= B. { A = 1; }
  1053 + * </pre>
  1054 + *
  1055 + * The parser will translate to something like:
  1056 + *
  1057 + * <code>
  1058 + * function yy_r0(){$this->_retvalue = 1;}
  1059 + * </code>
  1060 + */
  1061 + private $_retvalue;
  1062 +
  1063 + /**
  1064 + * Perform a reduce action and the shift that must immediately
  1065 + * follow the reduce.
  1066 + *
  1067 + * For a rule such as:
  1068 + *
  1069 + * <pre>
  1070 + * A ::= B blah C. { dosomething(); }
  1071 + * </pre>
  1072 + *
  1073 + * This function will first call the action, if any, ("dosomething();" in our
  1074 + * example), and then it will pop three states from the stack,
  1075 + * one for each entry on the right-hand side of the expression
  1076 + * (B, blah, and C in our example rule), and then push the result of the action
  1077 + * back on to the stack with the resulting state reduced to (as described in the .out
  1078 + * file)
  1079 + * @param int Number of the rule by which to reduce
  1080 + */
  1081 + function yy_reduce($yyruleno)
  1082 + {
  1083 + //int $yygoto; /* The next state */
  1084 + //int $yyact; /* The next action */
  1085 + //mixed $yygotominor; /* The LHS of the rule reduced */
  1086 + //SearchCommandParseryyStackEntry $yymsp; /* The top of the parser's stack */
  1087 + //int $yysize; /* Amount to pop the stack */
  1088 + $yymsp = $this->yystack[$this->yyidx];
  1089 + if (self::$yyTraceFILE && $yyruleno >= 0
  1090 + && $yyruleno < count(self::$yyRuleName)) {
  1091 + fprintf(self::$yyTraceFILE, "%sReduce (%d) [%s].\n",
  1092 + self::$yyTracePrompt, $yyruleno,
  1093 + self::$yyRuleName[$yyruleno]);
  1094 + }
  1095 +
  1096 + $this->_retvalue = $yy_lefthand_side = null;
  1097 + if (array_key_exists($yyruleno, self::$yyReduceMap)) {
  1098 + // call the action
  1099 + $this->_retvalue = null;
  1100 + $this->{'yy_r' . self::$yyReduceMap[$yyruleno]}();
  1101 + $yy_lefthand_side = $this->_retvalue;
  1102 + }
  1103 + $yygoto = self::$yyRuleInfo[$yyruleno]['lhs'];
  1104 + $yysize = self::$yyRuleInfo[$yyruleno]['rhs'];
  1105 + $this->yyidx -= $yysize;
  1106 + for($i = $yysize; $i; $i--) {
  1107 + // pop all of the right-hand side parameters
  1108 + array_pop($this->yystack);
  1109 + }
  1110 + $yyact = $this->yy_find_reduce_action($this->yystack[$this->yyidx]->stateno, $yygoto);
  1111 + if ($yyact < self::YYNSTATE) {
  1112 + /* If we are not debugging and the reduce action popped at least
  1113 + ** one element off the stack, then we can push the new element back
  1114 + ** onto the stack here, and skip the stack overflow test in yy_shift().
  1115 + ** That gives a significant speed improvement. */
  1116 + if (!self::$yyTraceFILE && $yysize) {
  1117 + $this->yyidx++;
  1118 + $x = new SearchCommandParseryyStackEntry;
  1119 + $x->stateno = $yyact;
  1120 + $x->major = $yygoto;
  1121 + $x->minor = $yy_lefthand_side;
  1122 + $this->yystack[$this->yyidx] = $x;
  1123 + } else {
  1124 + $this->yy_shift($yyact, $yygoto, $yy_lefthand_side);
  1125 + }
  1126 + } elseif ($yyact == self::YYNSTATE + self::YYNRULE + 1) {
  1127 + $this->yy_accept();
  1128 + }
  1129 + }
  1130 +
  1131 + /**
  1132 + * The following code executes when the parse fails
  1133 + *
  1134 + * Code from %parse_fail is inserted here
  1135 + */
  1136 + function yy_parse_failed()
  1137 + {
  1138 + if (self::$yyTraceFILE) {
  1139 + fprintf(self::$yyTraceFILE, "%sFail!\n", self::$yyTracePrompt);
  1140 + }
  1141 + while ($this->yyidx >= 0) {
  1142 + $this->yy_pop_parser_stack();
  1143 + }
  1144 + /* Here code is inserted which will be executed whenever the
  1145 + ** parser fails */
  1146 +#line 46 "SearchCommandParser.y"
  1147 +
  1148 + $this->parse_result = 'syntax';
  1149 +#line 1155 "SearchCommandParser.php"
  1150 + }
  1151 +
  1152 + /**
  1153 + * The following code executes when a syntax error first occurs.
  1154 + *
  1155 + * %syntax_error code is inserted here
  1156 + * @param int The major type of the error token
  1157 + * @param mixed The minor type of the error token
  1158 + */
  1159 + function yy_syntax_error($yymajor, $TOKEN)
  1160 + {
  1161 +#line 35 "SearchCommandParser.y"
  1162 +
  1163 + $this->parse_result = 'syntax';
  1164 + $this->parse_message = "";
  1165 +#line 1172 "SearchCommandParser.php"
  1166 + }
  1167 +
  1168 + /**
  1169 + * The following is executed when the parser accepts
  1170 + *
  1171 + * %parse_accept code is inserted here
  1172 + */
  1173 + function yy_accept()
  1174 + {
  1175 + if (self::$yyTraceFILE) {
  1176 + fprintf(self::$yyTraceFILE, "%sAccept!\n", self::$yyTracePrompt);
  1177 + }
  1178 + while ($this->yyidx >= 0) {
  1179 + $stack = $this->yy_pop_parser_stack();
  1180 + }
  1181 + /* Here code is inserted which will be executed whenever the
  1182 + ** parser accepts */
  1183 +#line 41 "SearchCommandParser.y"
  1184 +
  1185 + $this->parse_result = 'ok';
  1186 +#line 1194 "SearchCommandParser.php"
  1187 + }
  1188 +
  1189 + /**
  1190 + * The main parser program.
  1191 + *
  1192 + * The first argument is the major token number. The second is
  1193 + * the token value string as scanned from the input.
  1194 + *
  1195 + * @param int the token number
  1196 + * @param mixed the token value
  1197 + * @param mixed any extra arguments that should be passed to handlers
  1198 + */
  1199 + function doParse($yymajor, $yytokenvalue)
  1200 + {
  1201 +// $yyact; /* The parser action. */
  1202 +// $yyendofinput; /* True if we are at the end of input */
  1203 + $yyerrorhit = 0; /* True if yymajor has invoked an error */
  1204 +
  1205 + /* (re)initialize the parser, if necessary */
  1206 + if ($this->yyidx === null || $this->yyidx < 0) {
  1207 + /* if ($yymajor == 0) return; // not sure why this was here... */
  1208 + $this->yyidx = 0;
  1209 + $this->yyerrcnt = -1;
  1210 + $x = new SearchCommandParseryyStackEntry;
  1211 + $x->stateno = 0;
  1212 + $x->major = 0;
  1213 + $this->yystack = array();
  1214 + array_push($this->yystack, $x);
  1215 + }
  1216 + $yyendofinput = ($yymajor==0);
  1217 +
  1218 + if (self::$yyTraceFILE) {
  1219 + fprintf(self::$yyTraceFILE, "%sInput %s\n",
  1220 + self::$yyTracePrompt, self::$yyTokenName[$yymajor]);
  1221 + }
  1222 +
  1223 + do {
  1224 + $yyact = $this->yy_find_shift_action($yymajor);
  1225 + if ($yymajor < self::YYERRORSYMBOL &&
  1226 + !$this->yy_is_expected_token($yymajor)) {
  1227 + // force a syntax error
  1228 + $yyact = self::YY_ERROR_ACTION;
  1229 + }
  1230 + if ($yyact < self::YYNSTATE) {
  1231 + $this->yy_shift($yyact, $yymajor, $yytokenvalue);
  1232 + $this->yyerrcnt--;
  1233 + if ($yyendofinput && $this->yyidx >= 0) {
  1234 + $yymajor = 0;
  1235 + } else {
  1236 + $yymajor = self::YYNOCODE;
  1237 + }
  1238 + } elseif ($yyact < self::YYNSTATE + self::YYNRULE) {
  1239 + $this->yy_reduce($yyact - self::YYNSTATE);
  1240 + } elseif ($yyact == self::YY_ERROR_ACTION) {
  1241 + if (self::$yyTraceFILE) {
  1242 + fprintf(self::$yyTraceFILE, "%sSyntax Error!\n",
  1243 + self::$yyTracePrompt);
  1244 + }
  1245 + if (self::YYERRORSYMBOL) {
  1246 + /* A syntax error has occurred.
  1247 + ** The response to an error depends upon whether or not the
  1248 + ** grammar defines an error token "ERROR".
  1249 + **
  1250 + ** This is what we do if the grammar does define ERROR:
  1251 + **
  1252 + ** * Call the %syntax_error function.
  1253 + **
  1254 + ** * Begin popping the stack until we enter a state where
  1255 + ** it is legal to shift the error symbol, then shift
  1256 + ** the error symbol.
  1257 + **
  1258 + ** * Set the error count to three.
  1259 + **
  1260 + ** * Begin accepting and shifting new tokens. No new error
  1261 + ** processing will occur until three tokens have been
  1262 + ** shifted successfully.
  1263 + **
  1264 + */
  1265 + if ($this->yyerrcnt < 0) {
  1266 + $this->yy_syntax_error($yymajor, $yytokenvalue);
  1267 + }
  1268 + $yymx = $this->yystack[$this->yyidx]->major;
  1269 + if ($yymx == self::YYERRORSYMBOL || $yyerrorhit ){
  1270 + if (self::$yyTraceFILE) {
  1271 + fprintf(self::$yyTraceFILE, "%sDiscard input token %s\n",
  1272 + self::$yyTracePrompt, self::$yyTokenName[$yymajor]);
  1273 + }
  1274 + $this->yy_destructor($yymajor, $yytokenvalue);
  1275 + $yymajor = self::YYNOCODE;
  1276 + } else {
  1277 + while ($this->yyidx >= 0 &&
  1278 + $yymx != self::YYERRORSYMBOL &&
  1279 + ($yyact = $this->yy_find_shift_action(self::YYERRORSYMBOL)) >= self::YYNSTATE
  1280 + ){
  1281 + $this->yy_pop_parser_stack();
  1282 + }
  1283 + if ($this->yyidx < 0 || $yymajor==0) {
  1284 + $this->yy_destructor($yymajor, $yytokenvalue);
  1285 + $this->yy_parse_failed();
  1286 + $yymajor = self::YYNOCODE;
  1287 + } elseif ($yymx != self::YYERRORSYMBOL) {
  1288 + $u2 = 0;
  1289 + $this->yy_shift($yyact, self::YYERRORSYMBOL, $u2);
  1290 + }
  1291 + }
  1292 + $this->yyerrcnt = 3;
  1293 + $yyerrorhit = 1;
  1294 + } else {
  1295 + /* YYERRORSYMBOL is not defined */
  1296 + /* This is what we do if the grammar does not define ERROR:
  1297 + **
  1298 + ** * Report an error message, and throw away the input token.
  1299 + **
  1300 + ** * If the input token is $, then fail the parse.
  1301 + **
  1302 + ** As before, subsequent error messages are suppressed until
  1303 + ** three input tokens have been successfully shifted.
  1304 + */
  1305 + if ($this->yyerrcnt <= 0) {
  1306 + $this->yy_syntax_error($yymajor, $yytokenvalue);
  1307 + }
  1308 + $this->yyerrcnt = 3;
  1309 + $this->yy_destructor($yymajor, $yytokenvalue);
  1310 + if ($yyendofinput) {
  1311 + $this->yy_parse_failed();
  1312 + }
  1313 + $yymajor = self::YYNOCODE;
  1314 + }
  1315 + } else {
  1316 + $this->yy_accept();
  1317 + $yymajor = self::YYNOCODE;
  1318 + }
  1319 + } while ($yymajor != self::YYNOCODE && $this->yyidx >= 0);
  1320 + }
  1321 +}
0 1322 \ No newline at end of file
... ...
search2/search/SearchCommandParser.y 0 → 100755
  1 +%name SearchCommandParser
  2 +%declare_class {class SearchCommandParser}
  3 +
  4 +%include_class {
  5 +
  6 + private $expr_result;
  7 + private $parse_result;
  8 +
  9 + public function __construct()
  10 + {
  11 + $this->parse_result = 'ok';
  12 + }
  13 +
  14 + public function getExprResult()
  15 + {
  16 + return $this->expr_result;
  17 + }
  18 +
  19 + public function isExprOk()
  20 + {
  21 + return $this->parse_result == 'ok';
  22 + }
  23 +
  24 +}
  25 +
  26 +%type expr {Expr}
  27 +
  28 +%left OPOR.
  29 +%left OPAND.
  30 +%right NOT.
  31 +%left IS CONTAIN LIKE BETWEEN START END.
  32 +%left GT LE LT GE.
  33 +
  34 +%syntax_error
  35 +{
  36 + $this->parse_result = 'syntax';
  37 + $this->parse_message = "";
  38 +}
  39 +
  40 +%parse_accept
  41 +{
  42 + $this->parse_result = 'ok';
  43 +}
  44 +
  45 +%parse_failure
  46 +{
  47 + $this->parse_result = 'syntax';
  48 +}
  49 +
  50 +%start_symbol cmdline
  51 +
  52 +cmdline ::= expr(A).
  53 +{
  54 + $this->expr_result = A;
  55 +}
  56 +
  57 +expr(A) ::= expr(B) OPAND expr(C).
  58 +{
  59 + A = new OpExpr(B, ExprOp::OP_AND, C);
  60 +}
  61 +
  62 +expr(A) ::= expr(B) OPOR expr(C).
  63 +{
  64 + A = new OpExpr(B, ExprOp::OP_OR, C);
  65 +}
  66 +
  67 +expr(A) ::= NOT expr(B).
  68 +{
  69 + $expr = B;
  70 + $expr->not(!$expr->not());
  71 + A = $expr;
  72 +}
  73 +
  74 +expr(A) ::= PAR_OPEN expr(B) PAR_CLOSE.
  75 +{
  76 + A = B;
  77 +}
  78 +
  79 +expr(A) ::= terminal(B) operator(C) value(D).
  80 +{
  81 + $op = C;
  82 + $not = false;
  83 + if ($op == ExprOp::IS_NOT)
  84 + {
  85 + $op = ExprOp::IS;
  86 + $not = true;
  87 + }
  88 +
  89 + $fld = new OpExpr(B, $op, D);
  90 + $fld->not($not);
  91 + A = $fld;
  92 +}
  93 +
  94 +expr(A) ::= terminal(B) notop(C) BETWEEN value(D) OPAND value(E). [BETWEEN]
  95 +{
  96 + $expr = new OpExpr(B, ExprOp::BETWEEN, new BetweenValueExpr(D, E));
  97 + $expr->not(C);
  98 + A=$expr;
  99 +}
  100 +
  101 +expr(A) ::= terminal(B) notop(C) LIKE value(D).
  102 +{
  103 + $expr = new OpExpr(B, ExprOp::LIKE, D);
  104 + $expr->not(C);
  105 + A=$expr;
  106 +}
  107 +
  108 +expr(A) ::= terminal(B) IS notop(C) value(D).
  109 +{
  110 + $expr = new OpExpr(B, ExprOp::IS, D);
  111 + $expr->not(C);
  112 + A=$expr;
  113 +}
  114 +
  115 +expr(A) ::= terminal(B) DOES notop(C) CONTAIN value(D).
  116 +{
  117 + $expr = new OpExpr(B, ExprOp::CONTAINS, D);
  118 + $expr->not(C);
  119 + A=$expr;
  120 +}
  121 +
  122 +expr(A) ::= terminal(B) COLON value(C).
  123 +{
  124 + A = new OpExpr(B, ExprOp::CONTAINS, C);
  125 +}
  126 +
  127 +
  128 +notop(A) ::= .
  129 +{
  130 + A = false;
  131 +}
  132 +
  133 +notop(A) ::= NOT.
  134 +{
  135 + A = true;
  136 +}
  137 +
  138 +terminal(A) ::= SQUARE_OPEN value(B) SQUARE_CLOSE SQUARE_OPEN value(C) SQUARE_CLOSE.
  139 +{
  140 + $registry = ExprFieldRegistry::getRegistry();
  141 + $field = $registry->resolveMetadataField(B, C);
  142 + A = $field;
  143 +}
  144 +
  145 +terminal(A) ::= TERMINAL(B).
  146 +{
  147 + $registry = ExprFieldRegistry::getRegistry();
  148 + $field=$registry->resolveAlias(B);
  149 + A = $field;
  150 +}
  151 +
  152 +value(A) ::= VALUE(B).
  153 +{
  154 + A = B;
  155 +}
  156 +
  157 +value(A) ::= PAR_OPEN valuelist(B) PAR_CLOSE.
  158 +{
  159 + A = B;
  160 +}
  161 +
  162 +valuelist(A) ::= VALUE(B) COMMA valuelist(C).
  163 +{
  164 + C->addValue(B);
  165 + A = C;
  166 +}
  167 +
  168 +valuelist(A) ::= VALUE(B).
  169 +{
  170 + A = new ValueListExpr(B);
  171 +}
  172 +
  173 +operator(A) ::= CONTAINS.
  174 +{
  175 + A = ExprOp::CONTAINS;
  176 +}
  177 +
  178 +operator(A) ::= LT.
  179 +{
  180 + A = ExprOp::LESS_THAN;
  181 +}
  182 +
  183 +operator(A) ::= GT.
  184 +{
  185 + A = ExprOp::GREATER_THAN;
  186 +}
  187 +
  188 +operator(A) ::= LE.
  189 +{
  190 + A = ExprOp::LESS_THAN_EQUAL;
  191 +}
  192 +
  193 +operator(A) ::= GE.
  194 +{
  195 + A = ExprOp::GREATER_THAN_EQUAL;
  196 +}
  197 +
  198 +operator(A) ::= START WITH.
  199 +{
  200 + A = ExprOp::STARTS_WITH;
  201 +}
  202 +
  203 +operator(A) ::= END WITH.
  204 +{
  205 + A = ExprOp::ENDS_WITH;
  206 +}
  207 +
  208 +operator(A) ::= IS_NOT.
  209 +{
  210 + A = ExprOp::IS_NOT;
  211 +}
... ...
search2/search/bin/cronSavedSearch.php 0 → 100644
  1 +<?
  2 +
  3 +require_once(realpath('../../../config/dmsDefaults.php'));
  4 +//require_once('indexing/indexerCore.inc.php');
  5 +
  6 +// TODO!!
  7 +//$changed_docs = SearchHelper::getSavedSearchEvents();
  8 +
  9 +die('todo');
  10 +/*
  11 +
  12 +how this works -
  13 +
  14 +a saved search is created.
  15 +
  16 +1) any changes - ie new docs, checkins, metadata updates, etc are logged to the saved_search_events table
  17 +2) periodically, iterate through all documents - do search, and mail user results. remove the event indication.
  18 +
  19 +
  20 +*/
  21 +?>
0 22 \ No newline at end of file
... ...
search2/search/expr.inc.php 0 → 100755
  1 +<?php
  2 +
  3 +//require_once('../../config/dmsDefaults.php');
  4 +
  5 +/**
  6 + * This is the ideal case, but more complex
  7 + *
  8 + */
  9 +
  10 +require_once('indexing/indexerCore.inc.php');
  11 +require_once('search/fieldRegistry.inc.php');
  12 +require_once('search/exprConstants.inc.php');
  13 +
  14 +class RankManager
  15 +{
  16 + /**
  17 + * This array contains the rankings of fields on database tables.
  18 + *
  19 + * @var array
  20 + */
  21 + private $db;
  22 + /**
  23 + * Contains the rankings of metadata fields on fieldset/field combinations.
  24 + *
  25 + * @var array
  26 + */
  27 + private $metadata;
  28 + /**
  29 + * Contains ranking factor for discussion matching
  30 + *
  31 + * @var float
  32 + */
  33 + private $discussion;
  34 + /**
  35 + * Contains the ranking factor for text matching
  36 + *
  37 + * @var float
  38 + */
  39 + private $text;
  40 +
  41 + private function __construct()
  42 + {
  43 + $this->dbfields=array();
  44 + $sql = "SELECT groupname, itemname, ranking, type FROM search_ranking";
  45 + $rs = DBUtil::getResultArray($sql);
  46 + foreach($rs as $item)
  47 + {
  48 + switch ($item['type'])
  49 + {
  50 + case 'T':
  51 + $this->db[$item['groupname']][$item['itemname']] = $item['ranking']+0;
  52 + break;
  53 + case 'M':
  54 + $this->metadata[$item['groupname']][$item['itemname']] = $item['ranking']+0;
  55 + break;
  56 + case 'S':
  57 + switch($item['groupname'])
  58 + {
  59 + case 'Discussion':
  60 + $this->discussion = $item['ranking']+0;
  61 + break;
  62 + case 'DocumentText':
  63 + $this->text = $item['ranking']+0;
  64 + break;
  65 + }
  66 + break;
  67 + }
  68 + }
  69 + }
  70 +
  71 + /**
  72 + * Enter description here...
  73 + *
  74 + * @return RankManager
  75 + */
  76 + public static function get()
  77 + {
  78 + static $singleton = null;
  79 + if (is_null($singleton))
  80 + {
  81 + $singleton = new RankManager();
  82 + }
  83 + return $singleton;
  84 + }
  85 +
  86 + public function scoreField($groupname, $type='T', $itemname='')
  87 + {
  88 + switch($type)
  89 + {
  90 + case 'T':
  91 + return $this->db[$groupname][$itemname];
  92 + case 'M':
  93 + return $this->metadata[$groupname][$itemname];
  94 + case 'S':
  95 + switch($groupname)
  96 + {
  97 + case 'Discussion':
  98 + return $this->discussion;
  99 + case 'DocumentText':
  100 + return $this->text;
  101 + default:
  102 + return 0;
  103 + }
  104 + default:
  105 + return 0;
  106 + }
  107 + }
  108 +}
  109 +
  110 +
  111 +class Expr
  112 +{
  113 + /**
  114 + * The parent expression
  115 + *
  116 + * @var Expr
  117 + */
  118 + protected $parent;
  119 +
  120 + protected static $node_id = 0;
  121 +
  122 + protected $expr_id;
  123 +
  124 + public function __construct()
  125 + {
  126 + $this->expr_id = Expr::$node_id++;
  127 + }
  128 +
  129 + public function getExprId()
  130 + {
  131 + return $this->expr_id;
  132 + }
  133 +
  134 + /**
  135 + * Coverts the expression to a string
  136 + *
  137 + * @return string
  138 + */
  139 + public function __toString()
  140 + {
  141 + throw new Exception('Not yet implemented in ' . get_class($this));
  142 + }
  143 +
  144 + /**
  145 + * Reference to the parent expression
  146 + *
  147 + * @return Expr
  148 + */
  149 + public function &getParent()
  150 + {
  151 + return $this->parent;
  152 + }
  153 +
  154 + /**
  155 + * Sets the parent expiression
  156 + *
  157 + * @param Expr $parent
  158 + */
  159 + public function setParent(&$parent)
  160 + {
  161 + $this->parent = &$parent;
  162 + }
  163 +
  164 + /**
  165 + * Is the expression valid
  166 + *
  167 + * @return boolean
  168 + */
  169 + public function is_valid()
  170 + {
  171 + return true;
  172 + }
  173 +
  174 + public function isExpr()
  175 + {
  176 + return $this instanceof OpExpr;
  177 + }
  178 +
  179 + public function isOpExpr()
  180 + {
  181 + return $this instanceof OpExpr;
  182 + }
  183 + public function isValueExpr()
  184 + {
  185 + return $this instanceof ValueExpr;
  186 + }
  187 + public function isValueListExpr()
  188 + {
  189 + return $this instanceof ValueListExpr;
  190 + }
  191 +
  192 + public function isDbExpr()
  193 + {
  194 + return $this instanceof DBFieldExpr;
  195 + }
  196 +
  197 + public function isFieldExpr()
  198 + {
  199 + return $this instanceof FieldExpr;
  200 + }
  201 +
  202 + public function isSearchableText()
  203 + {
  204 + return $this instanceof SearchableText ;
  205 + }
  206 +
  207 + public function isMetadataField()
  208 + {
  209 + return $this instanceof MetadataField;
  210 + }
  211 +
  212 +
  213 +
  214 +
  215 +
  216 + public function toViz(&$str, $phase)
  217 + {
  218 + throw new Exception('To be implemented' . get_class($this));
  219 + }
  220 +
  221 + public function toVizGraph($options=array())
  222 + {
  223 + $str = "digraph tree {\n";
  224 + if (isset($options['left-to-right']) && $options['left-to-right'])
  225 + {
  226 + $str .= "rankdir=LR\n";
  227 + }
  228 +
  229 + $this->toViz($str, 0);
  230 + $this->toViz($str, 1);
  231 +
  232 + $str .= "}\n";
  233 +
  234 + if (isset($options['tofile']))
  235 + {
  236 + $path=dirname($options['tofile']);
  237 + $filename=basename($options['tofile']);
  238 + $ext = pathinfo($filename, PATHINFO_EXTENSION);
  239 + $base = substr($filename, 0, -strlen($ext)-1);
  240 +
  241 + $dotfile="$path/$base.$ext";
  242 + $jpgfile="$path/$base.jpg";
  243 + $fp = fopen($dotfile,'wt');
  244 + fwrite($fp, $str);
  245 + fclose($fp);
  246 +
  247 + system("dot -Tjpg -o$jpgfile $dotfile");
  248 +
  249 + if (isset($options['view']) && $options['view'])
  250 + {
  251 + system("eog $jpgfile");
  252 + }
  253 + }
  254 +
  255 + return $str;
  256 + }
  257 +}
  258 +
  259 +class FieldExpr extends Expr
  260 +{
  261 + /**
  262 + * Name of the field
  263 + *
  264 + * @var string
  265 + */
  266 + protected $field;
  267 +
  268 + protected $alias;
  269 +
  270 + protected $display;
  271 +
  272 +
  273 + /**
  274 + * Constructor for the field expression
  275 + *
  276 + * @param string $field
  277 + */
  278 + public function __construct($field, $display=null)
  279 + {
  280 + parent::__construct();
  281 + $this->field=$field;
  282 + if (is_null($display))
  283 + {
  284 + $display=get_class($this);
  285 + }
  286 + $this->display = $display;
  287 + $this->setAlias(get_class($this));
  288 + }
  289 +
  290 + public function setAlias($alias)
  291 + {
  292 + $this->alias=$alias;
  293 + }
  294 +
  295 + public function getDisplay()
  296 + {
  297 + return $this->display;
  298 + }
  299 +
  300 + public function getAlias()
  301 + {
  302 + return $this->alias;
  303 + }
  304 +
  305 + public function getFullName()
  306 + {
  307 + return $this->alias . '.' . $this->field;
  308 + }
  309 +
  310 + /**
  311 + * Returns the field
  312 + *
  313 + * @return string
  314 + */
  315 + public function getField()
  316 + {
  317 + return $this->field;
  318 + }
  319 +
  320 + /**
  321 + * Coverts the expression to a string
  322 + *
  323 + * @return string
  324 + */
  325 + public function __toString()
  326 + {
  327 + return $this->alias;
  328 + }
  329 +
  330 + public function toViz(&$str, $phase)
  331 + {
  332 + if ($phase == 0)
  333 + {
  334 + $expr_id = $this->getExprId();
  335 + $str .= "struct$expr_id [style=rounded, label=\"$expr_id: FIELD[$this->alias]\"]\n";
  336 + }
  337 + }
  338 +
  339 + public function rewrite(&$left, &$op, &$right, $not=false)
  340 + {
  341 + $input = $left->getInputRequirements();
  342 +
  343 + if ($input['value']['type'] != FieldInputType::FULLTEXT)
  344 + {
  345 + return;
  346 + }
  347 +
  348 +
  349 + if ($right->isValueExpr())
  350 + {
  351 + $value = $right->getValue();
  352 + }
  353 + else
  354 + {
  355 + $value = $right;
  356 + }
  357 +
  358 + if (substr($value,0,1) != '\'' || substr($value,-1) != '\'')
  359 + {
  360 + OpExpr::rewriteString($left, $op, $right, $not);
  361 + }
  362 + else
  363 + {
  364 + $right = new ValueExpr(trim(substr($value,1,-1)));
  365 + }
  366 + }
  367 +}
  368 +
  369 +class DBFieldExpr extends FieldExpr
  370 +{
  371 + /**
  372 + * The table the field is associated with
  373 + *
  374 + * @var string
  375 + */
  376 + protected $table;
  377 +
  378 + protected $jointable;
  379 + protected $joinfield;
  380 + protected $matchfield;
  381 + protected $quotedvalue;
  382 +
  383 +
  384 + /**
  385 + * Constructor for the database field
  386 + *
  387 + * @param string $field
  388 + * @param string $table
  389 + */
  390 + public function __construct($field, $table, $display=null)
  391 + {
  392 + if (is_null($display))
  393 + {
  394 + $display = get_class($this);
  395 + }
  396 +
  397 + parent::__construct($field, $display);
  398 +
  399 + $this->table=$table;
  400 + $this->jointable = null;
  401 + $this->joinfield = null;
  402 + $this->matchfield = null;
  403 + $this->quotedvalue=true;
  404 + }
  405 +
  406 + /**
  407 + * Returns the table name
  408 + *
  409 + * @return string
  410 + */
  411 + public function getTable()
  412 + {
  413 + return $this->table;
  414 + }
  415 +
  416 + public function joinTo($table, $field)
  417 + {
  418 + $this->jointable=$table;
  419 + $this->joinfield=$field;
  420 + }
  421 + public function matchField($field)
  422 + {
  423 + $this->matchfield = $field;
  424 + }
  425 +
  426 + public function modifyName($name)
  427 + {
  428 + return $name;
  429 + }
  430 +
  431 + public function modifyValue($value)
  432 + {
  433 + return $value;
  434 + }
  435 +
  436 +
  437 + public function getJoinTable() { return $this->jointable; }
  438 + public function getJoinField() { return $this->joinfield; }
  439 + public function getMatchingField() { return $this->matchfield; }
  440 + public function isValueQuoted($quotedvalue = null)
  441 + {
  442 + if (isset($quotedvalue))
  443 + {
  444 + $this->quotedvalue = $quotedvalue;
  445 + }
  446 + return $this->quotedvalue;
  447 + }
  448 +}
  449 +
  450 +class MetadataField extends DBFieldExpr
  451 +{
  452 + protected $fieldset;
  453 + protected $fieldid;
  454 + protected $fieldsetid;
  455 +
  456 + public function __construct($fieldset, $field, $fieldsetid, $fieldid)
  457 + {
  458 + parent::__construct($field, 'document_fields_link');
  459 + $this->fieldset=$fieldset;
  460 + $this->fieldid=$fieldid;
  461 + $this->fieldsetid=$fieldsetid;
  462 + }
  463 +
  464 + public function getFieldSet()
  465 + {
  466 + return $this->fieldset;
  467 + }
  468 +
  469 + public function getFieldId()
  470 + {
  471 + return $this->fieldid;
  472 + }
  473 +
  474 + public function getFieldSetId()
  475 + {
  476 + return $this->fieldsetid;
  477 + }
  478 +
  479 + public function getInputRequirements()
  480 + {
  481 + return array('value'=>array('type'=>FieldInputType::TEXT));
  482 + }
  483 +
  484 + /**
  485 + * Coverts the expression to a string
  486 + *
  487 + * @return string
  488 + */
  489 + public function __toString()
  490 + {
  491 + return "METADATA[$this->fieldset][$this->field]";
  492 + }
  493 +
  494 +}
  495 +
  496 +class SearchableText extends FieldExpr
  497 +{
  498 +}
  499 +
  500 +class ValueExpr extends Expr
  501 +{
  502 + /**
  503 + * The value
  504 + *
  505 + * @var mixed
  506 + */
  507 + protected $value;
  508 +
  509 + /**
  510 + * Constructor for the value expression
  511 + *
  512 + * @param mixed $value
  513 + */
  514 + public function __construct($value)
  515 + {
  516 + parent::__construct();
  517 + $this->value=$value;
  518 + }
  519 +
  520 + public function getValue()
  521 + {
  522 + return $this->value;
  523 + }
  524 +
  525 + /**
  526 + * Converts the value to a string
  527 + *
  528 + * @return unknown
  529 + */
  530 + public function __toString()
  531 + {
  532 + return (string) "\"$this->value\"";
  533 + }
  534 +
  535 + public function toViz(&$str, $phase)
  536 + {
  537 + if ($phase == 0)
  538 + {
  539 + $expr_id = $this->getExprId();
  540 + $value = addslashes($this->value);
  541 + $str .= "struct$expr_id [style=ellipse, label=\"$expr_id: \\\"$value\\\"\"]\n";
  542 + }
  543 + }
  544 +
  545 + public function getSQL($field, $fieldname, $op, $not=false)
  546 + {
  547 + $val = $field->modifyValue($this->getValue());
  548 + $quote = '';
  549 + if ($field->isValueQuoted())
  550 + {
  551 + $val = addslashes($val);
  552 + $quote = '\'';
  553 + }
  554 +
  555 + switch($op)
  556 + {
  557 + case ExprOp::CONTAINS:
  558 + $sql = "$fieldname LIKE '%$val%'";
  559 + break;
  560 + case ExprOp::STARTS_WITH:
  561 + $sql = "$fieldname LIKE '$val%'";
  562 + break;
  563 + case ExprOp::ENDS_WITH:
  564 + $sql = "$fieldname LIKE '%$val'";
  565 + break;
  566 + case ExprOp::IS:
  567 + $sql = "$fieldname = $quote$val$quote";
  568 + break;
  569 + case ExprOp::GREATER_THAN :
  570 + $sql = "$fieldname > $quote$val$quote";
  571 + break;
  572 + case ExprOp::GREATER_THAN_EQUAL :
  573 + $sql = "$fieldname >= $quote$val$quote";
  574 + break;
  575 + case ExprOp::LESS_THAN :
  576 + $sql = "$fieldname < $quote$val$quote";
  577 + break;
  578 + case ExprOp::LESS_THAN_EQUAL :
  579 + $sql = "$fieldname <= $quote$val$quote";
  580 + break;
  581 + default:
  582 + throw new Exception('Unknown op: ' . $op);
  583 + }
  584 +
  585 + if ($not)
  586 + {
  587 + $sql = "not ($sql)";
  588 + }
  589 +
  590 + return $sql;
  591 + }
  592 +
  593 +}
  594 +
  595 +class ValueListExpr extends Expr
  596 +{
  597 + /**
  598 + * The value
  599 + *
  600 + * @var mixed
  601 + */
  602 + protected $values;
  603 +
  604 + /**
  605 + * Constructor for the value expression
  606 + *
  607 + * @param mixed $value
  608 + */
  609 + public function __construct($value)
  610 + {
  611 + parent::__construct($value);
  612 + $this->values=array($value);
  613 + }
  614 +
  615 + public function addValue($value)
  616 + {
  617 + $this->values[] = $value;
  618 + }
  619 +
  620 +
  621 + public function getValue($param=null)
  622 + {
  623 + if (!empty($param))
  624 + {
  625 + return $this->values[$param];
  626 + }
  627 + $str = '';
  628 +
  629 + foreach($this->values as $value)
  630 + {
  631 + if ($str != '') $str .= ',';
  632 + $str .= "\"$value\"";
  633 + }
  634 +
  635 + return $str;
  636 + }
  637 +
  638 + /**
  639 + * Converts the value to a string
  640 + *
  641 + * @return unknown
  642 + */
  643 + public function __toString()
  644 + {
  645 + return $this->getValue();
  646 + }
  647 +
  648 + public function toViz(&$str, $phase)
  649 + {
  650 + if ($phase == 0)
  651 + {
  652 + $expr_id = $this->getExprId();
  653 +
  654 + $str .= "struct$expr_id [style=ellipse, label=\"$expr_id: ";
  655 + $i=0;
  656 + foreach($this->values as $value)
  657 + {
  658 + if ($i++>0) $str .= ',';
  659 + $value = addslashes($value);
  660 + $str .= "\\\"$value\\\"";
  661 + }
  662 + $str .= "\"]\n";
  663 + }
  664 + }
  665 +
  666 +
  667 +
  668 + public function rewrite(&$left, &$op, &$right, &$not)
  669 + {
  670 + if (count($this->values) == 1)
  671 + {
  672 + $right = new ValueExpr($this->values[0]);
  673 + return;
  674 + }
  675 + $newops = array();
  676 + foreach($this->values as $value)
  677 + {
  678 + $classname = get_class($left);
  679 + $class = new $classname;
  680 + $newop = new OpExpr($class, $op, $value);
  681 + $newops[] = $newop;
  682 + }
  683 +
  684 + $result = $newops[0];
  685 + for($i=1;$i<count($newops);$i++)
  686 + {
  687 + $result = new OpExpr($result, ExprOp::OP_OR, $newops[$i]);
  688 + }
  689 +
  690 + $left = $result->left();
  691 + $op = $result->op();
  692 + $right = $result->right();
  693 + }
  694 +
  695 +}
  696 +
  697 +
  698 +class BetweenValueExpr extends ValueExpr
  699 +{
  700 + protected $endvalue;
  701 +
  702 + public function __construct($start, $end)
  703 + {
  704 + parent::__construct($start);
  705 + $this->endvalue = $end;
  706 + }
  707 +
  708 + public function getStart()
  709 + {
  710 + return $this->getValue();
  711 + }
  712 +
  713 + public function getEnd()
  714 + {
  715 + return $this->endvalue;
  716 + }
  717 +
  718 + /**
  719 + * Converts the value to a string
  720 + *
  721 + * @return unknown
  722 + */
  723 + public function __toString()
  724 + {
  725 + return (string) $this->value . ' AND ' . $this->endvalue;
  726 + }
  727 +
  728 + public function toViz(&$str, $phase)
  729 + {
  730 + if ($phase == 0)
  731 + {
  732 + $value = addslashes($this->value);
  733 + $value2 = addslashes($this->endvalue);
  734 +
  735 + $expr_id = $this->getExprId();
  736 + $str .= "struct$expr_id [style=rounded, label=\"$expr_id: $value AND $value2\"]\n";
  737 + }
  738 + }
  739 +
  740 + public function getSQL($field, $fieldname, $op, $not=false)
  741 + {
  742 + if ($op != ExprOp::BETWEEN)
  743 + {
  744 + throw new Exception('Unexpected operator: ' . $op);
  745 + }
  746 +
  747 + $quote = '';
  748 +
  749 + $start = $field->modifyValue($this->getStart());
  750 + $end = $field->modifyValue($this->getEnd());
  751 +
  752 + if ($field->isValueQuoted())
  753 + {
  754 + $start = addslashes($start);
  755 + $end = addslashes($end);
  756 + $quote = '\'';
  757 + }
  758 +
  759 +
  760 + $not = $not?' NOT ':'';
  761 + return "$not ($fieldname $op $quote$start$quote AND $quote$end$quote) ";
  762 + }
  763 +}
  764 +
  765 +interface QueryBuilder
  766 +{
  767 + function buildComplexQuery($expr);
  768 +
  769 + function buildSimpleQuery($op, $group);
  770 +
  771 + function getRanking($result);
  772 +
  773 + function getResultText($result);
  774 +
  775 +}
  776 +
  777 +class TextQueryBuilder implements QueryBuilder
  778 +{
  779 + private $text;
  780 + private $query;
  781 +
  782 + public function buildComplexQuery($expr)
  783 + {
  784 + $left = $expr->left();
  785 + $right = $expr->right();
  786 + if (DefaultOpCollection::isBoolean($expr))
  787 + {
  788 + $query = '(' . $this->buildComplexQuery($left) . ' ' . $expr->op() . ' ' . $this->buildComplexQuery($right) . ')';
  789 +
  790 + if ($expr->not())
  791 + {
  792 + $query = "NOT $query";
  793 + }
  794 + }
  795 + else
  796 + {
  797 + $fieldname = $left->getField();
  798 + $value = addslashes($right->getValue());
  799 +
  800 + $not = $expr->not()?' NOT ':'';
  801 +
  802 + $query = "$not$fieldname: \"$value\"";
  803 + }
  804 +
  805 + return $query;
  806 + }
  807 +
  808 + public function buildSimpleQuery($op, $group)
  809 + {
  810 + $query = '';
  811 + foreach($group as $expr)
  812 + {
  813 + if (!empty($query))
  814 + {
  815 + $query .= " $op ";
  816 + }
  817 +
  818 + $left = $expr->left();
  819 + $right = $expr->right();
  820 +
  821 + $fieldname = $left->getField();
  822 + $value = addslashes($right->getValue());
  823 +
  824 + $not = $expr->not()?' NOT ':'';
  825 +
  826 + $query .= "$not$fieldname: \"$value\"";
  827 + }
  828 +
  829 + return $query;
  830 + }
  831 +
  832 + public function getRanking($result)
  833 + {
  834 + $init = $result->Rank;
  835 + $score=0;
  836 + $ranker = RankManager::get();
  837 + $discussion = $result->Discussion;
  838 + if (!empty($discussion))
  839 + {
  840 + $score += $init *$ranker->scoreField('Discussion', 'S');
  841 + }
  842 + else
  843 + {
  844 + $score += $init *$ranker->scoreField('DocumentText', 'S');
  845 +
  846 + }
  847 + return $score;
  848 + }
  849 +
  850 + public function setQuery($query)
  851 + {
  852 + $this->query = $query;
  853 + }
  854 +
  855 + private function extractText($word, $maxwords=40, $maxlen=512)
  856 + {
  857 + $offset=stripos($this->text, $word);
  858 +
  859 + if ($offset == false)
  860 + {
  861 + return array(false, false);
  862 + }
  863 +
  864 + $text = substr($this->text, 0 , $offset);
  865 +
  866 + $lastsentence = strrpos($text, '.');
  867 + if (!$lastsentence) $lastsentence=0;
  868 +
  869 + if ($offset - $lastsentence > $maxlen)
  870 + {
  871 + $lastsentence = $offset - $maxlen;
  872 + }
  873 +
  874 + $text = substr($this->text, $lastsentence, $offset - $lastsentence);
  875 +
  876 + $wordoffset= strlen($text)-1;
  877 + $words = $maxwords;
  878 + while ($words > 0)
  879 + {
  880 + $text = substr($text, 0, $wordoffset);
  881 + $foundoffset = strrpos($text, ' ');
  882 + if ($foundoffset === false)
  883 + {
  884 + break;
  885 + }
  886 + $wordoffset = $foundoffset;
  887 + $words--;
  888 + }
  889 +
  890 + $startOffset = $lastsentence + $wordoffset;
  891 +
  892 + $nextsentence = strpos($this->text, '.', $offset);
  893 +
  894 + $words = $maxwords;
  895 + $endOffset = $offset;
  896 + while ($words > 0)
  897 + {
  898 + $foundoffset = strpos($this->text, ' ', $endOffset+1);
  899 + if ($foundoffset === false)
  900 + {
  901 + break;
  902 + }
  903 + if ($endOffset > $offset + $maxlen)
  904 + {
  905 + break;
  906 + }
  907 + if ($endOffset > $nextsentence)
  908 + {
  909 + $endOffset = $nextsentence-1;
  910 + break;
  911 + }
  912 + $endOffset = $foundoffset;
  913 +
  914 + $words--;
  915 + }
  916 +
  917 + return array($startOffset, substr($this->text, $startOffset, $endOffset - $startOffset + 1));
  918 + }
  919 +
  920 +
  921 + public function getResultText($result)
  922 + {
  923 + $this->text = substr($result->Text,0,40960);
  924 + $words = array();
  925 + $sentences = array();
  926 +
  927 + preg_match_all('("[^"]*")',$this->query, $matches,PREG_OFFSET_CAPTURE);
  928 +
  929 + foreach($matches[0] as $word)
  930 + {
  931 + list($word,$offset) = $word;
  932 + $word = substr($word,1,-1);
  933 + $wordlen = strlen($word);
  934 + $res = $this->extractText($word);
  935 + list($sentenceOffset,$sentence) = $res;
  936 +
  937 + if ($sentenceOffset === false)
  938 + {
  939 + continue;
  940 + }
  941 +
  942 + if (array_key_exists($sentenceOffset, $sentences))
  943 + {
  944 + $sentences[$sentenceOffset]['score']++;
  945 + }
  946 + else
  947 + {
  948 + $sentences[$sentenceOffset] = array(
  949 + 'sentence'=>$sentence,
  950 + 'score'=>1
  951 + );
  952 + }
  953 +
  954 + $sentence = $sentences[$sentenceOffset]['sentence'];
  955 +
  956 + preg_match_all("@$word@i",$sentence, $swords,PREG_OFFSET_CAPTURE);
  957 + foreach($swords[0] as $wordx)
  958 + {
  959 + list($wordx,$offset) = $wordx;
  960 +
  961 + $sentence = substr($sentence,0, $offset) . '<b>' . substr($sentence, $offset, $wordlen) . '</b>' . substr($sentence, $offset + $wordlen);
  962 + }
  963 +
  964 + $sentences[$sentenceOffset]['sentence'] = $sentence;
  965 +
  966 + $words[$word] = array(
  967 + 'sentence'=>$sentenceOffset
  968 + );
  969 + }
  970 +
  971 + ksort($sentences);
  972 + $result = '';
  973 +
  974 + foreach($sentences as $o=>$i)
  975 + {
  976 + if (!empty($result)) $result .= '&nbsp;&nbsp;&nbsp;...&nbsp;&nbsp;&nbsp;&nbsp;';
  977 + $result .= $i['sentence'];
  978 + }
  979 +
  980 + return $result;
  981 + }
  982 +
  983 +}
  984 +
  985 +class SQLQueryBuilder implements QueryBuilder
  986 +{
  987 + private $used_tables;
  988 + private $aliases;
  989 + private $sql;
  990 + private $db;
  991 + private $metadata;
  992 +
  993 + public function __construct()
  994 + {
  995 + $this->used_tables = array(
  996 + 'documents'=>1,
  997 + 'document_metadata_version'=>1,
  998 + 'document_content_version'=>0,
  999 + 'tag_words'=>0,
  1000 + 'document_fields_link'=>0
  1001 + );
  1002 +
  1003 + $this->aliases = array(
  1004 + 'documents'=>'d',
  1005 + 'document_metadata_version'=>'dmv',
  1006 + 'document_content_version'=>'dcv',
  1007 + 'tag_words'=>'tw',
  1008 + 'document_fields_link'=>'pdfl'
  1009 + );
  1010 +
  1011 + $this->sql = '';
  1012 + $this->db = array();
  1013 + $this->metadata = array();
  1014 + }
  1015 +
  1016 + /**
  1017 + * This looks up a table name to find the appropriate alias.
  1018 + *
  1019 + * @param string $tablename
  1020 + * @return string
  1021 + */
  1022 + private function resolveTableToAlias($tablename)
  1023 + {
  1024 + if (array_key_exists($tablename, $this->aliases))
  1025 + {
  1026 + return $this->aliases[$tablename];
  1027 + }
  1028 + throw new Exception("Unknown tablename '$tablename'");
  1029 + }
  1030 +
  1031 + private function exploreExprs($expr, $parent=null)
  1032 + {
  1033 + if ($expr->isMetadataField())
  1034 + {
  1035 + $this->metadata[] = & $parent;
  1036 + }
  1037 + elseif ($expr->isDBExpr())
  1038 + {
  1039 + $this->db[] = & $parent;
  1040 + $this->used_tables[$expr->getTable()]++;
  1041 + }
  1042 + elseif ($expr->isOpExpr())
  1043 + {
  1044 + $left = & $expr->left();
  1045 + $right = & $expr->right();
  1046 + if (DefaultOpCollection::isBoolean($expr))
  1047 + {
  1048 + $this->exploreExprs($left, $expr);
  1049 + $this->exploreExprs($right, $expr);
  1050 + }
  1051 + else
  1052 + {
  1053 + // if it is not a boolean, we only need to explore left as it is the one where the main field is defined.
  1054 + $this->exploreExprs($left, $expr);
  1055 + }
  1056 + }
  1057 + }
  1058 +
  1059 + private function exploreGroup($group)
  1060 + {
  1061 + // split up metadata and determine table usage
  1062 + foreach($group as $expr)
  1063 + {
  1064 + $field = $expr->left();
  1065 +
  1066 + if ($field->isMetadataField())
  1067 + {
  1068 + $this->metadata[] = $expr->getParent();
  1069 + }
  1070 + elseif ($field->isDBExpr())
  1071 + {
  1072 + $this->db[] = $expr->getParent();
  1073 + $this->used_tables[$field->getTable()]++;
  1074 + }
  1075 + }
  1076 + }
  1077 +
  1078 + private function getFieldnameFromExpr($expr)
  1079 + {
  1080 + $field = $expr->left();
  1081 + if (is_null($field->getJoinTable()))
  1082 + {
  1083 + $alias = $this->resolveTableToAlias($field->getTable());
  1084 + $fieldname = $alias . '.' . $field->getField();
  1085 + }
  1086 + else
  1087 + {
  1088 + $offset = $this->resolveJoinOffset($expr);
  1089 + $matching = $field->getMatchingField();
  1090 + $tablename = $field->getJoinTable();
  1091 + $fieldname = "$tablename$offset.$matching";
  1092 + }
  1093 +
  1094 + return $fieldname;
  1095 + }
  1096 +
  1097 + private function getSQLEvalExpr($expr)
  1098 + {
  1099 + $left = $expr->left();
  1100 + $right = $expr->right();
  1101 + if ($left->isMetadataField())
  1102 + {
  1103 + $offset = $this->resolveMetadataOffset($expr) + 1;
  1104 +
  1105 + $fieldset = $left->getField();
  1106 + $query = '(' . "df$offset.name='$fieldset' AND " . $right->getSQL($left, "dfl$offset.value", $expr->op(), false) . ')';
  1107 +
  1108 + }
  1109 + else
  1110 + {
  1111 + $fieldname = $this->getFieldnameFromExpr($expr);
  1112 +
  1113 + $query = $right->getSQL($left, $left->modifyName($fieldname), $expr->op(), $expr->not());;
  1114 + }
  1115 + return $query;
  1116 + }
  1117 +
  1118 + private function buildCoreSQL()
  1119 + {
  1120 + if (count($this->metadata) + count($this->db) == 0)
  1121 + {
  1122 + throw new Exception('nothing to do');
  1123 + }
  1124 +
  1125 + // we are doing this because content table is dependant on metadata table
  1126 + if ($this->used_tables['document_content_version'] > 0) $this->used_tables['document_metadata_version']++;
  1127 +
  1128 + $sql =
  1129 + 'SELECT ' . "\n";
  1130 +
  1131 + $sql .=
  1132 + ' DISTINCT d.id, dmv.name as title';
  1133 +
  1134 + $offset=0;
  1135 + foreach($this->db as $expr)
  1136 + {
  1137 + $offset++;
  1138 + $sql .= ", ifnull(" . $this->getSQLEvalExpr($expr) . ",0) as expr$offset ";
  1139 + }
  1140 +
  1141 + foreach($this->metadata as $expr)
  1142 + {
  1143 + $offset++;
  1144 + $sql .= ", ifnull(" . $this->getSQLEvalExpr($expr) . ",0) as expr$offset ";
  1145 + }
  1146 +
  1147 + $sql .=
  1148 + "\n" . 'FROM ' ."\n" .
  1149 + ' documents d ' ."\n";
  1150 +
  1151 + if ($this->used_tables['document_metadata_version'] > 0)
  1152 + {
  1153 + $sql .= ' INNER JOIN document_metadata_version dmv ON d.metadata_version_id=dmv.id' . "\n";
  1154 + }
  1155 + if ($this->used_tables['document_content_version'] > 0)
  1156 + {
  1157 + $sql .= ' INNER JOIN document_content_version dcv ON dmv.content_version_id=dcv.id ' . "\n";
  1158 + }
  1159 + if ($this->used_tables['document_fields_link'] > 0)
  1160 + {
  1161 + $sql .= ' LEFT JOIN document_fields_link pdfl ON dmv.id=pdfl.metadata_version_id ' . "\n";
  1162 + }
  1163 +
  1164 + if ($this->used_tables['tag_words'] > 0)
  1165 + {
  1166 + $sql .= ' LEFT OUTER JOIN document_tags dt ON dt.document_id=d.id ' . "\n" .
  1167 + ' LEFT OUTER JOIN tag_words tw ON dt.tag_id = tw.id ' . "\n";
  1168 + }
  1169 +
  1170 + $offset = 0;
  1171 + foreach($this->db as $expr)
  1172 + {
  1173 + $field = $expr->left();
  1174 + $jointable=$field->getJoinTable();
  1175 + if (!is_null($jointable))
  1176 + {
  1177 + $fieldname = $this->resolveTableToAlias($field->getTable()) . '.' . $field->getField();
  1178 +
  1179 + $joinalias = "$jointable$offset";
  1180 + $joinfield = $field->getJoinField();
  1181 + $sql .= " LEFT OUTER JOIN $jointable $joinalias ON $fieldname=$joinalias.$joinfield\n";
  1182 + }
  1183 + $offset++;
  1184 + }
  1185 +
  1186 +
  1187 +
  1188 + $offset=0;
  1189 + foreach($this->metadata as $expr)
  1190 + {
  1191 + $offset++;
  1192 + $field = $expr->left();
  1193 +
  1194 + $fieldid = $field->getFieldId();
  1195 + $sql .= " LEFT JOIN document_fields_link dfl$offset ON dfl$offset.metadata_version_id=d.metadata_version_id AND dfl$offset.document_field_id=$fieldid" . "\n";
  1196 + $sql .= " LEFT JOIN document_fields df$offset ON df$offset.id=dfl$offset.document_field_id" . "\n";
  1197 + }
  1198 +
  1199 +
  1200 + $sql .=
  1201 + 'WHERE dmv.status_id=1 AND d.status_id=1 AND ' . "\n ";
  1202 +
  1203 + return $sql;
  1204 + }
  1205 +
  1206 + private function resolveMetadataOffset($expr)
  1207 + {
  1208 + assert($expr->left()->isMetadataField() );
  1209 +
  1210 + $offset=0;
  1211 + foreach($this->metadata as $item)
  1212 + {
  1213 + if ($item->getExprId() == $expr->getExprId())
  1214 + {
  1215 + return $offset;
  1216 + }
  1217 + $offset++;
  1218 + }
  1219 + throw new Exception('metadata field not found');
  1220 + }
  1221 +
  1222 + private function resolveJoinOffset($expr)
  1223 + {
  1224 +
  1225 +
  1226 + $offset=0;
  1227 + foreach($this->db as $item)
  1228 + {
  1229 + if ($item->getExprId() == $expr->getExprId())
  1230 + {
  1231 + return $offset;
  1232 + }
  1233 + $offset++;
  1234 + }
  1235 + throw new Exception('join field not found');
  1236 + }
  1237 +
  1238 + private function buildCoreSQLExpr($expr)
  1239 + {
  1240 + $left = $expr->left();
  1241 + $right = $expr->right();
  1242 + if (DefaultOpCollection::isBoolean($expr))
  1243 + {
  1244 + $query = '(' . $this->buildCoreSQLExpr($left) . ' ' . $expr->op() . ' ' . $this->buildCoreSQLExpr($right) . ')';
  1245 + }
  1246 + else
  1247 + {
  1248 + $query = $this->getSQLEvalExpr($expr);
  1249 + }
  1250 +
  1251 + if ($expr->not())
  1252 + {
  1253 + $query = "NOT $query";
  1254 + }
  1255 +
  1256 + return $query;
  1257 + }
  1258 +
  1259 + public function buildComplexQuery($expr)
  1260 + {
  1261 +// print "building complex \n\n";
  1262 + $this->exploreExprs($expr);
  1263 +
  1264 + $sql = $this->buildCoreSQL();
  1265 +
  1266 + $sql .= $this->buildCoreSQLExpr($expr);
  1267 +
  1268 + return $sql;
  1269 + }
  1270 +
  1271 + public function buildSimpleQuery($op, $group)
  1272 + {
  1273 +// print "building simple \n\n";
  1274 + $this->exploreGroup($group);
  1275 +
  1276 + $sql = $this->buildCoreSQL();
  1277 +
  1278 + $offset=0;
  1279 + foreach($this->db as $expr)
  1280 + {
  1281 + if ($offset++)
  1282 + {
  1283 + $sql .= " $op\n " ;
  1284 + }
  1285 +
  1286 + $field = $expr->left();
  1287 +
  1288 + if (is_null($field->getJoinTable()))
  1289 + {
  1290 + $alias = $this->resolveTableToAlias($field->getTable());
  1291 + $fieldname = $alias . '.' . $field->getField();
  1292 + }
  1293 + else
  1294 + {
  1295 + $offset = $this->resolveJoinOffset($expr);
  1296 + $matching = $field->getMatchingField();
  1297 + $tablename = $field->getJoinTable();
  1298 + $fieldname = "$tablename$offset.$matching";
  1299 + }
  1300 +
  1301 +
  1302 + $value = $expr->right();
  1303 + $sql .= $value->getSQL($field, $left->modifyName($fieldname), $expr->op(), $expr->not());
  1304 + }
  1305 +
  1306 + $moffset=0;
  1307 + foreach($this->metadata as $expr)
  1308 + {
  1309 + $moffset++;
  1310 + if ($offset++)
  1311 + {
  1312 + $sql .= " $op\n " ;
  1313 + }
  1314 +
  1315 + $field = $expr->left();
  1316 + $value = $expr->right();
  1317 +
  1318 + $sql .= $value->getSQL($field, "dfl$moffset.value", $expr->getOp());
  1319 + }
  1320 +
  1321 + return $sql;
  1322 + }
  1323 +
  1324 + public function getRanking($result)
  1325 + {
  1326 + $ranker = RankManager::get();
  1327 + $score = 0;
  1328 + foreach($result as $col=>$val)
  1329 + {
  1330 + if ($val + 0 == 0)
  1331 + {
  1332 + // we are not interested if the expression failed
  1333 + continue;
  1334 + }
  1335 +
  1336 + if (substr($col, 0, 4) == 'expr' && is_numeric(substr($col, 4)))
  1337 + {
  1338 +
  1339 + $exprno = substr($col, 4);
  1340 + if ($exprno <= count($this->db))
  1341 + {
  1342 + $expr = $this->db[$exprno-1];
  1343 + $left=$expr->left();
  1344 + $score += $ranker->scoreField($left->getTable(), 'T', $left->getField());
  1345 + }
  1346 + else
  1347 + {
  1348 + $exprno -= count($this->db);
  1349 + $expr = $this->metadata[$exprno-1];
  1350 + $left=$expr->left();
  1351 + $score += $ranker->scoreField($left->getTable(), 'M', $left->getField());
  1352 + }
  1353 + }
  1354 + }
  1355 +
  1356 + return $score;
  1357 + }
  1358 +
  1359 + public function getResultText($result)
  1360 + {
  1361 + $text = array();
  1362 + foreach($result as $col=>$val)
  1363 + {
  1364 + if (substr($col, 0, 4) == 'expr' && is_numeric(substr($col, 4)))
  1365 + {
  1366 + if ($val + 0 == 0)
  1367 + {
  1368 + // we are not interested if the expression failed
  1369 + continue;
  1370 + }
  1371 + $exprno = substr($col, 4);
  1372 + if ($exprno <= count($this->db))
  1373 + {
  1374 + $expr = $this->db[$exprno-1];
  1375 + }
  1376 + else
  1377 + {
  1378 + $exprno -= count($this->db);
  1379 + $expr = $this->metadata[$exprno-1];
  1380 + }
  1381 + $text[] = (string) $expr;
  1382 + }
  1383 + }
  1384 + return '(' . implode(') AND (', $text) . ')';
  1385 + }
  1386 +
  1387 +
  1388 +}
  1389 +
  1390 +
  1391 +
  1392 +class OpExpr extends Expr
  1393 +{
  1394 + /**
  1395 + * The left side of the expression
  1396 + *
  1397 + * @var Expr
  1398 + */
  1399 + protected $left_expr;
  1400 +
  1401 + /**
  1402 + * The operator on the left and right
  1403 + *
  1404 + * @var ExprOp
  1405 + */
  1406 + protected $op;
  1407 + /**
  1408 + * The right side of the expression
  1409 + *
  1410 + * @var Expr
  1411 + */
  1412 + protected $right_expr;
  1413 +
  1414 + /**
  1415 + * This indicates that the expression is negative
  1416 + *
  1417 + * @var boolean
  1418 + */
  1419 + protected $not;
  1420 +
  1421 + protected $point;
  1422 +
  1423 + protected $has_text;
  1424 + protected $has_db;
  1425 +
  1426 + private $debug = false;
  1427 +
  1428 +// protected $flattened;
  1429 +
  1430 + protected $results;
  1431 +
  1432 + public function setResults($results)
  1433 + {
  1434 + $this->results=$results;
  1435 + }
  1436 + public function getResults()
  1437 + {
  1438 + return $this->results;
  1439 + }
  1440 +
  1441 + public function setHasDb($value=true)
  1442 + {
  1443 + $this->has_db=$value;
  1444 + }
  1445 +
  1446 + public function setHasText($value=true)
  1447 + {
  1448 + $this->has_text=$value;
  1449 + }
  1450 +
  1451 + public function getHasDb()
  1452 + {
  1453 + return $this->has_db;
  1454 + }
  1455 + public function getHasText()
  1456 + {
  1457 + return $this->has_text;
  1458 + }
  1459 + public function setPoint($point)
  1460 + {
  1461 + $this->point = $point;
  1462 + /* if (!is_null($point))
  1463 + {
  1464 + $this->flattened = new FlattenedGroup($this);
  1465 + }
  1466 + else
  1467 + {
  1468 + if (!is_null($this->flattened))
  1469 + {
  1470 + unset($this->flattened);
  1471 + }
  1472 + $this->flattened = null;
  1473 + }*/
  1474 + }
  1475 +
  1476 + public function getPoint()
  1477 + {
  1478 + return $this->point;
  1479 + }
  1480 +
  1481 + public function hasSameOpAs($expr)
  1482 + {
  1483 + return $this->op() == $expr->op();
  1484 + }
  1485 +
  1486 + public static function rewriteString(&$left, &$op, &$right, $not=false)
  1487 + {
  1488 + if ($right->isValueExpr())
  1489 + {
  1490 + $value = $right->getValue();
  1491 + }
  1492 + else
  1493 + {
  1494 + $value = $right;
  1495 + }
  1496 +
  1497 + $text = array();
  1498 +
  1499 +
  1500 + preg_match_all('/[\']([^\']*)[\']/',$value, $matches);
  1501 +
  1502 + foreach($matches[0] as $item)
  1503 + {
  1504 + $text [] = $item;
  1505 +
  1506 + $value = str_replace($item, '', $value);
  1507 + }
  1508 +
  1509 + $matches = explode(' ', $value);
  1510 +
  1511 + foreach($matches as $item)
  1512 + {
  1513 + if (empty($item)) continue;
  1514 + $text[] = $item;
  1515 + }
  1516 +
  1517 + if (count($text) == 1)
  1518 + {
  1519 + return;
  1520 + }
  1521 +
  1522 + $doctext = $left;
  1523 +
  1524 + $left = new OpExpr($doctext, $op, new ValueExpr($text[0]));
  1525 +
  1526 + for($i=1;$i<count($text);$i++)
  1527 + {
  1528 + if ($i==1)
  1529 + {
  1530 + $right = new OpExpr($doctext, $op, new ValueExpr($text[$i]));
  1531 + }
  1532 + else
  1533 + {
  1534 + $join = new OpExpr($doctext, $op, new ValueExpr($text[$i]));
  1535 + $right = new OpExpr($join, ExprOp::OP_OR, $right);
  1536 + }
  1537 + }
  1538 +
  1539 + $op = ExprOp::OP_OR;
  1540 + }
  1541 +
  1542 +
  1543 + /**
  1544 + * Constructor for the expression
  1545 + *
  1546 + * @param Expr $left
  1547 + * @param ExprOp $op
  1548 + * @param Expr $right
  1549 + */
  1550 + public function __construct($left, $op, $right, $not = false)
  1551 + {
  1552 + // if left is a string, we assume we should convert it to a FieldExpr
  1553 + if (is_string($left))
  1554 + {
  1555 + $left = new $left;
  1556 + }
  1557 +
  1558 + // if right is not an expression, we must convert it!
  1559 + if (!($right instanceof Expr))
  1560 + {
  1561 + $right = new ValueExpr($right);
  1562 + }
  1563 +
  1564 + if ($right->isValueListExpr())
  1565 + {
  1566 + $right->rewrite($left, $op, $right, $not);
  1567 + }
  1568 + else
  1569 + // rewriting is based on the FieldExpr, and can expand a simple expression
  1570 + // into something a little bigger.
  1571 + if ($left->isFieldExpr())
  1572 + {
  1573 + $left->rewrite($left, $op, $right, $not);
  1574 + }
  1575 +
  1576 + // transformation is required to optimise the expression tree so that
  1577 + // the queries on the db and full text search are optimised.
  1578 + if (DefaultOpCollection::isBoolean($op))
  1579 + {
  1580 + $this->transform($left, $op, $right, $not);
  1581 + }
  1582 +
  1583 + parent::__construct();
  1584 +
  1585 + $left->setParent($this);
  1586 + $right->setParent($this);
  1587 + $this->left_expr=&$left;
  1588 + $this->op = $op;
  1589 + $this->right_expr=&$right;
  1590 + $this->not = $not;
  1591 + $this->has_text=false;
  1592 +
  1593 + // $this->setPoint('point');
  1594 +
  1595 + if ($left->isSearchableText())
  1596 + {
  1597 + $this->setHasText();
  1598 + }
  1599 + else if ($left->isDBExpr())
  1600 + {
  1601 + $this->setHasDb();
  1602 + }
  1603 + elseif ($left->isOpExpr())
  1604 + {
  1605 + if ($left->getHasText()) { $this->setHasText(); }
  1606 + if ($left->getHasDb()) { $this->setHasDb(); }
  1607 + }
  1608 +
  1609 + if ($right->isOpExpr())
  1610 + {
  1611 + if ($right->getHasText()) { $this->setHasText(); }
  1612 + if ($right->getHasDb()) { $this->setHasDb(); }
  1613 + }
  1614 + // $this->flattened=null;
  1615 +
  1616 + // $left_op, etc indicates that $left expression is a logical expression
  1617 + $left_op = ($left->isOpExpr() && DefaultOpCollection::isBoolean($left));
  1618 + $right_op = ($right->isOpExpr() && DefaultOpCollection::isBoolean($right));
  1619 +
  1620 + // check which trees match
  1621 + $left_op_match = ($left_op && $this->hasSameOpAs($left)) ;
  1622 + $right_op_match = ($right_op && $this->hasSameOpAs($left)) ;
  1623 +
  1624 + $point = null;
  1625 +
  1626 +
  1627 + if ($left_op_match && $right_op_match) { $point = 'point'; }
  1628 +
  1629 + $left_op_match_flex = $left_op_match || ($left->isOpExpr());
  1630 + $right_op_match_flex = $right_op_match || ($right->isOpExpr());
  1631 +
  1632 + if ($left_op_match_flex && $right_op_match_flex) { $point = 'point'; }
  1633 +
  1634 + if (!is_null($point))
  1635 + {
  1636 + if ($left_op_match && $left->getPoint() == 'point') { $left->setPoint(null); }
  1637 + if ($right_op_match && $right->getPoint() == 'point') { $right->setPoint(null); }
  1638 +
  1639 + if ($left->isMergePoint() && is_null($right->getPoint())) { $right->setPoint('point'); }
  1640 + if ($right->isMergePoint() && is_null($left->getPoint())) { $left->setPoint('point'); }
  1641 +
  1642 + if ($left->isMergePoint() || $right->isMergePoint())
  1643 + {
  1644 + $point = 'merge';
  1645 +
  1646 + if (!$left->isMergePoint()) { $left->setPoint('point'); }
  1647 + if (!$right->isMergePoint()) { $right->setPoint('point'); }
  1648 +
  1649 + if ($this->isDBonly() || $this->isTextOnly())
  1650 + {
  1651 + $this->clearPoint();
  1652 + $point = 'point';
  1653 + }
  1654 + }
  1655 + }
  1656 +
  1657 + if ($point == 'point')
  1658 + {
  1659 + if ($this->isDBandText())
  1660 + {
  1661 + $point = 'merge';
  1662 + $left->setPoint('point');
  1663 + $right->setPoint('point');
  1664 + }
  1665 + }
  1666 + if (is_null($point) && !DefaultOpCollection::isBoolean($op))
  1667 + {
  1668 + $point = 'point';
  1669 + }
  1670 +
  1671 + $this->setPoint($point);
  1672 + }
  1673 +
  1674 + private function isDBonly()
  1675 + {
  1676 + return $this->getHasDb() && !$this->getHasText();
  1677 + }
  1678 +
  1679 + private function isTextOnly()
  1680 + {
  1681 + return !$this->getHasDb() && $this->getHasText();
  1682 + }
  1683 +
  1684 + private function isDBandText()
  1685 + {
  1686 + return $this->getHasDb() && $this->getHasText();
  1687 + }
  1688 +
  1689 + /**
  1690 + * Enter description here...
  1691 + *
  1692 + * @param OpExpr $expr
  1693 + */
  1694 + protected function clearPoint()
  1695 + {
  1696 + if (DefaultOpCollection::isBoolean($this))
  1697 + {
  1698 + $this->left()->clearPoint();
  1699 + $this->right()->clearPoint();
  1700 + }
  1701 + if ($this->isMergePoint())
  1702 + {
  1703 + $this->setPoint(null);
  1704 + }
  1705 + }
  1706 +
  1707 +
  1708 + protected function isMergePoint()
  1709 + {
  1710 + return in_array($this->getPoint(), array('merge','point'));
  1711 + }
  1712 +
  1713 + /**
  1714 + * Returns the operator on the expression
  1715 + *
  1716 + * @return ExprOp
  1717 + */
  1718 + public function op()
  1719 + {
  1720 + return $this->op;
  1721 + }
  1722 +
  1723 + /**
  1724 + * Returns true if the negative of the operator should be used in evaluation
  1725 + *
  1726 + * @param boolean $not
  1727 + * @return boolean
  1728 + */
  1729 + public function not($not=null)
  1730 + {
  1731 + if (!is_null($not))
  1732 + {
  1733 + $this->not = $not;
  1734 + }
  1735 +
  1736 + return $this->not;
  1737 + }
  1738 +
  1739 + /**
  1740 + * The left side of the expression
  1741 + *
  1742 + * @return Expr
  1743 + */
  1744 + public function &left()
  1745 + {
  1746 + return $this->left_expr;
  1747 + }
  1748 +
  1749 + /**
  1750 + * The right side of the expression
  1751 + *
  1752 + * @return Expr
  1753 + */
  1754 + public function &right()
  1755 + {
  1756 + return $this->right_expr;
  1757 + }
  1758 +
  1759 + /**
  1760 + * Converts the expression to a string
  1761 + *
  1762 + * @return string
  1763 + */
  1764 + public function __toString()
  1765 + {
  1766 + $expr = $this->left_expr . ' ' . $this->op .' ' . $this->right_expr;
  1767 +
  1768 + if (is_null($this->parent))
  1769 + {
  1770 + return $expr;
  1771 + }
  1772 +
  1773 + if ($this->parent->isOpExpr())
  1774 + {
  1775 + if ($this->parent->op != $this->op && in_array($this->op, DefaultOpCollection::$boolean))
  1776 + {
  1777 + $expr = "($expr)";
  1778 + }
  1779 + }
  1780 +
  1781 + if ($this->not())
  1782 + {
  1783 + $expr = "!($expr)";
  1784 + }
  1785 +
  1786 + return $expr;
  1787 + }
  1788 +
  1789 + /**
  1790 + * Is the expression valid
  1791 + *
  1792 + * @return boolean
  1793 + */
  1794 + public function is_valid()
  1795 + {
  1796 + $left = $this->left();
  1797 + $right = $this->right();
  1798 + return $left->is_valid() && $right->is_valid();
  1799 + }
  1800 +
  1801 + /**
  1802 + * Finds the results that are in both record sets.
  1803 + *
  1804 + * @param array $leftres
  1805 + * @param array $rightres
  1806 + * @return array
  1807 + */
  1808 + protected static function intersect($leftres, $rightres)
  1809 + {
  1810 + if (empty($leftres) || empty($rightres))
  1811 + {
  1812 + return array(); // small optimisation
  1813 + }
  1814 + $result = array();
  1815 + foreach($leftres as $item)
  1816 + {
  1817 + $document_id = $item->DocumentID;
  1818 +
  1819 + if (!$item->IsLive)
  1820 + {
  1821 + continue;
  1822 + }
  1823 +
  1824 + if (array_key_exists($document_id, $rightres))
  1825 + {
  1826 + $check = $rightres[$document_id];
  1827 +
  1828 + $result[$document_id] = ($item->Rank < $check->Rank)?$check:$item;
  1829 + }
  1830 + }
  1831 + return $result;
  1832 + }
  1833 +
  1834 + /**
  1835 + * The objective of this function is to merge the results so that there is a union of the results,
  1836 + * but there should be no duplicates.
  1837 + *
  1838 + * @param array $leftres
  1839 + * @param array $rightres
  1840 + * @return array
  1841 + */
  1842 + protected static function union($leftres, $rightres)
  1843 + {
  1844 + if (empty($leftres))
  1845 + {
  1846 + return $rightres; // small optimisation
  1847 + }
  1848 + if (empty($rightres))
  1849 + {
  1850 + return $leftres; // small optimisation
  1851 + }
  1852 + $result = array();
  1853 +
  1854 + foreach($leftres as $item)
  1855 + {
  1856 + if ($item->IsLive)
  1857 + {
  1858 + $result[$item->DocumentID] = $item;
  1859 + }
  1860 + }
  1861 +
  1862 + foreach($rightres as $item)
  1863 + {
  1864 + if (!array_key_exists($item->DocumentID, $result) || $item->Rank > $result[$item->DocumentID]->Rank)
  1865 + {
  1866 + $result[$item->DocumentID] = $item;
  1867 + }
  1868 + }
  1869 + return $result;
  1870 + }
  1871 +
  1872 + /**
  1873 + * Enter description here...
  1874 + *
  1875 + * @param OpExpr $left
  1876 + * @param ExprOp $op
  1877 + * @param OpExpr $right
  1878 + * @param boolean $not
  1879 + */
  1880 + public function transform(& $left, & $op, & $right, & $not)
  1881 + {
  1882 +
  1883 + if (!$left->isOpExpr() || !$right->isOpExpr() || !DefaultOpCollection::isBoolean($op))
  1884 + {
  1885 + return;
  1886 + }
  1887 +
  1888 + if ($left->isTextOnly() && $right->isDBonly())
  1889 + {
  1890 + // we just swap the items around, to ease other transformations
  1891 + $tmp = $left;
  1892 + $left = $right;
  1893 + $right = $tmp;
  1894 + return;
  1895 + }
  1896 +
  1897 + if ($op != $right->op() || !DefaultOpCollection::isBoolean($right))
  1898 + {
  1899 + return;
  1900 + }
  1901 +
  1902 + if ($op == ExprOp::OP_OR && ($not || $right->not()))
  1903 + {
  1904 + // NOTE: we can't transform. e.g.
  1905 + // db or !(db or txt) => db or !db and !txt
  1906 + // so nothing to do
  1907 +
  1908 + // BUT: db and !(db and txt) => db and !db and !txt
  1909 + return;
  1910 + }
  1911 +
  1912 + $rightLeft = $right->left();
  1913 + $rightRight = $right->right();
  1914 +
  1915 + if ($left->isDBonly() && $rightLeft->isDBonly())
  1916 + {
  1917 + $newLeft = new OpExpr( $left, $op, $rightLeft );
  1918 +
  1919 + $right = $rightRight;
  1920 + $left = $newLeft;
  1921 + return;
  1922 + }
  1923 +
  1924 + if ($left->isTextOnly() && $rightRight->isTextOnly())
  1925 + {
  1926 + $newRight = new OpExpr($left, $op, $rightRight);
  1927 + $left = $rightLeft;
  1928 + $right = $newRight;
  1929 + return;
  1930 + }
  1931 +
  1932 + }
  1933 +
  1934 + private function findDBNode($start, $op, $what)
  1935 + {
  1936 + if ($start->op() != $op)
  1937 + {
  1938 + return null;
  1939 + }
  1940 + switch($what)
  1941 + {
  1942 + case 'db':
  1943 + if ($start->isDBonly())
  1944 + {
  1945 + return $start;
  1946 + }
  1947 + break;
  1948 + case 'txt':
  1949 + if ($start->isTextOnly())
  1950 + {
  1951 + return $start;
  1952 + }
  1953 + break;
  1954 + }
  1955 + $node = $this->findDBNode($start->left(), $op, $what);
  1956 + if (is_null($left))
  1957 + {
  1958 + $node = $this->findDBNode($start->right(), $op, $what);
  1959 + }
  1960 + return $node;
  1961 +
  1962 + }
  1963 +
  1964 + public function traverse($object, $method, $param)
  1965 + {
  1966 + if ($this->isOpExpr())
  1967 + {
  1968 + $object->$method($param);
  1969 + }
  1970 + }
  1971 +
  1972 + private function exploreItem($item, & $group, $interest)
  1973 + {
  1974 + if (($interest == 'db' && $item->getHasDb()) ||
  1975 + ($interest == 'text' && $item->getHasText()))
  1976 + {
  1977 + if (in_array($item->op(), array(ExprOp::OP_OR, ExprOp::OP_AND)))
  1978 + {
  1979 + $this->exploreItem($item->left(), $group, $interest);
  1980 + $this->exploreItem($item->right(), $group, $interest);
  1981 + }
  1982 + else
  1983 + {
  1984 + $group[] = $item;
  1985 + }
  1986 + }
  1987 + }
  1988 +
  1989 + private function explore($left, $right, & $group, $interest)
  1990 + {
  1991 + $this->exploreItem($left, $group, $interest);
  1992 + $this->exploreItem($right, $group, $interest);
  1993 + }
  1994 +
  1995 + private function exec_db_query($op, $group)
  1996 + {
  1997 + if (empty($group)) { return array(); }
  1998 +
  1999 + $exprbuilder = new SQLQueryBuilder();
  2000 +
  2001 + if (count($group) == 1)
  2002 + {
  2003 + $sql = $exprbuilder->buildComplexQuery($group[0]);
  2004 + }
  2005 + else
  2006 + {
  2007 + $sql = $exprbuilder->buildSimpleQuery($op, $group);
  2008 + }
  2009 +
  2010 + $results = array();
  2011 +
  2012 + if ($this->debug) print "\n\n$sql\n\n";
  2013 + $rs = DBUtil::getResultArray($sql);
  2014 +
  2015 + if (PEAR::isError($rs))
  2016 + {
  2017 + throw new Exception($rs->getMessage());
  2018 + }
  2019 +
  2020 + foreach($rs as $item)
  2021 + {
  2022 + $document_id = $item['id'];
  2023 + $rank = $exprbuilder->getRanking($item);
  2024 + if (!array_key_exists($document_id, $results) || $rank > $results[$document_id]->Rank)
  2025 + {
  2026 + $results[$document_id] = new MatchResult($document_id, $rank, $item['title'], $exprbuilder->getResultText($item));
  2027 + }
  2028 + }
  2029 +
  2030 + return $results;
  2031 +
  2032 + }
  2033 +
  2034 + private function exec_text_query($op, $group)
  2035 + {
  2036 + if (empty($group)) { return array(); }
  2037 +
  2038 + $exprbuilder = new TextQueryBuilder();
  2039 +
  2040 + if (count($group) == 1)
  2041 + {
  2042 + $query = $exprbuilder->buildComplexQuery($group[0]);
  2043 + }
  2044 + else
  2045 + {
  2046 + $query = $exprbuilder->buildSimpleQuery($op, $group);
  2047 + }
  2048 +
  2049 + $indexer = Indexer::get();
  2050 + if ($this->debug) print "\n\n$query\n\n";
  2051 + $results = $indexer->query($query);
  2052 + foreach($results as $item)
  2053 + {
  2054 + $item->Rank = $exprbuilder->getRanking($item);
  2055 + $exprbuilder->setQuery($query);
  2056 + $item->Text = $exprbuilder->getResultText($item);
  2057 + }
  2058 +
  2059 + return $results;
  2060 +
  2061 +
  2062 + }
  2063 +
  2064 + public function evaluate()
  2065 + {
  2066 + $left = $this->left();
  2067 + $right = $this->right();
  2068 + $op = $this->op();
  2069 + $point = $this->getPoint();
  2070 + $result = array();
  2071 + if (empty($point))
  2072 + {
  2073 + $point = 'point';
  2074 + }
  2075 +
  2076 + if ($point == 'merge')
  2077 + {
  2078 +
  2079 + $leftres = $left->evaluate();
  2080 + $rightres = $right->evaluate();
  2081 + switch ($op)
  2082 + {
  2083 + case ExprOp::OP_AND:
  2084 + if ($this->debug) print "\n\nmerge: intersect\n\n";
  2085 + $result = OpExpr::intersect($leftres, $rightres);
  2086 + break;
  2087 + case ExprOp::OP_OR:
  2088 + if ($this->debug) print "\n\nmerge: union\n\n";
  2089 + $result = OpExpr::union($leftres, $rightres);
  2090 + break;
  2091 + default:
  2092 + throw new Exception("this condition should not happen");
  2093 + }
  2094 + }
  2095 + elseif ($point == 'point')
  2096 + {
  2097 + if ($this->isDBonly())
  2098 + {
  2099 + $result = $this->exec_db_query($op, array($this));
  2100 + }
  2101 + elseif ($this->isTextOnly())
  2102 + {
  2103 + $result = $this->exec_text_query($op, array($this));
  2104 + }
  2105 + elseif (in_array($op, array(ExprOp::OP_OR, ExprOp::OP_AND)))
  2106 + {
  2107 + $db_group = array();
  2108 + $text_group = array();
  2109 + $this->explore($left, $right, $db_group, 'db');
  2110 + $this->explore($left, $right, $text_group, 'text');
  2111 +
  2112 + $db_result = $this->exec_db_query($op, $db_group);
  2113 + $text_result = $this->exec_text_query($op, $text_group);
  2114 +
  2115 + switch ($op)
  2116 + {
  2117 + case ExprOp::OP_AND:
  2118 + if ($this->debug) print "\n\npoint: intersect\n\n";
  2119 + $result = OpExpr::intersect($db_result, $text_result);
  2120 + break;
  2121 + case ExprOp::OP_OR:
  2122 + if ($this->debug) print "\n\nmerge: union\n\n";
  2123 + $result = OpExpr::union($db_result, $text_result);
  2124 + break;
  2125 + default:
  2126 + throw new Exception('how did this happen??');
  2127 + }
  2128 + }
  2129 + else
  2130 + {
  2131 + throw new Exception('and this?');
  2132 + }
  2133 + }
  2134 + else
  2135 + {
  2136 + // we don't have to do anything
  2137 + //throw new Exception('Is this reached ever?');
  2138 + }
  2139 +
  2140 + $permResults = array();
  2141 + foreach($result as $idx=>$item)
  2142 + {
  2143 + $doc = Document::get($item->DocumentID);
  2144 + if (Permission::userHasDocumentReadPermission($doc))
  2145 + {
  2146 + $permResults[$idx] = $item;
  2147 + }
  2148 + }
  2149 +
  2150 + return $permResults;
  2151 + }
  2152 +
  2153 + public function toViz(&$str, $phase)
  2154 + {
  2155 + $expr_id = $this->getExprId();
  2156 + $left = $this->left();
  2157 + $right = $this->right();
  2158 + $hastext = $this->getHasText()?'TEXT':'';
  2159 + $hasdb = $this->getHasDb()?'DB':'';
  2160 + switch ($phase)
  2161 + {
  2162 + case 0:
  2163 + $not = $this->not()?'NOT':'';
  2164 + $str .= "struct$expr_id [style=box, label=\"$expr_id: $not $this->op $this->point $hastext$hasdb\"]\n";
  2165 + break;
  2166 + case 1:
  2167 + $left_id = $left->getExprId();
  2168 + $str .= "struct$expr_id -> struct$left_id\n";
  2169 + $right_id = $right->getExprId();
  2170 + $str .= "struct$expr_id -> struct$right_id\n";
  2171 + break;
  2172 + }
  2173 + $left->toViz($str, $phase);
  2174 + $right->toViz($str, $phase);
  2175 + }
  2176 +
  2177 +}
  2178 +
  2179 +
  2180 +
  2181 +
  2182 +?>
0 2183 \ No newline at end of file
... ...
search2/search/exprConstants.inc.php 0 → 100755
  1 +<?php
  2 +
  3 +class ExprOp
  4 +{
  5 + const IS = 'is';
  6 + const CONTAINS = 'contains';
  7 + const BETWEEN = 'between';
  8 + const STARTS_WITH = 'start with';
  9 + const ENDS_WITH = 'ends with';
  10 + const LIKE = 'like';
  11 + const LESS_THAN = '<';
  12 + const GREATER_THAN = '>';
  13 + const LESS_THAN_EQUAL = '<=';
  14 + const GREATER_THAN_EQUAL = '>=';
  15 + const OP_AND = 'AND';
  16 + const OP_OR = 'OR';
  17 + const IS_NOT = 'is not';
  18 +
  19 +}
  20 +
  21 +
  22 +
  23 +/**
  24 + * This is a collection of various operators that may be used
  25 + */
  26 +class DefaultOpCollection
  27 +{
  28 + public static $is = array(ExprOp::IS);
  29 + public static $contains = array(ExprOp::CONTAINS, ExprOp::STARTS_WITH , ExprOp::ENDS_WITH );
  30 + public static $between = array(ExprOp::BETWEEN);
  31 + public static $boolean = array(ExprOp::OP_OR , ExprOp::OP_AND );
  32 +
  33 + /**
  34 + * Validates if the operator on the expression's parent is allowed
  35 + *
  36 + * @param Expr $expr
  37 + * @param array $collection
  38 + * @return boolean
  39 + */
  40 + public static function validateParent(&$expr, &$collection)
  41 + {
  42 + $parent = $expr->getParent();
  43 + if ($parent instanceof OpExpr)
  44 + {
  45 + return in_array($parent->op(), $collection);
  46 + }
  47 + return false;
  48 + }
  49 +
  50 + public static function validate(&$expr, &$collection)
  51 + {
  52 + if ($expr instanceof OpExpr)
  53 + {
  54 + return in_array($expr->op(), $collection);
  55 + }
  56 + return false;
  57 + }
  58 +
  59 + public static function isBoolean(&$expr)
  60 + {
  61 + if ($expr instanceof OpExpr)
  62 + {
  63 + return in_array($expr->op(), DefaultOpCollection::$boolean);
  64 + }
  65 + elseif(is_string($expr))
  66 + {
  67 + return in_array($expr, DefaultOpCollection::$boolean);
  68 + }
  69 + return false;
  70 + }
  71 +}
  72 +
  73 +class FieldInputType
  74 +{
  75 + const TEXT = 'STRING';
  76 + const INT = 'INT';
  77 + const REAL = 'FLOAT';
  78 + const BOOLEAN = 'BOOL';
  79 + const USER_LIST = 'USERLIST';
  80 + const DATE = 'DATE';
  81 + const MIME_TYPES = 'MIMETYPES';
  82 + const DOCUMENT_TYPES = 'DOCTYPES';
  83 + const DATEDIFF = 'DATEDIFF';
  84 + const FULLTEXT = 'FULLTEXT';
  85 + const FILESIZE = 'FILESIZE';
  86 +}
  87 +
  88 +?>
0 89 \ No newline at end of file
... ...
search2/search/fieldRegistry.inc.php 0 → 100755
  1 +<?php
  2 +
  3 +require_once('search/expr.inc.php');
  4 +
  5 +class ResolutionException extends Exception {}
  6 +
  7 +
  8 +/**
  9 + * This is the primary registry for fields.
  10 + *
  11 + */
  12 +class ExprFieldRegistry
  13 +{
  14 + /**
  15 + * Stores all registered fields.
  16 + *
  17 + * @var array
  18 + */
  19 + private $fields;
  20 +
  21 + /**
  22 + * Stores all registered aliases.
  23 + *
  24 + * @var array
  25 + */
  26 + private $alias;
  27 +
  28 + /**
  29 + * Path to location of class definitions
  30 + *
  31 + * @var string
  32 + */
  33 + private $path;
  34 +
  35 + private $metadata;
  36 +
  37 + private $display;
  38 +
  39 + /**
  40 + * Initialise the registry.
  41 + * This is private and class must be obtained via the get() method.
  42 + *
  43 + * @access private
  44 + *
  45 + */
  46 + private function __construct()
  47 + {
  48 + $this->fields = array();
  49 + $this->alias = array();
  50 + $this->metadata = array();
  51 + $this->display=array();
  52 +
  53 + $config = KTConfig::getSingleton();
  54 +
  55 + $this->path = $config->get('search/fieldsPath');
  56 + }
  57 +
  58 + /**
  59 + * Retuns a singleton to the class.
  60 + *
  61 + * @return ExprFieldRegistry
  62 + */
  63 + public static function getRegistry()
  64 + {
  65 + static $singleton = null;
  66 +
  67 + if (is_null($singleton))
  68 + {
  69 + $singleton = new ExprFieldRegistry();
  70 + $singleton->registerFields();
  71 + }
  72 +
  73 + return $singleton;
  74 + }
  75 +
  76 + /**
  77 + * Add a field to the registry.
  78 + *
  79 + * @param FieldExpr $field
  80 + */
  81 + private function registerField($field)
  82 + {
  83 + assert(!is_null($field));
  84 + $classname = strtolower(get_class($field));
  85 + $alias = strtolower($field->getAlias());
  86 +
  87 + if (array_key_exists($classname, $this->fields) || array_key_exists($alias, $this->alias))
  88 + {
  89 + throw new ResolutionException("Class $classname with alias $alias already registered.");
  90 + }
  91 +
  92 + $this->fields[$classname] = $field;
  93 + $this->alias[$alias] = $field;
  94 +
  95 + if ($field instanceof MetadataField )
  96 + {
  97 + $fieldsetn = $field->getFieldSet();
  98 + $fieldn= $field->getField();
  99 + $this->metadata[$fieldsetn][$fieldn] = $field;
  100 + $this->display[] = "[\"$fieldsetn\"][\"$fieldn\"]";
  101 + }
  102 + else
  103 + {
  104 + $this->display[] = $field->getAlias();
  105 + }
  106 +
  107 + }
  108 +
  109 + public function resolveAlias($alias)
  110 + {
  111 + return $this->getField($alias);
  112 + }
  113 +
  114 + public function resolveMetadataField($fieldset, $field)
  115 + {
  116 + if (!array_key_exists($fieldset,$this->metadata))
  117 + {
  118 + throw new ResolutionException("Metadata class for fieldset '$fieldset' and field '$field' not found.");
  119 + }
  120 + if (!array_key_exists($field,$this->metadata[$fieldset]))
  121 + {
  122 + throw new ResolutionException("Metadata class for fieldset '$fieldset' and field '$field' not found.");
  123 + }
  124 + return $this->metadata[$fieldset][$field];
  125 + }
  126 +
  127 +
  128 + /**
  129 + * A static method to lookup a field by fieldname.
  130 + *
  131 + * @param string $fieldname
  132 + * @return unknown
  133 + */
  134 + public static function lookupField($fieldname)
  135 + {
  136 + $registry = ExprFieldRegistry::get();
  137 + return $registry->getField($fieldname);
  138 + }
  139 +
  140 + /**
  141 + * Returns a field from the registry.
  142 + *
  143 + * @param string $fieldname
  144 + * @return ExprField
  145 + */
  146 + public function getField($fieldname)
  147 + {
  148 + $fieldname = strtolower($fieldname);
  149 + if (array_key_exists($fieldname, $this->fields))
  150 + {
  151 + return $this->fields[$fieldname];
  152 + }
  153 + if (array_key_exists($fieldname, $this->alias))
  154 + {
  155 + return $this->alias[$fieldname];
  156 + }
  157 + throw new ResolutionException('Field not found: ' . $fieldname);
  158 + }
  159 +
  160 + public function getAliasNames()
  161 + {
  162 + return $this->display;
  163 + }
  164 +
  165 + /**
  166 + * Load all fields into the registry
  167 + *
  168 + */
  169 + public function registerFields()
  170 + {
  171 + $this->fields = array();
  172 +
  173 + $dir = opendir($this->path);
  174 + while (($file = readdir($dir)) !== false)
  175 + {
  176 + if (substr($file,-13) == 'Field.inc.php')
  177 + {
  178 + require_once($this->path . '/' . $file);
  179 + $class = substr($file, 0, -8);
  180 +
  181 + if (!class_exists($class))
  182 + {
  183 + continue;
  184 + }
  185 +
  186 + $field = new $class;
  187 + if (is_null($field) || !($field instanceof FieldExpr))
  188 + {
  189 + continue;
  190 + }
  191 +
  192 + $this->registerField($field);
  193 + }
  194 + }
  195 + closedir($dir);
  196 +
  197 + $this->registerMetdataFields();
  198 + }
  199 +
  200 + /**
  201 + * Registers metdata fields in system.
  202 + *
  203 + */
  204 + private function registerMetdataFields()
  205 + {
  206 + $sql = "SELECT
  207 + fs.name as fieldset, f.name as field, fs.id as fsid, f.id as fid
  208 + FROM
  209 + fieldsets fs
  210 + INNER JOIN document_fields f ON f.parent_fieldset=fs.id
  211 + WHERE
  212 + fs.disabled=0";
  213 + $result = DBUtil::getResultArray($sql);
  214 +
  215 + foreach($result as $record)
  216 + {
  217 + $fieldset = $record['fieldset'];
  218 + $field = $record['field'];
  219 + $fieldsetid = $record['fsid'];
  220 + $fieldid = $record['fid'];
  221 + $classname = "MetadataField$fieldid";
  222 +
  223 + $classdefn = "
  224 + class $classname extends MetadataField
  225 + {
  226 + public function __construct()
  227 + {
  228 + parent::__construct('$fieldset','$field',$fieldsetid, $fieldid);
  229 + }
  230 + }";
  231 + eval($classdefn);
  232 +
  233 + $field = new $classname;
  234 + $this->registerField($field);
  235 + }
  236 + }
  237 +
  238 + public function getFields()
  239 + {
  240 + $result = array();
  241 + foreach($this->fields as $field)
  242 + {
  243 + if ($field instanceof MetadataField)
  244 + {
  245 + continue;
  246 + }
  247 + $result[] = $field;
  248 + }
  249 + return $result;
  250 + }
  251 +
  252 +}
  253 +
  254 +?>
0 255 \ No newline at end of file
... ...
search2/search/fields/AnyMetadataField.inc.php 0 → 100755
  1 +<?php
  2 +
  3 +class AnyMetadataField extends DBFieldExpr
  4 +{
  5 + public function __construct()
  6 + {
  7 + parent::__construct('id', 'document_fields_link', 'Any Metadata');
  8 + $this->setAlias('Metadata');
  9 + }
  10 +
  11 + public function getInputRequirements()
  12 + {
  13 + return array('value'=>array('type'=>FieldInputType::TEXT));
  14 + }
  15 +
  16 + public function is_valid()
  17 + {
  18 + return DefaultOpCollection::validateParent($this, DefaultOpCollection::$is);
  19 + }
  20 +}
  21 +
  22 +?>
0 23 \ No newline at end of file
... ...
search2/search/fields/CheckedOutByField.inc.php 0 → 100755
  1 +<?php
  2 +
  3 +class CheckedOutByField extends DBFieldExpr
  4 +{
  5 + public function __construct()
  6 + {
  7 + parent::__construct('checked_out_user_id', 'documents', 'Checked Out By');
  8 + $this->setAlias('CheckedOutBy');
  9 + $this->joinTo('users', 'id');
  10 + $this->matchField('name');
  11 + }
  12 +
  13 + public function getInputRequirements()
  14 + {
  15 + return array('value'=>array('type'=>FieldInputType::USER_LIST));
  16 + }
  17 +
  18 + public function is_valid()
  19 + {
  20 + return DefaultOpCollection::validateParent($this, DefaultOpCollection::$is);
  21 + }
  22 +}
  23 +
  24 +?>
0 25 \ No newline at end of file
... ...
search2/search/fields/CheckedOutDeltaField.inc.php 0 → 100644
  1 +<?php
  2 +
  3 +class CheckedOutDeltaField extends DBFieldExpr
  4 +{
  5 + private $modifiedName;
  6 +
  7 + public function __construct()
  8 + {
  9 + parent::__construct('checkedout', 'documents', 'Checked Out Delta');
  10 + $this->setAlias('CheckedoutDelta');
  11 + $this->isValueQuoted(false);
  12 + }
  13 +
  14 + public function modifyName($sql)
  15 + {
  16 + $this->modifiedName = $sql;
  17 + $now = date('Y-m-d');
  18 +
  19 +
  20 + return "cast('$now' as date)";
  21 + }
  22 +
  23 +
  24 + public function modifyValue($value)
  25 + {
  26 +
  27 + return "cast($this->modifiedName + $value as date)";
  28 + }
  29 +
  30 + public function getInputRequirements()
  31 + {
  32 + return array('value'=>array('type'=>FieldInputType::DATEDIFF));
  33 + }
  34 +
  35 + public function is_valid()
  36 + {
  37 + return DefaultOpCollection::validateParent($this, DefaultOpCollection::$between);
  38 + }
  39 +}
  40 +
  41 +?>
0 42 \ No newline at end of file
... ...
search2/search/fields/CheckedOutField.inc.php 0 → 100644
  1 +<?php
  2 +
  3 +class CheckedOutField extends DBFieldExpr
  4 +{
  5 + public function __construct()
  6 + {
  7 + parent::__construct('checkedout', 'documents', 'Checked Out');
  8 + $this->setAlias('CheckedOut');
  9 + }
  10 +
  11 + public function modifyName($sql)
  12 + {
  13 + return "cast($sql as date)";
  14 + }
  15 +
  16 + public function getInputRequirements()
  17 + {
  18 + return array('value'=>array('type'=>FieldInputType::DATE));
  19 + }
  20 +
  21 + public function is_valid()
  22 + {
  23 + return DefaultOpCollection::validateParent($this, DefaultOpCollection::$between);
  24 + }
  25 +}
  26 +
  27 +?>
0 28 \ No newline at end of file
... ...
search2/search/fields/CreatedByField.inc.php 0 → 100755
  1 +<?php
  2 +
  3 +class CreatedByField extends DBFieldExpr
  4 +{
  5 + public function __construct()
  6 + {
  7 + parent::__construct('creator_id', 'documents','Created By');
  8 + $this->setAlias('CreatedBy');
  9 + $this->joinTo('users', 'id');
  10 + $this->matchField('name');
  11 + }
  12 +
  13 + public function getInputRequirements()
  14 + {
  15 + return array('value'=>array('type'=>FieldInputType::USER_LIST));
  16 + }
  17 +
  18 + public function is_valid()
  19 + {
  20 + return DefaultOpCollection::validateParent($this, DefaultOpCollection::$is);
  21 + }
  22 +}
  23 +
  24 +?>
0 25 \ No newline at end of file
... ...
search2/search/fields/CreatedDeltaField.inc.php 0 → 100755
  1 +<?php
  2 +
  3 +class CreatedDeltaField extends DBFieldExpr
  4 +{
  5 + private $modifiedName;
  6 +
  7 + public function __construct()
  8 + {
  9 + parent::__construct('created', 'documents', 'Created Delta');
  10 + $this->setAlias('CreatedDelta');
  11 + $this->isValueQuoted(false);
  12 + }
  13 +
  14 + public function modifyName($sql)
  15 + {
  16 + $this->modifiedName = $sql;
  17 + $now = date('Y-m-d');
  18 +
  19 +
  20 + return "cast('$now' as date)";
  21 + }
  22 +
  23 +
  24 + public function modifyValue($value)
  25 + {
  26 +
  27 + return "cast($this->modifiedName + $value as date)";
  28 + }
  29 +
  30 + public function getInputRequirements()
  31 + {
  32 + return array('value'=>array('type'=>FieldInputType::DATEDIFF));
  33 + }
  34 +
  35 + public function is_valid()
  36 + {
  37 + return DefaultOpCollection::validateParent($this, DefaultOpCollection::$between);
  38 + }
  39 +}
  40 +
  41 +?>
0 42 \ No newline at end of file
... ...
search2/search/fields/CreatedField.inc.php 0 → 100755
  1 +<?php
  2 +
  3 +class CreatedField extends DBFieldExpr
  4 +{
  5 + public function __construct()
  6 + {
  7 + parent::__construct('created', 'documents', 'Created');
  8 + $this->setAlias('Created');
  9 + }
  10 +
  11 + public function modifyName($sql)
  12 + {
  13 + return "cast($sql as date)";
  14 + }
  15 +
  16 + public function getInputRequirements()
  17 + {
  18 + return array('value'=>array('type'=>FieldInputType::DATE));
  19 + }
  20 +
  21 + public function is_valid()
  22 + {
  23 + return DefaultOpCollection::validateParent($this, DefaultOpCollection::$between);
  24 + }
  25 +}
  26 +
  27 +?>
0 28 \ No newline at end of file
... ...
search2/search/fields/DiscussionTextField.inc.php 0 → 100755
  1 +<?php
  2 +
  3 +
  4 +class DiscussionTextField extends SearchableText
  5 +{
  6 + public function __construct()
  7 + {
  8 + parent::__construct('Discussion', 'Discussion Text');
  9 + $this->setAlias('DiscussionText');
  10 + }
  11 +
  12 + public function getInputRequirements()
  13 + {
  14 + return array('value'=>array('type'=>FieldInputType::FULLTEXT));
  15 + }
  16 +
  17 + public function is_valid()
  18 + {
  19 + return DefaultOpCollection::validateParent($this, DefaultOpCollection::$contains);
  20 + }
  21 +
  22 +
  23 +
  24 +}
  25 +
  26 +?>
0 27 \ No newline at end of file
... ...
search2/search/fields/DocumentIdField.inc.php 0 → 100755
  1 +<?php
  2 +
  3 +class DocumentIdField extends DBFieldExpr
  4 +{
  5 + public function __construct()
  6 + {
  7 + parent::__construct('id', 'documents', 'Document ID');
  8 + $this->setAlias('DocumentId');
  9 + }
  10 +
  11 + public function getInputRequirements()
  12 + {
  13 + return array('value'=>array('type'=>FieldInputType::INT));
  14 + }
  15 +
  16 + public function is_valid()
  17 + {
  18 + return DefaultOpCollection::validateParent($this, DefaultOpCollection::$is);
  19 + }
  20 +}
  21 +
  22 +?>
0 23 \ No newline at end of file
... ...
search2/search/fields/DocumentTextField.inc.php 0 → 100755
  1 +<?php
  2 +
  3 +
  4 +class DocumentTextField extends SearchableText
  5 +{
  6 + public function __construct()
  7 + {
  8 + parent::__construct('Content', 'Document Text');
  9 + $this->setAlias('DocumentText');
  10 + }
  11 +
  12 + public function getInputRequirements()
  13 + {
  14 + return array('value'=>array('type'=>FieldInputType::FULLTEXT));
  15 + }
  16 +
  17 + public function is_valid()
  18 + {
  19 + return DefaultOpCollection::validateParent($this, DefaultOpCollection::$contains);
  20 + }
  21 +
  22 +
  23 +
  24 +
  25 +
  26 +
  27 +}
  28 +
  29 +?>
0 30 \ No newline at end of file
... ...
search2/search/fields/DocumentTypeField.inc.php 0 → 100755
  1 +<?php
  2 +
  3 +class DocumentTypeField extends DBFieldExpr
  4 +{
  5 + public function __construct()
  6 + {
  7 + parent::__construct('document_type_id', 'document_metadata_version', 'Document Type');
  8 + $this->setAlias('DocumentType');
  9 + $this->joinTo('document_types_lookup', 'id');
  10 + $this->matchField('name');
  11 + }
  12 +
  13 + public function getInputRequirements()
  14 + {
  15 + return array('value'=>array('type'=>FieldInputType::DOCUMENT_TYPES));
  16 + }
  17 +
  18 + public function is_valid()
  19 + {
  20 + return DefaultOpCollection::validateParent($this, DefaultOpCollection::$is);
  21 + }
  22 +}
  23 +
  24 +?>
0 25 \ No newline at end of file
... ...
search2/search/fields/FilenameField.inc.php 0 → 100755
  1 +<?php
  2 +
  3 +class FilenameField extends DBFieldExpr
  4 +{
  5 + public function __construct()
  6 + {
  7 + parent::__construct('filename', 'document_content_version', 'Filename');
  8 + $this->setAlias('Filename');
  9 + }
  10 +
  11 + public function getInputRequirements()
  12 + {
  13 + return array('value'=>array('type'=>FieldInputType::TEXT));
  14 + }
  15 +
  16 + public function is_valid()
  17 + {
  18 + return DefaultOpCollection::validateParent($this, DefaultOpCollection::$is);
  19 + }
  20 +}
  21 +
  22 +?>
0 23 \ No newline at end of file
... ...
search2/search/fields/FilesizeField.inc.php 0 → 100755
  1 +<?php
  2 +
  3 +class FilesizeField extends DBFieldExpr
  4 +{
  5 + public function __construct()
  6 + {
  7 + parent::__construct('size', 'document_content_version', 'Filesize');
  8 + $this->setAlias('Filesize');
  9 + }
  10 +
  11 + public function getInputRequirements()
  12 + {
  13 + return array('value'=>array('type'=>FieldInputType::FILESIZE));
  14 + }
  15 +
  16 + public function is_valid()
  17 + {
  18 + return DefaultOpCollection::validateParent($this, DefaultOpCollection::$is);
  19 + }
  20 +}
  21 +
  22 +?>
0 23 \ No newline at end of file
... ...
search2/search/fields/FolderField.inc.php 0 → 100755
  1 +<?php
  2 +
  3 +class FolderField extends DBFieldExpr
  4 +{
  5 + public function __construct()
  6 + {
  7 + parent::__construct('folder_id', 'documents', 'Folder');
  8 + $this->setAlias('Folder');
  9 + $this->joinTo('folders', 'id');
  10 + $this->matchField('full_path');
  11 + }
  12 +
  13 + public function getInputRequirements()
  14 + {
  15 + return array('value'=>array('type'=>FieldInputType::TEXT));
  16 + }
  17 +
  18 + public function is_valid()
  19 + {
  20 + return DefaultOpCollection::validateParent($this, DefaultOpCollection::$is);
  21 + }
  22 +}
  23 +
  24 +?>
0 25 \ No newline at end of file
... ...
search2/search/fields/FolderFieldID.inc.php 0 → 100644
  1 +<?php
  2 +
  3 +class FolderIDField extends DBFieldExpr
  4 +{
  5 + public function __construct()
  6 + {
  7 + parent::__construct('folder_id', 'documents', 'Folder ID');
  8 + $this->setAlias('FolderID');
  9 + }
  10 +
  11 + public function getInputRequirements()
  12 + {
  13 + return array('value'=>array('type'=>FieldInputType::INT));
  14 + }
  15 +
  16 + public function is_valid()
  17 + {
  18 + return DefaultOpCollection::validateParent($this, DefaultOpCollection::$is);
  19 + }
  20 +}
  21 +
  22 +?>
0 23 \ No newline at end of file
... ...
search2/search/fields/GeneralTextField.inc.php 0 → 100755
  1 +<?php
  2 +
  3 +class GeneralTextField extends SearchableText
  4 +{
  5 + public function __construct()
  6 + {
  7 + parent::__construct('General', 'General Text');
  8 + $this->setAlias('GeneralText');
  9 + }
  10 +
  11 + public function getInputRequirements()
  12 + {
  13 + return array('value'=>array('type'=>FieldInputType::TEXT));
  14 + }
  15 +
  16 + public function is_valid()
  17 + {
  18 + return DefaultOpCollection::validateParent($this, DefaultOpCollection::$contains);
  19 + }
  20 +
  21 +
  22 + public function rewrite(&$left, &$op, &$right)
  23 + {
  24 + // note the grouping of the db queries
  25 +
  26 + $left = new OpExpr(new DocumentTextField(), ExprOp::CONTAINS, $right);
  27 +
  28 + $op = ExprOp::OP_OR;
  29 +
  30 + $right = new OpExpr(
  31 + new OpExpr(new FilenameField(), ExprOp::CONTAINS, $right),
  32 + ExprOp::OP_OR,
  33 + new OpExpr(
  34 + new OpExpr(
  35 + new TitleField(), ExprOp::CONTAINS, $right),
  36 + ExprOp::OP_OR,
  37 + new OpExpr(new AnyMetadataField(), ExprOp::CONTAINS, $right)
  38 + )
  39 + );
  40 + }
  41 +
  42 +
  43 +}
  44 +
  45 +?>
0 46 \ No newline at end of file
... ...
search2/search/fields/IsCheckedOutField.inc.php 0 → 100755
  1 +<?php
  2 +
  3 +class IsCheckedOutField extends DBFieldExpr
  4 +{
  5 + public function __construct()
  6 + {
  7 + parent::__construct('is_checked_out', 'documents','Is Checked Out');
  8 + $this->setAlias('IsCheckedOut');
  9 + $this->isValueQuoted(false);
  10 + }
  11 +
  12 + public function modifyValue($value)
  13 + {
  14 + if (is_numeric($value))
  15 + {
  16 + $value = ($value+0)?1:0;
  17 + }
  18 + else
  19 + {
  20 + switch(strtolower($value))
  21 + {
  22 + case 'true':
  23 + case 'yes':
  24 + $value=1;
  25 + break;
  26 + default:
  27 + $value=0;
  28 + }
  29 + }
  30 + return $value;
  31 + }
  32 +
  33 + public function getInputRequirements()
  34 + {
  35 + return array('value'=>array('type'=>FieldInputType::BOOLEAN));
  36 + }
  37 +
  38 + public function is_valid()
  39 + {
  40 + return DefaultOpCollection::validateParent($this, DefaultOpCollection::$is);
  41 + }
  42 +}
  43 +
  44 +?>
0 45 \ No newline at end of file
... ...
search2/search/fields/IsImmutableField.inc.php 0 → 100755
  1 +<?php
  2 +
  3 +class IsImmutableField extends DBFieldExpr
  4 +{
  5 + public function __construct()
  6 + {
  7 + parent::__construct('immutable', 'documents', 'Is Immutable');
  8 + $this->setAlias('IsImmutable');
  9 + $this->isValueQuoted(false);
  10 + }
  11 +
  12 + public function modifyValue($value)
  13 + {
  14 + if (is_numeric($value))
  15 + {
  16 + $value = ($value+0)?1:0;
  17 + }
  18 + else
  19 + {
  20 + switch(strtolower($value))
  21 + {
  22 + case 'true':
  23 + case 'yes':
  24 + $value=1;
  25 + break;
  26 + default:
  27 + $value=0;
  28 + }
  29 + }
  30 + return $value;
  31 + }
  32 +
  33 + public function getInputRequirements()
  34 + {
  35 + return array('value'=>array('type'=>FieldInputType::BOOLEAN));
  36 + }
  37 +
  38 + public function is_valid()
  39 + {
  40 + return DefaultOpCollection::validateParent($this, DefaultOpCollection::$is);
  41 + }
  42 +}
  43 +
  44 +?>
0 45 \ No newline at end of file
... ...
search2/search/fields/MimeTypeField.inc.php 0 → 100755
  1 +<?php
  2 +
  3 +class MimeTypeField extends DBFieldExpr
  4 +{
  5 + public function __construct()
  6 + {
  7 + parent::__construct('mime_id', 'document_content_version', 'Mime Type');
  8 + $this->setAlias('MimeType');
  9 + $this->joinTo('mime_types', 'id');
  10 + $this->matchField('mimetypes');
  11 + }
  12 +
  13 + public function getInputRequirements()
  14 + {
  15 + // ideally MIME_TYPES
  16 + // but we must rework the mime_types table to be prettier!
  17 + return array('value'=>array('type'=>FieldInputType::TEXT));
  18 + }
  19 +
  20 + public function is_valid()
  21 + {
  22 + return DefaultOpCollection::validateParent($this, DefaultOpCollection::$is);
  23 + }
  24 +}
  25 +
  26 +?>
0 27 \ No newline at end of file
... ...
search2/search/fields/ModifiedByField.inc.php 0 → 100755
  1 +<?php
  2 +
  3 +class ModifiedByField extends DBFieldExpr
  4 +{
  5 + public function __construct()
  6 + {
  7 + parent::__construct('modified_user_id', 'documents', 'Modified By');
  8 + $this->setAlias('ModifiedBy');
  9 + $this->joinTo('users', 'id');
  10 + $this->matchField('name');
  11 + }
  12 +
  13 + public function getInputRequirements()
  14 + {
  15 + return array('value'=>array('type'=>FieldInputType::USER_LIST));
  16 + }
  17 +
  18 + public function is_valid()
  19 + {
  20 + return DefaultOpCollection::validateParent($this, DefaultOpCollection::$is);
  21 + }
  22 +}
  23 +
  24 +?>
0 25 \ No newline at end of file
... ...
search2/search/fields/ModifiedDeltaField.inc.php 0 → 100755
  1 +<?php
  2 +
  3 +class ModifiedDeltaField extends DBFieldExpr
  4 +{
  5 + private $modifiedName;
  6 +
  7 + public function __construct()
  8 + {
  9 + parent::__construct('modified', 'documents', 'Modified Delta');
  10 + $this->setAlias('ModifiedDelta');
  11 + $this->isValueQuoted(false);
  12 + }
  13 +
  14 + public function modifyName($sql)
  15 + {
  16 + $this->modifiedName = $sql;
  17 + $now = date('Y-m-d');
  18 +
  19 +
  20 + return "cast('$now' as date)";
  21 + }
  22 +
  23 + public function modifyValue($value)
  24 + {
  25 + return "cast($this->modifiedName + $value as date)";
  26 + }
  27 +
  28 + public function getInputRequirements()
  29 + {
  30 + return array('value'=>array('type'=>FieldInputType::DATEDIFF));
  31 + }
  32 +
  33 + public function is_valid()
  34 + {
  35 + return DefaultOpCollection::validateParent($this, DefaultOpCollection::$between);
  36 + }
  37 +}
  38 +
  39 +?>
0 40 \ No newline at end of file
... ...
search2/search/fields/ModifiedField.inc.php 0 → 100755
  1 +<?php
  2 +
  3 +class ModifiedField extends DBFieldExpr
  4 +{
  5 + public function __construct()
  6 + {
  7 + parent::__construct('modified', 'documents', 'Modified');
  8 + $this->setAlias('Modified');
  9 + }
  10 +
  11 + public function modifyName($sql)
  12 + {
  13 + return "cast($sql as date)";
  14 + }
  15 +
  16 + public function getInputRequirements()
  17 + {
  18 + return array('value'=>array('type'=>FieldInputType::DATE));
  19 + }
  20 +
  21 + public function is_valid()
  22 + {
  23 + return DefaultOpCollection::validateParent($this, DefaultOpCollection::$between);
  24 + }
  25 +}
  26 +
  27 +?>
0 28 \ No newline at end of file
... ...
search2/search/fields/TagField.inc.php 0 → 100755
  1 +<?php
  2 +
  3 +class TagField extends DBFieldExpr
  4 +{
  5 + public function __construct()
  6 + {
  7 + parent::__construct('tag', 'tag_words', 'Tag');
  8 + $this->setAlias('Tag');
  9 + }
  10 +
  11 + public function getInputRequirements()
  12 + {
  13 + return array('value'=>array('type'=>FieldInputType::TEXT));
  14 + }
  15 +
  16 + public function is_valid()
  17 + {
  18 + return DefaultOpCollection::validateParent($this, DefaultOpCollection::$is);
  19 + }
  20 +}
  21 +
  22 +?>
0 23 \ No newline at end of file
... ...
search2/search/fields/TitleField.inc.php 0 → 100755
  1 +<?php
  2 +
  3 +class TitleField extends DBFieldExpr
  4 +{
  5 + public function __construct()
  6 + {
  7 + parent::__construct('name', 'document_metadata_version', 'Title');
  8 + $this->setAlias('Title');
  9 + }
  10 +
  11 + public function getInputRequirements()
  12 + {
  13 + return array('value'=>array('type'=>FieldInputType::TEXT));
  14 + }
  15 +
  16 + public function is_valid()
  17 + {
  18 + return DefaultOpCollection::validateParent($this, DefaultOpCollection::$is);
  19 + }
  20 +}
  21 +
  22 +?>
0 23 \ No newline at end of file
... ...
search2/search/fields/WorkflowField.inc.php 0 → 100755
  1 +<?php
  2 +
  3 +class WorkflowField extends DBFieldExpr
  4 +{
  5 + public function __construct()
  6 + {
  7 + parent::__construct('workflow_id', 'document_metadata_version', 'Workflow');
  8 + $this->setAlias('Workflow');
  9 + $this->joinTo('workflows', 'id');
  10 + $this->matchField('name');
  11 + }
  12 +
  13 + public function getInputRequirements()
  14 + {
  15 + return array('value'=>array('type'=>FieldInputType::TEXT));
  16 + }
  17 +
  18 + public function is_valid()
  19 + {
  20 + return DefaultOpCollection::validateParent($this, DefaultOpCollection::$is);
  21 + }
  22 +}
  23 +
  24 +?>
0 25 \ No newline at end of file
... ...
search2/search/fields/WorkflowIDField.inc.php 0 → 100644
  1 +<?php
  2 +
  3 +class WorkflowIDField extends DBFieldExpr
  4 +{
  5 + public function __construct()
  6 + {
  7 + parent::__construct('workflow_id', 'document_metadata_version', 'Workflow ID');
  8 + $this->setAlias('WorkflowID');
  9 + }
  10 +
  11 + public function getInputRequirements()
  12 + {
  13 + return array('value'=>array('type'=>FieldInputType::INT));
  14 + }
  15 +
  16 + public function is_valid()
  17 + {
  18 + return DefaultOpCollection::validateParent($this, DefaultOpCollection::$is);
  19 + }
  20 +}
  21 +
  22 +?>
0 23 \ No newline at end of file
... ...
search2/search/fields/WorkflowStateField.inc.php 0 → 100755
  1 +<?php
  2 +
  3 +class WorkflowStateField extends DBFieldExpr
  4 +{
  5 + public function __construct()
  6 + {
  7 + parent::__construct('workflow_state_id', 'document_metadata_version', 'Workflow State');
  8 + $this->setAlias('WorkflowState');
  9 + $this->joinTo('workflow_states', 'id');
  10 + $this->matchField('name');
  11 + }
  12 +
  13 + public function getInputRequirements()
  14 + {
  15 + return array('value'=>array('type'=>FieldInputType::TEXT));
  16 + }
  17 +
  18 + public function is_valid()
  19 + {
  20 + return DefaultOpCollection::validateParent($this, DefaultOpCollection::$is);
  21 + }
  22 +}
  23 +
  24 +?>
0 25 \ No newline at end of file
... ...
search2/search/fields/WorkflowStateIDField.inc.php 0 → 100644
  1 +<?php
  2 +
  3 +class WorkflowStateIDField extends DBFieldExpr
  4 +{
  5 + public function __construct()
  6 + {
  7 + parent::__construct('workflow_state_id', 'document_metadata_version', 'Workflow State ID');
  8 + $this->setAlias('WorkflowStateID');
  9 + }
  10 +
  11 + public function getInputRequirements()
  12 + {
  13 + return array('value'=>array('type'=>FieldInputType::INT));
  14 + }
  15 +
  16 + public function is_valid()
  17 + {
  18 + return DefaultOpCollection::validateParent($this, DefaultOpCollection::$is);
  19 + }
  20 +}
  21 +
  22 +?>
0 23 \ No newline at end of file
... ...
search2/search/search.inc.php 0 → 100755
  1 +<?php
  2 +
  3 +
  4 +require_once('search/SearchCommandParser.php');
  5 +require_once('search/SearchCommandLexer.php');
  6 +require_once('search/fieldRegistry.inc.php');
  7 +require_once('search/expr.inc.php');
  8 +
  9 +function rank_compare($a, $b)
  10 +{
  11 + if ($a->Rank == $b->Rank)
  12 + {
  13 + if ($a->Title == $b->Title)
  14 + return 0;
  15 + // we'll show docs in ascending order by name
  16 + return ($a->Title < $b->Title)?-1:1;
  17 + }
  18 + // we want to be in descending order
  19 + return ($a->Rank > $b->Rank)?-1:1;
  20 +}
  21 +
  22 +function search_alias_compare($a, $b)
  23 +{
  24 + if ($a['alias'] == $b['alias']) return 0;
  25 + return ($a['alias'] < $b['alias'])?-1:1;
  26 +}
  27 +
  28 +class SearchHelper
  29 +{
  30 + public static function getSavedSearchEvents()
  31 + {
  32 + // TODO
  33 + $sql = "";
  34 + }
  35 +
  36 + public static function getJSdocumentTypesStruct($documenttypes = null)
  37 + {
  38 + if (is_null($documenttypes))
  39 + {
  40 + $documenttypes = SearchHelper::getDocumentTypes();
  41 + }
  42 + $dt=0;
  43 + $documenttypes_str = '[';
  44 + foreach($documenttypes as $user)
  45 + {
  46 + if ($dt++ > 0) $documenttypes_str .= ',';
  47 + $id=$user['id'];
  48 + $name=$user['name'];
  49 +
  50 + $documenttypes_str .= "\n\t{id: \"$id\", name: \"$name\"}";
  51 + }
  52 + $documenttypes_str .= ']';
  53 + return $documenttypes_str;
  54 +
  55 + }
  56 +
  57 + public static function getJSmimeTypesStruct($mimetypes = null)
  58 + {
  59 + if (is_null($mimetypes))
  60 + {
  61 + $mimetypes = SearchHelper::getMimeTypes();
  62 + }
  63 + $mt=0;
  64 + $mimetypes_str = '[';
  65 + foreach($mimetypes as $user)
  66 + {
  67 + if ($mt++ > 0) $mimetypes_str .= ',';
  68 +
  69 + $name=$user['name'];
  70 +
  71 + $mimetypes_str .= "\n\t\"$name\"";
  72 + }
  73 + $mimetypes_str .= ']';
  74 +
  75 + return $mimetypes_str;
  76 + }
  77 +
  78 + public static function getJSusersStruct($users = null)
  79 + {
  80 + if (is_null($users))
  81 + {
  82 + $users = SearchHelper::getUsers();
  83 + }
  84 +
  85 + $uo=0;
  86 + $users_str = '[';
  87 + foreach($users as $user)
  88 + {
  89 + if ($uo++ > 0) $users_str .= ',';
  90 + $id=$user['id'];
  91 + $name=$user['name'];
  92 +
  93 + $users_str .= "\n\t{id: \"$id\", name: \"$name\"}";
  94 + }
  95 + $users_str .= ']';
  96 +
  97 + return $users_str;
  98 + }
  99 +
  100 + public static function getJSfieldsStruct($fields = null)
  101 + {
  102 + if (is_null($fields))
  103 + {
  104 + $fields = SearchHelper::getSearchFields();
  105 + }
  106 + $fields_str = '[';
  107 + $fo=0;
  108 + foreach($fields as $field)
  109 + {
  110 + if ($fo++ > 0) $fields_str .= ',';
  111 + $alias = $field['alias'];
  112 + $display = $field['display'];
  113 + $type = $field['type'];
  114 + $fields_str .= "\n\t{alias: \"$alias\", name: \"$display\", type:\"$type\"}";
  115 + }
  116 + $fields_str .= ']';
  117 +
  118 + return $fields_str;
  119 + }
  120 +
  121 + public static function getJSworkflowStruct($workflows = null)
  122 + {
  123 + if (is_null($workflows))
  124 + {
  125 + $workflows = SearchHelper::getWorkflows();
  126 + }
  127 +
  128 + $workflow_str = '[';
  129 + $wo=0;
  130 + foreach($workflows as $workflow)
  131 + {
  132 + if ($wo++ > 0) $workflow_str .= ',';
  133 + $wid = $workflow['id'];
  134 + $name = $workflow['name'];
  135 +
  136 + $workflow_str .= "\n\t{id:\"$wid\", name: \"$name\", states: [ ";
  137 +
  138 + $result['workflows'][$wid] = $workflow;
  139 + $states = SearchHelper::getWorkflowStates($wid);
  140 + $result['workflows'][$wid]['states'] = array();
  141 + $so=0;
  142 + foreach($states as $state)
  143 + {
  144 + if ($so++>0) $workflow_str .= ',';
  145 + $sid = $state['id'];
  146 + $name=$state['name'];
  147 + $result['workflows'][$wid]['states'][$sid] = $state;
  148 + $workflow_str .= "\n\t\t{id:\"$wid\", name: \"$name\"}";
  149 + }
  150 + $workflow_str .= ']}';
  151 + }
  152 + $workflow_str .= ']';
  153 +
  154 + return $workflow_str;
  155 + }
  156 +
  157 + public static function getJSfieldsetStruct($fieldsets = null)
  158 + {
  159 + if (is_null($fieldsets))
  160 + {
  161 + $fieldsets = SearchHelper::getFieldsets();
  162 + }
  163 +
  164 + $fieldset_str = '[';
  165 + $fso=0;
  166 + foreach($fieldsets as $fieldset)
  167 + {
  168 + $fsid=$fieldset['id'];
  169 + $name = $fieldset['name'];
  170 + $desc = $fieldset['description'];
  171 + if ($fso++>0) $fieldset_str .= ',';
  172 + $fieldset_str .= "\n\t{id:\"$fsid\",name:\"$name\",description:\"$desc\", fields: [";
  173 +
  174 +
  175 + $result['fieldsets'][$fsid] = $fieldset;
  176 + $fields = SearchHelper::getFields($fsid);
  177 + $result['fieldsets'][$fsid]['fields'] = array();
  178 + $fo=0;
  179 + foreach($fields as $field)
  180 + {
  181 + if ($fo++ >0) $fieldset_str .= ',';
  182 + $fid = $field['id'];
  183 + $name= $field['name'];
  184 + $desc = $field['description'];
  185 + $datatype=$field['datatype'];
  186 + $control=$field['control'];
  187 + $fieldset_str .= "\n\t\t{id:\"$fid\", name:\"$name\", description:\"$desc\", datatype:\"$datatype\", control:\"$control\", options: [";
  188 + $options = $field['options'];
  189 + $oo = 0;
  190 + foreach($options as $option)
  191 + {
  192 + if ($oo++ > 0) $fieldset_str .= ',';
  193 + $oid = $option['id'];
  194 + $name= $option['name'];
  195 + $fieldset_str .= "\n\t\t\t{id: \"$oid\", name: \"$name\"}";
  196 + }
  197 + $fieldset_str .= ']}';
  198 + $result['fieldsets'][$fsid]['fields'][$fid] = $field;
  199 + }
  200 + $fieldset_str .= ']}';
  201 +
  202 + }
  203 + $fieldset_str .= ']';
  204 +
  205 + return $fieldset_str;
  206 + }
  207 +
  208 +
  209 + public static function getSavedSearches($userID)
  210 + {
  211 + $sql = "SELECT id, name FROM search_saved WHERE type='S'";
  212 +
  213 + // if we are not the system admin, then we get only ours or shared searches
  214 + if (!Permission::userIsSystemAdministrator($userID))
  215 + {
  216 + $sql .= " and ( user_id=$userID OR shared=1 ) ";
  217 + }
  218 +
  219 + $rs = DBUtil::getResultArray($sql);
  220 + return $rs;
  221 + }
  222 +
  223 + public static function getSearchFields()
  224 + {
  225 + $registry = ExprFieldRegistry::getRegistry();
  226 +
  227 + $fields = $registry->getFields();
  228 +
  229 + $results = array();
  230 + foreach($fields as $field )
  231 + {
  232 + $type = $field->getInputRequirements();
  233 + $type = $type['value']['type'];
  234 + $results[] = array('alias'=>$field->getAlias(), 'display'=>$field->getDisplay(), 'type'=>$type);
  235 + }
  236 + usort($results, search_alias_compare);
  237 + return $results;
  238 + }
  239 +
  240 + public static function getFolder($folderID, $userid)
  241 + {
  242 + $folder = Folder::get($folderID + 0);
  243 + if (PEAR::isError($folder))
  244 + {
  245 + return $folder;
  246 + }
  247 +
  248 + if (!Permission::userHasFolderReadPermission($folder))
  249 + {
  250 + return new PEAR_Error('no permission to read folder');
  251 + }
  252 +
  253 + $sql = "SELECT id, name FROM folders WHERE parent_id=$folderID ORDER BY name";
  254 + $rs = DBUtil::getResultArray($sql);
  255 + if (PEAR::isError($rs))
  256 + {
  257 + return $rs;
  258 + }
  259 +
  260 + $folders = array();
  261 +
  262 + foreach($rs as $folder)
  263 + {
  264 + $fobj = Folder::get($folder['id']);
  265 +
  266 + if (Permission::userHasFolderReadPermission($fobj))
  267 + {
  268 + $folders[] = $folder;
  269 + }
  270 + }
  271 + return $folders;
  272 + }
  273 +
  274 + public static function getFields($fieldsetID)
  275 + {
  276 + if ($fieldsetID < 0)
  277 + {
  278 + $documentTypeID = sanitizeForSQL(-$fieldsetID);
  279 + $sql = "SELECT
  280 + df.id, df.name, df.data_type, df.has_lookup, df.has_lookuptree, df.description
  281 + FROM
  282 + document_type_fields_link dtfl
  283 + INNER JOIN document_fields df on dtfl.field_id=df.id
  284 + WHERE
  285 + dtfl.document_type_id=$documentTypeID";
  286 +
  287 +
  288 + }
  289 + else
  290 + {
  291 + $fieldsetID = sanitizeForSQL($fieldsetID);
  292 + $sql = "SELECT id, name, data_type, has_lookup, has_lookuptree, description FROM document_fields WHERE parent_fieldset=$fieldsetID";
  293 + }
  294 +
  295 + $rs = DBUtil::getResultArray($sql);
  296 + if (PEAR::isError($rs))
  297 + {
  298 + return $rs;
  299 + }
  300 + if (count($rs) == 0)
  301 + {
  302 + return new PEAR_Error('Fieldset was not found');
  303 + }
  304 +
  305 + $result=array();
  306 + foreach($rs as $item)
  307 + {
  308 + $fieldid=$item['id'];
  309 + $type='normal';
  310 + $options = array();
  311 + $haslookup =$item['has_lookup'] + 0 > 0;
  312 + $hastree = ($item['has_lookuptree']+0 > 1);
  313 +
  314 + if ($haslookup || $hastree)
  315 + {
  316 + $type = 'lookup';
  317 + $sql = "select id, name from metadata_lookup where document_field_id=$fieldid";
  318 + $options = DBUtil::getResultArray($sql);
  319 +
  320 + }
  321 + /*if ($hastree)
  322 + {
  323 + $type = 'lookup';
  324 + $sql = "select id, name, metadata_lookup_tree_parent as parent from metadata_lookup_tree where document_field_id=$fieldid";
  325 + $options = DBUtil::getResultArray($sql);
  326 + }*/
  327 +
  328 + if ($item['data_type'] == 'USERLIST')
  329 + {
  330 + $type = 'lookup';
  331 + $sql = "SELECT id, name from users WHERE disabled=0";
  332 + $options = DBUtil::getResultArray($sql);
  333 + }
  334 +
  335 + $ritem = array(
  336 + 'id'=>$fieldid,
  337 + 'name'=>$item['name'],
  338 + 'description'=>$item['description'],
  339 + 'datatype'=>$item['data_type'],
  340 + 'control'=>$type,
  341 + 'options'=>$options
  342 + );
  343 +
  344 + $result[]= $ritem;
  345 + }
  346 + return $result;
  347 + }
  348 +
  349 + public static function getFieldsets()
  350 + {
  351 + $sql = "SELECT id, name, description FROM fieldsets WHERE disabled=0";
  352 + $rs = DBUtil::getResultArray($sql);
  353 +
  354 + return $rs;
  355 + }
  356 +
  357 + public static function getDocumentTypeFieldsets($documentTypeID)
  358 + {
  359 + $documentTypeID = sanitizeForSQL($documentTypeID);
  360 + $sql = "SELECT
  361 + fs.id, fs.name, fs.description
  362 + FROM
  363 + fieldsets fs LEFT JOIN document_type_fieldsets_link dtfl ON dtfl.fieldset_id=fs.id
  364 + WHERE
  365 + fs.disabled=0 AND (dtfl.document_type_id=$documentTypeID OR fs.is_generic=1)";
  366 + $rs = DBUtil::getResultArray($sql);
  367 +
  368 + return $rs;
  369 + }
  370 +
  371 + public static function getDocumentTypes()
  372 + {
  373 + $sql = "SELECT id, name from document_types_lookup WHERE disabled=0";
  374 + $rs = DBUtil::getResultArray($sql);
  375 + return $rs;
  376 + }
  377 +
  378 + public static function getMimeTypes() {
  379 + $sql = "SELECT DISTINCT mimetypes as name FROM mime_types order by mimetypes ";
  380 + $rs = DBUtil::getResultArray($sql);
  381 + return $rs;
  382 + }
  383 +
  384 + public static function getWorkflows()
  385 + {
  386 + $sql = "SELECT id, human_name as name FROM workflows WHERE enabled=1";
  387 + $rs = DBUtil::getResultArray($sql);
  388 + return $rs;
  389 + }
  390 +
  391 + public static function getUsers()
  392 + {
  393 + $sql = "SELECT id, name FROM users WHERE disabled=0";
  394 + $rs = DBUtil::getResultArray($sql);
  395 + return $rs;
  396 + }
  397 +
  398 + public static function getWorkflowStates($workflowid)
  399 + {
  400 + $sql = "SELECT id,human_name as name FROM workflow_states WHERE workflow_id=$workflowid";
  401 + $rs = DBUtil::getResultArray($sql);
  402 + return $rs;
  403 + }
  404 +
  405 +}
  406 +
  407 +
  408 +function getExpressionLocalityString($expr_str, $locality, $length, $start_offset=10)
  409 +{
  410 + if ($locality - $start_offset < 0)
  411 + {
  412 + $locality = 0;
  413 + }
  414 + else
  415 + {
  416 + $locality -= $start_offset;
  417 + }
  418 +
  419 + return substr($expr_str, $locality, $length);
  420 +}
  421 +
  422 +/**
  423 + * This parses a query.
  424 + *
  425 + * @param OpExpr $expr_str
  426 + * @return array of MatchResult
  427 + */
  428 +function parseExpression($expr_str)
  429 +{
  430 + $parser = new SearchCommandParser();
  431 + $lexer = new SearchCommandLexer($expr_str);
  432 +
  433 +// $parser->PrintTrace();
  434 + $use_internal=false;
  435 +
  436 + try
  437 + {
  438 + while ($lexer->yylex())
  439 + {
  440 + //print "\n" . $lexer->value . "\n";
  441 +
  442 + $parser->doParse($lexer->token, $lexer->value);
  443 +
  444 + if (!$parser->isExprOk())
  445 + {
  446 + $use_internal=true;
  447 + $expr_str=getExpressionLocalityString($expr_str, $lexer->offset, 20);
  448 + throw new Exception("Parsing problem near '$lexer->value' in '$expr_str' of expression.");
  449 + }
  450 + }
  451 +
  452 + // we are now done
  453 + $parser->doParse(0, 0);
  454 +
  455 + if (!$parser->isExprOk())
  456 + {
  457 + $use_internal=true;
  458 + $expr_str=getExpressionLocalityString($expr_str, $lexer->offset, 20);
  459 + throw new Exception("There is a problem parsing the expression '$expr_str'");
  460 + }
  461 +
  462 + }
  463 + catch(ResolutionException $e)
  464 + {
  465 + throw $e;
  466 + }
  467 + catch(Exception $e)
  468 + {
  469 + if ($use_internal)
  470 + {
  471 + throw $e;
  472 + }
  473 + $expr_str=getExpressionLocalityString($expr_str, $lexer->offset, 20);
  474 + throw new Exception("Parsing problem near '$lexer->value' of expression '$expr_str'.");
  475 + }
  476 +
  477 + return $parser->getExprResult();
  478 +}
  479 +
  480 +
  481 +
  482 +?>
0 483 \ No newline at end of file
... ...