Merge pull request #308 from nextcloud/enh/submit
This commit is contained in:
commit
4f42db1bac
|
@ -1,4 +1,7 @@
|
|||
module.exports = {
|
||||
globals: {
|
||||
appName: true,
|
||||
},
|
||||
extends: [
|
||||
'@nextcloud',
|
||||
]
|
||||
|
|
|
@ -32,7 +32,6 @@ return [
|
|||
['name' => 'page#getResult', 'url' => '/{hash}/results', 'verb' => 'GET'],
|
||||
|
||||
['name' => 'page#goto_form', 'url' => '/{hash}', 'verb' => 'GET'],
|
||||
['name' => 'page#insert_submission', 'url' => '/insert/submission', 'verb' => 'POST'],
|
||||
|
||||
// Forms
|
||||
['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#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'],
|
||||
]
|
||||
|
|
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;
|
||||
|
||||
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\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 {
|
||||
|
||||
private $formMapper;
|
||||
protected $appName;
|
||||
|
||||
/** @var SubmissionMapper */
|
||||
private $submissionMapper;
|
||||
private $answerMapper;
|
||||
|
||||
/** @var FormMapper */
|
||||
private $formMapper;
|
||||
|
||||
/** @var QuestionMapper */
|
||||
private $questionMapper;
|
||||
|
||||
/** @var OptionMapper */
|
||||
private $optionMapper;
|
||||
|
||||
/** @var AnswerMapper */
|
||||
private $answerMapper;
|
||||
|
||||
/** @var ILogger */
|
||||
private $logger;
|
||||
|
||||
/** @var string */
|
||||
private $userId;
|
||||
/** @var IUserSession */
|
||||
private $userSession;
|
||||
|
||||
/** @var FormsService */
|
||||
private $formsService;
|
||||
|
||||
public function __construct(
|
||||
IRequest $request,
|
||||
$userId,
|
||||
FormMapper $formMapper,
|
||||
SubmissionMapper $submissionMapper,
|
||||
AnswerMapper $answerMapper,
|
||||
QuestionMapper $questionMapper,
|
||||
OptionMapper $optionMapper,
|
||||
ILogger $logger
|
||||
) {
|
||||
parent::__construct(Application::APP_ID, $request);
|
||||
public function __construct(string $appName,
|
||||
IRequest $request,
|
||||
$userId, // TODO remove & replace with userSession below.
|
||||
IUserSession $userSession,
|
||||
FormMapper $formMapper,
|
||||
SubmissionMapper $submissionMapper,
|
||||
AnswerMapper $answerMapper,
|
||||
QuestionMapper $questionMapper,
|
||||
OptionMapper $optionMapper,
|
||||
ILogger $logger,
|
||||
FormsService $formsService) {
|
||||
parent::__construct($appName, $request);
|
||||
$this->appName = $appName;
|
||||
$this->userId = $userId;
|
||||
$this->userSession = $userSession;
|
||||
$this->formMapper = $formMapper;
|
||||
$this->questionMapper = $questionMapper;
|
||||
$this->optionMapper = $optionMapper;
|
||||
$this->submissionMapper = $submissionMapper;
|
||||
$this->answerMapper = $answerMapper;
|
||||
$this->questionMapper = $questionMapper;
|
||||
$this->optionMapper = $optionMapper;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
$this->formsService = $formsService;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -140,20 +126,19 @@ class ApiController extends Controller {
|
|||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
*
|
||||
*
|
||||
* Read all information to edit a Form (form, questions, options, except submissions/answers).
|
||||
*/
|
||||
public function getForm(int $id): Http\JSONResponse {
|
||||
try {
|
||||
$form = $this->formMapper->findById($id);
|
||||
$results = $this->formsService->getForm($id);
|
||||
} catch (IMapperException $e) {
|
||||
$this->logger->debug('Could not find form');
|
||||
return new Http\JSONResponse([], Http::STATUS_BAD_REQUEST);
|
||||
}
|
||||
|
||||
$result = $form->read();
|
||||
$result['questions'] = $this->getQuestions($id);
|
||||
|
||||
return new Http\JSONResponse($result);
|
||||
return new Http\JSONResponse($results);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -603,4 +588,84 @@ class ApiController extends Controller {
|
|||
|
||||
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;
|
||||
|
||||
use OCA\Forms\AppInfo\Application;
|
||||
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 OCA\Forms\Service\FormsService;
|
||||
|
||||
use OCP\AppFramework\Controller;
|
||||
use OCP\AppFramework\Db\DoesNotExistException;
|
||||
use OCP\AppFramework\Http\ContentSecurityPolicy;
|
||||
use OCP\AppFramework\Http\RedirectResponse;
|
||||
use OCP\AppFramework\Http\TemplateResponse;
|
||||
use OCP\IGroup;
|
||||
use OCP\IGroupManager;
|
||||
use OCP\IInitialStateService;
|
||||
use OCP\IRequest;
|
||||
use OCP\ILogger;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\IUserManager;
|
||||
use OCP\User; //To do: replace according to API
|
||||
use OCP\IUserSession;
|
||||
use OCP\Util;
|
||||
|
||||
class PageController extends Controller {
|
||||
|
||||
private $userId;
|
||||
protected $appName;
|
||||
|
||||
/** @var FormMapper */
|
||||
private $formMapper;
|
||||
private $submissionMapper;
|
||||
private $answerMapper;
|
||||
|
||||
private $questionMapper;
|
||||
private $optionMapper;
|
||||
|
||||
/** @var IURLGenerator */
|
||||
private $urlGenerator;
|
||||
private $userMgr;
|
||||
|
||||
/** @var IGroupManager */
|
||||
private $groupManager;
|
||||
|
||||
/** @var IUserSession */
|
||||
private $userSession;
|
||||
|
||||
/** @var IInitialStateService */
|
||||
private $initialStateService;
|
||||
|
||||
/** @var FormsService */
|
||||
private $formService;
|
||||
|
||||
/** @var ILogger */
|
||||
private $logger;
|
||||
public function __construct(string $appName,
|
||||
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->urlGenerator = $urlGenerator;
|
||||
$this->userId = $userId;
|
||||
$this->appName = $appName;
|
||||
$this->formMapper = $formMapper;
|
||||
|
||||
$this->questionMapper = $questionMapper;
|
||||
$this->optionMapper = $optionMapper;
|
||||
$this->submissionMapper = $SubmissionMapper;
|
||||
$this->answerMapper = $AnswerMapper;
|
||||
$this->logger = $logger;
|
||||
$this->userSession = $userSession;
|
||||
$this->initialStateService = $initialStateService;
|
||||
$this->formsService = $formsService;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -126,6 +117,8 @@ class PageController extends Controller {
|
|||
/**
|
||||
* @NoAdminRequired
|
||||
* @NoCSRFRequired
|
||||
*
|
||||
* TODO: Implement cloning
|
||||
*
|
||||
* @return TemplateResponse
|
||||
*/
|
||||
|
@ -167,145 +160,57 @@ class PageController extends Controller {
|
|||
* @return TemplateResponse
|
||||
*/
|
||||
public function gotoForm($hash): ?TemplateResponse {
|
||||
// Inject style on all templates
|
||||
Util::addStyle($this->appName, 'forms');
|
||||
|
||||
// TODO: check if already submitted
|
||||
|
||||
try {
|
||||
$form = $this->formMapper->findByHash($hash);
|
||||
} catch (DoesNotExistException $e) {
|
||||
return new TemplateResponse('forms', 'no.acc.tmpl', []);
|
||||
return new TemplateResponse('forms', 'notfound');
|
||||
}
|
||||
|
||||
// If form expired, return Expired-Template
|
||||
if ( ($form->getExpires() !== 0) && (time() > $form->getExpires()) ) {
|
||||
return new TemplateResponse('forms', 'expired.tmpl');
|
||||
// Does the user have permissions to display
|
||||
if (!$this->hasUserAccess($form)) {
|
||||
return new TemplateResponse('forms', 'notfound');
|
||||
}
|
||||
|
||||
if ($this->hasUserAccess($form)) {
|
||||
$renderAs = $this->userId !== null ? 'user' : 'public';
|
||||
$res = new TemplateResponse('forms', 'submit.tmpl', [
|
||||
'form' => $form,
|
||||
'questions' => $this->getQuestions($form->getId()),
|
||||
], $renderAs);
|
||||
$csp = new ContentSecurityPolicy();
|
||||
$csp->allowEvalScript(true);
|
||||
$res->setContentSecurityPolicy($csp);
|
||||
return $res;
|
||||
// Has form expired
|
||||
if ($form->getExpires() !== 0 && time() > $form->getExpires()) {
|
||||
return new TemplateResponse('forms', 'expired');
|
||||
}
|
||||
|
||||
User::checkLoggedIn();
|
||||
return new TemplateResponse('forms', 'no.acc.tmpl', []);
|
||||
}
|
||||
$renderAs = $this->userSession->isLoggedIn() ? 'user' : 'public';
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
*/
|
||||
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);
|
||||
Util::addScript($this->appName, 'submit');
|
||||
$this->initialStateService->provideInitialState($this->appName, 'form', $this->formsService->getForm($form->getId()));
|
||||
return new TemplateResponse($this->appName, 'main', [], $renderAs);
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
* Check if user has access to this form
|
||||
*
|
||||
* @param Form $form
|
||||
* @return boolean
|
||||
*/
|
||||
private function hasUserAccess(Form $form): bool {
|
||||
$access = $form->getAccess();
|
||||
$ownerId = $form->getOwnerId();
|
||||
|
||||
$user = $this->userSession->getUser();
|
||||
|
||||
if ($access['type'] === 'public') {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Refuse access, if not public and no user logged in.
|
||||
if ($this->userId === null) {
|
||||
if (!$user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Always grant access to owner.
|
||||
if ($ownerId === $this->userId) {
|
||||
if ($ownerId === $user->getUID()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -313,7 +218,7 @@ class PageController extends Controller {
|
|||
if ($form->getSubmitOnce()) {
|
||||
$participants = $this->submissionMapper->findParticipantsByForm($form->getId());
|
||||
foreach($participants as $participant) {
|
||||
if ($participant === $this->userId) {
|
||||
if ($participant === $user->getUID()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -326,13 +231,13 @@ class PageController extends Controller {
|
|||
|
||||
// Selected Access remains.
|
||||
// Grant Access, if user is in users-Array.
|
||||
if (in_array($this->userId, $access['users'])) {
|
||||
if (in_array($user->getUID(), $access['users'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if access granted by group.
|
||||
foreach ($access['groups'] as $group) {
|
||||
if( $this->groupManager->isInGroup($this->userId, $group) ) {
|
||||
if( $this->groupManager->isInGroup($user->getUID(), $group) ) {
|
||||
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": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@nextcloud/l10n/-/l10n-1.2.3.tgz",
|
||||
|
|
|
@ -73,6 +73,7 @@
|
|||
"@nextcloud/axios": "^1.3.2",
|
||||
"@nextcloud/dialogs": "^1.2.2",
|
||||
"@nextcloud/event-bus": "^1.1.3",
|
||||
"@nextcloud/initial-state": "^1.1.2",
|
||||
"@nextcloud/l10n": "^1.2.3",
|
||||
"@nextcloud/moment": "^1.1.1",
|
||||
"@nextcloud/router": "^1.0.2",
|
||||
|
|
|
@ -28,7 +28,8 @@
|
|||
@click="enableEdit">
|
||||
<!-- Drag handle -->
|
||||
<!-- 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')" />
|
||||
|
||||
<!-- Header -->
|
||||
|
@ -45,7 +46,7 @@
|
|||
@input="onInput"
|
||||
@keyup="onTitleChange">
|
||||
<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">
|
||||
{{ t('forms', 'Delete question') }}
|
||||
</ActionButton>
|
||||
|
@ -96,6 +97,10 @@ export default {
|
|||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
readOnly: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
@ -107,14 +112,18 @@ export default {
|
|||
* Enable the edit mode
|
||||
*/
|
||||
enableEdit() {
|
||||
this.$emit('update:edit', true)
|
||||
if (!this.readOnly) {
|
||||
this.$emit('update:edit', true)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Disable the edit mode
|
||||
*/
|
||||
disableEdit() {
|
||||
this.$emit('update:edit', false)
|
||||
if (!this.readOnly) {
|
||||
this.$emit('update:edit', false)
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
<textarea ref="textarea"
|
||||
:aria-label="t('forms', 'A long answer for the question “{text}”', { text })"
|
||||
:placeholder="t('forms', 'Long answer text')"
|
||||
:readonly="edit"
|
||||
:required="true /* TODO: implement required option */"
|
||||
:value="values[0]"
|
||||
class="question__text"
|
||||
maxlength="1024"
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
:edit.sync="edit"
|
||||
@delete="onDelete"
|
||||
@update:text="onTitleChange">
|
||||
<ul class="question__content" :role="isUnique ? 'radiogroup' : ''">
|
||||
<ul class="question__content">
|
||||
<template v-for="(answer, index) in options">
|
||||
<li v-if="!edit" :key="answer.id" class="question__item">
|
||||
<!-- Answer radio/checkbox + label -->
|
||||
|
@ -42,8 +42,9 @@
|
|||
'checkbox question__checkbox': !isUnique,
|
||||
}"
|
||||
:name="`${id}-answer`"
|
||||
:readonly="true"
|
||||
:type="isUnique ? 'radio' : 'checkbox'">
|
||||
:required="isRequired(answer.id)"
|
||||
:type="isUnique ? 'radio' : 'checkbox'"
|
||||
@change="onChange($event, answer.id)">
|
||||
<label v-if="!edit"
|
||||
ref="label"
|
||||
:for="`${id}-answer-${answer.id}`"
|
||||
|
@ -114,6 +115,10 @@ export default {
|
|||
hasNoAnswer() {
|
||||
return this.options.length === 0
|
||||
},
|
||||
|
||||
areNoneChecked() {
|
||||
return this.values.length === 0
|
||||
},
|
||||
},
|
||||
|
||||
watch: {
|
||||
|
@ -126,15 +131,50 @@ export default {
|
|||
},
|
||||
|
||||
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
|
||||
* @param {number} index the option index
|
||||
* Is the provided answer checked ?
|
||||
* @param {number} id the answer id
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isChecked(index) {
|
||||
// TODO implement based on answers
|
||||
return false
|
||||
isChecked(id) {
|
||||
return this.values.indexOf(id) > -1
|
||||
},
|
||||
|
||||
/**
|
||||
* 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>
|
||||
|
||||
<style lang="scss">
|
||||
<style lang="scss" scoped>
|
||||
.question__content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.question__item {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
height: 44px;
|
||||
|
@ -247,11 +288,6 @@ export default {
|
|||
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
|
||||
|
@ -265,4 +301,12 @@ export default {
|
|||
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>
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
<input ref="input"
|
||||
:aria-label="t('forms', 'A short answer for the question “{text}”', { text })"
|
||||
:placeholder="t('forms', 'Short answer text')"
|
||||
:readonly="edit"
|
||||
:required="true /* TODO: implement required option */"
|
||||
:value="values[0]"
|
||||
class="question__input"
|
||||
maxlength="256"
|
||||
|
|
|
@ -57,8 +57,7 @@ __webpack_nonce__ = btoa(getRequestToken())
|
|||
// eslint-disable-next-line
|
||||
__webpack_public_path__ = generateFilePath('forms', '', 'js/')
|
||||
|
||||
/* eslint-disable-next-line no-new */
|
||||
new Vue({
|
||||
export default new Vue({
|
||||
el: '#content',
|
||||
// eslint-disable-next-line vue/match-component-file-name
|
||||
name: 'FormsRoot',
|
||||
|
|
|
@ -38,12 +38,16 @@ export default {
|
|||
icon: 'icon-answer-multiple',
|
||||
label: t('forms', 'Multiple choice'),
|
||||
unique: true,
|
||||
// Define conditions where this questions is not ok
|
||||
validate: question => question.options.length > 0,
|
||||
},
|
||||
|
||||
multiple: {
|
||||
component: QuestionMultiple,
|
||||
icon: 'icon-answer-checkbox',
|
||||
label: t('forms', 'Checkboxes'),
|
||||
// Define conditions where this questions is not ok
|
||||
validate: question => question.options.length > 0,
|
||||
},
|
||||
|
||||
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 { VueLoaderPlugin } = require('vue-loader')
|
||||
const webpack = require('webpack')
|
||||
|
||||
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 = {
|
||||
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: {
|
||||
path: path.resolve(__dirname, './js'),
|
||||
path: path.resolve('./js'),
|
||||
publicPath: '/js/',
|
||||
filename: 'forms.js',
|
||||
chunkFilename: 'chunks/[name].js',
|
||||
filename: `[name].js`,
|
||||
chunkFilename: `${appName}.[name].js?v=[contenthash]`,
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
|
@ -36,18 +45,14 @@ module.exports = {
|
|||
loader: 'babel-loader',
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
{
|
||||
test: /\.(png|jpg|gif|svg)$/,
|
||||
loader: 'url-loader',
|
||||
options: {
|
||||
limit: 8192,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new VueLoaderPlugin(),
|
||||
new StyleLintPlugin(),
|
||||
// Make appName & appVersion available as a constant
|
||||
new webpack.DefinePlugin({ appName }),
|
||||
new webpack.DefinePlugin({ appVersion }),
|
||||
],
|
||||
resolve: {
|
||||
extensions: ['*', '.js', '.vue'],
|
||||
|
|
Loading…
Reference in a new issue