Extension:QPoll

Purpose
This extension allows to create polls embedded into the wikipages. Every poll may contain a set of the questions of various types. The polls can be "chained" (when the dependent poll would be available for voting only after a successful submission of the "dependance" poll). Questions may have various types: with unique, single or multiple category selections. Categories can be placed into the groups (metacategories also known as category spans) to make a "subquestions" inside a question. Optional dependence of the question's proposal text on the user's choice of category in another previously voted poll.

Terminology and Syntax
Why a QPoll? Well, because there's already a Poll extension, which uses the tag. Also, because it has grown out of the Quiz extension (see history). Maybe also, because Q is the first letter of the word "Question".

Every poll is defined in the source text of the page enclosed into the tag. One wikipage can embed one or multiple poll(s) in the source text. ... Within the list of questions, you can have metacategories and categories. Note that neither of them have any relation to MediaWikis categories, nor the Category namespace, unless you deliberately create one adding links.
 * poll_attributes : xml list of qpoll element attributes
 * ... : list of questions, see below

Declaration/voting mode
This mode was the only mode available before v0.6.0. When the particular mode is used, be sure that poll/question attributes are compatible with that mode, otherwise you'll probably get syntax error messages. There are some common attributes which are suitable for any mode, see below.

Every poll may contain an one or multiple question(s):  {common_question metacategories categories ...
 * question_attributes}

{common_question metacategories categories ...
 * question_attributes}

{common_question metacategories categories ... []<> proposal text The above proposal line defines three input fields, the first is a checkbox, the second is a radiobutton, the third is a text field. Proposals are internally numbered from zero.
 * question_attributes}
 * common_question : Common question describes the purpose of the question at whole. It's not mandatory and thus can be blank. Questions are automatically numbered from the number one.
 * question_attributes : xml-style list of question attributes.
 * metacategories : optional source line, containing the row of cells, separated by special character ("|" or "!"). Every cell has either a name of metacategory or is an empty one. Empty cell(s) before a cell with the name of metacategory define the count of categories, placed into the group (metacategory). The groups make a "subquestions" in the question of "single" category selections type (see Question attributes). In the source code these are referred as "category spans". Real metacategories are separated with "|". It is also possible to create "fake" metacategories, which will be just a comments to categories and don't create real subquestions. In such case "!" character should be used as a cells separator. Wikilinks and templates can be used in metacategory names (since v0.4.1), though beware of posting non-existing templates, you'll get unexpected category names. Define your templates before submitting the poll.
 * categories : mandatory source line, containing the row of cells, separated by "|" character. Defines the list of categories (columns) in the question table. Categories are the "possible answers", internally numbered from zero. Wikilinks and templates can be used in category names (since v0.4.1), though beware of posting non-existing templates, you'll get unexpected category names. Define your templates before submitting the poll.
 * ... : mandatory list of proposals (one or more of source lines). For the types of question "", "unique", "[]" (see Question attributes) these lines contain just the list of proposal texts, one proposal per line. For the type of question "mixed" (since v0.4.0), proposal text is preceeded by list of input types, one element per category defined:

Note that category spans, metacategories, category groups are just a different description of the same logical category grouping. Grouping can be applied only to radiobuttons (for example, a group of two radiobuttons is a logical equivalent of checkbox).

Metacategories (category groups) are unavailable for the types of question "[]", "mixed".

Internal numbering of questions, categories and proposals is important because they might be addressed later as a parameter of the extension's.

Statistical display mode
 {question_attributes} {question_attributes} ... {question_attributes} Only the common question_attributes permitted in statistical display mode can be specified in this poll mode, one per line, enclosed into the curly braces.

