Merge pull request #308 from nextcloud/enh/submit
This commit is contained in:
commit
4f42db1bac
|
@ -1,4 +1,7 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
globals: {
|
||||||
|
appName: true,
|
||||||
|
},
|
||||||
extends: [
|
extends: [
|
||||||
'@nextcloud',
|
'@nextcloud',
|
||||||
]
|
]
|
||||||
|
|
|
@ -32,7 +32,6 @@ return [
|
||||||
['name' => 'page#getResult', 'url' => '/{hash}/results', 'verb' => 'GET'],
|
['name' => 'page#getResult', 'url' => '/{hash}/results', 'verb' => 'GET'],
|
||||||
|
|
||||||
['name' => 'page#goto_form', 'url' => '/{hash}', 'verb' => 'GET'],
|
['name' => 'page#goto_form', 'url' => '/{hash}', 'verb' => 'GET'],
|
||||||
['name' => 'page#insert_submission', 'url' => '/insert/submission', 'verb' => 'POST'],
|
|
||||||
|
|
||||||
// Forms
|
// Forms
|
||||||
['name' => 'api#getForms', 'url' => '/api/v1/forms', 'verb' => 'GET'],
|
['name' => 'api#getForms', 'url' => '/api/v1/forms', 'verb' => 'GET'],
|
||||||
|
@ -53,6 +52,7 @@ return [
|
||||||
['name' => 'api#deleteOption', 'url' => '/api/v1/option/{id}', 'verb' => 'DELETE'],
|
['name' => 'api#deleteOption', 'url' => '/api/v1/option/{id}', 'verb' => 'DELETE'],
|
||||||
|
|
||||||
['name' => 'api#getSubmissions', 'url' => '/api/v1/submissions/{hash}', 'verb' => 'GET'],
|
['name' => 'api#getSubmissions', 'url' => '/api/v1/submissions/{hash}', 'verb' => 'GET'],
|
||||||
|
['name' => 'api#insertSubmission', 'url' => '/api/v1/submissions/insert', 'verb' => 'POST'],
|
||||||
|
|
||||||
['name' => 'system#get_site_users_and_groups', 'url' => '/get/siteusers', 'verb' => 'POST'],
|
['name' => 'system#get_site_users_and_groups', 'url' => '/get/siteusers', 'verb' => 'POST'],
|
||||||
]
|
]
|
||||||
|
|
60
js/submit.js
60
js/submit.js
|
@ -1,60 +0,0 @@
|
||||||
var form = []
|
|
||||||
var questions = []
|
|
||||||
|
|
||||||
function sendDataToServer(survey) {
|
|
||||||
form.answers = survey.data;
|
|
||||||
form.userId = OC.getCurrentUser().uid;
|
|
||||||
if(form.userId == ''){
|
|
||||||
form.userId = 'anon_' + Date.now() + '_' + Math.floor(Math.random() * 10000)
|
|
||||||
}
|
|
||||||
form.questions = questions;
|
|
||||||
$.post(OC.generateUrl('apps/forms/insert/submission'), form)
|
|
||||||
.then((response) => {
|
|
||||||
}, (error) => {
|
|
||||||
/* eslint-disable-next-line no-console */
|
|
||||||
console.log(error.response)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function cssUpdate(survey, options){
|
|
||||||
console.log(options.cssClasses)
|
|
||||||
var classes = options.cssClasses
|
|
||||||
classes.root = 'sq-root'
|
|
||||||
classes.title = 'sq-title'
|
|
||||||
classes.item = 'sq-item'
|
|
||||||
classes.label = 'sq-label'
|
|
||||||
classes.description = 'sv-q-description'
|
|
||||||
|
|
||||||
if (options.question.isRequired) {
|
|
||||||
classes.title = 'sq-title sq-title-required'
|
|
||||||
classes.root = 'sq-root sq-root-required'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$(document).ready(function () {
|
|
||||||
var formJSON = $('#surveyContainer').attr('form')
|
|
||||||
var questionJSON = $('#surveyContainer').attr('questions')
|
|
||||||
|
|
||||||
form = JSON.parse(formJSON)
|
|
||||||
questions = JSON.parse(questionJSON)
|
|
||||||
|
|
||||||
var surveyJSON = {
|
|
||||||
title: form.title,
|
|
||||||
description: form.description,
|
|
||||||
questions: []
|
|
||||||
};
|
|
||||||
|
|
||||||
questions.forEach(q => {
|
|
||||||
var qChoices = []
|
|
||||||
q.options.forEach(o => {
|
|
||||||
qChoices.push(o.text);
|
|
||||||
});
|
|
||||||
surveyJSON.questions.push({type: q.type, name: q.text, choices: qChoices, isRequired: 'true'});
|
|
||||||
});
|
|
||||||
|
|
||||||
$('#surveyContainer').Survey({
|
|
||||||
model: new Survey.Model(surveyJSON),
|
|
||||||
onUpdateQuestionCssClasses: cssUpdate,
|
|
||||||
onComplete: sendDataToServer,
|
|
||||||
});
|
|
||||||
});
|
|
11
js/survey.jquery.min.js
vendored
11
js/survey.jquery.min.js
vendored
File diff suppressed because one or more lines are too long
|
@ -29,92 +29,78 @@
|
||||||
|
|
||||||
namespace OCA\Forms\Controller;
|
namespace OCA\Forms\Controller;
|
||||||
|
|
||||||
use OCA\Forms\AppInfo\Application;
|
|
||||||
use OCP\AppFramework\Controller;
|
|
||||||
use OCP\AppFramework\Http;
|
|
||||||
use OCP\AppFramework\Db\DoesNotExistException;
|
|
||||||
use OCP\AppFramework\Db\IMapperException;
|
|
||||||
|
|
||||||
use OCP\ILogger;
|
|
||||||
use OCP\IRequest;
|
|
||||||
use OCP\IUser;
|
|
||||||
use OCP\Security\ISecureRandom;
|
|
||||||
|
|
||||||
use OCA\Forms\Db\Form;
|
|
||||||
use OCA\Forms\Db\FormMapper;
|
|
||||||
use OCA\Forms\Db\Question;
|
|
||||||
use OCA\Forms\Db\QuestionMapper;
|
|
||||||
use OCA\Forms\Db\Option;
|
|
||||||
use OCA\Forms\Db\OptionMapper;
|
|
||||||
use OCA\Forms\Db\Submission;
|
|
||||||
use OCA\Forms\Db\SubmissionMapper;
|
|
||||||
use OCA\Forms\Db\Answer;
|
use OCA\Forms\Db\Answer;
|
||||||
use OCA\Forms\Db\AnswerMapper;
|
use OCA\Forms\Db\AnswerMapper;
|
||||||
|
use OCA\Forms\Db\Form;
|
||||||
|
use OCA\Forms\Db\FormMapper;
|
||||||
|
use OCA\Forms\Db\Option;
|
||||||
|
use OCA\Forms\Db\OptionMapper;
|
||||||
|
use OCA\Forms\Db\Question;
|
||||||
|
use OCA\Forms\Db\QuestionMapper;
|
||||||
|
use OCA\Forms\Db\Submission;
|
||||||
|
use OCA\Forms\Db\SubmissionMapper;
|
||||||
|
use OCA\Forms\Service\FormsService;
|
||||||
|
|
||||||
|
use OCP\AppFramework\Controller;
|
||||||
|
use OCP\AppFramework\Db\IMapperException;
|
||||||
|
use OCP\AppFramework\Http;
|
||||||
|
use OCP\ILogger;
|
||||||
|
use OCP\IRequest;
|
||||||
|
use OCP\IUserSession;
|
||||||
|
use OCP\Security\ISecureRandom;
|
||||||
|
|
||||||
class ApiController extends Controller {
|
class ApiController extends Controller {
|
||||||
|
|
||||||
private $formMapper;
|
protected $appName;
|
||||||
|
|
||||||
|
/** @var SubmissionMapper */
|
||||||
private $submissionMapper;
|
private $submissionMapper;
|
||||||
private $answerMapper;
|
|
||||||
|
/** @var FormMapper */
|
||||||
|
private $formMapper;
|
||||||
|
|
||||||
|
/** @var QuestionMapper */
|
||||||
private $questionMapper;
|
private $questionMapper;
|
||||||
|
|
||||||
|
/** @var OptionMapper */
|
||||||
private $optionMapper;
|
private $optionMapper;
|
||||||
|
|
||||||
|
/** @var AnswerMapper */
|
||||||
|
private $answerMapper;
|
||||||
|
|
||||||
/** @var ILogger */
|
/** @var ILogger */
|
||||||
private $logger;
|
private $logger;
|
||||||
|
|
||||||
/** @var string */
|
/** @var IUserSession */
|
||||||
private $userId;
|
private $userSession;
|
||||||
|
|
||||||
|
/** @var FormsService */
|
||||||
|
private $formsService;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(string $appName,
|
||||||
IRequest $request,
|
IRequest $request,
|
||||||
$userId,
|
$userId, // TODO remove & replace with userSession below.
|
||||||
FormMapper $formMapper,
|
IUserSession $userSession,
|
||||||
SubmissionMapper $submissionMapper,
|
FormMapper $formMapper,
|
||||||
AnswerMapper $answerMapper,
|
SubmissionMapper $submissionMapper,
|
||||||
QuestionMapper $questionMapper,
|
AnswerMapper $answerMapper,
|
||||||
OptionMapper $optionMapper,
|
QuestionMapper $questionMapper,
|
||||||
ILogger $logger
|
OptionMapper $optionMapper,
|
||||||
) {
|
ILogger $logger,
|
||||||
parent::__construct(Application::APP_ID, $request);
|
FormsService $formsService) {
|
||||||
|
parent::__construct($appName, $request);
|
||||||
|
$this->appName = $appName;
|
||||||
$this->userId = $userId;
|
$this->userId = $userId;
|
||||||
|
$this->userSession = $userSession;
|
||||||
$this->formMapper = $formMapper;
|
$this->formMapper = $formMapper;
|
||||||
|
$this->questionMapper = $questionMapper;
|
||||||
|
$this->optionMapper = $optionMapper;
|
||||||
$this->submissionMapper = $submissionMapper;
|
$this->submissionMapper = $submissionMapper;
|
||||||
$this->answerMapper = $answerMapper;
|
$this->answerMapper = $answerMapper;
|
||||||
$this->questionMapper = $questionMapper;
|
$this->questionMapper = $questionMapper;
|
||||||
$this->optionMapper = $optionMapper;
|
$this->optionMapper = $optionMapper;
|
||||||
$this->logger = $logger;
|
$this->logger = $logger;
|
||||||
}
|
$this->formsService = $formsService;
|
||||||
|
|
||||||
private function getOptions(int $questionId): array {
|
|
||||||
$optionList = [];
|
|
||||||
try{
|
|
||||||
$optionEntities = $this->optionMapper->findByQuestion($questionId);
|
|
||||||
foreach ($optionEntities as $optionEntity) {
|
|
||||||
$optionList[] = $optionEntity->read();
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (DoesNotExistException $e) {
|
|
||||||
//handle silently
|
|
||||||
} finally {
|
|
||||||
return $optionList;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private function getQuestions(int $formId): array {
|
|
||||||
$questionList = [];
|
|
||||||
try{
|
|
||||||
$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
|
|
||||||
}finally{
|
|
||||||
return $questionList;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -140,20 +126,19 @@ class ApiController extends Controller {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @NoAdminRequired
|
* @NoAdminRequired
|
||||||
|
*
|
||||||
|
*
|
||||||
* Read all information to edit a Form (form, questions, options, except submissions/answers).
|
* Read all information to edit a Form (form, questions, options, except submissions/answers).
|
||||||
*/
|
*/
|
||||||
public function getForm(int $id): Http\JSONResponse {
|
public function getForm(int $id): Http\JSONResponse {
|
||||||
try {
|
try {
|
||||||
$form = $this->formMapper->findById($id);
|
$results = $this->formsService->getForm($id);
|
||||||
} catch (IMapperException $e) {
|
} catch (IMapperException $e) {
|
||||||
$this->logger->debug('Could not find form');
|
$this->logger->debug('Could not find form');
|
||||||
return new Http\JSONResponse([], Http::STATUS_BAD_REQUEST);
|
return new Http\JSONResponse([], Http::STATUS_BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = $form->read();
|
return new Http\JSONResponse($results);
|
||||||
$result['questions'] = $this->getQuestions($id);
|
|
||||||
|
|
||||||
return new Http\JSONResponse($result);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -603,4 +588,84 @@ class ApiController extends Controller {
|
||||||
|
|
||||||
return new Http\JSONResponse($result);
|
return new Http\JSONResponse($result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
* @PublicPage
|
||||||
|
*
|
||||||
|
* Process a new submission
|
||||||
|
* @param int $formId
|
||||||
|
* @param array $answers [question_id => arrayOfString]
|
||||||
|
*/
|
||||||
|
public function insertSubmission(int $formId, array $answers) {
|
||||||
|
$this->logger->debug('Inserting submission: formId: {formId}, answers: {answers}', [
|
||||||
|
'formId' => $formId,
|
||||||
|
'answers' => $answers,
|
||||||
|
]);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$form = $this->formMapper->findById($formId);
|
||||||
|
$questions = $this->formsService->getQuestions($formId);
|
||||||
|
} catch (IMapperException $e) {
|
||||||
|
$this->logger->debug('Could not find form');
|
||||||
|
return new Http\JSONResponse(['message' => 'Could not find form'], Http::STATUS_BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = $this->userSession->getUser();
|
||||||
|
// TODO check again hasUserAccess?!
|
||||||
|
|
||||||
|
// Create Submission
|
||||||
|
$submission = new Submission();
|
||||||
|
$submission->setFormId($formId);
|
||||||
|
$submission->setTimestamp(time());
|
||||||
|
|
||||||
|
// If not logged in or anonymous use anonID
|
||||||
|
if (!$user || $form->getIsAnonymous()){
|
||||||
|
$anonID = "anon-user-". hash('md5', (time() + rand()));
|
||||||
|
$submission->setUserId($anonID);
|
||||||
|
} else {
|
||||||
|
$submission->setUserId($user->getUID());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert new submission
|
||||||
|
$this->submissionMapper->insert($submission);
|
||||||
|
$submissionId = $submission->getId();
|
||||||
|
|
||||||
|
// Process Answers
|
||||||
|
foreach($answers as $questionId => $answerArray) {
|
||||||
|
// Search corresponding Question, skip processing if not found
|
||||||
|
$questionIndex = array_search($questionId, array_column($questions, 'id'));
|
||||||
|
if ($questionIndex === false) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
$question = $questions[$questionIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach($answerArray as $answer) {
|
||||||
|
if($question['type'] === 'multiple' || $question['type'] === 'multiple_unique') {
|
||||||
|
// Search corresponding option, skip processing if not found
|
||||||
|
$optionIndex = array_search($answer, array_column($question['options'], 'id'));
|
||||||
|
if($optionIndex === false) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
$option = $question['options'][$optionIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load option-text
|
||||||
|
$answerText = $option['text'];
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$answerText = $answer; // Not a multiple-question, answerText is given answer
|
||||||
|
}
|
||||||
|
|
||||||
|
$answerEntity = new Answer();
|
||||||
|
$answerEntity->setSubmissionId($submissionId);
|
||||||
|
$answerEntity->setQuestionId($question['id']);
|
||||||
|
$answerEntity->setText($answerText);
|
||||||
|
$this->answerMapper->insert($answerEntity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Http\JSONResponse([]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,74 +29,65 @@
|
||||||
|
|
||||||
namespace OCA\Forms\Controller;
|
namespace OCA\Forms\Controller;
|
||||||
|
|
||||||
use OCA\Forms\AppInfo\Application;
|
|
||||||
use OCA\Forms\Db\Form;
|
use OCA\Forms\Db\Form;
|
||||||
use OCA\Forms\Db\FormMapper;
|
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\OptionMapper;
|
||||||
use OCA\Forms\Db\QuestionMapper;
|
use OCA\Forms\Db\QuestionMapper;
|
||||||
|
use OCA\Forms\Service\FormsService;
|
||||||
|
|
||||||
use OCP\AppFramework\Controller;
|
use OCP\AppFramework\Controller;
|
||||||
use OCP\AppFramework\Db\DoesNotExistException;
|
use OCP\AppFramework\Db\DoesNotExistException;
|
||||||
use OCP\AppFramework\Http\ContentSecurityPolicy;
|
|
||||||
use OCP\AppFramework\Http\RedirectResponse;
|
|
||||||
use OCP\AppFramework\Http\TemplateResponse;
|
use OCP\AppFramework\Http\TemplateResponse;
|
||||||
use OCP\IGroup;
|
|
||||||
use OCP\IGroupManager;
|
use OCP\IGroupManager;
|
||||||
|
use OCP\IInitialStateService;
|
||||||
use OCP\IRequest;
|
use OCP\IRequest;
|
||||||
use OCP\ILogger;
|
|
||||||
use OCP\IURLGenerator;
|
use OCP\IURLGenerator;
|
||||||
use OCP\IUserManager;
|
use OCP\IUserSession;
|
||||||
use OCP\User; //To do: replace according to API
|
|
||||||
use OCP\Util;
|
use OCP\Util;
|
||||||
|
|
||||||
class PageController extends Controller {
|
class PageController extends Controller {
|
||||||
|
|
||||||
private $userId;
|
protected $appName;
|
||||||
|
|
||||||
|
/** @var FormMapper */
|
||||||
private $formMapper;
|
private $formMapper;
|
||||||
private $submissionMapper;
|
|
||||||
private $answerMapper;
|
|
||||||
|
|
||||||
private $questionMapper;
|
|
||||||
private $optionMapper;
|
|
||||||
|
|
||||||
|
/** @var IURLGenerator */
|
||||||
private $urlGenerator;
|
private $urlGenerator;
|
||||||
private $userMgr;
|
|
||||||
|
/** @var IGroupManager */
|
||||||
private $groupManager;
|
private $groupManager;
|
||||||
|
|
||||||
|
/** @var IUserSession */
|
||||||
|
private $userSession;
|
||||||
|
|
||||||
|
/** @var IInitialStateService */
|
||||||
|
private $initialStateService;
|
||||||
|
|
||||||
|
/** @var FormsService */
|
||||||
|
private $formService;
|
||||||
|
|
||||||
/** @var ILogger */
|
public function __construct(string $appName,
|
||||||
private $logger;
|
IRequest $request,
|
||||||
|
IGroupManager $groupManager,
|
||||||
|
IURLGenerator $urlGenerator,
|
||||||
|
FormMapper $formMapper,
|
||||||
|
QuestionMapper $questionMapper,
|
||||||
|
OptionMapper $optionMapper,
|
||||||
|
IUserSession $userSession,
|
||||||
|
IInitialStateService $initialStateService,
|
||||||
|
FormsService $formsService) {
|
||||||
|
parent::__construct($appName, $request);
|
||||||
|
|
||||||
public function __construct(
|
|
||||||
IRequest $request,
|
|
||||||
IUserManager $userMgr,
|
|
||||||
IGroupManager $groupManager,
|
|
||||||
IURLGenerator $urlGenerator,
|
|
||||||
$userId,
|
|
||||||
FormMapper $formMapper,
|
|
||||||
|
|
||||||
QuestionMapper $questionMapper,
|
|
||||||
OptionMapper $optionMapper,
|
|
||||||
SubmissionMapper $SubmissionMapper,
|
|
||||||
AnswerMapper $AnswerMapper,
|
|
||||||
ILogger $logger
|
|
||||||
) {
|
|
||||||
parent::__construct(Application::APP_ID, $request);
|
|
||||||
$this->userMgr = $userMgr;
|
|
||||||
$this->groupManager = $groupManager;
|
$this->groupManager = $groupManager;
|
||||||
$this->urlGenerator = $urlGenerator;
|
$this->urlGenerator = $urlGenerator;
|
||||||
$this->userId = $userId;
|
$this->appName = $appName;
|
||||||
$this->formMapper = $formMapper;
|
$this->formMapper = $formMapper;
|
||||||
|
|
||||||
$this->questionMapper = $questionMapper;
|
$this->questionMapper = $questionMapper;
|
||||||
$this->optionMapper = $optionMapper;
|
$this->optionMapper = $optionMapper;
|
||||||
$this->submissionMapper = $SubmissionMapper;
|
$this->userSession = $userSession;
|
||||||
$this->answerMapper = $AnswerMapper;
|
$this->initialStateService = $initialStateService;
|
||||||
$this->logger = $logger;
|
$this->formsService = $formsService;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -126,6 +117,8 @@ class PageController extends Controller {
|
||||||
/**
|
/**
|
||||||
* @NoAdminRequired
|
* @NoAdminRequired
|
||||||
* @NoCSRFRequired
|
* @NoCSRFRequired
|
||||||
|
*
|
||||||
|
* TODO: Implement cloning
|
||||||
*
|
*
|
||||||
* @return TemplateResponse
|
* @return TemplateResponse
|
||||||
*/
|
*/
|
||||||
|
@ -167,145 +160,57 @@ class PageController extends Controller {
|
||||||
* @return TemplateResponse
|
* @return TemplateResponse
|
||||||
*/
|
*/
|
||||||
public function gotoForm($hash): ?TemplateResponse {
|
public function gotoForm($hash): ?TemplateResponse {
|
||||||
|
// Inject style on all templates
|
||||||
|
Util::addStyle($this->appName, 'forms');
|
||||||
|
|
||||||
|
// TODO: check if already submitted
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$form = $this->formMapper->findByHash($hash);
|
$form = $this->formMapper->findByHash($hash);
|
||||||
} catch (DoesNotExistException $e) {
|
} catch (DoesNotExistException $e) {
|
||||||
return new TemplateResponse('forms', 'no.acc.tmpl', []);
|
return new TemplateResponse('forms', 'notfound');
|
||||||
}
|
}
|
||||||
|
|
||||||
// If form expired, return Expired-Template
|
// Does the user have permissions to display
|
||||||
if ( ($form->getExpires() !== 0) && (time() > $form->getExpires()) ) {
|
if (!$this->hasUserAccess($form)) {
|
||||||
return new TemplateResponse('forms', 'expired.tmpl');
|
return new TemplateResponse('forms', 'notfound');
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->hasUserAccess($form)) {
|
// Has form expired
|
||||||
$renderAs = $this->userId !== null ? 'user' : 'public';
|
if ($form->getExpires() !== 0 && time() > $form->getExpires()) {
|
||||||
$res = new TemplateResponse('forms', 'submit.tmpl', [
|
return new TemplateResponse('forms', 'expired');
|
||||||
'form' => $form,
|
|
||||||
'questions' => $this->getQuestions($form->getId()),
|
|
||||||
], $renderAs);
|
|
||||||
$csp = new ContentSecurityPolicy();
|
|
||||||
$csp->allowEvalScript(true);
|
|
||||||
$res->setContentSecurityPolicy($csp);
|
|
||||||
return $res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
User::checkLoggedIn();
|
$renderAs = $this->userSession->isLoggedIn() ? 'user' : 'public';
|
||||||
return new TemplateResponse('forms', 'no.acc.tmpl', []);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
Util::addScript($this->appName, 'submit');
|
||||||
* @NoAdminRequired
|
$this->initialStateService->provideInitialState($this->appName, 'form', $this->formsService->getForm($form->getId()));
|
||||||
*/
|
return new TemplateResponse($this->appName, 'main', [], $renderAs);
|
||||||
public function getQuestions(int $formId): array {
|
|
||||||
$questionList = [];
|
|
||||||
try{
|
|
||||||
$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
|
|
||||||
}
|
|
||||||
|
|
||||||
return $questionList;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @NoAdminRequired
|
|
||||||
*/
|
|
||||||
public function getOptions(int $questionId): array {
|
|
||||||
$optionList = [];
|
|
||||||
|
|
||||||
try{
|
|
||||||
$optionEntities = $this->optionMapper->findByQuestion($questionId);
|
|
||||||
foreach ($optionEntities as $optionEntity) {
|
|
||||||
$optionList[] = $optionEntity->read();
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (DoesNotExistException $e) {
|
|
||||||
//handle silently
|
|
||||||
}
|
|
||||||
|
|
||||||
return $optionList;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @NoAdminRequired
|
|
||||||
* @PublicPage
|
|
||||||
* @param int $formId
|
|
||||||
* @param string $userId
|
|
||||||
* @param array $answers
|
|
||||||
* @param array $questions
|
|
||||||
* @return RedirectResponse
|
|
||||||
*/
|
|
||||||
public function insertSubmission($id, $userId, $answers, $questions) {
|
|
||||||
|
|
||||||
$form = $this->formMapper->findById($id);
|
|
||||||
$anonID = "anon-user-". hash('md5', (time() + rand()));
|
|
||||||
|
|
||||||
//Insert Submission
|
|
||||||
$submission = new Submission();
|
|
||||||
$submission->setFormId($id);
|
|
||||||
if($form->getIsAnonymous()){
|
|
||||||
$submission->setUserId($anonID);
|
|
||||||
|
|
||||||
}else{
|
|
||||||
$submission->setUserId($userId);
|
|
||||||
}
|
|
||||||
$submission->setTimestamp(time());
|
|
||||||
$this->submissionMapper->insert($submission);
|
|
||||||
$submissionId = $submission->getId();
|
|
||||||
|
|
||||||
//Insert Answers
|
|
||||||
foreach($questions as $question) {
|
|
||||||
// If question is answered, the questionText exists as key in $answers. Does not exist, when a (non-mandatory) question was not answered.
|
|
||||||
if (array_key_exists($question['text'], $answers)) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @NoAdminRequired
|
* @NoAdminRequired
|
||||||
* Check if user has access to this form
|
* Check if user has access to this form
|
||||||
|
*
|
||||||
|
* @param Form $form
|
||||||
|
* @return boolean
|
||||||
*/
|
*/
|
||||||
private function hasUserAccess(Form $form): bool {
|
private function hasUserAccess(Form $form): bool {
|
||||||
$access = $form->getAccess();
|
$access = $form->getAccess();
|
||||||
$ownerId = $form->getOwnerId();
|
$ownerId = $form->getOwnerId();
|
||||||
|
$user = $this->userSession->getUser();
|
||||||
|
|
||||||
if ($access['type'] === 'public') {
|
if ($access['type'] === 'public') {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refuse access, if not public and no user logged in.
|
// Refuse access, if not public and no user logged in.
|
||||||
if ($this->userId === null) {
|
if (!$user) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always grant access to owner.
|
// Always grant access to owner.
|
||||||
if ($ownerId === $this->userId) {
|
if ($ownerId === $user->getUID()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -313,7 +218,7 @@ class PageController extends Controller {
|
||||||
if ($form->getSubmitOnce()) {
|
if ($form->getSubmitOnce()) {
|
||||||
$participants = $this->submissionMapper->findParticipantsByForm($form->getId());
|
$participants = $this->submissionMapper->findParticipantsByForm($form->getId());
|
||||||
foreach($participants as $participant) {
|
foreach($participants as $participant) {
|
||||||
if ($participant === $this->userId) {
|
if ($participant === $user->getUID()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -326,13 +231,13 @@ class PageController extends Controller {
|
||||||
|
|
||||||
// Selected Access remains.
|
// Selected Access remains.
|
||||||
// Grant Access, if user is in users-Array.
|
// Grant Access, if user is in users-Array.
|
||||||
if (in_array($this->userId, $access['users'])) {
|
if (in_array($user->getUID(), $access['users'])) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if access granted by group.
|
// Check if access granted by group.
|
||||||
foreach ($access['groups'] as $group) {
|
foreach ($access['groups'] as $group) {
|
||||||
if( $this->groupManager->isInGroup($this->userId, $group) ) {
|
if( $this->groupManager->isInGroup($user->getUID(), $group) ) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
101
lib/Service/FormsService.php
Normal file
101
lib/Service/FormsService.php
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2020 John Molakvoæ <skjnldsv@protonmail.com>
|
||||||
|
*
|
||||||
|
* @author John Molakvoæ <skjnldsv@protonmail.com>
|
||||||
|
*
|
||||||
|
* @license GNU AGPL version 3 or any later version
|
||||||
|
*
|
||||||
|
* This code is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License, version 3,
|
||||||
|
* as published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* 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, version 3,
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace OCA\Forms\Service;
|
||||||
|
|
||||||
|
use OCA\Forms\Db\FormMapper;
|
||||||
|
use OCA\Forms\Db\OptionMapper;
|
||||||
|
use OCA\Forms\Db\QuestionMapper;
|
||||||
|
use OCP\AppFramework\Db\DoesNotExistException;
|
||||||
|
use OCP\AppFramework\Db\IMapperException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trait for getting forms information in a service
|
||||||
|
*/
|
||||||
|
class FormsService {
|
||||||
|
|
||||||
|
/** @var FormMapper */
|
||||||
|
private $formMapper;
|
||||||
|
|
||||||
|
/** @var QuestionMapper */
|
||||||
|
private $questionMapper;
|
||||||
|
|
||||||
|
/** @var OptionMapper */
|
||||||
|
private $optionMapper;
|
||||||
|
|
||||||
|
public function __construct(FormMapper $formMapper,
|
||||||
|
QuestionMapper $questionMapper,
|
||||||
|
OptionMapper $optionMapper) {
|
||||||
|
$this->formMapper = $formMapper;
|
||||||
|
$this->questionMapper = $questionMapper;
|
||||||
|
$this->optionMapper = $optionMapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function getOptions(int $questionId): array {
|
||||||
|
$optionList = [];
|
||||||
|
try{
|
||||||
|
$optionEntities = $this->optionMapper->findByQuestion($questionId);
|
||||||
|
foreach ($optionEntities as $optionEntity) {
|
||||||
|
$optionList[] = $optionEntity->read();
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (DoesNotExistException $e) {
|
||||||
|
//handle silently
|
||||||
|
} finally {
|
||||||
|
return $optionList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getQuestions(int $formId): array {
|
||||||
|
$questionList = [];
|
||||||
|
try{
|
||||||
|
$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
|
||||||
|
}finally{
|
||||||
|
return $questionList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a form data
|
||||||
|
*
|
||||||
|
* @param integer $id
|
||||||
|
* @return array
|
||||||
|
* @throws IMapperException
|
||||||
|
*/
|
||||||
|
public function getForm(int $id): array {
|
||||||
|
$form = $this->formMapper->findById($id);
|
||||||
|
$result = $form->read();
|
||||||
|
$result['questions'] = $this->getQuestions($id);
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
8
package-lock.json
generated
8
package-lock.json
generated
|
@ -1600,6 +1600,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@nextcloud/initial-state": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@nextcloud/initial-state/-/initial-state-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-AmewfDmsCgL9j062VWkgWPg+dfyu63xxqv29ErAJ1WZiEQK/gb2IyiILDMTXdVeNHGDY874mzBcAAkpFO/DxnQ==",
|
||||||
|
"requires": {
|
||||||
|
"core-js": "^3.6.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@nextcloud/l10n": {
|
"@nextcloud/l10n": {
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/@nextcloud/l10n/-/l10n-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/@nextcloud/l10n/-/l10n-1.2.3.tgz",
|
||||||
|
|
|
@ -73,6 +73,7 @@
|
||||||
"@nextcloud/axios": "^1.3.2",
|
"@nextcloud/axios": "^1.3.2",
|
||||||
"@nextcloud/dialogs": "^1.2.2",
|
"@nextcloud/dialogs": "^1.2.2",
|
||||||
"@nextcloud/event-bus": "^1.1.3",
|
"@nextcloud/event-bus": "^1.1.3",
|
||||||
|
"@nextcloud/initial-state": "^1.1.2",
|
||||||
"@nextcloud/l10n": "^1.2.3",
|
"@nextcloud/l10n": "^1.2.3",
|
||||||
"@nextcloud/moment": "^1.1.1",
|
"@nextcloud/moment": "^1.1.1",
|
||||||
"@nextcloud/router": "^1.0.2",
|
"@nextcloud/router": "^1.0.2",
|
||||||
|
|
|
@ -28,7 +28,8 @@
|
||||||
@click="enableEdit">
|
@click="enableEdit">
|
||||||
<!-- Drag handle -->
|
<!-- Drag handle -->
|
||||||
<!-- TODO: implement arrow key mapping to reorder question -->
|
<!-- TODO: implement arrow key mapping to reorder question -->
|
||||||
<div class="question__drag-handle icon-drag-handle"
|
<div v-if="!readOnly"
|
||||||
|
class="question__drag-handle icon-drag-handle"
|
||||||
:aria-label="t('forms', 'Drag to reorder the questions')" />
|
:aria-label="t('forms', 'Drag to reorder the questions')" />
|
||||||
|
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
|
@ -45,7 +46,7 @@
|
||||||
@input="onInput"
|
@input="onInput"
|
||||||
@keyup="onTitleChange">
|
@keyup="onTitleChange">
|
||||||
<h3 v-else class="question__header-title" v-text="text" />
|
<h3 v-else class="question__header-title" v-text="text" />
|
||||||
<Actions class="question__header-menu" :force-menu="true">
|
<Actions v-if="!readOnly" class="question__header-menu" :force-menu="true">
|
||||||
<ActionButton icon="icon-delete" @click="onDelete">
|
<ActionButton icon="icon-delete" @click="onDelete">
|
||||||
{{ t('forms', 'Delete question') }}
|
{{ t('forms', 'Delete question') }}
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
|
@ -96,6 +97,10 @@ export default {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
readOnly: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -107,14 +112,18 @@ export default {
|
||||||
* Enable the edit mode
|
* Enable the edit mode
|
||||||
*/
|
*/
|
||||||
enableEdit() {
|
enableEdit() {
|
||||||
this.$emit('update:edit', true)
|
if (!this.readOnly) {
|
||||||
|
this.$emit('update:edit', true)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Disable the edit mode
|
* Disable the edit mode
|
||||||
*/
|
*/
|
||||||
disableEdit() {
|
disableEdit() {
|
||||||
this.$emit('update:edit', false)
|
if (!this.readOnly) {
|
||||||
|
this.$emit('update:edit', false)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
<textarea ref="textarea"
|
<textarea ref="textarea"
|
||||||
:aria-label="t('forms', 'A long answer for the question “{text}”', { text })"
|
:aria-label="t('forms', 'A long answer for the question “{text}”', { text })"
|
||||||
:placeholder="t('forms', 'Long answer text')"
|
:placeholder="t('forms', 'Long answer text')"
|
||||||
:readonly="edit"
|
:required="true /* TODO: implement required option */"
|
||||||
:value="values[0]"
|
:value="values[0]"
|
||||||
class="question__text"
|
class="question__text"
|
||||||
maxlength="1024"
|
maxlength="1024"
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
:edit.sync="edit"
|
:edit.sync="edit"
|
||||||
@delete="onDelete"
|
@delete="onDelete"
|
||||||
@update:text="onTitleChange">
|
@update:text="onTitleChange">
|
||||||
<ul class="question__content" :role="isUnique ? 'radiogroup' : ''">
|
<ul class="question__content">
|
||||||
<template v-for="(answer, index) in options">
|
<template v-for="(answer, index) in options">
|
||||||
<li v-if="!edit" :key="answer.id" class="question__item">
|
<li v-if="!edit" :key="answer.id" class="question__item">
|
||||||
<!-- Answer radio/checkbox + label -->
|
<!-- Answer radio/checkbox + label -->
|
||||||
|
@ -42,8 +42,9 @@
|
||||||
'checkbox question__checkbox': !isUnique,
|
'checkbox question__checkbox': !isUnique,
|
||||||
}"
|
}"
|
||||||
:name="`${id}-answer`"
|
:name="`${id}-answer`"
|
||||||
:readonly="true"
|
:required="isRequired(answer.id)"
|
||||||
:type="isUnique ? 'radio' : 'checkbox'">
|
:type="isUnique ? 'radio' : 'checkbox'"
|
||||||
|
@change="onChange($event, answer.id)">
|
||||||
<label v-if="!edit"
|
<label v-if="!edit"
|
||||||
ref="label"
|
ref="label"
|
||||||
:for="`${id}-answer-${answer.id}`"
|
:for="`${id}-answer-${answer.id}`"
|
||||||
|
@ -114,6 +115,10 @@ export default {
|
||||||
hasNoAnswer() {
|
hasNoAnswer() {
|
||||||
return this.options.length === 0
|
return this.options.length === 0
|
||||||
},
|
},
|
||||||
|
|
||||||
|
areNoneChecked() {
|
||||||
|
return this.values.length === 0
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
|
@ -126,15 +131,50 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
onChange(event, answerId) {
|
||||||
|
const isChecked = event.target.checked === true
|
||||||
|
let values = this.values.slice()
|
||||||
|
|
||||||
|
// Radio
|
||||||
|
if (this.isUnique) {
|
||||||
|
this.$emit('update:values', [answerId])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checkbox
|
||||||
|
if (isChecked) {
|
||||||
|
values.push(answerId)
|
||||||
|
} else {
|
||||||
|
values = values.filter(id => id !== answerId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit values and remove duplicates
|
||||||
|
this.$emit('update:values', [...new Set(values)])
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Is the provided index checked
|
* Is the provided answer checked ?
|
||||||
* @param {number} index the option index
|
* @param {number} id the answer id
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
isChecked(index) {
|
isChecked(id) {
|
||||||
// TODO implement based on answers
|
return this.values.indexOf(id) > -1
|
||||||
return false
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the provided answer required ?
|
||||||
|
* This is needed for checkboxes as html5
|
||||||
|
* doesn't allow to require at least ONE checked.
|
||||||
|
* So we require the one that are checked or all
|
||||||
|
* if none are checked yet.
|
||||||
|
* @param {number} id the answer id
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
isRequired(id) {
|
||||||
|
if (this.isUnique) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return this.areNoneChecked || this.isChecked(id)
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -230,13 +270,14 @@ export default {
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss" scoped>
|
||||||
.question__content {
|
.question__content {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.question__item {
|
.question__item {
|
||||||
|
position: relative;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 44px;
|
height: 44px;
|
||||||
|
@ -247,11 +288,6 @@ export default {
|
||||||
margin: 14px !important;
|
margin: 14px !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure to respect readonly on radio/checkbox
|
|
||||||
input[readonly] {
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Using type to have a higher order than the input styling of server
|
// Using type to have a higher order than the input styling of server
|
||||||
|
@ -265,4 +301,12 @@ export default {
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input.question__radio,
|
||||||
|
input.question__checkbox {
|
||||||
|
z-index: -1;
|
||||||
|
// make sure browser warnings are properly
|
||||||
|
// displayed at the correct location
|
||||||
|
left: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
<input ref="input"
|
<input ref="input"
|
||||||
:aria-label="t('forms', 'A short answer for the question “{text}”', { text })"
|
:aria-label="t('forms', 'A short answer for the question “{text}”', { text })"
|
||||||
:placeholder="t('forms', 'Short answer text')"
|
:placeholder="t('forms', 'Short answer text')"
|
||||||
:readonly="edit"
|
:required="true /* TODO: implement required option */"
|
||||||
:value="values[0]"
|
:value="values[0]"
|
||||||
class="question__input"
|
class="question__input"
|
||||||
maxlength="256"
|
maxlength="256"
|
||||||
|
|
|
@ -57,8 +57,7 @@ __webpack_nonce__ = btoa(getRequestToken())
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
__webpack_public_path__ = generateFilePath('forms', '', 'js/')
|
__webpack_public_path__ = generateFilePath('forms', '', 'js/')
|
||||||
|
|
||||||
/* eslint-disable-next-line no-new */
|
export default new Vue({
|
||||||
new Vue({
|
|
||||||
el: '#content',
|
el: '#content',
|
||||||
// eslint-disable-next-line vue/match-component-file-name
|
// eslint-disable-next-line vue/match-component-file-name
|
||||||
name: 'FormsRoot',
|
name: 'FormsRoot',
|
||||||
|
|
|
@ -38,12 +38,16 @@ export default {
|
||||||
icon: 'icon-answer-multiple',
|
icon: 'icon-answer-multiple',
|
||||||
label: t('forms', 'Multiple choice'),
|
label: t('forms', 'Multiple choice'),
|
||||||
unique: true,
|
unique: true,
|
||||||
|
// Define conditions where this questions is not ok
|
||||||
|
validate: question => question.options.length > 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
multiple: {
|
multiple: {
|
||||||
component: QuestionMultiple,
|
component: QuestionMultiple,
|
||||||
icon: 'icon-answer-checkbox',
|
icon: 'icon-answer-checkbox',
|
||||||
label: t('forms', 'Checkboxes'),
|
label: t('forms', 'Checkboxes'),
|
||||||
|
// Define conditions where this questions is not ok
|
||||||
|
validate: question => question.options.length > 0,
|
||||||
},
|
},
|
||||||
|
|
||||||
short: {
|
short: {
|
||||||
|
|
36
src/submit.js
Normal file
36
src/submit.js
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2020 John Molakvoæ <skjnldsv@protonmail.com>
|
||||||
|
*
|
||||||
|
* @author John Molakvoæ <skjnldsv@protonmail.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/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { translate, translatePlural } from '@nextcloud/l10n'
|
||||||
|
import Vue from 'vue'
|
||||||
|
|
||||||
|
import Fill from './views/Submit'
|
||||||
|
|
||||||
|
Vue.prototype.t = translate
|
||||||
|
Vue.prototype.n = translatePlural
|
||||||
|
|
||||||
|
export default new Vue({
|
||||||
|
el: '#content',
|
||||||
|
// eslint-disable-next-line vue/match-component-file-name
|
||||||
|
name: 'FormsSubmitRoot',
|
||||||
|
render: h => h(Fill),
|
||||||
|
})
|
|
@ -1,31 +0,0 @@
|
||||||
<!--
|
|
||||||
- @copyright Copyright (c) 2020 John Molakvoæ <skjnldsv@protonmail.com>
|
|
||||||
-
|
|
||||||
- @author John Molakvoæ <skjnldsv@protonmail.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/>.
|
|
||||||
-
|
|
||||||
-->
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<span>TODO</span>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
name: 'Fill',
|
|
||||||
}
|
|
||||||
</script>
|
|
199
src/views/Submit.vue
Normal file
199
src/views/Submit.vue
Normal file
|
@ -0,0 +1,199 @@
|
||||||
|
<!--
|
||||||
|
- @copyright Copyright (c) 2020 John Molakvoæ <skjnldsv@protonmail.com>
|
||||||
|
-
|
||||||
|
- @author John Molakvoæ <skjnldsv@protonmail.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/>.
|
||||||
|
-
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Content app-name="forms">
|
||||||
|
<AppContent>
|
||||||
|
<!-- Forms title & description-->
|
||||||
|
<header>
|
||||||
|
<h3 id="form-title">
|
||||||
|
{{ form.title }}
|
||||||
|
</h3>
|
||||||
|
<p v-if="!loading && !success" id="form-desc">
|
||||||
|
{{ form.description }}
|
||||||
|
</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- Questions list -->
|
||||||
|
<form v-if="!loading && !success" @submit.prevent="onSubmit">
|
||||||
|
<ul>
|
||||||
|
<Questions
|
||||||
|
:is="answerTypes[question.type].component"
|
||||||
|
v-for="(question, index) in validQuestions"
|
||||||
|
ref="questions"
|
||||||
|
:key="question.id"
|
||||||
|
:read-only="true"
|
||||||
|
:model="answerTypes[question.type]"
|
||||||
|
:index="index + 1"
|
||||||
|
v-bind="question"
|
||||||
|
:values.sync="answers[question.id]" />
|
||||||
|
</ul>
|
||||||
|
<input class="primary"
|
||||||
|
type="submit"
|
||||||
|
:value="t('forms', 'Submit')"
|
||||||
|
:disabled="loading"
|
||||||
|
:aria-label="t('forms', 'Submit form')">
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<EmptyContent v-else-if="loading" icon="icon-loading">
|
||||||
|
{{ t('forms', 'Submitting form …') }}
|
||||||
|
</EmptyContent>
|
||||||
|
|
||||||
|
<EmptyContent v-else-if="success" icon="icon-checkmark">
|
||||||
|
{{ t('forms', 'Thank you for completing the survey!') }}
|
||||||
|
</EmptyContent>
|
||||||
|
</AppContent>
|
||||||
|
</Content>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { loadState } from '@nextcloud/initial-state'
|
||||||
|
import { generateUrl } from '@nextcloud/router'
|
||||||
|
import { showError } from '@nextcloud/dialogs'
|
||||||
|
import axios from '@nextcloud/axios'
|
||||||
|
import AppContent from '@nextcloud/vue/dist/Components/AppContent'
|
||||||
|
import Content from '@nextcloud/vue/dist/Components/Content'
|
||||||
|
|
||||||
|
import answerTypes from '../models/AnswerTypes'
|
||||||
|
|
||||||
|
import EmptyContent from '../components/EmptyContent'
|
||||||
|
import Question from '../components/Questions/Question'
|
||||||
|
import QuestionLong from '../components/Questions/QuestionLong'
|
||||||
|
import QuestionShort from '../components/Questions/QuestionShort'
|
||||||
|
import QuestionMultiple from '../components/Questions/QuestionMultiple'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'Submit',
|
||||||
|
|
||||||
|
components: {
|
||||||
|
AppContent,
|
||||||
|
Content,
|
||||||
|
EmptyContent,
|
||||||
|
Question,
|
||||||
|
QuestionLong,
|
||||||
|
QuestionShort,
|
||||||
|
QuestionMultiple,
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
form: loadState('forms', 'form'),
|
||||||
|
answerTypes,
|
||||||
|
answers: {},
|
||||||
|
loading: false,
|
||||||
|
success: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
validQuestions() {
|
||||||
|
return this.form.questions.filter(question => {
|
||||||
|
// All questions must have a valid title
|
||||||
|
if (question.text && question.text.trim() === '') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// If specific conditions provided, test against them
|
||||||
|
if ('validate' in answerTypes[question.type]) {
|
||||||
|
return answerTypes[question.type].validate(question)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* Submit the form after the browser validated it 🚀
|
||||||
|
*/
|
||||||
|
async onSubmit() {
|
||||||
|
this.loading = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
await axios.post(generateUrl('/apps/forms/api/v1/submissions/insert'), {
|
||||||
|
formId: this.form.id,
|
||||||
|
answers: this.answers,
|
||||||
|
})
|
||||||
|
this.success = true
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
showError(t('forms', 'There was an error submitting the form'))
|
||||||
|
} finally {
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
// Replace with new vue components release
|
||||||
|
#app-content,
|
||||||
|
#app-content-vue {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
header,
|
||||||
|
form {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 750px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Title & description header
|
||||||
|
header {
|
||||||
|
margin: 44px;
|
||||||
|
|
||||||
|
#form-title,
|
||||||
|
#form-desc {
|
||||||
|
width: 100%;
|
||||||
|
margin: 16px 0; // aerate the header
|
||||||
|
padding: 0 16px;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
#form-title {
|
||||||
|
font-size: 2em;
|
||||||
|
font-weight: bold;
|
||||||
|
padding-left: 14px; // align with description (compensate font size diff)
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
#form-desc {
|
||||||
|
min-height: 60px;
|
||||||
|
max-height: 200px;
|
||||||
|
margin-top: 0;
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
input[type=submit] {
|
||||||
|
align-self: flex-end;
|
||||||
|
margin: 5px;
|
||||||
|
padding: 10px 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
31
templates/expired.php
Normal file
31
templates/expired.php
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2019 Inigo Jiron <ijiron@terpmail.umd.edu>
|
||||||
|
*
|
||||||
|
* @author Inigo Jiron <ijiron@terpmail.umd.edu>
|
||||||
|
* @author John Molakvoæ <skjnldsv@protonmail.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/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
?>
|
||||||
|
<div id="emptycontent" class="">
|
||||||
|
<div class="icon-forms"></div>
|
||||||
|
<h2><?php p($l->t('Form Expired')); ?></h2>
|
||||||
|
<p><?php p($l->t('This Form has expired and is no longer taking answers')); ?></p>
|
||||||
|
</div>
|
|
@ -1,35 +0,0 @@
|
||||||
<?php
|
|
||||||
/**
|
|
||||||
* @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/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
use OCP\Util;
|
|
||||||
Util::addStyle('forms', 'main');
|
|
||||||
?>
|
|
||||||
<div id="emptycontent" class="">
|
|
||||||
<div class="icon-forms"></div>
|
|
||||||
<h1>
|
|
||||||
<?php p($l->t('Form Expired')); ?>
|
|
||||||
</h1>
|
|
||||||
<h2>
|
|
||||||
<?php p($l->t('This Form has expired and is no longer taking answers.')); ?>
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
|
@ -1,28 +0,0 @@
|
||||||
<?php
|
|
||||||
/**
|
|
||||||
* @copyright Copyright (c) 2017 Vinzenz Rosenkranz <vinzenz.rosenkranz@gmail.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
|
|
||||||
* 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/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
?>
|
|
||||||
|
|
||||||
<?php \OCP\Util::addScript('forms', 'forms'); ?>
|
|
||||||
<div id="app-forms" value = 'forms'></div>
|
|
|
@ -1,35 +0,0 @@
|
||||||
<?php
|
|
||||||
/**
|
|
||||||
* @copyright Copyright (c) 2017 Vinzenz Rosenkranz <vinzenz.rosenkranz@gmail.com>
|
|
||||||
*
|
|
||||||
* @author Vinzenz Rosenkranz <vinzenz.rosenkranz@gmail.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/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
use OCP\Util;
|
|
||||||
Util::addStyle('forms', 'main');
|
|
||||||
?>
|
|
||||||
<div id="emptycontent" class="">
|
|
||||||
<div class="icon-forms"></div>
|
|
||||||
<h1>
|
|
||||||
<?php p($l->t('Access denied')); ?>
|
|
||||||
</h1>
|
|
||||||
<h2>
|
|
||||||
<?php p($l->t('You are not allowed to view this form or the form does not exist.')); ?>
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
|
@ -1,35 +0,0 @@
|
||||||
<?php
|
|
||||||
/**
|
|
||||||
* @copyright Copyright (c) 2017 Vinzenz Rosenkranz <vinzenz.rosenkranz@gmail.com>
|
|
||||||
*
|
|
||||||
* @author Vinzenz Rosenkranz <vinzenz.rosenkranz@gmail.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/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
use OCP\Util;
|
|
||||||
Util::addStyle('forms', 'main');
|
|
||||||
?>
|
|
||||||
<div id="emptycontent" class="">
|
|
||||||
<div class="icon-forms"></div>
|
|
||||||
<h1>
|
|
||||||
<?php p($l->t('Access denied')); ?>
|
|
||||||
</h1>
|
|
||||||
<h2>
|
|
||||||
<?php p($l->t('You are not allowed to edit this form or the form does not exist.')); ?>
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
|
@ -1,35 +0,0 @@
|
||||||
<?php
|
|
||||||
/**
|
|
||||||
* @copyright Copyright (c) 2017 Vinzenz Rosenkranz <vinzenz.rosenkranz@gmail.com>
|
|
||||||
*
|
|
||||||
* @author Vinzenz Rosenkranz <vinzenz.rosenkranz@gmail.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/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
use OCP\Util;
|
|
||||||
Util::addStyle('forms', 'main');
|
|
||||||
?>
|
|
||||||
<div id="emptycontent" class="">
|
|
||||||
<div class="icon-forms"></div>
|
|
||||||
<h1>
|
|
||||||
<?php p($l->t('Access denied')); ?>
|
|
||||||
</h1>
|
|
||||||
<h2>
|
|
||||||
<?php p($l->t('You are either not allowed to delete this form or it doesn\'t exist.')); ?>
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
31
templates/notfound.php
Normal file
31
templates/notfound.php
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @copyright Copyright (c) 2019 Inigo Jiron <ijiron@terpmail.umd.edu>
|
||||||
|
*
|
||||||
|
* @author Inigo Jiron <ijiron@terpmail.umd.edu>
|
||||||
|
* @author John Molakvoæ <skjnldsv@protonmail.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/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
?>
|
||||||
|
<div id="emptycontent" class="">
|
||||||
|
<div class="icon-forms"></div>
|
||||||
|
<h2><?php p($l->t('Form not found')); ?></h2>
|
||||||
|
<p><?php p($l->t('This form does not exists')); ?></p>
|
||||||
|
</div>
|
|
@ -1,45 +0,0 @@
|
||||||
<?php
|
|
||||||
/**
|
|
||||||
* @copyright Copyright (c) 2017 Vinzenz Rosenkranz <vinzenz.rosenkranz@gmail.com>
|
|
||||||
*
|
|
||||||
* @author Vinzenz Rosenkranz <vinzenz.rosenkranz@gmail.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/>.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
use OCP\Util;
|
|
||||||
|
|
||||||
Util::addStyle('forms', 'submit');
|
|
||||||
|
|
||||||
Util::addScript('forms', 'submit');
|
|
||||||
Util::addScript('forms', 'survey.jquery.min');
|
|
||||||
|
|
||||||
/** @var \OCA\Forms\Db\Form $form */
|
|
||||||
$form = $_['form'];
|
|
||||||
/** @var OCA\Forms\Db\Question[] $questions */
|
|
||||||
$questions = $_['questions'];
|
|
||||||
|
|
||||||
?>
|
|
||||||
|
|
||||||
<?php if ($form->getIsAnonymous()):?>
|
|
||||||
*NOTE: This form is anonymous
|
|
||||||
<?php endif?>
|
|
||||||
|
|
||||||
<div id="surveyContainer"
|
|
||||||
form="<?php echo htmlentities(json_encode($form->read()))?>"
|
|
||||||
questions="<?php echo htmlentities(json_encode($questions))?>"
|
|
||||||
></div>
|
|
|
@ -1,14 +1,23 @@
|
||||||
const path = require('path')
|
const path = require('path')
|
||||||
const { VueLoaderPlugin } = require('vue-loader')
|
const webpack = require('webpack')
|
||||||
|
|
||||||
const StyleLintPlugin = require('stylelint-webpack-plugin')
|
const StyleLintPlugin = require('stylelint-webpack-plugin')
|
||||||
|
const VueLoaderPlugin = require('vue-loader/lib/plugin')
|
||||||
|
|
||||||
|
const appName = process.env.npm_package_name.toString()
|
||||||
|
const appVersion = process.env.npm_package_version.toString()
|
||||||
|
console.info('Building', appName, appVersion, '\n')
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
entry: path.join(__dirname, 'src', 'main.js'),
|
entry: {
|
||||||
|
forms: path.resolve(path.join('src', 'main.js')),
|
||||||
|
submit: path.resolve(path.join('src', 'submit.js')),
|
||||||
|
},
|
||||||
output: {
|
output: {
|
||||||
path: path.resolve(__dirname, './js'),
|
path: path.resolve('./js'),
|
||||||
publicPath: '/js/',
|
publicPath: '/js/',
|
||||||
filename: 'forms.js',
|
filename: `[name].js`,
|
||||||
chunkFilename: 'chunks/[name].js',
|
chunkFilename: `${appName}.[name].js?v=[contenthash]`,
|
||||||
},
|
},
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
|
@ -36,18 +45,14 @@ module.exports = {
|
||||||
loader: 'babel-loader',
|
loader: 'babel-loader',
|
||||||
exclude: /node_modules/,
|
exclude: /node_modules/,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
test: /\.(png|jpg|gif|svg)$/,
|
|
||||||
loader: 'url-loader',
|
|
||||||
options: {
|
|
||||||
limit: 8192,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new VueLoaderPlugin(),
|
new VueLoaderPlugin(),
|
||||||
new StyleLintPlugin(),
|
new StyleLintPlugin(),
|
||||||
|
// Make appName & appVersion available as a constant
|
||||||
|
new webpack.DefinePlugin({ appName }),
|
||||||
|
new webpack.DefinePlugin({ appVersion }),
|
||||||
],
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: ['*', '.js', '.vue'],
|
extensions: ['*', '.js', '.vue'],
|
||||||
|
|
Loading…
Reference in a new issue