Completed
Push — master ( 7da046...265fe5 )
by Aimeos
02:41
created

Base::addItemData()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 19
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 19
rs 9.4285
cc 3
eloc 10
nc 3
nop 4
1
<?php
2
3
/**
4
 * @license LGPLv3, http://opensource.org/licenses/LGPL-3.0
5
 * @copyright Aimeos (aimeos.org), 2015-2016
6
 * @package Admin
7
 * @subpackage JsonAdm
8
 */
9
10
11
namespace Aimeos\Admin\JsonAdm;
12
13
14
/**
15
 * JSON API common client
16
 *
17
 * @package Admin
18
 * @subpackage JsonAdm
19
 */
20
class Base
21
{
22
	private $view;
23
	private $context;
24
	private $templatePaths;
25
	private $path;
26
27
28
	/**
29
	 * Initializes the client
30
	 *
31
	 * @param \Aimeos\MShop\Context\Item\Iface $context MShop context object
32
	 * @param \Aimeos\MW\View\Iface $view View object
33
	 * @param array $templatePaths List of file system paths where the templates are stored
34
	 * @param string $path Name of the client separated by slashes, e.g "product/stock"
35
	 */
36
	public function __construct( \Aimeos\MShop\Context\Item\Iface $context, \Aimeos\MW\View\Iface $view, array $templatePaths, $path )
37
	{
38
		$this->view = $view;
39
		$this->context = $context;
40
		$this->templatePaths = $templatePaths;
41
		$this->path = $path;
42
	}
43
44
45
	/**
46
	 * Deletes the resource or the resource list
47
	 *
48
	 * @param string $body Request body
49
	 * @param array &$header Variable which contains the HTTP headers and the new ones afterwards
50
	 * @param integer &$status Variable which contains the HTTP status afterwards
51
	 * @return string Content for response body
52
	 */
53
	public function delete( $body, array &$header, &$status )
54
	{
55
		$header = array( 'Content-Type' => 'application/vnd.api+json; supported-ext="bulk"' );
56
		$context = $this->getContext();
57
		$view = $this->getView();
58
59
		try
60
		{
61
			$view = $this->deleteItems( $view, $body );
62
			$status = 200;
63
		}
64
		catch( \Aimeos\Admin\JsonAdm\Exception $e )
65
		{
66
			$status = $e->getCode();
67
			$view->errors = array( array(
68
				'title' => $context->getI18n()->dt( 'admin/jsonadm', $e->getMessage() ),
69
				'detail' => $e->getTraceAsString(),
70
			) );
71
		}
72
		catch( \Aimeos\MShop\Exception $e )
73
		{
74
			$status = 404;
75
			$view->errors = array( array(
76
				'title' => $context->getI18n()->dt( 'mshop', $e->getMessage() ),
77
				'detail' => $e->getTraceAsString(),
78
			) );
79
		}
80
		catch( \Exception $e )
81
		{
82
			$status = 500;
83
			$view->errors = array( array(
84
				'title' => $e->getMessage(),
85
				'detail' => $e->getTraceAsString(),
86
			) );
87
		}
88
89
		/** admin/jsonadm/standard/template-delete
90
		 * Relative path to the JSON API template for DELETE requests
91
		 *
92
		 * The template file contains the code and processing instructions
93
		 * to generate the result shown in the JSON API body. The
94
		 * configuration string is the path to the template file relative
95
		 * to the templates directory (usually in admin/jsonadm/templates).
96
		 *
97
		 * You can overwrite the template file configuration in extensions and
98
		 * provide alternative templates. These alternative templates should be
99
		 * named like the default one but with the string "standard" replaced by
100
		 * an unique name. You may use the name of your project for this. If
101
		 * you've implemented an alternative client class as well, "standard"
102
		 * should be replaced by the name of the new class.
103
		 *
104
		 * @param string Relative path to the template creating the body for the DELETE method of the JSON API
105
		 * @since 2015.12
106
		 * @category Developer
107
		 * @see admin/jsonadm/standard/template-get
108
		 * @see admin/jsonadm/standard/template-patch
109
		 * @see admin/jsonadm/standard/template-post
110
		 * @see admin/jsonadm/standard/template-put
111
		 * @see admin/jsonadm/standard/template-options
112
		 */
113
		$tplconf = 'admin/jsonadm/standard/template-delete';
114
		$default = 'delete-default.php';
115
116
		return $view->render( $view->config( $tplconf, $default ) );
117
	}
118
119
120
	/**
121
	 * Returns the requested resource or the resource list
122
	 *
123
	 * @param string $body Request body
124
	 * @param array &$header Variable which contains the HTTP headers and the new ones afterwards
125
	 * @param integer &$status Variable which contains the HTTP status afterwards
126
	 * @return string Content for response body
127
	 */
128
	public function get( $body, array &$header, &$status )
129
	{
130
		$header = array( 'Content-Type' => 'application/vnd.api+json; supported-ext="bulk"' );
131
		$view = $this->getView();
132
133
		try
134
		{
135
			$view = $this->getItem( $view );
136
			$status = 200;
137
		}
138
		catch( \Aimeos\MShop\Exception $e )
139
		{
140
			$status = 404;
141
			$view->errors = array( array(
142
				'title' => $this->getContext()->getI18n()->dt( 'mshop', $e->getMessage() ),
143
				'detail' => $e->getTraceAsString(),
144
			) );
145
		}
146
		catch( \Exception $e )
147
		{
148
			$status = 500;
149
			$view->errors = array( array(
150
				'title' => $e->getMessage(),
151
				'detail' => $e->getTraceAsString(),
152
			) );
153
		}
154
155
		/** admin/jsonadm/standard/template-get
156
		 * Relative path to the JSON API template for GET requests
157
		 *
158
		 * The template file contains the code and processing instructions
159
		 * to generate the result shown in the JSON API body. The
160
		 * configuration string is the path to the template file relative
161
		 * to the templates directory (usually in admin/jsonadm/templates).
162
		 *
163
		 * You can overwrite the template file configuration in extensions and
164
		 * provide alternative templates. These alternative templates should be
165
		 * named like the default one but with the string "standard" replaced by
166
		 * an unique name. You may use the name of your project for this. If
167
		 * you've implemented an alternative client class as well, "standard"
168
		 * should be replaced by the name of the new class.
169
		 *
170
		 * @param string Relative path to the template creating the body for the GET method of the JSON API
171
		 * @since 2015.12
172
		 * @category Developer
173
		 * @see admin/jsonadm/standard/template-delete
174
		 * @see admin/jsonadm/standard/template-patch
175
		 * @see admin/jsonadm/standard/template-post
176
		 * @see admin/jsonadm/standard/template-put
177
		 * @see admin/jsonadm/standard/template-options
178
		 */
179
		$tplconf = 'admin/jsonadm/standard/template-get';
180
		$default = 'get-default.php';
181
182
		return $view->render( $view->config( $tplconf, $default ) );
183
	}
184
185
186
	/**
187
	 * Updates the resource or the resource list partitially
188
	 *
189
	 * @param string $body Request body
190
	 * @param array &$header Variable which contains the HTTP headers and the new ones afterwards
191
	 * @param integer &$status Variable which contains the HTTP status afterwards
192
	 * @return string Content for response body
193
	 */
194
	public function patch( $body, array &$header, &$status )
195
	{
196
		$header = array( 'Content-Type' => 'application/vnd.api+json; supported-ext="bulk"' );
197
		$context = $this->getContext();
198
		$view = $this->getView();
199
200
		try
201
		{
202
			$view = $this->patchItems( $view, $body, $header );
203
			$status = 200;
204
		}
205
		catch( \Aimeos\Admin\JsonAdm\Exception $e )
206
		{
207
			$status = $e->getCode();
208
			$view->errors = array( array(
209
				'title' => $context->getI18n()->dt( 'admin/jsonadm', $e->getMessage() ),
210
				'detail' => $e->getTraceAsString(),
211
			) );
212
		}
213
		catch( \Aimeos\MShop\Exception $e )
214
		{
215
			$status = 404;
216
			$view->errors = array( array(
217
				'title' => $context->getI18n()->dt( 'mshop', $e->getMessage() ),
218
				'detail' => $e->getTraceAsString(),
219
			) );
220
		}
221
		catch( \Exception $e )
222
		{
223
			$status = 500;
224
			$view->errors = array( array(
225
				'title' => $e->getMessage(),
226
				'detail' => $e->getTraceAsString(),
227
			) );
228
		}
229
230
		/** admin/jsonadm/standard/template-patch
231
		 * Relative path to the JSON API template for PATCH requests
232
		 *
233
		 * The template file contains the code and processing instructions
234
		 * to generate the result shown in the JSON API body. The
235
		 * configuration string is the path to the template file relative
236
		 * to the templates directory (usually in admin/jsonadm/templates).
237
		 *
238
		 * You can overwrite the template file configuration in extensions and
239
		 * provide alternative templates. These alternative templates should be
240
		 * named like the default one but with the string "standard" replaced by
241
		 * an unique name. You may use the name of your project for this. If
242
		 * you've implemented an alternative client class as well, "standard"
243
		 * should be replaced by the name of the new class.
244
		 *
245
		 * @param string Relative path to the template creating the body for the PATCH method of the JSON API
246
		 * @since 2015.12
247
		 * @category Developer
248
		 * @see admin/jsonadm/standard/template-get
249
		 * @see admin/jsonadm/standard/template-post
250
		 * @see admin/jsonadm/standard/template-delete
251
		 * @see admin/jsonadm/standard/template-put
252
		 * @see admin/jsonadm/standard/template-options
253
		 */
254
		$tplconf = 'admin/jsonadm/standard/template-patch';
255
		$default = 'patch-default.php';
256
257
		return $view->render( $view->config( $tplconf, $default ) );
258
	}
259
260
261
	/**
262
	 * Creates or updates the resource or the resource list
263
	 *
264
	 * @param string $body Request body
265
	 * @param array &$header Variable which contains the HTTP headers and the new ones afterwards
266
	 * @param integer &$status Variable which contains the HTTP status afterwards
267
	 * @return string Content for response body
268
	 */
269
	public function post( $body, array &$header, &$status )
270
	{
271
		$header = array( 'Content-Type' => 'application/vnd.api+json; supported-ext="bulk"' );
272
		$context = $this->getContext();
273
		$view = $this->getView();
274
275
		try
276
		{
277
			$view = $this->postItems( $view, $body, $header );
278
			$status = 201;
279
		}
280
		catch( \Aimeos\Admin\JsonAdm\Exception $e )
281
		{
282
			$status = $e->getCode();
283
			$view->errors = array( array(
284
				'title' => $context->getI18n()->dt( 'admin/jsonadm', $e->getMessage() ),
285
				'detail' => $e->getTraceAsString(),
286
			) );
287
		}
288
		catch( \Aimeos\MShop\Exception $e )
289
		{
290
			$status = 404;
291
			$view->errors = array( array(
292
				'title' => $context->getI18n()->dt( 'mshop', $e->getMessage() ),
293
				'detail' => $e->getTraceAsString(),
294
			) );
295
		}
296
		catch( \Exception $e )
297
		{
298
			$status = 500;
299
			$view->errors = array( array(
300
				'title' => $e->getMessage(),
301
				'detail' => $e->getTraceAsString(),
302
			) );
303
		}
304
305
		/** admin/jsonadm/standard/template-post
306
		 * Relative path to the JSON API template for POST requests
307
		 *
308
		 * The template file contains the code and processing instructions
309
		 * to generate the result shown in the JSON API body. The
310
		 * configuration string is the path to the template file relative
311
		 * to the templates directory (usually in admin/jsonadm/templates).
312
		 *
313
		 * You can overwrite the template file configuration in extensions and
314
		 * provide alternative templates. These alternative templates should be
315
		 * named like the default one but with the string "standard" replaced by
316
		 * an unique name. You may use the name of your project for this. If
317
		 * you've implemented an alternative client class as well, "standard"
318
		 * should be replaced by the name of the new class.
319
		 *
320
		 * @param string Relative path to the template creating the body for the POST method of the JSON API
321
		 * @since 2015.12
322
		 * @category Developer
323
		 * @see admin/jsonadm/standard/template-get
324
		 * @see admin/jsonadm/standard/template-patch
325
		 * @see admin/jsonadm/standard/template-delete
326
		 * @see admin/jsonadm/standard/template-put
327
		 * @see admin/jsonadm/standard/template-options
328
		 */
329
		$tplconf = 'admin/jsonadm/standard/template-post';
330
		$default = 'post-default.php';
331
332
		return $view->render( $view->config( $tplconf, $default ) );
333
	}
334
335
336
	/**
337
	 * Creates or updates the resource or the resource list
338
	 *
339
	 * @param string $body Request body
340
	 * @param array &$header Variable which contains the HTTP headers and the new ones afterwards
341
	 * @param integer &$status Variable which contains the HTTP status afterwards
342
	 * @return string Content for response body
343
	 */
344
	public function put( $body, array &$header, &$status )
345
	{
346
		$header = array( 'Content-Type' => 'application/vnd.api+json; supported-ext="bulk"' );
347
		$status = 501;
348
349
		$context = $this->getContext();
350
		$view = $this->getView();
351
352
		$view->errors = array( array(
353
			'title' => $context->getI18n()->dt( 'admin/jsonadm', 'Not implemented, use PATCH instead' ),
354
		) );
355
356
		/** admin/jsonadm/standard/template-put
357
		 * Relative path to the JSON API template for PUT requests
358
		 *
359
		 * The template file contains the code and processing instructions
360
		 * to generate the result shown in the JSON API body. The
361
		 * configuration string is the path to the template file relative
362
		 * to the templates directory (usually in admin/jsonadm/templates).
363
		 *
364
		 * You can overwrite the template file configuration in extensions and
365
		 * provide alternative templates. These alternative templates should be
366
		 * named like the default one but with the string "standard" replaced by
367
		 * an unique name. You may use the name of your project for this. If
368
		 * you've implemented an alternative client class as well, "standard"
369
		 * should be replaced by the name of the new class.
370
		 *
371
		 * @param string Relative path to the template creating the body for the PUT method of the JSON API
372
		 * @since 2015.12
373
		 * @category Developer
374
		 * @see admin/jsonadm/standard/template-delete
375
		 * @see admin/jsonadm/standard/template-patch
376
		 * @see admin/jsonadm/standard/template-post
377
		 * @see admin/jsonadm/standard/template-get
378
		 * @see admin/jsonadm/standard/template-options
379
		 */
380
		$tplconf = 'admin/jsonadm/standard/template-put';
381
		$default = 'put-default.php';
382
383
		return $view->render( $view->config( $tplconf, $default ) );
384
	}
385
386
387
	/**
388
	 * Returns the available REST verbs and the available resources
389
	 *
390
	 * @param string $body Request body
391
	 * @param array &$header Variable which contains the HTTP headers and the new ones afterwards
392
	 * @param integer &$status Variable which contains the HTTP status afterwards
393
	 * @return string Content for response body
394
	 */
395
	public function options( $body, array &$header, &$status )
396
	{
397
		$context = $this->getContext();
398
		$view = $this->getView();
399
400
		try
401
		{
402
			$resources = $attributes = array();
403
404
			foreach( $this->getDomains( $view ) as $domain )
405
			{
406
				$manager = \Aimeos\MShop\Factory::createManager( $context, $domain );
407
				$resources = array_merge( $resources, $manager->getResourceType( true ) );
408
				$attributes = array_merge( $attributes, $manager->getSearchAttributes( true ) );
409
			}
410
411
			$view->resources = $resources;
412
			$view->attributes = $attributes;
413
414
			$header = array(
415
				'Content-Type' => 'application/vnd.api+json; supported-ext="bulk"',
416
				'Allow' => 'DELETE,GET,POST,OPTIONS'
417
			);
418
			$status = 200;
419
		}
420
		catch( \Aimeos\MShop\Exception $e )
421
		{
422
			$status = 404;
423
			$view->errors = array( array(
424
				'title' => $context->getI18n()->dt( 'mshop', $e->getMessage() ),
425
				'detail' => $e->getTraceAsString(),
426
			) );
427
		}
428
		catch( \Exception $e )
429
		{
430
			$status = 500;
431
			$view->errors = array( array(
432
				'title' => $e->getMessage(),
433
				'detail' => $e->getTraceAsString(),
434
			) );
435
		}
436
437
		/** admin/jsonadm/standard/template-options
438
		 * Relative path to the JSON API template for OPTIONS requests
439
		 *
440
		 * The template file contains the code and processing instructions
441
		 * to generate the result shown in the JSON API body. The
442
		 * configuration string is the path to the template file relative
443
		 * to the templates directory (usually in admin/jsonadm/templates).
444
		 *
445
		 * You can overwrite the template file configuration in extensions and
446
		 * provide alternative templates. These alternative templates should be
447
		 * named like the default one but with the string "standard" replaced by
448
		 * an unique name. You may use the name of your project for this. If
449
		 * you've implemented an alternative client class as well, "standard"
450
		 * should be replaced by the name of the new class.
451
		 *
452
		 * @param string Relative path to the template creating the body for the OPTIONS method of the JSON API
453
		 * @since 2015.12
454
		 * @category Developer
455
		 * @see admin/jsonadm/standard/template-delete
456
		 * @see admin/jsonadm/standard/template-patch
457
		 * @see admin/jsonadm/standard/template-post
458
		 * @see admin/jsonadm/standard/template-get
459
		 * @see admin/jsonadm/standard/template-put
460
		 */
461
		$tplconf = 'admin/jsonadm/standard/template-options';
462
		$default = 'options-default.php';
463
464
		return $view->render( $view->config( $tplconf, $default ) );
465
	}
466
467
468
	/**
469
	 * Deletes one or more items
470
	 *
471
	 * @param \Aimeos\MW\View\Iface $view View instance with "param" view helper
472
	 * @param string $body Request body
473
	 * @return \Aimeos\MW\View\Iface $view View object that will contain the "total" property afterwards
474
	 * @throws \Aimeos\Admin\JsonAdm\Exception If the request body is invalid
475
	 */
476
	protected function deleteItems( \Aimeos\MW\View\Iface $view, $body )
477
	{
478
		$manager = \Aimeos\MShop\Factory::createManager( $this->getContext(), $this->getPath() );
479
480
		if( ( $id = $view->param( 'id' ) ) == null )
481
		{
482
			if( ( $request = json_decode( $body ) ) === null || !isset( $request->data ) || !is_array( $request->data ) ) {
483
				throw new \Aimeos\Admin\JsonAdm\Exception( sprintf( 'Invalid JSON in body' ), 400 );
484
			}
485
486
			$ids = $this->getIds( $request );
487
			$manager->deleteItems( $ids );
488
			$view->total = count( $ids );
489
		}
490
		else
491
		{
492
			$manager->deleteItem( $id );
493
			$view->total = 1;
494
		}
495
496
		return $view;
497
	}
498
499
500
	/**
501
	 * Retrieves the item or items and adds the data to the view
502
	 *
503
	 * @param \Aimeos\MW\View\Iface $view View instance
504
	 * @return \Aimeos\MW\View\Iface View instance with additional data assigned
505
	 */
506
	protected function getItem( \Aimeos\MW\View\Iface $view )
507
	{
508
		$total = 1;
509
		$manager = \Aimeos\MShop\Factory::createManager( $this->getContext(), $this->getPath() );
510
		$include = ( ( $include = $view->param( 'include' ) ) !== null ? explode( ',', $include ) : array() );
511
512
		if( ( $id = $view->param( 'id' ) ) == null )
513
		{
514
			$search = $this->initCriteria( $manager->createSearch(), $view->param() );
515
			$view->data = $manager->searchItems( $search, array(), $total );
516
			$view->childItems = $this->getChildItems( $view->data, $include );
517
			$view->listItems = $this->getListItems( $view->data, $include );
518
		}
519
		else
520
		{
521
			$view->data = $manager->getItem( $id, array() );
522
			$view->childItems = $this->getChildItems( array( $id => $view->data ), $include );
523
			$view->listItems = $this->getListItems( array( $id => $view->data ), $include );
524
		}
525
526
		$view->refItems = $this->getRefItems( $view->listItems );
527
528
		$view->total = $total;
529
530
		return $view;
531
	}
532
533
	/**
534
	 * Returns the view object
535
	 *
536
	 * @return \Aimeos\MW\View\Iface View object
537
	 */
538
	protected function getView()
539
	{
540
		return $this->view;
541
	}
542
543
544
	/**
545
	 * Initializes the criteria object based on the given parameter
546
	 *
547
	 * @param \Aimeos\MW\Criteria\Iface $criteria Criteria object
548
	 * @param array $params List of criteria data with condition, sorting and paging
549
	 * @return \Aimeos\MW\Criteria\Iface Initialized criteria object
550
	 */
551
	protected function initCriteria( \Aimeos\MW\Criteria\Iface $criteria, array $params )
552
	{
553
		$this->initCriteriaConditions( $criteria, $params );
554
		$this->initCriteriaSortations( $criteria, $params );
555
		$this->initCriteriaSlice( $criteria, $params );
556
557
		return $criteria;
558
	}
559
560
561
	/**
562
	 * Initializes the criteria object with conditions based on the given parameter
563
	 *
564
	 * @param \Aimeos\MW\Criteria\Iface $criteria Criteria object
565
	 * @param array $params List of criteria data with condition, sorting and paging
566
	 */
567
	private function initCriteriaConditions( \Aimeos\MW\Criteria\Iface $criteria, array $params )
568
	{
569
		if( isset( $params['filter'] ) && is_array( $params['filter'] ) )
570
		{
571
			$existing = $criteria->getConditions();
572
			$criteria->setConditions( $criteria->toConditions( (array) $params['filter'] ) );
573
574
			$expr = array( $criteria->getConditions(), $existing );
575
			$criteria->setConditions( $criteria->combine( '&&', $expr ) );
576
		}
577
	}
578
579
580
	/**
581
	 * Initializes the criteria object with the slice based on the given parameter.
582
	 *
583
	 * @param \Aimeos\MW\Criteria\Iface $criteria Criteria object
584
	 * @param array $params List of criteria data with condition, sorting and paging
585
	 */
586
	private function initCriteriaSlice( \Aimeos\MW\Criteria\Iface $criteria, array $params )
587
	{
588
		$start = ( isset( $params['page']['offset'] ) ? $params['page']['offset'] : 0 );
589
		$size = ( isset( $params['page']['limit'] ) ? $params['page']['limit'] : 25 );
590
591
		$criteria->setSlice( $start, $size );
592
	}
593
594
595
	/**
596
	 * Initializes the criteria object with sortations based on the given parameter
597
	 *
598
	 * @param \Aimeos\MW\Criteria\Iface $criteria Criteria object
599
	 * @param array $params List of criteria data with condition, sorting and paging
600
	 */
601
	private function initCriteriaSortations( \Aimeos\MW\Criteria\Iface $criteria, array $params )
602
	{
603
		if( !isset( $params['sort'] ) ) {
604
			return;
605
		}
606
607
		$sortation = array();
608
609
		foreach( explode( ',', $params['sort'] ) as $sort )
610
		{
611
			if( $sort[0] === '-' ) {
612
				$sortation[] = $criteria->sort( '-', substr( $sort, 1 ) );
613
			} else {
614
				$sortation[] = $criteria->sort( '+', $sort ); break;
615
			}
616
		}
617
618
		$criteria->setSortations( $sortation );
619
	}
620
621
622
	/**
623
	 * Returns the list of domains that are available as resources
624
	 *
625
	 * @param \Aimeos\MW\View\Iface $view View object with "resource" parameter
626
	 * @return array List of domain names
627
	 */
628
	protected function getDomains( \Aimeos\MW\View\Iface $view )
629
	{
630
		if( ( $domains = $view->param( 'resource' ) ) == '' )
631
		{
632
			/** admin/jsonadm/domains
633
			 * A list of domain names whose clients are available for the JSON API
634
			 *
635
			 * The HTTP OPTIONS method returns a list of resources known by the
636
			 * JSON API including their URLs. The list of available resources
637
			 * can be exteded dynamically be implementing a new Jsonadm client
638
			 * class handling request for this new domain.
639
			 *
640
			 * To add the new domain client to the list of resources returned
641
			 * by the HTTP OPTIONS method, you have to add its name in lower case
642
			 * to the existing configuration.
643
			 *
644
			 * @param array List of domain names
645
			 * @since 2016.01
646
			 * @category Developer
647
			 */
648
			$default = array(
649
				'attribute', 'catalog', 'coupon', 'customer', 'locale', 'media',
650
				'order', 'plugin', 'price', 'product', 'service', 'supplier', 'tag', 'text'
651
			);
652
			$domains = $this->getContext()->getConfig()->get( 'admin/jsonadm/domains', $default );
653
		}
654
655
		return (array) $domains;
656
	}
657
658
659
	/**
660
	 * Returns the items with parent/child relationships
661
	 *
662
	 * @param array $items List of items implementing \Aimeos\MShop\Common\Item\Iface
663
	 * @param array $include List of resource types that should be fetched
664
	 * @return array List of items implementing \Aimeos\MShop\Common\Item\Iface
665
	 */
666
	protected function getChildItems( array $items, array $include )
667
	{
668
		return array();
669
	}
670
671
672
	/**
673
	 * Returns the list items for association relationships
674
	 *
675
	 * @param array $items List of items implementing \Aimeos\MShop\Common\Item\Iface
676
	 * @param array $include List of resource types that should be fetched
677
	 * @return array List of items implementing \Aimeos\MShop\Common\Item\Lists\Iface
678
	 */
679
	protected function getListItems( array $items, array $include )
680
	{
681
		return array();
682
	}
683
684
685
	/**
686
	 * Returns the items associated via a lists table
687
	 *
688
	 * @param array $listItems List of items implementing \Aimeos\MShop\Common\Item\Lists\Iface
689
	 * @return array List of items implementing \Aimeos\MShop\Common\Item\Iface
690
	 */
691
	protected function getRefItems( array $listItems )
692
	{
693
		$list = $map = array();
694
695
		foreach( $listItems as $listItem ) {
696
			$map[$listItem->getDomain()][] = $listItem->getRefId();
697
		}
698
699
		foreach( $map as $domain => $ids )
700
		{
701
			$manager = \Aimeos\MShop\Factory::createManager( $this->getContext(), $domain );
702
703
			$search = $manager->createSearch();
704
			$search->setConditions( $search->compare( '==', $domain . '.id', $ids ) );
705
706
			$list = array_merge( $list, $manager->searchItems( $search ) );
707
		}
708
709
		return $list;
710
	}
711
712
713
	/**
714
	 * Returns the IDs sent in the request body
715
	 *
716
	 * @param \stdClass $request Decoded request body
717
	 * @return array List of item IDs
718
	 */
719
	protected function getIds( $request )
720
	{
721
		$ids = array();
722
723
		if( isset( $request->data ) )
724
		{
725
			foreach( $request->data as $entry )
726
			{
727
				if( isset( $entry->id ) ) {
728
					$ids[] = $entry->id;
729
				}
730
			}
731
		}
732
733
		return $ids;
734
	}
735
736
737
	/**
738
	 * Returns the context item object
739
	 *
740
	 * @return \Aimeos\MShop\Context\Item\Iface Context object
741
	 */
742
	protected function getContext()
743
	{
744
		return $this->context;
745
	}
746
747
748
	/**
749
	 * Returns the paths to the template files
750
	 *
751
	 * @return array List of file system paths
752
	 */
753
	protected function getTemplatePaths()
754
	{
755
		return $this->templatePaths;
756
	}
757
758
759
	/**
760
	 * Returns the path to the client
761
	 *
762
	 * @return string Client path, e.g. "product/stock"
763
	 */
764
	protected function getPath()
765
	{
766
		return $this->path;
767
	}
768
769
770
	/**
771
	 * Saves new attributes for one or more items
772
	 *
773
	 * @param \Aimeos\MW\View\Iface $view View that will contain the "data" and "total" properties afterwards
774
	 * @param string $body Request body
775
	 * @param array &$header Associative list of HTTP headers as value/result parameter
776
	 * @throws \Aimeos\Admin\JsonAdm\Exception If "id" parameter isn't available or the body is invalid
777
	 * @return \Aimeos\MW\View\Iface Updated view instance
778
	 */
779
	protected function patchItems( \Aimeos\MW\View\Iface $view, $body, array &$header )
780
	{
781
		if( ( $request = json_decode( $body ) ) === null || !isset( $request->data ) ) {
782
			throw new \Aimeos\Admin\JsonAdm\Exception( sprintf( 'Invalid JSON in body' ), 400 );
783
		}
784
785
		$manager = \Aimeos\MShop\Factory::createManager( $this->getContext(), $this->getPath() );
786
787
		if( is_array( $request->data ) )
788
		{
789
			$data = $this->saveData( $manager, $request );
790
791
			$view->data = $data;
792
			$view->total = count( $data );
793
			$header['Content-Type'] = 'application/vnd.api+json; ext="bulk"; supported-ext="bulk"';
794
		}
795
		else
796
		{
797
			if( ( $id = $view->param( 'id' ) ) == null ) {
798
				throw new \Aimeos\Admin\JsonAdm\Exception( sprintf( 'No ID given' ), 400 );
799
			}
800
801
			$request->data->id = $id;
802
			$data = $this->saveEntry( $manager, $request->data );
803
804
			$view->data = $data;
805
			$view->total = 1;
806
		}
807
808
		return $view;
809
	}
810
811
812
	/**
813
	 * Creates one or more new items
814
	 *
815
	 * @param \Aimeos\MW\View\Iface $view View that will contain the "data" and "total" properties afterwards
816
	 * @param string $body Request body
817
	 * @param array &$header Associative list of HTTP headers as value/result parameter
818
	 * @return \Aimeos\MW\View\Iface Updated view instance
819
	 */
820
	protected function postItems( \Aimeos\MW\View\Iface $view, $body, array &$header )
821
	{
822
		if( ( $request = json_decode( $body ) ) === null || !isset( $request->data ) ) {
823
			throw new \Aimeos\Admin\JsonAdm\Exception( sprintf( 'Invalid JSON in body' ), 400 );
824
		}
825
826
		if( isset( $request->data->id ) || $view->param( 'id' ) != null ) {
827
			throw new \Aimeos\Admin\JsonAdm\Exception( sprintf( 'Client generated IDs are not supported' ), 403 );
828
		}
829
830
831
		$manager = \Aimeos\MShop\Factory::createManager( $this->getContext(), $this->getPath() );
832
833
		if( is_array( $request->data ) )
834
		{
835
			$data = $this->saveData( $manager, $request );
836
837
			$view->data = $data;
838
			$view->total = count( $data );
839
			$header['Content-Type'] = 'application/vnd.api+json; ext="bulk"; supported-ext="bulk"';
840
		}
841
		else
842
		{
843
			$request->data->id = null;
844
			$data = $this->saveEntry( $manager, $request->data );
845
846
			$view->data = $data;
847
			$view->total = 1;
848
		}
849
850
		return $view;
851
	}
852
853
854
	/**
855
	 * Creates of updates several items at once
856
	 *
857
	 * @param \Aimeos\MShop\Common\Manager\Iface $manager Manager responsible for the items
858
	 * @param \stdClass $request Object with request body data
859
	 * @return array List of items
860
	 */
861
	protected function saveData( \Aimeos\MShop\Common\Manager\Iface $manager, \stdClass $request )
862
	{
863
		$data = array();
864
865
		if( isset( $request->data ) )
866
		{
867
			foreach( (array) $request->data as $entry ) {
868
				$data[] = $this->saveEntry( $manager, $entry );
869
			}
870
		}
871
872
		return $data;
873
	}
874
875
876
	/**
877
	 * Saves and returns the new or updated item
878
	 *
879
	 * @param \Aimeos\MShop\Common\Manager\Iface $manager Manager responsible for the items
880
	 * @param \stdClass $entry Object including "id" and "attributes" elements
881
	 * @return \Aimeos\MShop\Common\Item\Iface New or updated item
882
	 */
883
	protected function saveEntry( \Aimeos\MShop\Common\Manager\Iface $manager, \stdClass $entry )
884
	{
885
		if( isset( $entry->id ) && $entry->id !== null ) {
886
			$item = $manager->getItem( $entry->id );
887
		} else {
888
			$item = $manager->createItem();
889
		}
890
891
		$item = $this->addItemData( $manager, $item, $entry, $item->getResourceType() );
892
		$manager->saveItem( $item );
893
894
		if( isset( $entry->relationships ) ) {
895
			$this->saveRelationships( $manager, $item, $entry->relationships );
896
		}
897
898
		return $manager->getItem( $item->getId() );
899
	}
900
901
902
	/**
903
	 * Saves the item references associated via the list
904
	 *
905
	 * @param \Aimeos\MShop\Common\Manager\Iface $manager Manager responsible for the items
906
	 * @param \Aimeos\MShop\Common\Item\Iface $item Domain item with an unique ID set
907
	 * @param \stdClass $relationships Object including the <domain>/data/attributes structure
908
	 */
909
	protected function saveRelationships( \Aimeos\MShop\Common\Manager\Iface $manager,
910
		\Aimeos\MShop\Common\Item\Iface $item, \stdClass $relationships )
911
	{
912
		$id = $item->getId();
913
		$listManager = $manager->getSubManager( 'lists' );
914
915
		foreach( (array) $relationships as $domain => $list )
916
		{
917
			if( isset( $list->data ) )
918
			{
919
				foreach( (array) $list->data as $data )
920
				{
921
					$listItem = $this->addItemData( $listManager, $listManager->createItem(), $data, $domain );
922
923
					if( isset( $data->id ) ) {
924
						$listItem->setRefId( $data->id );
925
					}
926
927
					$listItem->setParentId( $id );
928
					$listItem->setDomain( $domain );
929
930
					$listManager->saveItem( $listItem, false );
931
				}
932
			}
933
		}
934
	}
935
936
937
	/**
938
	 * Adds the data from the given object to the item
939
	 *
940
	 * @param \Aimeos\MShop\Common\Manager\Iface $manager Manager object
941
	 * @param \Aimeos\MShop\Common\Item\Iface $item Item object to add the data to
942
	 * @param \stdClass $data Object with "attributes" property
943
	 * @param string $domain Domain of the type item
944
	 * @return \Aimeos\MShop\Common\Item\Iface Item including the data
945
	 */
946
	protected function addItemData(\Aimeos\MShop\Common\Manager\Iface $manager,
947
		\Aimeos\MShop\Common\Item\Iface $item, \stdClass $data, $domain )
948
	{
949
		if( isset( $data->attributes ) )
950
		{
951
			$attr = (array) $data->attributes;
952
			$key = str_replace( '/', '.', $item->getResourceType() );
953
954
			if( isset( $attr[$key.'.type'] ) )
955
			{
956
				$typeItem = $manager->getSubManager( 'type' )->findItem( $attr[$key.'.type'], array(), $domain );
1 ignored issue
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Aimeos\MShop\Common\Manager\Iface as the method findItem() does only exist in the following implementations of said interface: Aimeos\MShop\Attribute\Manager\Lists\Type\Standard, Aimeos\MShop\Attribute\Manager\Standard, Aimeos\MShop\Attribute\Manager\Type\Standard, Aimeos\MShop\Catalog\Manager\Decorator\Base, Aimeos\MShop\Catalog\Manager\Decorator\Sitecheck, Aimeos\MShop\Catalog\Manager\Lists\Type\Standard, Aimeos\MShop\Catalog\Manager\Standard, Aimeos\MShop\Common\Manager\Decorator\Base, Aimeos\MShop\Common\Manager\Decorator\Changelog, Aimeos\MShop\Common\Manager\Decorator\Sitecheck, Aimeos\MShop\Common\Manager\Type\Base, Aimeos\MShop\Coupon\Manager\Code\Standard, Aimeos\MShop\Customer\Manager\Base, Aimeos\MShop\Customer\Manager\Group\Standard, Aimeos\MShop\Customer\Manager\Lists\Type\Standard, Aimeos\MShop\Customer\Manager\Standard, Aimeos\MShop\Locale\Manager\Site\Standard, Aimeos\MShop\Media\Manager\Lists\Type\Standard, Aimeos\MShop\Media\Manager\Type\Standard, Aimeos\MShop\Plugin\Manager\Type\Standard, Aimeos\MShop\Price\Manager\Lists\Type\Standard, Aimeos\MShop\Price\Manager\Type\Standard, Aimeos\MShop\Product\Manager\Lists\Type\Standard, Aimeos\MShop\Product\Man...\Property\Type\Standard, Aimeos\MShop\Product\Manager\Standard, Aimeos\MShop\Product\Man...tock\Warehouse\Standard, Aimeos\MShop\Product\Manager\Type\Standard, Aimeos\MShop\Service\Manager\Lists\Type\Standard, Aimeos\MShop\Service\Manager\Standard, Aimeos\MShop\Service\Manager\Type\Standard, Aimeos\MShop\Supplier\Manager\Lists\Type\Standard, Aimeos\MShop\Supplier\Manager\Standard, Aimeos\MShop\Tag\Manager\Type\Standard, Aimeos\MShop\Text\Manager\Lists\Type\Standard, Aimeos\MShop\Text\Manager\Type\Standard.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
957
				$attr[$key.'.typeid'] = $typeItem->getId();
958
			}
959
960
			$item->fromArray( $attr );
961
		}
962
963
		return $item;
964
	}
965
}
966