Extension:QPoll/Developing scripts

qpuserchoice parser function
This function can be used to extract the answer (user's category choice(s)) of some previously submitted poll. Possible parameters are:
 * poll_address : mandatory. the same as in the poll dependance attribute, given without double quotes.
 * question_id : mandatory, number, starting from 1.
 * proposal_id : mandatory, number, starting from 0.
 * default_answer : optional. if given, returns it's value when categoryX_answer was not given. If omitted or has a blank value, returns category_X_answer in case it's present in the list of parameters, otherwise returns the name of category number X for the current user, poll_address, question_id, proposal_id. Since v0.4.2, this parameter may optionally have special value  $  which indicates that number of category (counting from 0) choosen by current user, will be returned instead (in case the user had answered to the polls question). User selected numbers of categories of particular question can be used in template to calculate overall results (for example, in psychological tests).
 * categoryX_answer : optional, (X: 0..N). If given, returns it's value when category X was chosen by current user in poll_address, question_id, proposal_id. In case the value is blank or omitted, returns the text answer (in case it's not blank), or just the name of category number X for the current user, poll_address, question_id, proposal_id.

Category names of checkboxes "[]" and radiobuttons "" are treated as pre-defined (fixed) user proposal answers. Textfields "<>" may have "custom" texts of the user proposal answers (non-blank field).

Since v0.6.1, #iferror parser function may be used to detect non-existent / non-voted poll and to display a specified message in such case.

See an example below.

General introduction
Since v0.8.0 it's possible to analyze user-submitted poll form values via server-side scripts. Such interpretation scripts can validate user input, and either reject to store invalid user input (no poll voting data will be written into DB in such case) or to display calculated results (for example to implement real psychological or language tests). To edit and store such server-side scripts, new namespace is registered by extension: Interpretation:YourScriptName Script created at Interpretation:YourScriptName page should be usual php code, enclosed into qpinterpret xml tag: ... Because no feature-rich editor is provided yet, it's better to use your favorite editor like vim or notepad++ then copy / paste text inside enclosure.

 ...
 * To bind an existing poll to newly created interpretation script, interpretation attribute should be added to qpoll definition header:
 * Optional max_attempts attribute allows to limit number of poll answer submissions by current user. Such feature might be very useful for education tests.

All of usual poll / question / proposal / category attributes can be used to define poll forms. Users are encouraged to use question "name" attribute and proposal "name" attribute to make interpretation scripts code cleaner and simplier.

Technical details of interpretation

 * Currently scripts are executed in limited subset of PHP language, with (most of) dangerous built-in functions and classes being disallowed via php tokenizer. However, wise admin probably should restrict 'read' / 'edit' rights of 'NS_QP_INTERPRETATION' and 'NS_QP_INTERPRETATION_TALK' namespaces to sysops and bureaucrats only. Ordinary users and especially anonymous should be disallowed from looking at scripts code.
 * Syntax is checked via php lint when page in Interpretation namespace is saved. In case of errors these will be reported at the top of the page, usually with line number provided for convenience.
 * To see the list of allowed / disallowed language constructs look at qp_eval.php source.
 * There are plans to create Lua-bindings interpretation backend in the future.

Polls definition code
Imagine one is having the following two polls defined at arbitrary wiki page. Copy / paste the following wiki code into arbitrary main namespace page of your wiki after installation of Extension:QPoll :   {Personal information (mandatory fields are marked with *)
 * type="text" emptytext="yes" name="person"}
 * name="age" emptytext="no"|Age* <<::width=2>>
 * name="sex" emptytext="no"|Sex* <>
 * name="ethnic_origin" emptytext="no" catreq=1|Ethnic origin* <> or (arbitrary) : <<>>
 * birthplace|Birth place <<>>
 * livingplace|Living place <<>>
 * education|Education <<>>
 * qualification|Qualification <<>>
 * workplace|Workplace <<>>
 * occupance|Occupance <<>>

