Passed
Push — master ( b22f9f...a1c8c9 )
by Andreas
09:45
created

on_response()   B

Complexity

Conditions 9
Paths 18

Size

Total Lines 51
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 22
CRAP Score 11.9991

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 9
eloc 33
c 1
b 0
f 0
nc 18
nop 1
dl 0
loc 51
ccs 22
cts 33
cp 0.6667
crap 11.9991
rs 8.0555

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @package midcom.services
4
 * @author The Midgard Project, http://www.midgard-project.org
5
 * @copyright The Midgard Project, http://www.midgard-project.org
6
 * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License
7
 */
8
9
use Symfony\Component\HttpFoundation\Response;
10
use Symfony\Component\HttpFoundation\Request;
11
use Symfony\Component\HttpFoundation\BinaryFileResponse;
12
use Symfony\Component\HttpKernel\Event\ResponseEvent;
13
use Symfony\Component\HttpKernel\Event\RequestEvent;
14
use Symfony\Component\Cache\Adapter\AdapterInterface;
15
16
/**
17
 * This is the Output Caching Engine of MidCOM. It will intercept page output,
18
 * map it using the currently used URL and use the cached output on subsequent
19
 * requests.
20
 *
21
 * <b>Important note for application developers</b>
22
 *
23
 * Please read the documentation of the following functions thoroughly:
24
 *
25
 * - midcom_services_cache_module_content::no_cache();
26
 * - midcom_services_cache_module_content::uncached();
27
 * - midcom_services_cache_module_content::expires();
28
 * - midcom_services_cache_module_content::invalidate_all();
29
 * - midcom_services_cache_module_content::content_type();
30
 * - midcom_services_cache_module_content::enable_live_mode();
31
 *
32
 * You have to use these functions everywhere where it is applicable or the cache
33
 * will not work reliably.
34
 *
35
 * <b>Caching strategy</b>
36
 *
37
 * The cache takes three parameters into account when storing in or retrieving from
38
 * the cache: The current User ID, the current language and the request's URL.
39
 *
40
 * Only on a complete match a cached page is displayed, which should take care of any
41
 * permission checks done on the page. When you change the permissions of users, you
42
 * need to manually invalidate the cache though, as MidCOM currently cannot detect
43
 * changes like this (of course, this is true if and only if you are not using a
44
 * MidCOM to change permissions).
45
 *
46
 * When the HTTP request is not cacheable, the caching engine will automatically and
47
 * transparently go into no_cache mode for that request only. This feature
48
 * does neither invalidate the cache or drop the page that would have been delivered
49
 * normally from the cache. If you change the content, you need to do that yourself.
50
 *
51
 * HTTP 304 Not Modified support is built into this module, and will send a 304 reply if applicable.
52
 *
53
 * <b>Module configuration (see also midcom_config)</b>
54
 *
55
 * - <i>boolean cache_module_content_uncached</i>: Set this to true to prevent the saving of cached pages. This is useful
56
 *   for development work, as all other headers (like E-Tag or Last-Modified) are generated
57
 *   normally. See the uncached() and _uncached members.
58
 *
59
 * @package midcom.services
60
 */
