Passed
Push — master ( 921dd7...b90bc8 )
by Aimeos
05:36
created

Base::replaceSection()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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