Check your knowledge of English language.  {Please conjugate verb "accept".
 * type="text" emptytext="no" name="verb_test"}
 * name="infinitive"|Infinitive <<|to accept|accepted|accepting>>
 * name="participle"|Participle <<|to accept|accepted|accepting>>
 * name="gerund"|Gerund <<|to accept|accepted|accepting>>

{Complete the sentences. Hi! My <<|name|country|city>> is Andrew. How are you? Today is Sunday. Tomorrow is <<|Friday|Saturday|Monday>>.
 * type="text" emptytext="no" name="sentences"}
 * name="short_text"|A duck is a type of <<|fish|mammal|bird>>.


 * First poll has id personal_data . It's attached (bound) to interpretation script page Interpretation:Check_personal_data . The poll has one question with name person . That question has proposals with the following names defined: age, sex, ethnic_origin, birthplace, livingplace, education, qualification, workplace, occupance.


 * Second poll has id english_language_test . It's attached (bound) to interpretation script page Interpretation:Check_english_language_test . The poll has two question with the following names: verb_test, sentences . The question with name verb_test has proposals with the following names defined: infinitive, participle, gerund . The question with name sentences has proposal with the name short_text.
 * Also note that second poll has dependance="#personal_data" attribute, which means that it will be available for voting only after successful submission of first poll.


 * Let's explain declaration of proposal with name ethnic_origin :
 * name="ethnic_origin" emptytext="no" catreq=1|Ethnic origin* <> or (arbitrary) : <<>>


 * This proposal defines two categories: first one is select with four pre-defined text options: <> . Second category is ordinary text field: <<>>  . It would be very hard to list all possible ethnic origins of the world, so we place the pre-defined list of most common ethnic origins into first field (select) while allowing to supply arbitrary ethnic_origin proposal value via second text field. We disable the submission of empty text input (which would be meaningless) via emptytext="no" proposal attribute. We allow to fill only one, not all (not both) of category fields via catreq=1 attribute, because we assume that there is no multiple ethnic origins at once (which actually might be false, but that's just an example). Note that both fields still could be submitted by user (which is not mandatory yet possible), thus we'll address such case in the interpretation script below.
 * We use tabular layout of first poll to align form fields into the grid. Try removing that attribute from first poll header. You'll see that fields will float in the proposal text unaligned.
 * Second poll is language test so it's categories (fields) intentionally are made unaligned, which is more common to "natural" text.

Definition of interpretation scripts
Note that both polls have interpretation attribute in their headers. Poll with id personal_data is bound to interpretation script which should be located at the page Interpretation:Check_personal_data. You may create such page at your wiki then place the following code into it:

 $q = $answer['person']; qp_debug('person',$q);
 * 1) we have only one question; this is a shortcut

$result = array( 'options' => array( 'store_erroneous' => false ),  # possible qpc errors. 'person' is question name (we have only one question)  'error' => array( 'person' => array ),  'structured' => array( 'age' => $q['age'][0], 'sex' => $q['sex'][0], 'birthplace' => $q['birthplace'][0], 'livingplace' => $q['livingplace'][0], 'education' => $q['education'][0], 'qualification' => $q['qualification'][0], 'workplace' => $q['workplace'][0], 'occupance' => $q['occupance'][0] ) );

$min_age = 18; $max_age = 120; if ( !ctype_digit( $q['age'][0] ) ||    $q['age'][0] < $min_age || $q['age'][0] > $max_age ) { $result['error']['person']['age'] = 'Возраст должен быть числом от '. $min_age. ' до '. $max_age; }
 * 1) age must be a number from 18 to 100

if ( count( $q['ethnic_origin'] ) > 1 ) { $result['error']['person']['ethnic_origin'] = 'Национальность может быть только одна'; } else { $result['structured']['ethnic_origin'] = isset( $q['ethnic_origin'][0] ) ? $q['ethnic_origin'][0] : $q['ethnic_origin'][1]; }
 * 1) we assume that one person may have only one ethnical_origin

