Passed
Push — master ( 0c8a24...21be3d )
by Aimeos
02:33
created

Standard::get()   B

Complexity

Conditions 6
Paths 28

Size

Total Lines 49
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 28
c 1
b 0
f 0
dl 0
loc 49
rs 8.8497
cc 6
nc 28
nop 2
1
<?php
2
3
/**
4
 * @license LGPLv3, http://opensource.org/licenses/LGPL-3.0
5
 * @copyright Aimeos (aimeos.org), 2017-2020
6
 * @package Client
7
 * @subpackage JsonApi
8
 */
9
10
11
namespace Aimeos\Client\JsonApi\Order;
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
	/**
28
	 * Returns the resource or the resource list
29
	 *
30
	 * @param \Psr\Http\Message\ServerRequestInterface $request Request object
31
	 * @param \Psr\Http\Message\ResponseInterface $response Response object
32
	 * @return \Psr\Http\Message\ResponseInterface Modified response object
33
	 */
34
	public function get( ServerRequestInterface $request, ResponseInterface $response ) : \Psr\Http\Message\ResponseInterface
35
	{
36
		$view = $this->getView();
37
		$ref = $view->param( 'include', [] );
38
39
		if( is_string( $ref ) ) {
40
			$ref = explode( ',', $ref );
41
		}
42
43
		try
44
		{
45
			$cntl = \Aimeos\Controller\Frontend::create( $this->getContext(), 'order' )->uses( $ref );
46
47
			if( ( $id = $view->param( 'id' ) ) != '' )
48
			{
49
				$view->items = $cntl->get( $id );
50
				$view->total = 1;
51
			}
52
			else
53
			{
54
				$total = 0;
55
				$items = $cntl->parse( (array) $view->param( 'filter', [] ) )
56
					->slice( $view->param( 'page/offset', 0 ), $view->param( 'page/limit', 48 ) )
57
					->sort( $view->param( 'sort', '-order.id' ) )
58
					->search( $total );
59
60
				$view->items = $items;
61
				$view->total = $total;
62
			}
63
64
			$status = 200;
65
		}
66
		catch( \Aimeos\Controller\Frontend\Exception $e )
67
		{
68
			$status = 403;
69
			$view->errors = $this->getErrorDetails( $e, 'controller/frontend' );
70
		}
71
		catch( \Aimeos\MShop\Exception $e )
72
		{
73
			$status = 404;
74
			$view->errors = $this->getErrorDetails( $e, 'mshop' );
75
		}
76
		catch( \Exception $e )
77
		{
78
			$status = 500;
79
			$view->errors = $this->getErrorDetails( $e );
80
		}
81
82
		return $this->render( $response, $view, $status );
83
	}
84
85
86
	/**
87
	 * Creates or updates the resource or the resource list
88
	 *
89
	 * @param \Psr\Http\Message\ServerRequestInterface $request Request object
90
	 * @param \Psr\Http\Message\ResponseInterface $response Response object
91
	 * @return \Psr\Http\Message\ResponseInterface Modified response object
92
	 */
93
	public function post( ServerRequestInterface $request, ResponseInterface $response ) : \Psr\Http\Message\ResponseInterface
94
	{
95
		$view = $this->getView();
96
97
		try
98
		{
99
			$body = (string) $request->getBody();
100
101
			if( ( $payload = json_decode( $body ) ) === null || !isset( $payload->data->attributes ) ) {
102
				throw new \Aimeos\Client\JsonApi\Exception( sprintf( 'Invalid JSON in body' ), 400 );
103
			}
104
105
			if( !isset( $payload->data->attributes->{'order.baseid'} ) ) {
106
				throw new \Aimeos\Client\JsonApi\Exception( sprintf( 'Required attribute "order.baseid" is missing' ), 400 );
107
			}
108
109
			$basket = $this->getBasket( $payload->data->attributes->{'order.baseid'} );
110
			$item = $this->createOrder( $payload->data->attributes->{'order.baseid'} );
111
112
			$view->form = $this->getPaymentForm( $basket, $item, (array) $payload->data->attributes );
113
			$view->items = $item;
114
			$view->total = 1;
115
116
			$status = 201;
117
		}
118
		catch( \Aimeos\Client\JsonApi\Exception $e )
119
		{
120
			$status = $e->getCode();
121
			$view->errors = $this->getErrorDetails( $e, 'client/jsonapi' );
122
		}
123
		catch( \Aimeos\Controller\Frontend\Exception $e )
124
		{
125
			$status = 403;
126
			$view->errors = $this->getErrorDetails( $e, 'controller/frontend' );
127
		}
128
		catch( \Aimeos\MShop\Exception $e )
129
		{
130
			$status = 404;
131
			$view->errors = $this->getErrorDetails( $e, 'mshop' );
132
		}
133
		catch( \Exception $e )
134
		{
135
			$status = 500;
136
			$view->errors = $this->getErrorDetails( $e );
137
		}
138
139
		return $this->render( $response, $view, $status );
140
	}
