Documentation

Please note that these installation and use instructions are for the stand alone version. The WordPress and Wolf CMS plugins each have their own readme files with installation and use instructions.

Survey Generator

Introduction

What it is

Survey Generator gives you an easy way to generate, conduct, and summarize the responses to custom surveys you can place anywhere on your website. It's not a smartphone app or something you can put on your Facebook page. Survey Generator is a web developer's tool, though it is very easy to use.

How it works

After you include Survey Generator in your PHP project, you can invoke it anywhere you want with a single function. This function creates the survey form you specify, and processes the form when your visitor submits it. Surveys are described in simple text files, and responses to them are collected in CSV (comma-separated values) files. Although Excel and other applications can open survey response files, Survey Generator includes a basic summarizing function that may be all you need.

What's it for

You can use surveys for lots of things.

The fine print

Survey Generator is copyright 2015 by Robert Hallsey (rhallsey at yahoo.com) and released under the GNU General Public License v3 (GPLv3), so it is free software, and you can redistribute it or modify it under the terms of the GPLv3. Survey Generator is distributed in the hope that it is useful, but WITHOUT ANY WARRANTY, without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv3 for more details. The entire GPLv3 is available online at http://www.gnu.org/licenses/gpl.html.

Getting started

Obtaining Survey Generator

The most recent version of Survey Generator is always available from GitHub, at https://github.com/RobertHallsey/Survey-Generator. If you're not familiar with GitHub, after you arrive at the linked page, look for the Download ZIP button at the bottom-right of the screen. Click to download a brand new zip of Survey Generator.

Files in the Package

These are the files that should be in your zip archive. Unzip the entire archive to a folder somewhere on your web server. Make sure these files remain together: survey-main.php, Survey.php, View.php, and the views folder with its 10 files. The other files are not essential to the sofware's operation.

Using Survey Generator in your own code

To get Survey Generator ready for to use, you must do two things: define the constant SURVEY_BASE_PATH with the path to Survey Generator's files, and include the file survey-main.php into your own code (however you want to do it). The survey-main.php file itself includes the Survey and View class files.

<?php
	include('survey_main.php');
	define('SURVEY_BASE_PATH', 'path/to/survey/generator/files');
?>

To conduct a survey, use <?php survey_conduct('survey definition file') ?> anywhere you want the survey form to appear. This one function builds and presents the survey form to the visitor, and then validates and saves the form after the user submits it.

To summarize the responses to a survey, use <?php survey_summarize('survey definition file') ?>. The survey_summarize() function generates tables summarizing the responses by count and percentage. Note that the function's argument is the name of the survey definition file, not the responses file.

Finally, to get the name of a survey, the name embedded in the definition file, <?php survey_name('survey definition file') will do it. This is a simple function that returns false if it can't find the file or if the file apparently isn't a survey definition file, or returns the survey name, which is embedded in a comment in the first line of the file.

Running the demo

This is optional but can be useful to help understand how Survey Generator works. Load the file index.php in your internet browser, and you should see the survey form for the survey described in the file sample-survey, and following that, you should see the summary created from the file sample-survey.cvs. Fill out and submit the form a few times and check that the summary figures change.

Survey definition files

Open the file sample-survey and take a look at it. Survey definition files optionally start with the survey name embedded in a comment, which is a line that starts with a semicolon (;). This survey name is not used by the code and is there for your convenience. Following are one or more sections, one for each question or panel of questions in the survey. Each section begins with its name surrounded by square brackets, like [section_1]. Section names must not contain spaces and must be unique. However, section names are not meaningful. They don't have to be sorted nor do they sort themselves nor anything like that. Sections are just grouping mechanisms for the definitions of a question or panel of questions. There are three types of questions.

Type 1 Question

[section_1]
type = 1
title = "Preferences"
help = "S=small M=medium L=large"
questions[] = "What size Coke do you prefer?"
questions[] = "What size popcorn do you prefer?"
questions[] = "What size candy bar do you prefer?"
questions[] = "What size T-Shirt do you wear?"
answers[] = "S"
answers[] = "M"
answers[] = "L"

