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 |