$result['long'] = 'You are sucessfully registered for our polls.';
 * 1) do not display any result to end user

return $result;

Poll with id english_language_test is bound to interpretation script which should be located at the page Interpretation:Check_english_language_test. You may create such page at your wiki then place the following code into it:

 qp_debug('answer',$answer);

$personal_data = qp_getStructuredInterpretation( 'QPoll01#personal_data' ); qp_debug('personal_data',$personal_data);

$result = array( 'options' => array( 'store_erroneous' => true ),  # Possible qpc errors.  # 'verb_test' and 'sentences' keys are question names.  'error' => array( 'verb_test' => array, 'sentences' => array, ), 'structured' => array( # Structured data from previosely answered polls can be processed and stored again, 'approx_age_in_days' => $personal_data['age'] * 365, 'err_num' => 0, ) );

if ( $answer['verb_test']['infinitive'][0] !== 'to accept' ) { $result['error']['verb_test']['infinitive'] = 'Incorrect infinitive conjugation'; $result['structured']['err_num']++; }

if ( $answer['verb_test']['participle'][0] !== 'accepted' ) { $result['error']['verb_test']['participle'] = 'Incorrect participle conjugation'; $result['structured']['err_num']++; }

if ( $answer['verb_test']['gerund'][0] !== 'accepting' ) { $result['error']['verb_test']['gerund'] = 'Incorrect gerund conjugation'; $result['structured']['err_num']++; }

if ( $answer['sentences']['short_text'][0] !== 'bird' ) { $result['error']['sentences']['short_text'][0] = 'A bird is not a '. $answer['sentences']['short_text'][0]; $result['structured']['err_num']++; }

if ( $answer['sentences']['short_text'][1] !== 'name' ) { $result['error']['sentences']['short_text'][1] = 'A person is not a '. $answer['sentences']['short_text'][1]; $result['structured']['err_num']++; }

if ( $answer['sentences']['short_text'][2] !== 'Monday' ) { $result['error']['sentences']['short_text'][2] = 'Please study English days of the week.'; $result['structured']['err_num']++; }

if ( $result['structured']['err_num'] === 0 ) { $result['short'] = 'Well done!'; $result['long'] = 'Congratulations!'; if ( intval( $personal_data['age'] ) > 40 ) { $result['long'] .= " Especially because it is harder for people past 40 to learn foreign languages."; } } else { $result['short'] = 'Not so good.'; $result['long'] = "You have made ". $result['structured']['err_num']. " error(s). Try better next time."; }

qp_debug('result',$result); return $result;

Notes about general logic of interpretation


require_once( "$IP/extensions/QPoll/QPoll/qp_user.php" ); $wgDebugLogGroups['qpoll'] = 'my_arbitrary_qpoll_debug_file.txt';
 * Interpretation script executes only in case question / proposal restrictions are met: such as default or current catreq, max_attempts and emptytext attribute values.
 * When interpretation script begins to execute, user-submitted categories are placed into nested array $answers. When question names and proposal names are used (which is recommended), $answers keys will have form of $answer['question_name']['proposal_name'][$cidx], where $cidx is category index (0..n). Remember that each proposal may have one or multiple categories selected via multiple fields (text inputs, checkboxes) or selects with multiple options. Because of that, even proposals with single category defined are returned as ''$answer['question_name']['proposal_name'][0] for orthogonality of interpretation scripts.
 * When no question names / proposal names are used, $answer array will have the form of $answer[$qidx][$pidx][$cidx], where $qidx is (1..n), $pidx is (0..n), $cidx is (0..n).
 * To simplify script development, extension provides built-in logging function qp_debug. The only parameter of that function is PHP variable which content is dumped into log file. Log file named qpoll_debug_log.txt is written to $IP directory of the wiki. MediaWiki logger is used for writting that file. It's better either to comment out or to remove debugging calls or to change debug logging file name in LocalSettings.php via:
 * Interpretation result is expected to be placed into variable $result which is a nested array with the following keys: