Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 5 additions & 8 deletions appinfo/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,11 @@

return [
'routes' => [
['name' => 'page#index', 'url' => '/', 'verb' => 'GET'],

// Before /{hash} to avoid conflict
['name' => 'page#index', 'url' => '/new', 'verb' => 'GET', 'postfix' => 'create'],
['name' => 'page#index', 'url' => '/{hash}/edit', 'verb' => 'GET', 'postfix' => 'edit'],
['name' => 'page#index', 'url' => '/{hash}/clone', 'verb' => 'GET', 'postfix' => 'clone'],
['name' => 'page#index', 'url' => '/{hash}/results', 'verb' => 'GET', 'postfix' => 'results'],

// Before /{hash}/{action} to avoid conflict
['name' => 'page#goto_form', 'url' => '/{hash}', 'verb' => 'GET'],

// As parameters have defaults, this catches all routes from '/' to '/hash/edit'
['name' => 'page#index', 'url' => '/{hash}/{action}', 'verb' => 'GET', 'defaults' => ['hash' => '', 'action' => '']],
],
'ocs' => [
// Forms
Expand All @@ -43,6 +39,7 @@
['name' => 'api#cloneForm', 'url' => '/api/v1/form/clone/{id}', 'verb' => 'POST'],
['name' => 'api#updateForm', 'url' => '/api/v1/form/update', 'verb' => 'POST'],
['name' => 'api#deleteForm', 'url' => '/api/v1/form/{id}', 'verb' => 'DELETE'],
['name' => 'api#getSharedForms', 'url' => '/api/v1/shared_forms', 'verb' => 'GET'],

// Questions
['name' => 'api#newQuestion', 'url' => '/api/v1/question', 'verb' => 'POST'],
Expand Down
13 changes: 12 additions & 1 deletion docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,16 @@ Returns condensed objects of all Forms beeing owned by the authenticated user.
]
```

### List shared Forms
Returns condensed objects of all Forms, that are shared to the authenticated user via instance ([access-type](DataStructure.md#share-types) `registered` or `selected`) and have not expired yet.
- Endpoint: `/api/v1/shared_forms`
- Method: `GET`
- Parameters: None
- Response: Array of condensed Form Objects, sorted as newest first, similar to [List owned Forms](#list-owned-forms).
```
See above, 'List owned forms'
```

### Create a new Form
- Endpoint: `/api/v1/form`
- Method: `POST`
Expand All @@ -65,7 +75,8 @@ Returns condensed objects of all Forms beeing owned by the authenticated user.
"expires": null,
"isAnonymous": null,
"submitOnce": true,
"questions": []
"questions": [],
"canSubmit": true
}
```

Expand Down
4 changes: 3 additions & 1 deletion docs/DataStructure.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ This document describes the Obect-Structure, that is used within the Forms App a
| submitOnce | Boolean | | If users are only allowed to submit once to the form |
| questions | Array of [Questions](#question) | | Array of questions belonging to the form |
| submissions | Array of [Submissions](#submissions) | | Array of submissions belonging to the form |
| canSubmit | Boolean | | If the user can Submit to the form, i.e. calculated information out of `submitOnce` and existing submissions. |

```
{
Expand All @@ -33,7 +34,8 @@ This document describes the Obect-Structure, that is used within the Forms App a
"isAnonymous": false,
"submitOnce": false,
"questions": [],
"submissions": []
"submissions": [],
"canSubmit": true
}
```

Expand Down
39 changes: 36 additions & 3 deletions lib/Controller/ApiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,8 @@ public function __construct(string $appName,
/**
* @NoAdminRequired
*
* Read Form-List only with necessary information for Listing.
* Read Form-List of owned forms
* Return only with necessary information for Listing.
* @return DataResponse
*/
public function getForms(): DataResponse {
Expand All @@ -169,6 +170,38 @@ public function getForms(): DataResponse {
return new DataResponse($result);
}

/**
* @NoAdminRequired
*
* Read List of forms shared with current user
* Return only with necessary information for Listing.
* @return DataResponse
*/
public function getSharedForms(): DataResponse {
$forms = $this->formMapper->findAll();

$result = [];
foreach ($forms as $form) {
// Don't add if user is owner, user has no access, form has expired, form is link-shared
if ($form->getOwnerId() === $this->currentUser->getUID()
|| !$this->formsService->hasUserAccess($form->getId())
|| $this->formsService->hasFormExpired($form->getId())
|| $form->getAccess()['type'] === 'public') {
continue;
}

$result[] = [
'id' => $form->getId(),
'hash' => $form->getHash(),
'title' => $form->getTitle(),
'expires' => $form->getExpires(),
'partial' => true
];
}

return new DataResponse($result);
}

/**
* @NoAdminRequired
*
Expand Down Expand Up @@ -914,8 +947,8 @@ public function insertSubmission(int $formId, array $answers): DataResponse {
throw new OCSForbiddenException('Not allowed to access this form');
}

// Not allowed if form expired. Expires is '0' if the form does not expire.
if ($form->getExpires() && $form->getExpires() < time()) {
// Not allowed if form has expired.
if ($this->formsService->hasFormExpired($form->getId())) {
throw new OCSForbiddenException('This form is no longer taking answers');
}

Expand Down
31 changes: 21 additions & 10 deletions lib/Controller/PageController.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,15 @@
use OCP\AppFramework\Controller;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Http\Template\PublicTemplateResponse;
use OCP\AppFramework\Http\Response;
use OCP\AppFramework\Http\TemplateResponse;
use OCP\AppFramework\Http\RedirectResponse;
use OCP\IGroupManager;
use OCP\IInitialStateService;
use OCP\IL10N;
use OCP\ILogger;
use OCP\IRequest;
use OCP\IURLGenerator;
use OCP\IUser;
use OCP\IUserManager;
use OCP\IUserSession;
Expand Down Expand Up @@ -74,6 +77,9 @@ class PageController extends Controller {
/** @var ILogger */
private $logger;

/** @var IUrlGenerator */
private $urlGenerator;

/** @var IUserManager */
private $userManager;

Expand Down Expand Up @@ -101,6 +107,7 @@ public function __construct(string $appName,
IInitialStateService $initialStateService,
IL10N $l10n,
ILogger $logger,
IUrlGenerator $urlGenerator,
IUserManager $userManager,
IUserSession $userSession) {
parent::__construct($appName, $request);
Expand All @@ -115,6 +122,7 @@ public function __construct(string $appName,
$this->initialStateService = $initialStateService;
$this->l10n = $l10n;
$this->logger = $logger;
$this->urlGenerator = $urlGenerator;
$this->userManager = $userManager;
$this->userSession = $userSession;
}
Expand All @@ -137,9 +145,9 @@ public function index(): TemplateResponse {
* @NoCSRFRequired
* @PublicPage
* @param string $hash
* @return TemplateResponse
* @return RedirectResponse|TemplateResponse Redirect for logged-in users, public template otherwise.
*/
public function gotoForm($hash): ?TemplateResponse {
public function gotoForm(string $hash): Response {
// Inject style on all templates
Util::addStyle($this->appName, 'forms');

Expand All @@ -149,18 +157,21 @@ public function gotoForm($hash): ?TemplateResponse {
return $this->provideTemplate(self::TEMPLATE_NOTFOUND);
}

// Does the user have access to form
if (!$this->formsService->hasUserAccess($form->getId())) {
return $this->provideTemplate(self::TEMPLATE_NOTFOUND);
}
// If not link-shared, redirect to internal route
if ($form->getAccess()['type'] !== 'public') {
$internalLink = $this->urlGenerator->linkToRoute('forms.page.index', ['hash' => $hash, 'action' => 'submit']);

// Does the user have permissions to submit (resp. submitOnce)
if (!$this->formsService->canSubmit($form->getId())) {
return $this->provideTemplate(self::TEMPLATE_NOSUBMIT, $form);
if ($this->userSession->isLoggedIn()) {
// Directly internal view
return new RedirectResponse($internalLink);
} else {
// Internal through login
return new RedirectResponse($this->urlGenerator->linkToRoute('core.login.showLoginForm', ['redirect_url' => $internalLink]));
}
}

// Has form expired
if ($form->getExpires() !== 0 && time() > $form->getExpires()) {
if ($this->formsService->hasFormExpired($form->getId())) {
return $this->provideTemplate(self::TEMPLATE_EXPIRED, $form);
}

Expand Down
21 changes: 19 additions & 2 deletions lib/Service/FormsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -148,14 +148,15 @@ public function getForm(int $id): array {
$result['questions'] = $this->getQuestions($id);

// Set proper user/groups properties

// Make sure we have the bare minimum
$result['access'] = array_merge(['users' => [], 'groups' => []], $result['access']);

// Properly format users & groups
$result['access']['users'] = array_map([$this, 'formatUsers'], $result['access']['users']);
$result['access']['groups'] = array_map([$this, 'formatGroups'], $result['access']['groups']);

// Append canSubmit, to be able to show proper EmptyContent on internal view.
$result['canSubmit'] = $this->canSubmit($form->getId());

return $result;
}

Expand Down Expand Up @@ -188,6 +189,11 @@ public function canSubmit($formId) {
return true;
}

// Owner is always allowed to submit
if ($this->currentUser->getUID() === $form->getOwnerId()) {
return true;
}

// Refuse access, if SubmitOnce is set and user already has taken part.
if ($form->getSubmitOnce()) {
$participants = $this->submissionMapper->findParticipantsByForm($form->getId());
Expand Down Expand Up @@ -248,6 +254,17 @@ public function hasUserAccess(int $formId): bool {
return false;
}

/*
* Has the form expired?
*
* @param int $formId The id of the form to check.
* @return boolean
*/
public function hasFormExpired(int $formId): bool {
$form = $this->formMapper->findById($formId);
return ($form->getExpires() !== 0 && $form->getExpires() < time());
}

/**
* Format users access
*
Expand Down
Loading