From bf6c0346d8d35a719dd1bff1cb4d573d422f99ff Mon Sep 17 00:00:00 2001 From: Thomas Citharel Date: Wed, 31 May 2017 09:31:18 +0200 Subject: WIP Signed-off-by: Thomas Citharel --- .../Controller/InboxController.php | 43 +++ .../Controller/LikedController.php | 19 + .../Controller/MetadataController.php | 69 ++++ .../Controller/OutboxController.php | 23 ++ .../Controller/ProfileController.php | 425 +++++++++++++++++++++ .../Controller/RecommandController.php | 34 ++ 6 files changed, 613 insertions(+) create mode 100644 src/Wallabag/FederationBundle/Controller/InboxController.php create mode 100644 src/Wallabag/FederationBundle/Controller/LikedController.php create mode 100644 src/Wallabag/FederationBundle/Controller/MetadataController.php create mode 100644 src/Wallabag/FederationBundle/Controller/OutboxController.php create mode 100644 src/Wallabag/FederationBundle/Controller/ProfileController.php create mode 100644 src/Wallabag/FederationBundle/Controller/RecommandController.php (limited to 'src/Wallabag/FederationBundle/Controller') diff --git a/src/Wallabag/FederationBundle/Controller/InboxController.php b/src/Wallabag/FederationBundle/Controller/InboxController.php new file mode 100644 index 00000000..99cfeadf --- /dev/null +++ b/src/Wallabag/FederationBundle/Controller/InboxController.php @@ -0,0 +1,43 @@ +getDoctrine()->getManager(); + $response = new Response(); + + if ($activity = json_decode($request->getContent())) { + if ($activity->type === 'Follow' && isset($activity->actor->id)) { + $cloudId = new CloudId($activity->actor->id); + $account = new Account(); + $account->setServer($cloudId->getRemote()) + ->setUsername($cloudId->getUser()); + $em->persist($account); + $em->flush(); + + $response->setStatusCode(201); + } else { + $response->setStatusCode(400); + } + } + return $response; + } +} diff --git a/src/Wallabag/FederationBundle/Controller/LikedController.php b/src/Wallabag/FederationBundle/Controller/LikedController.php new file mode 100644 index 00000000..3e86d50d --- /dev/null +++ b/src/Wallabag/FederationBundle/Controller/LikedController.php @@ -0,0 +1,19 @@ +setContent(' +generateUrl('webfinger') . '?resource={uri}\"/> +'); + $response->headers->set('Content-Type', 'application/xrd+xml'); + return $response; + } + + /** + * @Route("/.well-known/webfinger", name="webfinger") + * @param Request $request + * @return JsonResponse + */ + public function getWebfingerData(Request $request) + { + $subject = $request->query->get('resource'); + + if (!$subject || strlen($subject) < 12 || 0 !== strpos($subject, 'acct:') || false !== strpos($subject, '@')) { + return new JsonResponse([]); + } + $subjectId = substr($subject, 5); + + $cloudId = $this->getCloudId($subjectId); + + + $data = [ + 'subject' => $subject, + 'aliases' => [$this->generateUrl('profile', $cloudId->getUser())], + 'links' => [ + [ + 'rel' => 'http://webfinger.net/rel/profile-page', + 'type' => 'text/html', + 'href' => $this->generateUrl('profile', $cloudId->getUser()) + ], + ] + ]; + return new JsonResponse($data); + } + + private function getCloudId($subjectId) + { + return new CloudId($subjectId); + } + + + # + # {"subject":"acct:tcit@social.tcit.fr","aliases":["https://social.tcit.fr/@tcit","https://social.tcit.fr/users/tcit"],"links":[{"rel":"http://webfinger.net/rel/profile-page","type":"text/html","href":"https://social.tcit.fr/@tcit"},{"rel":"http://schemas.google.com/g/2010#updates-from","type":"application/atom+xml","href":"https://social.tcit.fr/users/tcit.atom"},{"rel":"self","type":"application/activity+json","href":"https://social.tcit.fr/@tcit"},{"rel":"salmon","href":"https://social.tcit.fr/api/salmon/1"},{"rel":"magic-public-key","href":"data:application/magic-public-key,RSA.pXwYMUdFg3XUd-bGsh8CyiMRGpRGAWuCdM5pDWx5uM4pW2pM3xbHbcI21j9h8BmlAiPg6hbZD73KGly2N8Rt5iIS0I-l6i8kA1JCCdlAaDTRd41RKMggZDoQvjVZQtsyE1VzMeU2kbqqTFN6ew7Hvbd6O0NhixoKoZ5f3jwuBDZoT0p1TAcaMdmG8oqHD97isizkDnRn8cOBA6wtI-xb5xP2zxZMsLpTDZLiKU8XcPKZCw4OfQfmDmKkHtrFb77jCAQj_s_FxjVnvxRwmfhNnWy0D-LUV_g63nHh_b5zXIeV92QZLvDYbgbezmzUzv9UeA1s70GGbaDqCIy85gw9-w==.AQAB"},{"rel":"http://ostatus.org/schema/1.0/subscribe","template":"https://social.tcit.fr/authorize_follow?acct={uri}"}]} + # +} diff --git a/src/Wallabag/FederationBundle/Controller/OutboxController.php b/src/Wallabag/FederationBundle/Controller/OutboxController.php new file mode 100644 index 00000000..87ebdba1 --- /dev/null +++ b/src/Wallabag/FederationBundle/Controller/OutboxController.php @@ -0,0 +1,23 @@ +getAcceptableContentTypes(), true)) { + $data = [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'type' => 'Person', + 'id' => CloudId::getCloudIdFromAccount($user, $this->generateUrl('homepage', [], UrlGeneratorInterface::ABSOLUTE_URL))->getDisplayId(), + 'following' => $this->generateUrl('following', ['user' => $user->getUsername()], UrlGeneratorInterface::ABSOLUTE_URL), + 'followers' => $this->generateUrl('followers', ['user' => $user->getUsername()], UrlGeneratorInterface::ABSOLUTE_URL), + //'liked' => $this->generateUrl('recommended', ['user' => $user], UrlGeneratorInterface::ABSOLUTE_URL), + 'inbox' => $this->generateUrl('user-inbox', ['user' => $user], UrlGeneratorInterface::ABSOLUTE_URL), + 'outbox' => $this->generateUrl('user-outbox', ['user' => $user->getUsername()], UrlGeneratorInterface::ABSOLUTE_URL), + 'preferredUsername' => $user->getUser()->getName(), + 'name' => $user->getUsername(), + //'oauthAuthorizationEndpoint' => $this->generateUrl('fos_oauth_server_authorize', [], UrlGeneratorInterface::ABSOLUTE_URL), + 'oauthTokenEndpoint' => $this->generateUrl('fos_oauth_server_token', [], UrlGeneratorInterface::ABSOLUTE_URL), + //'publicInbox' => $this->generateUrl('public_inbox', [], UrlGeneratorInterface::ABSOLUTE_URL), + ]; + return new JsonResponse($data); + } + return $this->render( + 'WallabagFederationBundle:User:profile.html.twig', [ + 'user' => $user, + 'registration_enabled' => $this->getParameter('wallabag_user.registration_enabled'), + ] + ); + } + + /** + * @Route("/profile/@{user}/followings/{page}", name="following", defaults={"page" : 0}) + * @ParamConverter("user", class="WallabagFederationBundle:Account", options={ + * "repository_method" = "findOneByUsername"}) + * + * @param Request $request + * @param Account $user + * @param int $page + * @return JsonResponse|Response + */ + public function getUsersFollowing(Request $request, Account $user, $page = 0) + { + $qb = $this->getDoctrine()->getRepository('WallabagFederationBundle:Account')->getBuilderForFollowingsByAccount($user->getId()); + + $pagerAdapter = new DoctrineORMAdapter($qb->getQuery(), true, false); + + $following = new Pagerfanta($pagerAdapter); + $totalFollowing = $following->getNbResults(); + + $activityStream = in_array('application/ld+json; profile="https://www.w3.org/ns/activitystreams', $request->getAcceptableContentTypes(), true); + + if ($page === 0 && $activityStream) { + /** Home page */ + $dataPrez = [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'summary' => $user->getUsername() . " followings'", + 'type' => 'Collection', + 'id' => $this->generateUrl('following', ['user' => $user->getUsername()], UrlGeneratorInterface::ABSOLUTE_URL), + 'totalItems' => $totalFollowing, + 'first' => [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'type' => 'Link', + 'href' => $this->generateUrl('following', ['user' => $user->getUsername(), 'page' => 1], UrlGeneratorInterface::ABSOLUTE_URL), + 'name' => 'First page of ' . $user->getUsername() . ' followings' + ], + 'last' => [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'type' => 'Link', + 'href' => $this->generateUrl('following', ['user' => $user->getUsername(), 'page' => $following->getNbPages()], UrlGeneratorInterface::ABSOLUTE_URL), + 'name' => 'Last page of ' . $user->getUsername() . ' followings' + ] + ]; + return new JsonResponse($dataPrez); + //} + } + + $following->setMaxPerPage(30); + $following->setCurrentPage($page); + + if (!$activityStream) { + return $this->render('WallabagFederationBundle:User:followers.html.twig', [ + 'users' => $following, + 'user' => $user, + 'registration_enabled' => $this->getParameter('wallabag_user.registration_enabled'), + ]); + } + + $items = []; + + foreach ($following->getCurrentPageResults() as $follow) { + /** @var Account $follow */ + /** Items in the page */ + $items[] = [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'type' => 'Person', + 'name' => $follow->getUsername(), + 'id' => CloudId::getCloudIdFromAccount($follow), + ]; + } + + $data = [ + 'summary' => 'Page ' . $page . ' of ' . $user->getUsername() . ' followers', + 'partOf' => $this->generateUrl('following', ['user' => $user->getUsername()], UrlGeneratorInterface::ABSOLUTE_URL), + 'type' => 'OrderedCollectionPage', + 'startIndex' => ($page - 1) * 30, + 'orderedItems' => $items, + 'first' => [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'type' => 'Link', + 'href' => $this->generateUrl('following', ['user' => $user->getUsername(), 'page' => 1], UrlGeneratorInterface::ABSOLUTE_URL), + 'name' => 'First page of ' . $user->getUsername() . ' followings' + ], + 'last' => [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'type' => 'Link', + 'href' => $this->generateUrl('following', ['user' => $user->getUsername(), 'page' => $following->getNbPages()], UrlGeneratorInterface::ABSOLUTE_URL), + 'name' => 'Last page of ' . $user->getUsername() . ' followings' + ], + ]; + + /** Previous page */ + if ($page > 1) { + $data['prev'] = [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'type' => 'Link', + 'href' => $this->generateUrl('following', ['user' => $user->getUsername(), 'page' => $page - 1], UrlGeneratorInterface::ABSOLUTE_URL), + 'name' => 'Previous page of ' . $user->getUsername() . ' followings' + ]; + } + + /** Next page */ + if ($page < $following->getNbPages()) { + $data['next'] = [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'type' => 'Link', + 'href' => $this->generateUrl('following', ['user' => $user->getUsername(), 'page' => $page + 1], UrlGeneratorInterface::ABSOLUTE_URL), + 'name' => 'Next page of ' . $user->getUsername() . ' followings' + ]; + } + + return new JsonResponse($data); + } + + /** + * @Route("/profile/@{user}/followers/{page}", name="followers", defaults={"page" : 0}) + * @ParamConverter("user", class="WallabagFederationBundle:Account", options={ + * "repository_method" = "findOneByUsername"}) + * + * @param Request $request + * @param Account $user + * @return JsonResponse + */ + public function getUsersFollowers(Request $request, Account $user, $page) + { + $qb = $this->getDoctrine()->getRepository('WallabagFederationBundle:Account')->getBuilderForFollowersByAccount($user->getId()); + + $pagerAdapter = new DoctrineORMAdapter($qb->getQuery(), true, false); + + $followers = new Pagerfanta($pagerAdapter); + $totalFollowers = $followers->getNbResults(); + + $activityStream = in_array('application/ld+json; profile="https://www.w3.org/ns/activitystreams', $request->getAcceptableContentTypes(), true); + + if ($page === 0 && $activityStream) { + /** Home page */ + $dataPrez = [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'summary' => $user->getUsername() . " followers'", + 'type' => 'Collection', + 'id' => $this->generateUrl('followers', ['user' => $user->getUsername()], UrlGeneratorInterface::ABSOLUTE_URL), + 'totalItems' => $totalFollowers, + 'first' => [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'type' => 'Link', + 'href' => $this->generateUrl('followers', ['user' => $user->getUsername(), 'page' => 1], UrlGeneratorInterface::ABSOLUTE_URL), + 'name' => 'First page of ' . $user->getUsername() . ' followers' + ], + 'last' => [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'type' => 'Link', + 'href' => $this->generateUrl('followers', ['user' => $user->getUsername(), 'page' => $followers->getNbPages()], UrlGeneratorInterface::ABSOLUTE_URL), + 'name' => 'Last page of ' . $user->getUsername() . ' followers' + ] + ]; + return new JsonResponse($dataPrez); + } + + $followers->setMaxPerPage(30); + if (!$activityStream && $page === 0) { + $followers->setCurrentPage(1); + } else { + $followers->setCurrentPage($page); + } + + if (!$activityStream) { + return $this->render('WallabagFederationBundle:User:followers.html.twig', [ + 'users' => $followers, + 'user' => $user, + 'registration_enabled' => $this->getParameter('wallabag_user.registration_enabled'), + ]); + } + + $items = []; + + foreach ($followers->getCurrentPageResults() as $follow) { + /** @var Account $follow */ + /** Items in the page */ + $items[] = [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'type' => 'Person', + 'name' => $follow->getUsername(), + 'id' => CloudId::getCloudIdFromAccount($follow)->getDisplayId(), + ]; + } + $data = [ + 'summary' => 'Page ' . $page . ' of ' . $user->getUsername() . ' followers', + 'partOf' => $this->generateUrl('followers', ['user' => $user->getUsername()], UrlGeneratorInterface::ABSOLUTE_URL), + 'type' => 'OrderedCollectionPage', + 'startIndex' => ($page - 1) * 30, + 'orderedItems' => $items, + 'first' => [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'type' => 'Link', + 'href' => $this->generateUrl('followers', ['user' => $user->getUsername(), 'page' => 1], UrlGeneratorInterface::ABSOLUTE_URL), + 'name' => 'First page of ' . $user->getUsername() . ' followers' + ], + 'last' => [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'type' => 'Link', + 'href' => $this->generateUrl('followers', ['user' => $user->getUsername(), 'page' => $followers->getNbPages()], UrlGeneratorInterface::ABSOLUTE_URL), + 'name' => 'Last page of ' . $user->getUsername() . ' followers' + ], + ]; + + /** Previous page */ + if ($page > 1) { + $data['prev'] = [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'type' => 'Link', + 'href' => $this->generateUrl('followers', ['user' => $user->getUsername(), 'page' => $page - 1], UrlGeneratorInterface::ABSOLUTE_URL), + 'name' => 'Previous page of ' . $user->getUsername() . ' followers' + ]; + } + + /** Next page */ + if ($page < $followers->getNbPages()) { + $data['next'] = [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'type' => 'Link', + 'href' => $this->generateUrl('followers', ['user' => $user->getUsername(), 'page' => $page + 1], UrlGeneratorInterface::ABSOLUTE_URL), + 'name' => 'Next page of ' . $user->getUsername() . ' followers' + ]; + } + + return new JsonResponse($data); + } + + /** + * @Route("/profile/@{userToFollow}/follow", name="follow-user") + * @ParamConverter("userToFollow", class="WallabagFederationBundle:Account", options={ + * "repository_method" = "findOneByUsername"}) + * @param Account $userToFollow + */ + public function followAccountAction(Account $userToFollow) + { + // if we're on our own instance + if ($this->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_FULLY')) { + + /** @var Account $userAccount */ + $userAccount = $this->getUser()->getAccount(); + + if ($userToFollow === $userAccount) { + $this->createAccessDeniedException("You can't follow yourself"); + } + + $em = $this->getDoctrine()->getManager(); + + $userAccount->addFollowing($userToFollow); + $userToFollow->addFollower($userAccount); + + $em->persist($userAccount); + $em->persist($userToFollow); + + $em->flush(); + } else { + // ask cloud id and redirect to instance + } + } + + /** + * @Route("/profile/@{user}/recommendations", name="user-recommendations", defaults={"page" : 0}) + * @ParamConverter("user", class="WallabagFederationBundle:Account", options={ + * "repository_method" = "findOneByUsername"}) + * + * @param Request $request + * @param Account $user + * @param int $page + * @return JsonResponse|Response + */ + public function getUsersRecommendationsAction(Request $request, Account $user, $page = 0) + { + $qb = $this->getDoctrine()->getRepository('WallabagCoreBundle:Entry')->getBuilderForRecommendationsByUser($user->getUser()->getId()); + + $pagerAdapter = new DoctrineORMAdapter($qb->getQuery(), true, false); + + $recommendations = new Pagerfanta($pagerAdapter); + $totalRecommendations = $recommendations->getNbResults(); + + $activityStream = in_array('application/ld+json; profile="https://www.w3.org/ns/activitystreams', $request->getAcceptableContentTypes(), true); + + if ($page === 0 && $activityStream) { + $dataPrez = [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'summary' => $user->getUsername() . " recommendations'", + 'type' => 'Collection', + 'id' => $this->generateUrl('user-recommendations', ['user' => $user->getUsername()], UrlGeneratorInterface::ABSOLUTE_URL), + 'totalItems' => $totalRecommendations, + 'first' => [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'type' => 'Link', + 'href' => $this->generateUrl('user-recommendations', ['user' => $user->getUsername(), 'page' => 1], UrlGeneratorInterface::ABSOLUTE_URL), + 'name' => 'First page of ' . $user->getUsername() . ' followers' + ], + 'last' => [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'type' => 'Link', + 'href' => $this->generateUrl('user-recommendations', ['user' => $user->getUsername(), 'page' => $recommendations->getNbPages()], UrlGeneratorInterface::ABSOLUTE_URL), + 'name' => 'Last page of ' . $user->getUsername() . ' followers' + ] + ]; + return new JsonResponse($dataPrez); + } + + $recommendations->setMaxPerPage(30); + if (!$activityStream && $page === 0) { + $recommendations->setCurrentPage(1); + } else { + $recommendations->setCurrentPage($page); + } + + if (!$activityStream) { + return $this->render('WallabagFederationBundle:User:recommendations.html.twig', [ + 'recommendations' => $recommendations, + 'registration_enabled' => $this->getParameter('wallabag_user.registration_enabled'), + ]); + } + + $items = []; + + foreach ($recommendations->getCurrentPageResults() as $recommendation) { + $items[] = [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'type' => 'Person', + 'name' => $recommendation->getTitle(), + 'id' => $recommendation->getUrl(), + ]; + } + $data = [ + 'summary' => 'Page ' . $page . ' of ' . $user->getUsername() . ' recommendations', + 'partOf' => $this->generateUrl('user-recommendations', ['user' => $user->getUsername()], UrlGeneratorInterface::ABSOLUTE_URL), + 'type' => 'OrderedCollectionPage', + 'startIndex' => ($page - 1) * 30, + 'orderedItems' => $items, + 'first' => [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'type' => 'Link', + 'href' => $this->generateUrl('user-recommendations', ['user' => $user->getUsername(), 'page' => 1], UrlGeneratorInterface::ABSOLUTE_URL), + 'name' => 'First page of ' . $user->getUsername() . ' recommendations' + ], + 'last' => [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'type' => 'Link', + 'href' => $this->generateUrl('user-recommendations', ['user' => $user->getUsername(), 'page' => $recommendations->getNbPages()], UrlGeneratorInterface::ABSOLUTE_URL), + 'name' => 'Last page of ' . $user->getUsername() . ' recommendations' + ], + ]; + + if ($page > 1) { + $data['prev'] = [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'type' => 'Link', + 'href' => $this->generateUrl('user-recommendations', ['user' => $user->getUsername(), 'page' => $page - 1], UrlGeneratorInterface::ABSOLUTE_URL), + 'name' => 'Previous page of ' . $user->getUsername() . ' recommendations' + ]; + } + + if ($page < $recommendations->getNbPages()) { + $data['next'] = [ + '@context' => 'https://www.w3.org/ns/activitystreams', + 'type' => 'Link', + 'href' => $this->generateUrl('user-recommendations', ['user' => $user->getUsername(), 'page' => $page + 1], UrlGeneratorInterface::ABSOLUTE_URL), + 'name' => 'Next page of ' . $user->getUsername() . ' recommendations' + ]; + } + + return new JsonResponse($data); + } + +} diff --git a/src/Wallabag/FederationBundle/Controller/RecommandController.php b/src/Wallabag/FederationBundle/Controller/RecommandController.php new file mode 100644 index 00000000..ea5a6660 --- /dev/null +++ b/src/Wallabag/FederationBundle/Controller/RecommandController.php @@ -0,0 +1,34 @@ +getUser() !== $this->getUser()) { + $this->createAccessDeniedException("You can't recommend entries which are not your own"); + } + $em = $this->getDoctrine()->getManager(); + + $entry->setRecommended(true); + + $em->persist($entry); + $em->flush(); + + $this->get('event_dispatcher')->dispatch(RecommendedEntryEvent::NAME, new RecommendedEntryEvent($entry)); + + $this->redirectToRoute('view', ['id' => $entry->getId()]); + } +} -- cgit v1.2.3