| Code Coverage | ||||||||||
| Classes and Traits | Functions and Methods | Lines | ||||||||
| Total |  | 100.00% | 1 / 1 |  | 100.00% | 4 / 4 | CRAP |  | 100.00% | 35 / 35 | 
| Controller |  | 100.00% | 1 / 1 |  | 100.00% | 4 / 4 | 21 |  | 100.00% | 35 / 35 | 
| __construct |  | 100.00% | 1 / 1 | 1 |  | 100.00% | 2 / 2 | |||
| __invoke | n/a | 0 / 0 | 2 | n/a | 0 / 0 | |||||
| onKernelController |  | 100.00% | 1 / 1 | 1 |  | 100.00% | 7 / 7 | |||
| onKernelView |  | 100.00% | 1 / 1 | 7 |  | 100.00% | 19 / 19 | |||
| onKernelException | n/a | 0 / 0 | 5 | n/a | 0 / 0 | |||||
| getSubscribedEvents | n/a | 0 / 0 | 1 | n/a | 0 / 0 | |||||
| __call |  | 100.00% | 1 / 1 | 4 |  | 100.00% | 7 / 7 | |||
| 1 | <?php | 
| 2 | |
| 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 | |
| 20 | /** | 
| 21 | * Base class for controllers | 
| 22 | * | 
| 23 | * @package GNUsocial | 
| 24 | * @category Controller | 
| 25 | * | 
| 26 | * @author Hugo Sales <hugo@hsal.es> | 
| 27 | * @copyright 2020-2021 Free Software Foundation, Inc http://www.fsf.org | 
| 28 | * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later | 
| 29 | */ | 
| 30 | |
| 31 | namespace App\Core; | 
| 32 | |
| 33 | use function App\Core\I18n\_m; | 
| 34 | use App\Util\Common; | 
| 35 | use App\Util\Exception\ClientException; | 
| 36 | use App\Util\Exception\RedirectException; | 
| 37 | use Exception; | 
| 38 | use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; | 
| 39 | use Symfony\Component\EventDispatcher\EventSubscriberInterface; | 
| 40 | use Symfony\Component\HttpFoundation\JsonResponse; | 
| 41 | use Symfony\Component\HttpFoundation\RedirectResponse; | 
| 42 | use Symfony\Component\HttpFoundation\Request; | 
| 43 | use Symfony\Component\HttpFoundation\RequestStack; | 
| 44 | use Symfony\Component\HttpFoundation\Response; | 
| 45 | use Symfony\Component\HttpKernel\Event\ControllerEvent; | 
| 46 | use Symfony\Component\HttpKernel\Event\ExceptionEvent; | 
| 47 | use Symfony\Component\HttpKernel\Event\ViewEvent; | 
| 48 | use Symfony\Component\HttpKernel\KernelEvents; | 
| 49 | |
| 50 | class Controller extends AbstractController implements EventSubscriberInterface | 
| 51 | { | 
| 52 | private array $vars = []; | 
| 53 | private ?Request $request = null; | 
| 54 | |
| 55 | public function __construct(RequestStack $requestStack) | 
| 56 | { | 
| 57 | $this->request = $requestStack->getCurrentRequest(); | 
| 58 | } | 
| 59 | |
| 60 | /** | 
| 61 | * TODO: Not currently used, so not tested, but should be | 
| 62 | * | 
| 63 | * @codeCoverageIgnore | 
| 64 | */ | 
| 65 | public function __invoke(Request $request) | 
| 66 | { | 
| 67 | $this->request = $request; | 
| 68 | $class = get_called_class(); | 
| 69 | $method = 'on' . ucfirst(strtolower($request->getMethod())); | 
| 70 | if (method_exists($class, $method)) { | 
| 71 | return $class::$method($request, $this->vars); | 
| 72 | } else { | 
| 73 | return $class::handle($request, $this->vars); | 
| 74 | } | 
| 75 | } | 
| 76 | |
| 77 | /** | 
| 78 | * Symfony event when it's searching for which controller to use | 
| 79 | */ | 
| 80 | public function onKernelController(ControllerEvent $event) | 
| 81 | { | 
| 82 | $controller = $event->getController(); | 
| 83 | $request = $event->getRequest(); | 
| 84 | |
| 85 | $this->request = $request; | 
| 86 | $this->vars = ['controler' => $controller, 'request' => $request, 'have_user' => Common::user() !== null]; | 
| 87 | Event::handle('StartTwigPopulateVars', [&$this->vars]); | 
| 88 | |
| 89 | $event->stopPropagation(); | 
| 90 | return $event; | 
| 91 | } | 
| 92 | |
| 93 | /** | 
| 94 | * Symfony event when the controller result is not a Response object | 
| 95 | */ | 
| 96 | public function onKernelView(ViewEvent $event) | 
| 97 | { | 
| 98 | $request = $event->getRequest(); | 
| 99 | $response = $event->getControllerResult(); | 
| 100 | if (!is_array($response)) { | 
| 101 | // This means it's not one of our custom format responses, nothing to do | 
| 102 | // @codeCoverageIgnoreStart | 
| 103 | return $event; | 
| 104 | // @codeCoverageIgnoreEnd | 
| 105 | } | 
| 106 | |
| 107 | $this->vars = array_merge_recursive($this->vars, $response); | 
| 108 | Event::handle('EndTwigPopulateVars', [&$this->vars]); | 
| 109 | |
| 110 | $template = $this->vars['_template']; | 
| 111 | unset($this->vars['_template'], $this->vars['request'], $response['_template']); | 
| 112 | |
| 113 | // Respond in the the most preffered acceptable content type | 
| 114 | $accept = $request->getAcceptableContentTypes() ?: ['text/html']; | 
| 115 | $format = $request->getFormat($accept[0]); | 
| 116 | switch ($format) { | 
| 117 | case 'html': | 
| 118 | $event->setResponse($this->render($template, $this->vars)); | 
| 119 | break; | 
| 120 | case 'json': | 
| 121 | case 'jsonld': | 
| 122 | $event->setResponse(new JsonResponse($response)); | 
| 123 | break; | 
| 124 | default: | 
| 125 | throw new ClientException(_m('Unsupported format: {format}', ['format' => $format]), 406); // 406 Not Acceptable | 
| 126 | } | 
| 127 | |
| 128 | return $event; | 
| 129 | } | 
| 130 | |
| 131 | /** | 
| 132 | * Symfony event when the controller throws an exception | 
| 133 | * | 
| 134 | * @codeCoverageIgnore | 
| 135 | */ | 
| 136 | public function onKernelException(ExceptionEvent $event) | 
| 137 | { | 
| 138 | $except = $event->getThrowable(); | 
| 139 | if ($_ENV['APP_ENV'] !== 'dev') { | 
| 140 | // TODO: This is where our custom exception pages could go | 
| 141 | // $event->setResponse((new Response())->setStatusCode(455)); | 
| 142 | } | 
| 143 | do { | 
| 144 | if ($except instanceof RedirectException) { | 
| 145 | if (($redir = $except->redirect_response) != null) { | 
| 146 | $event->setResponse($redir); | 
| 147 | } else { | 
| 148 | $event->setResponse(new RedirectResponse($event->getRequest()->getPathInfo())); | 
| 149 | } | 
| 150 | } | 
| 151 | } while ($except != null && ($except = $except->getPrevious()) != null); | 
| 152 | return $event; | 
| 153 | } | 
| 154 | |
| 155 | /** | 
| 156 | * @codeCoverageIgnore | 
| 157 | */ | 
| 158 | public static function getSubscribedEvents() | 
| 159 | { | 
| 160 | return [ | 
| 161 | KernelEvents::CONTROLLER => 'onKernelController', | 
| 162 | KernelEvents::EXCEPTION => 'onKernelException', | 
| 163 | KernelEvents::VIEW => 'onKernelView', | 
| 164 | ]; | 
| 165 | } | 
| 166 | |
| 167 | /** | 
| 168 | * Get and convert GET parameters. Can be called with `int`, `bool`, etc | 
| 169 | * | 
| 170 | * @param string $name | 
| 171 | * | 
| 172 | * @throws ValidatorException | 
| 173 | */ | 
| 174 | public function __call(string $method, array $args) | 
| 175 | { | 
| 176 | $name = $args[0]; | 
| 177 | $value = $this->request->query->get($name); | 
| 178 | switch ($method) { | 
| 179 | case 'int': | 
| 180 | return (int) $value; | 
| 181 | case 'bool': | 
| 182 | return (bool) $value; | 
| 183 | default: | 
| 184 | // @codeCoverageIgnoreStart | 
| 185 | Log::critical($m = "Method '{$method}' on class App\\Core\\Controller not found (__call)"); | 
| 186 | throw new Exception($m); | 
| 187 | // @codeCoverageIgnoreEnd | 
| 188 | } | 
| 189 | } | 
| 190 | } |