Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
89.86% covered (warning)
89.86%
62 / 69
66.67% covered (warning)
66.67%
8 / 12
CRAP
0.00% covered (danger)
0.00%
0 / 1
Render
89.86% covered (warning)
89.86%
62 / 69
66.67% covered (warning)
66.67%
8 / 12
26.71
0.00% covered (danger)
0.00%
0 / 1
 withHtml
86.67% covered (warning)
86.67%
13 / 15
0.00% covered (danger)
0.00%
0 / 1
5.06
 html
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 withXml
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 withJson
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 json
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 xml
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 withLastModified
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 lastModified
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getCachableResponse
78.57% covered (warning)
78.57%
11 / 14
0.00% covered (danger)
0.00%
0 / 1
5.25
 redirect
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
4
 requireContainer
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 requireResponse
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
1<?php
2
3/**
4 * @package 115Mandant
5 * @copyright BerlinOnline Stadtportal GmbH & Co. KG
6 **/
7
8namespace BO\Slim;
9
10use App;
11use Psr\Container\ContainerInterface;
12use Psr\Http\Message\RequestInterface;
13use Psr\Http\Message\ResponseInterface;
14use BO\Slim\Response;
15
16class Render
17{
18    public static ?ContainerInterface $container = null;
19
20    public static ?RequestInterface $request = null;
21
22    public static ?ResponseInterface $response = null;
23
24    public static function withHtml(
25        ResponseInterface $response,
26        string $template,
27        array $parameters = [],
28        int $status = 200
29    ): ResponseInterface {
30        Profiler::add("Controller");
31        $response  = $response->withStatus($status);
32        $response  = $response->withHeader('Content-Type', 'text/html; charset=utf-8');
33        App::$templatedefaults['debug'] = App::DEBUG;
34        $request = self::$request;
35        if (null === $request && null !== self::$container) {
36            $request = self::$container->get('request');
37            $request = $request instanceof RequestInterface ? $request : null;
38        }
39        $templateRequest = $request instanceof Request ? $request : null;
40        App::$templatedefaults['includeUrl'] = Helper\TemplateUrls::resolveIncludeUrl($templateRequest);
41        App::$templatedefaults['baseUrl'] = Helper\TemplateUrls::resolveBaseUrl($templateRequest);
42        $parameters = array_merge(App::$templatedefaults, $parameters);
43        $response  = self::requireContainer()->get('view')->render($response, $template, $parameters);
44        Profiler::add("Rendering");
45        return $response ;
46    }
47
48    public static function html(string $template, array $parameters = [], int $status = 200): ResponseInterface
49    {
50        self::$response = self::withHtml(self::requireResponse(), $template, $parameters, $status);
51        return self::$response;
52    }
53
54    public static function withXml(ResponseInterface $response, string $data, int $status = 200): ResponseInterface
55    {
56        Profiler::add("Controller");
57        $response = $response->withStatus($status);
58        $response = $response->withHeader('Content-Type', 'application/soap+xml');
59        $response->getBody()->write($data);
60        Profiler::add("Rendering");
61        return $response;
62    }
63
64    public static function withJson(ResponseInterface $response, mixed $data, int $status = 200): ResponseInterface
65    {
66        Profiler::add("Controller");
67        $response = $response->withStatus($status);
68        $response = $response->withHeader('Content-Type', 'application/json');
69        $payload = json_encode($data, JSON_UNESCAPED_SLASHES);
70        $response->getBody()->write($payload !== false ? $payload : 'null');
71        Profiler::add("Rendering");
72        return $response;
73    }
74
75    public static function json(mixed $data, int $status = 200): ResponseInterface
76    {
77        self::$response = self::withJson(self::requireResponse(), $data, $status);
78        return self::$response;
79    }
80
81    public static function xml(string $data, int $status = 200): ResponseInterface
82    {
83        self::$response = self::withXml(self::requireResponse(), $data, $status);
84        return self::$response;
85    }
86
87    /**
88     * Add `Last-Modified` header to PSR7 response object
89     *
90     * @param ResponseInterface $response A PSR7 response object
91     * @param int|string        $date     A UNIX timestamp or a valid `strtotime()` string
92     * @param string $expires
93     *
94     * @return ResponseInterface           A new PSR7 response object with `Last-Modified` header
95     *
96     */
97    public static function withLastModified(
98        ResponseInterface $response,
99        string|int $date,
100        string $expires = '+5 minutes'
101    ): ResponseInterface {
102        return self::getCachableResponse($response, $date, $expires);
103    }
104
105    /**
106     * @param string|int $date strtotime interpreted
107     * @param string $expires strtotime interpreted
108     *
109     * @return ResponseInterface
110     */
111    public static function lastModified(string|int $date, string $expires = '+5 minutes'): ResponseInterface
112    {
113        self::$response = self::withLastModified(self::requireResponse(), $date, $expires);
114        return self::$response;
115    }
116
117    /**
118     * @param string|int $date strtotime interpreted
119     * @param string $expires strtotime interpreted
120     *
121     * @return ResponseInterface
122     */
123    public static function getCachableResponse(
124        ResponseInterface $response,
125        string|int $date,
126        string $expires = '+5 minutes'
127    ): ResponseInterface {
128        if (is_int($date)) {
129            $timestamp = $date !== 0 ? $date : time();
130        } else {
131            $parsed = strtotime($date);
132            $timestamp = $parsed !== false ? $parsed : time();
133        }
134
135        $expireTs = strtotime($expires);
136        if ($expireTs === false) {
137            $expires = '+' . $expires . ' seconds';
138            $maxAge = (int) $expires;
139        } else {
140            $maxAge = $expireTs - time();
141        }
142        $response = $response->withAddedHeader('Cache-Control', 'max-age=' . $maxAge);
143        $cache = self::requireContainer()->get('cache');
144        $response = $cache->withExpires($response, $expires);
145        $response = $cache->withLastModified($response, $timestamp);
146
147        return $response;
148    }
149
150    /**
151     * @param string $route_name
152     * @param array $arguments parameters in the route path
153     * @param array|null $parameter parameters to append with "?"
154     * @param int $statuscode see an HTTP reference
155     */
156    public static function redirect(
157        string $route_name,
158        array $arguments,
159        ?array $parameter = null,
160        int $statuscode = 302
161    ): Response {
162        Profiler::add("Controller");
163
164        $url = App::$slim->urlFor($route_name, $arguments);
165        $sanitized = Helper::proxySanitizeUri($url);
166        $url = is_string($sanitized) ? $sanitized : $url;
167        $url = preg_replace('#^.*?(https?://)#', '\1', $url) ?? $url; // allow http:// routes
168        if ($parameter !== null && $parameter !== []) {
169            $url .= '?' . http_build_query($parameter);
170        }
171
172        $response = App::$slim->getResponseFactory()->createResponse($statuscode);
173        $response = self::requireContainer()->get('cache')->denyCache($response);
174        /** @var Response $response */
175        $response = $response->withHeader('Location', $url);
176
177        return $response->withAddedHeader('Cache-Control', 'max-age=0');
178    }
179
180    private static function requireContainer(): ContainerInterface
181    {
182        $container = App::$slim->getContainer();
183        if (!$container instanceof ContainerInterface) {
184            throw new \RuntimeException('Slim container is not initialized');
185        }
186        return $container;
187    }
188
189    private static function requireResponse(): ResponseInterface
190    {
191        if (!self::$response instanceof ResponseInterface) {
192            throw new \RuntimeException('Render response is not initialized');
193        }
194        return self::$response;
195    }
196}