Standard   A
last analyzed

Complexity

Total Complexity 16

Size/Duplication

Total Lines 408
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 108
dl 0
loc 408
rs 10
c 0
b 0
f 0
wmc 16

6 Methods

Rating   Name   Duplication   Size   Complexity  
A getItem() 0 14 2
B get() 0 83 8
A getController() 0 51 2
A getItems() 0 13 2
A aggregate() 0 7 1
A options() 0 64 1
1
<?php
2
3
/**
4
 * @license LGPLv3, http://opensource.org/licenses/LGPL-3.0
5
 * @copyright Aimeos (aimeos.org), 2017-2025
6
 * @package Client
7
 * @subpackage JsonApi
8
 */
9
10
11
namespace Aimeos\Client\JsonApi\Product;
12
13
use Psr\Http\Message\ResponseInterface;
14
use Psr\Http\Message\ServerRequestInterface;
15
16
17
/**
18
 * JSON API standard client
19
 *
20
 * @package Client
21
 * @subpackage JsonApi
22
 */
23
class Standard
24
	extends \Aimeos\Client\JsonApi\Base
25
	implements \Aimeos\Client\JsonApi\Iface
26
{
27
	/** client/jsonapi/product/name
28
	 * Class name of the used product client implementation
29
	 *
30
	 * Each default JSON API client can be replace by an alternative imlementation.
31
	 * To use this implementation, you have to set the last part of the class
32
	 * name as configuration value so the client factory knows which class it
33
	 * has to instantiate.
34
	 *
35
	 * For example, if the name of the default class is
36
	 *
37
	 *  \Aimeos\Client\JsonApi\Product\Standard
38
	 *
39
	 * and you want to replace it with your own version named
40
	 *
41
	 *  \Aimeos\Client\JsonApi\Product\Myproduct
42
	 *
43
	 * then you have to set the this configuration option:
44
	 *
45
	 *  client/jsonapi/product/name = Myproduct
46
	 *
47
	 * The value is the last part of your own class name and it's case sensitive,
48
	 * so take care that the configuration value is exactly named like the last
49
	 * part of the class name.
50
	 *
51
	 * The allowed characters of the class name are A-Z, a-z and 0-9. No other
52
	 * characters are possible! You should always start the last part of the class
53
	 * name with an upper case character and continue only with lower case characters
54
	 * or numbers. Avoid chamel case names like "MyProduct"!
55
	 *
56
	 * @param string Last part of the class name
57
	 * @since 2017.03
58
	 * @category Developer
59
	 */
60
61
	/** client/jsonapi/product/decorators/excludes
62
	 * Excludes decorators added by the "common" option from the JSON API clients
63
	 *
64
	 * Decorators extend the functionality of a class by adding new aspects
65
	 * (e.g. log what is currently done), executing the methods of the underlying
66
	 * class only in certain conditions (e.g. only for logged in users) or
67
	 * modify what is returned to the caller.
68
	 *
69
	 * This option allows you to remove a decorator added via
70
	 * "client/jsonapi/common/decorators/default" before they are wrapped
71
	 * around the JsonApi client.
72
	 *
73
	 *  client/jsonapi/decorators/excludes = array( 'decorator1' )
74
	 *
75
	 * This would remove the decorator named "decorator1" from the list of
76
	 * common decorators ("\Aimeos\Client\JsonApi\Common\Decorator\*") added via
77
	 * "client/jsonapi/common/decorators/default" for the JSON API client.
78
	 *
79
	 * @param array List of decorator names
80
	 * @since 2017.07
81
	 * @category Developer
82
	 * @see client/jsonapi/common/decorators/default
83
	 * @see client/jsonapi/product/decorators/global
84
	 * @see client/jsonapi/product/decorators/local
85
	 */
86
87
	/** client/jsonapi/product/decorators/global
88
	 * Adds a list of globally available decorators only to the JsonApi client
89
	 *
90
	 * Decorators extend the functionality of a class by adding new aspects
91
	 * (e.g. log what is currently done), executing the methods of the underlying
92
	 * class only in certain conditions (e.g. only for logged in users) or
93
	 * modify what is returned to the caller.
94
	 *
95
	 * This option allows you to wrap global decorators
96
	 * ("\Aimeos\Client\JsonApi\Common\Decorator\*") around the JsonApi
97
	 * client.
98
	 *
99
	 *  client/jsonapi/product/decorators/global = array( 'decorator1' )
100
	 *
101
	 * This would add the decorator named "decorator1" defined by
102
	 * "\Aimeos\Client\JsonApi\Common\Decorator\Decorator1" only to the
103
	 * "product" JsonApi client.
104
	 *
105
	 * @param array List of decorator names
106
	 * @since 2017.07
107
	 * @category Developer
108
	 * @see client/jsonapi/common/decorators/default
109
	 * @see client/jsonapi/product/decorators/excludes
110
	 * @see client/jsonapi/product/decorators/local
111
	 */
112
113
	/** client/jsonapi/product/decorators/local
114
	 * Adds a list of local decorators only to the JsonApi client
115
	 *
116
	 * Decorators extend the functionality of a class by adding new aspects
117
	 * (e.g. log what is currently done), executing the methods of the underlying
118
	 * class only in certain conditions (e.g. only for logged in users) or
119
	 * modify what is returned to the caller.
120
	 *
121
	 * This option allows you to wrap local decorators
122
	 * ("\Aimeos\Client\JsonApi\Product\Decorator\*") around the JsonApi
123
	 * client.
124
	 *
125
	 *  client/jsonapi/product/decorators/local = array( 'decorator2' )
126
	 *
127
	 * This would add the decorator named "decorator2" defined by
128
	 * "\Aimeos\Client\JsonApi\Product\Decorator\Decorator2" only to the
129
	 * "product" JsonApi client.
130
	 *
131
	 * @param array List of decorator names
132
	 * @since 2017.07
133
	 * @category Developer
134
	 * @see client/jsonapi/common/decorators/default
135
	 * @see client/jsonapi/product/decorators/excludes
136
	 * @see client/jsonapi/product/decorators/global
137
	 */
138
139
140
	/**
141
	 * Returns the resource or the resource list
142
	 *
143
	 * @param \Psr\Http\Message\ServerRequestInterface $request Request object
144
	 * @param \Psr\Http\Message\ResponseInterface $response Response object
145
	 * @return \Psr\Http\Message\ResponseInterface Modified response object
146
	 */
147
	public function get( ServerRequestInterface $request, ResponseInterface $response ) : \Psr\Http\Message\ResponseInterface
148
	{
149
		$view = $this->view();
150
151
		try
152
		{
153
			if( $view->param( 'aggregate' ) != '' ) {
154
				$response = $this->aggregate( $view, $request, $response );
155
			} elseif( $view->param( 'id' ) != '' ) {
156
				$response = $this->getItem( $view, $request, $response );
157
			} else {
158
				$response = $this->getItems( $view, $request, $response );
159
			}
160
161
			$status = 200;
162
		}
163
		catch( \Aimeos\MShop\Exception $e )
164
		{
165
			$status = 404;
166
			$view->errors = $this->getErrorDetails( $e, 'mshop' );
167
		}
168
		catch( \Exception $e )
169
		{
170
			$status = $e->getCode() >= 100 && $e->getCode() < 600 ? $e->getCode() : 500;
171
			$view->errors = $this->getErrorDetails( $e );
172
		}
173
174
		if( $view->param( 'aggregate' ) != '' )
175
		{
176
			/** client/jsonapi/product/template-aggregate
177
			 * Relative path to the product aggregate JSON API template
178
			 *
179
			 * The template file contains the code and processing instructions
180
			 * to generate the result shown in the JSON API body. The
181
			 * configuration string is the path to the template file relative
182
			 * to the templates directory (usually in templates/client/jsonapi).
183
			 *
184
			 * You can overwrite the template file configuration in extensions and
185
			 * provide alternative templates. These alternative templates should be
186
			 * named like the default one but with the string "standard" replaced by
187
			 * an unique name. You may use the name of your project for this. If
188
			 * you've implemented an alternative client class as well, "standard"
189
			 * should be replaced by the name of the new class.
190
			 *
191
			 * @param string Relative path to the template creating the list of aggregated product counts
192
			 * @since 2017.03
193
			 * @category Developer
194
			 */
195
			$tplconf = 'client/jsonapi/product/template-aggregate';
196
			$default = 'aggregate-standard';
197
		}
198
		else
199
		{
200
			/** client/jsonapi/product/template
201
			 * Relative path to the product JSON API template
202
			 *
203
			 * The template file contains the code and processing instructions
204
			 * to generate the result shown in the JSON API body. The
205
			 * configuration string is the path to the template file relative
206
			 * to the templates directory (usually in templates/client/jsonapi).
207
			 *
208
			 * You can overwrite the template file configuration in extensions and
209
			 * provide alternative templates. These alternative templates should be
210
			 * named like the default one but with the string "standard" replaced by
211
			 * an unique name. You may use the name of your project for this. If
212
			 * you've implemented an alternative client class as well, "standard"
213
			 * should be replaced by the name of the new class.
214
			 *
215
			 * @param string Relative path to the template creating the body for the GET method of the JSON API
216
			 * @since 2017.03
217
			 * @category Developer
218
			 */
219
			$tplconf = 'client/jsonapi/product/template';
220
			$default = 'product/standard';
221
		}
222
223
		$body = $view->render( $view->config( $tplconf, $default ) );
224
225
		return $response->withHeader( 'Allow', 'GET,OPTIONS' )
226
			->withHeader( 'Cache-Control', 'max-age=300' )
227
			->withHeader( 'Content-Type', 'application/vnd.api+json' )
228
			->withBody( $view->response()->createStreamFromString( $body ) )
229
			->withStatus( $status );
0 ignored issues
show
Bug introduced by
The method withStatus() does not exist on Psr\Http\Message\MessageInterface. It seems like you code against a sub-type of Psr\Http\Message\MessageInterface such as Psr\Http\Message\ResponseInterface or Aimeos\Base\View\Helper\Request\Standard. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

229
			->/** @scrutinizer ignore-call */ withStatus( $status );
Loading history...
230
	}
231
232
233
	/**
234
	 * Returns the available REST verbs and the available parameters
235
	 *
236
	 * @param \Psr\Http\Message\ServerRequestInterface $request Request object
237
	 * @param \Psr\Http\Message\ResponseInterface $response Response object
238
	 * @return \Psr\Http\Message\ResponseInterface Modified response object
239
	 */
240
	public function options( ServerRequestInterface $request, ResponseInterface $response ) : \Psr\Http\Message\ResponseInterface
241
	{
242
		$view = $this->view();
243
244
		$view->filter = [
245
			'f_search' => [
246
				'label' => 'Return products whose text matches the user input',
247
				'type' => 'string', 'default' => '', 'required' => false,
248
			],
249
			'f_catid' => [
250
				'label' => 'Return products associated to this category ID',
251
				'type' => 'string|array', 'default' => '', 'required' => false,
252
			],
253
			'f_listtype' => [
254
				'label' => 'Return products which are associated to categories with this list type',
255
				'type' => 'string', 'default' => 'default', 'required' => false,
256
			],
257
			'f_attrid' => [
258
				'label' => 'Return products that reference all attribute IDs',
259
				'type' => 'array', 'default' => '[]', 'required' => false,
260
			],
261
			'f_optid' => [
262
				'label' => 'Return products that reference at least one of the attribute IDs',
263
				'type' => 'array', 'default' => '[]', 'required' => false,
264
			],
265
			'f_oneid' => [
266
				'label' => 'Return products that reference at least one of the attribute IDs per attribute type',
267
				'type' => 'array[<typecode>]', 'default' => '[]', 'required' => false,
268
			],
269
			'f_supid' => [
270
				'label' => 'Return products that reference at least one of the supplier IDs',
271
				'type' => 'array', 'default' => '[]', 'required' => false,
272
			],
273
		];
274
275
		$view->sort = [
276
			'relevance' => [
277
				'label' => 'Sort products by their category position',
278
				'type' => 'string', 'default' => true, 'required' => false,
279
			],
280
			'name' => [
281
				'label' => 'Sort products by their name (ascending, "-name" for descending)',
282
				'type' => 'string', 'default' => false, 'required' => false,
283
			],
284
			'price' => [
285
				'label' => 'Sort products by their price (ascending, "-price" for descending)',
286
				'type' => 'string', 'default' => false, 'required' => false,
287
			],
288
			'ctime' => [
289
				'label' => 'Sort products by their creating date/time (ascending, "-ctime" for descending)',
290
				'type' => 'string', 'default' => false, 'required' => false,
291
			],
292
		];
293
294
		$tplconf = 'client/jsonapi/template-options';
295
		$default = 'options-standard';
296
297
		$body = $view->render( $view->config( $tplconf, $default ) );
298
299
		return $response->withHeader( 'Allow', 'GET,OPTIONS' )
300
			->withHeader( 'Cache-Control', 'max-age=300' )
301
			->withHeader( 'Content-Type', 'application/vnd.api+json' )
302
			->withBody( $view->response()->createStreamFromString( $body ) )
303
			->withStatus( 200 );
304
	}
305
306
307
	/**
308
	 * Counts the number of products for the requested key
309
	 *
310
	 * @param \Aimeos\Base\View\Iface $view View instance
311
	 * @param \Psr\Http\Message\ServerRequestInterface $request Request object
312
	 * @param \Psr\Http\Message\ResponseInterface $response Response object
313
	 * @return \Psr\Http\Message\ResponseInterface Modified response object
314
	 */
315
	protected function aggregate( \Aimeos\Base\View\Iface $view, ServerRequestInterface $request, ResponseInterface $response ) : \Psr\Http\Message\ResponseInterface
316
	{
317
		$view->data = $this->getController( $view )->sort()
318
			->slice( $view->param( 'page/offset', 0 ), $view->param( 'page/limit', 10000 ) )
319
			->aggregate( $view->param( 'aggregate' ) );
320
321
		return $response;
322
	}
323
324
325
	/**
326
	 * Returns the initialized product controller
327
	 *
328
	 * @param \Aimeos\Base\View\Iface $view View instance
329
	 * @return \Aimeos\Controller\Frontend\Product\Iface Initialized product controller
330
	 */
331
	protected function getController( \Aimeos\Base\View\Iface $view )
332
	{
333
		$context = $this->context();
334
		$cntl = \Aimeos\Controller\Frontend::create( $context, 'product' )->sort( $view->param( 'sort', 'relevance' ) );
335
336
		/** client/jsonapi/product/levels
337
		 * Include products of sub-categories in the product list of the current category
338
		 *
339
		 * Sometimes it may be useful to show products of sub-categories in the
340
		 * current category product list, e.g. if the current category contains
341
		 * no products at all or if there are only a few products in all categories.
342
		 *
343
		 * Possible constant values for this setting are:
344
		 * * 1 : Only products from the current category
345
		 * * 2 : Products from the current category and the direct child categories
346
		 * * 3 : Products from the current category and the whole category sub-tree
347
		 *
348
		 * Caution: Please keep in mind that displaying products of sub-categories
349
		 * can slow down your shop, especially if it contains more than a few
350
		 * products! You have no real control over the positions of the products
351
		 * in the result list too because all products from different categories
352
		 * with the same position value are placed randomly.
353
		 *
354
		 * Usually, a better way is to associate products to all categories they
355
		 * should be listed in. This can be done manually if there are only a few
356
		 * ones or during the product import automatically.
357
		 *
358
		 * @param integer Tree level constant
359
		 * @since 2017.03
360
		 * @category Developer
361
		 */
362
		$level = $context->config()->get( 'client/jsonapi/product/levels', \Aimeos\MW\Tree\Manager\Base::LEVEL_ONE );
363
364
		foreach( (array) $view->param( 'filter/f_oneid', [] ) as $list ) {
365
			$cntl->oneOf( $list );
366
		}
367
368
		$cntl->allOf( $view->param( 'filter/f_attrid', [] ) )
369
			->oneOf( $view->param( 'filter/f_optid', [] ) )
370
			->text( $view->param( 'filter/f_search' ) )
371
			->price( $view->param( 'filter/f_price' ) )
372
			->supplier( $view->param( 'filter/f_supid', [] ), $view->param( 'filter/f_listtype', 'default' ) )
373
			->category( $view->param( 'filter/f_catid' ), $view->param( 'filter/f_listtype', 'default' ), $level );
374
375
		$params = (array) $view->param( 'filter', [] );
376
377
		unset( $params['f_catid'], $params['f_listtype'] );
378
		unset( $params['f_supid'], $params['f_search'], $params['f_price'] );
379
		unset( $params['f_attrid'], $params['f_optid'], $params['f_oneid'] );
380
381
		return $cntl->parse( $params )->slice( $view->param( 'page/offset', 0 ), $view->param( 'page/limit', 48 ) );
382
	}
383
384
385
	/**
386
	 * Retrieves the item and adds the data to the view
387
	 *
388
	 * @param \Aimeos\Base\View\Iface $view View instance
389
	 * @param \Psr\Http\Message\ServerRequestInterface $request Request object
390
	 * @param \Psr\Http\Message\ResponseInterface $response Response object
391
	 * @return \Psr\Http\Message\ResponseInterface Modified response object
392
	 */
393
	protected function getItem( \Aimeos\Base\View\Iface $view, ServerRequestInterface $request, ResponseInterface $response ) : \Psr\Http\Message\ResponseInterface
394
	{
395
		$ref = $view->param( 'include', [] );
396
397
		if( is_string( $ref ) ) {
398
			$ref = explode( ',', str_replace( '.', '/', $ref ) );
399
		}
400
401
		$cntl = \Aimeos\Controller\Frontend::create( $this->context(), 'product' );
402
403
		$view->items = $cntl->uses( $ref )->get( $view->param( 'id' ) );
404
		$view->total = 1;
405
406
		return $response;
407
	}
408
409
410
	/**
411
	 * Retrieves the items and adds the data to the view
412
	 *
413
	 * @param \Aimeos\Base\View\Iface $view View instance
414
	 * @param \Psr\Http\Message\ServerRequestInterface $request Request object
415
	 * @param \Psr\Http\Message\ResponseInterface $response Response object
416
	 * @return \Psr\Http\Message\ResponseInterface Modified response object
417
	 */
418
	protected function getItems( \Aimeos\Base\View\Iface $view, ServerRequestInterface $request, ResponseInterface $response ) : \Psr\Http\Message\ResponseInterface
419
	{
420
		$total = 0;
421
		$ref = $view->param( 'include', [] );
422
423
		if( is_string( $ref ) ) {
424
			$ref = explode( ',', str_replace( '.', '/', $ref ) );
425
		}
426
427
		$view->items = $this->getController( $view )->uses( $ref )->search( $total );
428
		$view->total = $total;
429
430
		return $response;
431
	}
432
}
433