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 tag. Also, because it has grew out of Quiz extension (see history). Maybe also, because Q is the first letter of the "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. ...
 * poll_attributes : xml list of qpoll element attributes
 * ... : list of questions, 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.

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, optional metacategories line and a mandatory categories line.

Poll address
Poll addresses are similar to addresses of wikipage section: Pagetitle#poll_id. However, the part after dash sign is not the section. It's the id of the poll on the page Pagetitle, specified as   . Poll addresses are currently used as a value of dependance attribute (see just below) and in.

Poll attributes

 * Mandatory id attribute defines string identifier of the poll. 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 are internally addressed within their container pages. Non-ASCII id's are possible, just place the value of attribute in quotes (an example in Russian):


 * 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.


 * 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 page. For example, perrow=3 would setup three question tables per row display layout.

$qp_enable_showresults = true; By default, showresuls does nothing and the polls statistics is available only to users with administrative rights on the Special:PollResults page.
 * Optional showresults attribute specifies to display statistics (percents of user votes for some of the question's category) inside the poll. To enable showresults functionality, an administrator of the wikisite should put the following line into LocalSettings.php:

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:


 * Mandatory type attribute defines the type of user's category selection in the poll. 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.
 * categoryX_answer: optional, (0..X..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).

See an example below.

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 Calc.

LocalSettings.php
Among general user settings (such as setting up 'read', 'edit' and other registered user rights, anonymous user rights and so on), there are two built-in global variables and some related wikiglobal variables which affects the extensions operation:
 *  $qp_enable_showresults = true;  (by default is off) enables showresults poll attribute
 *  $qp_AnonForwardedFor = true;  (by default is off) 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_AnonForwardedFor has a very little sense.
 * even if the wiki 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_AnonForwardedFor is needed.
 * even, when $wgUsePrivateIPs and $qp_AnonForwardedFor 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.

Installation
require_once( "$IP/extensions/QPoll/qp_user.php" );
 * download the latest available version and extract it to your wiki extension directory.
 * add the following line to LocalSettings.php
 * setup additional variables in LocalSettins.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
Now there's an example of three different polls:  {Please choose the side of conflict: ! Choose a score (I - less 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
 * type="unique"}
 * I | II | III | IV | V

{When I begin to work on some project as a member of 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 can this project bring to the group members to organize the project in such way that it would be implemented well enough
 * layout="proposals transpose" type="unique"}
 * 1 | 2 | 3 | 4 | 5

{In reality, I am understanding new ideas better and easier, when I can: Did you even think, which external reasons encourage you to invent something new? Did you ever had a trouble cause you're too much curious? Did you ever had a strong wish to improve design of the some material thing? Did you ever has a trouble due to your own stubborness?
 * layout="proposals" type=""}
 * "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_enable_showresults=true; in LocalSettings.php (showresults attribute). Questions of this poll will be displayed two per row (perrow=2 poll attribute).
 * layout="transpose" type="unique"}
 * 1 | 2 | 3 | 4 | 5

 {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 when 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 in other polls or parser function with Pagename#poll0 poll address (or, just #poll0 if 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
 * textwidth="20" type="mixed"}
 * Only the water | Juice | Beer | Other drink

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.

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.

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. If you've translated the messages, please send it to my [mailto:questpc@rambler.ru e-mail address], so I'll update the archive.

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

Todo
Improve "mixed" question type and a better text field statistics (result page).

History
This extension has originally grown out of the exising stable Quiz extension, taking it as a code base. After some revisions, when the majority of the code has been 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 be easily 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 would arise.