r23225 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r23224‎ | r23225 | r23226 >
Date:14:48, 22 June 2007
Author:mkroetzsch
Status:old
Tags:
Comment:
Remove old query code from repository.
Modified paths:
  • /trunk/extensions/SemanticMediaWiki/includes/SMW_InlineQueries.php (deleted) (history)

Diff [purge]

Index: trunk/extensions/SemanticMediaWiki/includes/SMW_InlineQueries.php
@@ -1,1045 +0,0 @@
2 -<?php
3 -/**
4 - * This feature implements inline queries, i.e. it offers the possibility
5 - * to query against MediaWiki's own semantic knowledgebase by putting questions
6 - * inside articles. In the rendered article, those questions are replaced with
7 - * the respective answers. Moreover, queries might also be executed in special
8 - * pages, thus displaying results directly without need of editing an article.
9 - *
10 - * Scalability is the major challenge for inline queries, so their complexity
11 - * can be restricted through various parameters. For example, one can adjust
12 - * them so that every query can be computed by the database in linear space
13 - * (wrt. the smallest result set obtained by one of the conditions)(1). Features
14 - * such as nesting of queries and sorting of results can also be switched off.(2)
15 - *
16 - * (1) Currently, this is automatic, since basic result sets consist only of subjects
17 - * and since queries have a simple form.
18 - * (2) Nesting depth of subqueries not controlled yet. But you can limit the number
19 - * of database tables used overall.
20 - *
21 - * TODO: add parameters for determining the maximum depth of subqueries, the
22 - * maximum number of conditions, the maximum number of disjunctions (?), the
23 - * maximum number of real joins (when selecting a condition for printout)
24 - * TODO: add ordering to aggregated printout
25 - * TODO: change database layout to have an optional page_id for objects of rels;
26 - * since subqueries can only select *existting* articles, it is save to join them
27 - * via this id, which will be much faster
28 - * TODO: change database layout to include attribute id (maybe instead of its name?);
29 - * attributes have to exist or are not stored, so this is save; CAREFUL: might
30 - * disallow fallback to Type:String for unknown attibutes ... but do we really have to
31 - * search for those?
32 - * TODO: include error reporting for query parsing; make new messages for informing
33 - * the user about severe problems in understanding the query, and for warning if
34 - * certain resources where used up (too deep subqueries etc.)
35 - * TODO: allow combination of subqueries and disjunctions
36 - * TODO: allow parentheses and disjunctions between query parts
37 - */
38 -
39 -require_once( "$IP/includes/Title.php" );
40 -require_once( "$IP/includes/Linker.php" );
41 -require_once('SMW_QueryPrinters.php');
42 -
43 -// This hook registers a hook in the parser
44 -function smwfOldRegisterInlineQueries( $semantic, $mediawiki, $rules ) {
45 - global $wgParser;
46 - $wgParser->setHook( 'ask', 'smwfProcessInlineQueries' );
47 - return true; // always return true, in order not to stop MW's hook processing!
48 -}
49 -
50 -/**
51 - * Everything that is between <ask> and </ask> gets processed here
52 - * $text is the query in the query format described elsewhere
53 - * $param is an array which might contain values for various parameters
54 - * (see SMWInlineQuery).
55 - */
56 -function smwfProcessInlineQueries( $text, $param ) {
57 - global $smwgIQEnabled;
58 - $iq = new SMWInlineQuery($param);
59 - if ($smwgIQEnabled) {
60 - return $iq->getHTMLResult($text);
61 - } else {
62 - return wfMsgForContent('smw_iq_disabled');
63 - }
64 -}
65 -
66 -// Constants for distinguishing various modes of printout;
67 -// used to record what part of a query should be printed; see mPrint field
68 -// below
69 -define('SMW_IQ_PRINT_CATS', 0); // print all direct categories
70 -define('SMW_IQ_PRINT_RELS', 1); // print all relations objects of a certain relation
71 -define('SMW_IQ_PRINT_ATTS', 2); // print all attribute values of a certain attribute
72 -define('SMW_IQ_PRINT_RSEL', 3); // print certain relation objects selected by the query
73 -define('SMW_IQ_PRINT_ASEL', 4); // print certain attribute values selected by the query
74 -
75 -
76 -/**
77 - * A simple data container for storing the essential components of SQL queries.
78 - * Used to have a struct as convenient return value to functions that construct queries.
79 - */
80 -class SMWSQLQuery {
81 - public $mConditions; // SQL conditions as a string
82 - public $mTables; // SQL table names and and possible aliases as comma-separated a string
83 - public $mSelect; // array of fields to select, no aliases are allowed here
84 - public $mOrderBy; // NULL or name of a single field by which the result should be ordered
85 - public $mFixedSubject; // true if the subject of the query has been given explicitly
86 - public $mPrint; // array of things to print; format: id=>array(label,mode[,namestring[,datavalue]])
87 -
88 - public $mDebug; // some field for debugging //DEBUG
89 -
90 - function SMWSQLQuery() {
91 - $this->mConditions = '';
92 - $this->mTables = '';
93 - $this->mSelect = array();
94 - $this->mOrderBy = NULL;
95 - $this->mFixedSubject = false;
96 - $this->mPrint = array();
97 - }
98 -}
99 -
100 -
101 -/**
102 - * The main class for processing semantic queries. Its main entry point is
103 - * getHTMLResult, which transforms a textual query into a HTML-formatted output
104 - * text of results. The formatting is controlled through the parameters with which
105 - * the query object was initialised. It is an array of option => value pairs,
106 - * which may contain the following optional keys:
107 - * limit -- Maximal number of answers. It will be capped by $smwgIQMaxInlineLimit anyway.
108 - * Defaults to $smwgIQDefaultLimit.
109 - * offset -- Number of first result to be displayed. Parameter not available for inline queries.
110 - * link -- Either none, subject or all. Makes links out of the results.
111 - * Defaults to $smwgIQDefaultLinking.
112 - * default -- If no result is found, this string will be returned.
113 - * intro -- Plain text that is to be displayed before the query, but only if any results are obtained.
114 - * searchlabel -- text to use for link to search forfurther results, or empty if this link should not be shown
115 - * sort -- Name of the row by which to sort.
116 - * order -- Either 'ascending' (equal to 'asc', default) or 'descending' (equal to 'desc' or 'reverse')
117 - * headers -- How to display the header properties of columns, can be one of 'show'
118 - * (default) and 'hide'. Maybe 'link' will follow in the future.
119 - * mainlabel -- Label to use for the column that shows the main subjects. Also used to indicate that
120 - * the subject should be displayed in cases where it would normally be hidden.
121 - * format -- Either 'list', 'ul' (for unordered bullet list), 'ol' (ordered and numbered list),
122 - * 'table', 'broadtable', 'timeline', 'eventline', 'embedded', 'template', 'count',
123 - * 'debug', or 'auto' (default).
124 - * Some formats have additional parameters:
125 - * sep (list only) -- Customized separator string.
126 - */
127 -class SMWInlineQuery {
128 -
129 - /**
130 - * Array of enabled formats for inline queries. Can be redefined in the settings to disallow certain
131 - * formats. The formats 'table' and 'list' are defaults that cannot be disabled. The format 'broadtable'
132 - * should not be disabled either in order not to break Special:ask.
133 - */
134 - static $formats = array('table', 'list', 'ol', 'ul', 'broadtable', 'embedded', 'timeline', 'eventline', 'template', 'count', 'debug');
135 -
136 - private $mInline; // is this really an inline query, i.e. are results used in an article or not? (bool)
137 -
138 - // parameters:
139 - private $mParameters; // full parameter list for later reference
140 - private $mLimit; // max number of answers, also used when printing values of one particular subject
141 - private $mOffset; // number of first returned answer
142 - private $mSort; // supplied name of the row by which to sort
143 - private $mSortkey; // db key version of the row name by which to sort
144 - private $mOrder; // string that identifies sort order, 'ASC' (default) or 'DESC'
145 - private $mFormat; // a string identifier describing a valid format
146 - private $mIntro; // text to print before the output in case it is *not* empty
147 - private $mSearchlabel; // text to use for link to further results, or empty if link should not be shown
148 - private $mLinkSubj; // should article names of the (unique) subjects be linked?
149 - private $mLinkObj; // should article names of the objects be linked?
150 - private $mDefault; // default return value for empty queries
151 - private $mShowHeaders; // should the headers (property names) be printed?
152 - private $mMainLabel; // label used for displaying the subject, or NULL if none was given
153 -
154 - // fields used during query processing:
155 - private $mQueryText; // the original query text for future reference
156 - private $dbr; // pointer to the database used throughout exectution of the query
157 - private $mRename; // integer counter to rename tables in SQL joins
158 - private $mSubQueries; // array of subqueries, indexed by placeholder indices
159 - private $mSQCount; // integer counter to replace subqueries
160 - private $mConditionCount; // count the number of conditions used so far
161 - private $mTableCount; // count the number of tables joined so far
162 - private $mPrintoutCount; // count the number of fields selected for separate printout so far
163 - private $mFurtherResults=false; // true if not all results to the query were shown
164 - private $mDisplayCount=0; // number of results that are displayed
165 - private $mCurDisplayCount=0; // number of results that were already displayed, used for the iteration
166 - private $mQueryResult; // retrieved query result
167 -
168 - // other stuff
169 - private $mHTMLPrinter; // is the current printer expecting HTML (true) or wikitext (false) for labels?
170 - private $mLinker; // we make our own linker for creating the links -- TODO: is this bad?
171 -
172 - public function SMWInlineQuery($param = array(), $inline = true) {
173 - global $smwgIQDefaultLimit, $smwgIQDefaultLinking;
174 -
175 - $this->mInline = $inline;
176 -
177 - $this->mLimit = $smwgIQDefaultLimit;
178 - $this->mOffset = 0;
179 - $this->mSort = NULL;
180 - $this->mSortkey = NULL;
181 - $this->mOrder = 'ASC';
182 - $this->mFormat = 'auto';
183 - $this->mIntro = '';
184 - $this->mSearchlabel = NULL; //indicates that printer default should be used
185 - $this->mLinkSubj = ($smwgIQDefaultLinking != 'none');
186 - $this->mLinkObj = ($smwgIQDefaultLinking == 'all');
187 - $this->mDefault = '';
188 - $this->mShowHeaders = true;
189 - $this->mMainLabel = NULL;
190 -
191 - $this->mLinker = new Linker();
192 -
193 - $this->setParameters($param);
194 - }
195 -
196 - /**
197 - * Set the internal settings according to an array of parameter values.
198 - */
199 - private function setParameters($param) {
200 - global $smwgIQMaxLimit, $smwgIQMaxInlineLimit;
201 - $this->mParameters = $param;
202 -
203 - if ($this->mInline)
204 - $maxlimit = $smwgIQMaxInlineLimit;
205 - else $maxlimit = $smwgIQMaxLimit;
206 -
207 - if ( !$this->mInline && (array_key_exists('offset',$param)) && (is_int($param['offset'] + 0)) ) {
208 - $this->mOffset = min($maxlimit - 1, max(0,$param['offset'] + 0)); //select integer between 0 and maximal limit -1
209 - }
210 - // set limit small enough to stay in range with chosen offset
211 - // it makes sense to have limit=0 in order to only show the link to the search special
212 - if ( (array_key_exists('limit',$param)) && (is_int($param['limit'] + 0)) ) {
213 - $this->mLimit = min($maxlimit - $this->mOffset, max(0,$param['limit'] + 0));
214 - }
215 - if (array_key_exists('sort', $param)) {
216 - $this->mSort = $param['sort'];
217 - $this->mSortkey = smwfNormalTitleDBKey($param['sort']);
218 - }
219 - if (array_key_exists('order', $param)) {
220 - if (('descending'==strtolower($param['order']))||('reverse'==strtolower($param['order']))||('desc'==strtolower($param['order']))) {
221 - $this->mOrder = "DESC";
222 - }
223 - }
224 - if (array_key_exists('format', $param)) {
225 - $this->mFormat = strtolower($param['format']);
226 - if ( !in_array($this->mFormat,SMWInlineQuery::$formats) ) {
227 - $this->mFormat = 'auto'; // If it is an unknown format, default to list again
228 - }
229 - }
230 - // TODO: of course the printer should specify the following, but links are currently
231 - // created during query parsing, while printers might be selected only after this.
232 - // This will be fixed by cleaning up the query parsing code to not create full links
233 - // at a later stage.
234 - if (array_key_exists('template', $param)) {
235 - $this->mHTMLPrinter = false;
236 - } else {
237 - $this->mHTMLPrinter = true;
238 - }
239 - if (array_key_exists('intro', $param)) {
240 - $this->mIntro = htmlspecialchars(str_replace('_', ' ', $param['intro']));
241 - }
242 - if (array_key_exists('searchlabel', $param)) {
243 - $this->mSearchlabel = htmlspecialchars($param['searchlabel']);
244 - }
245 - if (array_key_exists('link', $param)) {
246 - switch (strtolower($param['link'])) {
247 - case 'head': case 'subject':
248 - $this->mLinkSubj = true;
249 - $this->mLinkObj = false;
250 - break;
251 - case 'all':
252 - $this->mLinkSubj = true;
253 - $this->mLinkObj = true;
254 - break;
255 - case 'none':
256 - $this->mLinkSubj = false;
257 - $this->mLinkObj = false;
258 - break;
259 - }
260 - }
261 - if (array_key_exists('default', $param)) {
262 - $this->mDefault = htmlspecialchars(str_replace('_', ' ', $param['default']));
263 - }
264 - if (array_key_exists('headers', $param)) {
265 - if ( 'hide' == strtolower($param['headers'])) {
266 - $this->mShowHeaders = false;
267 - } else {
268 - $this->mShowHeaders = true;
269 - }
270 - }
271 - if (array_key_exists('mainlabel', $param)) {
272 - $this->mMainLabel = htmlspecialchars($param['mainlabel']);
273 - }
274 - }
275 -
276 - /**
277 - * Returns true if a query was executed and the chosen limit did not
278 - * allow all results to be displayed
279 - */
280 - public function hasFurtherResults() {
281 - return $this->mFurtherResults;
282 - }
283 -
284 - /**
285 - * After a query was executed, this function returns the number of results that have been
286 - * displayed (which is different from the overall number of results that exist).
287 - */
288 - public function getDisplayCount() {
289 - return $this->mDisplayCount;
290 - }
291 -
292 - /**
293 - * Returns all parameters in their original array form. Useful for printer objects that
294 - * want to introduce their own parameters.
295 - */
296 - public function getParameters() {
297 - return $this->mParameters;
298 - }
299 -
300 - /**
301 - * Return the name of the format that is to be used for printing the query.
302 - */
303 - public function getFormat() {
304 - return $this->mFormat;
305 - }
306 -
307 - /**
308 - * Return the introduction string for printing the query.
309 - */
310 - public function getIntro() {
311 - return $this->mIntro;
312 - }
313 -
314 - /**
315 - * Return the default string for printing of the query returns no results.
316 - */
317 - public function getDefault() {
318 - return $this->mDefault;
319 - }
320 -
321 - /**
322 - * Return the label that should be used for the link to the search for
323 - * more results. If '' (empty string) is returned, the link should not be
324 - * shown. If NULL is returned, the default of the printer should apply.
325 - */
326 - public function getSearchlabel() {
327 - return $this->mSearchlabel;
328 - }
329 -
330 - /**
331 - * Returns a boolean stating whether or not headers should be displayed
332 - */
333 - public function showHeaders() {
334 - return $this->mShowHeaders;
335 - }
336 -
337 - /**
338 - * Returns a boolean stating whether the query was executed inline.
339 - */
340 - public function isInline() {
341 - return $this->mInline;
342 - }
343 -
344 - /**
345 - * Returns the URL of the ask special page, with parameters corresponding to the
346 - * current query.
347 - */
348 - public function getQueryURL() {
349 - $title = Title::makeTitle(NS_SPECIAL, 'ask');
350 - return $title->getLocalURL('query=' . urlencode($this->mQueryText) . '&sort=' . urlencode($this->mSort) . '&order=' . urlencode($this->mOrder));
351 - }
352 -
353 - /**
354 - * During execution of a query, this method is used to fetch result rows one by
355 - * one. FALSE will be returned if no more rows that should be printed remain.
356 - */
357 - public function getNextRow() {
358 - $this->mCurDisplayCount++;
359 - $row = $this->dbr->fetchRow( $this->mQueryResult );
360 - if ( (!$row) || ($this->mCurDisplayCount > $this->mLimit) ) {
361 - if ($row) $this->mFurtherResults = true; // there are more result than displayed
362 - return false;
363 - } else return $row;
364 - }
365 -
366 - /**
367 - * During execution of a query, this method is used create an appropriate iterator
368 - * object that encapsulates the values that are to be printed in a certain row and
369 - * column.
370 - */
371 - public function getIterator($print_data, $row, $subject) {
372 - global $smwgIQSortingEnabled;
373 - $sql_params = array('LIMIT' => $this->mLimit);
374 - $linked = ( ($this->mLinkObj) || (($this->mLinkSubj) && ($subject)) );
375 -
376 - switch ($print_data[1]) {
377 - case SMW_IQ_PRINT_CATS:
378 - if ($smwgIQSortingEnabled)
379 - $sql_params['ORDER BY'] = "cl_to $this->mOrder";
380 - $res = $this->dbr->select( $this->dbr->tableName('categorylinks'),
381 - 'DISTINCT cl_to',
382 - 'cl_from=' . $row['page_id'],
383 - 'SMW::InlineQuery::Print' , $sql_params);
384 - return new SMWCategoryIterator($this,$this->dbr,$res,$linked);
385 - case SMW_IQ_PRINT_RELS:
386 - if ($smwgIQSortingEnabled)
387 - $sql_params['ORDER BY'] = "object_title $this->mOrder";
388 - $res = $this->dbr->select( $this->dbr->tableName('smw_relations'),
389 - 'DISTINCT object_title,object_namespace',
390 - 'subject_id=' . $row['page_id'] . ' AND relation_title=' . $this->dbr->addQuotes($print_data[2]),
391 - 'SMW::InlineQuery::Print' , $sql_params);
392 - return new SMWRelationIterator($this,$this->dbr,$res,$linked);
393 - case SMW_IQ_PRINT_ATTS:
394 - if ($smwgIQSortingEnabled) {
395 - if ($print_data[3]->isNumeric()) {
396 - $sql_params['ORDER BY'] = "value_num $this->mOrder";
397 - } else {
398 - $sql_params['ORDER BY'] = "value_xsd $this->mOrder";
399 - }
400 - }
401 - $res = $this->dbr->select( $this->dbr->tableName('smw_attributes'),
402 - 'DISTINCT value_unit,value_xsd',
403 - 'subject_id=' . $row['page_id'] . ' AND attribute_title=' . $this->dbr->addQuotes($print_data[2]),
404 - 'SMW::InlineQuery::Print' , $sql_params);
405 - return new SMWAttributeIterator($this->dbr, $res, $print_data[3]);
406 - case SMW_IQ_PRINT_RSEL:
407 - global $wgContLang;
408 - return new SMWSingleArticleIterator($this, $wgContLang->getNsText($row[$print_data[2] . 'namespace']), $row[$print_data[2] . 'title'],$linked,$subject);
409 - case SMW_IQ_PRINT_ASEL: // TODO: allow selection of attribute conditionals, and print them here
410 - return new SMWFixedIterator('---');
411 - }
412 - }
413 -
414 - /**
415 - * Main entry point for parsing, executing, and printing a given query text.
416 - */
417 - public function getHTMLResult( $text ) {
418 - global $smwgIQSortingEnabled, $smwgIQRunningNumber,$wgTitle;
419 - $this->mQueryText = $text;
420 -
421 - if (!isset($smwgIQRunningNumber)) {
422 - $smwgIQRunningNumber = 0;
423 - } else { $smwgIQRunningNumber++; }
424 -
425 - // This should be the proper way of substituting templates in a safe and comprehensive way
426 - $parser = new Parser();
427 - $parserOptions = new ParserOptions();
428 - //$parserOptions->setInterfaceMessage( true );
429 - $parser->startExternalParse( $wgTitle, $parserOptions, OT_HTML );
430 - $text = $parser->transformMsg( $text, $parserOptions );
431 -
432 - $this->dbr =& wfGetDB( DB_SLAVE ); // Note: if this fails, there were worse errors before; don't check it
433 - $this->mRename = 0;
434 - $this->mSubQueries = array();
435 - $this->mSQCount = 0;
436 - $this->mConditionCount = 0;
437 - $this->mTableCount = 0;
438 - $this->mPrintoutCount = 0;
439 - $sq = $this->parseQuery($text);
440 -
441 - $sql_options = array();
442 - if ($this->mFormat == 'count') {
443 - $sq->mSelect = array('COUNT(DISTINCT ' . $sq->mSelect[0] . ') AS count'); // just count
444 - } else {
445 - $sq->mSelect[0] .= ' AS page_id';
446 - $sq->mSelect[1] .= ' AS page_title';
447 - $sq->mSelect[2] .= ' AS page_namespace';
448 -
449 - $sql_options['LIMIT'] = $this->mLimit + 1;
450 - $sql_options['OFFSET'] = $this->mOffset;
451 - if ( $smwgIQSortingEnabled ) {
452 - if ( NULL == $sq->mOrderBy ) {
453 - $sql_options['ORDER BY'] = "page_title $this->mOrder "; // default
454 - } else {
455 - $sql_options['ORDER BY'] = "$sq->mOrderBy $this->mOrder ";
456 - }
457 - }
458 - }
459 -
460 - if ($this->mFormat == 'debug') { // DEBUG
461 - list( $startOpts, $useIndex, $tailOpts ) = $this->dbr->makeSelectOptions( $sql_options );
462 - $result = $sq->mDebug . " \n <b>Query</b>\n SELECT DISTINCT " . implode(',', $sq->mSelect) . " FROM " . $sq->mTables . " WHERE " . $sq->mConditions . $tailOpts . "; \n <b>Query options</b>\n";
463 - foreach ($sql_options as $key => $value) {
464 - $result .= " $key=$value";
465 - }
466 - return $result;
467 - }
468 -
469 - //*** Execute the query ***//
470 - $this->mQueryResult = $this->dbr->select(
471 - $sq->mTables,
472 - 'DISTINCT ' . implode(',', $sq->mSelect),
473 - $sq->mConditions,
474 - "SMW::InlineQuery" ,
475 - $sql_options );
476 -
477 - //*** Create the output ***//
478 -
479 - //No results, TODO: is there a better way than calling numRows (which counts all results)?
480 - if ( (!$this->mQueryResult) || (0 == $this->dbr->numRows( $this->mQueryResult )) ) return $this->mDefault;
481 -
482 - if ($this->mFormat == 'count') {
483 - $row = $this->dbr->fetchRow( $this->mQueryResult );
484 - if ($row['count'] !== '0') {
485 - return $this->mIntro . $row['count'];
486 - } else {
487 - if ($this->mDefault === '') $this->mDefault = '0';
488 - return $this->mDefault;
489 - }
490 - }
491 -
492 - $this->mDisplayCount = min($this->mLimit, $this->dbr->numRows( $this->mQueryResult ));
493 -
494 - // Cases in which to print the subject:
495 - if ((!$sq->mFixedSubject) || (0 == count($sq->mPrint)) || (NULL != $this->mMainLabel)) {
496 - if (NULL == $this->mMainLabel) {
497 - $sq->mPrint = array('' => array('',SMW_IQ_PRINT_RSEL,'page_')) + $sq->mPrint;
498 - } else {
499 - $sq->mPrint = array('' => array($this->mMainLabel,SMW_IQ_PRINT_RSEL,'page_')) + $sq->mPrint;
500 - }
501 - }
502 -
503 - // Guess suitable format if 'auto' is selected
504 - if ( 'auto' == $this->mFormat ) {
505 - if ( (count($sq->mPrint)>1) && ($this->mLimit > 0) )
506 - $this->mFormat = 'table';
507 - else $this->mFormat = 'list';
508 - }
509 -
510 - switch ($this->mFormat) {
511 - case 'table': case 'broadtable':
512 - $printer = new SMWTablePrinter($this,$sq);
513 - break;
514 - case 'ul': case 'ol': case 'list':
515 - $printer = new SMWListPrinter($this,$sq);
516 - break;
517 - case 'timeline': case 'eventline':
518 - $printer = new SMWTimelinePrinter($this,$sq);
519 - break;
520 - case 'embedded':
521 - $printer = new SMWEmbeddedPrinter($this,$sq);
522 - break;
523 - case 'template':
524 - $printer = new SMWTemplatePrinter($this,$sq);
525 - break;
526 - default: $printer = new SMWListPrinter($this,$sq);
527 - }
528 - $result = $printer->printResult();
529 - $this->dbr->freeResult($this->mQueryResult); // Things that should be free: #42 "Possibly large query results"
530 -
531 - return $result;
532 - }
533 -
534 - /*********************************************************************/
535 - /* Methods for query parsing and execution */
536 - /*********************************************************************/
537 -
538 - /**
539 - * Callback used for extracting sub queries from a query, and replacing
540 - * them by some reference for later evaluation.
541 - */
542 - function extractSubQuery($matches) {
543 - global $smwgIQSubQueriesEnabled;
544 - if ($smwgIQSubQueriesEnabled) {
545 - $this->mSubQueries[$this->mSQCount] = $matches[1];
546 - return '+' . $this->mSQCount++;
547 - } else { return '+'; } // just do a wildcard search instead of processing the subquery
548 - }
549 -
550 - /**
551 - * Basic function for extracting a query from a user-supplied string.
552 - * The result is an object of type SMWSQLQuery.
553 - */
554 - private function parseQuery($querytext) {
555 - global $wgContLang, $smwgIQDisjunctiveQueriesEnabled, $smwgIQSubcategoryInclusions, $smwgIQMaxConditions, $smwgIQMaxTables, $smwgIQMaxPrintout;
556 -
557 - // Extract subqueries:
558 - $querytext = preg_replace_callback("/<q>(.*)<\/q>/",
559 - array(&$this,'extractSubQuery'),$querytext);
560 - // Extract queryparts:
561 - $queryparts = preg_split("/[\s\n]*\[\[|\]\][\s\n]*\[\[|\]\][\s\n]*/",$querytext,-1,PREG_SPLIT_NO_EMPTY);
562 -
563 - $result = new SMWSQLQuery();
564 - $cat_sep = $wgContLang->getNsText(NS_CATEGORY) . ":";
565 - $has_namespace_conditions = false; //flag for deciding on including default namespace restrictions
566 -
567 - $pagetable = 't' . $this->mRename++;
568 - $result->mSelect = array($pagetable . '.page_id', $pagetable . '.page_title' , $pagetable . '.page_namespace'); // always select subject
569 - $result->mTables = $this->dbr->tableName('page') . " AS $pagetable";
570 -
571 - foreach ($queryparts as $q) {
572 - $qparts = preg_split("/(::|:=[><]?|^$cat_sep)/", $q, 2, PREG_SPLIT_DELIM_CAPTURE);
573 - if (count($qparts)<=2) { // $q was not something like "xxx:=yyy", probably a fixed subject
574 - $qparts = array('','',$q); // this saves a lot of code below ;-)
575 - }
576 - $op = $qparts[1];
577 -
578 - if (mb_substr($qparts[2],0,1) == '*') { // conjunct is a print command
579 - if ( $this->mPrintoutCount < $smwgIQMaxPrintout ) {
580 - $altpos = mb_strpos($qparts[2],'|');
581 - if (false !== $altpos) {
582 - $label = htmlspecialchars(mb_substr($qparts[2],$altpos+1));
583 - $qparts[2] = mb_substr($qparts[2],0,$altpos);
584 - } else {
585 - $label = ucfirst($qparts[0]);
586 - if ( '' === $label) $label = NULL;
587 - }
588 - if ($cat_sep == $op) { // eventually print all categories for the selected subjects
589 - if (NULL === $label) $label = $wgContLang->getNSText(NS_CATEGORY);
590 - $result->mPrint['C'] = array($label,SMW_IQ_PRINT_CATS);
591 - } elseif ( '::' == $op ) {
592 - $result->mPrint['R:' . $qparts[0]] = array($this->makeTitleString($wgContLang->getNsText(SMW_NS_RELATION) . ':' . $qparts[0],$label,true),
593 - SMW_IQ_PRINT_RELS,smwfNormalTitleDBKey($qparts[0]));
594 - } elseif ( ':=' == $op ) {
595 - $av = SMWDataValue::newAttributeValue($qparts[0]);
596 - $unit = mb_substr($qparts[2],1);
597 - if ($unit != '') { // desired unit selected:
598 - $av->setDesiredUnits(array($unit));
599 - }
600 - $result->mPrint['A:' . $qparts[0]] = array($this->makeTitleString($wgContLang->getNsText(SMW_NS_ATTRIBUTE) . ':' . $qparts[0],$label,true),SMW_IQ_PRINT_ATTS,smwfNormalTitleDBKey($qparts[0]),$av);
601 - } // else: operators like :=> are not supported for printing and are silently ignored
602 - $this->mPrintoutCount++;
603 - }
604 - } elseif ( ($this->mConditionCount < $smwgIQMaxConditions) && ($this->mTableCount < $smwgIQMaxTables) ) { // conjunct is a real condition
605 - $sq_title = '';
606 - if (mb_substr($qparts[2],0,1) == '+') { // sub-query or wildcard search
607 - $subq_id = mb_substr($qparts[2],1);
608 - if ( ('' != $subq_id) && (array_key_exists($subq_id,$this->mSubQueries)) ) {
609 - $sq = $this->parseQuery($this->mSubQueries[$subq_id]);
610 - if ( ('' != $sq->mConditions) && ($this->mConditionCount < $smwgIQMaxConditions) && ($this->mTableCount < $smwgIQMaxTables) ) {
611 - $result->mTables .= ',' . $sq->mTables;
612 - if ( '' != $result->mConditions ) $result->mConditions .= ' AND ';
613 - $result->mConditions .= '(' . $sq->mConditions . ')';
614 - $sq_title = $sq->mSelect[1];
615 - $sq_namespace = $sq->mSelect[2];
616 - } else {
617 - $values = array(); // ignore sub-query and make a wildcard search
618 - }
619 - }
620 - $values = array();
621 - } elseif ($smwgIQDisjunctiveQueriesEnabled) { // get single values
622 - $values = explode('||', $qparts[2]);
623 - } else {
624 - $values = array($qparts[2]);
625 - }
626 - $or_conditions = array(); //store disjunctive SQL conditions; simplifies serialisation below
627 - $condition = ''; // new sql condition
628 - $curtable = 't' . $this->mRename++; // alias for the current table
629 -
630 - if ($cat_sep == $op ) { // condition on category membership
631 - $result->mTables .= ',' . $this->dbr->tableName('categorylinks') . " AS $curtable";
632 - $condition = "$pagetable.page_id=$curtable.cl_from";
633 - // TODO: make subcat-inclusion more efficient
634 - foreach ($values as $idx => $v) {
635 - $values[$idx] = smwfNormalTitleDBKey($v);
636 - }
637 - $this->includeSubcategories($values,$smwgIQSubcategoryInclusions);
638 - foreach ($values as $v) {
639 - $or_conditions[] = "$curtable.cl_to=" . $this->dbr->addQuotes($v);
640 - }
641 - } elseif ('::' == $op ) { // condition on relations
642 - $relation = smwfNormalTitleDBKey($qparts[0]);
643 - $result->mTables .= ',' . $this->dbr->tableName('smw_relations') . " AS $curtable";
644 - $condition = "$pagetable.page_id=$curtable.subject_id AND $curtable.relation_title=" . $this->dbr->addQuotes($relation);
645 - if ('' != $sq_title) { // objects supplied by subquery
646 - $condition .= " AND $curtable.object_title=$sq_title AND $curtable.object_namespace=$sq_namespace";
647 - } else { // objects given explicitly
648 - // TODO: including redirects should become more efficient
649 - // (maybe by not creating a full symmetric transitive closure and
650 - // using a simple SQL query instead)
651 - // Also, redirects are not taken into account for sub-queries
652 - // anymore now.
653 - $vtitles = array();
654 - foreach ($values as $v) {
655 - $vtitle = Title::newFromText($v);
656 - if (NULL != $vtitle) {
657 - $id = $vtitle->getArticleID(); // create index for title
658 - if (0 == $id) $id = $vtitle->getPrefixedText();
659 - $vtitles[$vtitle->getArticleID()] = $vtitle; //convert values to titles
660 - }
661 - }
662 - $vtitles = $this->normalizeRedirects($vtitles);
663 -
664 - // search for values
665 - foreach ($vtitles as $vtitle) {
666 - //if (NULL != $vtitle) {
667 - $or_conditions[] = "$curtable.object_title=" . $this->dbr->addQuotes($vtitle->getDBKey()) . " AND $curtable.object_namespace=" . $vtitle->getNamespace();
668 - //}
669 - }
670 - }
671 - if ($relation == $this->mSortkey) {
672 - $result->mOrderBy = "$curtable.object_title";
673 - }
674 - } elseif ('' == $op) { // fixed subject, possibly namespace restriction
675 - if ('' != $sq_title) { // objects supplied by subquery
676 - $condition = "$pagetable.page_title=$sq_title AND $pagetable.page_namespace=$sq_namespace";
677 - } else { // objects given explicitly
678 - //Note: I do not think we have to include redirects here. Redirects should not
679 - // have annotations, so one can just write up the query correctly! -- mak
680 - foreach ($values as $v) {
681 - $v = smwfNormalTitleDBKey($v);
682 - if ((mb_strlen($v)>2) && (':' == mb_substr($v,0,1))) $v = mb_substr($v,1); // remove initial ':'
683 - // TODO: should this be done when normalizing the title???
684 - $ns_idx = $wgContLang->getNsIndex(mb_substr($v,0,-2)); // assume format "Namespace:+"
685 - if ((false === $ns_idx)||(mb_substr($v,-2,2) !== ':+')) {
686 - $vtitle = Title::newFromText($v);
687 - if (NULL != $vtitle) {
688 - $or_conditions[] = "$pagetable.page_title=" . $this->dbr->addQuotes($vtitle->getDBKey()) . " AND $pagetable.page_namespace=" . $vtitle->getNamespace();
689 - $result->mFixedSubject = true; // by default, only this case is a really "fixed" subject (even though it could still be combined with others); TODO: find a better way for deciding whether to show the first column or not
690 - $has_namespace_conditions = true; // fixed subjects might have namespaces, so we must discard any overall namespace restrictions to retrieve results
691 - }
692 - } else {
693 - $or_conditions[] = "$pagetable.page_namespace=$ns_idx";
694 - $has_namespace_conditions = true;
695 - }
696 - }
697 - }
698 - } else { // some attribute operator
699 - $attribute = smwfNormalTitleDBKey($qparts[0]);
700 - $av = SMWDataValue::newAttributeValue($attribute);
701 - switch ($op) {
702 - case ':=>': $comparator = '>='; break;
703 - case ':=<': $comparator = '<='; break;
704 - default: $comparator = '=';
705 - }
706 - foreach ($values as $v) {
707 - $av->setUserValue($v);
708 - if ($av->isValid()) {// TODO: it should be possible to ignore the unit for many types
709 - if ($av->isNumeric()) {
710 - $or_conditions[] = "$curtable.value_num$comparator" . $av->getNumericValue() . " AND $curtable.value_unit=" . $this->dbr->addQuotes($av->getUnit());
711 - } else {
712 - $or_conditions[] = "$curtable.value_xsd$comparator" . $this->dbr->addQuotes($av->getXSDValue()) . " AND $curtable.value_unit=" . $this->dbr->addQuotes($av->getUnit());
713 - }
714 - }
715 - }
716 - $result->mTables .= ',' . $this->dbr->tableName('smw_attributes') . " AS $curtable";
717 - $condition = "$pagetable.page_id=$curtable.subject_id AND $curtable.attribute_title=" . $this->dbr->addQuotes($attribute);
718 - if ($attribute == $this->mSortkey) {
719 - if ($av->isNumeric()) $result->mOrderBy = $curtable . '.value_num';
720 - else $result->mOrderBy = $curtable . '.value_xsd';
721 - }
722 - }
723 -
724 - // build query from disjuncts:
725 - $firstcond = true;
726 - foreach ($or_conditions as $cond) {
727 - if ($this->mConditionCount >= $smwgIQMaxConditions) break;
728 - if ($firstcond) {
729 - if ('' != $condition) $condition .= ' AND ';
730 - $condition .= '((';
731 - $firstcond = false;
732 - } else {
733 - $condition .= ') OR (';
734 - $this->mConditionCount++; // (the first condition is counted with the main part)
735 - }
736 - $condition .= $cond;
737 - }
738 - if (count($or_conditions)>0) $condition .= '))';
739 - if ('' != $condition) {
740 - if ('' != $result->mConditions) $result->mConditions .= ' AND ';
741 - $this->mConditionCount++;
742 - $this->mTableCount++;
743 - $result->mConditions .= $condition;
744 - }
745 - }
746 - }
747 -
748 - if (!$has_namespace_conditions) { // restrict namespaces to default setting
749 - global $smwgIQSearchNamespaces;
750 - if ($smwgIQSearchNamespaces !== NULL) {
751 - $condition = '';
752 - foreach ($smwgIQSearchNamespaces as $nsid) {
753 - if ($condition == '') {
754 - $condition .= '((';
755 - } else {
756 - $condition .= ') OR (';
757 - }
758 - $condition .= "$pagetable.page_namespace=$nsid";
759 - $this->mConditionCount++; // we do not check whether this exceeds the max, since it is somehow crucial and controlled by the site admins anyway
760 - }
761 - if ($condition != '') $condition .= '))';
762 - if ('' != $result->mConditions) $result->mConditions .= ' AND ';
763 - $result->mConditions .= $condition;
764 - }
765 - }
766 -
767 - $result->mDebug = "\n <b>Statistics</b> \n Condiditions: $this->mConditionCount Tables: $this->mTableCount Printouts: $this->mPrintoutCount"; //DEBUG
768 -
769 - return $result;
770 - }
771 -
772 - /**
773 - * Turns an array of article titles into an array of all these articles and
774 - * the transitive closure of all redirects from and to this articles.
775 - * Or, simply said: it gets all aliases of what you put in.
776 - *
777 - * FIXME: store intermediate result in a temporary DB table on the heap; much faster!
778 - * FIXME: include an option to ignore multiple redirects, or, even better, make a
779 - * plugable SQL-query to compute one-step back-and-forth redirects without any
780 - * materialisation.
781 - * FIXME: Actually, we should not support mutliple redirects, since they are discouraged
782 - * by MediaWiki anyway.
783 - */
784 - private function normalizeRedirects(&$titles) {
785 - global $smwgIQRedirectNormalization;
786 - if (!$smwgIQRedirectNormalization) {
787 - return $titles;
788 - }
789 -
790 - $stable = 0;
791 - //$check_titles = array_diff( $titles , array() ); // Copies the array
792 - $check_titles = array();
793 - $title_keys = array();
794 - foreach ($titles as $title) {
795 - $check_titles[] = $title;
796 - $title_keys[$title->getPrefixedText()] = true;
797 - }
798 - while ($stable<30) { // emergency stop after 30 iterations
799 - $stable++;
800 - $new_titles = array();
801 - foreach ( $check_titles as $title ) {
802 - // there...
803 - if ( 0 != $title->getArticleID() ) {
804 - $res = $this->dbr->select(
805 - array( 'page' , 'pagelinks' ),
806 - array( 'pl_title', 'pl_namespace'),
807 - array( 'page_id = ' . $title->getArticleID(),
808 - 'page_is_redirect = 1',
809 - 'page_id = pl_from' ) ,
810 - 'SMW::InlineQuery::NormalizeRedirects', array('LIMIT' => '1') );
811 - while ( $res && $row = $this->dbr->fetchRow( $res )) {
812 - $new_title = Title::newFromText($row['pl_title'], $row['pl_namespace']);
813 - if (NULL != $new_title) {
814 - $id = $new_title->getPrefixedText();
815 - if (!array_key_exists( $id , $title_keys)) {
816 - $title_keys[$id] = true;
817 - $titles[] = $new_title;
818 - $new_titles[] = $new_title;
819 - }
820 - }
821 - }
822 - $this->dbr->freeResult( $res );
823 - }
824 -
825 - // ... and back again
826 - $res = $this->dbr->select(
827 - array( 'page' , 'pagelinks' ),
828 - array( 'page_title', 'page_namespace' ),
829 - array( 'pl_title = ' . $this->dbr->addQuotes( $title->getDBkey() ),
830 - 'pl_namespace = ' . $this->dbr->addQuotes( $title->getNamespace() ),
831 - 'page_is_redirect = 1',
832 - 'page_id = pl_from' ) ,
833 - 'SMW::InlineQuery::NormalizeRedirects', array('LIMIT' => '1'));
834 - while ( $res && $row = $this->dbr->fetchRow( $res )) {
835 - $new_title = Title::newFromText( $row['page_title'], $row['page_namespace'] );
836 - $id = $new_title->getPrefixedText();
837 - if (!array_key_exists( $id , $title_keys)) {
838 - $title_keys[$id] = true;
839 - $titles[] = $new_title;
840 - $new_titles[] = $new_title;
841 - }
842 - }
843 - $this->dbr->freeResult( $res );
844 - }
845 - if (count($new_titles) == 0) {
846 - $stable= 500; // stop
847 - } else {
848 - $check_titles = array();
849 - foreach ($new_titles as $title) { // careful copy (do not accidently copy array by ref)
850 - $check_titles[] = $title;
851 - }
852 - }
853 - }
854 - return $titles;
855 - }
856 -
857 - /**
858 - * Turns an array of categories to an array of categories and its subcategories.
859 - * The number of relations followed is given by $levels.
860 - *
861 - * FIXME: store intermediate result in a temporary DB table on the heap; much faster!
862 - */
863 - private function includeSubcategories( &$categories, $levels ) {
864 - if (0 == $levels) return $categories;
865 -
866 - $checkcategories = array_diff($categories, array()); // Copies the array
867 - for ($level=$levels; $level>0; $level--) {
868 - $newcategories = array();
869 - foreach ($checkcategories as $category) {
870 - $res = $this->dbr->select( // make the query
871 - array( 'categorylinks', 'page' ),
872 - array( 'page_title' ),
873 - array( 'cl_from = page_id ',
874 - 'page_namespace = '. NS_CATEGORY,
875 - 'cl_to = '. $this->dbr->addQuotes($category) ) ,
876 - "SMW::SubCategory" );
877 - if ( $res ) {
878 - while ( $res && $row = $this->dbr->fetchRow( $res )) {
879 - if ( array_key_exists( 'page_title' , $row) ) {
880 - $new_category = $row[ 'page_title' ];
881 - if (!in_array($new_category, $categories)) {
882 - $newcategories[] = smwfNormalTitleDBKey($new_category);
883 - }
884 - }
885 - }
886 - $this->dbr->freeResult( $res );
887 - }
888 - }
889 - if (count($newcategories) == 0) {
890 - return $categories;
891 - } else {
892 - $categories = array_merge($categories, $newcategories);
893 - }
894 - $checkcategories = array_diff($newcategories, array());
895 - }
896 - return $categories;
897 - }
898 -
899 - /**
900 - * Create output string for an article title (possibly including namespace)
901 - * as given by $text.
902 - *
903 - * $subject states whether the given title is the subject (to which special
904 - * settings for linking apply).
905 - * If $label is null the standard label of the given article will be used.
906 - * If $label is the empty string, an empty string is returned.
907 - * $linked states whether the result should be a hyperlink
908 - * $exists states whether $text is known to be an existing article, in which
909 - * case we can save a DB lookup when creating links.
910 - */
911 - public function makeTitleString($text,$label,$linked,$exists=false) {
912 - if ( '' === $label) return ''; // no link desired
913 - $title = Title::newFromText( $text );
914 - if ($title === NULL) {
915 - return $text; // TODO maybe report an error here?
916 - } elseif ( $linked ) {
917 - if ( NULL === $label ) $label = $title->getText();
918 - if ($this->mHTMLPrinter) {
919 - if ($exists)
920 - return $this->mLinker->makeKnownLinkObj($title, $label);
921 - else return $this->mLinker->makeLinkObj($title, $label);
922 - } else {
923 - return '[[' . $title->getPrefixedText() . '|' . $label . ']]';
924 - }
925 - } else {
926 - return $title->getText(); // TODO: shouldn't this default to $label?
927 - }
928 - }
929 -
930 -}
931 -
932 -/*********************************************************************/
933 -/* Iterators for hiding SQL details from printers */
934 -/*********************************************************************/
935 -
936 -/**
937 - * "Empty" iterator. Used as return value in case of errors.
938 - */
939 -class SMWEmptyIterator {
940 - public function getNext() {
941 - return false;
942 - }
943 -}
944 -
945 -/**
946 - * Iterator for category article titles returned as SQL result.
947 - */
948 -class SMWCategoryIterator {
949 - private $mRes;
950 - private $mDB;
951 - private $mIQ;
952 - private $mLinked;
953 -
954 - public function SMWCategoryIterator($iq, $db, $res, $linked) {
955 - $this->mIQ = $iq;
956 - $this->mDB = $db;
957 - $this->mRes = $res;
958 - $this->mLinked = $linked;
959 - }
960 -
961 - public function getNext() {
962 - global $wgContLang;
963 - $row = $this->mDB->fetchRow($this->mRes);
964 - if ($row)
965 - return array($this->mIQ->makeTitleString($wgContLang->getNsText(NS_CATEGORY) . ':' . $row['cl_to'],NULL,$this->mLinked));
966 - else return false;
967 - }
968 -}
969 -
970 -/**
971 - * Iterator for attribute values, returned by some SQL query.
972 - */
973 -class SMWAttributeIterator {
974 - private $mRes;
975 - private $mDB;
976 - private $mDatavalue;
977 -
978 - public function SMWAttributeIterator($db, $res, $datavalue) {
979 - $this->mDB = $db;
980 - $this->mRes = $res;
981 - $this->mDatavalue = $datavalue;
982 - }
983 -
984 - public function getNext() {
985 - $row = $this->mDB->fetchRow($this->mRes);
986 - if ($row) {
987 - $this->mDatavalue->setXSDValue($row['value_xsd'],$row['value_unit']);
988 - return array($this->mDatavalue->getStringValue(), $this->mDatavalue);
989 - } else return false;
990 - }
991 -}
992 -
993 -/**
994 - * Iterator for relation objects, returned by some SQL query.
995 - */
996 -class SMWRelationIterator {
997 - private $mRes;
998 - private $mDB;
999 - private $mIQ;
1000 - private $mLinked;
1001 -
1002 - public function SMWRelationIterator($iq, $db, $res, $linked) {
1003 - $this->mIQ = $iq;
1004 - $this->mDB = $db;
1005 - $this->mRes = $res;
1006 - $this->mLinked = $linked;
1007 - }
1008 -
1009 - public function getNext() {
1010 - global $wgContLang;
1011 - $row = $this->mDB->fetchRow($this->mRes);
1012 - if ($row) {
1013 - $namespace = $wgContLang->getNsText($row['object_namespace']);
1014 - return array($this->mIQ->makeTitleString($namespace . ':' . $row['object_title'],NULL,$this->mLinked), $namespace, $row['object_title']);
1015 - } else return false;
1016 - }
1017 -}
1018 -
1019 -/**
1020 - * Iterator wrapping a single article
1021 - */
1022 -class SMWSingleArticleIterator {
1023 - private $mTitle;
1024 - private $mNamespace;
1025 - private $mHasNext=true;
1026 - private $mIQ;
1027 - private $mLinked;
1028 - private $mSubject;
1029 -
1030 - public function SMWSingleArticleIterator($iq, $namespace, $title, $linked, $subject=false) {
1031 - $this->mIQ = $iq;
1032 - $this->mTitle = $title;
1033 - $this->mNamespace = $namespace;
1034 - $this->mLinked = $linked;
1035 - $this->mSubject = $subject;
1036 - }
1037 -
1038 - public function getNext() {
1039 - if ($this->mHasNext) {
1040 - $this->mHasNext = false;
1041 - return array($this->mIQ->makeTitleString($this->mNamespace . ':' . $this->mTitle,NULL,$this->mLinked,$this->mSubject),$this->mNamespace,$this->mTitle);
1042 - } else return false;
1043 - }
1044 -}
1045 -
1046 -?>
\ No newline at end of file

Status & tagging log