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