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']) ? '.' : ' "' . $question['question'] . '".'; 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();