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 function array_key_exists; |
18
|
|
|
use DateTime; |
19
|
|
|
use FOS\RestBundle\Controller\FOSRestController; |
20
|
|
|
use Nelmio\ApiDocBundle\Annotation\Operation; |
21
|
|
|
use Nelmio\ApiDocBundle\Annotation\Model; |
22
|
|
|
use Swagger\Annotations as SWG; |
23
|
|
|
use SWP\Bundle\CoreBundle\Context\ScopeContextInterface; |
24
|
|
|
use Symfony\Component\Routing\Annotation\Route; |
25
|
|
|
use SWP\Bundle\MultiTenancyBundle\MultiTenancyEvents; |
26
|
|
|
use SWP\Component\Common\Response\ResourcesListResponse; |
27
|
|
|
use SWP\Component\Common\Response\ResponseContext; |
28
|
|
|
use SWP\Component\Common\Response\SingleResourceResponse; |
29
|
|
|
use SWP\Component\Common\Criteria\Criteria; |
30
|
|
|
use SWP\Component\Common\Pagination\PaginationData; |
31
|
|
|
use SWP\Bundle\CoreBundle\Form\Type\TenantType; |
32
|
|
|
use SWP\Component\MultiTenancy\Model\TenantInterface; |
33
|
|
|
use Symfony\Component\HttpFoundation\Request; |
34
|
|
|
use Symfony\Component\HttpKernel\Exception\ConflictHttpException; |
35
|
|
|
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; |
36
|
|
|
|
37
|
|
|
class TenantController extends FOSRestController |
|
|
|
|
38
|
|
|
{ |
39
|
|
|
/** |
40
|
|
|
* List all tenants/websites. |
41
|
|
|
* |
42
|
|
|
* @Operation( |
43
|
|
|
* tags={"tenant"}, |
44
|
|
|
* summary="List all tenants/websites", |
45
|
|
|
* @SWG\Parameter( |
46
|
|
|
* name="sorting", |
47
|
|
|
* in="query", |
48
|
|
|
* description="todo", |
49
|
|
|
* required=false, |
50
|
|
|
* type="string" |
51
|
|
|
* ), |
52
|
|
|
* @SWG\Response( |
53
|
|
|
* response="200", |
54
|
|
|
* description="Returned on success." |
55
|
|
|
* ) |
56
|
|
|
* ) |
57
|
|
|
* |
58
|
|
|
* @Route("/api/{version}/tenants/", options={"expose"=true}, defaults={"version"="v2"}, methods={"GET"}, name="swp_api_core_list_tenants") |
59
|
|
|
*/ |
60
|
|
|
public function listAction(Request $request) |
61
|
|
|
{ |
62
|
|
|
$tenants = $this->getTenantRepository() |
63
|
|
|
->getPaginatedByCriteria(new Criteria(), $request->query->get('sorting', []), new PaginationData($request)); |
64
|
|
|
$responseContext = new ResponseContext(); |
65
|
|
|
$responseContext->setSerializationGroups(['Default', 'api', 'details_api']); |
66
|
|
|
|
67
|
|
|
return new ResourcesListResponse($tenants, $responseContext); |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* Shows a single tenant/website. |
72
|
|
|
* |
73
|
|
|
* @Operation( |
74
|
|
|
* tags={"tenant"}, |
75
|
|
|
* summary="Show single tenant/website", |
76
|
|
|
* @SWG\Response( |
77
|
|
|
* response="200", |
78
|
|
|
* description="Returned on success.", |
79
|
|
|
* @Model(type=SWP\Bundle\CoreBundle\Model\Tenant::class, groups={"api"}) |
80
|
|
|
* ) |
81
|
|
|
* ) |
82
|
|
|
* |
83
|
|
|
* @Route("/api/{version}/tenants/{code}", options={"expose"=true}, defaults={"version"="v2"}, methods={"GET"}, name="swp_api_core_get_tenant", requirements={"code"="[a-z0-9]+"}) |
84
|
|
|
*/ |
85
|
|
|
public function getAction($code) |
86
|
|
|
{ |
87
|
|
|
return new SingleResourceResponse($this->findOr404($code)); |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
/** |
91
|
1 |
|
* Deletes a single tenant. |
92
|
|
|
* |
93
|
1 |
|
* @Operation( |
94
|
1 |
|
* tags={"tenant"}, |
95
|
|
|
* summary="Delete single tenant/website", |
96
|
1 |
|
* @SWG\Parameter( |
97
|
|
|
* name="force", |
98
|
1 |
|
* in="body", |
99
|
|
|
* description="Remove tenant ignoring attached articles", |
100
|
|
|
* required=false, |
101
|
|
|
* @SWG\Schema(type="bool") |
102
|
|
|
* ), |
103
|
|
|
* @SWG\Response( |
104
|
|
|
* response="204", |
105
|
|
|
* description="Returned on success." |
106
|
|
|
* ) |
107
|
|
|
* ) |
108
|
|
|
* |
109
|
|
|
* @Route("/api/{version}/tenants/{code}", options={"expose"=true}, defaults={"version"="v2"}, methods={"DELETE"}, name="swp_api_core_delete_tenant", requirements={"code"="[a-z0-9]+"}) |
110
|
|
|
*/ |
111
|
|
|
public function deleteAction(Request $request, $code) |
112
|
|
|
{ |
113
|
|
|
$tenantContext = $this->container->get('swp_multi_tenancy.tenant_context'); |
114
|
|
|
$eventDispatcher = $this->container->get('event_dispatcher'); |
115
|
|
|
$currentTenant = $tenantContext->getTenant(); |
116
|
|
|
|
117
|
6 |
|
$repository = $this->getTenantRepository(); |
118
|
|
|
$tenant = $this->findOr404($code); |
119
|
6 |
|
|
120
|
6 |
|
$forceRemove = $request->query->has('force'); |
121
|
|
|
if (!$forceRemove) { |
122
|
6 |
|
$tenantContext->setTenant($tenant); |
123
|
|
|
$eventDispatcher->dispatch(MultiTenancyEvents::TENANTABLE_ENABLE); |
124
|
6 |
|
$articlesRepository = $this->get('swp.repository.article'); |
125
|
|
|
$existingArticles = $articlesRepository->findAll(); |
126
|
5 |
|
if (0 !== \count($existingArticles)) { |
127
|
|
|
throw new ConflictHttpException('This tenant have articles attached to it.'); |
128
|
5 |
|
} |
129
|
|
|
} |
130
|
5 |
|
|
131
|
|
|
$repository->remove($tenant); |
132
|
5 |
|
|
133
|
|
|
$tenantContext->setTenant($currentTenant); |
134
|
5 |
|
$eventDispatcher->dispatch(MultiTenancyEvents::TENANTABLE_ENABLE); |
135
|
|
|
|
136
|
|
|
return new SingleResourceResponse(null, new ResponseContext(204)); |
137
|
1 |
|
} |
138
|
|
|
|
139
|
|
|
/** |
140
|
|
|
* Creates a new tenant/website. |
141
|
|
|
* |
142
|
|
|
* @Operation( |
143
|
|
|
* tags={"tenant"}, |
144
|
|
|
* summary="Create new tenant/website", |
145
|
|
|
* @SWG\Parameter( |
146
|
|
|
* name="body", |
147
|
|
|
* in="body", |
148
|
|
|
* @SWG\Schema( |
149
|
|
|
* ref=@Model(type=TenantType::class) |
150
|
|
|
* ) |
151
|
|
|
* ), |
152
|
|
|
* @SWG\Response( |
153
|
|
|
* response="201", |
154
|
|
|
* description="Returned on success.", |
155
|
|
|
* @Model(type=SWP\Bundle\CoreBundle\Model\Tenant::class, groups={"api"}) |
156
|
|
|
* ), |
157
|
2 |
|
* @SWG\Response( |
158
|
|
|
* response="400", |
159
|
2 |
|
* description="Returned on failure." |
160
|
|
|
* ), |
161
|
2 |
|
* @SWG\Response( |
162
|
|
|
* response="409", |
163
|
2 |
|
* description="Returned on conflict." |
164
|
|
|
* ) |
165
|
2 |
|
* ) |
166
|
|
|
* |
167
|
2 |
|
* @Route("/api/{version}/tenants/", options={"expose"=true}, defaults={"version"="v2"}, methods={"POST"}, name="swp_api_core_create_tenant") |
168
|
|
|
*/ |
169
|
2 |
|
public function createAction(Request $request) |
170
|
2 |
|
{ |
171
|
|
|
$tenant = $this->get('swp.factory.tenant')->create(); |
172
|
2 |
|
$tenantContext = $this->get('swp_multi_tenancy.tenant_context'); |
173
|
|
|
$tenantObjectManager = $this->get('swp.object_manager.tenant'); |
174
|
|
|
$form = $this->get('form.factory')->createNamed('', TenantType::class, $tenant, ['method' => $request->getMethod()]); |
175
|
|
|
$form->handleRequest($request); |
176
|
|
|
|
177
|
|
|
if ($form->isSubmitted() && $form->isValid()) { |
178
|
3 |
|
$this->ensureTenantDontExists($tenant->getDomainName(), $tenant->getSubdomain()); |
179
|
|
|
if (null === $tenant->getOrganization()) { |
180
|
3 |
|
$organization = $tenantObjectManager->merge($tenantContext->getTenant()->getOrganization()); |
181
|
|
|
$tenant->setOrganization($organization); |
182
|
|
|
} |
183
|
|
|
$this->getTenantRepository()->add($tenant); |
184
|
3 |
|
|
185
|
|
|
return new SingleResourceResponse($tenant, new ResponseContext(201)); |
186
|
|
|
} |
187
|
5 |
|
|
188
|
|
|
return new SingleResourceResponse($form, new ResponseContext(400)); |
189
|
5 |
|
} |
190
|
1 |
|
|
191
|
|
|
/** |
192
|
|
|
* Updates a single tenant. |
193
|
5 |
|
* |
194
|
|
|
* @Operation( |
195
|
|
|
* tags={"tenant"}, |
196
|
5 |
|
* summary="Update single tenant", |
197
|
|
|
* @SWG\Parameter( |
198
|
5 |
|
* name="body", |
199
|
4 |
|
* in="body", |
200
|
4 |
|
* description="", |
201
|
|
|
* required=true, |
202
|
4 |
|
* @SWG\Schema(ref=@Model(type=TenantType::class)) |
203
|
|
|
* ), |
204
|
|
|
* @SWG\Response( |
205
|
|
|
* response="200", |
206
|
4 |
|
* description="Returned on success.", |
207
|
|
|
* @Model(type=SWP\Bundle\CoreBundle\Model\Tenant::class, groups={"api"}) |
208
|
|
|
* ), |
209
|
5 |
|
* @SWG\Response( |
210
|
|
|
* response="400", |
211
|
|
|
* description="Returned on failure." |
212
|
6 |
|
* ), |
213
|
|
|
* @SWG\Response( |
214
|
6 |
|
* response="404", |
215
|
|
|
* description="Returned when not found." |
216
|
|
|
* ), |
217
|
|
|
* @SWG\Response( |
218
|
|
|
* response="409", |
219
|
|
|
* description="Returned on conflict." |
220
|
|
|
* ) |
221
|
|
|
* ) |
222
|
|
|
* |
223
|
|
|
* @Route("/api/{version}/tenants/{code}", options={"expose"=true}, defaults={"version"="v2"}, methods={"PATCH"}, name="swp_api_core_update_tenant", requirements={"code"="[a-z0-9]+"}) |
224
|
|
|
*/ |
225
|
|
|
public function updateAction(Request $request, $code) |
226
|
|
|
{ |
227
|
|
|
$tenant = $this->findOr404($code); |
228
|
|
|
$form = $this->get('form.factory')->createNamed('', TenantType::class, $tenant, ['method' => $request->getMethod()]); |
229
|
|
|
$form->handleRequest($request); |
230
|
|
|
|
231
|
|
|
if ($form->isSubmitted() && $form->isValid()) { |
232
|
|
|
$formData = $request->request->all(); |
233
|
|
|
$tenant->setUpdatedAt(new DateTime('now')); |
234
|
|
|
$this->get('swp.object_manager.tenant')->flush(); |
235
|
|
|
|
236
|
|
|
$tenantContext = $this->get('swp_multi_tenancy.tenant_context'); |
237
|
|
|
$tenantContext->setTenant($tenant); |
238
|
|
|
|
239
|
|
|
$settingsManager = $this->get('swp_settings.manager.settings'); |
240
|
|
|
|
241
|
|
|
if (array_key_exists('fbiaEnabled', $formData)) { |
242
|
|
|
$settingsManager->set('fbia_enabled', (bool) $formData['fbiaEnabled'], ScopeContextInterface::SCOPE_TENANT, $tenant); |
243
|
|
|
} |
244
|
|
|
if (array_key_exists('paywallEnabled', $formData)) { |
245
|
|
|
$settingsManager->set('paywall_enabled', (bool) $formData['paywallEnabled'], ScopeContextInterface::SCOPE_TENANT, $tenant); |
246
|
|
|
} |
247
|
|
|
if (array_key_exists('defaultLanguage', $formData)) { |
248
|
|
|
$settingsManager->set('default_language', $formData['defaultLanguage'], ScopeContextInterface::SCOPE_TENANT, $tenant); |
249
|
|
|
} |
250
|
|
|
|
251
|
|
|
return new SingleResourceResponse($tenant); |
252
|
|
|
} |
253
|
|
|
|
254
|
|
|
return new SingleResourceResponse($form, new ResponseContext(400)); |
255
|
|
|
} |
256
|
|
|
|
257
|
|
|
/** |
258
|
|
|
* @param string $code |
259
|
|
|
* |
260
|
|
|
* @throws NotFoundHttpException |
261
|
|
|
* |
262
|
|
|
* @return mixed|TenantInterface|null |
263
|
|
|
*/ |
264
|
|
|
private function findOr404($code) |
265
|
|
|
{ |
266
|
|
|
if (null === $tenant = $this->getTenantRepository()->findOneByCode($code)) { |
267
|
|
|
throw $this->createNotFoundException(sprintf('Tenant with code "%s" was not found.', $code)); |
268
|
|
|
} |
269
|
|
|
|
270
|
|
|
return $tenant; |
271
|
|
|
} |
272
|
|
|
|
273
|
|
|
/** |
274
|
|
|
* @param string $domain |
275
|
|
|
* @param string|null $subdomain |
276
|
|
|
* |
277
|
|
|
* @return mixed|TenantInterface|null |
278
|
|
|
*/ |
279
|
|
|
private function ensureTenantDontExists(string $domain, string $subdomain = null) |
280
|
|
|
{ |
281
|
|
|
if (null !== $tenant = $this->getTenantRepository()->findOneBySubdomainAndDomain($subdomain, $domain)) { |
282
|
|
|
throw new ConflictHttpException('Tenant for this host already exists.'); |
283
|
|
|
} |
284
|
|
|
|
285
|
|
|
return $tenant; |
286
|
|
|
} |
287
|
|
|
|
288
|
|
|
/** |
289
|
|
|
* @return object|\SWP\Bundle\MultiTenancyBundle\Doctrine\ORM\TenantRepository |
290
|
|
|
*/ |
291
|
|
|
private function getTenantRepository() |
292
|
|
|
{ |
293
|
|
|
return $this->get('swp.repository.tenant'); |
294
|
|
|
} |
295
|
|
|
} |
296
|
|
|
|
This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.