61
class midcom_services_cache_module_content extends midcom_services_cache_module
62
{
63
    /**
64
     * Flag, indicating whether the current page may be cached. If
65
     * false, the usual no-cache headers will be generated.
66
     */
67
    private bool $_no_cache = false;
68
69
    /**
70
     * An array storing all HTTP headers registered through register_sent_header().
71
     * They will be sent when a cached page is delivered.
72
     */
73
    private array $_sent_headers = [];
74
75
    /**
76
     * Set this to true if you want to inhibit storage of the generated pages in
77
     * the cache database. All other headers will be created as usual though, so
78
     * 304 processing will kick in for example.
79
     */
80
    private bool $_uncached = false;
81
82
    /**
83
     * Controls cache headers strategy
84
     * 'no-cache' activates no-cache mode that actively tries to circumvent all caching
85
     * 'revalidate' is the default which sets must-revalidate. Expiry defaults to current time, so this effectively behaves like no-cache if expires() was not called
86
     * 'public' and 'private' enable caching with the cache-control header of the same name, default expiry timestamps are generated using the default_lifetime
87
     */
88
    private string $_headers_strategy = 'revalidate';
89
90
    /**
91
     * Controls cache headers strategy for authenticated users, needed because some proxies store cookies, too,
92
     * making a horrible mess when used by mix of authenticated and non-authenticated users
93
     *
94
     * @see $_headers_strategy
95
     */
96
    private string $_headers_strategy_authenticated = 'private';
97
98
    /**
99
     * Default lifetime of page for public/private headers strategy
100
     * When generating the default expires header this is added to time().
101
     */
102
    private int $_default_lifetime = 0;
103
104
    /**
105
     * Default lifetime of page for public/private headers strategy for authenticated users
106
     *
107
     * @see $_default_lifetime
108
     */
109
    private int $_default_lifetime_authenticated = 0;
110
111
    /**
112
     * A cache backend used to store the actual cached pages.
113
     */
114
    private AdapterInterface $_data_cache;
115
116
    /**
117
     * GUIDs loaded per context in this request
118
     */
119
    private array $context_guids = [];
120
121
    private midcom_config $config;
122
123
    /**
124
     * Initialize the cache.
125
     *
126
     * The first step is to initialize the cache backends. The names of the
127
     * cache backends used for meta and data storage are derived from the name
128
     * defined for this module (see the 'name' configuration parameter above).
129
     * The name is used directly for the meta data cache, while the actual data
130
     * is stored in a backend postfixed with '_data'.
131
     *
132
     * After core initialization, the module checks for a cache hit (which might
133
     * trigger the delivery of the cached page and exit) and start the output buffer
134
     * afterwards.
135
     */
136 2
    public function __construct(midcom_config $config, AdapterInterface $backend, AdapterInterface $data_cache)
137
    {
138 2
        parent::__construct($backend);
139 2
        $this->config = $config;
140 2
        $this->_data_cache = $data_cache;
141
142 2
        $this->_uncached = $config->get('cache_module_content_uncached');
143 2
        $this->_headers_strategy = $this->get_strategy('cache_module_content_headers_strategy');
144 2
        $this->_headers_strategy_authenticated = $this->get_strategy('cache_module_content_headers_strategy_authenticated');
145 2
        $this->_default_lifetime = (int)$config->get('cache_module_content_default_lifetime');
146 2
        $this->_default_lifetime_authenticated = (int)$config->get('cache_module_content_default_lifetime_authenticated');
147
148 2
        if ($this->_headers_strategy == 'no-cache') {
149
            // we can't call no_cache() here, because it would try to call back to this class via the global getter
150
            $header = 'Cache-Control: no-store, no-cache, must-revalidate';
151
            $this->register_sent_header($header);
152
            midcom_compat_environment::header($header);
153
            $this->_no_cache = true;
154
        }
155
    }
156
157 353
    public function on_request(RequestEvent $event)
158
    {
159 353
        $request = $event->getRequest();
160 353
        if ($event->isMainRequest()) {
161
            /* Load and start up the cache system, this might already end the request
162
             * on a content cache hit. Note that the cache check hit depends on the i18n and auth code.
163
             */
164 1
            if ($response = $this->_check_hit($request)) {
165 1
                $event->setResponse($response);
166
            }
167 352
        } elseif ($content = $this->check_dl_hit($request)) {
168
            $event->setResponse(new Response($content));
169
        }
170
    }
171
172
    /**
173
     * This function holds the cache hit check mechanism. It searches the requested
174
     * URL in the cache database. If found, it checks, whether the cache page has
175
     * expired. If not, the response is returned. In all other cases this method simply
176
     * returns void.
177
     *
178
     * Also, any HTTP POST request will automatically circumvent the cache so that
179
     * any component can process the request. It will set no_cache automatically
180
     * to avoid any cache pages being overwritten by, for example, search results.
181
     *
182
     * Note, that HTTP GET is <b>not</b> checked this way, as GET requests can be
183
     * safely distinguished by their URL.
184
     *
185
     * @return void|Response
186
     */
187 1
    private function _check_hit(Request $request)
188
    {
189 1
        if (!$request->isMethodCacheable()) {
190
            debug_add('Request method is not cacheable, setting no_cache');
191
            $this->no_cache();
192
            return;
193
        }
194
195
        // Check for uncached operation
196 1
        if ($this->_uncached) {
197
            debug_add("Uncached mode");
198
            return;
199
        }
200
201
        // Check that we have cache for the identifier
202 1
        $request_id = $this->generate_request_identifier($request);
203
        // Load metadata for the content identifier connected to current request
204 1
        $content_id = $this->backend->getItem($request_id);
205 1
        if (!$content_id->isHit()) {
206 1
            debug_add("MISS {$request_id}");
207
            // We have no information about content cached for this request
208 1
            return;
209
        }
210 1
        $content_id = $content_id->get();
211 1
        debug_add("HIT {$request_id}");
212
213 1
        $headers = $this->backend->getItem($content_id);
214 1
        if (!$headers->isHit()) {
215
            debug_add("MISS meta_cache {$content_id}");
216
            // Content cache data is missing
217
            return;
218
        }
219
220 1
        debug_add("HIT {$content_id}");
221
222 1
        $response = new Response('', Response::HTTP_OK, $headers->get());
223 1
        if (!$response->isNotModified($request)) {
224 1
            $content = $this->_data_cache->getItem($content_id);
225 1
            if (!$content->isHit()) {
226
                debug_add("Current page is in not in the data cache, possible ghost read.", MIDCOM_LOG_WARN);
227
                return;
228
            }
229 1
            $response->setContent($content->get());
230
        }
231
        // disable cache writing in on_response
232 1
        $this->_no_cache = true;
233 1
        return $response;
234
    }
235
236
    /**
237
     * This completes the output caching, post-processes it and updates the cache databases accordingly.
238
     *
239
     * The first step is to check against _no_cache pages, which will be delivered immediately
240
     * without any further post processing. Afterwards, the system will complete the sent
241
     * headers by adding all missing headers. Note, that E-Tag will be generated always
242
     * automatically, you must not set this in your component.
243
     *
244
     * If the midcom configuration option cache_uncached is set or the corresponding runtime function
245
     * has been called, the cache file will not be written, but the header stuff will be added like
246
     * usual to allow for browser-side caching.
247
     *
248
     * @param ResponseEvent $event The request object
249
     */
250 354
    public function on_response(ResponseEvent $event)
251
    {
252 354
        $response = $event->getResponse();
253 354
        $request = $event->getRequest();
254 354
        if ($response->isServerError()) {
255 2
            return;
256
        }
257 353
        if (!$event->isMainRequest()) {
258 352
            $this->store_dl_content($request, $response);
259 352
            return;
260
        }
261 1
        if ($response instanceof BinaryFileResponse) {
262
            $this->cache_control_headers($response);
263
            // Store metadata in cache so _check_hit() can help us
264
            $this->write_meta_cache('A-' . $response->getEtag(), $event->getRequest(), $response);
265
            return;
266
        }
267 1
        foreach ($this->_sent_headers as $header => $value) {
268
            // This can happen in streamed responses which enable_live_mode
269
            if (!headers_sent()) {
270
                header_remove($header);
271
            }
272
            $response->headers->set($header, $value);
273
        }
274 1
        if ($this->_no_cache) {
275
            $response->prepare($request);
276
            return;
277
        }
278
279 1
        $cache_data = $response->getContent();
280
281
        // Register additional Headers around the current output request
282 1
        $this->complete_sent_headers($response);
283 1
        $response->prepare($request);
284
285
        // Generate E-Tag header.
286 1
        if (empty($cache_data)) {
287
            $etag = md5(serialize($response->headers->all()));
288
        } else {
289 1
            $etag = md5($cache_data);
290
        }
291 1
        $response->setEtag($etag);
292
293 1
        if ($this->_uncached) {
294
            debug_add('Not writing cache file, we are in uncached operation mode.');
295
            return;
296
        }
297 1
        $content_id = 'C-' . $etag;
298 1
        $this->write_meta_cache($content_id, $request, $response);
299 1
        $item = $this->_data_cache->getItem($content_id);
300 1
        $this->_data_cache->save($item->set($cache_data));
301
    }
302
303
    /**
304
     * Generate a valid cache identifier for a context of the current request
305
     */
306 2
    private function generate_request_identifier(Request $request) : string
307
    {
308 2
        $context = $request->attributes->get('context')->id;
309
        // Cache the request identifier so that it doesn't change between start and end of request
310 2
        static $identifier_cache = [];
311 2
        if (isset($identifier_cache[$context])) {
312 1
            return $identifier_cache[$context];
313
        }
314
315 2
        $identifier_source = '';
316
317 2
        $cache_strategy = $this->config->get('cache_module_content_caching_strategy');
318
319
        switch ($cache_strategy) {
320 2
            case 'memberships':
321
                if (!midcom::get()->auth->is_valid_user()) {
322
                    $identifier_source .= 'USER=ANONYMOUS';
323
                    break;
324
                }
325
326
                $mc = new midgard_collector('midgard_member', 'uid', midcom_connection::get_user());
327
                $mc->set_key_property('gid');
328
                $mc->execute();
329
                $gids = $mc->list_keys();
330
                $identifier_source .= 'GROUPS=' . implode(',', array_keys($gids));
331
                break;
332 2
            case 'public':
333
                $identifier_source .= 'USER=EVERYONE';
334
                break;
335 2
            case 'user':
336
            default:
337 2
                $identifier_source .= 'USER=' . midcom_connection::get_user();
338 2
                break;
339
        }
340
341 2
        $identifier_source .= ';URL=' . $request->getUri();
342 2
        debug_add("Generating context {$context} request-identifier from: {$identifier_source}");
343
344 2
        $identifier_cache[$context] = 'R-' . md5($identifier_source);
345 2
        return $identifier_cache[$context];
346
    }
347
348 2
    private function get_strategy(string $name) : string
349
    {
350 2
        $strategy = strtolower($this->config->get($name));
0 ignored issues
show
Bug introduced by
It seems like $this->config->get($name) can also be of type null; however, parameter $string of strtolower() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

350
        $strategy = strtolower(/** @scrutinizer ignore-type */ $this->config->get($name));
Loading history...
351 2
        $allowed = ['no-cache', 'revalidate', 'public', 'private'];
352 2
        if (!in_array($strategy, $allowed)) {
353
            throw new midcom_error($name . ' is not valid, try ' . implode(', ', $allowed));
354
        }
355 2
        return $strategy;
356
    }
357
358
    /**
359
     * Call this, if the currently processed output must not be cached for any
360
     * reason. Dynamic pages with sensitive content are a candidate for this
361
     * function.
362
     *
363
     * Note, that this will prevent <i>any</i> content invalidation related headers
364
     * like E-Tag to be generated automatically, and that the appropriate
365
     * no-store/no-cache headers from HTTP 1.1 and HTTP 1.0 will be sent automatically.
366
     * This means that there will also be no 304 processing.
367
     *
368
     * You should use this only for sensitive content. For simple dynamic output,
369
     * you are strongly encouraged to use the less strict uncached() function.
370
     *
371
     * @see uncached()
372
     */
373 199
    public function no_cache(?Response $response = null)
374
    {
375 199
        $settings = 'no-store, no-cache, must-revalidate';
376
        // PONDER: Send expires header (set to long time in past) as well ??
377
378 199
        if ($response) {
379
            $response->headers->set('Cache-Control', $settings);
380 199
        } elseif (!$this->_no_cache) {
381
            if (headers_sent()) {
382
                debug_add('Warning, we should move to no_cache but headers have already been sent, skipping header transmission.', MIDCOM_LOG_ERROR);
383
            } else {
384
                midcom::get()->header('Cache-Control: ' . $settings);
385
            }
386
        }
387 199
        $this->_no_cache = true;
388
    }
389
390
    /**
391
     * Call this, if the currently processed output must not be cached for any
392
     * reason. Dynamic pages or form processing results are the usual candidates
393
     * for this mode.
394
     *
395
     * Note, that this will still keep the caching engine active so that it can
396
     * add the usual headers (ETag, Expires ...) in respect to the no_cache flag.
397
     * As well, at the end of the processing, the usual 304 checks are done, so if
398
     * your page doesn't change in respect of E-Tag and Last-Modified, only a 304
399
     * Not Modified reaches the client.
400
     *
401
     * Essentially, no_cache behaves the same way as if the uncached configuration
402
     * directive is set to true, it is just limited to a single request.
403
     *
404
     * If you need a higher level of client side security, to avoid storage of sensitive
405
     * information on the client side, you should use no_cache instead.
406
     *
407
     * @see no_cache()
408
     */
409 4
    public function uncached(bool $uncached = true)
410
    {
411 4
        $this->_uncached = $uncached;
412
    }
413
414
    /**
415
     * Sets the content type for the current page. The required HTTP Headers for
416
     * are automatically generated, so, to the contrary of expires, you just have
417
     * to set this header accordingly.
418
     *
419
     * This is usually set automatically by MidCOM for all regular HTML output and
420
     * for all attachment deliveries. You have to adapt it only for things like RSS
421
     * output.
422
     */
423 8
    public function content_type(string $type)
424
    {
425 8
        midcom::get()->header('Content-Type: ' . $type);
426
    }
427
428
    /**
429
     * Put the cache into a "live mode". This will disable the
430
     * cache during runtime, correctly flushing the output buffer (if it's not empty)
431
     * and sending cache control headers.
432
     *
433
     * The midcom-exec URL handler of the core will automatically enable live mode.
434
     *
435
     * @see midcom_application::_exec_file()
436
     */
437
    public function enable_live_mode()
438
    {
439
        $this->no_cache();
440
        Response::closeOutputBuffers(0, ob_get_length() > 0);
441
    }
442
443
    /**
444
     * Store a sent header into the cache database, so that it will
445
     * be resent when the cache page is delivered. midcom_application::header()
446
     * will automatically call this function, you need to do this only if you use
447
     * the PHP header function.
448
     */
449 17
    public function register_sent_header(string $header)
450
    {
451 17
        if (str_contains($header, ': ')) {
452 17
            [$header, $value] = explode(': ', $header, 2);
453 17
            $this->_sent_headers[$header] = $value;
454
        }
455
    }
456
457
    /**
458
     * Looks for list of content and request identifiers paired with the given guid
459
     * and removes all of those from the caches.
460
     *
461
     * {@inheritDoc}
462
     */
463 321
    public function invalidate(string $guid, ?midcom_core_dbaobject $object = null)
464
    {
465 321
        $guidmap = $this->backend->getItem($guid);
466 321
        if (!$guidmap->isHit()) {
467 321
            debug_add("No entry for {$guid} in meta cache, ignoring invalidation request.");
468 321
            return;
469
        }
470
471
        foreach ($guidmap->get() as $content_id) {
472
            $this->backend->deleteItem($content_id);
473
            $this->_data_cache->deleteItem($content_id);
474
        }
475
    }
476
477
    public function invalidate_all()
478
    {
479
        parent::invalidate_all();
480
        $this->_data_cache->clear();
481
    }
482
483
    /**
484
     * All objects loaded within a request are stored into a list for cache invalidation purposes
485
     */
486 410
    public function register(string $guid)
487
    {
488
        // Check for uncached operation
489 410
        if ($this->_uncached) {
490 409
            return;
491
        }
492
493 1
        $context = midcom_core_context::get()->id;
494 1
        if ($context != 0) {
495
            // We're in a dynamic_load, register it for that as well
496 1
            $this->context_guids[$context] ??= [];
497 1
            $this->context_guids[$context][] = $guid;
498
        }
499
500
        // Register all GUIDs also to the root context
501 1
        $this->context_guids[0] ??= [];
502 1
        $this->context_guids[0][] = $guid;
503
    }
504
505
    /**
506
     * Writes meta-cache entry from context data using given content id
507
     * Used to be part of on_request, but needed by serve-attachment method in midcom_core_urlmethods as well
508
     */
509 1
    private function write_meta_cache(string $content_id, Request $request, Response $response)
510
    {
511 1
        if (   $this->_uncached
512 1
            || $this->_no_cache) {
513
            return;
514
        }
515
516
        // Construct cache identifier
517 1
        $request_id = $this->generate_request_identifier($request);
518
519 1
        $item = $this->backend->getItem($request_id);
520 1
        $this->backend->save($item->set($content_id));
521 1
        $item2 = $this->backend->getItem($content_id);
522 1
        $this->backend->save($item2->set($response->headers->all()));
523
524
        // Cache where the object have been
525 1
        $this->store_context_guid_map($content_id, $request_id);
526
    }
527
528 2
    private function store_context_guid_map(string $content_id, string $request_id)
529
    {
530 2
        $context = midcom_core_context::get()->id;
531
        // non-existent context
532 2
        if (!array_key_exists($context, $this->context_guids)) {
533 1
            return;
534
        }
535
536 1
        $maps = $this->backend->getItems($this->context_guids[$context]);
537 1
        if ($maps instanceof Traversable) {
538 1
            $maps = iterator_to_array($maps);
539
        }
540 1
        $to_save = [];
541 1
        foreach ($this->context_guids[$context] as $guid) {
542 1
            $guidmap = $maps[$guid]->get() ?? [];
543
544 1
            if (!in_array($content_id, $guidmap)) {
545 1
                $guidmap[] = $content_id;
546 1
                $to_save[$guid] = $maps[$guid]->set($guidmap);
547
            }
548
549 1
            if (!in_array($request_id, $guidmap)) {
550 1
                $guidmap[] = $request_id;
551 1
                $to_save[$guid] = $maps[$guid]->set($guidmap);
552
            }
553
        }
554
555 1
        foreach ($to_save as $item) {
556 1
            $this->backend->save($item);
557
        }
558
    }
559
560 352
    private function check_dl_hit(Request $request)
561
    {
562 352
        if ($this->_no_cache) {
563 352
            return false;
564
        }
565
        $dl_request_id = 'DL' . $this->generate_request_identifier($request);
566
        $dl_content_id = $this->backend->getItem($dl_request_id);
567
        if (!$dl_content_id->isHit()) {
568
            return false;
569
        }
570
571
        return $this->_data_cache->getItem($dl_content_id->get())->get();
572
    }
573
574 352
    private function store_dl_content(Request $request, Response $response)
575
    {
576 352
        if (   $this->_no_cache
577 352
            || $this->_uncached) {
578 351
            return;
579
        }
580 1
        $dl_cache_data = $response->getContent();
581 1
        $dl_request_id = 'DL' . $this->generate_request_identifier($request);
582 1
        $dl_content_id = 'DLC-' . md5($dl_cache_data);
583
584 1
        $item = $this->backend->getItem($dl_request_id)
585 1
            ->set($dl_content_id)
586 1
            ->expiresAfter($this->_default_lifetime);
587 1
        $this->backend->save($item);
588 1
        $item = $this->_data_cache->getItem($dl_content_id)
589 1
            ->set($dl_cache_data)
590 1
            ->expiresAfter($this->_default_lifetime);
591 1
        $this->_data_cache->save($item);
592
593
        // Cache where the object have been
594 1
        $this->store_context_guid_map($dl_content_id, $dl_request_id);
595
    }
596
597
    /**
598
     * This little helper ensures that the headers Content-Length
599
     * and Last-Modified are present. The lastmod timestamp is taken out of the
600
     * component context information if it is populated correctly there; if not, the
601
     * system time is used instead.
602
     *
603
     * To force browsers to revalidate the page on every request (login changes would
604
     * go unnoticed otherwise), the Cache-Control header max-age=0 is added automatically.
605
     */
606 1
    private function complete_sent_headers(Response $response)
607
    {
608 1
        if (!$response->getLastModified()) {
609
            /* Determine Last-Modified using MidCOM's component context,
610
             * Fallback to time() if this fails.
611
             */
612 1
            $time = midcom_core_context::get()->get_key(MIDCOM_CONTEXT_LASTMODIFIED) ?: time();
613 1
            $response->setLastModified(DateTime::createFromFormat('U', (string) $time));
614
        }
615
616
        /* TODO: Doublecheck the way this is handled, now we just don't send it
617
         * if headers_strategy implies caching */
618 1
        if (   !$response->headers->has('Content-Length')
619 1
            && !in_array($this->_headers_strategy, ['public', 'private'])) {
620 1
            $response->headers->set("Content-Length", strlen($response->getContent()));
621
        }
622
623 1
        $this->cache_control_headers($response);
624
    }
625
626 1
    private function cache_control_headers(Response $response)
627
    {
628
        // Just to be sure not to mess the headers sent by no_cache in case it was called
629 1
        if ($this->_no_cache) {
630
            $this->no_cache($response);
631
        } else {
632
            // Add Expiration and Cache Control headers
633 1
            $strategy = $this->_headers_strategy;
634 1
            $default_lifetime = $this->_default_lifetime;
635 1
            if (midcom::get()->auth->is_valid_user()) {
636
                $strategy = $this->_headers_strategy_authenticated;
637
                $default_lifetime = $this->_default_lifetime_authenticated;
638
            }
639
640 1
            $now = $expires = time();
641 1
            if ($strategy != 'revalidate') {
642
                $expires += $default_lifetime;
643
                if ($strategy == 'private') {
644
                    $response->setPrivate();
645
                } else {
646
                    $response->setPublic();
647
                }
648
            }
649 1
            $max_age = $expires - $now;
650
651 1
            $response
652 1
                ->setExpires(DateTime::createFromFormat('U', $expires))
653 1
                ->setMaxAge($max_age);
654 1
            if ($max_age == 0) {
655 1
                $response->headers->addCacheControlDirective('must-revalidate');
656
            }
657
        }
658
    }
659
}
660