General information
Every page can have one or more of polls. Every poll may contain one or more questions. Every question represents the table with rows and columns counting from zero, where columns are categories (possible user answers) and rows are proposals (poll author's questions). Polls and their questions have xml-style attributes, some of those are mandatory and thus should be properly customized. Question(s) also has a header, which contains a common question, question attributes, optional metacategories line and a mandatory categories line.

Poll address
Poll addresses are similar to addresses of wikipage section: Pagetitle#poll_id. "Title part" before the dash sign can be omitted to specify the address of the poll located at current page. However, the part after the dash sign is not a section. It's an ID of some poll declared at the page Pagetitle, specified as   . Poll addresses are currently used as value of address and dependance attributes (see just below) and in.

Poll attributes
Version 0.6.0 of extension introduces two separate modes of polls: declaration/voting mode and statistical display mode. These modes are selected by choosing appropriate poll attributes:
 * specifying id attribute causes the poll definition to use declaration/voting mode (the only mode available prior to v0.6.0)
 * address attribute causes the poll definition to use statistical display mode, which should be used to display the results of the polls at any wikipage - without the forms used to submit the polls.

The modes are mutually exclusive, so no both id and address attributes can be used together in one poll definition. Also, while some of poll attributes have sense in both modes, many of poll attributes can be used exclusively in one mode. However, nothing prevents the user to define the poll and to show it's statistical results at the same page - just define the poll twice - once with id attribute, another time with address attribute.

Mode definition poll attributes
...
 * id attribute switches the poll definition into declaration/voting mode . It defines string identifier of the poll at the current wikipage. It must be unique on the wikipage. The identifier with the same name might be re-used only at another wikipage. This is because polls cannot be remotely voted at another pages. Please note that id's should be specified without the preceding dash sign, for the very same reason. Non-ASCII id's are possible, just place the value of attribute in quotes (an example in Russian):

... or ...
 * address attribute switches the poll definition into statistical display mode . The value of this attribute specifies a short or full address of the poll, whose statistical data should be displayed. For example, it's very handy to create a page or column of the page, where the results of different polls from the different pages will be shown. For example:

Common poll attributes

 * Optional perrow attribute specifies to display question tables in cells, similar to MediaWiki's built-in   tag attribute). It's very useful when the polls categories and/or proposals are short texts (e.g. lists of numbers, yes/no choice). In such case you may fit more polls on the screen. For example, perrow=3 would setup three question tables per row display layout.


 * Optional showresults attribute specifies to display statistics (percents of user votes for some of the question's category) inside the poll. Originally showresults has only key with no value specified. Starting from v0.5.0 showresults value defines the list of statistical display parameters, separated by semicolons (CSS-like). First element of the list is numerical code of display type: 0 - suppress output (do not show the results), 1 - numerical percents, 2 - simple bars. Next elements define the values of CSS-like attributes: color - the color of bar, background - the background of bar, textcolor - the color of text, textbackground - the background of text. For example:

To enable showresults functionality, an administrator of the wikisite should set "global showresults level" which is specified by value of qp_Setup::$global_showresults property defined in LocalSettings.php after the inclusion of the extension.

Note: you need to set up a page to show the results, see.

Declaration/voting mode poll attributes

 * Optional dependance atttibute defines a relation of the current poll to the successful submission of the poll, address of whose is defined as a value of dependance attribute. For example, poll with the header    will be available for voting only when the user had successfully passed a poll with id="my_earlier_poll", located at Another page. Dependence of the polls can be chained further to the next poll, and the engine would then look for the first unsubmitted poll of the current user.

Common question attributes

 * Optional layout attribute provides various visual layouts of the question. (Note that the question data won't be altered, only the visual representation will be changed). Value of the layout attribute is the list of tokens (words), currently limited to proposals and transpose. Thus, a four possible layouts are available:


 * Optional showresults attribute defines the style of statistical display. The meaning of value is the same as poll's showresults attribute (see above), but it can be applied to every question separately, also the locally-undefined values will be inherited from the top element (poll), like in CSS. Such way, different questions of the same poll may have different values of showresults attribute defined, presenting statistical information in various ways and colors.

Declaration/voting mode question attributes

 * Mandatory type attribute defines the type of user's category selection in the current question. The following values are currently available:


 * Optional textwidth attribute defines the width of text fields in em's, when the categories of such type were defined in proposal line(s) of the question of "mixed" type.

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.

Interpretation scripts
Since v0.8.0 it's possible to analyse 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: ... Syntax is checked via php lint. 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. Of course all of usual poll attributes and the above mentioned new type of layout="tabular" attribute value can be included into qpoll xml tag definition as well. 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 access to viewing / editing of 'NS_QP_INTERPRETATION' and 'NS_QP_INTERPRETATION_TALK' namespaces to sysops and bureaucrats only to prevent ordinary users and anonymous from direct access to scripts code.

There are plans to create Lua-bindings interpretation backend in the future.

Special:PollResults
Special:PollResults special page, available to users with the administrative rights, provides various statistical information about polls currently running on the wiki site. You may browse polls and users, check which users had (/not) participated in the polls, view user choices as well as total's statistics (percents of the category selections). Precise total's statistics can also be exported into the XLS-format, to further analyze the data in OpenOffice / LibreOffice Calc or Excel. Since v0.7.0 it is also possible to export user votes in XLS-format.

LocalSettings.php
Among general user settings (such as setting up 'read', 'edit' and other rights for registered and anonymous users), there are three built-in qp_Setup class properties and some related wikiglobal variables which affect the extension's operation: qp_Setup::$global_showresults = value;
 *  qp_Setup::$global_showresults = 0;  (default value is 0) controls "global showresults level" of showresults poll / question attributes.

Following table shows all of currently available global showresults level values (which are applied to all polls of the wiki):

Boolean values are kept for the compatibility with pre v0.6.0. By default, without the value specified, showresults does nothing and the polls statistics is available only to users with administrative rights on the Special:PollResults page.

qp_Setup::$anon_forwarded_for = value;
 *  qp_Setup::$anon_forwarded_for = true;  (default is false) enables appending of X-Forwarded-For client address to the anonymous username (in form of "proxy_IP/client_IP"). Such username will be represented as a subpage of anonymous user page in NS_USER namespace.

Voting of anonymous users behind the proxy brings the technical difficulties, because anonymous username in MediaWiki is represented with it's IP address:
 * private subnets aren't appended to IP chain by default. To do so, place a $wgUsePrivateIPs = true; line into LocalSettings.php. Otherwise, qp_Setup::$anon_forwarded_for has a very little sense.
 * even if website admin enables $wgSquidServers[] in LocalSettings.php to resolve X-Forwarded-For proxy http header, many clients behind different proxies use the same private subnets (eg. 10.0.0.0/8). In such case, two users with the same private IP under different proxies can vote as the single user. That's why qp_Setup::$anon_forwarded_for is needed.
 * even, when $wgUsePrivateIPs and qp_Setup::$anon_forwarded_for are true, proxy headers can be spoofed. Some proxies have X-Forwarded-For turned off. In such case, use authorization. Consider adding a namespace for polls, which would be readable only to registered users. Try using available LDAP authorization extension.

qp_Setup::$cache_control = value;
 *  qp_Setup::$cache_control = true;  (default is false) makes the extension to flush Article and Parser caches only when the user actually submits the page. By default, extension always uses Parser::disableCache at every page where qpoll tag is presented, which reduces the performance. However, cache control feature is not completely tested so use it at your own risk! Also, after changing the value of this property, don't forget to run  ?Title=Page_where_the_poll_is_located&action=purge  query, to make sure the cache is purged.

Before v0.6.5, user settings were made by definition of global variables $qp_enable_showresults and $qp_AnonForwardedFor before the require_once( "$IP/extensions/QPoll/qp_user.php" ) line. Since v0.6.5, these settings are made by values of qp_Setup class properties qp_Setup::$global_showresults and qp_Setup::$anon_forwarded_for, after the require_once("$IP/extensions/QPoll/qp_user.php"). $qp_enable_showresults and $qp_AnonForwardedFor still can be defined for backward compatibility but discouraged to use.

Installation
require_once( "$IP/extensions/QPoll/qp_user.php" );
 * download the latest available version and extract it to your wiki extension directory. Note: if you have MediaWiki 1.18, download the trunk version, not 1.18 (see talk page).
 * add the following line to LocalSettings.php
 * setup additional variables in LocalSettings.php, if needed
 * check out Special:Version page to verify the installation
 * login as the user with administrative rights, go to Special:PollResults page. Database tables will be set up. Alternatively, you may import qpoll.sql to mysql database manually (remember that $wgDBprefix for the tables won't be used!).
 * if there are any glitches, make sure you've installed successfully and MediaWiki version is compatible with the extension. Please report the bugs at the talk page. Also you may try using previous version (if available).

Example of LocalSettings.php
v0.6.5: require_once( "$IP/extensions/QPoll/qp_user.php" ); qp_Setup::$cache_control = false; qp_Setup::$global_showresults = 1; qp_Setup::$anon_forwarded_for = true;

Declaration/voting mode
Now there's an example of three different polls:  {Please choose the side of conflict: ! Choose a score (I - least important, V - most important) describes my values and ideals describes my personal views the most logically suitable to the current situation explains their arguments in the most logical way gives the most useful idea
 * layout="transpose" type="unique" showresults="0"}
 * I | II | III | IV | V

{When I begin to work on some project as a member of a working group, the most important thing to myself is: understanding the goals and importance of this project to guess the goals and values of the working group members to define, how the project will be performed understanding, which benefits this project can brin to the group members to organize the project in such way that it would be implemented well enough
 * layout="proposals transpose" type="unique" showresults="1"}
 * 1 | 2 | 3 | 4 | 5

{In reality, I am understanding new ideas better and easier, when I can: Did you ever think, which external reasons encourage you to invent something new? Did you ever have trouble because you're too curious? Did you ever have a strong wish to improve the design of the some material thing? Did you ever have trouble due to your own stubbornness?
 * layout="proposals" type="" showresults="1;color:blue;background:yellow"}
 * "Real" myself ||| "Ideal" myself
 * Yes | No | Yes | Maybe | No

{Which picture do you like better? Let's assume the poll above is located at the wikipage Mainpage. It's unique identifier on the mainpage is #almaz1 (id=almaz1 poll attribute). Only a different title can re-use the same identifier. This poll will show it's statistics to everyone who can view and thus vote on the Mainpage, in case wiki admin permitted this by placing  qp_Setup::$global_showresults=1; </tt> in LocalSettings.php (showresults attribute). Questions of this poll will be displayed two per row (perrow=2 poll attribute). Questions of poll will have their statistics displayed in different style, according to their showresults attributes.
 * type="unique" showresults="2;color:blue;background:yellow;textcolor:#FF0000;textbackground:aquamarine"}
 * 1 | 2 | 3 | 4 | 5

<qpoll dependance="Mainpage#almaz1" id=poll0 showresults="2;color:green"> {Checking various types of questions, 1 understanding the goals guessing real goals of the project digging deeply into the problem
 * type=""}
 * Large metacategory for the full list of categories just below
 * Category 1 | Category 2 | Category 3

{Checking various types of questions, 2 understanding the real goals Did you mean choice? guessing true goals of the project digging even more deeply into the problem
 * type=""}
 * Two categories span "A"|| Two categories span "B"
 * 1 | 2 | 3 | 4

{Checking various types of questions, 3 understanding the goals guessing real goals of the project digging deeply into the problem Let's assume the second poll is located at the wikipage Pagename. It will be available for the current user only after the first poll (Mainpage#almaz1) was successfully submitted (dependance="Mainpage#almaz1" poll attribute). It's own id=poll0. It might be addressed in the future with Pagename#poll0 poll address (or, just #poll0 if the another poll or parser function is located on the same page).
 * type="[]"}
 * Metacategory for categories I & II | Metacategory for categories III & IV
 * I | II | III | IV

The question 2, proposal 1 of this poll calls a to display a custom proposal text depending on the user's category choice in the previously voted poll Mainpage#almaz1 question 1, proposal 2 (see "the most logically suitable to the current situation" proposal text line above). The displaying proposal text will be Did you mean ... choice?, where ... will be substituted according to the user choice of categories in Mainpage#almaz1 question 1, proposal 2

Note also, that the metacategory (spans) line of the question number 3 is equivalent to the line second metacategory was filled up with one additional empty cell automatically to include the rest of the categories (IV in this case).
 * Metacategory for categories I & II | Metacategory for categories III & IV
 * Metacategory for categories I & II || Metacategory for categories III & IV

{Your favorite drink [][]<> Winter [][]<> Spring [][]<> Summer [][]<> Autumn The poll of "mixed" type. Choose only the water, or any combination of juice, beer and/or other custom drink, which you would prefer during the proposed seasons of the year.
 * textwidth="20" type="mixed"}
 * Plain water | Juice | Beer | Other drink

{Your favorite drink [][]<> Winter Spring Summer Autumn
 * textwidth="20" type="mixed"}
 * Plain water | Juice | Beer | Other drink

Since the v0.4.3 it's possible to omit repeated declaration of exactly the same category types at the next proposal line. Starting from the second proposal line (Spring), there's only proposal text. Thus, the poll with id=multi2 has the same category types as the previously declared poll with id=multi.

Since the v0.7.0 it's possible to define just one category question when proposal have textfield type: {Please describe your short opinion on newspapers below <> Calgary Herald Edmonton Journal Lethbridge Herald Lethbridge Sun Times Lethbridge Journal Starting with v0.7.0 question's "common question" can be hidden when there is only one category defined and it's name equals to html entity 160 (non-breaking space). This might be useful when defining the fields for question comments and feedbacks. For example, you may replace common question title "Your feedback" to html entity 160 in the poll with id=multi3</tt> to have the question to be hidden.
 * textwidth="20" type="mixed"}
 * Your feedback

Statistical display mode
Please note that this mode is available only since v0.6.0. The following code can be placed at any wiki page where the extension was installed: <qpoll address="Mainpage#almaz1" showresults="1"> {showresults="2;color:green;"} {} {showresults="0"} {showresults="1"} If permitted by value of qp_Setup::$global_showresults property, will show the statistical results of poll with id almaz1 located at Mainpage.
 * first question will have green bars displayed
 * second will inherit showresults="1" from poll's value of showresults attribute, thus showing only numerical percents
 * third will not be displayed at all
 * fourth will have only numerical percents displayed

Technical notes
Parsing and storage of the polls on the wiki pages gives a flexibility. Yet, every kind of flexibility brings technical difficulties. When an existing poll has already been submitted, it's structure and results are stored in database. When some user alters the source text of such poll, troubles might occur. Generally, it's very much recommended to: Few simple precautions are made against of some notorious glitches. Eg, very incompatible stored/altered question headers cause reject of loading previous poll's category choices. Some of these problems can be fixed by developing a visual poll editor, however, this is against wiki idea - processing of source text, and would require too much of efforts.
 * add new categories only at the last column
 * add new proposals only at the last row
 * do not alter existing submitted metacategories
 * do not change the type of the poll

But, do not alter source text of ongoing polls at all, if you can. Setup the wiki so voting users only have a read permission, not an edit permission. Read permission is enough to submit the poll. This is the main reason, why the poll structure is stored only during the first successful submission, not during reading the page source. If you want poll_address to be stored without the poll structure, just submit the unfilled poll. This might be useful for setting up dependence chains without cumbersome answering. Storage of address will happen only if the poll has correct syntax.

Categories of checkbox/radiobutton input types store boolean "true"/"false" values. Categories of text type are considered "true" when the text answer is not empty. Additionally, text input is stored, so the user's answer can be seen on Special:PollResults page. "True" means that category was choosen (answered). That's why the selection of "exclusive" radiobutton in proposal row of "mixed" question type clears out both checkboxes and text fields, and vice versa.

For a technical note on anonymous usernames, read LocalSettings.php section.

Localization
Additional languages can be added into qp_i18n.php file. It is preferable to use http://translatewiki.net/ site interface to update the messages directly at svn server.

Download
To extract tgz archive in Windows, free 7zip program can be used.

Todo

 * 1) Statistical diagrams.
 * 2) Improve "mixed" question type.
 * 3) Make it possible to delete polls.

History
This extension has originally grown out of the exising stable Quiz extension, taking it as the code base. After some revisions, when majority of the code was changed, it has became apparent that Quiz compatibility should be dropped, because the purposes of both extensions are quite different - the storage scheme can't easily be made unified, and there's not much of demands to store Quiz results. Besides that, nested and rotating tables layout require html class renderer which uses arrays and objects to interpret the tags - with simple strings output tags nesting errors and general code bloat were arising.