Passed
Push — master ( dc5862...f6527a )
by Aimeos
04:15 queued 13s
created

Base::addMetaItems()   A

Complexity

Conditions 5
Paths 12

Size

Total Lines 58
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 12
c 0
b 0
f 0
nc 12
nop 4
dl 0
loc 58
rs 9.5555

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
/**
4
 * @license LGPLv3, http://opensource.org/licenses/LGPL-3.0
5
 * @copyright Metaways Infosystems GmbH, 2012
6
 * @copyright Aimeos (aimeos.org), 2015-2022
7
 * @package Client
8
 * @subpackage Html
9
 */
10
11
12
namespace Aimeos\Client\Html;
13
14
15
/**
16
 * Common abstract class for all HTML client classes.
17
 *
18
 * @package Client
19
 * @subpackage Html
20
 */
21
abstract class Base
22
	implements \Aimeos\Client\Html\Iface, \Aimeos\Macro\Iface
23
{
24
	use \Aimeos\Macro\Macroable;
25
26
27
	private $view;
28
	private $cache;
29
	private $object;
30
	private $context;
31
	private $subclients;
32
	private $cachedView;
33
34
35
	/**
36
	 * Initializes the class instance.
37
	 *
38
	 * @param \Aimeos\MShop\Context\Item\Iface $context Context object
39
	 */
40
	public function __construct( \Aimeos\MShop\Context\Item\Iface $context )
41
	{
42
		$this->context = $context;
43
	}
44
45
46
	/**
47
	 * Returns the HTML code for insertion into the body.
48
	 *
49
	 * @param string $uid Unique identifier for the output if the content is placed more than once on the same page
50
	 * @return string HTML code
51
	 */
52
	public function body( string $uid = '' ) : string
53
	{
54
		$html = '';
55
		$type = $this->clientType();
56
57
		$view = $this->cachedView = $this->cachedView ?? $this->object()->data( $this->view() );
58
59
		foreach( $this->getSubClients() as $subclient ) {
60
			$html .= $subclient->setView( $view )->body( $uid );
61
		}
62
63
		return $view->set( 'body', $html )
64
			->render( $view->config( 'client/html/${type}/template-body', $type . '/body' ) );
65
	}
66
67
68
	/**
69
	 * Adds the data to the view object required by the templates
70
	 *
71
	 * @param \Aimeos\MW\View\Iface $view The view object which generates the HTML output
72
	 * @param array &$tags Result array for the list of tags that are associated to the output
73
	 * @param string|null &$expire Result variable for the expiration date of the output (null for no expiry)
74
	 * @return \Aimeos\MW\View\Iface The view object with the data required by the templates
75
	 */
76
	public function data( \Aimeos\MW\View\Iface $view, array &$tags = [], string &$expire = null ) : \Aimeos\MW\View\Iface
77
	{
78
		foreach( $this->getSubClients() as $name => $subclient ) {
79
			$view = $subclient->data( $view, $tags, $expire );
80
		}
81
82
		return $view;
83
	}
84
85
86
	/**
87
	 * Returns the sub-client given by its name.
88
	 *
89
	 * @param string $type Name of the client type
90
	 * @param string|null $name Name of the sub-client (Default if null)
91
	 * @return \Aimeos\Client\Html\Iface Sub-client object
92
	 */
93
	public function getSubClient( string $type, string $name = null ) : \Aimeos\Client\Html\Iface
94
	{
95
		return $this->createSubClient( $this->clientType() . '/' . $type, $name );
96
	}
97
98
99
	/**
100
	 * Returns the HTML string for insertion into the header.
101
	 *
102
	 * @param string $uid Unique identifier for the output if the content is placed more than once on the same page
103
	 * @return string|null String including HTML tags for the header on error
104
	 */
105
	public function header( string $uid = '' ) : ?string
106
	{
107
		$type = $this->clientType();
108
		$view = $this->cachedView = $this->cachedView ?? $this->object()->data( $this->view() );
109
110
		return $view->render( $view->config( 'client/html/${type}/template-header', $type . '/header' ) );
111
	}
112
113
114
	/**
115
	 * Processes the input, e.g. store given values.
116
	 *
117
	 * A view must be available and this method doesn't generate any output
118
	 * besides setting view variables.
119
	 */
120
	public function init()
121
	{
122
		$view = $this->view();
123
124
		foreach( $this->getSubClients() as $subclient ) {
125
			$subclient->setView( $view )->init();
126
		}
127
	}
128
129
130
	/**
131
	 * Modifies the cached content to replace content based on sessions or cookies.
132
	 *
133
	 * @param string $content Cached content
134
	 * @param string $uid Unique identifier for the output if the content is placed more than once on the same page
135
	 * @return string Modified content
136
	 */
137
	public function modify( string $content, string $uid ) : string
138
	{
139
		$view = $this->view();
140
141
		foreach( $this->getSubClients() as $subclient )
142
		{
143
			$subclient->setView( $view );
144
			$content = $subclient->modify( $content, $uid );
145
		}
146
147
		return $content;
148
	}
149
150
151
	/**
152
	 * Returns the PSR-7 response object for the request
153
	 *
154
	 * @return \Psr\Http\Message\ResponseInterface Response object
155
	 */
156
	public function response() : \Psr\Http\Message\ResponseInterface
157
	{
158
		return $this->view()->response();
159
	}
160
161
162
	/**
163
	 * Injects the reference of the outmost client object or decorator
164
	 *
165
	 * @param \Aimeos\Client\Html\Iface $object Reference to the outmost client or decorator
166
	 * @return \Aimeos\Client\Html\Iface Client object for chaining method calls
167
	 */
168
	public function setObject( \Aimeos\Client\Html\Iface $object ) : \Aimeos\Client\Html\Iface
169
	{
170
		$this->object = $object;
171
		return $this;
172
	}
173
174
175
	/**
176
	 * Sets the view object that will generate the HTML output.
177
	 *
178
	 * @param \Aimeos\MW\View\Iface $view The view object which generates the HTML output
179
	 * @return \Aimeos\Client\Html\Iface Reference to this object for fluent calls
180
	 */
181
	public function setView( \Aimeos\MW\View\Iface $view ) : \Aimeos\Client\Html\Iface
182
	{
183
		$this->view = $view;
184
		return $this;
185
	}
186
187
188
	/**
189
	 * Returns the outmost decorator of the decorator stack
190
	 *
191
	 * @return \Aimeos\Client\Html\Iface Outmost decorator object
192
	 */
193
	protected function object() : \Aimeos\Client\Html\Iface
194
	{
195
		if( $this->object !== null ) {
196
			return $this->object;
197
		}
198
199
		return $this;
200
	}
201
202
203
	/**
204
	 * Returns the view object that will generate the HTML output.
205
	 *
206
	 * @return \Aimeos\MW\View\Iface $view The view object which generates the HTML output
207
	 */
208
	protected function view() : \Aimeos\MW\View\Iface
209
	{
210
		if( !isset( $this->view ) ) {
211
			throw new \Aimeos\Client\Html\Exception( sprintf( 'No view available' ) );
212
		}
213
214
		return $this->view;
215
	}
216
217
218
	/**
219
	 * Adds the decorators to the client object
220
	 *
221
	 * @param \Aimeos\Client\Html\Iface $client Client object
222
	 * @param array $decorators List of decorator name that should be wrapped around the client
223
	 * @param string $classprefix Decorator class prefix, e.g. "\Aimeos\Client\Html\Catalog\Decorator\"
224
	 * @return \Aimeos\Client\Html\Iface Client object
225
	 */
226
	protected function addDecorators( \Aimeos\Client\Html\Iface $client, array $decorators, string $classprefix ) : \Aimeos\Client\Html\Iface
227
	{
228
		foreach( $decorators as $name )
229
		{
230
			if( ctype_alnum( $name ) === false )
231
			{
232
				$classname = is_string( $name ) ? $classprefix . $name : '<not a string>';
233
				throw new \Aimeos\Client\Html\Exception( sprintf( 'Invalid class name "%1$s"', $classname ) );
234
			}
235
236
			$classname = $classprefix . $name;
237
238
			if( class_exists( $classname ) === false ) {
239
				throw new \Aimeos\Client\Html\Exception( sprintf( 'Class "%1$s" not found', $classname ) );
240
			}
241
242
			$client = new $classname( $client, $this->context );
243
244
			\Aimeos\MW\Common\Base::checkClass( '\\Aimeos\\Client\\Html\\Common\\Decorator\\Iface', $client );
245
		}
246
247
		return $client;
248
	}
249
250
251
	/**
252
	 * Adds the decorators to the client object
253
	 *
254
	 * @param \Aimeos\Client\Html\Iface $client Client object
255
	 * @param string $path Client string in lower case, e.g. "catalog/detail/basic"
256
	 * @return \Aimeos\Client\Html\Iface Client object
257
	 */
258
	protected function addClientDecorators( \Aimeos\Client\Html\Iface $client, string $path ) : \Aimeos\Client\Html\Iface
259
	{
260
		if( !is_string( $path ) || $path === '' ) {
0 ignored issues
show
introduced by
The condition is_string($path) is always true.
Loading history...
261
			throw new \Aimeos\Client\Html\Exception( sprintf( 'Invalid domain "%1$s"', $path ) );
262
		}
263
264
		$localClass = str_replace( '/', '\\', ucwords( $path, '/' ) );
265
		$config = $this->context->config();
266
267
		$classprefix = '\\Aimeos\\Client\\Html\\Common\\Decorator\\';
268
		$decorators = $config->get( 'client/html/' . $path . '/decorators/global', [] );
269
		$client = $this->addDecorators( $client, $decorators, $classprefix );
270
271
		$classprefix = '\\Aimeos\\Client\\Html\\' . $localClass . '\\Decorator\\';
272
		$decorators = $config->get( 'client/html/' . $path . '/decorators/local', [] );
273
		$client = $this->addDecorators( $client, $decorators, $classprefix );
274
275
		return $client;
276
	}
277
278
279
	/**
280
	 * Adds the cache tags to the given list and sets a new expiration date if necessary based on the given item.
281
	 *
282
	 * @param array|\Aimeos\MShop\Common\Item\Iface $items Item or list of items, maybe with associated list items
283
	 * @param string|null &$expire Expiration date that will be overwritten if an earlier date is found
284
	 * @param array &$tags List of tags the new tags will be added to
285
	 * @param array $custom List of custom tags which are added too
286
	 */
287
	protected function addMetaItems( $items, string &$expire = null, array &$tags, array $custom = [] )
288
	{
289
		/** client/html/common/cache/tag-all
290
		 * Adds tags for all items used in a cache entry
291
		 *
292
		 * Each cache entry storing rendered parts for the HTML header or body
293
		 * can be tagged with information which items like texts, media, etc.
294
		 * are used in the HTML. This allows removing only those cache entries
295
		 * whose content has really changed and only that entries have to be
296
		 * rebuild the next time.
297
		 *
298
		 * The standard behavior stores only tags for each used domain, e.g. if
299
		 * a text is used, only the tag "text" is added. If you change a text
300
		 * in the administration interface, all cache entries with the tag
301
		 * "text" will be removed from the cache. This effectively wipes out
302
		 * almost all cached entries, which have to be rebuild with the next
303
		 * request.
304
		 *
305
		 * Important: As a list or detail view can use several hundred items,
306
		 * this configuration option will also add this number of tags to the
307
		 * cache entry. When using a cache adapter that can't insert all tags
308
		 * at once, this slows down the initial cache insert (and therefore the
309
		 * page speed) drastically! It's only recommended to enable this option
310
		 * if you use the DB, Mysql or Redis adapter that can insert all tags
311
		 * at once.
312
		 *
313
		 * @param boolean True to add tags for all items, false to use only a domain tag
314
		 * @since 2014.07
315
		 * @category Developer
316
		 * @category User
317
		 * @see client/html/common/cache/force
318
		 * @see madmin/cache/manager/name
319
		 * @see madmin/cache/name
320
		 */
321
		$tagAll = $this->context->config()->get( 'client/html/common/cache/tag-all', false );
322
323
		$expires = [];
324
325
		foreach( map( $items ) as $item )
326
		{
327
			if( $item instanceof \Aimeos\MShop\Common\Item\ListsRef\Iface ) {
328
				$this->addMetaItemRef( $item, $expires, $tags, $tagAll );
329
			}
330
331
			$this->addMetaItemSingle( $item, $expires, $tags, $tagAll );
332
		}
333
334
		if( $expire !== null ) {
335
			$expires[] = $expire;
336
		}
337
338
		if( !empty( $expires ) ) {
339
			$expire = min( $expires );
340
		}
341
342
		$tags = array_unique( array_merge( $tags, $custom ) );
343
344
		return $items;
345
	}
346
347
348
	/**
349
	 * Adds expire date and tags for a single item.
350
	 *
351
	 * @param \Aimeos\MShop\Common\Item\Iface $item Item, maybe with associated list items
352
	 * @param array &$expires Will contain the list of expiration dates
353
	 * @param array &$tags List of tags the new tags will be added to
354
	 * @param bool $tagAll True of tags for all items should be added, false if only for the main item
355
	 */
356
	private function addMetaItemSingle( \Aimeos\MShop\Common\Item\Iface $item, array &$expires, array &$tags, bool $tagAll )
357
	{
358
		$domain = str_replace( '/', '_', $item->getResourceType() ); // maximum compatiblity
359
360
		if( $tagAll === true ) {
361
			$tags[] = $domain . '-' . $item->getId();
362
		} else {
363
			$tags[] = $domain;
364
		}
365
366
		if( $item instanceof \Aimeos\MShop\Common\Item\Time\Iface && ( $date = $item->getDateEnd() ) !== null ) {
367
			$expires[] = $date;
368
		}
369
370
		if( $item instanceof \Aimeos\MShop\Common\Item\ListsRef\Iface ) {
371
			$this->addMetaItemRef( $item, $expires, $tags, $tagAll );
372
		}
373
	}
374
375
376
	/**
377
	 * Adds expire date and tags for referenced items
378
	 *
379
	 * @param \Aimeos\MShop\Common\Item\ListsRef\Iface $item Item with associated list items
380
	 * @param array &$expires Will contain the list of expiration dates
381
	 * @param array &$tags List of tags the new tags will be added to
382
	 * @param bool $tagAll True of tags for all items should be added, false if only for the main item
383
	 */
384
	private function addMetaItemRef( \Aimeos\MShop\Common\Item\ListsRef\Iface $item, array &$expires, array &$tags, bool $tagAll )
385
	{
386
		foreach( $item->getListItems() as $listitem )
387
		{
388
			if( ( $refItem = $listitem->getRefItem() ) === null ) {
389
				continue;
390
			}
391
392
			if( $tagAll === true ) {
393
				$tags[] = str_replace( '/', '_', $listitem->getDomain() ) . '-' . $listitem->getRefId();
394
			}
395
396
			if( ( $date = $listitem->getDateEnd() ) !== null ) {
397
				$expires[] = $date;
398
			}
399
400
			$this->addMetaItemSingle( $refItem, $expires, $tags, $tagAll );
401
		}
402
	}
403
404
405
	/**
406
	 * Returns the client type of the class
407
	 *
408
	 * @return string Client type, e.g. "catalog/detail"
409
	 */
410
	protected function clientType() : string
411
	{
412
		return strtolower( trim( dirname( str_replace( '\\', '/', substr( get_class( $this ), 19 ) ) ), '/' ) );
413
	}
414
415
416
	/**
417
	 * Returns the sub-client given by its name.
418
	 *
419
	 * @param string $path Name of the sub-part in lower case (can contain a path like catalog/filter/tree)
420
	 * @param string|null $name Name of the implementation, will be from configuration (or Default) if null
421
	 * @return \Aimeos\Client\Html\Iface Sub-part object
422
	 */
423
	protected function createSubClient( string $path, string $name = null ) : \Aimeos\Client\Html\Iface
424
	{
425
		$path = strtolower( $path );
426
427
		if( $name === null ) {
428
			$name = $this->context->config()->get( 'client/html/' . $path . '/name', 'Standard' );
429
		}
430
431
		if( empty( $name ) || ctype_alnum( $name ) === false ) {
432
			throw new \Aimeos\Client\Html\Exception( sprintf( 'Invalid characters in client name "%1$s"', $name ) );
433
		}
434
435
		$subnames = str_replace( '/', '\\', ucwords( $path, '/' ) );
436
		$classname = '\\Aimeos\\Client\\Html\\' . $subnames . '\\' . $name;
437
438
		if( class_exists( $classname ) === false ) {
439
			throw new \Aimeos\Client\Html\Exception( sprintf( 'Class "%1$s" not available', $classname ) );
440
		}
441
442
		$object = new $classname( $this->context );
443
		$object = \Aimeos\MW\Common\Base::checkClass( '\\Aimeos\\Client\\Html\\Iface', $object );
444
		$object = $this->addClientDecorators( $object, $path );
445
446
		return $object->setObject( $object );
447
	}
448
449
450
	/**
451
	 * Returns the minimal expiration date.
452
	 *
453
	 * @param string|null $first First expiration date or null
454
	 * @param string|null $second Second expiration date or null
455
	 * @return string|null Expiration date
456
	 */
457
	protected function expires( string $first = null, string $second = null ) : ?string
458
	{
459
		return ( $first !== null ? ( $second !== null ? min( $first, $second ) : $first ) : $second );
460
	}
461
462
463
	/**
464
	 * Returns the parameters used by the html client.
465
	 *
466
	 * @param array $params Associative list of all parameters
467
	 * @param array $prefixes List of prefixes the parameters must start with
468
	 * @return array Associative list of parameters used by the html client
469
	 */
470
	protected function getClientParams( array $params, array $prefixes = ['f_', 'l_', 'd_'] ) : array
471
	{
472
		return map( $params )->filter( function( $val, $key ) use ( $prefixes ) {
473
			return \Aimeos\MW\Str::starts( $key, $prefixes );
474
		} )->toArray();
475
	}
476
477
478
	/**
479
	 * Returns the context object.
480
	 *
481
	 * @return \Aimeos\MShop\Context\Item\Iface Context object
482
	 */
483
	protected function context() : \Aimeos\MShop\Context\Item\Iface
484
	{
485
		return $this->context;
486
	}
487
488
489
	/**
490
	 * Generates an unique hash from based on the input suitable to be used as part of the cache key
491
	 *
492
	 * @param array $prefixes List of prefixes the parameters must start with
493
	 * @param string $key Unique identifier if the content is placed more than once on the same page
494
	 * @param array $config Multi-dimensional array of configuration options used by the client and sub-clients
495
	 * @return string Unique hash
496
	 */
497
	protected function getParamHash( array $prefixes = ['f_', 'l_', 'd_'], string $key = '', array $config = [] ) : string
498
	{
499
		$locale = $this->context()->locale();
500
		$pstr = map( $this->getClientParams( $this->view()->param(), $prefixes ) )->ksort()->toJson();
501
502
		if( ( $cstr = json_encode( $config ) ) === false ) {
503
			throw new \Aimeos\Client\Html\Exception( 'Unable to encode parameters or configuration options' );
504
		}
505
506
		return md5( $key . $pstr . $cstr . $locale->getLanguageId() . $locale->getCurrencyId() . $locale->getSiteId() );
507
	}
508
509
510
	/**
511
	 * Returns the list of sub-client names configured for the client.
512
	 *
513
	 * @return array List of HTML client names
514
	 */
515
	protected function getSubClientNames() : array
516
	{
517
		return [];
518
	}
519
520
521
	/**
522
	 * Returns the configured sub-clients or the ones named in the default parameter if none are configured.
523
	 *
524
	 * @return array List of sub-clients implementing \Aimeos\Client\Html\Iface	ordered in the same way as the names
525
	 */
526
	protected function getSubClients() : array
527
	{
528
		if( !isset( $this->subclients ) )
529
		{
530
			$this->subclients = [];
531
532
			foreach( $this->getSubClientNames() as $name ) {
533
				$this->subclients[$name] = $this->getSubClient( $name );
534
			}
535
		}
536
537
		return $this->subclients;
538
	}
539
540
541
	/**
542
	 * Returns the template for the given configuration key
543
	 *
544
	 * If the "l_type" parameter is present, a specific template for this given
545
	 * type is used if available.
546
	 *
547
	 * @param string $confkey Key to the configuration setting for the template
548
	 * @param string $default Default template if none is configured or not found
549
	 * @return string Relative template path
550
	 */
551
	protected function getTemplatePath( string $confkey, string $default ) : string
552
	{
553
		if( ( $type = $this->view->param( 'l_type' ) ) !== null && ctype_alnum( $type ) !== false ) {
554
			return $this->view->config( $confkey . '-' . $type, $this->view->config( $confkey, $default ) );
555
		}
556
557
		return $this->view->config( $confkey, $default );
558
	}
559
560
561
	/**
562
	 * Returns the cache entry for the given unique ID and type.
563
	 *
564
	 * @param string $type Type of the cache entry, i.e. "body" or "header"
565
	 * @param string $uid Unique identifier for the output if the content is placed more than once on the same page
566
	 * @param string[] $prefixes List of prefixes of all parameters that are relevant for generating the output
567
	 * @param string $confkey Configuration key prefix that matches all relevant settings for the component
568
	 * @return string|null Cached entry or null if not available
569
	 */
570
	protected function cached( string $type, string $uid, array $prefixes, string $confkey ) : ?string
571
	{
572
		$context = $this->context();
573
		$config = $context->config();
574
575
		/** client/html/common/cache/force
576
		 * Enforces content caching regardless of user logins
577
		 *
578
		 * Caching the component output is normally disabled as soon as the
579
		 * user has logged in. This enables displaying user or user group
580
		 * specific content without mixing standard and user specific output.
581
		 *
582
		 * If you don't have any user or user group specific content
583
		 * (products, categories, attributes, media, prices, texts, etc.),
584
		 * you can enforce content caching nevertheless to keep response
585
		 * times as low as possible.
586
		 *
587
		 * @param boolean True to cache output regardless of login, false for no caching
588
		 * @since 2015.08
589
		 * @category Developer
590
		 * @category User
591
		 * @see client/html/common/cache/tag-all
592
		 */
593
		$force = $config->get( 'client/html/common/cache/force', false );
594
		$enable = $config->get( $confkey . '/cache', true );
595
596
		if( $enable == false || $force == false && $context->user() !== null ) {
597
			return null;
598
		}
599
600
		$cfg = array_merge( $config->get( 'client/html', [] ), $this->getSubClientNames() );
601
602
		$keys = array(
603
			'body' => $this->getParamHash( $prefixes, $uid . ':' . $confkey . ':body', $cfg ),
604
			'header' => $this->getParamHash( $prefixes, $uid . ':' . $confkey . ':header', $cfg ),
605
		);
606
607
		if( !isset( $this->cache[$keys[$type]] ) ) {
608
			$this->cache = $context->cache()->getMultiple( $keys );
609
		}
610
611
		return ( isset( $this->cache[$keys[$type]] ) ? $this->cache[$keys[$type]] : null );
612
	}
613
614
615
	/**
616
	 * Returns the cache entry for the given type and unique ID.
617
	 *
618
	 * @param string $type Type of the cache entry, i.e. "body" or "header"
619
	 * @param string $uid Unique identifier for the output if the content is placed more than once on the same page
620
	 * @param string[] $prefixes List of prefixes of all parameters that are relevant for generating the output
621
	 * @param string $confkey Configuration key prefix that matches all relevant settings for the component
622
	 * @param string $value Value string that should be stored for the given key
623
	 * @param array $tags List of tag strings that should be assoicated to the given value in the cache
624
	 * @param string|null $expire Date/time string in "YYYY-MM-DD HH:mm:ss"	format when the cache entry expires
625
	 */
626
	protected function cache( string $type, string $uid, array $prefixes, string $confkey, string $value, array $tags, string $expire = null ) : string
627
	{
628
		$context = $this->context();
629
		$config = $context->config();
630
631
		$force = $config->get( 'client/html/common/cache/force', false );
632
		$enable = $config->get( $confkey . '/cache', true );
633
634
		if( !$enable || !$force && $context->user() !== null ) {
635
			return $value;
636
		}
637
638
		try
639
		{
640
			$cfg = array_merge( $config->get( 'client/html', [] ), $this->getSubClientNames() );
641
			$key = $this->getParamHash( $prefixes, $uid . ':' . $confkey . ':' . $type, $cfg );
642
643
			$context->cache()->set( $key, $value, $expire, array_unique( $tags ) );
644
		}
645
		catch( \Exception $e )
646
		{
647
			$context->logger()->notice( sprintf( 'Unable to set cache entry: %1$s', $e->getMessage() ), 'client/html' );
648
		}
649
650
		return $value;
651
	}
652
653
654
	/**
655
	 * Writes the exception details to the log
656
	 *
657
	 * @param \Exception $e Exception object
658
	 */
659
	protected function logException( \Exception $e )
660
	{
661
		$msg = $e->getMessage() . PHP_EOL . $e->getTraceAsString();
662
		$this->context->logger()->warning( $msg, 'client/html' );
663
	}
664
665
666
	/**
667
	 * Replaces the section in the content that is enclosed by the marker.
668
	 *
669
	 * @param string $content Cached content
670
	 * @param string $section New section content
671
	 * @param string $marker Name of the section marker without "<!-- " and " -->" parts
672
	 */
673
	protected function replaceSection( string $content, string $section, string $marker ) : string
674
	{
675
		$marker = '<!-- ' . $marker . ' -->';
676
		$clen = strlen( $content );
677
		$mlen = strlen( $marker );
678
		$len = strlen( $section );
679
		$start = 0;
680
681
		while( $start + $mlen < $clen && ( $start = @strpos( $content, $marker, $start ) ) !== false )
682
		{
683
			if( ( $end = strpos( $content, $marker, $start + 1 ) ) !== false ) {
684
				$content = substr_replace( $content, $section, $start, $end - $start + $mlen );
685
			}
686
687
			$start += 2 * $mlen + $len;
688
		}
689
690
		return $content;
691
	}
692
}
693