Alter Database

Signed-off-by: Jonas Rittershofer <jotoeri@users.noreply.github.com>
This commit is contained in:
Jonas Rittershofer 2020-03-30 13:48:14 +02:00
parent d402107cbb
commit 03e9ff4a86
30 changed files with 1060 additions and 842 deletions

View file

@ -5,7 +5,7 @@
<name>Forms</name>
<summary>A forms app, similar to Google Forms.</summary>
<description>A forms app, similar to Google Forms with the possibility to restrict access (members, certain groups/users, and public).</description>
<version>1.1.2</version>
<version>1.2.0</version>
<licence>agpl</licence>
<author>Vinzenz Rosenkranz</author>
<author>René Gieling</author>

View file

@ -33,23 +33,23 @@ return [
['name' => 'page#goto_form', 'url' => '/{hash}', 'verb' => 'GET'],
['name' => 'page#insert_vote', 'url' => '/insert/vote', 'verb' => 'POST'],
['name' => 'page#insert_submission', 'url' => '/insert/submission', 'verb' => 'POST'],
['name' => 'page#search', 'url' => '/search', 'verb' => 'POST'],
['name' => 'page#get_display_name', 'url' => '/get/displayname', 'verb' => 'POST'],
['name' => 'api#write_form', 'url' => '/write/form', 'verb' => 'POST'],
['name' => 'api#get_form', 'url' => '/get/form/{formIdOrHash}', 'verb' => 'GET'],
['name' => 'api#get_full_form', 'url' => '/get/fullform/{formIdOrHash}', 'verb' => 'GET'],
['name' => 'api#get_options', 'url' => '/get/options/{formId}', 'verb' => 'GET'],
['name' => 'api#get_shares', 'url' => '/get/shares/{formId}', 'verb' => 'GET'],
['name' => 'api#get_event', 'url' => '/get/event/{formId}', 'verb' => 'GET'],
['name' => 'api#get_form', 'url' => '/get/form/{formId}', 'verb' => 'GET'],
['name' => 'api#get_forms', 'url' => '/get/forms', 'verb' => 'GET'],
['name' => 'api#newForm', 'url' => 'api/v1/form', 'verb' => 'POST'],
['name' => 'api#deleteForm', 'url' => 'api/v1/form/{id}', 'verb' => 'DELETE'],
['name' => 'api#newQuestion', 'url' => 'api/v1/question/', 'verb' => 'POST'],
['name' => 'api#deleteQuestion', 'url' => 'api/v1/question/{id}', 'verb' => 'DELETE'],
['name' => 'api#newAnswer', 'url' => 'api/v1/answer/', 'verb' => 'POST'],
['name' => 'api#deleteAnswer', 'url' => 'api/v1/answer/{id}', 'verb' => 'DELETE'],
['name' => 'api#newOption', 'url' => 'api/v1/option/', 'verb' => 'POST'],
['name' => 'api#deleteOption', 'url' => 'api/v1/option/{id}', 'verb' => 'DELETE'],
['name' => 'api#getSubmissions', 'url' => 'api/v1/submissions/{hash}', 'verb' => 'GET'],
['name' => 'system#get_site_users_and_groups', 'url' => '/get/siteusers', 'verb' => 'POST'],

View file

@ -8,7 +8,7 @@ function sendDataToServer(survey) {
form.userId = 'anon_' + Date.now() + '_' + Math.floor(Math.random() * 10000)
}
form.questions = questions;
$.post(OC.generateUrl('apps/forms/insert/vote'), form)
$.post(OC.generateUrl('apps/forms/insert/submission'), form)
.then((response) => {
}, (error) => {
/* eslint-disable-next-line no-console */
@ -34,7 +34,7 @@ function cssUpdate(survey, options){
$(document).ready(function () {
var formJSON = $('#surveyContainer').attr('form')
var questionJSON = $('#surveyContainer').attr('questions')
form = JSON.parse(formJSON)
questions = JSON.parse(questionJSON)
@ -45,11 +45,11 @@ $(document).ready(function () {
};
questions.forEach(q => {
var ans = []
q.answers.forEach(a => {
ans.push(a.text);
var qChoices = []
q.options.forEach(o => {
qChoices.push(o.text);
});
surveyJSON.questions.push({type: q.type, name: q.text, choices: ans, isRequired: 'true'});
surveyJSON.questions.push({type: q.type, name: q.text, choices: qChoices, isRequired: 'true'});
});
$('#surveyContainer').Survey({

View file

@ -41,14 +41,17 @@ use OCP\IUser;
use OCP\IUserManager;
use OCP\Security\ISecureRandom;
use OCA\Forms\Db\Event;
use OCA\Forms\Db\EventMapper;
use OCA\Forms\Db\VoteMapper;
use OCA\Forms\Db\Form;
use OCA\Forms\Db\FormMapper;
use OCA\Forms\Db\Submission;
use OCA\Forms\Db\SubmissionMapper;
use OCA\Forms\Db\Answer;
use OCA\Forms\Db\AnswerMapper;
use OCA\Forms\Db\Question;
use OCA\Forms\Db\QuestionMapper;
use OCA\Forms\Db\Answer;
use OCA\Forms\Db\AnswerMapper;
use OCA\Forms\Db\Option;
use OCA\Forms\Db\OptionMapper;
use OCP\Util;
@ -56,10 +59,11 @@ class ApiController extends Controller {
private $groupManager;
private $userManager;
private $eventMapper;
private $voteMapper;
private $questionMapper;
private $formMapper;
private $submissionMapper;
private $answerMapper;
private $questionMapper;
private $optionMapper;
/** @var ILogger */
private $logger;
@ -74,10 +78,11 @@ class ApiController extends Controller {
* @param IRequest $request
* @param IUserManager $userManager
* @param string $userId
* @param EventMapper $eventMapper
* @param VoteMapper $voteMapper
* @param QuestionMapper $questionMapper
* @param FormMapper $formMapper
* @param SubmissionMapper $submissionMapper
* @param AnswerMapper $answerMapper
* @param QuestionMapper $questionMapper
* @param OptionMapper $optionMapper
*/
public function __construct(
$appName,
@ -85,20 +90,22 @@ class ApiController extends Controller {
IRequest $request,
IUserManager $userManager,
$userId,
EventMapper $eventMapper,
VoteMapper $voteMapper,
QuestionMapper $questionMapper,
FormMapper $formMapper,
SubmissionMapper $submissionMapper,
AnswerMapper $answerMapper,
QuestionMapper $questionMapper,
OptionMapper $optionMapper,
ILogger $logger
) {
parent::__construct($appName, $request);
$this->userId = $userId;
$this->groupManager = $groupManager;
$this->userManager = $userManager;
$this->eventMapper = $eventMapper;
$this->voteMapper = $voteMapper;
$this->questionMapper = $questionMapper;
$this->formMapper = $formMapper;
$this->submissionMapper = $submissionMapper;
$this->answerMapper = $answerMapper;
$this->questionMapper = $questionMapper;
$this->optionMapper = $optionMapper;
$this->logger = $logger;
}
@ -108,8 +115,8 @@ class ApiController extends Controller {
* @param string $item
* @return Array
*/
private function convertAccessList($item) {
$split = array();
private function convertAccessList($item) : array {
$split = [];
if (strpos($item, 'user_') === 0) {
$user = $this->userManager->get(substr($item, 5));
$split = [
@ -172,11 +179,11 @@ class ApiController extends Controller {
/**
* Set the access right of the current user for the form
* @param Array $event
* @param Array $form
* @param Array $shares
* @return String
*/
private function grantAccessAs($event, $shares) {
private function grantAccessAs($form, $shares) {
if (!\OC::$server->getUserSession()->getUser() instanceof IUser) {
$currentUser = '';
} else {
@ -185,13 +192,13 @@ class ApiController extends Controller {
$grantAccessAs = 'none';
if ($event['owner'] === $currentUser) {
if ($form['ownerId'] === $currentUser) {
$grantAccessAs = 'owner';
} elseif ($event['access'] === 'public') {
} elseif ($form['access'] === 'public') {
$grantAccessAs = 'public';
} elseif ($event['access'] === 'registered' && \OC::$server->getUserSession()->getUser() instanceof IUser) {
} elseif ($form['access'] === 'registered' && \OC::$server->getUserSession()->getUser() instanceof IUser) {
$grantAccessAs = 'registered';
} elseif ($event['access'] === 'hidden' && ($event['owner'] === \OC::$server->getUserSession()->getUser())) {
} elseif ($form['access'] === 'hidden' && ($form['ownerId'] === \OC::$server->getUserSession()->getUser())) {
$grantAccessAs = 'hidden';
} elseif ($this->checkUserAccess($shares)) {
$grantAccessAs = 'userInvitation';
@ -210,11 +217,11 @@ class ApiController extends Controller {
* @param Integer $formId
* @return Array
*/
public function getEvent($formId) {
public function getForm($formId) {
$data = array();
try {
$data = $this->eventMapper->find($formId)->read();
$data = $this->formMapper->find($formId)->read();
} catch (DoesNotExistException $e) {
// return silently
} finally {
@ -234,7 +241,7 @@ class ApiController extends Controller {
$accessList = array();
try {
$form = $this->eventMapper->find($formId);
$form = $this->formMapper->find($formId);
if (!strpos('|public|hidden|registered', $form->getAccess())) {
$accessList = explode(';', $form->getAccess());
$accessList = array_filter($accessList);
@ -248,14 +255,14 @@ class ApiController extends Controller {
}
public function getQuestions($formId) {
$questionList = array();
public function getQuestions($formId) : array {
$questionList = [];
try{
$questions = $this->questionMapper->findByForm($formId);
foreach ($questions as $questionElement) {
$temp = $questionElement->read();
$temp['answers'] = $this->getAnswers($formId, $temp['id']);
$questionList[] = $temp;
$questionEntities = $this->questionMapper->findByForm($formId);
foreach ($questionEntities as $questionEntity) {
$question = $questionEntity->read();
$question['options'] = $this->getOptions($question['id']);
$questionList[] = $question;
}
} catch (DoesNotExistException $e) {
@ -265,18 +272,18 @@ class ApiController extends Controller {
}
}
public function getAnswers($formId, $questionId) {
$answerList = array();
public function getOptions($questionId) : array {
$optionList = [];
try{
$answers = $this->answerMapper->findByForm($formId, $questionId);
foreach ($answers as $answerElement) {
$answerList[] = $answerElement->read();
$optionEntities = $this->optionMapper->findByQuestion($questionId);
foreach ($optionEntities as $optionEntity) {
$optionList[] = $optionEntity->read();
}
} catch (DoesNotExistException $e) {
//handle silently
}finally{
return $answerList;
return $optionList;
}
}
@ -286,7 +293,7 @@ class ApiController extends Controller {
* @param String $formIdOrHash form id or hash
* @return Array
*/
public function getForm($formIdOrHash) {
public function getFullForm($formIdOrHash) {
if (!\OC::$server->getUserSession()->getUser() instanceof IUser) {
$currentUser = '';
@ -299,32 +306,30 @@ class ApiController extends Controller {
try {
if (is_numeric($formIdOrHash)) {
$formId = $this->eventMapper->find(intval($formIdOrHash))->id;
$formId = $this->formMapper->find(intval($formIdOrHash))->id;
$result = 'foundById';
} else {
$formId = $this->eventMapper->findByHash($formIdOrHash)->id;
$formId = $this->formMapper->findByHash($formIdOrHash)->id;
$result = 'foundByHash';
}
$event = $this->getEvent($formId);
$shares = $this->getShares($event['id']);
$form = $this->getForm($formId);
$shares = $this->getShares($form['id']);
if ($event['owner'] !== $currentUser && !$this->groupManager->isAdmin($currentUser)) {
if ($form['ownerId'] !== $currentUser && !$this->groupManager->isAdmin($currentUser)) {
$mode = 'create';
} else {
$mode = 'edit';
}
$data = [
'id' => $event['id'],
'id' => $form['id'],
'result' => $result,
'grantedAs' => $this->grantAccessAs($event, $shares),
'grantedAs' => $this->grantAccessAs($form, $shares),
'mode' => $mode,
'event' => $event,
'form' => $form,
'shares' => $shares,
'options' => [
'formQuizQuestions' => $this->getQuestions($event['id'])
]
'questions' => $this->getQuestions($form['id']),
];
} catch (DoesNotExistException $e) {
$data['form'] = ['result' => 'notFound'];
@ -345,21 +350,20 @@ class ApiController extends Controller {
}
try {
$events = $this->eventMapper->findAll();
$forms = $this->formMapper->findAll();
} catch (DoesNotExistException $e) {
return new DataResponse($e, Http::STATUS_NOT_FOUND);
}
$eventsList = array();
foreach ($events as $eventElement) {
$event = $this->getForm($eventElement->id);
//if ($event['grantedAs'] !== 'none') {
$eventsList[] = $event;
$formsList = array();
foreach ($forms as $formElement) {
$form = $this->getFullForm($formElement->id);
//if ($form['grantedAs'] !== 'none') {
$formsList[] = $form;
//}
}
return new DataResponse($eventsList, Http::STATUS_OK);
return new DataResponse($formsList, Http::STATUS_OK);
}
/**
@ -370,17 +374,16 @@ class ApiController extends Controller {
*/
public function deleteForm(int $id) {
try {
$formToDelete = $this->eventMapper->find($id);
$formToDelete = $this->formMapper->find($id);
} catch (DoesNotExistException $e) {
return new Http\JSONResponse([], Http::STATUS_NOT_FOUND);
}
if ($this->userId !== $formToDelete->getOwner() && !$this->groupManager->isAdmin($this->userId)) {
if ($this->userId !== $formToDelete->getOwnerId() && !$this->groupManager->isAdmin($this->userId)) {
return new DataResponse(null, Http::STATUS_UNAUTHORIZED);
}
$this->voteMapper->deleteByForm($id);
$this->submissionMapper->deleteByForm($id);
$this->questionMapper->deleteByForm($id);
$this->answerMapper->deleteByForm($id);
$this->eventMapper->delete($formToDelete);
$this->formMapper->delete($formToDelete);
return new DataResponse(array(
'id' => $id,
'action' => 'deleted'
@ -391,30 +394,30 @@ class ApiController extends Controller {
/**
* Write form (create/update)
* @NoAdminRequired
* @param Array $event
* @param Array $form
* @param Array $options
* @param Array $shares
* @param String $mode
* @return DataResponse
*/
public function writeForm($event, $options, $shares, $mode) {
public function writeForm($form, $questions, $shares, $mode) {
if (!\OC::$server->getUserSession()->getUser() instanceof IUser) {
return new DataResponse(null, Http::STATUS_UNAUTHORIZED);
} else {
$currentUser = \OC::$server->getUserSession()->getUser()->getUID();
$AdminAccess = $this->groupManager->isAdmin($currentUser);
$adminAccess = $this->groupManager->isAdmin($currentUser);
}
$newEvent = new Event();
$newForm = new Form();
// Set the configuration options entered by the user
$newEvent->setTitle($event['title']);
$newEvent->setDescription($event['description']);
$newForm->setTitle($form['title']);
$newForm->setDescription($form['description']);
$newEvent->setIsAnonymous($event['isAnonymous']);
$newEvent->setUnique($event['unique']);
$newForm->setIsAnonymous($form['isAnonymous']);
$newForm->setSubmitOnce($form['submitOnce']);
if ($event['access'] === 'select') {
if ($form['access'] === 'select') {
$shareAccess = '';
foreach ($shares as $shareElement) {
if ($shareElement['type'] === 'user') {
@ -423,50 +426,50 @@ class ApiController extends Controller {
$shareAccess = $shareAccess . 'group_' . $shareElement['id'] . ';';
}
}
$newEvent->setAccess(rtrim($shareAccess, ';'));
$newForm->setAccess(rtrim($shareAccess, ';'));
} else {
$newEvent->setAccess($event['access']);
$newForm->setAccess($form['access']);
}
if ($event['expiration']) {
$newEvent->setExpire(date('Y-m-d H:i:s', strtotime($event['expirationDate'])));
if ($form['expires']) {
$newForm->setExpirationDate(date('Y-m-d H:i:s', strtotime($form['expirationDate'])));
} else {
$newEvent->setExpire(null);
$newForm->setExpirationDate(null);
}
if ($mode === 'edit') {
// Edit existing form
$oldForm = $this->eventMapper->findByHash($event['hash']);
$oldForm = $this->formMapper->findByHash($form['hash']);
// Check if current user is allowed to edit existing form
if ($oldForm->getOwner() !== $currentUser && !$AdminAccess) {
if ($oldForm->getOwnerId() !== $currentUser && !$adminAccess) {
// If current user is not owner of existing form deny access
return new DataResponse(null, Http::STATUS_UNAUTHORIZED);
}
// else take owner, hash and id of existing form
$newEvent->setOwner($oldForm->getOwner());
$newEvent->setHash($oldForm->getHash());
$newEvent->setId($oldForm->getId());
$this->eventMapper->update($newEvent);
$newForm->setOwnerId($oldForm->getOwnerId());
$newForm->setHash($oldForm->getHash());
$newForm->setId($oldForm->getId());
$this->formMapper->update($newForm);
} elseif ($mode === 'create') {
// Create new form
// Define current user as owner, set new creation date and create a new hash
$newEvent->setOwner($currentUser);
$newEvent->setCreated(date('Y-m-d H:i:s'));
$newEvent->setHash(\OC::$server->getSecureRandom()->generate(
$newForm->setOwnerId($currentUser);
$newForm->setCreated(date('Y-m-d H:i:s'));
$newForm->setHash(\OC::$server->getSecureRandom()->generate(
16,
ISecureRandom::CHAR_DIGITS .
ISecureRandom::CHAR_LOWER .
ISecureRandom::CHAR_UPPER
));
$newEvent = $this->eventMapper->insert($newEvent);
$newForm = $this->formMapper->insert($newForm);
}
return new DataResponse(array(
'id' => $newEvent->getId(),
'hash' => $newEvent->getHash()
'id' => $newForm->getId(),
'hash' => $newForm->getHash()
), Http::STATUS_OK);
}
@ -475,22 +478,22 @@ class ApiController extends Controller {
* @NoAdminRequired
*/
public function newForm(): Http\JSONResponse {
$event = new Event();
$form = new Form();
$currentUser = \OC::$server->getUserSession()->getUser()->getUID();
$event->setOwner($currentUser);
$event->setCreated(date('Y-m-d H:i:s'));
$event->setHash(\OC::$server->getSecureRandom()->generate(
$form->setOwnerId($currentUser);
$form->setCreated(date('Y-m-d H:i:s'));
$form->setHash(\OC::$server->getSecureRandom()->generate(
16,
ISecureRandom::CHAR_HUMAN_READABLE
));
$event->setTitle('New form');
$event->setDescription('');
$event->setAccess('public');
$form->setTitle('New form');
$form->setDescription('');
$form->setAccess('public');
$this->eventMapper->insert($event);
$this->formMapper->insert($form);
return new Http\JSONResponse($this->getForm($event->getHash()));
return new Http\JSONResponse($this->getFullForm($form->getHash()));
}
/**
@ -504,13 +507,13 @@ class ApiController extends Controller {
]);
try {
$form = $this->eventMapper->find($formId);
$form = $this->formMapper->find($formId);
} catch (IMapperException $e) {
$this->logger->debug('Could not find form');
return new Http\JSONResponse([], Http::STATUS_BAD_REQUEST);
}
if ($form->getOwner() !== $this->userId) {
if ($form->getOwnerId() !== $this->userId) {
$this->logger->debug('This form is not owned by the current user');
return new Http\JSONResponse([], Http::STATUS_FORBIDDEN);
}
@ -518,8 +521,8 @@ class ApiController extends Controller {
$question = new Question();
$question->setFormId($formId);
$question->setFormQuestionType($type);
$question->setFormQuestionText($text);
$question->setType($type);
$question->setText($text);
$question = $this->questionMapper->insert($question);
@ -536,18 +539,18 @@ class ApiController extends Controller {
try {
$question = $this->questionMapper->findById($id);
$form = $this->eventMapper->find($question->getFormId());
$form = $this->formMapper->find($question->getFormId());
} catch (IMapperException $e) {
$this->logger->debug('Could not find form or question of this answer');
$this->logger->debug('Could not find form or question');
return new Http\JSONResponse([], Http::STATUS_BAD_REQUEST);
}
if ($form->getOwner() !== $this->userId) {
if ($form->getOwnerId() !== $this->userId) {
$this->logger->debug('This form is not owned by the current user');
return new Http\JSONResponse([], Http::STATUS_FORBIDDEN);
}
$this->answerMapper->deleteByQuestion($id);
$this->optionMapper->deleteByQuestion($id);
$this->questionMapper->delete($question);
return new Http\JSONResponse($id);
@ -556,64 +559,64 @@ class ApiController extends Controller {
/**
* @NoAdminRequired
*/
public function newAnswer(int $formId, int $questionId, string $text): Http\JSONResponse {
$this->logger->debug('Adding new answer: formId: {formId}, questoinId: {questionId}, text: {text}', [
public function newOption(int $formId, int $questionId, string $text): Http\JSONResponse {
$this->logger->debug('Adding new option: formId: {formId}, questionId: {questionId}, text: {text}', [
'formId' => $formId,
'questionId' => $questionId,
'text' => $text,
]);
try {
$form = $this->eventMapper->find($formId);
$form = $this->formMapper->find($formId);
$question = $this->questionMapper->findById($questionId);
} catch (IMapperException $e) {
$this->logger->debug('Could not find form or question so answer can\'t be added');
$this->logger->debug('Could not find form or question so option can\'t be added');
return new Http\JSONResponse([], Http::STATUS_BAD_REQUEST);
}
if ($form->getOwner() !== $this->userId) {
if ($form->getOwnerId() !== $this->userId) {
$this->logger->debug('This form is not owned by the current user');
return new Http\JSONResponse([], Http::STATUS_FORBIDDEN);
}
if ($question->getFormId() !== $formId) {
$this->logger->debug('This question is not owned by the current user');
$this->logger->debug('This question is not part of the current form');
return new Http\JSONResponse([], Http::STATUS_FORBIDDEN);
}
$answer = new Answer();
$option = new Option();
$answer->setFormId($formId);
$answer->setQuestionId($questionId);
$answer->setText($text);
$option->setQuestionId($questionId);
$option->setText($text);
$answer = $this->answerMapper->insert($answer);
$option = $this->optionMapper->insert($option);
return new Http\JSONResponse($answer->getId());
return new Http\JSONResponse($option->getId());
}
/**
* @NoAdminRequired
*/
public function deleteAnswer(int $id): Http\JSONResponse {
$this->logger->debug('Deleting answer: {id}', [
public function deleteOption(int $id): Http\JSONResponse {
$this->logger->debug('Deleting option: {id}', [
'id' => $id
]);
try {
$answer = $this->answerMapper->findById($id);
$form = $this->eventMapper->find($answer->getFormId());
$option = $this->optionMapper->findById($id);
$question = $this->questionMapper->findById($option->getQuestionId());
$form = $this->formMapper->find($question->getFormId());
} catch (IMapperException $e) {
$this->logger->debug('Could not find form or answer');
$this->logger->debug('Could not find form or option');
return new Http\JSONResponse([], Http::STATUS_BAD_REQUEST);
}
if ($form->getOwner() !== $this->userId) {
if ($form->getOwnerId() !== $this->userId) {
$this->logger->debug('This form is not owned by the current user');
return new Http\JSONResponse([], Http::STATUS_FORBIDDEN);
}
$this->answerMapper->delete($answer);
$this->optionMapper->delete($option);
//TODO useful response
return new Http\JSONResponse($id);
@ -624,20 +627,30 @@ class ApiController extends Controller {
*/
public function getSubmissions(string $hash): Http\JSONResponse {
try {
$form = $this->eventMapper->findByHash($hash);
$form = $this->formMapper->findByHash($hash);
} catch (IMapperException $e) {
return new Http\JSONResponse([], Http::STATUS_BAD_REQUEST);
}
if ($form->getOwner() !== $this->userId) {
if ($form->getOwnerId() !== $this->userId) {
return new Http\JSONResponse([], Http::STATUS_FORBIDDEN);
}
$votes = $this->voteMapper->findByForm($form->getId());
$result = [];
foreach ($votes as $vote) {
$result[] = $vote->read();
$submissionList = $this->submissionMapper->findByForm($form->getId());
foreach ($submissionList as $submissionEntity) {
$answerList = $this->answerMapper->findBySubmission($submissionEntity->id);
foreach ($answerList as $answerEntity) {
$answer = $answerEntity->read();
//Temporary Adapt Data to be usable by old Results-View
$answer['userId'] = $submissionEntity->getUserId();
$question = $this->questionMapper->findById($answer['questionId']);
$answer['questionText'] = $question->getText();
$answer['questionType'] = $question->getType();
$result[] = $answer;
}
}
return new Http\JSONResponse($result);

View file

@ -29,12 +29,14 @@
namespace OCA\Forms\Controller;
use OCA\Forms\AppInfo\Application;
use OCA\Forms\Db\Event;
use OCA\Forms\Db\EventMapper;
use OCA\Forms\Db\Vote;
use OCA\Forms\Db\VoteMapper;
use OCA\Forms\Db\Form;
use OCA\Forms\Db\FormMapper;
use OCA\Forms\Db\Submission;
use OCA\Forms\Db\SubmissionMapper;
use OCA\Forms\Db\Answer;
use OCA\Forms\Db\AnswerMapper;
use OCA\Forms\Db\OptionMapper;
use OCA\Forms\Db\QuestionMapper;
use OCP\AppFramework\Controller;
@ -53,11 +55,12 @@ use OCP\Util;
class PageController extends Controller {
private $userId;
private $eventMapper;
private $voteMapper;
private $formMapper;
private $submissionMapper;
private $answerMapper;
private $questionMapper;
private $answerMapper;
private $optionMapper;
private $urlGenerator;
private $userMgr;
@ -69,21 +72,24 @@ class PageController extends Controller {
IGroupManager $groupManager,
IURLGenerator $urlGenerator,
$userId,
EventMapper $eventMapper,
FormMapper $formMapper,
QuestionMapper $questionMapper,
AnswerMapper $answerMapper,
VoteMapper $VoteMapper
OptionMapper $optionMapper,
SubmissionMapper $SubmissionMapper,
AnswerMapper $AnswerMapper
) {
parent::__construct(Application::APP_ID, $request);
$this->userMgr = $userMgr;
$this->groupManager = $groupManager;
$this->urlGenerator = $urlGenerator;
$this->userId = $userId;
$this->eventMapper = $eventMapper;
$this->formMapper = $formMapper;
$this->questionMapper = $questionMapper;
$this->answerMapper = $answerMapper;
$this->voteMapper = $VoteMapper;
$this->optionMapper = $optionMapper;
$this->submissionMapper = $SubmissionMapper;
$this->answerMapper = $AnswerMapper;
}
/**
@ -155,15 +161,15 @@ class PageController extends Controller {
*/
public function gotoForm($hash): ?TemplateResponse {
try {
$form = $this->eventMapper->findByHash($hash);
$form = $this->formMapper->findByHash($hash);
} catch (DoesNotExistException $e) {
return new TemplateResponse('forms', 'no.acc.tmpl', []);
}
if ($form->getExpire() === null) {
if ($form->getExpirationDate() === null) {
$expired = false;
} else {
$expired = time() > strtotime($form->getExpire());
$expired = time() > strtotime($form->getExpirationDate());
}
if ($expired) {
@ -172,7 +178,7 @@ class PageController extends Controller {
if ($this->hasUserAccess($form)) {
$renderAs = $this->userId !== null ? 'user' : 'public';
$res = new TemplateResponse('forms', 'vote.tmpl', [
$res = new TemplateResponse('forms', 'submit.tmpl', [
'form' => $form,
'questions' => $this->getQuestions($form->getId()),
], $renderAs);
@ -192,11 +198,11 @@ class PageController extends Controller {
public function getQuestions(int $formId): array {
$questionList = [];
try{
$questions = $this->questionMapper->findByForm($formId);
foreach ($questions as $questionElement) {
$temp = $questionElement->read();
$temp['answers'] = $this->getAnswers($formId, $temp['id']);
$questionList[] = $temp;
$questionEntities = $this->questionMapper->findByForm($formId);
foreach ($questionEntities as $questionEntity) {
$question = $questionEntity->read();
$question['options'] = $this->getOptions($question['id']);
$questionList[] = $question;
}
} catch (DoesNotExistException $e) {
//handle silently
@ -208,20 +214,20 @@ class PageController extends Controller {
/**
* @NoAdminRequired
*/
public function getAnswers(int $formId, int $questionId): array {
$answerList = [];
public function getOptions(int $questionId): array {
$optionList = [];
try{
$answers = $this->answerMapper->findByForm($formId, $questionId);
foreach ($answers as $answerElement) {
$answerList[] = $answerElement->read();
$optionEntities = $this->optionMapper->findByQuestion($questionId);
foreach ($optionEntities as $optionEntity) {
$optionList[] = $optionEntity->read();
}
} catch (DoesNotExistException $e) {
//handle silently
}
return $answerList;
return $optionList;
}
/**
@ -230,14 +236,14 @@ class PageController extends Controller {
* @return TemplateResponse|RedirectResponse
*/
public function deleteForm($formId) {
$formToDelete = $this->eventMapper->find($formId);
if ($this->userId !== $formToDelete->getOwner() && !$this->groupManager->isAdmin($this->userId)) {
$formToDelete = $this->formMapper->find($formId);
if ($this->userId !== $formToDelete->getOwnerId() && !$this->groupManager->isAdmin($this->userId)) {
return new TemplateResponse('forms', 'no.delete.tmpl');
}
$form = new Event();
$form = new Form();
$form->setId($formId);
$this->voteMapper->deleteByForm($formId);
$this->eventMapper->delete($form);
$this->submissionMapper->deleteByForm($formId);
$this->formMapper->delete($form);
$url = $this->urlGenerator->linkToRoute('forms.page.index');
return new RedirectResponse($url);
}
@ -247,51 +253,47 @@ class PageController extends Controller {
* @PublicPage
* @param int $formId
* @param string $userId
* @param string $answers
* @param string $options question id
* @param bool $changed
* @param array $answers
* @param array $questions
* @return RedirectResponse
*/
public function insertVote($id, $userId, $answers, $questions) {
public function insertSubmission($id, $userId, $answers, $questions) {
$form = $this->eventMapper->find($id);
$count_answers = count($answers);
$count = 1;
$form = $this->formMapper->find($id);
$anonID = "anon-user-". hash('md5', (time() + rand()));
for ($i = 0; $i < $count_answers; $i++) {
if($questions[$i]['type'] === "checkbox"){
foreach (($answers[$questions[$i]['text']]) as $value) {
$vote = new Vote();
$vote->setFormId($id);
if($form->getIsAnonymous()){
$vote->setUserId($anonID);
//Insert Submission
$submission = new Submission();
$submission->setFormId($id);
if($form->getIsAnonymous()){
$submission->setUserId($anonID);
}else{
$vote->setUserId($userId);
}
$vote->setVoteOptionText(htmlspecialchars($questions[$i]['text']));
$vote->setVoteAnswer($value);
$vote->setVoteOptionId($count);
$vote->setVoteOptionType($questions[$i]['type']);
$this->voteMapper->insert($vote);
}else{
$submission->setUserId($userId);
}
$submission->setTimestamp(date('Y-m-d H:i:s'));
$this->submissionMapper->insert($submission);
$submissionId = $submission->getId();
//Insert Answers
foreach($questions as $question) {
if($question['type'] === "checkbox"){
foreach(($answers[$question['text']]) as $ansText) {
$answer = new Answer();
$answer->setSubmissionId($submissionId);
$answer->setQuestionId($question['id']);
$answer->setText($ansText);
$this->answerMapper->insert($answer);
}
$count++;
} else {
$vote = new Vote();
$vote->setFormId($id);
if($form->getIsAnonymous()){
$vote->setUserId($anonID);
}else{
$vote->setUserId($userId);
}
$vote->setVoteOptionText(htmlspecialchars($questions[$i]['text']));
$vote->setVoteAnswer($answers[$questions[$i]['text']]);
$vote->setVoteOptionId($count++);
$vote->setVoteOptionType($questions[$i]['type']);
$this->voteMapper->insert($vote);
$answer = new Answer();
$answer->setSubmissionId($submissionId);
$answer->setQuestionId($question['id']);
$answer->setText($answers[$question['text']]);
$this->answerMapper->insert($answer);
}
}
$hash = $form->getHash();
$url = $this->urlGenerator->linkToRoute('forms.page.goto_form', ['hash' => $hash]);
return new RedirectResponse($url);
@ -379,12 +381,12 @@ class PageController extends Controller {
/**
* Check if user has access to this form
*
* @param Event $form
* @param Form $form
* @return bool
*/
private function hasUserAccess($form) {
$access = $form->getAccess();
$owner = $form->getOwner();
$ownerId = $form->getOwnerId();
if ($access === 'public' || $access === 'hidden') {
return true;
}
@ -392,8 +394,8 @@ class PageController extends Controller {
return false;
}
if ($access === 'registered') {
if ($form->getUnique()) {
$participants = $this->voteMapper->findParticipantsByForm($form->getId());
if ($form->getSubmitOnce()) {
$participants = $this->submissionMapper->findParticipantsByForm($form->getId());
foreach($participants as $participant) {
// Don't allow access if user has already taken part
if ($participant->getUserId() === $this->userId) return false;
@ -401,7 +403,7 @@ class PageController extends Controller {
}
return true;
}
if ($owner === $this->userId) {
if ($ownerId === $this->userId) {
return true;
}
Util::writeLog('forms', $this->userId, Util::ERROR);

View file

@ -2,9 +2,9 @@
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019 Inigo Jiron <ijiron@terpmail.umd.edu>
* @copyright Copyright (c) 2020 Jonas Rittershofer <jotoeri@users.noreply.github.com>
*
* @author Inigo Jiron <ijiron@terpmail.umd.edu>
* @author Jonas Rittershofer <jotoeri@users.noreply.github.com>
*
* @license GNU AGPL version 3 or any later version
*
@ -28,46 +28,33 @@ namespace OCA\Forms\Db;
use OCP\AppFramework\Db\Entity;
/**
* @method integer getFormId()
* @method void setFormId(integer $value)
* @method integer getSubmissionId()
* @method void setSubmissionId(integer $value)
* @method integer getQuestionId()
* @method void setQuestionId(integer $value)
* @method string getText()
* @method void setText(string $value)
* @method integer getTimestamp()
* @method void setTimestamp(integer $value)
*/
class Answer extends Entity {
/** @var int */
protected $formId;
/** @var int */
protected $submissionId;
protected $questionId;
/** @var string */
protected $text;
/** @var int */
protected $timestamp;
/**
* Answer constructor.
*/
public function __construct() {
$this->addType('id', 'integer');
$this->addType('formId', 'integer');
$this->addType('submissionId', 'integer');
$this->addType('questionId', 'integer');
$this->addType('timestamp', 'integer');
}
public function read(): array {
return [
'id' => $this->getId(),
'formId' => $this->getFormId(),
'submissionId' => $this->getSubmissionId(),
'questionId' => $this->getQuestionId(),
'text' => htmlspecialchars_decode($this->getText()),
'timestamp' => $this->getTimestamp()
];
}
}

View file

@ -1,12 +1,9 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019 Inigo Jiron <ijiron@terpmail.umd.edu>
*
* @author Inigo Jiron <ijiron@terpmail.umd.edu>
* @author Natalie Gilbert <ngilb634@umd.edu>
* @copyright Copyright (c) 2020 Jonas Rittershofer <jotoeri@users.noreply.github.com>
*
* @author Jonas Rittershofer <jotoeri@users.noreply.github.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
@ -33,89 +30,43 @@ use OCP\AppFramework\Db\QBMapper;
class AnswerMapper extends QBMapper {
/**
* TextMapper constructor.
* AnswerMapper constructor.
* @param IDBConnection $db
*/
public function __construct(IDBConnection $db) {
parent::__construct($db, 'forms_answers', Answer::class);
parent::__construct($db, 'forms_v2_answers', Answer::class);
}
// TODO: Change below functions to search by form and question id
/**
* @param int $formId
* @param int $questionId
* @param int $submissionId
* @throws \OCP\AppFramework\Db\DoesNotExistException if not found
* @return Answer[]
*/
public function findByForm(int $formId, int $questionId): array {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
->where(
$qb->expr()->eq('form_id', $qb->createNamedParameter($formId, IQueryBuilder::PARAM_INT))
)
->andWhere(
$qb->expr()->eq('question_id', $qb->createNamedParameter($questionId, IQueryBuilder::PARAM_INT))
);
return $this->findEntities($qb);
}
/**
* @param int $formId
* @param int $questionId
*/
public function deleteByFormAndQuestion(int $formId, int $questionId): void {
$qb = $this->db->getQueryBuilder();
$qb->delete($this->getTableName())
->where(
$qb->expr()->eq('form_id', $qb->createNamedParameter($formId, IQueryBuilder::PARAM_INT))
)
->andWhere(
$qb->expr()->eq('question_id', $qb->createNamedParameter($questionId, IQueryBuilder::PARAM_INT))
);
$qb->execute();
}
/**
* @param int $formId
*/
public function deleteByForm(int $formId): void {
$qb = $this->db->getQueryBuilder();
$qb->delete($this->getTableName())
->where(
$qb->expr()->eq('form_id', $qb->createNamedParameter($formId, IQueryBuilder::PARAM_INT))
);
$qb->execute();
}
public function findById(int $answerId): Answer {
public function findBySubmission(int $submissionId): array {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
->where(
$qb->expr()->eq('id', $qb->createNamedParameter($answerId))
$qb->expr()->eq('submission_id', $qb->createNamedParameter($submissionId, IQueryBuilder::PARAM_INT))
);
return $this->findEntity($qb);
return $this->findEntities($qb);
}
public function deleteByQuestion(int $questionId): void {
/**
* @param int $submissionId
*/
public function deleteBySubmission(int $submissionId): void {
$qb = $this->db->getQueryBuilder();
$qb->delete($this->getTableName())
->where(
$qb->expr()->eq('question_id', $qb->createNamedParameter($questionId))
);
->where(
$qb->expr()->eq('submission_id', $qb->createNamedParameter($submissionId, IQueryBuilder::PARAM_INT))
);
$qb->execute();
}
}

View file

@ -29,44 +29,43 @@ namespace OCA\Forms\Db;
use OCP\AppFramework\Db\Entity;
/**
* @method string getHash()
* @method void setHash(string $value)
* @method string getTitle()
* @method void setTitle(string $value)
* @method string getDescription()
* @method void setDescription(string $value)
* @method string getOwner()
* @method void setOwner(string $value)
* @method string getCreated()
* @method void setCreated(string $value)
* @method string getOwnerId()
* @method void setOwnerId(string $value)
* @method string getAccess()
* @method void setAccess(string $value)
* @method string getExpire()
* @method void setExpire(string $value)
* @method string getHash()
* @method void setHash(string $value)
* @method string getCreated()
* @method void setCreated(string $value)
* @method string getExpirationDate()
* @method void setExpirationDate(string $value)
* @method integer getIsAnonymous()
* @method void setIsAnonymous(integer $value)
* @method integer getUnique()
* @method void setUnique(integer $value)
* @method void setIsAnonymous(bool $value)
* @method integer getSubmitOnce()
* @method void setSubmitOnce(bool $value)
*/
class Event extends Entity {
class Form extends Entity {
protected $hash;
protected $title;
protected $description;
protected $owner;
protected $created;
protected $ownerId;
protected $access;
protected $expire;
protected $hash;
protected $created;
protected $expirationDate;
protected $isAnonymous;
protected $fullAnonymous;
protected $allowMaybe;
protected $unique;
protected $submitOnce;
/**
* Event constructor.
* Form constructor.
*/
public function __construct() {
$this->addType('isAnonymous', 'integer');
$this->addType('unique', 'integer');
$this->addType('isAnonymous', 'bool');
$this->addType('submitOnce', 'bool');
}
public function read() {
@ -74,12 +73,12 @@ class Event extends Entity {
if (!strpos('|public|hidden|registered', $accessType)) {
$accessType = 'select';
}
if ($this->getExpire() === null) {
if ($this->getExpirationDate() === null) {
$expired = false;
$expiration = false;
$expires = false;
} else {
$expired = time() > strtotime($this->getExpire());
$expiration = true;
$expired = time() > strtotime($this->getExpirationDate());
$expires = true;
}
return [
@ -87,15 +86,15 @@ class Event extends Entity {
'hash' => $this->getHash(),
'title' => $this->getTitle(),
'description' => $this->getDescription(),
'owner' => $this->getOwner(),
'ownerDisplayName' => \OC_User::getDisplayName($this->getOwner()),
'ownerId' => $this->getOwnerId(),
'ownerDisplayName' => \OC_User::getDisplayName($this->getOwnerId()),
'created' => $this->getCreated(),
'access' => $accessType,
'expiration' => $expiration,
'expires' => $expires,
'expired' => $expired,
'expirationDate' => $this->getExpire(),
'expirationDate' => $this->getExpirationDate(),
'isAnonymous' => $this->getIsAnonymous(),
'unique' => $this->getUnique()
'submitOnce' => $this->getSubmitOnce()
];
}
}

View file

@ -28,63 +28,63 @@ use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
use OCP\AppFramework\Db\QBMapper;
class EventMapper extends QBMapper {
class FormMapper extends QBMapper {
/**
* EventMapper constructor.
* FormMapper constructor.
* @param IDBConnection $db
*/
public function __construct(IDBConnection $db) {
parent::__construct($db, 'forms_events', Event::class);
parent::__construct($db, 'forms_v2_forms', Form::class);
}
/**
* @param Integer $id
* @throws \OCP\AppFramework\Db\DoesNotExistException if not found
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException if more than one result
* @return Event
* @return Form
*/
public function find(int $id): Event {
public function find(int $id): Form {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
->where(
$qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT))
);
$qb->select('*')
->from($this->tableName)
->where(
$qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT))
);
return $this->findEntity($qb);
return $this->findEntity($qb);
}
/**
* @param String $hash
* @throws \OCP\AppFramework\Db\DoesNotExistException if not found
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException if more than one result
* @return Event
* @return Form
*/
public function findByHash(string $hash): Event {
public function findByHash(string $hash): Form {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
->where(
$qb->expr()->eq('hash', $qb->createNamedParameter($hash, IQueryBuilder::PARAM_STR))
);
$qb->select('*')
->from($this->tableName)
->where(
$qb->expr()->eq('hash', $qb->createNamedParameter($hash, IQueryBuilder::PARAM_STR))
);
return $this->findEntity($qb);
return $this->findEntity($qb);
}
/**
* @throws \OCP\AppFramework\Db\DoesNotExistException if not found
* @return Event[]
* @return Form[]
*/
public function findAll(): array {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName());
$qb->select('*')
->from($this->tableName);
return $this->findEntities($qb);
return $this->findEntities($qb);
}
}

58
lib/Db/Option.php Normal file
View file

@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019 Inigo Jiron <ijiron@terpmail.umd.edu>
*
* @author Inigo Jiron <ijiron@terpmail.umd.edu>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Forms\Db;
use OCP\AppFramework\Db\Entity;
/**
* @method integer getQuestionId()
* @method void setQuestionId(integer $value)
* @method string getText()
* @method void setText(string $value)
*/
class Option extends Entity {
/** @var int */
protected $questionId;
/** @var string */
protected $text;
/**
* Option constructor.
*/
public function __construct() {
$this->addType('questionId', 'integer');
$this->addType('text', 'string');
}
public function read(): array {
return [
'id' => $this->getId(),
'questionId' => $this->getQuestionId(),
'text' => htmlspecialchars_decode($this->getText()),
];
}
}

84
lib/Db/OptionMapper.php Normal file
View file

@ -0,0 +1,84 @@
<?php
declare(strict_types=1);
/**
* @copyright Copyright (c) 2019 Inigo Jiron <ijiron@terpmail.umd.edu>
*
* @author Inigo Jiron <ijiron@terpmail.umd.edu>
* @author Natalie Gilbert <ngilb634@umd.edu>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Forms\Db;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
use OCP\AppFramework\Db\QBMapper;
class OptionMapper extends QBMapper {
/**
* OptionMapper constructor.
* @param IDBConnection $db
*/
public function __construct(IDBConnection $db) {
parent::__construct($db, 'forms_v2_options', Option::class);
}
/**
* @param int $questionId
* @throws \OCP\AppFramework\Db\DoesNotExistException if not found
* @return Option[]
*/
public function findByQuestion(int $questionId): array {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
->where(
$qb->expr()->eq('question_id', $qb->createNamedParameter($questionId))
);
return $this->findEntities($qb);
}
public function deleteByQuestion(int $questionId): void {
$qb = $this->db->getQueryBuilder();
$qb->delete($this->getTableName())
->where(
$qb->expr()->eq('question_id', $qb->createNamedParameter($questionId))
);
$qb->execute();
}
public function findById(int $optionId): Option {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
->where(
$qb->expr()->eq('id', $qb->createNamedParameter($optionId))
);
return $this->findEntity($qb);
}
}

View file

@ -29,39 +29,34 @@ use OCP\AppFramework\Db\Entity;
/**
* @method integer getFormId()
* @method void setFormId(integer $value)
* @method string getFormQuestionType()
* @method void setFormQuestionType(string $value)
* @method string getFormQuestionText()
* @method void setFormQuestionText(string $value)
* @method integer getTimestamp()
* @method void setTimestamp(integer $value)
* @method string getType()
* @method void setType(string $value)
* @method string getText()
* @method void setText(string $value)
*/
class Question extends Entity {
protected $formId;
protected $formQuestionType;
protected $formQuestionText;
protected $timestamp;
protected $type;
protected $mandatory;
protected $text;
/**
* Question constructor.
*/
public function __construct() {
$this->addType('formId', 'integer');
$this->addType('timestamp', 'integer');
$this->addType('type', 'string');
$this->addType('mandatory', 'bool');
$this->addType('text', 'string');
}
public function read(): array {
return [
'id' => $this->getId(),
'formId' => $this->getFormId(),
'type' => htmlspecialchars_decode($this->getFormQuestionType()),
'text' => htmlspecialchars_decode($this->getFormQuestionText()),
'timestamp' => $this->getTimestamp()
'type' => htmlspecialchars_decode($this->getType()),
'mandatory' => $this->getMandatory(),
'text' => htmlspecialchars_decode($this->getText()),
];
}
}

View file

@ -28,16 +28,22 @@ use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
use OCP\AppFramework\Db\QBMapper;
use OCA\Forms\Db\OptionMapper;
class QuestionMapper extends QBMapper {
public function __construct(IDBConnection $db) {
parent::__construct($db, 'forms_questions', Question::class);
private $optionMapper;
public function __construct(IDBConnection $db, OptionMapper $optionMapper) {
parent::__construct($db, 'forms_v2_questions', Question::class);
$this->optionMapper = $optionMapper;
}
/**
* @param int $formId
* @throws \OCP\AppFramework\Db\DoesNotExistException if not found
* @return Option[]
* @return Question[]
*/
public function findByForm(int $formId): array {
@ -58,6 +64,13 @@ class QuestionMapper extends QBMapper {
public function deleteByForm(int $formId): void {
$qb = $this->db->getQueryBuilder();
// First delete corresponding options.
$questionEntities = $this->findByForm($formId);
foreach ($questionEntities as $questionEntity) {
$this->optionMapper->deleteByQuestion($questionEntity->id);
}
// Delete Questions
$qb->delete($this->getTableName())
->where(
$qb->expr()->eq('form_id', $qb->createNamedParameter($formId, IQueryBuilder::PARAM_INT))
@ -72,7 +85,7 @@ class QuestionMapper extends QBMapper {
$qb->select('*')
->from($this->getTableName())
->where(
$qb->expr()->eq('id', $qb->createNamedParameter($questionId))
$qb->expr()->eq('id', $qb->createNamedParameter($questionId, IQueryBuilder::PARAM_INT))
);
return $this->findEntity($qb);

View file

@ -2,12 +2,10 @@
declare(strict_types=1);
/**
* @copyright Copyright (c) 2017 Vinzenz Rosenkranz <vinzenz.rosenkranz@gmail.com>
* @copyright Copyright (c) 2020 Jonas Rittershofer <jotoeri@users.noreply.github.com>
*
* @author Jonas Rittershofer <jotoeri@users.noreply.github.com>
*
* @author Vinzenz Rosenkranz <vinzenz.rosenkranz@gmail.com>
* @author Kai Schröer <git@schroeer.co>
* @author René Gieling <github@dartcafe.de>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
@ -34,39 +32,27 @@ use OCP\AppFramework\Db\Entity;
* @method void setFormId(integer $value)
* @method string getUserId()
* @method void setUserId(string $value)
* @method integer getVoteOptionId()
* @method void setVoteOptionId(integer $value)
* @method string getVoteOptionText()
* @method void setVoteOptionText(string $value)
* @method string getVoteAnswer()
* @method void setVoteAnswer(string $value)
* @method string getVoteOptionType()
* @method void setVoteOptionType(string $value)
* @method string getTimestamp()
* @method void setTimestamp(string $value)
*/
class Vote extends Entity {
class Submission extends Entity {
protected $formId;
protected $userId;
protected $voteOptionId;
protected $voteOptionText;
protected $voteAnswer;
protected $voteOptionType;
protected $timestamp;
/**
* Options constructor.
* Submission constructor.
*/
public function __construct() {
$this->addType('formId', 'integer');
$this->addType('voteOptionId', 'integer');
}
public function read(): array {
return [
'id' => $this->getId(),
'formId' => $this->getFormId(),
'userId' => $this->getUserId(),
'voteOptionId' => $this->getVoteOptionId(),
'voteOptionText' => htmlspecialchars_decode($this->getVoteOptionText()),
'voteAnswer' => $this->getVoteAnswer(),
'voteOptionType' => $this->getVoteOptionType()
'timestamp' => $this->getTimestamp(),
];
}

View file

@ -1,10 +1,9 @@
<?php
/**
* @copyright Copyright (c) 2017 Vinzenz Rosenkranz <vinzenz.rosenkranz@gmail.com>
* @copyright Copyright (c) 2020 Jonas Rittershofer <jotoeri@users.noreply.github.com>
*
* @author Jonas Rittershofer <jotoeri@users.noreply.github.com>
*
* @author Vinzenz Rosenkranz <vinzenz.rosenkranz@gmail.com>
* @author René Gieling <github@dartcafe.de>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
@ -28,31 +27,38 @@ use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IDBConnection;
use OCP\AppFramework\Db\QBMapper;
class VoteMapper extends QBMapper {
use OCA\Forms\Db\AnswerMapper;
class SubmissionMapper extends QBMapper {
private $answerMapper;
/**
* VoteMapper constructor.
* SubmissionMapper constructor.
* @param IDBConnection $db
* @param AnswerMapper $answerMapper
*/
public function __construct(IDBConnection $db) {
parent::__construct($db, 'forms_votes', Vote::class);
public function __construct(IDBConnection $db, AnswerMapper $answerMapper) {
parent::__construct($db, 'forms_v2_submissions', Submission::class);
$this->answerMapper = $answerMapper;
}
/**
* @param int $formId
* @throws \OCP\AppFramework\Db\DoesNotExistException if not found
* @return Vote[]
* @return Submission[]
*/
public function findByForm(int $formId): array {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from($this->getTableName())
->where(
$qb->expr()->eq('form_id', $qb->createNamedParameter($formId, IQueryBuilder::PARAM_INT))
);
$qb->select('*')
->from($this->getTableName())
->where(
$qb->expr()->eq('form_id', $qb->createNamedParameter($formId, IQueryBuilder::PARAM_INT))
);
return $this->findEntities($qb);
return $this->findEntities($qb);
}
/**
@ -63,31 +69,13 @@ class VoteMapper extends QBMapper {
public function findParticipantsByForm(int $formId, $limit = null, $offset = null): array {
$qb = $this->db->getQueryBuilder();
$qb->select('user_id')
->from($this->getTableName())
->where(
$qb->expr()->eq('form_id', $qb->createNamedParameter($formId, IQueryBuilder::PARAM_INT))
);
$qb->select('user_id')
->from($this->getTableName())
->where(
$qb->expr()->eq('form_id', $qb->createNamedParameter($formId, IQueryBuilder::PARAM_INT))
);
return $this->findEntities($qb);
}
/**
* @param int $formId
* @param string $userId
*/
public function deleteByFormAndUser(int $formId, string $userId): void {
$qb = $this->db->getQueryBuilder();
$qb->delete($this->getTableName())
->where(
$qb->expr()->eq('form_id', $qb->createNamedParameter($formId, IQueryBuilder::PARAM_INT))
)
->andWhere(
$qb->expr()->eq('user_id', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR))
);
$qb->execute();
return $this->findEntities($qb);
}
/**
@ -96,6 +84,13 @@ class VoteMapper extends QBMapper {
public function deleteByForm(int $formId): void {
$qb = $this->db->getQueryBuilder();
// First delete corresponding answers.
$submissionEntities = $this->findByForm($formId);
foreach ($submissionEntities as $submissionEntity) {
$this->answerMapper->deleteBySubmission($submissionEntity->id);
}
//Delete Submissions
$qb->delete($this->getTableName())
->where(
$qb->expr()->eq('form_id', $qb->createNamedParameter($formId, IQueryBuilder::PARAM_INT))

View file

@ -1,210 +0,0 @@
<?php
/**
* @copyright Copyright (c) 2017 René Gieling <github@dartcafe.de>
*
* @author René Gieling <github@dartcafe.de>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Forms\Migration;
use Doctrine\DBAL\Exception\TableNotFoundException;
use Doctrine\DBAL\Platforms\PostgreSqlPlatform;
use Doctrine\DBAL\Types\Type;
use OCP\DB\ISchemaWrapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\Migration\SimpleMigrationStep;
use OCP\Migration\IOutput;
/**
* Installation class for the forms app.
* Initial db creation
*/
class Version0009Date20190000000006 extends SimpleMigrationStep {
/** @var IDBConnection */
protected $connection;
/** @var IConfig */
protected $config;
/**
* @param IDBConnection $connection
* @param IConfig $config
*/
public function __construct(IDBConnection $connection, IConfig $config) {
$this->connection = $connection;
$this->config = $config;
}
/**
* @param IOutput $output
* @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
* @param array $options
* @return null|ISchemaWrapper
* @since 13.0.0
*/
public function changeSchema(IOutput $output, \Closure $schemaClosure, array $options) {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();
if (!$schema->hasTable('forms_events')) {
$table = $schema->createTable('forms_events');
$table->addColumn('id', Type::INTEGER, [
'autoincrement' => true,
'notnull' => true,
]);
$table->addColumn('hash', Type::STRING, [
'notnull' => false,
'length' => 64,
]);
$table->addColumn('title', Type::STRING, [
'notnull' => true,
'length' => 128,
]);
$table->addColumn('description', Type::STRING, [
'notnull' => true,
'length' => 1024,
]);
$table->addColumn('owner', Type::STRING, [
'notnull' => true,
'length' => 64,
]);
$table->addColumn('created', Type::DATETIME, [
'notnull' => false,
]);
$table->addColumn('access', Type::STRING, [
'notnull' => false,
'length' => 1024,
]);
$table->addColumn('expire', Type::DATETIME, [
'notnull' => false,
]);
$table->addColumn('is_anonymous', Type::INTEGER, [
'notnull' => false,
'default' => 0,
]);
$table->addColumn('full_anonymous', Type::INTEGER, [
'notnull' => false,
'default' => 0,
]);
$table->setPrimaryKey(['id']);
} else {
}
if (!$schema->hasTable('forms_questions')) {
$table = $schema->createTable('forms_questions');
$table->addColumn('id', Type::INTEGER, [
'autoincrement' => true,
'notnull' => true,
]);
$table->addColumn('form_id', Type::INTEGER, [
'notnull' => false,
]);
$table->addColumn('form_question_type', Type::STRING, [
'notnull' => false, // maybe true?
'length' => 256,
]);
$table->addColumn('form_question_text', Type::STRING, [
'notnull' => false, // maybe true?
'length' => 4096,
]);
$table->addColumn('timestamp', Type::INTEGER, [
'notnull' => false,
'default' => 0
]);
$table->setPrimaryKey(['id']);
}
if (!$schema->hasTable('forms_answers')) {
$table = $schema->createTable('forms_answers');
$table->addColumn('id', Type::INTEGER, [
'autoincrement' => true,
'notnull' => true,
]);
$table->addColumn('form_id', Type::INTEGER, [
'notnull' => false,
]);
$table->addColumn('question_id', Type::INTEGER, [
'notnull' => false,
]);
$table->addColumn('text', Type::STRING, [
'notnull' => false, // maybe true?
'length' => 4096,
]);
$table->addColumn('timestamp', Type::INTEGER, [
'notnull' => false,
'default' => 0
]);
$table->setPrimaryKey(['id']);
}
if (!$schema->hasTable('forms_votes')) {
$table = $schema->createTable('forms_votes');
$table->addColumn('id', Type::INTEGER, [
'autoincrement' => true,
'notnull' => true,
]);
$table->addColumn('form_id', Type::INTEGER, [
'notnull' => false,
]);
$table->addColumn('user_id', Type::STRING, [
'notnull' => true,
'length' => 64,
]);
$table->addColumn('vote_option_id', Type::INTEGER, [
'notnull' => true,
'default' => 0,
'length' => 64,
]);
$table->addColumn('vote_option_text', Type::STRING, [
'notnull' => false, // maybe true?
'length' => 4096,
]);
$table->addColumn('vote_answer', Type::STRING, [
'notnull' => false,
'length' => 4096,
]);
$table->addColumn('vote_option_type', Type::STRING, [
'notnull' => false,
'length' => 256,
]);
$table->setPrimaryKey(['id']);
}
if (!$schema->hasTable('forms_notif')) {
$table = $schema->createTable('forms_notif');
$table->addColumn('id', Type::INTEGER, [
'autoincrement' => true,
'notnull' => true,
]);
$table->addColumn('form_id', Type::INTEGER, [
'notnull' => false,
]);
$table->addColumn('user_id', Type::STRING, [
'notnull' => true,
'length' => 64,
]);
$table->setPrimaryKey(['id']);
}
return $schema;
}
}

View file

@ -23,7 +23,9 @@ class Version010102Date20200323120846 extends SimpleMigrationStep {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();
$schema->dropTable('forms_notif');
if ($schema->hasTable('forms_notif')) {
$schema->dropTable('forms_notif');
}
return $schema;
}

View file

@ -0,0 +1,352 @@
<?php
/**
* @copyright Copyright (c) 2020 Jonas Rittershofer <jotoeri@users.noreply.github.com>
*
* @author Jonas Rittershofer <jotoeri@users.noreply.github.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
namespace OCA\Forms\Migration;
use Doctrine\DBAL\Exception\TableNotFoundException;
use Doctrine\DBAL\Platforms\PostgreSqlPlatform;
use Doctrine\DBAL\Types\Type;
use OCP\DB\ISchemaWrapper;
use OCP\DB\QueryBuilder\IQueryBuilder;
use OCP\IConfig;
use OCP\IDBConnection;
use OCP\Migration\SimpleMigrationStep;
use OCP\Migration\IOutput;
/**
* Installation class for the forms app.
* Initial db creation
*/
class Version010200Date2020323141300 extends SimpleMigrationStep {
/** @var IDBConnection */
protected $connection;
/** @var IConfig */
protected $config;
/**
* @param IDBConnection $connection
* @param IConfig $config
*/
public function __construct(IDBConnection $connection, IConfig $config) {
$this->connection = $connection;
$this->config = $config;
}
/**
* @param IOutput $output
* @param \Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
* @param array $options
* @return null|ISchemaWrapper
* @since 13.0.0
*/
public function changeSchema(IOutput $output, \Closure $schemaClosure, array $options) {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();
if (!$schema->hasTable('forms_v2_forms')) {
$table = $schema->createTable('forms_v2_forms');
$table->addColumn('id', Type::INTEGER, [
'autoincrement' => true,
'notnull' => true,
]);
$table->addColumn('hash', Type::STRING, [
'notnull' => true,
'length' => 64,
]);
$table->addColumn('title', Type::STRING, [
'notnull' => true,
'length' => 256,
]);
$table->addColumn('description', Type::STRING, [
'notnull' => false,
'length' => 2048,
]);
$table->addColumn('owner_id', Type::STRING, [
'notnull' => true,
'length' => 64,
]);
$table->addColumn('access', Type::STRING, [
'notnull' => false,
'length' => 1024,
]);
$table->addColumn('created', Type::DATETIME, [
'notnull' => false,
]);
$table->addColumn('expiration_date', Type::DATETIME, [
'notnull' => false,
]);
$table->addColumn('is_anonymous', Type::BOOLEAN, [
'notnull' => true,
'default' => 0,
]);
$table->addColumn('submit_once', Type::BOOLEAN, [
'notnull' => true,
'default' => 0,
]);
$table->setPrimaryKey(['id']);
}
if (!$schema->hasTable('forms_v2_questions')) {
$table = $schema->createTable('forms_v2_questions');
$table->addColumn('id', Type::INTEGER, [
'autoincrement' => true,
'notnull' => true,
]);
$table->addColumn('form_id', Type::INTEGER, [
'notnull' => true,
]);
$table->addColumn('type', Type::STRING, [
'notnull' => true,
'length' => 256,
]);
$table->addColumn('mandatory', Type::BOOLEAN, [
'notnull' => true,
'default' => 1,
]);
$table->addColumn('text', Type::STRING, [
'notnull' => true,
'length' => 2048,
]);
$table->setPrimaryKey(['id']);
}
if (!$schema->hasTable('forms_v2_options')) {
$table = $schema->createTable('forms_v2_options');
$table->addColumn('id', Type::INTEGER, [
'autoincrement' => true,
'notnull' => true,
]);
$table->addColumn('question_id', Type::INTEGER, [
'notnull' => true,
]);
$table->addColumn('text', Type::STRING, [
'notnull' => true,
'length' => 1024,
]);
$table->setPrimaryKey(['id']);
}
if (!$schema->hasTable('forms_v2_submissions')) {
$table = $schema->createTable('forms_v2_submissions');
$table->addColumn('id', Type::INTEGER, [
'autoincrement' => true,
'notnull' => true,
]);
$table->addColumn('form_id', Type::INTEGER, [
'notnull' => true,
]);
$table->addColumn('user_id', Type::STRING, [
'notnull' => true,
'length' => 64,
]);
$table->addColumn('timestamp', Type::DATETIME, [
'notnull' => false,
]);
$table->setPrimaryKey(['id']);
}
if (!$schema->hasTable('forms_v2_answers')) {
$table = $schema->createTable('forms_v2_answers');
$table->addColumn('id', Type::INTEGER, [
'autoincrement' => true,
'notnull' => true,
]);
$table->addColumn('submission_id', Type::INTEGER, [
'notnull' => true,
]);
$table->addColumn('question_id', Type::INTEGER, [
'notnull' => true,
]);
$table->addColumn('text', Type::STRING, [
'notnull' => true,
'length' => 2048,
]);
$table->setPrimaryKey(['id']);
}
return $schema;
}
public function postSchemaChange(IOutput $output, \Closure $schemaClosure, array $options) {
/** @var ISchemaWrapper $schema */
$schema = $schemaClosure();
// if Database exists.
if( $schema->hasTable('forms_events') ){
$id_mapping = [];
$id_mapping['events'] = []; // Maps oldevent-id => newevent-id
$id_mapping['questions'] = []; // Maps oldquestion-id => newquestion-id
//Fetch & Restore Events
$qb_fetch = $this->connection->getQueryBuilder();
$qb_restore = $this->connection->getQueryBuilder();
$qb_fetch->select('id', 'hash', 'title', 'description', 'owner', 'created', 'access', 'expire', 'is_anonymous', 'unique')
->from('forms_events');
$cursor = $qb_fetch->execute();
while( $event = $cursor->fetch() ){
$qb_restore->insert('forms_v2_forms')
->values([
'hash' => $qb_restore->createNamedParameter($event['hash'], IQueryBuilder::PARAM_STR),
'title' => $qb_restore->createNamedParameter($event['title'], IQueryBuilder::PARAM_STR),
'description' => $qb_restore->createNamedParameter($event['description'], IQueryBuilder::PARAM_STR),
'owner_id' => $qb_restore->createNamedParameter($event['owner'], IQueryBuilder::PARAM_STR),
'access' => $qb_restore->createNamedParameter($event['access'], IQueryBuilder::PARAM_STR),
'created' => $qb_restore->createNamedParameter($event['created'], IQueryBuilder::PARAM_STR),
'expiration_date' => $qb_restore->createNamedParameter($event['expire'], IQueryBuilder::PARAM_STR),
'is_anonymous' => $qb_restore->createNamedParameter($event['is_anonymous'], IQueryBuilder::PARAM_BOOL),
'submit_once' => $qb_restore->createNamedParameter($event['unique'], IQueryBuilder::PARAM_BOOL)
]);
$qb_restore->execute();
$id_mapping['events'][$event['id']] = $qb_restore->getLastInsertId(); //Store new form-id to connect questions to new form.
}
$cursor->closeCursor();
//Fetch & restore Questions
$qb_fetch = $this->connection->getQueryBuilder();
$qb_restore = $this->connection->getQueryBuilder();
$qb_fetch->select('id', 'form_id', 'form_question_type', 'form_question_text')
->from('forms_questions');
$cursor = $qb_fetch->execute();
while( $question = $cursor->fetch() ){
//In case the old Question would have been longer than current possible length, create a warning and shorten text to avoid Error on upgrade.
if(strlen($question['form_question_text']) > 2048) {
$output->warning("Question-text is too long for new Database: '" . $question['form_question_text'] . "'");
$question['form_question_text'] = substr($question['form_question_text'], 0, 2048);
}
$qb_restore->insert('forms_v2_questions')
->values([
'form_id' => $qb_restore->createNamedParameter($id_mapping['events'][$question['form_id']], IQueryBuilder::PARAM_INT),
'type' => $qb_restore->createNamedParameter($question['form_question_type'], IQueryBuilder::PARAM_STR),
'text' => $qb_restore->createNamedParameter($question['form_question_text'], IQueryBuilder::PARAM_STR)
]);
$qb_restore->execute();
$id_mapping['questions'][$question['id']] = $qb_restore->getLastInsertId(); //Store new question-id to connect options to new question.
}
$cursor->closeCursor();
//Fetch all Answers and restore to Options
$qb_fetch = $this->connection->getQueryBuilder();
$qb_restore = $this->connection->getQueryBuilder();
$qb_fetch->select('question_id', 'text')
->from('forms_answers');
$cursor = $qb_fetch->execute();
while( $answer = $cursor->fetch() ){
//In case the old Answer would have been longer than current possible length, create a warning and shorten text to avoid Error on upgrade.
if(strlen($answer['text']) > 1024) {
$output->warning("Option-text is too long for new Database: '" . $answer['text'] . "'");
$answer['text'] = substr($answer['text'], 0, 1024);
}
$qb_restore->insert('forms_v2_options')
->values([
'question_id' => $qb_restore->createNamedParameter($id_mapping['questions'][$answer['question_id']], IQueryBuilder::PARAM_INT),
'text' => $qb_restore->createNamedParameter($answer['text'], IQueryBuilder::PARAM_STR)
]);
$qb_restore->execute();
}
$cursor->closeCursor();
/* Fetch old id_structure of event-ids and question-ids
* This is necessary to restore the $oldQuestionId, as the vote_option_ids do not use the true question_ids
*/
$event_structure = [];
$qb_fetch = $this->connection->getQueryBuilder();
$qb_fetch->select('id')
->from('forms_events');
$cursor = $qb_fetch->execute();
while( $tmp = $cursor->fetch() ){
$event_structure[$tmp['id']] = $tmp;
}
$cursor->closeCursor();
foreach ($event_structure as $eventkey => $event) {
$event_structure[$eventkey]['questions'] = [];
$qb_fetch = $this->connection->getQueryBuilder();
$qb_fetch->select('id', 'form_question_text')
->from('forms_questions')
->where($qb_fetch->expr()->eq('form_id', $qb_fetch->createNamedParameter($event['id'], IQueryBuilder::PARAM_INT)));
$cursor = $qb_fetch->execute();
while( $tmp = $cursor->fetch() ) {
$event_structure[$event['id']]['questions'][] = $tmp;
}
$cursor->closeCursor();
}
//Fetch Votes and restore to Submissions & Answers
$qb_fetch = $this->connection->getQueryBuilder();
$qb_restore = $this->connection->getQueryBuilder();
//initialize $last_vote
$last_vote = [];
$last_vote['form_id'] = 0;
$last_vote['user_id'] = '';
$last_vote['vote_option_id'] = 0;
$qb_fetch->select('id', 'form_id', 'user_id', 'vote_option_id', 'vote_option_text', 'vote_answer')
->from('forms_votes');
$cursor = $qb_fetch->execute();
while( $vote = $cursor->fetch() ){
//If the form changed, if the user changed or if vote_option_id became smaller than last one, then a new submission is interpreted.
if ( ($vote['form_id'] != $last_vote['form_id']) || ($vote['user_id'] != $last_vote['user_id']) || ($vote['vote_option_id'] < $last_vote['vote_option_id'])) {
$qb_restore->insert('forms_v2_submissions')
->values([
'form_id' => $qb_restore->createNamedParameter($id_mapping['events'][$vote['form_id']], IQueryBuilder::PARAM_INT),
'user_id' => $qb_restore->createNamedParameter($vote['user_id'], IQueryBuilder::PARAM_STR),
'timestamp' => $qb_restore->createNamedParameter(date('Y-m-d H:i:s'), IQueryBuilder::PARAM_STR) //Information not available. Just using Migration-Timestamp.
]);
$qb_restore->execute();
$id_mapping['currentSubmission'] = $qb_restore->getLastInsertId(); //Store submission-id to connect answers to submission.
}
$last_vote = $vote;
//In case the old Answer would have been longer than current possible length, create a warning and shorten text to avoid Error on upgrade.
if(strlen($vote['vote_answer']) > 2048) {
$output->warning("Answer-text is too long for new Database: '" . $vote['vote_answer'] . "'");
$vote['vote_answer'] = substr($vote['vote_answer'], 0, 2048);
}
/* Due to the unconventional storing fo vote_option_ids, the vote_option_id needs to get mapped onto old question-id and from there to new question-id.
* vote_option_ids count from 1 to x for the questions of a form. So the question at point [$vote[vote_option_id] - 1] within the id-structure is the corresponding question.
*/
$oldQuestionId = $event_structure[$vote['form_id']]['questions'][$vote['vote_option_id']-1]['id'];
//Just throw an Error, if aboves QuestionId-Mapping went wrong. Double-Checked by Question-Text.
if ($event_structure[$vote['form_id']]['questions'][$vote['vote_option_id']-1]['form_question_text'] != $vote['vote_option_text']) {
$output->warning("Some Question-Mapping went wrong within Submission-Mapping to new Database. On 'vote_id': " . $vote['id'] . " - 'vote_option_text': '" . $vote['vote_option_text'] . "'");
}
$qb_restore->insert('forms_v2_answers')
->values([
'submission_id' => $qb_restore->createNamedParameter($id_mapping['currentSubmission'], IQueryBuilder::PARAM_INT),
'question_id' => $qb_restore->createNamedParameter($id_mapping['questions'][$oldQuestionId], IQueryBuilder::PARAM_STR),
'text' => $qb_restore->createNamedParameter($vote['vote_answer'], IQueryBuilder::PARAM_STR)
]);
$qb_restore->execute();
}
}
}
}

View file

@ -107,7 +107,7 @@ export default {
selectedForm() {
// TODO: replace with form.hash
return this.forms.find(form => form.event.hash === this.hash)
return this.forms.find(form => form.form.hash === this.hash)
},
},
@ -141,7 +141,7 @@ export default {
const response = await axios.post(generateUrl('/apps/forms/api/v1/form'))
const newForm = response.data
this.forms.push(newForm)
this.$router.push({ name: 'edit', params: { hash: newForm.event.hash } })
this.$router.push({ name: 'edit', params: { hash: newForm.form.hash } })
} catch (error) {
showError(t('forms', 'Unable to create a new form'))
console.error(error)

View file

@ -26,8 +26,8 @@
<span v-if="options.expired" class="expired">
{{ t('forms', 'Expired') }}
</span>
<span v-if="options.expiration" class="open">
{{ t('forms', 'Expires %n', 1, expirationdate) }}
<span v-if="options.expires" class="open">
{{ t('forms', 'Expires %n', 1, expirationDate) }}
</span>
<span v-else class="open">
{{ t('forms', 'Expires never') }}
@ -39,12 +39,6 @@
<span v-if="options.isAnonymous" class="information">
{{ t('forms', 'Anonymous form') }}
</span>
<span v-if="options.fullAnonymous" class="information">
{{ t('forms', 'Usernames hidden to Owner') }}
</span>
<span v-if="options.isAnonymous & !options.fullAnonymous" class="information">
{{ t('forms', 'Usernames visible to Owner') }}
</span>
</div>
</template>
@ -61,7 +55,7 @@ export default {
},
computed: {
expirationdate() {
expirationDate() {
const date = moment(this.options.expirationDate, moment.localeData().longDateFormat('L')).fromNow()
return date
},

View file

@ -72,12 +72,12 @@
<img class="icontwo">
</div>
<div class="symbol icon-voted" />
<a :href="voteUrl" class="wrapper group-1-1">
<a :href="submitUrl" class="wrapper group-1-1">
<div class="name">
{{ form.event.title }}
{{ form.form.title }}
</div>
<div class="description">
{{ form.event.description }}
{{ form.form.description }}
</div>
</a>
</div>
@ -102,18 +102,18 @@
</div>
<div class="wrapper group-2-1">
<div v-tooltip="accessType" class="thumbnail access" :class="form.event.access">
<div v-tooltip="accessType" class="thumbnail access" :class="form.form.access">
{{ accessType }}
</div>
</div>
<div class="owner">
<UserDiv :user-id="form.event.owner" :display-name="form.event.ownerDisplayName" />
<UserDiv :user-id="form.form.ownerId" :display-name="form.form.ownerDisplayName" />
</div>
<div class="wrapper group-2-2">
<div class="created ">
{{ timeSpanCreated }}
</div>
<div class="expiry" :class="{ expired : form.event.expired }">
<div class="expiry" :class="{ expired : form.form.expired }">
{{ timeSpanExpiration }}
</div>
</div>
@ -150,13 +150,13 @@ export default {
computed: {
accessType() {
if (this.form.event.access === 'public') {
if (this.form.form.access === 'public') {
return t('forms', 'Public access')
} else if (this.form.event.access === 'select') {
} else if (this.form.form.access === 'select') {
return t('forms', 'Only shared')
} else if (this.form.event.access === 'registered') {
} else if (this.form.form.access === 'registered') {
return t('forms', 'Registered users only')
} else if (this.form.event.access === 'hidden') {
} else if (this.form.form.access === 'hidden') {
return t('forms', 'Hidden form')
} else {
return ''
@ -164,12 +164,12 @@ export default {
},
timeSpanCreated() {
return moment(this.form.event.created, 'YYYY-MM-DD HH:mm')
return moment(this.form.form.created, 'YYYY-MM-DD HH:mm')
},
timeSpanExpiration() {
if (this.form.event.expiration) {
return moment(this.form.event.expirationDate)
if (this.form.form.expires) {
return moment(this.form.form.expirationDate)
} else {
return t('forms', 'never')
}
@ -179,8 +179,8 @@ export default {
return this.form.shares.length
},
voteUrl() {
return OC.generateUrl('apps/forms/form/') + this.form.event.hash
submitUrl() {
return OC.generateUrl('apps/forms/form/') + this.form.form.hash
},
},
@ -196,7 +196,7 @@ export default {
copyLink() {
// this.$emit('copyLink')
this.$copyText(window.location.origin + this.voteUrl).then(
this.$copyText(window.location.origin + this.submitUrl).then(
function(e) {
OC.Notification.showTemporary(t('forms', 'Link copied to clipboard'))
},
@ -217,6 +217,7 @@ export default {
},
}
</script>
<style lang="scss">
$row-padding: 15px;
$table-padding: 4px;
@ -293,8 +294,6 @@ $mediabreak-3: $group-1-width + $owner-width + max($group-2-1-width, $group-2-2-
align-items: center;
position: relative;
flex-grow: 0;
div {
}
}
.name {

View file

@ -24,21 +24,20 @@
<div>{{ question.text }}</div>
<div>
<input v-show="(question.type != 'text') && (question.type != 'comment')"
v-model="newQuizAnswer"
v-model="newOption"
style="height:30px;"
:placeholder=" t('forms', 'Add Answer')"
@keyup.enter="emitNewAnswer(question)">
:placeholder=" t('forms', 'Add Option')"
@keyup.enter="emitNewOption(question)">
<transitionGroup
id="form-list"
name="list"
tag="ul"
class="form-table">
<TextFormItem
v-for="(answer, index) in answers"
:key="answer.id"
:option="answer"
@remove="emitRemoveAnswer(question, answer, index)"
@delete="question.answers.splice(index, 1)" />
v-for="(opt, index) in options"
:key="opt.id"
:option="opt"
@remove="emitRemoveOption(question, opt, index)" />
</transitionGroup>
</div>
<div>
@ -61,25 +60,25 @@ export default {
},
data() {
return {
nextQuizAnswerId: 1,
newQuizAnswer: '',
nextOptionId: 1,
newOption: '',
type: '',
}
},
computed: {
answers() {
return this.question.answers || []
options() {
return this.question.options || []
},
},
methods: {
emitNewAnswer(question) {
this.$emit('add-answer', this, question)
emitNewOption(question) {
this.$emit('addOption', this, question)
},
emitRemoveAnswer(question, answer, index) {
this.$emit('remove-answer', question, answer, index)
emitRemoveOption(question, option, index) {
this.$emit('deleteOption', question, option, index)
},
},
}

View file

@ -66,7 +66,7 @@
</div>
<div class="wrapper group-2">
<div class="ans">
{{ answers }}
{{ answerText }}
</div>
</div>
</div>
@ -83,7 +83,7 @@ export default {
type: Object,
default: undefined,
},
vote: {
answer: {
type: Object,
default: undefined,
},
@ -98,16 +98,16 @@ export default {
computed: {
participants() {
return this.vote.userId
return this.answer.userId
},
questionText() {
return this.vote.voteOptionText
return this.answer.questionText
},
answers() {
return this.vote.voteAnswer
answerText() {
return this.answer.text
},
questionNum() {
return this.vote.voteOptionId
return this.answer.questionId
},
},
}

View file

@ -27,17 +27,10 @@
*/
const formatForm = function(form) {
// clone form
const newForm = Object.assign({}, form, form.event)
// migrate object architecture
Object.assign(newForm, {
questions: form.options && form.options.formQuizQuestions,
})
const newForm = Object.assign({}, form, form.form)
// cleanup
delete newForm.options
delete newForm.event
return newForm
}

View file

@ -33,12 +33,12 @@
<label>{{ t('forms', 'Title') }}</label>
<input id="formTitle"
v-model="form.event.title"
v-model="form.form.title"
:class="{ error: titleEmpty }"
type="text">
<label>{{ t('forms', 'Description') }}</label>
<textarea id="formDesc" v-model="form.event.description" style="resize: vertical; width: 100%;" />
<textarea id="formDesc" v-model="form.form.description" style="resize: vertical; width: 100%;" />
</div>
<div>
@ -50,11 +50,15 @@
<option value="" disabled>
Select
</option>
<option v-for="option in options" :key="option.value" :value="option.value">
{{ option.text }}
<option v-for="type in questionTypes" :key="type.value" :value="type.value">
{{ type.text }}
</option>
</select>
<input v-model="newQuizQuestion" :placeholder="t('forms', 'Add Question')" @keyup.enter="addQuestion()">
<input
v-model="newQuestion"
:placeholder=" t('forms', 'Add Question') "
maxlength="2048"
@keyup.enter="addQuestion()">
<button id="questButton"
@click="addQuestion()">
{{ t('forms', 'Add Question') }}
@ -67,12 +71,12 @@
tag="ul"
class="form-table">
<QuizFormItem
v-for="(question, index) in form.options.formQuizQuestions"
v-for="(question, index) in form.questions"
:key="question.id"
:question="question"
:type="question.type"
@add-answer="addAnswer"
@remove-answer="deleteAnswer"
@addOption="addOption"
@deleteOption="deleteOption"
@deleteQuestion="deleteQuestion(question, index)" />
</transitionGroup>
</div>
@ -105,18 +109,18 @@ export default {
data() {
return {
placeholder: '',
newQuizAnswer: '',
newQuizQuestion: '',
nextQuizAnswerId: 1,
nextQuizQuestionId: 1,
newOption: '',
newQuestion: '',
nextOptionId: 1,
nextQuestionId: 1,
writingForm: false,
loadingForm: true,
titleEmpty: false,
selected: '',
uniqueName: false,
uniqueAns: false,
haveAns: false,
options: [
uniqueQuestionText: false,
uniqueOptionText: false,
allHaveOpt: false,
questionTypes: [
{ text: 'Radio Buttons', value: 'radiogroup' },
{ text: 'Checkboxes', value: 'checkbox' },
{ text: 'Short Response', value: 'text' },
@ -132,10 +136,10 @@ export default {
},
title() {
if (this.form.event.title === '') {
if (this.form.form.title === '') {
return t('forms', 'Create new form')
} else {
return this.form.event.title
return this.form.form.title
}
},
@ -173,7 +177,7 @@ export default {
created() {
if (this.$route.name === 'create') {
// TODO: manage this from Forms.vue, request a new form to the server
this.form.event.owner = OC.getCurrentUser().uid
this.form.form.owner = OC.getCurrentUser().uid
this.loadingForm = false
} else if (this.$route.name === 'edit') {
// TODO: fetch & update form?
@ -189,81 +193,81 @@ export default {
this.sidebar = !this.sidebar
},
checkNames() {
this.uniqueName = true
this.form.options.formQuizQuestions.forEach(q => {
if (q.text === this.newQuizQuestion) {
this.uniqueName = false
checkQuestionText() {
this.uniqueQuestionText = true
this.form.questions.forEach(q => {
if (q.text === this.newQuestion) {
this.uniqueQuestionText = false
}
})
},
async addQuestion() {
this.checkNames()
this.checkQuestionText()
if (this.selected === '') {
showError(t('forms', 'Select a question type!'), { duration: 3000 })
} else if (!this.uniqueName) {
} else if (!this.uniqueQuestionText) {
showError(t('forms', 'Cannot have the same question!'))
} else {
if (this.newQuizQuestion !== null & this.newQuizQuestion !== '' & (/\S/.test(this.newQuizQuestion))) {
const response = await axios.post(generateUrl('/apps/forms/api/v1/question/'), { formId: this.form.id, type: this.selected, text: this.newQuizQuestion })
if (this.newQuestion !== null & this.newQuestion !== '' & (/\S/.test(this.newQuestion))) {
const response = await axios.post(generateUrl('/apps/forms/api/v1/question/'), { formId: this.form.id, type: this.selected, text: this.newQuestion })
const questionId = response.data
this.form.options.formQuizQuestions.push({
this.form.questions.push({
id: questionId,
text: this.newQuizQuestion,
text: this.newQuestion,
type: this.selected,
answers: [],
options: [],
})
}
this.newQuizQuestion = ''
this.newQuestion = ''
}
},
async deleteQuestion(question, index) {
await axios.delete(generateUrl('/apps/forms/api/v1/question/{id}', { id: question.id }))
// TODO catch Error
this.form.options.formQuizQuestions.splice(index, 1)
this.form.questions.splice(index, 1)
},
checkAnsNames(item, question) {
this.uniqueAnsName = true
question.answers.forEach(q => {
if (q.text === item.newQuizAnswer) {
this.uniqueAnsName = false
checkOptionText(item, question) {
this.uniqueOptionText = true
question.options.forEach(o => {
if (o.text === item.newOption) {
this.uniqueOptionText = false
}
})
},
async addAnswer(item, question) {
this.checkAnsNames(item, question)
if (!this.uniqueAnsName) {
showError(t('forms', 'Two answers cannot be the same!'), { duration: 3000 })
async addOption(item, question) {
this.checkOptionText(item, question)
if (!this.uniqueOptionText) {
showError(t('forms', 'Two options cannot be the same!'), { duration: 3000 })
} else {
if (item.newQuizAnswer !== null & item.newQuizAnswer !== '' & (/\S/.test(item.newQuizAnswer))) {
const response = await axios.post(generateUrl('/apps/forms/api/v1/answer/'), { formId: this.form.id, questionId: question.id, text: item.newQuizAnswer })
const answerId = response.data
if (item.newOption !== null & item.newOption !== '' & (/\S/.test(item.newOption))) {
const response = await axios.post(generateUrl('/apps/forms/api/v1/option/'), { formId: this.form.id, questionId: question.id, text: item.newOption })
const optionId = response.data
question.answers.push({
id: answerId,
text: item.newQuizAnswer,
question.options.push({
id: optionId,
text: item.newOption,
})
}
item.newQuizAnswer = ''
item.newOption = ''
}
},
async deleteAnswer(question, answer, index) {
await axios.delete(generateUrl('/apps/forms/api/v1/answer/{id}', { id: answer.id }))
async deleteOption(question, option, index) {
await axios.delete(generateUrl('/apps/forms/api/v1/option/{id}', { id: option.id }))
// TODO catch errors
question.answers.splice(index, 1)
question.options.splice(index, 1)
},
allHaveAns() {
this.haveAns = true
this.form.options.formQuizQuestions.forEach(q => {
if (q.type !== 'text' && q.type !== 'comment' && q.answers.length === 0) {
this.haveAns = false
checkAllHaveOpt() {
this.allHaveOpt = true
this.form.questions.forEach(q => {
if (q.type !== 'text' && q.type !== 'comment' && q.options.length === 0) {
this.allHaveOpt = false
}
})
},
@ -273,13 +277,13 @@ export default {
}, 200),
writeForm() {
this.allHaveAns()
if (this.form.event.title.length === 0 | !(/\S/.test(this.form.event.title))) {
this.checkAllHaveOpt()
if (this.form.form.title.length === 0 | !(/\S/.test(this.form.form.title))) {
this.titleEmpty = true
showError(t('forms', 'Title must not be empty!'), { duration: 3000 })
} else if (!this.haveAns) {
} else if (!this.allHaveOpt) {
showError(t('forms', 'All questions need answers!'), { duration: 3000 })
} else if (this.form.event.expiration & this.form.event.expirationDate === '') {
} else if (this.form.form.expires & this.form.form.expirationDate === '') {
showError(t('forms', 'Need to pick an expiration date!'), { duration: 3000 })
} else {
this.writingForm = true
@ -288,12 +292,12 @@ export default {
axios.post(OC.generateUrl('apps/forms/write/form'), this.form)
.then((response) => {
this.form.mode = 'edit'
this.form.event.hash = response.data.hash
this.form.event.id = response.data.id
this.form.form.hash = response.data.hash
this.form.form.id = response.data.id
this.writingForm = false
showSuccess(t('forms', '%n successfully saved', 1, this.form.event.title), { duration: 3000 })
showSuccess(t('forms', '%n successfully saved', 1, this.form.form.title), { duration: 3000 })
}, (error) => {
this.form.event.hash = ''
this.form.form.hash = ''
this.writingForm = false
showError(t('forms', 'Error on saving form, see console'))
/* eslint-disable-next-line no-console */

View file

@ -43,8 +43,8 @@
v-for="(form, index) in forms"
:key="form.id"
:form="form"
@deleteForm="removeForm(index, form.event)"
@viewResults="viewFormResults(index, form.event, 'results')" />
@deleteForm="removeForm(index, form.form)"
@viewResults="viewFormResults(index, form.form, 'results')" />
</transition-group>
<LoadingOverlay v-if="loading" />
<modal-dialog />
@ -110,29 +110,28 @@ export default {
helpPage() {
window.open('https://github.com/nextcloud/forms/blob/master/Forms_Support.md')
},
viewFormResults(index, event, name) {
viewFormResults(index, form, name) {
this.$router.push({
name: name,
params: {
hash: event.id,
hash: form.id,
},
})
},
removeForm(index, event) {
removeForm(index, form) {
const params = {
title: t('forms', 'Delete form'),
text: t('forms', 'Do you want to delete "%n"?', 1, event.title),
text: t('forms', 'Do you want to delete "%n"?', 1, form.title),
buttonHideText: t('forms', 'No, keep form.'),
buttonConfirmText: t('forms', 'Yes, delete form.'),
onConfirm: () => {
// this.deleteForm(index, event)
axios.delete(OC.generateUrl('apps/forms/forms/{id}', { id: event.id }))
// this.deleteForm(index, form)
axios.delete(OC.generateUrl('apps/forms/forms/{id}', { id: form.id }))
.then((response) => {
this.forms.splice(index, 1)
OC.Notification.showTemporary(t('forms', 'Form "%n" deleted', 1, event.title))
OC.Notification.showTemporary(t('forms', 'Form "%n" deleted', 1, form.title))
}, (error) => {
OC.Notification.showTemporary(t('forms', 'Error while deleting Form "%n"', 1, event.title))
OC.Notification.showTemporary(t('forms', 'Error while deleting Form "%n"', 1, form.title))
/* eslint-disable-next-line no-console */
console.log(error.response)
}

View file

@ -43,10 +43,10 @@
:header="true" />
<li
is="resultItem"
v-for="(vote, index) in votes"
:key="vote.id"
:vote="vote"
@viewResults="viewFormResults(index, form.event, 'results')" />
v-for="(answer, index) in answers"
:key="answer.id"
:answer="answer"
@viewResults="viewFormResults(index, form.form, 'results')" />
</transition-group>
<LoadingOverlay v-if="loading" />
<modal-dialog />
@ -77,7 +77,7 @@ export default {
data() {
return {
loading: true,
votes: [],
answers: [],
}
},
@ -86,29 +86,29 @@ export default {
stats() {
const sums = []
if (this.votes != null) {
if (this.answers != null) {
const uniqueAns = []
const uniqueQs = []
const ansToQ = new Map()
for (let i = 0; i < this.votes.length; i++) {
if (this.votes[i].voteOptionType === 'radiogroup' || this.votes[i].voteOptionType === 'dropdown') {
if (uniqueAns.includes(this.votes[i].voteAnswer) === false) {
uniqueAns.push(this.votes[i].voteAnswer)
ansToQ.set(this.votes[i].voteAnswer, this.votes[i].voteOptionId)
for (let i = 0; i < this.answers.length; i++) {
if (this.answers[i].questionType === 'radiogroup' || this.answers[i].questionType === 'dropdown') {
if (uniqueAns.includes(this.answers[i].text) === false) {
uniqueAns.push(this.answers[i].text)
ansToQ.set(this.answers[i].text, this.answers[i].questionId)
}
if (uniqueQs.includes(this.votes[i].voteOptionId) === false) {
uniqueQs.push(this.votes[i].voteOptionId)
if (uniqueQs.includes(this.answers[i].questionId) === false) {
uniqueQs.push(this.answers[i].questionId)
}
}
}
for (let i = 0; i < uniqueAns.length; i++) {
sums[i] = 0
}
for (let i = 0; i < this.votes.length; i++) {
sums[uniqueAns.indexOf(this.votes[i].voteAnswer)]++
for (let i = 0; i < this.answers.length; i++) {
sums[uniqueAns.indexOf(this.answers[i].text)]++
}
for (let i = 0; i < sums.length; i++) {
sums[i] = 'Question ' + ansToQ.get(uniqueAns[i]) + ': ' + (sums[i] / ((this.votes.length / uniqueQs.length)) * 100).toFixed(2) + '%' + ' of respondents voted for answer choice: ' + uniqueAns[i]
sums[i] = 'Question ' + ansToQ.get(uniqueAns[i]) + ': ' + (sums[i] / ((this.answers.length / uniqueQs.length)) * 100).toFixed(2) + '%' + ' of respondents voted for answer choice: ' + uniqueAns[i]
}
}
@ -127,10 +127,10 @@ export default {
axios.get(generateUrl('apps/forms/api/v1/submissions/{hash}', { hash: this.$route.params.hash }))
.then((response) => {
if (response.data == null) {
this.votes = null
this.answers = null
OC.Notification.showTemporary('Access Denied')
} else {
this.votes = response.data
this.answers = response.data
}
this.loading = false
}, (error) => {
@ -139,22 +139,31 @@ export default {
this.loading = false
})
},
viewFormResults(index, event, name) {
viewFormResults(index, form, name) {
this.$router.push({
name: name,
params: {
hash: event.id,
hash: form.id,
},
})
},
download() {
this.loading = true
axios.get(OC.generateUrl('apps/forms/get/event/' + this.$route.params.hash))
axios.get(OC.generateUrl('apps/forms/get/form/' + this.$route.params.hash))
.then((response) => {
this.json2csvParser = ['userId', 'voteOptionId', 'voteOptionText', 'voteAnswer']
this.json2csvParser = ['userId', 'questionId', 'questionText', 'Answer'] // TODO Is this one necessary??
const formattedAns = []
this.answers.forEach(ans => {
formattedAns.push({
userId: ans['userId'],
questionId: ans['questionId'],
questionText: ans['questionText'],
answer: ans['text'],
})
})
const element = document.createElement('a')
element.setAttribute('href', 'data:text/csv;charset=utf-8,' + encodeURIComponent(json2csvParser.parse(this.votes)))
element.setAttribute('href', 'data:text/csv;charset=utf-8,' + encodeURIComponent(json2csvParser.parse(formattedAns)))
element.setAttribute('download', response.data.title + '.csv')
element.style.display = 'none'

View file

@ -21,47 +21,41 @@
-->
<template>
<AppSidebar :title="form.event.title">
<AppSidebar :title="form.form.title">
<div class="configBox ">
<label class="title icon-settings">
{{ t('forms', 'Form configurations') }}
</label>
<input id="anonymous"
v-model="form.event.isAnonymous"
<input id="isAnonymous"
v-model="form.form.isAnonymous"
type="checkbox"
class="checkbox">
<label for="anonymous" class="title">
<label for="isAnonymous" class="title">
{{ t('forms', 'Anonymous form') }}
</label>
<input id="unique"
v-model="form.event.unique"
:disabled="form.event.access !== 'registered' || form.event.isAnonymous"
<input id="submitOnce"
v-model="form.form.submitOnce"
:disabled="form.form.access !== 'registered' || form.form.isAnonymous"
type="checkbox"
class="checkbox">
<label for="unique" class="title">
<label for="submitOnce" class="title">
<span>{{ t('forms', 'Only allow one submission per user') }}</span>
</label>
<input v-show="form.event.isAnonymous"
id="trueAnonymous"
v-model="form.event.fullAnonymous"
<input id="expires"
v-model="form.form.expires"
type="checkbox"
class="checkbox">
<input id="expiration"
v-model="form.event.expiration"
type="checkbox"
class="checkbox">
<label class="title" for="expiration">
<label class="title" for="expires">
{{ t('forms', 'Expires') }}
</label>
<DatetimePicker v-show="form.event.expiration"
v-model="form.event.expirationDate"
<DatetimePicker v-show="form.form.expires"
v-model="form.form.expirationDate"
v-bind="expirationDatePicker"
:time-picker-options="{ start: '00:00', step: '00:05', end: '23:55' }"
@ -73,7 +67,7 @@
{{ t('forms', 'Access') }}
</label>
<input id="private"
v-model="form.event.access"
v-model="form.form.access"
type="radio"
value="registered"
@ -83,7 +77,7 @@
<span>{{ t('forms', 'Registered users only') }}</span>
</label>
<input id="public"
v-model="form.event.access"
v-model="form.form.access"
type="radio"
value="public"
@ -93,7 +87,7 @@
<span>{{ t('forms', 'Public access') }}</span>
</label>
<input id="select"
v-model="form.event.access"
v-model="form.form.access"
type="radio"
value="select"
@ -104,7 +98,7 @@
</label>
</div>
<ShareDiv v-show="form.event.access === 'select'"
<ShareDiv v-show="form.form.access === 'select'"
:active-shares="form.shares"
:placeholder="t('forms', 'Name of user or group')"
:hide-names="true"

View file

@ -23,12 +23,12 @@
use OCP\Util;
Util::addStyle('forms', 'vote');
Util::addStyle('forms', 'submit');
Util::addScript('forms', 'vote');
Util::addScript('forms', 'submit');
Util::addScript('forms', 'survey.jquery.min');
/** @var \OCA\Forms\Db\Event $form */
/** @var \OCA\Forms\Db\Form $form */
$form = $_['form'];
/** @var OCA\Forms\Db\Question[] $questions */
$questions = $_['questions'];