141
142
143
	/**
144
	 * Returns the available REST verbs and the available parameters
145
	 *
146
	 * @param \Psr\Http\Message\ServerRequestInterface $request Request object
147
	 * @param \Psr\Http\Message\ResponseInterface $response Response object
148
	 * @return \Psr\Http\Message\ResponseInterface Modified response object
149
	 */
150
	public function options( ServerRequestInterface $request, ResponseInterface $response ) : \Psr\Http\Message\ResponseInterface
151
	{
152
		$view = $this->getView();
153
154
		$view->attributes = [
155
			'order.baseid' => [
156
				'label' => 'ID of the stored basket (POST only)',
157
				'type' => 'string', 'default' => '', 'required' => true,
158
			],
159
		];
160
161
		$tplconf = 'client/jsonapi/standard/template-options';
162
		$default = 'options-standard';
163
164
		$body = $view->render( $view->config( $tplconf, $default ) );
165
166
		return $response->withHeader( 'Allow', 'GET,OPTIONS,POST' )
167
			->withHeader( 'Cache-Control', 'max-age=300' )
168
			->withHeader( 'Content-Type', 'application/vnd.api+json' )
169
			->withBody( $view->response()->createStreamFromString( $body ) )
170
			->withStatus( 200 );
171
	}
172
173
174
	/**
175
	 * Adds and returns a new order item for the given order base ID
176
	 *
177
	 * @param string $baseId Unique order base ID
178
	 * @return \Aimeos\MShop\Order\Item\Iface New order item
179
	 */
180
	protected function createOrder( string $baseId ) : \Aimeos\MShop\Order\Item\Iface
181
	{
182
		$context = $this->getContext();
183
		$cntl = \Aimeos\Controller\Frontend::create( $context, 'order' );
184
		$item = $cntl->add( $baseId, ['order.type' => 'jsonapi'] )->store();
185
186
		$context->getSession()->set( 'aimeos/orderid', $item->getId() );
187
188
		return $item;
189
	}
190
191
192
	/**
193
	 * Returns the basket object for the given ID
194
	 *
195
	 * @param string $basketId Unique order base ID
196
	 * @return \Aimeos\MShop\Order\Item\Base\Iface Basket object including only the services
197
	 * @throws \Aimeos\Client\JsonApi\Exception If basket ID is not the same as stored before in the current session
198
	 */
199
	protected function getBasket( string $basketId ) : \Aimeos\MShop\Order\Item\Base\Iface
200
	{
201
		$context = $this->getContext();
202
		$baseId = $context->getSession()->get( 'aimeos/order.baseid' );
203
204
		if( $baseId != $basketId )
205
		{
206
			$msg = sprintf( 'No basket for the "order.baseid" ("%1$s") found', $basketId );
207
			throw new \Aimeos\Client\JsonApi\Exception( $msg, 403 );
208
		}
209
210
		$parts = \Aimeos\MShop\Order\Item\Base\Base::PARTS_SERVICE;
211
		$cntl = \Aimeos\Controller\Frontend::create( $context, 'basket' );
212
213
		return $cntl->load( $baseId, $parts, false );
214
	}
215
216
217
	/**
218
	 * Returns the form helper object for building the payment form in the frontend
219
	 *
220
	 * @param \Aimeos\MShop\Order\Item\Base\Iface $basket Saved basket object including payment service object
221
	 * @param \Aimeos\MShop\Order\Item\Iface $orderItem Saved order item created for the basket object
222
	 * @param array $attributes Associative list of payment data pairs
223
	 * @return \Aimeos\MShop\Common\Helper\Form\Iface|null Form object with URL, parameters, etc.
224
	 * 	or null if no form data is required
225
	 */
226
	protected function getPaymentForm( \Aimeos\MShop\Order\Item\Base\Iface $basket,
227
		\Aimeos\MShop\Order\Item\Iface $orderItem, array $attributes ) : ?\Aimeos\MShop\Common\Helper\Form\Iface
228
	{
229
		$view = $this->getView();
230
		$context = $this->getContext();
231
		$total = $basket->getPrice()->getValue() + $basket->getPrice()->getCosts();
232
		$services = $basket->getService( \Aimeos\MShop\Order\Item\Base\Service\Base::TYPE_PAYMENT );
233
234
		if( $services === [] || $total <= '0.00' && $this->isSubscription( $basket->getProducts() ) === false )
235
		{
236
			$cntl = \Aimeos\Controller\Frontend::create( $context, 'order' );
237
			$cntl->save( $orderItem->setPaymentStatus( \Aimeos\MShop\Order\Item\Base::PAY_AUTHORIZED ) );
238
239
			$url = $this->getUrlConfirm( $view, [], ['absoluteUri' => true, 'namespace' => false] );
240
			return new \Aimeos\MShop\Common\Helper\Form\Standard( $url, 'GET' );
241
		}
242
243
		if( ( $service = reset( $services ) ) !== false )
244
		{
245
			$args = array( 'code' => $service->getCode(), 'orderid' => $orderItem->getId() );
246
			$config = array( 'absoluteUri' => true, 'namespace' => false );
247
			$urls = array(
248
				'payment.url-success' => $this->getUrlConfirm( $view, $args, $config ),
249
				'payment.url-update' => $this->getUrlUpdate( $view, $args, $config ),
250
			);
251
252
			foreach( $service->getAttributeItems() as $item ) {
253
				$attributes[$item->getCode()] = $item->getValue();
254
			}
255
256
			$serviceCntl = \Aimeos\Controller\Frontend::create( $context, 'service' );
257
			return $serviceCntl->process( $orderItem, $service->getServiceId(), $urls, $attributes );
258
		}
259
	}
