Completed
Push — master ( 501b94...69ae10 )
by Aimeos
09:47
created

Base::getCustomerData()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 8
nc 4
nop 3
dl 0
loc 15
rs 9.2
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * @license LGPLv3, http://opensource.org/licenses/LGPL-3.0
5
 * @copyright Metaways Infosystems GmbH, 2011
6
 * @copyright Aimeos (aimeos.org), 2015-2017
7
 * @package MShop
8
 * @subpackage Service
9
 */
10
11
12
namespace Aimeos\MShop\Service\Provider;
13
14
15
/**
16
 * Abstract class for all service provider implementations with some default methods.
17
 *
18
 * @package MShop
19
 * @subpackage Service
20
 */
21
abstract class Base
22
{
23
	private $context;
24
	private $serviceItem;
25
	private $communication;
26
	private $beGlobalConfig;
27
28
29
	/**
30
	 * Initializes the service provider object.
31
	 *
32
	 * @param \Aimeos\MShop\Context\Item\Iface $context Context object with required objects
33
	 * @param \Aimeos\MShop\Service\Item\Iface $serviceItem Service item with configuration for the provider
34
	 */
35
	public function __construct( \Aimeos\MShop\Context\Item\Iface $context, \Aimeos\MShop\Service\Item\Iface $serviceItem )
36
	{
37
		$this->context = $context;
38
		$this->serviceItem = $serviceItem;
39
	}
40
41
42
	/**
43
	 * Catch unknown methods
44
	 *
45
	 * @param string $name Name of the method
46
	 * @param array $param List of method parameter
47
	 * @throws \Aimeos\MShop\Common\Manager\Exception If method call failed
48
	 */
49
	public function __call( $name, array $param )
50
	{
51
		throw new \Aimeos\MShop\Service\Exception( sprintf( 'Unable to call method "%1$s"', $name ) );
52
	}
53
54
55
	/**
56
	 * Returns the price when using the provider.
57
	 * Usually, this is the lowest price that is available in the service item but can also be a calculated based on
58
	 * the basket content, e.g. 2% of the value as transaction cost.
59
	 *
60
	 * @param \Aimeos\MShop\Order\Item\Base\Iface $basket Basket object
61
	 * @return \Aimeos\MShop\Price\Item\Iface Price item containing the price, shipping, rebate
62
	 */
63
	public function calcPrice( \Aimeos\MShop\Order\Item\Base\Iface $basket )
64
	{
65
		$priceManager = \Aimeos\MShop\Factory::createManager( $this->context, 'price' );
66
		$prices = $this->serviceItem->getRefItems( 'price', 'default', 'default' );
67
68
		if( count( $prices ) > 0 ) {
69
			return $priceManager->getLowestPrice( $prices, 1 );
70
		}
71
72
		return $priceManager->createItem();
73
	}
74
75
76
	/**
77
	 * Checks the backend configuration attributes for validity.
78
	 *
79
	 * @param array $attributes Attributes added by the shop owner in the administraton interface
80
	 * @return array An array with the attribute keys as key and an error message as values for all attributes that are
81
	 * 	known by the provider but aren't valid resp. null for attributes whose values are OK
82
	 */
83
	public function checkConfigBE( array $attributes )
84
	{
85
		return [];
86
	}
87
88
89
	/**
90
	 * Checks the frontend configuration attributes for validity.
91
	 *
92
	 * @param array $attributes Attributes entered by the customer during the checkout process
93
	 * @return array An array with the attribute keys as key and an error message as values for all attributes that are
94
	 * 	known by the provider but aren't valid resp. null for attributes whose values are OK
95
	 */
96
	public function checkConfigFE( array $attributes )
97
	{
98
		return [];
99
	}
100
101
102
	/**
103
	 * Returns the configuration attribute definitions of the provider to generate a list of available fields and
104
	 * rules for the value of each field in the administration interface.
105
	 *
106
	 * @return array List of attribute definitions implementing \Aimeos\MW\Common\Critera\Attribute\Iface
107
	 */
108
	public function getConfigBE()
109
	{
110
		return [];
111
	}
112
113
114
	/**
115
	 * Returns the configuration attribute definitions of the provider to generate a list of available fields and
116
	 * rules for the value of each field in the frontend.
117
	 *
118
	 * @param \Aimeos\MShop\Order\Item\Base\Iface $basket Basket object
119
	 * @return array List of attribute definitions implementing \Aimeos\MW\Common\Critera\Attribute\Iface
120
	 */
121
	public function getConfigFE( \Aimeos\MShop\Order\Item\Base\Iface $basket )
122
	{
123
		return [];
124
	}
125
126
127
	/**
128
	 * Returns the service item which also includes the configuration for the service provider.
129
	 *
130
	 * @return \Aimeos\MShop\Service\Item\Iface Service item
131
	 */
132
	public function getServiceItem()
133
	{
134
		return $this->serviceItem;
135
	}
136
137
138
	/**
139
	 * Injects additional global configuration for the backend.
140
	 *
141
	 * It's used for adding additional backend configuration from the application
142
	 * like the URLs to redirect to.
143
	 *
144
	 * Supported redirect URLs are:
145
	 * - payment.url-success
146
	 * - payment.url-failure
147
	 * - payment.url-cancel
148
	 * - payment.url-update
149
	 *
150
	 * @param array $config Associative list of config keys and their value
151
	 */
152
	public function injectGlobalConfigBE( array $config )
153
	{
154
		$this->beGlobalConfig = $config;
155
	}
156
157
158
	/**
159
	 * Checks if payment provider can be used based on the basket content.
160
	 * Checks for country, currency, address, RMS, etc. -> in separate decorators
161
	 *
162
	 * @param \Aimeos\MShop\Order\Item\Base\Iface $basket Basket object
163
	 * @return boolean True if payment provider can be used, false if not
164
	 */
165
	public function isAvailable( \Aimeos\MShop\Order\Item\Base\Iface $basket )
166
	{
167
		return true;
168
	}
169
170
171
	/**
172
	 * Checks what features the payment provider implements.
173
	 *
174
	 * @param integer $what Constant from abstract class
175
	 * @return boolean True if feature is available in the payment provider, false if not
176
	 */
177
	public function isImplemented( $what )
178
	{
179
		return false;
180
	}
181
182
183
	/**
184
	 * Queries for status updates for the given order if supported.
185
	 *
186
	 * @param \Aimeos\MShop\Order\Item\Iface $order Order invoice object
187
	 */
188
	public function query( \Aimeos\MShop\Order\Item\Iface $order )
189
	{
190
		throw new \Aimeos\MShop\Service\Exception( sprintf( 'Method "%1$s" for provider not available', 'query' ) );
191
	}
192
193
194
	/**
195
	 * Looks for new update files and updates the orders for which status updates were received.
196
	 * If batch processing of files isn't supported, this method can be empty.
197
	 *
198
	 * @return boolean True if the update was successful, false if async updates are not supported
199
	 * @throws \Aimeos\MShop\Service\Exception If updating one of the orders failed
200
	 */
201
	public function updateAsync()
202
	{
203
		return false;
204
	}
205
206
207
	/**
208
	 * Updates the order status sent by payment gateway notifications
209
	 *
210
	 * @param \Psr\Http\Message\ServerRequestInterface Request object
211
	 * @return \Psr\Http\Message\ResponseInterface Response object
212
	 */
213
	public function updatePush( \Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response )
214
	{
215
		return $response->withStatus( 501, 'Not implemented' );
216
	}
217
218
219
	/**
220
	 * Updates the orders for whose status updates have been received by the confirmation page
221
	 *
222
	 * @param \Psr\Http\Message\ServerRequestInterface $request Request object with parameters and request body
223
	 * @param \Aimeos\MShop\Order\Item\Iface $order Order item that should be updated
224
	 * @return \Aimeos\MShop\Order\Item\Iface Updated order item
225
	 * @throws \Aimeos\MShop\Service\Exception If updating the orders failed
226
	 */
227
	public function updateSync( \Psr\Http\Message\ServerRequestInterface $request, \Aimeos\MShop\Order\Item\Iface $order )
228
	{
229
		return $order;
230
	}
231
232
233
	/**
234
	 * Sets the communication object for a service provider.
235
	 *
236
	 * @param \Aimeos\MW\Communication\Iface $communication Object of communication
237
	 */
238
	public function setCommunication( \Aimeos\MW\Communication\Iface $communication )
239
	{
240
		$this->communication = $communication;
241
	}
242
243
244
	/**
245
	 * Returns the communication object for the service provider.
246
	 *
247
	 * @return \Aimeos\MW\Communication\Iface Object for communication
248
	 */
249
	protected function getCommunication()
250
	{
251
		if( !isset( $this->communication ) ) {
252
			$this->communication = new \Aimeos\MW\Communication\Curl();
253
		}
254
255
		return $this->communication;
256
	}
257
258
259
	/**
260
	 * Calculates the last date behind the given timestamp depending on the other paramters.
261
	 *
262
	 * This method is used to calculate the date for comparing the order date to
263
	 * if e.g. credit card payments should be captured or direct debit should be
264
	 * checked after the given amount of days from external payment providers.
265
	 * This method can calculate with business/working days only if requested
266
	 * and use the given list of public holidays to take them into account.
267
	 *
268
	 * @param integer $timestamp Timestamp to use as starting point for the backward calculation
269
	 * @param integer $skipdays Number of days to calculate backwards
270
	 * @param boolean $businessOnly True if only business days should be used for calculation, false if not
271
	 * @param string $publicHolidays Comma separated list of public holidays in YYYY-MM-DD format
272
	 * @return string Date in YYY-MM-DD format to be compared to the order date
273
	 * @throws \Aimeos\MShop\Service\Exception If the given holiday string is in the wrong format and can't be processed
274
	 */
275
	protected function calcDateLimit( $timestamp, $skipdays = 0, $businessOnly = false, $publicHolidays = '' )
276
	{
277
		$holidays = $this->getPublicHolidays( $publicHolidays );
278
279
		if( !empty( $holidays ) )
280
		{
281
			for( $i = 0; $i <= $skipdays; $i++ )
282
			{
283
				$date = date( 'Y-m-d', $timestamp - $i * 86400 );
284
285
				if( isset( $holidays[$date] ) ) {
286
					$skipdays++;
287
				}
288
			}
289
		}
290
291
		if( $businessOnly === true )
292
		{
293
			// adds days for weekends
294
			for( $i = 0; $i <= $skipdays; $i++ )
295
			{
296
				$ts = $timestamp - $i * 86400;
297
298
				if( date( 'N', $ts ) > 5 && !isset( $holidays[date( 'Y-m-d', $ts )] ) ) {
299
					$skipdays++;
300
				}
301
			}
302
		}
303
304
		return date( 'Y-m-d', $timestamp - $skipdays * 86400 );
305
	}
306
307
308
	/**
309
	 * Checks required fields and the types of the config array.
310
	 *
311
	 * @param array $config Config parameters
312
	 * @param array $attributes Attributes for the config array
313
	 * @return array An array with the attribute keys as key and an error message as values for all attributes that are
314
	 * 	known by the provider but aren't valid resp. null for attributes whose values are OK
315
	 */
316
	protected function checkConfig( array $config, array $attributes )
317
	{
318
		$errors = [];
319
320
		foreach( $config as $key => $def )
321
		{
322
			if( $def['required'] === true && ( !isset( $attributes[$key] ) || $attributes[$key] === '' ) )
323
			{
324
				$errors[$key] = sprintf( 'Configuration for "%1$s" is missing', $key );
325
				continue;
326
			}
327
328
			if( isset( $attributes[$key] ) )
329
			{
330
				switch( $def['type'] )
331
				{
332
					case 'boolean':
333
						if( !is_string( $attributes[$key] ) || $attributes[$key] !== '0' && $attributes[$key] !== '1' ) {
334
							$errors[$key] = sprintf( 'Not a true/false value' ); continue 2;
335
						}
336
						break;
337
					case 'string':
338
						if( is_string( $attributes[$key] ) === false ) {
339
							$errors[$key] = sprintf( 'Not a string' ); continue 2;
340
						}
341
						break;
342
					case 'integer':
343
						if( ctype_digit( $attributes[$key] ) === false ) {
344
							$errors[$key] = sprintf( 'Not an integer number' ); continue 2;
345
						}
346
						break;
347
					case 'number':
348
						if( is_numeric( $attributes[$key] ) === false ) {
349
							$errors[$key] = sprintf( 'Not a number' ); continue 2;
350
						}
351
						break;
352
					case 'date':
353
						$pattern = '/^[0-9]{4}-[0-1][0-9]-[0-3][0-9]$/';
354
						if( !is_string( $attributes[$key] ) || preg_match( $pattern, $attributes[$key] ) !== 1 ) {
355
							$errors[$key] = sprintf( 'Not a date' ); continue 2;
356
						}
357
						break;
358
					case 'datetime':
359
						$pattern = '/^[0-9]{4}-[0-1][0-9]-[0-3][0-9] [0-2][0-9]:[0-5][0-9](:[0-5][0-9])?$/';
360
						if( !is_string( $attributes[$key] ) || preg_match( $pattern, $attributes[$key] ) !== 1 ) {
361
							$errors[$key] = sprintf( 'Not a date and time' ); continue 2;
362
						}
363
						break;
364
					case 'time':
365
						$pattern = '/^([0-2])?[0-9]:[0-5][0-9](:[0-5][0-9])?$/';
366
						if( !is_string( $attributes[$key] ) || preg_match( $pattern, $attributes[$key] ) !== 1 ) {
367
							$errors[$key] = sprintf( 'Not a time' ); continue 2;
368
						}
369
						break;
370
					case 'list':
371
					case 'select':
372
						if( !is_array( $def['default'] ) || !isset( $def['default'][$attributes[$key]] )
373
							&& !in_array( $attributes[$key], $def['default'] )
374
						) {
375
							$errors[$key] = sprintf( 'Not a listed value' ); continue 2;
376
						}
377
						break;
378
					case 'map':
379
						if( !is_array( $attributes[$key] ) ) {
380
							$errors[$key] = sprintf( 'Not a key/value map' ); continue 2;
381
						}
382
						break;
383
					default:
384
						throw new \Aimeos\MShop\Service\Exception( sprintf( 'Invalid type "%1$s"', $def['type'] ) );
385
				}
386
			}
387
388
			$errors[$key] = null;
389
		}
390
391
		return $errors;
392
	}
393
394
395
	/**
396
	 * Returns the criteria attribute items for the backend configuration
397
	 *
398
	 * @return \Aimeos\MW\Criteria\Attribute\Iface[] List of criteria attribute items
399
	 */
400
	protected function getConfigItems( array $configList )
401
	{
402
		$list = [];
403
404
		foreach( $configList as $key => $config ) {
405
			$list[$key] = new \Aimeos\MW\Criteria\Attribute\Standard( $config );
406
		}
407
408
		return $list;
409
	}
410
411
412
	/**
413
	 * Returns the configuration value that matches one of the given keys.
414
	 *
415
	 * The config of the service item and (optionally) the global config
416
	 * is tested in the order of the keys. The first one that matches will
417
	 * be returned.
418
	 *
419
	 * @param array|string $keys Key name or list of key names that should be tested for in the order to test
420
	 * @param mixed $default Returned value if the key wasn't was found
421
	 * @return mixed Value of the first key that matches or null if none was found
422
	 */
423
	protected function getConfigValue( $keys, $default = null )
424
	{
425
		$srvconfig = $this->getServiceItem()->getConfig();
426
427
		foreach( (array) $keys as $key )
428
		{
429
			if( isset( $srvconfig[$key] ) ) {
430
				return $srvconfig[$key];
431
			}
432
433
			if( isset( $this->beGlobalConfig[$key] ) ) {
434
				return $this->beGlobalConfig[$key];
435
			}
436
		}
437
438
		return $default;
439
	}
440
441
442
	/**
443
	 * Returns the context item.
444
	 *
445
	 * @return \Aimeos\MShop\Context\Item\Iface Context item
446
	 */
447
	protected function getContext()
448
	{
449
		return $this->context;
450
	}
451
452
453
	/**
454
	 * Returns the calculated amount of the price item
455
	 *
456
	 * @param \Aimeos\MShop\Price\Item\Iface $price Price item
457
	 * @param boolean $costs Include costs per item
458
	 * @param boolean $tax Include tax
459
	 * @return string Formatted money amount
460
	 */
461
	protected function getAmount( \Aimeos\MShop\Price\Item\Iface $price, $costs = true, $tax = true )
462
	{
463
		$amount = $price->getValue();
464
465
		if( $costs === true ) {
466
			$amount += $price->getCosts();
467
		}
468
469
		if( $tax === true && $price->getTaxFlag() === false )
470
		{
471
			$tmp = clone $price;
472
473
			if( $costs === false )
474
			{
475
				$tmp->clear();
476
				$tmp->setValue( $price->getValue() );
477
				$tmp->setTaxRate( $price->getTaxRate() );
478
				$tmp->setQuantity( $price->getQuantity() );
479
			}
480
481
			$amount += $tmp->getTaxValue();
482
		}
483
484
		return number_format( $amount, 2, '.', '' );
485
	}
486
487
488
	/**
489
	 * Returns the order item for the given ID.
490
	 *
491
	 * @param string $id Unique order ID
492
	 * @return \Aimeos\MShop\Order\Item\Iface $item Order object
493
	 */
494
	protected function getOrder( $id )
495
	{
496
		$manager = \Aimeos\MShop\Factory::createManager( $this->context, 'order' );
497
498
		$search = $manager->createSearch( true );
499
		$expr = [
500
			$search->getConditions(),
501
			$search->compare( '==', 'order.id', $id ),
502
			$search->compare( '==', 'order.base.service.code', $this->serviceItem->getCode() ),
503
		];
504
		$search->setConditions( $search->combine( '&&', $expr ) );
505
506
		$result = $manager->searchItems( $search );
507
508
		if( ( $item = reset( $result ) ) === false ) {
509
			throw new \Aimeos\MShop\Service\Exception( sprintf( 'No order for ID "%1$s" found', $id ) );
510
		}
511
512
		return $item;
513
	}
514
515
516
	/**
517
	 * Returns the base order which is equivalent to the basket.
518
	 *
519
	 * @param string $baseId Order base ID stored in the order item
520
	 * @param integer $parts Bitmap of the basket parts that should be loaded
521
	 * @return \Aimeos\MShop\Order\Item\Base\Iface Basket, optional with addresses, products, services and coupons
522
	 */
523
	protected function getOrderBase( $baseId, $parts = \Aimeos\MShop\Order\Item\Base\Base::PARTS_SERVICE )
524
	{
525
		return \Aimeos\MShop\Factory::createManager( $this->context, 'order/base' )->load( $baseId, $parts );
526
	}
527
528
529
	/**
530
	 * Saves the order item.
531
	 *
532
	 * @param \Aimeos\MShop\Order\Item\Iface $item Order object
533
	 * @return \Aimeos\MShop\Order\Item\Iface Order object including the generated ID
0 ignored issues
show
Documentation introduced by
Should the return type not be \Aimeos\MShop\Order\Item\Iface|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
534
	 */
535
	protected function saveOrder( \Aimeos\MShop\Order\Item\Iface $item )
536
	{
537
		return \Aimeos\MShop\Factory::createManager( $this->context, 'order' )->saveItem( $item );
538
	}
539
540
541
	/**
542
	 * Returns the service related data from the customer account if available
543
	 *
544
	 * @param string $type Type of the value that should be returned
545
	 * @param string $customerId Unique customer ID the service token belongs to
546
	 * @param string $serviceId Unique service ID the service token is valid for
547
	 * @return string|null Service data or null if none is available
548
	 */
549
	protected function getCustomerData( $type, $customerId, $serviceId )
550
	{
551
		if( $customerId == null ) {
552
			return;
553
		}
554
555
		$manager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'customer' );
556
		$item = $manager->getItem( $customerId, ['service'] );
557
558
		if( ( $listItem = $item->getListItem( $serviceId, 'service', 'default' ) ) !== null )
559
		{
560
			$config = $listItem->getConfig();
561
			return ( isset( $config[$type] ) ? $config[$type] : null );
562
		}
563
	}
