From fe43e02ea17ede509086a7d7bbde24bd07f93cb7 Mon Sep 17 00:00:00 2001 From: Cristian Scheid Date: Mon, 9 Feb 2026 09:03:40 -0300 Subject: [PATCH] feat: add endpoint to remove circle from parent circles Signed-off-by: Cristian Scheid --- appinfo/routes.php | 1 + lib/Activity/ProviderSubjectCircle.php | 20 +++ lib/Controller/LocalController.php | 22 +++ lib/Events/LeavingParentCirclesEvent.php | 41 ++++++ lib/Events/LeftParentCirclesEvent.php | 39 ++++++ .../CircleLeaveParentCircles.php | 126 ++++++++++++++++++ lib/Service/ActivityService.php | 24 ++++ lib/Service/CircleService.php | 32 +++++ lib/Service/EventService.php | 19 +++ 9 files changed, 324 insertions(+) create mode 100644 lib/Events/LeavingParentCirclesEvent.php create mode 100644 lib/Events/LeftParentCirclesEvent.php create mode 100644 lib/FederatedItems/CircleLeaveParentCircles.php diff --git a/appinfo/routes.php b/appinfo/routes.php index 9ad74e1f8..9448112d2 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -15,6 +15,7 @@ ['name' => 'Local#probeCircles', 'url' => '/probecircles', 'verb' => 'GET'], ['name' => 'Local#create', 'url' => '/circles', 'verb' => 'POST'], ['name' => 'Local#destroy', 'url' => '/circles/{circleId}', 'verb' => 'DELETE'], + ['name' => 'Local#leaveParentCircles', 'url' => '/circles/{circleId}/leave-parent-circles', 'verb' => 'PUT'], ['name' => 'Local#search', 'url' => '/search', 'verb' => 'GET'], ['name' => 'Local#circleDetails', 'url' => '/circles/{circleId}', 'verb' => 'GET'], ['name' => 'Local#members', 'url' => '/circles/{circleId}/members', 'verb' => 'GET'], diff --git a/lib/Activity/ProviderSubjectCircle.php b/lib/Activity/ProviderSubjectCircle.php index f591929cd..ffadd6bb2 100644 --- a/lib/Activity/ProviderSubjectCircle.php +++ b/lib/Activity/ProviderSubjectCircle.php @@ -46,4 +46,24 @@ public function parseSubjectCircleDelete(IEvent $event, array $params): void { throw new FakeException(); } + + /** + * @param IEvent $event + * @param array $params + * + * @throws FakeException + */ + public function parseSubjectCircleLeavingParentCircles(IEvent $event, array $params): void { + if ($event->getSubject() !== 'circle_leaving_parent_circles') { + return; + } + + $this->parseCircleEvent( + $event, $params, + $this->l10n->t('You removed {circle} from all teams it belonged to'), + $this->l10n->t('{author} removed {circle} from all teams it belonged to') + ); + + throw new FakeException(); + } } diff --git a/lib/Controller/LocalController.php b/lib/Controller/LocalController.php index 8a1e8f08f..25f39310a 100644 --- a/lib/Controller/LocalController.php +++ b/lib/Controller/LocalController.php @@ -160,6 +160,28 @@ public function destroy(string $circleId): DataResponse { } + /** + * @NoAdminRequired + * + * @param string $circleId + * + * @return DataResponse + * @throws OCSException + */ + public function leaveParentCircles(string $circleId): DataResponse { + try { + $this->setCurrentFederatedUser(); + + $circle = $this->circleService->leaveParentCircles($circleId); + + return new DataResponse($this->serializeArray($circle)); + } catch (Exception $e) { + $this->e($e, ['circleId' => $circleId]); + throw new OCSException($e->getMessage(), (int)$e->getCode()); + } + } + + /** * @NoAdminRequired * diff --git a/lib/Events/LeavingParentCirclesEvent.php b/lib/Events/LeavingParentCirclesEvent.php new file mode 100644 index 000000000..210f59df2 --- /dev/null +++ b/lib/Events/LeavingParentCirclesEvent.php @@ -0,0 +1,41 @@ +getFederatedEvent()->setResultEntry(string $key, array $data); + * + * @package OCA\Circles\Events + */ +class LeavingParentCirclesEvent extends CircleGenericEvent { + /** + * LeavingParentCirclesEvent constructor. + * + * @param FederatedEvent $federatedEvent + */ + public function __construct(FederatedEvent $federatedEvent) { + parent::__construct($federatedEvent); + } +} diff --git a/lib/Events/LeftParentCirclesEvent.php b/lib/Events/LeftParentCirclesEvent.php new file mode 100644 index 000000000..88ee7d469 --- /dev/null +++ b/lib/Events/LeftParentCirclesEvent.php @@ -0,0 +1,39 @@ +memberRequest = $memberRequest; + $this->circleRequest = $circleRequest; + $this->membershipService = $membershipService; + $this->eventService = $eventService; + $this->configService = $configService; + + $this->setup('app', Application::APP_ID); + } + + /** + * @param FederatedEvent $event + * + * @throws RequestBuilderException + * @throws MemberHelperException + * @throws MemberLevelException + */ + public function verify(FederatedEvent $event): void { + $circle = $event->getCircle(); + $initiator = $circle->getInitiator(); + $initiatorHelper = new MemberHelper($initiator); + $initiatorHelper->mustBeOwner(); + + $event->setOutcome($this->serialize($circle)); + } + + /** + * @param FederatedEvent $event + * + * @throws RequestBuilderException + * @throws MemberNotFoundException + */ + public function manage(FederatedEvent $event): void { + $circle = $event->getCircle(); + $parentCircles = $this->membershipService->getParentCircles($circle); + foreach ($parentCircles as $parentCircle) { + $member = $this->memberRequest->getMember( + $parentCircle->getSingleId(), + $circle->getSingleId() + ); + $this->memberRequest->delete($member); + $this->membershipService->onUpdate($member->getSingleId()); + $this->membershipService->updatePopulation($parentCircle); + } + $this->eventService->circleLeavingParentCircles($event); + } + + /** + * @param FederatedEvent $event + * @param array $results + */ + public function result(FederatedEvent $event, array $results): void { + $this->eventService->circleLeftParentCircles($event, $results); + } +} diff --git a/lib/Service/ActivityService.php b/lib/Service/ActivityService.php index c33a52eff..03823ce93 100644 --- a/lib/Service/ActivityService.php +++ b/lib/Service/ActivityService.php @@ -81,6 +81,30 @@ public function onCircleDestruction(Circle $circle): void { ); } + /** + * @param Circle $circle + */ + public function onCircleLeavingParentCircles(Circle $circle): void { + if ($circle->isConfig(Circle::CFG_PERSONAL)) { + return; + } + + $event = $this->generateEvent('circles_as_member'); + $event->setSubject( + 'circle_leaving_parent_circles', + [ + 'ver' => 2, + 'circle' => $this->shortenCircleData($circle), + 'initiator' => ($circle->hasInitiator() ? $this->shortenMemberData($circle->getInitiator()) : null), + ] + ); + + $this->publishEvent( + $event, + $this->memberRequest->getInheritedMembers($circle->getSingleId(), false, Member::LEVEL_MODERATOR) + ); + } + /** * @param Circle $circle * @param Member $member diff --git a/lib/Service/CircleService.php b/lib/Service/CircleService.php index 35fcf9311..c582bbde9 100644 --- a/lib/Service/CircleService.php +++ b/lib/Service/CircleService.php @@ -33,6 +33,7 @@ use OCA\Circles\FederatedItems\CircleEdit; use OCA\Circles\FederatedItems\CircleJoin; use OCA\Circles\FederatedItems\CircleLeave; +use OCA\Circles\FederatedItems\CircleLeaveParentCircles; use OCA\Circles\FederatedItems\CircleSetting; use OCA\Circles\IEntity; use OCA\Circles\IFederatedUser; @@ -253,6 +254,37 @@ public function destroy(string $circleId, bool $forceSync = false): array { } + /** + * @param string $circleId + * @param bool $forceSync + * + * @return array + * @throws CircleNotFoundException + * @throws FederatedEventException + * @throws FederatedItemException + * @throws InitiatorNotConfirmedException + * @throws InitiatorNotFoundException + * @throws OwnerNotFoundException + * @throws RemoteInstanceException + * @throws RemoteNotFoundException + * @throws RemoteResourceNotFoundException + * @throws RequestBuilderException + * @throws UnknownRemoteException + */ + public function leaveParentCircles(string $circleId, bool $forceSync = false): array { + $this->federatedUserService->mustHaveCurrentUser(); + + $circle = $this->getCircle($circleId); + + $event = new FederatedEvent(CircleLeaveParentCircles::class); + $event->setCircle($circle); + $event->forceSync($forceSync); + $this->federatedEventService->newEvent($event); + + return $event->getOutcome(); + } + + /** * @param string $circleId * @param int $config diff --git a/lib/Service/EventService.php b/lib/Service/EventService.php index ca5835175..b9e9e9969 100644 --- a/lib/Service/EventService.php +++ b/lib/Service/EventService.php @@ -26,6 +26,8 @@ use OCA\Circles\Events\Files\CreatingFileShareEvent; use OCA\Circles\Events\Files\FileShareCreatedEvent; use OCA\Circles\Events\Files\PreparingFileShareEvent; +use OCA\Circles\Events\LeavingParentCirclesEvent; +use OCA\Circles\Events\LeftParentCirclesEvent; use OCA\Circles\Events\MembershipsCreatedEvent; use OCA\Circles\Events\MembershipsRemovedEvent; use OCA\Circles\Events\PreparingCircleMemberEvent; @@ -100,6 +102,23 @@ public function circleDestroyed(FederatedEvent $federatedEvent, array $results): $this->eventDispatcher->dispatchTyped($event); } + /** + * @param FederatedEvent $federatedEvent + */ + public function circleLeavingParentCircles(FederatedEvent $federatedEvent): void { + $event = new LeavingParentCirclesEvent($federatedEvent); + $this->eventDispatcher->dispatchTyped($event); + $this->activityService->onCircleLeavingParentCircles($event->getCircle()); + } + + /** + * @param FederatedEvent $federatedEvent + * @param array $results + */ + public function circleLeftParentCircles(FederatedEvent $federatedEvent, array $results): void { + $event = new LeftParentCirclesEvent($federatedEvent, $results); + $this->eventDispatcher->dispatchTyped($event); + } /** * @param FederatedEvent $federatedEvent