Backend.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2016 Joas Schilling <coding@schilljs.com>
  4. *
  5. * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  6. * @author Joas Schilling <coding@schilljs.com>
  7. * @author Roeland Jago Douma <roeland@famdouma.nl>
  8. * @author Thomas Citharel <nextcloud@tcit.fr>
  9. *
  10. * @license GNU AGPL version 3 or any later version
  11. *
  12. * This program is free software: you can redistribute it and/or modify
  13. * it under the terms of the GNU Affero General Public License as
  14. * published by the Free Software Foundation, either version 3 of the
  15. * License, or (at your option) any later version.
  16. *
  17. * This program is distributed in the hope that it will be useful,
  18. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  19. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  20. * GNU Affero General Public License for more details.
  21. *
  22. * You should have received a copy of the GNU Affero General Public License
  23. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  24. *
  25. */
  26. namespace OCA\DAV\CalDAV\Activity;
  27. use OCA\DAV\CalDAV\Activity\Provider\Calendar;
  28. use OCA\DAV\CalDAV\Activity\Provider\Event;
  29. use OCA\DAV\CalDAV\CalDavBackend;
  30. use OCP\Activity\IEvent;
  31. use OCP\Activity\IManager as IActivityManager;
  32. use OCP\App\IAppManager;
  33. use OCP\IGroup;
  34. use OCP\IGroupManager;
  35. use OCP\IUser;
  36. use OCP\IUserManager;
  37. use OCP\IUserSession;
  38. use Sabre\VObject\Reader;
  39. /**
  40. * Class Backend
  41. *
  42. * @package OCA\DAV\CalDAV\Activity
  43. */
  44. class Backend {
  45. /** @var IActivityManager */
  46. protected $activityManager;
  47. /** @var IGroupManager */
  48. protected $groupManager;
  49. /** @var IUserSession */
  50. protected $userSession;
  51. /** @var IAppManager */
  52. protected $appManager;
  53. /** @var IUserManager */
  54. protected $userManager;
  55. public function __construct(IActivityManager $activityManager, IGroupManager $groupManager, IUserSession $userSession, IAppManager $appManager, IUserManager $userManager) {
  56. $this->activityManager = $activityManager;
  57. $this->groupManager = $groupManager;
  58. $this->userSession = $userSession;
  59. $this->appManager = $appManager;
  60. $this->userManager = $userManager;
  61. }
  62. /**
  63. * Creates activities when a calendar was creates
  64. *
  65. * @param array $calendarData
  66. */
  67. public function onCalendarAdd(array $calendarData) {
  68. $this->triggerCalendarActivity(Calendar::SUBJECT_ADD, $calendarData);
  69. }
  70. /**
  71. * Creates activities when a calendar was updated
  72. *
  73. * @param array $calendarData
  74. * @param array $shares
  75. * @param array $properties
  76. */
  77. public function onCalendarUpdate(array $calendarData, array $shares, array $properties) {
  78. $this->triggerCalendarActivity(Calendar::SUBJECT_UPDATE, $calendarData, $shares, $properties);
  79. }
  80. /**
  81. * Creates activities when a calendar was moved to trash
  82. *
  83. * @param array $calendarData
  84. * @param array $shares
  85. */
  86. public function onCalendarMovedToTrash(array $calendarData, array $shares): void {
  87. $this->triggerCalendarActivity(Calendar::SUBJECT_MOVE_TO_TRASH, $calendarData, $shares);
  88. }
  89. /**
  90. * Creates activities when a calendar was restored
  91. *
  92. * @param array $calendarData
  93. * @param array $shares
  94. */
  95. public function onCalendarRestored(array $calendarData, array $shares): void {
  96. $this->triggerCalendarActivity(Calendar::SUBJECT_RESTORE, $calendarData, $shares);
  97. }
  98. /**
  99. * Creates activities when a calendar was deleted
  100. *
  101. * @param array $calendarData
  102. * @param array $shares
  103. */
  104. public function onCalendarDelete(array $calendarData, array $shares): void {
  105. $this->triggerCalendarActivity(Calendar::SUBJECT_DELETE, $calendarData, $shares);
  106. }
  107. /**
  108. * Creates activities when a calendar was (un)published
  109. *
  110. * @param array $calendarData
  111. * @param bool $publishStatus
  112. */
  113. public function onCalendarPublication(array $calendarData, bool $publishStatus): void {
  114. $this->triggerCalendarActivity($publishStatus ? Calendar::SUBJECT_PUBLISH : Calendar::SUBJECT_UNPUBLISH, $calendarData);
  115. }
  116. /**
  117. * Creates activities for all related users when a calendar was touched
  118. *
  119. * @param string $action
  120. * @param array $calendarData
  121. * @param array $shares
  122. * @param array $changedProperties
  123. */
  124. protected function triggerCalendarActivity($action, array $calendarData, array $shares = [], array $changedProperties = []) {
  125. if (!isset($calendarData['principaluri'])) {
  126. return;
  127. }
  128. $principal = explode('/', $calendarData['principaluri']);
  129. $owner = array_pop($principal);
  130. $currentUser = $this->userSession->getUser();
  131. if ($currentUser instanceof IUser) {
  132. $currentUser = $currentUser->getUID();
  133. } else {
  134. $currentUser = $owner;
  135. }
  136. $event = $this->activityManager->generateEvent();
  137. $event->setApp('dav')
  138. ->setObject('calendar', (int) $calendarData['id'])
  139. ->setType('calendar')
  140. ->setAuthor($currentUser);
  141. $changedVisibleInformation = array_intersect([
  142. '{DAV:}displayname',
  143. '{http://apple.com/ns/ical/}calendar-color'
  144. ], array_keys($changedProperties));
  145. if (empty($shares) || ($action === Calendar::SUBJECT_UPDATE && empty($changedVisibleInformation))) {
  146. $users = [$owner];
  147. } else {
  148. $users = $this->getUsersForShares($shares);
  149. $users[] = $owner;
  150. }
  151. foreach ($users as $user) {
  152. if ($action === Calendar::SUBJECT_DELETE && !$this->userManager->userExists($user)) {
  153. // Avoid creating calendar_delete activities for deleted users
  154. continue;
  155. }
  156. $event->setAffectedUser($user)
  157. ->setSubject(
  158. $user === $currentUser ? $action . '_self' : $action,
  159. [
  160. 'actor' => $currentUser,
  161. 'calendar' => [
  162. 'id' => (int) $calendarData['id'],
  163. 'uri' => $calendarData['uri'],
  164. 'name' => $calendarData['{DAV:}displayname'],
  165. ],
  166. ]
  167. );
  168. $this->activityManager->publish($event);
  169. }
  170. }
  171. /**
  172. * Creates activities for all related users when a calendar was (un-)shared
  173. *
  174. * @param array $calendarData
  175. * @param array $shares
  176. * @param array $add
  177. * @param array $remove
  178. */
  179. public function onCalendarUpdateShares(array $calendarData, array $shares, array $add, array $remove) {
  180. $principal = explode('/', $calendarData['principaluri']);
  181. $owner = $principal[2];
  182. $currentUser = $this->userSession->getUser();
  183. if ($currentUser instanceof IUser) {
  184. $currentUser = $currentUser->getUID();
  185. } else {
  186. $currentUser = $owner;
  187. }
  188. $event = $this->activityManager->generateEvent();
  189. $event->setApp('dav')
  190. ->setObject('calendar', (int) $calendarData['id'])
  191. ->setType('calendar')
  192. ->setAuthor($currentUser);
  193. foreach ($remove as $principal) {
  194. // principal:principals/users/test
  195. $parts = explode(':', $principal, 2);
  196. if ($parts[0] !== 'principal') {
  197. continue;
  198. }
  199. $principal = explode('/', $parts[1]);
  200. if ($principal[1] === 'users') {
  201. $this->triggerActivityUser(
  202. $principal[2],
  203. $event,
  204. $calendarData,
  205. Calendar::SUBJECT_UNSHARE_USER,
  206. Calendar::SUBJECT_DELETE . '_self'
  207. );
  208. if ($owner !== $principal[2]) {
  209. $parameters = [
  210. 'actor' => $event->getAuthor(),
  211. 'calendar' => [
  212. 'id' => (int) $calendarData['id'],
  213. 'uri' => $calendarData['uri'],
  214. 'name' => $calendarData['{DAV:}displayname'],
  215. ],
  216. 'user' => $principal[2],
  217. ];
  218. if ($owner === $event->getAuthor()) {
  219. $subject = Calendar::SUBJECT_UNSHARE_USER . '_you';
  220. } elseif ($principal[2] === $event->getAuthor()) {
  221. $subject = Calendar::SUBJECT_UNSHARE_USER . '_self';
  222. } else {
  223. $event->setAffectedUser($event->getAuthor())
  224. ->setSubject(Calendar::SUBJECT_UNSHARE_USER . '_you', $parameters);
  225. $this->activityManager->publish($event);
  226. $subject = Calendar::SUBJECT_UNSHARE_USER . '_by';
  227. }
  228. $event->setAffectedUser($owner)
  229. ->setSubject($subject, $parameters);
  230. $this->activityManager->publish($event);
  231. }
  232. } elseif ($principal[1] === 'groups') {
  233. $this->triggerActivityGroup($principal[2], $event, $calendarData, Calendar::SUBJECT_UNSHARE_USER);
  234. $parameters = [
  235. 'actor' => $event->getAuthor(),
  236. 'calendar' => [
  237. 'id' => (int) $calendarData['id'],
  238. 'uri' => $calendarData['uri'],
  239. 'name' => $calendarData['{DAV:}displayname'],
  240. ],
  241. 'group' => $principal[2],
  242. ];
  243. if ($owner === $event->getAuthor()) {
  244. $subject = Calendar::SUBJECT_UNSHARE_GROUP . '_you';
  245. } else {
  246. $event->setAffectedUser($event->getAuthor())
  247. ->setSubject(Calendar::SUBJECT_UNSHARE_GROUP . '_you', $parameters);
  248. $this->activityManager->publish($event);
  249. $subject = Calendar::SUBJECT_UNSHARE_GROUP . '_by';
  250. }
  251. $event->setAffectedUser($owner)
  252. ->setSubject($subject, $parameters);
  253. $this->activityManager->publish($event);
  254. }
  255. }
  256. foreach ($add as $share) {
  257. if ($this->isAlreadyShared($share['href'], $shares)) {
  258. continue;
  259. }
  260. // principal:principals/users/test
  261. $parts = explode(':', $share['href'], 2);
  262. if ($parts[0] !== 'principal') {
  263. continue;
  264. }
  265. $principal = explode('/', $parts[1]);
  266. if ($principal[1] === 'users') {
  267. $this->triggerActivityUser($principal[2], $event, $calendarData, Calendar::SUBJECT_SHARE_USER);
  268. if ($owner !== $principal[2]) {
  269. $parameters = [
  270. 'actor' => $event->getAuthor(),
  271. 'calendar' => [
  272. 'id' => (int) $calendarData['id'],
  273. 'uri' => $calendarData['uri'],
  274. 'name' => $calendarData['{DAV:}displayname'],
  275. ],
  276. 'user' => $principal[2],
  277. ];
  278. if ($owner === $event->getAuthor()) {
  279. $subject = Calendar::SUBJECT_SHARE_USER . '_you';
  280. } else {
  281. $event->setAffectedUser($event->getAuthor())
  282. ->setSubject(Calendar::SUBJECT_SHARE_USER . '_you', $parameters);
  283. $this->activityManager->publish($event);
  284. $subject = Calendar::SUBJECT_SHARE_USER . '_by';
  285. }
  286. $event->setAffectedUser($owner)
  287. ->setSubject($subject, $parameters);
  288. $this->activityManager->publish($event);
  289. }
  290. } elseif ($principal[1] === 'groups') {
  291. $this->triggerActivityGroup($principal[2], $event, $calendarData, Calendar::SUBJECT_SHARE_USER);
  292. $parameters = [
  293. 'actor' => $event->getAuthor(),
  294. 'calendar' => [
  295. 'id' => (int) $calendarData['id'],
  296. 'uri' => $calendarData['uri'],
  297. 'name' => $calendarData['{DAV:}displayname'],
  298. ],
  299. 'group' => $principal[2],
  300. ];
  301. if ($owner === $event->getAuthor()) {
  302. $subject = Calendar::SUBJECT_SHARE_GROUP . '_you';
  303. } else {
  304. $event->setAffectedUser($event->getAuthor())
  305. ->setSubject(Calendar::SUBJECT_SHARE_GROUP . '_you', $parameters);
  306. $this->activityManager->publish($event);
  307. $subject = Calendar::SUBJECT_SHARE_GROUP . '_by';
  308. }
  309. $event->setAffectedUser($owner)
  310. ->setSubject($subject, $parameters);
  311. $this->activityManager->publish($event);
  312. }
  313. }
  314. }
  315. /**
  316. * Checks if a calendar is already shared with a principal
  317. *
  318. * @param string $principal
  319. * @param array[] $shares
  320. * @return bool
  321. */
  322. protected function isAlreadyShared($principal, $shares) {
  323. foreach ($shares as $share) {
  324. if ($principal === $share['href']) {
  325. return true;
  326. }
  327. }
  328. return false;
  329. }
  330. /**
  331. * Creates the given activity for all members of the given group
  332. *
  333. * @param string $gid
  334. * @param IEvent $event
  335. * @param array $properties
  336. * @param string $subject
  337. */
  338. protected function triggerActivityGroup($gid, IEvent $event, array $properties, $subject) {
  339. $group = $this->groupManager->get($gid);
  340. if ($group instanceof IGroup) {
  341. foreach ($group->getUsers() as $user) {
  342. // Exclude current user
  343. if ($user->getUID() !== $event->getAuthor()) {
  344. $this->triggerActivityUser($user->getUID(), $event, $properties, $subject);
  345. }
  346. }
  347. }
  348. }
  349. /**
  350. * Creates the given activity for the given user
  351. *
  352. * @param string $user
  353. * @param IEvent $event
  354. * @param array $properties
  355. * @param string $subject
  356. * @param string $subjectSelf
  357. */
  358. protected function triggerActivityUser($user, IEvent $event, array $properties, $subject, $subjectSelf = '') {
  359. $event->setAffectedUser($user)
  360. ->setSubject(
  361. $user === $event->getAuthor() && $subjectSelf ? $subjectSelf : $subject,
  362. [
  363. 'actor' => $event->getAuthor(),
  364. 'calendar' => [
  365. 'id' => (int) $properties['id'],
  366. 'uri' => $properties['uri'],
  367. 'name' => $properties['{DAV:}displayname'],
  368. ],
  369. ]
  370. );
  371. $this->activityManager->publish($event);
  372. }
  373. /**
  374. * Creates activities when a calendar object was created/updated/deleted
  375. *
  376. * @param string $action
  377. * @param array $calendarData
  378. * @param array $shares
  379. * @param array $objectData
  380. */
  381. public function onTouchCalendarObject($action, array $calendarData, array $shares, array $objectData) {
  382. if (!isset($calendarData['principaluri'])) {
  383. return;
  384. }
  385. $principal = explode('/', $calendarData['principaluri']);
  386. $owner = array_pop($principal);
  387. $currentUser = $this->userSession->getUser();
  388. if ($currentUser instanceof IUser) {
  389. $currentUser = $currentUser->getUID();
  390. } else {
  391. $currentUser = $owner;
  392. }
  393. $classification = $objectData['classification'] ?? CalDavBackend::CLASSIFICATION_PUBLIC;
  394. $object = $this->getObjectNameAndType($objectData);
  395. if (!$object) {
  396. return;
  397. }
  398. $action = $action . '_' . $object['type'];
  399. if ($object['type'] === 'todo' && strpos($action, Event::SUBJECT_OBJECT_UPDATE) === 0 && $object['status'] === 'COMPLETED') {
  400. $action .= '_completed';
  401. } elseif ($object['type'] === 'todo' && strpos($action, Event::SUBJECT_OBJECT_UPDATE) === 0 && $object['status'] === 'NEEDS-ACTION') {
  402. $action .= '_needs_action';
  403. }
  404. $event = $this->activityManager->generateEvent();
  405. $event->setApp('dav')
  406. ->setObject('calendar', (int) $calendarData['id'])
  407. ->setType($object['type'] === 'event' ? 'calendar_event' : 'calendar_todo')
  408. ->setAuthor($currentUser);
  409. $users = $this->getUsersForShares($shares);
  410. $users[] = $owner;
  411. // Users for share can return the owner itself if the calendar is published
  412. foreach (array_unique($users) as $user) {
  413. if ($classification === CalDavBackend::CLASSIFICATION_PRIVATE && $user !== $owner) {
  414. // Private events are only shown to the owner
  415. continue;
  416. }
  417. $params = [
  418. 'actor' => $event->getAuthor(),
  419. 'calendar' => [
  420. 'id' => (int) $calendarData['id'],
  421. 'uri' => $calendarData['uri'],
  422. 'name' => $calendarData['{DAV:}displayname'],
  423. ],
  424. 'object' => [
  425. 'id' => $object['id'],
  426. 'name' => $classification === CalDavBackend::CLASSIFICATION_CONFIDENTIAL && $user !== $owner ? 'Busy' : $object['name'],
  427. 'classified' => $classification === CalDavBackend::CLASSIFICATION_CONFIDENTIAL && $user !== $owner,
  428. ],
  429. ];
  430. if ($object['type'] === 'event' && strpos($action, Event::SUBJECT_OBJECT_DELETE) === false && $this->appManager->isEnabledForUser('calendar')) {
  431. $params['object']['link']['object_uri'] = $objectData['uri'];
  432. $params['object']['link']['calendar_uri'] = $calendarData['uri'];
  433. $params['object']['link']['owner'] = $owner;
  434. }
  435. $event->setAffectedUser($user)
  436. ->setSubject(
  437. $user === $currentUser ? $action . '_self' : $action,
  438. $params
  439. );
  440. $this->activityManager->publish($event);
  441. }
  442. }
  443. /**
  444. * Creates activities when a calendar object was moved
  445. */
  446. public function onMovedCalendarObject(array $sourceCalendarData, array $targetCalendarData, array $sourceShares, array $targetShares, array $objectData): void {
  447. if (!isset($targetCalendarData['principaluri'])) {
  448. return;
  449. }
  450. $sourcePrincipal = explode('/', $sourceCalendarData['principaluri']);
  451. $sourceOwner = array_pop($sourcePrincipal);
  452. $targetPrincipal = explode('/', $targetCalendarData['principaluri']);
  453. $targetOwner = array_pop($targetPrincipal);
  454. if ($sourceOwner !== $targetOwner) {
  455. $this->onTouchCalendarObject(
  456. Event::SUBJECT_OBJECT_DELETE,
  457. $sourceCalendarData,
  458. $sourceShares,
  459. $objectData
  460. );
  461. $this->onTouchCalendarObject(
  462. Event::SUBJECT_OBJECT_ADD,
  463. $targetCalendarData,
  464. $targetShares,
  465. $objectData
  466. );
  467. return;
  468. }
  469. $currentUser = $this->userSession->getUser();
  470. if ($currentUser instanceof IUser) {
  471. $currentUser = $currentUser->getUID();
  472. } else {
  473. $currentUser = $targetOwner;
  474. }
  475. $classification = $objectData['classification'] ?? CalDavBackend::CLASSIFICATION_PUBLIC;
  476. $object = $this->getObjectNameAndType($objectData);
  477. if (!$object) {
  478. return;
  479. }
  480. $event = $this->activityManager->generateEvent();
  481. $event->setApp('dav')
  482. ->setObject('calendar', (int) $targetCalendarData['id'])
  483. ->setType($object['type'] === 'event' ? 'calendar_event' : 'calendar_todo')
  484. ->setAuthor($currentUser);
  485. $users = $this->getUsersForShares(array_intersect($sourceShares, $targetShares));
  486. $users[] = $targetOwner;
  487. // Users for share can return the owner itself if the calendar is published
  488. foreach (array_unique($users) as $user) {
  489. if ($classification === CalDavBackend::CLASSIFICATION_PRIVATE && $user !== $targetOwner) {
  490. // Private events are only shown to the owner
  491. continue;
  492. }
  493. $params = [
  494. 'actor' => $event->getAuthor(),
  495. 'sourceCalendar' => [
  496. 'id' => (int) $sourceCalendarData['id'],
  497. 'uri' => $sourceCalendarData['uri'],
  498. 'name' => $sourceCalendarData['{DAV:}displayname'],
  499. ],
  500. 'targetCalendar' => [
  501. 'id' => (int) $targetCalendarData['id'],
  502. 'uri' => $targetCalendarData['uri'],
  503. 'name' => $targetCalendarData['{DAV:}displayname'],
  504. ],
  505. 'object' => [
  506. 'id' => $object['id'],
  507. 'name' => $classification === CalDavBackend::CLASSIFICATION_CONFIDENTIAL && $user !== $targetOwner ? 'Busy' : $object['name'],
  508. 'classified' => $classification === CalDavBackend::CLASSIFICATION_CONFIDENTIAL && $user !== $targetOwner,
  509. ],
  510. ];
  511. if ($object['type'] === 'event' && $this->appManager->isEnabledForUser('calendar')) {
  512. $params['object']['link']['object_uri'] = $objectData['uri'];
  513. $params['object']['link']['calendar_uri'] = $targetCalendarData['uri'];
  514. $params['object']['link']['owner'] = $targetOwner;
  515. }
  516. $event->setAffectedUser($user)
  517. ->setSubject(
  518. $user === $currentUser ? Event::SUBJECT_OBJECT_MOVE . '_' . $object['type'] . '_self' : Event::SUBJECT_OBJECT_MOVE . '_' . $object['type'],
  519. $params
  520. );
  521. $this->activityManager->publish($event);
  522. }
  523. }
  524. /**
  525. * @param array $objectData
  526. * @return string[]|false
  527. */
  528. protected function getObjectNameAndType(array $objectData) {
  529. $vObject = Reader::read($objectData['calendardata']);
  530. $component = $componentType = null;
  531. foreach ($vObject->getComponents() as $component) {
  532. if (in_array($component->name, ['VEVENT', 'VTODO'])) {
  533. $componentType = $component->name;
  534. break;
  535. }
  536. }
  537. if (!$componentType) {
  538. // Calendar objects must have a VEVENT or VTODO component
  539. return false;
  540. }
  541. if ($componentType === 'VEVENT') {
  542. return ['id' => (string) $component->UID, 'name' => (string) $component->SUMMARY, 'type' => 'event'];
  543. }
  544. return ['id' => (string) $component->UID, 'name' => (string) $component->SUMMARY, 'type' => 'todo', 'status' => (string) $component->STATUS];
  545. }
  546. /**
  547. * Get all users that have access to a given calendar
  548. *
  549. * @param array $shares
  550. * @return string[]
  551. */
  552. protected function getUsersForShares(array $shares) {
  553. $users = $groups = [];
  554. foreach ($shares as $share) {
  555. $principal = explode('/', $share['{http://owncloud.org/ns}principal']);
  556. if ($principal[1] === 'users') {
  557. $users[] = $principal[2];
  558. } elseif ($principal[1] === 'groups') {
  559. $groups[] = $principal[2];
  560. }
  561. }
  562. if (!empty($groups)) {
  563. foreach ($groups as $gid) {
  564. $group = $this->groupManager->get($gid);
  565. if ($group instanceof IGroup) {
  566. foreach ($group->getUsers() as $user) {
  567. $users[] = $user->getUID();
  568. }
  569. }
  570. }
  571. }
  572. return array_unique($users);
  573. }
  574. }