Completed
Push — master ( 6f2da7...e91e95 )
by Andreas
23:54
created

midcom_services_cache_module_content::_check_hit()   B

Complexity

Conditions 9
Paths 15

Size

Total Lines 54
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 12.2886

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 9
eloc 31
c 3
b 0
f 0
nc 15
nop 1
dl 0
loc 54
ccs 21
cts 32
cp 0.6563
crap 12.2886
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 Doctrine\Common\Cache\CacheProvider;
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>string cache_module_content_name</i>: The name of the cache database to use. This should usually be tied to the actual
56
 *   MidCOM site to have exactly one cache per site. This is mandatory (and populated by a sensible default
57
 *   by midcom_config, see there for details).
58
 * - <i>boolean cache_module_content_uncached</i>: Set this to true to prevent the saving of cached pages. This is useful
59
 *   for development work, as all other headers (like E-Tag or Last-Modified) are generated
60
 *   normally. See the uncached() and _uncached members.
61
 *
62
 * @package midcom.services
63
 */
64
class midcom_services_cache_module_content extends midcom_services_cache_module
65
{
66
    /**
67
     * Flag, indicating whether the current page may be cached. If
68
     * false, the usual no-cache headers will be generated.
69
     *
70
     * @var boolean
71
     */
72
    private $_no_cache = false;
73
74
    /**
75
     * Page expiration in seconds. If null (unset), the page does
76
     * not expire.
77
     *
78
     * @var int
79
     */
80
    private $_expires;
81
82
    /**
83
     * An array storing all HTTP headers registered through register_sent_header().
84
     * They will be sent when a cached page is delivered.
85
     *
86
     * @var array
87
     */
88
    private $_sent_headers = [];
89
90
    /**
91
     * Set this to true if you want to inhibit storage of the generated pages in
92
     * the cache database. All other headers will be created as usual though, so
93
     * 304 processing will kick in for example.
94
     *
95
     * @var boolean
96
     */
97
    private $_uncached = false;
98
99
    /**
100
     * Controls cache headers strategy
101
     * 'no-cache' activates no-cache mode that actively tries to circumvent all caching
102
     * '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
103
     * 'public' and 'private' enable caching with the cache-control header of the same name, default expiry timestamps are generated using the default_lifetime
104
     *
105
     * @var string
106
     */
107
    private $_headers_strategy = 'revalidate';
108
109
    /**
110
     * Controls cache headers strategy for authenticated users, needed because some proxies store cookies, too,
111
     * making a horrible mess when used by mix of authenticated and non-authenticated users
112
     *
113
     * @see $_headers_strategy
114
     * @var string
115
     */
116
    private $_headers_strategy_authenticated = 'private';
117
118
    /**
119
     * Default lifetime of page for public/private headers strategy
120
     * When generating the default expires header this is added to time().
121
     *
122
     * @var int
123
     */
124
    private $_default_lifetime = 0;
125
126
    /**
127
     * Default lifetime of page for public/private headers strategy for authenticated users
128
     *
129
     * @see $_default_lifetime
130
     * @var int
131
     */
132
    private $_default_lifetime_authenticated = 0;
133
134
    /**
135
     * A cache backend used to store the actual cached pages.
136
     *
137
     * @var Doctrine\Common\Cache\CacheProvider
138
     */
139
    private $_data_cache;
140
141
    /**
142
     * GUIDs loaded per context in this request
143
     */
144
    private $context_guids = [];
145
146
    /**
147
     * @var midcom_config
148
     */
149
    private $config;
150
151
    /**
152
     * Initialize the cache.
153
     *
154
     * The first step is to initialize the cache backends. The names of the
155
     * cache backends used for meta and data storage are derived from the name
156
     * defined for this module (see the 'name' configuration parameter above).
157
     * The name is used directly for the meta data cache, while the actual data
158
     * is stored in a backend postfixed with '_data'.
159
     *
160
     * After core initialization, the module checks for a cache hit (which might
161
     * trigger the delivery of the cached page and exit) and start the output buffer
162
     * afterwards.
163
     */
164 1
    public function __construct(midcom_config $config, CacheProvider $backend, CacheProvider $data_cache)
165
    {
166 1
        parent::__construct($backend);
167 1
        $this->config = $config;
168 1
        $this->_data_cache = $data_cache;
169 1
        $this->_data_cache->setNamespace($backend->getNamespace());
170
171 1
        $this->_uncached = $config->get('cache_module_content_uncached');
172 1
        $this->_headers_strategy = $this->get_strategy('cache_module_content_headers_strategy');
173 1
        $this->_headers_strategy_authenticated = $this->get_strategy('cache_module_content_headers_strategy_authenticated');
174 1
        $this->_default_lifetime = (int)$config->get('cache_module_content_default_lifetime');
175 1
        $this->_default_lifetime_authenticated = (int)$config->get('cache_module_content_default_lifetime_authenticated');
176
177 1
        if ($this->_headers_strategy == 'no-cache') {
178
            // we can't call no_cache() here, because it would try to call back to this class via the global getter
179
            $header = 'Cache-Control: no-store, no-cache, must-revalidate';
180
            $this->register_sent_header($header);
181
            midcom_compat_environment::get()->header($header);
182
            $this->_no_cache = true;
183
        }
184 1
    }
185
186
    /**
187
     * @param RequestEvent $event
188
     */
189 338
    public function on_request(RequestEvent $event)
190
    {
191 338
        if ($event->isMasterRequest()) {
192 1
            $request = $event->getRequest();
193
            /* Load and start up the cache system, this might already end the request
194
             * on a content cache hit. Note that the cache check hit depends on the i18n and auth code.
195
             */
196 1
            if ($response = $this->_check_hit($request)) {
197 1
                $event->setResponse($response);
198
            }
199
        }
200 338
    }
201
202
    /**
203
     * This function holds the cache hit check mechanism. It searches the requested
204
     * URL in the cache database. If found, it checks, whether the cache page has
205
     * expired. If not, the response is returned. In all other cases this method simply
206
     * returns void.
207
     *
208
     * The midcom-cache-invalidate URL method is handled before checking for a cache hit.
209
     *
210
     * Also, any HTTP POST request will automatically circumvent the cache so that
211
     * any component can process the request. It will set no_cache automatically
212
     * to avoid any cache pages being overwritten by, for example, search results.
213
     *
214
     * Note, that HTTP GET is <b>not</b> checked this way, as GET requests can be
215
     * safely distinguished by their URL.
216
     *
217
     * @param Request $request The request object
218
     * @return void|Response
219
     */
220 1
    private function _check_hit(Request $request)
221
    {
222 1
        foreach (midcom_connection::get_url('argv') as $arg) {
223 1
            if ($arg == 'midcom-cache-invalidate') {
224
                // Don't cache this.
225
                debug_add("uncached: $arg");
226
                return;
227
            }
228
        }
229
230 1
        if (!$request->isMethodCacheable()) {
231
            debug_add('Request method is not cacheable, setting no_cache');
232
            $this->no_cache();
233
            return;
234
        }
235
236
        // Check for uncached operation
237 1
        if ($this->_uncached) {
238
            debug_add("Uncached mode");
239
            return;
240
        }
241
242
        // Check that we have cache for the identifier
243 1
        $request_id = $this->generate_request_identifier($request);
244
        // Load metadata for the content identifier connected to current request
245 1
        $content_id = $this->backend->fetch($request_id);
246 1
        if ($content_id === false) {
247 1
            debug_add("MISS {$request_id}");
248
            // We have no information about content cached for this request
249 1
            return;
250
        }
251 1
        debug_add("HIT {$request_id}");
252
253 1
        $headers = $this->backend->fetch($content_id);
254 1
        if ($headers === false) {
255
            debug_add("MISS meta_cache {$content_id}");
256
            // Content cache data is missing
257
            return;
258
        }
259
260 1
        debug_add("HIT {$content_id}");
261
262 1
        $response = new Response('', Response::HTTP_OK, $headers);
263 1
        if (!$response->isNotModified($request)) {
264 1
            $content = $this->_data_cache->fetch($content_id);
265 1
            if ($content === false) {
266
                debug_add("Current page is in not in the data cache, possible ghost read.", MIDCOM_LOG_WARN);
267
                return;
268
            }
269 1
            $response->setContent($content);
270
        }
271
        // disable cache writing in on_response
272 1
        $this->_no_cache = true;
273 1
        return $response;
274
    }
275
276
    /**
277
     * This completes the output caching, post-processes it and updates the cache databases accordingly.
278
     *
279
     * The first step is to check against _no_cache pages, which will be delivered immediately
280
     * without any further post processing. Afterwards, the system will complete the sent
281
     * headers by adding all missing headers. Note, that E-Tag will be generated always
282
     * automatically, you must not set this in your component.
283
     *
284
     * If the midcom configuration option cache_uncached is set or the corresponding runtime function
285
     * has been called, the cache file will not be written, but the header stuff will be added like
286
     * usual to allow for browser-side caching.
287
     *
288
     * @param ResponseEvent $event The request object
289
     */
290 339
    public function on_response(ResponseEvent $event)
291
    {
292 339
        if (!$event->isMasterRequest()) {
293 338
            return;
294
        }
295 1
        $response = $event->getResponse();
296 1
        if ($response instanceof BinaryFileResponse) {
297
            return;
298
        }
299 1
        foreach ($this->_sent_headers as $header => $value) {
300
            // This can happen in streamed responses which enable_live_mode
301
            if (!headers_sent()) {
302
                header_remove($header);
303
            }
304
            $response->headers->set($header, $value);
305
        }
306 1
        $request = $event->getRequest();
307 1
        if ($this->_no_cache) {
308
            $response->prepare($request);
309
            return;
310
        }
311
312 1
        $cache_data = $response->getContent();
313
314
        // Register additional Headers around the current output request
315 1
        $this->complete_sent_headers($response);
316 1
        $response->prepare($request);
317
318
        // Generate E-Tag header.
319 1
        if (empty($cache_data)) {
320
            $etag = md5(serialize($response->headers->all()));
321
        } else {
322 1
            $etag = md5($cache_data);
323
        }
324 1
        $response->setEtag($etag);
325
326 1
        if ($this->_uncached) {
327
            debug_add('Not writing cache file, we are in uncached operation mode.');
328
            return;
329
        }
330 1
        $content_id = 'C-' . $etag;
331 1
        $this->write_meta_cache($content_id, $request, $response);
332 1
        $this->_data_cache->save($content_id, $cache_data);
333 1
    }
334
335
    /**
336
     * Generate a valid cache identifier for a context of the current request
337
     */
338 1
    private function generate_request_identifier(Request $request) : string
339
    {
340 1
        $context = $request->attributes->get('context')->id;
341
        // Cache the request identifier so that it doesn't change between start and end of request
342 1
        static $identifier_cache = [];
343 1
        if (isset($identifier_cache[$context])) {
344 1
            return $identifier_cache[$context];
345
        }
346
347 1
        $module_name = $this->config->get('cache_module_content_name');
348 1
        if ($module_name == 'auto') {
349 1
            $module_name = midcom_connection::get_unique_host_name();
350
        }
351 1
        $identifier_source = 'CACHE:' . $module_name;
352
353 1
        $cache_strategy = $this->config->get('cache_module_content_caching_strategy');
354
355 1
        switch ($cache_strategy) {
356 1
            case 'memberships':
357
                if (!midcom_connection::get_user()) {
358
                    $identifier_source .= ';USER=ANONYMOUS';
359
                    break;
360
                }
361
                $mc = new midgard_collector('midgard_member', 'uid', midcom_connection::get_user());
362
                $mc->set_key_property('gid');
363
                $mc->execute();
364
                $gids = $mc->list_keys();
365
                $identifier_source .= ';GROUPS=' . implode(',', array_keys($gids));
366
                break;
367 1
            case 'public':
368
                $identifier_source .= ';USER=EVERYONE';
369
                break;
370 1
            case 'user':
371
            default:
372 1
                $identifier_source .= ';USER=' . midcom_connection::get_user();
373 1
                break;
374
        }
375
376 1
        $identifier_source .= ';URL=' . $request->getRequestUri();
377 1
        debug_add("Generating context {$context} request-identifier from: {$identifier_source}");
378
379 1
        $identifier_cache[$context] = 'R-' . md5($identifier_source);
380 1
        return $identifier_cache[$context];
381
    }
382
383 1
    private function get_strategy(string $name) : string
384
    {
385 1
        $strategy = strtolower($this->config->get($name));
386 1
        $allowed = ['no-cache', 'revalidate', 'public', 'private'];
387 1
        if (!in_array($strategy, $allowed)) {
388
            throw new midcom_error($name . ' is not valid, try ' . implode(', ', $allowed));
389
        }
390 1
        return $strategy;
391
    }
392
393
    /**
394
     * Call this, if the currently processed output must not be cached for any
395
     * reason. Dynamic pages with sensitive content are a candidate for this
396
     * function.
397
     *
398
     * Note, that this will prevent <i>any</i> content invalidation related headers
399
     * like E-Tag to be generated automatically, and that the appropriate
400
     * no-store/no-cache headers from HTTP 1.1 and HTTP 1.0 will be sent automatically.
401
     * This means that there will also be no 304 processing.
402
     *
403
     * You should use this only for sensitive content. For simple dynamic output,
404
     * you are strongly encouraged to use the less strict uncached() function.
405
     *
406
     * @see uncached()
407
     */
408 191
    public function no_cache(Response $response = null)
409
    {
410 191
        $settings = 'no-store, no-cache, must-revalidate';
411
        // PONDER: Send expires header (set to long time in past) as well ??
412
413 191
        if ($response) {
414
            $response->headers->set('Cache-Control', $settings);
415 191
        } elseif (!$this->_no_cache) {
416
            if (headers_sent()) {
417
                debug_add('Warning, we should move to no_cache but headers have already been sent, skipping header transmission.', MIDCOM_LOG_ERROR);
418
            } else {
419
                midcom::get()->header('Cache-Control: ' . $settings);
420
            }
421
        }
422 191
        $this->_no_cache = true;
423 191
    }
424
425
    /**
426
     * Call this, if the currently processed output must not be cached for any
427
     * reason. Dynamic pages or form processing results are the usual candidates
428
     * for this mode.
429
     *
430
     * Note, that this will still keep the caching engine active so that it can
431
     * add the usual headers (ETag, Expires ...) in respect to the no_cache flag.
432
     * As well, at the end of the processing, the usual 304 checks are done, so if
433
     * your page doesn't change in respect of E-Tag and Last-Modified, only a 304
434
     * Not Modified reaches the client.
435
     *
436
     * Essentially, no_cache behaves the same way as if the uncached configuration
437
     * directive is set to true, it is just limited to a single request.
438
     *
439
     * If you need a higher level of client side security, to avoid storage of sensitive
440
     * information on the client side, you should use no_cache instead.
441
     *
442
     * @see no_cache()
443
     */
444 3
    public function uncached(bool $uncached = true)
445
    {
446 3
        $this->_uncached = $uncached;
447 3
    }
448
449
    /**
450
     * Sets the expiration time of the current page (Unix (GMT) Timestamp).
451
     *
452
     * <b>Note:</B> This generate error call will add browser-side cache control
453
     * headers as well to force a browser to revalidate a page after the set
454
     * expiry.
455
     *
456
     * You should call this at all places where you have timed content in your
457
     * output, so that the page will be regenerated once a certain article has
458
     * expired.
459
     *
460
     * Multiple calls to expires will only save the
461
     * "youngest" timestamp, so you can safely call expires where appropriate
462
     * without respect to other values.
463
     *
464
     * The cache's default (null) will disable the expires header. Note, that once
465
     * an expiry time on a page has been set, it is not possible, to reset it again,
466
     * this is for dynamic_load situation, where one component might depend on a
467
     * set expiry.
468
     *
469
     * @param int $timestamp The UNIX timestamp from which the cached page should be invalidated.
470
     */
471
    public function expires($timestamp)
472
    {
473
        if (   $this->_expires === null
474
            || $this->_expires > $timestamp) {
475
            $this->_expires = $timestamp;
476
        }
477
    }
478
479
    /**
480
     * Sets the content type for the current page. The required HTTP Headers for
481
     * are automatically generated, so, to the contrary of expires, you just have
482
     * to set this header accordingly.
483
     *
484
     * This is usually set automatically by MidCOM for all regular HTML output and
485
     * for all attachment deliveries. You have to adapt it only for things like RSS
486
     * output.
487
     *
488
     * @param string $type    The content type to use.
489
     */
490 8
    public function content_type($type)
491
    {
492 8
        midcom::get()->header('Content-Type: ' . $type);
493 8
    }
494
495
    /**
496
     * Put the cache into a "live mode". This will disable the
497
     * cache during runtime, correctly flushing the output buffer (if it's not empty)
498
     * and sending cache control headers.
499
     *
500
     * The midcom-exec URL handler of the core will automatically enable live mode.
501
     *
502
     * @see midcom_application::_exec_file()
503
     */
504
    public function enable_live_mode()
505
    {
506
        $this->no_cache();
507
        Response::closeOutputBuffers(0, ob_get_length() > 0);
508
    }
509
510
    /**
511
     * Store a sent header into the cache database, so that it will
512
     * be resent when the cache page is delivered. midcom_application::header()
513
     * will automatically call this function, you need to do this only if you use
514
     * the PHP header function.
515
     *
516
     * @param string $header The header that was sent.
517
     */
518 17
    public function register_sent_header($header)
519
    {
520 17
        if (str_contains($header, ': ')) {
521 17
            [$header, $value] = explode(': ', $header, 2);
522 17
            $this->_sent_headers[$header] = $value;
523
        }
524 17
    }
525
526
    /**
527
     * Looks for list of content and request identifiers paired with the given guid
528
     * and removes all of those from the caches.
529
     *
530
     * {@inheritDoc}
531
     */
532 289
    public function invalidate($guid, $object = null)
533
    {
534 289
        $guidmap = $this->backend->fetch($guid);
535 289
        if ($guidmap === false) {
536 289
            debug_add("No entry for {$guid} in meta cache, ignoring invalidation request.");
537 289
            return;
538
        }
539
540
        foreach ($guidmap as $content_id) {
541
            if ($this->backend->contains($content_id)) {
542
                $this->backend->delete($content_id);
543
            }
544
545
            if ($this->_data_cache->contains($content_id)) {
546
                $this->_data_cache->delete($content_id);
547
            }
548
        }
549
    }
550
551
    public function invalidate_all()
552
    {
553
        parent::invalidate_all();
554
        $this->_data_cache->flushAll();
555
    }
556
557
    /**
558
     * All objects loaded within a request are stored into a list for cache invalidation purposes
559
     */
560 393
    public function register($guid)
561
    {
562
        // Check for uncached operation
563 393
        if ($this->_uncached) {
564 393
            return;
565
        }
566
567
        $context = midcom_core_context::get()->id;
568
        if ($context != 0) {
569
            // We're in a dynamic_load, register it for that as well
570
            if (!isset($this->context_guids[$context])) {
571
                $this->context_guids[$context] = [];
572
            }
573
            $this->context_guids[$context][] = $guid;
574
        }
575
576
        // Register all GUIDs also to the root context
577
        if (!isset($this->context_guids[0])) {
578
            $this->context_guids[0] = [];
579
        }
580
        $this->context_guids[0][] = $guid;
581
    }
582
583
    /**
584
     * Writes meta-cache entry from context data using given content id
585
     * Used to be part of on_request, but needed by serve-attachment method in midcom_core_urlmethods as well
586
     */
587 1
    public function write_meta_cache($content_id, Request $request, Response $response)
588
    {
589 1
        if (   $this->_uncached
590 1
            || $this->_no_cache) {
591
            return;
592
        }
593
594 1
        if ($this->_expires !== null) {
595
            $lifetime = $this->_expires - time();
596
        } else {
597
            // Use default expiry for cache entry, most components don't bother calling expires() properly
598 1
            $lifetime = $this->_default_lifetime;
599
        }
600
601
        // Construct cache identifier
602 1
        $request_id = $this->generate_request_identifier($request);
603
604
        $entries = [
605 1
            $request_id => $content_id,
606 1
            $content_id => $response->headers->all()
607
        ];
608 1
        $this->backend->saveMultiple($entries, $lifetime);
609
610
        // Cache where the object have been
611 1
        $context = midcom_core_context::get()->id;
612 1
        $this->store_context_guid_map($context, $content_id, $request_id);
613 1
    }
614
615 1
    private function store_context_guid_map($context, string $content_id, string $request_id)
616
    {
617
        // non-existent context
618 1
        if (!array_key_exists($context, $this->context_guids)) {
619 1
            return;
620
        }
621
622
        $maps = $this->backend->fetchMultiple($this->context_guids[$context]);
623
        $to_save = [];
624
        foreach ($this->context_guids[$context] as $guid) {
625
            // Getting old map from cache or create new, empty one
626
            $guidmap = $maps[$guid] ?? [];
627
628
            if (!in_array($content_id, $guidmap)) {
629
                $guidmap[] = $content_id;
630
                $to_save[$guid] = $guidmap;
631
            }
632
633
            if (!in_array($request_id, $guidmap)) {
634
                $guidmap[] = $request_id;
635
                $to_save[$guid] = $guidmap;
636
            }
637
        }
638
639
        $this->backend->saveMultiple($to_save);
640
    }
641
642 16
    public function check_dl_hit(Request $request)
643
    {
644 16
        if ($this->_no_cache) {
645 16
            return false;
646
        }
647
        $dl_request_id = 'DL' . $this->generate_request_identifier($request);
648
        $dl_content_id = $this->backend->fetch($dl_request_id);
649
        if ($dl_content_id === false) {
650
            return false;
651
        }
652
653
        return $this->_data_cache->fetch($dl_content_id);
654
    }
655
656 4
    public function store_dl_content($context, $dl_cache_data, Request $request)
657
    {
658 4
        if (   $this->_no_cache
659 4
            || $this->_uncached) {
660 4
            return;
661
        }
662
        $dl_request_id = 'DL' . $this->generate_request_identifier($request);
663
        $dl_content_id = 'DLC-' . md5($dl_cache_data);
664
665
        if ($this->_expires !== null) {
666
            $lifetime = $this->_expires - time();
667
        } else {
668
            // Use default expiry for cache entry, most components don't bother calling expires() properly
669
            $lifetime = $this->_default_lifetime;
670
        }
671
        $this->backend->save($dl_request_id, $dl_content_id, $lifetime);
672
        $this->_data_cache->save($dl_content_id, $dl_cache_data, $lifetime);
673
        // Cache where the object have been
674
        $this->store_context_guid_map($context, $dl_content_id, $dl_request_id);
675
    }
676
677
    /**
678
     * This little helper ensures that the headers Content-Length
679
     * and Last-Modified are present. The lastmod timestamp is taken out of the
680
     * component context information if it is populated correctly there; if not, the
681
     * system time is used instead.
682
     *
683
     * To force browsers to revalidate the page on every request (login changes would
684
     * go unnoticed otherwise), the Cache-Control header max-age=0 is added automatically.
685
     */
686 1
    private function complete_sent_headers(Response $response)
687
    {
688 1
        if (!$response->getLastModified()) {
689
            /* Determine Last-Modified using MidCOM's component context,
690
             * Fallback to time() if this fails.
691
             */
692 1
            $time = midcom_core_context::get()->get_key(MIDCOM_CONTEXT_LASTMODIFIED) ?: time();
693 1
            $response->setLastModified(DateTime::createFromFormat('U', (string) $time));
0 ignored issues
show
Bug introduced by
It seems like DateTime::createFromFormat('U', (string)$time) can also be of type false; however, parameter $date of Symfony\Component\HttpFo...onse::setLastModified() does only seem to accept DateTimeInterface|null, 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

693
            $response->setLastModified(/** @scrutinizer ignore-type */ DateTime::createFromFormat('U', (string) $time));
Loading history...
694
        }
695
696 1
        if (!$response->headers->has('Content-Length')) {
697
            /* TODO: Doublecheck the way this is handled, now we just don't send it
698
             * if headers_strategy implies caching */
699 1
            if (!in_array($this->_headers_strategy, ['public', 'private'])) {
700 1
                $response->headers->set("Content-Length", strlen($response->getContent()));
701
            }
702
        }
703
704 1
        $this->cache_control_headers($response);
705 1
    }
706
707
    /**
708
     * @param Response $response
709
     */
710 1
    public function cache_control_headers(Response $response)
711
    {
712
        // Just to be sure not to mess the headers sent by no_cache in case it was called
713 1
        if ($this->_no_cache) {
714
            $this->no_cache($response);
715
        } else {
716
            // Add Expiration and Cache Control headers
717 1
            $strategy = $this->_headers_strategy;
718 1
            $default_lifetime = $this->_default_lifetime;
719 1
            if (   midcom::get()->auth->is_valid_user()
720 1
                || midcom_connection::get_user()) {
721
                $strategy = $this->_headers_strategy_authenticated;
722
                $default_lifetime = $this->_default_lifetime_authenticated;
723
            }
724
725 1
            $now = time();
726 1
            if ($strategy == 'revalidate') {
727
                // If expires is not set, we force the client to revalidate every time.
728
                // The timeout of a content cache entry is not affected by this.
729 1
                $expires = $this->_expires ?? $now;
730
            } else {
731
                $expires = $this->_expires ?? $now + $default_lifetime;
732
                if ($strategy == 'private') {
733
                    $response->setPrivate();
734
                } else {
735
                    $response->setPublic();
736
                }
737
            }
738 1
            $max_age = $expires - $now;
739
740
            $response
741 1
                ->setExpires(DateTime::createFromFormat('U', $expires))
0 ignored issues
show
Bug introduced by
It seems like DateTime::createFromFormat('U', $expires) can also be of type false; however, parameter $date of Symfony\Component\HttpFo...\Response::setExpires() does only seem to accept DateTimeInterface|null, 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

741
                ->setExpires(/** @scrutinizer ignore-type */ DateTime::createFromFormat('U', $expires))
Loading history...
742 1
                ->setMaxAge($max_age);
743 1
            if ($max_age == 0) {
744 1
                $response->headers->addCacheControlDirective('must-revalidate');
745
            }
746
        }
747 1
    }
748
}
749