The first line says this is a type 1 question. Type 1 questions consist of a panel of questions, all with the same possible answers.

The title property is also optional, but can be used in all three question types. It's a heading that appears before the question. It's optional so that you can group several sections under the same title.

The help property is a message displayed at the top of the questions, to the left of the answer headings. The help property is optional, and only type 1 questions recognize this property. You can't use it with types 2 and 3 questions.

Then comes a list of questions followed by a list of possible answers. Normally, property names must be unique, but the empty brackets mean that it's an array.

Type 2 Question

This type of question allows one and only one multiple-choice answer.

[section_2]
type = 2
questions[] = "What kind of car do you drive?"
answers[] = "Honda"
answers[] = "Toyota"
answers[] = "Ford"
answers[] = "General Motors"
answers[] = "Other"
answers[] = "I don't drive"

A good surveying practice is to always include a catchall answer. A catchall answer makes it possible for everyone to answer the question. In this case, people who don't drive any kind of car can select the last answer. Otherwise, they would be forced to either select a choice that was not true or abandon the survey. To prevent that, always provide a catchall.

Type 3 Question

This is the last survey question type. Like Type 2, it allows one multiple-choice question, except people can check all the answers that apply.

[section_3]
type = 3
questions[] = "Things you like about your job"
answers[] = "Short commute"
answers[] = "Good supervisor"
answers[] = "Good co-workers"
answers[] = "Fulfilling"
answers[] = "High status"
answers[] = "Fun environment"
answers[] = "Pays well"
answers[] = "I don't like my job"

In the case of Type 3 questions, the last possible answer must always be a catchall answer! Because this type of question allows multiple responses, the last answer is always assumed to be a form of "none of the above." And because it is not logical to select some of the above and then check the box for "none of the above," the survey won't let users select some of the answers and the final answer. Users may select some of the answers or the last answer but not both.

Survey response files

Opening a survey result file in a text editor shows it to be a collection of lines similar to this. If you opened the file in Excel, everything would be in columns instead of separated by commas.

"2015-06-22","21:28:05",1,3,2,1,3,1,0,1,0,1,0,1,0

The first and second comma-separated items are obviously the time and date. This is the same timestamp displayed after a survey is submitted. Although the surveys are anonymous, survey confirmation pages can be used to verify the accuracy of a response file by matching their timestamps.

The row of numbers that follows the timestamp corresponds to the survey's responses. Since the sample line was taken from the file sample-survey.csv, we must refer to the definition file sample-survey. The first four numbers are 1, 3, 2, and 1. Those are the responses to the first question, the panel of four. Whoever submitted the survey prefers small Coke, large popcorn, medium candy, and wears a small T-Shirt. Moving on, we see they drive a 3, that is, a Ford, and they checked off a short commute, good co-workers, high status, and good pay as things they like about their job.

Styling with CSS

Survey Generator provides functionality without imposing any look or feel, but both the survey form and the survey summary can by styled with CSS. To help you target your CSS specifically, the survey form and summary are surrounded by div sections with ID attributes. The IDs are "sf" and "ss" respectively. This way, if you assign a rule to the selector #sf p, you assign it only to the paragraphs within the #sf div and don't affect the rest of your layout. The included file survey.css can be used as a starting point, so feel free to incorporate it into your own style sheets.

Constants

Should you need to change them, these constants are defined at the very start of the file Survey.php.

SURVEY_RESET_BUTTON:
Text of the survey form's reset button, defaults to 'Reset'.

SURVEY_SUBMIT_BUTTON:
Text of the survey form's submit button, defaults to 'Submit'.

SURVEY_RESPONSE_FILE_EXT:
File extension for saving data, defaults to 'csv' (no dot).

SURVEY_ERROR_NO_RESPONSE:
Message returned when form is submitted with unanswered questions, string in sprintf format, defaults to 'Please answer question #%d'.

SURVEY_ERROR_EITHER_OR:
Message returned when form is submitted with catchall and other answers selected, string in sprintf format, defaults to 'Last option is either/or in question #%d'.

Internationalization

