EditFeeds.php 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. <?php
  2. declare(strict_types = 1);
  3. // {{{ License
  4. // This file is part of GNU social - https://www.gnu.org/software/social
  5. //
  6. // GNU social is free software: you can redistribute it and/or modify
  7. // it under the terms of the GNU Affero General Public License as published by
  8. // the Free Software Foundation, either version 3 of the License, or
  9. // (at your option) any later version.
  10. //
  11. // GNU social is distributed in the hope that it will be useful,
  12. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. // GNU Affero General Public License for more details.
  15. //
  16. // You should have received a copy of the GNU Affero General Public License
  17. // along with GNU social. If not, see <http://www.gnu.org/licenses/>.
  18. // }}}
  19. namespace Component\LeftPanel\Controller;
  20. use App\Core\Cache;
  21. use App\Core\Controller;
  22. use App\Core\DB\DB;
  23. use App\Core\Form;
  24. use function App\Core\I18n\_m;
  25. use App\Core\Router\Router;
  26. use App\Entity\Feed;
  27. use App\Util\Common;
  28. use App\Util\Exception\ClientException;
  29. use App\Util\Exception\RedirectException;
  30. use Component\Collection\Util\Controller\FeedController;
  31. use Functional as F;
  32. use Symfony\Component\Form\Extension\Core\Type\IntegerType;
  33. use Symfony\Component\Form\Extension\Core\Type\SubmitType;
  34. use Symfony\Component\Form\Extension\Core\Type\TextType;
  35. use Symfony\Component\Form\SubmitButton;
  36. use Symfony\Component\HttpFoundation\Request;
  37. use Symfony\Component\Routing\Exception\ResourceNotFoundException;
  38. class EditFeeds extends Controller
  39. {
  40. /**
  41. * Controller for editing the list of feeds in the user's left
  42. * panel. Adds and removes `\App\Entity\Feed`s as appropriate
  43. */
  44. public function __invoke(Request $request)
  45. {
  46. $user = Common::ensureLoggedIn();
  47. $key = Feed::cacheKey($user);
  48. $feeds = Feed::getFeeds($user);
  49. $form_definitions = [];
  50. foreach ($feeds as $feed) {
  51. $md5 = md5($feed->getUrl());
  52. $form_definitions[] = [$md5 . '-url', TextType::class, ['data' => $feed->getUrl(), 'label' => _m('URL'), 'block_prefix' => 'row_url']];
  53. $form_definitions[] = [$md5 . '-order', IntegerType::class, ['data' => $feed->getOrdering(), 'label' => _m('Order'), 'block_prefix' => 'row_order']];
  54. $form_definitions[] = [$md5 . '-title', TextType::class, ['data' => $feed->getTitle(), 'label' => _m('Title'), 'block_prefix' => 'row_title']];
  55. $form_definitions[] = [$md5 . '-remove', SubmitType::class, ['label' => _m('Remove'), 'block_prefix' => 'row_remove']];
  56. }
  57. $form_definitions[] = ['url', TextType::class, ['label' => _m('New feed'), 'required' => false]];
  58. $form_definitions[] = ['order', IntegerType::class, ['label' => _m('Order'), 'data' => (\count($form_definitions) / 4) + 1]];
  59. $form_definitions[] = ['title', TextType::class, ['label' => _m('Title'), 'required' => false]];
  60. $form_definitions[] = ['add', SubmitType::class, ['label' => _m('Add')]];
  61. $form_definitions[] = ['update_exisiting', SubmitType::class, ['label' => _m('Update existing')]];
  62. $form_definitions[] = ['reset', SubmitType::class, ['label' => _m('Reset to default values')]];
  63. $form = Form::create($form_definitions);
  64. $form->handleRequest($request);
  65. if ($form->isSubmitted() && $form->isValid()) {
  66. array_pop($form_definitions);
  67. array_pop($form_definitions);
  68. array_pop($form_definitions);
  69. array_pop($form_definitions);
  70. array_pop($form_definitions);
  71. $data = $form->getData();
  72. /** @var SubmitButton $update_existing */
  73. $update_existing = $form->get('update_exisiting');
  74. if ($update_existing->isClicked()) {
  75. // Each feed has a URL, an order and a title
  76. $feeds_data = array_chunk($data, 3, preserve_keys: true);
  77. // The last three would be the new one
  78. array_pop($feeds_data);
  79. // Sort by the order
  80. usort($feeds_data, fn ($fd_l, $fd_r) => next($fd_l) <=> next($fd_r));
  81. // Make the order sequential
  82. $order = 1;
  83. foreach ($feeds_data as $i => $fd) {
  84. next($fd);
  85. $feeds_data[$i][key($fd)] = $order++;
  86. }
  87. // Update the fields in the corresponding feed
  88. foreach ($feeds_data as $fd) {
  89. $md5 = str_replace('-url', '', array_key_first($fd));
  90. $feed = F\first($feeds, fn ($f) => md5($f->getUrl()) === $md5);
  91. $feed->setUrl($fd[$md5 . '-url']);
  92. $feed->setOrdering($fd[$md5 . '-order']);
  93. $feed->setTitle($fd[$md5 . '-title']);
  94. DB::merge($feed);
  95. }
  96. DB::flush();
  97. Cache::delete($key);
  98. throw new RedirectException();
  99. }
  100. // TODO fix orderings when removing
  101. // Remove feed
  102. foreach ($form_definitions as [$field, $type, $opts]) {
  103. if (str_ends_with($field, '-url')) {
  104. $remove_id = str_replace('-url', '-remove', $field);
  105. /** @var SubmitButton $remove_button */
  106. $remove_button = $form->get($remove_id);
  107. if ($remove_button->isClicked()) {
  108. // @phpstan-ignore-next-line -- Doesn't quite understand that _this_ $opts for the current $form_definitions does have 'data'
  109. DB::remove(DB::getReference('feed', ['actor_id' => $user->getId(), 'url' => $opts['data']]));
  110. DB::flush();
  111. Cache::delete($key);
  112. throw new RedirectException();
  113. }
  114. }
  115. }
  116. /** @var SubmitButton $reset_button */
  117. $reset_button = $form->get('reset');
  118. if ($reset_button->isClicked()) {
  119. F\map(DB::findBy('feed', ['actor_id' => $user->getId()]), fn ($f) => DB::remove($f));
  120. DB::flush();
  121. Cache::delete($key);
  122. Feed::createDefaultFeeds($user->getId(), $user);
  123. DB::flush();
  124. throw new RedirectException();
  125. }
  126. // Add feed
  127. try {
  128. $match = Router::match($data['url']);
  129. $route = $match['_route'];
  130. $controller = $match['_controller'];
  131. if (!is_subclass_of($controller, FeedController::class)) {
  132. throw new ClientException(_m('The page with url "{url}" is not a valid feed', ['{url}' => $data['url']]));
  133. }
  134. DB::persist(Feed::create([
  135. 'actor_id' => $user->getId(),
  136. 'url' => $data['url'],
  137. 'route' => $route,
  138. 'title' => $data['title'],
  139. 'ordering' => $data['order'],
  140. ]));
  141. DB::flush();
  142. Cache::delete($key);
  143. throw new RedirectException();
  144. } catch (ResourceNotFoundException) {
  145. throw new ClientException(_m('Invalid route with url "{url}"', ['{url}' => $data['url']]), code: 404);
  146. }
  147. }
  148. return [
  149. '_template' => 'left_panel/edit_feeds.html.twig',
  150. 'edit_feeds' => $form->createView(),
  151. ];
  152. }
  153. }