Hello. I'm developing a Semantic MediaWiki extension to partition an #ask query
based upon the first character of one of the queried properties so that the user
can page through partitions of what would otherwise be a very large query
result. For example, I'd like to query all pages in Category:Author in my wiki,
but there are almost 4,000 of those, so I want to partition the results
dynamically by the first character of the author's last name. I have a working
version, but I'm in the process of enhancing it to use Ajax so that it only
needs to refresh the <div> with the new query results rather than the entire
page as well as retaining previous partitions in hidden <div>'s so previous
queries don't need to be repeated. I'm having two parsing issues in the Ajax
callback. I do not believe that they are related to Ajax per se. The second
issue may be caused by my inexpert fix to the first issue, although I ran across
the same second issue previously in a different context. I have reproduced both
issues in a vastly simplified, minimal test program that I will discuss below.
I'm using MediaWiki 1.16.0 and SemanticMediaWiki 1.5.2.
The first issue is that I need access to the parser in the Ajax callback so I
can call recursiveTagParse on the results for the query. I tried using
$wgParser, but it does not appear to be in a good state in the callback. I had a
series of errors where the parser was calling functions on non-objects. I played
around with it a bit and was able to get past all of these errors with this
rather ugly code:
global $wgParser;
$wgParser->mOptions = new ParserOptions;
$wgParser->initialiseVariables();
$wgParser->clearState();
$wgParser->setTitle(new Title($title));
where $title is the title retrieved using getTitle on the original (pre-Ajax)
working parser and sent as a parameter to JavaScript and back. These calls allow
me to get a parser that does not give me run-time errors, but I'm not at all
confident that this is the correct approach.
The second issue I'm having is that when I call recursiveTagParse on the data
returned from the query in the Ajax callback, some of the data is "disappears".
I should note that I'm invoking the same query and parsing code in both the
initial display of the page and in the Ajax callback. It works initially but
does not work in the callback using the questionable parser instance described
above. However, I had also seen the same blanking behavior in a previous task
where I did not have the first contributing issue. In that case, I changed my
approach completely to avoid the recursiveTagParse, but I don't believe I have
the option of doing that here. I found a bug report that seems similar, but I'm
not sure if it is the same problem, and there doesn't seem to be any movement on
that bug:
https://bugzilla.wikimedia.org/show_bug.cgi?id=24556.
I'm reproducing my simplified test case below. You can run it by placing the
following wikitext on a page:
{{#testQuery:[[Category:Author]]
|limit=5
|searchlabel=
|format=table
}}
replacing [[Category:Author]] with the query term of your choice. When you
install the PHP extension code below, you'll see a page with a button and a
table containing up to 5 query results. When you press the button, the same
query is invoked, but the table is empty. If you comment out the line that
contains the call to recursiveTagParse in the PHP code below, you'll see that
the query results are indeed being returned in both cases, but they are getting
obliterated in the Ajax callback by recursiveTagParse.
It may very well be that solving the first issue will solve the second one, but
since I saw the second issue in another context previously, I wonder if they
really are two separate issues.
Thank you very much for any assistance in working through these issues!
Cindy
PHP extension code:
<?php
/**
* To activate the functionality of this extension include the following
* in your LocalSettings.php file:
* include_once("$IP/extensions/TestQuery/TestQuery.php");
*/
if( !defined( 'MEDIAWIKI' ) ) die( "This is an extension to the MediaWiki
package and cannot be run standalone." );
# credits
$wgExtensionCredits['parserhook'][] = array (
'name' => 'TestQuery',
'version' => '1.0',
'author' => "Cindy Cicalese",
'description' => "Bug test"
);
$wgUseAjax = true;
$wgAjaxExportList[] = 'testQueryPopulateDiv';
$wgHooks['LanguageGetMagic'][] = 'wfExtensionTestQuery_Magic';
$wgHooks['ParserFirstCallInit'][] = 'efTestQueryParserFunction_Setup';
function efTestQueryParserFunction_Setup (& $parser) {
$parser->setFunctionHook('testQuery', 'testQuery');
return true;
}
function wfExtensionTestQuery_Magic(& $magicWords, $langCode) {
$magicWords['testQuery'] = array (0, 'testQuery');
return true;
}
function testQuery($parser, $query) {
$params = func_get_args();
array_shift($params); // first is $parser; strip it
array_shift($params); // second is query string; strip it
$testQuery = new TestQuery();
$output = $testQuery->firstVisit($parser, $query, $params);
$parser->disableCache();
return array($parser->insertStripItem($output, $parser->mStripState),
'noparse' => false);
}
function testQueryPopulateDiv($query, $paramString, $title) {
$params = explode("|", $paramString);
$testQuery = new TestQuery();
$output = $testQuery->populateDiv($query, $params, $title);
return $output;
}
class TestQuery {
private $template = false;
function firstVisit($parser, $query, $params) {
$js = <<<EOT
<script type="text/javascript">
function buttonClicked() {
var query = document.forms['TestQuery'].Query.value;
var params = document.forms['TestQuery'].Params.value;
var title = document.forms['TestQuery'].Title.value;
var div = document.getElementById('TestDiv');
sajax_do_call('testQueryPopulateDiv', [query, params, title], div);
}
</script>
EOT;
$parser->mOutput->addHeadItem($js);
$this->parseParameters($params);
$output = $this->buildForm($query, $params, $parser->getTitle());
$result = $this->getData($parser, $query, $params);
$output .= $this->buildDiv($result);
return $output;
}
function populateDiv($query, $params, $title) {
global $wgParser;
$wgParser->mOptions = new ParserOptions;
$wgParser->initialiseVariables();
$wgParser->clearState();
$wgParser->setTitle(new Title($title));
$this->parseParameters($params);
$currentPartitionData = $this->getData($wgParser, $query, $params);
return $currentPartitionData;
}
private function parseParameters($params) {
foreach ($params as $param) {
if (preg_match("/^ *format *= *template *$/", $param) === 1) {
$this->template = true;
}
}
}
private function buildForm($query, $params, $title) {
$paramString = implode("|", $params);
$out = <<<EOT
<center>
<button type='button' id='TestButton'
onClick="buttonClicked()">Test Button
</button>
<form id='TestQuery' method='post' action=''>
<input type='hidden' name='Query' value='$query'>
<input type='hidden' name='Params' value='$paramString'>
<input type='hidden' name='Title' value='$title'>
</form>
</center><br>
EOT;
return $out;
}
private function getData($parser, $query, $params) {
$result = $this->doSMWAsk($parser, $query, $params);
$result = $parser->recursiveTagParse($result);
return $result;
}
private function doSMWAsk($parser, $query, $rawParams) {
SMWQueryProcessor::processFunctionParams($rawParams, $qs, $params,
$printouts);
$output = SMWQueryProcessor::getResultFromQueryString($query, $params,
$printouts, SMW_OUTPUT_WIKI);
return $output;
}
private function buildDiv($result) {
$output = "<div id='TestDiv' display='block'>";
$output .= $result;
$output .= "</div>";
return $output;
}
}
--
Dr. Cynthia Cicalese
Lead Software Systems Engineer
The MITRE Corporation