Survey Generator runs all its text through a function called __() (double underscore). The __() function is commonly used in internationalization. The included function doesn't do anything, but you can modify it or somehow incorporate an internationalization library.

F.A.Q.

None yet.

Technical discussion

You don't have to read this to use Survey Generator, but it might help you understand the code and the design decisions it reflects. If you have any suggestions or ideas, please feel free to let me know at rhallsey@yahoo.com. I will be very grateful!

Why surveys aren't stored in a database

A survey has one or more questions, and each question accepts one or more response from a list of two or more possible answers. While the structure of a survey fits easily into the free-form nature of the associative array, it doesn't fit so easily into the rows, columns, and tables of a database. It can be done, but surveys are not queried like mailing lists or sales figures, so there's not much point in doing it. On the other hand, PHP offers a handy command that reads a file in INI format and returns the contents in associative array format. If a single command can do that, then saving the surveys in INI files is an easy design decision.

Function survey_conduct($given_survey) in survey_main.php

This code displays the survey form and then receives and saves the survey data when the form is submitted. More precisely, it's an implementation of the survey class's methods in such a way that the survey form is displayed, etc. It's not the only possible implementation. You could implement it without any session code at all. Or you could split the display from the process part, the GET from the POST (though this would require a small change in one of the view files).

Executing for the first time, sessions are started if not in progress already. It's the first time through, no form has been submitted yet, so the request method will be "GET." We create our instance of the survey object, prepare the form, record that the survey is running, and then show the survey form.

When the form is submitted, we first check if there's a survey running, and exit if there isn't. Otherwise, we recreate our instance of the survey object and invoke its processSurvey method. This method rebuilds the survey's state from $_POST['survey_save'] and $_POST['survey_data']. The method also validates the submitted data and saves the responses if they pass validation. If the method saves the data, it returns true. It returns false in all other cases. When the method successfully saves the survey data, it unsets the "running" flag. This prevents re-posting if users refresh pages after submitting surveys.

The survey array

When described in definition files, surveys consist of a series of sections, each section having specific properties as well as variable-size arrays for questions, possible answers, and actual responses. The array that holds the survey mirrors this structure, consisting of a series of sub-arrays, each sub-array having specific elements as well as sub sub-arrays for questions, possible answers, etc., etc. So if go by the included sample-survey file, the PHP code foreach ($this->survey_data as $section_name => $section_data) loops three times through the array.

1st $section_name = 'section_1' and $section_data = array of name-value pairs in section_1
2nd $section_name = 'section_2' and $section_data = array of name-value pairs in section_2
3rd $section_name = 'section_3' and $section_data = array of name-value pairs in section_3

Within each loop, you can access every element of the survey. For example, when $section_name = 'section_2' in the 2nd iteration, $section_data['title'] contains 'Lifestyle.' Also, $section_data['questions'] is an array that can be navigated as usual to discover that, for example, $section_data['answers'][2] contains 'Toyota.'

The survey form

Survey forms are generated from template files and the data in their survey arrays. Let's first look at the code generated for each question type. Type 1 questions are the most complex, since they contain multiple questions.

Type 1 questions are placed in tables, and the table code includes colgroup and thead sections. Following this comes the tbody section, where the questions are laid out in rows. The code for a question looks like this.

<tr>
<th scope="row">1. What size Coke do you prefer?<input type="hidden" name="survey_data[section_1][responses][0]" value="0"></th>
<td><input type="radio" aria-label="What size Coke do you prefer?: S" name="survey_data[section_1][responses][0]" value="1"></td>
<td><input type="radio" aria-label="What size Coke do you prefer?: M" name="survey_data[section_1][responses][0]" value="2"></td>
<td><input type="radio" aria-label="What size Coke do you prefer?: L" name="survey_data[section_1][responses][0]" value="3"></td>
</tr>

This is all one row, the question, "What size Coke do you prefer?" The question is within a th element instead of td element. This is because the question, even though it appears to be a cell in the first column, is actually a table heading! It's just that it's a row table heading instead of the usual column table headings we see at top. So th is the correct element, and moreover, the scope="row" part helps reading assistance technology do its job. Notice there's a hidden field that returns the value zero. This creates a placeholder for the response even if the user doesn't choose one.

