1<?php
2/**
3 * @copyright Copyright (c) 2020 John Molakvoæ <skjnldsv@protonmail.com>
4 *
5 * @author John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
6 * @author Jonas Rittershofer <jotoeri@users.noreply.github.com>
7 *
8 * @license GNU AGPL version 3 or any later version
9 *
10 * This program is free software: you can redistribute it and/or modify
11 * it under the terms of the GNU Affero General Public License as
12 * published by the Free Software Foundation, either version 3 of the
13 * License, or (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 * GNU Affero General Public License for more details.
19 *
20 * You should have received a copy of the GNU Affero General Public License
21 * along with this program. If not, see <http://www.gnu.org/licenses/>.
22 *
23 */
24
25namespace OCA\Forms\Service;
26
27use OCA\Forms\Activity\ActivityManager;
28use OCA\Forms\Db\Form;
29use OCA\Forms\Db\FormMapper;
30use OCA\Forms\Db\OptionMapper;
31use OCA\Forms\Db\QuestionMapper;
32use OCA\Forms\Db\SubmissionMapper;
33use OCP\AppFramework\Db\DoesNotExistException;
34use OCP\AppFramework\Db\IMapperException;
35use OCP\IGroup;
36use OCP\IGroupManager;
37use OCP\ILogger;
38use OCP\IUser;
39use OCP\IUserManager;
40use OCP\IUserSession;
41use OCP\Share\IShare;
42
43/**
44 * Trait for getting forms information in a service
45 */
46class FormsService {
47
48	/** @var ActivityManager */
49	private $activityManager;
50
51	/** @var FormMapper */
52	private $formMapper;
53
54	/** @var OptionMapper */
55	private $optionMapper;
56
57	/** @var QuestionMapper */
58	private $questionMapper;
59
60	/** @var SubmissionMapper */
61	private $submissionMapper;
62
63	/** @var IGroupManager */
64	private $groupManager;
65
66	/** @var ILogger */
67	private $logger;
68
69	/** @var IUser */
70	private $currentUser;
71
72	/** @var IUserManager */
73	private $userManager;
74
75	public function __construct(ActivityManager $activityManager,
76								FormMapper $formMapper,
77								OptionMapper $optionMapper,
78								QuestionMapper $questionMapper,
79								SubmissionMapper $submissionMapper,
80								IGroupManager $groupManager,
81								ILogger $logger,
82								IUserManager $userManager,
83								IUserSession $userSession) {
84		$this->activityManager = $activityManager;
85		$this->formMapper = $formMapper;
86		$this->optionMapper = $optionMapper;
87		$this->questionMapper = $questionMapper;
88		$this->submissionMapper = $submissionMapper;
89		$this->groupManager = $groupManager;
90		$this->logger = $logger;
91		$this->userManager = $userManager;
92
93		$this->currentUser = $userSession->getUser();
94	}
95
96	/**
97	 * Load options corresponding to question
98	 *
99	 * @param integer $questionId
100	 * @return array
101	 */
102	public function getOptions(int $questionId): array {
103		$optionList = [];
104		try {
105			$optionEntities = $this->optionMapper->findByQuestion($questionId);
106			foreach ($optionEntities as $optionEntity) {
107				$optionList[] = $optionEntity->read();
108			}
109		} catch (DoesNotExistException $e) {
110			//handle silently
111		} finally {
112			return $optionList;
113		}
114	}
115
116	/**
117	 * Load questions corresponding to form
118	 *
119	 * @param integer $formId
120	 * @return array
121	 */
122	public function getQuestions(int $formId): array {
123		$questionList = [];
124		try {
125			$questionEntities = $this->questionMapper->findByForm($formId);
126			foreach ($questionEntities as $questionEntity) {
127				$question = $questionEntity->read();
128				$question['options'] = $this->getOptions($question['id']);
129				$questionList[] = $question;
130			}
131		} catch (DoesNotExistException $e) {
132			//handle silently
133		} finally {
134			return $questionList;
135		}
136	}
137
138	/**
139	 * Get a form data
140	 *
141	 * @param integer $id
142	 * @return array
143	 * @throws IMapperException
144	 */
145	public function getForm(int $id): array {
146		$form = $this->formMapper->findById($id);
147		$result = $form->read();
148		$result['questions'] = $this->getQuestions($id);
149
150		// Set proper user/groups properties
151		// Make sure we have the bare minimum
152		$result['access'] = array_merge(['users' => [], 'groups' => []], $result['access']);
153		// Properly format users & groups
154		$result['access']['users'] = array_map([$this, 'formatUsers'], $result['access']['users']);
155		$result['access']['groups'] = array_map([$this, 'formatGroups'], $result['access']['groups']);
156
157		// Append canSubmit, to be able to show proper EmptyContent on internal view.
158		$result['canSubmit'] = $this->canSubmit($form->getId());
159
160		return $result;
161	}
162
163	/**
164	 * Get a form data without sensitive informations
165	 *
166	 * @param integer $id
167	 * @return array
168	 * @throws IMapperException
169	 */
170	public function getPublicForm(int $id): array {
171		$form = $this->getForm($id);
172
173		// Remove sensitive data
174		unset($form['access']);
175		unset($form['ownerId']);
176
177		return $form;
178	}
179
180	/**
181	 * Can the user submit a form
182	 */
183	public function canSubmit($formId) {
184		$form = $this->formMapper->findById($formId);
185		$access = $form->getAccess();
186
187		// We cannot control how many time users can submit in public mode
188		if ($access['type'] === 'public') {
189			return true;
190		}
191
192		// Owner is always allowed to submit
193		if ($this->currentUser->getUID() === $form->getOwnerId()) {
194			return true;
195		}
196
197		// Refuse access, if SubmitOnce is set and user already has taken part.
198		if ($form->getSubmitOnce()) {
199			$participants = $this->submissionMapper->findParticipantsByForm($form->getId());
200			foreach ($participants as $participant) {
201				if ($participant === $this->currentUser->getUID()) {
202					return false;
203				}
204			}
205		}
206
207		return true;
208	}
209
210	/**
211	 * Check if user has access to this form
212	 *
213	 * @param integer $formId
214	 * @return boolean
215	 */
216	public function hasUserAccess(int $formId): bool {
217		$form = $this->formMapper->findById($formId);
218		$access = $form->getAccess();
219		$ownerId = $form->getOwnerId();
220
221		if ($access['type'] === 'public') {
222			return true;
223		}
224
225		// Refuse access, if not public and no user logged in.
226		if (!$this->currentUser) {
227			return false;
228		}
229
230		// Always grant access to owner.
231		if ($ownerId === $this->currentUser->getUID()) {
232			return true;
233		}
234
235		// Now all remaining users are allowed, if access-type 'registered'.
236		if ($access['type'] === 'registered') {
237			return true;
238		}
239
240		// Selected Access remains.
241		// Grant Access, if user is in users-Array.
242		if (in_array($this->currentUser->getUID(), $access['users'])) {
243			return true;
244		}
245
246		// Check if access granted by group.
247		foreach ($access['groups'] as $group) {
248			if ($this->groupManager->isInGroup($this->currentUser->getUID(), $group)) {
249				return true;
250			}
251		}
252
253		// None of the possible access-options matched.
254		return false;
255	}
256
257	/*
258	 * Has the form expired?
259	 *
260	 * @param int $formId The id of the form to check.
261	 * @return boolean
262	 */
263	public function hasFormExpired(int $formId): bool {
264		$form = $this->formMapper->findById($formId);
265		return ($form->getExpires() !== 0 && $form->getExpires() < time());
266	}
267
268	/**
269	 * Format users access
270	 *
271	 * @param string $userId
272	 * @return array
273	 */
274	private function formatUsers(string $userId): array {
275		$displayName = '';
276
277		$user = $this->userManager->get($userId);
278		if ($user instanceof IUser) {
279			$displayName = $user->getDisplayName();
280		}
281
282		return [
283			'shareWith' => $userId,
284			'displayName' => $displayName,
285			'shareType' => IShare::TYPE_USER
286		];
287	}
288
289	/**
290	 * Format groups access
291	 *
292	 * @param string $groupId
293	 * @return array
294	 */
295	private function formatGroups(string $groupId): array {
296		$displayName = '';
297
298		$group = $this->groupManager->get($groupId);
299		if ($group instanceof IGroup) {
300			$displayName = $group->getDisplayName();
301		}
302
303		return [
304			'shareWith' => $groupId,
305			'displayName' => $displayName,
306			'shareType' => IShare::TYPE_GROUP
307		];
308	}
309
310	/**
311	 * Compares two selected access arrays and creates activities for users.
312	 * @param Form $form Related Form
313	 * @param array $oldAccess old access-array
314	 * @param array $newAccess new access-array
315	 */
316	public function notifyNewShares(Form $form, array $oldAccess, array $newAccess) {
317		$newUsers = array_diff($newAccess['users'], $oldAccess['users']);
318		$newGroups = array_diff($newAccess['groups'], $oldAccess['groups']);
319
320		// Create Activities
321		foreach ($newUsers as $key => $newUserId) {
322			$this->activityManager->publishNewShare($form, $newUserId);
323		}
324		foreach ($newGroups as $key => $newGroupId) {
325			$this->activityManager->publishNewGroupShare($form, $newGroupId);
326		}
327	}
328}
329