Passed
Push — master ( f1dcbc...5d5f0a )
by Aimeos
03:51
created

Base   F

Complexity

Total Complexity 76

Size/Duplication

Total Lines 636
Duplicated Lines 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 159
dl 0
loc 636
rs 2.32
c 4
b 0
f 0
wmc 76

26 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 3 1
A setView() 0 4 1
A addMetaItemRef() 0 17 5
A view() 0 7 2
A replaceSection() 0 17 4
A data() 0 7 2
B addMetaItems() 0 62 7
A header() 0 9 2
A modifyHeader() 0 11 2
A addDecorators() 0 22 5
A getContext() 0 3 1
A getSubClients() 0 12 3
A getCached() 0 42 6
A createSubClient() 0 24 5
A addMetaItemSingle() 0 16 5
A init() 0 6 2
A expires() 0 3 3
A getTemplatePath() 0 7 3
A logException() 0 4 1
A setObject() 0 4 1
A getClientParams() 0 5 1
A getObject() 0 7 2
A setCached() 0 23 5
A getParamHash() 0 10 2
A addClientDecorators() 0 18 3
A modifyBody() 0 11 2

How to fix   Complexity   

Complex Class

Complex classes like Base often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Base, and based on these observations, apply Extract Interface, too.

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