The question's three possible answers are laid out in the second through fourth lines. The value returned is the ordinal value of the selected choice, 2 for 'M,' for example. The code uses the aria-label attribute to increase the code's accessibility.

Question types 2 and 3 use fieldsets instead of tables. Here's the code for the question, "What kind of car do you drivr?" The elements fieldset, legend, and label are used to make the form as accessible as possible.

<fieldset class="type2">
  <legend>5. What kind of car do you drive?</legend>
  <input type="hidden" name="survey_data[section_2][responses][0]" value="0">
  <input type="radio" id="Q50" name="survey_data[section_2][responses][0]" value="1">
  <label for="Q50">I don't drive</label><br>
  <input type="radio" id="Q51" name="survey_data[section_2][responses][0]" value="2">
  <label for="Q51">Honda</label><br>
  <input type="radio" id="Q52" name="survey_data[section_2][responses][0]" value="3">
  <label for="Q52">Toyota</label><br>
  <input type="radio" id="Q53" name="survey_data[section_2][responses][0]" value="4">
  <label for="Q53">Ford</label><br>
  <input type="radio" id="Q54" name="survey_data[section_2][responses][0]" value="5">
  <label for="Q54">General Motors</label><br>
  <input type="radio" id="Q55" name="survey_data[section_2][responses][0]" value="6">
  <label for="Q55">Other</label>
</fieldset>

In addition to variable combinations of sections, surveys contain uniform headers and footers. Here is the template code for the survey header (this is template code, not HTML output).

<div id="sf"><!-- sf survey form -->
<?php if ($error_question): ?>
<p><?php echo $error_msg ?></p>

<?php endif; ?>
<form id="form" method="post">
<input type="hidden" name="survey_file" value="<?php echo $survey_file ?>">
<input type="hidden" name="survey_save" value="<?php echo $survey_save ?>">

After opening the form, the code defines two hidden fields. These fields contain the survey's definition file name and the data from the survey array (the survey's sections, questions, answers, etc.). When the visitor submits the form, these hidden values survive and show up as variables in the $_POST array. It's kind of Rube Goldberg, but it's a commonly used technique to persist values across page refreshes. Other ways use sessions or rely on cookies, but hidden fields is the simplest way.

The View class

How is the survey array itself saved? Let's look first at how the View class works. Whether you provide them when you create an instance of the View class or later, you must give your view object a template file to use and a list of variable stuffed inside an array. Your view object's render method then returns the HTML code that results from inserting the values in the variables you passed into the appropriate places in the template file. An example will make this much easier to understand. Suppose you have this in a template file called address.php.

<div class = "address">
  <p>Mail to:</p>
  <p><?php echo $name ?><br>
  <?php echo $street ?><br>
  <?php echo $city ?></p>
</div><!-- address -->

You would then fill an array with variables, like this

// $customer_last = 'Jones'
// $customer_first = 'Thomas'
// $customers_address = '123 Main St.'
// $customer_city = 'Happy City'
// $customer_state = 'XX'
// $customer_zip = 'ZZZZZ'
$variables = array(
    'name' => $customer_last . ', ' . $customer_first,
    'street' => $customer_address,
    'city' => $customer_city . ', ' . $customer_state . ' ' . $customer_zip,
);

See how the key names in $variables match the variable names in the template file? How you get the values into the array doesn't matter. What matters is matching the values to the names being passed in the array. Now let's look at bringing it together.

$myView = new View;
$myView->assignFile('views/address');
$myView->assignVar($variables);
$x = $myView->render();

$x now contains this text,

<div class = "address">
<p>Mail to:</p>
<p>Jones, Thomas<br>
123 Main St.<br>
Happy City, XX ZZZZZ</p>

You could also shorten the whole thing to just $x = new View('views/address', $variables); and get the same result thanks to default methods.

Persisting the array data

