|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
/* |
|
4
|
|
|
* This file is part of the Superdesk Web Publisher Core Bundle. |
|
5
|
|
|
* |
|
6
|
|
|
* Copyright 2016 Sourcefabric z.ú. and contributors. |
|
7
|
|
|
* |
|
8
|
|
|
* For the full copyright and license information, please see the |
|
9
|
|
|
* AUTHORS and LICENSE files distributed with this source code. |
|
10
|
|
|
* |
|
11
|
|
|
* @copyright 2016 Sourcefabric z.ú |
|
12
|
|
|
* @license http://www.superdesk.org/license |
|
13
|
|
|
*/ |
|
14
|
|
|
|
|
15
|
|
|
namespace SWP\Bundle\CoreBundle\Controller; |
|
16
|
|
|
|
|
17
|
|
|
use DateTime; |
|
18
|
|
|
use DateTimeZone; |
|
19
|
|
|
use Doctrine\ORM\EntityManagerInterface; |
|
20
|
|
|
use Nelmio\ApiDocBundle\Annotation\Model; |
|
21
|
|
|
use Nelmio\ApiDocBundle\Annotation\Operation; |
|
22
|
|
|
use Swagger\Annotations as SWG; |
|
23
|
|
|
use SWP\Bundle\ContentBundle\ArticleEvents; |
|
24
|
|
|
use SWP\Bundle\ContentBundle\Event\ArticleEvent; |
|
25
|
|
|
use SWP\Bundle\ContentListBundle\Form\Type\ContentListItemsType; |
|
26
|
|
|
use SWP\Bundle\ContentListBundle\Services\ContentListServiceInterface; |
|
27
|
|
|
use SWP\Bundle\CoreBundle\Form\Type\ContentListItemType; |
|
28
|
|
|
use SWP\Bundle\CoreBundle\Model\ContentListInterface; |
|
29
|
|
|
use SWP\Bundle\CoreBundle\Model\ContentListItemInterface; |
|
30
|
|
|
use SWP\Bundle\CoreBundle\Repository\ArticleRepositoryInterface; |
|
31
|
|
|
use SWP\Bundle\CoreBundle\Repository\ContentListItemRepositoryInterface; |
|
32
|
|
|
use SWP\Component\Common\Criteria\Criteria; |
|
33
|
|
|
use SWP\Component\Common\Pagination\PaginationData; |
|
34
|
|
|
use SWP\Component\Common\Response\ResourcesListResponse; |
|
35
|
|
|
use SWP\Component\Common\Response\ResourcesListResponseInterface; |
|
36
|
|
|
use SWP\Component\Common\Response\ResponseContext; |
|
37
|
|
|
use SWP\Component\Common\Response\SingleResourceResponse; |
|
38
|
|
|
use SWP\Component\Common\Response\SingleResourceResponseInterface; |
|
39
|
|
|
use SWP\Component\ContentList\Repository\ContentListRepositoryInterface; |
|
40
|
|
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; |
|
41
|
|
|
use Symfony\Component\Form\FormFactoryInterface; |
|
42
|
|
|
use Symfony\Component\HttpFoundation\Request; |
|
43
|
|
|
use Symfony\Component\HttpKernel\Exception\ConflictHttpException; |
|
44
|
|
|
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; |
|
45
|
|
|
use Symfony\Component\Routing\Annotation\Route; |
|
46
|
|
|
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; |
|
47
|
|
|
|
|
48
|
|
|
class ContentListItemController extends AbstractController |
|
49
|
|
|
{ |
|
50
|
|
|
private $contentListItemRepository; |
|
51
|
|
|
|
|
52
|
|
|
private $entityManager; |
|
53
|
|
|
|
|
54
|
|
|
public function __construct(ContentListItemRepositoryInterface $contentListItemRepository, EntityManagerInterface $entityManager) |
|
55
|
|
|
{ |
|
56
|
|
|
$this->contentListItemRepository = $contentListItemRepository; |
|
57
|
|
|
$this->entityManager = $entityManager; |
|
58
|
|
|
} |
|
59
|
|
|
|
|
60
|
|
|
/** |
|
61
|
|
|
* List all items of content list. |
|
62
|
|
|
* |
|
63
|
|
|
* @Operation( |
|
64
|
|
|
* tags={"content list"}, |
|
65
|
|
|
* summary="Lists content list items", |
|
66
|
|
|
* @SWG\Parameter( |
|
67
|
|
|
* name="sticky", |
|
68
|
|
|
* in="query", |
|
69
|
|
|
* description="Keep item on top of the list", |
|
70
|
|
|
* required=false, |
|
71
|
|
|
* type="boolean" |
|
72
|
|
|
* ), |
|
73
|
|
|
* @SWG\Parameter( |
|
74
|
|
|
* name="sorting", |
|
75
|
|
|
* in="query", |
|
76
|
|
|
* description="example: [updatedAt]=asc|desc", |
|
77
|
|
|
* required=false, |
|
78
|
|
|
* type="string" |
|
79
|
|
|
* ), |
|
80
|
|
|
* @SWG\Response( |
|
81
|
|
|
* response="200", |
|
82
|
|
|
* description="Returned on success.", |
|
83
|
|
|
* @SWG\Schema( |
|
84
|
|
|
* type="array", |
|
85
|
|
|
* @SWG\Items(ref=@Model(type=\SWP\Bundle\CoreBundle\Model\ContentListItem::class, groups={"api"})) |
|
86
|
|
|
* ) |
|
87
|
|
|
* ), |
|
88
|
|
|
* @SWG\Response( |
|
89
|
|
|
* response="404", |
|
90
|
|
|
* description="Content list item not found." |
|
91
|
|
|
* ), |
|
92
|
|
|
* @SWG\Response( |
|
93
|
|
|
* response="500", |
|
94
|
|
|
* description="Unexpected error." |
|
95
|
|
|
* ) |
|
96
|
|
|
* ) |
|
97
|
|
|
* |
|
98
|
|
|
* @Route("/api/{version}/content/lists/{id}/items/", options={"expose"=true}, defaults={"version"="v2"}, methods={"GET"}, name="swp_api_core_list_items", requirements={"id"="\d+"}) |
|
99
|
|
|
*/ |
|
100
|
|
|
public function listAction(Request $request, int $id): ResourcesListResponseInterface |
|
101
|
|
|
{ |
|
102
|
|
|
$items = $this->contentListItemRepository->getPaginatedByCriteria( |
|
103
|
|
|
new Criteria([ |
|
104
|
|
|
'contentList' => $id, |
|
105
|
|
|
'sticky' => $request->query->get('sticky', ''), |
|
106
|
|
|
]), |
|
107
|
|
|
$request->query->get('sorting', ['sticky' => 'desc']), |
|
108
|
|
|
new PaginationData($request) |
|
109
|
|
|
); |
|
110
|
|
|
|
|
111
|
|
|
$responseContext = new ResponseContext(); |
|
112
|
|
|
$responseContext->setSerializationGroups( |
|
113
|
|
|
[ |
|
114
|
|
|
'Default', |
|
115
|
|
|
'api_packages_list', |
|
116
|
|
|
'api_content_list_item_details', |
|
117
|
|
|
'api_articles_list', |
|
118
|
|
|
'api_articles_featuremedia', |
|
119
|
|
|
'api_article_media_list', |
|
120
|
|
|
'api_article_media_renditions', |
|
121
|
|
|
'api_articles_statistics_list', |
|
122
|
|
|
'api_image_details', |
|
123
|
|
|
'api_routes_list', |
|
124
|
|
|
'api_tenant_list', |
|
125
|
|
|
] |
|
126
|
|
|
); |
|
127
|
|
|
|
|
128
|
|
|
return new ResourcesListResponse($items, $responseContext); |
|
129
|
|
|
} |
|
130
|
|
|
|
|
131
|
|
|
/** |
|
132
|
|
|
* @Operation( |
|
133
|
|
|
* tags={"content list"}, |
|
134
|
|
|
* summary="Get single content list item", |
|
135
|
|
|
* @SWG\Response( |
|
136
|
|
|
* response="200", |
|
137
|
|
|
* description="Returned on success.", |
|
138
|
|
|
* @Model(type=\SWP\Bundle\CoreBundle\Model\ContentListItem::class, groups={"api"}) |
|
139
|
|
|
* ) |
|
140
|
|
|
* ) |
|
141
|
|
|
* |
|
142
|
|
|
* @Route("/api/{version}/content/lists/{listId}/items/{id}", options={"expose"=true}, defaults={"version"="v2"}, methods={"GET"}, name="swp_api_core_show_lists_item", requirements={"id"="\d+"}) |
|
143
|
|
|
*/ |
|
144
|
|
|
public function getAction($listId, $id) |
|
145
|
|
|
{ |
|
146
|
|
|
return new SingleResourceResponse($this->findOr404($listId, $id)); |
|
147
|
|
|
} |
|
148
|
|
|
|
|
149
|
|
|
/** |
|
150
|
|
|
* @Operation( |
|
151
|
|
|
* tags={"content list"}, |
|
152
|
|
|
* summary="Update single content list item", |
|
153
|
|
|
* @SWG\Parameter( |
|
154
|
|
|
* name="body", |
|
155
|
|
|
* in="body", |
|
156
|
|
|
* @SWG\Schema( |
|
157
|
|
|
* ref=@Model(type=ContentListItemType::class) |
|
158
|
|
|
* ) |
|
159
|
|
|
* ), |
|
160
|
|
|
* @SWG\Response( |
|
161
|
|
|
* response="200", |
|
162
|
|
|
* description="Returned on success.", |
|
163
|
|
|
* @Model(type=\SWP\Bundle\CoreBundle\Model\ContentListItem::class, groups={"api"}) |
|
164
|
|
|
* ), |
|
165
|
|
|
* @SWG\Response( |
|
166
|
|
|
* response="400", |
|
167
|
|
|
* description="Returned when not valid data." |
|
168
|
|
|
* ), |
|
169
|
|
|
* @SWG\Response( |
|
170
|
|
|
* response="404", |
|
171
|
|
|
* description="Returned when not found." |
|
172
|
|
|
* ) |
|
173
|
|
|
* ) |
|
174
|
|
|
* |
|
175
|
|
|
* @Route("/api/{version}/content/lists/{listId}/items/{id}", options={"expose"=true}, defaults={"version"="v2"}, methods={"PATCH"}, name="swp_api_core_update_lists_item", requirements={"id"="\d+", "listId"="\d+"}) |
|
176
|
|
|
*/ |
|
177
|
|
View Code Duplication |
public function updateAction(Request $request, FormFactoryInterface $formFactory, $listId, $id): SingleResourceResponseInterface |
|
|
|
|
|
|
178
|
|
|
{ |
|
179
|
|
|
$contentListItem = $this->findOr404($listId, $id); |
|
180
|
|
|
$form = $formFactory->createNamed('', |
|
181
|
|
|
ContentListItemType::class, |
|
182
|
|
|
$contentListItem, |
|
183
|
|
|
['method' => $request->getMethod()] |
|
184
|
|
|
); |
|
185
|
|
|
|
|
186
|
|
|
$form->handleRequest($request); |
|
187
|
|
|
|
|
188
|
|
|
if ($form->isSubmitted() && $form->isValid()) { |
|
189
|
|
|
$contentListItem->getContentList()->setUpdatedAt(new DateTime()); |
|
190
|
|
|
$this->entityManager->flush(); |
|
191
|
|
|
|
|
192
|
|
|
return new SingleResourceResponse($contentListItem); |
|
193
|
|
|
} |
|
194
|
|
|
|
|
195
|
|
|
return new SingleResourceResponse($form, new ResponseContext(400)); |
|
196
|
|
|
} |
|
197
|
|
|
|
|
198
|
|
|
/** |
|
199
|
|
|
* Tips: |
|
200
|
|
|
* - position "-1" will place element at end of list. |
|
201
|
|
|
* - make sure that "updated_at" value is filled with value fetched from list. |
|
202
|
|
|
* |
|
203
|
|
|
* Possible actions: move, add, delete |
|
204
|
|
|
* |
|
205
|
|
|
* @Operation( |
|
206
|
|
|
* tags={"content list"}, |
|
207
|
|
|
* summary="Update many content list items", |
|
208
|
|
|
* @SWG\Parameter( |
|
209
|
|
|
* name="body", |
|
210
|
|
|
* in="body", |
|
211
|
|
|
* @SWG\Schema( |
|
212
|
|
|
* ref=@Model(type=ContentListItemsType::class) |
|
213
|
|
|
* ) |
|
214
|
|
|
* ), |
|
215
|
|
|
* @SWG\Response( |
|
216
|
|
|
* response="200", |
|
217
|
|
|
* description="Returned on success.", |
|
218
|
|
|
* @Model(type=\SWP\Bundle\CoreBundle\Model\ContentList::class, groups={"api"}) |
|
219
|
|
|
* ), |
|
220
|
|
|
* @SWG\Response( |
|
221
|
|
|
* response="400", |
|
222
|
|
|
* description="Returned when not valid data." |
|
223
|
|
|
* ), |
|
224
|
|
|
* @SWG\Response( |
|
225
|
|
|
* response="404", |
|
226
|
|
|
* description="Returned when not found." |
|
227
|
|
|
* ) |
|
228
|
|
|
* ) |
|
229
|
|
|
* |
|
230
|
|
|
* @Route("/api/{version}/content/lists/{listId}/items/", options={"expose"=true}, defaults={"version"="v2"}, methods={"PATCH"}, name="swp_api_core_batch_update_lists_item", requirements={"listId"="\d+"}) |
|
231
|
|
|
*/ |
|
232
|
|
|
public function batchUpdateAction( |
|
233
|
|
|
Request $request, |
|
234
|
|
|
FormFactoryInterface $formFactory, |
|
235
|
|
|
ContentListRepositoryInterface $contentListRepository, |
|
236
|
|
|
ArticleRepositoryInterface $articleRepository, |
|
237
|
|
|
ContentListServiceInterface $contentListService, |
|
238
|
|
|
EventDispatcherInterface $eventDispatcher, |
|
239
|
|
|
int $listId |
|
240
|
|
|
): SingleResourceResponseInterface { |
|
241
|
|
|
/** @var ContentListInterface $list */ |
|
242
|
|
|
$list = $contentListRepository->findOneBy(['id' => $listId]); |
|
243
|
|
|
if (null === $list) { |
|
244
|
|
|
throw new NotFoundHttpException(sprintf('Content list with id "%s" was not found.', $list)); |
|
245
|
|
|
} |
|
246
|
|
|
|
|
247
|
|
|
$form = $formFactory->createNamed('', ContentListItemsType::class, [], ['method' => $request->getMethod()]); |
|
248
|
|
|
|
|
249
|
|
|
$form->handleRequest($request); |
|
250
|
|
|
if ($form->isSubmitted() && $form->isValid()) { |
|
251
|
|
|
$data = $form->getData(); |
|
252
|
|
|
$updatedAt = DateTime::createFromFormat(DateTime::RFC3339, $data['updatedAt'], new DateTimeZone('UTC')); |
|
253
|
|
|
$updatedAt->setTimezone(new DateTimeZone('UTC')); |
|
254
|
|
|
$listUpdatedAt = $list->getUpdatedAt(); |
|
255
|
|
|
$listUpdatedAt->setTimezone(new DateTimeZone('UTC')); |
|
256
|
|
|
if ($updatedAt < $listUpdatedAt) { |
|
257
|
|
|
throw new ConflictHttpException('List was already updated'); |
|
258
|
|
|
} |
|
259
|
|
|
|
|
260
|
|
|
$updatedArticles = []; |
|
261
|
|
|
foreach ($data['items'] as $item) { |
|
262
|
|
|
if (!is_array($item)) { |
|
263
|
|
|
continue; |
|
264
|
|
|
} |
|
265
|
|
|
|
|
266
|
|
|
if (!isset($item['position']) || !is_numeric($item['position'])) { |
|
267
|
|
|
$item['position'] = 0; |
|
268
|
|
|
} |
|
269
|
|
|
|
|
270
|
|
|
switch ($item['action']) { |
|
271
|
|
View Code Duplication |
case 'move': |
|
|
|
|
|
|
272
|
|
|
$contentListItem = $this->findByContentOr404($list, $item['contentId']); |
|
273
|
|
|
$contentListItem->setPosition($item['position']); |
|
274
|
|
|
$list->setUpdatedAt(new DateTime('now')); |
|
275
|
|
|
$this->entityManager->flush(); |
|
276
|
|
|
$updatedArticles[$item['contentId']] = $contentListItem->getContent(); |
|
277
|
|
|
|
|
278
|
|
|
break; |
|
279
|
|
|
case 'add': |
|
280
|
|
|
$object = $articleRepository->findOneById($item['contentId']); |
|
|
|
|
|
|
281
|
|
|
$contentListItem = $contentListService->addArticleToContentList($list, $object, $item['position']); |
|
282
|
|
|
$updatedArticles[$item['contentId']] = $contentListItem->getContent(); |
|
283
|
|
|
|
|
284
|
|
|
break; |
|
285
|
|
View Code Duplication |
case 'delete': |
|
286
|
|
|
$contentListItem = $this->findByContentOr404($list, $item['contentId']); |
|
287
|
|
|
$this->entityManager->remove($contentListItem); |
|
288
|
|
|
$list->setUpdatedAt(new DateTime('now')); |
|
289
|
|
|
$this->entityManager->flush(); |
|
290
|
|
|
$updatedArticles[$item['contentId']] = $contentListItem->getContent(); |
|
291
|
|
|
|
|
292
|
|
|
break; |
|
293
|
|
|
} |
|
294
|
|
|
} |
|
295
|
|
|
|
|
296
|
|
|
foreach ($updatedArticles as $updatedArticle) { |
|
297
|
|
|
$eventDispatcher->dispatch(ArticleEvents::POST_UPDATE, new ArticleEvent( |
|
298
|
|
|
$updatedArticle, |
|
299
|
|
|
$updatedArticle->getPackage(), |
|
300
|
|
|
ArticleEvents::POST_UPDATE |
|
301
|
|
|
)); |
|
302
|
|
|
} |
|
303
|
|
|
|
|
304
|
|
|
return new SingleResourceResponse($list, new ResponseContext(201)); |
|
305
|
|
|
} |
|
306
|
|
|
|
|
307
|
|
|
return new SingleResourceResponse($form, new ResponseContext(400)); |
|
308
|
|
|
} |
|
309
|
|
|
|
|
310
|
|
View Code Duplication |
private function findByContentOr404($listId, $contentId): ContentListItemInterface |
|
|
|
|
|
|
311
|
|
|
{ |
|
312
|
|
|
/** @var ContentListItemInterface $listItem */ |
|
313
|
|
|
$listItem = $this->contentListItemRepository->findOneBy([ |
|
314
|
|
|
'contentList' => $listId, |
|
315
|
|
|
'content' => $contentId, |
|
316
|
|
|
]); |
|
317
|
|
|
|
|
318
|
|
|
if (null === $listItem) { |
|
319
|
|
|
throw new NotFoundHttpException(sprintf('Content list item with content_id "%s" was not found on that list. If You want to add new item - use action type "add".', $contentId)); |
|
320
|
|
|
} |
|
321
|
|
|
|
|
322
|
|
|
return $listItem; |
|
323
|
|
|
} |
|
324
|
|
|
|
|
325
|
|
View Code Duplication |
private function findOr404($listId, $id): ContentListItemInterface |
|
|
|
|
|
|
326
|
|
|
{ |
|
327
|
|
|
/** @var ContentListItemInterface $listItem */ |
|
328
|
|
|
$listItem = $this->contentListItemRepository->findOneBy([ |
|
329
|
|
|
'contentList' => $listId, |
|
330
|
|
|
'id' => $id, |
|
331
|
|
|
]); |
|
332
|
|
|
|
|
333
|
|
|
if (null === $listItem) { |
|
334
|
|
|
throw new NotFoundHttpException(sprintf('Content list item with id "%s" was not found.', $id)); |
|
335
|
|
|
} |
|
336
|
|
|
|
|
337
|
|
|
return $listItem; |
|
338
|
|
|
} |
|
339
|
|
|
} |
|
340
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.