Completed
Push — master ( 31a465...5c0d08 )
by Aimeos
03:08
created

Standard::addData()   B

Complexity

Conditions 8
Paths 64

Size

Total Lines 136
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 34
nc 64
nop 3
dl 0
loc 136
rs 8.1315
c 0
b 0
f 0

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-2020
7
 * @package Client
8
 * @subpackage Html
9
 */
10
11
12
namespace Aimeos\Client\Html\Catalog\Detail;
13
14
15
/**
16
 * Default implementation of catalog detail section HTML clients.
17
 *
18
 * @package Client
19
 * @subpackage Html
20
 */
21
class Standard
22
	extends \Aimeos\Client\Html\Catalog\Base
23
	implements \Aimeos\Client\Html\Common\Client\Factory\Iface
24
{
25
	/** client/html/catalog/detail/standard/subparts
26
	 * List of HTML sub-clients rendered within the catalog detail section
27
	 *
28
	 * The output of the frontend is composed of the code generated by the HTML
29
	 * clients. Each HTML client can consist of serveral (or none) sub-clients
30
	 * that are responsible for rendering certain sub-parts of the output. The
31
	 * sub-clients can contain HTML clients themselves and therefore a
32
	 * hierarchical tree of HTML clients is composed. Each HTML client creates
33
	 * the output that is placed inside the container of its parent.
34
	 *
35
	 * At first, always the HTML code generated by the parent is printed, then
36
	 * the HTML code of its sub-clients. The order of the HTML sub-clients
37
	 * determines the order of the output of these sub-clients inside the parent
38
	 * container. If the configured list of clients is
39
	 *
40
	 *  array( "subclient1", "subclient2" )
41
	 *
42
	 * you can easily change the order of the output by reordering the subparts:
43
	 *
44
	 *  client/html/<clients>/subparts = array( "subclient1", "subclient2" )
45
	 *
46
	 * You can also remove one or more parts if they shouldn't be rendered:
47
	 *
48
	 *  client/html/<clients>/subparts = array( "subclient1" )
49
	 *
50
	 * As the clients only generates structural HTML, the layout defined via CSS
51
	 * should support adding, removing or reordering content by a fluid like
52
	 * design.
53
	 *
54
	 * @param array List of sub-client names
55
	 * @since 2014.03
56
	 * @category Developer
57
	 */
58
	private $subPartPath = 'client/html/catalog/detail/standard/subparts';
59
60
	/** client/html/catalog/detail/service/name
61
	 * Name of the shipping cost part used by the catalog detail client implementation
62
	 *
63
	 * Use "Myname" if your class is named "\Aimeos\Client\Html\Catalog\Detail\Service\Myname".
64
	 * The name is case-sensitive and you should avoid camel case names like "MyName".
65
	 *
66
	 * @param string Last part of the client class name
67
	 * @since 2017.01
68
	 * @category Developer
69
	 */
70
71
	/** client/html/catalog/detail/seen/name
72
	 * Name of the seen part used by the catalog detail client implementation
73
	 *
74
	 * Use "Myname" if your class is named "\Aimeos\Client\Html\Catalog\Detail\Seen\Myname".
75
	 * The name is case-sensitive and you should avoid camel case names like "MyName".
76
	 *
77
	 * @param string Last part of the client class name
78
	 * @since 2014.03
79
	 * @category Developer
80
	 */
81
82
	/** client/html/catalog/detail/supplier/name
83
	 * Name of the supplier part used by the catalog detail client implementation
84
	 *
85
	 * Use "Myname" if your class is named "\Aimeos\Client\Html\Catalog\Detail\Supplier\Myname".
86
	 * The name is case-sensitive and you should avoid camel case names like "MyName".
87
	 *
88
	 * @param string Last part of the client class name
89
	 * @since 2014.03
90
	 * @category Developer
91
	 */
92
	private $subPartNames = ['seen', 'service', 'supplier'];
93
94
	private $tags = [];
95
	private $expire;
96
	private $view;
97
98
99
	/**
100
	 * Returns the HTML code for insertion into the body.
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 HTML code
104
	 */
105
	public function getBody( string $uid = '' ) : string
106
	{
107
		$prefixes = array( 'd' );
108
		$context = $this->getContext();
109
110
		/** client/html/catalog/detail/cache
111
		 * Enables or disables caching only for the catalog detail component
112
		 *
113
		 * Disable caching for components can be useful if you would have too much
114
		 * entries to cache or if the component contains non-cacheable parts that
115
		 * can't be replaced using the modifyBody() and modifyHeader() methods.
116
		 *
117
		 * @param boolean True to enable caching, false to disable
118
		 * @category Developer
119
		 * @category User
120
		 * @see client/html/catalog/filter/cache
121
		 * @see client/html/catalog/lists/cache
122
		 * @see client/html/catalog/stage/cache
123
		 */
124
125
		/** client/html/catalog/detail
126
		 * All parameters defined for the catalog detail component and its subparts
127
		 *
128
		 * This returns all settings related to the detail component.
129
		 * Please refer to the single settings for details.
130
		 *
131
		 * @param array Associative list of name/value settings
132
		 * @category Developer
133
		 * @see client/html/catalog#detail
134
		 */
135
		$confkey = 'client/html/catalog/detail';
136
137
		if( ( $html = $this->getCached( 'body', $uid, $prefixes, $confkey ) ) === null )
138
		{
139
			$view = $this->getView();
140
141
			/** client/html/catalog/detail/standard/template-body
142
			 * Relative path to the HTML body template of the catalog detail client.
143
			 *
144
			 * The template file contains the HTML code and processing instructions
145
			 * to generate the result shown in the body of the frontend. The
146
			 * configuration string is the path to the template file relative
147
			 * to the templates directory (usually in client/html/templates).
148
			 *
149
			 * You can overwrite the template file configuration in extensions and
150
			 * provide alternative templates. These alternative templates should be
151
			 * named like the default one but with the string "standard" replaced by
152
			 * an unique name. You may use the name of your project for this. If
153
			 * you've implemented an alternative client class as well, "standard"
154
			 * should be replaced by the name of the new class.
155
			 *
156
			 * @param string Relative path to the template creating code for the HTML page body
157
			 * @since 2014.03
158
			 * @category Developer
159
			 * @see client/html/catalog/detail/standard/template-header
160
			 */
161
			$tplconf = 'client/html/catalog/detail/standard/template-body';
162
			$default = 'catalog/detail/body-standard';
163
164
			try
165
			{
166
				if( !isset( $this->view ) ) {
167
					$view = $this->view = $this->getObject()->addData( $view, $this->tags, $this->expire );
168
				}
169
170
				$output = '';
171
				foreach( $this->getSubClients() as $subclient ) {
172
					$output .= $subclient->setView( $view )->getBody( $uid );
173
				}
174
				$view->detailBody = $output;
175
176
				$html = $view->render( $view->config( $tplconf, $default ) );
177
				$this->setCached( 'body', $uid, $prefixes, $confkey, $html, $this->tags, $this->expire );
178
179
				return $html;
180
			}
181
			catch( \Aimeos\Client\Html\Exception $e )
182
			{
183
				$error = array( $context->getI18n()->dt( 'client', $e->getMessage() ) );
184
				$view->detailErrorList = array_merge( $view->get( 'detailErrorList', [] ), $error );
185
			}
186
			catch( \Aimeos\Controller\Frontend\Exception $e )
187
			{
188
				$error = array( $context->getI18n()->dt( 'controller/frontend', $e->getMessage() ) );
189
				$view->detailErrorList = array_merge( $view->get( 'detailErrorList', [] ), $error );
190
			}
191
			catch( \Aimeos\MShop\Exception $e )
192
			{
193
				$error = array( $context->getI18n()->dt( 'mshop', $e->getMessage() ) );
194
				$view->detailErrorList = array_merge( $view->get( 'detailErrorList', [] ), $error );
195
			}
196
			catch( \Exception $e )
197
			{
198
				$error = array( $context->getI18n()->dt( 'client', 'A non-recoverable error occured' ) );
199
				$view->detailErrorList = array_merge( $view->get( 'detailErrorList', [] ), $error );
200
				$this->logException( $e );
201
			}
202
203
			$html = $view->render( $view->config( $tplconf, $default ) );
204
		}
205
		else
206
		{
207
			$html = $this->modifyBody( $html, $uid );
208
		}
209
210
		return $html;
211
	}
212
213
214
	/**
215
	 * Returns the HTML string for insertion into the header.
216
	 *
217
	 * @param string $uid Unique identifier for the output if the content is placed more than once on the same page
218
	 * @return string|null String including HTML tags for the header on error
219
	 */
220
	public function getHeader( string $uid = '' ) : ?string
221
	{
222
		$prefixes = array( 'd' );
223
		$confkey = 'client/html/catalog/detail';
224
225
226
		if( ( $html = $this->getCached( 'header', $uid, $prefixes, $confkey ) ) === null )
227
		{
228
			$view = $this->getView();
229
230
			/** client/html/catalog/detail/standard/template-header
231
			 * Relative path to the HTML header template of the catalog detail client.
232
			 *
233
			 * The template file contains the HTML code and processing instructions
234
			 * to generate the HTML code that is inserted into the HTML page header
235
			 * of the rendered page in the frontend. The configuration string is the
236
			 * path to the template file relative to the templates directory (usually
237
			 * in client/html/templates).
238
			 *
239
			 * You can overwrite the template file configuration in extensions and
240
			 * provide alternative templates. These alternative templates should be
241
			 * named like the default one but with the string "standard" replaced by
242
			 * an unique name. You may use the name of your project for this. If
243
			 * you've implemented an alternative client class as well, "standard"
244
			 * should be replaced by the name of the new class.
245
			 *
246
			 * @param string Relative path to the template creating code for the HTML page head
247
			 * @since 2014.03
248
			 * @category Developer
249
			 * @see client/html/catalog/detail/standard/template-body
250
			 */
251
			$tplconf = 'client/html/catalog/detail/standard/template-header';
252
			$default = 'catalog/detail/header-standard';
253
254
			try
255
			{
256
				if( !isset( $this->view ) ) {
257
					$view = $this->view = $this->getObject()->addData( $view, $this->tags, $this->expire );
258
				}
259
260
				$output = '';
261
				foreach( $this->getSubClients() as $subclient ) {
262
					$output .= $subclient->setView( $view )->getHeader( $uid );
263
				}
264
				$view->detailHeader = $output;
265
266
				$html = $view->render( $view->config( $tplconf, $default ) );
267
				$this->setCached( 'header', $uid, $prefixes, $confkey, $html, $this->tags, $this->expire );
268
269
				return $html;
270
			}
271
			catch( \Exception $e )
272
			{
273
				$this->logException( $e );
274
			}
275
		}
276
		else
277
		{
278
			$html = $this->modifyHeader( $html, $uid );
279
		}
280
281
		return $html;
282
	}
283
284
285
	/**
286
	 * Returns the sub-client given by its name.
287
	 *
288
	 * @param string $type Name of the client type
289
	 * @param string|null $name Name of the sub-client (Default if null)
290
	 * @return \Aimeos\Client\Html\Iface Sub-client object
291
	 */
292
	public function getSubClient( string $type, string $name = null ) : \Aimeos\Client\Html\Iface
293
	{
294
		/** client/html/catalog/detail/decorators/excludes
295
		 * Excludes decorators added by the "common" option from the catalog detail html client
296
		 *
297
		 * Decorators extend the functionality of a class by adding new aspects
298
		 * (e.g. log what is currently done), executing the methods of the underlying
299
		 * class only in certain conditions (e.g. only for logged in users) or
300
		 * modify what is returned to the caller.
301
		 *
302
		 * This option allows you to remove a decorator added via
303
		 * "client/html/common/decorators/default" before they are wrapped
304
		 * around the html client.
305
		 *
306
		 *  client/html/catalog/detail/decorators/excludes = array( 'decorator1' )
307
		 *
308
		 * This would remove the decorator named "decorator1" from the list of
309
		 * common decorators ("\Aimeos\Client\Html\Common\Decorator\*") added via
310
		 * "client/html/common/decorators/default" to the html client.
311
		 *
312
		 * @param array List of decorator names
313
		 * @since 2014.05
314
		 * @category Developer
315
		 * @see client/html/common/decorators/default
316
		 * @see client/html/catalog/detail/decorators/global
317
		 * @see client/html/catalog/detail/decorators/local
318
		 */
319
320
		/** client/html/catalog/detail/decorators/global
321
		 * Adds a list of globally available decorators only to the catalog detail html client
322
		 *
323
		 * Decorators extend the functionality of a class by adding new aspects
324
		 * (e.g. log what is currently done), executing the methods of the underlying
325
		 * class only in certain conditions (e.g. only for logged in users) or
326
		 * modify what is returned to the caller.
327
		 *
328
		 * This option allows you to wrap global decorators
329
		 * ("\Aimeos\Client\Html\Common\Decorator\*") around the html client.
330
		 *
331
		 *  client/html/catalog/detail/decorators/global = array( 'decorator1' )
332
		 *
333
		 * This would add the decorator named "decorator1" defined by
334
		 * "\Aimeos\Client\Html\Common\Decorator\Decorator1" only to the html client.
335
		 *
336
		 * @param array List of decorator names
337
		 * @since 2014.05
338
		 * @category Developer
339
		 * @see client/html/common/decorators/default
340
		 * @see client/html/catalog/detail/decorators/excludes
341
		 * @see client/html/catalog/detail/decorators/local
342
		 */
343
344
		/** client/html/catalog/detail/decorators/local
345
		 * Adds a list of local decorators only to the catalog detail html client
346
		 *
347
		 * Decorators extend the functionality of a class by adding new aspects
348
		 * (e.g. log what is currently done), executing the methods of the underlying
349
		 * class only in certain conditions (e.g. only for logged in users) or
350
		 * modify what is returned to the caller.
351
		 *
352
		 * This option allows you to wrap local decorators
353
		 * ("\Aimeos\Client\Html\Catalog\Decorator\*") around the html client.
354
		 *
355
		 *  client/html/catalog/detail/decorators/local = array( 'decorator2' )
356
		 *
357
		 * This would add the decorator named "decorator2" defined by
358
		 * "\Aimeos\Client\Html\Catalog\Decorator\Decorator2" only to the html client.
359
		 *
360
		 * @param array List of decorator names
361
		 * @since 2014.05
362
		 * @category Developer
363
		 * @see client/html/common/decorators/default
364
		 * @see client/html/catalog/detail/decorators/excludes
365
		 * @see client/html/catalog/detail/decorators/global
366
		 */
367
368
		return $this->createSubClient( 'catalog/detail/' . $type, $name );
369
	}
370
371
372
	/**
373
	 * Modifies the cached body content to replace content based on sessions or cookies.
374
	 *
375
	 * @param string $content Cached content
376
	 * @param string $uid Unique identifier for the output if the content is placed more than once on the same page
377
	 * @return string Modified body content
378
	 */
379
	public function modifyBody( string $content, string $uid ) : string
380
	{
381
		$content = parent::modifyBody( $content, $uid );
382
383
		return $this->replaceSection( $content, $this->getView()->csrf()->formfield(), 'catalog.detail.csrf' );
384
	}
385
386
387
	/**
388
	 * Processes the input, e.g. store given values.
389
	 *
390
	 * A view must be available and this method doesn't generate any output
391
	 * besides setting view variables if necessary.
392
	 */
393
	public function process()
394
	{
395
		$context = $this->getContext();
396
		$view = $this->getView();
397
398
		try
399
		{
400
			$site = $context->getLocale()->getSiteItem()->getCode();
401
			$params = $this->getClientParams( $view->param() );
402
			$context->getSession()->set( 'aimeos/catalog/detail/params/last/' . $site, $params );
403
404
			parent::process();
405
		}
406
		catch( \Aimeos\Client\Html\Exception $e )
407
		{
408
			$error = array( $context->getI18n()->dt( 'client', $e->getMessage() ) );
409
			$view->detailErrorList = array_merge( $view->get( 'detailErrorList', [] ), $error );
410
		}
411
		catch( \Aimeos\Controller\Frontend\Exception $e )
412
		{
413
			$error = array( $context->getI18n()->dt( 'controller/frontend', $e->getMessage() ) );
414
			$view->detailErrorList = array_merge( $view->get( 'detailErrorList', [] ), $error );
415
		}
416
		catch( \Aimeos\MShop\Exception $e )
417
		{
418
			$error = array( $context->getI18n()->dt( 'mshop', $e->getMessage() ) );
419
			$view->detailErrorList = array_merge( $view->get( 'detailErrorList', [] ), $error );
420
		}
421
		catch( \Exception $e )
422
		{
423
			$error = array( $context->getI18n()->dt( 'client', 'A non-recoverable error occured' ) );
424
			$view->detailErrorList = array_merge( $view->get( 'detailErrorList', [] ), $error );
425
			$this->logException( $e );
426
		}
427
	}
428
429
430
	/**
431
	 * Returns the list of sub-client names configured for the client.
432
	 *
433
	 * @return array List of HTML client names
434
	 */
435
	protected function getSubClientNames() : array
436
	{
437
		return $this->getContext()->getConfig()->get( $this->subPartPath, $this->subPartNames );
438
	}
439
440
441
	/**
442
	 * Sets the necessary parameter values in the view.
443
	 *
444
	 * @param \Aimeos\MW\View\Iface $view The view object which generates the HTML output
445
	 * @param array &$tags Result array for the list of tags that are associated to the output
446
	 * @param string|null &$expire Result variable for the expiration date of the output (null for no expiry)
447
	 * @return \Aimeos\MW\View\Iface Modified view object
448
	 */
449
	public function addData( \Aimeos\MW\View\Iface $view, array &$tags = [], string &$expire = null ) : \Aimeos\MW\View\Iface
450
	{
451
		$context = $this->getContext();
452
		$config = $context->getConfig();
453
		$domains = ['attribute', 'media', 'price', 'product', 'product/property', 'text'];
454
455
		/** client/html/catalog/domains
456
		 * A list of domain names whose items should be available in the catalog view templates
457
		 *
458
		 * @see client/html/catalog/detail/domains
459
		 */
460
		$domains = $config->get( 'client/html/catalog/domains', $domains );
461
462
		/** client/html/catalog/detail/domains
463
		 * A list of domain names whose items should be available in the product detail view template
464
		 *
465
		 * The templates rendering product details usually add the images,
466
		 * prices, texts, attributes, products, etc. associated to the product
467
		 * item. If you want to display additional or less content, you can
468
		 * configure your own list of domains (attribute, media, price, product,
469
		 * text, etc. are domains) whose items are fetched from the storage.
470
		 * Please keep in mind that the more domains you add to the configuration,
471
		 * the more time is required for fetching the content!
472
		 *
473
		 * Since version 2014.05 this configuration option overwrites the
474
		 * "client/html/catalog/domains" option that allows to configure the
475
		 * domain names of the items fetched for all catalog related data.
476
		 *
477
		 * @param array List of domain names
478
		 * @since 2014.03
479
		 * @category Developer
480
		 * @see client/html/catalog/domains
481
		 * @see client/html/catalog/lists/domains
482
		 */
483
		$domains = $config->get( 'client/html/catalog/detail/domains', $domains );
484
485
		/** client/html/catalog/detail/prodid-default
486
		 * The default product ID used if none is given as parameter
487
		 *
488
		 * To display a product detail view or a part of it for a specific
489
		 * product, you can configure its ID using this setting. This is
490
		 * most useful in a CMS where the product ID can be configured
491
		 * separately for each content node.
492
		 *
493
		 * @param string Product ID
494
		 * @since 2016.01
495
		 * @category User
496
		 * @category Developer
497
		 * @see client/html/catalog/detail/prodid-default
498
		 * @see client/html/catalog/lists/catid-default
499
		 */
500
		$id = $view->param( 'd_prodid', $config->get( 'client/html/catalog/detail/prodid-default' ) );
501
502
		/** client/html/catalog/detail/prodcode-default
503
		 * The default product code used if none is given as parameter
504
		 *
505
		 * To display a product detail view or a part of it for a specific
506
		 * product, you can configure its code using this setting. This is
507
		 * most useful in a CMS where the product code can be configured
508
		 * separately for each content node.
509
		 *
510
		 * @param string Product code
511
		 * @since 2019.10
512
		 * @category User
513
		 * @category Developer
514
		 * @see client/html/catalog/detail/prodid-default
515
		 * @see client/html/catalog/lists/catid-default
516
		 */
517
		$code = $config->get( 'client/html/catalog/detail/prodcode-default' );
518
519
		$name = $view->param( 'd_name', '' );
520
		$cntl = \Aimeos\Controller\Frontend::create( $context, 'product' )->uses( $domains );
521
522
		$productItem = ( $id ? $cntl->get( $id ) : ( $code ? $cntl->find( $code ) : $cntl->resolve( $name ) ) );
523
		$this->addMetaItems( $productItem, $expire, $tags );
524
525
		$propMap = $attrMap = [];
526
		$propItems = $productItem->getPropertyItems();
527
		$attrItems = $productItem->getRefItems( 'attribute', null, 'default' );
528
		$mediaItems = $productItem->getRefItems( 'media', 'default', 'default' );
529
530
		if( in_array( $productItem->getType(), ['bundle', 'select'] ) )
531
		{
532
			foreach( $productItem->getRefItems( 'product', null, 'default' ) as $subProdId => $subProduct )
533
			{
534
				$propItems->merge( $subProduct->getPropertyItems()->assign( ['parent', $subProdId] ) );
535
				$mediaItems->merge( $subProduct->getRefItems( 'media', 'default', 'default' ) );
536
				$attrItems->merge( $subProduct->getRefItems( 'attribute', null, 'default' )
537
					->merge( $subProduct->getRefItems( 'attribute', null, 'variant' ) )
538
					->assign( ['parent', $subProdId] ) );
539
			}
540
		}
541
542
		foreach( $attrItems as $attrId => $attrItem ) {
543
			$attrMap[$attrItem->getType()][$attrId] = $attrItem;
544
		}
545
546
		foreach( $propItems as $propItem ) {
547
			$propMap[$propItem->getType()][$propItem->getId()] = $propItem;
548
		}
549
550
551
		/** client/html/catalog/detail/stock/enable
552
		 * Enables or disables displaying product stock levels in product detail view
553
		 *
554
		 * This configuration option allows shop owners to display product
555
		 * stock levels for each product in the detail views or to disable
556
		 * fetching product stock information.
557
		 *
558
		 * The stock information is fetched via AJAX and inserted via Javascript.
559
		 * This allows to cache product items by leaving out such highly
560
		 * dynamic content like stock levels which changes with each order.
561
		 *
562
		 * @param boolean Value of "1" to display stock levels, "0" to disable displaying them
563
		 * @since 2014.03
564
		 * @category User
565
		 * @category Developer
566
		 * @see client/html/catalog/lists/stock/enable
567
		 * @see client/html/catalog/stock/url/target
568
		 * @see client/html/catalog/stock/url/controller
569
		 * @see client/html/catalog/stock/url/action
570
		 * @see client/html/catalog/stock/url/config
571
		 */
572
573
		if( (bool) $view->config( 'client/html/catalog/detail/stock/enable', true ) === true )
574
		{
575
			$products = $productItem->getRefItems( 'product', null, 'default' )->push( $productItem );
576
			$view->detailStockUrl = $this->getStockUrl( $view, $products );
577
		}
578
579
		$view->detailMediaItems = $mediaItems;
580
		$view->detailProductItem = $productItem;
581
		$view->detailPropertyMap = map( $propMap )->ksort();
582
		$view->detailAttributeMap = map( $attrMap )->ksort();
583
584
		return parent::addData( $view, $tags, $expire );
585
	}
586
}
587