Survey forms are not static content. They are generated on the fly by an algorith that reads survey definition files and produces HTML code using the values therein. Survey forms must further be presented at least twice, once for the initial presentation, and once again for the confirmation. Between those two times, submission errors could require presenting the form many more times. However, as is well known, each page refresh completely reinitializes the PHP environment, clearing all variables and arrays. The challenge then is how to persist the array data so that the dynamic form can be generated again after the page is refreshed.

The easiest way is to abandon the challenge and simply reload and reparse the definition file each time, but this is expensive and inelegant. Another method would be to use sessions or cookies, but the method used here, hidden form fields, is the simplest. To see how it's done, let's trace the path of the survey array from when it first appears. That happens in the loadSurveyFile() method, in the code $this->survey_data = parse_ini_file($this->survey_file, TRUE)

From there, if the survey array passes validation, it goes to theForm() method, where the survey array itself is saved in the hidden field like this: 'survey_save' => base64_encode(serialize($this->survey_data)). First, the survey array is serialized, but a serialized result contains binary zeroes, which cannot be expressed in strings, so the function base64_encode converts the serialized object into the ASCII/ANSI equivalent. This is a long and seemingly meaningless string that looks like this.

<input type="hidden" name="survey_save" value="OpaTow6NDtpOYTozOntzOjk6InNlY3Rpb25fMSI7YTo2OntzOjQ6
InR5cGUiO3M6MToiMSI7czo1OiJ0aXRsZSI7czoxMToiUHJlZmVyZW5jZXMiO3M6NDoiaGVscCI7czoyNDoiUz1zbWFsbCBNPW1
lZGl1bSBMPWxhcmdlIjtzOjk6InF1ZXN0aW9ucyI7YTo0OntpOjA7czoyOToiV2hhdCBzaXplIENva2UgZG8geW91IHByZWZlcj
8iO2k6MTt6MpOjY7aTowO">

Now the data to create the form is in the form itself. When the form is submitted, the survey data embedded in the hidden field goes to the $_POST array as $_POST['survey_save']. From there, it is passed to the processSurvey() method as $survey_save. The survey array is then restored with the code $this->survey_data = unserialize(base64_decode($survey_save)).

Javascript

The class uses two Javascript functions: formReset, to reset the form's controls, and formDisable, to disable them. Their execution is controlled by the protected property $js_function, the value of which is sent to the surveyfooter.php view file. When the survey object is first created, this property defaults to formReset. This ensures that, if the user presses the back button and returns to the pre-submission version of the form, the form is cleared. This prevents duplicate submissions.

After the form is submitted, the $js_function property is set to '' to avoid calling any Javascript. This is because, between the form's first submission and its eventual recording as saved, we actually want to persist the data in order to repopulate the form. Once the submitted data passes validation, $js_function is set to formDisable. As its name indicates, the formDisable function disables all the controls on the form. This effectively makes the survey submission confirmation page read-only. The completed survey can be printed, but its values can no longer be changed.

Form validation

The main validation method, validateForm(), loops through the survey's sections and calls the validation method specific to each section's question type. This is done using PHP's variable function feature, in which the name of a function is stored in a variable, and the variable is then executed as if it were a function.

Validating questions type 1 and type 2 only requires checking that they have responses. Any response is acceptable. Questions type 3, however, are a little different. In the case of type 3 questions, the last of the multiple-choice answers, must be either the only answer selected, or must not be selected at all. Users may not select other answers and the final answer because the final answer is a catch-all. This is discussed in the documentation on definition files.

The saveForm() method

The saveForm() method saves a survey form's responses to a text file, one line per submitted form. It starts by capturing the time and breaking it down to separate date and time representations. The method then adds each response value to the line, each value separated by a comma. The line is then ended with a Windows-style EOL (a carriage return followed by a new line).

The saveForm() method takes the survey's definition file name and, after removing any existing file extension, adds the file extension stored in SURVEY_RESPONSE_FILE_EXT (a dot should not be included when defining this extension -- it should not be .csv but just csv). This is the file name where the responses will be stored. The method then appends the line it just constructed to this file, closes the file, and sets formDisable as the Javascript function to execute.

End of Documentation File