r89972 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r89971‎ | r89972 | r89973 >
Date:15:32, 13 June 2011
Author:devayon
Status:deferred
Tags:
Comment:
Added a working implementation of a query form. Split from Special Ask
Modified paths:
  • /trunk/extensions/SemanticMediaWiki/languages/SMW_Messages.php (modified) (history)
  • /trunk/extensions/SemanticMediaWiki/specials/AskSpecial/SMW_SpecialQueryCreator.php (modified) (history)

Diff [purge]

Index: trunk/extensions/SemanticMediaWiki/specials/AskSpecial/SMW_SpecialQueryCreator.php
@@ -2,10 +2,13 @@
33
44 /**
55 * This special page for Semantic MediaWiki implements a customisable form for
6 - * executing queries outside of articles. Results are generated by
7 - * SMW_SpecialAsk.php. This page is currently under development as part of
8 - * the Google Summer of Code 2011 Program.
 6+ * executing queries outside of articles. Results and format options are generated by
 7+ * SMW_SpecialAsk.php.
98 *
 9+ * Currently adapted from current contents of SMW_SpecialAsk.php
 10+ * This page is currently under development as part of the Google Summer of
 11+ * Code 2011 Program.
 12+ *
1013 * @file SMW_SpecialQueryCreator.php
1114 * @ingroup SMWSpecialPage
1215 * @ingroup SpecialPage
@@ -14,11 +17,16 @@
1518 * @author Yaron Koren
1619 * @author Sanyam Goyal
1720 * @author Jeroen De Dauw
18 - * @author Devayon Das
 21+ *
1922 *
2023 */
2124 class SMWQueryCreatorPage extends SpecialPage {
2225
 26+ protected $m_querystring = '';
 27+ protected $m_params = array();
 28+ protected $m_printouts = array();
 29+ protected $m_editquery = false;
 30+
2331 /**
2432 * Constructor.
2533 */
@@ -34,9 +42,845 @@
3543 */
3644 public function execute( $p ) {
3745 global $wgOut, $wgRequest, $smwgQEnabled;
38 - $this->setHeaders();
39 - $wgOut->addWikiText( "Hi. This page isn't ready for viewing yet. May I suggest you view [[Special:Ask]]");
 46+
 47+ $this->setHeaders();
 48+ wfProfileIn( 'doSpecialAsk (SMW)' );
 49+
 50+ if ( !$smwgQEnabled ) {
 51+ $wgOut->addHTML( '<br />' . wfMsg( 'smw_iq_disabled' ) );
 52+ } else {
 53+ if ( $wgRequest->getCheck( 'showformatoptions' ) ) {
 54+ // handle Ajax action
 55+ $format = $wgRequest->getVal( 'showformatoptions' );
 56+ $params = $wgRequest->getArray( 'params' );
 57+ $wgOut->disable();
 58+ echo $this->showFormatOptions( $format, $params );
 59+ } else {
 60+ $this->extractQueryParameters( $p );
 61+ $this->makeHTMLResult();
 62+ }
 63+ }
 64+
 65+ SMWOutputs::commitToOutputPage( $wgOut ); // make sure locally collected output data is pushed to the output!
 66+ wfProfileOut( 'doSpecialAsk (SMW)' );
4067 }
4168
 69+ /**
 70+ * Extracts Query and option parameters, especially q (the query),
 71+ * offset and limit (number of results per-page), p (the result format and options), po (printouts) and
 72+ * eq(show/hide the query editor).
 73+ *
 74+ * This code rather hacky since there are many ways to call that special page, the most involved of
 75+ * which is the way that this page calls itself when data is submitted via the form (since the shape
 76+ * of the parameters then is governed by the UI structure, as opposed to being governed by reason).
 77+ *
 78+ * @param string $p
 79+ */
 80+ protected function extractQueryParameters( $p ) {
 81+ global $wgRequest, $smwgQMaxInlineLimit;
 82+
 83+ // First make all inputs into a simple parameter list that can again be parsed into components later.
 84+ if ( $wgRequest->getCheck( 'q' ) ) { // called by own Special, ignore full param string in that case
 85+ $query_val = $wgRequest->getVal( 'p' );
 86+
 87+ if ( !empty( $query_val ) )
 88+ // p is used for any additional parameters in certain links.
 89+ $rawparams = SMWInfolink::decodeParameters( $query_val, false );
 90+ else {
 91+ $query_values = $wgRequest->getArray( 'p' );
 92+
 93+ if ( is_array( $query_values ) ) {
 94+ foreach ( $query_values as $key => $val ) {
 95+ if ( empty( $val ) ) unset( $query_values[$key] );
 96+ }
 97+ }
 98+
 99+ // p is used for any additional parameters in certain links.
 100+ $rawparams = SMWInfolink::decodeParameters( $query_values, false );
 101+ }
 102+ } else { // called from wiki, get all parameters
 103+ $rawparams = SMWInfolink::decodeParameters( $p, true );
 104+ }
 105+
 106+ // Check for q= query string, used whenever this special page calls itself (via submit or plain link):
 107+ $this->m_querystring = $wgRequest->getText( 'q' );
 108+ if ( $this->m_querystring != '' ) {
 109+ $rawparams[] = $this->m_querystring;
 110+ }
 111+
 112+ // Check for param strings in po (printouts), appears in some links and in submits:
 113+ $paramstring = $wgRequest->getText( 'po' );
 114+
 115+ if ( $paramstring != '' ) { // parameters from HTML inputs
 116+ $ps = explode( "\n", $paramstring ); // params separated by newlines here (compatible with text-input for printouts)
 117+
 118+ foreach ( $ps as $param ) { // add initial ? if omitted (all params considered as printouts)
 119+ $param = trim( $param );
 120+
 121+ if ( ( $param != '' ) && ( $param { 0 } != '?' ) ) {
 122+ $param = '?' . $param;
 123+ }
 124+
 125+ $rawparams[] = $param;
 126+ }
 127+ }
 128+
 129+ // Now parse parameters and rebuilt the param strings for URLs.
 130+ SMWQueryProcessor::processFunctionParams( $rawparams, $this->m_querystring, $this->m_params, $this->m_printouts );
 131+
 132+ // Try to complete undefined parameter values from dedicated URL params.
 133+ if ( !array_key_exists( 'format', $this->m_params ) ) {
 134+ $this->m_params['format'] = 'broadtable';
 135+ }
 136+
 137+ if ( !array_key_exists( 'order', $this->m_params ) ) {
 138+ $order_values = $wgRequest->getArray( 'order' );
 139+
 140+ if ( is_array( $order_values ) ) {
 141+ $this->m_params['order'] = '';
 142+
 143+ foreach ( $order_values as $order_value ) {
 144+ if ( $order_value == '' ) $order_value = 'ASC';
 145+ $this->m_params['order'] .= ( $this->m_params['order'] != '' ? ',' : '' ) . $order_value;
 146+ }
 147+ }
 148+ }
 149+
 150+ $this->m_num_sort_values = 0;
 151+
 152+ if ( !array_key_exists( 'sort', $this->m_params ) ) {
 153+ $sort_values = $wgRequest->getArray( 'sort' );
 154+ if ( is_array( $sort_values ) ) {
 155+ $this->m_params['sort'] = implode( ',', $sort_values );
 156+ $this->m_num_sort_values = count( $sort_values );
 157+ }
 158+ }
 159+
 160+ // Find implicit ordering for RSS -- needed for downwards compatibility with SMW <=1.1
 161+ /*
 162+ if ( ($this->m_params['format'] == 'rss') && ($this->m_params['sort'] == '') && ($sortcount==0)) {
 163+ foreach ($this->m_printouts as $printout) {
 164+ if ((strtolower($printout->getLabel()) == "date") && ($printout->getTypeID() == "_dat")) {
 165+ $this->m_params['sort'] = $printout->getTitle()->getText();
 166+ $this->m_params['order'] = 'DESC';
 167+ }
 168+ }
 169+ }
 170+ */
 171+
 172+ if ( !array_key_exists( 'offset', $this->m_params ) ) {
 173+ $this->m_params['offset'] = $wgRequest->getVal( 'offset' );
 174+ if ( $this->m_params['offset'] == '' ) $this->m_params['offset'] = 0;
 175+ }
 176+
 177+ if ( !array_key_exists( 'limit', $this->m_params ) ) {
 178+ $this->m_params['limit'] = $wgRequest->getVal( 'limit' );
 179+
 180+ if ( $this->m_params['limit'] == '' ) {
 181+ $this->m_params['limit'] = ( $this->m_params['format'] == 'rss' ) ? 10 : 20; // Standard limit for RSS.
 182+ }
 183+ }
 184+
 185+ $this->m_params['limit'] = min( $this->m_params['limit'], $smwgQMaxInlineLimit );
 186+
 187+ $this->m_editquery = ( $wgRequest->getVal( 'eq' ) == 'no' ) || ( $this->m_querystring == '' );
 188+ }
 189+
 190+ /**
 191+ * Creates and adds the JavaScript and JS needed for autocompletion to $wgOut.
 192+ *
 193+ * @bug This method asks the store for all used and unused property names, which can be very expensive. It does not even use a limit to restrict the number of results. Cheaper ways to get the labels need to be sought. What sense does it make to add unused properties to the options on autocomplete for a query interface?
 194+ * @since 1.5.2
 195+ */
 196+ protected static function addAutocompletionJavascriptAndCSS() {
 197+ global $wgOut, $smwgScriptPath, $smwgJQueryIncluded, $smwgJQueryUIIncluded;
 198+
 199+ // Add CSS and JavaScript for jQuery and jQuery UI.
 200+ $wgOut->addExtensionStyle( "$smwgScriptPath/skins/jquery-ui/base/jquery.ui.all.css" );
 201+
 202+ $scripts = array();
 203+
 204+ if ( !$smwgJQueryIncluded ) {
 205+ $realFunction = array( 'OutputPage', 'includeJQuery' );
 206+ if ( is_callable( $realFunction ) ) {
 207+ $wgOut->includeJQuery();
 208+ } else {
 209+ $scripts[] = "$smwgScriptPath/libs/jquery-1.4.2.min.js";
 210+ }
 211+
 212+ $smwgJQueryIncluded = true;
 213+ }
 214+
 215+ if ( !$smwgJQueryUIIncluded ) {
 216+ $scripts[] = "$smwgScriptPath/libs/jquery-ui/jquery.ui.core.min.js";
 217+ $scripts[] = "$smwgScriptPath/libs/jquery-ui/jquery.ui.widget.min.js";
 218+ $scripts[] = "$smwgScriptPath/libs/jquery-ui/jquery.ui.position.min.js";
 219+ $scripts[] = "$smwgScriptPath/libs/jquery-ui/jquery.ui.autocomplete.min.js";
 220+ $smwgJQueryUIIncluded = true;
 221+ }
 222+
 223+ foreach ( $scripts as $js ) {
 224+ $wgOut->addScriptFile( $js );
 225+ }
 226+
 227+ /* collect property names for autocomplete */
 228+ $propertyNames[] = array();
 229+ $results = smwfGetStore()->getPropertiesSpecial();
 230+
 231+ foreach ( $results as $result ) {
 232+ $propertyNames[] = $result[0]->getLabel();
 233+ }
 234+
 235+ $results = smwfGetStore()->getUnusedPropertiesSpecial();
 236+
 237+ foreach ( $results as $result ) {
 238+ $propertyNames[] = $result->getLabel();
 239+ }
 240+
 241+ sort( $propertyNames );
 242+
 243+ $properties_po = "[";
 244+ foreach ( $propertyNames as $i => $property ) {
 245+ if ( $i > 0 ) {
 246+ $properties_po .= ", ";
 247+ }
 248+ $properties_po .= "'?" . $property . "'";
 249+ }
 250+ $properties_po .= "]";
 251+
 252+ $javascript_autocomplete_text = <<<END
 253+<script type="text/javascript">
 254+function split(val) {
 255+ return val.split('\\n');
42256 }
 257+function extractLast(term) {
 258+ return split(term).pop();
 259+}
 260+function escapeQuestion(term){
 261+ if (term.substring(0, 1) == "?") {
 262+ return term.substring(1);
 263+ } else {
 264+ return term;
 265+ }
 266+}
43267
 268+jQuery.noConflict();
 269+/* extending jQuery functions for custom highligting */
 270+jQuery.ui.autocomplete.prototype._renderItem = function( ul, item) {
 271+ var term_without_q = escapeQuestion(extractLast(this.term));
 272+ var re = new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term_without_q.replace("/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi", "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi");
 273+ var loc = item.label.search(re);
 274+ if (loc >= 0) {
 275+ var t = item.label.substr(0, loc) + '<strong>' + item.label.substr(loc, term_without_q.length) + '</strong>' + item.label.substr(loc + term_without_q.length);
 276+ } else {
 277+ var t = item.label;
 278+ }
 279+ jQuery( "<li></li>" )
 280+ .data( "item.autocomplete", item )
 281+ .append( " <a>" + t + "</a>" )
 282+ .appendTo( ul );
 283+};
 284+
 285+///* extending jquery functions for custom autocomplete matching */
 286+jQuery.extend( jQuery.ui.autocomplete, {
 287+ filter: function(array, term) {
 288+ var matcher = new RegExp("\\\b" + jQuery.ui.autocomplete.escapeRegex(term), "i" );
 289+ return jQuery.grep( array, function(value) {
 290+ return matcher.test( value.label || value.value || value );
 291+ });
 292+ }
 293+});
 294+
 295+jQuery(document).ready(function(){
 296+ jQuery("#add_property").autocomplete({
 297+ minLength: 1,
 298+ source: function(request, response) {
 299+ // delegate back to autocomplete, but extract the last term
 300+ response(jQuery.ui.autocomplete.filter({$properties_po}, escapeQuestion(extractLast(request.term))));
 301+ },
 302+ focus: function() {
 303+ // prevent value inserted on focus
 304+ return false;
 305+ },
 306+ select: function(event, ui) {
 307+ var terms = split( this.value );
 308+ // remove the current input
 309+ terms.pop();
 310+ // add the selected item
 311+ terms.push( ui.item.value );
 312+ // add placeholder to get the comma-and-space at the end
 313+ terms.push("");
 314+ this.value = terms.join("\\n");
 315+ return false;
 316+ }
 317+ });
 318+});
 319+</script>
 320+
 321+END;
 322+
 323+ $wgOut->addScript( $javascript_autocomplete_text );
 324+ }
 325+
 326+ /**
 327+ * TODO: document
 328+ */
 329+ protected function makeHTMLResult() {
 330+ global $wgOut, $smwgAutocompleteInSpecialAsk;
 331+
 332+ $delete_msg = wfMsg( 'delete' );
 333+
 334+ // Javascript code for the dynamic parts of the page
 335+ $javascript_text = <<<END
 336+<script type="text/javascript">
 337+function updateOtherOptions(strURL) {
 338+ jQuery.ajax({ url: strURL, context: document.body, success: function(data){
 339+ jQuery("#other_options").html(data);
 340+ }});
 341+}
 342+
 343+// code for handling adding and removing the "sort" inputs
 344+var num_elements = {$this->m_num_sort_values};
 345+
 346+function addInstance(starter_div_id, main_div_id) {
 347+ var starter_div = document.getElementById(starter_div_id);
 348+ var main_div = document.getElementById(main_div_id);
 349+
 350+ //Create the new instance
 351+ var new_div = starter_div.cloneNode(true);
 352+ var div_id = 'sort_div_' + num_elements;
 353+ new_div.className = 'multipleTemplate';
 354+ new_div.id = div_id;
 355+ new_div.style.display = 'block';
 356+
 357+ var children = new_div.getElementsByTagName('*');
 358+ var x;
 359+ for (x = 0; x < children.length; x++) {
 360+ if (children[x].name)
 361+ children[x].name = children[x].name.replace(/_num/, '[' + num_elements + ']');
 362+ }
 363+
 364+ //Create 'delete' link
 365+ var remove_button = document.createElement('span');
 366+ remove_button.innerHTML = '[<a href="javascript:removeInstance(\'sort_div_' + num_elements + '\')">{$delete_msg}</a>]';
 367+ new_div.appendChild(remove_button);
 368+
 369+ //Add the new instance
 370+ main_div.appendChild(new_div);
 371+ num_elements++;
 372+}
 373+
 374+function removeInstance(div_id) {
 375+ var olddiv = document.getElementById(div_id);
 376+ var parent = olddiv.parentNode;
 377+ parent.removeChild(olddiv);
 378+}
 379+</script>
 380+
 381+END;
 382+
 383+ $wgOut->addScript( $javascript_text );
 384+
 385+ if ( $smwgAutocompleteInSpecialAsk ) {
 386+ self::addAutocompletionJavascriptAndCSS();
 387+ }
 388+
 389+ $result = '';
 390+ $result_mime = false; // output in MW Special page as usual
 391+
 392+ // build parameter strings for URLs, based on current settings
 393+ $urltail = '&q=' . urlencode( $this->m_querystring );
 394+
 395+ $tmp_parray = array();
 396+ foreach ( $this->m_params as $key => $value ) {
 397+ if ( !in_array( $key, array( 'sort', 'order', 'limit', 'offset', 'title' ) ) ) {
 398+ $tmp_parray[$key] = $value;
 399+ }
 400+ }
 401+
 402+ $urltail .= '&p=' . urlencode( SMWInfolink::encodeParameters( $tmp_parray ) );
 403+ $printoutstring = '';
 404+
 405+ foreach ( $this->m_printouts as $printout ) {
 406+ $printoutstring .= $printout->getSerialisation() . "\n";
 407+ }
 408+
 409+ if ( $printoutstring != '' ) $urltail .= '&po=' . urlencode( $printoutstring );
 410+ if ( array_key_exists( 'sort', $this->m_params ) ) $urltail .= '&sort=' . $this->m_params['sort'];
 411+ if ( array_key_exists( 'order', $this->m_params ) ) $urltail .= '&order=' . $this->m_params['order'];
 412+
 413+ if ( $this->m_querystring != '' ) {
 414+ $queryobj = SMWQueryProcessor::createQuery( $this->m_querystring, $this->m_params, SMWQueryProcessor::SPECIAL_PAGE , $this->m_params['format'], $this->m_printouts );
 415+ $res = smwfGetStore()->getQueryResult( $queryobj );
 416+
 417+ // Try to be smart for rss/ical if no description/title is given and we have a concept query:
 418+ if ( $this->m_params['format'] == 'rss' ) {
 419+ $desckey = 'rssdescription';
 420+ $titlekey = 'rsstitle';
 421+ } elseif ( $this->m_params['format'] == 'icalendar' ) {
 422+ $desckey = 'icalendardescription';
 423+ $titlekey = 'icalendartitle';
 424+ } else { $desckey = false; }
 425+
 426+ if ( ( $desckey ) && ( $queryobj->getDescription() instanceof SMWConceptDescription ) &&
 427+ ( !isset( $this->m_params[$desckey] ) || !isset( $this->m_params[$titlekey] ) ) ) {
 428+ $concept = $queryobj->getDescription()->getConcept();
 429+
 430+ if ( !isset( $this->m_params[$titlekey] ) ) {
 431+ $this->m_params[$titlekey] = $concept->getText();
 432+ }
 433+
 434+ if ( !isset( $this->m_params[$desckey] ) ) {
 435+ $dv = end( smwfGetStore()->getPropertyValues( SMWWikiPageValue::makePageFromTitle( $concept ), new SMWDIProperty( '_CONC' ) ) );
 436+ if ( $dv instanceof SMWConceptValue ) {
 437+ $this->m_params[$desckey] = $dv->getDocu();
 438+ }
 439+ }
 440+ }
 441+
 442+ $printer = SMWQueryProcessor::getResultPrinter( $this->m_params['format'], SMWQueryProcessor::SPECIAL_PAGE );
 443+ $result_mime = $printer->getMimeType( $res );
 444+
 445+ global $wgRequest;
 446+
 447+ $hidequery = $wgRequest->getVal( 'eq' ) == 'no';
 448+
 449+ // if it's an export format (like CSV, JSON, etc.),
 450+ // don't actually export the data if 'eq' is set to
 451+ // either 'yes' or 'no' in the query string - just
 452+ // show the link instead
 453+ if ( $this->m_editquery || $hidequery ) $result_mime = false;
 454+
 455+ if ( $result_mime == false ) {
 456+ if ( $res->getCount() > 0 ) {
 457+ if ( $this->m_editquery ) $urltail .= '&eq=yes';
 458+ if ( $hidequery ) $urltail .= '&eq=no';
 459+
 460+ $navigation = $this->getNavigationBar( $res, $urltail );
 461+ $result .= '<div style="text-align: center;">' . "\n" . $navigation . "\n</div>\n";
 462+ $query_result = $printer->getResult( $res, $this->m_params, SMW_OUTPUT_HTML );
 463+
 464+ if ( is_array( $query_result ) ) {
 465+ $result .= $query_result[0];
 466+ } else {
 467+ $result .= $query_result;
 468+ }
 469+
 470+ $result .= '<div style="text-align: center;">' . "\n" . $navigation . "\n</div>\n";
 471+ } else {
 472+ $result = '<div style="text-align: center;">' . wfMsg( 'smw_result_noresults' ) . '</div>';
 473+ }
 474+ } else { // make a stand-alone file
 475+ $result = $printer->getResult( $res, $this->m_params, SMW_OUTPUT_FILE );
 476+ $result_name = $printer->getFileName( $res ); // only fetch that after initialising the parameters
 477+ }
 478+ }
 479+
 480+ if ( $result_mime == false ) {
 481+ if ( $this->m_querystring ) {
 482+ $wgOut->setHTMLtitle( $this->m_querystring );
 483+ } else {
 484+ $wgOut->setHTMLtitle( wfMsg( 'ask' ) );
 485+ }
 486+
 487+ $result = $this->getInputForm( $printoutstring, 'offset=' . $this->m_params['offset'] . '&limit=' . $this->m_params['limit'] . $urltail ) . $result;
 488+ $wgOut->addHTML( $result );
 489+ } else {
 490+ $wgOut->disable();
 491+
 492+ header( "Content-type: $result_mime; charset=UTF-8" );
 493+
 494+ if ( $result_name !== false ) {
 495+ header( "content-disposition: attachment; filename=$result_name" );
 496+ }
 497+
 498+ echo $result;
 499+ }
 500+ }
 501+
 502+ /**
 503+ * Generates the search box UI
 504+ *
 505+ * @param string $printoutstring
 506+ * @param string $urltail
 507+ *
 508+ * @return string
 509+ */
 510+ protected function getInputForm( $urltail ) {
 511+ global $smwgQSortingSupport, $smwgResultFormats;
 512+
 513+ $result = '';
 514+
 515+ if ( $this->m_editquery ) {
 516+ $spectitle = $this->getTitleFor( 'Ask' );
 517+ $result .= '<form name="ask" action="' . $spectitle->escapeLocalURL() . '" method="get">' . "\n" .
 518+ '<input type="hidden" name="title" value="' . $spectitle->getPrefixedText() . '"/>';
 519+
 520+ $result .= wfMsg('smw_qc_query_help');
 521+
 522+ // Main query and printouts.
 523+ $result .= '<p><strong>' . wfMsg( 'smw_ask_queryhead' ) . "</strong></p>\n";
 524+ $result .= '<p><textarea name="q" rows="6">' . htmlspecialchars( $this->m_querystring ) . '</textarea></p>';
 525+ //show|hide additional options and querying help
 526+ $result .= '<span id="show_additional_options" style="display:inline"><a href="#addtional" rel="nofollow" onclick="' .
 527+ "document.getElementById('additional_options').style.display='block';" .
 528+ "document.getElementById('show_additional_options').style.display='none';" .
 529+ "document.getElementById('hide_additional_options').style.display='inline';" . '">' .
 530+ wfMsg( 'smw_show_addnal_opts' ) . '</a></span>';
 531+ $result .= '<span id="hide_additional_options" style="display:none"><a href="#" rel="nofollow" onclick="' .
 532+ "document.getElementById('additional_options').style.display='none';" .
 533+ "document.getElementById('hide_additional_options').style.display='none';" .
 534+ "document.getElementById('show_additional_options').style.display='inline';" . '">' .
 535+ wfMsg( 'smw_hide_addnal_opts' ) . '</a></span>';
 536+ $result .=' | <a href="' . htmlspecialchars( wfMsg( 'smw_ask_doculink' ) ) . '">' . wfMsg( 'smw_ask_help' ) . '</a>';
 537+ //additional options
 538+ $result .= '<div id="additional_options" style="display:none">';
 539+ $result .= '<p><strong>' . wfMsg( 'smw_ask_printhead' ) . "</strong></p>\n" .
 540+ '<span style="font-weight: normal;">' . wfMsg( 'smw_ask_printdesc' ) . '</span>' . "\n" .
 541+ '<p><textarea id = "add_property" name="po" cols="20" rows="6"></textarea></p>' . "\n";
 542+// @TODO
 543+ // sorting inputs
 544+ if ( $smwgQSortingSupport ) {
 545+ if ( ! array_key_exists( 'sort', $this->m_params ) || ! array_key_exists( 'order', $this->m_params ) ) {
 546+ $orders = array(); // do not even show one sort input here
 547+ } else {
 548+ $sorts = explode( ',', $this->m_params['sort'] );
 549+ $orders = explode( ',', $this->m_params['order'] );
 550+ reset( $sorts );
 551+ }
 552+
 553+ foreach ( $orders as $i => $order ) {
 554+ $result .= "<div id=\"sort_div_$i\">" . wfMsg( 'smw_ask_sortby' ) . ' <input type="text" name="sort[' . $i . ']" value="' .
 555+ htmlspecialchars( $sorts[$i] ) . "\" size=\"35\"/>\n" . '<select name="order[' . $i . ']"><option ';
 556+
 557+ if ( $order == 'ASC' ) $result .= 'selected="selected" ';
 558+ $result .= 'value="ASC">' . wfMsg( 'smw_ask_ascorder' ) . '</option><option ';
 559+ if ( $order == 'DESC' ) $result .= 'selected="selected" ';
 560+
 561+ $result .= 'value="DESC">' . wfMsg( 'smw_ask_descorder' ) . "</option></select>\n";
 562+ $result .= '[<a href="javascript:removeInstance(\'sort_div_' . $i . '\')">' . wfMsg( 'delete' ) . '</a>]' . "\n";
 563+ $result .= "</div>\n";
 564+ }
 565+
 566+ $result .= '<div id="sorting_starter" style="display: none">' . wfMsg( 'smw_ask_sortby' ) . ' <input type="text" name="sort_num" size="35" />' . "\n";
 567+ $result .= ' <select name="order_num">' . "\n";
 568+ $result .= ' <option value="ASC">' . wfMsg( 'smw_ask_ascorder' ) . "</option>\n";
 569+ $result .= ' <option value="DESC">' . wfMsg( 'smw_ask_descorder' ) . "</option>\n</select>\n";
 570+ $result .= "</div>\n";
 571+ $result .= '<div id="sorting_main"></div>' . "\n";
 572+ $result .= '<a href="javascript:addInstance(\'sorting_starter\', \'sorting_main\')">' . wfMsg( 'smw_add_sortcondition' ) . '</a>' . "\n";
 573+ }
 574+
 575+ $printer = SMWQueryProcessor::getResultPrinter( 'broadtable', SMWQueryProcessor::SPECIAL_PAGE );
 576+ $url = SpecialPage::getSafeTitleFor( 'Ask' )->getLocalURL( "showformatoptions=' + this.value + '" );
 577+
 578+ foreach ( $this->m_params as $param => $value ) {
 579+ if ( $param !== 'format' ) {
 580+ $url .= '&params[' . Xml::escapeJsString( $param ) . ']=' . Xml::escapeJsString( $value );
 581+ }
 582+ }
 583+
 584+ $result .= "<br /><br />\n<p>" . wfMsg( 'smw_ask_format_as' ) . ' <input type="hidden" name="eq" value="yes"/>' . "\n" .
 585+ '<select id="formatSelector" name="p[format]" onChange="JavaScript:updateOtherOptions(\'' . $url . '\')">' . "\n" .
 586+ ' <option value="broadtable"' . ( $this->m_params['format'] == 'broadtable' ? ' selected' : '' ) . '>' .
 587+ $printer->getName() . ' (' . wfMsg( 'smw_ask_defaultformat' ) . ')</option>' . "\n";
 588+
 589+ $formats = array();
 590+
 591+ foreach ( array_keys( $smwgResultFormats ) as $format ) {
 592+ // Special formats "count" and "debug" currently not supported.
 593+ if ( $format != 'broadtable' && $format != 'count' && $format != 'debug' ) {
 594+ $printer = SMWQueryProcessor::getResultPrinter( $format, SMWQueryProcessor::SPECIAL_PAGE );
 595+ $formats[$format] = $printer->getName();
 596+ }
 597+ }
 598+
 599+ natcasesort( $formats );
 600+
 601+ foreach ( $formats as $format => $name ) {
 602+ $result .= ' <option value="' . $format . '"' . ( $this->m_params['format'] == $format ? ' selected' : '' ) . '>' . $name . "</option>\n";
 603+ }
 604+
 605+ $result .= "</select></p>\n";
 606+ $result .= '<fieldset><legend>' . wfMsg( 'smw_ask_otheroptions' ) . "</legend>\n";
 607+ $result .= "<div id=\"other_options\">" . $this->showFormatOptions( $this->m_params['format'], $this->m_params ) . "</div>";
 608+ $result .= "</fieldset>\n";
 609+ $urltail = str_replace( '&eq=yes', '', $urltail ) . '&eq=no';
 610+ $result .= '</div>';
 611+
 612+ $result .= '<br /><input type="submit" value="' . wfMsg( 'smw_ask_submit' ) . '"/>' .
 613+ '<input type="hidden" name="eq" value="no"/>' .
 614+ "\n</form>";
 615+ } else { // if $this->m_editquery == false
 616+ $urltail = str_replace( '&eq=no', '', $urltail ) . '&eq=yes';
 617+ $result .= '<p>' .
 618+ Html::element(
 619+ 'a',
 620+ array(
 621+ 'href' => SpecialPage::getSafeTitleFor( 'Ask' )->getLocalURL( $urltail ),
 622+ 'rel' => 'nofollow'
 623+ ),
 624+ wfMsg( 'smw_ask_editquery' )
 625+ ) .
 626+ '| ' . SMWAskPage::getEmbedToggle() .
 627+ '</p>';
 628+ }
 629+
 630+ return $result;
 631+ }
 632+
 633+ /**
 634+ * TODO: document
 635+ *
 636+ * @return string
 637+ */
 638+ protected static function getEmbedToggle() {
 639+ return '<span id="embed_show"><a href="#" rel="nofollow" onclick="' .
 640+ "document.getElementById('inlinequeryembed').style.display='block';" .
 641+ "document.getElementById('embed_hide').style.display='inline';" .
 642+ "document.getElementById('embed_show').style.display='none';" .
 643+ "document.getElementById('inlinequeryembedarea').select();" .
 644+ '">' . wfMsg( 'smw_ask_show_embed' ) . '</a></span>' .
 645+ '<span id="embed_hide" style="display: none"><a href="#" rel="nofollow" onclick="' .
 646+ "document.getElementById('inlinequeryembed').style.display='none';" .
 647+ "document.getElementById('embed_show').style.display='inline';" .
 648+ "document.getElementById('embed_hide').style.display='none';" .
 649+ '">' . wfMsg( 'smw_ask_hide_embed' ) . '</a></span>';
 650+ }
 651+
 652+ /**
 653+ * Build the navigation for some given query result, reuse url-tail parameters.
 654+ *
 655+ * @param SMWQueryResult $res
 656+ * @param string $urltail
 657+ *
 658+ * @return string
 659+ */
 660+ protected function getNavigationBar( SMWQueryResult $res, $urltail ) {
 661+ global $smwgQMaxInlineLimit;
 662+
 663+ $offset = $this->m_params['offset'];
 664+ $limit = $this->m_params['limit'];
 665+
 666+ // Prepare navigation bar.
 667+ if ( $offset > 0 ) {
 668+ $navigation = Html::element(
 669+ 'a',
 670+ array(
 671+ 'href' => SpecialPage::getSafeTitleFor( 'Ask' )->getLocalURL(
 672+ 'offset=' . max( 0, $offset - $limit ) .
 673+ '&limit=' . $limit . $urltail
 674+ ),
 675+ 'rel' => 'nofollow'
 676+ ),
 677+ wfMsg( 'smw_result_prev' )
 678+ );
 679+
 680+ } else {
 681+ $navigation = wfMsg( 'smw_result_prev' );
 682+ }
 683+
 684+ $navigation .=
 685+ '&#160;&#160;&#160;&#160; <b>' .
 686+ wfMsg( 'smw_result_results' ) . ' ' . ( $offset + 1 ) .
 687+ '&#150; ' .
 688+ ( $offset + $res->getCount() ) .
 689+ '</b>&#160;&#160;&#160;&#160;';
 690+
 691+ if ( $res->hasFurtherResults() ) {
 692+ $navigation .= Html::element(
 693+ 'a',
 694+ array(
 695+ 'href' => SpecialPage::getSafeTitleFor( 'Ask' )->getLocalURL(
 696+ 'offset=' . ( $offset + $limit ) .
 697+ '&limit=' . $limit . $urltail
 698+ ),
 699+ 'rel' => 'nofollow'
 700+ ),
 701+ wfMsg( 'smw_result_next' )
 702+ );
 703+ } else {
 704+ $navigation .= wfMsg( 'smw_result_next' );
 705+ }
 706+
 707+ $first = true;
 708+
 709+ foreach ( array( 20, 50, 100, 250, 500 ) as $l ) {
 710+ if ( $l > $smwgQMaxInlineLimit ) break;
 711+
 712+ if ( $first ) {
 713+ $navigation .= '&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;(';
 714+ $first = false;
 715+ } else {
 716+ $navigation .= ' | ';
 717+ }
 718+
 719+ if ( $limit != $l ) {
 720+ $navigation .= Html::element(
 721+ 'a',
 722+ array(
 723+ 'href' => SpecialPage::getSafeTitleFor( 'Ask' )->getLocalURL(
 724+ 'offset=' . $offset .
 725+ '&limit=' . $l . $urltail
 726+ ),
 727+ 'rel' => 'nofollow'
 728+ ),
 729+ $l
 730+ );
 731+ } else {
 732+ $navigation .= '<b>' . $l . '</b>';
 733+ }
 734+ }
 735+
 736+ $navigation .= ')';
 737+
 738+ return $navigation;
 739+ }
 740+
 741+ /**
 742+ * Display a form section showing the options for a given format,
 743+ * based on the getParameters() value for that format's query printer.
 744+ *
 745+ * @param string $format
 746+ * @param array $paramValues The current values for the parameters (name => value)
 747+ *
 748+ * @return string
 749+ */
 750+ protected function showFormatOptions( $format, array $paramValues ) {
 751+ $text = '';
 752+
 753+ $printer = SMWQueryProcessor::getResultPrinter( $format, SMWQueryProcessor::SPECIAL_PAGE );
 754+
 755+ $params = method_exists( $printer, 'getParameters' ) ? $printer->getParameters() : array();
 756+
 757+ // Ignore the format parameter, as we got a special control in the GUI for it already.
 758+ unset( $params['format'] );
 759+
 760+ $optionsHtml = array();
 761+
 762+ foreach ( $params as $param ) {
 763+ $param = $this->toValidatorParam( $param );
 764+ $currentValue = array_key_exists( $param->getName(), $paramValues ) ? $paramValues[$param->getName()] : false;
 765+
 766+ $optionsHtml[] =
 767+ Html::rawElement(
 768+ 'div',
 769+ array(
 770+ 'style' => 'width: 30%; padding: 5px; float: left;'
 771+ ),
 772+ htmlspecialchars( $param->getName() ) . ': ' .
 773+ $this->showFormatOption( $param, $currentValue ) .
 774+ '<br />' .
 775+ Html::element( 'em', array(), $param->getDescription() )
 776+ );
 777+ }
 778+
 779+ for ( $i = 0, $n = count( $optionsHtml ); $i < $n; $i++ ) {
 780+ if ( $i % 3 == 2 || $i == $n - 1 ) {
 781+ $optionsHtml[$i] .= "<div style=\"clear: both\";></div>\n";
 782+ }
 783+ }
 784+
 785+ $i = 0;
 786+ $rowHtml = '';
 787+ $resultHtml = '';
 788+
 789+ while ( $option = array_shift( $optionsHtml ) ) {
 790+ $rowHtml .= $option;
 791+ $i++;
 792+
 793+ if ( $i % 3 == 0 ) {
 794+ $resultHtml .= Html::rawElement(
 795+ 'div',
 796+ array(
 797+ 'style' => 'background: ' . ( $i % 6 == 0 ? 'white' : '#dddddd' ) . ';'
 798+ ),
 799+ $rowHtml
 800+ );
 801+ $rowHtml = '';
 802+ }
 803+ }
 804+
 805+ return $resultHtml;
 806+ }
 807+
 808+ /**
 809+ * Returns a Validator style Parameter definition.
 810+ * SMW 1.5.x style definitions are converted.
 811+ *
 812+ * @since 1.6
 813+ *
 814+ * @param mixed $param
 815+ *
 816+ * @return Parameter
 817+ */
 818+ protected function toValidatorParam( $param ) {
 819+ static $typeMap = array(
 820+ 'int' => Parameter::TYPE_INTEGER
 821+ );
 822+
 823+ if ( !( $param instanceof Parameter ) ) {
 824+ if ( !array_key_exists( 'type', $param ) ) {
 825+ $param['type'] = 'string';
 826+ }
 827+
 828+ $paramClass = $param['type'] == 'enum-list' ? 'ListParameter' : 'Parameter';
 829+ $paramType = array_key_exists( $param['type'], $typeMap ) ? $typeMap[$param['type']] : Parameter::TYPE_STRING;
 830+
 831+ $parameter = new $paramClass( $param['name'], $paramType );
 832+
 833+ if ( array_key_exists( 'description', $param ) ) {
 834+ $parameter->setDescription( $param['description'] );
 835+ }
 836+
 837+ if ( array_key_exists( 'values', $param ) && is_array( $param['values'] ) ) {
 838+ $parameter->addCriteria( new CriterionInArray( $param['values'] ) );
 839+ }
 840+
 841+ return $parameter;
 842+ }
 843+ else {
 844+ return $param;
 845+ }
 846+ }
 847+
 848+ /**
 849+ * Get the HTML for a single parameter input.
 850+ *
 851+ * @since 1.6
 852+ *
 853+ * @param Parameter $parameter
 854+ * @param mixed $currentValue
 855+ *
 856+ * @return string
 857+ */
 858+ protected function showFormatOption( Parameter $parameter, $currentValue ) {
 859+ $input = new ParameterInput( $parameter );
 860+ $input->setInputName( 'p[' . $parameter->getName() . ']' );
 861+
 862+ if ( $currentValue !== false ) {
 863+ $input->setCurrentValue( $currentValue );
 864+ }
 865+
 866+ return $input->getHtml();
 867+ }
 868+
 869+ /**
 870+ * Compatibility method to get the skin; MW 1.18 introduces a getSkin method in SpecialPage.
 871+ *
 872+ * @since 1.6
 873+ *
 874+ * @return Skin
 875+ */
 876+ public function getSkin() {
 877+ if ( method_exists( 'SpecialPage', 'getSkin' ) ) {
 878+ return parent::getSkin();
 879+ }
 880+ else {
 881+ global $wgUser;
 882+ return $wgUser->getSkin();
 883+ }
 884+ }
 885+
 886+}
 887+
Index: trunk/extensions/SemanticMediaWiki/languages/SMW_Messages.php
@@ -235,6 +235,10 @@
236236 'querycreator' => 'Query Creator',
237237 'smw_show_addnal_opts' => 'Show additional options',
238238 'smw_hide_addnal_opts' => 'Hide additional options',
 239+ 'smw_qc_query_help' => 'Enter a query using the form below to select wiki pages based on
 240+Categories (<strong>[[Category:Actor]]</strong>),
 241+Properties (<strong>[[Located in::Germany]]</strong>),
 242+Names(<strong>[[Germany]]</strong>) and Namespaces (<strong>[[Help:+]]</strong>)',
239243
240244 // Messages for the search by property special
241245 'searchbyproperty' => 'Search by property',