You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

692 lines
22 KiB

<?php
// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project
//
// All Rights Reserved. See copyright.txt for details and a complete list of authors.
// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
// $Id$
//this script may only be included - so its better to die if called directly.
if (strpos($_SERVER["SCRIPT_NAME"], basename(__FILE__)) !== false) {
header("location: index.php");
exit;
}
/**
*
*/
class SurveyLib extends TikiLib
{
private $surveysTable;
private $questionsTable;
private $optionsTable;
public function __construct()
{
parent::__construct();
$this->surveysTable = $this->table('tiki_surveys');
$this->questionsTable = $this->table('tiki_survey_questions');
$this->optionsTable = $this->table('tiki_survey_question_options');
$this->votesTable = $this->table('tiki_user_votings');
}
/**
* @param $offset
* @param $maxRecords
* @param $sort_mode
* @param $find
* @return array
*/
public function list_surveys($offset, $maxRecords, $sort_mode, $find, $perm = 'take_survey')
{
$conditions = [];
if ($find) {
$conditions['search'] = $this->surveysTable->expr('(`name` like ? or `description` like ?)', ["%$find%", "%$find%"]);
}
$surveys = $this->surveysTable->fetchAll(
$this->surveysTable->all(),
$conditions,
$maxRecords,
$offset,
$this->surveysTable->sortMode($sort_mode)
);
$surveys = Perms::filter(['type' => 'survey'], 'object', $surveys, ['object' => 'surveyId'], $perm);
foreach ($surveys as & $survey) {
$survey['questions'] = $this->questionsTable->fetchOne(
$this->questionsTable->count(),
['surveyId' => $survey['surveyId']]
);
}
$retval["data"] = $surveys;
$retval["cant"] = count($surveys);
return $retval;
}
/**
* @param $surveyId
*/
public function add_survey_hit($surveyId)
{
global $prefs, $user;
if (StatsLib::is_stats_hit()) {
$this->surveysTable->update(
[
'taken' => $this->surveysTable->increment(1),
'lastTaken' => $this->now
],
['surveyId' => $surveyId]
);
}
}
/**
* @param $questionId
* @param $value
* @return int
*/
public function register_survey_text_option_vote($questionId, $value)
{
$conditions = [
'questionId' => $questionId,
'qoption' => $value,
];
$result = $this->optionsTable->fetchColumn('optionId', $conditions);
if (! empty($result)) {
$optionId = $result[0];
$this->optionsTable->update(
[
'votes' => $this->optionsTable->increment(1),
],
$conditions
);
} else {
$optionId = $this->optionsTable->insert(
[
'questionId' => $questionId,
'qoption' => $value,
'votes' => 1,
]
);
}
return $optionId;
}
/**
* @param $questionId
* @param $rate
*/
public function register_survey_rate_vote($questionId, $rate)
{
$conditions = ['questionId' => $questionId];
$this->questionsTable->update(
[
'votes' => $this->questionsTable->increment(1),
'value' => $this->questionsTable->increment($rate),
],
$conditions
);
$this->questionsTable->update(
[
'average' => $this->questionsTable->expr('`value`/`votes`'),
],
$conditions
);
}
/**
* @param $questionId
* @param $optionId
*/
public function register_survey_option_vote($questionId, $optionId)
{
$this->optionsTable->update(
[
'votes' => $this->optionsTable->increment(1),
],
[
'questionId' => $questionId,
'optionId' => $optionId,
]
);
}
/**
* @param $surveyId
*/
public function clear_survey_stats($surveyId)
{
$conditions = ['surveyId' => $surveyId];
$this->surveysTable->update(
['taken' => 0],
$conditions
);
$questions = $this->questionsTable->fetchAll(
$this->questionsTable->all(),
$conditions
);
// Remove all the options for each question for text, wiki and fgal types
foreach ($questions as $question) {
$qconditions = ['questionId' => (int) $question['questionId']];
if (in_array($question['type'], ['t', 'g', 'x'])) {
// same table used for options and responses (nice)
$this->optionsTable->deleteMultiple($qconditions);
} else {
$this->optionsTable->updateMultiple(['votes' => 0], $qconditions);
}
}
$this->questionsTable->updateMultiple(
[
'average' => 0,
'value' => 0,
'votes' => 0
],
$conditions
);
$this->get()->table('tiki_user_votings')->deleteMultiple(
['id' => 'survey' . $surveyId]
);
}
/**
* @param $surveyId
* @param $name
* @param $description
* @param $status
* @return mixed
*/
public function replace_survey($surveyId, $name, $description, $status)
{
$newId = $this->surveysTable->insertOrUpdate(
[
'name' => $name,
'description' => $description,
'status' => $status,
],
['surveyId' => empty($surveyId) ? 0 : $surveyId]
);
return $newId ? $newId : $surveyId;
}
/**
* @param $questionId
* @param $question
* @param $type
* @param $surveyId
* @param $position
* @param $options
* @param string $mandatory
* @param int $min_answers
* @param int $max_answers
* @return mixed
*/
public function replace_survey_question(
$questionId,
$question,
$type,
$surveyId,
$position,
$options,
$mandatory = 'n',
$min_answers = 0,
$max_answers = 0
) {
if ($mandatory != 'y') {
$mandatory = 'n';
}
$min_answers = (int) $min_answers;
$max_answers = (int) $max_answers;
$newId = $this->questionsTable->insertOrUpdate(
[
'type' => $type,
'position' => $position,
'question' => $question,
'options' => $options,
'mandatory' => $mandatory,
'min_answers' => $min_answers,
'max_answers' => $max_answers,
],
[
'questionId' => $questionId,
'surveyId' => $surveyId,
]
);
$questionId = $newId ? $newId : $questionId;
$userOptions = $this->parse_options($options);
$questionOptions = $this->optionsTable->fetchAll(
['optionId','qoption'],
['questionId' => $questionId]
);
// Reset question options only if not a 'text', 'wiki' or 'filegal choice', because their options are dynamically generated
if (! in_array($type, ['t', 'g', 'x'])) {
foreach ($questionOptions as $qoption) {
if (! in_array($qoption['qoption'], $userOptions)) {
$this->optionsTable->delete([
'questionId' => $questionId,
'optionId' => $qoption['optionId'],
]);
} else {
$idx = array_search($qoption["qoption"], $userOptions);
unset($userOptions[$idx]);
}
}
foreach ($userOptions as $option) {
$this->optionsTable->insert([
'questionId' => $questionId,
'qoption' => $option,
'votes' => 0,
]);
}
}
return $questionId;
}
/**
* @param $surveyId
* @return array
*/
public function get_survey($surveyId)
{
return $this->surveysTable->fetchRow(
$this->surveysTable->all(),
['surveyId' => $surveyId]
);
}
/**
* @param $questionId
* @return bool
*/
public function get_survey_question($questionId)
{
$question = $this->questionsTable->fetchRow(
$this->questionsTable->all(),
['questionId' => $questionId]
);
$options = $this->optionsTable->fetchRow(
$this->optionsTable->all(),
['questionId' => $questionId]
);
$qoptions = [];
$votes = 0;
foreach ($options as $option) {
$qoptions[] = $option;
$votes += $option["votes"];
}
$question["ovotes"] = $votes;
$question["qoptions"] = $qoptions;
return $question;
}
/**
* @param $surveyId
* @param $offset
* @param $maxRecords
* @param $sort_mode
* @param $find
* @return array
*/
public function list_survey_questions($surveyId, $offset, $maxRecords, $sort_mode, $find, $u = '')
{
$filegallib = TikiLib::lib('filegal');
$conditions = ['surveyId' => $surveyId];
if ($find) {
$conditions['question'] = $this->questionsTable->like('%' . $find . '%');
}
$questions = $this->questionsTable->fetchAll(
$this->questionsTable->all(),
$conditions,
-1,
-1,
$this->questionsTable->sortMode($sort_mode)
);
$ret = [];
if ($u) {
$userVotedOptions = $this->get_user_voted_options($surveyId, $u);
} else {
$userVotedOptions = [];
}
foreach ($questions as & $question) {
// save user options
$userOptions = $this->parse_options($question["options"]);
if (! empty($question['options'])) {
if (in_array($question['type'], ['g', 'x', 'h'])) {
$question['explode'] = $userOptions;
} elseif (in_array($question['type'], ['r', 's'])) {
$question['explode'] = array_fill(1, $question['options'], ' ');
} elseif (in_array($question['type'], ['t'])) {
$question['cols'] = $question['options'];
}
}
$questionOptions = $this->optionsTable->fetchAll(
$this->optionsTable->all(),
['questionId' => $question["questionId"]],
-1,
-1,
$question['type'] === 'g' ?
['votes' => 'desc'] :
['optionId' => 'asc']
);
$question["options"] = count($questionOptions);
if ($question["type"] == 'r') {
$maxwidth = 5;
} else {
$maxwidth = 10;
}
$question["width"] = $question["average"] * 200 / $maxwidth;
$ret2 = [];
$votes = 0;
$total_votes = 0;
foreach ($questionOptions as & $questionOption) {
$total_votes += (int) $questionOption['votes'];
}
$ids = [];
TikiLib::lib('smarty')->loadPlugin('smarty_modifier_escape');
foreach ($questionOptions as & $questionOption) {
if (in_array($questionOption['optionId'], $userVotedOptions)) {
$questionOption['uservoted'] = true;
} else {
$questionOption['uservoted'] = false;
}
if ($total_votes) {
$average = ($questionOption["votes"] / $total_votes) * 100;
} else {
$average = 0;
}
$votes += $questionOption["votes"];
$questionOption["average"] = $average;
$questionOption["width"] = $average * 2;
$questionOption['qoptionraw'] = $questionOption['qoption'];
if ($question['type'] == 'x') {
$questionOption['qoption'] = TikiLib::lib('parser')->parse_data($questionOption['qoption']);
} else {
$questionOption['qoption'] = smarty_modifier_escape($questionOption['qoption']);
}
// when question with multiple options
// we MUST respect the user defined order
if (in_array($question['type'], ['m', 'c'])) {
$ret2[array_search($questionOption['qoptionraw'], $userOptions)] = $questionOption;
} else {
$ret2[] = $questionOption;
}
$ids[$questionOption['qoption']] = true;
}
// For a multiple choice from a file gallery, show all files in the stats results, even if there was no vote for those files
if ($question['type'] == 'g' && $question['options'] > 0) {
$files = $filegallib->get_files(0, -1, '', '', $userOptions[0], false, false, false, true, false, false, false, false, '', false, false);
foreach ($files['data'] as $f) {
if (! isset($ids[$f['id']])) {
$ret2[] = [
'qoption' => $f['id'],
'votes' => 0,
'average' => 0,
'width' => 0
];
}
}
unset($files);
}
$question["qoptions"] = $ret2;
$question["ovotes"] = $votes;
$ret[] = $question;
}
$retval = [];
$retval["data"] = $ret;
$retval["cant"] = count($questions);
return $retval;
}
/**
* @param $questionId
* @return bool
*/
public function remove_survey_question($questionId)
{
$conditions = ['questionId' => $questionId];
$this->optionsTable->deleteMultiple($conditions);
$this->questionsTable->delete($conditions);
return true;
}
/**
* @param $surveyId
* @return bool
*/
public function remove_survey($surveyId)
{
$conditions = ['surveyId' => $surveyId];
$this->surveysTable->delete($conditions);
$questions = $this->questionsTable->fetchColumn('questionId', $conditions);
foreach ($questions as $question) {
$this->optionsTable->deleteMultiple(['questionId' => (int) $question['questionId']]);
}
$this->questionsTable->deleteMultiple($conditions);
$this->remove_object('survey', $surveyId);
$this->get()->table('tiki_user_votings')->deleteMultiple(
['id' => 'survey' . $surveyId]
);
return true;
}
// Check mandatory fields and min/max number of answers and register vote/answers if ok
/**
* @param $surveyId
* @param $questions
* @param $answers
* @param null $error_msg
* @return bool
*/
public function register_answers($surveyId, $questions, $answers, &$error_msg = null)
{
global $user;
if ($surveyId <= 0 || empty($questions)) {
return false;
}
$errors = [];
foreach ($questions as $question) {
$key = 'question_' . $question['questionId'];
$nb_answers = empty($answers[$key]) ? 0 : 1;
$multiple_choice = in_array($question['type'], ['m', 'g']);
if ($multiple_choice) {
$nb_answers = is_array($answers[$key]) ? count($answers[$key]) : 0;
if ($question['max_answers'] < 1) {
$question['max_answers'] = $nb_answers;
}
}
$q = empty($question['question']) ? '.' : ' "<b>' . $question['question'] . '</b>".';
if ($multiple_choice) {
if ($question['mandatory'] == 'y') {
$question['min_answers'] = max(1, $question['min_answers']);
}
if ($question['min_answers'] == $question['max_answers'] && $nb_answers != $question['min_answers']) {
$errors[] = sprintf(tra('%d choice(s) must be made for the question'), $question['min_answers']) . $q;
} elseif ($nb_answers < $question['min_answers']) {
$errors[] = sprintf(tra('At least %d choice(s) must be made for the question'), $question['min_answers']) . $q;
} elseif ($question['max_answers'] > 0 && $nb_answers > $question['max_answers']) {
$errors[] = sprintf(tra('Fewer than %d choice(s) must be made for the question'), $question['max_answers']) . $q;
}
} elseif ($question['mandatory'] == 'y' && $nb_answers == 0 && $question["type"] !== 'h') {
$errors[] = sprintf(tra('At least %d choice(s) must be made for the question'), 1) . $q;
}
}
if (count($errors) > 0) {
if ($error_msg !== null) {
$error_msg = $errors;
}
return false;
} else {
// no errors, so record answers
//
// format for answers recorded in tiki_user_votings is "surveyX.YY"
// where X is surveyId and YY is the questionId
// and optionId is the id in tiki_survey_question_options
$this->register_user_vote($user, 'survey' . $surveyId, 0);
foreach ($questions as $question) {
$questionId = $question["questionId"];
if (isset($answers["question_" . $questionId])) {
if ($question["type"] == 'm') {
// If we have a multiple question
$ids = array_keys($answers["question_" . $questionId]);
// Now for each of the options we increase the number of votes
foreach ($ids as $optionId) {
$this->register_survey_option_vote($questionId, $optionId);
$this->register_user_vote($user, 'survey' . $surveyId . '.' . $questionId, $optionId);
}
} elseif ($question["type"] == 'g') {
// If we have a multiple choice of file from a gallery
$ids = $answers["question_" . $questionId];
// Now for each of the options we increase the number of votes
foreach ($ids as $optionId) {
$this->register_survey_text_option_vote($questionId, $optionId);
$this->register_user_vote($user, 'survey' . $surveyId . '.' . $questionId, $optionId);
}
} elseif ($question["type"] !== 'h') {
$value = $answers["question_" . $questionId];
if ($question["type"] == 'r' || $question["type"] == 's') {
$this->register_survey_rate_vote($questionId, $value);
$this->register_user_vote($user, 'survey' . $surveyId . '.' . $questionId, $value);
} elseif ($question["type"] == 't' || $question["type"] == 'x') {
$optionId = $this->register_survey_text_option_vote($questionId, $value);
$this->register_user_vote($user, 'survey' . $surveyId . '.' . $questionId, $optionId);
} else {
$this->register_survey_option_vote($questionId, $value);
$this->register_user_vote($user, 'survey' . $surveyId . '.' . $questionId, $value);
}
}
}
}
}
return true;
}
public function reorderQuestions($surveyId, $questionIds)
{
$counter = 1;
foreach ($questionIds as $id) {
$this->questionsTable->update(
['position' => $counter],
[
'questionId' => $id,
'surveyId' => $surveyId,
]
);
$counter++;
}
}
/**
* @return array question types: initial => translated label
*/
public function get_types()
{
return [
'c' => tra('One choice'),
'm' => tra('Multiple choices'),
'g' => tra('Thumbnails'),
't' => tra('Short text'),
'x' => tra('Wiki textarea'),
'r' => tra('Rate (1 to 5)'),
's' => tra('Rate (1 to 10)'),
'h' => tra('Heading'),
];
}
/**
* @param string $options comma-separated options string (use \, to include a comma)
* @return array
*/
private function parse_options($options)
{
if (! empty($options)) {
$comma = '~COMMA~';
$options = str_replace('\,', $comma, $options);
$options = explode(',', $options);
foreach ($options as & $option) {
$option = trim(str_replace($comma, ',', $option));
}
} else {
$options = [];
}
return $options;
}
private function get_user_voted_options($surveyId, $u)
{
$conditions['id'] = $this->votesTable->like('survey' . $surveyId . '%');
$conditions['user'] = $u;
$result = $this->votesTable->fetchAll(['optionId'], $conditions);
foreach ($result as $r) {
$ret[] = $r['optionId'];
}
return $ret;
}
public function list_users_that_voted($surveyId)
{
$conditions['id'] = 'survey' . $surveyId;
$conditions['optionId'] = 0;
$result = $this->votesTable->fetchAll(['user'], $conditions);
foreach ($result as $r) {
$ret[] = $r['user'];
}
return array_unique($ret);
}
}
$srvlib = new SurveyLib();