564
565
566
	/**
567
	 * Saves the base order which is equivalent to the basket and its dependent objects.
568
	 *
569
	 * @param \Aimeos\MShop\Order\Item\Base\Iface $base Order base object with associated items
570
	 * @param integer $parts Bitmap of the basket parts that should be stored
571
	 */
572
	protected function saveOrderBase( \Aimeos\MShop\Order\Item\Base\Iface $base, $parts = \Aimeos\MShop\Order\Item\Base\Base::PARTS_SERVICE )
573
	{
574
		\Aimeos\MShop\Factory::createManager( $this->context, 'order/base' )->store( $base, $parts );
575
	}
576
577
578
	/**
579
	 * Sets the attributes in the given service item.
580
	 *
581
	 * @param \Aimeos\MShop\Order\Item\Base\Service\Iface $orderServiceItem Order service item that will be added to the basket
582
	 * @param array $attributes Attribute key/value pairs entered by the customer during the checkout process
583
	 * @param string $type Type of the configuration values (delivery or payment)
584
	 */
585
	protected function setAttributes( \Aimeos\MShop\Order\Item\Base\Service\Iface $orderServiceItem, array $attributes, $type )
586
	{
587
		$manager = \Aimeos\MShop\Factory::createManager( $this->context, 'order/base/service/attribute' );
588
589
		foreach( $attributes as $key => $value )
590
		{
591
			$item = $manager->createItem();
592
			$item->setCode( $key );
593
			$item->setValue( $value );
594
			$item->setType( $type );
595
596
			$orderServiceItem->setAttributeItem( $item );
597
		}
598
	}