260
261
262
	/**
263
	 * Returns the URL to the confirm page.
264
	 *
265
	 * @param \Aimeos\MW\View\Iface $view View object
266
	 * @param array $params Parameters that should be part of the URL
267
	 * @param array $config Default URL configuration
268
	 * @return string URL string
269
	 */
270
	protected function getUrlConfirm( \Aimeos\MW\View\Iface $view, array $params, array $config ) : string
271
	{
272
		$target = $view->config( 'client/html/checkout/confirm/url/target' );
273
		$cntl = $view->config( 'client/html/checkout/confirm/url/controller', 'checkout' );
274
		$action = $view->config( 'client/html/checkout/confirm/url/action', 'confirm' );
275
		$config = $view->config( 'client/html/checkout/confirm/url/config', $config );
276
277
		return $view->url( $target, $cntl, $action, $params, [], $config );
278
	}
279
280
281
	/**
282
	 * Returns the URL to the update page.
283
	 *
284
	 * @param \Aimeos\MW\View\Iface $view View object
285
	 * @param array $params Parameters that should be part of the URL
286
	 * @param array $config Default URL configuration
287
	 * @return string URL string
288
	 */
289
	protected function getUrlUpdate( \Aimeos\MW\View\Iface $view, array $params, array $config ) : string
290
	{
291
		$target = $view->config( 'client/html/checkout/update/url/target' );
292
		$cntl = $view->config( 'client/html/checkout/update/url/controller', 'checkout' );
293
		$action = $view->config( 'client/html/checkout/update/url/action', 'update' );
294
		$config = $view->config( 'client/html/checkout/update/url/config', $config );
295
296
		return $view->url( $target, $cntl, $action, $params, [], $config );
297
	}
298
299
300
	/**
301
	 * Tests if one of the products is a subscription
302
	 *
303
	 * @param \Aimeos\Map $products Ordered products implementing \Aimeos\MShop\Order\Item\Base\Product\Iface
304
	 * @return bool True if at least one product is a subscription, false if not
305
	 */
306
	protected function isSubscription( \Aimeos\Map $products ) : bool
307
	{
308
		foreach( $products as $orderProduct )
309
		{
310
			if( $orderProduct->getAttributeItem( 'interval', 'config' ) ) {
311
				return true;
312
			}
313
		}
314
315
		return false;
316
	}
317
318
319
	/**
320
	 * Returns the response object with the rendered header and body
321
	 *
322
	 * @param \Psr\Http\Message\ResponseInterface $response Response object
323
	 * @param \Aimeos\MW\View\Iface $view View instance
324
	 * @param int $status HTTP status code
325
	 * @return \Psr\Http\Message\ResponseInterface Modified response object
326
	 */
327
	protected function render( ResponseInterface $response, \Aimeos\MW\View\Iface $view, int $status ) : \Psr\Http\Message\ResponseInterface
328
	{
329
		/** client/jsonapi/order/standard/template
330
		 * Relative path to the order JSON API template
331
		 *
332
		 * The template file contains the code and processing instructions
333
		 * to generate the result shown in the JSON API body. The
334
		 * configuration string is the path to the template file relative
335
		 * to the templates directory (usually in client/jsonapi/templates).
336
		 *
337
		 * You can overwrite the template file configuration in extensions and
338
		 * provide alternative templates. These alternative templates should be
339
		 * named like the default one but with the string "standard" replaced by
340
		 * an unique name. You may use the name of your project for this. If
341
		 * you've implemented an alternative client class as well, "standard"
342
		 * should be replaced by the name of the new class.
343
		 *
344
		 * @param string Relative path to the template creating the body of the JSON API
345
		 * @since 2017.03
346
		 * @category Developer
347
		 */
348
		$tplconf = 'client/jsonapi/order/standard/template';
349
		$default = 'order/standard';
350
351
		$body = $view->render( $view->config( $tplconf, $default ) );
352
353
		return $response->withHeader( 'Allow', 'GET,OPTIONS,POST' )
354
			->withHeader( 'Cache-Control', 'no-cache, private' )
355
			->withHeader( 'Content-Type', 'application/vnd.api+json' )
356
			->withBody( $view->response()->createStreamFromString( $body ) )
357
			->withStatus( $status );
358
	}
359
}
360