diff options
Diffstat (limited to 'src/Wallabag/FederationBundle/Controller/ProfileController.php')
-rw-r--r-- | src/Wallabag/FederationBundle/Controller/ProfileController.php | 425 |
1 files changed, 425 insertions, 0 deletions
diff --git a/src/Wallabag/FederationBundle/Controller/ProfileController.php b/src/Wallabag/FederationBundle/Controller/ProfileController.php new file mode 100644 index 00000000..7e472e11 --- /dev/null +++ b/src/Wallabag/FederationBundle/Controller/ProfileController.php | |||
@@ -0,0 +1,425 @@ | |||
1 | <?php | ||
2 | |||
3 | namespace Wallabag\FederationBundle\Controller; | ||
4 | |||
5 | use Pagerfanta\Adapter\DoctrineORMAdapter; | ||
6 | use Pagerfanta\Pagerfanta; | ||
7 | use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; | ||
8 | use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route; | ||
9 | use Symfony\Bundle\FrameworkBundle\Controller\Controller; | ||
10 | use Symfony\Component\HttpFoundation\JsonResponse; | ||
11 | use Symfony\Component\HttpFoundation\Request; | ||
12 | use Symfony\Component\HttpFoundation\Response; | ||
13 | use Symfony\Component\Routing\Generator\UrlGeneratorInterface; | ||
14 | use Wallabag\CoreBundle\Entity\Entry; | ||
15 | use Wallabag\FederationBundle\Entity\Account; | ||
16 | use Wallabag\FederationBundle\Federation\CloudId; | ||
17 | |||
18 | class ProfileController extends Controller | ||
19 | { | ||
20 | /** | ||
21 | * @Route("/profile/@{user}", name="user-profile") | ||
22 | * @ParamConverter("user", class="WallabagFederationBundle:Account", options={ | ||
23 | * "repository_method" = "findOneByUsername"}) | ||
24 | * | ||
25 | * @param Request $request | ||
26 | * @param Account $user | ||
27 | * @return JsonResponse|Response | ||
28 | */ | ||
29 | public function getUserProfile(Request $request, Account $user) | ||
30 | { | ||
31 | if (in_array('application/ld+json; profile="https://www.w3.org/ns/activitystreams', $request->getAcceptableContentTypes(), true)) { | ||
32 | $data = [ | ||
33 | '@context' => 'https://www.w3.org/ns/activitystreams', | ||
34 | 'type' => 'Person', | ||
35 | 'id' => CloudId::getCloudIdFromAccount($user, $this->generateUrl('homepage', [], UrlGeneratorInterface::ABSOLUTE_URL))->getDisplayId(), | ||
36 | 'following' => $this->generateUrl('following', ['user' => $user->getUsername()], UrlGeneratorInterface::ABSOLUTE_URL), | ||
37 | 'followers' => $this->generateUrl('followers', ['user' => $user->getUsername()], UrlGeneratorInterface::ABSOLUTE_URL), | ||
38 | //'liked' => $this->generateUrl('recommended', ['user' => $user], UrlGeneratorInterface::ABSOLUTE_URL), | ||
39 | 'inbox' => $this->generateUrl('user-inbox', ['user' => $user], UrlGeneratorInterface::ABSOLUTE_URL), | ||
40 | 'outbox' => $this->generateUrl('user-outbox', ['user' => $user->getUsername()], UrlGeneratorInterface::ABSOLUTE_URL), | ||
41 | 'preferredUsername' => $user->getUser()->getName(), | ||
42 | 'name' => $user->getUsername(), | ||
43 | //'oauthAuthorizationEndpoint' => $this->generateUrl('fos_oauth_server_authorize', [], UrlGeneratorInterface::ABSOLUTE_URL), | ||
44 | 'oauthTokenEndpoint' => $this->generateUrl('fos_oauth_server_token', [], UrlGeneratorInterface::ABSOLUTE_URL), | ||
45 | //'publicInbox' => $this->generateUrl('public_inbox', [], UrlGeneratorInterface::ABSOLUTE_URL), | ||
46 | ]; | ||
47 | return new JsonResponse($data); | ||
48 | } | ||
49 | return $this->render( | ||
50 | 'WallabagFederationBundle:User:profile.html.twig', [ | ||
51 | 'user' => $user, | ||
52 | 'registration_enabled' => $this->getParameter('wallabag_user.registration_enabled'), | ||
53 | ] | ||
54 | ); | ||
55 | } | ||
56 | |||
57 | /** | ||
58 | * @Route("/profile/@{user}/followings/{page}", name="following", defaults={"page" : 0}) | ||
59 | * @ParamConverter("user", class="WallabagFederationBundle:Account", options={ | ||
60 | * "repository_method" = "findOneByUsername"}) | ||
61 | * | ||
62 | * @param Request $request | ||
63 | * @param Account $user | ||
64 | * @param int $page | ||
65 | * @return JsonResponse|Response | ||
66 | */ | ||
67 | public function getUsersFollowing(Request $request, Account $user, $page = 0) | ||
68 | { | ||
69 | $qb = $this->getDoctrine()->getRepository('WallabagFederationBundle:Account')->getBuilderForFollowingsByAccount($user->getId()); | ||
70 | |||
71 | $pagerAdapter = new DoctrineORMAdapter($qb->getQuery(), true, false); | ||
72 | |||
73 | $following = new Pagerfanta($pagerAdapter); | ||
74 | $totalFollowing = $following->getNbResults(); | ||
75 | |||
76 | $activityStream = in_array('application/ld+json; profile="https://www.w3.org/ns/activitystreams', $request->getAcceptableContentTypes(), true); | ||
77 | |||
78 | if ($page === 0 && $activityStream) { | ||
79 | /** Home page */ | ||
80 | $dataPrez = [ | ||
81 | '@context' => 'https://www.w3.org/ns/activitystreams', | ||
82 | 'summary' => $user->getUsername() . " followings'", | ||
83 | 'type' => 'Collection', | ||
84 | 'id' => $this->generateUrl('following', ['user' => $user->getUsername()], UrlGeneratorInterface::ABSOLUTE_URL), | ||
85 | 'totalItems' => $totalFollowing, | ||
86 | 'first' => [ | ||
87 | '@context' => 'https://www.w3.org/ns/activitystreams', | ||
88 | 'type' => 'Link', | ||
89 | 'href' => $this->generateUrl('following', ['user' => $user->getUsername(), 'page' => 1], UrlGeneratorInterface::ABSOLUTE_URL), | ||
90 | 'name' => 'First page of ' . $user->getUsername() . ' followings' | ||
91 | ], | ||
92 | 'last' => [ | ||
93 | '@context' => 'https://www.w3.org/ns/activitystreams', | ||
94 | 'type' => 'Link', | ||
95 | 'href' => $this->generateUrl('following', ['user' => $user->getUsername(), 'page' => $following->getNbPages()], UrlGeneratorInterface::ABSOLUTE_URL), | ||
96 | 'name' => 'Last page of ' . $user->getUsername() . ' followings' | ||
97 | ] | ||
98 | ]; | ||
99 | return new JsonResponse($dataPrez); | ||
100 | //} | ||
101 | } | ||
102 | |||
103 | $following->setMaxPerPage(30); | ||
104 | $following->setCurrentPage($page); | ||
105 | |||
106 | if (!$activityStream) { | ||
107 | return $this->render('WallabagFederationBundle:User:followers.html.twig', [ | ||
108 | 'users' => $following, | ||
109 | 'user' => $user, | ||
110 | 'registration_enabled' => $this->getParameter('wallabag_user.registration_enabled'), | ||
111 | ]); | ||
112 | } | ||
113 | |||
114 | $items = []; | ||
115 | |||
116 | foreach ($following->getCurrentPageResults() as $follow) { | ||
117 | /** @var Account $follow */ | ||
118 | /** Items in the page */ | ||
119 | $items[] = [ | ||
120 | '@context' => 'https://www.w3.org/ns/activitystreams', | ||
121 | 'type' => 'Person', | ||
122 | 'name' => $follow->getUsername(), | ||
123 | 'id' => CloudId::getCloudIdFromAccount($follow), | ||
124 | ]; | ||
125 | } | ||
126 | |||
127 | $data = [ | ||
128 | 'summary' => 'Page ' . $page . ' of ' . $user->getUsername() . ' followers', | ||
129 | 'partOf' => $this->generateUrl('following', ['user' => $user->getUsername()], UrlGeneratorInterface::ABSOLUTE_URL), | ||
130 | 'type' => 'OrderedCollectionPage', | ||
131 | 'startIndex' => ($page - 1) * 30, | ||
132 | 'orderedItems' => $items, | ||
133 | 'first' => [ | ||
134 | '@context' => 'https://www.w3.org/ns/activitystreams', | ||
135 | 'type' => 'Link', | ||
136 | 'href' => $this->generateUrl('following', ['user' => $user->getUsername(), 'page' => 1], UrlGeneratorInterface::ABSOLUTE_URL), | ||
137 | 'name' => 'First page of ' . $user->getUsername() . ' followings' | ||
138 | ], | ||
139 | 'last' => [ | ||
140 | '@context' => 'https://www.w3.org/ns/activitystreams', | ||
141 | 'type' => 'Link', | ||
142 | 'href' => $this->generateUrl('following', ['user' => $user->getUsername(), 'page' => $following->getNbPages()], UrlGeneratorInterface::ABSOLUTE_URL), | ||
143 | 'name' => 'Last page of ' . $user->getUsername() . ' followings' | ||
144 | ], | ||
145 | ]; | ||
146 | |||
147 | /** Previous page */ | ||
148 | if ($page > 1) { | ||
149 | $data['prev'] = [ | ||
150 | '@context' => 'https://www.w3.org/ns/activitystreams', | ||
151 | 'type' => 'Link', | ||
152 | 'href' => $this->generateUrl('following', ['user' => $user->getUsername(), 'page' => $page - 1], UrlGeneratorInterface::ABSOLUTE_URL), | ||
153 | 'name' => 'Previous page of ' . $user->getUsername() . ' followings' | ||
154 | ]; | ||
155 | } | ||
156 | |||
157 | /** Next page */ | ||
158 | if ($page < $following->getNbPages()) { | ||
159 | $data['next'] = [ | ||
160 | '@context' => 'https://www.w3.org/ns/activitystreams', | ||
161 | 'type' => 'Link', | ||
162 | 'href' => $this->generateUrl('following', ['user' => $user->getUsername(), 'page' => $page + 1], UrlGeneratorInterface::ABSOLUTE_URL), | ||
163 | 'name' => 'Next page of ' . $user->getUsername() . ' followings' | ||
164 | ]; | ||
165 | } | ||
166 | |||
167 | return new JsonResponse($data); | ||
168 | } | ||
169 | |||
170 | /** | ||
171 | * @Route("/profile/@{user}/followers/{page}", name="followers", defaults={"page" : 0}) | ||
172 | * @ParamConverter("user", class="WallabagFederationBundle:Account", options={ | ||
173 | * "repository_method" = "findOneByUsername"}) | ||
174 | * | ||
175 | * @param Request $request | ||
176 | * @param Account $user | ||
177 | * @return JsonResponse | ||
178 | */ | ||
179 | public function getUsersFollowers(Request $request, Account $user, $page) | ||
180 | { | ||
181 | $qb = $this->getDoctrine()->getRepository('WallabagFederationBundle:Account')->getBuilderForFollowersByAccount($user->getId()); | ||
182 | |||
183 | $pagerAdapter = new DoctrineORMAdapter($qb->getQuery(), true, false); | ||
184 | |||
185 | $followers = new Pagerfanta($pagerAdapter); | ||
186 | $totalFollowers = $followers->getNbResults(); | ||
187 | |||
188 | $activityStream = in_array('application/ld+json; profile="https://www.w3.org/ns/activitystreams', $request->getAcceptableContentTypes(), true); | ||
189 | |||
190 | if ($page === 0 && $activityStream) { | ||
191 | /** Home page */ | ||
192 | $dataPrez = [ | ||
193 | '@context' => 'https://www.w3.org/ns/activitystreams', | ||
194 | 'summary' => $user->getUsername() . " followers'", | ||
195 | 'type' => 'Collection', | ||
196 | 'id' => $this->generateUrl('followers', ['user' => $user->getUsername()], UrlGeneratorInterface::ABSOLUTE_URL), | ||
197 | 'totalItems' => $totalFollowers, | ||
198 | 'first' => [ | ||
199 | '@context' => 'https://www.w3.org/ns/activitystreams', | ||
200 | 'type' => 'Link', | ||
201 | 'href' => $this->generateUrl('followers', ['user' => $user->getUsername(), 'page' => 1], UrlGeneratorInterface::ABSOLUTE_URL), | ||
202 | 'name' => 'First page of ' . $user->getUsername() . ' followers' | ||
203 | ], | ||
204 | 'last' => [ | ||
205 | '@context' => 'https://www.w3.org/ns/activitystreams', | ||
206 | 'type' => 'Link', | ||
207 | 'href' => $this->generateUrl('followers', ['user' => $user->getUsername(), 'page' => $followers->getNbPages()], UrlGeneratorInterface::ABSOLUTE_URL), | ||
208 | 'name' => 'Last page of ' . $user->getUsername() . ' followers' | ||
209 | ] | ||
210 | ]; | ||
211 | return new JsonResponse($dataPrez); | ||
212 | } | ||
213 | |||
214 | $followers->setMaxPerPage(30); | ||
215 | if (!$activityStream && $page === 0) { | ||
216 | $followers->setCurrentPage(1); | ||
217 | } else { | ||
218 | $followers->setCurrentPage($page); | ||
219 | } | ||
220 | |||
221 | if (!$activityStream) { | ||
222 | return $this->render('WallabagFederationBundle:User:followers.html.twig', [ | ||
223 | 'users' => $followers, | ||
224 | 'user' => $user, | ||
225 | 'registration_enabled' => $this->getParameter('wallabag_user.registration_enabled'), | ||
226 | ]); | ||
227 | } | ||
228 | |||
229 | $items = []; | ||
230 | |||
231 | foreach ($followers->getCurrentPageResults() as $follow) { | ||
232 | /** @var Account $follow */ | ||
233 | /** Items in the page */ | ||
234 | $items[] = [ | ||
235 | '@context' => 'https://www.w3.org/ns/activitystreams', | ||
236 | 'type' => 'Person', | ||
237 | 'name' => $follow->getUsername(), | ||
238 | 'id' => CloudId::getCloudIdFromAccount($follow)->getDisplayId(), | ||
239 | ]; | ||
240 | } | ||
241 | $data = [ | ||
242 | 'summary' => 'Page ' . $page . ' of ' . $user->getUsername() . ' followers', | ||
243 | 'partOf' => $this->generateUrl('followers', ['user' => $user->getUsername()], UrlGeneratorInterface::ABSOLUTE_URL), | ||
244 | 'type' => 'OrderedCollectionPage', | ||
245 | 'startIndex' => ($page - 1) * 30, | ||
246 | 'orderedItems' => $items, | ||
247 | 'first' => [ | ||
248 | '@context' => 'https://www.w3.org/ns/activitystreams', | ||
249 | 'type' => 'Link', | ||
250 | 'href' => $this->generateUrl('followers', ['user' => $user->getUsername(), 'page' => 1], UrlGeneratorInterface::ABSOLUTE_URL), | ||
251 | 'name' => 'First page of ' . $user->getUsername() . ' followers' | ||
252 | ], | ||
253 | 'last' => [ | ||
254 | '@context' => 'https://www.w3.org/ns/activitystreams', | ||
255 | 'type' => 'Link', | ||
256 | 'href' => $this->generateUrl('followers', ['user' => $user->getUsername(), 'page' => $followers->getNbPages()], UrlGeneratorInterface::ABSOLUTE_URL), | ||
257 | 'name' => 'Last page of ' . $user->getUsername() . ' followers' | ||
258 | ], | ||
259 | ]; | ||
260 | |||
261 | /** Previous page */ | ||
262 | if ($page > 1) { | ||
263 | $data['prev'] = [ | ||
264 | '@context' => 'https://www.w3.org/ns/activitystreams', | ||
265 | 'type' => 'Link', | ||
266 | 'href' => $this->generateUrl('followers', ['user' => $user->getUsername(), 'page' => $page - 1], UrlGeneratorInterface::ABSOLUTE_URL), | ||
267 | 'name' => 'Previous page of ' . $user->getUsername() . ' followers' | ||
268 | ]; | ||
269 | } | ||
270 | |||
271 | /** Next page */ | ||
272 | if ($page < $followers->getNbPages()) { | ||
273 | $data['next'] = [ | ||
274 | '@context' => 'https://www.w3.org/ns/activitystreams', | ||
275 | 'type' => 'Link', | ||
276 | 'href' => $this->generateUrl('followers', ['user' => $user->getUsername(), 'page' => $page + 1], UrlGeneratorInterface::ABSOLUTE_URL), | ||
277 | 'name' => 'Next page of ' . $user->getUsername() . ' followers' | ||
278 | ]; | ||
279 | } | ||
280 | |||
281 | return new JsonResponse($data); | ||
282 | } | ||
283 | |||
284 | /** | ||
285 | * @Route("/profile/@{userToFollow}/follow", name="follow-user") | ||
286 | * @ParamConverter("userToFollow", class="WallabagFederationBundle:Account", options={ | ||
287 | * "repository_method" = "findOneByUsername"}) | ||
288 | * @param Account $userToFollow | ||
289 | */ | ||
290 | public function followAccountAction(Account $userToFollow) | ||
291 | { | ||
292 | // if we're on our own instance | ||
293 | if ($this->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_FULLY')) { | ||
294 | |||
295 | /** @var Account $userAccount */ | ||
296 | $userAccount = $this->getUser()->getAccount(); | ||
297 | |||
298 | if ($userToFollow === $userAccount) { | ||
299 | $this->createAccessDeniedException("You can't follow yourself"); | ||
300 | } | ||
301 | |||
302 | $em = $this->getDoctrine()->getManager(); | ||
303 | |||
304 | $userAccount->addFollowing($userToFollow); | ||
305 | $userToFollow->addFollower($userAccount); | ||
306 | |||
307 | $em->persist($userAccount); | ||
308 | $em->persist($userToFollow); | ||
309 | |||
310 | $em->flush(); | ||
311 | } else { | ||
312 | // ask cloud id and redirect to instance | ||
313 | } | ||
314 | } | ||
315 | |||
316 | /** | ||
317 | * @Route("/profile/@{user}/recommendations", name="user-recommendations", defaults={"page" : 0}) | ||
318 | * @ParamConverter("user", class="WallabagFederationBundle:Account", options={ | ||
319 | * "repository_method" = "findOneByUsername"}) | ||
320 | * | ||
321 | * @param Request $request | ||
322 | * @param Account $user | ||
323 | * @param int $page | ||
324 | * @return JsonResponse|Response | ||
325 | */ | ||
326 | public function getUsersRecommendationsAction(Request $request, Account $user, $page = 0) | ||
327 | { | ||
328 | $qb = $this->getDoctrine()->getRepository('WallabagCoreBundle:Entry')->getBuilderForRecommendationsByUser($user->getUser()->getId()); | ||
329 | |||
330 | $pagerAdapter = new DoctrineORMAdapter($qb->getQuery(), true, false); | ||
331 | |||
332 | $recommendations = new Pagerfanta($pagerAdapter); | ||
333 | $totalRecommendations = $recommendations->getNbResults(); | ||
334 | |||
335 | $activityStream = in_array('application/ld+json; profile="https://www.w3.org/ns/activitystreams', $request->getAcceptableContentTypes(), true); | ||
336 | |||
337 | if ($page === 0 && $activityStream) { | ||
338 | $dataPrez = [ | ||
339 | '@context' => 'https://www.w3.org/ns/activitystreams', | ||
340 | 'summary' => $user->getUsername() . " recommendations'", | ||
341 | 'type' => 'Collection', | ||
342 | 'id' => $this->generateUrl('user-recommendations', ['user' => $user->getUsername()], UrlGeneratorInterface::ABSOLUTE_URL), | ||
343 | 'totalItems' => $totalRecommendations, | ||
344 | 'first' => [ | ||
345 | '@context' => 'https://www.w3.org/ns/activitystreams', | ||
346 | 'type' => 'Link', | ||
347 | 'href' => $this->generateUrl('user-recommendations', ['user' => $user->getUsername(), 'page' => 1], UrlGeneratorInterface::ABSOLUTE_URL), | ||
348 | 'name' => 'First page of ' . $user->getUsername() . ' followers' | ||
349 | ], | ||
350 | 'last' => [ | ||
351 | '@context' => 'https://www.w3.org/ns/activitystreams', | ||
352 | 'type' => 'Link', | ||
353 | 'href' => $this->generateUrl('user-recommendations', ['user' => $user->getUsername(), 'page' => $recommendations->getNbPages()], UrlGeneratorInterface::ABSOLUTE_URL), | ||
354 | 'name' => 'Last page of ' . $user->getUsername() . ' followers' | ||
355 | ] | ||
356 | ]; | ||
357 | return new JsonResponse($dataPrez); | ||
358 | } | ||
359 | |||
360 | $recommendations->setMaxPerPage(30); | ||
361 | if (!$activityStream && $page === 0) { | ||
362 | $recommendations->setCurrentPage(1); | ||
363 | } else { | ||
364 | $recommendations->setCurrentPage($page); | ||
365 | } | ||
366 | |||
367 | if (!$activityStream) { | ||
368 | return $this->render('WallabagFederationBundle:User:recommendations.html.twig', [ | ||
369 | 'recommendations' => $recommendations, | ||
370 | 'registration_enabled' => $this->getParameter('wallabag_user.registration_enabled'), | ||
371 | ]); | ||
372 | } | ||
373 | |||
374 | $items = []; | ||
375 | |||
376 | foreach ($recommendations->getCurrentPageResults() as $recommendation) { | ||
377 | $items[] = [ | ||
378 | '@context' => 'https://www.w3.org/ns/activitystreams', | ||
379 | 'type' => 'Person', | ||
380 | 'name' => $recommendation->getTitle(), | ||
381 | 'id' => $recommendation->getUrl(), | ||
382 | ]; | ||
383 | } | ||
384 | $data = [ | ||
385 | 'summary' => 'Page ' . $page . ' of ' . $user->getUsername() . ' recommendations', | ||
386 | 'partOf' => $this->generateUrl('user-recommendations', ['user' => $user->getUsername()], UrlGeneratorInterface::ABSOLUTE_URL), | ||
387 | 'type' => 'OrderedCollectionPage', | ||
388 | 'startIndex' => ($page - 1) * 30, | ||
389 | 'orderedItems' => $items, | ||
390 | 'first' => [ | ||
391 | '@context' => 'https://www.w3.org/ns/activitystreams', | ||
392 | 'type' => 'Link', | ||
393 | 'href' => $this->generateUrl('user-recommendations', ['user' => $user->getUsername(), 'page' => 1], UrlGeneratorInterface::ABSOLUTE_URL), | ||
394 | 'name' => 'First page of ' . $user->getUsername() . ' recommendations' | ||
395 | ], | ||
396 | 'last' => [ | ||
397 | '@context' => 'https://www.w3.org/ns/activitystreams', | ||
398 | 'type' => 'Link', | ||
399 | 'href' => $this->generateUrl('user-recommendations', ['user' => $user->getUsername(), 'page' => $recommendations->getNbPages()], UrlGeneratorInterface::ABSOLUTE_URL), | ||
400 | 'name' => 'Last page of ' . $user->getUsername() . ' recommendations' | ||
401 | ], | ||
402 | ]; | ||
403 | |||
404 | if ($page > 1) { | ||
405 | $data['prev'] = [ | ||
406 | '@context' => 'https://www.w3.org/ns/activitystreams', | ||
407 | 'type' => 'Link', | ||
408 | 'href' => $this->generateUrl('user-recommendations', ['user' => $user->getUsername(), 'page' => $page - 1], UrlGeneratorInterface::ABSOLUTE_URL), | ||
409 | 'name' => 'Previous page of ' . $user->getUsername() . ' recommendations' | ||
410 | ]; | ||
411 | } | ||
412 | |||
413 | if ($page < $recommendations->getNbPages()) { | ||
414 | $data['next'] = [ | ||
415 | '@context' => 'https://www.w3.org/ns/activitystreams', | ||
416 | 'type' => 'Link', | ||
417 | 'href' => $this->generateUrl('user-recommendations', ['user' => $user->getUsername(), 'page' => $page + 1], UrlGeneratorInterface::ABSOLUTE_URL), | ||
418 | 'name' => 'Next page of ' . $user->getUsername() . ' recommendations' | ||
419 | ]; | ||
420 | } | ||
421 | |||
422 | return new JsonResponse($data); | ||
423 | } | ||
424 | |||
425 | } | ||