599
600
601
	/**
602
	 * Adds the service data to the customer account if available
603
	 *
604
	 * @param string $type Type of the value that should be added
605
	 * @param string $customerId Unique customer ID the service token belongs to
606
	 * @param string $serviceId Unique service ID the service token is valid for
607
	 * @param string $data Service data to store
608
	 */
609
	protected function setCustomerData( $type, $customerId, $serviceId, $data )
610
	{
611
		if( $customerId == null ) {
612
			return;
613
		}
614
615
		$listManager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'customer/lists' );
616
		$manager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'customer' );
617
618
		$item = $manager->getItem( $customerId, ['service'] );
619
620
		if( ( $listItem = $item->getListItem( $serviceId, 'service', 'default', false ) ) === null )
621
		{
622
			$listTypeManager = \Aimeos\MShop\Factory::createManager( $this->getContext(), 'customer/lists/type' );
623
			$typeId = $listTypeManager->findItem( 'default', [], 'service' )->getId();
624
625
			$listItem = $listManager->createItem();
626
			$listItem->setParentId( $customerId );
627
			$listItem->setRefId( $serviceId );
628
			$listItem->setDomain( 'service' );
629
			$listItem->setTypeId( $typeId );
630
			$listItem->setStatus( 1 );
631
		}
632
633
		$listItem->setConfig( array_merge( $listItem->getConfig(), [$type => $data] ) );
634
		$listManager->saveItem( $listItem, false );
635
	}
636
637
638
	/**
639
	 * Returns the public holidays in ISO format
640
	 *
641
	 * @param string $list Comma separated list of public holidays in YYYY-MM-DD format
642
	 * @return array List of dates in YYYY-MM-DD format
643
	 * @throws \Aimeos\MShop\Service\Exception If the given holiday string is in the wrong format and can't be processed
644
	 */
645
	private function getPublicHolidays( $list )
646
	{
647
		$holidays = [];
648
649
		if( is_string( $list ) && $list !== '' )
650
		{
651
			$holidays = explode( ',', str_replace( ' ', '', $list ) );
652
653
			if( sort( $holidays ) === false ) {
654
				throw new \Aimeos\MShop\Service\Exception( sprintf( 'Unable to sort public holidays: "%1$s"', $list ) );
655
			}
656
657
			$holidays = array_flip( $holidays );
658
		}
659
660
		return $holidays;
661
	}
662
}
663