This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | declare(strict_types=1); |
||
4 | |||
5 | /* |
||
6 | * This file is part of the Sonata Project package. |
||
7 | * |
||
8 | * (c) Thomas Rabaix <[email protected]> |
||
9 | * |
||
10 | * For the full copyright and license information, please view the LICENSE |
||
11 | * file that was distributed with this source code. |
||
12 | */ |
||
13 | |||
14 | namespace Sonata\NewsBundle\Controller\Api; |
||
15 | |||
16 | use FOS\RestBundle\Context\Context; |
||
17 | use FOS\RestBundle\Controller\Annotations as REST; |
||
18 | use FOS\RestBundle\Request\ParamFetcherInterface; |
||
19 | use FOS\RestBundle\View\View; |
||
20 | use Nelmio\ApiDocBundle\Annotation\ApiDoc; |
||
21 | use Sonata\DatagridBundle\Pager\PagerInterface; |
||
22 | use Sonata\FormatterBundle\Formatter\Pool as FormatterPool; |
||
23 | use Sonata\NewsBundle\Mailer\MailerInterface; |
||
24 | use Sonata\NewsBundle\Model\Comment; |
||
25 | use Sonata\NewsBundle\Model\CommentManagerInterface; |
||
26 | use Sonata\NewsBundle\Model\Post; |
||
27 | use Sonata\NewsBundle\Model\PostManagerInterface; |
||
28 | use Symfony\Component\Form\FormFactoryInterface; |
||
29 | use Symfony\Component\Form\FormInterface; |
||
30 | use Symfony\Component\HttpFoundation\Request; |
||
31 | use Symfony\Component\HttpKernel\Exception\HttpException; |
||
32 | use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; |
||
33 | |||
34 | /** |
||
35 | * @author Hugo Briand <[email protected]> |
||
36 | */ |
||
37 | class PostController |
||
38 | { |
||
39 | /** |
||
40 | * @var PostManagerInterface |
||
41 | */ |
||
42 | protected $postManager; |
||
43 | |||
44 | /** |
||
45 | * @var CommentManagerInterface |
||
46 | */ |
||
47 | protected $commentManager; |
||
48 | |||
49 | /** |
||
50 | * @var MailerInterface |
||
51 | */ |
||
52 | protected $mailer; |
||
53 | |||
54 | /** |
||
55 | * @var FormFactoryInterface |
||
56 | */ |
||
57 | protected $formFactory; |
||
58 | |||
59 | /** |
||
60 | * @var FormatterPool |
||
61 | */ |
||
62 | protected $formatterPool; |
||
63 | |||
64 | public function __construct(PostManagerInterface $postManager, CommentManagerInterface $commentManager, MailerInterface $mailer, FormFactoryInterface $formFactory, FormatterPool $formatterPool) |
||
65 | { |
||
66 | $this->postManager = $postManager; |
||
67 | $this->commentManager = $commentManager; |
||
68 | $this->mailer = $mailer; |
||
69 | $this->formFactory = $formFactory; |
||
70 | $this->formatterPool = $formatterPool; |
||
71 | } |
||
72 | |||
73 | /** |
||
74 | * Retrieves the list of posts (paginated) based on criteria. |
||
75 | * |
||
76 | * @ApiDoc( |
||
77 | * resource=true, |
||
78 | * output={"class"="Sonata\DatagridBundle\Pager\PagerInterface", "groups"={"sonata_api_read"}} |
||
79 | * ) |
||
80 | * |
||
81 | * @REST\QueryParam(name="page", requirements="\d+", default="1", description="Page for posts list pagination") |
||
82 | * @REST\QueryParam(name="count", requirements="\d+", default="10", description="Number of posts by page") |
||
83 | * @REST\QueryParam(name="enabled", requirements="0|1", nullable=true, strict=true, description="Enabled/Disabled posts filter") |
||
84 | * @REST\QueryParam(name="dateQuery", requirements=">|<|=", default=">", description="Date filter orientation (>, < or =)") |
||
85 | * @REST\QueryParam(name="dateValue", requirements="[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-2][0-9]:[0-5][0-9]:[0-5][0-9]([+-][0-9]{2}(:)?[0-9]{2})?", nullable=true, strict=true, description="Date filter value") |
||
86 | * @REST\QueryParam(name="tag", requirements="\S+", nullable=true, strict=true, description="Tag name filter") |
||
87 | * @REST\QueryParam(name="author", requirements="\S+", nullable=true, strict=true, description="Author filter") |
||
88 | * @REST\QueryParam(name="mode", requirements="public|admin", default="public", description="'public' mode filters posts having enabled tags and author") |
||
89 | * |
||
90 | * @REST\View(serializerGroups={"sonata_api_read"}, serializerEnableMaxDepthChecks=true) |
||
91 | * |
||
92 | * @return PagerInterface |
||
93 | */ |
||
94 | public function getPostsAction(ParamFetcherInterface $paramFetcher) |
||
95 | { |
||
96 | $page = $paramFetcher->get('page'); |
||
97 | $count = $paramFetcher->get('count'); |
||
98 | |||
99 | $pager = $this->postManager->getPager($this->filterCriteria($paramFetcher), $page, $count); |
||
100 | |||
101 | return $pager; |
||
102 | } |
||
103 | |||
104 | /** |
||
105 | * Retrieves a specific post. |
||
106 | * |
||
107 | * @ApiDoc( |
||
108 | * requirements={ |
||
109 | * {"name"="id", "dataType"="integer", "requirement"="\d+", "description"="Post identifier"} |
||
110 | * }, |
||
111 | * output={"class"="sonata_news_api_form_post", "groups"={"sonata_api_read"}}, |
||
112 | * statusCodes={ |
||
113 | * 200="Returned when successful", |
||
114 | * 404="Returned when post is not found" |
||
115 | * } |
||
116 | * ) |
||
117 | * |
||
118 | * @REST\View(serializerGroups={"sonata_api_read"}, serializerEnableMaxDepthChecks=true) |
||
119 | * |
||
120 | * @param int $id A post identifier |
||
121 | * |
||
122 | * @return Post |
||
123 | */ |
||
124 | public function getPostAction($id) |
||
125 | { |
||
126 | return $this->getPost($id); |
||
127 | } |
||
128 | |||
129 | /** |
||
130 | * Adds a post. |
||
131 | * |
||
132 | * @ApiDoc( |
||
133 | * input={"class"="sonata_news_api_form_post", "name"="", "groups"={"sonata_api_write"}}, |
||
134 | * output={"class"="sonata_news_api_form_post", "groups"={"sonata_api_read"}}, |
||
135 | * statusCodes={ |
||
136 | * 200="Returned when successful", |
||
137 | * 400="Returned when an error has occurred while post creation", |
||
138 | * } |
||
139 | * ) |
||
140 | * |
||
141 | * @param Request $request Symfony request |
||
142 | * |
||
143 | * @throws NotFoundHttpException |
||
144 | * |
||
145 | * @return Post |
||
146 | */ |
||
147 | public function postPostAction(Request $request) |
||
148 | { |
||
149 | return $this->handleWritePost($request); |
||
150 | } |
||
151 | |||
152 | /** |
||
153 | * Updates a post. |
||
154 | * |
||
155 | * @ApiDoc( |
||
156 | * requirements={ |
||
157 | * {"name"="id", "dataType"="integer", "requirement"="\d+", "description"="Post identifier"} |
||
158 | * }, |
||
159 | * input={"class"="sonata_news_api_form_post", "name"="", "groups"={"sonata_api_write"}}, |
||
160 | * output={"class"="sonata_news_api_form_post", "groups"={"sonata_api_read"}}, |
||
161 | * statusCodes={ |
||
162 | * 200="Returned when successful", |
||
163 | * 400="Returned when an error has occurred while post update", |
||
164 | * 404="Returned when unable to find post" |
||
165 | * } |
||
166 | * ) |
||
167 | * |
||
168 | * @param int $id Post identifier |
||
169 | * @param Request $request Symfony request |
||
170 | * |
||
171 | * @throws NotFoundHttpException |
||
172 | * |
||
173 | * @return Post |
||
174 | */ |
||
175 | public function putPostAction($id, Request $request) |
||
176 | { |
||
177 | return $this->handleWritePost($request, $id); |
||
178 | } |
||
179 | |||
180 | /** |
||
181 | * Deletes a post. |
||
182 | * |
||
183 | * @ApiDoc( |
||
184 | * requirements={ |
||
185 | * {"name"="id", "dataType"="integer", "requirement"="\d+", "description"="Post identifier"} |
||
186 | * }, |
||
187 | * statusCodes={ |
||
188 | * 200="Returned when post is successfully deleted", |
||
189 | * 400="Returned when an error has occurred while post deletion", |
||
190 | * 404="Returned when unable to find post" |
||
191 | * } |
||
192 | * ) |
||
193 | * |
||
194 | * @param int $id Post identifier |
||
195 | * |
||
196 | * @throws NotFoundHttpException |
||
197 | * |
||
198 | * @return View |
||
199 | */ |
||
200 | public function deletePostAction($id) |
||
201 | { |
||
202 | $post = $this->getPost($id); |
||
203 | |||
204 | try { |
||
205 | $this->postManager->delete($post); |
||
206 | } catch (\Exception $e) { |
||
207 | return View::create(['error' => $e->getMessage()], 400); |
||
208 | } |
||
209 | |||
210 | return ['deleted' => true]; |
||
0 ignored issues
–
show
|
|||
211 | } |
||
212 | |||
213 | /** |
||
214 | * Retrieves the comments of specified post. |
||
215 | * |
||
216 | * @ApiDoc( |
||
217 | * requirements={ |
||
218 | * {"name"="id", "dataType"="integer", "requirement"="\d+", "description"="Post identifier"} |
||
219 | * }, |
||
220 | * output={"class"="Sonata\DatagridBundle\Pager\PagerInterface", "groups"={"sonata_api_read"}}, |
||
221 | * statusCodes={ |
||
222 | * 200="Returned when successful", |
||
223 | * 404="Returned when post is not found" |
||
224 | * } |
||
225 | * ) |
||
226 | * |
||
227 | * @REST\QueryParam(name="page", requirements="\d+", default="1", description="Page for comments list pagination") |
||
228 | * @REST\QueryParam(name="count", requirements="\d+", default="10", description="Number of comments by page") |
||
229 | * |
||
230 | * @REST\View(serializerGroups={"sonata_api_read"}, serializerEnableMaxDepthChecks=true) |
||
231 | * |
||
232 | * @param int $id Post identifier |
||
233 | * |
||
234 | * @return PagerInterface |
||
235 | */ |
||
236 | public function getPostCommentsAction($id, ParamFetcherInterface $paramFetcher) |
||
237 | { |
||
238 | $post = $this->getPost($id); |
||
239 | |||
240 | $page = $paramFetcher->get('page'); |
||
241 | $count = $paramFetcher->get('count'); |
||
242 | |||
243 | $criteria = $this->filterCriteria($paramFetcher); |
||
244 | $criteria['postId'] = $post->getId(); |
||
245 | |||
246 | /** @var PagerInterface $pager */ |
||
247 | $pager = $this->commentManager->getPager($criteria, $page, $count); |
||
248 | |||
249 | return $pager; |
||
250 | } |
||
251 | |||
252 | /** |
||
253 | * Adds a comment to a post. |
||
254 | * |
||
255 | * @ApiDoc( |
||
256 | * requirements={ |
||
257 | * {"name"="id", "dataType"="integer", "requirement"="\d+", "description"="Post identifier"} |
||
258 | * }, |
||
259 | * input={"class"="sonata_news_api_form_comment", "name"="", "groups"={"sonata_api_write"}}, |
||
260 | * output={"class"="Sonata\NewsBundle\Model\Comment", "groups"={"sonata_api_read"}}, |
||
261 | * statusCodes={ |
||
262 | * 200="Returned when successful", |
||
263 | * 400="Returned when an error has occurred while comment creation", |
||
264 | * 403="Returned when commenting is not enabled on the related post", |
||
265 | * 404="Returned when post is not found" |
||
266 | * } |
||
267 | * ) |
||
268 | * |
||
269 | * @REST\View(serializerGroups={"sonata_api_read"}, serializerEnableMaxDepthChecks=true) |
||
270 | * |
||
271 | * @param int $id Post identifier |
||
272 | * |
||
273 | * @throws HttpException |
||
274 | * |
||
275 | * @return Comment|FormInterface |
||
276 | */ |
||
277 | public function postPostCommentsAction($id, Request $request) |
||
278 | { |
||
279 | $post = $this->getPost($id); |
||
280 | |||
281 | if (!$post->isCommentable()) { |
||
282 | throw new HttpException(403, sprintf('Post (%d) not commentable', $id)); |
||
283 | } |
||
284 | |||
285 | $comment = $this->commentManager->create(); |
||
286 | $comment->setPost($post); |
||
287 | |||
288 | $form = $this->formFactory->createNamed(null, 'sonata_news_api_form_comment', $comment, ['csrf_protection' => false]); |
||
289 | $form->handleRequest($request); |
||
290 | |||
291 | if ($form->isValid()) { |
||
292 | $comment = $form->getData(); |
||
293 | $comment->setPost($post); |
||
294 | |||
295 | if (!$comment->getStatus()) { |
||
296 | $comment->setStatus($post->getCommentsDefaultStatus()); |
||
297 | } |
||
298 | |||
299 | $this->commentManager->save($comment); |
||
300 | $this->mailer->sendCommentNotification($comment); |
||
301 | |||
302 | return $comment; |
||
303 | } |
||
304 | |||
305 | return $form; |
||
306 | } |
||
307 | |||
308 | /** |
||
309 | * Updates a comment. |
||
310 | * |
||
311 | * @ApiDoc( |
||
312 | * requirements={ |
||
313 | * {"name"="postId", "dataType"="integer", "requirement"="\d+", "description"="Post identifier"}, |
||
314 | * {"name"="commentId", "dataType"="integer", "requirement"="\d+", "description"="Comment identifier"} |
||
315 | * }, |
||
316 | * input={"class"="sonata_news_api_form_comment", "name"="", "groups"={"sonata_api_write"}}, |
||
317 | * output={"class"="Sonata\NewsBundle\Model\Comment", "groups"={"sonata_api_read"}}, |
||
318 | * statusCodes={ |
||
319 | * 200="Returned when successful", |
||
320 | * 400="Returned when an error has occurred while comment update", |
||
321 | * 404="Returned when unable to find comment" |
||
322 | * } |
||
323 | * ) |
||
324 | * |
||
325 | * @REST\View(serializerGroups={"sonata_api_read"}, serializerEnableMaxDepthChecks=true) |
||
326 | * |
||
327 | * @param int $postId Post identifier |
||
328 | * @param int $commentId Comment identifier |
||
329 | * @param Request $request Symfony request |
||
330 | * |
||
331 | * @throws NotFoundHttpException |
||
332 | * @throws HttpException |
||
333 | * |
||
334 | * @return Comment |
||
335 | */ |
||
336 | public function putPostCommentsAction($postId, $commentId, Request $request) |
||
337 | { |
||
338 | $post = $this->getPost($postId); |
||
339 | |||
340 | if (!$post->isCommentable()) { |
||
341 | throw new HttpException(403, sprintf('Post (%d) not commentable', $postId)); |
||
342 | } |
||
343 | |||
344 | $comment = $this->commentManager->find($commentId); |
||
345 | |||
346 | if (null === $comment) { |
||
347 | throw new NotFoundHttpException(sprintf('Comment (%d) not found', $commentId)); |
||
348 | } |
||
349 | |||
350 | $comment->setPost($post); |
||
351 | |||
352 | $form = $this->formFactory->createNamed(null, 'sonata_news_api_form_comment', $comment, [ |
||
353 | 'csrf_protection' => false, |
||
354 | ]); |
||
355 | |||
356 | $form->handleRequest($request); |
||
357 | |||
358 | if ($form->isValid()) { |
||
359 | $comment = $form->getData(); |
||
360 | $this->commentManager->save($comment); |
||
361 | |||
362 | return $comment; |
||
363 | } |
||
364 | |||
365 | return $form; |
||
0 ignored issues
–
show
The return type of
return $form; (Symfony\Component\Form\FormInterface ) is incompatible with the return type documented by Sonata\NewsBundle\Contro...::putPostCommentsAction of type Sonata\NewsBundle\Model\Comment .
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design. Let’s take a look at an example: class Author {
private $name;
public function __construct($name) {
$this->name = $name;
}
public function getName() {
return $this->name;
}
}
abstract class Post {
public function getAuthor() {
return 'Johannes';
}
}
class BlogPost extends Post {
public function getAuthor() {
return new Author('Johannes');
}
}
class ForumPost extends Post { /* ... */ }
function my_function(Post $post) {
echo strtoupper($post->getAuthor());
}
Our function ![]() |
|||
366 | } |
||
367 | |||
368 | /** |
||
369 | * Filters criteria from $paramFetcher to be compatible with the Pager criteria. |
||
370 | * |
||
371 | * @return array The filtered criteria |
||
372 | */ |
||
373 | protected function filterCriteria(ParamFetcherInterface $paramFetcher) |
||
374 | { |
||
375 | $criteria = $paramFetcher->all(); |
||
376 | |||
377 | unset($criteria['page'], $criteria['count']); |
||
378 | |||
379 | foreach ($criteria as $key => $value) { |
||
380 | if (null === $value) { |
||
381 | unset($criteria[$key]); |
||
382 | } |
||
383 | } |
||
384 | |||
385 | if (\array_key_exists('dateValue', $criteria)) { |
||
386 | $date = new \DateTime($criteria['dateValue']); |
||
387 | $criteria['date'] = [ |
||
388 | 'query' => sprintf('p.publicationDateStart %s :dateValue', $criteria['dateQuery']), |
||
389 | 'params' => ['dateValue' => $date], |
||
390 | ]; |
||
391 | unset($criteria['dateValue'], $criteria['dateQuery']); |
||
392 | } else { |
||
393 | unset($criteria['dateQuery']); |
||
394 | } |
||
395 | |||
396 | return $criteria; |
||
397 | } |
||
398 | |||
399 | /** |
||
400 | * Retrieves post with id $id or throws an exception if it doesn't exist. |
||
401 | * |
||
402 | * @param int $id Post identifier |
||
403 | * |
||
404 | * @throws NotFoundHttpException |
||
405 | * |
||
406 | * @return Post |
||
407 | */ |
||
408 | protected function getPost($id) |
||
409 | { |
||
410 | $post = $this->postManager->find($id); |
||
411 | |||
412 | if (null === $post) { |
||
413 | throw new NotFoundHttpException(sprintf('Post (%d) not found', $id)); |
||
414 | } |
||
415 | |||
416 | return $post; |
||
417 | } |
||
418 | |||
419 | /** |
||
420 | * Write a post, this method is used by both POST and PUT action methods. |
||
421 | * |
||
422 | * @param Request $request Symfony request |
||
423 | * @param int|null $id Post identifier |
||
424 | * |
||
425 | * @return View|FormInterface |
||
426 | */ |
||
427 | protected function handleWritePost($request, $id = null) |
||
428 | { |
||
429 | $post = $id ? $this->getPost($id) : null; |
||
430 | |||
431 | $form = $this->formFactory->createNamed(null, 'sonata_news_api_form_post', $post, [ |
||
432 | 'csrf_protection' => false, |
||
433 | ]); |
||
434 | |||
435 | $form->handleRequest($request); |
||
436 | |||
437 | if ($form->isValid()) { |
||
438 | $post = $form->getData(); |
||
439 | $post->setContent($this->formatterPool->transform($post->getContentFormatter(), $post->getRawContent())); |
||
440 | $this->postManager->save($post); |
||
441 | |||
442 | $context = new Context(); |
||
443 | $context->setGroups(['sonata_api_read']); |
||
444 | |||
445 | // simplify when dropping FOSRest < 2.1 |
||
446 | if (method_exists($context, 'enableMaxDepth')) { |
||
447 | $context->enableMaxDepth(); |
||
448 | } else { |
||
449 | $context->setMaxDepth(10); |
||
0 ignored issues
–
show
The method
setMaxDepth() does not seem to exist on object<FOS\RestBundle\Context\Context> .
This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces. This is most likely a typographical error or the method has been renamed. ![]() |
|||
450 | } |
||
451 | |||
452 | $view = View::create($post); |
||
453 | $view->setContext($context); |
||
454 | |||
455 | return $view; |
||
456 | } |
||
457 | |||
458 | return $form; |
||
459 | } |
||
460 | } |
||
461 |
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_function
expects aPost
object, and outputs the author of the post. The base classPost
returns a simple string and outputting a simple string will work just fine. However, the child classBlogPost
which is a sub-type ofPost
instead decided to return anobject
, and is therefore violating the SOLID principles. If aBlogPost
were passed tomy_function
, PHP would not complain, but ultimately fail when executing thestrtoupper
call in its body.